Skip to content

Commit 57ff033

Browse files
Merge branch 'main' into issue/560
2 parents 601d369 + 21a15e5 commit 57ff033

71 files changed

Lines changed: 724 additions & 376 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/cli-release.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ on:
1313
type: boolean
1414
default: false
1515

16+
# Least privilege: the release job escalates to contents: write via its own
17+
# job-level permissions block.
18+
permissions:
19+
contents: read
20+
1621
jobs:
1722
# Build CLI for each platform.
1823
build:

.github/workflows/code-qa.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,56 @@ on:
99
merge_group:
1010
types: [checks_requested]
1111

12+
# Least privilege: every job below escalates only where it needs to.
13+
permissions:
14+
contents: read
15+
1216
jobs:
17+
dependency-review:
18+
runs-on: ubuntu-latest
19+
# Only meaningful for PRs — validates the dependency diff of the pull
20+
# request against GitHub's advisory database before merge.
21+
if: github.event_name == 'pull_request'
22+
steps:
23+
- name: Checkout code
24+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
25+
with:
26+
# This job never pushes — don't persist the GITHUB_TOKEN.
27+
persist-credentials: false
28+
- name: Dependency review
29+
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
30+
31+
invisible-chars:
32+
runs-on: ubuntu-latest
33+
# Reject invisible / homoglyph Unicode that GitHub's diff UI renders
34+
# invisibly and most editors hide. These compile fine, which is the
35+
# risk: identifier-splitting, string-literal injection, and the
36+
# "Trojan Source" bidi-override attack (U+202A-U+202E). Scanning raw
37+
# bytes catches them in strings, identifiers, and comments alike.
38+
steps:
39+
- name: Checkout code
40+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
41+
with:
42+
# This job never pushes — don't persist the GITHUB_TOKEN.
43+
persist-credentials: false
44+
- name: Reject invisible / homoglyph Unicode
45+
run: |
46+
# zero-width (U+200B-200F), word joiner (U+2060), BOM (U+FEFF),
47+
# bidi overrides (U+202A-202E), soft hyphen (U+00AD).
48+
# Covers source, release-adjacent executable scripts
49+
# (*.sh / *.cjs / *.cts / *.mts), and the executable shell
50+
# blocks inside GitHub workflow/action YAML.
51+
if grep -rnP '[\x{200B}-\x{200F}\x{202A}-\x{202E}\x{2060}\x{FEFF}\x{00AD}]' \
52+
--include='*.ts' --include='*.tsx' --include='*.js' --include='*.mjs' \
53+
--include='*.cjs' --include='*.cts' --include='*.mts' --include='*.sh' \
54+
--include='*.yml' --include='*.yaml' \
55+
--exclude-dir=node_modules --exclude-dir=dist --exclude-dir=out \
56+
--exclude-dir=coverage --exclude-dir=.turbo --exclude-dir=.vinxi \
57+
src webview-ui packages apps .github; then
58+
echo "::error::Found invisible or homoglyph Unicode characters (zero-width / bidi-override / BOM / soft hyphen)"
59+
exit 1
60+
fi
61+
1362
check-translations:
1463
runs-on: ubuntu-latest
1564
steps:

.github/workflows/codeql.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ on:
77
schedule:
88
- cron: '24 19 * * 3'
99

10+
# Least privilege: the analyze job escalates to security-events: write via its
11+
# own job-level permissions block.
12+
permissions:
13+
contents: read
14+
1015
jobs:
1116
analyze:
1217
name: Analyze (${{ matrix.language }})

.github/workflows/e2e.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ on:
77
merge_group:
88
types: [checks_requested]
99

10+
permissions:
11+
contents: read
12+
1013
jobs:
1114
e2e-mock:
1215
runs-on: ubuntu-latest

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

Lines changed: 37 additions & 1 deletion
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.
@@ -43,9 +43,29 @@ jobs:
4343
});
4444
}
4545
46+
// Only a `pull_request`/`pull_request_review` run that was itself triggered
47+
// FROM a fork gets a read-only GITHUB_TOKEN — label mutations there 403 with
48+
// "Resource not accessible by integration". `schedule` and `workflow_dispatch`
49+
// runs always execute in the base repo's context with a read/write token, even
50+
// when the PR they're reconciling happens to come from a fork, so they must NOT
51+
// be skipped or fork PRs would never get stale labels cleaned up.
52+
// See: https://docs.github.com/en/actions/concepts/security/github_token
53+
const isReadOnlyRun = Boolean(context.payload.pull_request) &&
54+
context.payload.pull_request.head?.repo?.owner?.login !== owner;
55+
56+
function isForkPR(pr) {
57+
return pr.head?.repo?.owner?.login && pr.head.repo.owner.login !== owner;
58+
}
59+
4660
// Strips stateLabels from a PR, optionally keeping one.
4761
// Also removes stale-awaiting-author when not keeping awaiting-author.
62+
// Only skipped when this run's own token is read-only (see isReadOnlyRun) —
63+
// schedule/workflow_dispatch runs reconcile fork PRs normally.
4864
async function reconcileLabels(pr, desiredLabel) {
65+
if (isReadOnlyRun && isForkPR(pr)) {
66+
core.info(`PR #${pr.number}: fork PR on a read-only run — skipping label mutation`);
67+
return;
68+
}
4969
const currentLabels = new Set(pr.labels.map(l => l.name));
5070
for (const label of stateLabels) {
5171
if (label !== desiredLabel && currentLabels.has(label)) {
@@ -105,6 +125,22 @@ jobs:
105125
continue;
106126
}
107127
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+
108144
// Check CI status for required checks on the PR's head commit only.
109145
// Scoping to required checks avoids advisory checks (e.g. codecov/patch)
110146
// incorrectly blocking label assignment on otherwise-ready PRs.

.github/workflows/marketplace-publish.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ on:
66
- "v*.*.*"
77
workflow_dispatch:
88

9+
# Least privilege: publish-stable escalates to contents: write via its own
10+
# job-level permissions block.
11+
permissions:
12+
contents: read
13+
914
jobs:
1015
check-pr-approval:
1116
runs-on: ubuntu-latest

.github/workflows/nightly-publish.yml

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,33 @@ jobs:
4141
exit 1
4242
fi
4343
44+
- name: Skip if this push is a release merge
45+
id: release-check
46+
env:
47+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48+
run: |
49+
package_version=$(node -p "require('./src/package.json').version")
50+
commit_subject=$(git log -1 --format=%s)
51+
skip=false
52+
53+
if [[ "$commit_subject" =~ ^chore:\ prepare\ v[0-9]+\.[0-9]+\.[0-9]+\ release ]]; then
54+
echo "Commit '${commit_subject}' looks like a release-prep merge; skipping nightly pre-release."
55+
skip=true
56+
else
57+
release_error=$(gh release view "v${package_version}" 2>&1 >/dev/null) && release_found=true || release_found=false
58+
if [ "$release_found" = "true" ]; then
59+
echo "v${package_version} is already published as a stable release; skipping nightly pre-release."
60+
skip=true
61+
elif [[ "$release_error" != *"release not found"* ]]; then
62+
echo "::error::gh release view failed unexpectedly: ${release_error}"
63+
exit 1
64+
fi
65+
fi
66+
67+
echo "skip=${skip}" >> "$GITHUB_OUTPUT"
68+
4469
- name: Set pre-release version
70+
if: steps.release-check.outputs.skip != 'true'
4571
id: version
4672
env:
4773
RUN_NUMBER: ${{ github.run_number }}
@@ -61,13 +87,15 @@ jobs:
6187
EOF
6288
6389
- name: Build workspace packages
90+
if: steps.release-check.outputs.skip != 'true'
6491
env:
6592
PKG_RELEASE_CHANNEL: prerelease
6693
run: |
6794
pnpm --filter @roo-code/build build
6895
pnpm --filter @roo-code/vscode-webview build
6996
7097
- name: Package pre-release VSIX
98+
if: steps.release-check.outputs.skip != 'true'
7199
env:
72100
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
73101
PKG_RELEASE_CHANNEL: prerelease
@@ -76,6 +104,7 @@ jobs:
76104
pnpm --filter ./src exec vsce package --pre-release --no-dependencies --out ../bin
77105
78106
- name: Verify VSIX contents
107+
if: steps.release-check.outputs.skip != 'true'
79108
env:
80109
VERSION_NUMBER: ${{ steps.version.outputs.number }}
81110
run: |
@@ -87,6 +116,7 @@ jobs:
87116
grep -q "extension/webview-ui/audio/celebration.wav" /tmp/zoo-code-vsix-contents.txt
88117
89118
- name: Validate packaged manifest identity
119+
if: steps.release-check.outputs.skip != 'true'
90120
env:
91121
VERSION_NUMBER: ${{ steps.version.outputs.number }}
92122
run: |
@@ -98,12 +128,36 @@ jobs:
98128
test "$artifact_name" = "zoo-code"
99129
test "$artifact_publisher" = "ZooCodeOrganization"
100130
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.
103131
- name: Publish pre-release to VS Code Marketplace
132+
if: steps.release-check.outputs.skip != 'true'
104133
env:
105134
VSCE_PAT: ${{ secrets.VSCE_PAT }}
106135
VERSION_NUMBER: ${{ steps.version.outputs.number }}
107136
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"
137+
npx @vscode/vsce publish --pre-release --skip-duplicate --packagePath "bin/zoo-code-${VERSION_NUMBER}.vsix"
138+
echo "Published or skipped existing ZooCodeOrganization.zoo-code ${VERSION_NUMBER} as a VS Code Marketplace pre-release"
139+
140+
# The VSIX built above with `vsce package --pre-release` already carries the
141+
# Microsoft.VisualStudio.Code.PreRelease manifest property, which is what Open
142+
# VSX reads to flag the version. `ovsx publish` ignores --pre-release for an
143+
# already-packaged .vsix (it only applies when ovsx does the packaging itself),
144+
# so it's intentionally omitted here.
145+
#
146+
# Open VSX's "latest" alias resolves to the highest semver across stable and
147+
# pre-release alike (pre-release only breaks ties at equal major.minor.patch),
148+
# so a nightly build can transiently become "latest" until the next stable
149+
# release outranks it. This mirrors how Marketplace pre-release users already
150+
# track the newest published version, so it's an accepted trade-off here too.
151+
- name: Publish pre-release to Open VSX Registry
152+
if: steps.release-check.outputs.skip != 'true'
153+
env:
154+
OVSX_PAT: ${{ secrets.OVSX_PAT }}
155+
VERSION_NUMBER: ${{ steps.version.outputs.number }}
156+
run: |
157+
set -o pipefail
158+
publish_output=$(pnpm exec ovsx publish "bin/zoo-code-${VERSION_NUMBER}.vsix" --skip-duplicate 2>&1 | tee /dev/stderr)
159+
if echo "$publish_output" | grep -q "is already published"; then
160+
echo "ZooCodeOrganization.zoo-code ${VERSION_NUMBER} was already published to Open VSX; skipped."
161+
else
162+
echo "Published ZooCodeOrganization.zoo-code ${VERSION_NUMBER} as an Open VSX pre-release"
163+
fi

.github/workflows/release-validation.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ on:
1616
- "releases/**"
1717
- ".github/workflows/release-validation.yml"
1818

19+
permissions:
20+
contents: read
21+
1922
jobs:
2023
validate-release:
2124
runs-on: ubuntu-latest

.vscode/settings.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
"dist": true // set this to false to include "dist" folder in search results
1010
},
1111
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
12-
"typescript.tsc.autoDetect": "off",
13-
"vitest.disableWorkspaceWarning": true
12+
"js/ts.tsc.autoDetect": "off",
13+
// Surface invisible (zero-width) and ambiguous/homoglyph Unicode in the
14+
// editor. Pairs with the code-qa.yml invisible-chars CI check.
15+
"editor.unicodeHighlight.invisibleCharacters": true,
16+
"editor.unicodeHighlight.ambiguousCharacters": true
1417
}

CHANGELOG.md

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

3+
## [3.66.0]
4+
5+
### Minor Changes
6+
7+
- Add Claude Sonnet 5 support across Anthropic, Bedrock, and Vertex providers (#777 by @navedmerchant, PR #778 by @navedmerchant)
8+
- Upgrade Semble to v0.4.1 with flattened result parsing and localized status messages (#733 by @navedmerchant, PR #734 by @navedmerchant)
9+
- Add task-lifecycle status transition guard and startup delegation reconciliation to prevent invalid task state transitions (#366 by @edelauna, PR #692 by @edelauna)
10+
- Fix: LiteLLM cache key collision and silent fallback to a non-existent default model (#638 by @awschmeder, PR #647 by @awschmeder)
11+
- Fix: reliable auto context condensing for the VS Code Language Model API (#714 by @simurg79, PR #710 by @simurg79)
12+
- Fix(ThinkingBudget): support `xhigh` and all extended reasoning effort values (#713 by @6rz6, PR #774 by @edelauna)
13+
- Fix(deepseek): round-trip `reasoning_content` in thinking mode to prevent 400 errors (#201 by @leosdad, PR #775 by @edelauna)
14+
- Fix(gemini): base64-encode `thoughtSignature` bypass token to fix the Vertex AI empty-response loop (#536 by @edelauna, PR #776 by @edelauna)
15+
- Fix: provider cache reset after settings import (#689 by @JunyongParkDev, PR #726 by @JunyongParkDev)
16+
- Fix(delegation): atomically serialize `reopenParentFromDelegation` (#365 by @edelauna, PR #725 by @edelauna)
17+
- Fix: shell default profile name type guard (#686 by @daewoongoh, PR #687 by @daewoongoh)
18+
- chore(security): dependency-review, invisible-char detection, and least-privilege workflow permissions (#782 by @edelauna, PR #783 by @edelauna)
19+
- chore: upgrade `@anthropic-ai/sdk` to 0.104.1 and `@anthropic-ai/vertex-sdk` to 0.17.1 (#438 by @p12tic, PR #600 by @p12tic)
20+
- chore: enforce no-floating-promises in core/task/ (PR #253 by @0xMink)
21+
- ci: improve PR label reconciliation with CI gating and event triggers (PR #228 by @app/roomote)
22+
- fix(deps): update AI SDKs and providers (PR #744 by @app/renovate)
23+
- chore(deps): update build, lint, and test tooling (PR #745 by @app/renovate)
24+
- chore(deps): update dependency mermaid to v11.16.0 (PR #742 by @app/renovate)
25+
- chore(deps): update dependency posthog-js to v1.393.5 (PR #746 by @app/renovate)
26+
- chore(deps): update dependency ajv to v8.20.0 (PR #747 by @app/renovate)
27+
- chore(deps): update dependency react-use to v17.6.1 (PR #740 by @app/renovate)
28+
- chore(deps): update dependency reconnecting-eventsource to v1.6.5 (PR #741 by @app/renovate)
29+
- chore(deps): update dependency pdf-parse to v1.1.4 (PR #739 by @app/renovate)
30+
- chore(deps): update dependency ovsx to v0.10.12 (PR #738 by @app/renovate)
31+
- chore(deps): update dependency only-allow to v1.2.2 (PR #737 by @app/renovate)
32+
333
## [3.64.0]
434

535
### Minor Changes

0 commit comments

Comments
 (0)