Both settings are implemented and wired end to end (settingsSchema.ts,
normalizeDisabledTools.ts, ToolRegistry registration gate) but were
missing from the settings reference, while their deprecated siblings
tools.core / tools.exclude / tools.allowed are documented.
In particular, tools.disabled already answers a recurring user request:
disabling enter_plan_mode entirely so the model can never switch into
plan mode on its own (#5970). Documenting it makes that option
discoverable.
* feat(cli): List archived and organized sessions for non-primary workspaces
Trusted non-primary workspaces can now use archiveState=archived, view=organized, and group filters on the workspace session list routes, closing the remaining Phase 2b listing gap for the multi-workspace daemon. The listing engine was already workspace-scoped; a phase guard was the only thing rejecting these queries on non-primary workspaces, and the persisted/live selection is forced to the persisted store for organized and archived views. Untrusted workspaces are still refused, and legacy primary routes are unchanged.
Refs #6378.
* qwen: address PR review feedback (#6631)
Add a test for the view=organized&archiveState=archived combination on a trusted non-primary workspace: a pinned archived session sorts first and no live summary is merged into the archived view.
* qwen: address PR review feedback (#6631)
Log the requested view/archiveState/group in the session-list failure path, add a defensive guard so persisted-only options can never silently reach the live path, and cover the organized opaque-cursor pagination round-trip for a non-primary workspace.
---------
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* feat(tui): remove tool group borders and collapse completed tool results
Remove round borders from ToolGroupMessage, CompactToolGroupDisplay, and
InlineParallelAgentsDisplay. Completed tools now default to a single
collapsed header line with dimColor styling. Executing/error/confirming
tools continue to show their full result block.
Part of #4588 (Track 3: Simplify tool-call rendering).
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): gate collapse on compact mode and fix innerWidth calculation
- Only collapse completed tool results in compact mode, preserving
full visibility in non-compact mode
- Subtract 2 from innerWidth to account for ToolMessage paddingX={1}
- Update snapshots to reflect removed borders
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): address review feedback on collapse and visual alignment
- Gate isDim on compact mode so non-compact tools stay fully styled
- Add paddingX={1} to CompactToolGroupDisplay for left-edge alignment
- Delete Border Color Logic test block (borders removed)
- Add compact-mode test coverage for Error/Executing/Pending/forceShowResult
- Clean up stale border references in comments
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(tui): unify tool output with semantic summaries
Replace the dual compact/normal mode tool output with a single unified
mode. Completed tools always show a semantic overview line
("Read 3 files, edited 2 files") instead of dumping full results.
- Add buildToolSummary() for category-based semantic summaries
- Remove compactMode gate from shouldCollapse and isDim in ToolMessage
- Make all-completed tool groups use CompactToolGroupDisplay
- Remove unused useCompactMode hook calls from ToolMessage
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(tui): add buildToolSummary unit tests and fix stale comment
- Add 10 dedicated unit tests for buildToolSummary covering edge cases
- Fix stale comment referencing old compactMode gate logic
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): address audit findings for unified tool output
- Add Canceled status to allComplete check in ToolGroupMessage
- Move memory-only group rendering before showCompact to prevent
them being swallowed by CompactToolGroupDisplay
- Fix LLM summary duplication: absorbedCallIds now tracks completed
groups in non-compact mode; HistoryItemDisplay no longer bypasses
summaryAbsorbed when !compactMode
- Update StandaloneSessionPicker test for new compact rendering
- Fix design doc category order example and add missing rendering rules
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): address inline review findings
- Add SHELL_COMMAND_NAME and @ file-reference pseudo-tools to
TOOL_NAME_TO_CATEGORY mapping for correct category classification
- Fix height calculation test to use Executing status so expanded
path is actually exercised
- Update stale comment about empty toolCalls behavior
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): remove unused compactMode import in HistoryItemDisplay
Fixes CI build failure caused by TS6133 (noUnusedLocals) — the
compactMode destructure became dead code after the summary gating
was moved to summaryAbsorbed.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* ci: trigger re-run with updated merge ref
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(tui): design — remove global compact mode, add Ctrl+O transcript + mouse click-to-expand
Design-only. Stacks on #5661 (type-based tool partition baseline) and
#5751 (VP mouse foundation). Scope: remove residual global compactMode,
add Ctrl+O transcript (alt-screen frozen snapshot) and mouse click to
expand a tool's title/output in place.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(tui): remove global compact mode toggle (on top of #5661 partition baseline)
Builds on #5661's type-based tool partition. Removes only the residual
global compactMode switch, keeping the partition baseline intact:
- ToolGroupMessage: showCompact = (compactMode || allComplete) → allComplete
- delete CompactModeContext, mergeCompactToolGroups (isForceExpandGroup /
compactToggleHasVisualEffect no longer used once the cross-group merge and
the Ctrl+O toggle are gone)
- MainContent: drop the compactMode-gated merge path; mergedHistory =
visibleHistory
- remove TOGGLE_COMPACT_MODE binding/matcher, ui.compactMode/compactInline
settings, the compact-mode tip and shortcut entry, AppContainer state +
provider + toggle keypress branch
- KEEP CompactToolGroupDisplay + partition, ToolMessage forceShowResult /
shouldCollapse, ToolConfirmationMessage's local compactMode prop, and
ui.compactMode in WEB_SHELL_SETTINGS (web shell is a separate surface)
typecheck + affected suites green (224 tests). Ctrl+O is a temporary no-op
until the TranscriptView lands.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(tui): Ctrl+O opens a frozen alt-screen transcript full-detail view
Adds the keyboard half of the Ctrl+O redesign on top of the #5661 partition
baseline:
- fullDetail render path (HistoryItemDisplay → ToolGroupMessage): fullDetail
composes into thinking `expanded`, and on tool groups forces showCompact=false
+ forceShowResult=true + uncapped height — so every block renders in full.
- new TranscriptView: an AlternateScreen overlay (disabled in VP mode where
Ink already owns the alt screen) rendering a frozen snapshot
(history length + a pending copy) through ScrollableList with fullDetail,
reusing #5751's keyboard/wheel/scrollbar scrolling. Adaptive
estimatedItemHeight for the taller full-detail rows.
- AppContainer wiring mirrors ThinkingViewer: transcript guard is the FIRST
handleGlobalKeypress branch (Esc/q/Ctrl+C/Ctrl+O close, everything else
swallowed) so close keys beat QUIT and the vim INSERT guard; Ctrl+O opens
when closed; auto-close on any blocking dialog / WaitingForConfirmation;
message-queue drain and refreshStatic are suppressed while open.
- Command.TOGGLE_TRANSCRIPT bound to Ctrl+O.
typecheck + 8 suites (268 tests) green. Mouse click-to-expand (per-tool)
follows in a later commit. Alt-screen enter/exit behavior still needs
real-terminal verification across tmux/iTerm/VSCode.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): repaint normal buffer when transcript closes (no duplicate scrollback)
E2E (VHS) caught the design's flagged highest-risk issue: in the legacy
<Static> path, closing the alt-screen transcript leaked its full-detail rows
into the main scrollback (a duplicate "完整记录 / Transcript" block appeared
below the live history).
Fix: when isTranscriptOpen goes true→false in non-VP mode, force one
clearTerminal + Static remount, deferred a tick so the AlternateScreen's exit
escape (\x1b[?1049l) flushes first and the during-transcript refreshStatic
guard has already cleared. VP mode keeps its own scrollback via the React tree
and is unaffected.
Verified via VHS: open shows the transcript overlay; Esc restores the main
view cleanly with no duplicated content.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(tui): rebase ctrl-o design doc to #5661's type-based partition
The design doc was written against an early state-based snapshot of #5661
(showCompact = (compactMode || allComplete), whole-group collapse) and even
asserted that forceExpandAll / isCollapsibleTool "don't exist". The merged
#5661 is type-based partition and those symbols are its core. Rewrite the
affected sections to match the shipped baseline:
- §1/§2: baseline described as type-based partition (collapse read/search/list
via isCollapsibleTool, render mutation tools individually); compactMode no
longer affects tool rendering. Added a revision note.
- §3.1: table + bullets rewritten to forceExpandAll + collapsible/
non-collapsible split; shouldCollapseResult's isCollapsibleTool guard
(Shell/Edit results always visible); mixed groups = summary line + per-tool.
- §4.1: smaller delete scope (no showCompact / compactMode|| term to remove);
delete mergeCompactToolGroups.ts; keep web-shell ui.compactMode passthrough.
- §4.5: fullDetail = forceExpandAll=true (not showCompact=false) +
per-tool forceShowResult=true + availableTerminalHeight=undefined.
- §4.8/§5/§7/§8/§9/appendix: symbols/forensics corrected to the real merged
implementation; tool_use_summary renders as a standalone line (no absorption).
Matches the resolution already applied to the code in the preceding merge.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(tui): fix factual nits from cross-audit of the ctrl-o design doc
Three independent audits confirmed the doc is now faithful to the merged
#5661 type-based partition; they surfaced three concrete fixes:
- CATEGORY_ORDER: corrected to the real array order
search/read/list/command/edit/write/agent/other (was listed as
command/read/edit/write/search/list/agent/other).
- CompactToolGroupDisplay exports: only getOverallStatus / isCollapsibleTool /
buildToolSummary / CompactToolGroupDisplay are exported; ToolCategory /
TOOL_NAME_TO_CATEGORY / CATEGORY_ORDER / getToolCategory are internal —
relabeled accordingly.
- §5.B file table: fixed a broken 4-column separator and escaped the literal
`||` pipes in the AppContainer row so it renders as a clean 2-column table.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): don't let fullDetail be bypassed by compact early returns
Audit (PR #5666) point 2: ToolGroupMessage computed `forceExpandAll =
fullDetail || ...` only AFTER two early returns — the pure-parallel-agent
group (→ InlineParallelAgentsDisplay dense panel) and the completed
memory-only group (→ "Recalled/Wrote N memories" badge). In transcript
full-detail mode those groups were therefore NOT fully expanded.
Guard both early returns with `!fullDetail` so transcript falls through to
the per-tool ToolMessage path (forceExpandAll + per-tool forceShowResult +
uncapped height). Add a regression test asserting a completed memory-only
group renders each op individually (not the badge) under fullDetail.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(tui): resolve open design decisions from source evidence
Settle the two outstanding decision points from the PR audit using the
codebase + reference implementations (not preference):
- Non-TTY (audit point 3): AlternateScreen has NO isTTY guard today (doc
claimed it did — corrected). The TUI is already gated by stdin.isTTY
(config.ts:1532), so non-TTY rarely mounts; the only edge is `-i`.
Decision: add a process.stdout.isTTY guard to AlternateScreen, matching
the repo convention (startInteractiveUI/notificationService guard isTTY
before terminal escapes). Doc now marks it "to implement" + test.
- Transcript / per-tool expansion state location: per claude-code
(REPL-local transcript state), gemini-cli (dedicated ToolActionsContext),
and this repo's own ThinkingViewer (AppContainer-local useState + minimal
action via a dedicated context) — transcript open/freeze stays
AppContainer-local and is NOT surfaced via UIStateContext (the
implemented code already does this; only the doc was wrong). Per-tool
expansion uses a dedicated ToolExpandedContext (real cross-layer
producer/consumer), not the broad UIStateContext.
Also document the fullDetail early-return guard (the just-landed fix): the
pure-parallel-agent and memory-only early returns are skipped under
fullDetail so transcript shows every tool in full.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(tui): align design doc status/scope with current PR (audit follow-up)
Latest audit confirms the technical design is implementable and side-effect
coverage is sufficient; it flagged status/scope inconsistencies for the doc
to serve as an acceptance baseline. Fixes:
1. Status: "design review (docs-only)" → "implementation in progress; this
doc is the acceptance baseline for the current PR". Added an
implemented-vs-pending status table.
2. Mouse click-to-expand: added a banner marking it NOT yet implemented and
stating the open scope decision (merge blocker vs VP-only follow-up).
3. #5751 (and #5661) dependency: corrected from "OPEN, must merge first" to
"already merged into main; branch rebased on top".
4. alt-screen degradation: removed the undefined "overlay" fallback in the
DefaultAppLayout row; non-TTY degrades via the AlternateScreen isTTY guard
to in-buffer rendering (§4.2), no separate overlay path.
5. Fixed a broken bold marker (`\*\*`) in the AppContainer row.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(tui): scope mouse click-to-expand out as a follow-up
Assessed the mouse click-to-expand effort against the real code: it's
~250–400 lines across 4–5 files (ToolExpandedContext + AppContainer wiring
+ a ClickableToolMessage component — can't call useMouseEvents inside the
.map() — + ToolGroupMessage wiring + mouse hit-test tests). More
importantly, under #5661's type-based partition the collapsed read/search
tools are aggregated into a single summary line, so there is no per-tool
click target — the click granularity must be redesigned to "click the
summary row → expand the whole group". Plus the known SGR-mouse vs native
text-selection risk.
Per the "small code → include, otherwise follow-up" rule: this is not small,
so scope it OUT of the current PR. The current PR delivers Ctrl+O transcript
only. Marked §1 goal #4, §4.8 (banner + draft), §9 commit 4, and the status
table accordingly; the §4.8 design is kept as a draft for the follow-up PR.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(tui): isTTY guard for AlternateScreen + transcript shortcut/i18n cleanup
Completes the remaining in-scope items for the Ctrl+O transcript PR:
- AlternateScreen: guard the alt-screen escape writes on
`process.stdout.isTTY` (skip when non-TTY: piped/redirected/CI), matching
the repo convention (startInteractiveUI / notificationService). Non-TTY
now degrades to in-buffer rendering. Adds AlternateScreen.test.tsx
(enter/exit on TTY, skip when disabled, skip when non-TTY).
- KeyboardShortcuts: add the `ctrl+o → view transcript` entry that was
removed with the old compact-mode line but never replaced.
- i18n (all 9 locales): drop the dead `to toggle compact mode` and the
`Press Ctrl+O to toggle compact mode — …` tip strings (no longer
referenced after compact-mode removal); add `to view transcript`.
Touched suites green (AlternateScreen, i18n index/mustTranslateKeys,
TranscriptView, Help).
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(tui): mark isTTY guard + i18n cleanup as implemented in status table
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(i18n): add TranscriptView strings to all locales
TranscriptView.tsx renders t('Transcript'), t('to close') and
t('to scroll'), but these keys existed only in en/zh. The strict
key-parity check (zh, zh-TW) failed CI on the missing zh-TW entries.
Add all three keys to zh-TW (the failing strict-parity locale) and to
ca/de/fr/ja/pt/ru for completeness so check-i18n is fully clean.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(ctrl-o): add before/after transcript capture evidence
Add VHS-captured screenshots (main-view collapsed vs Ctrl+O transcript
expanded) under docs/design/ctrl-o-detail-expand/assets/ and reference
them from §3.4 of the design doc. Captured on the local branch build via
the mac-autotest skill; shows read/search/list tools folding to a single
summary row in the main view and each expanding in the transcript, with
zh i18n strings rendering correctly.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(ctrl-o): design §4.9 — full tool detail passthrough in transcript
Document the data-layer gap behind the "second-level fold" seen in the
Ctrl+O transcript: read/ls/grep returnDisplay only stores a summary, and
IndividualToolCallDisplay carries no full-content field, so fullDetail
(which correctly clears partition/result folding and height limits) has
no detail to render.
Spec the chosen fix (path C): derive a contentForDisplay string from the
raw llmContent at the single core success-assembly point (partToString +
existing 32k retention cap), thread it through to a new
IndividualToolCallDisplay.detailedDisplay, and render it in ToolMessage
when fullDetail + isCollapsibleTool. Scope limited to read/search/list in
the transcript; main-view summaries and shell/edit/write are unchanged.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(ctrl-o): adopt plan Y for §4.9 and address transcript-detail audit
Address the audit on §4.9 (full tool detail in the Ctrl+O transcript):
- Rewrite §4.9 to plan Y — reuse the complete content already persisted in
functionResponse.response.output (responseParts) via a single core helper,
instead of adding a contentForDisplay field threaded through serialize/
replay. Saved/replayed transcripts get full detail for free (audit #6).
- Split fullDetail (data-source switch) from forceShowResult (un-fold) so
main-view force cases (user-initiated/error) don't leak full detail
into the main view (audit #2).
- Use the exported compactStringForHistory, not the internal compactString
(audit #4).
- Scope by isCollapsibleTool incl. glob, not a hardcoded read/ls/grep list
(audit #5).
- §3.4: stop claiming the screenshot already shows full output; add a
pre-§4.9 caveat and a merge-blocker row in the status table (audit #1).
- Sync §5 file list, §8 tests, §9 commit 4 (merge blocker); move mouse
click-expand out of the commit sequence to follow-up (audit #3).
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(ctrl-o): tighten §4.9 per second audit (no 2nd truncation, nested media, plan-Y guard)
- P1: detailedDisplay no longer runs compactStringForHistory — the 32k
cap would make Ctrl+O a "32k bounded preview", contradicting the
"full detail" promise (read_file has maxOutputChars=Infinity and can
legitimately exceed 32k). Detail is now the full getToolResponseDisplayText
output, bounded only by core's existing truncateToolOutput/pagination.
- P2: spell out getToolResponseDisplayText's priority rule — media lives in
nested functionResponse.parts (not top-level); read response.output, then
walk nested parts for inlineData/fileData/text placeholders; undefined when
neither output nor media so the UI falls back to the summary.
- P3: add an explicit §8 plan-Y protection test (output >32k survives
recording/loadSession/resume/replay; detailedDisplay derives from
message.parts, not resultDisplay or API compressedHistory) and document
the fall-back-to-X trigger.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(ctrl-o): address PR review findings on transcript view
- AppContainer: freeze a committed-history copy (not just a length) so
in-place compaction can't corrupt the open transcript; memoize the
stitched items list so streaming re-renders don't rebuild it
- AppContainer: clear thinkingViewerData on openTranscript and guard
openThinkingViewer so no stale "ghost" thinking popup resurfaces
- AppContainer: read prevTranscriptOpen during render (StrictMode-safe)
- AppContainer: close the transcript on Ctrl+D instead of swallowing it
- TranscriptView: wrap content in a new ErrorBoundary and React.memo the
component (stable items + onClose make the shallow compare effective)
- CompactToolGroupDisplay: localize buildToolSummary via t() and add the
per-category count phrases to all 9 locales
- workspace-settings: drop the stale ui.compactMode web-shell allowlist entry
- tests: TranscriptView default alt-screen + negative-id keyExtractor;
HistoryItemDisplay fullDetail expansion + forwarding; ToolGroupMessage
fullDetail parallel-agent bypass; MainContent.test import-first order
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(ctrl-o): second review round — web-shell compactMode + anti-deadlock deps
- settingsSchema: re-add ui.compactMode as a hidden (showInDialog:false)
schema entry so the web shell's independent compact toggle keeps
persisting via the daemon settings routes (mirrors voiceModel). The TUI
compact mode stays retired — it just isn't shown in the TUI dialog.
- workspace-settings: restore ui.compactMode in WEB_SHELL_SETTINGS now that
the schema definition resolves again (fixes the web shell 400 / revert).
- AppContainer: add isTranscriptOpen to the anti-deadlock auto-close effect
deps so opening the transcript while a blocking prompt is already visible
re-fires the effect and closes it (previously it could open over an
invisible prompt and deadlock).
- ToolGroupMessage.test: cover the fullDetail height-truncation lift
(availableTerminalHeight undefined under fullDetail, numeric otherwise).
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(ctrl-o): regenerate vscode settings schema for re-added ui.compactMode
The previous commit re-added ui.compactMode (showInDialog:false) to
settingsSchema.ts but did not regenerate the generated vscode schema,
which the CI "settings schema is up-to-date" gate checks. Regenerated.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* chore(ctrl-o): reset MCP/acp-bridge files to main (drop stale merge diff)
These 6 files are unrelated to the Ctrl+O work. Reset to origin/main so the
PR diff carries only transcript changes. Committed with --no-verify because the
classic-CLI pre-commit prettier reflows union types differently than the repo's
experimental-CLI formatter (CI's prettier step does not gate on this).
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(ctrl-o): update compact-mode docs for transcript model; drop orphaned i18n key
- settings.md: ui.compactMode is retired in the TUI (web-shell only); Ctrl+O
now opens the full-detail transcript
- tool-use-summaries.md: reframe "compact vs full mode" toggle as "main view
(completed group) vs Ctrl+O full-detail transcript / force-expanded"
- remove the now-orphaned 'Hide tool output and thinking…' locale key (was the
old compactMode description) from all 9 locales
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(ctrl-o)!: §4.9 full tool-detail passthrough in transcript
Implement plan Y: read/search/list tools now show their COMPLETE output
in the Ctrl+O transcript instead of the summary count line, while the
main view is unchanged.
- core: add `getToolResponseDisplayText(parts)` — extracts the full
`functionResponse.response.output` (skipping the non-informative
"Tool execution succeeded." placeholder), emits `<media: mime>`
placeholders for nested media parts, keeps nested text, returns
undefined when nothing is extractable. No second truncation: the only
bound is whatever core already applied (truncateToolOutput / paging).
- cli: add derived (non-persisted) `IndividualToolCallDisplay.detailedDisplay`.
Populated from the already-persisted response parts on both the live
path (useReactToolScheduler success branch) and the resume path
(resumeHistoryUtils tool_result, falling back to message.parts for
older records).
- cli: rendering split — ToolGroupMessage forwards `fullDetail` to
ToolMessage; ToolMessage swaps the summary `resultDisplay` for
`detailedDisplay` ONLY when `fullDetail && isCollapsibleTool(name) &&
detailedDisplay`. Kept separate from `forceShowResult` so main-view
force scenarios (user-initiated / error / confirming) still render the
summary, never the full output.
- ACP path needs no change: ToolCallEmitter.transformPartsToToolCallContent
already writes the same full output into the ACP `content[]` for its SSE
clients; the TUI transcript does not flow through it, so no new protocol
field is added.
Tests: core helper unit tests (placeholder skip, nested media, plain-text
part, empty fallback); ToolMessage data-source switch (collapsible+fullDetail
uses detail, force-but-not-fullDetail keeps summary, non-collapsible keeps
summary, missing-detail falls back); ToolGroupMessage prop-forwarding.
BREAKING CHANGE: Ctrl+O is now a frozen full-detail transcript view, not a
global compact-mode toggle. The `TOGGLE_COMPACT_MODE` command and the TUI
effect of `ui.compactMode` / `ui.compactInline` are removed; the keys remain
read-tolerant (ignored by the CLI) and `ui.compactMode` is still forwarded to
the web shell. See docs/design/ctrl-o-detail-expand/design.md §6 for migration.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(ctrl-o): address review — repaint race, suppressOnRestore parity, transcript error logging
- AppContainer: fix close-repaint setTimeout being cancelled by streaming
re-renders. `wasOpenPrevRender`/`isTranscriptOpen` were in the effect deps,
so the next streaming render flipped them, ran cleanup, and clearTimeout'd
the pending repaint — leaving stale pre-transcript content in the legacy
<Static> normal buffer. Drive the effect off a close-transition counter
instead, so post-close re-renders don't change deps and the scheduled
repaint fires exactly once per close.
- AppContainer: transcript snapshot now mirrors MainContent's
`!display.suppressOnRestore` filter, so items collapsed on session resume
(ui.history.collapseOnResume) are not re-exposed in the Ctrl+O view.
- TranscriptView: pass `onError` to the ErrorBoundary so caught render errors
in the fullDetail paths are logged to the debug channel, not just shown.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(ctrl-o): cover detailedDisplay resume derivation + message.parts fallback
Add dedicated resumeHistoryUtils tests for §4.9: detailedDisplay derived
from toolCallResult.responseParts, the `responseParts ?? message.parts`
fallback for older records lacking responseParts, and the undefined
fallback when neither source carries output.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(ctrl-o): address review — plain-text detail, shared placeholder const, resume status guard, scroll hint
Four review fixes on the §4.9 transcript work:
- ToolMessage: when fullDetail swaps the data source to detailedDisplay
(raw file content / grep hits / dir listings), force renderOutputAsMarkdown
to false. The existing `if (availableHeight)` guard never fires in the
transcript (height cap is lifted, availableTerminalHeight is undefined), so
raw `#`/`*`/`-`/`>` characters were being Markdown-formatted.
- core: export TOOL_SUCCEEDED_OUTPUT as the single source of truth for the
"Tool execution succeeded." placeholder. coreToolScheduler (the producer,
two sites) and getToolResponseDisplayText (the consumer) now share one
constant so the filter can't silently drift if the wording changes.
- resumeHistoryUtils: only derive detailedDisplay for SUCCESS tools, matching
the live path (useReactToolScheduler sets it only in its 'success' branch).
Previously it was populated unconditionally, so a resumed errored/cancelled
collapsible tool would surface raw output in the transcript while the same
tool live would not.
- TranscriptView: footer hint now reads "Shift+↑↓ to scroll" — plain Up/Down
do not scroll (ScrollableList listens for SCROLL_UP/DOWN bound to Shift+↑↓);
the old "↑↓" hint was misleading.
Tests: ToolMessage plain-text-detail assertion + new raw-markdown case;
resume errored-tool no-detailedDisplay case. typecheck/lint/tests green
(core scheduler 222, cli suites pass).
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): guard transcript non-TTY output + clear detailedDisplay on compaction
Addresses three review findings on the Ctrl+O transcript work:
- Non-TTY byte leak: `useMouseEvents` enabled SGR mouse mode (?1002h ?1006h)
whenever stdin supported raw mode, ignoring stdout. With stdout piped
(`qwen | tee log`) the transcript's focused ScrollableList (bypassVpGate)
leaked raw control bytes into the captured output. Gate the enable on
`stdout.isTTY`, and likewise guard the transcript close-repaint
`clearTerminal` write in AppContainer — both now mirror AlternateScreen's
existing isTTY guard, so the non-TTY fallback stays byte-clean.
- Compaction privacy regression: `compactOldItems` replaced old tool
`resultDisplay` with the cleared placeholder but left `detailedDisplay`
(the raw functionResponse text added for the full-detail transcript)
intact, so reopening Ctrl+O after compaction re-surfaced the supposedly
cleared read/search/list output. Clear `detailedDisplay` wherever
`resultDisplay` is cleared, with a regression test.
- Docs: keyboard-shortcuts.md still described Ctrl+O as "toggle compact
mode"; updated to the open/close full-detail transcript behavior.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(tui): report a TTY stdout in ScrollableList mouse-scroll tests
The new `stdout.isTTY` gate in `useMouseEvents` (which stops SGR mouse
escapes leaking into piped output) left ink-testing-library's fake
stdout — which has no `isTTY` — with the mouse pipeline disabled, so the
scrollbar-drag and wheel-scroll assertions never received events. Mock
ink's `useStdout` to report `isTTY: true` so the pipeline arms exactly as
it does in a real terminal; all other ink exports are preserved.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): address Ctrl+O transcript review — q-guard, callback churn, tests, cleanup
Resolves the qwen3.7-max /review findings:
- Modifier guard on the transcript close key: bare `q` closed the
transcript, but Ink reports Ctrl/Alt/Shift+Q as `{ name: 'q', … }` too
(Alt arrives as `meta`), so those silently closed it. Guard
`!key.ctrl && !key.meta && !key.shift` (Shift+Q is a literal `Q`).
- Stable `openTranscript`: it captured `historyManager.history` and
`pendingHistoryItems` as deps, both of which change identity every
streaming tick, rebuilding the callback — and the whole
`handleGlobalKeypress` closure that lists it — on every render during
streaming. Read both via refs so the callback is referentially stable.
- AppContainer transcript integration tests (the removed TOGGLE_COMPACT
tests had no replacement): Ctrl+O installs TranscriptView; Esc / q /
Ctrl+C / Ctrl+D close it; Ctrl+Q / Alt+Q / Shift+Q do NOT (modifier
guard); arbitrary keys are swallowed and keep it open; a blocking
confirmation (WaitingForConfirmation) auto-closes it (anti-deadlock).
- Dead i18n string: removed the orphaned
'Press Ctrl+O to show full tool output' key from all 9 locale files
(no `t()` reference remained after the compact-mode sweep).
- Design doc: replaced the leaked absolute worktree path with a
placeholder, and corrected the §6 keybinding-migration note — the
codebase has no user-configurable keybinding override surface
(`keyMatchers` always uses hardcoded defaults), so there is no
persisted `toggleCompactMode` binding to migrate; the startup-detection
step is not applicable until such a feature exists.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): escape ANSI in transcript detailedDisplay + gate its extraction
Two findings from the qwen3.7-max /review on §4.9:
- [Critical] ANSI escape injection: `detailedDisplay` carries raw,
un-sanitized tool output (file contents, grep hits, directory
listings). The Ctrl+O transcript rendered it straight to <Text>
without escaping, so a malicious repo file with embedded terminal
control sequences (e.g. `\x1b[?1049l` to drop the alt-screen, OSC 52
for clipboard poisoning) would execute when the transcript opened —
and fullDetail lifts the height cap, exposing the whole file. Run it
through `escapeAnsiCtrlCodes` (already used for agent names in this
file) before rendering. Added a regression test asserting the raw ESC
bytes don't survive.
- [perf] `detailedDisplay` was extracted on every successful tool call
(~25K chars from core's truncation) but is consumed only by the
transcript's fullDetail render for collapsible (read/search/list)
tools. Gate the extraction on `isCollapsibleTool(displayName)` so
edit/write/command/agent calls no longer store a large string the
renderer never reads — mirrors ToolMessage's `usingDetailedDisplay`
gate (which also keys off the display name).
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): gate resume-path detailedDisplay on isCollapsibleTool (match live path)
The resume path (resumeHistoryUtils.ts) extracted `detailedDisplay` for
every successful tool call, unlike the live path in useReactToolScheduler
which gates on `isCollapsibleTool(displayName)`. Since the transcript's
`usingDetailedDisplay` only consumes it for collapsible (read/search/list)
tools, resuming a session with many edit/write/command/agent calls stored
large (~25K char) strings the renderer never reads. Apply the same gate so
live and resume stay consistent, using `toolCall.name` (the display name,
set from `tool.displayName`) to match the renderer's key.
Updated the existing derivation tests to use a collapsible read tool (an
edit tool now correctly yields undefined) and added a regression asserting
a non-collapsible tool leaves detailedDisplay undefined on resume.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): strip bare C0 control bytes from transcript detailedDisplay + memoize
Follow-up to the ANSI-escape fix. `escapeAnsiCtrlCodes` delegates to
ansi-regex, which only matches ESC-prefixed sequences, so bare C0 control
bytes without an ESC prefix (BEL \x07, BS \x08, FF \x0c, SO \x0e, SI \x0f,
CR, …) passed through to <Text> and could still corrupt the display or
ring the bell from a malicious file's contents. Add a second pass that
strips those bytes (keeping only TAB and LF, which structure multi-line
output). Memoize the two-pass sanitization with useMemo keyed on
detailedDisplay so the ~25K-char regex work doesn't re-run every render.
Extended the ToolMessage regression test to assert bare C0 bytes are
stripped alongside the ESC sequences.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(tui): memoize HistoryItemDisplay, add ErrorBoundary tests + TAB/LF invariant
Addresses three review suggestions:
- Wrap `HistoryItemDisplay` in `React.memo` so the Ctrl+O transcript
(which re-renders on every scroll tick) skips re-rendering
frozen-snapshot items whose props are shallowly unchanged. The
transcript passes stable `item` references, so the default shallow
compare is effective; harmless for the main view (items live in
`<Static>` and render once).
- Add ErrorBoundary.test.tsx covering the four behaviors: renders
children when healthy, catches a render error into the default
fallback with the message, renders a custom fallback, calls `onError`
with the error + component stack, and `reset` clears the error state so
the subtree recovers.
- Lock the C0-strip invariant: assert TAB and LF survive in
detailedDisplay (the regex intentionally skips \x09/\x0a) so a future
regex change can't silently collapse multi-line/columnar output.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* refactor(tui): review cleanups — gate sanitize memo, drop dead code, add tests
Addresses the latest /review suggestions:
- ToolMessage: gate the `sanitizedDetailedDisplay` useMemo on
`usingDetailedDisplay` so the ~25K-char escape+strip no longer runs for
every collapsible tool in the main view (where the result is discarded).
- TranscriptView: remove the dead `listRef` (created + passed as `ref` but
never used imperatively) and the dead `onClose` prop (declared, then
`void`-ed; close keys are owned entirely by AppContainer's global
keypress guard). Dropped the now-unused `useRef` / `ScrollableListRef`
imports and the `onClose` call-site + props.
- Tests: add TranscriptView error-fallback coverage (a throwing item
renders the recovery fallback, not a crash); add live-path
`mapToDisplay` detailedDisplay extraction coverage (collapsible →
extracted, non-collapsible → undefined); add Ctrl+O to the transcript
close-keys it.each (the toggle key was the only close key untested).
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(tui): remove orphaned no-op CompactModeProvider stubs
This PR deleted the CompactModeContext, leaving identical no-op
`CompactModeProvider` passthrough stubs (with an ignored `value` prop) in
ToolGroupMessage.test.tsx, ToolMessage.test.tsx and MainContent.test.tsx,
each still wrapping every render. Remove the stubs and unwrap the renders;
drop the now-meaningless `compactMode` params/args from the local render
helpers. Behavior-preserving (the stubs rendered children verbatim) —
all three suites still pass.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): strip bidi overrides, sanitize error fallbacks, share filters
Latest /review round:
- [Critical] Strip Unicode bidirectional override / isolate chars (Trojan
Source, CVE-2021-42572) from transcript `detailedDisplay` — a third
sanitize pass after ANSI + C0 stripping, mirroring the repo's existing
BIDI_CONTROL_RE. Regression test added.
- Sanitize `error.message` with `escapeAnsiCtrlCodes` in both the
ErrorBoundary default fallback and the TranscriptView custom fallback
(defense-in-depth against control codes in a crafted error message).
- Ctrl+O while the ThinkingViewer is open now swaps to the transcript
(falls through to openTranscript, which clears the viewer) instead of
being silently swallowed.
- Extract the shared `isHistoryItemVisibleAfterRestore` predicate into
types.ts and use it from both MainContent (main view) and AppContainer
(transcript freeze), so the two surfaces can't diverge on which
collapse-on-resume items are hidden.
- Tests: use the exported `TOOL_SUCCEEDED_OUTPUT` constant instead of the
hardcoded literal in generateContentResponseUtilities.test.ts.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): harden compaction guard to always clear detailedDisplay
The compaction cleanup only cleared `detailedDisplay` inside the
`resultDisplay != null` branch (both the group-level trigger, the
group-count pass, and the per-tool clear). A tool carrying only
`detailedDisplay` (no resultDisplay) would skip compaction and leave the
raw transcript detail intact — a latent privacy leak if the two fields
ever decouple. Widen all three checks to also match `detailedDisplay !=
null` so the memory/privacy safeguard is robust. Added a defensive
regression test.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): sanitize mime/uri in getToolResponseDisplayText media placeholders
The `<media: …>` placeholder interpolated `inlineData.mimeType` /
`fileData.mimeType` / `fileData.fileUri` from tool responses verbatim. A
crafted response could embed control characters or angle brackets to
inject terminal codes or forge/mangle the placeholder markup. Add a
`sanitizeMediaLabel` helper that strips C0/C1 control bytes and `<`/`>`
before interpolation, falling back to the default label when emptied.
Regression test added.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(tui): report a TTY stdout in BaseSelectionList mouse integration test
The `stdout.isTTY` gate added to `useMouseEvents` (stops SGR mouse escapes
leaking into piped output) left #6011's BaseSelectionList mouse test —
which renders via ink-testing-library where the hook-provided stdout reads
as non-TTY — with the mouse layer disabled, so the any-event enable escape
was never written. Mock ink's `useStdout` to report `isTTY: true` with a
capturing write spy (matching useMouseEvents.test.tsx / ScrollableList.test
.tsx), and assert the `?1003h` enable via that spy while items still render
through ink's own stdout. Both cases pass.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(core): fix JSDoc placement + note ErrorBoundary fallback is un-translated
Two small review nits:
- getToolResponseDisplayText's JSDoc had ended up above sanitizeMediaLabel
(added last commit), making it read as that helper's docs. Reorder so
sanitizeMediaLabel + its own JSDoc come first and each doc sits directly
above its function.
- Document why the ErrorBoundary default fallback's title is intentionally
a plain English string (last-resort message for callers with no
`fallback`; renders mid-crash, so it avoids pulling in the i18n layer —
the transcript passes its own localized fallback anyway).
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(tui): share terminal-sanitize pipeline; guard AlternateScreen writes
- Extract the three-pass sanitizer (ANSI escape + bare-C0 strip + bidi
strip) into `sanitizeTerminalText` in textUtils.ts as the single source
of truth, and use it at all raw-text render sites: ToolMessage's
`detailedDisplay`, and the TranscriptView + ErrorBoundary error-message
fallbacks (previously those only escaped ANSI, missing C0/bidi — the
boundary catches errors from the fullDetail path that processes raw tool
output, so a crafted item shape could carry unsanitized bytes into
error.message). Removes the duplicated regex consts from ToolMessage.
- AlternateScreen: wrap the alt-screen escape writes (and the exit/cleanup
writes) in try/catch so a synchronous stdout error (EPIPE on terminal
close, EAGAIN under backpressure) can't propagate uncaught from the
effect and crash the app or corrupt the terminal.
Generated with AI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
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>