* chore: remove outdated pr-review skill
The .qwen/skills/pr-review skill is outdated and no longer needed.
Also removes the dangling reference from terminal-capture skill.
* chore: add token-stats script to e2e-testing skill
* fix(test): remove flaky AskUserQuestionDialog Submit tab test
The "shows Submit tab for multiple questions" test fails on macOS CI
due to terminal width truncation causing "Submit answers" to render
as just "Submit".
Some models (e.g., glm-5.1) ignore the {{model}} template in code
blocks and write their own footer without the model name. Fix:
1. BundledSkillLoader prepends YOUR_MODEL_ID="glm-5.1" as a top-level
declaration at the start of the skill body — impossible to miss
2. SKILL.md references YOUR_MODEL_ID in footer instructions
3. Empty model → empty string (no "unknown" — prefer omission)
4. YOUR_MODEL_ID declaration only prepended when model is available
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(core): prevent followup suggestion input/output from appearing in tool call UI
The follow-up suggestion generation was leaking into the conversation UI
through three channels:
1. The forked query included tools in its generation config, allowing the
model to produce function calls during suggestion generation. Fixed by
setting `tools: []` in runForkedQuery's per-request config (kept in
createForkedChat for speculation which needs tools).
2. logApiResponse and logApiError recorded suggestion API events to the
chatRecordingService, causing them to appear in session JSONL files
and the WebUI. Fixed by adding isInternalPromptId() guard that skips
chatRecordingService for 'prompt_suggestion' and 'forked_query' IDs.
uiTelemetryService.addEvent() is preserved so /stats still tracks
suggestion token usage.
3. LoggingContentGenerator logged suggestion requests/responses to the
OpenAI logger and telemetry pipeline. Fixed by skipping logApiRequest,
buildOpenAIRequestForLogging, and logOpenAIInteraction for internal
prompt IDs. _logApiResponse is preserved (for /stats) but its
chatRecordingService path is filtered by fix#2.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: deduplicate isInternalPromptId into shared export from loggers.ts
Address review feedback: extract isInternalPromptId() to a single
exported function in telemetry/loggers.ts and import it in
LoggingContentGenerator, eliminating the duplicate private method.
Also update loggingContentGenerator.test.ts mock to use importOriginal
so the real isInternalPromptId is available during tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract isInternalPromptId to shared utils, add tests
Address maintainer review feedback:
1. Move isInternalPromptId() to packages/core/src/utils/internalPromptIds.ts
using a ReadonlySet for the ID registry. Adding new internal prompt IDs
only requires changing one file. loggers.ts re-exports for compatibility,
loggingContentGenerator.ts imports directly from utils.
2. Extract `tools: []` magic value to a frozen NO_TOOLS constant in
forkedQuery.ts.
3. Add unit tests for isInternalPromptId: prompt_suggestion → true,
forked_query → true, user_query → false, empty string → false.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address Copilot review — docs, stream optimization, tests
1. Update forkedQuery.ts module docs to reflect that runForkedQuery
overrides tools: [] at the per-request level while createForkedChat
retains the full generationConfig for speculation callers.
2. Propagate isInternal into loggingStreamWrapper to skip response
collection and consolidation for internal prompts, avoiding
unnecessary CPU/memory overhead.
3. Add logApiResponse chatRecordingService filter tests: verify
prompt_suggestion/forked_query skip recording while normal IDs
still record.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: deep-freeze NO_TOOLS, add internal prompt guard tests
Address Copilot review round 3:
1. Deep-freeze NO_TOOLS.tools array to prevent shared mutable state
across forked query calls.
2. Add LoggingContentGenerator tests verifying that internal prompt IDs
(prompt_suggestion, forked_query) skip logApiRequest and OpenAI
interaction logging while preserving logApiResponse.
3. Add logApiError chatRecordingService filter tests matching the
existing logApiResponse coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: reconcile createForkedChat JSDoc with module header
Clarify that createForkedChat retains the full generationConfig
(including tools) for speculation callers, while runForkedQuery
strips tools at the per-request level via NO_TOOLS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: build errors and Copilot round 4 feedback
1. Fix NO_TOOLS type: Object.freeze produces readonly array incompatible
with ToolUnion[]. Use Readonly<Pick<>> instead; spread in requestConfig
already creates a fresh mutable copy per call.
2. Fix test missing required 'model' field in ContentGeneratorConfig.
3. Track firstResponseId/firstModelVersion in loggingStreamWrapper so
_logApiResponse/_logApiError have accurate values even when full
response collection is skipped for internal prompts.
4. Strengthen OpenAI logger test assertion: assert OpenAILogger was
constructed (not guarded by if), then assert logInteraction was
not called.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove dead Object.keys check, add streaming internal prompt test
1. Simplify runForkedQuery: requestConfig always has tools:[] from
NO_TOOLS spread, so the Object.keys().length > 0 ternary is dead
code. Pass requestConfig directly.
2. Add generateContentStream test for internal prompt IDs to match
the existing generateContent coverage, ensuring the streaming
wrapper also skips logApiRequest and OpenAI interaction logging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent Enter accept from re-inserting suggestion into buffer
When accepting a followup suggestion via Enter, accept() queued
buffer.insert(suggestion) in a microtask that executed after
handleSubmitAndClear had already cleared the buffer, leaving the
suggestion text stuck in the input.
Add skipOnAccept option to accept() so the Enter path bypasses the
onAccept callback. Also add runForkedQuery unit tests verifying
tools: [] is passed in per-request config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(core): add speculation to internal IDs, fix logToolCall filtering, improve suggestion prompt
- Add 'speculation' to INTERNAL_PROMPT_IDS so speculation API traffic
and tool calls are hidden from chat recordings and tool call UI
- Add isInternalPromptId check to logToolCall() for consistency with
logApiError/logApiResponse
- Improve SUGGESTION_PROMPT: prioritize assistant's last few lines and
extract actionable text from explicit tips (e.g. "Tip: type X")
- Fix garbled unicode in prompt text
- Update design docs and user docs to reflect changes
- Add test coverage for all new behavior
* fix(core): deep-freeze NO_TOOLS, add speculation to loggingContentGenerator tests
- Object.freeze NO_TOOLS and its tools array to prevent runtime mutation
- Add 'speculation' to loggingContentGenerator internal prompt ID tests
for consistency with loggers.test.ts and internalPromptIds.ts
* fix(core): fix NO_TOOLS Object.freeze type error
Use `as const` with type assertion to satisfy TypeScript while keeping
runtime immutability via Object.freeze.
* refactor(core): remove unused isInternalPromptId re-export from loggers.ts
All consumers import directly from utils/internalPromptIds.js.
The re-export was dead code with no importers.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ui): prevent useEffect from running every render in InputPrompt
getDirectories() returns a new array reference each call, causing the
useEffect dependency check to fail on every render. Move the call
inside the effect body and use stable dependencies [config, dirs] so
the effect only re-runs when they actually change.
* fix(ui): use serialized dep key for directory change detection
Move from [config, dirs] deps (both stable refs that miss external
changes) to a dirKey string (join of current directories). This
preserves the perf fix (no new array ref in deps) while still
detecting directory additions/removals from /add-dir etc.
* refactor(cli): remove unused dirs state from InputPrompt
The dirs parameter passed to useCommandCompletion() was never read
inside that hook, making the dirs state and sync effect in InputPrompt
dead code. Remove the parameter, the state, the effect, and all test
call-site args.
The previous version bump commit (bb4376c) only updated the root
package.json but did not run `npm run release:version` to propagate
the version and sandboxImageUri to all workspace packages.
This caused Docker sandbox integration tests to fail in CI with
"manifest unknown" because build_sandbox.js built image 0.14.1
(from packages/cli/package.json) while sandboxConfig.ts expected
image 0.14.2 (from root package.json).
Fixes: https://github.com/QwenLM/qwen-code/actions/runs/24135197272/job/70424966323
- Hide "? for shortcuts" when a custom status line is configured
(status line already occupies the top row, hint is redundant)
- Hide status line during Ctrl+C/D exit prompts to keep footer
at one row during exit flow
- Matches upstream Claude Code suppressHint + exitMessage behavior
AutoAcceptIndicator and ShellModeIndicator returned Box>Text, which
prevented the parent Text wrap="truncate" from working (ink cannot
nest Box inside Text). Change both to return plain Text elements
so the footer's truncation applies correctly on narrow terminals.
Wrap leftBottomContent in Text with wrap="truncate" so the
hints/mode row stays on a single line, matching upstream behavior.
This guarantees the footer is at most 2 rows (status line + hints).
Restructure footer to match Claude Code's layout:
- Left column: status line (top, truncated) + hints/mode (bottom)
- Both rows coexist instead of being mutually exclusive
- Status line uses wrap="truncate" to guarantee single line
- Approval mode returns to the hints row (inline, not separate row)
- Left column uses flexShrink for narrow terminals
Add flexGrow/flexShrink to left section so it takes available space
but yields to right items. Add flexShrink={0} to right section so
context usage, verbose, sandbox indicators are never compressed.
Add overflow="hidden" to left section for clean truncation.
The status line is now inlined in the footer's left section,
so horizontal padding is no longer applicable. Remove padding
from StatusLineConfig, settings schema, JSON schema, and docs.
Move status line from a dedicated row below the footer into the
footer's left section, replacing "? for shortcuts" when active.
High-priority messages (Ctrl+C/D, Esc, vim INSERT, shell mode)
still override the status line.
Move approval mode indicator to a separate row below the footer,
shown only when mode is non-default. This eliminates the empty
gap in default mode and matches upstream layout.
- Compute remaining_percentage as round(100 - used) to guarantee
used + remaining always sums to exactly 100.0
- Reject empty or whitespace-only command strings in config validation
- Add qwen3.6-plus to both China and Global/Intl regions as the first
model in the Coding Plan template (1M context, enable_thinking)
- Set qwen3.6-plus as the new default MAINLINE_CODER_MODEL
- Add image+video input modality support for qwen3.6-plus
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
useStatusLine hook accesses sessionStats.metrics.tools.totalCalls,
sessionStats.metrics.files.totalLinesAdded, and currentModel which
were missing from the mock UIState, causing a TypeError crash during
render and making 4 Footer tests fail in CI.
Restructure the status line stdin JSON for clarity and accuracy:
- Rename model.id → model.display_name, cwd → workspace.current_dir
- Replace raw context_window size/count with used_percentage,
remaining_percentage, current_usage, context_window_size, and
total_input_tokens/total_output_tokens
- Add version field from cfg.getCliVersion()
- Add git.branch, metrics.models, metrics.files
- Remove upstream-only fields: tokens.tool (never populated),
session (start_time/elapsed_time not live-updating),
streaming_state, approval_mode, terminal, metrics.tools
- Rename tokens.candidates → tokens.completion (Qwen API convention)
- Fix template string escaping in builtin-agents to avoid
templateString() placeholder collision
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align with observed provider prompt-cache TTL (~5 min). Add
`context.gapThresholdMinutes` setting so users can tune the threshold
for providers with different cache TTLs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename the subcommand to accurately reflect its behavior (exits plan
mode and restores previous approval mode, does not trigger execution).
Update source, tests, i18n keys (6 locales), and docs.
Most Qwen OAuth users don't have a fast model configured for this
feature, so it fires a wasted API request on every turn with no
visible benefit. Default to off; users can opt in via settings.
Same class of Windows CI timing flake — the backspace keypress
doesn't propagate through the paste/keypress pipeline fast enough
on slow runners, so replaceRangeByOffset is never called (0 calls).
The test has a stale closure race condition: the 50ms wait between
pressing '2' and Enter may not be enough for React/Ink to re-render
and re-subscribe the useKeypress callback with the updated
selectedIndex, causing it to read the default value (0) instead of
the expected value (1) on slow CI runners (Windows + Node 20).
* feat(core): implement mid-turn queue drain for agent execution
Inject queued user messages between tool execution steps within a single
turn, so the model sees them immediately instead of waiting for the
entire round to complete.
- Add `dequeueAll()` to AsyncMessageQueue
- Add `midTurnDrain` callback to ReasoningLoopOptions
- Drain queue after processFunctionCalls, inject as text parts
- AgentComposer always enqueues directly (no local buffering)
- Add QUEUE_MESSAGES_CONSUMED event for UI sync
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): add mid-turn queue drain to main session
Extend mid-turn queue drain to the main session's tool execution path
(useGeminiStream). Previously only agent tabs had this feature.
- Add midTurnDrainRef parameter to useGeminiStream
- Inject queued messages in handleCompletedTools before submitQuery
- Bridge useMessageQueue to drain ref in AppContainer via ref pattern
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address Copilot review feedback on mid-turn drain
- Guard midTurnDrain with abort check to prevent message loss on cancel
- Synchronously clear messageQueueRef to prevent duplicate drains
- Only clear pending display on IDLE status, not all status changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: scope mid-turn drain to main session only
Revert subagent-path changes (AgentCore, AgentInteractive,
AgentComposer, AsyncMessageQueue, agent-events) to keep the PR
focused on the main session, which is easier to test and validate.
Subagent mid-turn drain can be added in a follow-up PR.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address Copilot review on main session mid-turn drain
- Move synchronous queue ref into useMessageQueue itself, expose
drainQueue() for atomic drain (fixes race between addMessage and drain)
- Record drained messages as USER history items so the transcript
stays complete
- Simplify AppContainer bridge to just midTurnDrainRef.current = drainQueue
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: guard mid-turn drain against cancelled turns
- Skip drain when turnCancelledRef or abortController signal is set,
so queued messages stay for the next turn instead of being lost
- Restore ref-based queue bridge (drainQueue removed from useMessageQueue)
- Keep synchronous ref clear to prevent duplicate drains
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add model attribution to no-findings LGTM path
- Handle empty string from getModel() with .trim() || 'unknown'
- Add tests for {{model}} with args and empty model ID
- Fix doc contradiction: PR autofix pushes automatically from worktree
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename "should use unknown when model is not available" to
"when getModel returns undefined" — the mock config does define
getModel, it just returns undefined.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Copilot-instructions.md precedence: prefer .github/ path, do not
load both when both exist
- Simplify getModel() call: remove unnecessary typeof guard since
Config already defines getModel()
- Fix TS2352 type error in test: use proper mock cast pattern
- Add getModel to base mockConfig for test consistency
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add {{model}} template variable support in BundledSkillLoader. When a
skill body contains {{model}}, it is replaced with the runtime model ID
from config.getModel(). Only skills that use the variable are affected.
The /review skill now appends a model attribution footer to PR review
summaries: "Reviewed by {model} via Qwen Code /review"
This enables cross-model review workflows (e.g., develop with model A,
review with model B) with accurate attribution in PR comments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per maintainer review (tanzhenxin): default verboseMode reverted to true
to preserve existing behavior — compact mode is opt-in via Ctrl+O.
Also addresses wenshao's security concern: in compact mode, tool groups
now force-expand on Error status (in addition to existing Confirming
handling), and ToolMessage force-shows result for both Confirming and
Error statuses so users always see diffs before approval and error
details for debugging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
"Use the Agent tool with subagent_type" is more direct than
"Create an Agent", reducing ambiguity for the model.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Footer comment now accurately states only the "? for shortcuts"
hint is suppressed, not all left-section items
- Docs now note that Windows uses cmd.exe by default and suggest
wrapping commands with bash -c or using a bash script
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When entering plan mode, Config now saves the previous approval mode
(e.g. AUTO_EDIT, YOLO) so it can be restored when exiting. Previously,
/plan execute and ExitPlanModeTool both hardcoded a return to DEFAULT,
losing the user's prior mode.
Changes:
- Config: add prePlanMode field, getPrePlanMode(), auto-track in
setApprovalMode()
- planCommand: /plan execute restores prePlanMode instead of DEFAULT
- ExitPlanModeTool: ProceedOnce restores prePlanMode instead of DEFAULT
- Tests: 4 new Config tests, 1 new planCommand test, 1 new
ExitPlanModeTool test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix default value: compact mode (verboseMode=false) is now the default,
matching PR description and intended UX
- Extract shared ToolStatusIndicator component to eliminate duplicate
status icon rendering between ToolMessage and CompactToolGroupDisplay
- Memoize VerboseModeProvider context value to prevent unnecessary
re-renders of all consumer components
- Clear frozenSnapshot on WaitingForConfirmation state to ensure tool
confirmation UI remains interactive during mid-stream toggle
- Replace magic string 'Shell' with SHELL_NAME constant in ToolMessage
- Remove unused i18n translation keys (verbose/compact mode messages)
- Update snapshots for Footer and ToolGroupMessage tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Agent can now ask for clarification when PS1 is not found
- Clear pending debounce timer before immediate doUpdate on command
change to prevent redundant second execution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When statusLineCommand becomes undefined (user removes the setting),
kill any in-flight child process, bump generation counter, and clear
the debounce timer so stale callbacks cannot resurrect the output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Set cwd to config.getTargetDir() so commands like pwd/git run in the
correct workspace directory
- Strip only trailing newline instead of trim() to preserve intentional
leading/trailing whitespace in command output
- Match footer's marginLeft/marginRight on the status line row so it
aligns with the rest of the footer content
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>