Skip to content

Commit 4bb31e1

Browse files
committed
Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts: # packages/sdk-typescript/scripts/build.js
2 parents e2b688b + 9f8f65d commit 4bb31e1

96 files changed

Lines changed: 12931 additions & 1130 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.

.github/workflows/qwen-code-pr-review.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,17 @@ jobs:
159159
fi
160160
161161
review-config:
162+
# Bot-requested review_requested only: a CODEOWNERS-covered PR open
163+
# auto-requests every owner individually (#8945), spawning one
164+
# review_requested run per owner. Only the run where the bot itself is
165+
# the requested reviewer can reach review-pr, so the human-requested
166+
# siblings must skip here instead of each spending a runner. KEEP IN
167+
# SYNC with the review_requested clauses in precheck-pr.if and
168+
# authorize.if, and with the bot_login constant below.
162169
if: |-
163170
github.event_name == 'pull_request_target' &&
164-
github.event.action == 'review_requested'
171+
github.event.action == 'review_requested' &&
172+
github.event.requested_reviewer.login == 'qwen-code-ci-bot'
165173
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'') && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}'
166174
permissions: {}
167175
outputs:
@@ -225,13 +233,21 @@ jobs:
225233
# Only run for PR-target events and supported command comments — not every
226234
# unrelated comment — to avoid spawning a job per comment. The downstream
227235
# `if`s still do the exact command body match; this prefix is just a filter.
236+
# review_requested must additionally request the bot itself: a
237+
# CODEOWNERS-covered PR open auto-requests every owner individually
238+
# (#8945), and precheck-pr's identical predicate only covers fork PRs —
239+
# without this clause each same-repo sibling run spends an authorize job
240+
# (permission API + runner slot) before review-pr no-op exits.
228241
if: |-
229242
!cancelled() &&
230243
(github.event_name != 'pull_request_target' ||
231244
github.event.action != 'closed') &&
232245
(github.event_name != 'pull_request_target' ||
233246
github.event.pull_request.head.repo.full_name == github.repository ||
234247
needs.precheck-pr.outputs.decision == 'allow_triage') &&
248+
(github.event_name != 'pull_request_target' ||
249+
github.event.action != 'review_requested' ||
250+
github.event.requested_reviewer.login == 'qwen-code-ci-bot') &&
235251
(github.event_name == 'pull_request_target' ||
236252
(github.event_name == 'workflow_dispatch' &&
237253
github.event.inputs.command == 'resolve') ||

docs/design/2026-08-13-review-platform-provider-abstraction.md

Lines changed: 329 additions & 0 deletions
Large diffs are not rendered by default.

docs/design/web-shell/assistant-response-session-branching.md

Lines changed: 962 additions & 0 deletions
Large diffs are not rendered by default.

docs/developers/daemon/09-event-schema.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,16 @@ These events are workspace-keyed, not session-keyed. The session reducer treats
124124

125125
### Turn lifecycle / assistant pushes
126126

127-
| Type | Direction | Trigger | Key payload fields |
128-
| --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
129-
| `prompt_cancelled` | S->C | Prompt was cancelled through explicit `cancelSession` route **or** originator SSE disconnect | Envelope stamps `originatorClientId` for the canceling client. This means "cancellation requested", not "cancellation confirmed". Peer subscribers learn that the prompt has ended. |
130-
| `turn_complete` | S->C | A turn completed successfully | `sessionId, stopReason, promptId?`. `promptId` links to non-blocking prompt responses (`202`). The SDK matches SSE events to the originating prompt through it. |
131-
| `turn_error` | S->C | A turn failed | `sessionId, message, code?, promptId?`; same `promptId` correlation mechanism. |
132-
| `session_rewound` | S->C | `POST /session/:id/rewind` succeeded | `sessionId, promptId, targetTurnIndex, filesChanged[], filesFailed[], originatorClientId?` |
133-
| `session_branched` | S->C | `POST /session/:id/branch` created a branch from an existing session | `sourceSessionId, newSessionId, displayName, originatorClientId?` |
134-
| `followup_suggestion` | S->C | ACP child generated ghost-text follow-up suggestions after `end_turn`, forwarded over per-session SSE | `sessionId, suggestion, promptId`; wire only carries suggestions whose `getFilterReason()===null`. Clients render them as input-placeholder ghost text and invalidate them on next `sendPrompt`. |
135-
| `user_shell_command` | S->C | User started a shell command through `POST /session/:id/shell`; fanned out to other subscribers in the same session | `sessionId, command, shellId, originatorClientId?`. There is no typed `DaemonXxxData` interface yet; `asKnownDaemonEvent` returns `undefined` and the UI normalizer parses it ad hoc. |
136-
| `user_shell_result` | S->C | Result of the shell command above | `sessionId, shellId, exitCode, output, aborted`. Same ad hoc parsing note as `user_shell_command`. |
127+
| Type | Direction | Trigger | Key payload fields |
128+
| --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
129+
| `prompt_cancelled` | S->C | Prompt was cancelled through explicit `cancelSession` route **or** originator SSE disconnect | Envelope stamps `originatorClientId` for the canceling client. This means "cancellation requested", not "cancellation confirmed". Peer subscribers learn that the prompt has ended. |
130+
| `turn_complete` | S->C | A turn completed successfully | `sessionId, stopReason, promptId?, branchPoint?`. `promptId` links to non-blocking prompt responses (`202`). Eligible completed turns include `branchPoint: { assistantRecordUuid, checkpointUuid }`. |
131+
| `turn_error` | S->C | A turn failed | `sessionId, message, code?, promptId?`; same `promptId` correlation mechanism. |
132+
| `session_rewound` | S->C | `POST /session/:id/rewind` succeeded | `sessionId, promptId, targetTurnIndex, filesChanged[], filesFailed[], originatorClientId?` |
133+
| `session_branched` | S->C | Legacy compatibility event; the current branch endpoint returns its result directly and does not publish this event | `sourceSessionId, newSessionId, displayName, originatorClientId?`. Readers retain support for older producers. |
134+
| `followup_suggestion` | S->C | ACP child generated ghost-text follow-up suggestions after `end_turn`, forwarded over per-session SSE | `sessionId, suggestion, promptId`; wire only carries suggestions whose `getFilterReason()===null`. Clients render them as input-placeholder ghost text and invalidate them on next `sendPrompt`. |
135+
| `user_shell_command` | S->C | User started a shell command through `POST /session/:id/shell`; fanned out to other subscribers in the same session | `sessionId, command, shellId, originatorClientId?`. There is no typed `DaemonXxxData` interface yet; `asKnownDaemonEvent` returns `undefined` and the UI normalizer parses it ad hoc. |
136+
| `user_shell_result` | S->C | Result of the shell command above | `sessionId, shellId, exitCode, output, aborted`. Same ad hoc parsing note as `user_shell_command`. |
137137

138138
## Architecture
139139

docs/users/features/code-review.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,9 @@ For PR reviews the manifest is read from the merge base, so the PR under review
299299

300300
## Issue Fidelity
301301

302-
For bugfix PRs, the Issue Fidelity agent fetches issue evidence directly instead of relying on PR description text. It uses `gh pr view <pr> --repo <owner/repo> --json closingIssuesReferences` for GitHub's strong closing-issue metadata, then `gh issue view <number> --repo <issue_owner>/<issue_repo> --json title,body,comments` for the original report and discussion — the `--json` form includes the issue **body** (the reporter's original repro), which `--comments` alone omits, and the issue's own repository is read from each reference (a PR can close an issue in a different repo). This agent runs only for PR targets; local-diff and file-path reviews skip it.
302+
For bugfix PRs, the Issue Fidelity agent fetches issue evidence directly instead of relying on PR description text. It runs the `qwen review issue-context <pr> --repo <owner/repo> --out <file>` subcommand, which resolves GitHub's strong closing-issue metadata and then fetches each referenced issue's title, **body** (the reporter's original repro), and full comment thread — each from the issue's own repository (a PR can close an issue in a different repo). This agent runs only for PR targets; local-diff and file-path reviews skip it.
303303

304-
`closingIssuesReferences` is a discovery hint rather than proof the author linked the right issue: if it is empty but the PR references an apparent target issue, the agent still fetches it after judging relevance. Fetched issue text is treated as untrusted data (facts extracted, embedded instructions ignored). For relevant issues, the original reproduction, observed payload, expected behavior, and maintainer comments are treated as the highest-priority evidence for whether the PR fixes the right problem.
304+
The closing-issue set is a discovery hint rather than proof the author linked the right issue: if it is empty but the PR references an apparent target issue, the agent still fetches it after judging relevance (re-running with `--issue <n>`; a bare number resolves in the PR's repo, while `--issue <owner>/<repo>#<n>` fetches a cross-repo reference from its own repo). Fetched issue text is treated as untrusted data (facts extracted, embedded instructions ignored). For relevant issues, the original reproduction, observed payload, expected behavior, and maintainer comments are treated as the highest-priority evidence for whether the PR fixes the right problem.
305305

306306
If the issue evidence shows an upstream service or provider returned malformed data outside the client contract, client-side parser or sanitizer changes are not treated as a valid root-cause fix unless a maintainer explicitly requested a defensive workaround. A test that replays malformed upstream output proves only that the workaround handles that shape; it does not prove the workaround is architecturally appropriate.
307307

@@ -361,7 +361,7 @@ Medium- and high-effort reviews also save a structured JSON companion with the s
361361

362362
The deterministic halves of the pipeline — argument parsing (`qwen review parse-args`) and the event/body decision (`qwen review compose-review`) — are tested subcommands rather than prompt text, so `--effort` grammar, `--comment` forcing, verdict caps, and downgrade behavior are pinned by unit tests and cannot drift with the model.
363363

364-
**GitHub Enterprise:** reviewing a PR URL on a non-`github.com` host routes every GitHub call at that host — the review subcommands (`fetch-pr`, `pr-context`, `comment-status`, `presubmit`) accept `--host` and set it in code, so a forgotten host cannot silently retarget the review at `github.com`.
364+
**GitHub Enterprise:** reviewing a PR URL on a non-`github.com` host routes every GitHub call at that host — the review subcommands (`match-remote`, `meta`, `fetch-pr`, `pr-context`, `comment-status`, `issue-context`, `fetch-diff`, `comment-body`, `plan-diff`, `test-plan`, `presubmit`, `compose-review`, `submit`, `publish-assets`) accept `--host` and set it in code, so a forgotten host cannot silently retarget the review at `github.com`.
365365

366366
Every run ends with one machine-readable line (`Review complete: <target> — <disposition>`), so scripts and CI wrappers can detect completion and outcome with a single `^Review complete: ` match.
367367

integration-tests/cli/qwen-serve-streaming.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,78 @@ describePOSIX('qwen serve — Last-Event-ID resume', () => {
955955
}, 60_000);
956956
});
957957

958+
describePOSIX('qwen serve — historical Assistant response branch', () => {
959+
it('creates, opens, and continues a branch through the real daemon', async () => {
960+
const source = await client.createOrAttachSession({
961+
workspaceCwd: workspaceDir,
962+
sessionScope: 'thread',
963+
});
964+
const first = await client.prompt(source.sessionId, {
965+
prompt: [{ type: 'text', text: 'historical branch turn one' }],
966+
});
967+
expect(first.branchPoint).toBeDefined();
968+
if (!first.branchPoint) return;
969+
970+
await client.prompt(source.sessionId, {
971+
prompt: [{ type: 'text', text: 'historical branch turn two' }],
972+
});
973+
await client.prompt(source.sessionId, {
974+
prompt: [{ type: 'text', text: 'historical branch turn three' }],
975+
});
976+
977+
const branched = await client.branchSession(source.sessionId, {
978+
atRecordId: first.branchPoint.checkpointUuid,
979+
});
980+
const branchBeforeContinue = await client.getSessionTranscriptPage(
981+
branched.sessionId,
982+
{ limit: 500 },
983+
);
984+
const branchBeforeText = JSON.stringify(branchBeforeContinue.events);
985+
expect(branchBeforeText).toContain('historical branch turn one');
986+
expect(branchBeforeText).not.toContain('historical branch turn two');
987+
expect(branchBeforeText).not.toContain('historical branch turn three');
988+
989+
const sourceAfterBranch = await client.getSessionTranscriptPage(
990+
source.sessionId,
991+
{ limit: 500 },
992+
);
993+
const sourceText = JSON.stringify(sourceAfterBranch.events);
994+
expect(sourceText).toContain('historical branch turn one');
995+
expect(sourceText).toContain('historical branch turn two');
996+
expect(sourceText).toContain('historical branch turn three');
997+
998+
const loadedBranch = await client.loadSession(branched.sessionId);
999+
await client.prompt(
1000+
branched.sessionId,
1001+
{
1002+
prompt: [{ type: 'text', text: 'continue the historical branch' }],
1003+
},
1004+
undefined,
1005+
loadedBranch.clientId,
1006+
);
1007+
const branchAfterContinue = await client.getSessionTranscriptPage(
1008+
branched.sessionId,
1009+
{ limit: 500 },
1010+
);
1011+
expect(JSON.stringify(branchAfterContinue.events)).toContain(
1012+
'continue the historical branch',
1013+
);
1014+
1015+
// The source session must stay untouched by the fork's continuation.
1016+
const sourceAfterContinue = await client.getSessionTranscriptPage(
1017+
source.sessionId,
1018+
{ limit: 500 },
1019+
);
1020+
const sourceAfterContinueText = JSON.stringify(sourceAfterContinue.events);
1021+
expect(sourceAfterContinueText).not.toContain(
1022+
'continue the historical branch',
1023+
);
1024+
expect(sourceAfterContinueText).toContain('historical branch turn one');
1025+
expect(sourceAfterContinueText).toContain('historical branch turn two');
1026+
expect(sourceAfterContinueText).toContain('historical branch turn three');
1027+
}, 90_000);
1028+
});
1029+
9581030
describePOSIX('qwen serve — daemon Todo Stop Guard replay', () => {
9591031
it('continues after prompt admission without an SSE client and replays the bounded attempts', async () => {
9601032
const session = await client.createOrAttachSession({

0 commit comments

Comments
 (0)