Commit graph

6226 commits

Author SHA1 Message Date
Shaojin Wen
bb0db932fa
fix(core): default GLM-5.2+ and GLM-6.x onward to 1M context (#5103)
GLM-5.2 ships a 1M context window, and 1M is becoming the norm for newer
GLM releases. The previous `/^glm-5/` rule capped the whole GLM-5 line at
202752, so every new model would need a code change.

Make 1M the forward default for GLM-5.2+, GLM-6.x..9.x and two-digit
majors, while pinning the confirmed 200K families (GLM-5 / 5.0 / 5.1 and
GLM-4.x or older) explicitly. Third-party deploy prefixes (e.g.
`pai/glm-5.3`) are already stripped by normalize(), so they match the same
rules. Non-numeric names (e.g. glm-z1) stay on the conservative fallback.
2026-06-14 21:17:45 +08:00
ytahdn
9be731ce75
feat(cli,web-shell): persist goal status in daemon transcript events (#5098)
Previously /goal state lived only in frontend memory — page refresh or
multi-device sessions lost the active goal. Now the CLI emits goal status
updates as structured daemon events (_meta.goalStatus), which flow through
the transcript as status blocks (source: 'goal', data: {...}). The web-shell
rebuilds goal state from transcript blocks on connect, making goal status
survivable across page refreshes and syncable across devices.

- CLI: emitGoalStatus on goal set/clear, pass outputHistoryItems through
  nonInteractiveCliCommands, add setAt to goalCommand output
- SDK: widen DaemonUiStatusEvent source/data types, preserve them in
  transcript blocks
- webui: normalize _meta.goalStatus in DaemonSessionProvider, replace
  sentinel-prefix text encoding with structured data
- web-shell: derive activeGoal from transcript blocks (getLatestActiveGoalFromBlocks),
  remove optimistic client-side goal dispatch, parse structured goal data
  in GoalStatusMessage/SystemMessage
- Tests: cover emitGoalStatus, outputHistoryItems passthrough, transcript
  block serialization, DaemonSessionProvider event conversion
- Also: harden McpDialog restart result type check with isRestartEntriesResult

Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-06-14 21:08:06 +08:00
易良
4694d11c5f
fix(ci): fail PR review job when the run aborts mid-review (#5053)
The review-pr job only checked qwen's exit code, the tee status, the
timeout sentinel, and an empty log. When the model connection drops
mid-review, qwen still exits 0 and emits a terminal stream-json result
event with subtype=success / is_error=false whose text carries the
inlined '[API Error: ...]'. All existing checks pass, so the job goes
green without ever posting a review comment.

Inspect the terminal result event explicitly: fail when it is missing,
when is_error is true or subtype is not success, or when the result text
contains an inlined API error. A failed check now triggers the existing
fallback-comment step instead of a silent green success.

Refs #5052
2026-06-14 20:28:25 +08:00
Shaojin Wen
dc0706dc18
feat(web-shell): make input shortcuts discoverable and clickable (#5096)
* feat(web-shell): make input shortcuts discoverable and clickable

- Add an always-on, clickable hint row below the input: ↑ previous / ↓ next history, ctrl+r search, / commands, @ files — each click runs the matching action.
- Make the status bar "? for shortcuts" a persistent clickable button shown next to the mode indicator.
- Dismiss the shortcuts panel by clicking outside it or pressing Esc (no dedicated close button).
- Cancel reverse-i-search by clicking outside the panel (same as Esc), restoring the original draft.
- Distinguish ctrl+r (search history) from up/down (cycle history) in the shortcuts panel.
- Auto-prepend a space for mid-word @-mentions, and make the / and @ triggers idempotent so a second click re-opens the menu instead of producing "//" or "@ @".

* fix(web-shell): address PR review — touch dismissal, primary-button guard, @ idempotency

- Outside-press dismissal for the shortcuts panel and reverse-i-search now also listens for touchstart, ignores non-primary (middle/right) buttons, and respects defaultPrevented — matching the Settings/Mode inline panels.
- insertText('@') no longer inserts a duplicate '@' when the cursor sits directly before an existing '@'; it steps over the existing one and opens the menu.

* fix(web-shell): close shortcuts panel idempotently on outside-press

Touch fires touchstart plus a synthesized mousedown; a toggle onClose would reopen the panel right after closing. Use a dedicated close (set false) for the outside-press / Escape dismissal, matching the other inline panels.

* fix(web-shell): address /review — disabled guard, mid-line slash, focus, dedup

- Hide the hint row while the editor is disabled and bail the history/search callbacks on disabledRef, so the buttons can't bypass the disabled guard. insertText stays usable for App-driven injection; the now-hidden row is the only internal path that passes '/' or '@'.
- Clicking the / hint on non-empty, non-slash text replaces the content with '/' so the command menu actually opens, instead of leaving a stray mid-line '/'.
- Outside-click search dismissal no longer steals focus from the clicked target (closeSearch keepFocus=false).
- Extract a shared hintProps() helper for the five hint buttons.

* fix(web-shell): address /qreview — capture-phase Escape, hint aria-haspopup

- The shortcuts panel's Escape now wins over the App-level global Escape (capture phase + preventDefault/stopPropagation), so Esc closes the panel instead of being swallowed (clearing queued prompts / cancelling the stream) while it's open.
- Add aria-haspopup to the popup-opening hint buttons (dialog for ctrl+r, listbox for / and @), matching the sibling toolbar buttons.

* fix(web-shell): theme the slash-completion popup scrollbar

The CodeMirror autocomplete list and info panel used the browser-default (light) scrollbar, clashing with the dark theme. Apply the repo's scrollbar convention (thin + var(--border-color) thumb over a transparent track, with the -webkit fallback) so it matches in both themes.

* fix(web-shell): don't destroy the draft when clicking / on non-empty input

Replacing the document with '/' (the previous /review fix) silently wiped a typed draft. Instead, no-op when the line isn't already a command and the editor is non-empty — the slash menu needs a line-leading '/', which can't be added without either a stray mid-line '/' or clobbering the draft. Empty input still inserts '/' and opens the menu.

* fix(web-shell): guard hint history nav on multi-line input; hide hints behind dialogs

- navigatePrev/NextHistory now early-return on doc.lines > 1, matching the ArrowUp/ArrowDown keymap, so clicking the up/down hints no longer replaces a multi-line draft with a single history entry.
- showShortcutHints also requires !dialogOpen, matching the Ctrl+R keymap guard, so the hint buttons aren't interactive while a dialog is open.

* feat(web-shell): undo click-inserted / or @ on cancel; align hint nav with keymap

- Clicking the / or @ hint then pressing Escape (without typing past the inserted char) now removes the trigger too — it was clicked in, not typed. Editing past it cancels this.
- navigatePrev/NextHistory move the completion selection when the menu is open, matching the ArrowUp/ArrowDown keymap.
- The / hint no-op (non-empty draft) no longer fires startCompletion, which would pop an empty menu; a line-leading / still re-opens it.

* fix(web-shell): undo click-inserted trigger on any completion dismissal, not just Escape

Only the Escape keymap undid the click-inserted / or @. Clicking away or blurring also closes the menu but left a stray trigger behind. Watch the completion status (active -> closed) via an updateListener instead, so every dismissal path (Escape, click-away, blur) removes an untouched click-inserted trigger; the per-key special-case in the Escape handler and restarter is removed.

* feat(web-shell): grey out history hint arrows when there's nowhere to go

useInputHistory now exposes a nav { canUp, canDown } state, kept in sync on push/navigate/reset. The up/down hint buttons are disabled when there's no older entry to recall, or when not currently browsing history. Keyboard and mouse share the same state, so the affordance stays accurate however history is navigated.
2026-06-14 16:41:37 +08:00
jinye
64a1efb20f
fix(acp): add internal Kind.Agent, keep ACP wire on 'other' (no-regression) (#5085)
* feat(core): add Kind.Agent for Agent tool to improve UI categorization

The Agent tool was using Kind.Other as a catch-all, causing WebUI
permission dialogs to show generic titles and descriptions. Adding a
dedicated Kind.Agent value enables agent-specific UI rendering in
permission drawers, tool labels, and export normalization.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* test(webui): lock agent label mapping in labelUtils test

Address wenshao review on PR #5085 — add assertion that
getToolDisplayLabel({ kind: 'agent' }) returns 'Agent', matching the
sibling task/skill cases.

* test(cli): cover agent kind in export normalization + document acp-sdk cast

Address wenshao review on PR #5085:
- Add normalize.test.ts case asserting an agent-kind tool call is
  preserved as 'agent' through normalizeSessionData.
- Add TODO(acp-sdk) comment on the KIND_MAP 'agent' cast explaining why
  we emit 'agent' (webui SSE consumer) rather than mapping to 'other'.

* fix(acp): map Kind.Agent to 'other' on the wire; drop unusable 'agent' kind

wenshao's daemon-level A/B verification showed emitting kind:'agent' over
ACP is a regression: the daemon's ClientSideConnection Zod-validates every
session/update + session/request_permission from the qwen --acp child before
SSE fan-out, and @agentclientprotocol/sdk has no 'agent' ToolKind (verified
through 0.25.1), so the frame is rejected (invalid_union) and dropped — the
agent tool_call + permission dialog that previously reached SSE clients as
kind:'other' now never arrive.

Shrink to a no-regression safe version:
- ToolCallEmitter maps the internal Kind.Agent to 'other' on the wire (drops
  the 'agent' as ToolKind cast that only fooled tsc, not the runtime schema).
- Revert the wire/UI/protocol 'agent' additions that depended on a value the
  protocol can't carry: PermissionDrawer/labelUtils kind branches, web-shell
  DaemonMessageToolKind + inferToolKind, Java SDK schema/enum, export allowlist.

Kind.Agent stays in core as the internal tool category. The dedicated agent
permission dialog will be delivered via _meta.toolName (which already rides the
validated wire) in a follow-up PR, not via a protocol kind.
2026-06-14 14:59:24 +08:00
tt-a1i
8471b6d254
fix(cli): wrap long status lines (#5093)
Some checks failed
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
Sync cua-driver to Aliyun OSS / Mirror cua-driver binaries to Aliyun OSS (push) Has been cancelled
* fix(cli): wrap long status lines

* test(cli): cover wrapped status line token
2026-06-14 13:28:50 +08:00
yao
f9080e44fb
fix(cli,core): harden OOM prevention — idempotent compaction tests, explicit GC, debug log defaults (#4914)
* test(cli): add compactOldItems idempotency regression tests

Cover the scenario fixed in commit 595701096 where already-compacted
tool groups (resultDisplay === UI_COMPACT_CLEARED_MESSAGE) were
incorrectly counted as having real output, causing over-compaction.

Three new test cases:
- Already-compacted groups are not re-compacted; second call is a no-op
- All tool groups already compacted → no-op
- Mixed tool group (some tools real, some cleared) → only groups with
  real output are compacted

* fix(cli,core): enable explicit GC and disable debug log by default

- enableExplicitGC defaults to true, --expose-gc added to start/dev scripts
- isDebugLogFileEnabled() defaults to false (opt-in via QWEN_DEBUG_LOG_FILE=1)
- Add safety tests: trigger_gc only in critical tier, global.gc() only in
  memoryPressureMonitor.ts trigger_gc case

* fix: address R1 review comments for memory pressure monitor

- Replace brittle source-parsing test with behavioral tests for global.gc()
- Export UI_COMPACT_CLEARED_MESSAGE constant and use in tests
- Remove redundant NODE_OPTIONS override from start script
- Add production bin wrapper with --expose-gc for OOM protection
- Remove unused path import from memoryPressureMonitor.test.ts

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix: forward --expose-gc to all deployment modes

Standalone package shims and daemon-spawned sessions (AcpBridge,
httpAcpBridge) were missing --expose-gc, causing explicit GC to
silently fail under critical memory pressure.

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix: forward child process signal in cli-entry wrapper

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(cli,channels): filter --inspect flags when forwarding execArgv to daemon children

* fix: make cli-entry.js executable (mode 100755)

* fix(core): reject whitespace-only QWEN_DEBUG_LOG_FILE and add QWEN_MEMORY_ENABLE_GC=0 opt-out

* fix(scripts): include cli-entry.js wrapper in dist package for npm publish

* fix(acp-bridge): forward --expose-gc and filter --inspect in spawnChannel

- Add --expose-gc to getAcpMemoryArgs() so daemon-spawned ACP children
  have global.gc() available for critical memory pressure cleanup
- Filter --inspect/-brk flags from process.execArgv to prevent port
  conflicts in multi-session daemon mode
- Update spawnChannel.test.ts for new getAcpMemoryArgs() return shape

This change was previously in httpAcpBridge.ts but lost during the
daemon refactor merge (#4490) that moved spawn logic to acp-bridge.

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-14 10:40:53 +08:00
Shaojin Wen
800507598c
feat(web-shell): reveal full tool detail and auto-collapse finished tools (#5088)
* feat(web-shell): reveal full tool detail and auto-collapse finished tools

Long tool descriptions were hard-capped at 120 characters and finished
tools (shell/edit/write) stayed expanded indefinitely, so commands were
unreadable and the transcript filled with stale output.

- Lift the 120-char description cap so the full command/path reaches the
  DOM; collapsed rows ellipsise via CSS (adapts to width) and a click
  reflows the full text into a wrapped block below the header.
- Add a leading disclosure chevron; any row with detail output or a long
  description is now expandable.
- Auto-collapse a tool to its one-line summary once it completes
  successfully. Running tools stay expanded (live output) and failures
  stay expanded (error visible); agents keep their own manual expand state.

* fix(web-shell): preserve manual expand on completion; correct auto-expand comment

Review feedback on #5088:

- shouldAutoExpand: rewrite the comment to match the code. Only the verbose
  kinds (shell/edit/write/ask) auto-expand and stay expanded on failure; other
  kinds are collapsed by default (their summary line shows the outcome and they
  stay click-to-expand). Force-expanding every failed tool was rejected because
  tools without an expanded-detail renderer would then hide the summary line
  and show an empty body — i.e. hide the error.
- Auto-collapse-on-completion no longer overrides an explicit user toggle: a
  userToggledRef latch (set on header click, reset on tool-identity change)
  guards the collapse effect, so a row the user expanded/collapsed keeps its
  state when the tool finishes.

* test(web-shell): assert tool-detail relocation via DOM, not textContent

Review feedback (#5088): the expand test asserted container.textContent
contains the command before and after the click, which passes regardless of
whether the description is relocated from the header span to the wrapped
block (textContent concatenates the whole subtree). Assert the DOM move
instead — the command is in a leaf <span> while collapsed and in none while
expanded — so a regression dropping the relocation now fails the test.
2026-06-14 10:11:13 +08:00
顾盼
e8342715e5
feat(core): migrate Computer Use to cua-driver (cross-platform) (#5051)
Migrate the built-in Computer Use tool surface from open-computer-use
(npm/npx) to cua-driver-rs (native Rust driver, trycua/cua).

- Replace the 9-tool ocu surface with the full 35-tool cua-driver surface
  (page/CDP, cursor, session, recording, config, app lifecycle, …),
  generated from the live `cua-driver mcp` tools/list — pinned to v0.5.2.
- Per-platform signed + notarized binary distribution: download into
  ~/.qwen/computer-use/ with SHA-256 integrity verification, a three-tier
  Windows unzip fallback, headers/idle timeouts, and a bounded retry loop.
- macOS TCC permission flow via CuaDriver.app (com.trycua.driver), polled
  one-at-a-time through the no-gate status daemon.
- Mirror cua-driver assets on the qwen-code-assets OSS bucket with a
  push-to-main sync workflow guarded by checksums.
- Gate high-risk tools (kill_app, launch_app, start_recording, set_config,
  replay_trajectory, page JS execution) to an explicit confirmation type so
  AUTO_EDIT cannot silently auto-approve them; AUTO defers to the classifier,
  YOLO auto-approves.
2026-06-14 09:26:09 +08:00
Shaojin Wen
8472c6fcea
fix(webui): defer DaemonClient disposal to survive React StrictMode (#5091)
Under StrictMode (dev default), DaemonWorkspaceProvider's useEffect
cleanup called client.dispose() synchronously, destroying the memoized
DaemonClient that the second effect invocation reused. This left the
transport closed before the session provider could attach, surfacing as
"Transport connection closed" and a permanent "Loading..." / disconnected
state in the web-shell.

Defer disposal by one microtask. StrictMode's synchronous re-mount
cancels the pending disposal before the microtask fires, preserving the
shared client. Real unmounts and client replacements still dispose
normally since no cancellation occurs in those paths.
2026-06-14 08:12:36 +08:00
yao
75cc3ce15e
fix(cli): add OSC 52 clipboard fallback for SSH environments (#4929)
* fix(cli): add OSC 52 clipboard fallback for SSH environments

- Add writeOsc52() helper in commandUtils.ts and vim.ts
- Fall back to OSC 52 escape sequence when xclip/xsel/wl-copy unavailable
- Fixes /copy command and vim yank (yy, yw, etc.) over SSH without X11

* refactor(cli): extract writeOsc52 to shared clipboardUtils

- Add writeOsc52() export in clipboardUtils.ts with TTY check, error handling,
  and boolean return for success/failure
- Remove duplicate writeOsc52 from commandUtils.ts and vim.ts
- Update both to import from clipboardUtils.ts
- Add 6 tests verifying OSC 52 escape sequence output to stdout/stderr,
  TTY detection, special chars, empty string, and error handling
- Fix commandUtils test to assert OSC 52 fallback instead of throwing

Fixes code duplication and inconsistent error handling noted in PR review.

* test(clipboard): fix spawn timeout test hanging with fake timers

Remove vi.useFakeTimers() which is incompatible with process.nextTick
in the mock pattern. Use real timers with 10s test timeout instead.

* fix(cli): check writeOsc52 return value in callers

Warn when OSC 52 clipboard write fails (no TTY available) instead of
silently ignoring the failure.

* fix(test): clear wl-paste image type cache before BMP-to-PNG tests

BMP-to-PNG test block was missing its own beforeEach to reset the
cachedWlPasteImageTypes cache, causing the 'prefer PNG over BMP' test
to fail intermittently in CI when a previous test had populated the cache.

* fix(test): use dynamic import instead of vi.resetModules() to fix test pollution

* fix(test): correct spawn call assertion in BMP-to-PNG clipboard test

* fix(test): clean up /tmp/test before BMP-to-PNG clipboard test to fix flaky assertion

* fix(test): restore vi.resetModules() with dynamic imports for test isolation

Replace stale top-level imports of clipboardUtils with describe-level
variables populated by dynamic import() after vi.resetModules() in
beforeEach. This ensures every test gets a fresh module instance,
eliminating cross-test state pollution from cachedWlPasteImageTypes
and linuxClipboardTool.

* fix(cli): throw error when all clipboard methods fail and restore vi.resetModules for test isolation

* fix(cli): try OSC 52 before throwing when xclip/xsel fail

Ensures consistent behavior between /copy and vim yank commands
in SSH environments where xclip/xsel are installed but fail
due to missing X display.

* test(cli): add OSC 52 fallback tests for xclip/xsel failure scenario

Adds two tests to verify OSC 52 behavior when clipboard tools
exist but fail (e.g., SSH without X forwarding):
- Verifies OSC 52 is attempted and fails gracefully when no TTY
- Verifies OSC 52 succeeds when TTY is available

* test(cli): mock isTTY in OSC 52 no-TTY fallback test to prevent flakiness

* fix(cli): wrap OSC 52 sequence with wrapForMultiplexer for tmux/screen support

* fix(cli): try OSC 52 fallback in vim writeClipboard when cached tool fails at runtime

* fix(cli): prefer stderr over stdout for OSC 52 to avoid Ink rendering pipeline interference

* fix(cli): mention OSC 52 attempt in error message when xclip/xsel and OSC 52 both fail

* fix(cli): harden OSC 52 clipboard with size limit and async error handling

     - Cap OSC 52 payload at 75KB (~100KB base64) to prevent terminal crashes/hangs
       from oversized escape sequences (iTerm2 ~100KB, xterm ~8KB limits)
     - Add write callback to capture async failures on stdout/stderr streams
     - Apply same hardening to duplicate implementation in AuthenticateStep.tsx
     - Strengthen tests: stub TMUX/STY for determinism, add tmux/screen DCS wrap tests
2026-06-14 07:55:24 +08:00
jinye
87dc1a3932
test(cli): Cover rewind selection and confirm flow (#5044)
* test(cli): Cover rewind selection and confirm flow

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

* codex: address PR review feedback (#5044)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-14 07:07:08 +08:00
jinye
24a9828b2e
fix(core): Persist file history snapshot updates (#5057)
* fix(core): persist file history snapshot updates

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

* codex: address PR review feedback (#5057)

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

* fix(core): Address file history persistence review feedback

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

* fix(core): Address file history review follow-ups

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-14 06:47:03 +08:00
Yufeng He
b6b15e45e8
fix(cli): drop tool calls after cancellation (#5020) 2026-06-14 06:36:48 +08:00
Yufeng He
533fafa2d2
fix(cli): ignore expired live agents in focus navigation (#5070)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
2026-06-14 03:19:30 +08:00
顾盼
06345a2fe9
feat(core): Workflow P3 — agent({schema, agentType, model, isolation:'worktree'}) (#4721) (#5034)
* feat(core): Workflow P3 — agent({schema, agentType, model, isolation:'worktree'}) (#4721)

Adds the P3 dispatch options to the workflow runtime, completing the
contract qwen-code's workflow tool matches against upstream Claude Code
2.1.168. P1/P2 stubs (workflow-sandbox.ts:508-527) are replaced with
production paths routed through `SubagentManager.createAgentHeadless` so
per-call model overrides go through `buildRuntimeContentGeneratorView`
(provider routing), per-agent MCP servers / hooks get isolated
lifecycles, and worktree-isolated subagents run against a rebound Config.

- agent({agentType: 'X'}) resolves against the declarative-agents
  registry (#4842 + #4996) via findSubagentByName; unresolved names throw
  "agent({agentType}): agent type 'X' not found" verbatim from upstream.
- agent({model: 'qwen3-max'}) is threaded into SubagentConfig.model so
  the runtime view sees it (modelConfigOverrides alone would only swap
  the model name within the existing provider's view).
- Workflow's disallowed-tool floor [SendMessage, ExitPlanMode] is unioned
  with the agentType's own disallowedTools so a permissive agentType
  cannot re-enable them for a workflow subagent.
- agent({isolation: 'worktree'}) provisions a fresh worktree via
  GitWorktreeService.createUserWorktree (slug agent-<7hex>, mirrors
  AgentTool 1849-1963), rebinds cwd/getTargetDir/getFileService/
  getWorkspaceContext on a prototype-chained Config override, and on
  completion auto-removes the worktree if clean or preserves the path +
  branch (appended to the result string) when the subagent left changes.
  Parent-dirty trees are refused with a clear error to avoid silently
  running the subagent against a stale HEAD.
- agent({isolation: 'remote'}) throws "agent({isolation:'remote'}) is
  not available in this build" verbatim (upstream 2.1.168 parity).
- agent({schema: S}) injects a per-call SyntheticOutputTool (existing
  tools/syntheticOutput.ts, AJV-backed) into a fresh per-subagent
  ToolRegistry built via rebuildToolRegistryOnOverride, then watches
  AgentEventEmitter TOOL_CALL/TOOL_RESULT events for `structured_output`
  invocations. A successful call's args are captured as the dispatch
  return value (object, not string); after two failed attempts the
  third failure aborts the dispatch and throws "subagent completed
  without calling StructuredOutput (after 2 in-conversation nudges)"
  verbatim. No agent-core.ts changes — the entire 2-nudge counter
  lives in the dispatch layer so the shared subagent loop is unaffected.

The sandbox's agent() wrapper now revives per-call object returns into
the vm realm (JSON round-trip inside the vm runInContext block), closing
the same T1/T8/T14 host-prototype-escape vector that P2's per-element
revival closed for parallel/pipeline. Two new sandbox security tests
(constructor-chain probe + non-JSON-serializable collapse) regress this.

WorkflowAgentResult widens from `string` to `string | object`; the
fast-path (no agentType/model/isolation/schema) is preserved byte-for-byte
to keep P1/P2 zero-overhead.

Tests: 159 workflow-suite tests + 217 adjacent (subagents / syntheticOutput /
agent-override) all green. Real-LLM E2E follow-up planned (mirroring P2's
13/13 qwen3-max validation).

Related #4721 (parent design — multi-phase, not closed by this PR)
Related #4732 (P1 merged) #4947 (P2 merged) #4842 #4996 (declarative agents)

* chore(core): P3 self-review R1 — align worktree suffix wording + 6 test gaps

R1 of pre-push adversarial self-review on PR #5034 surfaced 6 confirmed
findings across 6 diverse lenses (correctness / security / reuse-altitude
/ self-invariant / consumer-breakage / test-gaps). Each finding faced 2
independent skeptics defaulting to refuted=true; 6 survived majority
challenge.

Source code:
- Worktree-preserved suffix wording now matches AgentTool's
  formatWorktreeSuffix (agent.ts:1700-1719) verbatim, including the
  `git worktree add <path> <branch>` recovery hint for the directory-
  removed-but-branch-preserved race.

Test gaps closed:
- schema-mode success after 1 nudge (round-2 args captured)
- schema-mode success after 2 nudges (round-3 args captured)
- schema-mode + agentType together — floor disallowedTools still unioned
- schema-mode caller-abort takes priority over the StructuredOutput
  terminal error (signal.aborted check at workflow-orchestrator.ts:489-490)
- override path dispose() runs in finally on the success path
- override path dispose() runs in finally on the terminate-mode-error path

Declined R1 finding: negative tests for invalid opt types (schema/model/
agentType passed null/number/empty-string). Adding upfront type
validation is scope creep — upstream does not, P1/P2 do not, and the
workflow tool is model-authored where these inputs are extremely
unlikely. Existing AJV / SubagentManager downstream errors are descriptive
enough. Will revisit if R2 makes a stronger case.

166/166 tests pass (workflow suite + adjacent + workflow-orchestrator).
typecheck + lint clean across packages/core, packages/cli,
integration-tests, sdk, webui.

* chore(core): P3 self-review R2 — vm-realm opts revive + error-msg sanitize + 12 tests

R2 of pre-push adversarial self-review on PR #5034. 6 diverse-lens
finders (60 agents, ~2.5M tokens, 24 min) over the R1-fix-applied
code, with 2 independent skeptics defaulting to refuted=true.
12 confirmed survivors after adversarial verify; decisions below.

Security (FIX):
- agent() wrapper in workflow-sandbox.ts now JSON-revives agentOpts
  inside the vm runInContext block BEFORE passing them to the host
  dispatch. Closes a Proxy/inherited-getter escape that P3 introduced
  along with the user-supplied schema object: a script could have
  wrapped agentOpts.schema in a Proxy whose getter ran host-side code
  during SyntheticOutputTool construction / AJV compile. Same
  mechanism as args / parallel-result revival.
- runOverridePath now sanitizes opts.agentType through
  sanitizeForErrorMessage() (control chars → space) before
  interpolation into the "agent type 'X' not found" error message.
  Prevents a model-authored agentType containing CRLF / NUL from
  fragmenting a single-line error across log records / OTLP fields.

Reuse-altitude (FIX):
- Added JSDoc block to WorkflowWorktreeIsolation interface
  documenting each field's role for cleanup.

Test gaps (FIX, 12 new tests):
- agentType control-char sanitization regression
- dispose() runs in finally when subagent.execute throws
- isolation:'worktree' provision error branches (5):
  nested parent / git unavailable / not a git repo / parent dirty /
  createUserWorktree returns failure
- isolation:'worktree' cleanup branches (3):
  removeUserWorktree fails / branchPreserved race / removeUserWorktree
  throws — each preserves the worktree (or branch) with the right
  user-facing suffix
- combinations (2): model + isolation:'worktree' threads model AND
  provisions worktree; schema + isolation:'worktree' returns
  structured payload verbatim (preserved suffix only on string return)

Test infrastructure: vi.mock'd GitWorktreeService at the module level
(partial mock; preserves the existing exports the unrelated
worktreeCleanup.ts depends on) with a per-test beforeEach reset.

Declined R2 findings (kept the R1 line):
- [major] Schema parameter upfront validation: same scope-creep
  decline as R1. Upstream doesn't do it; AJV's downstream error is
  descriptive enough.
- [major] Worktree provision extracted to shared util with AgentTool:
  agreed in principle but out of P3 scope. A separate refactor PR
  should land that with AgentTool maintainers in the loop.

178/178 tests pass (workflow + adjacent suites). typecheck + lint
clean across packages/core, packages/cli, integration-tests, sdk,
webui.

* fix(core): address wenshao R1+R2 review on Workflow P3 (PR #5034)

Round 1 (15:41) + Round 2 (17:24) review from wenshao surfaced 7 inline
findings across schema-mode dispatch correctness, worktree cleanup
coverage, and error attribution. Each fix is paired with a regression
test that was RED before the change landed.

T0 [Critical] Worktree leak when schema setup throws after provision
  workflow-orchestrator.ts: outer try MOVED to start immediately after
  provisionWorkflowWorktree. Previously the try opened only after
  createSchemaConfigOverride / createSchemaModeState / signal listener
  attachment — so any throw in those three (broken MCP server during
  the per-call ToolRegistry rebuild was the trigger wenshao cited)
  orphaned the just-provisioned worktree under .qwen/worktrees/.
  Test: "isolation:'worktree' + schema setup throws → worktree is
  still cleaned up" — simulates createToolRegistry failure during
  createSchemaConfigOverride; asserts removeUserWorktree was called.

T1 [Critical] / T4 [H1] agentType + schema silently dead-ended
  workflow-orchestrator.ts: schema-mode augmented config now (a)
  appends ToolNames.STRUCTURED_OUTPUT to baseConfig.tools when the
  allowlist is restricted (no '*' and doesn't already contain it), so
  prepareTools / getFunctionDeclarationsFiltered doesn't filter
  structured_output out of the subagent's surface; (b) preserves the
  resolved agentType's persona by APPENDING the schema-contract
  instruction block instead of replacing the systemPrompt outright.
  Replace remains only on the ephemeral no-agentType path where
  baseConfig.systemPrompt IS WORKFLOW_SUBAGENT_SYSTEM_PROMPT (schema
  variant is its strict superset; avoids two near-identical prompts).
  Tests: structured_output appears in the allowlist alongside the
  agentType's existing tools; persona prompt is contained in the
  effective systemPrompt.

T2 [Suggestion] / T5 [M1] Parent-abort listener leaked per schema call
  workflow-orchestrator.ts: named listener stored at outer scope,
  removed in the outer finally regardless of how the dispatch ended.
  Previous `{ once: true }` only auto-removed on actual parent abort;
  the happy-path schema dispatch — success capture / 3-failure abort
  fires the CHILD controller without the parent ever aborting — left
  the listener stuck on the per-run signal. With N schema calls per
  workflow N listeners + N child-controller closures accumulated.
  Test: 5 sequential schema dispatches over the same parent signal
  end with zero live listeners.

T6 [M2] Terminate mode misdiagnosed as nudge exhaustion
  workflow-orchestrator.ts: schema path now distinguishes
  terminateMode before attributing failure to schema mode. TIMEOUT /
  MAX_TURNS / ERROR throw the existing "did not complete (terminate
  mode: X)" message that the non-schema path uses. Only the actual
  schema-failure cases produce schema wording, and those are split:
  attempts > 2 keeps the upstream-verbatim "(after 2 in-conversation
  nudges)" wording; attempts === 0 throws an accurate "no validation
  attempt — model produced plain-text content" instead of misleadingly
  citing nudges that never happened. (The existing 0-call test was
  updated to match the new accurate message; the 3-failure test
  retains the verbatim wording.)
  Tests: parametric over TIMEOUT/MAX_TURNS/ERROR asserting "did not
  complete"; companion test pinning the verbatim wording to the
  3-failure path.

T3 [Suggestion] Schema-mode JSON revival sentinel — clarified
  workflow-sandbox.ts: added a block comment documenting that the
  JSON-round-trip + null-on-throw is a SECURITY backstop (errors-as-data
  convention from parallel/pipeline) rather than a contract path —
  unreachable in production schema mode because the host return is
  LLM tool_call args, always JSON-serializable. No behavior change.

Tests: 75/75 orchestrator + 111/111 sandbox/tool/limiter green.
typecheck + lint clean across packages/core and packages/cli.

R1+R2 self-review commits (e1c5ec79c / 62624a994) precede this commit
on the same branch — they predate wenshao's review and address
distinct findings; reviewer L1 (worktree-lifecycle unit coverage) is
already closed by R2's 11 worktree tests.
2026-06-14 03:16:39 +08:00
Yufeng He
0db3273174
fix(cli): submit fast tool results after stream end (#5071) 2026-06-14 02:44:11 +08:00
ChiGao
ce4b0cf629
feat(sdk,serve): DaemonTransport abstraction + ACP standard compliance (#5040)
* feat(sdk): DaemonTransport abstraction — pluggable transport for REST/ACP-HTTP/ACP-WS

- DaemonTransport interface with fetch + subscribeEvents
- RestSseTransport: extract current SSE logic from DaemonClient
- AcpWsTransport: WebSocket multiplexer + URL-to-JSON-RPC mapping
- AcpHttpTransport: POST /acp + session-scoped SSE
- AcpEventDenormalizer: JSON-RPC notification -> DaemonEvent
- AutoReconnectTransport: opt-in reconnect + fallback wrapper
- negotiateTransport(): auto-detect best transport via GET /capabilities
- Provider: DaemonWorkspaceProvider gains transport prop
- Server: GET /capabilities advertises supported transports
- Zero breaking changes: no transport = current REST behavior

Generated with AI

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

* docs(design): include DaemonTransport design doc in implementation PR

* fix(sdk): address 6 verification findings — bundle size, WS hang, error types, ACP compat

- Remove ACP transport class re-exports from barrel (index.ts) to avoid
  ~19.7KB browser bundle bloat; keep type-only exports
- Fix WS dial hang: reject connect promise in onerror when not yet
  connected (Node WebSocket may only fire error, not close)
- Fix parked generators: maintain _activeGenerators set, abort all on
  WS close so generators throw DaemonTransportClosedError
- Forward abort signal through AcpHttpTransport.sendRequest to fetch
- Restore DaemonHttpError in RestSseTransport (was plain Error)
- ACP endpoint compat: extract connectionId from initialize, send
  Acp-Connection-Id header, add _qwen/ prefix for vendor methods,
  preserve real HTTP status in error mapping, fetch /capabilities
  from REST endpoint for correct shape

Generated with AI

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

* fix(sdk): address 16 review findings + CI bundle size

- CI: move negotiateTransport to separate file, extract DaemonHttpError
  to break static import chain from barrel -> DaemonClient. Browser
  bundle drops from 136KB to 115KB, under the 116KB budget.
- Route table: extract shared acpRouteTable.ts, used by both transports.
  Unify method naming (remove _qwen/ prefix inconsistency).
- Token: move from URL query to Authorization header on WS upgrade
- Error type: DaemonHttpError extracted to DaemonHttpError.ts; import
  in RestSseTransport no longer pulls in DaemonClient.
- Init retry: reset failed initPromise so next call retries
- Reconnect mutex: prevent concurrent reconnect storms
- Generator queue: cap at 256, drop-oldest
- WS init timeout: 30s default
- negotiate: clear timer on all paths, catch dispose rejection
- Headers: forward init.headers in ACP transports via mergeHeaders()
- Dead code: remove unused pendingRequests/sseAbort fields
- Provider: dispose client on unmount
- Helpers: extract matchRoute/synthesizeResponse/jsonRpcErrorToHttpStatus/
  isRecord/composeAbortSignals to shared acpTransportUtils.ts
- Package exports: add deep import paths for ACP transports
- Tests: add AcpEventDenormalizer unit tests (17 cases)

Generated with AI

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

* fix(sdk): ESLint array-type rule — ReadonlyArray<T> → readonly T[]

Generated with AI

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

* fix(sdk): fix 3 ACP wire bugs + bundle size + npm exports

Wire bugs (verified broken against real daemon):
1. AcpHttpTransport: read connectionId from response header + correct JSON path
2. AcpWsTransport: send token via Authorization header, not URL query
3. AcpEventDenormalizer: read params.update.sessionUpdate, not params.type

Bundle: remove negotiateTransport from barrel-reachable imports
Exports: add package.json deep import paths for ACP transports

Generated with AI

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

* test(sdk): comprehensive ACP transport test suite (~175 tests)

- RestSseTransport: fetch delegation, SSE subscribe, auth, timeout, signal
- AcpWsTransport: route mapping, token auth, event filtering, queue cap
- AcpHttpTransport: connectionId extraction, header injection, init retry
- AutoReconnectTransport: reconnect mutex, fallback, delegation
- negotiateTransport: capability probing, timeout, fallback
- acpRouteTable: URL→method mapping, param extraction

Generated with AI

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

* fix(sdk): route table coverage, browser WS auth, header forwarding, capabilities type

- Route table: add file/stat/list/glob/write/edit paths (all DaemonClient URLs)
- Route table: add session diagnostic routes (context, tasks, stats, rewind, language)
- Route table: add bulk sessions/delete
- WS auth: document browser limitation, Node uses headers, browser needs proxy
- Headers: forward X-Qwen-Client-Id via JSON-RPC _meta in WS transport
- DaemonCapabilities: add transports field to SDK type
- Package exports: remove unreachable deep exports, document monorepo usage
- Provider bypass: document limitation for glob/stat/list in workspace actions

Generated with AI

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

* fix(sdk): add missing detach + hooks routes per QA doc

Cross-referenced with daemon-acp-integration-qa.md route table.
Added POST /session/:id/detach and GET /session/:id/hooks.

Generated with AI

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

* fix(serve,sdk): enforce ACP standard session/new — always isolated session

ACP standard mandates session/new MUST create a new isolated session.

Server-side (dispatch.ts):
- Force sessionScope='thread' on /acp session/new, ignoring client params
- REST POST /session retains 'single' default for backward compat

SDK-side (acpRouteTable.ts):
- Strip sessionScope from session/new params in ACP transports
- Document that ACP follows the standard (no extensions)

Generated with AI

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

* fix(serve): ACP session/new returns standard models/modes fields

ACP standard NewSessionResponse includes optional `models` and `modes`
top-level fields alongside `configOptions`. Extract model/mode state
from configOptions and surface them as standard-shaped objects:
- models: { currentModelId, availableModels: [{id}] }
- modes:  { currentModeId, availableModes: [{id}] }

Also update test to verify sessionScope is always forced to 'thread'
(ACP standard compliance — session/new always creates isolated session).

Generated with AI

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

* feat(serve): add standard ACP methods session/set_mode, session/set_model, session/fork

Align /acp endpoint with ACP standard protocol:

- session/set_mode: dedicated method for mode changes (standard)
  Maps to bridge.setSessionApprovalMode(). Params: {modeId, sessionId}
- session/set_model: dedicated method for model changes (unstable)
  Maps to bridge.setSessionModel(). Params: {modelId, sessionId}
- session/fork: create a branched copy of an existing session
  Maps to bridge.branchSession(). Response includes configOptions,
  models, modes per ACP standard.
- session/load, session/resume: responses now include configOptions,
  models, modes (per ACP LoadSessionResponse/ResumeSessionResponse)

Generated with AI

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

* fix(serve): TS2345 — pass persist: false to setSessionApprovalMode

Generated with AI

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

* fix(webui): add dispose() to MockDaemonClient in provider tests

DaemonClient now has dispose() (called in provider cleanup effect).
Mock clients in test files need to implement it.

Generated with AI

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

* fix(serve): add sessionId pre-validation + remove type assertion

- session/set_mode, session/set_model: add explicit sessionId empty
  check before requireOwned (consistent with session/fork)
- session/set_model: remove `as unknown as` type assertion, pass
  proper {modelId, sessionId} matching SetSessionModelRequest

Generated with AI

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

* fix(sdk,serve): align route table with dispatcher + AcpHttp SSE response correlation

Route table:
- Add _qwen/ prefix to all vendor session/workspace methods
- Split workspace catch-all into granular dispatcher methods
- Fix session/branch → session/fork, model → session/set_model
- Remove routes with no dispatcher handler

AcpHttpTransport:
- Implement conn-scoped SSE stream for response correlation
- POST returns 202 (ack), real response rides SSE stream
- Map<id, {resolve, reject}> for pending request correlation

dispatch.ts:
- Remove session/set_mode, session/set_model from CONN_ROUTED_METHODS

Generated with AI

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

* fix(sdk): bump browser bundle budget 116KB→118KB for transport abstraction

Main uses 117,753 bytes (99.1% of 116KB budget). The transport
abstraction adds ~1.5KB (DaemonTransport interface + RestSseTransport
default constructor in DaemonClient). Bump to 118KB (120,832 bytes).

Also change RestSseTransport to type-only export from barrel (class
is constructed internally by DaemonClient, not needed as a value
export for consumers).

Generated with AI

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

* fix(sdk): fix 2 test failures — SSE error message + workspace catch-all route

- RestSseTransport: error message 'SSE response has no body' → 'No SSE body'
  (matches existing DaemonClient.test.ts assertion)
- acpRouteTable: re-add GET/POST /workspace/* catch-all after granular routes
  (AcpWsTransport.test.ts expects generic workspace path to resolve)

Generated with AI

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

* fix(sdk): align RestSseTransport test with updated error message

Generated with AI

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

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-14 02:37:06 +08:00
Shaojin Wen
dc6edcd523
feat(web-shell): show time on parallel-agents box and sub-agent tools (#5084)
The message-time-on-hover feature (#5079) wraps each transcript message
with MessageTimestamp, but two sub-agent surfaces were left out and so
looked inconsistent with the rest of the transcript:

- The "Parallel agents · N/N done" box (ParallelAgentsGroup) renders
  directly in MessageList, bypassing MessageItem/MessageTimestamp, so it
  showed no time. Carry the first grouped launch's timestamp onto the
  parallel_agents display item and wrap the box in MessageTimestamp.

- Each sub-tool row inside a SubAgentPanel's Tools list showed no time.
  Wrap each row in a scoped hover tooltip (.toolTimeRow/.toolTimeTip,
  kept separate from MessageTimestamp's .row/.tip so the nested tooltip
  stays independent of the enclosing message's) keyed off the tool's
  startTime.

Both reuse formatTimestamp for an identical HH:mm:ss (or dated) format
revealed on hover in the top-right corner, matching the main transcript.
2026-06-14 00:46:42 +08:00
Yufeng He
c631d3af58
fix(cli): show plan gate failures with full plan (#5077) 2026-06-13 23:29:51 +08:00
tanzhenxin
2ba4ca90ad
feat(core): durable cron jobs — /loop tasks that survive restarts (#5004)
Some checks are pending
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
Persist /loop tasks per-project under ~/.qwen/tmp/<project-hash>/ so they survive restarts; the default stays session-only. Missed one-shots are surfaced at startup confirm-first; overdue recurring jobs catch up once then resume. A per-project lock elects a single firing session across concurrent sessions, with takeover on owner exit. Recurring jobs expire after 7 days (final fire), and never-matching cron expressions are rejected at creation. Durable storage lives in the user runtime dir, not the working tree, so it is never committed or shared via the repo.
2026-06-13 19:30:40 +08:00
jinye
acb0275ecd
fix(serve): Add prompt queue backpressure (#5033)
* fix(serve): add prompt queue backpressure

Add per-session prompt admission limits across the bridge, REST and ACP entrypoints, and SDK clients. The server now rejects full prompt queues before returning accepted semantics, advertises the active limit through capabilities, and documents the behavior with focused tests and design artifacts.

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

* test(sdk): stabilize pending prompt cleanup

Close the mocked SSE stream explicitly in the pending prompt cap test so cleanup does not rely on abort-driven stream cancellation timing in CI.

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

* test(sdk): stabilize subscription prompt race

Reject accepted subscription prompts if the event stream has already ended, and make the prompt-cap tests wait for the pending registration before closing or injecting SSE frames.

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

* fix(sdk): map prompt queue full responses

Map server-side prompt_queue_full responses to DaemonPendingPromptLimitError for both blocking and non-blocking prompt calls, include the session id in the local limit error, and cross-reference the duplicated default prompt cap constants.

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

* test: keep qwen planning docs ignored

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

* fix(serve): address prompt backpressure review

Log synchronous prompt queue rejections, document the sync admission contract, clean up SDK prompt-slot release, and cover the reviewed backpressure edge cases.

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

* fix(sdk): restore daemon bundle budget headroom

Reduce the generated daemon client bundle slightly and raise the browser daemon SDK bundle budget to 116 KiB so the PR merge ref has practical headroom.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-06-13 18:46:01 +08:00
Shaojin Wen
84d01e7070
feat(web-shell): show message time on hover (#5079)
* fix(acp-bridge): preserve original timestamp when replaying session history

History replay re-emits each persisted record with its original epoch-ms time nested in update._meta, but BridgeClient.sessionUpdate published the frame without lifting it to the envelope. EventBus.publish then stamped envelope _meta.serverTimestamp with publish-time Date.now(), which the client's extractServerTimestamp picks up at higher priority than the nested original — so a resumed session rendered every historical message at the resume moment instead of when it was sent.

Lift update._meta.timestamp (or serverTimestamp) to the envelope serverTimestamp so EventBus preserves it. Live updates without such a timestamp keep the Date.now() fallback unchanged.

* feat(web-shell): show each history message's time on hover

Carry each transcript block's wall-clock time (serverTimestamp ?? clientReceivedAt) onto every message and reveal it as a CSS-only hover tooltip in the message list. Same-day messages show HH:mm:ss; older ones show yyyy-MM-dd HH:mm:ss (local time, zero-padded).
2026-06-13 15:30:44 +08:00
ytahdn
aebf82cd29
fix(web-shell): improve slash command panel layering (#5078)
Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-06-13 14:41:48 +08:00
Shaojin Wen
b748ef4b73
feat(web-shell): revamp floating todo panel interactions (#5069)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
The "Current tasks" panel above the composer was a static display:
always expanded, rotate-to-front ordering with jumbled numbering,
no progress summary, and it vanished the instant the last item
completed.

- Collapsible header (persisted in localStorage); collapsed mode is a
  single line showing progress + the current in-progress item
- Progress counter (completed/total) in the header
- Natural-order window anchored on the in-progress item replaces the
  rotation: one completed context line above, pendings below, with
  clickable "N completed" / "N more" summary lines that expand the
  full list (and "Show less" to return)
- All-done moment: a finished list stays visible as "All tasks
  completed" until the next user prompt instead of disappearing
  instantly; historical finished lists stay hidden on session restore
- Locate button scrolls the transcript to the source TodoWrite/plan
  message with a flash highlight (new MessageList imperative
  scrollToMessage, callId fallback for compact-merged tool groups)
- Visual consistency: in_progress uses the accent color, PlanMessage
  adopts the shared icon set, items ellipsize to one line with a
  hover tooltip, and the number column scales past 9 items so the
  status icons stay aligned

getFloatingTodos moves to utils/todos.ts and now reports
{todos, allCompleted, sourceMessageId, sourceCallId}; panel visibility
is a render-time state machine so the active-to-completed transition
does not unmount the panel for a frame. New i18n keys for en/zh-CN
and 17 new unit tests.
2026-06-13 11:51:03 +08:00
ytahdn
c61006b978
feat(web-shell): daemon web-shell improvements — token usage, settings, retry, streaming metrics, hidden commands (#5066)
* feat(web-shell): daemon web-shell improvements

- Align daemon token usage with structured DaemonTokenUsage type
- Optimize settings panel with i18n, theme/language pickers, compact mode
- Handle missing session recovery (404/410) with configurable behavior
- Restore settings event signal bump for workspace changes
- Prevent queued prompt loss on useEffect dependency change
- Align streaming loading indicator with CLI metrics logic
- Add Ctrl+Y retry for turn_error with daemon support
- Hide non-essential UI elements on narrow screens (≤700px)
- Prevent loading indicator flicker on page refresh
- Hydrate displayName from persisted session title on load

* fix(web-shell): harden retry affordance

* fix(web-shell): gate retry handling

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-06-13 02:58:08 +00:00
Yufeng He
66c69865c7
fix(core): preserve background agent launch flags (#5061)
Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
2026-06-13 08:51:34 +08:00
qqqys
44627a24be
feat(mcp): project .mcp.json + workspace approval gating with aligned scope precedence (#4615) (#4713)
* feat(mcp): project .mcp.json + workspace approval gating with aligned scope precedence (#4615)

Adds untrusted-source approval gating for MCP servers and a coherent
cross-source precedence model.

Sources & precedence (low -> high):
  user/default settings < project .mcp.json < workspace/system settings < session(ACP/IDE) < --mcp-config

- Load project servers from .mcp.json (pure read, never connects), tagged
  scope:'project'.
- Tag workspace/system settings servers with provenance scope at merge time so
  the winning entry keeps its source; centralize assembly in assembleMcpServers.
- Gate checked-in/shareable sources (project + workspace) behind a hash-bound
  approval store; .mcp.json edits revert approval to pending. system/user/CLI/
  extension/session sources are never gated.
- .mcp.json now overrides USER settings (Claude parity) but never enterprise
  'system' settings.
- Route ACP/IDE-injected servers through a top-tier sessionMcpServers param so a
  repo .mcp.json can't override or gate them.
- Startup approval dialog + 'qwen mcp approve|reject' + 'qwen mcp list' cover
  both gated sources; non-interactive sessions auto-approve (lenient).

Co-Authored-By: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): cover mcp scope stamping

* fix(mcp): harden approval binding

* fix(mcp): harden approval store persistence

* fix(mcp): address approval review feedback

* refactor(cli): rename gated MCP approval helper

* fix(mcp): surface approval metadata in prompts

* docs(mcp): clarify pending approval snapshot

* fix(mcp): persist prototype-named approval records

* test(mcp): cover pending approval guard paths

* chore(mcp): refresh approval gating checks

* fix(mcp): enforce approval gate outside interactive

* fix(cli): label MCP server sources accurately

* fix(cli): include project MCP servers in reconnect

* docs(cli): correct MCP approval noninteractive note

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-13 08:23:33 +08:00
Shaojin Wen
098367a485
refactor(web-shell): remove duplicate agents panel, contain SubAgent views (#5059)
* refactor(web-shell): remove duplicate agents panel, contain SubAgent views

- Remove ActiveAgentsPanel above the status bar: the SubAgentPanel in
  the message history is mouse-operable and shows the same data. Drop
  its focus chain (EditorHandle.blur, onFocusActiveAgents renamed to
  onFocusFooter) and the activeAgents i18n strings.
- Cap completed SubAgent results and the Tools tab in 400px scroll
  windows (same cap as the expanded live stream) so a panel never
  grows past one screen; the tools window follows the newest call
  while running and snaps back to the top on completion. Gated on
  compactThinking, so hosts without it are unchanged.
- Reuse the transcript ToolLine for sub-tools so they collapse and
  expand with the same detail views (bash output, diffs, file
  content) instead of a fixed one-line summary.

* fix(web-shell): drop duplicate total from parallel-agents header

The header rendered "Parallel agents · 9 · 9/9 done" — the standalone
total repeats the denominator already shown in the done counter. Keep
"Parallel agents · 9/9 done".
2026-06-13 08:23:20 +08:00
Yufeng He
662197e3fb
fix: enable fork subagents by default (#4963)
* fix: enable fork subagents by default

* fix: respect fork subagent config flag
2026-06-13 08:21:14 +08:00
qqqys
f5e512e6e6
feat(serve): deliver A2UI surfaces over MCP — bridge extraction and action endpoint (#4961)
* feat(serve): A2UI over MCP — bridge extraction and action endpoint

Deliver Google A2UI (v0.9) surfaces from MCP tool results to web clients,
with zero changes to core / ACP schema / tool registry:

- acp-bridge BridgeClient.sessionUpdate: detect a2ui UI-server tool results
  (by _meta.serverId containing "a2ui", tool-name fallback), extract the
  leading A2UI command array (core flattens EmbeddedResource to text and
  drops the application/a2ui+json mime), publish a separate
  sessionUpdate:'a2ui' frame {surfaceId, callId, commands} onto the event
  bus (journal/replay included), and sanitize the original tool frame so
  raw command JSON never reaches transcripts.
- serve POST /session/:id/a2ui-action: proxy user actions on A2UI surfaces
  to the UI MCP server's standard `action` tool (per the official
  A2UI-over-MCP guide). UI server discovery prefers the live workspace MCP
  status (covers runtime-registered servers) and falls back to workspace
  settings; stdio and streamableHTTP transports supported. Continuation
  frames are returned synchronously as {commands, fallback}.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(serve): address review — robustness, error mapping, and tests for A2UI

Review fixes for #4961:

- connect/callTool now both carry a 15s timeout so an unresponsive UI MCP
  server cannot hang the HTTP request.
- callTool results with isError are surfaced as failures (502) instead of a
  200 with null commands; the error detail is logged server-side only and the
  client receives a generic message (no internal paths/URLs leak).
- transport.close() is called alongside client.close() so a stdio child
  spawned by a half-failed connect cannot leak.
- stdio env is merged over process.env (spawn treats env as a full
  replacement; a partial env would strip PATH/HOME), matching mcp-client.ts.
- array-valued `context` bodies are rejected instead of forwarded; settings
  fallback is async (fs/promises); dead `url` config field removed (legacy
  SSE intentionally unsupported); license header aligned.
- multi-surface tool results are now split into one a2ui frame per surface
  (first-appearance order) instead of publishing only the first surfaceId;
  multiple a2ui+json blocks keep explicit first-wins semantics.
- extraction/detection helpers are exported and covered by unit tests
  (22 cases: balanced-array parser edge cases incl. nested arrays/escaped
  quotes/unbalanced brackets, detection by serverId/tool name, grouping,
  sanitization, endpoint validation/discovery/fallback/error mapping).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(serve): cover A2UI action result extraction

* fix(acp): sanitize unrecognized a2ui updates

* test(serve): cover a2ui action transport lifecycle

* test(core): import mocked session root context

* test(acp-bridge): cover A2UI session update publishing

---------

Co-authored-by: 衍星 <qiuyusheng.qys@alibaba-inc.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 07:52:09 +08:00
yao
8000342667
fix(core): remove unused debugResponses array and dead extractUsageFromGeminiClient (#4982) 2026-06-13 07:51:19 +08:00
Yufeng He
92c4a82390
fix(memory): avoid stale tool schema recall (#5058)
* fix(memory): avoid stale tool schema recall

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* fix(memory): seed resumed tool recall context

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

---------

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
2026-06-13 06:39:10 +08:00
jinye
2be01a104e
fix(daemon): Sanitize logs and type MCP restarts (#5006)
* fix(daemon): Sanitize ACP delete logs and type MCP restarts

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

* test(daemon): Cover PR review edge cases

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

* fix(web-shell): Show MCP restart entry failures

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

* fix(daemon): Harden ACP delete log sanitization

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

* fix(daemon): Fix ACP log sanitizer lint

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-13 06:23:20 +08:00
kkhomej33-netizen
3a224d1efe
feat(skills): support user-invocable frontmatter (#5037) 2026-06-13 06:09:23 +08:00
tanzhenxin
d3cded95f7
chore: sync package-lock.json with packages/cli ws dependencies (#5023)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
2026-06-13 05:42:32 +08:00
Yufeng He
233e8e0caa
feat(core): let grep results satisfy prior-read checks (#5043) 2026-06-13 05:40:09 +08:00
qqqys
4ae788623e
feat(core,cli): bubble background subagent permission prompts to the parent session (#4955)
* feat(core,cli): bubble background subagent permission prompts to the parent session

Background subagents auto-deny any tool call that needs interactive confirmation, so a single permission-gated step (a git push, an rm, a network call) silently fails and the work bounces back to the parent turn — defeating the point of backgrounding. This adds an opt-in approvalMode value for subagent definitions, `bubble`: instead of denying, the call is parked on the BackgroundTaskRegistry and surfaced in the Background tasks dialog, where the user answers it through the shared ToolConfirmationMessage; the agent then resumes.

- `bubble` is a subagent-only approvalMode (deliberately not a session-level ApprovalMode value); it resolves to `default` run behavior and only flips the background path from deny to surface, in interactive sessions. Headless / non-interactive contexts keep auto-deny.
- BackgroundTaskRegistry grows a parked-approval queue (add/resolve/clear), an approval-change callback, and an event bridge (TOOL_WAITING_APPROVAL parks, TOOL_RESULT clears stale prompts). Every terminal transition auto-rejects parked calls so the agent loop never hangs on an unanswerable prompt; cancel() rejects before aborting so respond(Cancel) actually fires ahead of the abort-driven queue clear. Auto-reject failures are caught on the promise, not via try/catch around a voided async call.
- The launch path (agent.ts) and the resume path (background-agent-resume.ts) share the same gate, so a resumed agent of the same definition keeps bubbling instead of silently reverting to auto-deny.
- TUI: the footer pill shows a "needs approval" marker, dialog list rows are flagged, and the detail view embeds the confirmation prompt. While a prompt is up, left (back) and x (stop agent) remain available as escape hatches so a re-parking agent cannot trap the keyboard.

Closes #4928

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

* fix(i18n): add zh-TW translations for background approval strings

check-i18n requires every zh key to have a zh-TW counterpart; the three
strings added for permission bubbling were registered in en/zh only.

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

* fix(core): harden background approval edge cases from review

- resolvePendingApproval: if respond() rejects, the tool call is still parked in the scheduler, so re-add the approval and re-emit instead of silently clearing the prompt (which left the UI showing nothing pending while the agent hung). Returns false on failure.
- reset() and finalizeCancellationIfPending(): reject parked approvals defensively. The /resume and /clear paths already gate on hasBlockingBackgroundWork() so these only run on terminal entries today, but rejecting here means a future caller dropping that guard can't strand an unanswered respond() callback.
- resolveSubagentApprovalMode: resolve the subagent-only 'bubble' mode to Default explicitly rather than via approvalModeToPermissionMode's default fall-through, so a future ApprovalMode.BUBBLE enum member can't silently change it.

Adds tests for the fail / finalizeCancelled / reset auto-reject paths and the respond()-rejection re-park.

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

* fix(core): include args in approval test events

* test(core): update nested yaml parser expectations

* fix(cli): reuse selected background agent id

* fix(core): fail consumed background approval retries

* fix(core): prevent persistent bubbled approvals

* fix(core): harden bubbled approval handling

* fix(core): cover background approval edge cases

* fix(cli): isolate bubbled question approval keys

* fix(cli): localize background approval labels

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-13 01:50:26 +08:00
Dragon
7cb95bebbd
fix(docs): update Coding Plan model list and fix stale references in developer docs (#5054)
* fix(docs): update Coding Plan model list and fix stale references in developer docs

- model-providers.md: expand Coding Plan model table from 3 to 9 models (add qwen3.6-plus, glm-5, kimi-k2.5, MiniMax-M2.5, qwen3-coder-next, glm-4.7) to match the provider registry
- auth.md: update Coding Plan model list to match all 9 models and use the correct date-stamped model ID (qwen3-max-2026-01-23)
- contributing.md: fix Node.js version requirement from 18+ to 22+ to match package.json engines
- sdk-typescript.md: fix nonexistent tool name run_terminal_cmd → run_shell_command
- integration-tests.md: fix file extension from .test.js to .test.ts to match actual test files
- qwen-serve-protocol.md: fix legacy tool name search_file_content → grep_search and remove nonexistent ripgrep tool reference

* fix(docs): add missing qwen3.7-plus to Coding Plan model docs

MODELSTUDIO_MODELS in alibaba-coding-plan.ts defines 10 models, but the
inline list in auth.md and the table in model-providers.md only listed 9,
omitting qwen3.7-plus (1M context, thinking enabled). Add it after
qwen3.6-plus in both, matching the source order.
2026-06-13 01:34:14 +08:00
jinye
a99b01b020
feat(core): persist oversized tool results to disk (#4095 Phase 4) (#5042)
* feat(core): persist oversized tool results to disk (#4095 Phase 4)

Large tool outputs (>28K chars) are now saved to disk as
tool-results/<callId>.txt and replaced with a 2KB preview stub
in the LLM context, preventing OOM and context pollution.

Key mechanics:
- Triple-skip gate: read_file exempt → already-truncated skip → threshold+3K headroom
- Budget: 50MB per-file cap, 500MB per-session cumulative (Buffer.byteLength)
- Security: atomicWriteFile with mode 0o600, noFollow, forceMode; path.basename sanitization
- Cleanup: 24h expiry on startup and /clear
- Error branch: large stderr also persisted
- Fallback: budget exhausted → preview-only stub; write failure → legacy truncateAndSaveToFile

* fix(core): suppress no-control-regex lint for null-byte sanitization

The \x00 regex is intentional security hardening to strip null bytes
from callIds before using them as filenames.

* fix(core): address wenshao review round 1

- Fix mock Config in coreToolScheduler.test.ts: add getToolResultBytesWritten,
  trackToolResultBytes, and storage.getToolResultsDir to all 4 mock instances
- isAlreadyTruncated: change includes to startsWith for <persisted-output>
  to avoid false positives from tool output containing the literal string
- Remove dead code: recalcContentLength function and its call site
  (contentLength is unconditionally overwritten downstream)
- buildStub: non-persisted stubs no longer use <persisted-output> tag
  to avoid misleading model into wasted read_file calls
- Add GATE_HEADROOM rationale comment

* fix(core): update prompts.test.ts snapshots for persisted-output guidance

The new <persisted-output> model guidance added to prompts.ts changed
the system prompt output, requiring snapshot updates.
2026-06-13 01:27:13 +08:00
jinye
a283ca0479
fix(telemetry): Propagate daemon ACP trace context (#5047)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-13 00:11:15 +08:00
jinye
a064779e2e
feat(daemon): gate direct session shell behind explicit opt-in (#5031)
* feat(cli): gate direct session shell execution

* fix(cli): address session shell review feedback

* codex: address PR review feedback (#5031)
2026-06-12 23:07:51 +08:00
qwen-code-ci-bot
e66281590d
chore(release): v0.18.0 [skip ci]
* chore(release): v0.18.0

* docs(changelog): sync for v0.18.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-12 22:59:56 +08:00
易良
c2962eef73
fix(release): allow fzfWorker.js in standalone dist allowlist (#5049)
esbuild emits dist/fzfWorker.js as a standalone entry next to cli.js, but create-standalone-package.js's DIST_ALLOWED_ENTRIES did not list it, so 'Build Standalone Archives' failed with 'Unexpected dist asset'. prepare-package.js already whitelists it for the npm tarball; this syncs the standalone packer.
2026-06-12 14:12:57 +00:00
qqqys
78f063517a
feat(acp): broadcast session title updates to daemon clients (#5035)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* feat(acp): broadcast session title updates to daemon clients

* test(cli): update session worktree chat recorder mock
2026-06-12 19:39:03 +08:00
tanzhenxin
fa684552b0
fix(test): unbreak qwen serve integration suites after daemon batch merge (#5041)
Three integration tests have failed every nightly Release and E2E run
since the daemon-mode feature batch (#4490) merged, because these
suites only run post-merge:

- routes: resync the capabilities envelope baseline with the features
  the batch added (verified against a live daemon), and strip the env
  toggles that flip conditional tags so the exact-equality assertion
  is hermetic on dev machines.
- baseline: the 2xN MCP grandchildren tripwire fired as designed —
  the workspace MCP pool eliminated the bootstrap/session duplicate
  discovery. Assert exactly N pooled children and cross-check the
  pool's per-server accounting against pgrep.
- streaming: the permission test could finish with its turn still
  blocked on a second permission request nobody would ever answer;
  the abandoned request wedges the shared session's prompt FIFO and
  the downstream Last-Event-ID resume test times out waiting for a
  turn_complete that never comes (reproduced empirically). Pin the
  session to default approval mode (hermetic vs host user settings)
  and cancel the possibly-in-flight turn before finishing.

The daemon-side wedge (abandoned permission request blocks the FIFO
until an explicit cancel) is real beyond tests and tracked separately.
2026-06-12 19:22:23 +08:00
贲冠然
e07d069720
fix(stats): dedup usage records by sessionId and skip in-progress writes (#4995)
* fix(stats): dedup usage records by sessionId and skip in-progress writes (#4994)

Opening /stats during the first-ever turn followed by /clear (or exit) used
to write the same sessionId twice into ~/.qwen/usage_record.jsonl, permanently
inflating every aggregate (sessions / tokens / durations / tools / heatmap /
projects) 2x for that session. Closes #4994.

Defense in depth:
- Read side: loadUsageHistory dedups records by sessionId (last-wins), so any
  duplicates already on disk from this bug stop inflating future aggregates.
- Write side: rebuildFromSessionJsonl skips the in-progress session when its
  sessionId is passed in; statsDataService threads currentSession.sessionId
  through. New duplicates are no longer created at the source.

Regression coverage in usageHistoryService.test.ts mirrors the exact bug
sequence (open /stats during first turn -> /clear -> re-open /stats) and
asserts sessionCount=1, totalTokens=1600 for a single ~1.6k-token session.

* fix(stats): address PR review — log dedup count and rename rebuild-skip param

Why:
- dedupBySessionId silently dropped duplicate records, making it
  impossible to observe how many users were affected by the #4994 bug.
  Now logs the removed count at debug level.
- The new loadUsageHistory parameter was named currentSessionId but only
  controls the write-side skip during rebuildFromSessionJsonl — the read
  path ignores it. Renaming to skipSessionInRebuild makes the limited
  scope explicit; statsDataService still strips/re-pushes the live
  current session for defense-in-depth.

PR review feedback on #4995.
2026-06-12 18:51:58 +08:00
易良
9895decdbe
test(i18n): raise timeout for slow must-translate locale suites (#5024)
The must-translate locale coverage tests switch locales and build the full
built-in command tree (loadCommands runs twice per strict-parity locale),
which triggers dynamic locale imports plus command construction. On cold
Windows CI runners this intermittently exceeds vitest's default 5s per-test
budget and times out (zh-TW / zh-CN strict-parity cases), while ubuntu and
macOS pass. The test logic is unchanged.

Give the three locale-iterating it.each blocks an explicit 20000ms timeout,
matching the convention already used by the sibling i18n suite
(index.test.ts).
2026-06-12 17:56:56 +08:00
callmeYe
bc2a5cfbb7
fix(core): support .toml command files in extension command discovery (#5017)
* fix(core): support .toml command files in extension command discovery

loadCommandsFromDir only globbed for **/*.md, causing extensions like
caveman that ship .toml commands to have their commands silently ignored
during installation and loading. The CLI-layer FileCommandLoader already
supports both formats, but the core discovery function did not.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(extension): address CR comments on .toml command discovery

- Merge two separate glob calls into single **/*.{md,toml} pattern
- Fix Windows path separator regression: use /[/\\]/ instead of path.sep
- Remove Set dedup in loadCommandsFromDir so consent UI shows true count

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(extension): add colon sanitization test for command names

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(extension): add ENOENT branch coverage for missing commands directory

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-12 17:54:28 +08:00
tanzhenxin
4363d58758
fix(core): serialize team task claims per agent and add mailbox lock parity (#4981)
The auto-claim busy-check had a TOCTOU: claimTask read isAgentBusy before
taking the per-task lock, and the racing claims (scanIdleAgentsForTasks vs a
message flush, for the same idle agent) held different task locks — so moving
the check inside the per-task lock couldn't close it. Both passed the stale
read and the agent ended up owning two in_progress tasks, breaking the
one-task-per-agent invariant the UI and auto-claim rely on.

Add a per-agent claim mutex (keyed by agentId) around the busy-check + claim so
the second claim observes the first's committed write and bails. Distinct
agents never block each other; the loser refuses on its next iteration.

Separately, bring tasks.ts to parity with mailbox.ts's locking: an in-process
per-file mutex (withTaskFileLock) wraps every task-file lock site so
same-process writers queue in memory instead of stampeding the OS lockfile
(the Windows ELOCKED cause — most acute when up to MAX_TEAMMATES claimants race
the same first-pending task), plus randomize:true jitter on the retry backoff.
Acquisition order is always agent-mutex → file-mutex → OS lock; only claimTask
nests the two, and dependency cycles (which the reverse order would need) are
rejected, so no deadlock.

Also give the reciprocal edge-mirror writes a RECIPROCAL_CALLER sentinel
instead of an empty callerName, so the intentional ownership-guard bypass is
greppable; it can't collide with a sanitized [a-z0-9-] agent name.

Regression tests cover the per-agent serialization (mutation-verified: fails
with double-ownership when reverted), cross-agent same-task contention, and the
sentinel bypass.
2026-06-12 17:50:15 +08:00