Commit graph

378 commits

Author SHA1 Message Date
Shaojin Wen
e93da9e387
feat(autofix): escalate stopped takeover PRs and age out unanswered pauses (#8960)
* feat(autofix): escalate stopped takeover PRs and age out unanswered pauses

Takeover PRs that hit the round cap (or a circuit breaker) went silent:
no label, no dashboard entry, no escalation — five PRs had been paused
for days. The fleet shepherd only tracked bot-authored PRs, so the whole
35-PR human takeover pool was invisible.

The autofix scan now applies an autofix/needs-human label whenever a PR
reaches its cap (the write rides every cap detection, so already-paused
PRs backfill on the regular scan rotation), and removes it wherever
management resumes or a human releases the PR. The fleet shepherd
enumerates the takeover pool onto its dashboard (state, stop reason,
pause age, plus an awaiting-human section for released PRs) and gains a
single bounded lever: a takeover whose pause went unanswered for
AUTO_RELEASE_DAYS days gets its takeover label removed with a bilingual
summary, keeping the needs-human label as the filterable TODO. Resume
evidence newer than the pause notice — bot markers, trusted re-arm
commands, fresh labeled events — vetoes the release; every read fails
closed and a per-tick cap bounds blast radius.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): harden the takeover auto-release against review round 1

Addresses the PR review's two Criticals and eleven Suggestions:

- Command-comment resume evidence now counts only while FRESH (2h grace)
  and UNSUPERSEDED by a refusal ack (fork-refused/base-refused/
  skip-blocked) — an ignored command expires instead of vetoing the
  release forever, and no permission logic is mirrored from the route.
- The release lever's population comes from the needs-human enumeration
  (needs-human ∩ takeover), never the display window; both enumerations
  cap at 100 with saturation warnings, and a failed enumeration degrades
  to an error row so the dashboard write (and its liveness watermark)
  always runs.
- The auto-release summary posts before the label DELETE, dedup'd by its
  own marker — neither half can strand the other on a transient failure.
- Awaiting-human rows use neutral wording (capped bot PRs land there too)
  and a shepherd-side heal clears stale needs-human labels left by manual
  UI releases on fork PRs (human unlabeled event, budgeted, skip-vetoed).
- Fail-closed deferrals now still render a dashboard row (the row append
  moved outside the evaluation arms); tick summary and dashboard header
  report the same counters; days_since() replaces pasted epoch math.
- Tests: command-evidence gate replays (fresh/refused/expired/acked),
  refusal-variant and command-string cross-file pins, DELETE-target and
  fallback-assignment pins, heal jq replays, unified-row-render pin.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): close review round 2 — cycle-scoped heal, retryable summary

- The stale-label heal now only counts a human unlabel NEWER than the
  latest label-apply, so an unlabel from an earlier takeover cycle can no
  longer heal the current cycle's needs-human after an auto-release (R2-1).
- The summary dedup marker is scoped to the current pause cycle (markers
  older than the latest cap notice are ignored), so a re-armed and
  re-capped PR still gets its second release summary (R2-4).
- The two DELETE levers no longer redirect act()'s stdout, keeping the
  DRY-RUN preview and failure warning visible (R2-5).
- AUTO_RELEASE_DAYS is base-10 normalized after the numeric guard, so a
  zero-padded repo variable can't silently kill the lever (R2-6).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): close review round 2 — cycle-scoped markers and mutation-tested pins

- Heal is cycle-correlated: only a human unlabel NEWER than the latest
  label-apply counts (an earlier cycle's unlabel can't heal this cycle).
- The release summary dedup marker is scoped to the current pause cycle,
  so a re-armed and re-capped PR still gets its second summary.
- act() stdout is no longer redirected on the two DELETE levers (DRY-RUN
  preview and failure warning stay visible).
- AUTO_RELEASE_DAYS is base-10 normalized so a zero-padded repo variable
  cannot silently kill the lever.
- Doc/workflow-header text corrected to the implemented order (summary
  first, marker-dedup'd) and to the idle-backoff backfill timing.
- Mutation-tested test pins for every gap the reviewer probed: days_since
  replay, NH_PREFIX interpolation + truth map, loop-1 deferral, full
  cross-file marker/refusal-set equality, label-constant cross-pin,
  EVENT_TS merge + promotion ordering, CLEANUPS increment, unclassified
  headline classification, filter byte-identity, exit-spelling ban,
  @uri encoding, sort/field-list attribution, paginate shapes, scope
  --arg bindings, LIVE_LABELS_JSON wiring, positional append pin,
  label-create idempotence + POST guard, and per-branch removal
  attribution in the toggle replay.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): close review round 3 — release-ack label gate and shared classifiers

- R3-1 (Critical): the every-scan cap-branch label POST is now suppressed
  when a release ack (takeover-ack released) is newer than the last re-arm,
  so a released bot PR is not re-labeled each scan (which would fight every
  release-side removal and ping-pong with the shepherd cleanup). A re-arm
  advances the window past the release ack, re-enabling the label.
- R3-7: the /retry re-arm's needs-human removal now honors autofix/skip,
  mirroring the takeover-command guard — a frozen PR keeps its only
  filterable escalation state.
- R3-2 (Critical): the takeover-enum error row no longer claims 'no release
  evaluation ran' — the lever is fed by the needs-human enumeration.
- R3-8: the conflict-dispatch lever refuses a paused (needs-human) PR
  instead of spending a dispatch slot the scan would refuse.
- R1-10: extracted pending_checks()/failed_test_url() helpers so both
  dashboard loops share one CI-status classifier (the round-1 reply was
  wrong that the restructure removed this duplication — it did not).
- Hardened the mutation-tested pins: exact terminal-headline count (5),
  full rearm DELETE line + single-API-write, AUTO_RELEASE_DAYS guard order,
  runRearm env/stub/assertion for the /retry DELETE + skip guard, and the
  scope-guard comparison operator.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): close review round 4 — label-lifecycle hardening

- R4-C1: the conflict-dispatch lever reads needs-human from the LIVE label
  payload (after live_skip), not the tick-start snapshot, so a label applied
  after enumeration is still honored.
- R4-C2: a re-armed PR that still carries needs-human (a resume-side removal
  failed) now gets a bounded, skip-vetoed cleanup retry instead of staying
  pinned in the paused population forever.
- R4-C3: the per-tick release budget is consumed before the first external
  write — a DELETE outage can no longer mutate many PRs while RELEASES=0.
- R4-C4: dashboard row routing follows post-action label state — a released
  PR moves to Awaiting human, a healed one drops off entirely.
- R4-C5: the AUTO_RELEASE_DAYS guard also rejects over-long digit strings
  before any arithmetic (Bash-int overflow would wrap negative and pass -ge).
- R4-32: takeover-command stop only removes needs-human when the takeover
  release actually landed (REMOVED_OK; 404 counts) — a failed release no
  longer strands the escalation label while latching RELEASE_ACKED.
- R4-2: the /retry skip guard fails closed — an unreadable label state keeps
  the label (mirrors takeover-ack's exit-1 convention).
- R4-3: the takeover-ack released arm and the stop branch both honor
  autofix/skip when removing needs-human.
- R4-S1: producer headlines must be explicitly classified terminal or
  transient — an unclassified headline now fails the cross-file test.
- Pins updated/added for every behavior above.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): close review round 4 — robust release detection and marker-true gates

- R4-1/R4-5: release detection now uses the takeover unlabeled EVENT
  (recorded on every removal path, unlike the tolerated-lost ack comment),
  and the suppression only applies to human-authored PRs — a bot PR
  released from takeover returns to standard management and keeps the cap
  notice + escalation label.
- R4-6: the conflict-dispatch lever requires marker truth (conflict_paused)
  — an armed PR with a stale needs-human label is dispatched normally.
- R4-C1: the pause check reads needs-human from the live label payload.
- R4-C2: re-armed PRs with a stale label get a bounded cleanup retry.
- R4-C3: the release budget is consumed before the first external write.
- R4-C4: dashboard rows route on post-action label state.
- R4-C5: AUTO_RELEASE_DAYS rejects over-long digit strings before arithmetic.
- R4-32/R4-2/R4-3: stop/ack/retry removal paths gate on REMOVED_OK and skip.
- R4-9/R4-10/R4-13: membership check, STATE escaping, HM_OK-branched error row.
- R4-14: command evidence requires a write/maintain/admin commenter.
- R4-11/R4-15/R4-24: behavioral replays for the classifiers, the release
  jq, and the gate nesting.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): keep the label-DELETE idiom byte-identical across workflows

R4-32's REMOVED_OK tracking reworked the takeover-command stop branch's
404-tolerance block, breaking the pr-self-report-label ↔ qwen-autofix
contract test that pins the two workflows' label-DELETE idiom
byte-identical. Keep the canonical idiom and derive REMOVED_OK from
REMOVE_ERR's content afterward (empty = landed, 404 = already off,
anything else = release did not land) — same behavior, contract intact.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(autofix): close review round 4b — mutation-tested harness hardening

- R4-4: re-bound the conflict-lever regex spans and anchor on
  conflict_paused so the pin can't resolve live_skip against the sync
  lever's call site.
- R4-16: runAck records gh calls and asserts per-branch needs-human DELETE
  counts (engaged/released=1; base-refused/skip=0).
- R4-17: pin the first-pickup scan DELETE inside the engage-ack success
  branch.
- R4-18/R4-19: ordering pins — takeover POST before needs-human DELETE
  (engage), marker comment before cleanup DELETE (/retry).
- R4-20: deleteFail stub branch replays non-404 (warns, status 0) and 404
  (silent) DELETE outcomes.
- R4-21: identity-failure paths assert no DELETE ran.
- R4-22: runRearm stub serves labels only when --json labels is requested.
- R4-23: full api-write census pinned (exactly api user + one DELETE).
- R4-25: skip fixture uses the production multi-label shape.
- R4-28: loop-2 fetch pins include the jq -s 'add // []' merge program.
- R4-29: cmdGate scenario where a refusal is OLDER than the fresh command.
- R4-30: takeoverEnum asserts its own sort:updated-asc qualifier.
- R4-31: multi-entry fixtures pin the max/last/length aggregation operators
  on CMD_TS, EVENT_TS, REASON, SUMMARY_POSTED, and the heal lever's
  LATEST_LABEL_TS/UNLABEL_ACTOR programs.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): close review round 5 — trust boundaries and evidence freshness

- R4-5 residual: PR_META now fetches author so IS_BOT_AUTHOR actually
  resolves (the exemption was dead on arrival), with a behavioral replay.
- R5-1: REMOVED_OK derives from the captured stream ('HTTP ' non-404 = not
  landed) instead of output emptiness — GitHub returns a body on success.
- R5-2: /retry only drops needs-human when management actually resumes
  (takeover label present or bot-authored) — an auto-released human PR
  keeps its escalation label.
- R5-3: conflict_paused requires a real cap notice AND a newer resume
  marker — label-present/notice-absent now fails closed toward paused.
- R5-4: a failed permission read defers the release (PERM_READ_FAILED),
  never counts as no-permission — at both evaluation points.
- R5-5: compute_resume_ts scans in-grace commands newest-first and
  permission-checks each (≤2 reads), so a stranger's echo can't shadow a
  maintainer's command.
- R5-6: the release branch re-fetches evidence and recomputes resume state
  immediately before the first write.
- R5-8: the heal re-checks the takeover label from the live payload before
  clearing needs-human.
- R5-9: same-second ties resolve toward resume/release suppression in both
  files (RESUME>=TERM; RELEASE_ACKED >= window).
- R5-10: the heal anchors to the current pause boundary (latest needs-human
  apply event); an absent anchor skips the cleanup, fail closed.
- Tests: whole-function compute_resume_ts replay (permission/shadow/tie/
  grace/refusal cases), heal anchor fixtures, toggle stub models the real
  DELETE body, runRearm orphan case.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): close review round 6 — contract-safe gates and subshell flag fix

- B5: the bot-fleet enumeration failure now degrades to a loud error row
  and falls through (FLEET_OK gate) instead of exiting before the
  independently-fed takeover/needs-human processing and the dashboard's
  liveness-watermark write.
- B12: the cap-branch LIVE_LABELS consent re-read fails closed on an
  unreadable gh pr view (a collapse to '' ignored a concurrently added
  skip for standard bot PRs).
- R6-1/R6-19: the takeover-release landed flag is keyed on the DELETE exit
  status (LBL_DEL_FAILED set inside the pinned idiom's failure branch) —
  never on output text, which lies in both directions. The
  pr-self-report-label idiom evolves identically to keep the cross-workflow
  contract green (and its own 'removed' log line no longer lies either).
- R5-4 residual: compute_resume_ts now returns via globals
  (RESUME_OUT/PERM_READ_FAILED) and both call sites invoke it directly —
  the previous  subshell silently dropped PERM_READ_FAILED, leaving
  the fail-closed defer branches dead.
- R6-3: command candidates are deduped by author before permission reads,
  so a stranger posting N commands can't burn the 2-read budget and shadow
  a maintainer's command.
- R6-4: an unreadable release history is reported as such, not as
  'released'.

* fix(autofix): close review round 7 — lever starvation, re-arm anchoring, permission shadows

- R5-7: the release lever gets its OWN enumeration of the paused population
  (takeover+needs-human, stale-first) instead of the long-lived needs-human
  display window — released-awaiting PRs aging back into that window could
  truncate exactly the fresh pauses that become release-eligible, starving
  the lever and making the zombie state permanent and self-feeding.
- R6-3: the 2-read permission budget now sets PERM_READ_FAILED on exhaustion
  (it was failing open), and the candidate walk sorts newest-first per author
  (group_by+max_by+sort) instead of unique_by's alphabetical order, so two
  read-only strangers can't shadow a maintainer's newer command.
- R7-1: the stale-label cleanup anchors on the current pause boundary (latest
  needs-human apply) and is marker-confirmed only — not keyed on TERM_TS, and
  never on command/label evidence — so a re-paused PR with a lost cycle-2
  notice isn't read as re-armed on stale cycle-1 evidence.
- R7-7: the /takeover stop success echo is gated on REMOVED_OK — a failed
  DELETE no longer logs 'removed'.
- R7-2: TAKEOVER_COMMAND/RETRY_COMMAND mirrored into the shepherd env and
  passed via --arg, so the resume matcher can't drift from the route.
- Tests: conflict_paused + re-arm guard behavioral replays, mirrored-command
  cross-file pin, engaged/released-with-skip ack matrix cells, LBL_DEL_FAILED
  branching, gnuDateShim hoisted to module scope, R4-24 nesting indices.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(autofix): complete the R4-16 ack-matrix DELETE-count coverage

Add fork-refused and skip-blocked ack cases to the takeover-ack harness
— management never resumed on either, so zero needs-human DELETEs, each
asserted by total DELETE count (not just toContain).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): close review round 8 — reachable re-arm cleanup, release race guards, pin census

* fix(autofix): close review round 9 — honest release-failed ack, cleanup attempt budgets, dashboard single-owner routing

- /takeover stop whose label DELETE failed no longer posts a
  'Takeover released' ack: a release-failed variant names the retry
  (R9-4), and the R7-7 echo pair gains symmetric log pins (R9-3)
- stale-label cleanups count ATTEMPTS like the release budget, so a
  DELETE outage trips the cap instead of leaving it inert (R9-5)
- dashboard renders each both-label PR exactly once: loop 1 defers by
  paused membership, loop 3 is the render of last resort (R9-1/R9-13)
- a 404 from the collaborators-permission endpoint classifies the
  author read-only instead of renewably deferring the release (R9-10)
- cap-branch release evidence reuses the per-iteration events fetch
  under a success flag (R9-18); release-clock comment corrected (R9-11)
- harness gates end-anchor the --json field list (R9-14/R9-15); the
  escalation POST and the ack-body census gain count pins (R9-16);
  the date shim answers only the +%s shape it emulates (R9-9)

* fix(autofix): close review round 10 Criticals — exact HTTP 404 release classification, isolated replay fixtures

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): close review round 11 Criticals — engaged stale-ack guard, exact HTTP 404 permission classification

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-15 17:32:23 +00:00
Heyang Wang
9f8f65dde0
feat: support fork from any conversation (#8817)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* feat(web-shell): branch from completed assistant responses

Add durable response checkpoints so Web Shell sessions can branch from
eligible completed Assistant turns without mutating the source history.

- Record and validate checkpoints behind serialized topology fences
- Preserve historical anchors through replay, daemon, SDK, and UI layers
- Publish bounded forks with crash-safe ownership and referenced backups
- Serialize prompt, rewind, branch, automatic turn, and close mutations
- Cover stale anchors, replay pagination, cleanup, and pending UI states

Note: Responses recorded before this change remain non-branchable.

# Conflicts:
#	packages/acp-bridge/src/bridge.ts
#	packages/acp-bridge/src/bridgeTypes.ts
#	packages/cli/src/acp-integration/acpAgent.test.ts
#	packages/cli/src/acp-integration/acpAgent.ts
#	packages/cli/src/serve/routes/session.ts
#	packages/cli/src/serve/server.test.ts
#	packages/core/src/services/chatRecordingService.ts
#	packages/core/src/services/sessionService.test.ts
#	packages/core/src/services/sessionService.ts
#	packages/sdk-typescript/src/daemon/DaemonClient.ts
#	packages/web-shell/client/components/MessageItem.tsx
#	packages/web-shell/client/components/MessageList.tsx

* fix(session): preserve historical branch checkpoints

Keep Assistant-response branching intact across the daemon stack after
rebases, including history serialization and persisted-session ownership.

- Forward durable checkpoint IDs through Bridge, SDK, and UI layers
- Serialize live history mutations and retain valid nested branch anchors
- Preserve persisted branches during generation cleanup
- Add cross-layer regression tests for replay and stale checkpoints

* fix(web-shell): harden response session branching

* chore: remove PR comment evaluation artifact

Keep the PR review report as a local ignored backup instead of
shipping it with the feature branch.

- Remove the generated PR comment evaluation from tracked files
- Preserve the report under the ignored analyze directory

* fix(web-shell): guard historical branch mutations

Historical branch requests could outlive the client timeout during an
active turn, and interactive forks lacked the recorder's cross-process
writer-lease barrier.

- Hide Assistant Branch actions while a turn is active
- Run interactive fork creation inside the recorder write barrier
- Use the concrete checkpoint recorder contract in Session
- Document committed-session ownership and implemented design status

* perf(core): index historical branch points during transcript scan

Build branch catalogs during the frozen index scan so the first history
page no longer reopens and materializes the complete active chain.

- Retain a compact projection for shared branch-point resolution
- Correlate live branch anchors with the completed prompt and final reply
- Complete recorder mocks required by the concrete Session contract
- Update the reviewed design with performance and correlation invariants

* fix(core): address review findings — dead code, boundary remap, promptId guard, stale toast (#8274)

* fix(core): address review findings — dead code, boundary remap, promptId guard, stale toast (#8274)

* fix(core): address review findings — archived GC, subtype registration, UUID validation, dead code (#8274)

* test: strengthen branch-point and fork coverage from review (#8274)

Add focused tests requested in PR review:
- branch catalog resolves checkpoints that fall on a later page
- accept a parallel tool batch closed within a single turn
- exercise the linkSync->copyFileSync fork backup fallback success path
- prove a remapped checkpoint stays usable via a nested fork
- isolate each branch-point validation conjunct across bridge and SDK

* fix: address round-4 review feedback for session branching (#8274)

- Make the directory-fsync durability test platform-aware (skip on win32),
  since fsyncDirectoryBestEffort swallows the injected error on Windows and
  the rejection path is non-Windows by design.
- Reject atRecordId on the side-task fork path instead of silently discarding
  it, so the API surface no longer implies acceptance.
- Correct the design doc: name the real promptQueue FIFO (not the nonexistent
  historyMutationQueue) and describe filtered checkpoint boundaries as
  remapped to the nearest retained predecessor, not unconditionally null.
- Add focused tests: branch-point assistantRecordUuid mismatch rejection, and
  insight-block branchRecordId anchoring (insight-only block must not anchor
  onto the previous reply).

* fix: address round-5 review feedback for session branching (#8274)

* fix: address round-6 review feedback for session branching (#8274)

* fix: address round-7 review feedback for session branching (#8274)

* fix(core): harden branch-point resolution against malformed transcript shapes (#8274)

- Filter null/non-object part elements in the shared branch resolver so a
  transcript containing null parts no longer makes forkSession throw a
  TypeError for every checkpoint.
- Tag tool calls carried in from the pre-boundary prefix so a dangling call
  left by a crashed turn no longer permanently disables checkpoint
  recording; only calls issued inside the turn must close.
- Merge duplicate-uuid records first-wins for identity fields in the
  transcript reader, matching the byUuid index and fork aggregation, so the
  reader never advertises a branch marker the fork path must reject.

* fix: address round-8 review feedback for session branching (#8274)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: address round-9 review feedback for session branching (#8274)

* test(core): pin branch GC isolation from throwing warning callbacks (#8274)

* fix(acp-bridge): reject rewind at admission while a prompt is active (#8274)

* fix(web-shell): harden session branch publication

Preserve direct ACP prompt preemption while fencing branch and rewind history mutations at the Session boundary. Convert branch publication, backup staging, cleanup, and stale-claim GC to asynchronous filesystem APIs, and surface unsupported hard-link commits as typed ACP and HTTP errors. Expand regression coverage and update the reviewed design contract.

* fix(web-shell): harden historical response branching

Reject branches during prompt admission and keep dispatched mutations
owned until their real outcome is known.

- remove detached timeouts across ACP, SDK, and WebUI
- bound branch cleanup and make title scans asynchronous
- avoid full branch-point scans during transcript pagination
- add regression coverage from Core through the real daemon and browser

* fix: address round-11 review feedback for session branching (#8274)

* fix: address round-12 review feedback for session branching (#8274)

* refactor(branching): remove branch-specific overdesign

Simplify historical session branching around the minimum persistence,
recording, and navigation invariants required by the Web Shell flow.

- Replace branch claims and garbage collection with staged publication
- Validate completed turns incrementally instead of reloading transcripts
- Separate persisted branch creation from live session restoration
- Bound SDK waits and prevent late results from replacing navigation
- Remove unused checkpoint prompt IDs while reading legacy records

Note: A pre-commit crash may leave hidden staging or orphan backups.

* chore(sdk): update browser bundle budget

Account for the combined historical branching and transcript projection APIs after merging main while keeping the browser bundle size guard narrowly bounded.

* fix(branching): address review lifecycle gaps

Harden historical session branching against cancellation, observer,
navigation, and shutdown races found during review.

- Normalize cancellation keys and bound close-time mutation waits
- Preserve anchors after observer completion and load persisted forks
- Report success only when the guarded session switch starts
- Cover recorder cursors, fork cleanup, admission, and rollback
- Align daemon events and branch errors with runtime behavior

* refactor(session): simplify branching safeguards

Reduce the session branching surface after review while preserving the
critical concurrency, durability, and ownership guarantees.

- Remove the unused full-chain resolver and test production entry points
- Copy backups from verified open handles instead of using hard links
- Reuse the bounded title scan instead of maintaining an async mirror
- Deduplicate UI branch requests and fail fast for busy automatic turns
- Consolidate repeated mutation tests and retain critical race coverage
- Document the retained invariants and rejected overdesign explicitly

* test(branching): simplify regression coverage

Reduce duplicated branching tests while retaining regression coverage for
the safety, concurrency, and lifecycle fixes introduced by this feature.

- Consolidate symmetric bridge and agent scenarios with table-driven cases
- Remove repeated cross-layer assertions and brittle implementation spies
- Drop redundant UI permutations and branch-only visual snapshots

* fix(serve): handle branch busy admission

* fix(sdk): preserve v1 branch session contract

Keep existing latest-state branch callers source- and wire-compatible while
retaining the persisted-only behavior for historical checkpoint branches.

- Restore no-anchor branches before returning their live client identity
- Add a separate typed result for persisted historical branch requests
- Clean up restored attachments on stale navigation and disconnect races
- Cover immediate continuation and historical persistence independently

* fix(daemon): guard branching history mutations

Prevent branch creation and automatic Goal turns from racing session
teardown or interactive history mutations.

- Reject branch admission while a conditional close is authorized
- Serialize Goal continuations behind the history mutation gate
- Limit branch checkpoints to interactive prompts
- Add regressions for close and Goal scheduling races

* fix(branching): preserve fork and checkpoint semantics

Keep branch checkpoints and file-history snapshots correct across resumed,
forked, and non-interactive session flows.

- Track the restored active-chain base before the first appended turn
- Preserve backup file modes during fork publication
- Exclude authenticated channel prompts from checkpoint recording
- Add regressions for all three review failures

* fix(branching): harden branch and rewind behavior

Handle the remaining branch and rewind review findings without widening
the feature contract.

- Ignore benign concurrent branch rejections in the Web Shell
- Validate rewind prompt IDs before using string operations
- Pin mutation ordering, cleanup, compaction, and checkpoint invariants
- Align sourced-fork fixtures with the canonical side_task value

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: 易良 <1204183885@qq.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-15 10:01:49 +00:00
Shaojin Wen
dc7e234876
feat(review): absorb prose gh commands into platform-backed subcommands (#9096)
* docs(design): /review platform provider abstraction (GitHub + Aone Code)

* feat(review): absorb prose gh commands into platform-backed subcommands

The skill prose and agent briefs carried raw gh commands for the model to
execute (repo resolution, head-SHA fetches, issue evidence, lightweight
diffs, truncated-body refetches) — the prose-carried class that keeps
shipping parsing bugs and drops the Enterprise host unless a prose rule
remembers GH_HOST.

Four new subcommands absorb them, built on a review-platform reader seam
(lib/platform) whose first provider is GitHub over lib/gh.ts:

- meta: repo identity + live headSha/webUrl (was gh repo view / gh pr view)
- issue-context: closing-issue evidence file for Agent 0, incl. cross-repo
  issues and --issue for referenced-but-unlinked targets
- fetch-diff: lightweight-mode diff to file (was gh pr diff redirects)
- comment-body: one comment body by kind; pr-context truncation notes now
  name this command (with --host baked in) instead of a gh api route

SKILL.md, the Agent 0 brief, and the role-0 generated prompt no longer
contain model-executed gh calls; the GH_HOST prefixing prose rule is gone.

* test(review): pin the bare-number host source as review meta

Step 1 now derives a bare PR number's owner/repo/host with the meta
subcommand instead of a prose gh repo view; the pin follows.

* fix(review): address PR #9096 review findings

Critical:
- tests: resolve() expectations on Windows-asserted --out paths
- issue-context: same-repo-keyed closing/extra dedup, extras self-dedup,
  and a failed single-issue fetch degrades to an explicit section instead
  of aborting the whole evidence file
- meta: apply the URL-discovered host to gh routing before the PR call,
  validate --repo without requiring a number, usage errors exit 2
- agent-prompt: shellQuotePath the welded --out evidence path; plan-diff
  gains --host so a lightweight run welds it into the Agent 0 command
- lib/gh: ghRaw (no trim) for diff/comment-body payloads whose edges are
  content; resolveGhHost normalizes an empty --host flag
- SKILL.md: restore the constructable Posted:-link fallback; scope the
  no-model-run-gh-calls claim (Step 4 scratch-repo carve-out named)
- issue-context: actionable error when gh < 2.72.0 lacks
  closingIssuesReferences

Suggestions: drop dead host fields from run-function arg interfaces,
exit-2 consistency, pin the previously unpinned contracts (setGhHost
ordering x4, buildMarkdown host baking, welded GHE command, mkdirSync
guards, no-comments placeholder, GH_HOST save/restore, 422 meta pin),
fetch-diff handler tests, ClosingIssueRef dead fields removed, design-doc
corrections (D1 subset note, D2 cell, D7 amend-delta rule, testing
strategy wording, carve-out exemption), code-review.md --out fix.

* fix(review): address PR #9096 round-2 review findings

Critical:
- lib/gh: ghRaw now returns bytes untouched — the unconditional CRLF
  rewrite would strip blob-content \r from every hunk of a CRLF-file diff
  (heavy mode's raw-bytes policy; the justification comment was wrong)
- SKILL.md: Step 7's head-SHA fallback meta call carries the Enterprise
  --host annotation like every sibling call site
- SKILL.md: the render-adjudication carve-out runs in a verifier
  subagent's shell — the Enterprise note now says exported-GH_HOST only,
  otherwise adjudication is unavailable (a --host note here cannot reach
  the subagent); the GHE enumeration also names submit

Suggestions:
- agent-prompt welds the plan's pr/ownerRepo/host only after re-validation
  (the plan is a file on disk; compose-review already re-validates)
- plan-diff validates --host against HOSTNAME_RE before recording it
- pins: full emitted-command prefix at all three sites, setGhHost ordering
  now includes ensureAuthenticated (x4), ghRaw no-trim/no-rewrite,
  plan-diff host write side, closing-ref repository-less fallback,
  --issue handler wiring, comment-body --out JSON marker + malformed-repo
  exit 2 + usage-error preempts auth, meta cwd-branch flag precedence,
  buildMarkdown host baking for inline/issue kinds
- agent-briefs: --issue extras fetch from the PR's own repo — disclosed
- pr-context: fix the resolveGhHost comment (env host IS baked)
- docs: design doc corrections (gh.ts not-unchanged note, plan-diff in
  the inventory + D8, Phase 1 is new-implementation-not-refactor note,
  carve-out row/phase-3 ownership), review DESIGN.md issue-fetch path

* fix(review): address PR #9096 round-3 review findings

Critical:
- lib/gh: HOSTNAME_RE now requires an alphanumeric first char and
  REPO_SEGMENT rejects a leading dash — flag-shaped values (--help,
  -evil/repo) no longer pass validation only to be misparsed as CLI
  options downstream of the unquoted weld

Suggestions:
- agent-prompt weld: the plan re-validation (digit prNumber, isOwnerRepo,
  HOSTNAME_RE-gated host) is now pinned by tampered-plan tests
- setGhHost trims once so raw and resolved --host inputs agree
- all four subcommands validate --repo before the auth gate (usage error
  exit 2, never preempted by an auth failure), pinned with
  ensureAuthenticated-not-called assertions
- issue-context: bodies render untrimmed (leading-indent log pastes keep
  their code block); closing/extra dedup compares repos case-insensitively;
  a failed closing-issue discovery degrades into a named section (with the
  gh >= 2.72.0 hint) while --issue extras still fetch; numeric args get
  positive-integer validation with exit 2 (also --issue, id, --pr)
- agent-briefs: retry-once guidance extended — unfetchable sections mean
  re-run with --issue before declaring evidence unavailable
- SKILL.md: Step 5's lightweight block no longer re-fetches the diff
  Step 1 already wrote (one fetch, no head-advance race); SKILL.test.ts
  gains the revert guard for the lightweight capture + host note
- meta: env-GH_HOST label for explicit --repo pinned

* fix(review): address PR #9096 round-4 review findings

Critical:
- agent-briefs: the retry rule no longer sends unfetchable CLOSING refs
  through --issue (extras resolve in the PR's own repo — a cross-repo
  closing number would fetch the same-numbered unrelated issue); a plain
  re-run is the retry, closing refs are re-fetched every run
- fetch-diff: an empty PR diff writes a 0-byte file, not a one-blank-line
  file that plan-diff dies on with a coverage error instead of taking the
  designed empty-plan branch

Suggestions:
- setGhHost: only genuinely-absent input resets; a non-empty
  all-whitespace --host now fails validation instead of silently
  restoring the default
- agent-prompt weld trims the plan host before re-validating (fetch-pr
  records the raw flag); pr-context validates the resolved host against
  HOSTNAME_RE before baking it into emitted refetch commands
- empty --out is a usage error (exit 2) classified before any fetch, in
  comment-body/fetch-diff/issue-context; plan-diff's handler maps the new
  --host usage error to exit 2 instead of an uncaught crash
- issue-context: extras section header no longer claims NOT-in-closing
  when the closing set is UNKNOWN (discovery failed)
- SKILL.md: Step 1's lightweight item spells out the fetch-diff failure
  stop rule; the Enterprise enumeration now lists every --host subcommand
  (adds plan-diff, test-plan, publish-assets); code-review.md matches
- design doc: D1 names the ensureAuthenticated gate; the D2 carve-out row
  describes the shipped behavior (exported-GH_HOST only), not a welded
  prefix that never existed
- pins: full-wrapper assertions extended, numeric usage gates at all
  three remaining handlers, --pr success-path plumbing, --out JSON marker,
  setGhHost TypeError class + trim/whitespace behavior, ghRaw byte
  fidelity, unfetchable extras in the JSON, cross-repo ownerRepo in the
  JSON, untrimmed body rendering, extras-section absence, discovery-failed
  header wording, runPrContext-level host baking (flag + env + rejected
  alias), SKILL revert guards for Step 7's meta rewiring

* fix(review): address PR #9096 round-5 review findings

Critical:
- fetch-pr records the TRIMMED host into the fetch report, so the two
  downstream readers that re-validate it (compose-review's plan identity,
  the agent-prompt weld) see the canonical form — a padded-but-valid
  GHE host no longer drops to github.com anchor links
- a non-empty all-whitespace --host no longer silently falls through to the
  env/default in resolveGhHost (it is returned as '', not swallowed), and
  publish-assets validates the raw flag via setGhHost before resolving —
  the Contents-API write can no longer be retargeted at github.com by a
  whitespace-only flag; match-remote now fails closed (exit 6) on the same
  input instead of matching github.com

Suggestions:
- plan-diff: drop the doubled `plan-diff:` prefix from the two thrown
  TypeErrors (the handler prepends it once); reject a whitespace-only
  --host instead of dropping it from the plan
- new shared assertWritableOutPath (lib/paths): empty/whitespace AND
  directory --out targets are classified as usage errors BEFORE any fetch
  in comment-body/fetch-diff/issue-context (the directory case previously
  died EISDIR after the fetches and exit-coded as a runtime failure)
- resolveRepo fetches `parent` and prefers it when the resolved repo is a
  fork — gh's default-repo preference is a remote literally named
  `upstream`, not an API fork check, so an origin-only fork clone no longer
  targets a fork's same-numbered PR
- scope the comment-body exit-2 comment to the handler-level guards (yargs
  -layer missing-arg / invalid-choice failures exit 1 — a known gap)
- SKILL.test revert guards: rule-4 issue-context weld + absence of the
  pre-absorption `--json closingIssuesReferences` syntax; the 422
  `commit_id` comparison clause and the `fetch-diff`-output rename; the
  Step 6 tail-fetch `--out` sentence and the Posted: fallback grounding;
  the lightweight-capture host note
- pins: malformed-host handler exit-2 in fetch-diff/issue-context/meta;
  issue-context exit-1 auth branch; padded-host weld trim; pr-context
  setGhHost routing (flag + env); whitespace-only --out in all three;
  numeric-gate tests reset process.exitCode between invocations and add
  non-integer cases; plan-diff asserts the metacharacter host is never
  recorded into the plan

* fix(review): address PR #9096 round-5 findings (meta host guard, plan-diff stderr)

- meta's discovery branch validates the routed host against HOSTNAME_RE
  before setGhHost: a host gh tolerates but the subcommands reject
  (underscore intranet aliases, IPv6 literals) is an environmental
  condition, so it now names the actual source (--host flag vs discovered
  repo-URL host) and fails exit 1, never as a usage error blaming a flag
  the caller never passed
- plan-diff's handler catch uses writeStderrLineSafe (a broken stderr must
  not let the throw escape and lose the exit-2/exit-1 classification)

* fix(review): address PR #9096 round-6 Critical findings

- lib/gh: split the byte/text raw modes. execGhWithRetry gains a
  mode ('default' | 'bytes' | 'text'); the bytes mode runs with encoding
  'buffer' and decodes latin1, so a diff of a non-UTF-8 (Latin-1/Shift-JIS)
  file no longer loses every invalid byte to U+FFFD. ghRaw is the bytes
  mode (fetch-diff writes it back with latin1 — byte fidelity end to end);
  new ghRawText is UTF-8-with-edges-preserved, which comment-body uses
  (comment bodies are always valid UTF-8 from the API; the leading-indent
  code-block fidelity holds, but bytes are not corrupted into mojibake)
- lib/gh: split the leading-dash ban per segment — owners cannot start
  with a hyphen but REPO names can (yezhaodan/-Git exists), so a leading
  dash on the repo half is no longer rejected (the ban only protected
  against the flag-shaped OWNER half anyway)
- github resolveRepo: take the host from the resolved repo's OWN url —
  gh's `parent` field carries no url (only id/name/owner), so reading
  target.url crashed every origin-only fork clone with TypeError; the
  meta.test fork fixture now matches the real gh shape
- publish-assets: the round-5 raw-flag validation guarded on
  `trim() !== ''`, which skipped exactly the whitespace-only host it
  exists to refuse — guard on presence instead so setGhHost(' ') throws
  the documented TypeError (exit-3 refusal, no silent Contents-API
  retarget at the env/default host)

* fix(review): address PR #9096 round-6 gpt-5.6-sol Critical findings

- agent-prompt weld: a present-but-invalid plan host now fails closed
  (throws) instead of being silently dropped to null — a tampered host can
  no longer quietly reroute the evidence fetch to github.com's same-named
  repo (a missing host stays optional)
- comment-body: read `.body` off the JSON-parsed response instead of
  `--jq '.body // ""'` — the jq form appends a trailing newline (a body
  not ending in one gained a byte; an empty body became "\n"); JSON parse
  returns the exact bytes GitHub stores
- issue-context: --issue now accepts `owner/repo#123` as well as `123`, so
  a referenced issue living in a DIFFERENT repo is fetched there instead
  of silently reading the PR repo's same-numbered unrelated issue; dedup
  is by (repo, number) pair, case-insensitively, which also fixes the
  cross-repo-closing-shadows-same-repo-extra edge uniformly
- lib/gh: drop the now-unused ghRawText text mode (comment-body moved to
  the JSON parse)

* fix(review): address PR #9096 round-7 review findings

Critical:
- R7-1: the Agent 0 brief, SKILL.md rule 4, and code-review.md still taught
  "issue-context cannot fetch a referenced issue in a different repo —
  declare it unavailable", contradicting the cross-repo `--issue
  owner/repo#123` capability shipped in round 6. All three carriers now
  teach the qualified form, and the wrong-issue warning / retry ban is
  narrowed to bare numbers (a qualified retry is a correct retry)

Suggestions (all directly pin or harden this PR's changes):
- agent-prompt weld fails closed on a present-but-NON-STRING host and on a
  present-but-whitespace-only host (both were silently dropped to null,
  rerouting the evidence fetch), matching the sibling identity fields
- gh.test.ts: the ghRaw byte-fidelity test now returns a real Buffer with
  an invalid-UTF-8 byte (0xE9) — the latin1 decode genuinely executes (the
  previous string mock made String.prototype.toString an identity call)
- meta: the explicit-`--repo` branch gates the emitted host with
  HOSTNAME_RE, same as the discovery branch (an unroutable GH_HOST env
  value no longer emits a host label every sibling rejects)
- publish-assets: pin the round-6 whitespace-host refusal (exit 3, no gh
  call, `(from --host)` in stderr)
- issue-context: pin the documented `--issue owner/repo#n` grammar end to
  end through the handler regex
- code-review.md: the GHE `--host` enumeration adds match-remote (the
  pipeline's first host-sensitive step)

* fix(review): address PR #9096 round-8 review findings

Critical:
- R8-1: the round-7 non-string-host guard threw on `host: null` — which
  fetch-pr writes unconditionally into every same-repo github.com plan
  (`args.host?.trim() || null`), so every ordinary review would have
  failed at the roster build. null is now tolerated (only a present
  non-null non-string host throws); regression test added
- R8-2: comment-body validates `--kind` is a single admitted token before
  any platform call — a duplicated `--kind` arrives as an array that
  passes yargs' element-wise choices, and String() would coerce it to
  'review,inline' into the wrong API collection

Suggestions:
- assertWritableOutPath rejects a trailing-separator --out (the POSIX
  directory spelling that resolve() normalizes away)
- comment-body prints the body via process.stdout.write (byte-exact, no
  invented trailing newline)
- agent-prompt weld prNumber guard strengthened (rejects 0 and unsafe
  integers, matching the welded handler's contract)
- pins: meta explicit-branch HOSTNAME_RE gate, isOwnerRepo dash asymmetry
  both directions, ghRaw retry with buffer stderr, whitespace-only and
  null plan hosts, issue-context qualified-grammar rejection side,
  corrected the misleading case-insensitive dedup test, fs mocks no longer
  consult ambient /tmp state (existsSync/statSync overridden)
- R8-13 (extras-header double-render assertion) deferred to #9194 per the
  reviewer's own note
2026-08-15 09:16:48 +00:00
Shaojin Wen
4257916e7e
feat(daemon): guard cross-worktree Git mutations (#8687)
* feat(daemon): guard cross-worktree Git mutations

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): keep Git guard off serve fast path

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): close daemon Git guard parser bypasses

Rebuild the daemon-side Git relocation guard parser so the runtime-verified
bypasses from review are closed: comment/glob tokens, backslash
continuations, shell wrapper and path-qualified invocations, cwd-shifting
builtins, env-var relocations, gitfile/symlink/worktree-admin indirection,
-C vs relative git-dir ordering, --output and textconv-capable read-only
subcommands, command-valued -c config, and dynamic expansion forms all fail
closed for mutations outside the session working directory. Command
splitting, canonicalization, and containment now reuse the core helpers.

Key the child-side v1 restrictions (/fork, agent-backed workspace memory)
and per-call daemon round trips on a real external provider being attached
instead of on guard plumbing presence: under the built-in guard alone,
hidden-agent tool calls traverse the same daemon-side policy, so those
features stay available and non-shell tools resolve locally.

Denial reasons are length-clamped and control-character-stripped so they
always satisfy the guard result validation.

* fix(serve): keep daemon Git guard out of serve fast-path closure

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): close re-reviewed daemon Git guard bypasses and subagent regression

Address the re-review at b1b7606: keep the outermost entry cwd as the
containment basis inside shell wrappers, fail closed on unrecognized
programs that still reference a relocated Git command, skip leading
shell keywords, deny undecidable and fused `-c` payloads, inspect
command-executing `-c` config before the read-only allowance, drop
`grep`/`status` from the relocated read-only set, validate the
model-supplied `directory` against the effective working directory,
and stop modelling `--exec-path`/`--list-cmds` as value-taking.

Context-less shell paths (subagents, cron turns, background
notifications, resumed background agents) previously failed closed
under the now-unconditional managed guard: fall back to the
scheduler-owned session id and validate those requests by session
ownership, while external-provider consultation still requires a
prompt binding. Move the guard's canonicalization off the daemon
event loop with a promise-based realpathNearestExisting, drop the
unread workspaceCwd request field, and restore the top-level guard
import in run-qwen-serve.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): restore lazy daemon Git guard import

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): close daemon Git guard shell front-end bypasses

All six forms below were reproduced against the real guard and confirmed to
really escape the boundary with a real shell and git 2.47.3 (the outside
worktree was reset, or the textconv marker file was created outside).

- `cat-file --textconv`/`--filters` run programs configured by the *target*
  repository, so the relocated read-only allowance no longer applies when a
  `--textconv`, `--filters`, or `--output` flag is present, wherever it
  appears in the invocation.
- `export GIT_WORK_TREE=<outside> && git reset --hard` hid the relocation in
  its own segment. `export`/`declare`/`typeset`/`readonly` operands (and plain
  assignments after `set -a`) are now recorded as exported relocations that
  apply to every later command in the chain, including wrapper payloads and
  substitution bodies.
- `builtin cd <outside>` masked cwd tracking; `builtin` now takes the same
  prefix-skipping path as `command`.
- `cd -P <outside>` consumed the flag as the directory operand, so containment
  was evaluated against `<cwd>/-P` — inside the boundary whenever such a
  directory exists. `cd`/`pushd` option words are skipped when locating the
  operand.
- `cd <outside> && nice git reset --hard` passed because an unrecognized
  program word was only checked for relocation markers. It is now also denied
  when the tracked working directory is unknown or already outside the
  boundary, while the same command inside the boundary stays allowed.
- `echo $(git -C <outside> reset --hard)` was folded into an opaque token.
  `$(…)` and backtick bodies are extracted from the raw segment and analysed
  as nested commands; `$((…))` is stepped over as arithmetic and an
  unterminated substitution is denied.

The guard also covers the `monitor` tool, which spawns its `command` through
the same shell with the same `directory` argument and was previously
short-circuited to allow by the child before the daemon ever saw it. The
shell-executing tool set is shared through acp-bridge so the child and the
daemon policy cannot drift apart.

* fix(serve): restore the shell-wrapper analysis return shape

The unrecognized-shell-wrapper branch of `analyzeRun` still returned the
pre-`assignmentsOnly` `other` shape, so `tsc --build` failed on the union.
Every CI job that installs dependencies runs that build, so all three went
red on it.

* fix(serve): close the remaining daemon Git guard front-end gaps

Round-4 review findings, each reproduced against the real guard before the
fix and pinned by a regression test with an in-boundary control.

- An unrecognized program word only failed closed on Git-flag markers, so a
  `cd` inside its quoted payload slipped through (`su -c 'cd <outside> && git
  reset --hard'`, the same through `xargs … sh -c`). `cd`/`pushd` now count as
  relocation markers in that scan.
- The Git word was matched case-sensitively while program classification
  lowercases, so `nice GIT reset --hard` escaped on a case-insensitive
  filesystem.
- A program word the daemon cannot read at all (`cd <outside> && $CMD git
  reset --hard`, also behind `command`) skipped the containment check that
  unrecognized program words get.
- `export GIT_DIR` with no `=` exported an earlier shell-local assignment
  invisibly; shell-local GIT_* assignments are now tracked and promoted on a
  name-only export. `+=` appends and `set -o $OPT` are recorded as unresolved.
- `eval` propagated only its cwd, so `eval 'export GIT_WORK_TREE=<outside>'`
  lost the export it performed in the current shell.
- `--shallow-file` and `--attr-source` were not modelled as value-taking, so
  their value was read as the subcommand — which ends option parsing and hid
  every relocation after it.
- The command-executing config set missed `core.sshCommand`,
  `diff.<driver>.textconv`/`command`, `merge.<driver>.driver`,
  `sequence.editor`, `gpg.program`, `pager.*`, `core.askPass` and
  `uploadpack.packObjectsHook`, and matched case-sensitively although Git
  config keys are not.
- `env -S` accepted a dynamic payload as literal text and ignored the fused
  `env -S'cmd'` form.

Docs: the Non-goals list claimed `CoreToolScheduler` was untouched although
this PR adds the scheduler-owned `sessionId` to the guard context; the
protocol capability row and the user guide still described the guard as
`run_shell_command`-only and mis-stated the denial-message prefixes; and the
relocation-never-revoked over-approximation (`unset GIT_DIR` does not clear a
recorded relocation) is now stated in Limitations.

* fix(serve): resolve the repository Git discovers, not just the directory

Two more escapes from the round-4 review, both reproduced with a real shell
and git 2.47.3 before the fix.

- A `.git` gitfile inside the boundary redirects Git to an outside
  repository: `git -C <inside>/decoy commit` moved the outside repo's HEAD
  while the directory itself passed containment. A `cwd` target now resolves
  the first `.git` between it and the boundary through the same
  `resolveGitDirRepository` path `--git-dir` targets use, which keeps a
  linked-worktree session working because its own gitfile resolves back to
  that worktree's checkout — pinned by a test that runs a session whose `.git`
  points at an outside admin directory.
- `cd -P <symlink>/..` lands the shell in the parent of the symlink's real
  target, which a lexical resolve places back inside the boundary. A `-P` cd
  whose target contains `..` now drops the tracked directory. The default
  logical form is unchanged and still allowed, because bash resolves it
  against the logical path and really does stay inside.

* fix(serve): match chdir, brace-expansion and here-string semantics

Three more escapes from the round-4 review, each reproduced against the real
guard and then confirmed with a real shell and git 2.47.3.

- `git -C <symlink>/.. reset --hard` escaped: git's `-C` reaches the kernel as
  a chdir, which resolves each component's symlinks, while the guard collapsed
  `..` lexically and landed back inside the boundary. `-C`, `env -C`,
  `sudo -D` and `cd -P` now resolve physically, component by component; bash's
  default `cd` stays lexical because that is what the shell itself does.
- `git {-C,<outside>} reset --hard` escaped: brace expansion happens after
  this parse, so the tokens git receives were never the tokens the guard saw.
  A brace-expansion token now marks the invocation unresolved.
- `sh <<< 'git -C <outside> reset --hard'` escaped: the tokenizer dropped
  redirect operands, and a here-string carries its whole payload in the
  command line. Redirect operands stay in the run, so the here-string is
  scanned like any other token; ordinary `>`/`2>` targets are inert text and
  a regression test keeps them allowed.

Checked and not reproduced, so left alone: `describe --dirty` did not rewrite
the target index, `GIT_OBJECT_DIRECTORY=<outside>` did not write objects
there, and `bash -o allexport -c '…'` cannot export into the parent shell
because the payload runs in a subprocess.

* fix(serve): treat relocated git describe as a target-repo write

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(serve): state what git describe actually rewrites

Keeping `describe` out of the relocated read-only set is right, but the
reason as written does not match git 2.47.3. Measured against a real
repository with a stale stat cache (mtime-only touch), `describe --dirty`,
`--broken` and `--always --dirty` rewrite the target repository's
`.git/index`, while a plain `describe`, `--tags` and `--always` leave it
untouched. The subcommand still belongs outside the set — the flag is one
token away from any describe a model writes — so only the comments and the
design doc change.

* test(serve): cover the provider-attached marker with a real handshake

The only assertion on the provider marker was the negative case, so a break
in the attached path — the marker comparison or the conditional child-env
spread — would have gone unnoticed. This drives a loopback provider through
the real `/v1/handshake` and asserts the child env carries the attached
marker alongside the plumbing one. Verified load-bearing: forcing the marker
to `undefined` fails it.

* fix(serve): close the round-3 shell and repository-discovery gaps

Every payload below was reproduced against the real guard first; the two
that turn on git's own behaviour were measured with git 2.47.3.

Repository discovery
- `ls-files` executes the target repository's `core.fsmonitor` — the exact
  property that removed `status` — so it leaves the relocated read-only set.
  Measured: `git -C <outside> ls-files` runs the hook; `rev-parse` and
  `cat-file` remain side-effect free.
- The discovery check was tied to a *cwd* relocation, so a `--work-tree`-only
  or bare relocation skipped it. Git discovers its repository from the cwd
  whenever no `--git-dir` names one, so the check now runs on that basis, and
  an unrecognized program (`cd sub && nice git branch`) gets it too. A linked
  worktree whose own gitfile points at an outside admin directory stays
  allowed — pinned from both sides.

Shell front-end
- `eval > /dev/null '…'` swallowed the redirection into its payload and lost
  the command. Redirect operands (and an `N>` descriptor prefix) are now
  flagged: still scanned for markers, never joined into argv.
- `cd` glued to a control operator (`true;cd <outside>`) was not a marker.
- Letters after `c` in a short bundle are more flags, not a fused payload:
  `bash -cx 'cd <outside> && …'` and `sh -co ignoreeof '…'` took their real
  payload from a later argv entry the guard never read.
- `( … )` is now scoped like a subshell: `(cd <outside>); git commit` is
  allowed again, while `(cd <outside> && git reset --hard)` still denies.
- `sudo -R <rootfs>`/`--chroot=` and a `PATH=`/`GIT_EXEC_PATH=` assignment
  make every path the daemon resolves meaningless, so they fail closed.
- `env --unset=NAME`, `-uNAME` and `--split-string=` in their attached forms
  no longer read as unrecognized options, which was denying decidable
  commands.
- Values assigned earlier in the same command are substituted before the
  dynamic-program check (`X=git; Y='-C <outside> …'; $X $Y`), `eval` carries
  its shell locals back out, and `export $NAME` fails closed.
- A command that relinks a path (`ln`, `mv`) invalidates containment proved
  afterwards: `ln -s <outside> bait && git -C bait reset --hard` is checked
  while `bait` is still the original directory.
- `resolvePhysicalPath` treated `\` as a separator on POSIX, where it is an
  ordinary filename character.
- `gpg.<format>.program` and `core.hooksPath` join the command-executing
  config keys.

* fix(serve): scope the relink invalidation to path-resolving Git runs

The rule I added a commit ago denied every Git run that followed an `ln` or
`mv` in the same command, which takes out `mv old new && git add -A` — as
ordinary as it gets. The invalidation only makes sense for an invocation that
resolves a path, so it now requires a relocation target or a shifted cwd:
`ln -s <outside> bait && git -C bait reset --hard` still denies, while
staging renamed files does not.

* fix(serve): rebuild the relink defense and close the round-4 gaps

Reproduced against the real guard first; the two environment claims were
measured with git 2.47.3.

Fixing my own two previous commits
- The relink invalidation was both too wide and too narrow. It now records
  which paths a run may have re-pointed instead of setting one flag: a
  relinked `.git` invalidates repository discovery for every later command
  (`ln -s <outside>/.git .git && git status` mutated the outside repo), while
  a relinked directory only affects a run that resolves that very path, so
  `mv old new && git add -A` stays allowed. The scan no longer keys on
  `run[0]`, which `env ln …`, `X=1 ln …` and `nice ln …` walked straight past,
  and `cp -s` joins `ln`/`mv`.
- Flagging redirect operands (the here-string fix) left `consumeShellWrapper`
  reading one as the `-c` payload: `sh -c > /dev/null 'git -C <outside> …'`
  dropped the real payload from analysis.
- Leaving a subshell rolled back only the tracked cwd, so exports and
  shell locals made inside `( … )` kept denying later commands.
- Shell-local reconstruction was last-assignment-wins, losing `X+=` appends.

Repository discovery
- A non-relocated Git command never reached discovery, so a planted `.git`
  gitfile at the session root redirected plain `git commit` outside. Discovery
  now runs for those too; a session bound below its repository is unaffected
  because the walk stops at the boundary, and a linked worktree still resolves
  back to its own checkout — both pinned.

Environment and parsing
- `GIT_OBJECT_DIRECTORY`, `GIT_ALTERNATE_OBJECT_DIRECTORIES`, `GIT_CONFIG*`
  and `SHELLOPTS` mark the invocation unresolved. Measured:
  `GIT_OBJECT_DIRECTORY=<outside>/.git/objects git add` writes the blob there,
  and `GIT_CONFIG_GLOBAL=<outside>/cfg` makes git read that config.
- `$'…'` is ANSI-C quoting where a backslash escapes, so `$'a\'b'` no longer
  leaves the substitution scanner a quote out of phase — which had hidden a
  following `$(git -C <outside> …)` from every analysis pass.

Over-denial
- A program with its own `-C` (`grep -C 5 git`, `tar -C dir`) no longer reads
  as a Git relocation.

* fix(serve): carry relink and shell state across nested scopes

Round-5 findings, all reproduced against the real guard. Four of the five
are in the machinery I added over the last two commits.

- Relink state was local to one `evaluateCommandWithCwd` call, so a symlink
  created inside `sh -c '…'`, `eval '…'` or a `$(…)` body was invisible to the
  parent, and a relink made in the parent was invisible to a nested Git run.
  It is now shared by reference in both directions.
- Nothing consulted it for an unrecognized or dynamic program word, so
  `… && nice git add -A` after a relinked `.git` was allowed, and
  `X=ln; $X -s <outside>/.git .git` recorded nothing at all. A dynamic program
  word may itself be `ln`, so its operands are recorded too.
- `<(…)` opens a paren that shell-quote reports without one, while its `)`
  still arrives — so `(cd <outside>; <(true); git reset --hard)` popped the
  subshell early and lost the `cd`. My round-4 triage called this one "not
  reproduced" because the probe used the top-level shape, which survives on
  the `Math.max(0, …)` clamp; the nested shape does not.
- `eval` ran with an empty shell-local map, so
  `GIT_DIR=<outside>/meta; eval 'export GIT_DIR'` promoted invisibly. Locals
  now flow into `eval` and into substitution subshells (by copy, since their
  own assignments die with them); a `sh -c` subprocess still gets none.
- Any unreadable word in a shell wrapper's argv can be the `-c` that carries
  the command, so `bash $A "$P"` is undecidable rather than absent.

* fix(daemon): evaluate the guard against the directory the tool runs in

A sub-agent pinned to a worktree — `working_dir`, or `isolation`, which sets
`ov.targetDir` on the child Config — executes at `config.getTargetDir()` while
still reporting the parent session id. The guard context carried only that
session id, so the daemon evaluated every such call against the parent
session's `effectiveCwd`: a plain `git commit` from an isolated sub-agent was
judged in-boundary and allowed while running somewhere else entirely, and a
relative `-C` resolved against a directory the command would never be in.

The invocation now carries the directory it will run in, from
`config.getTargetDir()` through the child guard to the daemon. It is
explicitly untrusted, so the daemon accepts it only where it can verify it
from state it owns: inside the session's effective working directory, or
inside the worktree tree that session owns — worktrees live under
`GitWorktreeService.getWorktreesDir(<session id>)`, and the session id is
already validated by `ownsSession`. Anywhere else the scope cannot be
established and the call fails closed.

When an owned worktree is accepted it becomes the boundary, so an isolated
sub-agent is contained to its own worktree instead of to its parent's
checkout — reaching back into the parent is now denied, which is the escape
this was reported for.

* fix(daemon): reach the real guard path and close the round-6 escapes

The headline finding is that the previous round's fix never reached the code
it was written for. `Session.runTool` — the path daemon ACP sessions actually
execute tools through — built the guard context without `sessionId` and
without `cwd`, so both the session fallback this PR added and the execution
directory added last round were unreachable there. Both are now supplied,
exactly as `CoreToolScheduler` does.

Escapes, each reproduced against the real guard first:
- `GIT_SSH_COMMAND`, `GIT_EDITOR`, `GIT_SEQUENCE_EDITOR`, `GIT_ASKPASS`,
  `GIT_PAGER`, `GIT_EXTERNAL_DIFF`, `GIT_SSH` are programs git executes, and
  `GIT_CONFIG_PARAMETERS`/`GIT_CONFIG_COUNT`/`GIT_CONFIG_KEY_<n>` are its
  environment config channel — none were modelled.
- `diff.external`, `core.gitProxy`, `interactive.diffFilter`,
  `credential.<url>.helper`, `remote.<name>.uploadpack`/`receivepack`/`proxy`,
  `tar.<format>.command`, `browser.<tool>.cmd`, `web.browser`,
  `help.browser`, `gc.recentObjectsHook` and `ssh.variant` join the
  command-executing config keys.
- An unrecognized wrapper laundered that config: `nice git -c alias.pwn='!…'`
  never reached the git analysis. It is checked there too now.
- `find <outside> -execdir git reset --hard` relocates through the program's
  own flag, leaving no marker.
- An archive decides where it writes, so `tar`/`unzip`/`cpio`/`rsync` make
  their extraction directory suspect rather than their operands.
- `alias g='git reset --hard'; cd <outside>; g` and the function-definition
  form both defer a body to wherever the bare word is later used.

Over-denials, all introduced by earlier rounds of this PR:
- `SHELLOPTS=errexit git status` (SHELLOPTS is bash's own options state — the
  rationale I gave for listing it was simply wrong, and it never reproduced
  as an escape), `env --ignore-environment`/`--null`/`--debug`,
  `curl -C - …`, `env -iS 'cmd'`, `d=<inside>; cd $d; git status`, and
  `set +a` turning allexport back off.

Also: the sub-agent worktree test created and recursively deleted a directory
under the user's global Qwen dir, keyed on a session id a real session could
own. It now uses a process-unique id and cleans up in `finally`.

* fix(daemon): contain a sub-agent to an in-project agent worktree

`AgentTool` with `isolation: 'worktree'` provisions under
`<projectRoot>/.qwen/worktrees/`, which is inside the session — so the
acceptance rule's "inside the effective working directory" branch left the
boundary alone and one sub-agent could still reach into a sibling's worktree,
the very thing this PR is named for.

A reported directory that is a checkout root in its own right now becomes the
boundary wherever it lives, not only under the session-owned worktree tree.
An ordinary subdirectory resolves to the session's own repository and changes
nothing, which is what keeps `cd packages/cli && git commit` working.

Also drops the unread `entryCwd` parameter from `evaluateUnrecognizedRun`,
whose signature implied containment behaviour that function never had, and
covers the child-side `invocationCwd` forwarding with a direct test — it is
the only link between a pinned sub-agent's real execution directory and the
daemon's check.

* test(acp): assert the session identity and cwd the guard now receives

Adding `sessionId`/`cwd` to the guard context in `Session.runTool` changed
the shape these two assertions pin, and I ran the guard, acpAgent and serve
suites but not this one — CI caught what I should have.

* fix(daemon): record ordinary targets a dynamic relinker re-points

The dynamic-program branch resolved the operands of an unreadable program
word but only used them to raise the `.git` flag, so an ordinary target was
never added to the relink set: `X=ln; $X -s <outside> src && git -C src reset
--hard` validated `src` against what it pointed at before the same command
replaced it.

Ordinary operands are now recorded alongside the `.git` case. Verified
load-bearing: dropping the new line fails the added regression test and
nothing else.

* fix(daemon): close the round-7 escapes, verified with the reported payloads

The reviewers were right that my previous round's "denies as written" replies
were built on non-equivalent counter-probes: a `.git` inside the path, or a
literal `-C`, tripped an unrelated marker rule while the reported mechanism
went untouched. Re-run with each payload verbatim — against a path with no
Git word in it — five of them were allowed. All are fixed, and the new tests
use those exact payloads.

- `$'…'` is ANSI-C quoting only OUTSIDE double quotes, so the substitution in
  `echo "$'$(GIT_DIR=<outside>/.git git reset --hard HEAD~1)'"` is live. Both
  scanners now gate the skip on `!single && !double`.
- The export attribute sticks to the name: after `export GIT_DIR`, a LATER
  assignment to it reaches the git subprocess. Name-only exports of a
  relocation key are recorded so those assignments count as exported.
- Both sides of a pipe run in subshells, so a pipe-side `cd` must not move
  the shell. Segments that are pipeline components restore the directory they
  started with; the top-level separators are read with the same quoting rules
  `splitCommands` uses, and any disagreement falls back to treating every
  segment of a piped command as a component.
- A bare digit before a *spaced* redirect is a real argv word, not a file
  descriptor, and nothing in the token stream distinguishes it — so it is
  marked ambiguous and a payload built from it fails closed instead of
  silently dropping the word.
- `-o`/`-O` before `c` in a short bundle does not cancel the `c`: bash still
  executes it, taking the command from a later argv entry. The bundle is now
  parsed for how many entries the value flags consume on either side.
- Rebuilding a command line out of separate argv words re-quotes anything
  that would otherwise split, so a path with a space stays one word and a
  `-C` value cannot shrink. `eval` keeps the verbatim join it needs, since it
  re-parses its argument as shell text.

* fix(daemon): repair the round-7 patch and bound what this guard promises

Two halves.

First, the round-7 patch introduced seven defects of its own, six of them
reproduced here before fixing:

- the fd digit of `2>…` was eligible as a `-c` payload, because
  `nextArgvIndex` skipped only redirect-flagged tokens;
- `o`/`O` letters after `c` in a bundle were counted by presence rather than
  per letter, shifting the extracted payload left;
- `env --split-string=` still rebuilt its payload with the verbatim join
  while both sibling branches had moved to the re-quoting one;
- the separator scan recorded no lone `&` and mistook `>|` for a pipe, and
  its disagreement fallback scoped nothing instead of everything;
- the export-attribute set neither crossed `eval` nor rolled back with a
  subshell;
- deferred alias and function bodies were keyed on `run[0]` rather than on
  the program word, so any prefix hid them.

Second, and more important than any single rule: the docs now bound what
this control claims. It is reliable against Git relocation written in the
literal forms the design doc lists — the mis-targeted command it exists for
— and best-effort, not a boundary, against shell text written to defeat it.

Seven rounds of adversarial review support that framing rather than
contradict it: each round closed the reported bypasses and the next found
more, several inside the rules the previous round added. The gap is
structural — the guard reads command text before a shell interprets it — so
the honest fix is to move the decision off the text, deciding where a command
may write when it runs rather than predicting it beforehand. That is a
separate change with its own design, and this one should not grow into it by
accretion. Saying so plainly is itself a safety property: an operator who
believes the daemon cannot reach a sibling worktree would grant it more trust
than the mechanism earns.

* fix(daemon): close the round-9 critical forms an agent may actually emit

Scoped to the Critical findings that reproduced against the real guard with
the reviewer's payloads verbatim (Git-word-free path). Common shell forms,
not adversarial exotica; the parser-edge tail stays under the bounded promise
this PR now documents.

- `&>`/`&>>` is a redirect operator, no longer read as a background `&`.
- `function NAME { … }` (the keyword form, `()` optional) is recognised as a
  definition.
- `git -c include.path=`/`includeIf.<cond>.path=` pull in a config file the
  guard cannot read; it can carry a `core.worktree` redirect or executable
  config, so it is treated as dangerous config and fails closed.
- `imap.tunnel`, `instaweb.httpd` join the command-executing config keys, and
  `GIT_DIFFTOOL_EXTCMD` the executed-env keys.
- `GIT_DIR=… set -a` persists (a prefix assignment on the special builtin
  `set`) and exports; that leading assignment is now carried, not dropped.
- alias/function recognition starts at the program word — past a leading
  redirect (`2>/dev/null alias …`), keyword (`if …; then alias …`) or
  assignment — and records every pair of a multi-alias statement.
- a heredoc body is stdin data, not commands: it is stripped before command
  splitting so a body `cd` cannot launder the tracked directory.
- a function body that `splitCommands` cuts across segments is now captured
  whole and replayed, so a `-C <outside>` inside it is seen, not just the
  name.

Two round-9 Criticals are deliberately not "fixed" here: `cd <outside> &
git …` runs git in the parent shell at the in-boundary cwd, so allowing it is
correct; and an archive that plants a `.git` for a later path-less discovery
is a TOCTOU (the unpack happens after the decision), left to the same
limitation as the symlink race rather than denying every `tar && git commit`.

* fix(daemon): replay an alias with the args its invocation appends

`alias gg='git'; gg -C <outside> reset --hard` ran `git -C <outside> reset
--hard`, but the guard replayed only the recorded body (`git`) and dropped
the appended argv, so the relocation was invisible and the command was
allowed. An alias now replays as `body + trailing args`, so the invocation's
own `-C <outside>` is seen. A function is unchanged: its args arrive through
`$@` inside the body, which the recorded body already carries.

Verified with in-boundary controls (`alias gg='git'; gg status`,
`alias gg='git commit'; gg -m x`) staying allowed.

* fix(daemon): carry a function/alias body's cwd and exports to the caller

A shell function and an alias both run in the current shell, so a `cd` or an
export inside the recorded body survives the call. The replay discarded
`nested.cwdAfter` and the exported state, so `f() { cd <outside>; }; f; git
reset --hard` kept the old in-boundary tracked cwd and the path-free git
mutation was judged inside while the real shell had moved outside. The nested
cwd, exports and shell-locals now propagate back, exactly as an `eval`
payload already does. Distinct from the earlier case where git appeared in
the body itself.

Verified: `f() { cd nested; }; f; git status` and `f() { echo hi; }; f; git
commit` stay allowed.

* fix(daemon): inherit the caller's allexport into a same-shell body

A body run in the current shell — `eval`, an alias, or a function — inherits
the enclosing `set -a`, so a plain `GIT_WORK_TREE=<outside>` assignment there
is exported to the following git. The nested evaluation initialized
`allExport` to false instead of the caller's value, so with allexport on the
assignment was treated as shell-local, no relocation was recorded, and the
path-free mutation was allowed. `allExport` now flows into the same-shell
scopes (and back out). An unexported assignment stays shell-local and is
still ignored.

* fix(daemon): complete the same-shell state model for bodies and substitutions

Three related gaps, all in the shell-state sharing this PR has been building:

- A command substitution inherits the enclosing `set -a` but did not carry
  it in, so `set -a; echo $(GIT_WORK_TREE=<outside>; git reset --hard)` was
  allowed. The substitution scope now inherits allexport (by copy — its own
  changes still die with the subshell).
- A same-shell body could turn allexport on but not off: the merge-back only
  handled the truthy result, so `set -a; f() { set +a; }; f;
  GIT_WORK_TREE=<outside>; git status` denied even though bash leaves the
  later assignment unexported. Both the function and eval merges now
  propagate the boolean in both directions.
- Recorded function/alias definitions were local to each evaluator, so a
  body could not see a function the caller had already defined:
  `inner() { cd <outside>; }; outer() { inner; }; outer; git reset --hard`
  ran `inner` as an opaque command and lost the cwd. The definition tables
  are now shared by reference with same-shell bodies (`eval`, function/alias
  replay) and copied for substitution subshells.

* fix(daemon): resolve shadowing and exported functions; isolate pipe subshells

Four related function-model findings, all reproduced first:

- A recorded function shadows the git program or a builtin, and bash resolves
  it before either — `git() { cd <outside>; command git status; }; git` and
  `cd() { command cd <outside>; }; cd nested; git reset --hard` were allowed
  because `analyzeRun` classified `git`/`cd` before the body lookup. Recorded
  bodies are now resolved before program/builtin dispatch, via a shared
  `invokeDefinedBody`; `command`/`builtin` name a different program word and
  bypass it as bash does.
- A function/alias redefinition in a pipeline component runs in a subshell and
  must not persist, but sharing `definedBodies` (previous commit) let it leak:
  `f() { cd <outside>; }; f() { :; } | cat; f` was modelled as a no-op. Pipe
  and background components no longer record a definition into the parent, and
  their cwd/allexport are already rolled back.
- `export -f f` makes a function visible inside a `bash -c` subprocess, unlike
  an ordinary function. Those names are tracked and the subprocess payload is
  seeded with only the exported subset; an unexported function stays invisible
  to `bash -c`.

* fix(daemon): close the interlocking gaps in my function-model work

Four gaps in the recorded-body machinery the last commits built, all
reproduced first:

- `invokeDefinedBody` did not carry `exportedFunctions` into the replayed
  body, so a `export -f`'d function invoked from another function's body was
  invisible to its `bash -c`.
- A prefix assignment on the invocation (`GIT_WORK_TREE=<outside> gg`) was
  dropped, because the defined-body gate skips `analyzeRun`; the run's leading
  assignments are now applied to the body as ambient relocations.
- The pipe-component rollback restored cwd/allexport/definitions but leaked
  the subshell's exports, export attributes and shell-locals into the parent;
  all of them now roll back.

* fix(daemon): deny relocations disguised by a redirection

Two reachable escapes with ordinary (non-adversarial) commands:

- `cd <outside> >&2; git reset --hard` — a stderr redirect on the `cd`, whose
  `&` was read as a background separator so the tracked cwd was rewound while
  the real shell had moved outside. `>&`/`<&` file-descriptor redirects are no
  longer treated as backgrounding.
- `git 2>/dev/null -C <outside> reset --hard` — the redirect operand among the
  git args ended `readGitInvocation`'s option parsing before the `-C`, so the
  relocation was invisible. It now skips redirect/fd-flagged tokens. Ordinary
  trailing redirects (`git status 2>/dev/null`) stay allowed.

* fix(daemon): deny relocation hidden by a leading redirect or a background &

Two more reachable escapes with ordinary commands, triaged out of the R8
batch (the rest of which is Windows paths, docs wording, test coverage or
adversarial parser edges under the documented best-effort promise):

- `2>/dev/null gg` where `gg` is a recorded alias/function ran the body in
  bash, but `readProgramWord` returned the fd token instead of the program
  word, so the invocation was not resolved. It now skips redirect/fd operands.
- `true & cd <outside>; git reset --hard` — only the segment a `&` follows is
  backgrounded (a subshell); the segment after it runs in the foreground, so
  its `cd` persists. The pipe-component test now treats a segment as a
  subshell only when it precedes `&`, while both sides of a `|` still are.

* fix(daemon): don't let a harmless or removed shadow mask a relocation

Two escapes where the guard replayed a recorded body while the real
interpreter ran a relocating external git, both reproduced first:

- `export -f` functions were seeded into every subprocess shell, but only
  bash imports them. `git() { :; }; export -f git; dash -c "git -C <outside>
  reset --hard"` was allowed because the guard replayed the harmless `:` for
  dash, while real dash resolves the external git and relocates. Exported
  functions are now seeded only for a bash child.
- `definedBodies`/`gitShapedNames`/`exportedFunctions` only ever gained
  entries, so a removed shadow still replayed. `unset -f`/`unalias` now drop
  the function/alias (and `-a` clears all), and `export -n -f` clears the
  export attribute — `git() { :; }; unset -f git; git -C <outside> reset
  --hard` and the `unalias git` form now deny, while a live compatible shadow
  (bash-imported function, an alias still in effect) stays modelled.

* fix(daemon): drop exported functions when env clears the child environment

Two follow-ups to the per-interpreter shadow modelling:

- The `unalias`/`unset -f` removal branch compared `removalProgram` against
  `'unalias'` after `isFunctions` had already narrowed it to `'unset'`, which
  `tsc --build` rejects as a no-overlap comparison (TS2367). `isFunctions`
  already covers every `unalias` case, so drop the redundant term.
- `env -i` / `-` / `--ignore-environment` start the child from an empty
  environment, so a bash `-c` payload no longer inherits the parent's
  `export -f` functions. The env wrapper now records that the environment was
  cleared and the bash payload stops importing exported functions when it was,
  so `git() { :; }; export -f git; env -i bash -c "git -C <outside> reset
  --hard"` denies while `env -i bash -c "... rev-parse HEAD"` and an
  un-cleared `env FOO=bar bash -c` stay allowed. Regressions added.

* test(daemon): pin the sh-wrapper fail-closed contract and document it

`sh` is bash on macOS and dash elsewhere, so its `export -f` import behaviour
cannot be decided from the basename. The guard already treats `sh` as
non-importing — it never replays an exported shadow for `sh -c`, because doing
so on a dash-backed `sh` would recreate the relocation escape. Pin that
fail-closed contract with a regression (`export -f git; sh -c "git -C
<outside> reset --hard"` denies) so a future change that widens the bash gate
to include `sh` breaks a test, and record the deliberate over-denial in the
design doc's non-goals.

* fix(daemon): model shell-definition removal the way the real shell does

The removal-builtin handling added earlier was too broad and dropped live
relocating shadows, and the exported-function set was shared into subprocess
scopes by reference. Each escape below was reproduced against the guard first.

- `unset` has no `-a` option and `unalias -a` clears only aliases, yet both
  were treated as "clear every definition", so `pwn(){ git -C <outside> reset
  --hard; }; unset -a; pwn` (and the `unalias -a` form) wiped the function and
  ran it unrecognized. Removal is now kind-aware: `unalias` touches only
  aliases, `unset -f`/bare `unset` only functions.
- A function shadowing `unset`/`unalias`/`export` runs instead of the builtin,
  so the removal never happens; the branch now fires only when the name is not
  itself a recorded shadow, and otherwise falls through to replay the shadow.
- The bash `-c` subprocess and command-substitution scopes received the
  parent's `exportedFunctions` set by reference (or, for `$( )`, not at all),
  so a child `unset -f` retracted the parent's export and a substitution saw
  none. Both now take a copy.

Adds regressions for each and keeps the existing shadow/removal cases green.

* fix(daemon): bare unset keeps the function and env -u strips exported functions

Two more escapes doudouOUC reproduced in the removal model, both verified
against the guard first.

- A bare `unset NAME` unsets a same-name variable first and removes the
  function only when none exists. This evaluator tracks no ordinary variables,
  so it cannot tell the two apart; treating every bare `unset NAME` as a
  function removal dropped a live relocating shadow
  (`pwn(){ git -C <outside> …; }; pwn=1; unset pwn; pwn`). Only `unset -f`
  now removes a function; a bare `unset` leaves it, the safe over-deny choice.
- A bash `export -f foo` travels as a `BASH_FUNC_foo%%` environment entry, so
  `env -u BASH_FUNC_foo%%` (and the `--unset=` / attached forms) strips it
  before `bash -c` and the child runs the real program. The env wrapper now
  records unset keys in PrefixState and the payload seeding drops functions
  whose `BASH_FUNC_*` entry was removed, so a stripped harmless `git` shadow no
  longer masks the real relocation.

Adds regressions for both; keeps `unset -f`, unrelated `env -u`, and live
shadows behaving as before.

* fix(daemon): fail closed when a removal builtin could retract a tracked shadow

Modelling exactly which definition an `unset`/`unalias`/`export -n` removes is
general shell semantics this guard does not attempt: a bare `unset NAME` drops
a same-name variable before the function, `enable -n unset` turns the builtin
into a no-op, a `command`/`builtin` prefix or a `( … )` subshell changes what
runs, and fused flag clusters (`-nf`) hide the mode. Every attempt to model
these precisely left a live relocating shadow reachable through a form it did
not cover.

Collapse the whole removal path to one rule: when a removal references a name
tracked as a shadow (a defined body, a git-shaped name, or an exported
function) — or clears all while any shadow exists — fail closed. This denies
the previously-allowed `git(){ :; }; unset git; git -C <outside> …`,
`export -nf`, `command unset -f`, `enable -n unset; unset -f`, and
`( unset -f git ); git` forms, while a removal of an untracked name and every
live-shadow replay behave exactly as before. Removes the earlier kind-aware
bookkeeping the same escapes kept slipping through.

* fix(daemon): skip leading redirections before the removal-builtin prefix scan

The `command`/`builtin` strip in the shadow-removal guard started at raw token
zero, but bash strips redirections from argv. A leading `2>/dev/null` before
`command unset -f <tracked-function>` left the scan looking at the redirect
operand, so `command` was never consumed, `readProgramWord` returned `command`
rather than `unset`, the removal went unrecorded, and the stale harmless
function masked the later external Git relocation.

Skip redirect/fd operands before and between the `command`/`builtin` prefixes,
the same normalization `readProgramWord` applies. Adds the leading-redirection
variant to the command-prefix regression.

* fix(daemon): replay a shadowed removal builtin and drop unset variables

Two more escapes doudouOUC reproduced in the fail-closed removal rule.

- The rule's early `continue` fired even when `unset`/`unalias`/`export` was
  itself a recorded function and the operands named only untracked state, so a
  shadowing `unset(){ git -C <outside> …; }; unset other` was classified as a
  harmless builtin removal and never reached the shadow dispatch that replays
  the relocating body. The branch now runs only when the program is not a
  shadowed function (a `command`/`builtin` prefix still forces the builtin).
- The removal never dropped tracked variables, so `A=nested; unset A; cd $A`
  kept expanding the stale in-bounds value while bash's `unset A` leaves `$A`
  empty and `cd $A` lands at $HOME. `unset NAME`/`unset -v NAME` now deletes
  the shell-local, turning the later `$A` into an unresolved reference the cd
  fails closed on. `unset -f` is functions-only and leaves variables intact.

Adds regressions for both.

* fix(daemon): honor shadowed command/builtin prefixes and PATH-based relocation

Two escapes surfaced by the round-11 review, both reproduced against the guard.

- The shadow-removal prefix scan trusted a literal `command`/`builtin` word to
  force the real builtin, but bash resolves a function of that name first. A
  `command(){ git -C <outside> …; }; command unset other` therefore
  early-continued as a harmless builtin removal and never replayed the
  relocating body. The prefix loop now stops when the prefix word is itself a
  recorded shadow, leaving it for the normal shadow dispatch.
- The unrecognized-program marker scans covered GIT_DIR/GIT_WORK_TREE-family
  assignments but not GIT_PROGRAM_ENV_KEYS (`PATH`/`GIT_EXEC_PATH`), which
  decide which git binary runs. The direct `PATH=/evil git …` was denied while
  `find … -exec sh -c 'PATH=/evil git …'` slipped through. Both marker scans
  now include those keys, and they remain gated on a co-present git word so an
  ordinary `PATH=… make` is unaffected.

Adds regressions for the shadowed prefixes and the wrapped PATH/GIT_EXEC_PATH
forms.

* fix(daemon): catch delimiter-glued relocations and redirect-decoy prefix drops

Two escapes surfaced by the round-12 review, both reproduced against the guard.

- The env-assignment arm of both text marker patterns required `(^|\s)` before
  the key, while the sibling `cd`/`pushd` arm already allowed `;&|(){}`
  boundaries. A relocation glued to a delimiter inside a quoted wrapper payload
  (`su -c 'true;GIT_DIR=<outside> git reset --hard'`) therefore evaded the
  unrecognized-program backstop. Both arms now share the same boundary class.
- `invokeDefinedBody` located the invoked name with a raw `findIndex` that
  also matched redirect operands, so a decoy `> g` whose target equals the
  function name truncated the prefix-assignment scan to empty and dropped the
  call's `GIT_DIR=` relocation. The lookup now skips redirect/fd operands like
  `readProgramWord` does.

Adds regressions for the delimiter-glued and redirect-decoy forms.

* fix(daemon): deny trailer/man/sendemail command-executing config keys

The dangerous-config model already denies `git -c <key>=<command>` for the
command-executing config families, but omitted three documented ones:
`trailer.<token>.command`, `man.<tool>.cmd`, and
`sendemail.(sendmailcmd|tocmd|cccmd)`. `git -c trailer.sign.command='…'
interpret-trailers` (and the man/sendemail forms) ran the configured shell
command while the guard allowed it. Adds the three patterns and regressions.

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-14 09:55:59 +00:00
Shaojin Wen
4ee6a087e5
feat(autofix): judge review-feedback validity by content, not author (#8996)
* feat(autofix): judge review-feedback validity by content, not author

Wrong feedback drives wrong rounds regardless of who wrote it: maintainers
increasingly draft comments with models, so author identity carries no
correctness signal. The trust gate stays as the injection/authorization
boundary it always was, but the validity layer becomes source-blind and
execution-based, enforced by the verification gate rather than prose.

Three mechanisms:

- Bite check: a round changing both source and tests has its changed tests
  re-run against the pre-round tree (origin/<branch> sources with the
  round's test files overlaid). All green there means the claimed defect
  never reproduced — the shape of a plausible-but-false finding implemented
  as a fix — and the round is rejected, non-retryable, with the measurement
  in LAST_REJECTION so the next round can decline or escalate the finding.
  Fails open on every scope limit: single-workspace rounds only (gitignored
  dist carries the round's build across the detach, the same confound that
  A/B-exempts typecheck), runnable unit tests only, and any pre-round
  failure counts as biting.

- Sensitive-area footprint: a round may not expand into CI/verification
  machinery the PR itself never touched — .github/, .husky/, eslint/vitest/
  tsconfig configs, and the scripts section of existing root or first-level
  workspace manifests (the gate's own command surface). Judged by area
  class so takeover on an infra PR keeps full freedom; round-added
  workspace manifests are exempt. Rejected retryably (the repair pass can
  revert).

- Test-deletion advisory: shrinking coverage is surfaced by a gate-authored
  section in the round report (deleted files, net test lines), never by the
  agent's own prose, so a maintainer reads the agent's justification next
  to the machine measurement.

SKILL.md rewrites the address-review protocol to match: identical
verification for every author, probe evidence outranks any assertion,
refuted maintainer claims are escalated with the measurement instead of
silently obeyed or overridden, and severity tags alone no longer make an
item Required — the claim must be checkable and reproduced.

* fix(autofix): harden the validity gates per review round

- Scan round/PR diffs NUL-delimited with --no-renames: a rename out of a
  sensitive area now classifies the vacated source path (moving a
  workflow out of .github/ is a removal of verification machinery), and
  specially named files are no longer core.quotePath-mangled past the
  case patterns.
- Narrow the capability classes: .github/workflows|actions, .github/
  scripts, and passive .github metadata are separate areas (an
  issue-template PR no longer licenses workflow rewrites), and the
  transitive executable surface — repo scripts/ (minus scripts/tests/)
  and .npmrc/.nvmrc — joins the protected set.
- Gate the bite consequence on machine-read intent: rejection now
  requires the round to RESOLVE a Critical-tagged or CHANGES_REQUESTED
  finding (resolved-comments.txt matched against rc.json/rv.json);
  every other src+test round gets a gate-authored advisory on all-green
  instead — a behavior-preserving refactor pinning existing behavior is
  no longer rejected.
- Drop the blanket *.md exclusion from bite source detection: skill
  markdown is executable agent behavior, and the intent gating now keeps
  doc-only rounds safe from rejection.
- Sanitize deleted-test filenames in the gate advisory through a safe
  character set: a backtick in a legal git filename could close the code
  span and forge gate-authored markdown.
- Replace per-path basename spawns with parameter expansion.
- Tests: rename-evasion, metadata-vs-workflow class split, repo-scripts
  class with the scripts/tests carve-out, filename-forgery rendering,
  enforce-vs-advisory bite consequences (Critical tag and CR review),
  and tree-state-proving runners that flip on pre-round source with the
  round's test overlaid (plus the round-leak negative control).

One reviewed finding is declined with evidence in the thread: existential
batch semantics for mixed Critical rounds (per-behavior probe binding
needs test-result parsing; documented as a known limit at the check).

* fix(autofix): close the round-2 validity-gate findings

Sensitive-area scan: read NUL records directly (no tr re-mangling — a
newline filename cannot mint phantom footprint grants); resolve declared
workspace manifests and workspace-root configs through the trusted
resolver (nested workspaces protected, src-tree scaffolds exempt); split
root vs workspace manifest classes; guard the root workspaces array; give
the loop's own workflow and gate script their own class; classify .qwen/
(skills are executable agent behavior); anchor footprint content compares
at the merge base; sanitize violation paths in the rejection document.

Bite check: tolerate rc:-prefixed and CRLF resolved-comment ids (the
handle format SKILL prescribes — enforcement never fired without this);
count replies resolved in Critical-rooted threads as defect claims; skip
non-vitest workspaces (a vacuous --if-present pass must never reject),
self-package-name imports (dist confound), and rounds with paths outside
the resolved workspace; include renamed tests and changed snapshots in
the overlay; drop nested fences from the rejection document; surface
test-only defect claims as an advisory; document the already-fixed
re-raise limit and steer it to a no-code round.

Tests: classifier probe over every arm, footprint cases for the new
classes, enforce-vs-advisory negatives, reply-root enforcement, and the
rc:/CRLF handle round-trip.

* fix(autofix): close the round-3 Critical findings on the validity gates

- Gate-consumed helper scripts (resolve-owning-packages, settings-schema
  and contracts checks) join the autofix-loop class: an unrelated
  .github/scripts footprint no longer licenses rewriting machinery the
  gate executes.
- Skip round-scan files whose content equals current origin/main: a
  round that merges main (the flow SKILL prescribes on conflicts) made
  ROUND_RANGE degenerate and attributed all incoming main churn to the
  round, false-rejecting ordinary base updates.
- Round-added workspace-root configs are the round's own surface (same
  cat-file exemption manifests have); deleted workspace manifests are
  classified from pre-round existence instead of the on-disk resolver
  that can no longer see them.
- The bite vitest guard reads the PRE-ROUND manifest — the tree whose
  test script the detached runner actually executes.

* fix(autofix): close R4 validity-gate findings — gate-consumed surfaces join the taxonomy

- Supply-chain surfaces classify: lockfiles/shrinkwraps (root and nested)
  and patches/ (patch-package runs on every install) as supply-chain;
  .gitattributes (root and nested) as measurement-config — a -diff rule
  could blind numstat-based advisories.
- manifest_scripts_changed inspects resolution fields too: workspace
  manifests compare {scripts, exports, main, types}; the root manifest
  adds exports alongside workspaces.
- resolve-sandbox-image.mjs joins the autofix-loop class (it establishes
  the loop's isolation boundary).
- The noop path emits verified_head, making the prescribed no-code
  re-verification round mechanically able to resolve threads.
- The bite transcript is cleaned at gate start like its sibling logs;
  the advisory's test definition aligns with the growth brake's six
  globs (__tests__/, test-utils/ included).

R4-3 (post-round on-disk workspace resolution racing a same-round
workspaces negation) is declined in-thread: it requires the PR footprint
to already license manifest-scripts-root, which is the accountability
boundary working as designed; pre-round-tree resolution is queued with
the census follow-up. R4-5 (advisory in failure paths) queued likewise.

* fix(autofix): deflake the bite harness and align the test taxonomy

- Isolate fixture git from ambient global/system config (the sibling A/B
  fixture's GIT_CONFIG_GLOBAL=/dev/null pattern) and fail loudly on spawn
  errors with the exit status in the assertion message — the advisory
  sub-case intermittently died spawn-level under load with empty streams
  and no diagnostic (reproduced 1/6 locally, once on CI).
- BITE_SRC excludes __tests__/ like the gate's own TEST_PATHSPEC.
- SKILL's boundary enumeration names the supply-chain and
  measurement-config classes and the full protected manifest fields.

* fix(autofix): close R6 validity-gate findings

- Test-side defect claims take the advisory arm: when every resolved
  Critical thread sits on a test file (rc.json .path), the fixed test
  legitimately passes pre-round — enforcement grade 'advisory', never a
  rejection; the test-only advisory also no longer requires a matching
  *.test.* glob (snapshot-/helper-only resolutions surface too).
- Classifier arms: newline-bearing paths fail CLOSED as their own class;
  qwen-pr-safety-precheck.yml + pr-safety-precheck.mjs join autofix-loop;
  nested .npmrc/.nvmrc; eslint.legacy-filenames.mjs (imported by the lint
  leg's config); root manifest filter carries main/types.
- The self-import dist-confound guard matches the package name delimited
  (quote or subpath), so @qwen-code/qwen-code no longer swallows its
  -core sibling's imports.
- Test isolation extends to the footprint and advisory spawns (R5's
  rationale applied everywhere), spawn errors fail loudly there too, the
  classifier probe pins the supply-chain/measurement-config arms, the
  coverageOnly fixture asserts the advisory text, and the neutralization
  ledger header matches its count.
- The resolve-threads design doc records the widened no-op
  verified_head rule and its safety argument.

Deferred to the backlog per the convergence note: the bite-side restore
crash-contract test (shared-fixture work), origin/main-advanced footprint
fixtures, and advisory append-order pins.

* fix(autofix): close R7 validity-gate findings

- The resolve/reply pass is a shared function serving BOTH the pushed and
  no-op outcomes: the no-code re-verification escape can now actually
  resolve threads, and no-op declines finally post their in-thread
  replies (a pre-existing silence gap). The design doc states the shared
  path, its guards, and the named first-round residual.
- TESTSIDE demotion votes only over resolved CRITICAL threads (a source
  Suggestion resolved alongside no longer breaks it; a source Critical
  alongside keeps full enforcement) — three fixtures pin the matrix.
- The shrinkage advisory measures with --no-renames (a rename out of
  runner discovery is a shrink) and NUL-safe deleted names.
- Bite inputs pass through the merge-freight filter the class scan
  already applies, and BITE_SRC collects NUL-safe.
- Demoted rounds get their own advisory text (all-green is their
  expected shape, not a failed reproduction).
- The manifest block comment matches the resolver-backed code; the
  footprint and advisory test spawns get the isolation and loud
  spawn-error handling previously claimed — the R6 reply overstated
  that fix and this commit is the correction.

* fix(autofix): close the review-body re-checks on the validity gates

- Deleted-manifest classification honors the fixture exemption from the
  PRE-ROUND root manifest's workspaces globs (was_workspace_dir) — a
  deleted src-tree fixture manifest is no longer false-rejected, while a
  deleted declared workspace still classifies; the PR-footprint scan
  gets the same treatment anchored at the merge base, so a PR-deleted
  workspace keeps licensing later rounds.
- A config added into a PRE-EXISTING workspace is machinery (the gate's
  legs execute it); only a config born with its round-added workspace
  keeps the exemption.
- The shrinkage advisory applies the merge-freight skip per file (NUL
  numstat records), so a base-merging round is not charged main-side
  test churn in trusted-voice text.
- The bite rejection document renders filenames through the safe
  charset and collapses backtick runs in the runner tail below the
  outer fence length.
- AGENTS.md/CLAUDE.md classify as agent-policy; the root-manifest
  comparator covers lint-staged and config (sandboxImageUri) too.

Still standing by recorded design, acknowledged in the review body:
R1-9 (already-fixed re-raise), R1-27 (existential batch semantics),
R4-5 (post-round resolver vs same-round workspaces negation).

* fix(autofix): close the round-9 validity-gate re-checks

- TESTSIDE's critical() carries the CHANGES_REQUESTED review-state arm
  and receives rv.json, mirroring BITE_ENFORCE — a CR-enforced test-side
  claim demotes to the advisory arm, and a CR-enforced source claim can
  no longer collapse into it (R8-1, both directions).
- was_workspace_dir matches workspaces globs PATH-AWARE ('*' stops at
  '/', '**' spans, '?' single, '!' entries skipped conservatively): a
  nested src-tree fixture manifest deletion no longer false-rejects
  while a declared workspace deletion still classifies (R9-1); both
  pinned by fixtures.
- The PR-footprint manifest arm answers aliveness and membership from
  refs (origin/<branch> / merge base), never the round's on-disk tree —
  a PR-added workspace a round later deletes keeps its footprint class
  instead of walling the deletion (R9-3).

---------

Co-authored-by: verify <verify@local>
2026-08-14 09:54:51 +00:00
callmeYe
c0a2fee4f7
fix(serve): allow preinstall skill batch toggles (#9139)
Treat names absent from the installed Skill snapshot as valid batch targets so their workspace state can be declared before installation. Preserve the existing persistence semantics: an undeclared enable is a no-op, an existing workspace disable is removed, and disable writes the declaration.
2026-08-14 09:04:09 +00:00
Changxiao Ruan
0e0f35ec29
fix(core): honor Shell truncation threshold setting (#9014)
* fix(core): honor Shell truncation threshold setting

Preserve Shell's 30k default only when the global threshold is absent, and use an explicit value for both producer and scheduler truncation.

* fix(core): handle null Shell truncation thresholds

* fix(core): bound Shell previews by configured threshold

* docs(core): clarify Shell threshold explicitness
2026-08-14 08:28:03 +00:00
ytahdn
c5bf222474
fix(webui): revert #8882's transactional session switching to the loading-skeleton model (#9129)
The transactional cross-session switching from #8882 staged a handoff and
kept the old attachment live until the target load committed. It added a
large transition state machine (intent staging, same-session capture,
watchdog deadlines, controlled rebind) across the daemon session layer
and the web-shell provider, and left the UI pinned to the previous
session while a switch prepared.

Restore the loading-skeleton model: switching a session clears the
transcript, shows the loading skeleton, and waits for the load result.

- Remove sessionTransition state, onSessionTransitionCommit and the
  transactional target logic from WorkspaceSessionProvider.
- Strip the transition state machine from DaemonSessionProvider and
  restore single-session restores: restore_in_progress retries stay
  bounded by the existing watchdog, and the skeleton UI keys on
  loadingTranscript.
- Move useDaemonSessionOwnerGuard back under the daemon index export.
- Delete the transactional design docs and both daemon integration
  tests; the restored behavior is covered by unit tests.
- Drop the dead desiredSessionTargetPending prop (write gating now keys
  on loadingTranscript alone) and stop a failed switch's target
  workspace from leaking into the next workspace-less load.

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-14 08:00:25 +00:00
ytahdn
30134200a9
fix(web-shell): surface loop detection turn errors (#8853)
* fix(web-shell): surface loop detection turn errors

* fix(cli): expect loop-detected turn error in invalid-params stop test (#8853)

* fix(cli): reject loop-detected stops only for foreground ACP turns

Stop-hook continuations are shared with cron and background-notification
turns, which must keep their pre-loop-error graceful end-turn handling;
only the foreground prompt chain now rejects a loop-detected stop.

Also folds in review feedback: drop the unreachable repeated-failure
branch from the continuation, extract the cancellation-precedence
helper, defer and freshness-guard the bounded-refresh turn-error replay,
skip the phantom forward-failed compensation for structured turn errors,
key the structured code gate on structuredness, share the retryability
predicate in Web Shell, and keep the turn_complete error signal alive
for loop-detected turns.

* fix(acp-bridge): harden loop-detected turn errors per review feedback

Address round-3 review findings:

- The bounded-refresh append guard no longer compares the in-memory
  turn_error against the last journaled event of any kind. The published
  event is stored on the entry at broadcast time; any newer turn terminal
  clears it, and only turn-content events journaled after it block the
  append (queue/config bookkeeping no longer hides the error on refresh).
  The staleness check reads the in-flight journal via a new
  liveJournalSnapshot() accessor instead of flattening the replay window.
- Channel turns keep the graceful end-turn handling like cron and
  background-notification turns, so their collected text is still
  delivered when loop protection stops them.
- Loop-detected rejections drain the cron/notification queues in
  prompt()'s finally, preserving the pre-rejection drain invariant for
  queued automatic work.
- Tests pin each behavior: queued-bookkeeping refresh append, stale-error
  no-append, prompt_cancelled absence, foreground stop-hook rejection,
  background-notification graceful default, and the user-facing message
  constant.

* fix(cli): keep channel-prompt turns graceful on loop-detected stops

Channel tasks prompted through CHANNEL_PROMPT_META_KEY (DaemonChannelBridge /
AcpBridge) carry no channelDelivery meta, so the loop-protection exemption
keyed on channelDelivery missed them: a per-turn tool-call cap trip rejected
the turn with LOOP_DETECTED, the channel bridge never emitted promptComplete,
and the collected response text was lost as a failed task. Extend the
graceful end-turn exemption to channel-prompt-meta turns, mirroring the
repeated-failure guard forcing those turns already receive.

* fix(acp-bridge): keep loop turn error on refresh after idle bookkeeping (#8853)

* fix(acp-bridge): harden loop-protection turn state per review feedback (#8853)

* fix(acp): gate channel-turn classification and harden loop-error refresh replay (#8853)

Close two trust-gate gaps in the channel-turn classification introduced
for loop-detected turn errors: the `qwen.daemon.channelDelivery` sibling
key was not gated like `qwen.channel.prompt` at the standalone ACP
boundary, and on the daemon-hosted path a client-forged
`qwen.channel.prompt` survived the serve route and bridge admission strip
to be re-injected for trusted parents. Both keys are now stripped from
untrusted callers and honored only from trusted context (private parent
state, or the channel-worker prompt authorization validated by the daemon
prompt route).

Also harden the refresh-append guard for pending turn errors: the
synthetic `history_truncated` journal marker and idle latest-wins
`session_update` snapshots no longer defeat the append, and a queued
terminal that folds newer turn content supersedes the stale error before
the fold erases the evidence. Web Shell now derives turn_complete's error
from the same backward walk as the retry decision.

* fix(acp): close loop-protection bypasses in delivery classification and retry re-arm (#8853)

* fix(acp): cover idle workspace fan-out events in the refresh-append allowlist (#8853)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-14 06:14:06 +00:00
jinye
53a7f2fd1b
feat(daemon): track background shells in activeWork (#9042)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-14 03:25:50 +00:00
jinye
d670bb8109
feat(telemetry): Trace main agent invocations (#9107)
Some checks failed
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
SDK Python / Classify PR (push) Has been cancelled
SDK Python / SDK Python (3.10) (push) Has been cancelled
SDK Python / SDK Python (3.11) (push) Has been cancelled
SDK Python / SDK Python (3.12) (push) Has been cancelled
* feat(telemetry): Trace main agent invocations

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#9107)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-14 02:50:22 +00:00
jinye
c9cb53398d
refactor(cli): Generalize the Conversations runtime foundation (#8890)
* docs: Design standalone daemon sessions

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: Align standalone session implementation stages

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* refactor(cli): Generalize the Conversations runtime foundation

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: Clarify Conversations runtime lazy startup

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): Cover Conversations lifecycle guards

Clarify standalone transaction recovery outcomes and remove the unused Conversations-only Live Host workflow trigger.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#8890)

Harden owned runtime publication and complete the standalone transaction safety contract.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#8890)

Keep the dedicated Live Host workflow aligned with the generalized Conversations runtime path.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): Keep owned runtime validation unpublished

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): Address Conversations review coverage

Clarify standalone lifecycle failure and compatibility contracts, and pin the Conversations runtime publication and ownership invariants identified in review.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#8890)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#8890)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#8890)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-14 02:33:24 +00:00
ytahdn
0493b4a2e6
fix(daemon): reduce subagent live replay journal (#9057)
* fix(daemon): reduce subagent live replay journal

* fix(acp-bridge): let summary loads share an in-flight full restore

The restore-coalesce fence compared liveReplayMode strictly, so a WebUI
summary load racing any full-mode load of the same session (keepalive
revive, acp-http clients, older WebUI tabs) threw restore_in_progress
instead of coalescing. The summary journal is a strict subset of the
full journal, so a summary waiter can safely share an in-flight full
restore and project the superset down. Only the reverse — a full
request joining a summary restore — would return a missing-detail
projection, so that direction stays fenced.

* test(acp-bridge): add regression fences for live-replay review findings (#9057)

* fix(acp-bridge): guard coalesced waiter recompute against channel death (#9057)

Review feedback addressed:

- Critical R3-7: the coalesced waiter's replay-field recompute awaited
  refreshedReplayFieldsFor after registerClient/recordAttachRef with no
  post-await re-assert or rollback. refreshedReplayFieldsFor swallows
  every fetch error into the in-memory fallback, so a channel death
  during the transcript-page await attached the waiter to a session the
  daemon had already torn down. Move the recompute ahead of
  registration — the existing-entry attach path's shape — and re-assert
  after the await, rolling back the pre-folded coalesce reservation on
  rejection. Add a regression test that parks the waiter on the
  transcript fetch, crashes the channel, and expects
  SessionNotFoundError (flip-checked: without the fix the waiter
  resolves attached:true).
- R3-2: drop the false "summary journal is a strict subset of the full
  journal" rationale from the coalesce fence comment, the
  summary-onto-full coalesce test comment, and the design doc. Under
  cap pressure the two journals evict independently and can diverge, so
  coalescing is safe solely because the waiter recomputes its own mode's
  replay fields from the registered entry.

---------

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: qwen-code-autofix[bot] <qwen-code-autofix[bot]@users.noreply.github.com>
2026-08-14 02:13:15 +00:00
易良
85a06bbec8
fix(desktop): bridge Electron users on Windows and Linux (#9079)
* fix(desktop): bridge Electron users on Windows and Linux

* test(desktop): satisfy bridge contract lint

* fix(desktop): harden Electron bridge migration

* chore(desktop): note sibling-script regex sync for bridge artifacts

* fix(desktop): satisfy release manifest lint

* fix(desktop): validate Electron uninstaller path before migration

The migration ExecWait target is assembled from the HKCU InstallLocation
registry value; require the uninstaller executable to exist before running
it, and pin the conjunct in the release contract suite.
2026-08-13 15:42:31 +00:00
jinye
c0e649b53c
perf(serve): Restore large sessions selectively (#9055)
* feat(core): Add selective session restore projection

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* feat(serve): Use selective session restore

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): Address selective restore feedback (#9055)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): Keep Goal correction best effort

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): Align selective restore lease gating

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-13 14:34:03 +00:00
Shaojin Wen
97ec96ec54
feat(cli): Add review settings for attribution, default effort, and default comment (#8994)
* feat(cli): Add review settings for attribution, default effort, and default comment

* fix(cli): resolve review settings from operator scopes and close gate gaps (#8994)

Address review feedback on the review settings:

- Resolve review.attribution/effort/comment from operator-controlled
  settings scopes only (system defaults, user, system); a repository's
  .qwen/settings.json is content under review and must not control
  whether findings publish, whether the review names its model, or how
  deeply the pipeline verifies.
- Normalize the configured review.effort through the same case-
  insensitive validation as the --effort flag, so "Low" cannot miss the
  exact comparisons the forcings run and invalid values cannot leak into
  the verdict.
- Gate the modelId requirement and footer-safety validation on
  attribution: with the footer gated off, the field has no consumer and
  must not refuse the run.
- Pass the standing review.comment setting into publish-assets' call of
  the shared authorisation gate, so both callers agree on what
  authorises a run.
- Make presubmit's self-comment detection footer-independent by also
  matching the reviewing account's own top-level comments, so
  attribution-off posts still dedup.
- Align SKILL.md's Step 7 gate and every --comment branch on
  comment.effective, and add handler-level wiring tests for all
  configured defaults.

* test(cli): pin the review-settings operator defaults with unit tests (#8994)

* fix(cli): share the guarded footer strip and pin the gate audit text (#8994)

* fix(cli): raise the repository-context array bound to 256 (#8994)

* fix(cli): validate review setting values and tighten the review gates (#8994)

* fix(cli): align presubmit dedup with severityOf and normalize auto effort (#8994)

* fix(cli): show the review settings in the settings dialog (#8994)

* fix(cli): bound the footer strip tail and match refusal advice to its class (#8994)

---------

Co-authored-by: qwen-code-autofix[bot] <qwen-code-autofix[bot]@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
2026-08-13 13:27:39 +00:00
jinye
344ce4d1af
fix(core): Preserve OTel session ownership in daemons (#9077)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-13 09:53:18 +00:00
ytahdn
a8bcaefea7
feat(web-shell): support workspace file uploads (#8874)
* feat: add web shell workspace file uploads

* fix(serve): accept plain string targets in shared atomic publisher (#8874)

* fix(review): bound web shell related paths

* fix(review): address web shell file upload review findings (#8874)

* test(serve): include upload capability in baseline

* fix(review): pin workspace_file_upload in the serve capabilities integration baseline (#8874)

* fix(review): address round-2 web shell file upload review findings (#8874)

* fix(review): address round-3 web shell file upload review findings (#8874)

* fix(review): address round-4 web shell file upload review findings (#8874)

* fix(review): address round-5 web shell file upload review findings (#8874)

* fix(review): address remaining file upload findings (#8874)

* fix(review): address round-6 web shell file upload review findings (#8874)

---------

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-13 06:42:07 +00:00
callmeYe
d912f4c6a8
feat(web-shell): configure Qwen 3.8 reasoning (#8974)
* feat(web-shell): configure Qwen 3.8 reasoning

* test(serve): update telemetry route audit counts

* fix(webui): serialize reasoning mutations with refresh

* refactor(sdk): reuse JSON request for config options

* fix(webui): discard stale session config snapshots

* fix(cli): hide controls for mandatory thinking

* test(cli): assert generic reasoning options
2026-08-13 05:01:03 +00:00
jinye
abfd443698
docs(design): Define selective session restore (#8743)
* docs(design): plan selective session restore

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(design): harden selective session restore plan

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(design): Refine selective session restore plan

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(design): Finalize selective restore lifecycle

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(design): Tighten selective restore lifecycle

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(design): finalize selective restore contracts

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(design): align selective restore prerequisites

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(design): narrow selective restore lifecycle

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(design): refine selective restore execution

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(plan): verify resumed channel updates

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(design): update selective restore prerequisites

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(design): allow stacked selective restore development

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: update selective restore prerequisites

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: tighten selective restore review gates

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: close selective restore evidence gaps

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: preserve goal evidence failure semantics

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: mark restore shape prerequisite merged

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-13 03:11:44 +00:00
Zqc
4a281f2efc
feat(cli): report retained tool-result stats in /doctor memory (#8875)
* feat(cli): report retained tool-result stats in /doctor memory

Add a tool-result retention section to /doctor memory (and --json) covering
the phase-1 diagnostics from #4184: retained tool-result count/total/largest,
oversized flagging against the 30k widest legal per-tool budget, plus
duplication signals for UI history and compression input. Sizes and counts
only (never content); history is scanned by reference so the diagnostic adds
no memory pressure.

* docs(design): document tool-output offload/preview state transitions and privacy model

Satisfies acceptance criterion 2 of #4184: state transitions of the layered
truncation/offload mitigation (implemented in #4880) and its privacy model,
plus the tmux E2E report for the /doctor memory retention diagnostics.

* fix(cli): align retention diagnostics with per-tool budgets and raw-char sizes

Address review round 1: measure retained tool results with the compression
pipeline's estimatePartChars (raw chars for string outputs, image estimate
for nested media) instead of JSON.stringify, fixing false oversized flags on
newline-dense compliant outputs; compare each result against its own tool's
declared budget resolved from the tool registry instead of a fixed 30k
threshold, so compliant high-budget results (e.g. MCP) are never flagged;
scope the UI-history duplication scan to tool_group result displays so model
text is excluded and rendered tool outputs are actually detected; omit
toolResultRetention from --json when unavailable; log retention collection
failures via debug logger; fix the design note's IO-error persistence bullet;
re-verify the E2E report with the new measurement basis.

* fix(cli): calibrate oversized detection against truncation layers

- Canonicalize tool names before registry lookup (legacy aliases resolve)
- Skip sentinel-prefixed results and apply combined-pass 2x tolerance
- Fall back to configured global threshold for tools declaring no budget
- Compare UI history displays against per-tool budgets
- Document persistence gate in design note; re-verify e2e scenario 3

* fix(core): share tolerance constant and align imageTokenEstimate with compression pipeline

Review (round 4) fixes:

- R4-3: Extract COMBINED_PASS_TOLERANCE_FACTOR into truncation.ts and
  share it between the scheduler's combined pass and the retention
  diagnostics so both use the same tolerance factor.
- R4-4: Add imageTokenEstimate option to analyzeToolResultRetention
  (defaults to DEFAULT_IMAGE_TOKEN_ESTIMATE); doctorCommand now resolves
  it via resolveSlimmingConfig (env > settings > default), the same
  source the compression pipeline uses. Export resolveSlimmingConfig from
  the core barrel.
- R4-5/R4-6: Add tests for configurable imageTokenEstimate and Infinity
  threshold guard.
- R4-7: Guard oversizedThresholdChars against Infinity — when truncation
  is disabled (threshold <= 0 → Infinity), report 0 instead of Infinity
  so JSON.stringify does not drop the key to null.
- R3-3: Fix UI history budget resolution — IndividualToolCallDisplay.name
  stores the tool's displayName (e.g. 'Shell'), not the registry key (e.g.
  'shell'), so getTool(displayName) returned undefined and budgets fell
  back to the global threshold. Build a displayName → maxOutputChars map
  from getAllTools() at scan time.
- R4-12: Declare string-only scope for UI history scanning in code
  comment and design doc §5 — structured display objects (file diffs,
  ANSI captures, agent result summaries) are out of scope for phase 1.
- 3750020138: Update design doc §5 to reflect resolveSlimmingConfig
  alignment and display-name map.

* fix(doctor): align UI scan to 2x tolerance, fix threshold/error/sentinel gaps

R5-1: threshold uses config.getTruncateToolOutputThreshold() first so
disabled (Infinity) doesn't fall to 0
R5-3: UI scan uses budget * COMBINED_PASS_TOLERANCE_FACTOR to match
API-history 2x tolerance
R5-7: oversized check uses rawChars (output.length) instead of
estimatePartChars (which adds 64 wrapper floor)
R5-8: read output ?? error and check <persisted-output> sentinel
R5-12: UI lookup consults ToolDisplayNamesMigration for legacy names

Tests: 3 core + 1 CLI updated for rawChars/2x; 3 core + 2 CLI added
Doc: test counts (core 14→19, CLI 9→11), scenario 4 JSON, 2x wording

* docs: fix design note accuracy (R5-9/R5-15/R5-10)

R5-9: clarify shell/MCP truncate in-tool before gate; split recovery paths
R5-15: note sentinel skip in combined-pass re-check; fix mermaid routing
R5-10: describe two-stage persist failure (fallback saves full payload)

* fix(doctor): add envelope slack for token-aware truncation fallback

R6 review: truncateAndSaveToFile's token-aware fallback returns the
original content (sentinel-less) when the wrapped form would not be
smaller, so the diagnostic's oversized check must tolerate that band.

- Export TRUNCATION_FALLBACK_ENVELOPE_SLACK=500 from truncation.ts
- Apply slack to the rawChars > 2x budget comparison
- Adjust 4 test fixtures broken by the new threshold boundary
- Fix 7 factual errors in design/E2E docs (mermaid, project-hash,
  web-search 102k, re-entrancy guard, shell/MCP bullet)
- Add JSDoc caveat for MCP-disconnect limitation (R6-16)

* docs: align §5 sentinel description with diagnostic code

Bot R6-10 re-report: design doc §5 claimed three markers are skipped
(prefix, in-body marker, <persisted-output> stub), but the diagnostic
only checks two (prefix and <persisted-output> stub). Aligned the doc
to match the code. The re-entrancy guard bullet still describes the
scheduler's three-marker isAlreadyTruncated check, which is accurate.

---------

Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-12 19:02:22 +00:00
Dragon
011303239e
fix(cli): improve slash command history feedback (#8365)
* fix(cli): improve slash command history feedback

* fix(cli): centralize auxiliary model guard

* test(cli): cover model picker left key

* fix(cli): persist slash command invocation hiding across resume

* fix: keep model picker feedback consistent

* docs(cli): align design doc with /stats root-level hiding

* fix(cli): preserve model picker history safely

* fix: preserve hidden command feedback on resume

* fix(desktop): match slash results to invocations

* fix: keep slash command feedback paired with invocations

- Record message-type command results through the recording wrapper so
  rejections and errors land in the result record and replay on resume
- Move the NO_COLOR /theme rejection into the command action so the result
  record is written after the invocation, and keep that invocation visible
  because the command prints feedback instead of opening the dialog
- Skip the /auth result record when the dialog auto-opened without an
  /auth invocation to pair with
- Guard the model picker against re-entering a selection while a switch
  is already in flight
- Walk only uuid -> parentUuid links when matching desktop slash results
  to invocations instead of retaining every parsed transcript record

* fix(desktop): bound slash result lookup to user turn

* test(cli): cover hidden command result replay on resume

* fix(cli): guard dialog result recording

* fix(cli,desktop): tighten slash-command feedback recording

- Pin the revealed invocation as the first history item in the
  argument-validation reveal test, matching the processor's ordering.
- Derive the /model picker-flag pattern from a single shared flag list.
- Pair each transcript invocation with at most one result during desktop
  reconstruction, so a later same-name orphan result (e.g. an auto-fired
  recap) cannot re-emit an already-paired invocation's user row.
- Retarget the textElements guard at the synthesized slash user row.

* fix(cli): guard partially persisted model switches

* test(cli): cover isPickerOnlyModelInvocation regex boundaries directly

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Autofix <autofix@qwen-code.dev>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-12 17:15:01 +00:00
Edenman
01ef9bc8b5
feat(web-shell): expose channel sessions in sidebar and settings (#8457)
* feat(web-shell): show channel sessions in sidebar

* feat(web-shell): configure channel session scope

* fix(web-shell): refresh channel session transcripts

* feat(web-shell): group channel sessions by platform

* fix(channels): address session review feedback

* test(web-shell): harden channel session coverage

* test: pin remaining channel session review findings

Close the round-1 review gaps the previous fixes left unpinned: the
channel-base/acp-bridge display-text wire key now has a cross-package
equality assertion, the secondary-workspace catalog test covers pinned
channel sessions, the collect-coalescing test pins the raw display text
when buffered messages carry model-only metadata, the editor descriptor
labels diverge from the i18n values so missing keys surface, and the
channels e2e asserts the legacy fixture is absent from the Tasks tab.

* fix: harden channel session sidebar and display projections (#8457)

Round-2 review fixes:

- Guard the sidebar channel-instance lookup with Object.hasOwn so a
  sourceId like 'constructor' cannot crash the Channels view through
  Object.prototype (R2-1).
- Drop the {text:''} part synthesis for empty displayText replays so
  image-only channel prompts stop emitting a spurious
  unknown_record_or_part diagnostic and complete:false (R2-2).
- Route the webhook display projection through the same
  truncate+sanitize treatment as the model prompt via a shared helper
  (R2-3).
- Refresh the per-workspace channel catalog on the session poll tick
  and render the channel list flat — never under organization groups —
  while the catalog is unavailable (R2-4).
- Remove the dead prompt re-truncation in sessionService (R2-5).
- Extract getChannelPromptDisplayText as the single source of truth for
  the echo, pending-entry, and re-arm display-text reads (R2-7).
- Add workspaceCwd to the channel e2e session fixtures (R2-8).
- Pin the archived source switch, the deleteSessionData validation
  contract, and the stale-response ordering guard with tests
  (R2-10, R2-11, R2-13).

* fix(channels): address session review feedback

* fix(channels): address channel session review feedback

* fix(channels): harden channel session review round-7 criticals (#8457)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): harden first-visit session section latches (#8457)

* fix(channels): address review round-8 findings (#8457)

* test(web-shell): pin round-8 sidebar behavior fixes (#8457)

* fix(channels): address review round-9 findings (#8457)

* fix(web-shell): pin Other channels group after platform sections (#8457)

---------

Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 丁炳智 <dingbingzhi@Edenmans-Office-Mac.local>
Co-authored-by: qwen-code-autofix[bot] <qwen-code-autofix[bot]@users.noreply.github.com>
2026-08-12 17:11:07 +00:00
易良
464e8910e8
fix(desktop): harden release pipeline (#9009)
* fix(desktop): harden release pipeline

* fix(desktop): resolve release hardening review
2026-08-12 16:38:12 +00:00
jinye
4980a2c20d
fix(cli): bound headless tool result content (#9012)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-12 16:22:36 +00:00
Dragon
50097c154c
fix(cli): enable footer text selection in VP mode (#8329)
* fix(cli): enable footer text selection in VP mode

* test(cli): cover cross-region footer drag

* fix(cli): stabilize footer selection invalidation

* fix(cli): preserve line selection from layout cells

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-12 15:03:30 +00:00
ytahdn
f2de42dec4
feat(web-shell): improve compact tool activity (#8973)
* feat(web-shell): improve compact tool activity

* fix(web-shell): preserve tool timing metadata

* fix(web-shell): address compact activity review

* fix(web-shell): preserve customized tool metadata

* test(web-shell): cover completed agent tokens

---------

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
2026-08-12 12:48:08 +00:00
易良
a32ec1ee4a
feat(desktop): add Aliyun OSS release mirror (#8976)
* feat(desktop): mirror releases to Aliyun OSS

* fix(desktop): harden OSS mirror workflow and tests

- Add ref guard to sync-desktop-to-oss.yml (dispatch only from main)
- Add diagnostic error messages for missing Windows/Linux installers
- Harden test: pin verify-index > 0 before ordering comparison
- Harden test: pin confirm-before-publish ordering and source comparison
- Add test: stable-only release validation in reusable sync job

* fix(desktop): harden OSS mirror permissions, stable-version guard, and non-latest repair

- Remove workflow-level actions:read; grant it only to the sync-oss caller job
- Reject suffixed versions for published stable releases in prepare
- Turn latest-feed comparison into a non-fatal check; condition publish/verify on match
- Assert both check_for_update call sites in release test
- Add jq stable-only guard assertion and endpoint default alignment test

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-12 10:59:10 +00:00
jinye
675dd45880
fix(web-shell): Harden prompt admission ownership (#8955)
Some checks are pending
E2E Tests / web-shell Browser Regression (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* fix(web-shell): Harden prompt admission ownership

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): Address prompt retry review feedback (#8955)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-12 09:33:21 +00:00
jinye
9ff1519ac9
fix(webui): Close same-session refresh race gaps (#8990)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-12 09:31:02 +00:00
jinye
5f29e7f788
fix(webui): make same-session refresh transactional (#8939)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-12 06:20:26 +00:00
jinye
9259c35500
feat(serve): Propagate session list cancellation (#8954)
* feat(serve): Propagate session list cancellation

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#8954)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-12 05:03:19 +00:00
jinye
6447f17129
fix(serve): Close daemon ACP resource guard gaps (#8947)
* fix(serve): close daemon ACP resource guard gaps

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#8947)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(acp): address bounded transport review feedback

Preserve owned bulk responses, stop bounded estimators once their limits are reached, and compensate approval-mode changes when an attach loses a teardown race.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-12 04:15:30 +00:00
Nothing Chan
de470b95b4
feat(telemetry): align session lifecycle with OpenTelemetry (#8616)
* fix(telemetry): align session lifecycle with OTel

* feat(telemetry): complete session lifecycle coverage

* fix(telemetry): deduplicate deferred session starts

* test(telemetry): cover duplicate session starts

* fix(serve): emit daemon session starts

* fix(telemetry): repair session lifecycle test wiring and record attributes (#8616)

Restore the missing logSessionEnd export in the config-session-env mock
(the Test-check failure), add event.timestamp to the session lifecycle
records to match every sibling emitter, and pin the previously untested
wiring: end-before-start ordering and the /clear non-continuation rule at
the Config level, the deferred-init catch-up guard, the shutdown emission,
and the loggers->session-events link. Correct the telemetry docs claims
about session.previous_id, end_session, and the log event catalog.

* fix(telemetry): skip session lifecycle transition on same-id resume (#8616)

* test(telemetry): pin session lifecycle behaviors per review (#8616)

Mutation testing in review round 4 showed three properties survived the
whole suite unguarded: the session-start guard resetting on session.end,
the daemon runtime config's isTelemetryInitializationDeferred flag, and
the catch-up session.start emitting only after NodeSDK.start(). Add
focused assertions for each, and replace a duplicated config mock literal
with the makeFakeConfig factory.

* fix(telemetry): emit session start catch-up on every init path (#8616)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: zjunothing <zjunothing@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-12 02:53:06 +00:00
qqqys
de48637aa0
refactor(serve): default project memory to workspace scope (#8856)
* refactor(serve): default project memory to workspace scope

* fix(serve): preserve launch env access guard

* fix(serve): harden project memory scope resolution

* fix(serve): harden project memory scope diagnostics

* fix(serve): keep memory scope operator-owned

---------

Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
2026-08-12 02:38:09 +00:00
callmeYe
a64d1291d2
feat(extensions): support Agent Plugins v1 (#8834)
* feat(extensions): support Agent Plugins v1

* fix(extensions): address Agent Plugins review blockers

* fix(extensions): address second review blockers
2026-08-11 19:45:11 +00:00
carffuca
0e8a361b37
fix(web-shell): improve ask user question keyboard interactions (#8876)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
npm cache producer / Save npm cache (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
* test(web-shell): cover question keyboard workflow

* fix(web-shell): improve question keyboard flow

* test(web-shell): harden question keyboard coverage

* fix(web-shell): suppress inactive submit shortcut

* test(web-shell): cover direct Enter submission

* fix(web-shell): submit final question with Enter

* test(web-shell): cover compact shortcut hints

* fix(web-shell): compact question shortcut hints

* fix(web-shell): prevent narrow question footer overflow

* test(web-shell): cover custom-row Enter hint

* fix(web-shell): clarify custom-row Enter hint

* test(web-shell): cover shorter question replacement

* fix(web-shell): guard transient question replacement

* test(web-shell): reproduce rapid multi-select clicks

* fix(web-shell): preserve rapid multi-select updates

* fix(web-shell): avoid default multi-select answer

* fix(web-shell): refine ask question keyboard controls

* fix(web-shell): address ask question review findings
2026-08-11 17:50:15 +00:00
jinye
962dc8eadc
fix(serve): Keep restore request shapes distinct (#8933)
* fix(serve): keep restore request shapes distinct

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(webui): fence cancelled restore lifecycles

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 17:20:47 +00:00
jinye
d96de59acf
feat(serve): bound daemon ACP NDJSON buffers (#8911)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 14:36:49 +00:00
jinye
74cfbccf49
fix(web-shell): Enforce prompt-safe session navigation (#8931)
* fix(web-shell): Enforce prompt-safe session navigation

Refs #8923

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(webui): Harden session navigation cleanup

Refs #8923

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): Preserve admission state across navigation

Refs #8923

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 14:33:35 +00:00
jinye
00248b1a53
fix(webui): Make cross-session switching transactional (#8882)
* fix(webui): make cross-session restores transactional

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): preserve sessions during navigation

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#8882)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(webui): address transactional switch review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): preserve catalog updates across session switches

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 09:38:36 +00:00
jinye
63fbf1bd14
perf(cli): Cache persisted session catalogs (#8892)
* perf(cli): Cache persisted session catalogs

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): Pin LiveTask rollback storage runtime

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): Strengthen session catalog cache coverage

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): Pin LiveTask bridge identity

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 09:34:49 +00:00
jinye
096bb0e3b9
feat(web-shell): Share session catalog scheduling (#8891)
* feat(web-shell): Share session catalog scheduling

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): Harden session catalog reconciliation

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): Restore session catalog CI coverage

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 09:13:14 +00:00
ytahdn
1a2c5026b2
fix(web-shell): reconcile mid-turn messages with daemon state (#8798)
* fix(web-shell): reconcile mid-turn messages with daemon state

* test(serve): update mid-turn capability expectation

* test(mid-turn): cover reconciliation mutants and restore serve protocol docs (#8798)

* fix(serve): close mid-turn promotion admission and delivery gaps (#8798)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): keep anonymous mid-turn enqueues off the shared queue surface (#8798)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(sdk): account for mid-turn APIs in bundle budget

* test(acp-bridge): use vi.waitFor for async prompt-drain assertions (#8798)

* fix(serve): reconcile mid-turn steering safely

* fix: reconcile mid-turn messages safely

---------

Co-authored-by: ytahdn <ytahdn@users.noreply.github.com>
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-11 07:24:56 +00:00
jinye
60c18256b6
feat(cli): clean up OpenAI logs in non-interactive sessions (#8893)
* feat(cli): clean up OpenAI logs in non-interactive sessions

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#8893)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#8893)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 07:09:28 +00:00
Shaojin Wen
dea5b6da89
refactor(review): extract the toolchain adapter boundary (#8776)
* refactor(review): extract the toolchain adapter boundary

`review build-test` combined three responsibilities in one module: reading
the review plan, deciding which toolchain can be verified deterministically,
and implementing npm workspace installation, affected-package selection,
dependency widening, build execution, test execution, and reporting.

Move the npm implementation behind an internal `ReviewToolchainAdapter`
contract. `build-test.ts` keeps CLI routing, plan reading, output trimming,
env shaping, and the spawn boundary; `lib/npm-toolchain.ts` owns npm
detection and the verification algorithm; `lib/disk.ts` holds the shared
free-disk floors.

This is a move, not a rewrite: 95% of the lines removed from build-test.ts
reappear verbatim in the new files. The CLI arguments, the BuildTestReport
JSON shape, and every npm behaviour are unchanged, and the existing
build-test suite is the compatibility oracle for that.

Selection requires exactly one applicable adapter and fails closed to the
`unsupported` handoff otherwise, so a second toolchain lands as a
registration rather than another branch in this file.

* docs(review): record the toolchain adapter boundary design

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-11 05:47:31 +00:00
Shaojin Wen
95e17691a9
chore(serve): remove the /demo debug page (#8805)
* chore(serve): remove the /demo debug page

The daemon has shipped a real browser UI for a while: `resolveWebShellDir()`
finds the bundled Web Shell assets and `mountWebShellAssets()` serves them at
`/`, so `qwen serve` already opens onto a full client. `/demo` stayed behind as
a 663-line inline-HTML console covering the same ground with none of the
reach — nobody drives the daemon through it, and `npm run dev:daemon` starts
the Web Shell dev server rather than the demo page.

Keeping it around costs more than the dead code. It is the only file in the
tree that pairs an event log with daemon HTTP, so work that starts as a Web
Shell observation lands there instead: #8762 was found while running `/review`
through the Web Shell and was fixed entirely inside the demo page's rendering,
with "no Web Shell changes" in its own risk note. Deleting the page removes
that decoy.

Nothing is lost for protocol-level debugging: `GET /session/:id/events`
streams the same raw frames the Events tab printed.

`/health` shared `routes/health-demo.ts` with the demo handler, so the module
is now `routes/health.ts` / `createHealthRoutes()` and drops its `getPort`
dependency. The rate-limit exemption, the boot breadcrumb, and the daemon docs
lose their `/demo` arms; the loopback self-origin shim regression test already
asserted through `/health` and only needed its title corrected.

* test(serve): pin the removed /demo contract and the pre-auth surface

Review follow-up. Three of the removal hunks shipped ungated, and two doc
sentences the removal rewrote were describing the pre-auth surface wrong —
both before and after the edit.

Deleting the `/demo` route took its assertions with it, so nothing failed if
the handler came back: the Web Shell suite only exercised a generic deep link,
and the rate-limit exemption could be widened again with the suite still green.
`/demo` is now pinned as what it became — an ordinary unknown path: a
non-navigation request 404s, a browser navigation is answered by the SPA
fallback like any other deep link, and once a token is configured (with or
without `--require-auth`) that navigation is refused with 401, because the
fallback sits behind the bearer. The rate-limit test pins that `/health` is the
only exempt GET, so re-adding a second pre-auth page to the predicate fails
instead of silently escaping the limiter. Each new assertion was checked by
reverting the hunk it guards and confirming it goes red.

The `--allow-origin '*'` warning and both `--allow-origin` doc paragraphs
enumerated `/health` as the residual tokenless surface and said nothing about
the Web Shell static assets, which are mounted before the bearer in every
launch mode and stay reachable even under `--require-auth` — the enumeration
also claimed `/health` stays pre-auth on non-loopback binds, where it is
registered behind the bearer and 401s. A probe across all three launch modes
established the actual matrix; the warning and the docs now match it and name
`--no-web` as the way to remove the residual browser surface. The warning text
is asserted by a test for the first time.

* fix(serve): correct Web Shell doc claims and re-pin the pre-auth CORS wall

Review follow-up. The removal rewrote the daemon docs around the Web
Shell, and three of the rewritten claims did not match what the runtime
actually does: §1 never said how the bearer reaches the browser (with
auth on, the plain URL loads a shell whose every API call 401s), §8
called the shell writable on any bind (on a non-loopback bind without
`--allow-origin` its POSTs hit the CORS wall and 403), and §8 served
`/session/:id` without the document-navigation qualifier its own code
enforces. The §9 call-chain diagram also still listed the deleted
`/demo` route, the developer flag references had no `--web`/`--no-web`
row despite the new guidance pointing at the flag, and both design docs
listed the JSON body parser ahead of post-auth `/health` while
`createServeApp()` registers them the other way round.

The deleted `/demo` CORS test was also the only assertion that a
pre-auth page sits behind the Origin wall — every surviving Origin test
targets an API path. Re-pin it for the shell root so a mount-order
regression fails instead of exposing the pre-auth HTML surface
cross-origin.

* fix(serve): finish demo rename sweep and scope pre-auth shell claims to loopback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-10 13:31:15 +00:00
jinye
a292c89a3b
feat(cli): add background cleanup for OpenAI API logs (#8862)
* feat(cli): add background cleanup for OpenAI API logs

With model.enableOpenAILogging on, every API call appends a full
request/response JSON under logs/openai with no rotation — heavy usage
accumulates hundreds of thousands of files (tens of GB) within months.

Register a third cleaner in the existing background housekeeping
pipeline that sweeps openai-*.json files older than the new
model.openAILogRetentionDays setting (default 7 days). The
filename-embedded UTC date is used as a fast path to avoid one stat()
per file; the boundary day and unparseable names fall back to mtime.
Throttling is keyed on the resolved log dir, so both the default
per-CWD layout and a shared custom openAILoggingDir are swept at most
once a day. The sweep runs regardless of whether logging is currently
enabled, so residue from earlier debugging sessions still gets cleaned.

Scope note: housekeeping only starts for interactive sessions, so
headless (-p) / SDK processes are not covered yet.

* codex: address PR review feedback (#8862)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-10 12:20:33 +00:00
jinye
fa8cae5418
fix(serve): Allow approved external built-in text writes (#8852)
* fix(serve): allow approved external built-in text writes

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): keep write provenance off startup bundle

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-10 12:19:58 +00:00
顾盼
c0c0352e25
feat(cli): mirror Live Host releases through OSS (#8674)
* feat(cli): mirror Live Host releases through OSS

* fix(cli): harden Live Host OSS mirror fallback
2026-08-10 11:17:49 +00:00