Skip to content

Commit 3e10f24

Browse files
committed
Merge remote-tracking branch 'upstream/main' into feat/kenari-provider
# Conflicts: # webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx
2 parents 77173c2 + 7d0879b commit 3e10f24

170 files changed

Lines changed: 6625 additions & 1469 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.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"zoo-code": patch
3+
---
4+
5+
Fix Anthropic provider silently replacing a custom/unrecognized `apiModelId` with the hardcoded default model.
6+
7+
`AnthropicHandler.getModel()` coerced any `apiModelId` not present in the static `anthropicModels` table down to `anthropicDefaultModelId` ("claude-sonnet-4-5"), and that coerced id was what actually got sent as `model` in the API request -- silently ignoring a user-configured custom model name (e.g. a custom Anthropic-compatible deployment or proxy). This produced confusing "model does not exist" errors for the default model instead of the model the user actually selected (#418).
8+
9+
The same fallback also affected capability lookups used to build the `thinking` request parameter: an unrecognized id fell back to the default model's info, which can be from an older model generation with a different API contract, causing the request to use the legacy `thinking: {type: "enabled", budget_tokens}` shape and get rejected with a 400 by models that require `{type: "adaptive"}`.
10+
11+
The model id sent to the API now always honors a user-configured `apiModelId`. For unrecognized values, capabilities are best-effort guessed by matching known model-family substrings (mirroring the existing `BedrockHandler.guessModelInfoFromId` heuristic) instead of defaulting to `anthropicDefaultModelId`'s info.

.github/workflows/codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252

5353
# Initializes the CodeQL tools for scanning.
5454
- name: Initialize CodeQL
55-
uses: github/codeql-action/init@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3
55+
uses: github/codeql-action/init@411c4c9a36b3fca4d674f06b6396b2c6d23522c6 # v3
5656
with:
5757
languages: ${{ matrix.language }}
5858
build-mode: ${{ matrix.build-mode }}
@@ -80,6 +80,6 @@ jobs:
8080
exit 1
8181
8282
- name: Perform CodeQL Analysis
83-
uses: github/codeql-action/analyze@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3
83+
uses: github/codeql-action/analyze@411c4c9a36b3fca4d674f06b6396b2c6d23522c6 # v3
8484
with:
8585
category: "/language:${{matrix.language}}"

.github/workflows/label-pr-review-state.yml

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
with:
2727
script: |
2828
const { owner, repo } = context.repo;
29-
const stateLabels = ['awaiting-author', 'awaiting-review'];
29+
const stateLabels = ['awaiting-author', 'awaiting-review', 'has-conflicts'];
3030
3131
// When triggered by a single PR event, only reconcile that PR.
3232
// The hourly schedule and workflow_dispatch reconcile all open PRs.
@@ -125,6 +125,22 @@ jobs:
125125
continue;
126126
}
127127
128+
// `mergeable`/`mergeable_state` are only returned by the single-PR GET
129+
// endpoint, and are computed asynchronously by GitHub — a PR fetched via
130+
// pulls.list (schedule/workflow_dispatch runs) never has them, and even a
131+
// single-PR fetch can return `null`/"unknown" if the merge check hasn't
132+
// finished yet. Re-fetch the single PR to get a fresh value, and treat
133+
// "unknown" as not-yet-computed rather than as conflicting.
134+
const prDetail = prNumber
135+
? pr
136+
: (await github.rest.pulls.get({ owner, repo, pull_number: pr.number })).data;
137+
138+
if (prDetail.mergeable === false && prDetail.mergeable_state === 'dirty') {
139+
core.info(`PR #${pr.number}: has merge conflicts — labeling has-conflicts`);
140+
await reconcileLabels(pr, 'has-conflicts');
141+
continue;
142+
}
143+
128144
// Check CI status for required checks on the PR's head commit only.
129145
// Scoping to required checks avoids advisory checks (e.g. codecov/patch)
130146
// incorrectly blocking label assignment on otherwise-ready PRs.
@@ -137,9 +153,28 @@ jobs:
137153
}),
138154
]);
139155
156+
// listForRef returns every check run ever recorded on the ref, including
157+
// stale superseded ones (e.g. a failed run later re-run green). Branch
158+
// protection and the PR UI only consider the latest run per check name, so
159+
// reduce to that before evaluating — otherwise a single stale failure makes
160+
// ciFailed true forever and state labels never come back. See issue #884.
161+
//
162+
// Unlike listReviews (which documents oldest-first order), listForRef's
163+
// ordering is unspecified, so we pick the latest by run.id — GitHub assigns
164+
// monotonically increasing IDs, and id is never null (a freshly re-queued
165+
// run can have started_at: null, which would lose a string comparison
166+
// against an older completed run's timestamp).
167+
const latestByName = new Map();
168+
for (const run of checkRuns) {
169+
const prev = latestByName.get(run.name);
170+
if (!prev || run.id > prev.id) {
171+
latestByName.set(run.name, run);
172+
}
173+
}
174+
140175
// Filter to required checks only (or all checks if rules unavailable).
141176
// Always exclude this workflow's own run to avoid self-referential loops.
142-
const relevantRuns = checkRuns.filter(run => {
177+
const relevantRuns = [...latestByName.values()].filter(run => {
143178
if (run.name === 'Reconcile PR review state labels') return false;
144179
return requiredCheckNames ? requiredCheckNames.has(run.name) : true;
145180
});

.github/workflows/nightly-publish.yml

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,21 @@ jobs:
4141
exit 1
4242
fi
4343
44+
- name: Skip if this push is a release merge
45+
id: release-check
46+
run: |
47+
commit_subject=$(git log -1 --format=%s)
48+
skip=false
49+
50+
if [[ "$commit_subject" =~ ^chore:\ prepare\ v[0-9]+\.[0-9]+\.[0-9]+\ release ]]; then
51+
echo "Commit '${commit_subject}' looks like a release-prep merge; skipping nightly pre-release."
52+
skip=true
53+
fi
54+
55+
echo "skip=${skip}" >> "$GITHUB_OUTPUT"
56+
4457
- name: Set pre-release version
58+
if: steps.release-check.outputs.skip != 'true'
4559
id: version
4660
env:
4761
RUN_NUMBER: ${{ github.run_number }}
@@ -61,13 +75,15 @@ jobs:
6175
EOF
6276
6377
- name: Build workspace packages
78+
if: steps.release-check.outputs.skip != 'true'
6479
env:
6580
PKG_RELEASE_CHANNEL: prerelease
6681
run: |
6782
pnpm --filter @roo-code/build build
6883
pnpm --filter @roo-code/vscode-webview build
6984
7085
- name: Package pre-release VSIX
86+
if: steps.release-check.outputs.skip != 'true'
7187
env:
7288
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
7389
PKG_RELEASE_CHANNEL: prerelease
@@ -76,6 +92,7 @@ jobs:
7692
pnpm --filter ./src exec vsce package --pre-release --no-dependencies --out ../bin
7793
7894
- name: Verify VSIX contents
95+
if: steps.release-check.outputs.skip != 'true'
7996
env:
8097
VERSION_NUMBER: ${{ steps.version.outputs.number }}
8198
run: |
@@ -87,6 +104,7 @@ jobs:
87104
grep -q "extension/webview-ui/audio/celebration.wav" /tmp/zoo-code-vsix-contents.txt
88105
89106
- name: Validate packaged manifest identity
107+
if: steps.release-check.outputs.skip != 'true'
90108
env:
91109
VERSION_NUMBER: ${{ steps.version.outputs.number }}
92110
run: |
@@ -98,12 +116,36 @@ jobs:
98116
test "$artifact_name" = "zoo-code"
99117
test "$artifact_publisher" = "ZooCodeOrganization"
100118
101-
# Open VSX is intentionally excluded: it has no pre-release channel concept,
102-
# so pre-release builds would surface as the latest stable version for all users.
103119
- name: Publish pre-release to VS Code Marketplace
120+
if: steps.release-check.outputs.skip != 'true'
104121
env:
105122
VSCE_PAT: ${{ secrets.VSCE_PAT }}
106123
VERSION_NUMBER: ${{ steps.version.outputs.number }}
107124
run: |
108-
npx @vscode/vsce publish --pre-release --packagePath "bin/zoo-code-${VERSION_NUMBER}.vsix"
109-
echo "Published ZooCodeOrganization.zoo-code ${VERSION_NUMBER} as a VS Code Marketplace pre-release"
125+
npx @vscode/vsce publish --pre-release --skip-duplicate --packagePath "bin/zoo-code-${VERSION_NUMBER}.vsix"
126+
echo "Published or skipped existing ZooCodeOrganization.zoo-code ${VERSION_NUMBER} as a VS Code Marketplace pre-release"
127+
128+
# The VSIX built above with `vsce package --pre-release` already carries the
129+
# Microsoft.VisualStudio.Code.PreRelease manifest property, which is what Open
130+
# VSX reads to flag the version. `ovsx publish` ignores --pre-release for an
131+
# already-packaged .vsix (it only applies when ovsx does the packaging itself),
132+
# so it's intentionally omitted here.
133+
#
134+
# Open VSX's "latest" alias resolves to the highest semver across stable and
135+
# pre-release alike (pre-release only breaks ties at equal major.minor.patch),
136+
# so a nightly build can transiently become "latest" until the next stable
137+
# release outranks it. This mirrors how Marketplace pre-release users already
138+
# track the newest published version, so it's an accepted trade-off here too.
139+
- name: Publish pre-release to Open VSX Registry
140+
if: steps.release-check.outputs.skip != 'true'
141+
env:
142+
OVSX_PAT: ${{ secrets.OVSX_PAT }}
143+
VERSION_NUMBER: ${{ steps.version.outputs.number }}
144+
run: |
145+
set -o pipefail
146+
publish_output=$(pnpm exec ovsx publish "bin/zoo-code-${VERSION_NUMBER}.vsix" --skip-duplicate 2>&1 | tee /dev/stderr)
147+
if echo "$publish_output" | grep -q "is already published"; then
148+
echo "ZooCodeOrganization.zoo-code ${VERSION_NUMBER} was already published to Open VSX; skipped."
149+
else
150+
echo "Published ZooCodeOrganization.zoo-code ${VERSION_NUMBER} as an Open VSX pre-release"
151+
fi

CHANGELOG.md

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

3+
## [3.68.0]
4+
5+
### Minor Changes
6+
7+
- Add Friendli provider with GLM-5.2 support for another hosted way to use the latest GLM model (#722 by @Lee-Si-Yoon, PR #721 by @Lee-Si-Yoon)
8+
- Add native thinking/reasoning support for Ollama models to preserve reasoning output end-to-end (#831 by @navedmerchant, PR #832 by @navedmerchant)
9+
- Fix(anthropic): honor custom `apiModelId` selections instead of silently defaulting to `claude-sonnet-4-5` (#418 by @tatianadenel-devops, #843 by @grizmin, PR #842 by @grizmin)
10+
- Fix(ollama): correctly handle tool results and prevent premature context condensing (#847 by @navedmerchant, PR #848 by @navedmerchant)
11+
- Improve Anthropic Vertex Claude content block handling for more reliable responses (#788 by @daewoongoh, PR #789 by @daewoongoh)
12+
- Fix(task-lifecycle): preserve the parent-child link when a delegated subtask is interrupted (#560 by @edelauna, PR #787 by @edelauna)
13+
- Refactor: remove the deprecated `openai-error-handler` shim and use the shared `error-handler` directly (#766 by @daewoongoh, PR #767 by @daewoongoh)
14+
- Feat(nightly-publish): publish Open VSX pre-releases and skip nightly publish on release merges (#784 by @edelauna, PR #790 by @edelauna)
15+
- Fix(ci): don't skip fork-PR label reconciliation on scheduled and manual runs (PR #234 by @app/roomote)
16+
- Fix(label-pr-review-state): detect merge conflicts and label PRs with `has-conflicts` (PR #269 by @app/roomote)
17+
- Chore(deps): update the `github/codeql-action` digest to `411c4c9` (PR #803 by @app/renovate)
18+
- Chore(deps): update `@types/react` to `v18.3.31` (PR #805 by @app/renovate)
19+
- Chore(deps): update `axios` to `v1.18.1` (PR #806 by @app/renovate)
20+
- Chore: merge the v3.66.0 release preparation branch into `main` (PR #795 by @navedmerchant)
21+
322
## [3.66.0]
423

524
### Minor Changes

README.md

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,15 @@
5353
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)
5454
for this exact support, so if you are having problems or if you have question, jump on and ask.
5555

56-
## What's New in v3.66.0
57-
58-
- **Claude Sonnet 5 support** — the latest Claude model is now available across Anthropic, Bedrock, and Vertex providers
59-
- **Semble v0.4.1 upgrade** — flattened result parsing and localized status messages
60-
- **Task-lifecycle status transition guard** — a new status transition guard and startup delegation reconciliation prevent invalid task state transitions
61-
- Fix: LiteLLM cache key collision and silent fallback to a non-existent default model
62-
- Fix: reliable auto context condensing for the VS Code Language Model API
63-
- Fix: ThinkingBudget now supports `xhigh` and all extended reasoning effort values
64-
- Fix: round-trip DeepSeek `reasoning_content` in thinking mode to prevent 400 errors
65-
- Fix: base64-encode Gemini `thoughtSignature` bypass token to fix the Vertex AI empty-response loop
66-
- Fix: provider cache reset after settings import
67-
- Fix: atomically serialize `reopenParentFromDelegation`
68-
- Fix: shell default profile name type guard
69-
- Security: dependency-review, invisible-char detection, and least-privilege workflow permissions
70-
- Upgrade `@anthropic-ai/sdk` to 0.104.1 and `@anthropic-ai/vertex-sdk` to 0.17.1
71-
- Dependency and tooling updates
56+
## What's New in v3.68.0
57+
58+
- **Friendli provider with GLM-5.2 support** — use the latest GLM model through Friendli.
59+
- **Native Ollama thinking/reasoning support** — preserve reasoning output end-to-end when you use Ollama models.
60+
- **Anthropic custom `apiModelId` fix** — custom Anthropic model IDs now stay selected instead of silently falling back to `claude-sonnet-4-5`.
61+
- Fix: Ollama provider tool result handling and premature context condensing.
62+
- Fix: preserve the parent-child task link when a delegated subtask is interrupted.
63+
- Improve Anthropic Vertex Claude content block handling for more reliable responses.
64+
- CI, nightly publishing, and dependency/tooling updates.
7265

7366
<details>
7467
<summary>🌐 Available languages</summary>

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
"@roo-code/config-typescript": "workspace:^",
4343
"@types/node": "20.19.43",
44-
"@types/react": "18.3.23",
44+
"@types/react": "18.3.31",
4545
"@vitest/coverage-v8": "4.1.9",
4646
"ink-testing-library": "4.0.0",
4747
"rimraf": "6.0.1",

apps/cli/src/ui/components/autocomplete/triggers/HistoryTrigger.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface HistoryResult extends AutocompleteItem {
2121
/** Mode the task was run in */
2222
mode?: string
2323
/** Task status */
24-
status?: "active" | "completed" | "delegated"
24+
status?: "active" | "completed" | "delegated" | "interrupted"
2525
}
2626

2727
/**
@@ -133,8 +133,22 @@ export function createHistoryTrigger(config: HistoryTriggerConfig): Autocomplete
133133

134134
renderItem: (item: HistoryResult, isSelected: boolean) => {
135135
// Status indicator
136-
const statusIcon = item.status === "completed" ? "✓" : item.status === "active" ? "●" : "○"
137-
const statusColor = item.status === "completed" ? "green" : item.status === "active" ? "yellow" : "gray"
136+
const statusIcon =
137+
item.status === "completed"
138+
? "✓"
139+
: item.status === "active"
140+
? "●"
141+
: item.status === "interrupted"
142+
? "⏸"
143+
: "○"
144+
const statusColor =
145+
item.status === "completed"
146+
? "green"
147+
: item.status === "active"
148+
? "yellow"
149+
: item.status === "interrupted"
150+
? "cyan"
151+
: "gray"
138152

139153
// Mode indicator (if available)
140154
const modeText = item.mode ? ` [${item.mode}]` : ""
@@ -178,7 +192,7 @@ export function toHistoryResult(item: {
178192
totalCost?: number
179193
workspace?: string
180194
mode?: string
181-
status?: "active" | "completed" | "delegated"
195+
status?: "active" | "completed" | "delegated" | "interrupted"
182196
}): HistoryResult {
183197
return {
184198
key: item.id, // Use task ID as the unique key

apps/cli/src/ui/components/autocomplete/triggers/__tests__/HistoryTrigger.test.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,24 @@ describe("HistoryTrigger", () => {
188188
expect(output).toContain("○")
189189
})
190190

191+
it("should render interrupted status with correct indicator", () => {
192+
const trigger = createHistoryTrigger({ getHistory: () => mockHistoryItems })
193+
194+
const interruptedItem: HistoryResult = {
195+
key: "task-interrupted",
196+
id: "task-interrupted",
197+
task: "Interrupted subtask waiting to resume",
198+
ts: Date.now() - 1000 * 60 * 5,
199+
mode: "ask",
200+
status: "interrupted",
201+
}
202+
const { lastFrame } = render(trigger.renderItem(interruptedItem, false) as React.ReactElement)
203+
204+
const output = lastFrame()
205+
// Should contain the interrupted status indicator (⏸)
206+
expect(output).toContain("⏸")
207+
})
208+
191209
it("should render selected items with different styling", () => {
192210
const trigger = createHistoryTrigger({ getHistory: () => mockHistoryItems })
193211

apps/cli/src/ui/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export interface TaskHistoryItem {
109109
totalCost?: number
110110
workspace?: string
111111
mode?: string
112-
status?: "active" | "completed" | "delegated"
112+
status?: "active" | "completed" | "delegated" | "interrupted"
113113
tokensIn?: number
114114
tokensOut?: number
115115
}

0 commit comments

Comments
 (0)