merge: sync 264 upstream QwenLM/qwen-code commits into HopCode - #82
Conversation
* feat(channels): add channel agent bridge abstraction Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(channels): handle bridge session lifecycle cleanup Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(channels): close bridge lifecycle review gaps Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#5978) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#5978) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix: address channel bridge review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#5978) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#5978) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: ytahdn <ytahdn@gmail.com>
* fix(ci): cover release integration regressions * fix(ci): retry linter archive downloads * fix(ci): keep release CI PR focused
…ned endpoints (QwenLM#3535) (QwenLM#5962) * feat(core): add --insecure flag to skip TLS verification for self-signed endpoints (QwenLM#3535) Enable skipping TLS certificate verification for outbound model API connections via a new --insecure CLI flag, QWEN_TLS_INSECURE, or NODE_TLS_REJECT_UNAUTHORIZED=0. The setting is applied to the undici dispatcher Qwen Code installs: a direct connection uses connect TLS options, while a proxied connection disables verification for the upstream origin (requestTls) and a self-signed HTTPS proxy (proxyTls). Off by default; behavior is unchanged when not enabled. Fixes QwenLM#3535 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(core): address review on --insecure (security + simplification) (QwenLM#3535) - Block project .env from enabling QWEN_TLS_INSECURE by adding it to PROJECT_ENV_HARDCODED_EXCLUSIONS, so an untrusted repo cannot silently disable TLS verification for all API connections. - Remove the unverifiable Bun fetch `tls` special-casing; Bun users can still opt out via NODE_TLS_REJECT_UNAUTHORIZED=0, which Bun honors natively. - Do not suggest `--insecure` in the TLS error hint when verification is already disabled; show a network/protocol-oriented message instead. - Add tests: env-flag regex/falsy branches, loadCliConfig env side-effect, fetch hint variants, and a security guard for the project .env exclusion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(core): harden --insecure per review (QwenLM#3535) - Block NODE_TLS_REJECT_UNAUTHORIZED from project .env too (initial load only consults PROJECT_ENV_HARDCODED_EXCLUSIONS), since isTlsVerificationDisabled() honors it. - When opting out, set NODE_TLS_REJECT_UNAUTHORIZED=0 process-wide in loadCliConfig and emit a stderr MITM warning. This makes the opt-out effective on the Bun runtime and the proxy-creation fallback path (which the undici dispatcher does not cover), and gives a user-visible signal. - Avoid evaluating isTlsVerificationDisabled() twice on the proxy path via a default parameter on getOrCreateSharedDispatcher. - Update tests accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): broaden --insecure warning and log it; add env-path tests (QwenLM#3535) - Broaden the TLS-disabled warning to state the process-wide blast radius (API, OAuth, MCP servers, child processes), since NODE_TLS_REJECT_UNAUTHORIZED=0 is set process-wide. - Also emit the warning via debugLogger so the state is discoverable in ~/.qwen/debug/ after terminal scrollback is gone. - Add tests for the env-var-only path (pre-set QWEN_TLS_INSECURE) and the already-disabled guard (no duplicate assignment/warning). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…QwenLM#6009) getLastModelMessageText() concatenated all text parts from the last model message without filtering out thought parts (part.thought === true). This caused the Stop hook's last_assistant_message to include the model's internal reasoning text, making structured output validation difficult. Added !part.thought to the filter condition in both GeminiChat and the client fallback implementation, consistent with existing thought filtering in copyCommand, partUtils, and stripThoughtPartsFromContent. Added two unit tests: - filters out thought parts from mixed model messages - returns undefined when all text parts are thoughts Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…ubleshooting (QwenLM#4943) * feat(cli): add safe mode * fix(test): add isSafeMode to test mocks and config stubs * fix(ui): hide memory toggles in safe mode * fix(cli,core): harden safe mode guards for permissions and extensions Add safeMode guards to all permission-related settings in loadCliConfig that previously only checked bareMode — mergedAllow, mergedAsk, mergedDeny, resolvedCoreTools, disabledSlashCommands, disabledTools, coreTools, allowedTools, autoMode, and approvalMode. Without these guards, a malicious repo settings.json could auto-approve all shell commands even in --safe-mode. Refactor the extension loading conditional to eliminate the empty if-body (safeMode branch) that was fragile against linter auto-fixes. Add diagnostic log line in Config constructor when safe mode activates, so daemon/headless/CI contexts have a log entry explaining which subsystems are disabled. Add comprehensive safe mode test coverage (7 tests) in CLI config tests. Co-Authored-By: Qwen Code <noreply@alibaba-inc.com> * fix(test): add isSafeMode to config stub in gemini.test.tsx The 'writes non-interactive warnings' test from main was missing isSafeMode() on its configStub, causing a TypeError after the merge. Co-Authored-By: Qwen Code <noreply@alibaba-inc.com> * fix(cli,core): close safe-mode bypass gaps in context loading and child agents Add isSafeMode() guards to code paths that previously only checked bareMode, allowing project/user customizations to leak through in safe mode: - AppContainer performMemoryRefresh: skip context file loading - /directory add: skip include-directory memory reload - CLI config: output-language.md, includeDirectories, sandbox, loadMemoryFromIncludeDirectories - Config.getProjectHooks/getUserHooks: return undefined in safe mode - Daemon workspace agents: pass real safe-mode state instead of hardcoded false Propagate safeMode through AgentPersistedCliFlags so child agents spawned via the agent tool inherit safe mode. Add debug logging when subagent-manager overrides a requested level in safe mode. Add unit tests for safe-mode branches in subagent-manager and skill-manager that were previously untested. Co-Authored-By: Qwen Code <noreply@alibaba-inc.com> * fix(test): add isSafeMode to directoryCommand mock config The new config.isSafeMode() call in directoryCommand.tsx threw TypeError in tests because the mock config did not stub it. The error was caught by the surrounding try/catch, flipping messageType from info to warning and failing 4 tests. Co-Authored-By: Qwen Code <noreply@alibaba-inc.com> * fix(safe-mode): address code review feedback and improve consistency - Fix type error in config test (approvalMode string literal) - Fix dead else-if branch in extension loading logic - Remove unnecessary definite assignment assertion on safeMode field - Add safe-mode guards for disabledSkillNamesProvider and getMcpServers() - Gate getPreventSystemSleepEnabled() with isSafeMode() check - Extract shared isSafeModeEnv() helper to deduplicate env var resolution - Refactor comma operator in subagent-manager ternary for readability - Use config.getWorkingDir() instead of process.cwd() in AppContainer - Add warning when --core-tools flag is used in safe mode - Add test coverage for getMcpServers() returning empty in safe mode * fix: rename safeMode.ts to safe-mode.ts for kebab-case convention * fix(safe-mode): close extension leak and restore bare-mode sleep prevention - Remove dead else-if branch: adding !isSafeMode() guard made it unreachable since the first if already covers !safeMode && !bareMode - Remove unused getExplicitExtensionNames() private method - Remove unrelated !getBareMode() from getPreventSystemSleepEnabled to preserve bare-mode sleep inhibition * fix(safe-mode): address review feedback on bare-mode extensions, env parsing, and test mock - Add missing Protocol export to contentGenerator mock in safe-mode test - Restore bare-mode explicit extension loading that was dropped in previous fix - Use shared isTruthy helper for consistent env var parsing in isSafeModeEnv * fix(core): add missing DEFAULT_SENSITIVE_SPAN_ATTRIBUTE_MAX_LENGTH to safe-mode test mock The telemetry mock in config.safe-mode.test.ts was missing the DEFAULT_SENSITIVE_SPAN_ATTRIBUTE_MAX_LENGTH export, causing all 14 tests to fail with: No "DEFAULT_SENSITIVE_SPAN_ATTRIBUTE_MAX_LENGTH" export is defined on the "../telemetry/index.js" mock Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen Code <noreply@alibaba-inc.com> Co-authored-by: 易良 <1204183885@qq.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…lling in VP mode (QwenLM#6002) * fix(cli): wrap thought viewer text to visual lines The full-screen ThinkingViewer split `data.text` on '\n' and rendered each logical line with `wrap="truncate-end"`. A thought is usually a single long paragraph with no newlines, so it collapsed to one ellipsised row above an empty box and `maxScroll` stayed 0 (could not scroll). Pre-wrap the text to visual rows at the inner content width (border + paddingX = 4 cols) before slicing, reusing the existing `wrapToVisualLines` helper (now exported from ConversationMessages). Scrolling and rendering now operate on the same rows the user sees. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): collapse VP viewport to content and sink the composer In terminal-buffer (VP) mode the conversation wasted vertical space: - VirtualizedList pinned its root box to the full `containerHeight`, so short content left a tall blank gap and pushed the composer far down. Collapse the box to `min(containerHeight, totalHeight)` so it grows with its content like the legacy <Static> path; the scroll math still uses the full height, so overflow scrolling is unchanged. - `availableTerminalHeight` subtracted `staticExtraHeight` + `MAIN_CONTENT_HEIGHT_RESERVATION`, the <Static> overflow-flicker guards. VP clips natively and does not need them, so they stranded ~5 blank rows below the composer (input never reached the bottom). Drop the reservation in VP; non-VP keeps it unchanged. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * perf(cli): coalesce VP scroll events per frame Terminal mouse reporting emits one event per row crossed, so a brisk wheel spin or scrollbar drag fired a rapid burst, each applied synchronously with a full Ink reflow + terminal flush — the source of the choppy scroll. Accumulate wheel deltas / the latest drag row in refs and flush at most once per ~16ms frame. A press still applies instantly; under NODE_ENV==='test' updates apply synchronously so the existing timer-free tests keep passing. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): cancel queued scroll on scrollbar press A wheel burst schedules a 16ms coalescing flush. If the user clicked the scrollbar within that window, the press applied its row immediately but the still-armed timer then fired `scrollBy` with the leftover wheel delta, yanking the view off the clicked row. Clear the pending wheel/drag intent and cancel the timer when a scrollbar press takes over. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): keep a small height slack in VP mode Zeroing the VP layout reservation removed all slack, so when the composer grew (multi-line input) the one-frame-late controlsHeight measurement briefly oversized the list and overflowed the terminal — the exact jitter the layout change aimed to remove. Drop only the Static-specific staticExtraHeight (3) and keep MAIN_CONTENT_HEIGHT_RESERVATION (2) as a transient-measurement buffer; the composer still sits far closer to the bottom than before (was ~5 stranded rows). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(cli): move wrapToVisualLines to textUtils It lived in the 1000-line ConversationMessages component and ThinkingViewer imported it across components. Co-locate it with its sibling visual-wrapping helper (sliceTextByVisualHeight) in utils/textUtils.ts and import from there. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(cli): share frame-coalesced scroll hook and test it Extract the per-frame scroll coalescing into useFrameCoalescedFlush and use it from both ScrollableList and ThinkingViewer (whose wheel handler was still un-batched, so a brisk spin in the expanded thought viewer stuttered). Drop the NODE_ENV==='test' escape hatch that made the production timer path unreachable in tests: the mouse-scroll tests now advance a real frame before asserting, exercising the batching/accumulation/precedence logic. Adds a regression test for a scrollbar press canceling a still-pending wheel flush. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): match legacy bottom spacing in VP mode (no reservation) The static/<Static> path reserves no blank rows under the composer — the staticExtraHeight + MAIN_CONTENT_HEIGHT_RESERVATION budget only caps inline streaming-message height, while the composer flows to the very bottom of the terminal. Reserve nothing in VP so its composer reaches the bottom the same way, instead of leaving a 2-row gap. The one-frame controlsHeight measurement lag on composer growth mirrors legacy mode letting the terminal scroll. 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>
* fix(cli): guard serve fast-path bundle closure Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): tighten serve fast-path bundle guard Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#5995) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#5995) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#5995) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…LM#6025) * fix(web-shell): prevent queued-prompt loss from drain race The auto-drain effect popped a queued prompt, called setQueuedPrompts, then submitted via setTimeout(0). Because the daemon flips streamingState asynchronously, the setState re-render could re-run the effect and pop a second prompt before the first registered as streaming — both submitted back-to-back and the first was lost. Arm an "awaiting turn start" gate synchronously at pop so the re-run is blocked until streamingState goes non-idle, released by a dedicated effect with a safety-net timer for a prompt that never streams (e.g. a queued slash command). Cleanup no longer cancels/re-queues the pending submit while the gate is armed. * feat(web-shell): friendlier Esc interruption + queued-prompt UX * refactor(web-shell): tidy Esc/queue code per review Behavior-preserving cleanups addressing review feedback on the Esc-interruption and queued-prompt changes: - Remove the now-dead queue.footer i18n key (EN + ZH) and the unreferenced .queuedHint CSS, orphaned when the Esc-clears-queue behavior was dropped. - Co-locate the queued-prompt styles in QueuedPromptDisplay.module.css instead of reaching into the parent App.module.css. - Make the Esc confirm-window constants the single source of truth: export them from escapeIntent.ts and drive the countdown-ring duration from one of them via a CSS custom property. - Nudge the queue-drain safety net with a dedicated tick counter instead of cloning queuedPrompts, so it no longer re-renders the composer for a no-op. - Drop a redundant !compact guard in StatusBar left over from flattening a ternary. - Document the pop/gate-arm ordering invariant in the drain effect.
…ns (QwenLM#6015) * fix(cli): stop scroll snap-back during parallel-agent runs (non-VP) In non-VP mode ink clears the whole terminal (including scrollback) on every repaint once the non-<Static> live frame exceeds the terminal height (see ink's shouldClearTerminalForFrame). During a parallel-agent run the pure-parallel inline panel rendered the UNFILTERED toolCalls, so running subagents were shown inline AND in LiveAgentPanel below the composer — two full rosters that push the live frame past the viewport. The per-second elapsed/token ticks then fire that clear continuously, so scroll-up snaps straight back to the bottom and flickers. Route the pure-parallel branch through the same inlineToolCalls hand-off as every other group: during the live phase render only the agents the panel is not showing (terminal rows en route to <Static>), keeping the header total honest via totalAgentCount. Add an availableTerminalHeight backstop to InlineParallelAgentsDisplay that windows the rows ("+N more") so the panel can never exceed its height budget. Refs QwenLM#5798 Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): gate SGR mouse tracking to VP mode so non-VP keeps native scrollback Enabling SGR mouse tracking (?1002h) makes the host terminal stop doing native scrollback on the wheel — it hands wheel events to the app (Terminal.app even diverts them to arrow keys, which then drive composer input history and the background-agent panel). That is only acceptable when the app itself owns the wheel: VP mode (ScrollableList) or an alternate-screen modal (ThinkingViewer), neither of which relies on main-screen scrollback. But a collapsed thinking block armed mouse tracking just for click-to-expand, unconditionally, so any non-VP session with thinking blocks could no longer scroll its transcript. Make useMouseEvents gate on VP mode by default: mouse tracking turns on only when `ui.useTerminalBuffer` is set. Surfaces that legitimately own the wheel pass the new `bypassVpGate` option (ScrollableList, ThinkingViewer). The thinking-block click handler does not, so in non-VP it stays dormant and native terminal scrollback is preserved (the block still expands via Alt+T). This is a single chokepoint, so future non-VP mouse subscribers can't reintroduce the regression. Refs QwenLM#5798 Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): tighten inline parallel-agent windowing and drop redundant filters Address review feedback on the parallel-agent snap-back fix: - InlineParallelAgentsDisplay: the height backstop kept Math.max(1, budget-2) rows, so at availableTerminalHeight <= 2 it rendered header + indicator + 1 data row (3 lines) and overflowed the very budget meant to cap it, which can re-trigger the shouldClearTerminalForFrame snap-back. Reserve the header and (when overflowing) the indicator first, then window the remaining rows; at a budget of 1 keep only the header (its label still states the total). Total rendered height now never exceeds the budget. Adds budget=1/2 regression tests. - ToolGroupMessage: isPureParallelAgentGroup already guarantees every entry is a subagent, so the isSubagentToolEntry filters on inlineToolCalls and on totalAgentCount were no-ops; use inlineToolCalls and toolCalls.length directly. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): cap inline parallel-agents only in the live phase + test gaps Address /review feedback: - ToolGroupMessage: only forward availableTerminalHeight to InlineParallelAgentsDisplay during the live phase (isPending). The height backstop guards the non-<Static> live frame; once committed the rows live in <Static> with no snap-back risk, and MainContent passes staticAreaMaxItemHeight (>=100) for committed items. Forwarding that let the cap fire on scrollback and could permanently hide completed agents behind a static "+N more". Pass undefined when committed, per the component's documented contract. - InlineParallelAgentsDisplay.test: add a "generous budget" case (10 agents, budget 20) pinning the `rows.length + 1 > budget` boundary so a regression to >= would be caught instead of silently truncating when there is room. - ThinkingViewer.test (new): pin that the modal wheel handler subscribes WITH bypassVpGate: true (mirror of the HistoryItemDisplay test that pins the opposite contract), so a future refactor dropping the flag can't silently break wheel scrolling in non-VP mode. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): pin live-only height cap and windowing boundary Address /review test-coverage feedback: - ToolGroupMessage.test: add a contrast case that renders the same many-agent group with isPending={true} vs isPending={false} at the same tight availableTerminalHeight, asserting the overflow indicator appears only in the live phase and every agent renders in the committed phase. Pins the load-bearing `isPending ? availableTerminalHeight : undefined` conditional. - InlineParallelAgentsDisplay.test: add the budget=3 boundary case (the first budget at which a single data row appears: rowsFit = budget - 2 = 1), so an off-by-one in the windowing arithmetic is caught at the transition point between budget=2 (zero rows) and budget=3 (one row). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(cli): document the 1-line-per-AgentRow invariant the height backstop relies on Address /review feedback: the windowing math (rowsFit = availableTerminalHeight - 2) counts exactly one terminal line per AgentRow. That invariant — held today by the wrap="truncate-end" Texts in AgentRow — was undocumented, so a future change adding multi-line content (wrapped activity column, progress bar) would silently overflow the budget and re-trigger the shouldClearTerminalForFrame snap-back this PR fixes. Add the invariant at both the AgentRow definition and its render site. Comment-only; no behavior change. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): pin the windowing `>` boundary at the exact-fit budget Address /review feedback: the "generous budget" case used budget=20 with 10 agents, but `11 > 20` and `11 >= 20` are both false, so a regression from `>` to `>=` in `rows.length + 1 > availableTerminalHeight` would go undetected. Use budget=11 (= rows.length + 1) instead: `11 > 11` is false (no windowing) while `11 >= 11` is true (windowing fires a spurious "+1 more"). The not.toContain( 'more agent') assertion catches the flip — the line count coincides at 11 either way, so it is the indicator, not the height, that pins the boundary. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(cli): correct InlineParallelAgentsDisplay docstring to dual-phase usage Address /review feedback: the module docstring claimed the panel is "Rendered in the committed phase only", but this PR routes it through ToolGroupMessage's inlineToolCalls hand-off so it renders in BOTH phases — live phase showing only terminal agents (running/background owned by LiveAgentPanel) under an availableTerminalHeight windowing cap, committed phase showing the full roster uncapped. Update the docstring to describe the dual-phase behaviour and the height backstop. Comment-only; no behaviour change. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): enforce 1-line-per-element with wrap="truncate-end" everywhere Address /review [Critical]: the height backstop budgets the header, the optional "+N more" overflow indicator, and each AgentRow as exactly 1 terminal line, but the header Text, the indicator Text, and the AgentRow trailing (elapsed · tokens) Text lacked wrap="truncate-end". On a narrow terminal any of them could wrap to 2+ lines, pushing the rendered frame past availableTerminalHeight and re-firing the shouldClearTerminalForFrame snap-back this PR fixes — and it made the documented "all Text elements use truncate-end" invariant false. Add wrap="truncate-end" to every Text in the panel (header, indicator, glyph, trailing) so the 1-line invariant actually holds. No behaviour change at normal widths (the labels already fit); tightens the degenerate-width guarantee. 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>
…-in SDK transports export (QwenLM#5852) * fix(daemon): resume /acp session stream via Last-Event-ID (recover mid-turn content) The `/acp` Streamable-HTTP session event stream was live-only: it emitted no SSE `id:` sequence and ignored a `Last-Event-ID` reconnect header. When a control-plane proxy idle-closed the long-lived SSE mid-turn, every content frame the daemon produced during the gap (`session/update` carrying agent_thought_chunk / agent_message_chunk) was lost — the turn still settled, so the UI showed "done" with an empty/truncated body, and only a re-send recovered it (tracked as §1.8 in the integration notes). The replay engine already exists and is battle-tested on the REST surface: EventBus assigns a monotonic per-session `id`, keeps a bounded ring, and `subscribeEvents({ lastEventId })` replays `id > lastEventId` before live events flow. This wires the `/acp` transport to it — no eventBus/bridge change. - transport-stream / sse-stream / ws-stream: `send(message, id?)`. SSE emits an `id:` line when `id` is present (mirrors REST `formatSseFrame`); WS ignores it (stateful, no replay). - connection-registry: `sendSession(…, id?)` threads the cursor; the pre-attach session buffer stores `{ frame, id? }` so a buffered frame keeps its `id:`. - dispatch: `translateEvent` passes `event.id` for bus events; `pumpSessionEvents` forwards `lastEventId` to `subscribeEvents`. - index: the `GET /acp` session branch reads `Last-Event-ID` (strict decimal-only parse, same rule as REST) and passes it to the pump. Bus-originated frames (session/update, request_permission, daemon notifies) carry an `id:`; JSON-RPC responses and synthetic terminal frames do not, so they don't burn a slot in the resume sequence. Backward compatible: clients that send no `Last-Event-ID` get live-only behaviour as before, and `id:` lines are inert for clients that ignore them. Design: docs/design/daemon-acp-http/sse-resumable-stream.md * fix(daemon): make /acp resume actually engage — session-stream grace/reclaim + replay guards Addresses three review Criticals on the §1.8 plumbing: on its own the `id:`/`Last-Event-ID` wiring never fired in the real close-then-reconnect flow, and once it does fire two replay-correctness gaps become reachable. 1. Session-stream grace/reclaim (the core fix). A transport-level session-stream close used to run the FULL `closeSessionStream` teardown — removing ownership, aborting the in-flight prompt, detaching the bridge client. In the real EventSource/proxy order (old socket closes first, then reconnect) that meant the reconnect carrying `Last-Event-ID` was rejected 403 before the cursor was read, and the prompt was already aborted — so replay had nothing to resume. Now a transport close DETACHES (`detachSessionStream`): it stops only the stream + subscription and keeps the binding, ownership, prompt, and bridge-client alive for a grace window (`SESSION_GRACE_MS`, mirrors `CONN_GRACE_MS`). A reconnect within the window reclaims (clears the timer); otherwise the grace timer runs the full teardown, bounding runaway cost. Full teardown stays immediate for explicit `session/close` and connection destroy. The GET handler branches on `stream.isClosed` (transport close → grace; pump-ended-while-open → full close). 2. No double-delivery (buffer ↔ ring overlap). `attachSessionStream` records the max bus id flushed from the pre-attach buffer; the GET handler advances the replay cursor to `max(Last-Event-ID, lastFlushedEventId)` so the ring replay doesn't re-emit an already-flushed frame. 3. Idempotent `permission_request` under replay. `translateEvent` reuses the existing `conn.pending` entry for a `bridgeRequestId` (re-sends the same outbound id) instead of minting a second id+entry — no orphan pending, no duplicate prompt on a ring-replayed permission. Also: extract `parseLastEventId` to a shared `serve/sse-last-event-id.ts` used by both REST and `/acp` (no drift; logs the rejected value); log `lastEventId` in the pump error. Tests: real close-then-reconnect order (200 not 403 + prompt not aborted); overflow Last-Event-ID; replayed permission reuses pending id; registry grace/reclaim + buffer-flush-preserves-id. Full acp-http suite green (216). * feat(sdk): expose ACP transports via opt-in ./daemon/transports subpath The resumable ACP-over-HTTP transport (AcpHttpTransport, native supportsReplay + Last-Event-ID) and the negotiateTransport factory were reachable only from source paths inside the monorepo — the published `@qwen-code/sdk/daemon` barrel intentionally omits them to keep its budget-checked browser bundle lean, so external consumers (agent-web) had no import path short of forking. Add a separate opt-in subpath `@qwen-code/sdk/daemon/transports` that ships AcpHttpTransport / AcpWsTransport / AutoReconnectTransport / RestSseTransport / negotiateTransport as their own browser+node bundle. The default `./daemon` barrel and its byte budget are unchanged, so REST-only consumers stay tree-shaken and pay nothing for the transports. Also add a `fetchFn` option to NegotiateTransportOptions so callers can inject auth/proxy/test fetch instead of the hardcoded global. - build.js: emit dist/daemon/transports.{js,cjs}; reuse the node-builtin guard for the new browser bundle (no size budget — it legitimately ships the transports) while keeping the default barrel's budget check. - daemon/index.ts: update the rationale comment to point at the subpath. - daemon-transports-surface.test.ts: lock the runtime + type surface and the package.json exports entry. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): resume cursor must not skip in-flight-lost frames Round-3 review (qwen-code-ci-bot) flagged a silent-frame-loss Critical in the §1.8 resume path I added: `resumeCursor = max(Last-Event-ID, lastFlushedEventId)` advances the ring-replay cursor past the buffer, but a frame sent to the now-dead socket yet never received by the client has a bus id BELOW the buffer's ids and ABOVE the client's cursor — so the max() skips it and the ring replay never re-emits it. Exactly the proxy idle-close mid-turn frame §1.8 is meant to recover. Fix without trading loss for duplicates: a buffered bus event is ALSO in the EventBus ring (it was published there to get its id), so the ring replay started at the client's cursor is the single delivery path for every bus event after the cursor. `attachSessionStream` now takes the resume cursor and, when resuming, does NOT flush id-bearing buffered frames — the ring owns them, delivering each exactly once including the in-flight-lost frame. Id-less frames (JSON-RPC replies via `replySession`, not ring events) are still flushed — their only delivery path. The GET handler sets `resumeCursor = lastEventId` verbatim; `lastFlushedEventId` is removed. Also from the same review: - sse-last-event-id `safeLogValue`: strip ALL C0 control chars + DEL (not just CR/LF) so a crafted `Last-Event-ID` can't smuggle ANSI ESC / null bytes onto an operator's terminal via stderr. - ws-stream: regression test asserting `send(msg, id)` keeps the WS wire frame bare JSON (no SSE `id:` framing leak). - connection-registry: resume-path test (id-bearing frames skipped, id-less reply still flushed); design doc updated. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(daemon): inline resumeCursor alias to lastEventId Review nit (yiliang114): after the prior commit dropped the `max()` logic, `resumeCursor` is a pure alias for `lastEventId`. Use `lastEventId` directly in the `pumpSessionEvents` call and the error log; drop the alias. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(daemon): refresh stale resume comments + add gap-delivery test Round-4 review (qwen-code-ci-bot, against the now-corrected resume model): - connection-registry: the attachSessionStream CONTRACT comment still cited a `promptAbort?.abort()` call in the index.ts onClose handler that an earlier commit removed. Rewrite it to describe the current model — each stream's pump has its own abort controller and teardown is identity-guarded in `onPumpSettled`, so installing the new stream first makes the old stream settle into detach-with-grace rather than tearing down the in-flight prompt. - dispatch: the stream_error frame comment ("no bus id, so no SSE id: line") contradicted the code passing `event.id`. Make it truthful: pass the cursor through if present; a synthetic terminal frame has no id so none is written. - connection-registry.test: add the explicit detach → produce gap events → reattach → flush-exactly-once test (the PR's core value prop at the registry layer), incl. a second reattach asserting the buffer drained. The two Criticals in the same review referenced `resumeCursor` / `lastFlushedEventId` / `Math.max`, all removed in prior commits — obsolete against current code (answered + resolved on the threads). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): preserve stream order on resume + harden grace/permission paths Round-5 review (wenshao + qwen-code-ci-bot): - [Critical, wenshao] Out-of-order completion on resume. attachSessionStream flushed id-less buffered JSON-RPC replies (e.g. a session/prompt result that landed during the detach gap) immediately — ahead of the ring replay that redelivers the content chunks preceding them, so a client could see "prompt complete" before the body (the truncated-body failure §1.8 fixes). Now on resume those id-less frames are DEFERRED in the buffer; the event pump releases them via flushBufferedSessionFrames once the replay boundary (replay_complete / state_resync_required) passes, preserving original order. Fresh connects (no cursor, no replay) still flush the whole buffer in order. - [Critical, ci-bot] Permission auto-denied during the reconnect grace window: a permission_request arriving while binding.stream is detached cancel-denies, so a client reconnecting within grace can't vote. The structural fix (defer the vote across grace) belongs with the §1.7 permission-coordination follow-up; here, log an operator breadcrumb when it fires during grace, and document the synchronous-translateEvent INVARIANT the direct binding.stream .send relies on. - [ci-bot] Stale-stream detach is now tested (reclaim installs s2; a late s1 close is a no-op — no teardown, no grace re-arm). Grace-expiry teardown now logs a breadcrumb so a vanished session is distinguishable from explicit close. TS4111: bracket-access the index-signature exports entry in the SDK surface test. transports browser bundle now has a size budget (MAX_TRANSPORTS_BROWSER_BUNDLE_BYTES = 48KB; current ~29KB). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): route session-scoped /acp responses so prompts don't hang [Critical, wenshao] The published AcpHttpTransport could hang real session/prompt + config requests. Its subscribeEvents() opened REST GET /session/:id/events and only sendRequest's connection-scoped stream resolved responses — but the daemon's replySession() routes session-scoped JSON-RPC replies onto the session-scoped /acp stream, which the transport never read. So a session/prompt reply was never observed → the pending request never settled. Switch subscribeEvents to the session-scoped /acp stream (GET /acp + Acp-Session-Id) — the resumable §1.8 stream the daemon puts session replies on — and dispatch each raw JSON-RPC frame by shape: - response (id, no method) → resolve the shared pending map (the fix) - notification (method, no id) → DaemonEvent via denormalizeAcpNotification, stamped with the real bus id from the SSE `id:` line (the synthetic denormalizer id is not resume-compatible; supportsReplay=true now tracks the authoritative cursor) - session/request_permission → surfaced as a permission_request event so consumers still see prompts (responding to the vote is the §1.7 follow-up) The connection-scoped stream still carries replies to connection-level requests (initialize, session/new). Adds 4 subscribeEvents unit tests (stream selection + headers, notification→event+busId, response consumed-not- yielded, permission surfaced). Full SDK suite green (1062). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk,daemon): harden /acp SSE parser + grace/flush observability Round-6 review (qwen-code-ci-bot), all additive / no behavioural side effects: - [Critical] Unbounded SSE buffer in the new AcpHttpTransport session-stream parser → OOM (tab crash for browser consumers). Add a 16 MiB cap mirroring parseSseStream's MAX_BUF_CHARS, and reuse parseSseStream's CRLF-aware `consumeFrames` splitter (now exported) instead of an inline `\n\n` scan — closing the CRLF, multi-line `data:` join, and trailing-CR gaps in one go. - Deferred-flush ordering race: the pump's post-loop safety flushDeferred() now runs only on a non-aborted exit. An abort means the stream was detached/reclaimed; flushing there could drain the deferred reply onto a reclaiming stream ahead of its own replay (reintroducing the out-of-order delivery the deferral prevents). On error the frames stay buffered for the next attach — never lost. - Grace reclaim now logs (detach + grace-expiry already did) so the reconnect trail is complete for operators. - sse-last-event-id doc: corrected the "shared by REST and ACP" claim — after the QwenLM#5809 serve-route split REST keeps its own copy; unifying them would touch REST, so it's deferred (this PR keeps REST untouched). Thread on a full deferred-flush integration test: the ordering invariant is already locked at the unit layer (flushBufferedSessionFrames defer test + gap-delivery test); a full-HTTP timing test against the FakeBridge would be flake-prone, so it's intentionally not added. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon,sdk): harden /acp resumable stream from review round 7 Address review-pr findings on the §1.8 resumable stream, all additive / backward-compatible (REST untouched, no behavioural side effects): - connection-registry: guard flushBufferedSessionFrames against a closed stream so deferred replies stay buffered for the next reconnect instead of being dropped onto a dead socket. Keep the synchronous in-order enqueue (SseStream serializes via one writeChain) — an await-per-frame drain would let a live event interleave between deferred frames and reorder the very replies this deferral preserves (W1). - connection-registry: log at the moment of detach so an operator can measure the real disconnect→reconnect gap against the grace window. - index: route sessionId through logSafe() in the event-pump error log, matching every other log line this PR adds (terminal-escape hardening). - AcpHttpTransport: remove the abort listener in the finally block so a long-lived signal reused across reconnects doesn't accumulate listeners. - AcpHttpTransport: parse the SSE `id:` cursor with the server's strict /^\d+$/ + MAX_SAFE_INTEGER rule instead of lenient Number() (rejects proxy-mangled hex/exponential/empty cursors). - AcpHttpTransport: document that an unparseable non-empty data frame is a corrupt frame (not a heartbeat); tracing it is a follow-up once the SDK grows a logger (the package lint config forbids console). - tests: add sse-last-event-id.test.ts (parseLastEventId accept/reject + safeLogValue control-char stripping/truncation) and a flushBufferedSessionFrames closed-stream-retains-buffer case. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon,sdk): round-8 review hardening for /acp resumable stream All additive / backward-compatible (REST untouched, no behavioural side effects): - AcpHttpTransport: attach a no-op catch to abortPromise so an already-aborted signal at entry (loop never enters, Promise.race never consumes the rejection) can't surface as an unhandled rejection. - AcpHttpTransport: document that opts.maxQueued does not apply to the /acp transport (the session stream is backed by the daemon's server-controlled EventBus ring; there is no client-tunable queue to forward it to) — intentionally ignored, not silently mis-applied. - index: run err.message through logSafe() in the event-pump error log (CR/LF/ANSI in a bridge error string would otherwise reach stderr raw), and add operator breadcrumbs for the previously-silent onPumpSettled branches (pump-ended-while-open full close; superseded-stream no-op), completing the detach/reclaim/grace trail. - tests: assert subscribeEvents writes Last-Event-ID on the outbound GET when resuming and omits it on a first connect (the resume cursor must reach the wire), plus an already-aborted-signal case that would fail on an unhandled rejection without the catch above. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): flush deferred /acp replies on replay_complete only The EventBus emits `state_resync_required` BEFORE the replay frames (the `epoch_reset` and `ring_evicted` paths both fall through to the replay loop and still emit `replay_complete` at the end). The pump was releasing the deferred id-less replies on EITHER boundary, so on a resync-triggering resume the buffered `session/prompt` result was flushed ahead of the replayed content chunks — the exact truncated-body reordering §1.8 fixes (client sees "done" before the body). Flush on `replay_complete` only. The live-only case (no cursor ⇒ no replay ⇒ no `replay_complete`) is still covered by the pump's post-loop safety flush. Add an over-the-wire integration test (resume with a reply buffered during the detach gap, bridge replays resync → content → replay_complete) asserting the reply lands AFTER the replayed content; verified it fails against the previous dual-boundary flush. Design doc updated. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): close two §1.8 grace/replay-ordering holes Both additive / in-scope / no REST change: - Replay-window reply ordering (connection-registry, dispatch): the resumptive-attach deferral only covered id-less replies ALREADY buffered from the detach gap. A prompt that finished AFTER the new stream attached but BEFORE replay drained went straight out live via `sendSession`, overtaking replay frames not yet sent. Add a per-binding `replayPending` flag (armed on resumptive attach, cleared on `replay_complete` in `flushBufferedSessionFrames`) and route `replySession`'s out-of-band replies through a new `sendSessionReply` that defers while it's set. In-band pump frames keep using `sendSession`, so the `replay_complete` frame itself can't be deferred (which would deadlock the release). - Connection reaper vs session grace (index, connection-registry): the conn-stream-close reaper treated only LIVE session streams as activity, so a session detached into its own `SESSION_GRACE_MS` window (stream undefined, graceTimer armed) didn't count — the connection could be reaped at `CONN_GRACE_MS`, 404-ing the imminent session resume and aborting the in-flight prompt early. Add `hasRecoverableSession()` and treat a grace-armed session as activity in the reaper guard. Unit tests for both at the registry layer. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): thread query params into ACP transport route extractors The exported ACP HTTP/WS transports reduced request URLs to `pathname` before the route table built JSON-RPC params, so every query parameter from the REST-style DaemonClient helpers was dropped — e.g. `readWorkspaceFile('a.ts', { maxBytes: 123 })` (`/file?path=a.ts&maxBytes=123`) produced `_qwen/file/read` with `params: {}`. Same for `/file/bytes`, `/stat`, `/list`, `/glob`, and `context-usage?detail=true`. Pass `parsedUrl.searchParams` into `extractParams` and coerce each query value to the type the daemon's ACP handlers require — the daemon validates `maxBytes`/`line`/`limit`/`offset` as real numbers and `detail` as the boolean `true`, neither of which a raw query string satisfies. Helpers `strParam`/`numParam`/`boolParam` keep the per-route extractors terse. `query` is optional so the existing path-only extractors are unaffected. (`/workspace/voice/transcribe` has no ACP route at all — separate gap, binary audio doesn't belong on the JSON-RPC transport.) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): route session-stream replies via a background pump (no-subscriber prompt) The daemon answers POST /session/:id/prompt (and session/cancel, set_config_option, set_mode, set_model) with 202 and routes the JSON-RPC result onto the SESSION stream via replySession — not the connection stream the transport pumps. So a DaemonClient that calls prompt() but never iterates subscribeEvents had nothing reading that reply, and sendRequest()'s pending promise never resolved → prompt() hung forever. For these session-reply methods, sendRequest now opens a reference-counted background session-reply pump (GET /acp + Acp-Session-Id) that routes JSON-RPC responses to `pending`, released when the request settles. It's suppressed when a subscribeEvents consumer is already iterating that session (tracked via activeSessionSubscriptions) — the daemon's session stream is single-reader, so a competing GET would detach the consumer's; in that case the consumer already routes the reply (the W2 fix). The pump skips notifications and permission requests (method-bearing frames) so a permission request id can't be mis-routed onto a pending response slot. All five methods require an owned session, so the pump's GET is always authorized. Disposed pumps are aborted in dispose(). Verified the new test times out without the pump and passes with it. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): sequence /acp deferred replies by bus watermark + harden grace/reap Address review on the §1.8 resumable-stream fixes: - replayPending is now set from the current attach mode every time (resume arms, fresh connect clears) so an aborted resume that skipped its boundary flush can't strand the flag and buffer every later reply forever (MsyIq, MylZ4). - Deferred out-of-band replies carry a watermark (anchorId = bus head at produce time) and release only once the pump delivers through that id, via per-event releaseDeferredSessionReplies + endReplayDeferral at replay_complete. A result produced during a slow replay no longer jumps ahead of tail content still flowing as live events behind the boundary (MsyIt). Unanchored fallback replies still release at the boundary. - Connection reap re-evaluates after a session reclaim grace expires (connGraceExpired + onSessionGraceExpired), so a conn blocked from reaping by a then-recoverable session no longer lingers to the 30-min idle sweep (MsyIs). - Wrap the grace-timer teardown in try/catch so a throwing detach callback can't crash the daemon from a bare setTimeout (MylZ8). - sse-last-event-id reuses the shared logSafe sanitizer (covers C1 + Unicode bidi) instead of a narrower divergent regex (M1isz); refresh stale replayPending/flush JSDoc (MselO). Unit tests cover the replayPending reset, watermark ordering, grace expiry hook, and grace-timer try/catch. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): scope pending sweeps per stream + reset bus cursor on invalid id Address review on the ACP HTTP transport: - Tag each pending request with its routing scope (connection vs a sessionId). A connection-stream failure now sweeps only conn-scoped pendings, so it can't reject a session/prompt the session stream is about to resolve; the session reply pump mirrors this for its own scope (MselM). - An invalid id: line later in an SSE frame resets the bus cursor to undefined rather than carrying a stale earlier value into the event (MselW). - Strengthen the W2 response-routing test to register a pending request and assert the frame RESOLVES it, not merely that it isn't yielded (MylZ-). Add tests for the per-stream sweep partition and the id reset. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): accept `kind`-tagged _qwen/notify envelopes (don't drop resume signals) The daemon's session-stream translateEvent stamps `_qwen/notify` events under `kind` (state_resync_required, replay_complete, stream_error, model_switched, …), but denormalizeAcpNotification read only `type` and returned undefined for them — so subscribeEvents silently dropped every such event. During a ring-overflow resume the SDK would never see state_resync_required and would apply replayed events to stale state. Read `params['type'] ?? params['kind']` (preferring `type`, so other producers are unaffected) and add an SDK test feeding a `kind`-tagged notify through subscribeEvents (M2bvl). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): reject session-scoped pendings when the subscription stream closes A `session/prompt` reply routed through an active subscribeEvents consumer (no reply pump is started while a subscription is live) would hang if that session SSE stream closed before the reply arrived: the connection-stream catch only sweeps conn-scoped pendings, and subscribeEventsInner's finally cleaned up the reader but never the pendings. Sweep session-scoped pendings in that finally too, gated so it only fires when this is the session's last delivery route (no other active subscription — the ref-count still includes self here — and no reply pump), mirroring the reply-pump and connection-stream sweeps. Add a regression test (M2iHz). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): harden ACP SSE readers + reply-pump handoff + empty query param Address the latest review wave on the transport: - pumpConnStream now bounds its unread SSE buffer with the same MAX_SSE_BUF_CHARS guard the two session readers already have — the OOM vector (a server that never emits a `\n\n` boundary) was open on 1 of 3 readers — and attaches the no-op abortPromise.catch() crash guard (M3BYQ). - pumpSessionReplies mirrors subscribeEventsInner's abort handling: named listener ref removed in finally (no leak on a clean drain of a reused signal) + abortPromise.catch() so a pre-aborted signal can't surface an unhandledrejection; and it throws the HTTP status on a non-OK response so the failure is diagnosable rather than a silent void return (M3BYT, M3BYY). - subscribeEvents aborts any existing background reply pump for the session before opening the consumer stream. The single-reader session stream detaches the pump anyway; aborting it skips its teardown sweep so it can't spuriously reject the very `session/prompt` the consumer now delivers (M3BYa). - numParam treats an empty value (`?maxBytes=`) as absent, not Number('')===0 (M3BYd). Tests: empty-numeric-param omission, and the reply-pump abort-on-subscribe handoff (pump aborted, its pending not rejected). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): log a breadcrumb when the replySession anchor is unavailable The getSessionLastEventId fallback (deferring a reply unanchored when the ACP binding briefly outlives the bridge session) was silent. Emit a scoped stderr breadcrumb so an operator can tell that benign teardown race apart from an unexpected bridge regression that starts exercising the fallback (M3BYf). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): validate content-type in pumpSessionReplies before parsing as SSE pumpSessionReplies fed any 2xx body straight to the SSE frame parser. A non-SSE response (an HTML error page / a JSON proxy error injected by a CDN) would be consumed as garbage or hang the pump waiting for `data:` lines that never arrive — strictly weaker validation than its sibling subscribeEventsInner, which already guards content-type. Mirror that guard: between the res.ok check and getReader(), reject a body that isn't text/event-stream (cancelling it first). Add a test that a no-subscriber session/prompt whose reply pump GET returns text/html rejects instead of hanging (M3pAM). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): close reply-pump handoff strand race + scope-guard reply resolution Address the latest review wave: - subscribeEvents now removes the aborted reply pump's map entry SYNCHRONOUSLY, not just aborting it. Otherwise, if the subscription exited before the pump's async `.finally` deleted the entry, BOTH stranded-pending guards missed (the consumer sweep saw the entry still present and deferred; the pump's sweep skipped on abort) — a live session/prompt stayed in `pending` forever. Synchronous removal makes the consumer sweep deterministically responsible (M3w6Y). - Reply resolution (both the session-reply pump and the subscribeEvents consumer path) now skips a reply whose pending is scoped to a DIFFERENT session — defense-in-depth against a future daemon misroute silently cross-delivering across the SDK boundary (M3w6d). - denormalizeAcpNotification prefers a NON-EMPTY `type`; an empty-string `type` no longer wins over a valid `kind` and drops the event (M3w6i). Tests: reply-pump handoff happy-path (delivers/resolves) + strand case (rejects, not stranded); empty-`type`→`kind` fallback; the SSE buffer cap firing; the unanchored-reply hold/release branches; and connGraceExpired reset on reconnect (M3w6e, M3w6f, M3w6g). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): sweep session pendings in the subscribe wrapper + carry pump error Two follow-ups on the reply-pump handoff: - The session-scoped pending sweep moves from subscribeEventsInner's read-loop finally to the subscribeEvents WRAPPER finally. The read-loop finally only runs once the pump reaches the loop; a fast failure (fetch reject / non-OK / wrong content-type, all before the loop) skipped it and stranded the pending. The wrapper finally always runs, so it covers the fast-fail path too (M4DWq). - ensureSessionReplyPump captures the pump's error (HTTP 401/404, wrong content-type) and rejects swept pendings WITH it instead of a generic message, so a caller can tell auth failure from a network drop (M4DWx). Test: a 401 on the session GET (inner throws before its read loop) still rejects the in-flight session-scoped pending via the wrapper sweep. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): carry the subscription error into the wrapper sweep + guard tests Follow-ups on the reply-pump handoff: - The subscribeEvents wrapper sweep now rejects with the actual cause of the subscription's exit (captured from a try/catch around the inner generator) instead of a hard-coded generic message. On the fast-fail path (401 / wrong content-type thrown before the inner read loop) this wrapper finally is the only sweep that fires, so the caller now sees the real failure — parity with the reply-pump's pumpError reason (M4W9a). Tests: - the fast-fail sweep reason carries the 401 (not a generic message); - the M3pAM non-SSE rejection asserts the content-type cause reaches the caller (proves pumpError propagation) (M4W9g); - cross-session scope guard, both the consumer and the reply-pump resolution paths: a reply on session A's stream must not resolve a pending scoped to session B (M4W9e). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): guard onSessionGraceExpired in the grace timer against an uncaught throw The session grace-expiry setTimeout protected closeSessionStream with a try/catch but called the owner-supplied onSessionGraceExpired callback outside it. From a bare setTimeout, an uncaught throw there would crash the whole daemon — the same hazard the teardown guard exists for. Wrap it in its own try/catch (separate from teardown, so the conn-reap re-check still runs even if teardown threw). Add a regression test (M4i9z). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): make conn-stream pump CRLF-aware; align replay opt-in doc The connection-scoped SSE reply pump split frames with an LF-only `buf.indexOf('\n\n')`. A server or proxy emitting `\r\n\r\n` frame separators produces no `\n\n` substring, so the scan never found a boundary: the unread buffer grew to the OOM cap and the pump threw, leaving every connection-scoped JSON-RPC reply unresolved. Reuse the shared CRLF-aware `consumeFrames` splitter (and strip a trailing CR per data line) so the conn pump frames exactly like the session readers. Add a regression test that delivers a conn-scoped reply over `\r\n\r\n` and asserts it resolves. Also update the design doc: the in-repo SDK `AcpHttpTransport` opts in to replay in this PR (`supportsReplay = true` + resends Last-Event-ID), so the backward-compat note no longer reads as "keeps false until it opts in". Only the external agent-web transport flip stays deferred (already listed under Out of scope). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon,sdk): log replay-deferral arm; document session-reply routing invariant Add a stderr breadcrumb when a resume arms `replayPending`: while armed, `sendSessionReply` defers every out-of-band reply until the pump delivers `replay_complete`. If that sentinel never arrives (a dropped frame or a pump error), the replies stay buffered indefinitely with no other trace — the log gives operators a starting point. Silent on a fresh connect (no deferral). Covered by a new test. Also strengthen the `SESSION_STREAM_REPLY_METHODS` doc comment: name the authoritative daemon call sites (dispatch.ts), spell out the hang failure mode if the set drifts, and record a build-time grep / shared-constant enforcement as a follow-up (a cross-package invariant the SDK can't type-check). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): use bracket notation for _meta index-signature access (TS4111) `extractParams` returns `Record<string, unknown>`, so dot access to `params._meta` violates `noPropertyAccessFromIndexSignature` (set in the root tsconfig). The esbuild bundle path doesn't typecheck, so CI's build stayed green, but strict `tsc --noEmit` reports 6 × TS4111 at these sites (added with the query-param routing change). Switch all six to `params['_meta']`. Purely syntactic — runtime behavior is unchanged. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(sdk): don't silently hang conn-scoped requests on a failed conn stream `pumpConnStream` swallowed two failure paths: a non-2xx / no-body `GET /acp` did a bare `return`, and read-loop errors were caught and dropped. Either way the pump promise RESOLVED, so `openConnStream`'s `.catch` never ran — connection-scoped JSON-RPC pendings stayed in the map forever, and `connStreamAbort` was never cleared, so `ensureConnStream` saw it non-null and never reopened the stream (every later 202 request hung with no pump to deliver its reply). - A non-2xx / missing-body response now throws (HTTP status in the message) so the catch sweep rejects the conn-scoped pendings. - The read-loop catch rethrows real errors and only swallows an intentional abort (dispose / reconnect, which owns its own cleanup). - `openConnStream` clears `connStreamAbort` in a `.finally` (guarded on controller identity) so the stream reopens on the next request after ANY settle — clean close, error, or abort. Regression test: a 500 `GET /acp` rejects the conn-scoped pending (leaves session-scoped ones for their own stream) and the next request reopens. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon,sdk): conn-stream error propagation, listener cleanup, buffer eviction, param parsing Address review findings on the resumable /acp stream and exported SDK transports — all additive/backward-compatible, REST untouched: - openConnStream: reject connection-scoped pendings with the pump's REAL error (HTTP 401/503, network drop) instead of a generic message, mirroring ensureSessionReplyPump. - pumpConnStream: keep the abort listener in a named ref and remove it in finally so a long-lived signal reused across reconnects doesn't accumulate listeners (mirrors the session readers). - sendRequest: remove the abort listener on the happy path (the `{ once: true }` listener self-removes only when the signal fires), preventing per-call listener buildup on a shared caller signal. - pushCapped: under a content flood, evict a REPLAYABLE id-bearing frame (the ring redelivers it) before an irreplaceable id-less deferred reply — dropping the latter would hang the session/prompt caller — and log the dropped id. - acpRouteTable.boolParam: treat a present-but-empty value (`?detail=`) as absent, matching numParam, so `{ detail: false }` isn't forwarded for an unset param. - connection-registry resume flush: hoist the `splice(0)` snapshot into a named local to make the re-entrant copy-semantics invariant visible. Tests: boolParam empty-value omission; pre-attach buffer keeps the id-less reply under a 400-frame content flood. Document two exported-transport limitations (permission voting; session RPC awaited inside the subscribeEvents loop) as §1.7-adjacent follow-ups in the design doc. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): never evict an irreplaceable id-less reply from the pre-attach buffer The previous pushCapped change preferred evicting id-bearing (ring-replayable) frames, but left a degenerate hole: when the buffer fills with ONLY id-less deferred replies (no id-bearing entry exists), findIndex returned -1, dropIndex fell back to 0, and the oldest deferred JSON-RPC reply was evicted — silently hanging its session/prompt caller, the exact failure the guard exists to prevent (wenshao). Fix: when there is no replayable id-bearing frame to evict, do NOT drop — append and let the id-less replies exceed the soft cap. The cap is a memory bound against a CONTENT flood (id-bearing frames); id-less replies are bounded by the number of in-flight session RPCs the client actually issued (client-controlled, tiny), so they can't run away in practice. Log once when over the soft cap. The connection buffer (no id accessor) keeps its FIFO eviction unchanged. Test: 300 all-id-less replies buffered past the 256 cap are all delivered on reconnect, none evicted. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): hard ceiling + transition-only logging for the id-less reply buffer Follow-ups on the prior id-less-eviction fix (wenshao): - Defense-in-depth HARD cap. The soft-cap path never drops id-less replies, relying on "id-less replies are RPC-bounded" — true today but enforced only by convention. Add HARD_BUFFERED_FRAMES_CAP (4× soft = 1024): past it, drop the oldest id-less reply and log loudly, so a future non-RPC-bounded producer or a buggy client can't grow the daemon heap without limit. - Log at the soft-cap transition only (buf.length === MAX_BUFFERED_FRAMES), not on every over-cap push — the comment said "once" but it logged linearly with over-cap depth (~44 lines for 300 entries). Tests: assert the soft-cap warning fires exactly once for a 300-entry overflow; new test that 1100 id-less replies are bounded at the 1024 hard cap (oldest dropped, newest kept, loud breach log emitted). Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon,sdk): release all deferred replies when replay evicted frames; guard conn pump against session-scoped pending When ring replay overflows and emits state_resync_required, the watermark anchor guarantee is void (the anchored frame may have been evicted), so hold-until-watermark could freeze deferred session replies indefinitely. Track eviction through the pump loop and flush ALL buffered session frames at replay_complete in that case instead of waiting on the watermark. Also harden the SDK conn-stream pump: never resolve a session-scoped pending entry from the connection stream (scope guard), and document the fresh-attach (non-resumptive) caveat for ensureSessionReplyPump. Tests: add FakeBridge.getSessionLastEventId so integration replySession no longer throws (anchorId now reachable); cover the eviction cascade-release path, the conn-stream session-scope guard, and shared reply-pump ref-counting. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): flush deferred replies on mid-replay iterator error; cover anchored watermark e2e On an iterator error mid-replay the catch path re-throws, which drives onPumpSettled; while the session stream is still open that takes the closeSessionStream branch (full teardown, not a detach-with-grace), so any still-deferred session replies in the binding buffer were dropped rather than preserved. Flush them in the catch before signalling stream_error — same safety flush as the happy-path completion (the iterator has terminated, so no content frame can still race ahead of them). Correct the now-inaccurate happy-path comment that claimed error-path frames stay buffered. Add an end-to-end transport test for the anchored watermark path: with a real getSessionLastEventId, a deferred reply is held through pre-watermark content and released ON its anchor mid-replay, before replay_complete — distinguishing the watermark release from the unanchored release-at-boundary path. Document two deferrals in the design doc: response-replay idempotency for an already-resolved permission (a conformant client dedupes on _meta.requestId; full re-send belongs with the permission-coordination follow-up) and an automated guard for the SESSION_STREAM_REPLY_METHODS drift. Generated with AI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * feat(daemon,sdk): log replay effectiveness; de-shadow pump sweep; document resume/permission edges Add an operator breadcrumb at replay completion (resumed-from cursor, delivery high-water mark, bus replayed count, eviction flag) so 'did resume recover the gap?' is answerable from server logs. Rename the reply-pump sweep loop variable so it no longer shadows the outer pump-map entry (unrelated types). Clarify why the resume path drops id-bearing buffered frames (the event pump is aborted on detach, so only id-less out-of-band replies accumulate during the gap; ring replay owns id-bearing recovery and eviction is signalled via state_resync_required). Document two opt-in-transport edges as permission-coordination follow-ups: the no-subscriber reply pump's GET stream causing an agent permission_request to be routed to the pump and dropped, and why an automated SESSION_STREAM_REPLY_METHODS drift guard needs dataflow (the prompt reply is decoupled from its case block). 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> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
* fix(core): allow subagents to exit plan mode Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): guard subagent AUTO cleanup under AUTO parent Prevent a child approval-mode override from restoring shared AUTO permission rules while the parent config is still in AUTO mode. Add regression coverage for cleanup and child AUTO exits when the parent owns the AUTO lifecycle. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): read subagent approval mode from child config Ensure approval-mode overrides own the Config prototype getter so resumed agents and other wrapped configs read the child approvalMode instead of an inherited parent mock getter. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): cover subagent approval override state isolation Document the Config private-field coupling in the approval override and add focused assertions for plan blocking, plan gate copies, denial-state isolation, and AUTO-parent permission-manager restoration on throws. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…enLM#6012) * feat(core): support glob patterns in mcp.allowed and mcp.excluded Add matchesServerPattern/matchesAnyServerPattern helpers that support * (any sequence) and ? (single char) glob syntax. Apply symmetrically to both allow and deny predicates in getMcpServers, isMcpServerDisabled, and getMcpServerUnavailableReason. Existing exact-match configs are unaffected (no glob chars → string equality). Update settings schema descriptions to document the new glob support. Closes QwenLM#4940 (retargeted to glob matching; the deny-list capability already existed as mcp.excluded). * chore: regenerate settings schema for glob pattern descriptions * fix(core): fix missed .includes() call sites for glob MCP pattern matching - getBlockedMcpServers() used exact match, causing UI inconsistency when mcp.allowed contained glob patterns (critical) - tool-registry.ts disable action added redundant exact entries when a glob already covered the server - acpAgent.ts enable/disable paths silently misbehaved with globs: enable was a no-op, disable persisted stale exact entries Also add test for exclude-takes-precedence-over-allow with glob patterns. * fix(core): replace regex glob matcher with two-pointer to prevent ReDoS The regex-based glob conversion (*→.*, ?→.) was vulnerable to catastrophic backtracking with pathological patterns like *?*?*?*. Replaced with an iterative two-pointer algorithm that runs in O(n×m) worst case with no backtracking. * fix(cli): fix enable/disable glob handling and add test coverage - Enable action: only remove exact-match patterns from exclusion list, preserving glob patterns to prevent collateral server re-enable - Disable action: return accurate changed status (false when glob already covers the server, no write needed) - Add 8 new test cases: two-pointer edge cases, negative assertions, getBlockedMcpServers glob, exclude-over-allow with both globs, regex special char $ coverage Addresses PR QwenLM#6012 review feedback. * fix(cli): use matchesAnyServerPattern in UI exclusion write guards Replace .includes() with matchesAnyServerPattern() in 4 UI call sites (McpServerActionsView, InstalledTab, MCPManagementDialog) so that disabling a server already covered by a glob pattern does not add a redundant exact-match entry. Addresses wenshao review on PR QwenLM#6012.
…en resuming collapsed sessions (QwenLM#5848) * feat: add ui.history.collapsePreviewCount to show last N turns on resume * chore: regenerate settings schema for collapsePreviewCount --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* feat(loop): add autonomous mode for a bare /loop A bare /loop (no prompt, no interval) had no behavior beyond showing usage. This adds an autonomous "keep the user's work moving while they're away" mode: a bare /loop arms <<autonomous-loop-dynamic>> (self-paced) and /loop <interval> arms <<autonomous-loop>> (cron). At fire time the sentinel expands into a steward preamble — advance work the conversation already established (maintain the PR, fix CI, honor commitments), act on the transcript, never invent new work or make irreversible changes without authorization, stop when quiet. The full preamble is delivered once (deduped via a shared marker), a short tick after. A loop.md loop whose file disappears now converges on the same autonomous preamble (run the autonomous check, re-arm the loop.md sentinel to catch recreation) instead of no-op'ing forever. Extends the existing LoopTickResolver — reusing commit-after-delivery and compaction-reset — rather than adding a module; the autonomous and loop.md-absent paths share one dedup marker. Ships the default "steward / stop-when-quiet" preamble; the persistent variant + gate and full headless wiring are follow-ups. Closes QwenLM#5990 Co-Authored-By: Qwen-Coder <noreply@qwen.ai> * fix(loop): address autonomous tick review * fix(loop): harden autonomous sentinel handling * fix(cli): expand autonomous loop sentinels in tui * test(cli): fix autonomous loop sentinel assertion * fix(cli): defer autonomous loop delivery marking * fix(ci): add serve fast path bundle check script * fix(cli): keep autonomous loop out of serve fast path * fix(cli): avoid duplicate autonomous barrel export * fix(cli): keep serve helpers off acp shim * fix(loop): harden autonomous tick dedup marker * test(loop): cover autonomous tick resolver * fix(loop): skip missed autonomous sentinels * fix(cli): type stale scheduler callback in test --------- Co-authored-by: Qwen-Coder <noreply@qwen.ai>
* fix(cli): keep serve health responsive before runtime load Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix deferred runtime route startup (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6013) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): handle ACP read_file local roots Allow ACP read_file calls to fall back to local reads for explicitly permitted local roots when the serve workspace boundary rejects them, and preserve useful messages for plain object read errors. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Add the missing getUserAutoMemoryRoot export to the acpAgent test core mock so the updated acpAgent import resolves under Vitest. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(acp): Narrow local read fallback temp roots Remove the broad OS temp directory from default read_file allow roots and ACP local read fallback roots. Keep qwen-managed temp roots readable and reuse the shared isSubpath helper after realpath resolution for ACP fallback containment. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix CI failure on PR QwenLM#6021 Add the serve fast-path bundle check script and root npm script that the current CI workflow invokes. This mirrors the already-merged mainline check without pulling unrelated workflow or test config changes into this PR. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): address ACP read error review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): harden ACP error normalization Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix Windows CI path expectation (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6021) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): sanitize subagent result tags * fix(core): resolve subagent result review comments * test(core): complete resume subagent stats mock * fix(core): preserve failed subagent diagnostics * test(core): cover plain subagent summary result * fix(core): address subagent result review feedback * refactor(core): simplify subagent result sanitization * fix(core): address remaining subagent result feedback * fix(core): close subagent result review gaps
* fix(core): avoid cloning full history on API errors * fix(core): address history OOM review feedback * fix(core): resolve history OOM review comments * fix(core): address remaining history OOM feedback * fix(core): resolve API error diagnostic review * fix(core): resolve history OOM review comments * fix(core): exclude thought parts from error report textPreview Thought-tagged parts (model reasoning tokens) were included in the textPreview field of API error diagnostic summaries, potentially leaking internal reasoning into error reports. Filter them out consistently with the rest of the codebase. * style(core): format compaction trigger reason union
* fix(channels): structure DingTalk stream logs * fix(channels): harden dingtalk downstream handling * fix(channels): harden dingtalk downstream fields * fix(channels): address dingtalk review blockers * fix(channels): validate dingtalk downstream routing * fix(ci): add serve fast path bundle check
* fix(cli): support Windows-style tilde paths * fix(cli): share tilde path expansion * test: make USERPROFILE path expectations cross-platform * fix(cli): address tilde path review feedback * fix(core): preserve tilde trailing separators * fix(core): preserve home path trailing separators
* feat(web-shell): queue prompts while turns are running * fix(web-shell): address pending prompt review feedback * fix(web-shell): tighten queued prompt event handling * fix(web-shell): avoid showing active prompt as queued * fix(web-shell): address queued prompt review follow-ups * fix(web-shell): address pending prompt review issues * fix(web-shell): reconcile queued prompt actions from server * fix(daemon): address pending prompt critical review * test(webui): expect submit abort signal forwarding * fix(web-shell): avoid duplicate queued prompt sync * fix(web-shell): keep local slash commands out of queue * fix(webui): avoid aborting prompt admission * fix(daemon): avoid queued prompt cancel cascades * fix(webui): avoid stale client id for queue cleanup * test(webui): update stale session queue cleanup expectation * fix(web-shell): preserve queue reconciliation identity * fix(web-shell): guard queue clear session writes --------- Co-authored-by: ytahdn <ytahdn@gmail.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
* ci(workflows): remind authors not to force-push active PRs Add a workflow that detects force-pushes (rebase/amend/reset) to open PRs via the pull_request_target synchronize event and posts a one-time, bilingual reminder that force-pushing invalidates existing review comments and that the integration bots squash all changes into a single commit automatically. A normal push (compare status "ahead") is ignored; the reminder is posted at most once per PR, bot-initiated pushes are skipped, and a failed compare is treated conservatively (no comment). * ci(workflows): address review — add issues:write, serialize without cancel - Add `issues: write`: the listComments/createComment calls go through the Issues API; declaring it matches the repo's other PR-commenting workflows and avoids any risk of a 403 making the workflow inert. - Set `cancel-in-progress: false`: an in-flight run that already detected a force-push must finish and post. The concurrency group still serializes runs per PR, and the once-per-PR marker prevents duplicates, so later pushes queue and then no-op instead of cancelling (and silently dropping) a pending reminder. * ci(workflows): harden force-push detection per review - Marker dedup now requires the comment to be from github-actions[bot], so a user pasting the marker string into a comment can't suppress reminders. - Skip known automation logins (qwen-code-dev-bot et al.) that push via PAT as sender.type 'User', not just GitHub App bots (mirrors qwen-autofix KNOWN_BOTS). - Narrow the compare catch to 404 (orphaned old tip -> skip); rethrow other errors so auth/rate failures go red instead of silently no-op'ing. - Wrap createComment with structured error logging + rethrow. Kept 3-dot compare and base-repo owner: verified that 3-dot returns diverged/behind for force-pushes and that the base repo resolves fork-PR commits, while the suggested 2-dot syntax 404s in the REST API. * test(ci): add structural test for the force-push reminder workflow - Add scripts/tests/pr-force-push-reminder-workflow.test.js (runs under test:scripts, which CI chains into test:ci). It asserts the trigger, repo guard, permissions, serialized concurrency, KNOWN_AUTOMATION sync with qwen-autofix, the 3-dot compare on the base repo, 404-vs-rethrow, the marker author check, and the bilingual body — locking in the reviewed behaviors. - Wrap the listComments paginate call in the same core.error + rethrow the other two API calls already use. - Note that KNOWN_AUTOMATION must stay in sync with qwen-autofix.yml KNOWN_BOTS. * ci(workflows): drop concurrency group, rely on marker for idempotency A concurrency group keeps at most one pending run per group, so a burst of pushes can cancel a still-pending force-push run before it reaches the script, dropping the reminder this workflow exists to post. Remove the group entirely: every synchronize event now runs independently and is always evaluated, and the once-per-PR marker provides idempotency. A rare double-post on two near-simultaneous first force-pushes is the acceptable cost of never silently missing one. Update the structural test to assert there is no concurrency block. The reviewer's suggested `queue: max` is not a valid GitHub Actions concurrency key (only `group`/`cancel-in-progress` are allowed) and fails actionlint. * test(ci): use Qwen Team header and assert the dedup skip path - Switch the copyright header to the prevailing `Qwen Team` (14 of 17 sibling test files use it; this file had copied an older Google LLC header). - Assert the idempotency skip log line so removing the marker guard fails a test. * test(ci): mechanically enforce KNOWN_AUTOMATION sync with qwen-autofix Read qwen-autofix.yml's KNOWN_BOTS and assert each login is also skipped here, so adding a bot there without updating this workflow fails the test instead of silently drifting. Replaces the hardcoded login list whose comment overclaimed that the sync was verified.
QwenLM#5999) * fix(cli): replace all emoji status icons with Unicode text symbols Comprehensive cleanup of emoji in TUI rendering paths (follow-up to QwenLM#5787 / QwenLM#5788). Replaces width-2 emoji with width-1 Unicode text symbols from the project's existing glyph vocabulary. ## Source changes (25 source files) Status indicators: - McpStatus: 🟢→● 🔄→◐ 🔴→● (colors preserved via Text color prop) - ideCommand: 🟢→● 🟡→◐ 🔴→● (both getIdeStatusMessage functions) - Footer: 🔒 removed (sandbox text label is self-explanatory) - Footer: ⚙→▷ (workflow active indicator) - ToolMessage: ⏳→◌ (progress + queued approval) - McpStatus: ⏳→◌ (server startup message) - LiveAgentPanel: ⏸→‖ (pause glyph) Text prefixes and labels: - StatusMessages: 🔎→◎ (vision bridge gutter prefix) - Session: 🚫→✗ (blocked notifications) - useGeminiStream: 🔎→◎ 🚫→✗ 💡→★ (prefixes and headers) - useContextualTips: 💡→★ (tip prefix) - WelcomeBackDialog: 👋🎯📋 removed (text labels are self-explanatory) - CreationSummary: ✅→✓ ❌→✗ - summaryCommand: ❌→✗ - AppContainer: ❌→✗ - AgentEditStep: ❌→✗ - useAutoAcceptIndicator: ℹ️→i - McpStatus Tips: 💡→★ Core package: - agent-statistics: 📋→▸ 🔧→● ⏱️→(stripped) 🔁→● 🔢→● ✅→✓ 🚀→● 💡→★ - shell: 🤖 removed from PR attribution footer - artifact-tool: 📄 removed from publish display - coreToolScheduler:⚠️ →⚠ (strip VS16) Standardized⚠️ (U+26A0+VS16, width-2) → ⚠ (U+26A0, width-1): - CommandFormatMigrationNudge, command-migration-tool, useGeminiStream i18n: all 9 locale files updated for changed keys. ## Test changes (5 test files) - ideCommand.test.ts: 5 emoji assertions updated - McpStatus.test.tsx: 13 snapshots updated - HistoryItemDisplay.test.tsx: 🔎→◎ assertions - useGeminiStream.test.tsx: VS16 + 🔄 assertions - agent-statistics.test.ts: all emoji assertions updated * fix(cli): address review feedback — fix docs, tests, and glyph issues - Fix McpStatus test snapshots to actually exercise connecting/disconnected states via serverStatus prop instead of ineffective vi.spyOn mock - Update user docs (status-line.md, ide-integration.md) to match new symbols - Change Footer workflow indicator from ⚙ to ▷ for consistency - Fix Duration line leading space in agent-statistics formatCompact/formatDetailed - Revert ‖ (U+2016, EAW=Ambiguous) back to ⏸ (EAW=Narrow) for CJK terminals - Restore 🤖 in PR attribution (GitHub web UI, not TUI) - Add old emoji to lspCommand.test.ts negative assertion - Tighten Footer test to assert full ▷ glyph text * fix(cli): sync JSDoc with code for shell attribution and regenerate McpStatus snapshot * fix(cli): distinct IDE status glyphs + mark LLM-facing retry directives - ideCommand: Connected and Disconnected both rendered an identical bullet differing only by color, indistinguishable under deuteranopia/protanopia, in NO_COLOR terminals, or when copy-pasted. Use the PR's established ✓/✗ vocabulary (✓ Connected, ✗ Disconnected); Connecting keeps ◐. - coreToolScheduler: note that the ⚠ in the two RETRY_LOOP directives is an LLM-facing prompt string, not a TUI glyph, so it isn't 'fixed' for width later. Co-Authored-By: Qwen-Coder <noreply@qwen.ai> --------- Co-authored-by: pomelo.lcw <pomelo.lcw@alibaba-inc.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: Qwen-Coder <noreply@qwen.ai>
…6003) * feat(web-shell): add mobile sidebar drawer with session list Replace the display:none behavior at viewport <=760px with an overlay drawer pattern. A hamburger menu button appears on mobile, tapping it slides the existing WebShellSidebar in as a fixed overlay with a semi-transparent backdrop. Selecting or creating a session auto-closes the drawer. Desktop layout (>=761px) is unaffected. Closes QwenLM#6000 * fix(web-shell): address review feedback for mobile sidebar drawer - Use display:contents for desktop wrapper transparency (Critical: sidebar was hidden) - Fix z-index stacking so sidebar renders above backdrop in drawer - Force sidebar expand when mobile drawer is open (collapsed state) - Hide resizeHandle on mobile to prevent touch scroll conflicts - Reset drawer state on viewport resize via matchMedia listener - Add role=dialog, aria-modal, Escape key dismissal, body scroll lock - Add aria-expanded to hamburger button - Close drawer when opening Settings or resuming sessions * fix(web-shell): address second round of review feedback - Remove dead :global(.sidebar) selector (CSS Modules hash class names) - Fix Escape key capture-phase handler to not intercept sidebar inputs - Conditionally apply role=dialog/aria-modal only when drawer is open - Stop toggling collapsed prop on drawer open/close to preserve sidebar state - Add closeMobileDrawer() for bare /resume command path - Fix hamburger button vertical centering in empty chat state on mobile * fix(web-shell): fix stacking context and escape handler in mobile drawer * fix(web-shell): prevent iOS Safari background scroll when drawer is open * chore: remove accidentally committed .qwen-session and gitignore it The .qwen-session file is a developer-local session UUID generated by qwen serve. It was accidentally committed to the repo and should never be tracked. * fix(web-shell): address review feedback for mobile drawer - Don't preventDefault touchmove inside the drawer so the session list can scroll natively; only block scrolling on the page behind it. - Defer Escape to a pending tool/permission approval (reject) instead of closing the drawer when a prompt is visible. - Reuse isEditableTarget from utils/dom and only bail out for editable targets outside the drawer, so the drawer search input still closes on the first Escape. - Close the drawer before awaiting loadSession so it doesn't linger over the old transcript, matching the other session-switch paths. - Keep the drawer panel visible until the backdrop finishes fading out to avoid a one-frame flicker on close. Co-Authored-By: Qwen-Coder <noreply@qwen.ai> * fix(web-shell): mobile drawer ignores collapsed rail + block backdrop scroll - collapsed: a user who collapsed the desktop sidebar got a mobile drawer that still rendered as the icon rail (no session list — the whole point of the drawer). Force the expanded layout while the drawer is open. - touchmove: the allowlist matched the outer [data-mobile-drawer] wrapper, which also contains the full-screen backdrop, so a touchmove starting on the dim backdrop skipped preventDefault and let iOS Safari scroll the page behind. Exclude the backdrop so only the panel keeps native scroll. Co-Authored-By: Qwen-Coder <noreply@qwen.ai> * fix(web-shell): harden mobile drawer collapse, error path, and width cap - Hide the sidebar collapse button while the mobile drawer is open so its no-op toggle can no longer silently persist desktop collapsed state. - Close the drawer before awaiting createSession() so a failed create no longer leaves the drawer stuck open with page scroll locked. - Drop redundant width/min-width/position from .sidebar.mobileOpen and cap it with max-width:100vw so a wide persisted width can't overflow phones. Co-Authored-By: Qwen-Coder <noreply@qwen.ai> --------- Co-authored-by: pomelo-nwu <czynwu@gmail.com> Co-authored-by: Qwen-Coder <noreply@qwen.ai>
* fix(web-shell): constrain virtual scroll rows * test(web-shell): cover virtual message rows --------- Co-authored-by: ytahdn <ytahdn@gmail.com>
Add POSIX /tmp to ACP local read fallback roots without changing read_file's default permission behavior. Also add QWEN_ACP_LOCAL_READ_ROOTS as an append-only absolute-path override for ACP fallback reads. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): default unknown context windows to 200k * fix(core): only stamp known model context windows in resolver - Use knownTokenLimit() in the env-var resolver fallback so unknown models keep contextWindowSize undefined instead of being labeled 'auto-detected from model' with the generic default - Add resolver tests: known limit differing from the default (gpt-4o), unknown model stays undefined, settings value not overridden - Recalibrate the config.getWarnings default-window test for the 200K fallback - Sync the vscode-ide-companion copy of DEFAULT_TOKEN_LIMIT to 200K
…QwenLM#6388) * feat(web-shell): add token-usage analytics dashboard to Daemon Status Add a "统计 / Usage" tab to the Daemon Status page: a Today/7D/30D period toggle over the selected range's token totals and input/output/cache-read breakdown, a 12-month token heatmap (per-day tokens + cache-read tooltip, localized month labels), per-model token share, skill-call counts, and daily token/session charts. Backend: a new read-only GET /usage/dashboard daemon API backed by a core usage-dashboard service that aggregates the durable local usage history (cross-project ~/.qwen), reusing loadUsageHistory + aggregateUsage. Skill counts are threaded through the shared usage pipeline. No new instrumentation — every metric is read from data qwen-code already persists. * fix(web-shell): address usage-dashboard review feedback - cap `aggregateUsage` topSkills at 25 like topTools, so the aggregate and dashboard payload stay bounded - fix a DST drift in the heatmap grid: advance the day/month cursor by calendar day (setDate) instead of a fixed `i * MS_PER_DAY` offset - cache the loaded history once (range-independent) so toggling Today/7D/30D re-aggregates from a single disk read; split a pure `buildUsageDashboard(records, opts)` out of `loadUsageDashboard` - drop the unused per-day streak computation and the dead `daemon.usage.streak` i18n key - add debug logging to the dashboard builder and a direct `aggregateUsage`-skills unit test * fix(usage-dashboard): make the dashboard load read-only + fix cache coalescing - Make the daemon dashboard side-effect free: `loadUsageHistory` gains a `persistRebuild` option, and the route passes `persistRebuild: false`, so serving a GET never writes to `~/.qwen`. The transcript-rebuild fallback previously persisted rebuilt records (including an in-progress session), violating the read-only contract. - Fix cache coalescing on the slow path: a pending history load is now reused regardless of age (the TTL starts at settlement), so a request arriving after the TTL while the load is still pending no longer kicks off a second full load. - Tests: read-only rebuild writes nothing, `metricsToUsageRecord` copies `SessionMetrics.skills`, and a pending load is shared past the TTL.
* feat(cli): add Phase 1 workspace runtime registry Introduce the internal single-workspace runtime registry for qwen serve and wire the primary runtime through the existing server assembly without changing route schemas. Also migrate daemon log and telemetry identity to daemon-scoped values, keep workspace hash as metadata, and reject repeated explicit --workspace inputs until multi-workspace serve is enabled. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (QwenLM#6394) Memoize daemon telemetry workspace hashes and let runQwenServe honestly accept yargs workspace array inputs while keeping internal ServeOptions single-workspace. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…guish feat from refactor (QwenLM#6369) * Initial plan * fix(triage): exclude test files from core module size gate and distinguish feat from refactor - Add anti-hallucination rule to SKILL.md preventing invented blocking policies - Stage 0 size calculation now excludes test files (*.test.ts, *.spec.ts, __tests__/) - Only production logic lines count toward the 500-line threshold - feat-type PRs touching core escalate instead of hard-blocking - Add soft large-PR advisory (non-blocking) for >1000 production lines - Update AGENTS.md to match refined policy - Clarify conventional commit matching patterns for feat/refactor detection Closes QwenLM#6365 * fix(triage): clarify core gate escalation paths * fix(triage): define generated schema exclusions * fix(triage): report core diff size in gate template * fix(triage): clarify core gate review flow --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: yiliang114 <effortyiliang@gmail.com> Co-authored-by: 易良 <1204183885@qq.com>
…enLM#6359) * fix(cli): keep model picker entries contiguous * fix(cli): account for the error box when capping model picker rows The model list's row budget didn't reserve space for the inline error message shown after a failed switch, so it could still overflow a short terminal in that state. Also cover the capping formula's untested branches (floor at very small heights, the two-row description path, the undefined-height fallback) and the DescriptiveRadioButtonSelect ReactNode description path introduced by the same change. * fix(cli): pad error-row estimate for wrapped error text errorMessageRows only counted explicit newlines, undercounting rows when the error Text wraps on narrow terminals. Add a small buffer and tighten the regression test's assertion to the exact expected value. * fix(cli): show scroll arrows and document the model dialog row budget Short terminals can now cap the model list well below its old worst case of 10, hiding most entries with no indicator that the list scrolls (unlike ThemeDialog, ApprovalModeDialog, and ArenaStartDialog, which already show scroll arrows). Enable them here too, and reserve the 2 extra chrome rows they add. Also document the fixed-rows budget so future layout changes know to keep it in sync. * fix(cli): drop model picker scroll arrows when they would crowd out entries The scroll arrows are two always-rendered chrome rows, so on dialogs too short to fit them plus a single option row they pushed the option rows past the dialog's clipped height — the picker showed arrows, title, and footer but no entries. Hide the arrows in that case and spend their rows on the list instead. Verified with an E2E height sweep (rows 14-34): at least one entry is now visible at every height and windows stay contiguous, with arrows still shown wherever they fit. * fix(cli): remove model picker scroll arrows to reclaim rows for entries The ▲/▼ indicators are two always-rendered chrome rows, and in a height-capped dialog those rows are the scarcest resource — enabling them cost two visible entries at every constrained height and required extra logic to avoid crowding out the list entirely on very short dialogs. Remove them and restore the 14-row chrome budget: the entry numbering already shows where the visible window sits in the list, and the footer hint covers navigation. Supersedes the earlier change that enabled the arrows. * test(cli): cover the max-item clamp for tall terminals
* fix(autofix): run verification before committing * ci(autofix): trigger review addressing on feedback * fix(autofix): narrow npm command allowlist * fix(autofix): address review workflow guards * fix(autofix): keep review addressing on scheduled sweep
…LM#5629) * feat(core): surface PreToolUse hook 'ask' as a TUI confirmation A PreToolUse hook returning permissionDecision:'ask' was treated the same as 'deny'. The hook fires in the execution phase (_executeToolCallBody), after the confirmation flow in _schedule has finished, so an 'ask' could only block the tool as EXECUTION_DENIED instead of prompting the user. Bounce the tool from the execution phase back to awaiting_approval when a hook asks: build a synthetic 'info' confirmation whose onConfirm routes through handleConfirmationResponse (ProceedOnce re-executes, Cancel cancels). PreToolUse keeps its "before execution" timing — only the 'ask' branch is new; 'denied'/'stop' keep deny-as-error. A non-interactive CLI or background agent cannot prompt, so 'ask' falls back to deny there. The re-execution after approval skips both the hook re-fire (no infinite re-ask loop) and the non-idempotent path-unescape prelude. A walk-away abort sets a terminal status so the turn cannot hang, and the tool span survives the bounce so it is finalized exactly once. Tests: add coverage for ask->awaiting_approval, approve->execute-once (no re-ask loop), decline->cancelled, non-interactive/background deny, walk-away abort, single span finalize, and no double path-unescape. * fix(core): handle PreToolUse 'ask' bounce edge cases from review Round 1 review of QwenLM#5629 surfaced edge cases in the bounce mechanism: - Multi-tool batch hang: a bounced tool approved while a sibling was still executing stayed stuck in 'scheduled'. attemptExecutionOfScheduledCalls now loops, re-checking for newly-scheduled bounce-approved calls after each batch drains. - Orphaned hook events: the post-approval re-execution generated a fresh tool_use_id, leaving PreToolUse(old)/PostToolUse(new) unpaired. Preserve and reuse the original id across the bounce. - ModifyWithEditor double-unescape: request.args is unescaped in place before the hook fires, so the ModifyWithEditor branch must skip its own unescape for a bounced tool (it would double-strip escaped metacharacters). - Missing signal.aborted re-check before bouncing: mirror the confirmation-phase guard so an aborted signal falls through to deny instead of flashing a confirmation nobody can answer. Tests: multi-tool-hang regression (RED before the loop fix), non-interactive STREAM_JSON and Zed bounce paths, and span-finalize assertions on the walk-away abort test. * fix(core): keep PreToolUse 'ask' gate when a sibling is auto-approved Round 2 review: autoApproveCompatiblePendingTools auto-approved every awaiting_approval tool when a sibling was approved with ProceedAlways — including tools bounced by a PreToolUse 'ask'. The bounced tool would be auto-approved and re-executed with the hook skipped (isPostAskReexecution), silently defeating the hook's confirmation gate. Exclude bounced callIds from the auto-approve filter so a hook 'ask' always requires explicit confirmation. Test: a sibling's ProceedAlways no longer auto-approves a bounced ask (RED before the filter guard). * fix(cli): preserve hook ask prompts on approval mode change * fix(core): handle PreToolUse ask edge cases * fix(core): cancel scheduled calls during ask abort drain --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
QwenLM#6371) When a file is accessed via a symlinked path (e.g., in git worktrees or monorepos with symlinked directories), conditional rules and skills keyed on the real path would fail to activate. Add resolveSymlinkAwareRelativePaths() that returns both the original and realpath-resolved relative paths, so glob patterns match either form. Resolve both the file path and projectRoot via realpath to handle macOS /private/tmp prefix normalization correctly. Make matchAndConsume() async in both ConditionalRulesRegistry and SkillActivationRegistry to support the realpath I/O. Fixes QwenLM#6356 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: ytahdn <ytahdn@gmail.com>
…ents string (QwenLM#6250) * fix(core): preserve no-argument tool calls that stream an empty arguments string For tools that take no parameters, some OpenAI-compatible providers stream `arguments: ""` (or omit the field entirely) and never send an argument fragment. The streaming parser dropped such calls wholesale (`meta?.name && buffer.trim()`), while the non-streaming path keeps them with `args: {}` — so a turn containing only that call looked empty and geminiChat raised "Model stream ended with empty response text", triggering pointless retries. Align the streaming parser with the non-streaming path: emit the call with empty args when the buffer is empty at stream end. Rewrite the unit test that encoded the drop, and add regression coverage at parser and converter chunk level. * fix(core): use name metadata as slot-occupancy signal for no-argument tool calls Follow-up to review feedback: after empty buffers became a legal end state for no-argument tool calls, three parser methods still used buffer.trim() to decide whether an index slot was occupied. A provider reusing indices could then silently overwrite a completed no-argument call (addChunk collision guard, findNextAvailableIndex) or append stray continuation chunks to it (findMostRecentIncompleteIndex). Switch the occupancy signal in all three places to the name metadata, keeping the JSON-completeness check for non-empty buffers. Add regression tests for both corruption paths and update the stale getCompletedToolCalls JSDoc. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): collapse non-object argument parses at emit and lock canonical empty-opener shape Review follow-up on the no-ID continuation routing at addChunk. Mid-stream, an empty buffer with name metadata is formally undecidable between "completed no-argument call" and "canonical opener awaiting its first argument fragment" (every OpenAI-compatible provider opens with arguments:"" and streams fragments ID-less at the same index). Routing must favor the canonical shape, so the guard stays; a new test pins that shape, which the suite previously did not cover. The corruption concern from review is instead bounded at emit time: a buffer polluted by a stray fragment can parse or repair to a non-object value, which now collapses to {} so a polluted no-argument call still emits empty args. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): add debug logging for empty-buffer emission and non-object argument collapse Review follow-up: a stray fragment that happens to parse as a valid JSON object is indistinguishable from real arguments at emit time, so log both the non-object collapse and empty-buffer emissions to aid diagnosis. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): extend replay guard to opener-shaped replays of no-argument tool calls Review follow-up: after empty buffers became a legal completed state, a replayed opener (duplicate ID, QwenLM#5107 lineage) could overwrite a completed no-argument call's name metadata, since the replay guard only engaged on non-empty buffers. Swallowing every known-ID chunk at that state would drop ID-bearing argument fragments for providers whose opener streams empty arguments, so the guard uses the protocol shape as discriminator: a chunk carrying a name but no argument content is an opener replay and is ignored; a chunk with argument content is a continuation and appends. Regression tests cover both directions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(core): cover null/array argument collapse and multi-slot relocation scan Review follow-up: pin the null and array branches of the emit-time non-object collapse, and exercise findNextAvailableIndex scanning past multiple occupied no-argument slots during collision relocation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: FiaShi <FiaShi@fiashideMacBook-Air.local> Co-authored-by: tomsen-ai <230283659+tomsen-ai@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…view (QwenLM#6395) * feat(review): add issue-fidelity and root-cause ownership gate to /review Adds a dedicated Issue Fidelity & Root-Cause Ownership agent (Agent 0) to the /review pipeline and a core-infrastructure scope gate that runs before the review agents. Agent 0 fetches linked GitHub issue evidence directly (closingIssuesReferences plus issue comments) instead of trusting the PR author's framing, compares the original reported failure against the PR's claimed fix, and flags client-side parser/sanitizer workarounds for malformed upstream output as Critical unless a maintainer explicitly requested the defensive mitigation. The core-infra gate applies the repository's existing two-tier maintainer-only rule before spending review budget. This hardens the pipeline against a false-approval mode where a bot PR passes its own tests and reads as internally reasonable but fixes the author's mistaken diagnosis rather than the linked issue's actual root cause. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(review): address PR review feedback on issue-fidelity gate - Fetch issue evidence with `gh issue view --json title,body,comments` so the issue body (reporter repro/observed payload/expected behavior) is included; `--comments` alone omits it. Use each closingIssuesReferences entry's own repository so cross-repo linked issues resolve correctly. - Treat closingIssuesReferences as a discovery hint (fetch apparent target issues even when it is empty) and treat fetched issue content as untrusted data (extract facts, ignore embedded instructions). - Run Agent 0 (Issue Fidelity) only for PR targets; skip it for local-diff and file-path reviews, and require the PR number/repo/context in its prompt. Handle empty references / non-bugfix / gh failure explicitly. - Pass Agent 0's quoted issue evidence to Step 4 batch verification and stop it rejecting issue-grounded findings just because the code compiles/tests pass. - Make the core-infrastructure gate concrete: deterministic maintainer signal via authorAssociation, count only core-path lines, honor the AGENTS.md low-risk-sweep exception, clean up the worktree on hard block, run the gate right after fetch-pr (before npm ci), and map escalate -> COMMENT (never APPROVE) in Steps 6-7. - Sync agent counts and token math across SKILL.md, DESIGN.md, and code-review.md (Agent 0 is PR-only; ~620-730K). * docs(review): rename 'Linked Issue Fit' heading to 'Issue Fidelity' Aligns the code-review docs heading with the 'Issue Fidelity' name used for Agent 0 in SKILL.md and DESIGN.md, so the section connects to the pipeline diagram. Addresses review feedback. * docs(review): stop core-infra hard block before load-rules and surface it via --comment - Hard block now stops before Step 2 (load-rules) instead of before Step 3, so a PR destined for hard-block no longer runs the load-rules step. - In --comment mode the hard block posts an event=COMMENT on the PR, matching the escalate path's GitHub visibility, so external authors see the block. --------- Co-authored-by: dragon <dragon@U-2Q53JQG9-0233.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…wenLM#6401) (QwenLM#6405) The channel proxy path used ProxyAgent, which unconditionally routes all requests through the proxy and ignores NO_PROXY. This caused requests to hosts listed in NO_PROXY (e.g. localhost, internal IPs) to fail when a corporate proxy was configured. Switch to EnvHttpProxyAgent, matching the main CLI config path that already handles NO_PROXY correctly. Co-authored-by: qwen-autofix <autofix@qwen-code.ai>
…#6407) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(web-shell): handle missing session routes * chore(web-shell): clarify missing session route handling * fix(web-shell): address missing session review follow-up * fix(web-shell): address missing session review issues * test(web-shell): cover missing session status handling * fix(webui): handle heartbeat terminal states * fix(web-shell): preserve missing session state * fix(webui): harden missing session diagnostics * fix(web-shell): stabilize missing session recovery * fix(webui): preserve missing session heartbeat state * fix(webui): stabilize missing session recovery * fix(webui): cover missing session review gaps --------- Co-authored-by: ytahdn <ytahdn@gmail.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
…wenLM#6411) Co-authored-by: Claude <noreply@anthropic.com>
Merge upstream/main (bcdb44c) into HopCode main using 'ours' conflict resolution strategy to preserve HopCode branding and bug fixes. Net-new upstream files and features are brought in automatically. Upstream commits range: f876f4a..bcdb44c (QwenLM#5961 -> QwenLM#6411) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> # Conflicts: # .github/workflows/qwen-code-pr-review.yml # .hopcode/design/2026-06-30-unified-reasoning-effort-cli.md # .hopcode/design/2026-07-01-channel-lifecycle-status-adapters.md # .hopcode/design/2026-07-01-channel-lifecycle-status-umbrella.md # .hopcode/design/2026-07-05-large-frame-handling-measurement.md # .hopcode/design/daemon-extension-at-mention.md # .hopcode/design/daemon-multi-workspace-phase1-registry.md # .hopcode/design/webshell-mention-icon-chips.md # .hopcode/e2e-tests/2026-07-01-channel-lifecycle-status-umbrella.md # .hopcode/e2e-tests/webshell-mention-icon-chips.md # .hopcode/plans/2026-07-01-channel-lifecycle-status-adapters.md # .hopcode/plans/2026-07-01-channel-lifecycle-status-umbrella.md # packages/cli/src/commands/review/deterministic.ts # packages/cua-driver/swift/Sources/CuaDriverCLI/ConfigCommand.swift # packages/cua-driver/swift/Tests/integration/test_hermes_form_fill_hopcode.py
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Important Review skippedToo many files! This PR contains 1503 files, which is 1353 over the limit of 150. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (15)
📒 Files selected for processing (1503)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 339278efee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # Gate the principal on having write+ permission before any agent runs: | ||
| # - pull_request_target / `/triage` comment -> gates triage (read-only), | ||
| # keyed on the PR author / the commenter respectively. | ||
| needs: ['precheck-pr'] |
There was a problem hiding this comment.
Let authorize run when the precheck is skipped
Because precheck-pr is intentionally skipped for same-repo PRs and issue_comment triggers, adding it as a normal needs dependency makes GitHub apply the implicit success condition and skip authorize whenever that precheck job is skipped. In those contexts triage later sees needs.authorize.outputs.should_run unset, so same-repo automatic PR triage and @hopcode /triage comments never run; use an always()-guarded dependency path or only depend on the precheck for fork PRs.
Useful? React with 👍 / 👎.
| ['prompt_injection:approve_pr', /\bapprove (?:this )?pr\b/i], | ||
| [ | ||
| 'prompt_injection:qwen_command', | ||
| /@qwen-code\s+\/(?:triage|review|resolve|tmux)\b/i, |
There was a problem hiding this comment.
Match HopCode commands in the safety precheck
This precheck runs in the HopCode repository, where the automation command prefix is @hopcode, but the prompt-injection pattern only recognizes @qwen-code. A fork PR body or diff containing the HopCode command form such as @hopcode /review or @hopcode /triage will be classified as allow_triage instead of manual_required, bypassing the command-specific safety signal this check is meant to enforce.
Useful? React with 👍 / 👎.
| type VoiceStreamConfig, | ||
| type VoiceStreamSession, | ||
| } from './voice-stream-session'; | ||
| import { openQwenAsrRealtimeStream } from './qwen-asr-realtime-session'; |
There was a problem hiding this comment.
Import the realtime voice module by its actual name
The new desktop voice handler imports ./qwen-asr-realtime-session, but this commit adds hopcode-asr-realtime-session.ts and no qwen-asr-realtime-session module in the same directory. Any desktop typecheck/build or runtime path that loads voice-ws-handler fails module resolution, so realtime voice support cannot start until the import matches the file that was added.
Useful? React with 👍 / 👎.
What this PR does
Syncs 264 upstream commits from QwenLM/qwen-code (f876f4a..bcdb44c) into HopCode main.
Commit range: QwenLM#5961 → QwenLM#6411
Key upstream features brought in:
Conflict Resolution
Conflicts resolved using -X ours\ (HopCode changes preferred), then:
Reviewer Test Plan
How to verify
Build and typecheck should pass:
\
npm run build && npm run typecheck
\\
Tested on
Risk & Scope