Commit graph

7010 commits

Author SHA1 Message Date
Shaojin Wen
e35a08645a fix(web-shell): i18n for SubAgent Result/Tools tab labels
Replace hardcoded "Result" and "Tools (N)" strings in SubAgentPanel
with t() calls so they render correctly in non-English locales.
Add subagent.result and subagent.tools keys for en and zh-CN.
2026-07-08 12:03:15 +08:00
Shaojin Wen
001d20ff26
feat(scheduled-tasks): run each task in its own dedicated, named session (#6389)
* feat(scheduled-tasks): run each task in its own dedicated, named session

Scheduled tasks created through the Web Shell management page were never firing
in the daemon-only case: the durable-cron tick runs inside an active agent
session, and the Web Shell creates a session only lazily on the first prompt, so
a task created on the management page (with no chat open) had nothing ticking it.

This binds every management-page task to a dedicated session, minted at create
time and named " <task>". The task fires ONLY inside that session — its
transcript is the task's run history — instead of via the shared per-project
durable owner. A daemon-side keepalive heartbeats those sessions so the idle
reaper doesn't stop them, and a boot-time rehydration reloads them after a
restart. Archiving, deleting, or unarchiving the session disables, removes, or
re-enables the bound task (covered on both the REST and ACP surfaces).

Also adds task editing, a live next-run countdown, run history, a one-per-row
card layout, and a "run now" that executes in the task's bound session and
updates the last-run time. All resident-session management is opt-in and enabled
only by the real daemon (runQwenServe), so createServeApp embeds/tests are
unaffected.

* fix(scheduled-tasks): address code review on per-session task feature

Review fixes for #6389:

- Distinguish archive-disabled from user-disabled tasks: disableTasksForSessions
  now marks disabledByArchive; enableTasksForSessions only re-enables tasks
  carrying that flag, so a task the user deliberately disabled stays disabled
  across an archive/unarchive cycle. [Critical]
- Rehydrate task sessions concurrently with a per-session 30s timeout so one
  hung loadSession can't stall the boot sweep or leave healthy tasks dormant.
  [Critical]
- Await runScheduledTask + reload before executing the prompt in handleRunNow,
  so a record failure surfaces and the card's "last run" reflects the trigger.
  [Critical]
- Log keepalive/rehydrate read + heartbeat failures at debug instead of
  swallowing them silently, so a persistently-failing keepalive is diagnosable.
  [Critical]
- Add integration tests: deleteDaemonSessions -> removeTasksForSessions and
  unarchiveDaemonSessions -> enableTasksForSessions (guard the coupling). [Critical]
- DELETE route: single atomic updateCronTasks that captures the bound session
  and removes the task in one cycle, closing the read-then-remove TOCTOU.
- Stop the keepalive timer during shutdown (matters for embedders that don't
  process.exit) so it can't fire against a disposed bridge.
- Deduplicate DEFAULT_BUILDER: export it once from scheduledTasksSchedule and
  drop the dialog's copy so the create form and cron-reversal can't drift.
- Reject empty-string sessionId in isValidTask: a bound task with "" would
  silently run unbound under the scheduler's truthy guard.

* fix(scheduled-tasks): isolate task sessions, fix catch-up/jitter/revive

Second review round (#6389):

- Force `sessionScope: 'thread'` when minting a task's session. The daemon's
  default scope is 'single', which attaches to (reuses) the shared workspace
  session — so a second task, or a task alongside an open chat, would bind to
  the same session, rename it, land runs in the wrong transcript, and close it
  on delete. Thread scope guarantees each task an isolated session. [Critical]
- Re-seat a recurring task's schedule anchor to now when a PATCH changes its
  cron (or flips one-shot→recurring), not just on re-enable. A bound task's
  catch-up runs on every file-watch reload, so a bare cron edit to an
  expression with an already-past slot would fire immediately on save. [Critical]
- Revive a non-resident bound session from the keepalive when its heartbeat
  fails (reaper let it go while disabled/archived, now re-enabled). Covers the
  unarchive and PATCH false→true paths uniformly and retries each interval, so
  a re-enabled task actually resumes instead of showing a live countdown that
  never fires. Best-effort, timeout-bounded, non-blocking. [Critical]
- Report `nextRunAt` using the scheduler's jittered fire time
  (`nextDurableFireMs`) instead of the bare cron boundary, so the UI countdown
  lines up with the real fire (the tick offsets each fire by up to the jitter
  window) rather than expiring early and advancing prematurely.

All four are mutation-verified. The cross-daemon double-fire on bound tasks
(same session live in two schedulers) is a separate, architecturally-invasive
fix (claim-then-fire on the durable file) tracked as a follow-up.

* fix(scheduled-tasks): sync bound session name on task rename

Create names a task's session after the task (` <name>`), but a later PATCH
that renamed the task (or edited the prompt of an unnamed task) left the
session's display name stale. The PATCH route now re-applies
`updateSessionMetadata` with the task's effective label whenever that label
actually changes — a bare cron/enabled edit does not touch the session.
Best-effort: a metadata failure doesn't fail the committed schedule change.
Mutation-verified.

* fix(scheduled-tasks): mirror run sessionId on client type; clarify server wiring

Review follow-up (#6389, qqqys):

- [Medium] `DaemonScheduledTaskRun` now mirrors the daemon's `CronTaskRun`
  `sessionId?: string`, so run-attribution the wire already sends isn't silently
  dropped by the client type (not surfaced in the UI yet; passthrough cast means
  no mapping change needed).
- [Nit] Comment the `app.locals.stopScheduledTaskKeepalive` set site, noting it
  follows the same convention as `fsFactory`/`boundWorkspace`/`acpHandle` and is
  read by the run-qwen-serve shutdown path (kept the convention rather than
  diverge to a one-off return value / declaration merge).
- [Nit] Comment the outer `.catch(() => {})` on rehydrate as intentional
  defense-in-depth (the function already handles read + per-session failures).

* fix(scheduled-tasks): couple archive/enable + record manual run only on enqueue

Two [Critical] review items (#6389, gpt-5-codex):

- PATCH re-enable coupling: reject `enabled: true` on a task disabled BY
  archiving its session (`disabledByArchive`) with 409 `task_session_archived`.
  Re-enabling it here would show an enabled task with a countdown while its
  bound session stays archived and can never fire — the caller must unarchive
  the session (which clears the marker and reloads it). A user-disabled task
  (no marker) and non-enable edits are unaffected.

- Manual "run now" ordering: record the run only AFTER the prompt is enqueued,
  not before. `runTaskManually` now returns a promise that resolves on enqueue
  and rejects if the bound session can't be opened (archived/deleted), is
  superseded, or times out; the dialog awaits it before writing
  /scheduled-tasks/:id/run, so a failed session switch no longer leaves a
  phantom run in history. Runs are serialized (one pending at a time, button
  disabled) so two quick clicks can't drop a prompt on the single bound-run
  latch. Added coverage for failed session load and double-click; all new
  tests mutation-verified.

* fix(scheduled-tasks): close dormancy/orphan/overflow gaps from review

Five items from GPT-5 /review (#6389):

- [Critical] Bind tasks to sessions only when resident management is on:
  createServeApp now passes the bridge to the scheduled-task routes only when
  `manageScheduledTaskSessions` is set. Embedders that leave it off get UNBOUND
  tasks (shared-owner firing) instead of bound tasks nothing keeps resident or
  reloads (which would silently go dormant).
- [Critical] Keep the keepalive/revive loop running whenever task sessions are
  managed, not only when a reaper is active — archiving closes a task session,
  so a re-enabled one still needs reviving with the reaper disabled. Size the
  interval under the reaper window (≤ half of it) so a small idle timeout can't
  let a session be reaped before its first heartbeat.
- [Critical] Record a manual run only after the prompt is admitted: the bound
  run latch now resolves only if `sendPrompt` admitted the prompt and rejects on
  cancellation (e.g. onSubmitBefore) / failure, so a cancelled Run now no longer
  advances lastFiredAt or appends history.
- [Critical] Clamp the dialog's reload timer to the 32-bit setTimeout ceiling
  (~24.8 days) so a months-away schedule can't overflow and spin a reload loop.
- [Suggestion] Pre-check the task cap before spawning a session, so an over-cap
  create never mints an orphan task session it must roll back.

New tests (route unbound-when-no-bridge, cap-no-spawn, computeKeepaliveIntervalMs
bounds, far-future timer clamp) mutation-verified; full server suite green.

* fix(scheduled-tasks): guard catch-up double-fire, run-now hang, /run + cron edits

Four items from GPT-5 /review (#6389):

- [Critical] Bound-task catch-up could double-fire: detection ran on every
  file-watch reload and read the stale on-disk lastFiredAt, so a reload racing
  the async catch-up persist (a foreign write to the tasks file) re-detected and
  re-fired the same overdue slot. Track ids whose catch-up was DELIVERED but not
  yet persisted (`deliveredCatchUp`) and skip re-detecting them until the write
  lands; a merely-buffered-then-dropped catch-up isn't tracked, so it still
  re-detects from disk (recovery preserved).
- [Critical] "Run now" hung the full 30s switch timeout when the bound session
  was ALREADY the current, loaded one (no dep change → the consuming effect
  never re-ran). Fire the enqueue directly after loadSidebarSession resolves as
  well as from the effect; whoever runs first nulls the latch, so it runs once.
- [Critical] POST /run recorded a run with no enabled/disabledByArchive guard,
  unlike PATCH — a direct API caller could write a phantom "ran" record onto a
  paused/archived task. Return 409 task_disabled for a disabled task.
- [Suggestion] Anchor re-seat on cron edit compared the raw string, so a
  cosmetic change (`0 9 * * *` → `00 9 * * *`) dropped a pending catch-up.
  Compare the canonical (parsed) schedule instead.

(The setTimeout-overflow and keepalive-floor reports were already fixed in
2a12cba.) New tests for the first three + the cosmetic-cron case are
mutation-verified; full core scheduler + route suites green.

* fix(scheduled-tasks): block disabled-task run in UI; record manual run at admission

Two [Critical] review follow-ups (#6389):

- A disabled task could still EXECUTE from the Web Shell: the Run button was
  only gated on `runningTaskId`, so clicking it enqueued the prompt and the
  server's `/run` `task_disabled` guard merely refused the later history write —
  a real, unrecorded run. Gate `handleRunNow` and disable the button on
  `!task.enabled` too, so a disabled task's prompt is never enqueued.
- Manual run recorded only after the whole turn: the bound-run latch resolved
  via sendPrompt, which completes through waitForAcceptedPromptCompletion, so a
  long/permission-blocked run or a closed tab could execute without ever being
  recorded. Add an `onAdmitted` callback to sendPrompt (fired when the daemon
  accepts the prompt, before the turn) and resolve the manual-run latch at
  admission instead — cancellation before admission still rejects.

New dialog test (disabled task → no enqueue) mutation-verified; webui/web-shell
typecheck + existing session-action tests green.

* fix(scheduled-tasks): guard tick double-fire, cap rehydration, harden lifecycle writes

Review follow-ups (#6389):

- Extend the fire-persist re-detection guard to ON-TIME tick fires, not just
  catch-ups (renamed deliveredCatchUp → firePersistPending): a bound task fired
  by the tick advances lastFiredAt asynchronously, so a reload racing that write
  (bound detection runs every reload) could re-detect the slot and double-fire.
  The tick persist now adds its ids to the guard and clears them when the write
  lands, symmetric to the catch-up persist.
- Bound boot-rehydration concurrency (batches of 4): each loadSession forks a
  child, so loading up to 50 at once spiked the host and risked spawn failures
  that strand tasks. The keepalive revive path was already sequential.
- Archive disable failure is now logged (was fully swallowed) so a broken
  archive→pause coupling — where the keepalive would revive the just-archived
  session — is diagnosable.
- Unarchive re-enable failure is surfaced in the result `errors` and logged, and
  enableTasksForSessions also runs for already-active sessions — so a task left
  stranded ({enabled:false, disabledByArchive:true}) by a prior failed enable is
  recoverable by re-unarchiving, instead of being permanently stuck.
- Create rollback now removes the persisted session (close + removeSession), so
  the loser of a concurrent create at the cap boundary (passes the pre-check,
  loses the authoritative write) doesn't leave an orphan named session.

New tests (tick-fire guard, bounded rehydration, already-active recovery)
mutation-verified; full core scheduler + serve suites green.

* fix(scheduled-tasks): one-shot run/edit correctness; tick persist non-regression

Review follow-ups (#6389, ci-bot):

- [Critical] Manual /run on a ONE-SHOT task now removes it from the store. Its
  slot is still in the future, so stamping lastFiredAt=now didn't stop the
  scheduler firing it again at its original time — a double run. A one-shot's
  manual run IS its single fire, so the task is spent.
- [Critical] PATCH recurring:false now re-seats the one-shot's createdAt anchor.
  The old (long-past) anchor made the scheduler read it as a MISSED one-shot and
  fire + permanently delete it. Re-seating createdAt points its next fire at the
  upcoming occurrence. Also covers a cron edit on an existing one-shot.
- [Suggestion] The tick persist no longer regresses lastFiredAt: it skips the
  write when the on-disk stamp is already >= the tick slot (a concurrent manual
  /run or catch-up may have stamped newer), mirroring the catch-up persist guard.
- [Suggestion] Added the missing create-rollback test: a post-spawn commit
  failure closes AND removes the minted session (no orphan).

New tests (one-shot run removal, recurring→one-shot re-seat, rollback teardown)
mutation-verified; core scheduler + route suites green.

* fix(scheduled-tasks): ref-count fire guard, real rehydration cap, authoritative run check

Four [Critical] review follow-ups (#6389):

- Ref-count firePersistPending (was a boolean Set): the same task can have two
  lastFiredAt persists in flight (fired again before the first write landed);
  clearing on the first settle dropped the guard while the second was still
  pending, re-opening the double-fire window. The count holds it until the last
  persist settles.
- Rehydration concurrency is now enforced on the REAL loads: loadSession isn't
  abortable, so a timed-out load kept forking in the background while the next
  batch started. A bounded worker pool holds each slot until the underlying load
  actually settles, so in-flight child spawns never exceed the cap.
- Unarchive recovery reports failures for the full resume set: it enables both
  unarchived AND already-active sessions but only logged/returned errors for
  unarchived, so a failed already-active recovery surfaced errors:[] and left a
  task stranded. Deduped one list used for the call, log, and errors.
- Manual "run now" re-checks server-authoritative state before enqueuing: the
  dialog snapshot can be stale (another tab/API disabled/deleted the task), so
  it would execute the prompt and only the /run record would 409. It now
  refreshes, bails if gone/disabled, and enqueues the FRESH prompt/session.

New tests (ref-count, slot-held-past-timeout, stale-disabled re-check)
mutation-verified; core scheduler + serve + dialog suites green.

* fix(scheduled-tasks): catch-up non-regression, disabled-edit re-seat, run/timer/keepalive hardening

Review follow-ups (#6389):

- [Critical] Catch-up persist no longer regresses lastFiredAt: use `>=` like the
  tick persist, so a newer stamp (a cross-process manual /run) landing while the
  catch-up write is in flight isn't overwritten back to the older minute.
- [Critical] The PATCH anchor re-seat now runs for schedule edits even while the
  task is disabled — editing a disabled one-shot's cron then re-enabling it (two
  separate requests) no longer leaves a stale anchor that fires + deletes it.
- [High] Manual "run now" of a bound ONE-SHOT consumes it server-side (/run,
  which deletes) BEFORE enqueuing, so a record failure leaves a recoverable
  "recorded but never ran" instead of a silent double execution at its slot.
- [Medium] The dialog reload timer backs off a stuck past-due nextRunAt (fast
  reloads to catch a just-fired advance, then a slow lane) instead of spinning a
  1 Hz GET loop.
- [Medium] The manual-run latch bounds the admission phase with a timeout, so a
  send that wedges before admission degrades to a visible "run failed" instead
  of freezing the run controls.
- [Suggestion] Keepalive: an in-flight guard skips a tick while the previous
  pass runs (no duplicate concurrent loadSession spawns), and per-session
  exponential backoff stops retrying a permanently-gone session every interval.

New tests mutation-verified. Two deeper items (a task session winning the
durable lock and firing unbound tasks; tearing down a consumed one-shot's
session) are left open as tracked follow-ups — both need new daemon↔child
infrastructure.

* fix(scheduled-tasks): one-shot anchor on unarchive, memoize next-fire, sanitize + log

Review follow-ups (#6389):

- [Critical] enableTasksForSessions now re-seats a ONE-SHOT's createdAt anchor
  (not just recurring's lastFiredAt) on unarchive — otherwise unarchiving a task
  that was converted to recurring:false while disabled fires it as a missed
  one-shot and permanently deletes it.
- [Critical] Log the DELETE-path removeTasksForSessions failure (was fully
  swallowed) like the archive/unarchive paths — the session is already gone, so
  a silent write failure leaves the still-enabled bound task a permanent ghost.
- [Medium] Memoize nextDurableFireMs (deterministic per id/cron/recurring/anchor)
  — a sparse cron costs hundreds of ms per scan and the route recomputed it per
  task on every request, stalling the event loop for 50 yearly tasks.
- [Nit] The consumed one-shot /run response now nulls nextRunAt (it was
  advertising a future fire on an entity the next GET omits).
- [Suggestion] scheduledTaskSessionName strips terminal control sequences (the
  bridge title guard rejects them → silently drops the rename) and truncates on
  a code-point boundary (no lone surrogate broadcast as U+FFFD).
- [Critical/doc] Document that firePersistPending is instance-scoped — the
  narrow cross-instance restart window is an accepted edge.
- Added the missing test for editing an enabled one-shot's cron.

New tests mutation-adjacent; suites green. Two deeper items (session deleted
outside the daemon orphaning a bound task; surfacing bound tasks in cron_list)
are left open as tracked follow-ups.

* fix(scheduled-tasks): re-seat one-shot anchor on re-enable; guard duplicate revive

Two [Critical] review follow-ups (#6389):

- Re-enabling a one-shot now re-seats its createdAt anchor (added justReEnabled
  to the one-shot branch). A one-shot disabled past its slot then re-enabled was
  otherwise read as a missed one-shot on the next reload — fired immediately and
  permanently deleted. Updated the prior "leaves anchor untouched" test to the
  safe behavior (fires at next occurrence).
- Keepalive revive no longer spawns a duplicate child: loadSession isn't
  abortable, so a timed-out revive keeps running; a later tick (past its backoff)
  would start a SECOND load for the same session. An in-flight `reviving` set
  (cleared on the load's TRUE settlement, not the timeout) blocks that — without
  holding the sequential tick, so other sessions' heartbeats aren't delayed.
  Added a configurable reviveTimeoutMs for the test.

Both mutation-verified. (The one-shot /run session teardown raised again is the
same item as the open deferral — a synchronous close there would break the run,
which executes after /run; it's tracked for the keepalive orphan-sweep.)

* fix(scheduled-tasks): strip bidi override/isolate chars from session name

The bridge's title guard (hasControlCharacter) only rejects C0/DEL, so
Unicode bidi override/embedding/isolate controls (U+202A–202E, U+2066–2069)
slip past it and can visually reorder a scheduled-task session name in the
session list — a Trojan-Source-style attack (CVE-2021-42574). Strip them
alongside the existing terminal-control-sequence pass, matching core's
stripDisplayControlChars canonical set.

Adds a test built from code points so the test file itself carries no
reordering controls.

* fix(scheduled-tasks): close review findings — rehydrate deadlock, manual-run recording, shared helpers, tests

Addresses the review findings on the per-task-session work:

- keepalive rehydrate no longer awaits a non-abortable loadSession after its
  timeout. A genuinely hung load would pin its worker and, with enough hangs,
  wedge the whole boot sweep (Promise.all never settles) so later task sessions
  never rehydrated. The worker now records the timeout as failed and pulls the
  next queued session; the background load is left to settle. Rewrote the test
  that pinned the old "hold the slot" behavior into a no-wedge regression guard.
- web-shell manual run drops its pre-admission timeout. sendPrompt isn't
  abortable, so rejecting on the timer while the send was still in flight let a
  LATE admission execute an UNRECORDED run the user could retry into a
  duplicate. The run is now tied to admission (accepted prompts are always
  recorded); the "session never becomes active" phase stays bounded by the
  switch timeout in runTaskManually.
- extract collectBoundSessionIds() shared by the heartbeat + rehydrate passes
  (was duplicated) and isBoundTask() in the lifecycle module (was the lone
  `sessionId !== undefined` check vs. the strict one used everywhere else).
- spell the nextDurableFireMs cache-key separator as `\x00` rather than a
  literal NUL byte, so cronScheduler.ts no longer reads as binary to ripgrep.
- add App.test coverage for the manual-run orchestration (admission-resolve,
  cancel/error reject, immediate fire, supersede, switch timeout) and a
  keepalive test that a disabled task gets no heartbeat and no revive.

* fix(web-shell): "create via chat" opens a fresh session in scheduled tasks

The scheduled-tasks "Create via chat" button switched to the chat view but
stayed on the CURRENT session, piling the task-creation conversation onto
whatever the user was already doing. It now starts a new session first
(createNewSession) and jumps to it before priming the composer, so task
creation gets its own chat. Covered by a new App.test case asserting
clearSession() is called.

* fix(scheduled-tasks): address follow-up review findings

- keepalive rehydrate: guard the onError callback with try/catch. If it threw
  (e.g. stderr EPIPE during log rotation) the rejection escaped loadOne, failed
  its worker, and short-circuited Promise.all — stranding every other queued
  session.
- cronScheduler catch-up: use the strict `typeof sessionId === 'string' &&
  length > 0` bound-check instead of `!== undefined`, matching every other
  "is bound?" site.
- server rehydration: log the outer defense-in-depth catch instead of swallowing
  it, so an unexpected throw isn't a silent "tasks never fire".
- session-name sanitizer: also strip the standalone Bidi_Control marks U+061C /
  U+200E / U+200F, not just the override/isolate ranges.
- scheduled-tasks dialog: when a consumed one-shot then fails to deliver, show a
  specific "deleted but never ran — recreate it" error instead of the generic
  "run failed" that hid the deletion. Kept the deliberate consume-first ordering.

* fix(web-shell): don't prime the composer when "create via chat" can't start a new session

onCreateViaChat's deferred composer-priming ran unconditionally: if
createNewSession() failed, the task-starter text was dropped into the CURRENT
session (only onSessionIdChange was gated on success). Gate all post-create
side effects on `created`, matching handleMissingSessionNewSession. Adds an
App.test failure-path case (new session fails → composer not primed).

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-07 06:22:36 +00:00
han
9ee8546a60
fix(shell): avoid Unix pager default on Windows (#6390)
* fix(shell): avoid Unix pager default on Windows

* fix(shell): clear inherited pager env on Windows

* docs(shell): clarify platform-specific pager default

* fix(shell): normalize pager env handling

* fix(shell): preserve git pager fallback behavior

* test(shell): stabilize pager env coverage
2026-07-07 06:16:18 +00:00
Dragon
067cfbba62
docs: consolidate design docs and plans under docs/ (#6417)
Design docs and implementation plans were scattered across .qwen/design,
.qwen/plans, and docs/superpowers. The .qwen/ locations are git-ignored, so
docs written there never got tracked, while docs/design already held the
richer, version-controlled set. Consolidate everything under docs/design and
docs/plans, relocate two stray root docs into docs/design, and repoint the
references left dangling by the move (moved-doc cross-links and a few source
comments).

Also update AGENTS.md and the feat-dev skill so the documented workflow writes
new design docs and plans to the tracked docs/ locations.

Co-authored-by: DragonnZhang <dragonzhang1024@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 06:05:05 +00:00
Aleks-0
4c884e47bd
fix(core): prevent KV-cache invalidation on tool_search by reordering reminderParts (#6420)
Reorder reminderParts in getInitialChatHistory() so stable parts (MCP instructions, skills snapshot, startup context) come first and volatile deferred-tools reminder is last — prefix-caching servers retain the KV-cache for the shared prefix, only the tail recomputes.

Co-authored-by: Aleks-0 <aleks-0@users.noreply.github.com>
2026-07-07 06:02:28 +00:00
VectorPeak
46bbe76835
fix(core): align monitor limit parameter schemas (#6413)
* fix(core): align monitor limit parameter schemas

Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>

* test(core): cover monitor fractional limit validation

Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>

---------

Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
2026-07-07 05:21:18 +00:00
易良
ce00e932ae
fix(core): allow rewind after compressed history (#6358)
* fix(core): allow rewind after compressed history

* fix(core): separate compressed prefix rewind handling

* fix(core): handle rewind after restored startup context

* test(core): cover compressed rewind sentinel mismatch

* fix(cli): align rewind mapping after compression
2026-07-07 05:21:16 +00:00
易良
132801b739
feat(core): add maxSubAgents setting to limit parallel sub-agent count (#6354)
* feat(core): add maxSubAgents setting to limit parallel sub-agent count

Adds a `maxSubAgents` configuration option that limits the number of
sub-agents running in parallel. Excess agents are queued without
timeout countdown until a slot becomes available.

Closes #5176

* fix(core): apply sub-agent concurrency cap to foreground runs

* fix(core): narrow sub-agent concurrency scope

* fix(core): clarify invalidated slot reservations

* fix(core): correct sub-agent slot accounting

* fix(core): drain silent cancellation waiters

* test(core): cover background slot reservation paths
2026-07-07 05:10:56 +00:00
jinye
ff317d61cf
perf(core): Add session start profiler (#6349)
* perf(core): Add session start profiler

Add an opt-in internal profiler for GeminiClient.startChat so session initialization can be broken down by bounded stages before choosing the next #6312 optimization.

The profiler writes best-effort JSONL records only when QWEN_CODE_PROFILE_SESSION_START=1 and avoids sensitive values such as prompts, paths, session IDs, hook output, model responses, and tool names.

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

* fix(core): Keep session profiler finish best-effort

Wrap session-start profiler finish metadata collection in the same best-effort boundary as record writes, and cover repeat finish plus sync failure handling in tests.

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

* test(core): Cover profiler review suggestions

Deduplicate startChat profile finalization attributes and add coverage for repeated stage duration accumulation.

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

* test(core): Cover profiler failure paths

Strengthen session-start profiler tests for first-failure tracking and startChat sync-stage failure finalization.

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

* fix(core): Harden session profiler output

Restrict session-start profiler JSONL output permissions and add review-requested tests for optional fields and first-stage warm failures.

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

* test(core): Reuse session profiler env constant

Use the profiler env constant in the JSONL test so the test cannot drift from the runtime gate.

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

* test(core): Cover profiler timing edge cases

Add coverage for fractional session profiler rounding and the absence of session context application timing when SessionStart returns no additional context.

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

* test(core): Cover non-zero profiler counts

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

* codex: address PR review feedback (#6349)

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

* codex: address PR review feedback (#6349)

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

* codex: address PR review feedback (#6349)

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

* codex: address PR review feedback (#6349)

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

* codex: address PR review feedback (#6349)

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

* codex: address PR review feedback (#6349)

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

* codex: address PR review feedback (#6349)

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

* test(core): cover disabled session profile env values

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-07 05:05:43 +00:00
AlexHuang
06cd7ce13f
feat(cli): add --project and --global flags to /model for per-project model persistence (#6060)
* feat(cli): add --project and --global flags to /model for per-project model persistence

Add scope control to the /model command so users can persist model
selections to either project-level or user-level settings independently.

- /model --project: persist to workspace .qwen/settings.json
- /model --global: persist to user ~/.qwen/settings.json
- /model (no flag): unchanged behavior (backward compatible)
- Model dialog title shows scope: 'Select Model (this project)' / 'Select Model (global)'
- Completion and argumentHint updated with new flags
- Full i18n support for zh/en

Closes #6052

Signed-off-by: Alex <alex.tech.lab@outlook.com>

* fix(cli): add missing zh-TW translations for /model scope flags

Signed-off-by: Alex <alex.tech.lab@outlook.com>

* fix(cli): address PR review — scope flags, subcommand persistScope, titles, tests

- parseScopeFlags: use (?:^|\s) instead of \b for --flag matching
  (\b fails because - is not a word character)
- Completion: strip all flags to isolate model prefix, supports any order
- Subcommand dialogs (fast/voice/vision) now propagate persistScope
- slashCommandProcessor forwards persistScope for all subcommand cases
- ModelDialog title combines subcommand mode + scope label
  e.g. 'Select Fast Model (this project)'
- Subcommand confirmations show scope suffix (project/global)
- Extract persistScopeSpread() helper to reduce duplication
- Add 9 tests covering scope flags, dialog returns, confirmations
- Add i18n keys for scope suffix labels in zh/en/zh-TW

Signed-off-by: Alex <alex.tech.lab@outlook.com>

* fix(cli): use Partial<Config> & {[key:string]:unknown} to fix index signature TS error

Replace Record<string,unknown> with Partial<Config> & {[key:string]:unknown}
to satisfy TS4111 index signature access rule in the CI build.

Signed-off-by: Alex <alex.tech.lab@outlook.com>

* fix(cli): add scope suffix to ModelDialog history items

Address review comment: historyManager.addItem for voice/fast/vision/main
model selections now shows scope indicator like ' (this project)' or
' (global)', consistent with CLI direct-set confirmations.

Affected: handleModelSwitchSuccess (main), handleSelect (voice/fast/vision)
Signed-off-by: Alex <alex.tech.lab@outlook.com>

* fix(cli): wrap scopeSuffix in t() and unify wording with ModelDialog

- scopeSuffix in modelCommand.ts now uses t(' (this project)') / t(' (global)')
  instead of hardcoded English strings, matching ModelDialog.tsx wording
- Main model confirmation uses shared scopeSuffix instead of separate
  i18n keys, eliminating 'Model: {{model}} (project)' duplication
- Remove unused i18n keys from en/zh/zh-TW locales
- Update tests to expect '(this project)' wording

Signed-off-by: Alex <alex.tech.lab@outlook.com>

* fix(cli): address code review feedback — scope validation, i18n, tests

- Reject inline prompt + scope flag combination with clear error (#1)
- Add mutual exclusivity check for --project and --global (#5)
- Verify setValue scope parameter in tests + add --global test (#2)
- Extract scopeSuffix to shared variable, remove duplication (#3)
- Remove dead i18n keys 'Select Model (this project)' / '(global)' (#4)
- Fix scopeSuffix placement on model line not API key line (#8)
- Add fr.js / ja.js translations for scope keys (#10)
- Remove unused export ModelDialogPersistScope (#6)
- Wrap non-interactive help text in t() with new flags (#7)
- Fix argumentHint grouping to show mode vs scope flags (#11)

Signed-off-by: Alex <alex.tech.lab@outlook.com>

* fix(cli): reject --project when workspace is untrusted

Reject --project scope flag before direct persistence or opening ModelDialog
when settings.isTrusted is false. Workspace settings are ignored on merge in
that state, so the save would silently not take effect.

Also mirrors the guard in ModelDialog.tsx resolvePersistScope() to fall back
to user scope when the dialog is opened with --project on an untrusted folder.

Default mock settings now includes isTrusted: true.

Signed-off-by: Alex <alex.tech.lab@outlook.com>

---------

Signed-off-by: Alex <alex.tech.lab@outlook.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-07 04:49:36 +00:00
jinye
1ee9780223
fix(core): Gate large PDF text extraction (#6409)
* fix(core): Gate large PDF text extraction

Prevent text-only PDF fallback from injecting full large-document extraction results into the prompt. Large attachment reads now become short references, direct no-pages reads return a short file-too-large error, and page-range extraction is token guarded.

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

* test(core): Stabilize large PDF reference test

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

* fix(core): Address PDF budget review feedback

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

* test(core): Stabilize PDF read-file test

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

* fix(core): Allow page-range reads for huge PDFs

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

* fix(core): Clarify PDF text truncation contract

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

* fix(core): Address PDF review follow-ups

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

* test(core): Cover multi-page PDF guidance

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

* fix(core): Harden paged PDF extraction guards

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

* fix(core): Restore authoritative PDF page-count gate

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

* fix(core): Keep large PDF references independent of pdftotext

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

* fix(core): Narrow dense PDF retry guidance

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-07 04:40:18 +00:00
MikeWang0316tw
db8e3448e3
fix(cli): smoother streaming table rendering (#6345)
* fix(cli): smoother streaming table rendering

Follow-up to the streaming table hold-back, on its own branch so the
cue-removal PR (#6340) can land undisturbed. Makes a live table stream
predictably instead of jittering, flashing, or hanging.

- Atomic rows: hold a frontier row back until it has ALL its columns. A
  multi-column row passes through intermediate states that are themselves
  valid rows with fewer cells (`| a |`, `| a | b |` toward `| a | b | c |`),
  so the old hold-back let it fill in cell by cell. Now the whole row
  (border + every cell) appears in one step.

- Widths track the current rows (no freeze): a wider row redraws the whole
  table once; a narrower row changes nothing (widths are a max over all
  rows, so they only ever grow). Redraw-on-wider only, never per token.

- Bias the streaming preview to the horizontal format: while a table is the
  live frontier it only falls back to the vertical `label: value` list when
  the terminal is genuinely too narrow, not because an early row wraps tall.
  This stops a table from briefly rendering as a vertical list and then
  flipping to a horizontal table (a visible jump).

- Hold a forming table back until it is recognizable: a header (and any
  partial separator) is trimmed while pending until a separator matching the
  header's column count arrives, so the header no longer streams in char by
  char as raw `| a | b |` text before snapping into a box. Fenced code-block
  content is left untouched.

- Draw the empty header box as soon as the table is recognized, before the
  first row completes, so the table area does not sit blank (no box, no cue)
  and look like a hang if generation stalls in that window. A zero-row box
  omits the header/body divider so it reads as a clean header, not an empty
  row.

Only the live frontier table is affected; completed and committed tables use
the normal logic. 211 tests pass (MarkdownDisplay + TableRenderer).

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

* fix(cli): guard the two remaining zero-row / non-table edge cases

Review follow-up (two [Critical] findings).

- TableRenderer: the maxLineWidth safety check is a second path to the
  vertical format, unguarded for zero-row tables. On a very narrow terminal
  a zero-row streaming header box would fall through it and render an empty
  string — the box vanishes. Skip that fallback when there are no rows so the
  header stays visible even if it slightly overflows.

- MarkdownDisplay: the pre-loop header hold-back trimmed ANY trailing run of
  pipe-leading lines. When the first line is not a complete `| … |` row,
  headerCells was 0 and the run was trimmed anyway — so non-table pipe text
  (an un-fenced shell pipeline `| grep foo`, pipe-prefixed log output) would
  vanish from the live preview until commit. Only hold back when the first
  pipe-line is a plausible table header (a complete row).

Tests cover both. 215 pass.

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

* fix(cli): hold a multi-column header mid-type without hiding pipe text

The previous commit (restricting the header hold-back to a complete `| … |`
row, to stop non-table pipe text from vanishing) reintroduced the cell-by-
cell header flash: while a header is typed (`| Alpha`, `| Alpha | Bet`, …)
it is not yet a complete row, so it rendered as raw text.

Discriminate by column count instead of closed-ness: a table header has ≥2
columns; a single-pipe line (shell pipeline `| grep foo`, pipe-prefixed log)
has one cell. Count cells on the first line whether or not it is closed, and
hold the run only when it has ≥2 columns and no matching separator yet. So a
multi-column header held mid-type no longer flashes, while single-pipe non-
table text still renders (the earlier [Critical] fix stands). A header still
typing its very first cell is indistinguishable from a single-pipe line, so
it shows briefly until the second column appears — the narrowest flash
possible without hiding real pipe text.

217 tests pass.

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

* fix(cli): make table format decision consistent, not streaming-biased

The horizontal-vs-vertical bias (force a live table horizontal while
streaming) backfired for tables that genuinely belong in the vertical
`label: value` format — a wide table with many columns of long, wrapping
text. It rendered horizontal (tall, clamped, looking stuck) while it was
the streaming frontier, then flipped to vertical the moment it stopped
being the frontier (the next block started) or committed — a visible
format flip, and worse than the vertical-list flash it was meant to avoid.

Drop the streaming bias: the horizontal-vs-vertical decision is now the
same while pending and once committed, so a table never flips format
between the two. Removes the now-unused isStreaming / isStreamingFrontier
plumbing.

Known residual (pre-existing, not from this change): because column widths
track content (redraw-on-wider), a borderline table's wrapped-row height
can still cross the vertical threshold mid-stream. Fully stabilizing that
needs a content-independent format decision — a separate change.

217 tests pass.

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

* docs(cli): note the redraw-on-wider format-oscillation trade-off

Document the accepted limitation next to the horizontal-vs-vertical
decision: because column widths track content (redraw-on-wider), a table
with very long cell text sitting right at MAX_ROW_LINES can still oscillate
format while streaming. Only extreme wide/long-text tables hit it; the
alternatives (content-independent decision, or frozen widths) each cost
more than the residual is worth.

Comment-only; no behaviour change.

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

* fix(cli): count held-back header columns like the table detector

The streaming hold-back counted header columns on the full line with
empty cells filtered out, while the main table detector strips the outer
pipes and splits without filtering. For a header with an empty-named
column like `| A || B |` the two disagreed (2 vs 3), so the hold-back
never found the matching 3-column separator and hid the table for the
whole stream. Count columns the same way in both places.

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

* fix(cli): release multi-cell non-table pipe content during streaming

The streaming hold-back keeps a run of pipe-lines back until a matching
separator arrives, so a real multi-column header does not flash in cell
by cell. But multi-cell non-table pipe content — a shell pipeline
(`| grep foo | wc -l`), a log excerpt (`| 200 | OK | GET /x`), an
ASCII-art border — also has >=2 cells, so it was held for the entire
stream and only appeared on commit.

A markdown table's separator is the line immediately after the header, so
once a line follows the header and does not even begin like a separator
(optional pipe, optional colon, then a dash), the run is decided: not a
forming table. Release it. A lone header still being typed (no line after
it yet) is still held, so the no-flash behavior is unchanged.

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

* fix(cli): anchor the vertical-format decision to the first row (no flip)

The horizontal-vs-vertical choice used maxRowLines measured over EVERY row,
so a table that started horizontal (short first row) flipped to vertical the
moment a later, taller-wrapping row streamed in — a visible mid-stream format
change. Measure only the header + the first data row instead. The first row is
representative for the common case, so the format is decided once and stays
put as rows append. Column widths still track all rows (redraw-on-wider is
unchanged); only the format choice is anchored.

Trade-off: a table whose first row is short but a later row wraps very tall
stays a (taller) horizontal grid rather than flipping to vertical — rare, and
preferable to a visible flip.

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

* fix(cli): release a dash-led data row from the streaming hold-back

The "could this pipe run still become a table?" check treated any line after
the header that merely started with a dash as a possible separator, so an
options table whose first data cell begins with a flag — `| --verbose | … |`
— was held back for the whole stream. Use tableSeparatorRegex instead: it
still matches a partial separator being typed (`|--`) so a real header is
held until its separator lands, but rejects a dash-led data cell (trailing
letters), which now renders live.

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

* fix(cli): defer a streaming table until its first row (no empty-box flip)

A recognized table with no complete data row yet was drawn immediately as an
empty header box. A zero-row table can only render horizontally (the vertical
fallback needs rows), so once a long first row landed the box flipped to the
vertical label:value format — a visible format change that cannot be avoided
by looking at the header alone (column names are short; width comes from the
values). Defer the table while pending until its first row completes, so it
first appears already in its final format with no flip.

Cost: the table area stays blank while the header + first row stream (the
pre-loop trim already hid the header text, so this only extends that blank).
Committed tables always have rows, so their behavior is unchanged.

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

* fix(cli): address review — code-fence tracking, held-back edge cases, committed format

Five review findings:

- [Critical] The pre-loop hold-back's code-fence check used a naive boolean
  toggle that ignored fence char/length, so a nested fence (```` with an inner
  ```) mis-closed and a real code line like `| A | B |` was held back and
  vanished while streaming. Track the open fence's delimiter and validate the
  close (same char, >= length), mirroring the main parser.
- A COMPLETE separator whose column count already differs from the header can
  never match, so release the pipe run instead of holding it for the whole
  stream (the main parser treats it as text).
- The end-of-content table flush now uses the same `tableRows.length > 0` guard
  as the mid-content handler, so a degenerate zero-row table behaves the same
  whichever way it ends — no EOF-vs-mid asymmetry.
- TableRenderer's first-row-only maxRowLines (no-flip) applied to committed
  tables too; a committed short-first-row + tall-later-row table wrongly stayed
  horizontal. Gate on a new `isPending` prop: measure the first row only while
  streaming, every row once committed (most readable, no flip concern).
- Renamed the test block that claimed a nonexistent `isStreaming` prop; added
  committed-vs-streaming format tests.

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

* fix(cli): don't flip a completed mid-content table's format at commit

A table closed by a following line is complete even while the message keeps
streaming, but it was still rendered with the first-row-only format anchor —
so a short-first-row + tall-later-row mid-content table showed horizontal and
then flipped to vertical the moment the message committed.

Split the two concerns that were both riding on `isPending`:
- the height clamp still tracks whether the MESSAGE is streaming (so a
  mid-content table stays bounded and the estimator's clamped cost can't
  under-estimate the render);
- the format anchor now tracks whether THIS TABLE is the streaming frontier.
  The mid-content flush passes isFrontier={false} → all rows measured → final
  format now; only the end-of-content (frontier) table anchors to the first row.

Renamed TableRenderer's format-anchor prop to `isStreaming` (it is not the
message-level pending flag). Added mid-content and tilde-fence tests.

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

* fix(cli): don't hold a pipe line inside an open $$ math block

The streaming table hold-back tracked code fences so a `| A | B |` code line
would render, but not display-math (`$$ … $$`) blocks. The main parser pushes
math content verbatim (never as a table), so a `| a | b |` norm/matrix line at
the frontier of an open math block was treated as a forming table and blanked
until the block closed. Track math fences in the trim's fence scan too, mirroring
the main parser's precedence (code block wins, then math), and skip the hold-back
while inside one. Addresses the low-confidence review observation on #6345.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 04:07:08 +00:00
callmeYe
686326863a
fix(web-shell): polish scheduled task timeline UI (#6386)
* feat(web-shell): mark scheduled task turns in timeline

* fix(web-shell): confine locate flash to message content

* fix(web-shell): flash parallel agent locate target

* fix(web-shell): keep scheduled marker source optional

* fix(web-shell): omit default scheduled timeline flag

* fix(web-shell): repair scheduled timeline UI conflict

* fix(web-shell): remove stale shell output prop

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-07 04:04:28 +00:00
Dragon
80340fb73f
fix(review): remove qwen-code-specific core-infra gate from bundled /review (#6412)
The bundled /review skill is a general command that runs against arbitrary
repositories (and cross-repo PRs), but a previous change baked qwen-code's own
"core infrastructure is maintainer-only" governance into the shipped prompt:
hardcoded packages/core and packages/*/src/{auth,providers,models,config,tools,services}
paths, a 500+ line hard block, and an authorAssociation-based maintainer check.
Those path names are generic — src/auth, src/config, src/tools, src/services are
common across monorepos — so an external contributor's large PR to an unrelated
repo would be hard-blocked as "must be maintainer-initiated" under a policy that
repo never adopted.

Remove the gate and its escalate-flag plumbing (Steps 1, 6, and 7) from the
bundled skill, along with the matching DESIGN.md rationale and the user-doc
section. qwen-code's maintainer-only policy stays documented in AGENTS.md for
this repo. The Issue Fidelity / root-cause ownership agent (Agent 0) is a
universal review principle and is left unchanged.

Co-authored-by: dragon <dragon@U-2Q53JQG9-0233.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 03:03:49 +00:00
Shaojin Wen
f41e95ac18
feat(web-shell): add Session Overview panel and in-window split view (#6400)
* feat(web-shell): add Session Overview panel and in-window split view

Add a large-screen "Session Overview" mission-control panel and an
in-window split view so users can monitor and drive multiple daemon
sessions at once.

- SessionOverviewPanel: ranked live cards (needs-approval -> running ->
  idle) merging the workspace session list with the detail=full status
  report. Multi-select opens the selected sessions as a split view in
  the current tab ("Open in split") or in a new browser tab ("Open in
  new tab", via a ?split=a,b URL).
- SplitView + ChatPane: one DaemonWorkspaceProvider hosting N
  DaemonSessionProvider panes, each a self-contained interactive chat
  (transcript, composer, streaming, tool/ask approvals). Browser focus
  scopes the keyboard per pane, so panes never contend over approvals.
- Sidebar entry points gated to large screens; the split view's Back
  returns to the Session Overview.

* refactor(web-shell): address review feedback on the session overview / split view

- SessionOverviewPanel: prune the selection Set when a session leaves the list
  (so a reappearing session isn't silently reselected) and make select-all use
  the intersection rather than prev.size.
- Extract isAskUserPermission into a shared util so App.tsx and ChatPane.tsx no
  longer keep verbatim copies that can drift.
- SplitView: dismiss the "add session" picker on Escape or a click outside it.
- Tests: MAX_PANES cap, popup-blocked path, checkbox-selects-without-navigating,
  stale-selection pruning, and a direct test for the extracted util.

* fix(web-shell): address /review findings on the split view

- ToolApproval: add a `keyboardActive` prop; split panes pass false so global
  Enter/Escape/digit shortcuts can't confirm the wrong session's approval, and
  the outer session's approval overlay is no longer rendered behind the split
  (where it would keep its global shortcuts while hidden).
- ChatPane: defer the composer commit until sendPrompt resolves, so a rejected
  prompt (transcript loading / disconnected / turn active) preserves the draft
  instead of silently dropping it.
- SplitView: include a per-mount nonce in each pane's clientId so two tabs
  opening the same split don't share a client id — which suppressOwnUserEcho
  would treat as a self-echo and drop from the transcript.
- SessionOverviewPanel: cap the split selection to MAX_SPLIT_PANES before
  building the ?split= URL or opening the in-window split, with a hint when more
  are selected; also dismiss the split picker on Escape / click-outside.
- Tests covering each.

* fix(web-shell): address second /review round on the split view

- SplitView: wrap each pane in its own ErrorBoundary, so a render crash in one
  pane (malformed block, unexpected tool shape) shows an inline fallback with a
  close action instead of white-screening the whole split.
- splitUrl / overview: carry the daemon token into the new-tab split URL's
  fragment. The current tab has already stripped the token from its URL, so a
  token-auth (`serve --open`) deployment would otherwise open the split tab
  unauthenticated. The token rides the hash (never sent to the server / logs).
- Tests: per-pane error isolation, token-in-fragment (and none without a token),
  and the overview polling effects (interval fires, document.hidden skips, and
  the in-flight guard prevents overlapping polls).

* fix(web-shell): hide the outer chat under the split and share app-level contexts

- App: hide (display:none) + aria-hide the outer chat subtree whenever
  mainView !== 'chat', not only when a panel is open. Previously the outer
  chat/composer/toolbar stayed reachable by keyboard/AT behind the full-page
  split (it was only covered visually). State is preserved (node stays mounted).
- App: wrap SplitView in the app-level WebShellCustomizationProvider and
  CompactModeContext so split panes render markdown / tool-headers / thinking
  the same way the single-session chat does. Todo contexts stay chat-only —
  they belong to the outer session, not the panes.

* refactor(web-shell): address review suggestions — coverage, dedup, split UX

- ToolApproval: add a dedicated test on the real component that the global
  keyboard shortcut is armed by default and NOT armed when keyboardActive=false
  (the cross-pane approval safety mechanism).
- SplitView: auto-exit to the Session Overview when the last pane is closed
  (guarded so an initial empty seed doesn't bounce straight back out).
- ChatPane: add tests for the cancel action, the empty/whitespace submit guard,
  and error routing to the onError prop.
- Extract the shared session-list page size + organization feature flag into
  constants/sessions.ts, used by the overview, split view, and sidebar, so the
  values can't drift between the three.

* fix(web-shell): surface outer approval + failed refresh in overview/split

- Split view: when the outer (main) session is waiting on an approval
  that's hidden behind the split, show a non-blocking notice banner with
  a "Go to it" button that returns to the chat where the approval lives.
- Auto-close the split (like the overview panel) when the viewport shrinks
  below the large-screen breakpoint, so users aren't stranded.
- Session Overview: surface a failed refresh inline (keeping the last-good
  cards) instead of silently swallowing it once cards are on screen.
- Tests: status-report poll cadence, picker dismiss (Escape / outside /
  inside click), inline refresh-failure banner.

* fix(web-shell): sever window.opener on split tab; tighten hidden-chat test

- openSelectedInNewTab now clears win.opener (the split tab carries a
  daemon token in its URL fragment) to prevent reverse tabnabbing, matching
  the existing bug-report window.open path.
- Strengthen the split-view App test so a missing outer-chat subtree fails
  instead of passing vacuously through an optional chain.

* fix(web-shell): split-view focus/stability/robustness follow-ups

- Refocus the composer after a shrink-driven split close so keyboard users
  aren't dropped onto <body> (skips when an approval or panel takes over).
- Stabilize SplitView onExit via useCallback so its last-pane-close effect
  doesn't re-fire on every App re-render.
- ChatPane: surface a per-pane connection-loss banner instead of silently
  showing stale messages when a pane's daemon connection drops.
- ChatPane: anchor the streaming timer to the active turn's start (last user
  message timestamp) so a pane opened mid-turn shows real elapsed time.
- Tests: split auto-close on shrink, outer-approval split notice + return-to-
  chat, connection banner, and streaming-timer anchoring.
2026-07-07 02:44:18 +00:00
顾盼
245defbb01
fix(core): gate image payload replacement behind threshold (#6380)
* fix(core): reduce multimodal history payload size

* fix(core): use kebab-case image payload filenames

* fix(core): address image payload review blockers

* fix(core): preserve current request image payloads

* ci: disable implicit actionlint pyflakes integration

* fix(core): reattach recent unique image payloads

* fix(core): preserve referenced image payloads

* fix(core): tolerate partial config mocks in MCP discovery

* fix(core): preserve current images during recovery

* fix(core): gate image payload replacement behind threshold

The always-on image payload replacement introduced by PR #6045
replaced ALL historical images with text references on every request,
causing users' old screenshots to be reattached and triggering
infinite fix loops when the model mistook stale buggy screenshots
for current state.

Replace the always-on approach with a threshold-gated design:
- Below 20 images (configurable): zero transformation, images stay
  in-place in history
- At or above 20: in-place replace historical images with text
  references, reattach only the most recent 3 unique images
- Replacement is persistent (mutates this.history), so the count
  resets and won't re-trigger until 20 new images accumulate
- Current user request images are protected via skipContent

Also lower DEFAULT_SCREENSHOT_TRIGGER_THRESHOLD from 50 to 20 to
align with the new image payload threshold.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-07 02:39:15 +00:00
lcheng
6352d97173
fix(memory): don't advance AutoMemory cursor when extractor makes zero tool calls (#6398)
Fixes #6311

The extract cursor previously advanced unconditionally after the
forked extractor agent reported 'completed', even when it made zero
real tool calls (e.g. a small/local model hallucinating a bash
command instead of calling write_file). This silently and
permanently skipped those history messages from being reprocessed.

Also fixes extractionAgentPlanner.ts using filesTouched (attempted
paths, unconfirmed) instead of filesWritten (confirmed successful
writes) when deriving touchedTopics, matching the pattern already
used in remember.ts.

touchedTopics.length > 0 alone is not sufficient to gate the cursor
advance: a legitimate 'nothing durable to save' outcome also produces
an empty touchedTopics array and would otherwise be treated the same
as a hallucinated run. A new hasToolActivity signal (derived from
filesTouched, which includes read-only calls like read_file)
distinguishes 'agent engaged with the task and found nothing new to
save' (legitimate noop, cursor still advances) from 'agent made zero
tool calls at all' (hallucination, cursor held for retry).
2026-07-07 01:44:02 +00:00
jinye
70220fb281
feat(cli): Add Phase 2a workspace foundation (#6410)
* feat(cli): Add Phase 2a workspace foundation

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

* test(cli): Clarify registry reload capability

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

* fix(cli): Reject valueless repeated workspace args

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

* fix(cli): Fallback for empty workspace fast path

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

* test(cli): Address workspace foundation suggestions

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

* test(cli): Cover registry injection happy paths

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

* fix(cli): Tighten workspace foundation guardrails

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

* test(cli): Cover injected client MCP registry path

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-07 01:40:01 +00:00
Dragon
bcdb44c5d3
docs(hooks): document PreToolUse permissionDecision 'ask' behavior (#6411)
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
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-06 22:18:49 +00:00
ytahdn
be7e874fd1
Handle missing web-shell sessions without redirecting (#6357)
* fix(web-shell): handle missing session routes

* chore(web-shell): clarify missing session route handling

* fix(web-shell): address missing session review follow-up

* fix(web-shell): address missing session review issues

* test(web-shell): cover missing session status handling

* fix(webui): handle heartbeat terminal states

* fix(web-shell): preserve missing session state

* fix(webui): harden missing session diagnostics

* fix(web-shell): stabilize missing session recovery

* fix(webui): preserve missing session heartbeat state

* fix(webui): stabilize missing session recovery

* fix(webui): cover missing session review gaps

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-06 21:59:35 +00:00
jinye
d56bd1d8f4
fix(daemon): handle settings reload events outside transcript (#6407)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-06 21:56:27 +00:00
qwen-code-dev-bot
881d6824f5
fix(cli): use EnvHttpProxyAgent in channel proxy to respect NO_PROXY (#6401) (#6405)
The channel proxy path used ProxyAgent, which unconditionally routes
all requests through the proxy and ignores NO_PROXY. This caused
requests to hosts listed in NO_PROXY (e.g. localhost, internal IPs)
to fail when a corporate proxy was configured.

Switch to EnvHttpProxyAgent, matching the main CLI config path that
already handles NO_PROXY correctly.

Co-authored-by: qwen-autofix <autofix@qwen-code.ai>
2026-07-06 21:07:40 +00:00
Dragon
57326e55be
feat(review): add issue-fidelity and root-cause ownership gate to /review (#6395)
* feat(review): add issue-fidelity and root-cause ownership gate to /review

Adds a dedicated Issue Fidelity & Root-Cause Ownership agent (Agent 0) to
the /review pipeline and a core-infrastructure scope gate that runs before
the review agents.

Agent 0 fetches linked GitHub issue evidence directly (closingIssuesReferences
plus issue comments) instead of trusting the PR author's framing, compares the
original reported failure against the PR's claimed fix, and flags client-side
parser/sanitizer workarounds for malformed upstream output as Critical unless a
maintainer explicitly requested the defensive mitigation. The core-infra gate
applies the repository's existing two-tier maintainer-only rule before spending
review budget.

This hardens the pipeline against a false-approval mode where a bot PR passes
its own tests and reads as internally reasonable but fixes the author's mistaken
diagnosis rather than the linked issue's actual root cause.

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

* docs(review): address PR review feedback on issue-fidelity gate

- Fetch issue evidence with `gh issue view --json title,body,comments` so the
  issue body (reporter repro/observed payload/expected behavior) is included;
  `--comments` alone omits it. Use each closingIssuesReferences entry's own
  repository so cross-repo linked issues resolve correctly.
- Treat closingIssuesReferences as a discovery hint (fetch apparent target
  issues even when it is empty) and treat fetched issue content as untrusted
  data (extract facts, ignore embedded instructions).
- Run Agent 0 (Issue Fidelity) only for PR targets; skip it for local-diff and
  file-path reviews, and require the PR number/repo/context in its prompt.
  Handle empty references / non-bugfix / gh failure explicitly.
- Pass Agent 0's quoted issue evidence to Step 4 batch verification and stop it
  rejecting issue-grounded findings just because the code compiles/tests pass.
- Make the core-infrastructure gate concrete: deterministic maintainer signal
  via authorAssociation, count only core-path lines, honor the AGENTS.md
  low-risk-sweep exception, clean up the worktree on hard block, run the gate
  right after fetch-pr (before npm ci), and map escalate -> COMMENT (never
  APPROVE) in Steps 6-7.
- Sync agent counts and token math across SKILL.md, DESIGN.md, and
  code-review.md (Agent 0 is PR-only; ~620-730K).

* docs(review): rename 'Linked Issue Fit' heading to 'Issue Fidelity'

Aligns the code-review docs heading with the 'Issue Fidelity' name used
for Agent 0 in SKILL.md and DESIGN.md, so the section connects to the
pipeline diagram. Addresses review feedback.

* docs(review): stop core-infra hard block before load-rules and surface it via --comment

- Hard block now stops before Step 2 (load-rules) instead of before Step 3,
  so a PR destined for hard-block no longer runs the load-rules step.
- In --comment mode the hard block posts an event=COMMENT on the PR, matching
  the escalate path's GitHub visibility, so external authors see the block.

---------

Co-authored-by: dragon <dragon@U-2Q53JQG9-0233.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 17:05:48 +00:00
ShiZai
ac123bbc59
fix(core): preserve no-argument tool calls that stream an empty arguments string (#6250)
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
* fix(core): preserve no-argument tool calls that stream an empty arguments string

For tools that take no parameters, some OpenAI-compatible providers
stream `arguments: ""` (or omit the field entirely) and never send an
argument fragment. The streaming parser dropped such calls wholesale
(`meta?.name && buffer.trim()`), while the non-streaming path keeps
them with `args: {}` — so a turn containing only that call looked
empty and geminiChat raised "Model stream ended with empty response
text", triggering pointless retries.

Align the streaming parser with the non-streaming path: emit the call
with empty args when the buffer is empty at stream end. Rewrite the
unit test that encoded the drop, and add regression coverage at parser
and converter chunk level.

* fix(core): use name metadata as slot-occupancy signal for no-argument tool calls

Follow-up to review feedback: after empty buffers became a legal end
state for no-argument tool calls, three parser methods still used
buffer.trim() to decide whether an index slot was occupied. A provider
reusing indices could then silently overwrite a completed no-argument
call (addChunk collision guard, findNextAvailableIndex) or append stray
continuation chunks to it (findMostRecentIncompleteIndex).

Switch the occupancy signal in all three places to the name metadata,
keeping the JSON-completeness check for non-empty buffers. Add
regression tests for both corruption paths and update the stale
getCompletedToolCalls JSDoc.

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

* fix(core): collapse non-object argument parses at emit and lock canonical empty-opener shape

Review follow-up on the no-ID continuation routing at addChunk. Mid-stream,
an empty buffer with name metadata is formally undecidable between "completed
no-argument call" and "canonical opener awaiting its first argument fragment"
(every OpenAI-compatible provider opens with arguments:"" and streams
fragments ID-less at the same index). Routing must favor the canonical shape,
so the guard stays; a new test pins that shape, which the suite previously
did not cover.

The corruption concern from review is instead bounded at emit time: a buffer
polluted by a stray fragment can parse or repair to a non-object value, which
now collapses to {} so a polluted no-argument call still emits empty args.

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

* fix(core): add debug logging for empty-buffer emission and non-object argument collapse

Review follow-up: a stray fragment that happens to parse as a valid JSON
object is indistinguishable from real arguments at emit time, so log both
the non-object collapse and empty-buffer emissions to aid diagnosis.

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

* fix(core): extend replay guard to opener-shaped replays of no-argument tool calls

Review follow-up: after empty buffers became a legal completed state, a
replayed opener (duplicate ID, #5107 lineage) could overwrite a completed
no-argument call's name metadata, since the replay guard only engaged on
non-empty buffers.

Swallowing every known-ID chunk at that state would drop ID-bearing
argument fragments for providers whose opener streams empty arguments, so
the guard uses the protocol shape as discriminator: a chunk carrying a
name but no argument content is an opener replay and is ignored; a chunk
with argument content is a continuation and appends. Regression tests
cover both directions.

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

* test(core): cover null/array argument collapse and multi-slot relocation scan

Review follow-up: pin the null and array branches of the emit-time
non-object collapse, and exercise findNextAvailableIndex scanning past
multiple occupied no-argument slots during collision relocation.

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

---------

Co-authored-by: FiaShi <FiaShi@fiashideMacBook-Air.local>
Co-authored-by: tomsen-ai <230283659+tomsen-ai@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:09:08 +00:00
ytahdn
0f98842ff2
fix(web-shell): refine tool detail cards (#6399)
Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-06 14:58:22 +00:00
DennisYu07
2fc6b08b52
fix(core): resolve symlinks when matching conditional rules and skills (#6371)
When a file is accessed via a symlinked path (e.g., in git worktrees or
monorepos with symlinked directories), conditional rules and skills
keyed on the real path would fail to activate.

Add resolveSymlinkAwareRelativePaths() that returns both the original
and realpath-resolved relative paths, so glob patterns match either form.
Resolve both the file path and projectRoot via realpath to handle macOS
/private/tmp prefix normalization correctly.

Make matchAndConsume() async in both ConditionalRulesRegistry and
SkillActivationRegistry to support the realpath I/O.

Fixes #6356

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-06 14:45:10 +00:00
顾盼
7281eb58fc
feat(core): surface PreToolUse hook 'ask' as a TUI confirmation (#5629)
* feat(core): surface PreToolUse hook 'ask' as a TUI confirmation

A PreToolUse hook returning permissionDecision:'ask' was treated the same as 'deny'. The hook fires in the execution phase (_executeToolCallBody), after the confirmation flow in _schedule has finished, so an 'ask' could only block the tool as EXECUTION_DENIED instead of prompting the user.

Bounce the tool from the execution phase back to awaiting_approval when a hook asks: build a synthetic 'info' confirmation whose onConfirm routes through handleConfirmationResponse (ProceedOnce re-executes, Cancel cancels). PreToolUse keeps its "before execution" timing — only the 'ask' branch is new; 'denied'/'stop' keep deny-as-error. A non-interactive CLI or background agent cannot prompt, so 'ask' falls back to deny there.

The re-execution after approval skips both the hook re-fire (no infinite re-ask loop) and the non-idempotent path-unescape prelude. A walk-away abort sets a terminal status so the turn cannot hang, and the tool span survives the bounce so it is finalized exactly once.

Tests: add coverage for ask->awaiting_approval, approve->execute-once (no re-ask loop), decline->cancelled, non-interactive/background deny, walk-away abort, single span finalize, and no double path-unescape.

* fix(core): handle PreToolUse 'ask' bounce edge cases from review

Round 1 review of #5629 surfaced edge cases in the bounce mechanism:

- Multi-tool batch hang: a bounced tool approved while a sibling was still executing stayed stuck in 'scheduled'. attemptExecutionOfScheduledCalls now loops, re-checking for newly-scheduled bounce-approved calls after each batch drains.

- Orphaned hook events: the post-approval re-execution generated a fresh tool_use_id, leaving PreToolUse(old)/PostToolUse(new) unpaired. Preserve and reuse the original id across the bounce.

- ModifyWithEditor double-unescape: request.args is unescaped in place before the hook fires, so the ModifyWithEditor branch must skip its own unescape for a bounced tool (it would double-strip escaped metacharacters).

- Missing signal.aborted re-check before bouncing: mirror the confirmation-phase guard so an aborted signal falls through to deny instead of flashing a confirmation nobody can answer.

Tests: multi-tool-hang regression (RED before the loop fix), non-interactive STREAM_JSON and Zed bounce paths, and span-finalize assertions on the walk-away abort test.

* fix(core): keep PreToolUse 'ask' gate when a sibling is auto-approved

Round 2 review: autoApproveCompatiblePendingTools auto-approved every awaiting_approval tool when a sibling was approved with ProceedAlways — including tools bounced by a PreToolUse 'ask'. The bounced tool would be auto-approved and re-executed with the hook skipped (isPostAskReexecution), silently defeating the hook's confirmation gate. Exclude bounced callIds from the auto-approve filter so a hook 'ask' always requires explicit confirmation.

Test: a sibling's ProceedAlways no longer auto-approves a bounced ask (RED before the filter guard).

* fix(cli): preserve hook ask prompts on approval mode change

* fix(core): handle PreToolUse ask edge cases

* fix(core): cancel scheduled calls during ask abort drain

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-06 14:35:38 +00:00
VectorPeak
42921028fb
fix(core): require integer ReadFile pagination params (#6381) 2026-07-06 14:26:51 +00:00
易良
5f7b57f933
fix(autofix): improve review addressing and verification (#6382)
* fix(autofix): run verification before committing

* ci(autofix): trigger review addressing on feedback

* fix(autofix): narrow npm command allowlist

* fix(autofix): address review workflow guards

* fix(autofix): keep review addressing on scheduled sweep
2026-07-06 14:10:58 +00:00
易良
57b3dcdcb2
fix(cli): ignore current review run in presubmit CI (#6397) 2026-07-06 14:09:36 +00:00
tanzhenxin
063535fc83
fix(core): log OpenAI error request IDs (#6379) 2026-07-06 14:07:02 +00:00
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
Copilot
f20925e9eb
fix(triage): exclude test files from core module size gate and distinguish feat from refactor (#6369)
* Initial plan

* fix(triage): exclude test files from core module size gate and distinguish feat from refactor

- Add anti-hallucination rule to SKILL.md preventing invented blocking policies
- Stage 0 size calculation now excludes test files (*.test.ts, *.spec.ts, __tests__/)
- Only production logic lines count toward the 500-line threshold
- feat-type PRs touching core escalate instead of hard-blocking
- Add soft large-PR advisory (non-blocking) for >1000 production lines
- Update AGENTS.md to match refined policy
- Clarify conventional commit matching patterns for feat/refactor detection

Closes #6365

* fix(triage): clarify core gate escalation paths

* fix(triage): define generated schema exclusions

* fix(triage): report core diff size in gate template

* fix(triage): clarify core gate review flow

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yiliang114 <effortyiliang@gmail.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-07-06 14:03:14 +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
tanzhenxin
c7fa13d6fb
fix(core): default context windows to 200k (#6387)
* fix(core): default unknown context windows to 200k

* fix(core): only stamp known model context windows in resolver

- Use knownTokenLimit() in the env-var resolver fallback so unknown
  models keep contextWindowSize undefined instead of being labeled
  'auto-detected from model' with the generic default
- Add resolver tests: known limit differing from the default (gpt-4o),
  unknown model stays undefined, settings value not overridden
- Recalibrate the config.getWarnings default-window test for the 200K
  fallback
- Sync the vscode-ide-companion copy of DEFAULT_TOKEN_LIMIT to 200K
2026-07-06 12:43:13 +00:00
易良
cb4963336e
test(core): make context warning threshold test dynamic (#6391) 2026-07-06 11:24:58 +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
ytahdn
b726b7cdaa
fix(web-shell): constrain virtual scroll rows (#6362)
* fix(web-shell): constrain virtual scroll rows

* test(web-shell): cover virtual message rows

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-06 07:41:59 +00:00
GuiYang
6f2f21ff7e
docs: standardize GitHub Actions capitalization (#6367) 2026-07-06 06:55:07 +00:00
易良
0c28ddc180
ci(autofix): move agent prompts into a project skill (#6306)
* ci(autofix): move agent prompts into project skill

* ci(autofix): avoid rewriting bot branch history

* fix(ci): address autofix skill review comments

* fix(ci): preserve autofix skill guardrails

* fix(ci): restore autofix review self-check

* test(ci): pin autofix review self-check

* fix(ci): tighten autofix skill guardrails

* fix(ci): restore autofix skill guardrails

* fix(ci): restore autofix merge verification guidance

* ci(autofix): add maintainer comment dry-run trigger

* fix(autofix): expand skill prompt in workflow

* fix(autofix): preserve qwen failure artifacts

* fix(autofix): move prompt runner into skill

* refactor(autofix): slim skill runner

* refactor(autofix): trim skill prompt

* fix(autofix): clarify verification handoff

* fix(autofix): skip issues with open bot PRs

* fix(autofix): expose existing PR context to skill

* fix(autofix): require strict null-safe TypeScript patches

* feat(autofix): formalize full pipeline phases in skill specification

Expand the autofix skill from 3 modes to an 8-phase pipeline definition
covering design, review-design, develop, verify, repair, cross-review,
and address-review. Adds bounded repair, scope creep self-check, and
structured failure classification. Future phases clearly marked.

* fix(autofix): keep skill scope to active modes

* fix(autofix): tighten manual command routing

* fix(autofix): harden comment-triggered runs

* fix(autofix): remove comment-triggered autofix route

* fix(autofix): restore review safety checks

* test(autofix): relax runner failure timeout

* fix(autofix): keep issue fixes in workflow checkout

* fix(autofix): harden review feedback handling

* fix(autofix): guard forced routing, improve runner observability (#6306)

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

* fix(autofix): address workflow review feedback

* fix(autofix): tighten review feedback handling

* test(autofix): trim runner test boilerplate

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-06 06:28:16 +00:00
Shaojin Wen
170ce7917d
feat(web-shell): show Settings and Daemon Status as an in-place panel (#6341)
* feat(web-shell): show Settings and Daemon Status as an in-place panel

The Settings and Daemon Status buttons opened centered modal overlays that
dimmed the whole app. Render them as a full-height panel that replaces the
chat surface instead — a Back button or Escape returns to the chat — with the
content left-aligned and filling the chat pane width rather than centered in a
narrow column. On the Daemon Status page, give the time-series charts a taller
plot and the overview cards a wider track so a wide window is actually used;
the tab grouping (overview / metrics / diagnostics) is kept.

* fix(web-shell): preserve composer draft and refine panel focus/escape

Address review feedback on the in-place Settings / Daemon Status panel:

- Keep the chat view (message list + composer) mounted and just hidden while a
  panel is shown, so typing a prompt, opening Settings/Status, then going Back
  no longer discards the unsent draft and attachments (the composer subtree was
  being unmounted and remounted empty).
- Focus the Back button when a panel opens and restore focus to the composer
  when it closes, replacing the focus management DialogShell used to provide.
- Reload workspace settings after the fast-model command resolves so the still-
  mounted Settings panel doesn't keep showing the previous value.
- Don't close the panel when Escape is handled inside the sidebar (its search
  input clears on Escape without stopping the event).
- Guard the overview card grid with min(100%, 340px) so a 340px track can't
  overflow a narrow panel.

* fix(web-shell): reset panel scroll when switching Settings/Status

Add key={activePanel} on the panel body so switching directly between Settings
and Daemon Status (activePanel goes 'settings' -> 'status' without passing
through null) remounts the scroll container and opens at the top instead of
inheriting the previous panel's scrollTop.

* fix(web-shell): restore new-chat vertical centering

The chatViewWrap added to keep the composer mounted while a panel is shown was
filling the pane, which cancelled the empty (new-chat) state's vertical
centering (welcome header + composer stuck to the top instead of centered).
Shrink the wrapper to its content in the empty state so
`.appChatEmpty .chatPane { justify-content: center }` centers it again, matching
how `.content` behaves there.

* fix(web-shell): restore session-org test destructuring dropped in merge

My earlier merge of origin/main 3-way-combined WebShellSidebar.test.tsx
incorrectly, dropping the `{ }` destructuring from the 7 session-organization
tests. renderSidebar returns `{ container, rerender }` (not the element), so
`const container = renderSidebar(...)` made `container.querySelector` throw.
Restore the file to origin/main so the tests pass again.

* fix(web-shell): surface pending approvals over Settings/Status panel

The in-place Settings/Daemon Status panel hides the chat footer with
display:none, and the ToolApproval / AskUserQuestion overlays live in
that footer. A gated tool call that arrived while a panel was open
rendered the approval into a hidden container, so the turn hung with no
visible prompt (reported [Critical]).

Close the panel when an actionable approval is pending so it surfaces.
Only actionable approvals count (pendingToolApproval / pendingAskUserApproval
already gate on canActOnPendingApproval), so a non-owner in a shared
session isn't yanked out of Settings by someone else's prompt. Leave
focus for the overlay rather than the composer on this path: ToolApproval
uses a window-level key handler that ignores editable targets, so
focusing the composer would swallow its shortcuts.

Also refocus the Back button on panel->panel switches (not just on open),
so focus no longer relies on the Back button being the same DOM node
across the keyed panel body, and expose SvgLineChart's plot height as
--chart-height (fallback 140px) so a constrained caller can shrink it.

Adds App-level tests: the panel auto-closes on a pending tool approval,
and stays open when the only block is a resolved (non-actionable) one.

* test(web-shell): cover AskUserQuestion auto-close; robust panel selector

Follow-up to the approval auto-close fix, addressing review suggestions:

- Add data-testid="inline-panel" to the panel <section> and query by it in
  App.test instead of querySelector('section'), which would false-positive if
  any future component renders a <section>.
- Add a companion test for the pendingAskUserApproval branch of the auto-close
  effect. The ask-user block carries toolCall.input.questions so
  isAskUserPermission() classifies it as an AskUserQuestion; mutation-checked
  (restricting the effect to pendingToolApproval fails only this test).
- Document why handleFastModelSelect's post-sendPrompt reload is not racy:
  sendPrompt awaits waitForAcceptedPromptCompletion, so the /model --fast turn
  has already applied when reloadWorkspaceSettings() runs. Also note why the
  command path needs the explicit reload that the setWorkspaceSetting pickers
  (vision/voice) get for free via the settingsVersion signal.

* fix(web-shell): close inline panel when resuming a session

The /resume <id> command and the ResumeDialog onSelect both call
loadSession() without closePanel(), unlike createNewSession and
loadSidebarSession. Loading a session means the user wants that chat, so
leaving a Settings/Daemon Status panel open would hide it. Add closePanel()
to both paths for consistency (a no-op when no panel is open).

Currently reachable only in theory — the composer that submits /resume is
display:none while a panel is shown — but the guard keeps the invariant if
a non-composer entry point (sidebar/shortcut) is ever added.

Adds a mutation-checked test (removing closePanel from the /resume handler
fails it) and gives the ChatEditor test mock a focus() method, since the
panel-close focus effect now runs editorRef.focus() on this no-approval path.

* fix(web-shell): keep composer dormant while an approval overlay is up

Follow-up to the approval auto-close fix. When an approval arrives while a
Settings/Status panel is open, the auto-close clears activePanel, so
interactionBlocked flips false and useComposerCore's dialogOpen effect
refocuses the still-mounted composer. ToolApproval ignores approval
shortcuts from editable targets, so the now-visible approval stops
responding to Enter/Escape/number keys until focus moves away.

Key the ChatEditor dialogOpen prop off the pending approval too, so the
composer stays blurred while an approval owns the keyboard. Consolidate the
"an approval overlay is active" condition — previously duplicated in the
auto-close effect and the panel focus guard — into a single
approvalOverlayActive value so the three consumers can't drift.

Uses the actionable-approval condition (pendingToolApproval /
pendingAskUserApproval) rather than raw pendingApproval, matching the
auto-close gating and avoiding a blurred composer when a non-actionable
approval renders no overlay.

Adds a mutation-checked test asserting dialogOpen is true while an approval
is pending with no panel open.

* test(web-shell): cover the Daemon Status panel branch

A review note observed that all five panel tests open via /settings, so the
activePanel === 'status' branch (DaemonStatusDialog) had no coverage — a
regression in the 'status' literal would go undetected. Add a test that
opens Daemon Status through the sidebar and asserts the panel opens and
auto-closes on a pending approval, exercising that branch and confirming the
auto-close is panel-type-agnostic.

Gives the WebShellSidebar test mock an onOpenDaemonStatus button, since
there is no slash command to open the status panel. Mutation-checked:
neutering setActivePanel('status') fails only this test.

* fix(web-shell): dismiss stacked dialogs on approval, focus overlay, a11y

Addresses a review round on the inline Settings/Status panel.

[Critical] When an approval arrives while a DialogShell sub-dialog (model
picker / approval-mode picker) is open over the panel, the auto-close
removed the panel but left the sub-dialog backdrop covering the footer
approval overlay, so the turn hung. Worse, the approval-mode picker stayed
usable — selecting "yolo" auto-approved (handleSetMode) a tool call the
user never saw. Dismiss the panel AND both sub-dialogs when an actionable
approval is pending.

[Critical] After the panel closes for an approval, focus fell to <body>
(the Back button unmounted; ToolApproval has no autofocus). Move focus onto
the ToolApproval overlay wrapper (tabindex=-1, so its window-listener
shortcuts keep working and Enter doesn't confirm early) once it is visible.
AskUserQuestion keeps managing its own focus.

Suggestions:
- Guard reloadWorkspaceSettings() rejection (was unhandled).
- aria-hidden the display:none chat view so AT can't wander into it.
- Close the panel before /model --fast so its response shows in the chat
  in context instead of piling up behind the hidden panel.
- Remove the dead .panelBodyInner wrapper (redundant width:100%).
- Rename data-mobile-drawer -> data-sidebar-shell (it wraps the desktop
  sidebar for all viewports) across App.tsx + standalone.css.

Tests (+5, mutation-checked): sub-dialog dismissal on approval, overlay
focus, Escape closes panel / sidebar-Escape does not, dialogOpen while a
panel replaces the chat. Adds an observable DialogShell test mock.

* fix(web-shell): restore composer focus after approval resolves; cleanups

[Critical] The panel focus effect consumed prevActivePanelRef to null on the
approval auto-close (correctly skipping editor focus then). When the approval
later resolved with no panel to return to, neither branch fired and focus was
left on <body> — the visible composer took no keyboard input. Track the
approvalOverlayActive transition too and restore composer focus on
approval-resolve-after-panel-close. (useComposerCore's dialogOpen effect also
covers this; the extra branch makes the panel effect self-contained.)

Suggestions:
- Log the reloadWorkspaceSettings() rejection instead of swallowing it.
- Remove the dead :global([data-dialog-fullscreen]) .grid rule (the
  allowFullscreen source was removed when Daemon Status became a panel).

Tests (+4, focus-restore mutation-checked): focus restored after an approval
resolves post-auto-close; Back-button closes the panel and restores focus;
fast-model pick closes the panel, sends /model --fast, and reloads settings;
chat view is aria-hidden while a panel is shown. Adds interactive
SettingsMessage / ModelDialog mocks and stable editor-focus / settings-reload
spies.

* fix(web-shell): make Settings/Status panel and Scheduled Tasks mutually exclusive

Opening Scheduled Tasks (mainView — a position:absolute z-index overlay) and
then Daemon Status (activePanel) rendered the Daemon Status panel *behind* the
Scheduled Tasks overlay, so the button looked like it did nothing. These are
mutually-exclusive full-pane views, so opening one must close the other.

Centralize into openPanel() / openScheduledTasks() helpers (last-opened wins)
and route all six open sites (the /settings and /schedule commands, the three
sidebar handlers, and the StatusBar) through them.

Mutation-checked tests: opening Daemon Status closes the Scheduled Tasks page
(the reported repro), and opening Scheduled Tasks closes an open panel.
2026-07-06 05:06:46 +00:00
zhangxy-zju
1783ae86f3
docs(web-shell): document chart renderer integration (#6353)
* docs(web-shell): document chart renderer integration

* docs(web-shell): describe daemon-backed chart artifacts

* docs(web-shell): clarify chart ref validation layers
2026-07-06 04:55:26 +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
ytahdn
fa6e0f942c
fix(web-shell): suppress stale pending prompt refresh errors (#6352)
Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-06 03:02:49 +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
VectorPeak
47f62a466c
fix(desktop): preserve glued automation history records (#6344)
* fix(desktop): preserve glued automation history records

* fix(desktop): refine automation history recovery

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

* test(desktop): cover automation history rewrite normalization

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

* fix(desktop): preserve recovered history before stray brace

* test(desktop): assert history rewrite after stray suffix

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-05 23:53:49 +00:00
Dragon
be0b0749c1
docs: fix settings.json reference drift against schema (#6351)
Correct and complete the user-facing settings documentation against
packages/cli/src/config/settingsSchema.ts:

- settings.md: fix general.defaultFileEncoding type (enum, not string);
  document the general.voice.* dictation settings, top-level
  modelFallbacks and modelPricing, tools.computerUse.idleTimeoutMs,
  mcp.toolIdleTimeoutMs, and the skills.disabled denylist.
- model-providers.md: correct the resolution-layers table — only
  --openai-api-key/--openai-base-url exist; there are no
  provider-specific credential CLI flags.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-05 23:40:36 +00:00
Kagura
fc701f8608
perf: memoize skill scans, debounce sleep-inhibitor log, guard IDE readdir (#6155)
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
* perf: memoize skill scans, debounce sleep-inhibitor log, guard IDE readdir (#6134)

Three startup / session performance-noise fixes:

1. **Memoize collectAvailableSkillEntries()**: Add a short-lived (2 s) WeakMap
   cache keyed by SkillManager instance. Near-simultaneous callers during
   startup (SkillTool, drainSkillAndCommandReminders, buildAvailableSkillsReminder,
   coreToolScheduler) now share a single skill scan instead of each re-invoking
   listSkills(). The cache is explicitly cleared on refreshSkills() so
   skill-set mutations are picked up immediately.

2. **SleepInhibitor one-time latch**: Add exitedWhileActiveLogged flag so the
   'exited while active' debug message fires at most once per run. On Windows
   the PowerShell inhibitor subprocess may be killed between release()/acquire()
   cycles, previously logging on every tool call. The flag resets when
   activeCount drops to 0 or on dispose().

3. **IDE client ENOENT guard**: In getAllConnectionConfigs(), catch ENOENT from
   readdir on ~/.qwen/ide silently (return []) instead of logging a debug
   message on every startup in CLI-only setups without an IDE companion.

* test: add coverage for memo cache, ENOENT guard, and dedup latch

* fix: correct Config import path in skill-utils test

---------

Co-authored-by: 易良 <1204183885@qq.com>
2026-07-05 16:02:28 +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