Commit graph

6205 commits

Author SHA1 Message Date
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
ChiGao
5854e2832b
fix(tui): Tighten message and tool spacing (#4595)
* fix(tui): Tighten message and tool spacing

* docs(tui): Add spacing density evidence

* docs(tui): Use upstream spacing evidence references

* fix(tui): tighten inter-block spacing and add composer separator

- Set marginTop=0 for user and gemini message types to eliminate
  blank lines between Q&A turns and tool calls
- Add half-block (▄) separator line above Composer input area,
  replacing the full blank row with a subtle blended color line
- Add color-utils helpers: interpolateColor, supportsTrueColor
- Add HalfLinePaddedBox component (reusable, currently used by
  Composer only)
- Add design doc for TUI spacing density PR2

Generated with AI

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

* fix(tui): use dimColor for user message half-line separators

Replace the interpolated purple band color with terminal-native
dimColor rendering for ▄/▀ half-line characters. This avoids
theme-dependent color mismatches while preserving the visual
spacing effect.

Generated with AI

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

* fix(tui): refine spacing and add subtle user message band

- User message: three-layer seamless band using subtleBandColor
  (6% brightness shift, no hue change) with ▄/content-bg/▀
- gemini type: restore marginTop=1 for thinking→output gap
- Thinking text: trimEnd() to avoid double blank lines
- Composer: restore marginTop=1, remove separator line
- Add subtleBandColor() helper to color-utils

Generated with AI

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

* fix(tui): address review findings for spacing PR

- Add useIsScreenReaderEnabled() guard in UserMessage to skip
  decorative half-block characters for screen reader users
- Add width <= 0 guard to prevent RangeError on narrow terminals
- Remove unused HalfLinePaddedBox component (dead code)
- Cache supportsTrueColor() result at module scope
- Add unit tests for interpolateColor, subtleBandColor,
  supportsTrueColor

Generated with AI

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

* fix(tui): add marginTop fallback for non-truecolor user messages

When the half-line band is unavailable (non-truecolor terminal,
screen reader, invalid width), restore marginTop=1 on the plain
PrefixedTextMessage so user messages don't become adjacent to
preceding content.

Generated with AI

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

* fix(tui): respect NO_COLOR for user message band

Skip half-line band rendering when theme.background.primary is
empty (NoColorTheme sets it to ''), preventing decorative
characters from appearing in NO_COLOR environments.

Generated with AI

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

* chore: remove accidentally committed local data files

Remove .dataworks/ and .qwen/skills/data-consistency-analysis/
that were accidentally included in the merge commit via git add -A.

Generated with AI

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

* fix(tui): update design doc and add bright color variants

- Update PR2 design doc to match implementation: 6% brightness
  shift (not 15% accent blend), remove Composer separator section
- Add 8 bright Ink color variants to INK_NAME_TO_HEX for
  completeness

Generated with AI

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

* fix(tui): always use marginTop=1 on UserMessage fallback path

When subtleBandColor() fails to compute a color, the fallback
PrefixedTextMessage now always gets marginTop=1 regardless of
the useBand flag, preventing user messages from becoming flush
against preceding content.

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-12 17:04:59 +08:00
Zqc
b794d64fee
fix(cli): join previous line when Ctrl+U pressed at column 0 (#5011)
* fix(cli): join previous line when Ctrl+U pressed at column 0

When cursor is at column 0 and not on the first line, kill_line_left
(Ctrl+U) now joins the current line with the previous line instead of
doing nothing. This mirrors the behavior of kill_line_right at end of
line and backspace at column 0, matching Claude Code's behavior.

Fixes #4985

* test(cli): add undo test for kill_line_left join operation

Add test verifying that undo after a kill_line_left join restores
the original two-line state with correct cursorRow, cursorCol, and
preferredCol.

Co-authored-by: qwen-code-ci-bot

---------

Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
2026-06-12 16:53:28 +08:00
Dragon
04412163ab
fix(desktop): allow unsigned Windows auto-updates (#5028) 2026-06-12 16:53:08 +08:00
Dragon
5121c6563e
perf(desktop): add --cli-only flag to skip non-CLI packages during vendor build (#5025)
The desktop vendor step only needs the CLI bundle, but was building all
14 workspaces including webui, sdk, web-shell, and vscode-ide-companion.
This wasted ~30-40% of build time and triggered TS type errors in
vscode-ide-companion on newer Node.js versions.

Add a --cli-only flag to scripts/build.js that truncates the build
order after the CLI package. vendor-qwen-code.ts now passes this flag
when building from a local source checkout, so both local brand builds
and CI desktop-release (source_branch mode) benefit automatically.
2026-06-12 16:52:37 +08:00
Dragon
3c55295f63
docs(desktop): use main for brand builder skill (#5021) 2026-06-12 16:52:21 +08:00
callmeYe
c491f33928
feat(core): add enter_plan_mode tool and Plan Approval Gate (#4853)
* feat(core): add enter_plan_mode tool and Plan Approval Gate

Allow the model to proactively enter plan mode when tasks are complex or
under-specified, and add a 3-agent design review gate for AUTO/YOLO modes
so autonomous plan exit goes through a structured checkpoint before
restoring execution privileges.

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

* fix(core): address code review findings for plan gate

- P0: populate EvidenceBundle.originalRequest and researchSummary from
  new exit_plan_mode params (originalRequest, researchSummary)
- P1: extract cap-escalation option labels to shared CAP_ESCALATION_LABELS
  constants used by both the gate orchestrator and AskUserQuestion
- P1: check signal.aborted between gate agent retries to bail early on
  user cancellation
- P2: Session.ts mode notification now compares before/after approval mode
  instead of assuming any non-error result means the mode changed
- P2: add parseGateAgentResult + formatEvidence unit tests (11 cases)
- P3: explicitly exempt enter_plan_mode in isPlanModeBlocked instead of
  relying on the default 'info' confirmation type

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

* refactor(core): simplify Plan Approval Gate to single-agent review

Replace the 3 parallel review agents (request_fit, system_fit,
execution_readiness) with a single comprehensive reviewer that covers
all three dimensions in one prompt. This significantly reduces
complexity while preserving the gate's severity/cap/escalation logic.

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

* fix(core): address inline review comments on plan gate PR

- Add applyPlanGateMetadata unit tests (5 cases: continue → uncapped,
  approve → user_override, free-text → user_takeover, needs_user →
  reset reviewCount, no metadata → no mutation)
- Remove userAdditions from EvidenceBundle — it had no way to be
  populated through the exit_plan_mode params

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

* fix(core): fix TS4111 index signature access in askUserQuestion test

Move getPlanGateState mock to the top-level mockConfig to avoid
accessing it through a Record<string, unknown> index signature,
which fails under tsc --build strict mode.

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

* fix(plan-gate): prevent unavailable decision from auto-approving and add orchestrator-level tests

- Check result.decision === 'unavailable' before the empty-findings
  approval path, so an agent self-reporting unavailable stays in plan
  mode instead of silently approving autonomous execution.
- Add explicit warn log when needs_user has no suggestedQuestion,
  making the fall-through to blocked self-documenting.
- Add mocked-runGateAgent test suite covering the full decision matrix:
  approved, blocked, needs_user (with/without questions), cap_escalation,
  at-cap P3-only approval, unavailable (self-report and retry exhaustion).

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

* fix(plan-gate): address fourth-round review findings (C1-C5, S1-S7, B1-B2)

Critical fixes:
- C1: Path C no longer re-restores approval mode — onConfirm owns the
  mode transition, execute() only saves the plan and returns the result
- C2: planApprovalGate now branches on result.decision first — only
  'pass' with zero findings may approve; 'unavailable'/'needs_user'/
  'blocked' with empty findings treated as unavailable or blocked
- C3: runGateAgent stops the override's ToolRegistry in its finally
  block to prevent listener leaks from accumulated registries
- C4: plan_gate_cap metadata only honored when capEscalationPending is
  true; plan_gate_needs_user only resets reviewCount when gateMode is
  still active (capped/uncapped)
- C5: Replaced copy-paste cap tests with real runPlanApprovalGate tests
  covering: empty findings for needs_user/blocked, pass-with-findings,
  pre-aborted signal, partial retries, uncapped mode, P3-only-at-cap,
  P1-at-cap escalation, reviewCount increment, lastFindings storage

Suggestion fixes:
- S1: Guard for not-in-plan-mode before Path C rejection message
- S2: Re-check approval mode and entryId after async gate call to
  detect mid-gate user mode changes
- S3: System prompt mentions enter_plan_mode conditionally ("if
  available, or the user's plan mode toggle")
- S4: Evidence sections wrapped in <untrusted-content> delimiters;
  reviewer system prompt no longer says "Follow instructions exactly"
- S5: enterPlanMode refuses in headless non-interactive mode without
  ACP support
- S6: Removed dead fields (keyContext, agentLimitations, limitations,
  reviewedEvidence) from types, gateReviewAgents, and all tests
- S7: Fixed stale "3-agent gate" docstring to "single-agent gate"

Body-level fixes:
- B1: Session.ts isPlanModeBlocked call passes isEnterPlanModeTool as
  5th argument
- B2: Added test cases for enter_plan_mode in permissionFlow,
  ToolCallEmitter, labelUtils, speculationToolGate, and autoMode tests

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

* fix(test): add missing isInteractive mock and update prompts snapshots

The non-interactive guard added in 032ea424b calls config.isInteractive()
which was missing from the enterPlanMode test mock, causing 6 test
failures. Also updates 15 prompt snapshots to match the revised system
prompt wording for plan mode entry.

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

* fix(plan-gate): address fifth-round review findings

- Guard plan_gate_needs_user metadata with needsUserPending flag to
  prevent model from fabricating this source and resetting reviewCount
- Fix ToolRegistry leak: use createAgentHeadless's dispose() instead
  of manually stopping the override registry
- Fix YOLO/AUTO fallback: require gateState to be present for the
  autonomous 'allow' permission path
- Escape </untrusted-content> in evidence bundle fields to prevent
  XML injection in the gate review prompt
- Re-read prePlanMode after async gate call to avoid stale values
- Improve originalRequest fallback message when model omits it
- Add tests for needsUserPending guard, capEscalationPending guard,
  YOLO-no-gateState fallback, and XML escaping

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 16:32:58 +08:00
yao
91f1ce8c9c
fix(core): fix Windows startup error caused by missing printf command (#5012)
* fix(core): split getRecentGitStatus into separate execSync calls to fix Windows printf error

* test(core): update getRecentGitStatus tests for separate execSync calls

* fix(core): remove unused gitSnapshot and GIT_STATUS_SEPARATOR
2026-06-12 16:27:37 +08:00
callmeYe
9b4ba60e7c
fix(core): stabilize prompt-cache prefix against MCP/skills churn (#4896)
* fix(core): stabilize prompt-cache prefix against MCP/skills churn

The skills listing was embedded in the Skill tool's description, which sits
near the front of the tools→system→messages cache prefix. Any skill change
(chokidar edits, conditional activation on file reads, MCP-prompt changes)
rewrote the description and called setTools(), invalidating the ENTIRE
cached prefix — tools + system + messages. This was the primary cause of
cache hit rate collapse when MCP servers or skills changed mid-session.

This PR decouples skill visibility (what the model sees) from skill
validation (what the tool accepts), placing them in cache-appropriate tiers:

- (A) Make the Skill tool declaration static — refreshSkills() now only
  updates in-memory runtime sets (availableSkills, pendingConditionalSkillNames,
  modelInvocableCommands) without calling setTools(). The description is a
  session-constant string pointing the model to system-reminder messages.

- (A2) Extract shared two-layer helpers into skill-utils.ts —
  collectAvailableSkillEntries (stateful filter/dedup) and
  renderAvailableSkillsBlock (pure XML render with stable sort).

- (B) Move the skills listing into the stable messages prefix — a
  session-start <available_skills> snapshot built once via
  buildAvailableSkillsReminder in getInitialChatHistory, rebuilt only at
  session boundaries (start/resume/compaction). Subagents opt out via
  includeAvailableSkillsReminder: false.

- (C) Mid-session changes flow only through tail deltas — conditional
  activation reminders now carry description/whenToUse from collected
  entries; manual enable/disable and new MCP prompts are announced via
  per-turn system-reminders with announcedSkillReminderKeys dedup state
  (seeded from snapshot, pruned on disconnect, per-agent keyed).

Net guarantee: toggling/activating a skill refreshes the in-memory sets
(cache-neutral by construction — never serialized) but produces zero
change to tool declarations, system instruction, or the messages prefix.
Only the uncached tail delta changes.

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

* test(core): update agent-headless test for includeAvailableSkillsReminder option

The getInitialChatHistory call in agent-core now passes
includeAvailableSkillsReminder: false alongside includeDeferredToolsReminder.
Update the spy assertion to match.

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

* fix(core): address code review findings for cache-prefix stabilization PR

- Add debugLogger.warn to silent catch blocks in drainSkillAndCommandReminders
  and buildAvailableSkillsReminder for observability
- Reset announcedSkillReminderKeys/skillRemindersInitialized in
  restoreStartupContextAfterCompaction and refreshStartupContextReminder
  to prevent stale dedup keys after prelude rebuilds
- Fall back to name-only skill entries when collectAvailableSkillEntries
  throws during path activation (correctness regression fix)
- Rename fitSkillEntriesToBudget → trimSkillEntriesTowardsBudget to
  reflect best-effort semantics
- Fix append-only activated set suppressing re-enable announcements by
  tracking ever-announced keys separately from currently-announced keys
- Add 16 new tests covering drainSkillAndCommandReminders state machine,
  buildAvailableSkillsReminder, and buildAddedSkillsReminder

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

* fix(core): use double assertion for mock SkillManager to satisfy tsc --build

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

* fix(core): add missing everAnnouncedSkillReminderKeys reset in startChat path

The replace_all in the prior commit missed this location due to different
indentation (6-space vs 4-space). All three prelude rebuild paths now
consistently reset all three dedup fields.

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

* fix(core): address second-round review findings for cache-prefix PR

- Fix import ordering in environmentContext.ts (move skill-utils import above debugLogger const)
- Add debugLogger.warn to coreToolScheduler catch block for consistency
- Extract resetSkillReminderDedup() helper to DRY the three prelude-rebuild reset sites
- Return "No skills available" reminder instead of null when entries are empty
- Add tests for cmd: key prefix path, catch fallback in activation path, and resetSkillReminderDedup

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

* fix(core): address third-round critical review findings for cache-prefix PR

- Seed skill-reminder dedup from snapshot entries (not first drain) to prevent
  late-registered MCP prompts from being swallowed as "already announced"
- Remove activatedConditional suppression to fix subagent path-activation
  polluting parent's suppression via shared SkillManager (duplicate announcements
  are harmless; omissions are permanent)
- Compute includeAvailableSkillsReminder from effective tool surface instead of
  hardcoding false — subagents with Skill tool now get the snapshot
- Apply trimSkillEntriesTowardsBudget to buildAddedSkillsReminder to bound
  MCP prompt descriptions

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

* fix(core): address fourth-round review findings for cache-prefix PR

1. First-drain fallback no longer silently swallows late registrations:
   when seedSkillReminderDedupFromSnapshot was never called (edge-case
   construction path), the drain now announces all entries instead of
   marking them as "already announced" without the model ever seeing them.

2. Subagent shared-SkillManager double-announcement fix: coreToolScheduler
   now records inline-announced skill keys via Config.addInlineAnnouncedSkillKeys,
   and drainSkillAndCommandReminders consumes them before building the
   delta reminder, preventing the same skill from being announced both
   inline on the tool result and again in the per-turn tail reminder.

3. Background agent resume now computes includeAvailableSkillsReminder
   dynamically from the subagent's tool surface (via subagentWillHaveSkillTool)
   instead of hardcoding false. Subagents that include the Skill tool now
   get the <available_skills> snapshot on resume.

4. buildAddedSkillsReminder now caps individual entry descriptions to
   first line / MAX_TRIMMED_SKILL_DESC_LEN before rendering, guarding
   against unbounded remote-controlled MCP prompt descriptions.

5. Stale comments swept: references to the removed setTools() re-render
   mechanism updated in slashCommandProcessor.ts, skill-manager.ts,
   config.ts, nonInteractiveCliCommands.ts, skill.ts, and skill.test.ts.

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

* fix(test): add missing getSkillManager mock in background-agent-resume tests

The subagentWillHaveSkillTool() helper calls buildAvailableSkillsReminder
which needs config.getSkillManager(). Without this mock, the call throws
silently and resumeBackgroundAgent returns undefined.

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 06:47:26 +00:00
Yufeng He
161c6ec847
fix(core): stabilize truncated tool retry key (#4970) 2026-06-12 14:33:51 +08:00
Dragon
546b2758fb
fix(docs): correct stale settings keys, wrong defaults, and missing commands (#4969) 2026-06-12 14:31:31 +08:00
pomelo
3622cb47d4
fix(core): add Tool Fallback rule to system prompt to prevent premature tool abandonment (#4931) 2026-06-12 14:22:19 +08:00
顾盼
88fdf41ed7
feat(core): Workflow P2 — parallel() + pipeline() concurrent fan-out (#4721) (#4947)
* feat(core): add createConcurrencyLimiter sliding-window util

A p-limit-style concurrency limiter that keeps at most `limit` thunks in
flight and starts a queued thunk the instant a slot frees — so one instance
can be SHARED across several fan-out calls and still hold the total in-flight
count under a single cap. The existing in-repo concurrency control
(memoryDiscovery.ts) is fixed-size sequential batching, not a sliding window,
so this is a new primitive rather than a refactor.

API:
- run(thunk): schedule one thunk through the shared window; rejections
  propagate raw.
- settleAll(thunks): batch convenience that resolves to a position-aligned
  Array<T|null> where a rejected thunk becomes null (errors-as-data) and the
  ONLY rejection is an abort of the limiter's signal — so an aborted run
  surfaces as a rejection rather than a silent array of nulls.

Guards a non-positive-integer limit (mirrors background-tasks.ts), preserves
input order, short-circuits empty input, and (with an AbortSignal) refuses to
start new queued work once aborted. 13 unit tests cover the window cap,
errors-as-data, order, sharing across calls, and the abort paths.

* feat(core): Workflow P2 — parallel() + pipeline() + 1000-agent cap

Implements the P2 phase of the dynamic-workflow port (#4721): concurrent
fan-out primitives on top of P1's sequential agent().

parallel(thunks)
- Runs thunks through a per-run shared sliding window (createConcurrencyLimiter,
  cap = max(1, min(16, cpus-2)) — the max() guards 1–2 core machines).
- Resolves to a position-aligned array; a thunk that throws becomes null at its
  index (errors-as-data). parallel() itself only rejects on abort, so an aborted
  run surfaces a rejection rather than a silent array of nulls.
- Rejects on a non-function element (eager promise instead of a thunk).

pipeline(items, ...stages)
- Parallel-of-chains: one thunk per item, all sharing the SAME window, so it is
  staggered (item A can be in stage 3 while item B is in stage 1) with no
  inter-stage barrier. Stage callbacks receive (prev, item, idx); the first
  stage's prev is the item itself. A stage that throws OR returns null drops
  that item to null and skips its remaining stages.

1000-agent-per-run cap
- The orchestrator wraps this.dispatch with a counter, so EVERY agent() call —
  sequential, parallel, or pipeline — funnels through one chokepoint. A fan-out
  cannot bypass the cap; the 1001st call throws.

SECURITY — vm-realm result revival (closes an uncovered escape)
- vmAsync's resolve path is verbatim: it does NOT re-wrap resolved values. The
  host parallel/pipeline impl resolves with a HOST-realm array, so handing it to
  the script would reopen the T1/T8/T14 escape
  (out.constructor.constructor('return process')() reaches host process via the
  host Array.prototype chain). The vm wrapper now revives the array in-realm with
  JSON.parse(JSON.stringify(...)) — the same mechanism that makes `args` safe —
  before the script sees it. The pre-P2 escape test only probed the *Promise*
  (already vm-realm), not the resolved array; new tests probe the resolved array
  (outer + nested) and were verified to FAIL against a verbatim wrapper.

Real impls are injected by the orchestrator; the sandbox keeps its throwing
P1-unsupported stubs as the default when parallel/pipeline are not injected, so
an un-wired sandbox still gives a clear error. Tool description updated to
document the P2 surface. Full workflow suite + new orchestrator/sandbox/tool
tests green; tsc + eslint clean on all touched files.

* fix(core): Workflow P2 self-review — per-element revival + honest tool description

Adversarial self-review (6-dimension finder fan-out) surfaced two genuine
defects plus three test gaps.

EAD-1 [major] — reviveInRealm did JSON.parse(JSON.stringify(WHOLE array)), so a
single slot whose VALUE is non-serializable (a thunk that returns a BigInt or a
circular object) threw on the entire array and REJECTED the whole
parallel()/pipeline(), destroying every sibling result. That defeats
errors-as-data for return values. Revive PER-ELEMENT instead: a bad slot becomes
null at its index, siblings survive, and the outer array is still built in-realm
so the host-process escape stays closed. Regression test:
parallel([() => 'a', () => 1n, () => 'c', () => circular]) => ['a', null, 'c', null].

API-1 [major] — the WorkflowTool top-level description (passed to super()) still
read "No parallel, no pipeline" while the param-schema description and the runtime
both now support them. Updated to describe the P2 surface (parallel/pipeline,
≤16 in flight, ≤1000 total). Also refreshed the now-stale "scheduled for P2"
messages on the un-injected fallback stubs to an accurate "unavailable: sandbox
created without an implementation" wording.

Test gaps closed:
- TST-1: pipeline() now has a concurrency test proving it shares the SAME per-run
  window as parallel (peak in-flight === cap), so a pipeline impl that bypassed
  the shared limiter would fail.
- TST-2: pipeline() staggering is now tested — item 0 reaches stage 2 long before
  item 1's slow stage 1 finishes, proving there is no inter-stage barrier (a
  stage-by-stage barrier impl would fail the <50ms threshold).
- TST-3: mid-flight abort through the orchestrator is now tested (the prior test
  only used a pre-aborted controller), proving parallel() rejects after
  dispatches start rather than resolving with a silent array of nulls.

150 workflow-suite tests green; tsc + eslint clean on touched files.

* feat(core): make Workflow P2 caps env-overridable

Mirror the established QWEN_CODE_MAX_BACKGROUND_AGENTS / P1
QWEN_CODE_MAX_WORKFLOW_SECONDS precedent so operators can tune the P2 caps
without a code change:

- QWEN_CODE_MAX_WORKFLOW_AGENTS  — override the per-run 1000-agent cap.
- QWEN_CODE_MAX_WORKFLOW_CONCURRENCY — override the cpu-derived
  min(16, cpus-2) in-flight window with an explicit integer.

Both use the house resolver shape (resolveMaxConcurrentBackgroundAgents):
a non-integer / <1 value is rejected with a debug warning and the default
is used. The agent cap default is renamed DEFAULT_MAX_AGENTS_PER_RUN and the
cap message is now built from the resolved value. Resolvers take an injectable
env arg for pure unit testing.

Adds resolver unit tests (default / valid / invalid) plus an integration test
proving QWEN_CODE_MAX_WORKFLOW_AGENTS=3 makes a 4-thunk parallel() yield
exactly 3 results + 1 null at run time. 154 workflow-suite tests green.

* fix(core): throttle Workflow P2 concurrency at the dispatch layer, not the thunk layer

P2 self-review (independent adversarial fan-out) caught a real deadlock the
mock-tested suite missed: the concurrency window was applied at the thunk
level (parallel()/pipeline() scheduled their thunks through the shared
limiter). A nested fan-out — e.g. `pipeline([items], item => parallel([...]))`,
the canonical /deep-research shape — would have every outer slot held by an
outer thunk awaiting an inner settleAll() whose thunks can never acquire a
slot. pump() only re-runs from an in-flight thunk's finally, so the queue
never drains: unrecoverable silent hang until the 30-min wall clock. On 1-3
core machines (limit = 1) a SINGLE nested call deadlocks; abort cannot break
it because pump() is never re-invoked.

Fix: the window throttles AGENT DISPATCHES, not orchestration thunks. The
limiter now wraps `this.dispatch` inside countedDispatch, so only leaf agent()
calls acquire a slot; parallel()/pipeline() compose promises freely via a
plain Promise.allSettled + position-aligned null-map (settleToNullArray) and
cannot deadlock when nested. This is also the correct "N agents in flight per
run" semantics (the cap is about concurrent model calls, not orchestration
depth) and makes abort prompt (dispatch slots free normally).

The limiter's unused settleAll() is removed — its errors-as-data null-mapping
+ abort-reject moved into settleToNullArray in the orchestrator, where the
batch semantics belong. The tool description's "≤16" is softened to "16 by
default" now that QWEN_CODE_MAX_WORKFLOW_CONCURRENCY can raise it.

Adds two RED-verified regression tests (nested parallel-in-pipeline and
parallel-of-parallel, forced to concurrency=1) that deadlocked before the fix
and now resolve in ms. 150 workflow-suite tests green; tsc + eslint clean.

* fix(core): Workflow P2 round-2 self-review — prompt queue abort + AbortError consistency + doc accuracy

Round-2 adversarial review of the post-F1 code (fresh finders + skeptics) found
no new critical/major behaviour bugs (architectural convergence after the
dispatch-layer concurrency fix), but surfaced two real correctness items plus
a doc-accuracy cleanup pass.

createConcurrencyLimiter — prompt queue abort
- The limiter previously rejected queued jobs only lazily, inside pump(),
  which re-runs from an in-flight thunk's .finally. So if an in-flight thunk
  never settled (a buggy/hung future dispatcher), queued jobs would hang
  forever even after abort. Production today never hits this because
  subagent.execute always settles, but the limiter shouldn't lean on an
  unenforced invariant. Now an `{once: true}` 'abort' listener drains the
  queue the moment the signal fires. Adds a RED-confirmed regression test
  (limit=1, in-flight = `new Promise(()=>{})`, abort → queued must reject
  within 200ms).

settleToNullArray — abort error type consistency
- Was throwing `new Error('Workflow run aborted.')`, which `isAbortError()`
  (utils/errors.ts) does NOT recognise — an aborted parallel/pipeline would
  surface as a generic run failure. Now throws
  `new DOMException('Workflow run aborted.', 'AbortError')` to match the
  limiter, so the whole P2 abort path classifies uniformly.

Doc accuracy pass (review caught 5 stale strings)
- Tool descriptions accurately state the default cap is `min(16, cpus-2)`
  (not a flat "16"), document both env-overrides
  (QWEN_CODE_MAX_WORKFLOW_CONCURRENCY, QWEN_CODE_MAX_WORKFLOW_AGENTS), and
  note that a thunk resolving to a non-JSON-serializable value also becomes
  null at its index.
- makeParallelImpl docstring updated: parallel() rejects on invalid input OR
  abort (not "only on abort" — that was contradicted by the array/function
  validation right above).
- WorkflowTool fileoverview no longer claims "P1 sequential only".
- Orchestrator.run() comment updated to describe the actual P2 signal flow
  (per-run limiter derived from abortOnTimeout, not P1's
  "sandbox-level signal intentionally not exposed").
- Wall-clock rationale loses its stale "P1 sequential" framing.

151 workflow-suite tests pass (was 150 + 1 new lazy-abort regression);
tsc + eslint clean.

* docs(core): Workflow P2 round-3 self-review — wall-clock honesty + cpu-floor + symmetric pipeline docs

Round-3 adversarial review found one confirmed factual error in the round-2
doc rewrite plus three real consistency gaps. No new behaviour bugs; the
architecture has converged after the round-1 dispatch-layer fix and the
round-2 prompt-queue-abort + AbortError consistency.

(1) wall-clock docstring: the round-2 rewrite claimed "even a long pipeline
with the 1000-agent cap is bounded well under" 30 min. Arithmetically false:
1000 agents × 10-min subagent cap ÷ default 16-concurrency ≈ 10.5 hours,
20× the wall clock. Rewritten honestly: the wall clock is a 0-token-hang
backstop, NOT a precise cost cap; for cost control point operators at the
env-overridable per-run cap (QWEN_CODE_MAX_WORKFLOW_AGENTS) and concurrency
window (QWEN_CODE_MAX_WORKFLOW_CONCURRENCY).

(2) tool descriptions now show the actual default formula
`max(1, min(16, cpus-2))`, including the outer max(1, ...) floor — without
it, the displayed default would be -1 on a 1-CPU container even though the
runtime clamps to 1.

(3) tool descriptions now document the non-JSON-serializable→null rule for
pipeline() as well as parallel() — they share the same reviveInRealm code
path (per-element JSON round-trip), so the asymmetric docs were inaccurate.

(4) settleToNullArray's AbortError comment is corrected: the round-2 commit
overclaimed "uniform classification via isAbortError() at the WorkflowTool
boundary". In reality the DOMException name is preserved at the HOST callsite
inside the orchestrator, but vmAsync re-throws the script-visible rejection
as a fresh `new Error(msg)` and the outer catch wraps it as
WorkflowExecutionError — so isAbortError() at the tool boundary returns false
either way. The DOMException is still useful as host-internal consistency,
but the script-observability claim was wrong.

Declined this round (intentional, documented):
- F4/P2-R3-F2 wall-clock is plain Error, not AbortError — wall-clock IS a
  timeout, not an abort; semantically correct as-is. A unified abort surface
  is P3+ work.
- R2-MIN-1 limiter listener leak — only triggers if signal outlives the
  limiter and never aborts; production caller is per-run and always aborts.
- F6 new symbols not re-exported from index.ts — same internal-API decision
  as createConcurrencyLimiter.
- Various test-vacuity nits — tests already cover real failure modes.

151 workflow-suite tests pass; tsc + eslint clean.

* fix(core): Workflow P2 PR review R1 — observability + hard ceilings + SECURITY comment

R1 review by @wenshao (4 [Suggestion] threads + 1 review-body comment). The
fifth thread, [Critical] nested deadlock on a thunk-level limiter, was already
caught and fixed in commit 0401ac88f by the dispatch-layer refactor — that
commit was authored before the review posted, so the thread is on the
pre-fix code; verified resolved by independent round-1 self-review fan-out
that converged on the same finding from a different angle, and by a real-LLM
E2E scenario (parallel-in-pipeline at concurrency=1 against qwen3-max).

settleToNullArray observability + abort docs (T2 / T3 wenshao):
- settled.map now logs the discarded rejection reason at debug level when a
  thunk rejects. Operators investigating a workflow that returned unexpected
  nulls can now disambiguate the four indistinguishable null paths (dispatch
  failure, 1000-cap, pipeline stage exception, non-JSON-serializable result)
  via the WORKFLOW debug logger; the contract to the script stays opaque.
- Docstring now explicitly explains the abort-responsiveness path: the
  apparent Promise.allSettled "wait for all to complete" is in practice "wait
  for all to reach an abort-aware rejection" because the dispatch signal is
  threaded all the way down to subagent.execute, and the limiter's separate
  abort listener drains the not-yet-started queue instantly.

env-override hard ceilings (T4 wenshao):
- HARD_MAX_AGENTS_PER_RUN_CEILING = 10000 caps QWEN_CODE_MAX_WORKFLOW_AGENTS.
- HARD_MAX_CONCURRENCY_CEILING = 64 caps QWEN_CODE_MAX_WORKFLOW_CONCURRENCY.
- Both clamp with a debug warning rather than silently dropping the override.
- Two RED-verified tests cover the over-ceiling clamp path (and a just-under
  value preserved).
- Not a security issue (env is operator-controlled), but stops a fat-finger
  =999999999 from silently uncapping the run.

reviveInRealm SECURITY comment (T5 wenshao):
- Added a SECURITY block warning future maintainers that the revival function
  MUST stay inside the vm init runInContext block. JSON / Array / Object here
  are vm-realm globals; extracting this textually-identical helper into a
  host-side utility would resolve those names against the host realm and
  silently reopen the T1/T8/T14 escape that the revival is designed to
  prevent. The textual identity to a host-side util is exactly the trap.

Declined this round (review-body 4 pipeline test-coverage sub-claims):
- pipeline abort, parallel+pipeline shared-limiter, pipeline 1000-cap,
  pipeline E2E. After the dispatch-layer refactor in 0401ac88f, parallel and
  pipeline mechanically share the SAME countedDispatch.limiter.run path — the
  parallel-side abort / cap / concurrency tests cover the mechanism. Explicit
  per-shape sibling tests would not catch a regression that the parallel
  versions don't already catch.

145/145 workflow-suite tests pass; tsc + eslint clean. The 2 config tests
fail locally only because the rebase pulled in #4844 (Agent Team)'s new
proper-lockfile dependency which the symlinked node_modules doesn't have —
CI resolves on fresh install.

* fix(core): Workflow P2 PR review R2 — staggering test + revival logging + pipeline E2E

R2 review by @DragonnZhang (re-review after R1 push) and @qwen-code-ci-bot
(post-fix review). Three real items addressed; one style/wording inconsistency
in the bot's review body declined per the round-weighted bar.

T6 [Bug] staggering test deterministically fails on macOS-14 CI (DragonnZhang)
- The test asserted item 0 reaches stage 2 within 50ms while item 1's stage 1
  takes 120ms. That timing assumption holds only at concurrency ≥ 2. On
  GitHub's macos-14 runners (3 CPU cores) cpu-derived concurrency = 1, FIFO
  forces all stage-1 dispatches to settle before any stage-2 starts, and the
  ~122ms s2-of-0 timestamp blows the threshold. The test passed on my workstation
  but blocks the macos-latest CI matrix — root cause of the failing
  `Test (macos-latest, Node 22.x)` check that downgraded the prior APPROVE.
- Replaced with a deterministic gate-based assertion that does NOT depend on
  wall-clock thresholds: force QWEN_CODE_MAX_WORKFLOW_CONCURRENCY=2, have
  item 1's stage 1 block on a Promise gate that only item 0's stage 2 can
  release. A staggered impl completes (item 0 advances while item 1 is held);
  a barrier impl deadlocks (item 0's stage 2 can't start until item 1's stage
  1 finishes, which can't finish until item 0 reaches stage 2). Vitest timeout
  catches the barrier-deadlock case; the assertion `item0ReachedStage2 ===
  true` is timing-free.

T7 [Suggestion] reviveInRealm catch silently sets null with no log (qwen-code-ci-bot)
- The R1 fix added debugLogger.warn for rejected thunks in settleToNullArray,
  but a thunk that *resolves* to a non-JSON-serializable value (BigInt /
  circular object) takes a different path through reviveInRealm's catch in
  the vm init script. Operators with debug logging on still couldn't
  distinguish "rejected" from "resolved-but-unserializable" — symmetric
  observability was missing. The R1 audit should have caught the sibling and
  didn't.
- Added a host-side `logRevivalFailure(idx, reason)` hook to the bridge
  (debugLogger.warn host-side) and call it from reviveInRealm's catch with
  the coerced-to-string error message. The bridge contract is preserved:
  only primitive strings/numbers cross back; reviveInRealm itself stays
  inside the vm runInContext block per the SECURITY comment.

T8 [Suggestion] no pipeline() end-to-end test through WorkflowTool (qwen-code-ci-bot)
- This is the SAME finding wenshao raised in his R1 review-body, which I
  declined on a "parallel/pipeline share mechanism — symmetric tests
  redundant" basis. The bot's R2 raise provides specific mechanism evidence
  that breaks that argument: pipeline's vm wrapper uses
  `callPipeline.apply(null, arguments)` and `[items].concat(stages)` to
  spread the variadic stage list, a code path structurally distinct from
  parallel's single-argument call. A regression in the vm-to-host stage
  forwarding would not be caught by the parallel E2E. My R1 decline was
  based on incomplete grep — apologies, accepting now.
- Added a pipeline E2E test mirroring the parallel E2E shape: full stack
  drive through WorkflowTool → orchestrator → sandbox revival, asserting
  the chained stage results `[11, 21]`.

Declined this round (review-body):
- qwen-code-ci-bot's workflow() stub wording inconsistency ("not supported
  in P1" vs the new "is unavailable: ..." on parallel/pipeline). R2 style/
  nit per the round-weighted bar; no behavioural impact.

146/146 workflow-suite tests pass; tsc + eslint clean.

* chore(core): Workflow P2 — dedupe WORKFLOW debugLogger + simplify revival error coercion

Post-R2 /simplify pass. Two findings that are pure cleanup of code added in
the R2 commit, with no scope drift:

(1) The R2 fix added `createDebugLogger('WORKFLOW')` to workflow-sandbox.ts,
duplicating the identical call in workflow-orchestrator.ts:21. Export the
sandbox-side instance and import it in orchestrator — single source of
truth, one fewer logger object retained for process lifetime. Direction is
natural (orchestrator already imports from sandbox; the reverse would be
circular).

(2) The reviveInRealm catch coercion `String((e && e.message != null) ?
e.message : e)` collapses to `String(e?.message ?? e)`. The truthy/null
distinction the original drew (treating empty-string message different from
the toString fallback) was not meaningful for a debug log line. Same
behaviour for any realistic error; less noise to read.

Deferred per the same self-review discipline that the R2 commit message
documented:
- `withEnv` helper to dedupe the 6-line env-var save/restore boilerplate
  (now 3 sites in workflow-orchestrator.test.ts): real ~10 LOC win but
  touches 2 pre-existing tests, out of R2 scope.
- Rename `bridge.logRevivalFailure` to a generic `bridge.warn(category,
  msg)` for future vmAsync silent-reject logging: speculative; per the
  altitude analysis, "zero rename cost when the second consumer arrives"
  means deferring loses nothing.

146/146 workflow-suite tests pass; tsc + eslint clean.
2026-06-12 14:16:32 +08:00
顾盼
e25d7eec04
feat(core): port declarative-agent mcpServers + hooks (CC 2.1.168 parity follow-up) (#4996)
* fix(core): replace yaml-parser stringify with eemeli/yaml for safe nested round-trip

PR #4870 swapped `parse` over to the `yaml` library so block scalars and
nested structures load correctly, but left the hand-rolled `stringify`
in place. The hand-rolled serializer only walks one level of nesting and
emits `[object Object]` for any value below — so any caller that does
`parse → modify → stringify` (e.g. SubagentManager saving a frontmatter
file, or a Claude-Code-format converter writing back a `.qwen/agents/*.md`)
silently corrupts nested fields like `mcpServers` or `hooks` on disk.

This commit:
- Delegates `stringify` to `yaml.stringify` with `lineWidth: 0`, matching
  the parse side's library choice and unlocking arbitrary-depth round-trip.
- Drops the now-unused `formatValue` helper.
- Replaces the byte-exact escape-sequence assertions with property-based
  `parse(stringify(x)) === x` round-trip checks — the previous tests
  pinned hand-rolled quote output that eemeli/yaml legitimately chooses
  differently from. The contract that matters at the public API boundary
  is round-trip, not stable bytes.
- Adds two CC-shape nested round-trip tests (mcpServers + hooks) that
  would have been impossible to express under the old serializer.

The parse-side safety guards added in PR #4870 (schema 'core', timestamp
/ binary tag filtering, `Object.create(null)`, Date/Uint8Array sanitize,
parseSimple fallback) are preserved untouched.

* feat(core): port declarative-agent mcpServers + hooks end-to-end

Builds on PR #4842 (which deferred these two fields) and PR #4870
(which made `parse` nested-safe) by adding the remaining surface +
runtime wiring so a `.qwen/agents/*.md` with per-agent MCP servers
and hooks works the same way as the equivalent `.claude/agents/*.md`.

## Schema layer

`agent-frontmatter-schema.ts` gains two lenient DL7-parity parsers:

- `parseAgentMcpServers` — keeps a record-of-records shape and drops
  per-key entries whose value is a scalar / array / null (mirrors CC's
  `gS8` shallow validation; per-spec union is enforced later by the
  MCP loader).
- `parseAgentHooks` — keeps a record-of-arrays shape and drops events
  whose value isn't an array (mirrors CC's `TKO` / `_u`).

Both return `undefined` when no entries survive shape filtering, so the
caller can omit the field entirely rather than emit an empty object.

## SubagentConfig surface

- `types.ts`: adds optional `mcpServers?: Record<string, unknown>` and
  `hooks?: Record<string, unknown>`.
- `subagent-manager.ts` `parseSubagentContent`: extracts both fields
  with warn-and-drop on top-level shape failure, matching the existing
  posture for `permissionMode` / `maxTurns` / `color`.
- `subagent-manager.ts` `serializeSubagent`: emits both back to YAML.
  The previous skip-list carve-out in `claude-converter.ts`
  (`NESTED_FIELDS_NOT_ROUND_TRIPPABLE`) is gone — round-trip is safe
  now that the YAML stringifier is eemeli/yaml.
- `claude-converter.ts`: emits `mcpServers` (was missing) when
  converting from a CC plugin agent file.

## Runtime wiring

- `hookRegistry.ts`: new public `addAgentHooks(hooks, scopeId): () => void`
  appends ephemeral entries tagged with `agentScope`, runs them through
  the same per-definition validation pipeline as session/user/project
  hooks (so a malformed entry is logged + dropped instead of breaking
  the spawn), and returns an unregister callback. The duplicate-detection
  key includes `agentScope` so identical hooks from different subagents —
  or from a subagent and the session — coexist instead of swallowing
  one another.

  v1 scope limitation: while a subagent's entries live in the registry
  they fire for every event of their declared type regardless of which
  agent is currently active. Per-agent scope filtering at firing time
  is a follow-up; the limitation is documented in the user-facing doc.

- `subagent-manager.ts` `buildSubagentContextOverride`: now takes the
  `SubagentConfig` and, when per-agent `mcpServers` are present,
  overrides `getMcpServers()` on the subagent's Config wrapper to return
  the union of session + agent servers (agent wins on key collision,
  matching CC's `scope: 'agent'` semantics). The skip-rebuild
  optimization is bypassed in this case — without a fresh
  `rebuildToolRegistryOnOverride` anchored on the override Config, the
  pre-existing wrapper-owned `McpClientManager` would still see only
  the session set and the discovery loop below would silently no-op.
  After rebuild, the loop explicitly discovers each per-agent server so
  its tools land in the subagent's registry before `AgentHeadless.run`.

- `subagent-manager.ts` `createAgentHeadless`: when `config.hooks` is
  set, registers via `HookRegistry.addAgentHooks` with a per-spawn
  scope ID (`agent:<name>:<uuid>`) and wraps the caller-provided
  `AgentHooks.onStop` so the unregister callback fires after the
  caller's handler, in a `finally` block to survive a throwing user
  handler. Errors from `AgentHeadless.create` itself unregister the
  hooks before re-throwing.

## Tests

- `agent-frontmatter-schema.test.ts`: +8 tests covering shape filtering,
  drop-on-bad-top-level, drop-when-empty for both new parsers.
- `subagent-manager.test.ts`: +7 tests covering parse, serialize, and
  drop-on-malformed for both fields.
- `subagent-manager-override.test.ts`: +2 tests for the
  session+agent MCP merge and the no-override pass-through case.
- `hookRegistry.test.ts`: +4 tests for `addAgentHooks` — scope tag,
  no-collision with existing same-identity entries, two concurrent
  agents keep their own copies, empty payload no-op.

* docs(core): declarative agents follow-up + yaml-parser audit

- `docs/yaml-parser-replacement.md` is the new audit doc covering the
  PR #4870 → eemeli/yaml decision (parse-side), the security probe
  results (`maxAliasCount` default, `!!js/function` becomes literal
  string + warning, merge keys disabled by default, custom-tag filter
  for timestamp/binary), and the stringify-side gap this follow-up
  closes.
- `docs/declarative-agents-port.md` status table now marks `mcpServers`
  and `hooks` as **shipped (follow-up)** with a one-line note on the
  runtime wiring strategy + v1 scope limitation for hooks. The
  reverse-engineering record below the table is unchanged and remains
  the reference for the still-deferred fields (`effort`, `memory`,
  `isolation`, `initialPrompt`, `skills`).
- `docs/users/features/sub-agents.md` adds `mcpServers` + `hooks` rows
  to the CC-compatibility table and a full example frontmatter showing
  all four shipped fields composed together. The v1 hooks scope
  limitation is called out as a blockquote so users picking up
  per-agent hooks know to prefer fire-globally-safe handlers (logging)
  over behavior-mutating ones until the firing-time scope filter lands.

* fix(core): self-review round 1 — proto-pollution defense + parallel MCP discovery

Audited the three commits adversarially across four lenses (correctness,
security, reuse, test quality). Two real findings, two test gaps.

## Real bugs fixed

### `parseAgentMcpServers` / `parseAgentHooks` could pollute the result's prototype

Both helpers wrote into a plain `{}` while iterating an input object that
yaml-parser hands back as null-prototype. A YAML key of literal
`__proto__` survives that null-prototype guarantee as an own property,
so `Object.entries(record)` walks it, and the assignment
`result['__proto__'] = spec` triggers the inherited setter — silently
re-wiring `result`'s prototype chain to point at the attacker's spec.

Object.prototype itself stays untouched (the setter is per-instance) and
the downstream spread `{ ...result }` only copies own enumerables, so the
pollution doesn't directly leak through current callers. But returning
an object with a hijacked prototype is a latent footgun: a future caller
that uses `for…in`, `result.someKey`, or any property access that misses
the own table would pick up values from the polluted chain.

Switching to `Object.create(null)` makes the assignment a plain own
property — matching the null-prototype invariant the rest of
`yaml-parser.ts` already maintains. Two regression tests pin the
defense (one per parser), each constructing a null-prototype input the
way `yaml.parse` actually produces it (`{ __proto__: … }` in an
object literal triggers the setter at construction and so does NOT
reproduce the attack input shape).

### Per-agent MCP discovery serialised through every server

`buildSubagentContextOverride` called `discoverToolsForServer` in a
sequential `for…of` loop with `await`. A misbehaving server (stdio
command that hangs at startup, remote endpoint that times out at the
MCP layer's default 30s) blocks every following server, so the subagent
spawn paid the sum of every per-server timeout instead of the max.

Switched to `Promise.allSettled` over the server list. Each call still
carries the MCP layer's own per-server timeout (`stdio` default 30s,
remote default 5s, per-spec `discoveryTimeoutMs` override); `allSettled`
only removes the serialisation between siblings. Rejections still
log-and-drop so a single bad server doesn't block its siblings'
tools from landing in the subagent's registry.

## Test gaps closed

### `addAgentHooks` coexistence test only asserted count

The "coexists with session/user hooks of the same identity" test
asserted `getAllHooks()` had length 2 after the add but did NOT verify
which entries were present. A regression that dropped `agentScope`
from the dedup key would still leave two entries by ordering luck and
silently break concurrent-agent isolation. Replaced the bare count
check with explicit assertions for `(source: User, agentScope: undefined)`
+ `(source: Session, agentScope: 'agent:test:def')`.

### Empty-record edge cases for `parseAgentMcpServers` / `parseAgentHooks`

The existing "returns undefined when nothing survives shape filtering"
tests covered the case where every key had a bad shape. The empty
input case `parseAgent…({})` was on the same code path but never
exercised — callers rely on the `undefined → omit field` behaviour for
both. Added one test per parser.

* fix(core): pr #4996 review round 1 — leak fixes via explicit dispose contract

Reviewer flagged two Criticals + one Suggestion + one Nice-to-have. The two
Criticals share a root cause (subagent execute()'s inner try/finally
doesn't fire on every exit path), so they fold into a single API change.

## [Critical] Hook cleanup leak on AgentHeadless.execute() early exits

`wrapAgentHooksForCleanup` relied on `onStop` firing inside execute()'s
inner try/finally. Two early-exit paths bypass that finally:

1. `createChat()` returning null at agent-headless.ts:224-226 — returns
   before the outer `try` at 233 is even entered.
2. `prepareTools()` throwing at 234 — propagates through the outer
   `finally` at 335, which only calls `abortController.abort()` and
   never reaches the inner finally that fires `onStop`.

The pre-fix `catch` block only guarded `AgentHeadless.create()`, not
`execute()`. Leaked HookRegistry entries fire globally for every matching
event in the session, polluting unrelated tool calls.

## [Critical] Per-agent MCP server processes leak after every spawn

`discoverToolsForServer` connects real MCP clients (stdio child
processes, HTTP/SSE sockets) in the force-rebuilt subagent ToolRegistry.
Nothing stopped that registry: `Config.shutdown` only reaches the root's
`this.toolRegistry`, and AgentTool's existing `agentConfig.getToolRegistry()
.stop()` (fg + bg + resume finally blocks) only stops the parent's
registry, not the override's distinct fresh one. Every subagent
invocation that declared `mcpServers` orphaned a child process for the
rest of the host process's lifetime.

## Shared fix — caller-driven `dispose` contract

`SubagentManager.createAgentHeadless` now returns
`{ subagent, dispose }`. Callers MUST invoke `dispose()` in the same
`finally` block that wraps `subagent.execute()`. That `finally` lives
in the caller's scope (AgentTool fg/bg, BackgroundAgentResumeService),
which is reachable on every execute() exit — including the two early-
exit paths the previous `onStop` hook never reached.

`dispose` is a single closure that calls the previously-separate
cleanup callbacks in order:

1. `unregisterAgentHooks` returned from `HookRegistry.addAgentHooks`
   (when per-agent hooks were registered).
2. `disposeRegistry` returned alongside the new `buildSubagentContextOverride`
   return shape `{ context, disposeRegistry }` (set only when this call
   force-rebuilt the registry for `mcpServers`).

Both cleanups are wrapped in `try/finally` that logs and re-arms so an
exception in one path doesn't block the other and doesn't double-fire.
The pre-existing constructor-failure catch in `createAgentHeadless` now
runs the same closure directly — the caller never received the return
value, so it cannot fire `dispose` itself.

The three callers gain one variable + one `void dispose?.().catch()`
inside their existing finally:

- `agent.ts:2039` (foreground) — finally at `agent.ts:2904`
- `agent.ts:2131` (background) — finally at `agent.ts:2502`
- `background-agent-resume.ts:630` (resume) — finally at `:852`

Fork subagents share the parent's lifecycle; their `dispose` stays
undefined and the `?.()` is a no-op.

`wrapAgentHooksForCleanup` is removed — it was load-bearing only for
the happy + inner-reasoning-loop-failure paths and is now obsolete.

## [Suggestion] Repeated guard condition

`config.hooks && Object.keys(config.hooks).length > 0` no longer appears
in both `if` / `else if` arms. Single outer guard + nested branch on
`hookRegistry`.

## [Nice-to-have] claude-converter.ts:290 missing `.trim()`

`stringifyYaml(newFrontmatter).trim()` brings the converter into line
with `subagent-manager.ts:651`. Without trim, eemeli/yaml's trailing
newline produced an extra blank line before the closing `---`
delimiter — cosmetic (both readers tolerate it) but the asymmetry
between the two writers was a real consistency bug.

## Tests

3 new RED-first tests in `subagent-manager.test.ts` pin the dispose
contract:

1. `returns { subagent, dispose }; dispose unregisters per-agent hooks`
2. `dispose unregisters even when execute() never runs (early-exit leak fix)`
   — the case where `createChat()` → null or `prepareTools()` throws
3. `dispose is a safe no-op when neither hooks nor mcpServers are declared`

Override test helper updated to destructure the new
`buildSubagentContextOverride` return shape. AgentTool + BackgroundAgent
test mocks updated to return `{ subagent, dispose }`. All 2087 in-scope
tests pass.

* refactor(core): /simplify cleanup pass on the dispose contract

Three cleanups from a multi-angle quality review (reuse / simplification /
altitude lenses, all on commit 720f0e4a1):

1. Drop the null-out guard inside `runCleanup`. Both inner callbacks are
   already idempotent at the source (`HookRegistry.addAgentHooks` filters
   removal by `agentScope`; `ToolRegistry.stop` documents itself
   idempotent), so the outer `unregisterAgentHooks = undefined` /
   `disposeSubagentRegistry = undefined` finally blocks were buying nothing
   beyond a marginal short-circuit on duplicate `dispose()` calls — at the
   cost of 8 LOC and a "is this load-bearing?" question for readers.
   Comment now states the idempotency guarantee explicitly.

2. Rename the `buildSubagentContextOverride` return field
   `disposeRegistry` → `cleanup`. Pairs the sibling override builder
   `createApprovalModeOverride` whose return shape is
   `ApprovalModeOverrideHandle = { config, cleanup }`. The `context` field
   stays as-is because `config` would shadow the same-named parameter
   inside this method's scope (the parent helper doesn't take a `config`
   parameter, which is why it can use that name).

3. Add a 4th test pinning the constructor-failure cleanup path. The
   `try { ... } catch { await runCleanup(); throw }` block at the end of
   `createAgentHeadless` runs when `AgentHeadless.create` rejects — at
   which point the caller has not received `{ subagent, dispose }` and
   cannot run cleanup itself. The three existing tests covered the
   happy-path and execute()-never-runs scenarios; this one closes the
   "constructor blows up after hooks were registered" gap.

No behavior change to callers — same `{ subagent, dispose }` return
shape, same dispose semantics. Findings skipped:

- Parallelizing the two cleanups inside `runCleanup`: synchronous
  unregister + async registry stop, the `await` only blocks the registry
  stop; the order has no measurable cost.
- Parallelizing the parent/agent registry stops at the 3 call sites:
  they already run concurrently because the call sites use
  `void X.stop().catch(...)` (fire-and-forget), not `await`.
- Extracting a `executeHeadlessSubagent` helper that owns the dispose
  lifecycle: real win against future call-site drift, but reaches well
  outside the round-1 review diff into AgentTool's three execution
  shapes (fg sync / bg fire-and-forget / resume embedded).
- Fixing `AgentHeadless.execute()`'s early-exit paths upstream: the
  round-1 commit's explicit altitude choice; revisiting it would re-open
  a settled design call.
2026-06-12 14:15:51 +08:00
Jason
12bc80c308
fix(cli): debounce resize repaint and clear stale scrollback on settle (#4919)
Dragging a terminal window edge during streaming left fragmented content
at mixed widths in the scrollback on macOS Terminal.app / iTerm2.

Root cause (legacy `ui.useTerminalBuffer=false` path, the default):
- A window drag fires dozens of `resize` events with intermediate widths
  (useTerminalSize is intentionally undebounced; other consumers want the
  live value).
- PR #3967 (4bab7a1a) changed the width-change repaint from a full
  clearTerminal to cursorTo(0,0)+eraseDown, which only erases the visible
  viewport and cannot reach output already scrolled into the scrollback.
- Each width change also restarts the #3899 progressive <Static> replay.
  When the next resize arrives mid-replay, chunks already scrolled above
  the viewport are unreachable by eraseDown and stay in scrollback at
  whatever width the terminal had at that instant — one stranded layer
  per event, i.e. the reported alternating narrow/wide box-border segments.

Fix: debounce the resize repaint to the trailing edge of the burst
(RESIZE_REPAINT_SETTLE_MS = 200ms). On width change we schedule a timer;
the effect cleanup cancels it when the next width change (or unmount)
arrives, so only the last event fires. On settle we call the existing
refreshStatic() — a full clearTerminal (incl. ESC[3J, which clears
scrollback) plus a <Static> remount — wiping the stale fragments and
re-emitting the history exactly once at the final width. A drag that
returns to the starting width fires nothing (the settled-width ref is
only updated when the repaint actually fires).

The debounce logic lives in a small, dedicated hook
(useResizeSettleRepaint) so it can be unit-tested deterministically with
renderHook + fake timers; the inline AppContainer effect could not be
exercised through ink-testing-library, which does not flush update-time
passive effects. Behavior is unchanged.

Trade-offs (intentional):
- The full-screen flash #3967 removed now happens at most once per resize
  gesture (vs. every event pre-#3967, vs. never-correct post-#3967).
- The settle-time ESC[3J clears pre-session scrollback — identical to what
  every resize event did before #3967 and what /clear / refreshStatic
  callers still do today; strictly less destructive than pre-#3967.
- During the ~200ms drag window the static region is briefly stale while
  ink reflows the dynamic region live; the final state is correct.

Scope: no change to useTerminalSize, the VP (useTerminalBuffer=true)
rendering path, or the #3899 progressive-replay machinery.

Fixes #4891
2026-06-12 14:13:07 +08:00
Yufeng He
adbdff8b1f
fix(cli): avoid headless browser open crashes (#4716)
* fix(cli): avoid headless browser open crashes

* fix(cli): reuse browser launch guard

* test(core): isolate browser launcher env

* fix(core): make browser env launches non-blocking

* fix(core): handle LSP stdio write failures
2026-06-12 13:58:35 +08:00
Puneet Dixit
1cd5b81aa3
fix(desktop): keep composer sendable after idle escape (#4788)
Co-authored-by: pratyushjaiswal0806-dot <pratyushjaiswal0806@gmail.com>
2026-06-12 13:57:29 +08:00
jinye
246a0a1fc5
feat(core): persist file history snapshots for cross-session /rewind (T2.1) (#4897)
* feat(core): persist file history snapshots to JSONL for cross-session /rewind (T2.1)

File history snapshots were purely in-memory — lost on process exit, making
/rewind unusable after session resume. This adds JSONL persistence so restored
sessions can rewind to any pre-resume turn.

Key changes:
- Serialize/deserialize FileHistorySnapshot to/from JSONL system records
- Record each snapshot after makeSnapshot succeeds (incremental per turn)
- Re-record surviving snapshots after rewind (full batch on active branch)
- Parse file_history_snapshot records in sessionService.loadSession()
- Restore snapshot chain in config.getFileHistoryService() on resume
- Copy backup files on session fork (hard link with copy fallback)
- Add session_resume capability tag (stable alias for unstable_session_resume)
- validateRestoredSnapshots with dedup + batched parallel stat

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

* fix: address Copilot review — last-wins dedup + isEnabled guard

- Change snapshot dedup from first-wins to last-wins so rewind batch
  records (which contain the most up-to-date snapshot state) override
  earlier incremental records for the same promptId.
- Guard validateRestoredSnapshots behind isEnabled() to skip I/O
  when file checkpointing is disabled.

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

* fix: address wenshao review — ghost snapshots + test assertion

- Session.ts: slice snapshots to targetTurnIndex+1 to exclude
  turns being discarded (fixes ghost-snapshot persistence)
- AppContainer.tsx: only pass snapshots when file restore succeeded
  (avoids writing un-truncated snapshots for conversation-only rewind)
- Session.test.ts: update rewindRecording assertion to expect 3 args

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

* fix: address wenshao R2 — slice snapshots in AppContainer + add deserialize warning

- AppContainer.tsx: use .slice(0, targetTurnIndex + 1) to match
  Session.ts behavior (prevents ghost snapshots for conversation-only rewind)
- sessionService.ts: log warning instead of silent continue on malformed
  file_history_snapshot deserialization

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

* fix: enable daemon file checkpointing + seed TUI promptCount on resume

Two fixes from wenshao's local verification:

1. ACP daemon sessions had fileCheckpointingEnabled=false because
   stdin is a pipe (non-TTY). Add enableFileCheckpointing() to Config
   and call it in acpAgent.newSessionConfig so daemon /rewind works.

2. TUI prompt counter restarted at 0 on --resume, colliding with
   restored snapshot promptIds and corrupting the chain via last-wins
   dedup. Seed promptCount from the resumed conversation's user turn
   count so new promptIds don't overlap.

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

* fix: add makeSnapshot + recordFileHistorySnapshot to ACP prompt path

ACP sessions drive the chat through Session.prompt → GeminiChat,
bypassing GeminiClient.sendMessageStream where makeSnapshot lives.
This meant daemon-created sessions never produced file history
snapshots, leaving /rewind non-functional.

Add makeSnapshot + recordFileHistorySnapshot at the start of each
ACP prompt turn (mirroring client.ts:1488), using the existing
sessionId########turn promptId format.

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

* fix: add session_resume to integration test capability assertion

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

* fix: add debug logging to ACP makeSnapshot catch blocks

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

* fix(acp): move makeSnapshot after slash-command and hook checks

Locally handled slash commands (/help, /memory, etc.) previously
created file history snapshots even though no model turn was added.
This caused the snapshot index to drift from the real user turn count,
breaking rewind in web-shell sessions that use slash commands frequently.

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

* fix: address qwen-code-ci-bot review round 5

- Remove stale line number from comment (Session.ts)
- Single cast instead of double cast for systemPayload (sessionService.ts)
- Guard mkdir in copyFileHistoryBackups to prevent fork failure (sessionService.ts)

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

* fix: address wenshao review — mock, ordering guard, JSDoc placement

- Add makeSnapshot/rewind to FileHistoryService mock in Session.test.ts
- Invalidate cached FileHistoryService on enableFileCheckpointing() to
  prevent stale enabled=false if service was lazily created first
- Move copyFileHistoryBackups above class JSDoc to fix association

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

* fix: truncate in-memory snapshots on ACP rewind + deduplicate recordFileHistorySnapshot

- Call restoreFromSnapshots(survivingSnapshots) in ACP rewindToTurn to
  prevent phantom snapshots from accumulating in the in-memory array
  after conversation-only rewind
- Simplify recordFileHistorySnapshot to delegate to the batch variant

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

* fix: add restoreFromSnapshots to FileHistoryService mock

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 13:50:16 +08:00
顾盼
963fc543d1
ci(desktop): mac code-signing + App Store Connect API-key notarization (#5013)
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
* chore(desktop): drop dead NOTARIZE env flag from mac signing paths

electron-builder (>=24) auto-notarizes via notarytool whenever APPLE_ID,
APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID are present in the env. The
NOTARIZE=true flag set in the release workflow, build-dmg.sh, and
scripts/build/darwin.ts was never read by electron-builder, and the
build-dmg.sh comment claiming it enabled notarization was misleading.
Remove the no-op and document the actual auto-detection behavior.

* ci(desktop): notarize via App Store Connect API key instead of Apple ID

Switch the macOS desktop release notarization path from the Apple ID +
app-specific password method to the App Store Connect API key method,
which is more robust (no 2FA, no password expiry) and reuses the notary
key already provisioned for the org.

The signing step now reads APPLE_NOTARY_API_KEY_P8_BASE64,
APPLE_NOTARY_KEY_ID, and APPLE_NOTARY_ISSUER_ID, decodes the .p8 to a
temp file, and exports APPLE_API_KEY/APPLE_API_KEY_ID/APPLE_API_ISSUER,
which electron-builder (>=24) consumes to notarize via notarytool.
Published mac releases now require those notary secrets plus
APPLE_TEAM_ID.
2026-06-12 13:14:43 +08:00
qqqys
2cfa32f785
Add /cd command (#4890)
* feat(cli): add /cd command

* fix(cli): stabilize cd command checks

* fix(cli): cap pending cd trust confirmations

* fix(core): allow resuming migrated cd sessions

* test(core): fix cd relocation path expectations

* fix(core): accept migrated sessions in all project checks

* fix(core): preserve runtime workspace dirs on cd

* fix(cli): keep branched relocated sessions loadable

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-12 13:11:34 +08:00
qqqys
f2ebfeaece
fix(goal): persist iteration count across resume so MAX_GOAL_ITERATIONS bounds the whole session (#5000)
* fix(goal): persist iteration count across resume so MAX_GOAL_ITERATIONS bounds the whole session

On resume, restoreGoalFromHistory re-arms an unfinished /goal via registerGoalHook,
which always primed the store with iterations: 0. Since findGoalToRestore only
returned the condition, the running count recorded in the transcript was dropped,
so the MAX_GOAL_ITERATIONS safety cap was re-granted in full on every resume —
an unreachable goal could auto-loop another full budget after each /resume.

The count is already persisted (checking goal_status items carry iterations), so
the fix just reads it back:
- findGoalToRestore returns { condition, iterations } from the latest non-terminal
  goal_status item (set items restore at 0).
- registerGoalHook accepts an optional initialIterations (default 0, clamped at 0).
- restoreGoalFromHistory threads the restored count through.

Resume re-arm stays passive — continuation timing is unchanged.

Closes #4999

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

* fix(goal): persist cumulative checking iterations

* fix(goal): record checking status during continuations

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-12 13:10:06 +08:00
qqqys
5bcd2665da
chore(daemon): remove dead code and simplify control flow (#4789)
* chore(daemon): remove dead code and simplify control flow

- acpAgent.ts: remove unreachable `if (!bootstrapSkipsMcpDiscovery)`
  block (32 lines) — the constant was hardcoded `true`, making the
  MCP-ready wait and failed-server warning permanently dead code.
  Inline the flag directly into the `config.initialize()` call.

- bridge.ts: remove no-op try/catch in `removeRuntimeMcpServer` —
  every path through the catch block re-threw the error unchanged.
  Collapse to a direct `await Promise.race(...)`.

- bridge.ts: merge `'resolved'` and `'recorded'` switch cases in
  `respondToSessionPermission` — both returned `true`.

- bridge.ts: remove 19-line orphaned deletion-archaeology comments
  (lines documenting `resolveAnyTrustedClientId` removal and the
  F3 permission-mediator lift — the functions no longer exist here).

- bridgeClient.ts: remove 9-line orphaned deletion-archaeology
  comment (documents types moved to permissionMediator.ts).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(daemon): preserve provider status metadata

* fix(daemon): sanitize provider status urls

* fix(daemon): sanitize account info base url

* fix(daemon): sanitize provider list base urls

---------

Co-authored-by: 衍星 <qiuyusheng.qys@alibaba-inc.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 13:08:28 +08:00