* feat(web-shell): per-task token & time detail on completed todos
Expanding a completed task in the todo list now reveals when it ran (start /
end / duration) and what it spent: input / output / cached tokens, API time,
and tool time.
The agent stamps a cumulative-usage snapshot onto each todo (plan) update via
`_meta.stats`; the SDK normalizer carries it into the TodoWrite tool call's
rawOutput, and the web-shell diffs consecutive snapshots for tokens and API
time while summing transcript tool durations for tool time.
Works live (no polling race) and on /resume: tokens and tool time are
reconstructed from persisted usage metadata, while API time is live-only since
per-turn durations are not replayed. Sessions whose agent never stamped a
snapshot degrade gracefully to start/end + tool time.
* refactor(web-shell): show task duration inline on the end-time row
Trail the elapsed duration after the end time as a dimmed parenthetical
("12:34:15 (4m 14s)") instead of a separate row, since it's derived from the
start/end pair.
* fix(web-shell): correct per-task detail for reused todo ids; review follow-ups
- computeTodoDetails: when a completed id+content key restarts as in_progress (positional plan-N ids repeat across plans), reset the window so the new task diffs its own start instead of the prior task's far-earlier boundary — which rendered a cross-plan window with wildly inflated token/time numbers. Correct the todoStateKey JSDoc accordingly.
- Tool time: sort spans once and binary-search the task window instead of an O(todos x spans) scan per completed task.
- Tests: add the reuse-reset case, a windowed tool-time case, an SDK-normalizer -> extractTodoStats contract test (locks the stats passthrough so a field rename fails loudly), and a stopPropagation test (expander click must not bubble to the tool-row header).
* fix(web-shell): reset todo detail window on reopen via pending, not just direct
Track keys that have ever reached 'completed' instead of checking prev === 'completed': a reopened task can pass through 'pending' (completed → pending → in_progress), where prev at the re-activation is 'pending' and the direct check missed it, leaving a stale baseline that diffs across both runs. A pause/resume that never completed (in_progress → pending → in_progress) still keeps its first baseline, so its diff captures the whole task.
* fix(web-shell,cli): harden todo stats against NaN poisoning and partial snapshots
- MessageEmitter: only fold finite usage/duration values into the cumulative accumulator. A NaN/Infinity (incl. a NaN that survives `?? 0`) would poison the running total forever, making every later snapshot fail extractTodoStats and silently show 'not captured' for the rest of the session.
- extractTodoStats: require the token fields but default the live-only apiTimeMs to 0 when absent/non-finite, so a snapshot that omits it keeps its valid token counts instead of being dropped whole.
- computeTodoDetails: gate the start baseline on the stored value, not Map.has — a stats-less start (e.g. a plain plan message) recorded undefined, which Map.has treated as already-set, blocking a later stats-bearing snapshot from upgrading the baseline.
- Document the MessageEmitter-before-PlanEmitter ordering invariant in both emitters.
* fix(core): bound active tool result history
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): keep tool result budget defaults lightweight
Move the new tool-result history budget default into a lightweight config defaults module so microcompaction does not load the full Config graph during service tests. Update the ACP worktree test mock to include the public default export used by settings schema imports.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): address tool result budget review
Handle negative legacy idle thresholds consistently, clarify size compaction diagnostics for pending tool results, promote successful microcompaction logs to info, and strengthen tests/docs around skipped results and soft thresholds.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): log protected tool result overages
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(computer-use): configurable screenshot max dimension (setting + env)
Add a user-level knob for cua-driver's screenshot longest-edge cap. The
old open-computer-use backend exposed this via OPEN_COMPUTER_USE_IMAGE_*
env vars; the cua-driver migration dropped them, leaving only the
model-driven set_config tool. This restores deterministic user control.
- Setting tools.computerUse.maxImageDimension (number; default -1 = keep
cua-driver's built-in default of 1568; 0 disables resizing / full
resolution; a positive value caps the longest edge).
- Env override QWEN_COMPUTER_USE_MAX_IMAGE_DIMENSION (takes precedence
over the setting; invalid/negative values fall through).
- Resolution lives in resolveMaxImageDimension(); applied via the
cua-driver set_config tool once per (re)connect in
ComputerUseClient.doStart — best-effort, never aborts startup, and
re-applied after a daemon-restart reconnect.
- Docs: document tools.computerUse.{enabled,maxImageDimension} in
settings.md (the block was previously undocumented). Refresh stale
ocu/npx comments left in client.ts + install-state.ts by the migration.
Precedence: env var > setting > cua-driver default.
* chore(computer-use): finish ocu→cua-driver cleanup in schema-sync script
The cua-driver migration (#5051) left scripts/sync-computer-use-schemas.ts
pointing at the old open-computer-use backend: it npx'd
@qwen-code/open-computer-use, hard-coded the 9-tool ocu surface, and emitted an
"open-computer-use" header. Re-running it — which constants.ts' version-bump
procedure tells maintainers to do — would have clobbered the migrated 35-tool
cua-driver schemas.ts.
- Drive the locally-pinned `cua-driver mcp` binary (binaryPath /
CUA_DRIVER_VERSION from constants.ts) instead of npx'ing ocu; expect 35
tools and warn (don't fail) on drift.
- Emit the cua-driver-flavored schemas.ts header.
- Refresh install-state.test.ts fixtures from ocu package specs to the
cua-driver-rs approval-key form the field actually stores now.
Verified the fixed script reproduces the committed 35-tool surface exactly
(modulo prettier formatting). No dead env-var handling remained — the module
reads only QWEN_COMPUTER_USE_{AUTO_APPROVE,DOWNLOAD_HOST,MAX_IMAGE_DIMENSION}.
#5036 carved the deterministic identical-tool-call check out of the
`model.skipLoopDetection` gate, turning it into a hard-stop that fires
even when loop detection is disabled. Because `skipLoopDetection`
defaults to true (settingsSchema: "to avoid false-positive
interruptions"), this silently re-enabled loop halts for the default
configuration and broke the documented escape hatch — the
non-interactive guidance in nonInteractiveCli.ts told users to set
`model.skipLoopDetection: true`, which no longer disabled the halt and
is unreachable in non-interactive mode (no disable dialog).
Gate both the deterministic and heuristic detector paths behind the
single flag again. The deterministic split, retry-reset, and pending
tool-call splice introduced by #5036 still apply once detection is
explicitly enabled (skipLoopDetection: false), so the runaway guard
remains available as opt-in without overriding the default-off contract.
* fix(dual-output): prevent FIFO blocking on startup when no reader connected
DualOutputBridge's ENXIO fallback used a blocking createWriteStream on
FIFOs, causing the TUI to hang indefinitely when launched with
`--json-file <fifo>` before a reader connects (issue #4727).
Fix: use O_RDWR | O_NONBLOCK for the FIFO fallback path. This POSIX
trick satisfies the kernel's "at least one reader" requirement without
blocking. A buffer high-water-mark (1 MB) self-disables the bridge if
no consumer ever drains the pipe.
Also updates Quick start docs to recommend regular files as the default,
with FIFOs documented as an advanced option that now works without
ordering constraints.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(dual-output): address review feedback — guardActive, stream.destroy, tests
- Rename isBufferOverflowing() to guardActive() (command-query separation)
- Apply buffer guard to all write methods, not just processEvent
- Call stream.destroy() on overflow so FIFO consumers get EOF
- Handle destroyed stream in shutdown() to prevent hanging
- Add test: bridge disables on buffer overflow + stream is destroyed
- Fix doc: --input-file requires regular file (not FIFO), stat.size=0
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>
* feat(web-shell): collapsible TodoWrite history with status diff
Inline todo_write updates rendered as a generic, non-collapsible tool row
crammed in with surrounding tool calls — the todo-specific renderer was
effectively dead code because the detector matched the literal "todowrite"
while the wire name is "todo_write" (kind "think").
- Detect the todo tool by name (todo_write / todowrite) instead of relying
on the unrelated tool kind, reviving the rich rendering and also
populating the floating todo panel that was empty on the daemon path.
- Render each update as its own standalone group, collapsed by default to
the per-snapshot diff (just-completed / just-started items) or the current
step, expanding to the full checklist; the header shows completed/total.
- Use consistent status glyphs (●/◐/○) in both collapsed and expanded views
via a shared TodoView component used by ToolGroup and PlanMessage.
* fix(web-shell): scope todo diff per task identity; address review
- [Critical] computeTodoTimeline keyed its running state on todo.id alone, but
ids aren't globally unique (ACP assigns positional ids, models renumber per
plan), so a later plan diffed against a previous plan's stale terminal status
and silently dropped events in the collapsed view. Key on id+content so
distinct tasks stay separate; add an id-reuse regression test.
- Stabilize the TodoTimelineContext value with a signature-cached Map so
streaming ticks that don't touch a todo snapshot no longer re-render every
todo/plan row.
- Drop the unused completed/total from TodoSnapshotDiff (consumers compute the
count locally — single source of truth).
- Fall back to the raw result summary when a todo_write payload is unparseable.
- Add PlanMessage rendering tests and extractTodosFromToolCall coverage; remove
stale "step time" comments left after dropping per-step timing.
* test(web-shell): document todo-diff keying limits; cover signature
Follow-up to review on the id+content keying in computeTodoTimeline:
- Document the two rare trade-offs in the todoStateKey doc (a mid-task reword on
a stable id, and unrelated plans reusing both id and content), both degrading
to "the collapsed diff omits one event" while the expanded list stays correct.
- Pin behavior with tests: an item carried over and completed in a later turn
(which id+content handles but a user-turn reset would drop), plus the two
documented gaps.
- Add todoTimelineSignature tests: stable across non-todo edits; changes on any
id/status/content change.
* refactor(web-shell): isolate plan context read; expand todo test coverage
Address follow-up review:
- Extract PlanEventSummary as the sole TodoTimelineContext consumer, mirroring
ToolGroup's TodoToolBody so the memo-shielded PlanMessage stays stable when
the timeline Map reference changes.
- Note the spurious-`started` axis of the reword trade-off in the todoStateKey
doc (a reword can drop a completion or emit a stray start).
- Add direct isTodoWriteToolName tests (incl. the `todowrite` ACP variant) and
a todoTimelineSignature empty-transcript test.
The /copy command supports code block selection with language filtering,
LaTeX, and Mermaid — but the TUI autocomplete only showed "[N]" with a
description about copying the full AI reply. Users had no way to discover
these capabilities from the prompt.
Update argumentHint to "[N] [<lang>|code|latex|mermaid] [<index>]"
and description to mention all supported targets, noting N counts from
the last message.
* feat(acp): carry _meta.toolName on permission frame; agent drawer (vscode)
WIP: producer mirrors _meta.toolName onto session/request_permission;
webui PermissionDrawer + vscode webview map it to render 'Launch this
agent?' for the Agent tool without a protocol kind. Daemon/web-shell
surface + tests follow.
* feat(web-shell): dedicated agent permission prompt via _meta.toolName
Thread the canonical tool name from the permission frame's _meta.toolName
through web-shell's PermissionRequest so ToolApproval renders 'Launch this
agent?' for the Agent tool, mirroring the vscode PermissionDrawer. Add
tests for the toolName extraction and the agent drawer title.
* fix(acp): mirror _meta.toolName on second producer path + address review
Address @wenshao's review on #5105:
- [Critical] SubAgentTracker's approval handler builds its own
RequestPermissionRequest (the second producer path, for nested sub-agent
tool calls) and was missing `_meta: { toolName }`. Session.ts adds it on
the primary path; mirror it here so nested agents (and any future tool
relying on _meta.toolName for specialized UI) don't fall back to the
generic prompt. Locked with a _meta assertion in the approval test.
- Dedupe the three hardcoded 'agent' string matches behind a single shared
`AGENT_TOOL_NAME` / `isAgentTool` in @qwen-code/webui (re-exported via
daemon-react-sdk, same pattern as DAEMON_APPROVAL_MODES), consumed by
PermissionDrawer (webui) and ToolApproval (web-shell).
- Move the agent check to the top of PermissionDrawer.getTitle() so it wins
over kind-based checks, matching ToolApproval's isAgent-first ordering
across the two surfaces.
- Extract the _meta.toolName lifting logic in useWebViewMessages into a
testable `liftToolNameFromMeta` helper and cover the three cases wenshao
flagged: lift onto toolName, preserve a pre-existing toolName, no-op when
_meta is absent (plus undefined-toolCall guard).
🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
* refactor(core): unify retry delay policy
* refactor(core): tighten retry policy after review
Address PR review feedback on the unified retry policy:
- Document RetryAfterMode semantics (ignore/minimum/prefer) and the implicit
rule that jitter is not applied when Retry-After is honored.
- Dedupe the 5-minute single-wait constant — INTERACTIVE_RETRY_AFTER_CAP_MS
now reuses PERSISTENT_MAX_BACKOFF_MS so a future tweak does not silently
desync the two.
- Drop inert fields at the two 'prefer'-mode call sites in retry.ts; with the
Retry-After value already gated upstream, the delay reduces to a direct
Math.min against the cap (also avoids re-parsing the header inside the
policy).
* test(core): cover retry policy edge cases
* fix(core): preserve retry-after waits in http retry
* fix(core): make retry waits abort-aware
* refactor(core): remove unused retry-after prefer mode
* refactor(core): classify retry errors
Add shared retry error classifier with structured diagnostic fields for
HTTP, SSE, provider-code, transport, abort, and provider-business failures.
Cherry-picked from PR #3850 clean state (2239efa62), replacing the
corrupted squash merge that replaced source files with pointer strings.
* fix(core): close TOCTOU race in delay() abort handling
Move the initial signal.aborted check inside the Promise constructor
and add a re-check after addEventListener to close the race window
where an abort between the check and listener registration would be
silently lost. Also add clarifying comments on the intentional
attempt:1 usage in retry delay calculations.
* fix(core): align retry diagnostics with retry policy
* test(core): cover pre-aborted retry waits
* fix(core): propagate custom retry codes
* fix: update copyright year to 2026 in new files [skip ci]
* fix(core): cap retry delays at the setTimeout ceiling
Clamp both the Retry-After-derived delay (seconds and HTTP-date paths) and
the exponential backoff to the signed 32-bit setTimeout limit. An oversized
Retry-After previously overflowed the timer and fired immediately, turning a
long server-directed wait into a 0ms tight retry loop; the exponent could also
reach Infinity for very large persistent attempt counts.
Also parse Retry-After with an RFC 7231 decimal-only matcher so non-RFC shapes
Number() would accept (0x10, 1e3) fall through instead of producing a wrong
delay, while fractional seconds remain honored.
Addresses review feedback on #3827.
* fix(core): refine retry error classification accuracy
Prefer a transport-level cause over the HTTP status when the status is itself
transient (5xx) or absent, so a socket reset surfaced as the cause of an SDK
error is classified as transport rather than a provider server error. A
definitive 4xx status stays authoritative — a transient cause must not relabel
a permanent failure as retryable.
Relabel 529 from 'fallback-eligible' to 'retryable': this PR retries 529 and
does not implement model/provider fallback, so the old label implied behavior
that does not exist.
Addresses review feedback on #3827.
* fix(core): honor 503 Retry-After and bound fail-fast persistent retry
Parse Retry-After for 503 as well as 429 (both carry it per RFC 7231 and the
stream-side path already honors both), via a shared hasRetryAfterStatus helper
used by the persistent and normal paths.
Keep permanent business errors out of the unbounded persistent loop: an error
classified as 'fail-fast' (e.g. DashScope Throttling.AllocationQuota, which
surfaces as HTTP 429) now falls back to the maxAttempts-bounded retry path
instead of retrying for hours. This wires the classifier into a single control
decision; normal retry control still follows shouldRetryOnError.
Also classify and log the original error before the Qwen quota fast-fail so its
status/request-id/body is never discarded, and drop the redundant status===429
check in defaultShouldRetry (isRateLimitError already covers 429/503).
Addresses review feedback on #3827.
* fix(core): honor provider retry codes in stream retry predicate
generateContentStream's custom shouldRetryOnError only checked HTTP status, so
provider-specific rate-limit codes (e.g. 1302/1305 or caller-supplied
retryErrorCodes) that lack a 429/5xx status were silently dropped. Delegate to
isRateLimitError so they trigger retry, matching defaultShouldRetry. Also hoist
the duplicated getContentGeneratorConfig() call into a local cgConfig.
Addresses review feedback on #3827.
* fix(core): bound all retry delays by the setTimeout ceiling and fast-fail aborts
Address Copilot review on the prior commits:
- getRetryDelayMs now clamps the exponential and jittered delays (and the
Retry-After cap) by min(maxDelayMs, MAX_TIMEOUT_MS), so an oversized
caller-supplied maxDelayMs cannot let a computed delay overflow setTimeout.
- retryWithBackoff fast-fails on an abort/cancel error regardless of a
permissive shouldRetryOnError — cancellation is authoritative.
- Update the classifyRetryError JSDoc to note it drives the single fail-fast
persistent-loop exclusion.
Addresses review feedback on #3827.
* fix(core): return best-effort response when content retries exhaust
When shouldRetryOnContent keeps rejecting the response until the attempt budget
is spent, the loop previously fell through to a context-free
"Retry attempts exhausted" error, discarding the actual response. Track the
last rejected response and return it (best-effort) so the caller keeps the real
content and its context. The bare throw remains only as a defensive type-safety
fallback.
Addresses post-merge review feedback on #3827.
* fix(core): polish retry logging and provider-code classification
Address remaining review suggestions on #3827:
- logRetryAttempt now includes the computed backoff delay in its message.
- Normal-path Retry-After retries log at error level for 5xx (e.g. 503) and
warn for 429, via a shared logRetryAtStatusLevel helper.
- Content-retry path now emits a diagnostic log before sleeping.
- getProviderFields no longer echoes a numeric HTTP-status code (e.g.
{ status: 429, code: 429 }) as providerCode.
- Add a client.test.ts test that retryErrorCodes is forwarded to
retryWithBackoff, plus classification/logging regression tests.
GLM-5.2 ships a 1M context window, and 1M is becoming the norm for newer
GLM releases. The previous `/^glm-5/` rule capped the whole GLM-5 line at
202752, so every new model would need a code change.
Make 1M the forward default for GLM-5.2+, GLM-6.x..9.x and two-digit
majors, while pinning the confirmed 200K families (GLM-5 / 5.0 / 5.1 and
GLM-4.x or older) explicitly. Third-party deploy prefixes (e.g.
`pai/glm-5.3`) are already stripped by normalize(), so they match the same
rules. Non-numeric names (e.g. glm-z1) stay on the conservative fallback.
Previously /goal state lived only in frontend memory — page refresh or
multi-device sessions lost the active goal. Now the CLI emits goal status
updates as structured daemon events (_meta.goalStatus), which flow through
the transcript as status blocks (source: 'goal', data: {...}). The web-shell
rebuilds goal state from transcript blocks on connect, making goal status
survivable across page refreshes and syncable across devices.
- CLI: emitGoalStatus on goal set/clear, pass outputHistoryItems through
nonInteractiveCliCommands, add setAt to goalCommand output
- SDK: widen DaemonUiStatusEvent source/data types, preserve them in
transcript blocks
- webui: normalize _meta.goalStatus in DaemonSessionProvider, replace
sentinel-prefix text encoding with structured data
- web-shell: derive activeGoal from transcript blocks (getLatestActiveGoalFromBlocks),
remove optimistic client-side goal dispatch, parse structured goal data
in GoalStatusMessage/SystemMessage
- Tests: cover emitGoalStatus, outputHistoryItems passthrough, transcript
block serialization, DaemonSessionProvider event conversion
- Also: harden McpDialog restart result type check with isRestartEntriesResult
Co-authored-by: ytahdn <ytahdn@gmail.com>
The review-pr job only checked qwen's exit code, the tee status, the
timeout sentinel, and an empty log. When the model connection drops
mid-review, qwen still exits 0 and emits a terminal stream-json result
event with subtype=success / is_error=false whose text carries the
inlined '[API Error: ...]'. All existing checks pass, so the job goes
green without ever posting a review comment.
Inspect the terminal result event explicitly: fail when it is missing,
when is_error is true or subtype is not success, or when the result text
contains an inlined API error. A failed check now triggers the existing
fallback-comment step instead of a silent green success.
Refs #5052
* feat(web-shell): make input shortcuts discoverable and clickable
- Add an always-on, clickable hint row below the input: ↑ previous / ↓ next history, ctrl+r search, / commands, @ files — each click runs the matching action.
- Make the status bar "? for shortcuts" a persistent clickable button shown next to the mode indicator.
- Dismiss the shortcuts panel by clicking outside it or pressing Esc (no dedicated close button).
- Cancel reverse-i-search by clicking outside the panel (same as Esc), restoring the original draft.
- Distinguish ctrl+r (search history) from up/down (cycle history) in the shortcuts panel.
- Auto-prepend a space for mid-word @-mentions, and make the / and @ triggers idempotent so a second click re-opens the menu instead of producing "//" or "@ @".
* fix(web-shell): address PR review — touch dismissal, primary-button guard, @ idempotency
- Outside-press dismissal for the shortcuts panel and reverse-i-search now also listens for touchstart, ignores non-primary (middle/right) buttons, and respects defaultPrevented — matching the Settings/Mode inline panels.
- insertText('@') no longer inserts a duplicate '@' when the cursor sits directly before an existing '@'; it steps over the existing one and opens the menu.
* fix(web-shell): close shortcuts panel idempotently on outside-press
Touch fires touchstart plus a synthesized mousedown; a toggle onClose would reopen the panel right after closing. Use a dedicated close (set false) for the outside-press / Escape dismissal, matching the other inline panels.
* fix(web-shell): address /review — disabled guard, mid-line slash, focus, dedup
- Hide the hint row while the editor is disabled and bail the history/search callbacks on disabledRef, so the buttons can't bypass the disabled guard. insertText stays usable for App-driven injection; the now-hidden row is the only internal path that passes '/' or '@'.
- Clicking the / hint on non-empty, non-slash text replaces the content with '/' so the command menu actually opens, instead of leaving a stray mid-line '/'.
- Outside-click search dismissal no longer steals focus from the clicked target (closeSearch keepFocus=false).
- Extract a shared hintProps() helper for the five hint buttons.
* fix(web-shell): address /qreview — capture-phase Escape, hint aria-haspopup
- The shortcuts panel's Escape now wins over the App-level global Escape (capture phase + preventDefault/stopPropagation), so Esc closes the panel instead of being swallowed (clearing queued prompts / cancelling the stream) while it's open.
- Add aria-haspopup to the popup-opening hint buttons (dialog for ctrl+r, listbox for / and @), matching the sibling toolbar buttons.
* fix(web-shell): theme the slash-completion popup scrollbar
The CodeMirror autocomplete list and info panel used the browser-default (light) scrollbar, clashing with the dark theme. Apply the repo's scrollbar convention (thin + var(--border-color) thumb over a transparent track, with the -webkit fallback) so it matches in both themes.
* fix(web-shell): don't destroy the draft when clicking / on non-empty input
Replacing the document with '/' (the previous /review fix) silently wiped a typed draft. Instead, no-op when the line isn't already a command and the editor is non-empty — the slash menu needs a line-leading '/', which can't be added without either a stray mid-line '/' or clobbering the draft. Empty input still inserts '/' and opens the menu.
* fix(web-shell): guard hint history nav on multi-line input; hide hints behind dialogs
- navigatePrev/NextHistory now early-return on doc.lines > 1, matching the ArrowUp/ArrowDown keymap, so clicking the up/down hints no longer replaces a multi-line draft with a single history entry.
- showShortcutHints also requires !dialogOpen, matching the Ctrl+R keymap guard, so the hint buttons aren't interactive while a dialog is open.
* feat(web-shell): undo click-inserted / or @ on cancel; align hint nav with keymap
- Clicking the / or @ hint then pressing Escape (without typing past the inserted char) now removes the trigger too — it was clicked in, not typed. Editing past it cancels this.
- navigatePrev/NextHistory move the completion selection when the menu is open, matching the ArrowUp/ArrowDown keymap.
- The / hint no-op (non-empty draft) no longer fires startCompletion, which would pop an empty menu; a line-leading / still re-opens it.
* fix(web-shell): undo click-inserted trigger on any completion dismissal, not just Escape
Only the Escape keymap undid the click-inserted / or @. Clicking away or blurring also closes the menu but left a stray trigger behind. Watch the completion status (active -> closed) via an updateListener instead, so every dismissal path (Escape, click-away, blur) removes an untouched click-inserted trigger; the per-key special-case in the Escape handler and restarter is removed.
* feat(web-shell): grey out history hint arrows when there's nowhere to go
useInputHistory now exposes a nav { canUp, canDown } state, kept in sync on push/navigate/reset. The up/down hint buttons are disabled when there's no older entry to recall, or when not currently browsing history. Keyboard and mouse share the same state, so the affordance stays accurate however history is navigated.
* feat(core): add Kind.Agent for Agent tool to improve UI categorization
The Agent tool was using Kind.Other as a catch-all, causing WebUI
permission dialogs to show generic titles and descriptions. Adding a
dedicated Kind.Agent value enables agent-specific UI rendering in
permission drawers, tool labels, and export normalization.
🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
* test(webui): lock agent label mapping in labelUtils test
Address wenshao review on PR #5085 — add assertion that
getToolDisplayLabel({ kind: 'agent' }) returns 'Agent', matching the
sibling task/skill cases.
* test(cli): cover agent kind in export normalization + document acp-sdk cast
Address wenshao review on PR #5085:
- Add normalize.test.ts case asserting an agent-kind tool call is
preserved as 'agent' through normalizeSessionData.
- Add TODO(acp-sdk) comment on the KIND_MAP 'agent' cast explaining why
we emit 'agent' (webui SSE consumer) rather than mapping to 'other'.
* fix(acp): map Kind.Agent to 'other' on the wire; drop unusable 'agent' kind
wenshao's daemon-level A/B verification showed emitting kind:'agent' over
ACP is a regression: the daemon's ClientSideConnection Zod-validates every
session/update + session/request_permission from the qwen --acp child before
SSE fan-out, and @agentclientprotocol/sdk has no 'agent' ToolKind (verified
through 0.25.1), so the frame is rejected (invalid_union) and dropped — the
agent tool_call + permission dialog that previously reached SSE clients as
kind:'other' now never arrive.
Shrink to a no-regression safe version:
- ToolCallEmitter maps the internal Kind.Agent to 'other' on the wire (drops
the 'agent' as ToolKind cast that only fooled tsc, not the runtime schema).
- Revert the wire/UI/protocol 'agent' additions that depended on a value the
protocol can't carry: PermissionDrawer/labelUtils kind branches, web-shell
DaemonMessageToolKind + inferToolKind, Java SDK schema/enum, export allowlist.
Kind.Agent stays in core as the internal tool category. The dedicated agent
permission dialog will be delivered via _meta.toolName (which already rides the
validated wire) in a follow-up PR, not via a protocol kind.
* test(cli): add compactOldItems idempotency regression tests
Cover the scenario fixed in commit 595701096 where already-compacted
tool groups (resultDisplay === UI_COMPACT_CLEARED_MESSAGE) were
incorrectly counted as having real output, causing over-compaction.
Three new test cases:
- Already-compacted groups are not re-compacted; second call is a no-op
- All tool groups already compacted → no-op
- Mixed tool group (some tools real, some cleared) → only groups with
real output are compacted
* fix(cli,core): enable explicit GC and disable debug log by default
- enableExplicitGC defaults to true, --expose-gc added to start/dev scripts
- isDebugLogFileEnabled() defaults to false (opt-in via QWEN_DEBUG_LOG_FILE=1)
- Add safety tests: trigger_gc only in critical tier, global.gc() only in
memoryPressureMonitor.ts trigger_gc case
* fix: address R1 review comments for memory pressure monitor
- Replace brittle source-parsing test with behavioral tests for global.gc()
- Export UI_COMPACT_CLEARED_MESSAGE constant and use in tests
- Remove redundant NODE_OPTIONS override from start script
- Add production bin wrapper with --expose-gc for OOM protection
- Remove unused path import from memoryPressureMonitor.test.ts
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* fix: forward --expose-gc to all deployment modes
Standalone package shims and daemon-spawned sessions (AcpBridge,
httpAcpBridge) were missing --expose-gc, causing explicit GC to
silently fail under critical memory pressure.
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* fix: forward child process signal in cli-entry wrapper
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* fix(cli,channels): filter --inspect flags when forwarding execArgv to daemon children
* fix: make cli-entry.js executable (mode 100755)
* fix(core): reject whitespace-only QWEN_DEBUG_LOG_FILE and add QWEN_MEMORY_ENABLE_GC=0 opt-out
* fix(scripts): include cli-entry.js wrapper in dist package for npm publish
* fix(acp-bridge): forward --expose-gc and filter --inspect in spawnChannel
- Add --expose-gc to getAcpMemoryArgs() so daemon-spawned ACP children
have global.gc() available for critical memory pressure cleanup
- Filter --inspect/-brk flags from process.execArgv to prevent port
conflicts in multi-session daemon mode
- Update spawnChannel.test.ts for new getAcpMemoryArgs() return shape
This change was previously in httpAcpBridge.ts but lost during the
daemon refactor merge (#4490) that moved spawn logic to acp-bridge.
---------
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* feat(web-shell): reveal full tool detail and auto-collapse finished tools
Long tool descriptions were hard-capped at 120 characters and finished
tools (shell/edit/write) stayed expanded indefinitely, so commands were
unreadable and the transcript filled with stale output.
- Lift the 120-char description cap so the full command/path reaches the
DOM; collapsed rows ellipsise via CSS (adapts to width) and a click
reflows the full text into a wrapped block below the header.
- Add a leading disclosure chevron; any row with detail output or a long
description is now expandable.
- Auto-collapse a tool to its one-line summary once it completes
successfully. Running tools stay expanded (live output) and failures
stay expanded (error visible); agents keep their own manual expand state.
* fix(web-shell): preserve manual expand on completion; correct auto-expand comment
Review feedback on #5088:
- shouldAutoExpand: rewrite the comment to match the code. Only the verbose
kinds (shell/edit/write/ask) auto-expand and stay expanded on failure; other
kinds are collapsed by default (their summary line shows the outcome and they
stay click-to-expand). Force-expanding every failed tool was rejected because
tools without an expanded-detail renderer would then hide the summary line
and show an empty body — i.e. hide the error.
- Auto-collapse-on-completion no longer overrides an explicit user toggle: a
userToggledRef latch (set on header click, reset on tool-identity change)
guards the collapse effect, so a row the user expanded/collapsed keeps its
state when the tool finishes.
* test(web-shell): assert tool-detail relocation via DOM, not textContent
Review feedback (#5088): the expand test asserted container.textContent
contains the command before and after the click, which passes regardless of
whether the description is relocated from the header span to the wrapped
block (textContent concatenates the whole subtree). Assert the DOM move
instead — the command is in a leaf <span> while collapsed and in none while
expanded — so a regression dropping the relocation now fails the test.
Migrate the built-in Computer Use tool surface from open-computer-use
(npm/npx) to cua-driver-rs (native Rust driver, trycua/cua).
- Replace the 9-tool ocu surface with the full 35-tool cua-driver surface
(page/CDP, cursor, session, recording, config, app lifecycle, …),
generated from the live `cua-driver mcp` tools/list — pinned to v0.5.2.
- Per-platform signed + notarized binary distribution: download into
~/.qwen/computer-use/ with SHA-256 integrity verification, a three-tier
Windows unzip fallback, headers/idle timeouts, and a bounded retry loop.
- macOS TCC permission flow via CuaDriver.app (com.trycua.driver), polled
one-at-a-time through the no-gate status daemon.
- Mirror cua-driver assets on the qwen-code-assets OSS bucket with a
push-to-main sync workflow guarded by checksums.
- Gate high-risk tools (kill_app, launch_app, start_recording, set_config,
replay_trajectory, page JS execution) to an explicit confirmation type so
AUTO_EDIT cannot silently auto-approve them; AUTO defers to the classifier,
YOLO auto-approves.
Under StrictMode (dev default), DaemonWorkspaceProvider's useEffect
cleanup called client.dispose() synchronously, destroying the memoized
DaemonClient that the second effect invocation reused. This left the
transport closed before the session provider could attach, surfacing as
"Transport connection closed" and a permanent "Loading..." / disconnected
state in the web-shell.
Defer disposal by one microtask. StrictMode's synchronous re-mount
cancels the pending disposal before the microtask fires, preserving the
shared client. Real unmounts and client replacements still dispose
normally since no cancellation occurs in those paths.
* fix(cli): add OSC 52 clipboard fallback for SSH environments
- Add writeOsc52() helper in commandUtils.ts and vim.ts
- Fall back to OSC 52 escape sequence when xclip/xsel/wl-copy unavailable
- Fixes /copy command and vim yank (yy, yw, etc.) over SSH without X11
* refactor(cli): extract writeOsc52 to shared clipboardUtils
- Add writeOsc52() export in clipboardUtils.ts with TTY check, error handling,
and boolean return for success/failure
- Remove duplicate writeOsc52 from commandUtils.ts and vim.ts
- Update both to import from clipboardUtils.ts
- Add 6 tests verifying OSC 52 escape sequence output to stdout/stderr,
TTY detection, special chars, empty string, and error handling
- Fix commandUtils test to assert OSC 52 fallback instead of throwing
Fixes code duplication and inconsistent error handling noted in PR review.
* test(clipboard): fix spawn timeout test hanging with fake timers
Remove vi.useFakeTimers() which is incompatible with process.nextTick
in the mock pattern. Use real timers with 10s test timeout instead.
* fix(cli): check writeOsc52 return value in callers
Warn when OSC 52 clipboard write fails (no TTY available) instead of
silently ignoring the failure.
* fix(test): clear wl-paste image type cache before BMP-to-PNG tests
BMP-to-PNG test block was missing its own beforeEach to reset the
cachedWlPasteImageTypes cache, causing the 'prefer PNG over BMP' test
to fail intermittently in CI when a previous test had populated the cache.
* fix(test): use dynamic import instead of vi.resetModules() to fix test pollution
* fix(test): correct spawn call assertion in BMP-to-PNG clipboard test
* fix(test): clean up /tmp/test before BMP-to-PNG clipboard test to fix flaky assertion
* fix(test): restore vi.resetModules() with dynamic imports for test isolation
Replace stale top-level imports of clipboardUtils with describe-level
variables populated by dynamic import() after vi.resetModules() in
beforeEach. This ensures every test gets a fresh module instance,
eliminating cross-test state pollution from cachedWlPasteImageTypes
and linuxClipboardTool.
* fix(cli): throw error when all clipboard methods fail and restore vi.resetModules for test isolation
* fix(cli): try OSC 52 before throwing when xclip/xsel fail
Ensures consistent behavior between /copy and vim yank commands
in SSH environments where xclip/xsel are installed but fail
due to missing X display.
* test(cli): add OSC 52 fallback tests for xclip/xsel failure scenario
Adds two tests to verify OSC 52 behavior when clipboard tools
exist but fail (e.g., SSH without X forwarding):
- Verifies OSC 52 is attempted and fails gracefully when no TTY
- Verifies OSC 52 succeeds when TTY is available
* test(cli): mock isTTY in OSC 52 no-TTY fallback test to prevent flakiness
* fix(cli): wrap OSC 52 sequence with wrapForMultiplexer for tmux/screen support
* fix(cli): try OSC 52 fallback in vim writeClipboard when cached tool fails at runtime
* fix(cli): prefer stderr over stdout for OSC 52 to avoid Ink rendering pipeline interference
* fix(cli): mention OSC 52 attempt in error message when xclip/xsel and OSC 52 both fail
* fix(cli): harden OSC 52 clipboard with size limit and async error handling
- Cap OSC 52 payload at 75KB (~100KB base64) to prevent terminal crashes/hangs
from oversized escape sequences (iTerm2 ~100KB, xterm ~8KB limits)
- Add write callback to capture async failures on stdout/stderr streams
- Apply same hardening to duplicate implementation in AuthenticateStep.tsx
- Strengthen tests: stub TMUX/STY for determinism, add tmux/screen DCS wrap tests
* feat(core): Workflow P3 — agent({schema, agentType, model, isolation:'worktree'}) (#4721)
Adds the P3 dispatch options to the workflow runtime, completing the
contract qwen-code's workflow tool matches against upstream Claude Code
2.1.168. P1/P2 stubs (workflow-sandbox.ts:508-527) are replaced with
production paths routed through `SubagentManager.createAgentHeadless` so
per-call model overrides go through `buildRuntimeContentGeneratorView`
(provider routing), per-agent MCP servers / hooks get isolated
lifecycles, and worktree-isolated subagents run against a rebound Config.
- agent({agentType: 'X'}) resolves against the declarative-agents
registry (#4842 + #4996) via findSubagentByName; unresolved names throw
"agent({agentType}): agent type 'X' not found" verbatim from upstream.
- agent({model: 'qwen3-max'}) is threaded into SubagentConfig.model so
the runtime view sees it (modelConfigOverrides alone would only swap
the model name within the existing provider's view).
- Workflow's disallowed-tool floor [SendMessage, ExitPlanMode] is unioned
with the agentType's own disallowedTools so a permissive agentType
cannot re-enable them for a workflow subagent.
- agent({isolation: 'worktree'}) provisions a fresh worktree via
GitWorktreeService.createUserWorktree (slug agent-<7hex>, mirrors
AgentTool 1849-1963), rebinds cwd/getTargetDir/getFileService/
getWorkspaceContext on a prototype-chained Config override, and on
completion auto-removes the worktree if clean or preserves the path +
branch (appended to the result string) when the subagent left changes.
Parent-dirty trees are refused with a clear error to avoid silently
running the subagent against a stale HEAD.
- agent({isolation: 'remote'}) throws "agent({isolation:'remote'}) is
not available in this build" verbatim (upstream 2.1.168 parity).
- agent({schema: S}) injects a per-call SyntheticOutputTool (existing
tools/syntheticOutput.ts, AJV-backed) into a fresh per-subagent
ToolRegistry built via rebuildToolRegistryOnOverride, then watches
AgentEventEmitter TOOL_CALL/TOOL_RESULT events for `structured_output`
invocations. A successful call's args are captured as the dispatch
return value (object, not string); after two failed attempts the
third failure aborts the dispatch and throws "subagent completed
without calling StructuredOutput (after 2 in-conversation nudges)"
verbatim. No agent-core.ts changes — the entire 2-nudge counter
lives in the dispatch layer so the shared subagent loop is unaffected.
The sandbox's agent() wrapper now revives per-call object returns into
the vm realm (JSON round-trip inside the vm runInContext block), closing
the same T1/T8/T14 host-prototype-escape vector that P2's per-element
revival closed for parallel/pipeline. Two new sandbox security tests
(constructor-chain probe + non-JSON-serializable collapse) regress this.
WorkflowAgentResult widens from `string` to `string | object`; the
fast-path (no agentType/model/isolation/schema) is preserved byte-for-byte
to keep P1/P2 zero-overhead.
Tests: 159 workflow-suite tests + 217 adjacent (subagents / syntheticOutput /
agent-override) all green. Real-LLM E2E follow-up planned (mirroring P2's
13/13 qwen3-max validation).
Related #4721 (parent design — multi-phase, not closed by this PR)
Related #4732 (P1 merged) #4947 (P2 merged) #4842#4996 (declarative agents)
* chore(core): P3 self-review R1 — align worktree suffix wording + 6 test gaps
R1 of pre-push adversarial self-review on PR #5034 surfaced 6 confirmed
findings across 6 diverse lenses (correctness / security / reuse-altitude
/ self-invariant / consumer-breakage / test-gaps). Each finding faced 2
independent skeptics defaulting to refuted=true; 6 survived majority
challenge.
Source code:
- Worktree-preserved suffix wording now matches AgentTool's
formatWorktreeSuffix (agent.ts:1700-1719) verbatim, including the
`git worktree add <path> <branch>` recovery hint for the directory-
removed-but-branch-preserved race.
Test gaps closed:
- schema-mode success after 1 nudge (round-2 args captured)
- schema-mode success after 2 nudges (round-3 args captured)
- schema-mode + agentType together — floor disallowedTools still unioned
- schema-mode caller-abort takes priority over the StructuredOutput
terminal error (signal.aborted check at workflow-orchestrator.ts:489-490)
- override path dispose() runs in finally on the success path
- override path dispose() runs in finally on the terminate-mode-error path
Declined R1 finding: negative tests for invalid opt types (schema/model/
agentType passed null/number/empty-string). Adding upfront type
validation is scope creep — upstream does not, P1/P2 do not, and the
workflow tool is model-authored where these inputs are extremely
unlikely. Existing AJV / SubagentManager downstream errors are descriptive
enough. Will revisit if R2 makes a stronger case.
166/166 tests pass (workflow suite + adjacent + workflow-orchestrator).
typecheck + lint clean across packages/core, packages/cli,
integration-tests, sdk, webui.
* chore(core): P3 self-review R2 — vm-realm opts revive + error-msg sanitize + 12 tests
R2 of pre-push adversarial self-review on PR #5034. 6 diverse-lens
finders (60 agents, ~2.5M tokens, 24 min) over the R1-fix-applied
code, with 2 independent skeptics defaulting to refuted=true.
12 confirmed survivors after adversarial verify; decisions below.
Security (FIX):
- agent() wrapper in workflow-sandbox.ts now JSON-revives agentOpts
inside the vm runInContext block BEFORE passing them to the host
dispatch. Closes a Proxy/inherited-getter escape that P3 introduced
along with the user-supplied schema object: a script could have
wrapped agentOpts.schema in a Proxy whose getter ran host-side code
during SyntheticOutputTool construction / AJV compile. Same
mechanism as args / parallel-result revival.
- runOverridePath now sanitizes opts.agentType through
sanitizeForErrorMessage() (control chars → space) before
interpolation into the "agent type 'X' not found" error message.
Prevents a model-authored agentType containing CRLF / NUL from
fragmenting a single-line error across log records / OTLP fields.
Reuse-altitude (FIX):
- Added JSDoc block to WorkflowWorktreeIsolation interface
documenting each field's role for cleanup.
Test gaps (FIX, 12 new tests):
- agentType control-char sanitization regression
- dispose() runs in finally when subagent.execute throws
- isolation:'worktree' provision error branches (5):
nested parent / git unavailable / not a git repo / parent dirty /
createUserWorktree returns failure
- isolation:'worktree' cleanup branches (3):
removeUserWorktree fails / branchPreserved race / removeUserWorktree
throws — each preserves the worktree (or branch) with the right
user-facing suffix
- combinations (2): model + isolation:'worktree' threads model AND
provisions worktree; schema + isolation:'worktree' returns
structured payload verbatim (preserved suffix only on string return)
Test infrastructure: vi.mock'd GitWorktreeService at the module level
(partial mock; preserves the existing exports the unrelated
worktreeCleanup.ts depends on) with a per-test beforeEach reset.
Declined R2 findings (kept the R1 line):
- [major] Schema parameter upfront validation: same scope-creep
decline as R1. Upstream doesn't do it; AJV's downstream error is
descriptive enough.
- [major] Worktree provision extracted to shared util with AgentTool:
agreed in principle but out of P3 scope. A separate refactor PR
should land that with AgentTool maintainers in the loop.
178/178 tests pass (workflow + adjacent suites). typecheck + lint
clean across packages/core, packages/cli, integration-tests, sdk,
webui.
* fix(core): address wenshao R1+R2 review on Workflow P3 (PR #5034)
Round 1 (15:41) + Round 2 (17:24) review from wenshao surfaced 7 inline
findings across schema-mode dispatch correctness, worktree cleanup
coverage, and error attribution. Each fix is paired with a regression
test that was RED before the change landed.
T0 [Critical] Worktree leak when schema setup throws after provision
workflow-orchestrator.ts: outer try MOVED to start immediately after
provisionWorkflowWorktree. Previously the try opened only after
createSchemaConfigOverride / createSchemaModeState / signal listener
attachment — so any throw in those three (broken MCP server during
the per-call ToolRegistry rebuild was the trigger wenshao cited)
orphaned the just-provisioned worktree under .qwen/worktrees/.
Test: "isolation:'worktree' + schema setup throws → worktree is
still cleaned up" — simulates createToolRegistry failure during
createSchemaConfigOverride; asserts removeUserWorktree was called.
T1 [Critical] / T4 [H1] agentType + schema silently dead-ended
workflow-orchestrator.ts: schema-mode augmented config now (a)
appends ToolNames.STRUCTURED_OUTPUT to baseConfig.tools when the
allowlist is restricted (no '*' and doesn't already contain it), so
prepareTools / getFunctionDeclarationsFiltered doesn't filter
structured_output out of the subagent's surface; (b) preserves the
resolved agentType's persona by APPENDING the schema-contract
instruction block instead of replacing the systemPrompt outright.
Replace remains only on the ephemeral no-agentType path where
baseConfig.systemPrompt IS WORKFLOW_SUBAGENT_SYSTEM_PROMPT (schema
variant is its strict superset; avoids two near-identical prompts).
Tests: structured_output appears in the allowlist alongside the
agentType's existing tools; persona prompt is contained in the
effective systemPrompt.
T2 [Suggestion] / T5 [M1] Parent-abort listener leaked per schema call
workflow-orchestrator.ts: named listener stored at outer scope,
removed in the outer finally regardless of how the dispatch ended.
Previous `{ once: true }` only auto-removed on actual parent abort;
the happy-path schema dispatch — success capture / 3-failure abort
fires the CHILD controller without the parent ever aborting — left
the listener stuck on the per-run signal. With N schema calls per
workflow N listeners + N child-controller closures accumulated.
Test: 5 sequential schema dispatches over the same parent signal
end with zero live listeners.
T6 [M2] Terminate mode misdiagnosed as nudge exhaustion
workflow-orchestrator.ts: schema path now distinguishes
terminateMode before attributing failure to schema mode. TIMEOUT /
MAX_TURNS / ERROR throw the existing "did not complete (terminate
mode: X)" message that the non-schema path uses. Only the actual
schema-failure cases produce schema wording, and those are split:
attempts > 2 keeps the upstream-verbatim "(after 2 in-conversation
nudges)" wording; attempts === 0 throws an accurate "no validation
attempt — model produced plain-text content" instead of misleadingly
citing nudges that never happened. (The existing 0-call test was
updated to match the new accurate message; the 3-failure test
retains the verbatim wording.)
Tests: parametric over TIMEOUT/MAX_TURNS/ERROR asserting "did not
complete"; companion test pinning the verbatim wording to the
3-failure path.
T3 [Suggestion] Schema-mode JSON revival sentinel — clarified
workflow-sandbox.ts: added a block comment documenting that the
JSON-round-trip + null-on-throw is a SECURITY backstop (errors-as-data
convention from parallel/pipeline) rather than a contract path —
unreachable in production schema mode because the host return is
LLM tool_call args, always JSON-serializable. No behavior change.
Tests: 75/75 orchestrator + 111/111 sandbox/tool/limiter green.
typecheck + lint clean across packages/core and packages/cli.
R1+R2 self-review commits (e1c5ec79c / 62624a994) precede this commit
on the same branch — they predate wenshao's review and address
distinct findings; reviewer L1 (worktree-lifecycle unit coverage) is
already closed by R2's 11 worktree tests.
The message-time-on-hover feature (#5079) wraps each transcript message
with MessageTimestamp, but two sub-agent surfaces were left out and so
looked inconsistent with the rest of the transcript:
- The "Parallel agents · N/N done" box (ParallelAgentsGroup) renders
directly in MessageList, bypassing MessageItem/MessageTimestamp, so it
showed no time. Carry the first grouped launch's timestamp onto the
parallel_agents display item and wrap the box in MessageTimestamp.
- Each sub-tool row inside a SubAgentPanel's Tools list showed no time.
Wrap each row in a scoped hover tooltip (.toolTimeRow/.toolTimeTip,
kept separate from MessageTimestamp's .row/.tip so the nested tooltip
stays independent of the enclosing message's) keyed off the tool's
startTime.
Both reuse formatTimestamp for an identical HH:mm:ss (or dated) format
revealed on hover in the top-right corner, matching the main transcript.
Persist /loop tasks per-project under ~/.qwen/tmp/<project-hash>/ so they survive restarts; the default stays session-only. Missed one-shots are surfaced at startup confirm-first; overdue recurring jobs catch up once then resume. A per-project lock elects a single firing session across concurrent sessions, with takeover on owner exit. Recurring jobs expire after 7 days (final fire), and never-matching cron expressions are rejected at creation. Durable storage lives in the user runtime dir, not the working tree, so it is never committed or shared via the repo.
* fix(serve): add prompt queue backpressure
Add per-session prompt admission limits across the bridge, REST and ACP entrypoints, and SDK clients. The server now rejects full prompt queues before returning accepted semantics, advertises the active limit through capabilities, and documents the behavior with focused tests and design artifacts.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(sdk): stabilize pending prompt cleanup
Close the mocked SSE stream explicitly in the pending prompt cap test so cleanup does not rely on abort-driven stream cancellation timing in CI.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(sdk): stabilize subscription prompt race
Reject accepted subscription prompts if the event stream has already ended, and make the prompt-cap tests wait for the pending registration before closing or injecting SSE frames.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(sdk): map prompt queue full responses
Map server-side prompt_queue_full responses to DaemonPendingPromptLimitError for both blocking and non-blocking prompt calls, include the session id in the local limit error, and cross-reference the duplicated default prompt cap constants.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test: keep qwen planning docs ignored
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(serve): address prompt backpressure review
Log synchronous prompt queue rejections, document the sync admission contract, clean up SDK prompt-slot release, and cover the reviewed backpressure edge cases.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(sdk): restore daemon bundle budget headroom
Reduce the generated daemon client bundle slightly and raise the browser daemon SDK bundle budget to 116 KiB so the PR merge ref has practical headroom.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* fix(acp-bridge): preserve original timestamp when replaying session history
History replay re-emits each persisted record with its original epoch-ms time nested in update._meta, but BridgeClient.sessionUpdate published the frame without lifting it to the envelope. EventBus.publish then stamped envelope _meta.serverTimestamp with publish-time Date.now(), which the client's extractServerTimestamp picks up at higher priority than the nested original — so a resumed session rendered every historical message at the resume moment instead of when it was sent.
Lift update._meta.timestamp (or serverTimestamp) to the envelope serverTimestamp so EventBus preserves it. Live updates without such a timestamp keep the Date.now() fallback unchanged.
* feat(web-shell): show each history message's time on hover
Carry each transcript block's wall-clock time (serverTimestamp ?? clientReceivedAt) onto every message and reveal it as a CSS-only hover tooltip in the message list. Same-day messages show HH:mm:ss; older ones show yyyy-MM-dd HH:mm:ss (local time, zero-padded).
The "Current tasks" panel above the composer was a static display:
always expanded, rotate-to-front ordering with jumbled numbering,
no progress summary, and it vanished the instant the last item
completed.
- Collapsible header (persisted in localStorage); collapsed mode is a
single line showing progress + the current in-progress item
- Progress counter (completed/total) in the header
- Natural-order window anchored on the in-progress item replaces the
rotation: one completed context line above, pendings below, with
clickable "N completed" / "N more" summary lines that expand the
full list (and "Show less" to return)
- All-done moment: a finished list stays visible as "All tasks
completed" until the next user prompt instead of disappearing
instantly; historical finished lists stay hidden on session restore
- Locate button scrolls the transcript to the source TodoWrite/plan
message with a flash highlight (new MessageList imperative
scrollToMessage, callId fallback for compact-merged tool groups)
- Visual consistency: in_progress uses the accent color, PlanMessage
adopts the shared icon set, items ellipsize to one line with a
hover tooltip, and the number column scales past 9 items so the
status icons stay aligned
getFloatingTodos moves to utils/todos.ts and now reports
{todos, allCompleted, sourceMessageId, sourceCallId}; panel visibility
is a render-time state machine so the active-to-completed transition
does not unmount the panel for a frame. New i18n keys for en/zh-CN
and 17 new unit tests.