Skip to content

Commit 22e3296

Browse files
committed
Merge branch 'main' into issue/515
2 parents 0382bd5 + e479b7e commit 22e3296

168 files changed

Lines changed: 5367 additions & 499 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/itchy-moles-thank.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"zoo-code": patch
3+
---
4+
5+
Fix bedrock DNS resolution when behind corporate proxy

.github/actions/setup-node-pnpm/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ inputs:
66
node-version:
77
description: "Node.js version to use"
88
required: false
9-
default: "20.20.2"
9+
default: "22.23.1"
1010
pnpm-version:
1111
description: "pnpm version to use"
1212
required: false

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20.20.2
1+
22.23.1

.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
pnpm 10.8.1
2-
nodejs 20.20.2
2+
nodejs 22.23.1

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
# Zoo Code Changelog
22

3+
## [3.72.0]
4+
5+
### Minor Changes
6+
7+
- Add the Moonshot provider with live model discovery, streaming, model metadata, and a model picker (PR #857 by @grizmin)
8+
- Add the Kimi Code provider with OAuth device-flow authentication (PR #945 by @taltas)
9+
- Add Claude Opus 5 support across all providers (PR #1010 by @app/zoomote)
10+
- Add Kimi K3 to the Moonshot and OpenCode Go providers (#932 by @navedmerchant, PR #996 by @app/zoomote)
11+
- Add Gemini 3.6 Flash model support (PR #975 by @app/zoomote)
12+
- Add MiniMax-M3 model support (#888 by @RayWinter0816, PR #946 by @app/zoomote)
13+
- Add a safe way to abandon interrupted subtasks by severing stale parent-child links and surfacing delegation status (#559 by @edelauna, PR #935 by @edelauna)
14+
- Add Dart support to codebase indexing (#940 by @WebMad, PR #941 by @WebMad)
15+
- Fix codebase indexing for plain-text files (#931 by @tool-buddy, PR #938 by @WebMad)
16+
- Enable image input for DeepSeek V4 models (#964 by @grizmin, PR #963 by @grizmin)
17+
- Fix ChatGPT OAuth requests for GPT-5.6 Luna being rejected by the Codex backend (PR #889 by @taltas)
18+
- Preserve `reasoning_content` for known reasoning model families when using LiteLLM (#891 by @daewoongoh, PR #899 by @daewoongoh)
19+
- Fix task-history cache invalidation races by routing `invalidate()` and `invalidateAll()` through the task-history lock (#698 by @edelauna, PR #912 by @morgan-coded)
20+
- Fix Settings mode changes by synchronizing the local `cachedState` editing buffer (#914 by @easonLiangWorldedtech, PR #925 by @easonLiangWorldedtech)
21+
- Dismiss the welcome screen after successful Zoo Gateway sign-in (#961 by @JohnCanty, PR #962 by @JamesRobert20)
22+
- Add `CompletePromptOptions` to the `completePrompt` API so callers can configure prompt completion (#615 by @edelauna, PR #901 by @easonLiangWorldedtech)
23+
- Centralize provider identifiers into canonical shared types (#951 by @WebMad, PR #952 by @WebMad)
24+
- Refactor provider categories to use canonical provider identifiers (PR #989 by @WebMad)
25+
- Remove obsolete MCP server-creation translations (#895 by @edelauna, PR #943 by @WebMad)
26+
- Add regression coverage for resuming interrupted subtasks (#566 by @myk1yt, PR #911 by @edelauna)
27+
- Upload coverage reports as GitHub Actions artifacts to simplify CI debugging (PR #939 by @app/zoomote)
28+
- Update `esbuild-wasm` to v0.28.1 (PR #829 by @app/renovate)
29+
330
## [3.70.0]
431

532
### Minor Changes

README.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@
3535
You can find a quick guide for migrating from Roo Code to Zoo Code in the [Roo→Zoo migration guide](https://docs.zoocode.dev/roo-to-zoo-migration). We plan to try and help users as they transition over, we have our [Reddit](https://www.reddit.com/r/ZooCode) and [Discord](https://discord.gg/VxfP4Vx3gX)
3636
for this exact support, so if you are having problems or if you have question, jump on and ask.
3737

38-
## What's New in v3.70.0
38+
## What's New in v3.72.0
3939

40-
- **OpenAI GPT-5.6 family**`Sol`, `Terra`, and `Luna` are now available across both the OpenAI Codex and OpenAI Native provider paths.
41-
- **Grok 4.5 support**xAI's new flagship model, plus a reasoning-effort format fix that also benefits Grok 4 Mini.
42-
- **Kenari provider support**a first-class, OpenAI-compatible AI gateway billed in Rupiah covering Claude, GPT, DeepSeek, GLM, Kimi and more.
43-
- Surface the context-compaction button and context window progress bar in the collapsed task header.
44-
- Fix: terminal output loss and premature task completion on cold terminals.
45-
- Fix: image attach for Zoo Gateway and Vercel AI Gateway models now follows live vision-capability tags instead of a static allowlist.
46-
- Dependency and tooling updates.
40+
- **Moonshot and Kimi Code providers**discover Moonshot models dynamically or sign in to Kimi Code through its OAuth device flow.
41+
- **Latest model support**use Claude Opus 5 across providers, plus Kimi K3, Gemini 3.6 Flash, and MiniMax-M3.
42+
- **Improved subtask workflows**abandon interrupted subtasks cleanly, with safer task-history invalidation and better resume coverage.
43+
- **Expanded codebase indexing** — index Dart and plain-text files.
44+
- **Provider reliability** — fixes for GPT-5.6 Luna with ChatGPT OAuth, LiteLLM reasoning content, and DeepSeek V4 image input.
45+
- **Smoother setup and settings** — improved Zoo Gateway sign-in and mode-switch behavior.
46+
- API, provider-type, dependency, localization, and CI improvements.
4747

4848
<details>
4949
<summary>🌐 Available languages</summary>
@@ -66,8 +66,7 @@ for this exact support, so if you are having problems or if you have question, j
6666
- [Tiếng Việt](locales/vi/README.md)
6767
- [简体中文](locales/zh-CN/README.md)
6868
- [繁體中文](locales/zh-TW/README.md)
69-
- ...
70-
</details>
69+
</details>
7170

7271
---
7372

apps/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"@roo-code/config-eslint": "workspace:^",
4242
"@trpc/server": "^11.18.0",
4343
"@roo-code/config-typescript": "workspace:^",
44-
"@types/node": "20.19.43",
44+
"@types/node": "22.20.1",
4545
"@types/react": "18.3.31",
4646
"@vitest/coverage-v8": "4.1.9",
4747
"ink-testing-library": "4.0.0",

apps/vscode-e2e/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@roo-code/types": "workspace:^",
1818
"@copilotkit/aimock": "1.35.0",
1919
"@types/mocha": "10.0.10",
20-
"@types/node": "20.19.43",
20+
"@types/node": "22.20.1",
2121
"@types/vscode": "1.100.0",
2222
"@vscode/test-electron": "2.5.2",
2323
"dotenv-cli": "11.0.0",

apps/vscode-e2e/src/fixtures/subtasks.ts

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ const SUBTASK_XPROFILE_DIFFERENT_CHILD_MARKER = "SUBTASK_CHILD_DIFFERENT_PROFILE
1818
const SUBTASK_CHILD_PROMPT = `${SUBTASK_CHILD_MARKER}: Ask the user exactly this follow-up question: What is the square root of 81? After the user answers, complete with only the answer.`
1919
export const SUBTASK_PARENT_PROMPT = `${SUBTASK_PARENT_MARKER}: Use the new_task tool exactly once. Create an ask-mode subtask with this exact message: "${SUBTASK_CHILD_PROMPT}" Do not answer directly.`
2020
export const SUBTASK_CHILD_FOLLOWUP_ANSWER = "9"
21-
const SUBTASK_FAST_CHILD_PROMPT = `${SUBTASK_FAST_CHILD_MARKER}: Complete immediately with the exact result "Fast child completed".`
21+
export const SUBTASK_FAST_CHILD_RESULT = "Fast child completed"
22+
const SUBTASK_FAST_CHILD_PROMPT = `${SUBTASK_FAST_CHILD_MARKER}: Complete immediately with the exact result "${SUBTASK_FAST_CHILD_RESULT}".`
2223
export const SUBTASK_FAST_PARENT_PROMPT = `${SUBTASK_FAST_PARENT_MARKER}: Use the new_task tool exactly once. Create an ask-mode subtask with this exact message: "${SUBTASK_FAST_CHILD_PROMPT}" Do not answer directly.`
2324

2425
const SUBTASK_INTERRUPT_CHILD_PROMPT = `${SUBTASK_INTERRUPT_CHILD_MARKER}: Ask the user exactly this follow-up question: What is the square root of 81? After the user answers, complete with only the answer.`
@@ -54,6 +55,31 @@ const requestContains = (req: ChatCompletionRequest, expected: string[]) => {
5455
return expected.every((text) => rawRequest.includes(text))
5556
}
5657

58+
// aimock's `userMessage` matcher only inspects the LAST user message and joins only the
59+
// `type: "text"` content parts (see getTextContent in aimock's router). Fixtures that need
60+
// whole-request exclusions must replicate that scoping inside a predicate so they keep the
61+
// same matching semantics as the bare-regex fixtures they replace.
62+
const lastUserMessageContains = (req: ChatCompletionRequest, text: string) => {
63+
const userMessages = req.messages?.filter((message) => message.role === "user") ?? []
64+
const last = userMessages.at(-1)
65+
if (!last) return false
66+
const content =
67+
typeof last.content === "string"
68+
? last.content
69+
: (last.content ?? [])
70+
.filter((part): part is { type: "text"; text: string } => part?.type === "text")
71+
.map((part) => part.text)
72+
.join("")
73+
return content.includes(text)
74+
}
75+
76+
// reopenParentFromDelegation injects the child result into the resumed parent's history as
77+
// `Subtask <childId> completed.\n\nResult:\n<summary>`. Matching on this injected prefix (in
78+
// its JSON-serialized form) keeps parent-resume fixtures robust when
79+
// validateAndFixToolResultIds rewrites tool-use ids on resume — matching on the new_task
80+
// tool-call id directly proved flaky (the id can be rewritten, the fixture then misses, and
81+
// a looser child fixture wins and serves the child's response to the parent).
82+
const SUBTASK_RESULT_INJECTION = "completed.\\n\\nResult:"
5783
const completionAfterAnswer = (followupId: string, completionId: string) => ({
5884
match: {
5985
predicate: (req: ChatCompletionRequest) =>
@@ -100,25 +126,33 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
100126
},
101127
})
102128

129+
// The parent prompt embeds SUBTASK_FAST_CHILD_MARKER verbatim, so parent-resume turns
130+
// can also match a bare substring check (same collision class as #561). Exclude the
131+
// parent marker so those turns fall through to the parent-resume fixture below.
103132
mock.addFixture({
104133
match: {
105-
userMessage: new RegExp(SUBTASK_FAST_CHILD_MARKER),
134+
predicate: (req: ChatCompletionRequest) =>
135+
lastUserMessageContains(req, SUBTASK_FAST_CHILD_MARKER) &&
136+
!requestContains(req, [SUBTASK_FAST_PARENT_MARKER]),
106137
},
107138
response: {
108139
toolCalls: [
109140
{
110141
name: "attempt_completion",
111-
arguments: JSON.stringify({ result: "Fast child completed" }),
142+
arguments: JSON.stringify({ result: SUBTASK_FAST_CHILD_RESULT }),
112143
id: "call_subtasks_fast_child_completion_002",
113144
},
114145
],
115146
},
116147
})
117148

149+
// Guard on SUBTASK_RESULT_INJECTION (not the child result text): the child result is
150+
// embedded verbatim in SUBTASK_FAST_PARENT_PROMPT, so it cannot distinguish the parent's
151+
// initial request from its resume turn.
118152
mock.addFixture({
119153
match: {
120154
predicate: (req: ChatCompletionRequest) =>
121-
requestContains(req, [SUBTASK_FAST_PARENT_MARKER, "call_subtasks_fast_parent_new_task_001"]),
155+
requestContains(req, [SUBTASK_FAST_PARENT_MARKER, SUBTASK_RESULT_INJECTION]),
122156
},
123157
response: {
124158
toolCalls: [
@@ -131,9 +165,15 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
131165
},
132166
})
133167

168+
// This fixture is shared by several tests, so sequenceIndex cannot guard it. Exclude
169+
// resume turns via the injected tool_result prefix instead: when the tool_result is
170+
// serialized as role:"tool", the original parent prompt is the last user message again
171+
// and would otherwise re-serve new_task on the parent's resume turn.
134172
mock.addFixture({
135173
match: {
136-
userMessage: new RegExp(SUBTASK_PARENT_MARKER),
174+
predicate: (req: ChatCompletionRequest) =>
175+
lastUserMessageContains(req, SUBTASK_PARENT_MARKER) &&
176+
!requestContains(req, [SUBTASK_RESULT_INJECTION]),
137177
},
138178
response: {
139179
toolCalls: [
@@ -149,9 +189,12 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
149189
},
150190
})
151191

192+
// Same collision guard as the fast-child fixture above: SUBTASK_PARENT_PROMPT embeds
193+
// SUBTASK_CHILD_MARKER verbatim, so parent-resume turns must not match this fixture.
152194
mock.addFixture({
153195
match: {
154-
userMessage: new RegExp(SUBTASK_CHILD_MARKER),
196+
predicate: (req: ChatCompletionRequest) =>
197+
lastUserMessageContains(req, SUBTASK_CHILD_MARKER) && !requestContains(req, [SUBTASK_PARENT_MARKER]),
155198
},
156199
response: {
157200
toolCalls: [
@@ -172,7 +215,7 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
172215
mock.addFixture({
173216
match: {
174217
predicate: (req: ChatCompletionRequest) =>
175-
requestContains(req, [SUBTASK_PARENT_MARKER, "call_subtasks_parent_new_task_001"]),
218+
requestContains(req, [SUBTASK_PARENT_MARKER, SUBTASK_RESULT_INJECTION]),
176219
},
177220
response: {
178221
toolCalls: [
@@ -238,14 +281,12 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
238281
},
239282
})
240283

284+
// Same as the fast parent-resume fixture: the child result is embedded verbatim in
285+
// SUBTASK_API_HANG_PARENT_PROMPT, so guard on the injected tool_result prefix instead.
241286
mock.addFixture({
242287
match: {
243288
predicate: (req: ChatCompletionRequest) =>
244-
requestContains(req, [
245-
SUBTASK_API_HANG_PARENT_MARKER,
246-
"call_api_hang_parent_new_task_001",
247-
SUBTASK_API_HANG_CHILD_RESULT,
248-
]),
289+
requestContains(req, [SUBTASK_API_HANG_PARENT_MARKER, SUBTASK_RESULT_INJECTION]),
249290
},
250291
response: {
251292
toolCalls: [
@@ -433,7 +474,7 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
433474
mock.addFixture({
434475
match: {
435476
predicate: (req: ChatCompletionRequest) =>
436-
requestContains(req, [SUBTASK_INTERRUPT_PARENT_MARKER, "call_interrupt_parent_new_task_001"]),
477+
requestContains(req, [SUBTASK_INTERRUPT_PARENT_MARKER, SUBTASK_RESULT_INJECTION]),
437478
},
438479
response: {
439480
toolCalls: [

apps/vscode-e2e/src/suite/subtasks.test.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
SUBTASK_API_HANG_PARENT_RESULT,
1515
SUBTASK_API_HANG_RESUME_MESSAGE,
1616
SUBTASK_CHILD_FOLLOWUP_ANSWER,
17+
SUBTASK_FAST_CHILD_RESULT,
1718
SUBTASK_FAST_PARENT_PROMPT,
1819
SUBTASK_INTERRUPT_CHILD_FOLLOWUP_ANSWER,
1920
SUBTASK_INTERRUPT_PARENT_PROMPT,
@@ -101,7 +102,8 @@ suite("Roo Code Subtasks", function () {
101102
([taskId, messages]) =>
102103
taskId !== parentTaskId &&
103104
messages.some(
104-
({ say, text }) => say === "completion_result" && text?.trim() === "Fast child completed",
105+
({ say, text }) =>
106+
say === "completion_result" && text?.trim() === SUBTASK_FAST_CHILD_RESULT,
105107
),
106108
),
107109
"Immediately-completing child should emit its expected result",
@@ -357,15 +359,23 @@ suite("Roo Code Subtasks", function () {
357359

358360
await api.cancelCurrentTask()
359361

362+
// Gate on the async settle before asserting the parent never resumed: a spurious
363+
// resume would be an async downstream effect of the cancellation, so a synchronous
364+
// check right after cancelCurrentTask() proves nothing.
365+
await waitFor(() => api.getCurrentTaskStack().at(-1) === spawnedTaskId)
366+
await waitFor(
367+
() => asks[spawnedTaskId!]?.some(({ type, ask }) => type === "ask" && ask === "resume_task") ?? false,
368+
)
369+
360370
assert.ok(
361371
messages[parentTaskId]?.find(({ type, text }) => type === "say" && text === "Parent task resumed") ===
362372
undefined,
363373
"Parent task should not have resumed after subtask cancellation",
364374
)
365-
366-
await waitFor(() => api.getCurrentTaskStack().at(-1) === spawnedTaskId)
367-
await waitFor(
368-
() => asks[spawnedTaskId!]?.some(({ type, ask }) => type === "ask" && ask === "resume_task") ?? false,
375+
assert.strictEqual(
376+
messages[parentTaskId]?.find(({ say }) => say === "completion_result"),
377+
undefined,
378+
"Parent must not have completed after subtask cancellation",
369379
)
370380

371381
await api.clearCurrentTask()
@@ -539,15 +549,15 @@ suite("Roo Code Subtasks", function () {
539549
says[childTaskId!]
540550
?.filter(({ say }) => say === "completion_result")
541551
.map(({ text }) => text?.trim())
542-
.find((text) => text === SUBTASK_API_HANG_CHILD_RESULT),
552+
.find((text): text is string => !!text),
543553
SUBTASK_API_HANG_CHILD_RESULT,
544554
"Child should complete with its expected result after resume",
545555
)
546556
assert.strictEqual(
547557
says[parentTaskId]
548558
?.filter(({ say }) => say === "completion_result")
549559
.map(({ text }) => text?.trim())
550-
.find((text) => text === SUBTASK_API_HANG_PARENT_RESULT),
560+
.find((text): text is string => !!text),
551561
SUBTASK_API_HANG_PARENT_RESULT,
552562
"Parent should resume and complete with its expected result",
553563
)

0 commit comments

Comments
 (0)