* feat(core): add run_in_background support for Agent tool
Enable sub-agents to run asynchronously via `run_in_background: true`
parameter. Background agents execute independently from the parent,
which receives an immediate launch confirmation and continues working.
A notification is injected into the parent conversation when the
background agent completes.
Key changes:
- BackgroundTaskRegistry tracks lifecycle of background agents
- Agent tool gains async execution path with fire-and-forget semantics
- Background agents use YOLO approval mode to prevent deadlock
- Independent AbortControllers survive parent ESC cancellation
- CLI bridges notifications via useMessageQueue for between-turn delivery
- State race guards prevent complete/fail after cancellation
- Session cleanup aborts all running background agents
* feat(background): improve notification formatting and UI handling
- Add prefix/separator protocol to distinguish background notifications from user input
- Show concise summary in UI while sending full details to LLM
- Add 'notification' history item type with specialized display
- Add 'background' agent status for background-running agents
- Prevent notifications from polluting prompt history (up-arrow)
- Truncate long descriptions in display text
This improves the UX for background agents by showing cleaner, more concise
notifications while preserving full context for the LLM.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(background): reject run_in_background in non-interactive mode
Headless mode skips AppContainer, so the notification callback is never
registered and background agent results would be silently dropped. Return
an error prompting the model to retry without run_in_background.
* refactor(background): replace prefix/separator protocol with typed notification queue
Replace the stringly-typed \x00__BG_NOTIFY__\x00 prefix/separator
encoding with a typed notification path using SendMessageType.Notification.
- Add SendMessageType.Notification to the enum
- Change BackgroundNotificationCallback to emit (displayText, modelText)
- Move notification queue from AppContainer into useGeminiStream (mirrors
the cron queue pattern): register on registry, queue structured items,
drain on idle via submitQuery
- prepareQueryForGemini short-circuits for Notification type (skips slash
commands, shell mode, @-commands, prompt history logging)
- Remove BACKGROUND_NOTIFICATION_PREFIX/SEPARATOR constants
* refactor(background): move abortAll to Config.shutdown
Background agent cleanup belongs in Config.shutdown() alongside other
resource teardown (skillManager, toolRegistry, arenaRuntime), not in
AppContainer's registerCleanup. This also ensures headless mode gets
cleanup for free.
* fix(background): persist notification items for session resume
Background agent notifications were missing after session resume because
they were never recorded in the chat history. The model text was absent
from the API history and the display item was lost.
- Add recordNotification() to ChatRecordingService — stores as user-role
message with subtype 'notification' and displayText payload
- Thread notificationDisplayText through submitQuery → sendMessageStream
- Restore as HistoryItemNotification in resumeHistoryUtils
* fix(background): replace YOLO with deny-by-default for background agents
Background agents were using YOLO approval mode which auto-approves all
tool calls — too permissive. Replace with shouldAvoidPermissionPrompts
which auto-denies tool calls that need interactive approval, matching
claw-code's approach.
The permission flow for background agents is now:
1. L3/L4 permission rules (allow/deny) — same as foreground
2. Approval mode overrides (AUTO_EDIT for edits) — same as foreground
3. PermissionRequest hooks — can override the denial
4. Auto-deny — if no hook decided, deny because prompts are unavailable
* fix(background): add missing getBackgroundTaskRegistry mock in useGeminiStream tests
* refactor(core): move fork subagent params from execute() to construction time
Identity-shaping fork inputs (parent history, generationConfig, tool decls,
env-skip flag) were threaded through `AgentHeadless.execute()`'s options bag
and re-passed by the SubagentStop hook retry loop. They belong on the agent's
construction-time configs, not its per-invocation options.
- PromptConfig gains `renderedSystemPrompt` (verbatim, bypasses templating
and userMemory injection) and drops the `systemPrompt`/`initialMessages`
XOR so fork can carry both. createChat skips env bootstrap when
`initialMessages` is non-empty.
- AgentHeadless.execute() shrinks to (context, signal?). Fork dispatch in
agent.ts builds synthetic PromptConfig/ModelConfig/ToolConfig from the
parent's cache-safe params and calls AgentHeadless.create directly
(bypassing SubagentManager). Parent's tool decls flow through verbatim
including the `agent` tool itself for cache parity.
- Recursive-fork prevention switches from fork-side tool stripping to a
runtime guard. The previous `isInForkChild(history)` helper was dead
code (it scanned the main GeminiClient's history, not the fork child's
chat). Replaced with `isInForkExecution()` backed by AsyncLocalStorage:
the fork's background execution runs inside `runInForkContext`, and the
ALS frame propagates through the standard async chain into nested
AgentTool.execute() calls where the guard fires.
* refactor(core): move agent tool files into dedicated tools/agent/ directory
Move agent.ts, agent.test.ts, and fork-subagent.ts under
tools/agent/ and update all import paths accordingly.
* refactor(core): remove dead temp and top_p fields from ModelConfig
These fields were never populated from subagent frontmatter and served
no purpose in the fork path either. The ModelConfig interface retains
only the actively-used model field.
* refactor(core): read parent generation config directly instead of getCacheSafeParams
Fork subagent now reads system instruction and tool declarations from
the live GeminiChat via getGenerationConfig() instead of the global
getCacheSafeParams() snapshot. This removes the cross-module coupling
between the agent tool and the followup infrastructure.
* fix(core): prevent duplicate tool declarations when toolConfig has only inline decls
prepareTools() treated asStrings.length === 0 as "add all registry tools",
which is correct when no tools are specified at all, but wrong when the
caller provides only inline FunctionDeclaration[] (no string names). The
fork path passes parent tool declarations as inline decls for cache parity,
so prepareTools was adding the full registry set on top — duplicating every
non-excluded tool.
Add onlyInlineDecls.length === 0 to the condition so that pure-inline
toolConfigs bypass the registry entirely.
* feat(core): support agent-level `background: true` in frontmatter
Subagent definitions can now declare `background: true` in their YAML
frontmatter to always run as background tasks. This is OR'd with the
`run_in_background` tool parameter — useful for monitors, watchers, and
proactive agents so the LLM doesn't need to remember to set the flag.
* fix(core): address background subagent lifecycle gaps
- Inherit bgConfig from agentConfig so the resolved approval mode is
preserved for background agents (foreground would run AUTO_EDIT but
background fell back to DEFAULT, which combined with shouldAvoid-
PermissionPrompts would auto-deny every permission request).
- Honor SubagentStop blocking decisions in background runs by looping
on hook output up to 5 iterations, matching runSubagentWithHooks.
- Check terminate mode before reporting completion; non-GOAL modes
(ERROR, MAX_TURNS, TIMEOUT) are now reported as failures instead of
emitting a success notification for an incomplete run.
- Exclude SendMessageType.Notification from the UserPromptSubmit hook
guard so background completion messages are not rewritten or blocked
as if they were user input.
* feat(cli): headless support and SDK task events for background agents (#3379)
* feat(cli): unify notification queue for cron and background agents
Migrate cron from its own queue (cronQueueRef / cronQueue) to the shared
notification queue used by background agents. Both producers now push the
same item shape { displayText, modelText, sendMessageType } and a single
drain effect / helper processes them in FIFO order.
Cron fires render as HistoryItemNotification (● prefix) instead of
HistoryItemUser (> prefix), with a "Cron: <prompt>" display label.
Records use subtype 'cron' for clean resume and analytics separation.
Lift the non-interactive rejection for background agents. Register a
notification callback in nonInteractiveCli.ts with a terminal hold-back
phase (100ms poll) that keeps the process alive until all background
agents complete and their notifications are processed.
* feat(cli): emit SDK task events for background subagents
Emit `task_started` when a background agent registers and
`task_notification` when it completes, fails, or is cancelled, so
headless/SDK consumers can track lifecycle without parsing display
text. Model-facing text is now structured XML with status, summary,
truncated result, and usage stats. Completion stats (tokens, tool
uses, duration) are captured from the subagent and included in both
the SDK payload and the model XML.
* fix: address codex review issues for background subagents
- Background subagents now inherit the resolved approval mode from
agentConfig instead of the raw session config, so a subagent with
`approvalMode: auto-edit` (or execution in a trusted folder) keeps
that override when it runs asynchronously.
- Non-interactive cron drains are single-flight: concurrent cron fires
now await the same in-flight drain, and the cron-done check gates
on it, preventing the final result from being emitted while a cron
turn is still streaming.
- Background forks go through createForkSubagent so they retain the
parent's rendered system prompt and inherited history instead of
degrading to a plain FORK_AGENT.
* fix(cli): restore cancellation, approval, and error paths in queued drain
- Hold-back loop now reacts to SIGINT/SIGTERM: when the main abort
signal fires it calls registry.abortAll() so background agents with
their own AbortControllers stop promptly instead of pinning the
process open.
- Queued-turn tool execution forwards the stream-json approval update
callback (onToolCallsUpdate) so permission-gated tools inside a
background-notification follow-up emit can_use_tool requests.
- Queued-turn stream loop mirrors the main loop's text-mode handling
of GeminiEventType.Error, writing to stderr and throwing so provider
errors produce a non-zero exit code instead of silently succeeding.
- Interactive cron prompts go through the normal slash/@-command/shell
preprocessing again; only Notification messages skip that path.
* fix(cli): skip duplicate user-message item for cron prompts
Cron prompts already render as a `● Cron: …` notification via the queue
drain, so adding them again as a `USER` history item produced a
duplicate `> …` line.
* fix(cli): honor SIGINT/SIGTERM during cron scheduler wait
The non-interactive cron phase awaits a Promise that resolves only when
scheduler.size reaches 0 and no drain is in flight. Recurring cron jobs
never drop the scheduler size to 0 on their own, so the previous abort
handling (added to the hold-back loop) was unreachable — the process
hung indefinitely after SIGINT/SIGTERM. Attach an abort listener inside
the promise so abort stops the scheduler and resolves immediately,
allowing the hold-back loop to run and the process to exit cleanly.
* feat(core): propagate tool-use id through background agent notifications
Plumb the scheduler's callId into AgentToolInvocation via an optional
setCallId hook on the invocation, detected structurally in
buildInvocation. The agent tool forwards it as toolUseId on the
BackgroundTaskRegistry entry so completion notifications can carry a
<tool-use-id> tag and SDK task_started / task_notification events can
emit tool_use_id — letting consumers correlate background completions
back to the original Agent tool-use that spawned them.
* fix(cli): drain single-flight race kept task_notification from emitting
drainLocalQueue wrapped its body in an async IIFE and cleared the
promise reference via finally. When the queue is empty the IIFE has
no awaits, so its finally runs synchronously as part of the RHS of
the assignment `drainPromise = (async () => {...})()` — clearing
drainPromise BEFORE the outer assignment overwrites it with the
resolved promise. The reference then stayed stuck on that fulfilled
promise forever, so later calls short-circuited through
`if (drainPromise) return drainPromise` and never processed
queued notifications.
Symptom: in headless `--output-format json` (and `stream-json`),
task_started emitted but task_notification never did, even after
the background agent completed. The process sat in the hold-back
loop until SIGTERM.
Fix: move the null-clearing out of the async body into an outer
`.finally()` on the returned promise. `.finally()` runs as a
microtask after the current synchronous block, so it clears the
latest drainPromise reference instead of the pre-assignment null.
* fix(cli): append newline to text-mode emitResult so zsh PROMPT_SP doesn't erase the line
Headless text mode wrote `resultMessage.result` without a trailing newline.
In a TTY, zsh themes that use PROMPT_SP (powerlevel10k, agnoster, …) detect
the missing `\n` and emit `\r\033[K` before drawing the next prompt, which
wipes the final line off the screen. Pipe-captured output was unaffected,
so the bug only surfaced for interactive shell users — most visibly in the
background-agent flow where the drain-loop's final assistant message is
the *only* stdout write in text mode.
Append `\n` to both the success (stdout) and error (stderr) writes.
* docs(skill): tighten worked-example blurb in structured-debugging
Mirror the simplified blurb from .claude/skills/structured-debugging/SKILL.md
(knowledge repo). Drops the round-by-round narrative; keeps the contradiction
+ two lessons.
* docs(skill): mirror SKILL.md improvements (reframing failure mode, generalized path, value-logging guidance)
Mirror of knowledge repo commit 38eb28d into the qwen-code .qwen/skills
copy.
* docs(skill): mirror worked example into .qwen/skills/structured-debugging/
Mirrors knowledge/.claude/skills/structured-debugging/examples/
headless-bg-agent-empty-stdout.md so the .qwen copy of the skill links
resolve.
* docs(skill): mirror generalized side-note path guidance
* fix(cli): harden headless cron and background-agent failure paths
Three regressions surfaced by Codex review of feat/background-subagent:
- Cron drain rejections were dropped by a bare `void`, so a failing
queued turn left the outer Promise unresolved and hung the run. Route
drain failures through the Promise's reject so they propagate to the
outer catch.
- The background-agent registry entry was inserted before
`createForkSubagent()` / `createAgentHeadless()` was awaited. Failed
init returned an error from the tool call but left a phantom `running`
entry, and the headless hold-back loop (`registry.getRunning()`) waited
forever. Register only after init succeeds.
- SIGINT/SIGTERM during the hold-back phase aborted background tasks,
then fell through to `emitResult({ isError: false })`, so a cancelled
`qwen -p ...` exited 0 with the prior assistant text. Route through
`handleCancellationError()` so cancellation exits non-zero, matching
the main turn loop.
* test(cli): update stdout/stderr assertions for trailing newline
`feadf052f` appended `\n` to text-mode `emitResult` output, but the
nonInteractiveCli tests still asserted the pre-change strings. Update
the 11 affected assertions to expect the trailing newline.
* fix: address review comments on background-agent notifications
Four additional issues from the PR review that the prior regression-fix
commit didn't cover:
- Escape XML metacharacters when interpolating `description`, `result`,
`error`, `agentId`, `toolUseId`, and `status` into the task-notification
envelope. Subagent output (which itself may carry untrusted tool output,
fetched HTML, or another agent's notification) could contain
`</result>` or `</task-notification>` and forge sibling tags the parent
model would treat as trusted metadata. Truncate result text *before*
escaping so the truncation never slices through an entity like `&`.
- Emit the terminal notification from `cancel()` and `abortAll()`. The
fire-and-forget `complete()`/`fail()` from the subagent task is guarded
by `status !== 'running'` and was no-op'd after cancellation, so SDK
consumers saw `task_started` with no matching `task_notification`,
breaking the contract this PR establishes. Updated two race-guard
tests that asserted the old behavior.
- Call `adapter.finalizeAssistantMessage()` before the abort-triggered
early return inside `drainOneItem`'s stream loop. Without it,
`startAssistantMessage()` had already been called, so stream-json mode
left `message_start` unpaired.
- Enforce `config.getMaxSessionTurns()` in `drainOneItem` for symmetry
with the main turn loop. Cron fires and notification replies otherwise
bypass the budget cap in headless runs.
* fix: address codex review comments for background subagents
- Wrap background fork execute() in runInForkContext so the
recursive-fork guard (AsyncLocalStorage-based) fires when a
background fork's child model calls `agent` again. Previously only
the foreground fork path was wrapped, so background forks could
spawn nested implicit forks.
- Emit queued terminal task_notifications on SIGINT/SIGTERM before
handleCancellationError exits. abortAll() enqueues cancellation
notifications via the registry callback, but the process was
exiting before the drain loop had a chance to flush them — leaving
stream-json consumers that already saw task_started without a
matching terminal task_notification. Extracted the SDK-emit block
into a shared emitNotificationToSdk helper reused by the normal
drain and the cancellation flush.
- Skip notification/cron subtypes in ACP HistoryReplayer. These
records are persisted as type: 'user' so the model's chat history
keeps them for continuity, but they were never user input —
replaying them leaked raw <task-notification> XML (and cron
prompts) back into the ACP session as if the user typed them.
* test(cli): sync JsonOutputAdapter text-mode assertions with trailing newline
Commit 0da1182b7 appended a newline to text-mode emitResult output
(zsh PROMPT_SP fix) and updated the nonInteractiveCli tests, but
four assertions in JsonOutputAdapter.test.ts were missed. Update
them to expect the trailing newline so CI passes.
* refactor: simplify background subagent plumbing
- Extract the SubagentStop hook blocking-decision loop into a
runSubagentStopHookLoop helper so the foreground and background
paths no longer duplicate the iteration/abort/log scaffolding.
- Unify BackgroundTaskRegistry.abortAll to delegate to cancel,
removing copy-pasted abort/notification bookkeeping.
- Drop the unused findByName and BackgroundAgentEntry.name field.
- In nonInteractiveCli drain, hoist inputFormat and
toolCallUpdateCallback out of the inner tool loop, and drop the
unreachable try/catch around the readonly registry.
- Trim boilerplate doc/narration comments while keeping load-bearing
WHY comments.
* fix: address codex review comments for background subagents
- Use tool callId (or short random suffix) instead of Date.now() for
background agentIds; avoids registry collisions when parallel
same-type agents launch in the same millisecond.
- Reset loopDetector and lastPromptId for Notification turns so a
prior turn's loop count doesn't trip LoopDetected on the
notification response.
- Replay notification/cron displayText in ACP HistoryReplayer so
the assistant reply has an antecedent in resumed transcripts.
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs: add auto-memory implementation log
* feat(core): add managed auto-memory storage scaffold
* feat(core): load managed auto-memory index
* feat(core): add managed auto-memory recall
* feat(core): add managed auto-memory extraction
* feat(cli): add managed auto-memory dream commands
* feat(core): add auxiliary side-query foundation
* feat(memory): add model-driven recall selection
* feat(memory): add model-driven extraction planner
* feat(core): add background task runtime foundation
* feat(memory): schedule auto dream in background
* feat(core): add background agent runner foundation
* feat(memory): add extraction agent planner
* feat(core): add dream agent planner
* feat(core): rebuild managed memory index
* feat(memory): add governance status commands
* feat(memory): add managed forget flow
* feat(core): harden background agent planning
* feat(memory): complete managed parity closure
* test(memory): add managed lifecycle integration coverage
* feat: same to cc
* feat(memory-ui): add memory saved notification and memory count badge
Feature 3 - Memory Saved Notification:
- Add HistoryItemMemorySaved type to types.ts
- Create MemorySavedMessage component for rendering '● Saved/Updated N memories'
- In useGeminiStream: detect in-turn memory writes via mapToDisplay's
memoryWriteCount field and emit 'memory_saved' history item after turn
- In client.ts: capture background dream/extract promises and expose
via consumePendingMemoryTaskPromises(); useGeminiStream listens
post-turn and emits 'Updated N memories' notification for background tasks
Feature 4 - Memory Count Badge:
- Add isMemoryOp field to IndividualToolCallDisplay
- Add memoryWriteCount/memoryReadCount to HistoryItemToolGroup
- Add detectMemoryOp() in useReactToolScheduler using isAutoMemPath
- ToolGroupMessage renders '● Recalled N memories, Wrote N memories' badge
at the top of tool groups that touch memory files
Fix: process.env bracket-access in paths.ts (noPropertyAccessFromIndexSignature)
Fix: MemoryDialog.test.tsx mock useSettings to satisfy SettingsProvider requirement
* fix(memory-ui): auto-approve memory writes, collapse memory tool groups, fix MEMORY.md path
Problem 1 - Auto-approve memory file operations:
- write-file.ts: getDefaultPermission() checks isAutoMemPath; returns 'allow'
for managed auto-memory files, 'ask' for all other files
- edit.ts: same pattern
Problem 2 - Feature 4 UX: collapse memory-only tool groups:
- ToolGroupMessage: detect when all tool calls have isMemoryOp set (pure memory
group) and all are complete; render compact '● Recalled/Wrote N memories
(ctrl+o to expand)' instead of individual tool call rows
- ctrl+o toggles expand/collapse when isFocused and group is memory-only
- Mixed groups (memory + other tools) keep badge-at-top behaviour
- Expanded state shows individual tool calls with '● Memory operations
(ctrl+o to collapse)' header
Problem 3 - MEMORY.md path mismatch:
- prompt.ts: Step 2 now references full absolute path ${memoryDir}/MEMORY.md
so the model writes to the correct location inside the memory directory,
not to the parent project directory
Fix tests:
- write-file.test.ts: add getProjectRoot to mockConfigInternal
- prompt.test.ts: update assertion to match full-path section header
* fix(memory-ui): fix duplicate notification, broken ctrl+o, and Edit tool detection
- Remove duplicate 'Saved N memories' notification: the tool group badge already
shows 'Wrote N memories'; the separate HistoryItemMemorySaved addItem after
onComplete was double-counting. Keep only the background-task path
(consumePendingMemoryTaskPromises).
- Remove ctrl+o expand: Ink's Static area freezes items on first render and
cannot respond to user input. useInput/useState(isExpanded) in a Static item
is a no-op. Removed the dead code; memory-only groups now always render as
the compact summary (no fake interactive hint).
- Fix Edit tool detection: detectMemoryOp was checking for 'edit_file' but the
real tool name constant is 'edit'. Also removed non-existent 'create_file'
(write_file covers all writes). Now editing MEMORY.md is correctly identified
as a memory write op, collapses to 'Wrote N memories', and is auto-approved.
* fix(dream): run /dream as a visible submit_prompt turn, not a silent background agent
The previous implementation ran an AgentHeadless background agent that could
take 5+ minutes with zero UI feedback — user saw a blank screen for the entire
duration and then at most one line of text.
Fix: /dream now returns submit_prompt with the consolidation task prompt so it
runs as a regular AI conversation turn. Tool calls (read_file, write_file, edit,
grep_search, list_directory, glob) are immediately visible as collapsed tool
groups as the model works through the memory files — identical UX to Claude Code.
Also export buildConsolidationTaskPrompt from dreamAgentPlanner so dreamCommand
can reuse the same detailed consolidation prompt that was already written.
* fix(memory): auto-allow ls/glob/grep on memory base directory
Add getMemoryBaseDir() to getDefaultPermission() allow list in ls.ts,
glob.ts, and grep.ts — mirrors the existing pattern in read-file.ts.
Without this, ListFiles/Glob/Grep on ~/.qwen/* would trigger an
approval dialog, blocking /dream at its very first step.
* fix(background): prevent permission prompt hangs in background agents
Match Claude Code's headless-agent intent: background memory agents must never
block on interactive permission prompts.
Wrap background runtime config so getApprovalMode() returns YOLO, ensuring any
ask decision is auto-approved instead of hanging forever. Add regression test
covering the wrapped approval mode.
* fix(memory): run auto extract through forked agent
Make managed auto-memory extraction follow the Claude Code architecture:
background extraction now uses a forked agent to read/write memory files
directly, instead of planning patches and applying them with a separate
filesystem pipeline.
Keep the old patch/model path only as fallback if the forked agent fails.
Add regression tests covering the new execution path and tool whitelist.
* refactor(memory): remove legacy extract fallback pipeline
Delete the old patch/model/heuristic extraction path entirely.
Managed auto-memory extract now runs only through the forked-agent
execution flow, with no planner/apply fallback stages remaining.
Also remove obsolete exports/tests and update scheduler/integration
coverage to use the forked-agent-only architecture.
* refactor(memory): move auxiliary files out of memory/ directory
meta.json, extract-cursor.json, and consolidation.lock are internal
bookkeeping files, not user-visible memories. Move them one level up
to the project state dir (parent of memory/) so that the memory/
directory contains only MEMORY.md and topic files, matching the
clean layout of the upstream reference implementation.
Add getAutoMemoryProjectStateDir() helper in paths.ts and update the
three path accessors + store.test.ts path assertions accordingly.
* fix(memory): record lastDreamAt after manual /dream run
The /dream command submits a prompt to the main agent (submit_prompt),
which writes memory files directly. Because it bypasses dreamScheduler,
meta.json was never updated and /memory always showed 'never'.
Fix by:
- Exporting writeDreamManualRunToMetadata() from dream.ts
- Adding optional onComplete callback to SubmitPromptActionReturn and
SubmitPromptResult (types.ts / commands/types.ts)
- Propagating onComplete through slashCommandProcessor.ts
- Firing onComplete after turn completion in useGeminiStream.ts
- Providing the callback in dreamCommand.ts to write lastDreamAt
* fix(memory): remove scope params from /remember in managed auto-memory mode
--global/--project are legacy save_memory tool concepts. In managed
auto-memory mode the forked agent decides the appropriate type
(user/feedback/project/reference) based on the content of the fact.
Also improve the prompt wording to explicitly ask the agent to choose
the correct type, reducing the tendency to default to 'project'.
* feat(ui): show '✦ dreaming' indicator in footer during background dream
Subscribe to getManagedAutoMemoryDreamTaskRegistry() in Footer via a
useDreamRunning() hook. While any dream task for the current project is
pending or running, display '✦ dreaming' in the right section of the
footer bar, between Debug Mode and context usage.
* refactor(memory): align dream/extract infrastructure with Claude Code patterns
Five improvements based on Claude Code parity audit:
1. Memoize getAutoMemoryRoot (paths.ts)
- Add _autoMemoryRootCache Map, keyed by projectRoot
- findCanonicalGitRoot() walks the filesystem per call; memoize avoids
repeated git-tree traversal on hot-path schedulers/scanners
- Expose clearAutoMemoryRootCache() for test teardown
2. Lock file stores PID + isProcessRunning reclaim (dreamScheduler.ts)
- acquireDreamLock() writes process.pid to the lock file body
- lockExists() reads PID and calls process.kill(pid, 0); dead/missing
PID reclaims the lock immediately instead of waiting 2h
- Stale threshold reduced to 1h (PID-reuse guard, same as CC)
3. Session scan throttle (dreamScheduler.ts)
- Add SESSION_SCAN_INTERVAL_MS = 10min (same as CC)
- Add lastSessionScanAt Map<projectRoot, number> to ManagedAutoMemoryDreamRuntime
- When time-gate passes but session-gate doesn't, throttle prevents
re-scanning the filesystem on every user turn
4. mtime-based session counting (dreamScheduler.ts)
- Replace fragile recentSessionIdsSinceDream Set in meta.json with
filesystem mtime scan (listSessionsTouchedSince)
- Mirrors Claude Code's listSessionsTouchedSince: reads session JSONL
files from Storage.getProjectDir()/chats/, filters by mtime > lastDreamAt
- Immune to meta.json corruption/loss; no per-turn metadata write
- ManagedAutoMemoryDreamRuntime accepts injectable SessionScannerFn
for clean unit testing without real session files
5. Extraction mutual exclusion extended to write_file/edit (extractScheduler.ts)
- historySliceUsesMemoryTool() now checks write_file/edit/replace/create_file
tool calls whose file_path is within isAutoMemPath()
- Previously only detected save_memory; missed direct file writes by
the main agent, causing redundant background extraction
* docs(memory): add user-facing memory docs, i18n for all locales, simplify /forget
- Add docs/users/features/memory.md: comprehensive user-facing guide covering
QWEN.md instructions, auto-memory behaviour, all memory commands, and
troubleshooting; replaces the placeholder auto-memory.md
- Update docs/users/features/_meta.ts: rename entry auto-memory → memory
- Update docs/users/features/commands.md: add /init, /remember, /forget,
/dream rows; fix /memory description; remove /init duplicate
- Update docs/users/configuration/settings.md: add memory.* settings section
(enableManagedAutoMemory, enableManagedAutoDream) between tools and permissions
- Remove /forget --apply flag: preview-then-apply flow replaced with direct
deletion; update forgetCommand.ts, en.js, zh.js accordingly
- Add all auto-memory i18n keys to de, ja, pt, ru locales (18 keys each):
Open auto-memory folder, Auto-memory/Auto-dream status lines, never/on/off,
✦ dreaming, /forget and /remember usage strings, all managed-memory messages
- Remove dead save_memory branch from extractScheduler.partWritesToMemory()
- Add ✦ dreaming indicator to Footer.tsx with i18n; fix Footer.test.tsx mocks
- Refactor MemoryDialog.tsx auto-dream status line to use i18n
- Remove save_memory tool (memoryTool.ts/test); clean up webui references
- Add extractionPlanner.ts, const.ts and associated tests
- Delete stale docs/users/configuration/memory.md and
docs/developers/tools/memory.md (content superseded)
* refactor(memory): remove all Claude Code references from comments and test names
* test(memory): remove empty placeholder test files that cause vitest to fail
* fix eslint
* fix test in windows
* fix test
* fix(memory): address critical review findings from PR #3087
- fix(read-file): narrow auto-allow from getMemoryBaseDir() (~/.qwen) to
isAutoMemPath(projectRoot) to prevent exposing settings.json / OAuth
credentials without user approval (wenshao review)
- fix(forget): per-entry deletion instead of whole-file unlink
- assign stable per-entry IDs (relativePath:index for multi-entry files)
so the model can target individual entries without removing siblings
- rewrite file keeping unmatched entries; only unlink when file becomes
empty (wenshao review)
- fix(entries): round-trip correctness for multi-entry new-format bodies
- parseAutoMemoryEntries: plain-text line closes current entry and opens
a new one (was silently ignored when current was already set)
- renderAutoMemoryBody: emit blank line between adjacent entries so the
parser can detect entry boundaries on re-read (wenshao review)
- fix(entries): resolve two CodeQL polynomial-regex alerts
- indentedMatch: \s{2,}(?:[-*]\s+)? → [\t ]{2,}(?:[-*][\t ]+)?
- topLevelMatch: :\s*(.+)$ → :[ \t]*(\S.*)$
(github-advanced-security review)
- fix(scan.test): use forward-slash literal for relativePath expectation
since listMarkdownFiles() normalises all separators to '/' on all
platforms including Windows
* fix(memory): replace isAutoMemPath startsWith with path.relative()
Using path.relative() instead of string startsWith() is more robust
across platforms — it correctly handles Windows path-separator
differences and avoids potential edge cases where a path prefix match
could succeed on non-separator boundaries.
Addresses github-actions review item 3 (PR #3087).
* feat(telemetry): add auto-memory telemetry instrumentation
Add OpenTelemetry logs + metrics for the five auto-memory lifecycle
events: extract, dream, recall, forget, and remember.
Telemetry layer (packages/core/src/telemetry/):
- constants.ts: 5 new event-name constants
(qwen-code.memory.{extract,dream,recall,forget,remember})
- types.ts: 5 new event classes with typed constructor params
(MemoryExtractEvent, MemoryDreamEvent, MemoryRecallEvent,
MemoryForgetEvent, MemoryRememberEvent)
- metrics.ts: 8 new OTel instruments (5 Counters + 3 Histograms)
with recordMemoryXxx() helpers; registered inside initializeMetrics()
- loggers.ts: logMemoryExtract/Dream/Recall/Forget/Remember() — each
emits a structured log record and calls its recordXxx() counterpart
- index.ts: re-exports all new symbols
Instrumentation call-sites:
- extractScheduler.ts ManagedAutoMemoryExtractRuntime.runTask():
emits extract event with trigger=auto, completed/failed status,
patches_count, touched_topics, and wall-clock duration
- dream.ts runManagedAutoMemoryDream():
emits dream event with trigger=auto, updated/noop status,
deduped_entries, touched_topics, and duration; covers both
agent-planner and mechanical fallback paths
- recall.ts resolveRelevantAutoMemoryPromptForQuery():
emits recall event with strategy, docs_scanned/selected, and
duration; covers model, heuristic, and none paths
- forget.ts forgetManagedAutoMemoryEntries():
emits forget event with removed_entries_count, touched_topics,
and selection_strategy (model/heuristic/none)
- rememberCommand.ts action():
emits remember event with topic=managed|legacy at command
invocation time (before agent decides the actual memory type)
* refactor(telemetry): remove memory forget/remember telemetry events
Remove EVENT_MEMORY_FORGET and EVENT_MEMORY_REMEMBER along with all
associated infrastructure that is no longer needed:
- constants.ts: remove EVENT_MEMORY_FORGET, EVENT_MEMORY_REMEMBER
- types.ts: remove MemoryForgetEvent, MemoryRememberEvent classes
- metrics.ts: remove MEMORY_FORGET_COUNT, MEMORY_REMEMBER_COUNT constants,
memoryForgetCounter, memoryRememberCounter module vars,
their initialization in initializeMetrics(), and
recordMemoryForgetMetrics(), recordMemoryRememberMetrics() functions
- loggers.ts: remove logMemoryForget(), logMemoryRemember() functions
and their imports
- index.ts: remove all re-exports for the above symbols
- memory/forget.ts: remove logMemoryForget call-site and import
- cli/rememberCommand.ts: remove logMemoryRemember call-sites and import
* change default value
* fix forked agent
* refactor(background): unify fork primitives into runForkedAgent + cleanup
- Merge runForkedQuery into runForkedAgent via TypeScript overloads:
with cacheSafeParams → GeminiChat single-turn path (ForkedQueryResult)
without cacheSafeParams → AgentHeadless multi-turn path (ForkedAgentResult)
- Delete forkedQuery.ts; move its test to background/forkedAgent.cache.test.ts
- Remove forkedQuery export from followup/index.ts
- Migrate all callers (suggestionGenerator, speculation, btwCommand, client)
to import from background/forkedAgent
- Add getFastModel() / setFastModel() to Config; expose in CLI config init
and ModelDialog / modelCommand
- Remove resolveFastModel() from AppContainer — now delegated to config.getFastModel()
- Strip Claude Code references from code comments
* fix(memory): address wenshao's critical review findings
- dream.ts: writeDreamManualRunToMetadata now persists lastDreamSessionId
and resets recentSessionIdsSinceDream, preventing auto-dream from firing
again in the same session after a manual /dream
- config.ts: gate managed auto-memory injection on getManagedAutoMemoryEnabled();
when disabled, previously saved memories are no longer injected into new sessions
- rememberCommand.ts: remove legacy save_memory branch (tool was removed);
fall back to submit_prompt directing agent to write to QWEN.md instead
- BuiltinCommandLoader.ts: only register /dream and /forget when managed
auto-memory is enabled, matching the feature's runtime availability
- forget.ts: return early in forgetManagedAutoMemoryMatches when matches is
empty, avoiding unnecessary directory scaffolding as a side effect
* fix test
* fix ci test
* feat(memory): align extract/dream agents to Claude Code patterns
- fix(client): move saveCacheSafeParams before early-return paths so
extract agents always have cache params available (fixes extract never
triggering in skipNextSpeakerCheck mode)
- feat(extract): add read-only shell tool + memory-scoped write
permissions; create inline createMemoryScopedAgentConfig() with
PermissionManager wrapper (isToolEnabled + evaluate) that allows only
read-only shell commands and write/edit within the auto-memory dir
- feat(extract): align prompt to Claude Code patterns — manifest block
listing existing files, parallel read-then-write strategy, two-step
save (memory file then index)
- feat(dream): remove mechanical fallback; runManagedAutoMemoryDream is
now agent-only and throws without config
- feat(dream): align prompt to Claude Code 4-phase structure
(Orient/Gather/Consolidate/Prune+Index); add narrow transcript grep,
relative→absolute date conversion, stale index pruning, index size cap
- fix(permissions): add isToolEnabled() to MemoryScopedPermissionManager
to prevent TypeError crash in CoreToolScheduler._schedule
- test: update dreamScheduler tests to mock dream.js; replace removed
mechanical-dedup test with scheduler infrastructure verification
* move doc to design
* refactor(memory): unify extract+dream background task management into MemoryBackgroundTaskHub
- Add memoryTaskHub.ts: single BackgroundTaskRegistry + BackgroundTaskDrainer shared
by all memory background tasks; exposes listExtractTasks() / listDreamTasks()
typed query helpers and a unified drain() method
- extractScheduler: ManagedAutoMemoryExtractRuntime accepts hub via constructor
(defaults to defaultMemoryTaskHub); test factory gets isolated fresh hub
- dreamScheduler: same pattern — sessionScanner + hub injection; BackgroundTask-
Scheduler initialized from injected hub; test factory gets isolated hub
- status.ts: replace two separate getRegistry() calls with defaultMemoryTaskHub
typed query methods
- Footer.tsx (useDreamRunning): subscribe to shared registry, filter by
DREAM_TASK_TYPE so extract tasks do not trigger the dream spinner
- index.ts: re-export memoryTaskHub.ts so defaultMemoryTaskHub/DREAM_TASK_TYPE/
EXTRACT_TASK_TYPE are available as top-level package exports
* refactor(background): introduce general-purpose BackgroundTaskHub
Replace memory-specific MemoryBackgroundTaskHub with a domain-agnostic
BackgroundTaskHub in the background/ layer. Any future background task
runtime (3rd, 4th, …) plugs in by accepting a hub via constructor
injection — no new infrastructure required.
Changes:
- Add background/taskHub.ts: BackgroundTaskHub (registry + drainer +
createScheduler() + listByType(taskType, projectRoot?)) and the
globalBackgroundTaskHub singleton. Zero knowledge of any task type.
- Delete memory/memoryTaskHub.ts: its narrow listExtractTasks /
listDreamTasks helpers are replaced by the generic listByType() call.
- Move EXTRACT_TASK_TYPE to extractScheduler.ts (owned by the runtime
that defines it); replace 3 hardcoded string literals with the const.
- Move DREAM_TASK_TYPE to dreamScheduler.ts; use hub.createScheduler()
instead of manually wiring new BackgroundTaskScheduler(reg, drain).
- status.ts: globalBackgroundTaskHub.listByType(EXTRACT_TASK_TYPE, ...)
- Footer.tsx: globalBackgroundTaskHub.registry (shared, filtered by type)
- index.ts: export background/taskHub.js; drop memory/memoryTaskHub.js
* test(background): add BackgroundTaskHub unit tests and hub isolation checks
- background/taskHub.test.ts (11 tests):
- createScheduler(): tasks registered via scheduler appear in hub registry;
multiple calls return distinct scheduler instances
- listByType(): filters by taskType, filters by projectRoot, returns []
for unknown types, two types co-exist in registry but stay separated
- drain(): resolves false on timeout, resolves true when tasks complete,
resolves true immediately when no tasks in flight
- isolation: tasks in hubA do not appear in hubB
- globalBackgroundTaskHub: is a BackgroundTaskHub instance with registry/drainer
- extractScheduler.test.ts (+1 test):
- factory-created runtimes have isolated registries; tasks in runtimeA
are invisible to runtimeB; all tasks carry EXTRACT_TASK_TYPE
- dreamScheduler.test.ts (+1 test):
- factory-created runtimes have isolated registries; tasks in runtimeA
are invisible to runtimeB; all tasks carry DREAM_TASK_TYPE
* refactor(memory): consolidate all memory state into MemoryManager
Replace BackgroundTaskRegistry/Drainer/Scheduler/Hub helper classes and
module-level globals with a single MemoryManager class owned by Config.
## Changes
### New
- packages/core/src/memory/manager.ts — MemoryManager with:
- scheduleExtract / scheduleDream (inline queuing + deduplication logic)
- recall / forget / selectForgetCandidates / forgetMatches
- getStatus / drain / appendToUserMemory
- subscribe(listener) compatible with useSyncExternalStore
- storeWith() atomic record registration (no double-notify)
- Distinct skippedReason 'scan_throttled' vs 'min_sessions' for dream
- packages/core/src/utils/forkedAgent.ts — pure cache util (moved from background/)
- packages/core/src/utils/sideQuery.ts — pure util (moved from auxiliary/)
### Deleted
- background/taskRegistry, taskDrainer, taskScheduler, taskHub and all tests
- background/forkedAgent (moved to utils/)
- auxiliary/sideQuery (moved to utils/)
- memory/extractScheduler, dreamScheduler, state and all tests
### Modified
- config/config.ts — Config owns MemoryManager instance; getMemoryManager()
- core/client.ts — all memory ops via config.getMemoryManager()
- core/client.test.ts — mock MemoryManager instead of individual modules
- memory/status.ts — accepts MemoryManager param, drops globalBackgroundTaskHub
- index.ts — memory exports reduced from 14 modules to 5 (manager/types/paths/store/const)
- cli/commands/dreamCommand.ts — via config.getMemoryManager()
- cli/commands/forgetCommand.ts — via config.getMemoryManager()
- cli/components/Footer.tsx — useSyncExternalStore replacing setInterval polling
- cli/components/Footer.test.tsx — add getMemoryManager mock
The "Compact Mode" label is more intuitive than "Verbose Mode" for users,
as it directly describes the default compact view experience. This change
inverts the boolean semantics (compactMode=false means show full output)
and exposes the setting in the /settings dialog (showInDialog: true).
- Rename ui.verboseMode → ui.compactMode with inverted default (false)
- Rename VerboseModeContext → CompactModeContext (file and exports)
- Rename TOGGLE_VERBOSE_MODE → TOGGLE_COMPACT_MODE in key bindings
- Update all consumer components with inverted logic
- Update i18n keys across 6 locales (verbose → compact)
- Update VS Code settings schema
- Add ui.compactMode documentation to settings.md
- Fix Ctrl+O description in keyboard-shortcuts.md
- Add null/undefined guard in formatBtwError to avoid "null"/"undefined" strings
- Add type guard for btw property in HistoryItemDisplay to prevent crash
- Extract isBtwCommand regex to module-level constant and simplify with [/?]
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow users to ask quick "by the way" questions that use the current
conversation context but don't pollute the main conversation history.
Closes#2370
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Consolidate individual message component files into two modules:
- ConversationMessages.tsx for user/assistant/think messages
- StatusMessages.tsx for info/warning/error/retry messages
Rename Gemini* components to Assistant*/Think* for clarity.
Add 'code' semantic color token for consistent code styling.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat: add Ctrl+Y shortcut to retry failed requests
- Add Ctrl+Y keyboard shortcut for retrying the last failed request
- Add isNetworkError() to detect transient network failures (ECONNREFUSED, ETIMEDOUT, etc.)
- Add DashScope 1305 error code to rate limit detection
- Add error hint \"Press Ctrl+Y to retry\" in error messages
- Support user-defined error codes for retry via config
- Add retryLastPrompt() hook in useGeminiStream
- Update keyboard shortcuts documentation
* feat: improve Ctrl+Y retry feature with tests, docs, and rate limit config
- Add comprehensive tests for Ctrl+Y retry shortcut in InputPrompt
- Add unit tests for retryLastPrompt in useGeminiStream hook
- Add detailed JSDoc comments for retryLastPrompt function and Ctrl+Y shortcut
- Extend isRateLimitError to support custom error codes via retryErrorCodes config
- Fix rate limit retry log variable reference (RATE_LIMIT_RETRY_OPTIONS → maxRateLimitRetries)
- Add Eclipse IDE files to .gitignore
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* refactor(ui): consolidate retry countdown as inline hint in error messages
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(cli): enhance error handling with improved retry mechanism
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
- Modify ErrorMessage component to remove dim color from hint text
- Update useGeminiStream hook to improve retry countdown behavior with option to preserve or clear hints
- Adjust tests to match new error handling implementation
* feat: add Ctrl+Y shortcut to retry the last failed request
When a request errors out, the error message shows an inline hint
"(Press Ctrl+Y to retry.)" in secondary color. Pressing Ctrl+Y
re-submits the same prompt, commits the error text to history
(without the hint), and clears the hint from the UI.
- Add retryLastPrompt action wired to Ctrl+Y via keyBindings and InputPrompt
- Track last submitted prompt and error state in useGeminiStream refs
- Show retry hint inline with error text in ErrorMessage component,
wrapping naturally on narrow terminals while preserving hint color
- Expose retryLastPrompt through UIActionsContext
- Add keyboard shortcut entry in KeyboardShortcuts display
- Add i18n strings for hint and no-retry-available message
- Document Ctrl+Y in keyboard-shortcuts.md
* docs(configuration): Update model provider configuration document
* chore: remove YOLO mode code from core
* fix: prevent Ctrl+Y hint from overriding auto-retry countdown
When an auto-retry countdown is active (retryCountdownTimerRef is set),
handleErrorEvent should not overwrite it with the Ctrl+Y hint. The auto-retry
hint ("retrying in Xs...") and manual retry hint ("Press Ctrl+Y to retry.")
are mutually exclusive:
- Auto-retry errors (e.g., rate limits): show countdown hint
- Other errors: show Ctrl+Y hint
Also removed retryErrorCodes from ContentGeneratorConfig as it's not part
of the minimal Ctrl+Y feature scope.
* simplify: remove complex options from clearRetryCountdown
Revert clearRetryCountdown to simplest form without options parameter.
The function now just clears the timer and pending item without any
automatic history commit logic.
* fix: restore pendingRetryCountdownItem as separate state from pendingRetryErrorItem
Auto-retry countdown and manual retry hint are now independent:
- pendingRetryErrorItem: displays error message with optional hint
- pendingRetryCountdownItem: displays separate countdown line for auto-retry
This ensures both can be shown simultaneously without overriding each other.
* fix: restore RetryCountdownMessage rendering in HistoryItemDisplay
The retry_countdown type should be rendered as a separate message,
not inline in ErrorMessage. This allows auto-retry countdown and
manual retry hint to coexist properly.
* fix(cli): properly commit retry error item to history before clearing
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): remove trailing period from retry hint translations
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Remove unnecessary period from 'Press Ctrl+Y to retry' translation strings in both en.js and zh.js locales. Also update the corresponding usage in useGeminiStream hook.
* chore(sdk-java): add Eclipse project configuration files
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Add .project configuration files for client and qwencode modules to support Eclipse IDE development environment.
* feat(cli): add retry countdown hint to error message
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* Revert "chore(sdk-java): add Eclipse project configuration files"
This reverts commit da83b5e571.
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Add AgentViewContext, AgentTabBar, and AgentChatView components for tab-based
agent switching. Add useArenaInProcess hook bridging ArenaManager events to
React state. Add agentHistoryAdapter converting AgentMessage[] to HistoryItem[].
Core support changes:
- Replace stream buffers with ROUND_TEXT events (complete round text)
- Add TOOL_OUTPUT_UPDATE events for live tool output streaming
- Add pendingApprovals/liveOutputs/shellPids state to AgentInteractive
- Fix missing ROUND_END emission for final text rounds
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Introduces a new Arena system for running multiple AI agents in parallel
terminal sessions with support for iTerm and Tmux backends.
Core:
- Add ArenaManager and ArenaAgentClient for orchestrating multi-agent sessions
- Add terminal backends (ITermBackend, TmuxBackend) with feature detection
- Add git worktree service for isolated agent workspaces
- Add arena event system for real-time status updates
CLI:
- Add /arena command with start, stop, status, and select subcommands
- Add Arena dialogs (Select, Start, Status, Stop)
- Add ArenaCards component for displaying parallel agent outputs
- Consolidate message components into StatusMessages and ConversationMessages
- Add MultiSelect component for agent selection
Config:
- Add arena-related settings to schema and config
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>