Skip to content

Commit a3683a5

Browse files
Merge branch 'main' into fix/172-opencode-go-models-no-auth
2 parents e06e845 + f792c1b commit a3683a5

525 files changed

Lines changed: 24527 additions & 5127 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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"zoo-code": minor
3+
---
4+
5+
Add settings to control whether editor tabs Zoo opens during diff edits are auto-closed after accept/reject: auto-close transiently-opened files, auto-close even after user interaction (a refinement of the first), and auto-close newly created files.

‎.changeset/add-glm-5-2-support.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"zoo-code": minor
3+
---
4+
5+
Add GLM-5.2 support with High/Max `reasoning_effort` tiers. The default effort is High (deep reasoning stays opt-in), Max is selected only when the user explicitly picks it, and the parameter is omitted entirely when reasoning is disabled.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"zoo-code": patch
3+
---
4+
5+
Fix diff view scroll position and tab handling when applying edits. The diff now opens scrolled to the first changed line (including end-of-file removals, which are clamped to a valid line in the modified document) instead of forcing the viewport to the top. After accepting or rejecting a diff, files that were already open are restored to their pre-edit scroll position, and files that were not open before the edit have their transiently opened tab closed -- unless the user activated that tab during the diff, in which case it is kept open. Focus is no longer pulled back to the edited file when the user has navigated elsewhere. A file that was open in a preview tab is restored in a preview tab with the original scroll position, even if the target file replaced the preview tab and was automatically closed after the diff was accepted or rejected. A file that was pinned before the edit is re-pinned after the diff closes, so applying a diff no longer drops the tab's pinned state.
6+
7+
When a user clicks or edits inside the diff pane, the target file's tab is kept open after saving -- even if it was not previously open. If the user only scrolls in the diff pane, the existing close behavior is preserved. When the target file is re-revealed after a diff, it scrolls to the position most recently viewed by the user: if the user last scrolled in the diff pane, the target file mirrors that scroll position; if the user last scrolled in the target file's own editor, that position is used instead; otherwise the pre-edit scroll position is restored.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"zoo-code": patch
3+
---
4+
5+
Fix command auto-approval for multi-line shell constructs that must be treated as a single command.
6+
7+
**Quoted multi-line arguments** (`sh -c '...'`, `sh -c $'...'`, `sh -c "..."`): the parser previously split on every newline before handling quotes, so newlines inside a quoted argument were treated as separate commands, defeating allowlist auto-approval. Single-quoted, ANSI-C (`$'...'`), and double-quoted strings are now masked before the newline split so embedded newlines and operators stay within their command.
8+
9+
**Heredocs** (`<< EOF`, `<< 'EOF'`, `<< "EOF"`, `<<- EOF`): the entire heredoc -- opener line, body, and terminator -- is now treated as a single quoted region. Body lines are not split into independent sub-commands. All heredoc delimiter quoting styles (unquoted, single-quoted, double-quoted, backslash-escaped) are supported. An unterminated heredoc (missing terminator) is treated as malformed and returned as a single opaque token.
10+
11+
**Locale quoting** (`$"..."`): treated as a distinct token analogous to ANSI-C quoting, preserving the `$` prefix and preventing the double-quote handler from stripping it.
12+
13+
Quote masking is comment-aware: a quote character inside a `#` comment is not paired with a quote on a later line, so a comment cannot hide a real newline separator and merge two distinct commands. Commands with an unterminated quote are detected with a quote-aware scanner and returned as a single opaque token, preventing a line inside the unclosed quote from surfacing as an independently auto-approvable command. Genuine unquoted newlines still split into separate sub-commands, each of which must be allowlisted for auto-approval.
14+
15+
**Pattern selector (UI)**: the command pattern breakdown shown after execution now uses the same heredoc- and quote-aware parser (`parseCommand`) before extracting patterns, so an unterminated or terminated heredoc no longer produces spurious tokens like `EOF`, body-line words, or `<<` fragments in the allow/deny selector.
16+
17+
Note: this change only prevents _auto-approval_ of fragments from a malformed command; it does not reject malformed commands before execution, which will be addressed in a separate PR to keep the scope focused here.
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+
Forward the active task ID to the LiteLLM proxy as an `X-Zoo-Session-ID` request header so individual conversations can be correlated in LiteLLM logs and spend tracking. The header is only sent when a task ID is present, and follows the `x-<vendor>-session-id` convention used by Claude Code (`x-claude-code-session-id`) and GitHub Copilot (`x-copilot-session-id`).

‎.github/workflows/codeql.yml‎

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

4848
# Initializes the CodeQL tools for scanning.
4949
- name: Initialize CodeQL
50-
uses: github/codeql-action/init@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3
50+
uses: github/codeql-action/init@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3
5151
with:
5252
languages: ${{ matrix.language }}
5353
build-mode: ${{ matrix.build-mode }}
@@ -75,6 +75,6 @@ jobs:
7575
exit 1
7676
7777
- name: Perform CodeQL Analysis
78-
uses: github/codeql-action/analyze@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3
78+
uses: github/codeql-action/analyze@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3
7979
with:
8080
category: "/language:${{matrix.language}}"
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: Label PR review state
2+
3+
on:
4+
schedule:
5+
- cron: '0 * * * *' # hourly
6+
workflow_dispatch:
7+
8+
permissions:
9+
pull-requests: write
10+
11+
concurrency:
12+
group: label-pr-review-state
13+
cancel-in-progress: false
14+
15+
jobs:
16+
reconcile:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Reconcile PR review state labels
20+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
21+
with:
22+
script: |
23+
const { owner, repo } = context.repo;
24+
const stateLabels = ['awaiting-author', 'awaiting-review'];
25+
const failures = [];
26+
27+
const prs = await github.paginate(github.rest.pulls.list, {
28+
owner, repo, state: 'open', per_page: 100,
29+
});
30+
31+
for (const pr of prs) {
32+
try {
33+
const reviews = await github.paginate(github.rest.pulls.listReviews, {
34+
owner, repo, pull_number: pr.number, per_page: 100,
35+
});
36+
37+
// Reviews are returned chronologically, so later entries replace
38+
// each reviewer's earlier decision.
39+
const latest = new Map();
40+
for (const r of reviews) {
41+
if (r.state !== 'COMMENTED') {
42+
latest.set(r.user.login, r);
43+
}
44+
}
45+
46+
const changeRequestReviewers = [...latest.entries()]
47+
.filter(([, review]) => review.state === 'CHANGES_REQUESTED')
48+
.map(([login]) => login);
49+
const requestedReviewers = new Set(
50+
pr.requested_reviewers.map(reviewer => reviewer.login),
51+
);
52+
53+
let desiredLabel = null;
54+
if (changeRequestReviewers.length > 0) {
55+
desiredLabel = changeRequestReviewers.every(
56+
reviewer => requestedReviewers.has(reviewer),
57+
)
58+
? 'awaiting-review'
59+
: 'awaiting-author';
60+
}
61+
62+
const currentLabels = new Set(pr.labels.map(label => label.name));
63+
for (const label of stateLabels) {
64+
if (label !== desiredLabel && currentLabels.has(label)) {
65+
await github.rest.issues.removeLabel({
66+
owner, repo, issue_number: pr.number, name: label,
67+
});
68+
}
69+
}
70+
71+
if (desiredLabel && !currentLabels.has(desiredLabel)) {
72+
await github.rest.issues.addLabels({
73+
owner, repo, issue_number: pr.number, labels: [desiredLabel],
74+
});
75+
}
76+
77+
if (
78+
desiredLabel !== 'awaiting-author' &&
79+
currentLabels.has('stale-awaiting-author')
80+
) {
81+
await github.rest.issues.removeLabel({
82+
owner, repo, issue_number: pr.number,
83+
name: 'stale-awaiting-author',
84+
});
85+
}
86+
} catch (error) {
87+
failures.push(`#${pr.number}: ${error.message}`);
88+
core.error(`Failed to reconcile PR #${pr.number}: ${error.message}`);
89+
}
90+
}
91+
92+
if (failures.length > 0) {
93+
core.setFailed(`Failed to reconcile ${failures.length} PR(s): ${failures.join('; ')}`);
94+
}

‎.github/workflows/marketplace-publish.yml‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,53 @@ 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
56+
environment: marketplace-production
1257
permissions:
1358
contents: write
1459

@@ -29,6 +74,13 @@ jobs:
2974
test "$package_name" = "zoo-code"
3075
test "$publisher" = "ZooCodeOrganization"
3176
77+
- name: Validate publish ref
78+
run: |
79+
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && [ "$GITHUB_REF_NAME" != "main" ]; then
80+
echo "Manual stable publishes must run from main, not ${GITHUB_REF_NAME}."
81+
exit 1
82+
fi
83+
3284
- name: Validate release tag
3385
if: github.event_name == 'push'
3486
run: |

‎.github/workflows/nightly-publish.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ concurrency:
1515
jobs:
1616
publish-prerelease:
1717
runs-on: ubuntu-latest
18+
environment: marketplace-prerelease
1819

1920
steps:
2021
- name: Checkout code
@@ -33,6 +34,13 @@ jobs:
3334
test "$package_name" = "zoo-code"
3435
test "$publisher" = "ZooCodeOrganization"
3536
37+
- name: Validate publish ref
38+
run: |
39+
if [ "$GITHUB_REF_NAME" != "main" ]; then
40+
echo "Pre-release publishes must run from main, not ${GITHUB_REF_NAME}."
41+
exit 1
42+
fi
43+
3644
- name: Set pre-release version
3745
id: version
3846
env:

‎.github/workflows/stale.yml‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Close stale PRs
2+
3+
on:
4+
schedule:
5+
- cron: '0 0 * * *'
6+
workflow_dispatch:
7+
8+
permissions:
9+
pull-requests: write
10+
issues: write
11+
12+
jobs:
13+
stale-inactivity:
14+
name: 60-day inactivity
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
18+
with:
19+
repo-token: ${{ secrets.GITHUB_TOKEN }}
20+
21+
days-before-issue-stale: -1
22+
days-before-issue-close: -1
23+
days-before-pr-stale: 60
24+
days-before-pr-close: 7
25+
26+
stale-pr-label: stale-inactive
27+
stale-pr-message: >
28+
This PR has had no activity for 60 days and will be automatically
29+
closed in 7 days unless there is new activity. If you'd like to
30+
continue, please rebase and leave a comment.
31+
close-pr-message: >
32+
Closing due to 60+ days of inactivity. Feel free to reopen if
33+
you'd like to resume this work.
34+
35+
exempt-pr-labels: 'do-not-close,pinned,work-in-progress'
36+
37+
stale-review-inactivity:
38+
name: 14-day author inactivity after requested changes
39+
runs-on: ubuntu-latest
40+
steps:
41+
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
42+
with:
43+
repo-token: ${{ secrets.GITHUB_TOKEN }}
44+
45+
only-labels: awaiting-author
46+
days-before-issue-stale: -1
47+
days-before-issue-close: -1
48+
days-before-pr-stale: 14
49+
days-before-pr-close: 7
50+
51+
stale-pr-label: stale-awaiting-author
52+
stale-pr-message: >
53+
This PR has been awaiting author changes for 14 days and will be
54+
automatically closed in 7 days. Please address the review comments
55+
or leave a comment if you need more time.
56+
close-pr-message: >
57+
Closing due to author inactivity after requested changes. Feel free
58+
to reopen once the requested changes have been addressed.
59+
60+
exempt-pr-labels: 'do-not-close,pinned,work-in-progress'

0 commit comments

Comments
 (0)