The daemon-managed channel worker reads each channel's settings (tokens, proxy, per-channel model) once when it starts, so applying settings.json changes previously required restarting the whole daemon. This adds an explicit reload that stops and relaunches the worker so it re-reads settings.json, without bouncing the daemon or its live sessions.
The reload is exposed as a strict-gated POST /workspace/channel/reload route, an SDK reloadChannelWorker() method, and a qwen channel reload CLI command, advertised through a channel_reload capability only when the daemon was started with --channel. The worker supervisor gains a restart() that coalesces concurrent reloads onto a single relaunch, resets the crash-restart budget so a failed worker recovers, and latches a disposed flag on hard shutdown so a racing reload cannot relaunch a worker into a tearing-down daemon.
Refs #5976
Suggestion-level findings were routed to a single updatable issue comment
(the "suggestion summary") while only Critical findings became inline review
comments. That split traded away two things that turned out to matter more
than the convergence it bought:
- An issue comment has no lifecycle. GitHub folds an inline review thread away
as Outdated once the author edits the line it is anchored to, so an addressed
finding removes itself from the page. The summary comment just sits in the PR
conversation forever; PATCHing it to "all addressed" replaces its content but
not the comment. The mechanism meant to prevent clutter was the clutter.
- A Markdown table cannot carry a one-click fix. GitHub renders a ```suggestion
fence as an applicable change only inside a review comment on a diff line.
Suggestion findings are exactly the mechanical, localized cleanups that
benefit most from one-click apply, so the split withheld the feature from the
findings that needed it most.
Both severities now post as inline comments, distinguished by a **[Critical]**
or **[Suggestion]** body prefix. The `qwen review post-suggestions` subcommand
and its plumbing are removed.
Follow-on changes required by the reroute:
- pr-context: the "Previous suggestion summary" section is gone. Legacy summary
comments are still recognised so they stay out of "Already discussed", but the
exclusion is now marker-only rather than author-gated. The author check missed
summaries posted by the *other* identity: /review runs as a maintainer locally
and as qwen-code-ci-bot in CI, and roughly half of the last 60 PRs carry a
bot-authored summary. Those leaked into "Already discussed" and told the review
agents not to re-report the findings listed there. The check originally guarded
promotion into a trusted rendering section; that section no longer exists, so
it only gated exclusion, where a third party embedding the marker merely hides
their own comment.
- qwen-autofix: the workflow filters "suggestion summaries" out of the autofix
bot's actionable queue, but only on the issue-comment channel. With Suggestions
now inline, they entered the unfiltered inline channel and the bot would apply
non-blocking recommendations and spend a review round on them. The inline
channel now applies the same gate, keyed on the **[Suggestion]** prefix plus
the /review footer so a human quoting the prefix stays actionable.
- Step 7 gains a 422 fallback. Create Review is all-or-nothing, so one Suggestion
anchored outside the diff would take the Critical findings down with it — a risk
that did not exist when Suggestions travelled on a line-agnostic issue comment.
GitHub's 422 does not name the offending entry, so the model rechecks anchors
against the diff, relocates failing Criticals into the body, discards failing
Suggestions, and degrades to an all-prose review rather than posting nothing.
COMMENT reviews now always carry a one-line body: an empty body is only known
to be accepted alongside inline comments on REQUEST_CHANGES, and a Suggestion-
only review is the common case for a clean PR.
* fix(core): configurable vision bridge timeout + retry with fresh budget
The vision bridge capped image transcription at a hardcoded 30s. On a
slow or proxied vision endpoint one latency spike permanently lost the
image: the retry inside the side query shared the same abort signal, so
a second attempt inherited whatever seconds were left of the first
attempt's budget.
Add a visionBridgeTimeoutMs setting (per attempt; unset keeps 30s,
non-positive values are ignored) and retry a timed-out attempt once at
the bridge level with a freshly created timeout signal. Non-timeout
failures still fail immediately, and user cancellation is still
reported as skipped.
Fixes#6524
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(core): harden visionBridgeTimeoutMs against invalid timer values
Maintainer E2E review found that fractional or out-of-range values such as 30000.5 and 4294967296 could pass the old number-typed config path and Config's Number.isFinite && > 0 guard. Node rejects fractional AbortSignal.timeout values with RangeError and can degrade oversized timer values to a 1ms timeout, which made image turns fail before any model request.
Tighten the Config guard to positive integers within the supported 32-bit timer ceiling, make visionBridgeTimeoutMs a bounded integer setting so /config and the generated JSON schema reject bad values up front, and move AbortSignal.timeout/any creation inside the bridge try block so any future bad value becomes a safe failure result instead of an escaped rejection. Also mark the setting requiresRestart because it is read once in the Config constructor.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Audit findings against the current codebase:
- model-providers.md, auth.md: the documented modelProviders shape used
the reverted `{ protocol, models }` wrapper. The canonical shape is a
bare `ModelConfig[]` array per provider id (a wrapped entry in a
migrated settings file is silently skipped). Update all examples and
prose, document the separate top-level `providerProtocol` map for
custom provider ids, and correct the unknown-key behavior.
- settings.md: correct the default for
`model.chatCompression.screenshotTriggerThreshold` (20, not 50).
- commands.md: add the missing `/reload-plugins` command and note that
`/dream` and `/forget` are registered only when managed auto-memory
is available.
- Add a Computer Use feature page (on-by-default desktop automation via
the cua-driver native driver) and wire it into the features nav and
the qc-helper doc index.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(memory): make background memory agent timeouts configurable
Adds a memory.agentTimeoutMinutes setting that overrides the hardcoded
max runtime of the four background memory agents (extraction, dream,
remember, skill review). Unset keeps each agent's built-in default
(2-5 minutes); 0 disables the time limit entirely.
Local LLM setups load large extraction prompts far slower than hosted
models, so the fixed 2-minute extractor budget times out before the
context even finishes loading — and each retry carries a longer
conversation, making the next timeout more likely.
Fixes#6308
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(memory): address review — wire agentTimeoutMinutes to skill review, clamp negatives, add tests
The auto-skill scheduling path always passed an explicit timeoutMs, so
the new setting never reached the skill review agent; drop the redundant
pass-through so the planner's config fallback applies. Clamp negative
settings values at the Config constructor (schema validation only runs
on interactive edit paths). Add positive override tests for the dream,
remember, and skill review planners, and reduce the settings.md diff to
the single new table row.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(memory): cover negative-clamp and remember default-timeout paths
Review follow-up: assert the Config constructor treats a negative
memory.agentTimeoutMinutes as unset, and that the remember planner keeps
its built-in 5-minute default when nothing is configured.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
getAutoMemoryRoot() resolved linked worktrees back to the canonical
repository root, so every worktree of a repo shared one project memory.
Focused worktree sessions polluted the shared MEMORY.md index with
unrelated entries, and chats/, workflows/, and team memory were already
per-worktree — project memory was the only shared exception.
Anchor project memory at the nearest git root (same resolution team
memory already uses) so each worktree gets its own memory directory.
The main checkout resolves to the same path as before, so existing
memory is unaffected.
Fixes#6449
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(channels): add dmPolicy config to disable private/DM messages
Add DmGate class mirroring GroupGate to gate DM/private messages in
channel adapters. Operators can now set dmPolicy: 'disabled' in their
channel config to silently drop all DM messages while keeping group
messages active.
Closes#6392
* fix(channels): address review feedback for dmPolicy
- Add dmPolicy: 'open' to all test config factories (8 files) to
maintain type correctness with required ChannelConfig field
- Add integration tests in ChannelBase.test.ts:
- preflightInbound: DM dropped + group passes when dmPolicy=disabled
- isStoredLoopTargetAuthorized: DM loop job disabled + group passes
- Add dmPolicy assertions in config-utils.test.ts (default + explicit)
- Keep dmPolicy as required field (not optional) for strict parity
with groupPolicy
The WeCom intelligent robot channel (added in #6436) has its own guide and _meta.ts entry, but the channels overview was never updated. Add WeCom to the platform list, quick-start guide links, the `type` option and `token` exclusion note, new `botId`/`secret` option rows, the media-support note, and the slash-command channel enumeration.
Co-authored-by: Claude <noreply@anthropic.com>
* perf(cli): defer startup prefetch tasks
* fix(cli): await IDE for prompt-interactive startup
* perf(cli): defer interactive telemetry startup
* test(cli): add missing assertions and Zed/ACP path coverage for startup prefetch
Address three test coverage gaps identified during code review:
- Assert mockStartEarlyStartupPrefetches in both kitty protocol tests
(C1: API preconnect call was wired but never verified)
- Add Zed/ACP integration test verifying deferIdeConnection is false
when getExperimentalZedIntegration returns true (C2: Zed path was
entirely untested)
- Assert mockStartBackgroundHousekeeping in startup-prefetch test
(C3: unconditional housekeeping dispatch was never verified)
* docs: move startup prefetch design doc to performance subdirectory
* docs: translate startup prefetch design doc to English
* fix(cli): address startup prefetch review comments
Tighten the startup prefetch follow-up fixes from review while keeping
prompt-interactive telemetry on the fast interactive startup path.
- Preserve Error objects when deferred startup tasks fail
- Remove the unbalanced api_preconnect profiler lifecycle event
- Guard background housekeeping so it only runs for interactive configs
- Document and test prompt-interactive telemetry deferral semantics
* fix(cli): initialize telemetry for prompt-interactive prompts
Ensure sessions launched with an initial interactive prompt have
telemetry ready before the auto-submitted first request runs.
- Exclude prompt-interactive startup from telemetry deferral
- Pass a post-render telemetry option through interactive UI startup
- Skip duplicate post-render telemetry startup for initial prompts
- Update tests to cover the first-prompt telemetry guarantee
Note: Plain interactive TUI startup still defers telemetry post-render.
* fix(cli): preserve startup first-request guarantees
Keep deferred startup work from weakening first-request behavior in
interactive sessions that submit prompts automatically or remotely.
- Store telemetry deferral on Config and reuse that decision at render time
- Keep IDE startup awaited for prompt-interactive and input-file sessions
- Add a timeout for deferred IDE connection failures
- Cover ordinary interactive telemetry deferral and IDE startup edge cases
* fix(cli): make post-render IDE connection opt-in
Default startInteractiveUI to the already-connected IDE path so future
callers do not accidentally connect twice when initializeApp used its
eager default.
- Change the post-render IDE connection default to false
- Update startInteractiveUI tests to assert the safer default
* perf(cli): surface deferred IDE connection status
Make ordinary interactive IDE startup visible while preserving the
post-render prefetch path and first-paint performance tradeoff.
- Emit deferred IDE connection lifecycle events for connecting, success,
and failure states
- Surface IDE startup status in the TUI footer without blocking input
- Log late underlying IDE failures after timeout for better diagnostics
- Document telemetry deferral tradeoffs and add startup lifecycle tests
---------
Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
* feat(channels): add natural channel memory intents
* fix(channels): add explicit guard and exhaustiveness check for clear_confirm intent
The clear_confirm path was handled as implicit fall-through at the bottom
of handleChannelMemoryIntent. If a new intent kind were added to the
ChannelMemoryIntent union, it would silently execute clearChannelMemory
without user confirmation — a data-loss risk.
Add explicit if (intent.kind === 'clear_confirm') guard and a
const _exhaustive: never assertion so TypeScript flags any unhandled
kinds at compile time.
* fix(channels): close session leak in classifier and fix regex separator
- BridgeChannelMemoryIntentClassifier now wraps prompt() in try/finally
to always call cancelSession(), preventing daemon session leaks on
every classifier invocation. Cleanup errors are caught so they cannot
mask a successful classification result.
- Add missing optional punctuation separator to the 以后记住 regex
pattern for consistency with other Chinese remember patterns.
* fix(channels): enforce pending clear state for channel memory confirmation
The clear_confirm intent executed clearChannelMemory directly without
verifying a prior clear_request was issued for the same chat. Any
authorized user could clear any chat's memory by sending the confirmation
phrase standalone, bypassing the two-step flow.
Add a per-target pending clear map (chatId + threadId, 60s TTL) that
is set during clear_request and verified+consumed during clear_confirm.
Standalone confirmation phrases now get rejected with a prompt to
issue the clear request first.
* fix(channels): include senderId in pendingClears key to prevent cross-user confirmation
User A could initiate clear_request in a group chat and User B could
confirm it, since the pending key only included chatId+threadId.
Add senderId to the key so only the user who initiated the clear
can confirm it.
* fix(channels): harden memory intent review fixes
* fix(channels): cover memory clear sender guard
* fix(channels): block group memory mutations
* fix(channels): avoid ambiguous memory saves
* test(channels): cover memory classifier cleanup
* test(channels): cover memory clear expiry
* fix(channels): restore channel memory slash aliases
* test(channels): cover memory intent edge cases
Design docs and implementation plans were scattered across .qwen/design,
.qwen/plans, and docs/superpowers. The .qwen/ locations are git-ignored, so
docs written there never got tracked, while docs/design already held the
richer, version-controlled set. Consolidate everything under docs/design and
docs/plans, relocate two stray root docs into docs/design, and repoint the
references left dangling by the move (moved-doc cross-links and a few source
comments).
Also update AGENTS.md and the feat-dev skill so the documented workflow writes
new design docs and plans to the tracked docs/ locations.
Co-authored-by: DragonnZhang <dragonzhang1024@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* 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>
The bundled /review skill is a general command that runs against arbitrary
repositories (and cross-repo PRs), but a previous change baked qwen-code's own
"core infrastructure is maintainer-only" governance into the shipped prompt:
hardcoded packages/core and packages/*/src/{auth,providers,models,config,tools,services}
paths, a 500+ line hard block, and an authorAssociation-based maintainer check.
Those path names are generic — src/auth, src/config, src/tools, src/services are
common across monorepos — so an external contributor's large PR to an unrelated
repo would be hard-blocked as "must be maintainer-initiated" under a policy that
repo never adopted.
Remove the gate and its escalate-flag plumbing (Steps 1, 6, and 7) from the
bundled skill, along with the matching DESIGN.md rationale and the user-doc
section. qwen-code's maintainer-only policy stays documented in AGENTS.md for
this repo. The Issue Fidelity / root-cause ownership agent (Agent 0) is a
universal review principle and is left unchanged.
Co-authored-by: dragon <dragon@U-2Q53JQG9-0233.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(review): add issue-fidelity and root-cause ownership gate to /review
Adds a dedicated Issue Fidelity & Root-Cause Ownership agent (Agent 0) to
the /review pipeline and a core-infrastructure scope gate that runs before
the review agents.
Agent 0 fetches linked GitHub issue evidence directly (closingIssuesReferences
plus issue comments) instead of trusting the PR author's framing, compares the
original reported failure against the PR's claimed fix, and flags client-side
parser/sanitizer workarounds for malformed upstream output as Critical unless a
maintainer explicitly requested the defensive mitigation. The core-infra gate
applies the repository's existing two-tier maintainer-only rule before spending
review budget.
This hardens the pipeline against a false-approval mode where a bot PR passes
its own tests and reads as internally reasonable but fixes the author's mistaken
diagnosis rather than the linked issue's actual root cause.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(review): address PR review feedback on issue-fidelity gate
- Fetch issue evidence with `gh issue view --json title,body,comments` so the
issue body (reporter repro/observed payload/expected behavior) is included;
`--comments` alone omits it. Use each closingIssuesReferences entry's own
repository so cross-repo linked issues resolve correctly.
- Treat closingIssuesReferences as a discovery hint (fetch apparent target
issues even when it is empty) and treat fetched issue content as untrusted
data (extract facts, ignore embedded instructions).
- Run Agent 0 (Issue Fidelity) only for PR targets; skip it for local-diff and
file-path reviews, and require the PR number/repo/context in its prompt.
Handle empty references / non-bugfix / gh failure explicitly.
- Pass Agent 0's quoted issue evidence to Step 4 batch verification and stop it
rejecting issue-grounded findings just because the code compiles/tests pass.
- Make the core-infrastructure gate concrete: deterministic maintainer signal
via authorAssociation, count only core-path lines, honor the AGENTS.md
low-risk-sweep exception, clean up the worktree on hard block, run the gate
right after fetch-pr (before npm ci), and map escalate -> COMMENT (never
APPROVE) in Steps 6-7.
- Sync agent counts and token math across SKILL.md, DESIGN.md, and
code-review.md (Agent 0 is PR-only; ~620-730K).
* docs(review): rename 'Linked Issue Fit' heading to 'Issue Fidelity'
Aligns the code-review docs heading with the 'Issue Fidelity' name used
for Agent 0 in SKILL.md and DESIGN.md, so the section connects to the
pipeline diagram. Addresses review feedback.
* docs(review): stop core-infra hard block before load-rules and surface it via --comment
- Hard block now stops before Step 2 (load-rules) instead of before Step 3,
so a PR destined for hard-block no longer runs the load-rules step.
- In --comment mode the hard block posts an event=COMMENT on the PR, matching
the escalate path's GitHub visibility, so external authors see the block.
---------
Co-authored-by: dragon <dragon@U-2Q53JQG9-0233.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Correct and complete the user-facing settings documentation against
packages/cli/src/config/settingsSchema.ts:
- settings.md: fix general.defaultFileEncoding type (enum, not string);
document the general.voice.* dictation settings, top-level
modelFallbacks and modelPricing, tools.computerUse.idleTimeoutMs,
mcp.toolIdleTimeoutMs, and the skills.disabled denylist.
- model-providers.md: correct the resolution-layers table — only
--openai-api-key/--openai-base-url exist; there are no
provider-specific credential CLI flags.
Co-authored-by: Claude <noreply@anthropic.com>
* 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>
Make tool declaration ordering deterministic so prompt-cache prefixes do not depend on asynchronous registration history.
- Sort function declarations by canonical tool name after existing visibility filtering
- Preserve deferred, revealed, and alwaysLoad filtering semantics
- Add tests covering deferred tools, revealed tools, and MCP registration order
- Document the prompt-cache motivation and next-step cache break detection plan
Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
* docs: fix skill invocation syntax and include Feishu in channel lists
* docs: add Feishu column to channel media-handling table
Address review feedback on PR #6320: after adding Feishu to the prose
channel lists, the 'Platform differences' media-handling table still
omitted it. Add a Feishu column (images/files via the authenticated Open
API resources endpoint, 50MB limit; rich-text 'post' captions), verified
against packages/channels/feishu/src/media.ts and FeishuAdapter.ts. Add a
note that QQ Bot ignores incoming media (per QQChannel.ts) so it has no
row.
* docs: clarify Feishu post messages drop embedded images
The Platform differences table claimed Feishu rich-text (post) messages
carry 'mixed text + images', but FeishuAdapter's post parser only
extracts text/a/at nodes and silently drops img nodes (both the live
handler and the history-backfill path). Correct the Captions cell to
say text is extracted and embedded images are ignored.
* docs: mark clientId/clientSecret as required for Feishu too
Feishu declares requiredConfigFields: ['clientId', 'clientSecret']
(packages/channels/feishu/src/index.ts), same as DingTalk, but the
channel options table listed both fields as DingTalk-only. Update the
Required column and descriptions to cover Feishu (App ID / App Secret).
* docs: note token is not needed for Feishu in channel config table
* docs: note 50MB limit on Feishu image downloads
Feishu images and files both flow through the same downloadMedia() in
packages/channels/feishu/src/media.ts, which enforces a single
MAX_DOWNLOAD_BYTES = 50MB cap. Add the (50MB limit) note to the Images
cell for consistency with the Files cell.
* docs: clarify /skills panel-vs-run behavior per review feedback
- skills.md: add a migration Note that /skills <name> now opens the
Skills panel and ignores trailing args; use /<skill-name> to run.
- commands.md: list the /skills Usage cell as /skills, /<skill-name>
for consistency with the rest of the table.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: 易良 <1204183885@qq.com>
* fix(core): treat request timeout of 0 as disabled instead of aborting immediately
A provider `generationConfig.timeout` of `0` (and `QWEN_CODE_API_TIMEOUT_MS=0`) now
disables the request timeout, matching the existing `QWEN_STREAM_IDLE_TIMEOUT_MS=0`
convention, instead of being coerced to the 120s default (Anthropic `||`) or passed
to the OpenAI SDK as `timeout: 0` (which the SDK treats as an immediate abort).
- add `resolveRequestTimeout()` + `DISABLED_REQUEST_TIMEOUT_MS`, mapping a disabled
timeout to the JS timer ceiling (2^31-1 ms), reusing the same ceiling already used
for `MAX_STREAM_IDLE_TIMEOUT_MS`
- use it in the OpenAI default/dashscope providers and the Anthropic provider
- accept `0` in the `QWEN_CODE_API_TIMEOUT_MS` env override without weakening the
shared `parsePositiveIntegerEnv` (relied on by ~15 other callers to reject 0)
- document the timeout unit and 0-disables semantics in settings.md
Fixes#6049
* Update packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
* test(core): fix broken constants mock merge in dashscope.test
Commit 0f5aa0c interleaved two vi.mock('../constants.js') blocks, leaving
orphaned fragments that produced TS syntax errors and stopped the dashscope
suite from loading. Replace with a single importOriginal-based mock that
overrides only DASHSCOPE_PROXY_BASE_URL and delegates every other constant
(timeouts, DISABLED_REQUEST_TIMEOUT_MS, resolveRequestTimeout) to the real
module, so the mock cannot drift from the implementation.
---------
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
* 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>
* 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.
Carry nested-agent lineage (parentAgentId, parentName, depth) through the
daemon tasks snapshot as optional fields and render the web-shell tasks
panel as a tree: children group under their parent with a ↳ marker and
clamped indentation, agents whose parent left the roster are promoted to
root with a "from <parent>" annotation, and the detail view gains a
nesting line. The [blocking] tag and the two-step stop confirmation now
apply only to provably user-blocking chains, mirroring the TUI's
agent-forest semantics from #6191.
* feat(scheduler): make recurring cron/loop job expiration configurable
Recurring cron/loop jobs previously auto-expired after a hardcoded 7 days with no way to extend or disable the limit, forcing long-running daemon deployments to recreate jobs weekly.
Add an experimental.cronRecurringMaxAgeDays setting (default 7) and a QWEN_CODE_CRON_MAX_AGE_DAYS environment-variable override (takes precedence, for cloud/container deployments). A value of 0 disables expiry so jobs run until deleted; negative or unparseable values fall back to the 7-day default. The configured limit also applies to durable tasks restored from disk, and the CronCreate tool description now reflects the effective limit instead of a hardcoded '7 days'.
Closes#6167
* refactor(scheduler): drop unused DEFAULT_RECURRING_MAX_AGE_MS export
Review feedback on #6173 — the ms constant is only used inside the module as the constructor default; keep only the days constant public.
* fix(scheduler): align zero max-age semantics and warn on cron expiry misconfiguration
Review feedback on #6173:
- CronScheduler constructor now maps 0 to Infinity (never expire), matching the config layer instead of silently substituting the 7-day default for direct callers.
- Invalid QWEN_CODE_CRON_MAX_AGE_DAYS / cronRecurringMaxAgeDays values now log a warning before falling back to the default, leaving a breadcrumb for misconfigured deployments.
- Durable tasks found past the recurring max age at load now log a warning before their final fire + delete, since a lowered max age retroactively expires long-lived tasks and deletion is unrecoverable.
- New durable-restore tests: a custom max age applies to tasks reloaded from disk, and a disabled max age (Infinity) restores a 30-day-old task as live instead of aging it out.
* fix(scheduler): surface cron expiry warnings on the console
Review feedback on #6173:
- The invalid-config and retroactive-expiry warnings moved from debugLogger (file-only, off unless QWEN_DEBUG_LOG_FILE is set) to console.warn so they reach container/daemon logs where this knob matters; the config warning is latched to fire once per Config instance.
- CronCreateTool's constructor now resolves the max age once instead of three times, so an invalid env var can't emit duplicate warnings during tool registration.
* fix(scheduler): reject non-finite timestamps in durable task validation
Review feedback on #6173: JSON like -1e999 parses to -Infinity, which passes the typeof-number check and then poisons date math — with a finite lastFiredAt the entry reads as an overdue aged task and the retroactive-expiry warning's toISOString() throws RangeError mid-load, so one malformed persisted entry blocks durable cron startup/takeover. Validation now requires finite createdAt (and lastFiredAt when non-null), routing such entries through the existing fix-or-delete contract for corrupt files. Verified the new end-to-end test reproduces the RangeError without the fix.
* refactor(scheduler): single-source the max-age contract and freeze it at Config construction
Review follow-ups on #6173:
- Extract normalizeRecurringMaxAge as the single owner of the
0/Infinity no-expiry contract, used by both the Config layer and the
CronScheduler constructor, so the constructor's 0 handling can no
longer be removed as apparent dead code.
- Resolve QWEN_CODE_CRON_MAX_AGE_DAYS once at Config construction into
a readonly field, honoring the setting's requiresRestart contract;
mid-session env changes can no longer make the tool description,
tool output, and scheduler report different expiries. The warn
once-latch is now unnecessary (construction warns at most once).
* feat(core): allow bounded nested sub-agent spawning via maxSubagentDepth
Sub-agents may now spawn sub-agents up to a configurable maximum nesting
depth (default 5; 1 reproduces the previous no-nesting behavior).
Enforced in two layers sharing one predicate: prepareTools() hides the
agent tool from leaf-depth sub-agents, and AgentTool.execute() rejects
over-depth spawns as an authoritative backstop. Teammates, forks, and
the workflow tool remain excluded from nesting. Launch depth is
persisted in the agent meta sidecar and restored on resume (including
deferred-approval continuations and in-process AgentInteractive frames)
so a resumed nested agent cannot regain spawn capacity.
See knowledge/qwen-code/design/nested-subagents.md.
* fix(core): address review findings on nested sub-agent spawning
- Deny the agent tool to workflow-spawned subagents: depth gating would
otherwise re-admit it, letting a workflow leaf spawn outside the
orchestrator's concurrency cap, agent accounting, and token budget.
- Reject non-finite maxSubagentDepth values (JSON 1e309 parses to
Infinity and would unbound the recursion cap; NaN would silently block
all nesting) and cap the knob at 100 to catch typos.
- Add a --max-subagent-depth CLI flag mirroring sibling budget flags,
with loud validation for flag typos, and document the setting.
- Log guard rejections (depth, fork containment) and silent
fork-to-subagent downgrades through the agent debug logger.
- Refresh stale comments (depthOverride resume pinning, depth-gated
AgentTool exclusion) and drop references to a design doc that lives
outside the repository.
- Fill review-noted test gaps: nesting predicate primitives, fork-context
prepareTools, persisted-depth restoration on background resume,
nested AgentInteractive depth pinning, nested fork fallback, and the
blocked-spawn returnDisplay shape.
* fix(cli): add maxSubagentDepth to the CliArgs test literal
The exhaustive CliArgs mock in gemini.test.tsx missed the new field,
failing CI's clean tsc build (the local incremental build skipped the
test file).
* fix(core): add a teammate backstop to the agent spawn guards
execute() backstopped depth and fork containment but not the teammate
exclusion, so its guards covered less than prepareTools() gates. A
teammate spawn call that slipped past schema-hiding would have nested.
Block it symmetrically with the fork guard, log the rejection, and pin
the behavior in a test.
* fix(core): normalize persisted maxSubagentDepth on resume
The resume path trusted the raw sidecar value, bypassing the Config
clamp — a tampered or malformed sidecar (1e309 parses to Infinity;
JSON.stringify turns Infinity into null) would remove the nesting cap
for resumed agents. Extract the clamp into a shared
normalizeMaxSubagentDepth used by both the Config constructor and the
flag-restore path, and refresh the stale settings schema description
(clamp range, non-finite fallback, workflow-agent wording).
* test(core): pin null-to-default normalization of resumed maxSubagentDepth
JSON.stringify(Infinity) === 'null', so a sidecar can legitimately carry
null; widen the persisted flag type to admit it and parameterize the
resume test over both the clamp (5000 -> 100) and the null fallback
(null -> 5).
* fix(core): harden nesting depth edges from final review pass
- Normalize persisted meta.depth on resume: the sidecar is untrusted
JSON, and a tampered negative depth (or -1e309 → -Infinity) would pin
the resumed frame below zero and pass canSpawnNestedAgent for every
cap. Invalid values fail closed to the depth ceiling — the agent
keeps running but cannot spawn.
- Register monitor notification routing for in-process interactive
agents: framing runLoop() made their monitors agent-owned, and owned
dispatch has no session fallback, so notifications were silently
dropped. InProcessBackend now routes them into the agent's message
queue and tears the routing down on release.
- Downgrade background spawn requests from nested launchers to awaited
foreground runs: a nested launcher cannot honor the background
completion contract (send_message/task_stop excluded, notifications
session-scoped), which orphaned the child's results.
- Extract spawnBlockReason() as the single spawn-exclusion policy
shared by prepareTools() and execute(), replacing two hand-kept
copies of the depth/teammate/fork rules.
- Share DEFAULT_MAX_SUBAGENT_DEPTH / MAX_SUBAGENT_DEPTH_LIMIT across
the core normalizer, the CLI flag validator, and the settings schema.
- Log dropped teammate names from nested spawns; revert the impossible
|null persisted-flag widening to an honest tampered-sidecar framing;
document the constructor-time depth capture invariant.
* test(cli): add DEFAULT_MAX_SUBAGENT_DEPTH to core package mocks
settingsSchema.ts now imports the shared constant, so CLI tests that
mock @qwen-code/qwen-code-core with an explicit export list need the
new export.
* feat(cli): display nested sub-agents as a tree in the TUI (#6191)
* feat(core): allow bounded nested sub-agent spawning via maxSubagentDepth
Sub-agents may now spawn sub-agents up to a configurable maximum nesting
depth (default 5; 1 reproduces the previous no-nesting behavior).
Enforced in two layers sharing one predicate: prepareTools() hides the
agent tool from leaf-depth sub-agents, and AgentTool.execute() rejects
over-depth spawns as an authoritative backstop. Teammates, forks, and
the workflow tool remain excluded from nesting. Launch depth is
persisted in the agent meta sidecar and restored on resume (including
deferred-approval continuations and in-process AgentInteractive frames)
so a resumed nested agent cannot regain spawn capacity.
See knowledge/qwen-code/design/nested-subagents.md.
* feat(cli): display nested sub-agents as a tree in the TUI
Render nested agents depth-first with indent + dim '↳' in the live agent
panel and background tasks view; promote orphaned children to root with a
'· from <parent>' annotation. Detail view gains a level badge, Parent
breadcrumb, and Sub-agents section. The [blocking] tag and two-step cancel
confirm now apply only to provably user-blocking foreground chains. Parent
completion summaries carry a '· N sub-agents' tail (guard-rejected spawns
now record as failed tool calls so the count stays honest). Also fixes the
live-panel Enter-for-detail order mismatch by sharing one display order
between the panel render and the composer keyboard mapping.
* fix(core): address round-1 review on nested sub-agent spawning
- Derive launch metadata (hooks, spans, task rows, meta sidecar) from the
resolved subagent config instead of the raw requested type, so a fork
request that falls back to the awaitable path no longer reports "fork".
- Pin the blocked-spawn failure contract in tests: error is set and
returnDisplay.status is 'failed' for both the depth and fork guards; also
document the failure-path routing at buildSpawnBlockedResult.
- Drop source-comment references to private knowledge/ design docs that do
not exist in this repository.
* test: address round-2 review on sub-agent counting and fork fallback
- Exercise the legacy 'task' alias in the scrollback sub-agent count so
the migration-aware name set is covered, not just the canonical name.
- Pin the nested-fork downgrade: a sub-agent requesting a fork falls back
to the awaitable general-purpose subagent even in interactive mode.
- Drop a duplicated 'nesting depth guard' describe block left behind by
the automated base-branch merge (kept the copy with the failure-shape
assertions).
* fix(core): keep actionable guidance in blocked-spawn error messages
The scheduler's failure path sends only error.message to the model and
the scrollback, discarding llmContent. With the terse terminateReason as
the message, a blocked spawn lost its "do the task yourself instead"
instruction, inviting retry loops. Carry the full guidance text in
error.message and keep terminateReason for the display card.
* test(cli): pin the tree indent clamp at depth beyond TREE_INDENT_MAX_LEVELS
Maintainer mutation-testing on the PR found that removing the clamp in
treeRowPrefix survived the suite. Assert a depth-4 row indents 3 levels
(12 spaces), plus the base marker/indent behavior.
---------
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
---------
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>