Skip to content

Commit 8893d0b

Browse files
authored
Merge branch 'Zoo-Code-Org:main' into main
2 parents 0a3f847 + c7f1d19 commit 8893d0b

54 files changed

Lines changed: 3053 additions & 1311 deletions

Some content is hidden

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

.github/workflows/marketplace-publish.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,51 @@ on:
77
workflow_dispatch:
88

99
jobs:
10+
check-pr-approval:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
pull-requests: read
14+
steps:
15+
- name: Check PR approval status
16+
env:
17+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18+
REPO: ${{ github.repository }}
19+
SHA: ${{ github.sha }}
20+
run: |
21+
pr_number=$(gh api "repos/${REPO}/commits/${SHA}/pulls" \
22+
--header "Accept: application/vnd.github+json" \
23+
--jq '.[0].number // empty')
24+
25+
if [ -z "$pr_number" ]; then
26+
echo "No PR found for commit ${SHA}. Deployment requires an approved PR."
27+
exit 1
28+
fi
29+
30+
review_decision=$(gh api graphql \
31+
-f owner="${REPO%%/*}" \
32+
-f name="${REPO#*/}" \
33+
-F number="$pr_number" \
34+
-f query='
35+
query($owner: String!, $name: String!, $number: Int!) {
36+
repository(owner: $owner, name: $name) {
37+
pullRequest(number: $number) {
38+
reviewDecision
39+
}
40+
}
41+
}' \
42+
--jq '.data.repository.pullRequest.reviewDecision // "NONE"')
43+
44+
echo "PR #${pr_number} review_decision: ${review_decision}"
45+
46+
if [ "$review_decision" != "APPROVED" ]; then
47+
echo "PR #${pr_number} is not approved (state: ${review_decision}). Deployment blocked."
48+
exit 1
49+
fi
50+
51+
echo "PR #${pr_number} is approved. Proceeding."
52+
1053
publish-stable:
54+
needs: [check-pr-approval]
1155
runs-on: ubuntu-latest
1256
environment: marketplace-production
1357
permissions:

.roo/commands/release.md

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,9 @@ mode: code
3636
- Do not manually edit `src/README.md`; the extension bundle step copies root `README.md` into `src/README.md`.
3737
- Check for stale upstream Roo Code wording that should now say Zoo Code.
3838

39-
6. Create the release notes as a changeset file at `.changeset/v[version].md`, then run `pnpm changeset:version` on the release branch before opening the PR.
40-
41-
```md
42-
---
43-
"zoo-code": patch|minor|major
44-
---
45-
46-
[list of changes]
47-
```
39+
6. Write the release notes directly into `CHANGELOG.md` on the release branch.
4840

41+
- Use the heading format `## [version]` (with square brackets) — e.g. `## [3.58.1]`. The publish workflow at `.github/workflows/marketplace-publish.yml` extracts release notes by matching this exact pattern; headings without brackets will be missed and the GitHub release will fall back to a generic message.
4942
- Always include contributor attribution and the PR number: use `(PR #<prNumber> by @username)`.
5043
- For PRs that close issues, include both issue and PR authors: `- Fix: Description (#123 by @reporter, PR #456 by @contributor)`.
5144
- For PRs without linked issues, include the PR number and author: `- Add support for feature (PR #456 by @contributor)`.
@@ -67,13 +60,10 @@ mode: code
6760
git checkout -b release/v[version]
6861
```
6962
70-
9. Generate the final release state on that branch:
71-
72-
```bash
73-
pnpm changeset:version
74-
```
63+
9. Bump the version in `src/package.json` to the target release version and ensure `CHANGELOG.md` and `src/CHANGELOG.md` are up to date.
7564
76-
- This should update `src/package.json`, `CHANGELOG.md`, and `src/CHANGELOG.md`, then consume the `.changeset` file.
65+
- Verify the `CHANGELOG.md` heading uses `## [version]` (with brackets).
66+
- Copy or sync `CHANGELOG.md` to `src/CHANGELOG.md` if the project keeps both.
7767
- Review the generated version and changelog before opening the PR.
7868
7969
10. Open a single release PR with the fully generated release state.
@@ -92,32 +82,31 @@ mode: code
9282
- If the release includes translated README or package-localization updates, include those files in the same PR.
9383
- Let the release validation workflow and normal PR checks run before merge.
9484
95-
11. After the release PR is merged, stop for a release review on the resulting `main` commit.
96-
97-
```bash
98-
git switch main
99-
git pull origin main
100-
REVIEWED_SHA=$(git rev-parse HEAD)
101-
git rev-parse --short "$REVIEWED_SHA"
102-
```
85+
11. Once the release PR is open and passing checks, get it approved by a reviewer before proceeding.
10386
104-
- Review the merged release state before any publish step.
105-
- Confirm that `src/package.json`, `CHANGELOG.md`, `src/CHANGELOG.md`, and the Marketplace-facing `README.md` all reflect the intended release.
106-
- Check that the release PR checks passed and that the merged commit is the one you want to ship.
107-
- Share that review summary, including `REVIEWED_SHA`, with the user and wait for explicit confirmation before creating the tag.
108-
- Do not create the tag or trigger publishing until the user says to proceed.
87+
- Do not create the tag until the PR has at least one approval — the publish workflow enforces this automatically and will fail if no approved PR is found for the tagged commit.
10988
110-
12. Only after explicit confirmation, create the release tag on that reviewed `main` commit:
89+
12. After the PR is approved, create the release tag on the release branch tip and push it:
11190
11291
```bash
113-
git tag v[version] "$REVIEWED_SHA"
92+
git tag v[version]
11493
git push origin v[version]
11594
```
11695
117-
- If `main` advances after the review pause, keep using the pinned `REVIEWED_SHA` for the tag instead of silently tagging a newer commit.
96+
- Tag the branch tip as-is. Do not rebase or merge additional commits into the release branch before tagging — doing so changes the commit SHA and may pull in unreviewed changes that weren't part of the approval.
97+
- The publish workflow validates that the tag version matches `src/package.json`.
98+
99+
13. The tag push triggers the stable publish workflow.
118100
119-
13. The stable publish workflow runs from the `v[version]` tag.
101+
- The workflow first checks that the tagged commit belongs to an approved PR. If the PR is not yet approved this step fails — approve the PR first, then retrigger by recreating and pushing the tag: `git tag -d v[version] && git push origin :refs/tags/v[version] && git tag v[version] && git push origin v[version]`.
102+
- Once the approval check passes, the `marketplace-production` environment gate fires and notifies the configured approvers.
103+
- A human approver must then approve the deployment before the extension is published to VS Code Marketplace and Open VSX.
120104
121-
- Do not create the tag before the release PR is merged.
122-
- The publish workflow validates that the tag version matches `src/package.json`.
123-
- Marketplace and Open VSX publishing use the configured CI secrets.
105+
14. After a successful deployment, add the release PR to the merge queue.
106+
107+
```bash
108+
gh pr merge [pr-number] --auto --squash
109+
```
110+
111+
- Do not merge before the deployment succeeds — merging first and then discovering a publish failure leaves `main` ahead of what was actually shipped.
112+
- The merge queue runs all required checks against the release branch before merging to `main`.

CHANGELOG.md

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

3+
## [3.58.1]
4+
5+
### Patch Changes
6+
7+
- Fix: Remove unsupported `--no-absolute-filenames` tar argument (#491 by @kazenshi, PR #492 by @kazenshi)
8+
39
## 3.58.0
410

511
### Minor Changes

apps/cli/package.json

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@
4141
"devDependencies": {
4242
"@roo-code/config-eslint": "workspace:^",
4343
"@roo-code/config-typescript": "workspace:^",
44-
"@types/node": "^24.1.0",
45-
"@types/react": "^19.1.6",
46-
"ink-testing-library": "^4.0.0",
47-
"rimraf": "^6.0.1",
48-
"tsup": "^8.4.0",
49-
"@vitest/coverage-v8": "^3.2.3",
50-
"vitest": "^3.2.3"
44+
"@types/node": "24.2.1",
45+
"@types/react": "18.3.23",
46+
"@vitest/coverage-v8": "3.2.6",
47+
"ink-testing-library": "4.0.0",
48+
"rimraf": "6.0.1",
49+
"tsup": "8.5.0",
50+
"tsx": "4.19.4",
51+
"vitest": "3.2.6"
5152
}
5253
}

apps/vscode-e2e/package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
"@roo-code/config-typescript": "workspace:^",
1717
"@roo-code/types": "workspace:^",
1818
"@copilotkit/aimock": "1.15.1",
19-
"@types/mocha": "^10.0.10",
20-
"@types/node": "^20.19.25",
21-
"@types/vscode": "^1.95.0",
22-
"@vscode/test-cli": "^0.0.11",
23-
"@vscode/test-electron": "^2.4.0",
24-
"glob": "^11.1.0",
25-
"mocha": "^11.1.0",
26-
"rimraf": "^6.0.1"
19+
"@types/mocha": "10.0.10",
20+
"@types/node": "20.19.41",
21+
"@types/vscode": "1.100.0",
22+
"@vscode/test-cli": "0.0.11",
23+
"@vscode/test-electron": "2.5.2",
24+
"glob": "11.1.0",
25+
"mocha": "11.2.2",
26+
"rimraf": "6.0.1"
2727
}
2828
}

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

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,24 @@ import { toolResultContains } from "./tool-result"
55

66
const SUBTASK_PARENT_MARKER = "SUBTASK_PARENT_CANCELLATION_SMOKE"
77
const SUBTASK_CHILD_MARKER = "SUBTASK_CHILD_CALCULATOR_SMOKE"
8+
const SUBTASK_FAST_PARENT_MARKER = "SUBTASK_PARENT_IMMEDIATE_COMPLETION"
9+
const SUBTASK_FAST_CHILD_MARKER = "SUBTASK_CHILD_IMMEDIATE_COMPLETION"
10+
const SUBTASK_XPROFILE_PARENT_MARKER = "SUBTASK_PARENT_CROSS_PROFILE"
11+
const SUBTASK_XPROFILE_SAME_CHILD_MARKER = "SUBTASK_CHILD_SAME_PROFILE"
12+
const SUBTASK_XPROFILE_DIFFERENT_CHILD_MARKER = "SUBTASK_CHILD_DIFFERENT_PROFILE"
813

914
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.`
1015
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.`
1116
export const SUBTASK_CHILD_FOLLOWUP_ANSWER = "9"
17+
const SUBTASK_FAST_CHILD_PROMPT = `${SUBTASK_FAST_CHILD_MARKER}: Complete immediately with the exact result "Fast child completed".`
18+
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.`
19+
20+
const SUBTASK_XPROFILE_SAME_CHILD_PROMPT = `${SUBTASK_XPROFILE_SAME_CHILD_MARKER}: Complete immediately with the exact result "Same-profile child completed".`
21+
const SUBTASK_XPROFILE_DIFFERENT_CHILD_PROMPT = `${SUBTASK_XPROFILE_DIFFERENT_CHILD_MARKER}: Complete immediately with the exact result "Different-profile child completed".`
22+
export const SUBTASK_XPROFILE_PARENT_PROMPT = `${SUBTASK_XPROFILE_PARENT_MARKER}: First use new_task to create a code-mode subtask with this exact message: "${SUBTASK_XPROFILE_SAME_CHILD_PROMPT}" After it returns, create an ask-mode subtask with the next instructions you receive.`
23+
export const SUBTASK_XPROFILE_SAME_CHILD_RESULT = "Same-profile child completed"
24+
export const SUBTASK_XPROFILE_DIFFERENT_CHILD_RESULT = "Different-profile child completed"
25+
export const SUBTASK_XPROFILE_PARENT_RESULT = "Sequential cross-profile parent resumed"
1226

1327
const requestContains = (req: ChatCompletionRequest, expected: string[]) => {
1428
const rawRequest = JSON.stringify(req)
@@ -40,6 +54,55 @@ const completionAfterAnswer = (followupId: string, completionId: string) => ({
4054
})
4155

4256
export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
57+
mock.addFixture({
58+
match: {
59+
userMessage: new RegExp(SUBTASK_FAST_PARENT_MARKER),
60+
sequenceIndex: 0,
61+
},
62+
response: {
63+
toolCalls: [
64+
{
65+
name: "new_task",
66+
arguments: JSON.stringify({
67+
mode: "ask",
68+
message: SUBTASK_FAST_CHILD_PROMPT,
69+
}),
70+
id: "call_subtasks_fast_parent_new_task_001",
71+
},
72+
],
73+
},
74+
})
75+
76+
mock.addFixture({
77+
match: {
78+
userMessage: new RegExp(SUBTASK_FAST_CHILD_MARKER),
79+
},
80+
response: {
81+
toolCalls: [
82+
{
83+
name: "attempt_completion",
84+
arguments: JSON.stringify({ result: "Fast child completed" }),
85+
id: "call_subtasks_fast_child_completion_002",
86+
},
87+
],
88+
},
89+
})
90+
91+
mock.addFixture({
92+
match: {
93+
toolCallId: "call_subtasks_fast_parent_new_task_001",
94+
},
95+
response: {
96+
toolCalls: [
97+
{
98+
name: "attempt_completion",
99+
arguments: JSON.stringify({ result: "Fast parent resumed" }),
100+
id: "call_subtasks_fast_parent_completion_003",
101+
},
102+
],
103+
},
104+
})
105+
43106
mock.addFixture({
44107
match: {
45108
userMessage: new RegExp(SUBTASK_PARENT_MARKER),
@@ -92,4 +155,95 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
92155
],
93156
},
94157
})
158+
159+
// Issue #457 sequence: a same-profile child returns first, then the resumed
160+
// parent delegates to a child whose mode uses a different API profile.
161+
mock.addFixture({
162+
match: {
163+
userMessage: new RegExp(SUBTASK_XPROFILE_PARENT_MARKER),
164+
sequenceIndex: 0,
165+
},
166+
response: {
167+
toolCalls: [
168+
{
169+
name: "new_task",
170+
arguments: JSON.stringify({
171+
mode: "code",
172+
message: SUBTASK_XPROFILE_SAME_CHILD_PROMPT,
173+
}),
174+
id: "call_subtasks_xprofile_parent_same_child_001",
175+
},
176+
],
177+
},
178+
})
179+
180+
mock.addFixture({
181+
match: {
182+
userMessage: new RegExp(SUBTASK_XPROFILE_SAME_CHILD_MARKER),
183+
},
184+
response: {
185+
toolCalls: [
186+
{
187+
name: "attempt_completion",
188+
arguments: JSON.stringify({ result: SUBTASK_XPROFILE_SAME_CHILD_RESULT }),
189+
id: "call_subtasks_xprofile_same_child_completion_002",
190+
},
191+
],
192+
},
193+
})
194+
195+
mock.addFixture({
196+
match: {
197+
predicate: (req: ChatCompletionRequest) =>
198+
requestContains(req, [SUBTASK_XPROFILE_PARENT_MARKER, SUBTASK_XPROFILE_SAME_CHILD_RESULT]) &&
199+
!requestContains(req, [SUBTASK_XPROFILE_DIFFERENT_CHILD_RESULT]),
200+
},
201+
response: {
202+
toolCalls: [
203+
{
204+
name: "new_task",
205+
arguments: JSON.stringify({
206+
mode: "ask",
207+
message: SUBTASK_XPROFILE_DIFFERENT_CHILD_PROMPT,
208+
}),
209+
id: "call_subtasks_xprofile_parent_different_child_003",
210+
},
211+
],
212+
},
213+
})
214+
215+
mock.addFixture({
216+
match: {
217+
userMessage: new RegExp(SUBTASK_XPROFILE_DIFFERENT_CHILD_MARKER),
218+
},
219+
response: {
220+
toolCalls: [
221+
{
222+
name: "attempt_completion",
223+
arguments: JSON.stringify({ result: SUBTASK_XPROFILE_DIFFERENT_CHILD_RESULT }),
224+
id: "call_subtasks_xprofile_different_child_completion_004",
225+
},
226+
],
227+
},
228+
})
229+
230+
mock.addFixture({
231+
match: {
232+
predicate: (req: ChatCompletionRequest) =>
233+
requestContains(req, [
234+
SUBTASK_XPROFILE_PARENT_MARKER,
235+
SUBTASK_XPROFILE_SAME_CHILD_RESULT,
236+
SUBTASK_XPROFILE_DIFFERENT_CHILD_RESULT,
237+
]),
238+
},
239+
response: {
240+
toolCalls: [
241+
{
242+
name: "attempt_completion",
243+
arguments: JSON.stringify({ result: SUBTASK_XPROFILE_PARENT_RESULT }),
244+
id: "call_subtasks_xprofile_parent_completion_005",
245+
},
246+
],
247+
},
248+
})
95249
}

apps/vscode-e2e/src/runTest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ async function main() {
161161
extensionTestsPath,
162162
launchArgs: [testWorkspace],
163163
extensionTestsEnv,
164-
version: process.env.VSCODE_VERSION || "1.101.2",
164+
version: process.env.VSCODE_VERSION || "1.100.0",
165165
})
166166
} catch (error) {
167167
console.error("Failed to run tests", error)

apps/vscode-e2e/src/suite/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ export async function run() {
7979
throw new Error(`No test files found matching criteria: ${process.env.TEST_FILE || "all tests"}`)
8080
}
8181

82+
// Run provider suites last so their teardown (which may leave per-mode profile
83+
// pins pointing at non-default providers) doesn't affect tool suites that start
84+
// tasks in specific modes and expect the default openrouter config.
85+
testFiles.sort((a, b) => {
86+
const aIsProvider = a.includes("/providers/")
87+
const bIsProvider = b.includes("/providers/")
88+
if (aIsProvider === bIsProvider) return a.localeCompare(b)
89+
return aIsProvider ? 1 : -1
90+
})
91+
8292
testFiles.forEach((testFile) => mocha.addFile(path.resolve(cwd, testFile)))
8393

8494
return new Promise<void>((resolve, reject) =>

0 commit comments

Comments
 (0)