Commit graph

3338 commits

Author SHA1 Message Date
tanzhenxin
879b854e8a
fix(cli): Keep model picker entries contiguous in short terminals (#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
2026-07-06 14:05:32 +00:00
jinye
3744cd09ae
feat(cli): Add Phase 1 workspace runtime registry (#6394)
* 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 (#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>
2026-07-06 14:01:14 +00:00
Shaojin Wen
350191e101
feat(web-shell): add token-usage analytics dashboard to Daemon Status (#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.
2026-07-06 13:43:41 +00:00
jinye
5c8af1a1fa
fix(cli): allow ACP local fallback reads from /tmp (#6370)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
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>
2026-07-06 07:42:46 +00:00
Shaojin Wen
9a63c03224
feat(web-shell): add a Scheduled Tasks management page (#6348)
* feat(web-shell): add scheduled tasks management page

Add a "Scheduled tasks" page to the Web Shell for managing durable cron tasks against the current workspace.

- Sidebar entry opens a full-pane page (replaces the chat area, not a modal) listing tasks with enable/disable toggle, delete, run-now, and human-readable schedules.
- "New scheduled task" opens a modal with a schedule builder (daily / weekdays / weekly / hourly / every-N-minutes / custom cron) and a live preview.
- "Create via chat" returns to the chat and primes the composer so the agent creates the task through its cron_create tool.
- Daemon CRUD routes (GET/POST/PATCH/DELETE /scheduled-tasks) read/write the existing per-project scheduled_tasks.json; task firing stays with the session-side scheduler.
- Extend DurableCronTask with optional name/enabled (backward compatible); the scheduler skips tasks with enabled:false.
- Add /scheduled-tasks to the vite dev-server proxy allowlist so the page works under npm run dev:daemon.

* chore(web-shell): address review feedback on scheduled tasks

- cron_list: surface name/enabled so the agent can tell a disabled durable task from an active one (a disabled task no longer looks identical to an active one).
- core: export only the tasks-file functions the daemon route actually uses (drop unused addCronTask / getCronFilePath / CRON_TASKS_DISPLAY_PATH from the public barrel).
- CronScheduler: warn when a durable reload fails and the prior view is kept, since a just-disabled or -deleted task can keep firing until the next successful reload.
- Extract the schedule helpers (buildCron / describeCron / parseHhmm / describeLastRun) into a pure module and add unit tests for them.
- Add route tests for PATCH cron/prompt/recurring, empty-patch rejection, and POST field-length / boolean-type validation.

* chore(web-shell): address second review round on scheduled tasks

- Log CRUD errors server-side (writeStderrLine) in each route catch block, matching the other daemon routes.
- Share one id generator (generateCronTaskId in cronTasksFile) between the scheduler and the daemon route instead of duplicating it.
- describeCron: recognize cron day-of-week 7 as an alternate notation for Sunday.
- Reset the builder time to :00 when switching to the hourly frequency (its time picker is hidden, so it no longer silently carries the daily minute).
- Tests: cron_list name/disabled output; route Feb-30 impossible-cron and corrupt-file 500 read-failure; describeCron dow=7.

* chore(web-shell): address third review round on scheduled tasks

- Run now: report sendPrompt rejections via the toast/error path instead of dropping the promise.
- Block chat interaction while the full-pane Scheduled Tasks view is open, so the covered composer can't receive keystrokes/Escape.
- Guard reload() with a request-sequence id so a slow load can't overwrite a newer list after a mutation.
- Re-enabling a task that had genuinely fired resumes from now instead of catching up work paused while it was disabled.
- Restrict "every N minutes" to divisors of 60 (a non-divisor */N fires more often than the label claims).
- Show a Repeats / Runs once label on each card so tool-created one-shots aren't mistaken for repeating schedules.
- Return generic 500 client messages (no internal file path); the detail is logged server-side.
- Tests: SDK scheduled-task methods (method/URL/id-encoding/headers/errors); route re-enable behavior both ways.

* chore(web-shell): address fourth review round (minor suggestions)

- Route error logs interpolate the actual task id instead of the literal ":id".
- cron_list returnDisplay includes the task name (matching llmContent) so terminal /cron list shows UI-assigned names.
- Truncate the delete-confirm label so an unnamed task's long prompt doesn't blow up the confirm() dialog.
- Cap the create-form prompt textarea at MAX_PROMPT_LENGTH and drop the dead typeof-window guard.
- Test generateCronTaskId (format + near-uniqueness).

* chore(web-shell): address fifth review round on scheduled tasks

- Re-enable now resumes any recurring task from now (stamp on every false→true), not only ones that had already fired — a task disabled before its first run no longer catch-up-fires the slot it was paused through.
- describeCron applies the same divisor-of-60 check as buildCron, so a hand-edited/persisted */45 falls back to the raw expression instead of a misleading "every 45 minutes".
- Strengthen the corrupt-file route test to assert the generic client message and no leaked file path.
- Tests: recurring-disabled-before-first-run and one-shot re-enable; describeCron non-divisor fallback.

* test(cli): cover legacy scheduled-task normalization on GET

Seed a pre-fields task (no name/enabled) directly to disk and assert the GET response normalizes it to name:null / enabled:true, guarding backward compatibility with existing scheduled_tasks.json files.

* fix(core): cap durable cron loads against a durable-only budget

The daemon route accepts up to MAX_JOBS durable tasks on disk, but the scheduler previously capped durable loads against its combined job map (session-only + durable). A session holding session-only cron jobs could push the map to MAX_JOBS and make loadFileTasks silently skip durable tasks the route had already accepted — a create that returned 201 would then never fire.

Cap durable installs against a durable-only count instead, and share one MAX_JOBS constant between the scheduler and the daemon route, so a successful create is always loadable. Adds a scheduler test that 40 session-only jobs no longer crowd out 20 durable loads.
2026-07-06 03:47:17 +00:00
Shaojin Wen
edc0555ed1
feat(web-shell): named session groups and color tags in the sidebar (#6350)
* feat(web-shell): named session groups and color tags in the sidebar

Extend web-shell session organization with named groups (create / rename /
delete, assign a session to a group) alongside quick color tags, and surface
pin / archive state. The grouping data is plumbed end-to-end through the
daemon.

- core: session-organization-service carries group id / name / color and
  pin / archive metadata on organized-list entries
- sdk / acp-bridge: session-list entries gain groupId / groupName /
  groupColor / archivedAt; add SessionGroupColor and list-session-groups
  result types
- cli/serve: dispatch + session routes expose listing and assigning groups
- web-shell: sidebar group management UI (create / rename / delete groups,
  color picker, pin, archive) and reuse the shared "Group" label for the
  group action, dropping the redundant "Move to group" string

* fix(cli): exclude color-tagged sessions from the ungrouped filter

Color / named group / recent are mutually exclusive buckets in the web-shell
sidebar — a color-tagged session shows in its color section, not "recent".
But the organized session-list `group=ungrouped` filter only checked
`groupId == null`, so a color-tagged session with no named group leaked into
ungrouped results for REST/ACP consumers, disagreeing with the UI taxonomy.

Align the server filter: ungrouped means no named group and no color tag.
Adds an ACP session/list test asserting a color-tagged session is excluded
from group=ungrouped (fails on the old filter, passes on the new one).

* fix(web-shell): clear color tag when creating a group for a session

saveGroupEditor's create-with-target path assigned the new group but left any
existing color tag in place, unlike the sibling assignSessionGroup /
assignSessionColor paths that keep color and named group mutually exclusive.
Because color takes precedence in the sidebar's section bucketing, the session
stayed in its color section and the group assignment had no visible effect.

Send `color: null` alongside `groupId` on that path, and extend the
create-group dialog test to assert the assignment clears the color.

* fix(cli): exclude color-tagged sessions from the named-group filter

Follow-up to the ungrouped filter fix: the per-group filter (group=<id>) also
ignored color precedence. Core and the REST/ACP update paths can persist both
groupId and color, and the sidebar renders such a session in its color bucket,
so group=<id> API consumers saw a session the web-shell shows elsewhere.
Require `color == null` there too, matching the sidebar taxonomy
(color > group > recent).

Adds an ACP session/list test for a session with both groupId and color set.
2026-07-06 02:19:16 +00:00
MikeWang0316tw
1b58ede8e7
fix(cli): smoother live streaming preview — drop "generating more" cue, hold back partial table rows (#6340)
* fix(cli): drop redundant "generating more" cue from the live preview

In non-VP mode the live markdown preview is clipped to a rendered-height
budget so the frame never overflows the viewport and triggers ink's
scroll-to-top full redraw. It used to append a "... generating more ..."
cue (and code/math/mermaid blocks appended their own) to signal that the
clipped tail was still coming.

Since #6170 landed the incremental scrollback commit, that tail is
streamed into <Static> in real time — clipped content is "still
streaming" and reappears within a commit cycle, not "delayed output".
The cue is therefore redundant noise that flickers in step with the
commit cycle, so remove all four occurrences (outer preview clip, code
block, mermaid block, math block).

The row each cue used to occupy is reclaimed for content, so the total
rendered height is unchanged: the code/math/mermaid RESERVED_LINES drop
by one and the outer slice trigger switches from the (now-inlined)
`clipped` flag to `keptLines < allLines.length`. The TableRenderer
"… more rows streaming …" clamp is intentionally kept — an in-progress
oversized table is not yet in scrollback, so that cue still carries
information.

Also gitignore the nested `.qwen/computer-use/` marker so the
auto-generated artifact stops showing up as untracked.

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

* fix(cli): hold back the unterminated table row while streaming

While a markdown table streams, the frontier line is often a half-typed
row like `| a | b` with no closing `|` yet. Because TABLE_ROW_RE requires
both a leading and trailing pipe, that partial line does not match, so the
parser closed the table and rendered the partial as a plain text line
below it — then, once the closing `|` arrived, flipped it into the table.
This per-token flip changed the frame height and re-ran column autosizing
on every keystroke, jittering the live table.

Hold the partial row back instead: when pending, if the final line is an
unterminated table row and at least one complete row already exists, skip
it so `inTable` stays set and the end-of-content handler keeps rendering
the accumulated rows as a live table. The row appears the moment it
terminates. The `tableRows.length > 0` guard keeps the header + separator
from blanking out while the very first row is still being typed.

Note: this smooths the table content itself; it does not change the
streaming repaint frequency, so the fixed bottom controls still repaint on
each tick (that is the domain of the flicker-reduction work, e.g. #5396).

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

* docs(cli): fix two stale "generating more" cue references in comments

Review follow-up: two comments still referenced the removed outer cue.

- TABLE_PENDING_RESERVED_ROWS: reword "marginY 2 + the outer cue" to
  "marginY 2 + one row of wrapped-cell safety headroom". The reserve stays
  at 3 on purpose — tables under-estimate their rendered height the most
  (wrapped cells), so they keep one more backstop row than the other
  blocks; lowering it would shrink that safety margin.
- pending-rendered-height PendingSliceResult.keptLines JSDoc: drop the
  "plus a 'more' cue" phrasing — the caller now renders nothing rather
  than an oversized row.

Comment-only; no behaviour change. 155 tests pass.

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

* fix(cli): hold back the partial first table row too; de-dup reserve constant

Review follow-up on the streaming table hold-back.

- The `tableRows.length > 0` guard skipped the hold-back for the FIRST
  data row: a partial first row fell through to the table-closing branch,
  which also requires a row, so the header + separator were dropped and
  the partial rendered as a stray text line — the same per-token flip the
  change is meant to remove, just for the first row. Relax the guard to
  `tableHeaders.length > 0` so an unterminated first row/separator is held
  back too; the table is simply not drawn until its first row terminates,
  then pops in complete and grows one row at a time. Comment corrected to
  describe the actual behaviour.
- Add a test for that edge case (partial first row held back, table
  appears once the row terminates).
- De-duplicate the magic `3`: the slice-side `tableClampRows` estimate now
  references `TABLE_PENDING_RESERVED_ROWS` (moved to the top-of-file
  constants) instead of a literal, so the estimate and RenderTable's
  render-side `maxHeight` cap can never diverge.

157 tests pass.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:56:48 +00:00
qqqys
b23f888d73
[codex] add proactive channel loop tools (#6287)
* feat(channel): add proactive loop tools

* fix(channels): stabilize proactive loop routing

* fix(channels): gate loop tools in shared sessions

* fix(channels): tighten channel loop tool routing

* fix(channels): close loop tool review blockers

* fix(dingtalk): preserve markdown tables

* fix(dingtalk): use app token for reactions

* fix(channels): scope loop tools to active caller

* fix(channels): preserve group session metadata

* fix(channels): normalize loop targets

* test(cli): cover settings cron disable path

* fix(channels): address dingtalk review suggestions

* fix(dingtalk): restore table normalization

* fix(channels): mark loop tool failures

* fix(channels): tighten loop mcp protocol handling

* test(channels): cover loop tool guard paths

* fix(channels): await loop mcp registration

* test(channels): preserve base proactive target default

* refactor(channels): clarify loop target promotion

* fix(channels): harden loop recurring input

* fix(channels): ack loop mcp notifications

* fix(channels): preserve legacy loop targets

* test(channels): cover channel loop wiring paths

* fix(channels): retry skipped loop mcp registration

* fix(channels): keep promoted loop targets visible

* fix(channels): harden loop mcp input logging
2026-07-05 15:49:48 +00:00
jinye
11c874dfba
feat(cli): Add large pipe frame measurement (#6335)
* feat(cli): Add large pipe frame measurement

Add an internal NDJSON message observer for ACP pipe frames and wire qwen serve to record low-sensitive attribution for large frames without changing transport behavior.

Document the measurement-only design and cover observer hooks, large-frame classification, rate limiting, and wiring behavior in targeted tests.

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

* codex: address PR review feedback (#6335)

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

* codex: fix CI failure on PR #6335

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

* codex: address PR review feedback (#6335)

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

* codex: address PR review feedback (#6335)

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

* codex: address PR review feedback (#6335)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.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>
2026-07-05 15:46:53 +00:00
han
82fb6a4f0d
fix(cli): allow queued input during compression (#6336) 2026-07-05 13:50:34 +00:00
Heyang Wang
3bf0fa0af0
Feat: LSP Server support hot reload (#5953)
* feat(core): Add LSP server config hot-reload support

- Implement reconcileServerConfigs to diff desired vs current LSP configs and apply minimal add/remove/restart operations with a serialized reconcile queue
- Add configHash utility to detect config changes via stable hashing
- Add lspConfigWatcher in CLI to watch .lsp.json and trigger reconciliation on file changes
- Extend LspServerManager with per-server config hash tracking and detailed debug logging
- Add design docs for LSP runtime reinitialization and hot-reload overview
- Include comprehensive unit tests for all new modules

* refactor(cli): Extract registerLspHotReload from main function

Move the LSP config file watcher setup and reconciliation logic into a dedicated module-private function registerLspHotReload, reducing the size and nesting depth of the main startup flow. Added a JSDoc summarizing responsibilities, early-return conditions, and the AppEvent.LspStatusChanged side effect.

* fix(lsp): release server resources during reload

* fix(lsp): address hot reload review feedback

* fix(lsp): harden hot reload reconciliation

* docs(lsp): update hot reload design notes

* fix(lsp): harden hot reload retry semantics

* fix(lsp): harden hot reload lifecycle

* fix(lsp): harden hot reload lifecycle

* fix(lsp): isolate hot reload recovery paths

* fix(lsp): align command probes and replay tracking

* fix(lsp): prevent crash restarts during shutdown

* fix(lsp): preserve reload state across failures

* fix(lsp): cancel reloads during shutdown

* fix(lsp): handle socket startup races

* fix(lsp): harden command probe env and socket startup

* fix(lsp): report skipped reload and restart states

* fix(lsp): harden hot reload lifecycle cleanup

* chore: add one comment for `Object.create(null)`

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-05 13:50:00 +00:00
易良
c7170b5e04
feat(cli): support multi-folder workspaces in file system boundary checks (#6278)
* feat(cli): support multi-folder workspaces in file system boundary checks

The CLI daemon's file system boundary enforcement only recognized a single
`boundWorkspace` root, so when a user opened multiple folders in a VSCode
workspace, files in every folder except the terminal's cwd were rejected
with "path escapes workspace."

Change `resolveWithinWorkspace` and `WorkspaceFileSystem` to accept an
array of workspace roots. Each root gets independent ignore rules and
boundary checking. The VSCode extension now passes
`QWEN_CODE_IDE_WORKSPACE_PATH` (all folders, delimiter-separated) as a
terminal env var so the daemon can discover additional roots at boot.

Nested roots are rejected at registration time to avoid ambiguity.
Single-folder workspaces pass a one-element array, so behavior is
unchanged.

Closes #1766

* fix(cli): resolve multi-root workspace review feedback

* fix(cli): tolerate invalid IDE workspace env roots

* fix(cli): reuse workspace containment helper

* fix(cli): honor IDE workspaces in serve app factory

* fix(cli): resolve workspace boundary feedback

* fix(cli): resolve multi-root workspace review comments

* fix(cli): constrain secondary workspace roots

* fix(cli): preserve dangling symlink write guard

* fix(cli): resolve multi-workspace review comments

* fix(cli): resolve workspace root review comments

* refactor(cli): trim multi-workspace serve changes

* fix(cli): resolve workspace env review comments

* fix(cli): use literal IDE workspace env lookup

* fix(cli): resolve workspace glob review comments

* fix(cli): tighten multi-root glob errors

* test(cli): cover multi-root workspace review gaps

* fix(cli): handle multi-root workspace edge cases

* fix(cli): preserve trusted nested workspace roots

* fix(cli): preserve read existence check for aliases

* fix(cli): share daemon write locks across serve paths

* fix(cli): harden multi-root workspace env parsing
2026-07-05 13:10:33 +00:00
易良
0d1d24052c
feat(core): model fallback chain — auto-switch to backup models on overload (#6273)
* feat(core): implement model fallback chain for capacity/availability errors

When the primary model hits 429/503/529 and same-model retries are
exhausted, automatically try configured fallback models in sequence
before giving up.

Config layer:
- Add `modelFallbacks` setting (comma-separated, max 3)
- Add `--fallback-model` CLI flag (repeatable or comma-separated)
- Normalize, deduplicate, cap at 3 in core Config

Core layer:
- Add `isFallbackEligible()` helper to retryErrorClassification
- Update HTTP 529 diagnosis to `fallback-eligible` (was `retryable`)
- Add fallback chain logic in geminiChat `sendMessageStream`:
  each fallback gets its own retry budget via `makeApiCallWithFallbackGenerator`
- Resolve fallback models cross-provider via `resolveForModel({ failClosed: true })`
- Skip fallback when `QWEN_CODE_UNATTENDED_RETRY` persistent mode is active
- Non-fallback-eligible errors (auth/client) stop the chain immediately

UI layer:
- TUI notification: "Model X unavailable, falling back to Y"
- Daemon SSE adapter: model_fallback event handling
- Non-interactive: system message with model_fallback subtype

Closes #6116

* fix(core): retry fallback stream failures

* refactor(core): address review findings for model fallback chain

- Delete unused makeApiCallWithFallbackGenerator (merged into
  makeApiCallAndProcessStream via overrides parameter)
- Import HeartbeatInfo type for persistent-mode heartbeat callback
- Use popPendingPartialAssistantTurn (removes partial turn from
  history) instead of clearPendingPartialState before model switch
- Add AbortError early-return in both fallback catch blocks to
  propagate user cancellation immediately
- Reorder guard: check fallbackModels.length > 0 before calling
  classifyRetryError to avoid unnecessary work
- Rename errorCode → statusCode in ModelFallbackInfo and all
  consumers for consistency with RetryErrorClassification
- Add TODO for retry loop duplication in makeFallbackStreamWithRetries

* fix(core): clean fallback partial turns

* fix(core): address model fallback review comments

* fix(core): tighten model fallback handling

* fix(core): align fallback recovery semantics

* fix(core): tighten fallback failure handling

* fix(core): reduce fallback stream scope

* fix(core): resolve fallback review comments

* fix(core): review polish for model fallback chain

- Add .filter(Boolean) to CLI --fallback-model coerce to strip empty
  strings from leading/trailing commas
- Clarify isFallbackEligible() JSDoc: reason-based check intentionally
  covers 'retryable' diagnosis (429/503) not just literal
  'fallback-eligible' diagnosis (529)
- Preserve original capacity error as cause when all fallbacks exhaust
  (previously the cause was the last resolution/fallback error)
- Add test: provider-level rate-limit (numeric code 1302, no HTTP
  status) is correctly identified as fallback-eligible

* fix(core): resolve model fallback review comments

* fix(core): dedupe unresolved fallback aliases

* fix(core): trim model fallback scope

* fix(core): stop fallback after emitted output

* fix(core): clear fallback tool call state

* fix(core): skip unresolved fallback aliases

* fix(core): address fallback review feedback
2026-07-05 12:29:10 +00:00
jinye
fe816f625f
feat(cli): Surface daemon prompt queue status (#6325)
* feat(cli): surface daemon prompt queue status

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

* codex: address PR review feedback (#6325)

* codex: address PR review feedback (#6325)

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

* codex: address PR review feedback (#6325)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-05 08:25:42 +00:00
jinye
7a528d078a
feat(daemon): Add session organization (#6305)
* feat(daemon): add session organization

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

* fix(daemon): address session organization review feedback

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

* test(daemon): cover session organization review cases

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

* codex: address PR review feedback (#6305)

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

* codex: address PR review feedback (#6305)

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

* codex: address PR review feedback (#6305)

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

* codex: address PR review feedback (#6305)

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

* codex: address PR review feedback (#6305)

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

* codex: address PR review feedback (#6305)

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

* fix(web-shell): Address session organization review feedback

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

* fix(core): Harden session organization review edge cases

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

* fix(core): Address session organization review feedback

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-05 07:52:56 +00:00
Shaojin Wen
52a190b5c6
feat(web-shell): time-series metrics charts on Daemon Status (#6307)
* feat(web-shell): time-series metrics charts on Daemon Status

Add seven bottleneck-analysis line charts (concurrency, requests, API
latency, prompt latency, event-loop lag, memory, token burn) to the
Daemon Status dashboard, backed by a new server-side metrics ring.

The status endpoint is a point-in-time snapshot, so line charts need a
time series. A bounded ring buffer in the daemon (daemon-metrics-ring.ts)
seals one bucket every 5s (~15min retained) from three seams:
- HTTP request rate/latency via the telemetry middleware
- prompt queue-wait/duration via the bridge telemetry hooks
- per-round token usage sniffed at the bridge session/update fan-in
  (new DaemonBridgeTelemetryMetrics.tokenUsage hook)
plus memory / active sessions+prompts / a window-scoped event-loop lag
p99 read as gauges at seal time.

The series rides the existing GET /daemon/status contract
(runtime.metrics.series), threaded through the SDK types (JSON passthrough)
to a dependency-free inline-SVG chart component in web-shell -- no charting
library added to the CSP-strict serve --web bundle.

Tests: metrics-ring math, token-usage sniffing on the real sessionUpdate
path, and SVG chart rendering. Verified end-to-end against a live daemon
(GLM-5.2): requests/latency/memory/event-loop, real token burn and prompt
duration, with the concurrency gauge tracking active prompts.

* feat(web-shell): tabs, chart tooltips, and fullscreen for Daemon Status

Split the now chart-heavy Daemon Status dashboard into Overview / Metrics /
Diagnostics tabs (status badge, refresh, and issues stay global) so
monitoring, configuration, and troubleshooting each get their own space
instead of one long 70vh scroll.

Add an interactive hover cursor to the charts: a vertical time line, a dot on
each series, and a tooltip reading the bucket time plus every series' value at
that point -- previously only the latest value and peak were legible, from the
legend.

Add an opt-in fullscreen toggle to DialogShell (via allowFullscreen, wired for
Daemon Status) that expands the panel to near the full viewport; scrolling is
consolidated into the shell body so the content actually grows with it.

Tests: tab switching + diagnostics-behind-tab, SVG tooltip rendering, and the
DialogShell fullscreen toggle. Verified end-to-end against a live daemon
(GLM-5.2) with real request / token / prompt data.

* feat(web-shell): add CPU, LLM-latency, queue-depth, IPC & connection metrics

Extend the Daemon Status metrics ring with more bottleneck-analysis
dimensions, filling the two biggest gaps — resource cost had only memory
(no CPU), and latency had only client->daemon HTTP (not daemon->model):

- CPU %: process.cpuUsage() delta, core-normalized (memoryPressureMonitor
  formula, clamped 0-100), sampled alongside memory.
- LLM API latency p50/p95: the token frame's _meta.durationMs (the
  daemon->model round-trip), separating 'model is slow' from 'we are slow'.
- Prompt queue depth: a new bridge.pendingPromptTotal aggregate, folded into
  the concurrency chart beside active tasks.
- IPC pipe throughput: daemon<->ACP-child stdio bytes (already measured; now
  windowed via metricsRing.recordPipe).
- Connection counts (SSE/WS/ACP) and rate-limit rejections, read lazily in the
  sampler from the ACP handle registry and the rate limiter.

The tokenUsage telemetry hook is widened to carry durationMs. Verified
end-to-end against a live daemon (GLM-5.2): LLM p95 28.6s vs HTTP p95 324ms,
queue depth 1, IPC peak 0.3MB, SSE gauge 1 on a live stream.

* feat(web-shell): add ACP child process CPU/memory (self-reported over ACP)

The daemon's own CPU/memory only tell half the story — the real LLM/tool work
runs in the spawned 'qwen --acp' child, which is where the resource cost lives.
Surface it: the child self-reports its rss + cpuPercent to the daemon over a new
read-only ACP extMethod (qwen/status/workspace/resource); the bridge caches the
latest sample on the live channel, and the metrics sampler reads it
synchronously each tick (firing an async refresh for the next, off the hot path).

The child computes cpuPercent as a process.cpuUsage() delta between polls (no
dependency on MemoryPressureMonitor's tool-gated sampling), core-normalized and
clamped. Rendered as a second line on the CPU and Memory charts (daemon vs
child, side by side).

Verified end-to-end (GLM-5.2): child RSS ~300MB vs daemon RSS ~225MB, child CPU
tracking above the daemon's -- the child is the resource hog, now visible.

* test(web-shell): cover Metrics tab, chart rendering, and the recordRequest seam

Address review — the metrics dashboard's rendering and its HTTP data seam had
no tests:
- DaemonStatusDialog: switching to the Metrics tab renders the charts from the
  series (one SvgLineChart per card) and hides the Overview panel; an empty
  series shows the collecting-metrics placeholder.
- daemonTelemetryMiddleware: recordRequest fires once with (durationMs,
  statusCode) on a matched route (real status code; once across finish/close),
  is not called for unmatched routes, and is a silent no-op when omitted.

* fix(web-shell): enlarge Daemon Status charts in fullscreen

Fullscreen widened the panel but the charts stayed small — the grid just packed
in more 280px cards at a fixed 52px SVG height, so the extra viewport bought
more small charts, not bigger ones. Now the DialogShell body carries a
`data-dialog-fullscreen` marker; the chart grid switches to wider cards (min
480px → fewer columns) and the SVG grows to 120px, so fullscreen actually
enlarges the plots. Verified: 2 wide columns at 120px vs 3-4 columns at 52px.

* fix(web-shell): resolve chart colors in portal, guard child-resource polling

Address review (real-user + ci-bot):
- [Critical] Chart colors (--primary, --agent-blue-400) resolved to nothing in
  the DialogShell portal (createPortal to document.body escapes the app root that
  defines them), so ~half the chart lines rendered stroke:none. Add both vars to
  DialogShell's own theme scope. Verified: 25/25 path strokes colored (was 5 none).
- [Critical] refreshChildResource had no in-flight guard; requestWorkspaceStatus
  waits up to 10s (> the 5s cadence), so a degraded child accumulated concurrent
  polls. Add a single-flight guard.
- [Critical] getChildResourceSnapshot returned last-good rss/cpu forever; add a
  30s staleness window so a stuck child reads 0 instead of looking healthy.
- Exclude GET /daemon/status (the dashboard's own poll) from the metrics-ring
  request rate, so the Requests chart doesn't count itself.
- Fix cpuPercent JSDoc (percent of total capacity across cores, clamped [0,100])
  in the ring + SDK mirror; add a keep-in-sync cross-reference on the mirror.

Tests: recordRequest excludes /daemon/status; buildDaemonStatusResponse embeds
runtime.metrics.series when provided and omits it otherwise.

* fix(web-shell): address Daemon Status charts review feedback

Correctness fixes surfaced in review:

- bridgeClient: guard token accounting on a live `entry`. On the
  `session/load` path HistoryReplayer re-emits saved usage as live
  session/update frames before the session entry is registered, which
  otherwise dumped a session's historical token total into the current
  metrics window as a phantom burn spike with no model call.
- run-qwen-serve metrics sampler: wrap each tick in try/catch/finally so a
  throwing getter can't crash the daemon; reset the event-loop-lag histogram
  in finally so a thrown tick can't permanently discard it; skip the CPU
  delta (and leave the baseline untouched) when process.cpuUsage() throws;
  seed the rate-reject baseline on the first tick instead of reporting the
  whole since-start backlog as one spike.
- acpAgent workspaceResource: advance the child-CPU baseline only on a
  successful read, avoiding a ~2x phantom spike on the poll after a failure.
- bridge.pendingPromptTotal: count only queued prompts (state === 'queued'),
  not the running one, so the "Queued" chart reflects real backpressure and
  no longer shadows the "Active tasks" line.
- Make the new Daemon Status bridge hooks optional in AcpSessionBridge and
  optional-chain them in the sampler, so a bridge injected via
  RunQwenServeDeps.bridge that predates them degrades gracefully.

Robustness / UX:

- daemon-metrics-ring sanitizes non-finite gauges to 0 so a bad reading
  never serializes as JSON null and gaps the chart.
- child-resource refresh logs failures at debug for observability.
- formatBytes drops to KB/B for sub-MB pipe traffic (was "0.0 MB").
- SvgLineChart peak label is now i18n'd (daemon.charts.peak).
- Daemon Status tabs get the full WAI-ARIA tabs pattern: aria-controls,
  role=tabpanel, and Arrow/Home/End keyboard navigation with roving tabindex.

Tests: replay token guard (no live entry), pipe/gauge/sample-cap defenses,
large-value legend formatting, and tab keyboard navigation.

* fix(web-shell): keep Daemon Status fullscreen + tooltip correct in dialog portal

Two DialogShell-portal theme-scope issues surfaced by a follow-up review:

- Fullscreen was clamped back to 80vh on narrow screens: the
  `@media (max-width: 560px)` `.panel` rule has equal specificity and later
  source order than the base `.panelFullscreen`, so it won. Add a media-scoped
  `.panelFullscreen` override so fullscreen actually expands on mobile.
- SvgLineChart tooltip background used `var(--popover, var(--card))`, neither of
  which the portal theme scope defines, so the declaration dropped and the
  tooltip rendered transparent over the chart. Fall back to `--background`
  (which the dialog scope does define).

* fix(web-shell): flip chart tooltip below cursor near scroll-container top

The Daemon Status charts live inside DialogShell's overflow-y:auto body, so the
topmost chart's upward tooltip (bottom: calc(100% + 4px)) clipped against the
scroll container's top edge, truncating the time header / first series row on
hover. SvgLineChart now resolves its nearest scroll parent and flips the tooltip
below the cursor when the plot sits within ~one tooltip-height of that clip
boundary.

* fix(daemon-status): harden child-resource CPU/memory + sampler lag on failure

Follow-up review fixes:
- acpAgent: prevChildCpu inits to null (not {0,0}) and the workspaceResource
  handler gates the delta on a live prevCpu baseline, so an init-time
  cpuUsage() failure no longer manufactures a phantom spike on the first poll
  — mirrors the daemon sampler's safeCpuUsage null-on-failure contract.
- acpAgent: guard process.memoryUsage() too, reporting 0 rss on failure while
  keeping the already-computed cpuPercent instead of throwing the handler.
- bridge: require Number.isFinite() (typeof NaN === 'number' is true) and
  clamp cpuPercent to [0,100] when caching the child's self-report.
- run-qwen-serve sampler: gate the 5s child-resource refresh on an active
  SSE/WS client (idle staleness already reads 0), and hoist the event-loop
  lag read before the try so a thrown tick charts the real accumulated lag
  instead of a misleading 0.

* fix(daemon-status): protect artifact path from metrics callback + share CPU delta

Follow-up review fixes:
- bridgeClient: wrap recordLiveTokenUsage in try/catch so a throwing injected
  onTokenUsage callback can't skip the critical artifact processing after it —
  metrics are optional, artifacts are not.
- Extract computeCpuPercent() into daemon-metrics-ring and share it between the
  daemon self-sampler and the ACP child's workspaceResource handler, removing
  the duplicated delta/normalize/clamp math and giving it direct unit coverage
  (null sample, non-positive window, normalization, phantom-spike + negative
  clamps).
- Add a single-flight test for bridge.refreshChildResource (two rapid calls
  collapse to one in-flight RPC).
2026-07-05 03:06:09 +00:00
VectorPeak
4675274ee4
fix(cli): preserve partial remote input JSONL records (#6317)
* fix(cli): preserve partial remote input JSONL records

* test(cli): cover mixed remote input partial record
2026-07-04 22:19:58 +00:00
jinye
2b732f5fc6
fix(serve): resolve false auth warning in preflight when API key is set via settings (#6296)
* fix(serve): resolve false auth warning in preflight when API key is set via settings

The preflight auth check only looked at process.env for well-known env
var keys (e.g. OPENAI_API_KEY), missing credentials provided through
settings.security.auth.apiKey or provider-specific envKey in settings.env.
This caused a spurious "None of the env vars [OPENAI_API_KEY] is set"
warning on the daemon status dashboard even when the key was properly
configured.

Fall back to the already-resolved generation config apiKey (which folds
all credential sources: env vars, settings.security.auth.apiKey, provider
envKey, and CLI flags) when the env-var check fails.  Also fix the
apiKeyVars.length === 0 branch to report 'ok' instead of 'unknown' when
the resolved key is present.

* test(serve): add auth preflight tests for settings-based API key fallback

Cover the generationConfig.apiKey fallback path added in the previous
commit: one test asserts status 'ok' when the key is present in
generationConfig but absent from process.env, and one asserts status
'warning' when both sources are empty.

* test(serve): add auth preflight tests for non-env-keyed auth branch

Cover the apiKeyVars.length === 0 branch in buildAuthPreflightCell:
- ok when generationConfig.apiKey is present (non-env-keyed provider)
- unknown when no apiKey anywhere (defers to session boot)
- Fix existing preflight test to include getModelsConfig mock
  so auth cell path doesn't silently throw TypeError

* fix(serve): exclude qwen-oauth placeholder from auth preflight fallback

Skip the generationConfig.apiKey fallback for qwen-oauth auth type,
which unconditionally sets apiKey to 'QWEN_OAUTH_DYNAMIC_TOKEN'
placeholder. Without this, preflight falsely reports ok for qwen-oauth
when no OAuth flow has been completed.

Also use 'custom-provider' instead of 'qwen' in non-env-keyed auth
tests to avoid confusion with real AuthType enum values.

* fix(serve): use AUTH_PREFLIGHT_WAIVED_AUTH_TYPES set instead of hardcoded qwen-oauth check

Replace hardcoded AuthType.QWEN_OAUTH guard with the centralized
AUTH_PREFLIGHT_WAIVED_AUTH_TYPES set for the generationConfig fallback.
Also add getModelsConfig mock to the second "6 cells" regression test.

* test(serve): add getGenerationConfig to base mock to prevent silent TypeError

The base mockConfig's getModelsConfig return value was missing
getGenerationConfig, causing a silent TypeError when tests inheriting
this mock exercise the auth preflight path added in this PR.
2026-07-04 21:16:44 +00:00
jinye
e23c8e8459
feat(acp): Batch session load replay (#6309)
* feat(acp): batch session load replay

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

* codex: address PR review feedback (#6309)

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

* codex: address PR review feedback (#6309)

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

* codex: address PR review feedback (#6309)

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

* fix(cli): replay all initial snapshot events

Initial response-mode replay was filtering the bridge snapshot down to session_update frames before subscribing from the snapshot high-water mark. That could skip other snapshot-backed events permanently.

Extend the ACP transport regression test so initial replay includes a non-session_update bridge event.

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

* fix(acp): harden bulk replay restore cleanup

Clean up response-mode restore entries if replay seeding fails so retries do not attach to a closed zombie bus.

Also normalize bulk replay timestamps before returning the private envelope and preserve partial replay usage accounting.

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

* fix(cli): expose partial ACP load replay status

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-04 21:02:31 +00:00
jinye
90e1e3d47e
perf(cli): cache LoadedSettings per workspace with stat-based invalidation (#6310)
* perf(cli): cache LoadedSettings per workspace with stat-based invalidation

The ACP child under `qwen serve` is long-lived and re-runs a full
loadSettings() on the shared event loop for every session/new,
session/load and session/resume: four settings files read, parsed,
migration-checked and structuredClone'd, the .env tree walked, home
.env re-read, ${VAR} references re-resolved, and all scopes merged.
Same-cwd repeat sessions (the typical serve workload) pay full price
every time.

Add a process-level cache keyed by resolved workspace dir (LRU 64).
Freshness is checked deterministically on every access via a
fingerprint of every filesystem input: stat signatures
(mtimeMs:size:ino) of the four settings files, the re-discovered .env
file list with signatures, IDE trust, realpath(cwd) and
realpath(homedir). Any change -> full reload; fingerprint errors fail
open to a reload; loadSettings() throws propagate uncached.

Only the three hot ACP session handlers switch to loadSettingsCached();
all other loadSettings() callers (ext-methods write paths etc.) keep
their direct read semantics.

Known accepted differences (documented in the module doc): direct
process.env mutation without any file change does not re-bake ${VAR}
references on a hit; a .env edit racing the miss-path load itself is
the usual mtime-cache TOCTOU microsecond window; an in-place overwrite
preserving mtime+size+ino is invisible (self-writes go through
temp+rename, which changes the inode).

Part of the qwen serve multi-session performance work (#6263).

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

* test(cli): add IDE trust flip invalidation test for settings cache

Covers the ideTrust fingerprint component, which is the only trust input
that can change within a live process (trustedFolders.json is a permanent
singleton, folder-trust toggles live in the settings files). Addresses a
Copilot review suggestion to guard against stale-cache trust regressions.

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

* refactor(cli): harden settings cache observability and fail-open coverage

Addresses three review suggestions:
- Warn comment on settingsFileSigs that the 4-scope path list must stay in
  sync with loadSettings() (unlike envFileSigs, it is enumerated separately).
- Add a createDebugLogger('SETTINGS_CACHE'), matching the SETTINGS /
  SETTINGS_WATCHER / CONFIG convention in neighbouring config modules, and
  log hit/miss, each fail-open catch (with the swallowed error), and eviction.
- Add a fault-injection test asserting the cache reloads (never throws) when
  the fingerprint check fails, then recovers once the fault clears.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
2026-07-04 15:29:25 +00:00
易良
aa4bccfc47
feat(acp): advertise vision-bridge image capability in initialize response (#6269)
* feat(acp): advertise vision-bridge image capability in initialize response

Adds `_meta.imageCapability` to both stdio and HTTP ACP initialize
responses so external hosts like sudowork can feature-detect native
image handling instead of maintaining a hardcoded allowlist.

Resolves #6086

* test(acp): cover image capability advertisement

* docs(acp): clarify image capability threshold

* fix(acp): align image capability contract
2026-07-04 14:08:45 +00:00
MikeWang0316tw
9b3aa524a1
fix(cli): stream long responses into scrollback to stop scroll-to-top lock (#6170)
* fix(cli): stream long responses into scrollback to stop scroll-to-top lock

## Problem

In non-VP (default) mode, scrolling up while the model streams a long reply —
especially one containing a markdown table — jumps the viewport to the very top
and locks it there until the response finishes (issue #5941).

Root cause: when the live (below-`<Static>`) frame grows taller than the
terminal, ink can no longer do its incremental cursor-up redraw and falls back
to clearing + repainting the whole frame from the top on every token. A markdown
table renders ~2 rows per data row (TableRenderer draws a separator between
every row), so #6081's source-line budget under-counted the rendered height and
the frame still overflowed for tables / wide CJK text.

## Fix

Incremental scrollback streaming + a rendered-height safety net:

- useGeminiStream: commit finished chunks of the streaming reply into `<Static>`
  (scrollback) so the pending live item stays short. The commit is
  rendered-height-aware (tables count double, wide/CJK lines wrap) and bounded by
  the live content-area height (threaded via `availableTerminalHeightRef`), with
  a reserve so it fires before the render-side clip. It commits in a `while` loop
  and splits only at `findLastSafeSplitPoint` boundaries (never inside a fenced
  code block).

- MarkdownDisplay: a rendered-height-aware slice of the pending preview as a last
  line of defence — it guarantees the live frame never exceeds the viewport
  regardless of how the stream is chunked (tables charged at ~2x; non-table lines
  charged their wrapped height). A completed table renders in full; a table still
  being written renders live and is clamped by TableRenderer's new `maxHeight`.

Result: long replies (and tables) flow smoothly into scrollback, tables draw
live, and the viewport never locks to the top.

Refs #5941, #6081

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

* fix(cli): address review — share rendered-height estimator + guard edges

Follow-up to review feedback on the incremental-scrollback streaming fix:

- Extract a shared rendered-height estimator (`pendingRenderedHeight.ts`:
  `fitPendingSlice` / `estimateWrappedRows` / `isTableStart`) and use it from
  BOTH the useGeminiStream commit and the MarkdownDisplay safety-net slice, so
  the two agree on table (block: 2*dataRows + chrome) and wrap accounting
  instead of diverging.
- useGeminiStream: use a conservative content-area fallback (terminalHeight
  minus a composer reserve) when `availableTerminalHeightRef` is not yet
  populated, so a short terminal never commits with an over-large budget.
- MarkdownDisplay: allow the pending slice to keep 0 lines — a single very wide
  / CJK line that wraps past the budget now renders only the "generating more"
  cue instead of one oversized row that would bypass the height bound.
- Add missing `useCallback` deps (terminalWidth / terminalHeight /
  availableTerminalHeightRef) — fixes the CI ESLint failure.
- Tests: unit tests for the shared estimator (table detection, zero/negative
  width, CJK wrapping, cut-before / clamp / keptLines=0 boundaries) and for
  TableRenderer's `maxHeight` clamp (fit, clip+cue, vertical fallback, undefined
  passthrough).

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

* fix(cli): rename shared util to kebab-case to satisfy check-file lint

The new pendingRenderedHeight.{ts,test.ts} tripped the check-file/filename-naming-convention (KEBAB_CASE) ESLint rule on new files in packages/cli/src. Rename to pending-rendered-height.{ts,test.ts} and update imports.

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

* fix(cli): point imports at renamed pending-rendered-height module

The previous rename commit landed the file rename but not the importer edits
(a stale pathspec aborted the git add), leaving MarkdownDisplay, useGeminiStream
and the test importing the old ./pendingRenderedHeight.js path — a module-not-
found in CI. Update the imports to the kebab-case path.

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

* fix(cli): drop unused eslint-disable directive on while(true)

reportUnusedDisableDirectives + --max-warnings 0 flags the no-constant-condition
disable as an unused directive (the rule doesn't flag while(true) here). Remove it.

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

* fix(cli): unify table parsing in shared module + cover commit edge cases

Address the second review pass:

- Move splitMarkdownTableRow and the table regexes (TABLE_ROW_RE /
  TABLE_SEPARATOR_RE) into pending-rendered-height.ts as the single source of
  truth; MarkdownDisplay now imports them instead of keeping duplicate copies.
- isTableStart now also checks the separator's column count matches the header
  (mirroring the renderer's table detection) so the height estimator and the
  renderer agree on what is a table.
- Tests: shared-module coverage for splitMarkdownTableRow and the isTableStart
  column-count check; tighten the incremental-commit assertion (budget-relative,
  requires multiple commits); add coverage for the splitPoint<=0 loop-break
  guard and for the populated-availableTerminalHeightRef production path.

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

* fix(cli): commit streaming chunks only at block boundaries (no split tables)

The incremental scrollback commit could cut a markdown table mid-way (e.g. when
the tail row was still streaming): the committed chunk kept the header+rows and
rendered as a table, but the continuation started with headerless `| ... |`
rows that render as raw text (visible orphaned rows below a table).

Only commit at a blank-line block boundary. A table (or list / code block) has
no internal blank line, so it is never split into a headerless continuation; a
still-streaming table stays pending — bounded in view by MarkdownDisplay's
clamp — until it is complete, then commits whole.

Tests: the oversized-commit test now uses blank-line-separated content and
asserts every committed chunk ends at a block boundary; add a regression test
that a streaming table taller than the budget is never committed as a headerless
fragment (its header stays with its rows in the pending item).

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

* fix(cli): don't treat code-fence content as a table in the height estimator

fitPendingSlice called isTableStart on every line regardless of fenced-code-
block state, so table-like lines inside a ``` block were charged as a table
(2*dataRows + chrome) while MarkdownDisplay renders them as code (one row each).
Track the code fence and charge fenced lines individually. Share CODE_FENCE_RE
from the module (MarkdownDisplay now imports it too) to keep a single source of
truth. Adds a unit test.

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

* fix(cli): account for vertical-format table height + tilde code fences

Address the third review pass:

- (Critical) fitPendingSlice charged the horizontal table height (2*dataRows+5)
  only, but TableRenderer falls back to the vertical key-value format on a narrow
  terminal / when cells wrap tall, which is much taller for 3+ column tables.
  Charge the larger of the horizontal and vertical estimates (dataRows*colCount +
  separators + marginY), still capped by the clamp — under-charging could let a
  vertical-format table overflow the viewport and re-introduce the scroll lock.
- findLastSafeSplitPoint only recognised triple-backtick fences while the
  estimator's CODE_FENCE_RE also matches ~~~; a ~~~ block with an internal blank
  line could be split mid-block. isIndexInsideCodeBlock / findEnclosingCodeBlockStart
  now track both fence types (matching by fence character).
- Tests: vertical-format table cost, ~~~ fence tracking, inline math and
  multi-backtick spans in splitMarkdownTableRow, and a ~~~ split-safety case.

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

* fix(cli): only charge vertical-format table height on a narrow terminal

The prior fix charged the max of the horizontal and vertical table estimates
unconditionally, which over-estimated a table's height on a wide terminal (where
it actually renders in the shorter horizontal format) and clipped small tables
early with a premature "generating more". Mirror TableRenderer's width-based
vertical decision (contentWidth < max(24, 6*colCount + 5)) and charge the format
it will actually render: horizontal when the terminal is wide enough, vertical
only when narrow — so a narrow-terminal vertical render still can't overflow and
lock, but a small table on a wide terminal is no longer clipped prematurely.

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

* fix(cli): fix phantom code fences + read commit width from a live ref

Address the fourth review pass:

- (Critical) isIndexInsideCodeBlock / findEnclosingCodeBlockStart used
  indexOf('```', ...) which matches only the first three characters of a longer
  fence run, so a 4+ backtick/tilde fence was miscounted as two delimiters
  (phantom close-then-reopen). That could mark a blank line inside a code block
  as outside it, letting findLastSafeSplitPoint split mid-block and commit an
  unclosed code block to scrollback. findNextFence now returns the full run
  length, callers advance past the whole run, and a fence only closes a block
  opened with the same character and a run at least as long.
- The commit loop read height live from availableTerminalHeightRef but width
  from the render-time closure, so a mid-stream resize handled the two
  inconsistently. Pair a terminalWidthRef with the height ref and read both live.

Tests: a 6-backtick fenced block is not split at its internal blank line.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 11:40:00 +00:00
jinye
59e771cef6
feat(daemon): Add session export endpoint (#6297)
* feat(daemon): add session export endpoint

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

* codex: address PR review feedback (#6297)

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

* codex: fix PR integration capability baseline (#6297)

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

* codex: address export tool call id review (#6297)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.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>
2026-07-04 09:33:44 +00:00
易良
0e684a3444
fix(auth): prevent persistent 401 after API key change (#6284)
* fix(auth): prevent persistent 401 after API key change

Empty-string environment variables (e.g. `DASHSCOPE_API_KEY=` from Docker
env files or shell profiles) blocked settings.env from loading because
Object.hasOwn returned true. Treat empty-string as effectively unset so
settings.env can fill the gap.

Also warn users at /auth time when a shell or .env variable will shadow
the newly saved key on restart, and add debug logging when
applyResolvedModelDefaults finds no API key for a model.

Closes #6283
Refs #5979, #6129, #3417

* fix(auth): tighten env precedence handling

* fix(cli): preserve settings env on reload

* fix(auth): surface env shadowing warning
2026-07-04 09:20:02 +00:00
jinye
2a6a9514e3
fix(acp): pass per-session settings explicitly instead of racing on this.settings (#6292)
* fix(acp): pass per-session settings explicitly instead of racing on this.settings

ACP session handlers run concurrently, and session creation awaits config
load, MCP discovery, and auth refresh between "load settings" and
"construct Session". Two reads of the shared mutable `this.settings` race
across that window:

- `createAndStoreSession` constructed `Session` with whatever instance the
  most recent handler loaded, so a slow session creation could bind another
  workspace's LoadedSettings — which Session persists model changes
  through, writing into the wrong workspace's settings.json.
- `loadSession`/`unstable_resumeSession` ran their existence check under
  the previous handler's `advanced.runtimeOutputDir`, producing spurious
  "session not found" errors across workspaces.

Each handler now loads its workspace's settings once at the top and
threads that instance through `newSessionConfig` and
`createAndStoreSession`; `this.settings` remains a "latest loaded" cache
for agent-level readers.

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

* fix(acp): adopt settings cache only after session existence is confirmed

A failed loadSession/unstable_resumeSession probe (stale id, different
cwd) must not repoint the agent-level `this.settings` cache at the failed
request's workspace — readers like `authenticate` and provider ext-methods
would otherwise pick it up. The existence check itself only needs the
local per-request instance, so move the cache adoption after the 404
throw, restoring the pre-fix cache timing exactly.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
2026-07-04 03:42:22 +00:00
tanzhenxin
cdf83d8bd0
fix(core): give Stop-hook continuations a fresh per-turn tool-call budget; make the cap configurable (#6238)
* fix(core): give Stop-hook continuations a fresh per-turn tool-call budget; make the cap configurable

A blocking Stop-hook continuation (e.g. a /goal iteration) feeds a fresh
user-role prompt to the model — a new logical turn — but the loop detector
never reset, so an entire goal chain billed one per-turn tool-call budget
and healthy long-running goals halted with turn_tool_call_cap. The ACP
daemon path already used per-continuation budgets; core now matches.

- Reset loop detection at each blocking Stop-hook continuation
- Add model.maxToolCallsPerTurn setting (default 100; <= 0 disables),
  resolved once in Config (<= 0 maps to Infinity)
- Honor the in-session 'Disable loop detection for this session' choice
  in the per-turn cap, as the dialog always claimed
- Point the headless halt message at the setting; update dialog/docs

* test(cli): add DEFAULT_MAX_TOOL_CALLS_PER_TURN to core module mocks

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-04 02:36:59 +00:00
Edenman
ad7e23f99f
feat(web-shell): add MCP mentions and iconized @ references (#6279)
* feat(web-shell): add MCP server mentions in @ completion

* fix(web-shell): polish @ completion groups

* feat(web-shell): add icons for @ references

* fix(web-shell): refine @ completion behavior

* fix(cli): show MCP mentions for bare @

---------

Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com>
2026-07-04 02:13:42 +00:00
jinye
5dc2e1501f
feat(serve): Add runtime.activity fields to daemon status API (#6270)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* feat(serve): add runtime.activity fields to daemon status API

Add activePrompts, lastActivityAt, and idleSinceMs to the
GET /daemon/status runtime section. These fields already exist on the
bridge (and are exposed via GET /health?deep=1) but were missing from
the richer status endpoint that operators use for troubleshooting.

The idleSinceMs value is computed from a cached lastActivityAt read
(same pattern as the health handler) to ensure consistency within a
single response.

* feat(serve): add MCP server health summary to workspace status

Extract serversConnected, serversErrored, and serversDisabled counts
from the MCP servers array into the workspace.mcp.summary object.
Operators can see MCP fleet health at a glance without expanding the
full JSON.

* fix(serve): guard activity fields against undefined bridge getters

Add ?? null / ?? 0 fallbacks for lastActivityAt and activePromptCount
to prevent RangeError when a test fake bridge omits these properties.
2026-07-03 20:19:02 +00:00
pomelo
741780517e
feat(review): route suggestion-level findings to an updatable PR comment (#5786)
* feat(review): route suggestion-level findings to an updatable PR comment

Suggestion-level /review findings now go to a single issue comment that is PATCHed in place across runs, instead of becoming per-line inline comments. Critical findings stay inline.

Why: every /review run re-emitted a fresh batch of inline comments with no notion of "this suggestion was already posted and is still open", so the PR Files-changed view grew noisier each round and issues never converged — worst for agentic authors who feel forced to resolve each thread one-by-one. One updatable comment keeps the suggestion list a single refreshable view; the locate-and-PATCH lives in a new deterministic `qwen review post-suggestions` subcommand so the LLM never reposts a duplicate.

* fix(review): validate SUMMARY_MARKER in body-file and harden payload cleanup

Add runtime validation that the body-file contains SUMMARY_MARKER before
posting to GitHub, preventing duplicate summary comments when the marker
is accidentally omitted.

Move writeFileSync(payloadPath) inside the try block so that finally's
unlinkSync cannot throw ENOENT when preceding code throws before the file
is written. Wrap unlinkSync in try/catch as best-effort cleanup.

* fix(review): add runPostSuggestions tests and clear stale summaries

Add 4 integration tests covering the PATCH/POST branching, marker
validation, and payload cleanup on error (previously untested I/O path).

Update SKILL.md and DESIGN.md so that when a /review run finds zero
new Suggestions but a prior summary comment exists, the stale table is
replaced with an 'all addressed' message instead of being left frozen.

* fix(review): resolve SKILL.md contradictions and add COMMENT event example

- Fix body rule to allow unmappable Critical findings in review body
- Add JSON example for Suggestion-only COMMENT event reviews
- Align --body-file describe text and SKILL.md marker wording with
  actual includes() validation (was documented as startsWith)

* fix(review): exclude suggestion summaries from Already-discussed section in pr-context

Filter issue comments containing SUMMARY_MARKER out of the 'Already
discussed — do NOT re-report' section and render them in a dedicated
'Previous suggestion summary (evaluate afresh)' section instead.

Without this, review agents treat prior suggestion rows as already
discussed, produce zero new Suggestions, and the all-addressed path
overwrites the summary even though nothing was actually fixed.

* fix(review): add author verification to suggestion summary filter

* fix(review): ensure out dir exists and frame gh-api parse errors in post-suggestions

- mkdirSync(dirname(out)) before writing the payload/report, matching every
  peer review subcommand (pr-context, fetch-pr, load-rules, deterministic).
  Without it, an --out under a not-yet-created dir (e.g. .qwen/tmp/) crashed
  with a raw ENOENT.
- Wrap both JSON.parse(raw) of the gh-api response so a non-JSON body (empty,
  rate-limit JSON, HTML during an outage) throws a diagnostic error naming the
  failed call and showing the raw output, like fetch-pr.ts does, instead of a
  bare SyntaxError.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(review): render previous suggestion summary verbatim in pr-context

The 'Previous suggestion summary (evaluate afresh)' section passed the
summary body through snippet(), which collapses all whitespace into
single spaces and truncates at 500 chars. The summary is a multi-row
Markdown table, so this mangled it into an unreadable single line and
dropped rows — defeating the 're-evaluate each row' purpose. Render the
body verbatim (only stripping the locator marker); it is our own
author-verified comment, so preserving its structure is safe.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* chore(review): restore non-review files to main to keep PR diff scoped

The old branch carried prettier/.editorconfig-driven reformats of files
unrelated to the /review suggestion-summary feature (mcp-client, acp-bridge,
feishu adapter, workflow-orchestrator/client-mcp tests, and channel-loop /
settings docs). These are cosmetic-only and not enforced by CI (the prettier
step runs 'prettier --write .' without a diff gate), but they polluted the PR.
Restore them verbatim to origin/main so the PR diff is review-only.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* test(review): expect post-suggestions in registered subcommand list

main's PR #6092 added review.test.ts locking the 'qwen review' subcommand
surface to exactly 5 helpers. This PR adds a 6th, post-suggestions, so the
guard test must include it. Keeps the deterministic-removal guards intact.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

* fix(review): exclude all stale summaries, add pr-context tests, clarify event rule

Address the latest /review suggestions:
- pr-context: build summaryIds from every one of my summary comments, not just
  the latest — a leftover older summary (e.g. after a failed PATCH+POST) was
  leaking into the 'Already discussed' section and could suppress still-open
  findings. Extract the author+marker selection into a pure, exported
  collectSuggestionSummaries() and cover the prompt-injection guard, latest-wins
  ordering, and full-exclusion behavior in a new pr-context.test.ts.
- post-suggestions.test: cover the non-JSON gh-api diagnostic paths (PATCH/POST)
  and the mkdirSync(dirname(out)) call added in b05280c.
- SKILL.md: make the event rule unambiguous — APPROVE only when there are no
  Critical AND no Suggestion findings; COMMENT for Suggestion-only.

Co-Authored-By: Qwen-Coder <noreply@qwen.ai>

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <noreply@qwen.ai>
2026-07-03 16:51:49 +00:00
qwen-code-ci-bot
4e3fd29781
chore(release): v0.19.6 (#6280)
* chore(release): v0.19.6

* docs(changelog): sync for v0.19.6

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-03 16:37:54 +00:00
qwen-code-dev-bot
cc64d7ce7f
feat(daemon): expose visionModelId in workspace provider status and web-shell model dialog (#6262)
* ci(autofix): restore sandbox image flow

* feat(daemon): expose visionModelId in workspace provider status and web-shell model dialog (#6195)

---------

Co-authored-by: yiliang114 <effortyiliang@gmail.com>
Co-authored-by: Qwen Autofix <autofix@qwen-code.ai>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-03 15:50:34 +00:00
jinye
3911b1dc34
fix(serve): optimize daemon NDJSON stream handling (#6263)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-03 15:27:28 +00:00
callmeYe
fe3dd93e8f
Add sessionless workspace memory forget and dream (#6227)
* feat(serve): add sessionless memory forget and dream

* fix(serve): thread abort through memory forget

* fix(serve): address workspace memory review feedback

* fix(serve): address memory review follow-up

* fix(memory): harden forget review paths

* fix(serve): classify memory availability failures

* fix(serve): document memory task capacity tiers

* fix(memory): address review edge cases

* chore: remove mobile-mcp formatting noise
2026-07-03 11:00:53 +00:00
tanzhenxin
2a21963026
feat(web-shell): display nested sub-agents as a tree in the tasks panel (#6239)
Carry nested-agent lineage (parentAgentId, parentName, depth) through the
daemon tasks snapshot as optional fields and render the web-shell tasks
panel as a tree: children group under their parent with a ↳ marker and
clamped indentation, agents whose parent left the roster are promoted to
root with a "from <parent>" annotation, and the detail view gains a
nesting line. The [blocking] tag and the two-step stop confirmation now
apply only to provably user-blocking chains, mirroring the TUI's
agent-forest semantics from #6191.
2026-07-03 10:01:07 +00:00
ChiGao
9658dccfbb
feat(daemon): add session artifact APIs (#5895)
* docs: add session artifacts daemon API design

* docs: tighten session artifacts design scope

* docs: frame artifacts API as complete v1 capability

* docs: address artifacts review follow-ups

* docs: clarify artifacts reset boundary

* docs: clarify batch hook artifact flow

* docs: address latest artifact design audit

* docs: tighten artifact event and store semantics

* docs: simplify artifact v1 merge policy

* docs: resolve artifact v1 review blockers

* docs: tighten artifact trust and retention semantics

* docs: close artifact v1 boundary gaps

* feat(daemon): add session artifact APIs

* fix(daemon): harden session artifact semantics

* fix(sdk): update daemon browser bundle budget

* fix(daemon): tighten artifact ingestion boundaries

* fix(daemon): cache artifact workspace realpath

* fix(daemon): sanitize artifact add dispatch input

* docs(daemon): align artifact change wire shape

* fix(daemon): harden artifact status validation

* test(daemon): cover artifact acp dispatch

* test(daemon): update artifact capability baseline

* fix(daemon): clear workspace locator on published artifacts

* fix(core): forward post-tool batch artifacts

* fix(daemon): harden artifact status refresh

* fix(daemon): guard artifact event ingestion

* test(daemon): cover non-strict artifact drops

* fix(core): align artifact display validation

* fix(daemon): serialize artifact store operations

* chore(daemon): clarify artifact publisher tool name

* fix(daemon): coordinate artifact route mutations

* fix(daemon): harden artifact refresh comparison

* fix(daemon): harden artifact ingress edge cases

* fix(daemon): guard artifact rpc mutations during archive

* fix(daemon): gate session metadata mutation auth

* fix(daemon): harden artifact route boundaries

* fix(channels): compact drained group history

* fix(daemon): address artifact review findings

* fix(daemon): address artifact review follow-ups

* fix(daemon): preserve hook artifact success output

* fix(daemon): handle artifact review edge cases

* fix(daemon): address artifact review hardening

* test(daemon): cover artifact review edge cases

* fix(daemon): validate hook artifact aggregation

* fix(daemon): improve artifact ingestion diagnostics

* fix(daemon): address artifact review feedback

* fix(daemon): address artifact review feedback

* fix(daemon): harden session artifact ingress

* fix(daemon): harden artifact edge cases

* fix(daemon): tighten artifact path validation

* fix(daemon): address artifact review races

* fix(daemon): surface artifact path inspection errors

* fix(daemon): forward batch hook artifacts in ACP

* fix(daemon): clean artifact bridge metadata

* test(daemon): cover artifact store edge cases

* fix(daemon): resolve artifact file url symlinks

* fix(daemon): harden artifact ingestion paths

* fix(daemon): harden artifact review paths

* test(daemon): cover artifact tool name sync

* fix(daemon): harden artifact republish validation

* chore(daemon): remove unrelated artifact PR churn

* fix(daemon): address artifact review gaps

* test(daemon): cover artifact url rejection

* chore(daemon): drop unrelated formatting churn

* chore(daemon): update settings schema

* fix(daemon): harden artifact validation

* fix(daemon): tighten artifact event validation

* docs(core): clarify artifact env flag comment

* test(cli): align soft failure artifact expectation

* fix(daemon): address artifact review edge cases

* fix(daemon): enable artifact metadata recording

* fix(daemon): harden artifact store review paths

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-03 08:58:43 +00:00
Shaojin Wen
da22360c25
feat(web-shell): show the qwen-code version in the sidebar footer (#6222)
* feat(web-shell): show the qwen-code version in the sidebar footer

The Web Shell had no visible version. Show the running qwen-code version (from the daemon capabilities) in the sidebar footer, inline with the Settings button so it stays visible without taking its own row.

Render the version consistently wherever it appears:
- Prefix "v" only for a real semver release; a non-semver fallback such as "unknown" is shown as-is, so we never render a bogus "vunknown". Applied to the Web Shell badge and the TUI header.
- Dev builds (scripts/dev.js) now report the real package version instead of the "dev" sentinel, matching scripts/start.js, so the UI shows the actual version (e.g. v0.19.4). DEV=true / NODE_ENV=development remain the signals that mark a dev build.

* test: add readFileSync to node:fs mock in dev.test.js

scripts/dev.js now reads package.json via readFileSync at module load to
report the real CLI_VERSION, but the node:fs mock in dev.test.js did not
export readFileSync, causing vitest to throw "No readFileSync export is
defined on the node:fs mock" and failing the suite.
2026-07-03 08:56:08 +00:00
ytahdn
8dfa7613be
fix(serve): respect disabled skill settings (#6223)
* fix(serve): respect disabled skills in status

* test(serve): align skill disabled status coverage

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-03 06:58:46 +00:00
Tianyuan
0633b8a985
feat(scheduler): make recurring cron/loop job expiration configurable (#6173)
* feat(scheduler): make recurring cron/loop job expiration configurable

Recurring cron/loop jobs previously auto-expired after a hardcoded 7 days with no way to extend or disable the limit, forcing long-running daemon deployments to recreate jobs weekly.

Add an experimental.cronRecurringMaxAgeDays setting (default 7) and a QWEN_CODE_CRON_MAX_AGE_DAYS environment-variable override (takes precedence, for cloud/container deployments). A value of 0 disables expiry so jobs run until deleted; negative or unparseable values fall back to the 7-day default. The configured limit also applies to durable tasks restored from disk, and the CronCreate tool description now reflects the effective limit instead of a hardcoded '7 days'.

Closes #6167

* refactor(scheduler): drop unused DEFAULT_RECURRING_MAX_AGE_MS export

Review feedback on #6173 — the ms constant is only used inside the module as the constructor default; keep only the days constant public.

* fix(scheduler): align zero max-age semantics and warn on cron expiry misconfiguration

Review feedback on #6173:

- CronScheduler constructor now maps 0 to Infinity (never expire), matching the config layer instead of silently substituting the 7-day default for direct callers.
- Invalid QWEN_CODE_CRON_MAX_AGE_DAYS / cronRecurringMaxAgeDays values now log a warning before falling back to the default, leaving a breadcrumb for misconfigured deployments.
- Durable tasks found past the recurring max age at load now log a warning before their final fire + delete, since a lowered max age retroactively expires long-lived tasks and deletion is unrecoverable.
- New durable-restore tests: a custom max age applies to tasks reloaded from disk, and a disabled max age (Infinity) restores a 30-day-old task as live instead of aging it out.

* fix(scheduler): surface cron expiry warnings on the console

Review feedback on #6173:

- The invalid-config and retroactive-expiry warnings moved from debugLogger (file-only, off unless QWEN_DEBUG_LOG_FILE is set) to console.warn so they reach container/daemon logs where this knob matters; the config warning is latched to fire once per Config instance.
- CronCreateTool's constructor now resolves the max age once instead of three times, so an invalid env var can't emit duplicate warnings during tool registration.

* fix(scheduler): reject non-finite timestamps in durable task validation

Review feedback on #6173: JSON like -1e999 parses to -Infinity, which passes the typeof-number check and then poisons date math — with a finite lastFiredAt the entry reads as an overdue aged task and the retroactive-expiry warning's toISOString() throws RangeError mid-load, so one malformed persisted entry blocks durable cron startup/takeover. Validation now requires finite createdAt (and lastFiredAt when non-null), routing such entries through the existing fix-or-delete contract for corrupt files. Verified the new end-to-end test reproduces the RangeError without the fix.

* refactor(scheduler): single-source the max-age contract and freeze it at Config construction

Review follow-ups on #6173:
- Extract normalizeRecurringMaxAge as the single owner of the
  0/Infinity no-expiry contract, used by both the Config layer and the
  CronScheduler constructor, so the constructor's 0 handling can no
  longer be removed as apparent dead code.
- Resolve QWEN_CODE_CRON_MAX_AGE_DAYS once at Config construction into
  a readonly field, honoring the setting's requiresRestart contract;
  mid-session env changes can no longer make the tool description,
  tool output, and scheduler report different expiries. The warn
  once-latch is now unnecessary (construction warns at most once).
2026-07-03 06:34:37 +00:00
tanzhenxin
8de93b876b
feat(core): allow sub-agents to spawn nested sub-agents up to a configurable depth (#6189)
* feat(core): allow bounded nested sub-agent spawning via maxSubagentDepth

Sub-agents may now spawn sub-agents up to a configurable maximum nesting
depth (default 5; 1 reproduces the previous no-nesting behavior).
Enforced in two layers sharing one predicate: prepareTools() hides the
agent tool from leaf-depth sub-agents, and AgentTool.execute() rejects
over-depth spawns as an authoritative backstop. Teammates, forks, and
the workflow tool remain excluded from nesting. Launch depth is
persisted in the agent meta sidecar and restored on resume (including
deferred-approval continuations and in-process AgentInteractive frames)
so a resumed nested agent cannot regain spawn capacity.

See knowledge/qwen-code/design/nested-subagents.md.

* fix(core): address review findings on nested sub-agent spawning

- Deny the agent tool to workflow-spawned subagents: depth gating would
  otherwise re-admit it, letting a workflow leaf spawn outside the
  orchestrator's concurrency cap, agent accounting, and token budget.
- Reject non-finite maxSubagentDepth values (JSON 1e309 parses to
  Infinity and would unbound the recursion cap; NaN would silently block
  all nesting) and cap the knob at 100 to catch typos.
- Add a --max-subagent-depth CLI flag mirroring sibling budget flags,
  with loud validation for flag typos, and document the setting.
- Log guard rejections (depth, fork containment) and silent
  fork-to-subagent downgrades through the agent debug logger.
- Refresh stale comments (depthOverride resume pinning, depth-gated
  AgentTool exclusion) and drop references to a design doc that lives
  outside the repository.
- Fill review-noted test gaps: nesting predicate primitives, fork-context
  prepareTools, persisted-depth restoration on background resume,
  nested AgentInteractive depth pinning, nested fork fallback, and the
  blocked-spawn returnDisplay shape.

* fix(cli): add maxSubagentDepth to the CliArgs test literal

The exhaustive CliArgs mock in gemini.test.tsx missed the new field,
failing CI's clean tsc build (the local incremental build skipped the
test file).

* fix(core): add a teammate backstop to the agent spawn guards

execute() backstopped depth and fork containment but not the teammate
exclusion, so its guards covered less than prepareTools() gates. A
teammate spawn call that slipped past schema-hiding would have nested.
Block it symmetrically with the fork guard, log the rejection, and pin
the behavior in a test.

* fix(core): normalize persisted maxSubagentDepth on resume

The resume path trusted the raw sidecar value, bypassing the Config
clamp — a tampered or malformed sidecar (1e309 parses to Infinity;
JSON.stringify turns Infinity into null) would remove the nesting cap
for resumed agents. Extract the clamp into a shared
normalizeMaxSubagentDepth used by both the Config constructor and the
flag-restore path, and refresh the stale settings schema description
(clamp range, non-finite fallback, workflow-agent wording).

* test(core): pin null-to-default normalization of resumed maxSubagentDepth

JSON.stringify(Infinity) === 'null', so a sidecar can legitimately carry
null; widen the persisted flag type to admit it and parameterize the
resume test over both the clamp (5000 -> 100) and the null fallback
(null -> 5).

* fix(core): harden nesting depth edges from final review pass

- Normalize persisted meta.depth on resume: the sidecar is untrusted
  JSON, and a tampered negative depth (or -1e309 → -Infinity) would pin
  the resumed frame below zero and pass canSpawnNestedAgent for every
  cap. Invalid values fail closed to the depth ceiling — the agent
  keeps running but cannot spawn.
- Register monitor notification routing for in-process interactive
  agents: framing runLoop() made their monitors agent-owned, and owned
  dispatch has no session fallback, so notifications were silently
  dropped. InProcessBackend now routes them into the agent's message
  queue and tears the routing down on release.
- Downgrade background spawn requests from nested launchers to awaited
  foreground runs: a nested launcher cannot honor the background
  completion contract (send_message/task_stop excluded, notifications
  session-scoped), which orphaned the child's results.
- Extract spawnBlockReason() as the single spawn-exclusion policy
  shared by prepareTools() and execute(), replacing two hand-kept
  copies of the depth/teammate/fork rules.
- Share DEFAULT_MAX_SUBAGENT_DEPTH / MAX_SUBAGENT_DEPTH_LIMIT across
  the core normalizer, the CLI flag validator, and the settings schema.
- Log dropped teammate names from nested spawns; revert the impossible
  |null persisted-flag widening to an honest tampered-sidecar framing;
  document the constructor-time depth capture invariant.

* test(cli): add DEFAULT_MAX_SUBAGENT_DEPTH to core package mocks

settingsSchema.ts now imports the shared constant, so CLI tests that
mock @qwen-code/qwen-code-core with an explicit export list need the
new export.

* feat(cli): display nested sub-agents as a tree in the TUI (#6191)

* feat(core): allow bounded nested sub-agent spawning via maxSubagentDepth

Sub-agents may now spawn sub-agents up to a configurable maximum nesting
depth (default 5; 1 reproduces the previous no-nesting behavior).
Enforced in two layers sharing one predicate: prepareTools() hides the
agent tool from leaf-depth sub-agents, and AgentTool.execute() rejects
over-depth spawns as an authoritative backstop. Teammates, forks, and
the workflow tool remain excluded from nesting. Launch depth is
persisted in the agent meta sidecar and restored on resume (including
deferred-approval continuations and in-process AgentInteractive frames)
so a resumed nested agent cannot regain spawn capacity.

See knowledge/qwen-code/design/nested-subagents.md.

* feat(cli): display nested sub-agents as a tree in the TUI

Render nested agents depth-first with indent + dim '↳' in the live agent
panel and background tasks view; promote orphaned children to root with a
'· from <parent>' annotation. Detail view gains a level badge, Parent
breadcrumb, and Sub-agents section. The [blocking] tag and two-step cancel
confirm now apply only to provably user-blocking foreground chains. Parent
completion summaries carry a '· N sub-agents' tail (guard-rejected spawns
now record as failed tool calls so the count stays honest). Also fixes the
live-panel Enter-for-detail order mismatch by sharing one display order
between the panel render and the composer keyboard mapping.

* fix(core): address round-1 review on nested sub-agent spawning

- Derive launch metadata (hooks, spans, task rows, meta sidecar) from the
  resolved subagent config instead of the raw requested type, so a fork
  request that falls back to the awaitable path no longer reports "fork".
- Pin the blocked-spawn failure contract in tests: error is set and
  returnDisplay.status is 'failed' for both the depth and fork guards; also
  document the failure-path routing at buildSpawnBlockedResult.
- Drop source-comment references to private knowledge/ design docs that do
  not exist in this repository.

* test: address round-2 review on sub-agent counting and fork fallback

- Exercise the legacy 'task' alias in the scrollback sub-agent count so
  the migration-aware name set is covered, not just the canonical name.
- Pin the nested-fork downgrade: a sub-agent requesting a fork falls back
  to the awaitable general-purpose subagent even in interactive mode.
- Drop a duplicated 'nesting depth guard' describe block left behind by
  the automated base-branch merge (kept the copy with the failure-shape
  assertions).

* fix(core): keep actionable guidance in blocked-spawn error messages

The scheduler's failure path sends only error.message to the model and
the scrollback, discarding llmContent. With the terse terminateReason as
the message, a blocked spawn lost its "do the task yourself instead"
instruction, inviting retry loops. Carry the full guidance text in
error.message and keep terminateReason for the display card.

* test(cli): pin the tree indent clamp at depth beyond TREE_INDENT_MAX_LEVELS

Maintainer mutation-testing on the PR found that removing the clamp in
treeRowPrefix survived the suite. Assert a depth-4 row indents 3 levels
(12 spaces), plus the base marker/indent behavior.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-03 03:18:57 +00:00
Gaurav
2cb0031160
fix(cli): add bootstrap fast paths (#6188)
* fix(cli): add bootstrap fast paths

* fix(cli): address bootstrap review feedback

* test(core): make MCP retry backoff test deterministic

* fix(cli): address bootstrap validation feedback

* fix(cli): keep global-flag MCP invocations on full parser

* fix(cli): harden bootstrap review gaps

* fix(cli): copy package wrapper from script directory

* fix(cli): cover bootstrap review edge cases

* test(cli): cover bootstrap fallback paths

* fix(cli): minimize wrapper version imports

---------

Co-authored-by: 易良 <1204183885@qq.com>
2026-07-02 22:28:11 +00:00
Matt Van Horn
e6e939e020
fix: resolve macOS seatbelt profile path from bundle dir, not chunks/ (#6172)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-07-02 22:27:45 +00:00
qwen-code-ci-bot
2126474c28
chore(release): v0.19.5 (#6194)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* chore(release): v0.19.5

* docs(changelog): sync for v0.19.5

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-02 13:38:46 +00:00
Dragon
2e669c0697
fix(cli): drop /effort tier autocompletion for an argument-hint placeholder (#6179)
Bare /effort now reliably opens the picker dialog instead of letting Enter auto-select the first completed tier. The tiers are surfaced as a placeholder via argumentHint ([low|medium|high|xhigh|max]) rather than as submenu-like completion entries; typing /effort <tier> still sets one directly.
2026-07-02 20:55:57 +08:00
jinye
39f3108a20
feat(cli): add credential redaction for worker stderr forwarding (#6146)
* feat(cli): add credential redaction for worker stderr forwarding

Add a `redactLogCredentials` function that strips credentials from
worker log lines before they reach the daemon's stderr and log file.
Covers Bearer/QQBot tokens, Authorization headers, common API key
prefixes, env-var secret assignments, URL-embedded credentials, JSON
secret fields, and platform-specific headers (DingTalk).

Integrate the redaction into both stderr forwarding paths:

- ACP children: `createStderrForwarder` now applies redaction in both
  the normal flush and 64 KiB forced-truncation code paths.

- Daemon channel worker: change supervisor stdio from `'inherit'` to
  `'pipe'` for stderr, add a line-buffered forwarder with redaction,
  64 KiB buffer cap, and try-catch to prevent daemon crashes. Wire
  `onDiagnosticLine` so worker stderr also reaches the daemon log file
  (previously it only went to the daemon's terminal).

Issue: #5976 (V1.5 follow-up)

* fix(cli): align redaction marker and bound URL scheme length

- Change redaction marker from ***REDACTED*** to <redacted> to match
  the supervisor's existing convention and avoid double-redaction output
  mismatches in tests.

- Bound URL credential regex scheme to {0,31} chars (matching the
  supervisor's pattern) to prevent O(n²) backtracking on long strings
  of scheme-like characters.

* fix(cli): use daemon-local heartbeat timestamp and split multiline env secrets

Two fixes for unresolved review threads from #6098:

- Heartbeat: use daemon's own `new Date().toISOString()` instead of
  reflecting the worker-supplied `message.at` value. Prevents a
  compromised adapter from injecting arbitrary data into `/daemon/status`.

- PEM multiline: split multi-line sensitive env values (e.g. PEM keys)
  into per-line redaction patterns so individual logged lines match.
  The full value is kept as a pattern too for single-line matches.

* fix(cli): address review feedback on credential redaction

- Fix sensitiveEnvValues: change `lines.length > 1` to `> 0` so single
  matching lines from multi-line env values are also added as patterns.

- Add hyphens to sk- charset for compound prefixes (sk-proj-, sk-ant-).

- Add github_pat_ (fine-grained PATs) and ghu_ (app user tokens).

- Add ASIA prefix for AWS STS temporary credentials alongside AKIA.

- Update Authorization catch-all comment to accurately describe the
  2-token limitation.

* fix(cli): add heartbeat rationale comment

Add comment explaining why heartbeat uses daemon clock instead of
worker-supplied message.at (security: compromised adapter injection).

* test(acp-bridge): add onEnd redaction test and fix lint

Add test verifying credential redaction on partial lines flushed via
forwarder.onEnd(). Suppress pre-existing vitest/no-conditional-expect
lint errors in getAcpMemoryArgs tests (system-dependent heapArg).

* test(acp-bridge): add onEnd redaction test and revert spawnChannel lint

Move the onEnd credential redaction test to logRedaction.test.ts to
avoid triggering pre-existing vitest/no-conditional-expect lint errors
in spawnChannel.test.ts. Revert the spawnChannel test file to its
upstream state.

* fix(test): remove unused eslint-disable directives

vitest/no-conditional-expect is not enabled in this repo's eslint
config. The directives cause CI failure via reportUnusedDisableDirectives
warn + --max-warnings 0.

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-07-02 20:55:38 +08:00
Shaojin Wen
76addf4ef6
fix(serve): keep skill slash commands available when the ACP child is unavailable (#6169)
* fix(serve): keep skill slash commands available after the ACP child is reaped

`GET /workspace/skills` is answered exclusively by the ACP child — the
daemon has no local SkillManager. `requestWorkspaceStatus` only checks for
an already-live channel (`liveChannelInfo()`, never `ensureChannel()`), so
when no child is running it returns the idle placeholder
(`initialized: false`, empty `skills`).

That is the norm before the first session, and — crucially — again after
the child is reaped on session close, which happens immediately by default
(`--channel-idle-timeout-ms` defaults to 0 = immediate kill). Unlike
`/workspace/providers`, skills have no daemon-local status provider to fall
back on. So once a user has created and closed a session, every subsequent
pre-first-prompt `/workspace/skills` query returns empty, the Web Shell's
slash-command list falls back to the hardcoded built-ins (which omit
skills), and `/rev` stops autocompleting `/review`.

Retain the last skills status a live child produced and replay it while no
channel is live, so skill-backed slash commands keep autocompleting; the
next live query refreshes the cache. `initialized` cleanly separates a real
child answer (always `true`) from the idle placeholder (always `false`).

Completes #6153, which wired the Web Shell to fetch `/workspace/skills` in
the deferred-connect path but could not surface skills the daemon was
unable to answer without a live child.

* fix(serve): enumerate workspace skills locally when the ACP child is unavailable

The cache from the previous commit keeps the last child answer alive across
a reap, but it never warms when the child never answers at all — most
visibly under `npm run dev`, where the on-demand-transpiled child's
`initialize` handshake routinely exceeds the 10s preheat budget, so preheat
times out and no channel ever comes up. `/workspace/skills` then stays empty
until the first prompt, dropping `/review` and every other skill from the
Web Shell's pre-first-prompt autocomplete even though the skills exist on
disk (typing `/review` in full still runs it, since submitting spawns a
session — hence "not in the list, but usable").

Add a daemon-local skills provider that enumerates skills straight from the
filesystem via SkillManager (a lightweight Config shim — no child, no MCP
init), mirroring the existing daemon-local providers-status provider. The
facade falls back to it only after both a live child answer and the cached
last answer are unavailable, so the live child stays authoritative (and
keeps extension-provided skills) while a never-preheated child still yields
the on-disk skills — `/review` included.

* fix(serve): fall back to cached/local skills when the child query throws mid-flight

Addresses review feedback on #6169: the channel can die after
`liveChannelInfo()` returns a valid channel but before the RPC completes, so
`queryWorkspaceStatus` rejects. Previously that exception propagated even
though the cache or the daemon-local provider could still answer. Wrap the
query in try/catch (logging via writeStderrLine, matching
getWorkspaceEnvStatus / getWorkspacePreflightStatus) and treat a mid-flight
failure as "no live child", so the request degrades to the cached last answer
or daemon-local enumeration instead of failing.

* refactor(serve): address review feedback on daemon-local skills provider

- Extract the SkillConfig → ServeWorkspaceSkillStatus mapping into a shared
  workspace-skills-mapping module used by both the ACP child's
  buildWorkspaceSkillsStatus and the daemon-local provider, so the two skill
  listings can't drift; cover it (including the disable-model-invocation
  branch) with a unit test.
- Memoize the SkillManager per workspace so repeat queries reuse its in-memory
  cache instead of re-scanning every skill level on each call.
- Honor the safe-mode env (isSafeModeEnv, as Config does) instead of hardcoding
  isSafeMode to false; keep bareMode off (the daemon never runs `--bare`).
- Log daemon-local enumeration failures via writeStderrLine, matching the rest
  of the workspace-service error handling.

* test(serve): cover daemon-local skills error path; guard the facade provider call

- Test the previously-uncovered `buildWorkspaceSkillsStatus` catch branch:
  when enumeration fails it returns `{ initialized: false, skills: [],
  errors: [{ kind: 'skills', status: 'error', error }] }` and logs to stderr.
  Also cover the per-workspace SkillManager memoization.
- Wrap the facade's `workspaceSkillsStatusProvider` call in try/catch so a
  throwing injected provider degrades to the idle placeholder instead of
  failing the request (matching getWorkspaceEnvStatus / getWorkspacePreflightStatus),
  with a facade test for the throw path.
2026-07-02 11:31:36 +00:00
qqqys
6509e8de08
feat(channels): show lifecycle status in adapters (#6114)
* chore: ignore local worktrees

* docs(channels): design identity and task lifecycle p0

* docs(channels): plan identity and task lifecycle p0

* feat(channels): add identity and task lifecycle metadata

* fix(channels): suppress cancelled tool call lifecycle

* docs: add channel lifecycle status adapter design

* docs: add channel lifecycle status adapter plan

* feat(channels): map telegram lifecycle to typing

* feat(channels): map weixin lifecycle to typing

* fix(channels): reset weixin typing state after failed start

* feat(channels): map dingtalk lifecycle to reactions

* feat(channels): add feishu card status labels

* fix(channels): preserve feishu collapsible status labels

* feat(channels): show feishu lifecycle card status

* fix(channels): cover loop lifecycle metadata

* fix(channels): preserve feishu terminal card status

* chore: remove internal task reports from branch

* fix(channels): clear late lifecycle status starts

* fix(channels): harden lifecycle event edges

* fix(channels): clean adapter lifecycle state

* fix(channels): finalize cancelled lifecycle before cleanup

* fix(feishu): keep cancelled card status visible

* docs(channels): add lifecycle status no-op coverage

* fix(channels): address lifecycle review suggestions

* docs(channels): align lifecycle status documentation

* fix(channels): route adapter stop through lifecycle cancel

* fix(channels): close lifecycle cancellation races

* fix(channels): keep first feishu terminal status

* fix(telegram): guard lifecycle typing updates

* test(qqbot): cover lifecycle status no-ops

* fix(feishu): preserve completed card race status

* docs(lifecycle): align status review docs

* fix(channels): separate pending cancel state

* fix(channels): clean adapter lifecycle status edges

* fix(channels): order clear cancellation lifecycle

* fix(feishu): preserve user stop status label

* fix(channels): suppress loop chunks during pending cancel

* test(channels): use active session in cancel regression

* fix(channels): harden adapter cancellation tests

* fix(channels): sanitize lifecycle tool fields

* fix(channels): route shared tool call lifecycle

* fix(channels): preserve pending cancel intent

* fix(channels): preserve pending cancel intent

* fix(telegram): track typing by session

* fix(feishu): preserve stop status during finalization

* fix(channels): preserve responses after failed cancel

* fix(channels): tighten lifecycle cancellation reasons

* fix(channels): close lifecycle cancel races and validate identity config

Address the outstanding review findings on #6105:

- carry a typed reason on ChannelLoopSkippedError and report disabled
  loops as 'dropped' instead of 'timeout'
- treat a turn as committed once delivery starts: /cancel re-checks
  deliveryStarted after the cancel RPC settles, and neither prompt path
  lets a late-settling cancel rewrite a delivered turn into cancelled
  (or follow a /clear cancellation with completed)
- tag failed lifecycle events with phase: agent vs delivery
- validate identity/memoryScope shape at config parse time instead of
  throwing an opaque TypeError on the first prompt of every session
- guard onPromptStart hooks, pass job.id to loop onPromptStart/End,
  append the boundary block after operator instructions, gate
  /who + /status identity lines on configured identity/memoryScope,
  cache the boundary prompt, and route error logs through
  lifecycleError()

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

* test(qqbot): keep lifecycle mock in sync

* fix(channels): unify cancel state machine and adapter lifecycle edges

Address the remaining review findings on #6114:

- /cancel now delegates to requestActivePromptCancellation — one cancel
  state machine for slash command and adapter stop buttons; the helper
  refuses to claim success once delivery started and sanitizes its logs
- share isTerminalTaskLifecycleType from channel-base instead of four
  hand-rolled terminal checks
- DingTalk: only attach reactions for message ids seen inbound (loop job
  ids no longer trigger doomed emotion API calls), log reaction API
  failures, and recall reactions when a session dies
- Feishu: track userStopped on the card state so every wind-down path
  renders 已停止生成 after a Stop click, and pass terminal labels through
  updateCard's statusLabel param at the remaining baked-text sites
- Weixin: guard the typing .then() against a racing disconnect
- document onTaskLifecycle as the canonical hook (onPromptStart/End are
  back-compat), fix TS4111 bracket access in the DingTalk test

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

* fix(channels): keep failed terminal event when cancel settles post-delivery

Self-review follow-up: once delivery started, the catch paths no longer
reconcile a pending cancel — a late-resolving cancel RPC used to flip
cancelled=true there, suppressing the failed emit while the /cancel
handler (seeing deliveryStarted) also declined to emit, leaving a
started task with no terminal lifecycle event at all.

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

* fix(channels): close self-review findings on adapter lifecycle edges

- DingTalk: track inbound message ids in a capped insertion-ordered set
  instead of the TTL-swept dedup map, so a turn queued minutes behind a
  long predecessor still attaches its reaction
- Feishu: reset userStopped when the cancel RPC fails (later wind-down
  must render the real terminal status), and give handleStop's plain
  message fallback the same ---/label shape the strip regex expects

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

* fix(channels): hold streamed chunks while a cancel is pending

Chunks arriving while a /cancel RPC is in flight were pushed straight
into the BlockStreamer, which can send a block on a size/paragraph
threshold before the cancel resolves — leaking output a successful
cancel can never recall. Hold the pending-window chunks instead: replay
them (block streaming + text_chunk transcript) when the cancel fails,
discard them when it succeeds. onResponseChunk stays live through the
window so adapter-accumulated display state has no permanent hole on a
failed cancel; adapters gate visible updates on their own stop flags.

Also stop passing the loop job id to onPromptStart/onPromptEnd (and the
/clear eviction path): the hook contract is inbound platform message
ids, and adapters act on them — cards and reactions keyed to a fake id.
Lifecycle events still carry job.id for correlation.

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

* fix(feishu): strip every status-label layout from quoted-reply context

Replace the two $-anchored strip regexes in extractCardText with one
line-granular status-block filter. The anchored patterns missed four
layouts the cards actually render: the two-block truncation card
(notice block + label block), terminal labels joined mid-string before
a collapsible panel body, the 停止失败,请重试 label (never in the
alternation), and label-only stopped cards where the divider leads the
text. Tests now assert the real rendered shapes.

Also update the adapter-cancellation suppression test to the new
hold-and-replay chunk semantics from the base layer.

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

* fix(feishu): ignore loop job ids in prompt hooks

* fix(channels): defer adapter chunks while cancel is pending

* fix(feishu): preserve bare status text in quotes

* fix(channels): release held chunks on failed cancel

* fix(feishu): preserve generated stop fallback status

* fix(telegram): clear typing on dead sessions

* fix(feishu): share status label definitions

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-02 11:01:07 +00:00
jinye
4b372d39ec
feat(channels): add listSessions to ChannelAgentBridge (#6182)
Add optional `listSessions()` method to `ChannelAgentBridge` interface
so channel consumers can enumerate sessions currently attached to the
bridge. Only `DaemonChannelBridge` implements it — reads from the
internal `sessions` Map and `activePrompts` Set to build a snapshot.

The daemon-worker facade forwards the method unconditionally when
present, matching the existing `getAvailableCommands` pattern.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
2026-07-02 10:36:43 +00:00
qwen-code-dev-bot
1fd47b31eb
fix(cli): skip MCP approval dialogs in YOLO mode (#6177)
MCP servers configured in .qwen/settings.json or .mcp.json were
triggering interactive approval dialogs even when approval mode was
set to YOLO, blocking the CLI. The MCP gating system operated
independently from the YOLO bypass in permissionFlow.ts.

Three-point fix:
- Boot-time: skip pendingMcpServers computation in YOLO mode so all
  MCP servers connect immediately (config.ts)
- Startup dialog: return empty pending list when YOLO is active
  (useMcpApproval.ts)
- Hot-reload: suppress pending computation and McpPendingApprovalChanged
  event emission when YOLO is active (hot-reload.ts)

Fixes #6131

Co-authored-by: Qwen Code Autofix <autofix@qwen-code.ai>
2026-07-02 09:50:41 +00:00
qqqys
ca61d7827e
feat(channels): add identity and task lifecycle metadata (#6105)
* chore: ignore local worktrees

* docs(channels): design identity and task lifecycle p0

* docs(channels): plan identity and task lifecycle p0

* feat(channels): add identity and task lifecycle metadata

* fix(channels): suppress cancelled tool call lifecycle

* fix(channels): cover loop lifecycle metadata

* fix(channels): harden lifecycle event edges

* fix(channels): finalize cancelled lifecycle before cleanup

* fix(channels): address lifecycle review suggestions

* fix(channels): close lifecycle cancellation races

* fix(channels): separate pending cancel state

* fix(channels): order clear cancellation lifecycle

* fix(channels): suppress loop chunks during pending cancel

* test(channels): use active session in cancel regression

* fix(channels): sanitize lifecycle tool fields

* fix(channels): route shared tool call lifecycle

* fix(channels): preserve pending cancel intent

* fix(channels): preserve responses after failed cancel

* fix(channels): tighten lifecycle cancellation reasons

* fix(channels): close lifecycle cancel races and validate identity config

Address the outstanding review findings on #6105:

- carry a typed reason on ChannelLoopSkippedError and report disabled
  loops as 'dropped' instead of 'timeout'
- treat a turn as committed once delivery starts: /cancel re-checks
  deliveryStarted after the cancel RPC settles, and neither prompt path
  lets a late-settling cancel rewrite a delivered turn into cancelled
  (or follow a /clear cancellation with completed)
- tag failed lifecycle events with phase: agent vs delivery
- validate identity/memoryScope shape at config parse time instead of
  throwing an opaque TypeError on the first prompt of every session
- guard onPromptStart hooks, pass job.id to loop onPromptStart/End,
  append the boundary block after operator instructions, gate
  /who + /status identity lines on configured identity/memoryScope,
  cache the boundary prompt, and route error logs through
  lifecycleError()

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

* fix(channels): keep failed terminal event when cancel settles post-delivery

Self-review follow-up: once delivery started, the catch paths no longer
reconcile a pending cancel — a late-resolving cancel RPC used to flip
cancelled=true there, suppressing the failed emit while the /cancel
handler (seeing deliveryStarted) also declined to emit, leaving a
started task with no terminal lifecycle event at all.

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

* fix(channels): hold streamed chunks while a cancel is pending

Chunks arriving while a /cancel RPC is in flight were pushed straight
into the BlockStreamer, which can send a block on a size/paragraph
threshold before the cancel resolves — leaking output a successful
cancel can never recall. Hold the pending-window chunks instead: replay
them (block streaming + text_chunk transcript) when the cancel fails,
discard them when it succeeds. onResponseChunk stays live through the
window so adapter-accumulated display state has no permanent hole on a
failed cancel; adapters gate visible updates on their own stop flags.

Also stop passing the loop job id to onPromptStart/onPromptEnd (and the
/clear eviction path): the hook contract is inbound platform message
ids, and adapters act on them — cards and reactions keyed to a fake id.
Lifecycle events still carry job.id for correlation.

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

* fix(channels): defer adapter chunks while cancel is pending

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-02 09:04:07 +00:00
易良
489bea9771
fix(release): reduce npm package scan triggers (#6164)
* fix(release): reduce npm package scan triggers

* fix(release): remove browser MCP dev dependencies

* test(serve): stabilize CDP tunnel acceptance startup

* fix(serve): remove unused chrome devtools MCP helper

* fix(serve): remove unused path import

* fix(serve): address CDP MCP review comments
2026-07-02 08:06:43 +00:00