mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-10 01:29:17 +00:00
3373 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0fd3502c10
|
Merge branch 'main' into worktree-feat+runtime-context | ||
|
|
6fdd0fc710
|
fix(core): Support large text range reads (#6404)
* fix(core): support large text range reads Allow text reads to stream bounded line ranges for files larger than the previous 10MB guard, while preserving media size limits and forwarding cancellation through read_file/read_many_files/ACP paths. Refs #6403 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): address large text review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): propagate abort signals in text reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): validate streamed utf8 reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): handle disabled line truncation for large reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): use kebab-case for text range reader Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): preserve artifact size errors for large sources Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): address large text review follow-up Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): allow default large text reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): honor text read byte caps Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): clarify invalid utf8 range read errors Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): prevent truncated full large reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): preserve unbounded line-zero reads Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): forward artifact read cancellation Pass artifact execution abort signals into source file reads and preserve cancellation semantics when the read is aborted. Add regression coverage for unbounded large UTF-8 range reads and offsets beyond EOF. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): address file read review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): preserve large text mutation reads Allow default unbounded readTextFile calls to keep reading full large text files so mutation tools can prepare complete snapshots after a prior ranged read. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
22a19e0cf2 |
merge: sync with main
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
736b710ed0
|
feat(core): add tools.visible config for selective deferred-tool visibility at startup (#6372)
* feat(core): add tools.visible config for selective deferred-tool visibility * fix(cli): wire tools.visible from settings.json into Config Add settingsSchema entry for tools.visible and plumb it through loadCliConfig into ConfigParameters.visibleTools. Without this the core-level visibleTools support was unreachable from settings.json. Adds 3 CLI-level tests: visibleTools passthrough, empty default, safe-mode suppression. Fixes #6368 * fix(core): exclude visibleTools from tool_search candidates and reveal path Add visibleTools gate to collectCandidates() and loadAndReturnSchemas in tool-search.ts to prevent KV-cache invalidation when tool_search is invoked for a visible-deferred tool. Without this, a tool listed in tools.visible would still appear as a keyword-search candidate and select: would still trigger revealDeferredTool + setTools, defeating the purpose of promoting it to first-class visibility. 3 tests: keyword-search exclusion, select: no-reveal/no-setTools, and select: still works for non-visible deferred tools. * fix(cli): include visibleTools in /context per-tool token breakdown Add config.getVisibleTools().has(tool.name) gate to the deferred-tool skip condition in collectContextData. Without this, visible-deferred tools appear in the headline total (via getFunctionDeclarations()) but are excluded from the per-tool breakdown, causing the sum to mismatch. 1 test: visibleTools included in breakdown despite deferred+unrevealed. * fix: address all 7 review suggestions for tools.visible 1. settingsSchema: user-facing description instead of internal jargon 2. config.ts: use normalizeToolNameList (generic name) for both disabled and visible 3. config.test.ts: add bare-mode exclusion test 4. tool-registry.test.ts: disabledTools > visibleTools priority test 5. tool-registry.ts: update JSDoc for getDeferredToolSummary 6. tool-search.test.ts: mixed select: visible+non-visible test 7. tool-registry.test.ts: visible survives clearRevealedDeferredTools * chore: regenerate settings.schema.json after description update CI check detected that settings.schema.json was out of sync with settingsSchema.ts after the description was changed in commit 73e879882. * fix: address wenshao review — extract isDeferredAndHidden, clean up abstractions 1. Remove normalizeToolNameList (empty wrapper, violates AGENTS.md no-abstraction rule) 2. Extract ToolRegistry.isDeferredAndHidden() — 5 call sites reduced to 1 predicate source 3. Add dirty-input test for tools.visible (whitespace, duplicates, empty strings) 4. Add MergeStrategy.UNION test for tools.visible across user + workspace scopes --------- Co-authored-by: Aleks-0 <aleks-0@users.noreply.github.com> |
||
|
|
7e0e79b6bc
|
fix(daemon): preserve user message source metadata (#6385) | ||
|
|
5d2bfbd21b
|
feat(core): add Tool(param:value) permission syntax for parameter-level access control (#6106)
* feat(core): add Tool(param:value) permission syntax for parameter-level access control Introduces key:value parameter matching in permission rules, allowing users to grant or deny tool access based on specific input parameters. - Parse key:value pairs from specifiers for literal-kind rules - Support wildcard patterns (*), multiple params, and mixed syntax - Thread toolParams through PermissionCheckContext and matchesRule - Add 11 unit tests covering parsing, matching, wildcards, and edge cases - Maintain backward compatibility with existing specifier kinds Example rules: Agent(model:opus) # deny agents using Opus model Agent(coder,model:*) # deny coder-type agents with any model Bash(git:*) # still works (legacy :* → git *) Closes #6100 * fix(permissions): resolve PR #6106 review comments for tool param permission syntax - buildPermissionRules now propagates toolParamMatchers for 'Always Allow' flow - MCP tool rules now check param matchers after name matching - Added Object.hasOwn check to prevent prototype chain lookup vulnerability - Replaced matchesCommandPattern with matchesParamValuePattern for param value matching - Added diagnostic logging for param matching failures - Added warnings for empty valuePattern, invalid keys, and non-literal key:value syntax - Added type checking for non-primitive param values * fix(core): address critical review feedback on Tool(param:value) permission syntax - Add 's' flag to RegExp in matchesParamValuePattern for multiline support - Filter buildPermissionRules to stable param keys only (model, subagent_type, skill, server_name) to prevent sensitive data leakage - Reject MCP rules with unsupported specifiers instead of silently ignoring - Fix :* wildcard conversion to use global replace for backward compatibility - Extract shared evaluateParamMatchers helper to deduplicate MCP and standard branches * fix(permissions): address remaining review feedback for Tool(param:value) syntax - Remove unused @ts-expect-error in gitWorktreeService.ts (CI blocker) - Fix ReDoS in matchesParamValuePattern: replace regex with linear-time glob matcher using indexOf, avoiding catastrophic backtracking on multi-wildcard patterns like *a*a*a*a*b - Fix MCP backward compatibility: exclude MCP tools from key:value parsing in parseRule and buildPermissionRules to preserve existing MCP deny rule semantics - Add tests for MCP + param matcher, partial wildcards, ReDoS prevention, number coercion, and buildPermissionRules with toolParams (stable params, volatile params, sensitive data, round-trip) * fix(permissions): address PR #6106 review comments and fix useStatusLine test timeout - Make matchesParamValuePattern case-insensitive to match matchesDomainPattern convention - Remove duplicate JSDoc block before matchesParamValuePattern - Remove dead server_name from stableParamKeys in buildPermissionRules - Add PermissionManager integration tests with toolParams (evaluate, findMatchingDenyRule, hasRelevantRules, hasMatchingAskRule) - Add type guard tests for evaluateParamMatchers (null, undefined, boolean, object) - Fix useStatusLine.test.ts timeout by stubbing cron-task exports in core mock --------- Co-authored-by: 易良 <1204183885@qq.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
fde18828ae
|
feat(cli): support stacked slash-skill invocations (#6361)
* feat(cli): support stacked slash-skill invocations (#6355) Allow users to chain multiple slash-skill commands in a single prompt (e.g. `/feat-dev /e2e-testing implement X`). The skill bodies are concatenated with the remaining user text and submitted as one `submit_prompt` to the model, so the model receives all loaded skill contexts at once. - Add `parseStackedSlashCommands()` with a `MAX_STACKED_SKILLS = 5` cap - Integrate stacked detection into both interactive (slashCommandProcessor) and non-interactive dispatch paths - Record `recordSkillInvocation` telemetry for each stacked skill - Emit a warning when more than 5 skills are requested - 29 new test cases covering parsing edge cases and both dispatch paths Closes #6355 * fix(cli): address review feedback for stacked skill invocations - Fix whitespace tokenization to match all \s chars, not just spaces - Align telemetry recording: record success based on actual result type (both dispatch paths now consistent) - Surface error messages from non-submit_prompt skill results - Propagate modelOverride from first submit_prompt skill - Add 7 new tests: tab whitespace, mixed whitespace, non-submit_prompt exclusion, telemetry accuracy, modelOverride propagation * fix(acp-bridge): use static import in logRedaction test to avoid timeout The dynamic `await import('./spawnChannel.js')` inside the test body was pulling in a heavy module graph at runtime. Under CI contention (667 tests running concurrently), this exceeded the 5-second default timeout. Convert to a static top-level import — the same pattern used by spawnChannel.test.ts. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): move stacked skill block inside try/finally and collect onComplete callbacks - Move stacked skill dispatch into try block so finally cleanup runs (setIsProcessing, chat recording, telemetry) preventing TUI freeze - Set invocationSentToModel=true for stacked invocations so chat history correctly classifies the command as sent to model - Collect and forward onComplete callbacks from all submit_prompt skill results in both interactive and non-interactive paths --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
57b3dcdcb2
|
fix(cli): ignore current review run in presubmit CI (#6397) | ||
|
|
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 |
||
|
|
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> |
||
|
|
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. |
||
|
|
5c8af1a1fa
|
fix(cli): allow ACP local fallback reads from /tmp (#6370)
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> |
||
|
|
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. |
||
|
|
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. |
||
|
|
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> |
||
|
|
b23f888d73
|
[codex] add proactive channel loop tools (#6287)
* feat(channel): add proactive loop tools * fix(channels): stabilize proactive loop routing * fix(channels): gate loop tools in shared sessions * fix(channels): tighten channel loop tool routing * fix(channels): close loop tool review blockers * fix(dingtalk): preserve markdown tables * fix(dingtalk): use app token for reactions * fix(channels): scope loop tools to active caller * fix(channels): preserve group session metadata * fix(channels): normalize loop targets * test(cli): cover settings cron disable path * fix(channels): address dingtalk review suggestions * fix(dingtalk): restore table normalization * fix(channels): mark loop tool failures * fix(channels): tighten loop mcp protocol handling * test(channels): cover loop tool guard paths * fix(channels): await loop mcp registration * test(channels): preserve base proactive target default * refactor(channels): clarify loop target promotion * fix(channels): harden loop recurring input * fix(channels): ack loop mcp notifications * fix(channels): preserve legacy loop targets * test(channels): cover channel loop wiring paths * fix(channels): retry skipped loop mcp registration * fix(channels): keep promoted loop targets visible * fix(channels): harden loop mcp input logging |
||
|
|
11c874dfba
|
feat(cli): Add large pipe frame measurement (#6335)
* feat(cli): Add large pipe frame measurement Add an internal NDJSON message observer for ACP pipe frames and wire qwen serve to record low-sensitive attribution for large frames without changing transport behavior. Document the measurement-only design and cover observer hooks, large-frame classification, rate limiting, and wiring behavior in targeted tests. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6335) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix CI failure on PR #6335 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6335) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6335) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6335) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
0677a38fd8 | merge: sync with main | ||
|
|
82fb6a4f0d
|
fix(cli): allow queued input during compression (#6336) | ||
|
|
3bf0fa0af0
|
Feat: LSP Server support hot reload (#5953)
* feat(core): Add LSP server config hot-reload support - Implement reconcileServerConfigs to diff desired vs current LSP configs and apply minimal add/remove/restart operations with a serialized reconcile queue - Add configHash utility to detect config changes via stable hashing - Add lspConfigWatcher in CLI to watch .lsp.json and trigger reconciliation on file changes - Extend LspServerManager with per-server config hash tracking and detailed debug logging - Add design docs for LSP runtime reinitialization and hot-reload overview - Include comprehensive unit tests for all new modules * refactor(cli): Extract registerLspHotReload from main function Move the LSP config file watcher setup and reconciliation logic into a dedicated module-private function registerLspHotReload, reducing the size and nesting depth of the main startup flow. Added a JSDoc summarizing responsibilities, early-return conditions, and the AppEvent.LspStatusChanged side effect. * fix(lsp): release server resources during reload * fix(lsp): address hot reload review feedback * fix(lsp): harden hot reload reconciliation * docs(lsp): update hot reload design notes * fix(lsp): harden hot reload retry semantics * fix(lsp): harden hot reload lifecycle * fix(lsp): harden hot reload lifecycle * fix(lsp): isolate hot reload recovery paths * fix(lsp): align command probes and replay tracking * fix(lsp): prevent crash restarts during shutdown * fix(lsp): preserve reload state across failures * fix(lsp): cancel reloads during shutdown * fix(lsp): handle socket startup races * fix(lsp): harden command probe env and socket startup * fix(lsp): report skipped reload and restart states * fix(lsp): harden hot reload lifecycle cleanup * chore: add one comment for `Object.create(null)` --------- Co-authored-by: heyang.why <heyang.why@alibaba-inc.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
c7170b5e04
|
feat(cli): support multi-folder workspaces in file system boundary checks (#6278)
* feat(cli): support multi-folder workspaces in file system boundary checks The CLI daemon's file system boundary enforcement only recognized a single `boundWorkspace` root, so when a user opened multiple folders in a VSCode workspace, files in every folder except the terminal's cwd were rejected with "path escapes workspace." Change `resolveWithinWorkspace` and `WorkspaceFileSystem` to accept an array of workspace roots. Each root gets independent ignore rules and boundary checking. The VSCode extension now passes `QWEN_CODE_IDE_WORKSPACE_PATH` (all folders, delimiter-separated) as a terminal env var so the daemon can discover additional roots at boot. Nested roots are rejected at registration time to avoid ambiguity. Single-folder workspaces pass a one-element array, so behavior is unchanged. Closes #1766 * fix(cli): resolve multi-root workspace review feedback * fix(cli): tolerate invalid IDE workspace env roots * fix(cli): reuse workspace containment helper * fix(cli): honor IDE workspaces in serve app factory * fix(cli): resolve workspace boundary feedback * fix(cli): resolve multi-root workspace review comments * fix(cli): constrain secondary workspace roots * fix(cli): preserve dangling symlink write guard * fix(cli): resolve multi-workspace review comments * fix(cli): resolve workspace root review comments * refactor(cli): trim multi-workspace serve changes * fix(cli): resolve workspace env review comments * fix(cli): use literal IDE workspace env lookup * fix(cli): resolve workspace glob review comments * fix(cli): tighten multi-root glob errors * test(cli): cover multi-root workspace review gaps * fix(cli): handle multi-root workspace edge cases * fix(cli): preserve trusted nested workspace roots * fix(cli): preserve read existence check for aliases * fix(cli): share daemon write locks across serve paths * fix(cli): harden multi-root workspace env parsing |
||
|
|
0d1d24052c
|
feat(core): model fallback chain — auto-switch to backup models on overload (#6273)
* feat(core): implement model fallback chain for capacity/availability errors
When the primary model hits 429/503/529 and same-model retries are
exhausted, automatically try configured fallback models in sequence
before giving up.
Config layer:
- Add `modelFallbacks` setting (comma-separated, max 3)
- Add `--fallback-model` CLI flag (repeatable or comma-separated)
- Normalize, deduplicate, cap at 3 in core Config
Core layer:
- Add `isFallbackEligible()` helper to retryErrorClassification
- Update HTTP 529 diagnosis to `fallback-eligible` (was `retryable`)
- Add fallback chain logic in geminiChat `sendMessageStream`:
each fallback gets its own retry budget via `makeApiCallWithFallbackGenerator`
- Resolve fallback models cross-provider via `resolveForModel({ failClosed: true })`
- Skip fallback when `QWEN_CODE_UNATTENDED_RETRY` persistent mode is active
- Non-fallback-eligible errors (auth/client) stop the chain immediately
UI layer:
- TUI notification: "Model X unavailable, falling back to Y"
- Daemon SSE adapter: model_fallback event handling
- Non-interactive: system message with model_fallback subtype
Closes #6116
* fix(core): retry fallback stream failures
* refactor(core): address review findings for model fallback chain
- Delete unused makeApiCallWithFallbackGenerator (merged into
makeApiCallAndProcessStream via overrides parameter)
- Import HeartbeatInfo type for persistent-mode heartbeat callback
- Use popPendingPartialAssistantTurn (removes partial turn from
history) instead of clearPendingPartialState before model switch
- Add AbortError early-return in both fallback catch blocks to
propagate user cancellation immediately
- Reorder guard: check fallbackModels.length > 0 before calling
classifyRetryError to avoid unnecessary work
- Rename errorCode → statusCode in ModelFallbackInfo and all
consumers for consistency with RetryErrorClassification
- Add TODO for retry loop duplication in makeFallbackStreamWithRetries
* fix(core): clean fallback partial turns
* fix(core): address model fallback review comments
* fix(core): tighten model fallback handling
* fix(core): align fallback recovery semantics
* fix(core): tighten fallback failure handling
* fix(core): reduce fallback stream scope
* fix(core): resolve fallback review comments
* fix(core): review polish for model fallback chain
- Add .filter(Boolean) to CLI --fallback-model coerce to strip empty
strings from leading/trailing commas
- Clarify isFallbackEligible() JSDoc: reason-based check intentionally
covers 'retryable' diagnosis (429/503) not just literal
'fallback-eligible' diagnosis (529)
- Preserve original capacity error as cause when all fallbacks exhaust
(previously the cause was the last resolution/fallback error)
- Add test: provider-level rate-limit (numeric code 1302, no HTTP
status) is correctly identified as fallback-eligible
* fix(core): resolve model fallback review comments
* fix(core): dedupe unresolved fallback aliases
* fix(core): trim model fallback scope
* fix(core): stop fallback after emitted output
* fix(core): clear fallback tool call state
* fix(core): skip unresolved fallback aliases
* fix(core): address fallback review feedback
|
||
|
|
fe816f625f
|
feat(cli): Surface daemon prompt queue status (#6325)
* feat(cli): surface daemon prompt queue status Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6325) * codex: address PR review feedback (#6325) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6325) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
7a528d078a
|
feat(daemon): Add session organization (#6305)
* feat(daemon): add session organization Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): address session organization review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(daemon): cover session organization review cases Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6305) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6305) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6305) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6305) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6305) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6305) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): Address session organization review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Harden session organization review edge cases Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): Address session organization review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
52a190b5c6
|
feat(web-shell): time-series metrics charts on Daemon Status (#6307)
* feat(web-shell): time-series metrics charts on Daemon Status
Add seven bottleneck-analysis line charts (concurrency, requests, API
latency, prompt latency, event-loop lag, memory, token burn) to the
Daemon Status dashboard, backed by a new server-side metrics ring.
The status endpoint is a point-in-time snapshot, so line charts need a
time series. A bounded ring buffer in the daemon (daemon-metrics-ring.ts)
seals one bucket every 5s (~15min retained) from three seams:
- HTTP request rate/latency via the telemetry middleware
- prompt queue-wait/duration via the bridge telemetry hooks
- per-round token usage sniffed at the bridge session/update fan-in
(new DaemonBridgeTelemetryMetrics.tokenUsage hook)
plus memory / active sessions+prompts / a window-scoped event-loop lag
p99 read as gauges at seal time.
The series rides the existing GET /daemon/status contract
(runtime.metrics.series), threaded through the SDK types (JSON passthrough)
to a dependency-free inline-SVG chart component in web-shell -- no charting
library added to the CSP-strict serve --web bundle.
Tests: metrics-ring math, token-usage sniffing on the real sessionUpdate
path, and SVG chart rendering. Verified end-to-end against a live daemon
(GLM-5.2): requests/latency/memory/event-loop, real token burn and prompt
duration, with the concurrency gauge tracking active prompts.
* feat(web-shell): tabs, chart tooltips, and fullscreen for Daemon Status
Split the now chart-heavy Daemon Status dashboard into Overview / Metrics /
Diagnostics tabs (status badge, refresh, and issues stay global) so
monitoring, configuration, and troubleshooting each get their own space
instead of one long 70vh scroll.
Add an interactive hover cursor to the charts: a vertical time line, a dot on
each series, and a tooltip reading the bucket time plus every series' value at
that point -- previously only the latest value and peak were legible, from the
legend.
Add an opt-in fullscreen toggle to DialogShell (via allowFullscreen, wired for
Daemon Status) that expands the panel to near the full viewport; scrolling is
consolidated into the shell body so the content actually grows with it.
Tests: tab switching + diagnostics-behind-tab, SVG tooltip rendering, and the
DialogShell fullscreen toggle. Verified end-to-end against a live daemon
(GLM-5.2) with real request / token / prompt data.
* feat(web-shell): add CPU, LLM-latency, queue-depth, IPC & connection metrics
Extend the Daemon Status metrics ring with more bottleneck-analysis
dimensions, filling the two biggest gaps — resource cost had only memory
(no CPU), and latency had only client->daemon HTTP (not daemon->model):
- CPU %: process.cpuUsage() delta, core-normalized (memoryPressureMonitor
formula, clamped 0-100), sampled alongside memory.
- LLM API latency p50/p95: the token frame's _meta.durationMs (the
daemon->model round-trip), separating 'model is slow' from 'we are slow'.
- Prompt queue depth: a new bridge.pendingPromptTotal aggregate, folded into
the concurrency chart beside active tasks.
- IPC pipe throughput: daemon<->ACP-child stdio bytes (already measured; now
windowed via metricsRing.recordPipe).
- Connection counts (SSE/WS/ACP) and rate-limit rejections, read lazily in the
sampler from the ACP handle registry and the rate limiter.
The tokenUsage telemetry hook is widened to carry durationMs. Verified
end-to-end against a live daemon (GLM-5.2): LLM p95 28.6s vs HTTP p95 324ms,
queue depth 1, IPC peak 0.3MB, SSE gauge 1 on a live stream.
* feat(web-shell): add ACP child process CPU/memory (self-reported over ACP)
The daemon's own CPU/memory only tell half the story — the real LLM/tool work
runs in the spawned 'qwen --acp' child, which is where the resource cost lives.
Surface it: the child self-reports its rss + cpuPercent to the daemon over a new
read-only ACP extMethod (qwen/status/workspace/resource); the bridge caches the
latest sample on the live channel, and the metrics sampler reads it
synchronously each tick (firing an async refresh for the next, off the hot path).
The child computes cpuPercent as a process.cpuUsage() delta between polls (no
dependency on MemoryPressureMonitor's tool-gated sampling), core-normalized and
clamped. Rendered as a second line on the CPU and Memory charts (daemon vs
child, side by side).
Verified end-to-end (GLM-5.2): child RSS ~300MB vs daemon RSS ~225MB, child CPU
tracking above the daemon's -- the child is the resource hog, now visible.
* test(web-shell): cover Metrics tab, chart rendering, and the recordRequest seam
Address review — the metrics dashboard's rendering and its HTTP data seam had
no tests:
- DaemonStatusDialog: switching to the Metrics tab renders the charts from the
series (one SvgLineChart per card) and hides the Overview panel; an empty
series shows the collecting-metrics placeholder.
- daemonTelemetryMiddleware: recordRequest fires once with (durationMs,
statusCode) on a matched route (real status code; once across finish/close),
is not called for unmatched routes, and is a silent no-op when omitted.
* fix(web-shell): enlarge Daemon Status charts in fullscreen
Fullscreen widened the panel but the charts stayed small — the grid just packed
in more 280px cards at a fixed 52px SVG height, so the extra viewport bought
more small charts, not bigger ones. Now the DialogShell body carries a
`data-dialog-fullscreen` marker; the chart grid switches to wider cards (min
480px → fewer columns) and the SVG grows to 120px, so fullscreen actually
enlarges the plots. Verified: 2 wide columns at 120px vs 3-4 columns at 52px.
* fix(web-shell): resolve chart colors in portal, guard child-resource polling
Address review (real-user + ci-bot):
- [Critical] Chart colors (--primary, --agent-blue-400) resolved to nothing in
the DialogShell portal (createPortal to document.body escapes the app root that
defines them), so ~half the chart lines rendered stroke:none. Add both vars to
DialogShell's own theme scope. Verified: 25/25 path strokes colored (was 5 none).
- [Critical] refreshChildResource had no in-flight guard; requestWorkspaceStatus
waits up to 10s (> the 5s cadence), so a degraded child accumulated concurrent
polls. Add a single-flight guard.
- [Critical] getChildResourceSnapshot returned last-good rss/cpu forever; add a
30s staleness window so a stuck child reads 0 instead of looking healthy.
- Exclude GET /daemon/status (the dashboard's own poll) from the metrics-ring
request rate, so the Requests chart doesn't count itself.
- Fix cpuPercent JSDoc (percent of total capacity across cores, clamped [0,100])
in the ring + SDK mirror; add a keep-in-sync cross-reference on the mirror.
Tests: recordRequest excludes /daemon/status; buildDaemonStatusResponse embeds
runtime.metrics.series when provided and omits it otherwise.
* fix(web-shell): address Daemon Status charts review feedback
Correctness fixes surfaced in review:
- bridgeClient: guard token accounting on a live `entry`. On the
`session/load` path HistoryReplayer re-emits saved usage as live
session/update frames before the session entry is registered, which
otherwise dumped a session's historical token total into the current
metrics window as a phantom burn spike with no model call.
- run-qwen-serve metrics sampler: wrap each tick in try/catch/finally so a
throwing getter can't crash the daemon; reset the event-loop-lag histogram
in finally so a thrown tick can't permanently discard it; skip the CPU
delta (and leave the baseline untouched) when process.cpuUsage() throws;
seed the rate-reject baseline on the first tick instead of reporting the
whole since-start backlog as one spike.
- acpAgent workspaceResource: advance the child-CPU baseline only on a
successful read, avoiding a ~2x phantom spike on the poll after a failure.
- bridge.pendingPromptTotal: count only queued prompts (state === 'queued'),
not the running one, so the "Queued" chart reflects real backpressure and
no longer shadows the "Active tasks" line.
- Make the new Daemon Status bridge hooks optional in AcpSessionBridge and
optional-chain them in the sampler, so a bridge injected via
RunQwenServeDeps.bridge that predates them degrades gracefully.
Robustness / UX:
- daemon-metrics-ring sanitizes non-finite gauges to 0 so a bad reading
never serializes as JSON null and gaps the chart.
- child-resource refresh logs failures at debug for observability.
- formatBytes drops to KB/B for sub-MB pipe traffic (was "0.0 MB").
- SvgLineChart peak label is now i18n'd (daemon.charts.peak).
- Daemon Status tabs get the full WAI-ARIA tabs pattern: aria-controls,
role=tabpanel, and Arrow/Home/End keyboard navigation with roving tabindex.
Tests: replay token guard (no live entry), pipe/gauge/sample-cap defenses,
large-value legend formatting, and tab keyboard navigation.
* fix(web-shell): keep Daemon Status fullscreen + tooltip correct in dialog portal
Two DialogShell-portal theme-scope issues surfaced by a follow-up review:
- Fullscreen was clamped back to 80vh on narrow screens: the
`@media (max-width: 560px)` `.panel` rule has equal specificity and later
source order than the base `.panelFullscreen`, so it won. Add a media-scoped
`.panelFullscreen` override so fullscreen actually expands on mobile.
- SvgLineChart tooltip background used `var(--popover, var(--card))`, neither of
which the portal theme scope defines, so the declaration dropped and the
tooltip rendered transparent over the chart. Fall back to `--background`
(which the dialog scope does define).
* fix(web-shell): flip chart tooltip below cursor near scroll-container top
The Daemon Status charts live inside DialogShell's overflow-y:auto body, so the
topmost chart's upward tooltip (bottom: calc(100% + 4px)) clipped against the
scroll container's top edge, truncating the time header / first series row on
hover. SvgLineChart now resolves its nearest scroll parent and flips the tooltip
below the cursor when the plot sits within ~one tooltip-height of that clip
boundary.
* fix(daemon-status): harden child-resource CPU/memory + sampler lag on failure
Follow-up review fixes:
- acpAgent: prevChildCpu inits to null (not {0,0}) and the workspaceResource
handler gates the delta on a live prevCpu baseline, so an init-time
cpuUsage() failure no longer manufactures a phantom spike on the first poll
— mirrors the daemon sampler's safeCpuUsage null-on-failure contract.
- acpAgent: guard process.memoryUsage() too, reporting 0 rss on failure while
keeping the already-computed cpuPercent instead of throwing the handler.
- bridge: require Number.isFinite() (typeof NaN === 'number' is true) and
clamp cpuPercent to [0,100] when caching the child's self-report.
- run-qwen-serve sampler: gate the 5s child-resource refresh on an active
SSE/WS client (idle staleness already reads 0), and hoist the event-loop
lag read before the try so a thrown tick charts the real accumulated lag
instead of a misleading 0.
* fix(daemon-status): protect artifact path from metrics callback + share CPU delta
Follow-up review fixes:
- bridgeClient: wrap recordLiveTokenUsage in try/catch so a throwing injected
onTokenUsage callback can't skip the critical artifact processing after it —
metrics are optional, artifacts are not.
- Extract computeCpuPercent() into daemon-metrics-ring and share it between the
daemon self-sampler and the ACP child's workspaceResource handler, removing
the duplicated delta/normalize/clamp math and giving it direct unit coverage
(null sample, non-positive window, normalization, phantom-spike + negative
clamps).
- Add a single-flight test for bridge.refreshChildResource (two rapid calls
collapse to one in-flight RPC).
|
||
|
|
4675274ee4
|
fix(cli): preserve partial remote input JSONL records (#6317)
* fix(cli): preserve partial remote input JSONL records * test(cli): cover mixed remote input partial record |
||
|
|
2b732f5fc6
|
fix(serve): resolve false auth warning in preflight when API key is set via settings (#6296)
* fix(serve): resolve false auth warning in preflight when API key is set via settings The preflight auth check only looked at process.env for well-known env var keys (e.g. OPENAI_API_KEY), missing credentials provided through settings.security.auth.apiKey or provider-specific envKey in settings.env. This caused a spurious "None of the env vars [OPENAI_API_KEY] is set" warning on the daemon status dashboard even when the key was properly configured. Fall back to the already-resolved generation config apiKey (which folds all credential sources: env vars, settings.security.auth.apiKey, provider envKey, and CLI flags) when the env-var check fails. Also fix the apiKeyVars.length === 0 branch to report 'ok' instead of 'unknown' when the resolved key is present. * test(serve): add auth preflight tests for settings-based API key fallback Cover the generationConfig.apiKey fallback path added in the previous commit: one test asserts status 'ok' when the key is present in generationConfig but absent from process.env, and one asserts status 'warning' when both sources are empty. * test(serve): add auth preflight tests for non-env-keyed auth branch Cover the apiKeyVars.length === 0 branch in buildAuthPreflightCell: - ok when generationConfig.apiKey is present (non-env-keyed provider) - unknown when no apiKey anywhere (defers to session boot) - Fix existing preflight test to include getModelsConfig mock so auth cell path doesn't silently throw TypeError * fix(serve): exclude qwen-oauth placeholder from auth preflight fallback Skip the generationConfig.apiKey fallback for qwen-oauth auth type, which unconditionally sets apiKey to 'QWEN_OAUTH_DYNAMIC_TOKEN' placeholder. Without this, preflight falsely reports ok for qwen-oauth when no OAuth flow has been completed. Also use 'custom-provider' instead of 'qwen' in non-env-keyed auth tests to avoid confusion with real AuthType enum values. * fix(serve): use AUTH_PREFLIGHT_WAIVED_AUTH_TYPES set instead of hardcoded qwen-oauth check Replace hardcoded AuthType.QWEN_OAUTH guard with the centralized AUTH_PREFLIGHT_WAIVED_AUTH_TYPES set for the generationConfig fallback. Also add getModelsConfig mock to the second "6 cells" regression test. * test(serve): add getGenerationConfig to base mock to prevent silent TypeError The base mockConfig's getModelsConfig return value was missing getGenerationConfig, causing a silent TypeError when tests inheriting this mock exercise the auth preflight path added in this PR. |
||
|
|
e23c8e8459
|
feat(acp): Batch session load replay (#6309)
* feat(acp): batch session load replay Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6309) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6309) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6309) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): replay all initial snapshot events Initial response-mode replay was filtering the bridge snapshot down to session_update frames before subscribing from the snapshot high-water mark. That could skip other snapshot-backed events permanently. Extend the ACP transport regression test so initial replay includes a non-session_update bridge event. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(acp): harden bulk replay restore cleanup Clean up response-mode restore entries if replay seeding fails so retries do not attach to a closed zombie bus. Also normalize bulk replay timestamps before returning the private envelope and preserve partial replay usage accounting. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): expose partial ACP load replay status Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
90e1e3d47e
|
perf(cli): cache LoadedSettings per workspace with stat-based invalidation (#6310)
* perf(cli): cache LoadedSettings per workspace with stat-based invalidation
The ACP child under `qwen serve` is long-lived and re-runs a full
loadSettings() on the shared event loop for every session/new,
session/load and session/resume: four settings files read, parsed,
migration-checked and structuredClone'd, the .env tree walked, home
.env re-read, ${VAR} references re-resolved, and all scopes merged.
Same-cwd repeat sessions (the typical serve workload) pay full price
every time.
Add a process-level cache keyed by resolved workspace dir (LRU 64).
Freshness is checked deterministically on every access via a
fingerprint of every filesystem input: stat signatures
(mtimeMs:size:ino) of the four settings files, the re-discovered .env
file list with signatures, IDE trust, realpath(cwd) and
realpath(homedir). Any change -> full reload; fingerprint errors fail
open to a reload; loadSettings() throws propagate uncached.
Only the three hot ACP session handlers switch to loadSettingsCached();
all other loadSettings() callers (ext-methods write paths etc.) keep
their direct read semantics.
Known accepted differences (documented in the module doc): direct
process.env mutation without any file change does not re-bake ${VAR}
references on a hit; a .env edit racing the miss-path load itself is
the usual mtime-cache TOCTOU microsecond window; an in-place overwrite
preserving mtime+size+ino is invisible (self-writes go through
temp+rename, which changes the inode).
Part of the qwen serve multi-session performance work (#6263).
🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
* test(cli): add IDE trust flip invalidation test for settings cache
Covers the ideTrust fingerprint component, which is the only trust input
that can change within a live process (trustedFolders.json is a permanent
singleton, folder-trust toggles live in the settings files). Addresses a
Copilot review suggestion to guard against stale-cache trust regressions.
🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
* refactor(cli): harden settings cache observability and fail-open coverage
Addresses three review suggestions:
- Warn comment on settingsFileSigs that the 4-scope path list must stay in
sync with loadSettings() (unlike envFileSigs, it is enumerated separately).
- Add a createDebugLogger('SETTINGS_CACHE'), matching the SETTINGS /
SETTINGS_WATCHER / CONFIG convention in neighbouring config modules, and
log hit/miss, each fail-open catch (with the swallowed error), and eviction.
- Add a fault-injection test asserting the cache reloads (never throws) when
the fingerprint check fails, then recovers once the fault clears.
🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
|
||
|
|
aa4bccfc47
|
feat(acp): advertise vision-bridge image capability in initialize response (#6269)
* feat(acp): advertise vision-bridge image capability in initialize response Adds `_meta.imageCapability` to both stdio and HTTP ACP initialize responses so external hosts like sudowork can feature-detect native image handling instead of maintaining a hardcoded allowlist. Resolves #6086 * test(acp): cover image capability advertisement * docs(acp): clarify image capability threshold * fix(acp): align image capability contract |
||
|
|
9b3aa524a1
|
fix(cli): stream long responses into scrollback to stop scroll-to-top lock (#6170)
* fix(cli): stream long responses into scrollback to stop scroll-to-top lock ## Problem In non-VP (default) mode, scrolling up while the model streams a long reply — especially one containing a markdown table — jumps the viewport to the very top and locks it there until the response finishes (issue #5941). Root cause: when the live (below-`<Static>`) frame grows taller than the terminal, ink can no longer do its incremental cursor-up redraw and falls back to clearing + repainting the whole frame from the top on every token. A markdown table renders ~2 rows per data row (TableRenderer draws a separator between every row), so #6081's source-line budget under-counted the rendered height and the frame still overflowed for tables / wide CJK text. ## Fix Incremental scrollback streaming + a rendered-height safety net: - useGeminiStream: commit finished chunks of the streaming reply into `<Static>` (scrollback) so the pending live item stays short. The commit is rendered-height-aware (tables count double, wide/CJK lines wrap) and bounded by the live content-area height (threaded via `availableTerminalHeightRef`), with a reserve so it fires before the render-side clip. It commits in a `while` loop and splits only at `findLastSafeSplitPoint` boundaries (never inside a fenced code block). - MarkdownDisplay: a rendered-height-aware slice of the pending preview as a last line of defence — it guarantees the live frame never exceeds the viewport regardless of how the stream is chunked (tables charged at ~2x; non-table lines charged their wrapped height). A completed table renders in full; a table still being written renders live and is clamped by TableRenderer's new `maxHeight`. Result: long replies (and tables) flow smoothly into scrollback, tables draw live, and the viewport never locks to the top. Refs #5941, #6081 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): address review — share rendered-height estimator + guard edges Follow-up to review feedback on the incremental-scrollback streaming fix: - Extract a shared rendered-height estimator (`pendingRenderedHeight.ts`: `fitPendingSlice` / `estimateWrappedRows` / `isTableStart`) and use it from BOTH the useGeminiStream commit and the MarkdownDisplay safety-net slice, so the two agree on table (block: 2*dataRows + chrome) and wrap accounting instead of diverging. - useGeminiStream: use a conservative content-area fallback (terminalHeight minus a composer reserve) when `availableTerminalHeightRef` is not yet populated, so a short terminal never commits with an over-large budget. - MarkdownDisplay: allow the pending slice to keep 0 lines — a single very wide / CJK line that wraps past the budget now renders only the "generating more" cue instead of one oversized row that would bypass the height bound. - Add missing `useCallback` deps (terminalWidth / terminalHeight / availableTerminalHeightRef) — fixes the CI ESLint failure. - Tests: unit tests for the shared estimator (table detection, zero/negative width, CJK wrapping, cut-before / clamp / keptLines=0 boundaries) and for TableRenderer's `maxHeight` clamp (fit, clip+cue, vertical fallback, undefined passthrough). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): rename shared util to kebab-case to satisfy check-file lint The new pendingRenderedHeight.{ts,test.ts} tripped the check-file/filename-naming-convention (KEBAB_CASE) ESLint rule on new files in packages/cli/src. Rename to pending-rendered-height.{ts,test.ts} and update imports. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): point imports at renamed pending-rendered-height module The previous rename commit landed the file rename but not the importer edits (a stale pathspec aborted the git add), leaving MarkdownDisplay, useGeminiStream and the test importing the old ./pendingRenderedHeight.js path — a module-not- found in CI. Update the imports to the kebab-case path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): drop unused eslint-disable directive on while(true) reportUnusedDisableDirectives + --max-warnings 0 flags the no-constant-condition disable as an unused directive (the rule doesn't flag while(true) here). Remove it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): unify table parsing in shared module + cover commit edge cases Address the second review pass: - Move splitMarkdownTableRow and the table regexes (TABLE_ROW_RE / TABLE_SEPARATOR_RE) into pending-rendered-height.ts as the single source of truth; MarkdownDisplay now imports them instead of keeping duplicate copies. - isTableStart now also checks the separator's column count matches the header (mirroring the renderer's table detection) so the height estimator and the renderer agree on what is a table. - Tests: shared-module coverage for splitMarkdownTableRow and the isTableStart column-count check; tighten the incremental-commit assertion (budget-relative, requires multiple commits); add coverage for the splitPoint<=0 loop-break guard and for the populated-availableTerminalHeightRef production path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): commit streaming chunks only at block boundaries (no split tables) The incremental scrollback commit could cut a markdown table mid-way (e.g. when the tail row was still streaming): the committed chunk kept the header+rows and rendered as a table, but the continuation started with headerless `| ... |` rows that render as raw text (visible orphaned rows below a table). Only commit at a blank-line block boundary. A table (or list / code block) has no internal blank line, so it is never split into a headerless continuation; a still-streaming table stays pending — bounded in view by MarkdownDisplay's clamp — until it is complete, then commits whole. Tests: the oversized-commit test now uses blank-line-separated content and asserts every committed chunk ends at a block boundary; add a regression test that a streaming table taller than the budget is never committed as a headerless fragment (its header stays with its rows in the pending item). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): don't treat code-fence content as a table in the height estimator fitPendingSlice called isTableStart on every line regardless of fenced-code- block state, so table-like lines inside a ``` block were charged as a table (2*dataRows + chrome) while MarkdownDisplay renders them as code (one row each). Track the code fence and charge fenced lines individually. Share CODE_FENCE_RE from the module (MarkdownDisplay now imports it too) to keep a single source of truth. Adds a unit test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): account for vertical-format table height + tilde code fences Address the third review pass: - (Critical) fitPendingSlice charged the horizontal table height (2*dataRows+5) only, but TableRenderer falls back to the vertical key-value format on a narrow terminal / when cells wrap tall, which is much taller for 3+ column tables. Charge the larger of the horizontal and vertical estimates (dataRows*colCount + separators + marginY), still capped by the clamp — under-charging could let a vertical-format table overflow the viewport and re-introduce the scroll lock. - findLastSafeSplitPoint only recognised triple-backtick fences while the estimator's CODE_FENCE_RE also matches ~~~; a ~~~ block with an internal blank line could be split mid-block. isIndexInsideCodeBlock / findEnclosingCodeBlockStart now track both fence types (matching by fence character). - Tests: vertical-format table cost, ~~~ fence tracking, inline math and multi-backtick spans in splitMarkdownTableRow, and a ~~~ split-safety case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): only charge vertical-format table height on a narrow terminal The prior fix charged the max of the horizontal and vertical table estimates unconditionally, which over-estimated a table's height on a wide terminal (where it actually renders in the shorter horizontal format) and clipped small tables early with a premature "generating more". Mirror TableRenderer's width-based vertical decision (contentWidth < max(24, 6*colCount + 5)) and charge the format it will actually render: horizontal when the terminal is wide enough, vertical only when narrow — so a narrow-terminal vertical render still can't overflow and lock, but a small table on a wide terminal is no longer clipped prematurely. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): fix phantom code fences + read commit width from a live ref Address the fourth review pass: - (Critical) isIndexInsideCodeBlock / findEnclosingCodeBlockStart used indexOf('```', ...) which matches only the first three characters of a longer fence run, so a 4+ backtick/tilde fence was miscounted as two delimiters (phantom close-then-reopen). That could mark a blank line inside a code block as outside it, letting findLastSafeSplitPoint split mid-block and commit an unclosed code block to scrollback. findNextFence now returns the full run length, callers advance past the whole run, and a fence only closes a block opened with the same character and a run at least as long. - The commit loop read height live from availableTerminalHeightRef but width from the render-time closure, so a mid-stream resize handled the two inconsistently. Pair a terminalWidthRef with the height ref and read both live. Tests: a 6-backtick fenced block is not split at its internal blank line. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
59e771cef6
|
feat(daemon): Add session export endpoint (#6297)
* feat(daemon): add session export endpoint Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6297) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix PR integration capability baseline (#6297) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address export tool call id review (#6297) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
0e684a3444
|
fix(auth): prevent persistent 401 after API key change (#6284)
* fix(auth): prevent persistent 401 after API key change Empty-string environment variables (e.g. `DASHSCOPE_API_KEY=` from Docker env files or shell profiles) blocked settings.env from loading because Object.hasOwn returned true. Treat empty-string as effectively unset so settings.env can fill the gap. Also warn users at /auth time when a shell or .env variable will shadow the newly saved key on restart, and add debug logging when applyResolvedModelDefaults finds no API key for a model. Closes #6283 Refs #5979, #6129, #3417 * fix(auth): tighten env precedence handling * fix(cli): preserve settings env on reload * fix(auth): surface env shadowing warning |
||
|
|
2a6a9514e3
|
fix(acp): pass per-session settings explicitly instead of racing on this.settings (#6292)
* fix(acp): pass per-session settings explicitly instead of racing on this.settings ACP session handlers run concurrently, and session creation awaits config load, MCP discovery, and auth refresh between "load settings" and "construct Session". Two reads of the shared mutable `this.settings` race across that window: - `createAndStoreSession` constructed `Session` with whatever instance the most recent handler loaded, so a slow session creation could bind another workspace's LoadedSettings — which Session persists model changes through, writing into the wrong workspace's settings.json. - `loadSession`/`unstable_resumeSession` ran their existence check under the previous handler's `advanced.runtimeOutputDir`, producing spurious "session not found" errors across workspaces. Each handler now loads its workspace's settings once at the top and threads that instance through `newSessionConfig` and `createAndStoreSession`; `this.settings` remains a "latest loaded" cache for agent-level readers. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(acp): adopt settings cache only after session existence is confirmed A failed loadSession/unstable_resumeSession probe (stale id, different cwd) must not repoint the agent-level `this.settings` cache at the failed request's workspace — readers like `authenticate` and provider ext-methods would otherwise pick it up. The existence check itself only needs the local per-request instance, so move the cache adoption after the 404 throw, restoring the pre-fix cache timing exactly. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) |
||
|
|
cdf83d8bd0
|
fix(core): give Stop-hook continuations a fresh per-turn tool-call budget; make the cap configurable (#6238)
* fix(core): give Stop-hook continuations a fresh per-turn tool-call budget; make the cap configurable A blocking Stop-hook continuation (e.g. a /goal iteration) feeds a fresh user-role prompt to the model — a new logical turn — but the loop detector never reset, so an entire goal chain billed one per-turn tool-call budget and healthy long-running goals halted with turn_tool_call_cap. The ACP daemon path already used per-continuation budgets; core now matches. - Reset loop detection at each blocking Stop-hook continuation - Add model.maxToolCallsPerTurn setting (default 100; <= 0 disables), resolved once in Config (<= 0 maps to Infinity) - Honor the in-session 'Disable loop detection for this session' choice in the per-turn cap, as the dialog always claimed - Point the headless halt message at the setting; update dialog/docs * test(cli): add DEFAULT_MAX_TOOL_CALLS_PER_TURN to core module mocks --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
ad7e23f99f
|
feat(web-shell): add MCP mentions and iconized @ references (#6279)
* feat(web-shell): add MCP server mentions in @ completion * fix(web-shell): polish @ completion groups * feat(web-shell): add icons for @ references * fix(web-shell): refine @ completion behavior * fix(cli): show MCP mentions for bare @ --------- Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com> |
||
|
|
5dc2e1501f
|
feat(serve): Add runtime.activity fields to daemon status API (#6270)
* feat(serve): add runtime.activity fields to daemon status API Add activePrompts, lastActivityAt, and idleSinceMs to the GET /daemon/status runtime section. These fields already exist on the bridge (and are exposed via GET /health?deep=1) but were missing from the richer status endpoint that operators use for troubleshooting. The idleSinceMs value is computed from a cached lastActivityAt read (same pattern as the health handler) to ensure consistency within a single response. * feat(serve): add MCP server health summary to workspace status Extract serversConnected, serversErrored, and serversDisabled counts from the MCP servers array into the workspace.mcp.summary object. Operators can see MCP fleet health at a glance without expanding the full JSON. * fix(serve): guard activity fields against undefined bridge getters Add ?? null / ?? 0 fallbacks for lastActivityAt and activePromptCount to prevent RangeError when a test fake bridge omits these properties. |
||
|
|
741780517e
|
feat(review): route suggestion-level findings to an updatable PR comment (#5786)
* feat(review): route suggestion-level findings to an updatable PR comment
Suggestion-level /review findings now go to a single issue comment that is PATCHed in place across runs, instead of becoming per-line inline comments. Critical findings stay inline.
Why: every /review run re-emitted a fresh batch of inline comments with no notion of "this suggestion was already posted and is still open", so the PR Files-changed view grew noisier each round and issues never converged — worst for agentic authors who feel forced to resolve each thread one-by-one. One updatable comment keeps the suggestion list a single refreshable view; the locate-and-PATCH lives in a new deterministic `qwen review post-suggestions` subcommand so the LLM never reposts a duplicate.
* fix(review): validate SUMMARY_MARKER in body-file and harden payload cleanup
Add runtime validation that the body-file contains SUMMARY_MARKER before
posting to GitHub, preventing duplicate summary comments when the marker
is accidentally omitted.
Move writeFileSync(payloadPath) inside the try block so that finally's
unlinkSync cannot throw ENOENT when preceding code throws before the file
is written. Wrap unlinkSync in try/catch as best-effort cleanup.
* fix(review): add runPostSuggestions tests and clear stale summaries
Add 4 integration tests covering the PATCH/POST branching, marker
validation, and payload cleanup on error (previously untested I/O path).
Update SKILL.md and DESIGN.md so that when a /review run finds zero
new Suggestions but a prior summary comment exists, the stale table is
replaced with an 'all addressed' message instead of being left frozen.
* fix(review): resolve SKILL.md contradictions and add COMMENT event example
- Fix body rule to allow unmappable Critical findings in review body
- Add JSON example for Suggestion-only COMMENT event reviews
- Align --body-file describe text and SKILL.md marker wording with
actual includes() validation (was documented as startsWith)
* fix(review): exclude suggestion summaries from Already-discussed section in pr-context
Filter issue comments containing SUMMARY_MARKER out of the 'Already
discussed — do NOT re-report' section and render them in a dedicated
'Previous suggestion summary (evaluate afresh)' section instead.
Without this, review agents treat prior suggestion rows as already
discussed, produce zero new Suggestions, and the all-addressed path
overwrites the summary even though nothing was actually fixed.
* fix(review): add author verification to suggestion summary filter
* fix(review): ensure out dir exists and frame gh-api parse errors in post-suggestions
- mkdirSync(dirname(out)) before writing the payload/report, matching every
peer review subcommand (pr-context, fetch-pr, load-rules, deterministic).
Without it, an --out under a not-yet-created dir (e.g. .qwen/tmp/) crashed
with a raw ENOENT.
- Wrap both JSON.parse(raw) of the gh-api response so a non-JSON body (empty,
rate-limit JSON, HTML during an outage) throws a diagnostic error naming the
failed call and showing the raw output, like fetch-pr.ts does, instead of a
bare SyntaxError.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(review): render previous suggestion summary verbatim in pr-context
The 'Previous suggestion summary (evaluate afresh)' section passed the
summary body through snippet(), which collapses all whitespace into
single spaces and truncates at 500 chars. The summary is a multi-row
Markdown table, so this mangled it into an unreadable single line and
dropped rows — defeating the 're-evaluate each row' purpose. Render the
body verbatim (only stripping the locator marker); it is our own
author-verified comment, so preserving its structure is safe.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* chore(review): restore non-review files to main to keep PR diff scoped
The old branch carried prettier/.editorconfig-driven reformats of files
unrelated to the /review suggestion-summary feature (mcp-client, acp-bridge,
feishu adapter, workflow-orchestrator/client-mcp tests, and channel-loop /
settings docs). These are cosmetic-only and not enforced by CI (the prettier
step runs 'prettier --write .' without a diff gate), but they polluted the PR.
Restore them verbatim to origin/main so the PR diff is review-only.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* test(review): expect post-suggestions in registered subcommand list
main's PR #6092 added review.test.ts locking the 'qwen review' subcommand
surface to exactly 5 helpers. This PR adds a 6th, post-suggestions, so the
guard test must include it. Keeps the deterministic-removal guards intact.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(review): exclude all stale summaries, add pr-context tests, clarify event rule
Address the latest /review suggestions:
- pr-context: build summaryIds from every one of my summary comments, not just
the latest — a leftover older summary (e.g. after a failed PATCH+POST) was
leaking into the 'Already discussed' section and could suppress still-open
findings. Extract the author+marker selection into a pure, exported
collectSuggestionSummaries() and cover the prompt-injection guard, latest-wins
ordering, and full-exclusion behavior in a new pr-context.test.ts.
- post-suggestions.test: cover the non-JSON gh-api diagnostic paths (PATCH/POST)
and the mkdirSync(dirname(out)) call added in
|
||
|
|
4e3fd29781
|
chore(release): v0.19.6 (#6280)
* chore(release): v0.19.6 * docs(changelog): sync for v0.19.6 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
cc64d7ce7f
|
feat(daemon): expose visionModelId in workspace provider status and web-shell model dialog (#6262)
* ci(autofix): restore sandbox image flow * feat(daemon): expose visionModelId in workspace provider status and web-shell model dialog (#6195) --------- Co-authored-by: yiliang114 <effortyiliang@gmail.com> Co-authored-by: Qwen Autofix <autofix@qwen-code.ai> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |