Skip to content

feat(cli): add /fork background-agent command#4780

Merged
wenshao merged 13 commits into
QwenLM:mainfrom
qqqys:fork-cmd-pr
Jun 8, 2026
Merged

feat(cli): add /fork background-agent command#4780
wenshao merged 13 commits into
QwenLM:mainfrom
qqqys:fork-cmd-pr

Conversation

@qqqys

@qqqys qqqys commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a /fork <directive> slash command that spawns a background agent inheriting the full conversation (system prompt, history, tools, model, prompt-cache parity) and works the directive without blocking the main conversation. The fork reports back through the existing background-tasks panel and a terminal task-notification.

This realizes the /fork/branch split: /fork = background agent, /branch = copy-to-new-session.

Closes #4757.

fork

What's included

Commit 1 — feat(cli): add /fork background-agent command

  • New forkCommand.ts: routes through the Agent tool's background path (omitting subagent_type selects the implicit FORK_AGENT), reusing BackgroundTaskRegistry, live-activity tracking, JSONL transcript, stop, and the completion task-notification. Verified behaviorally identical to a model-launched fork.
  • Guards: empty directive, missing config/model, mid-stream / in-flight, no conversation history yet, and a non-throwing failed launch (e.g. concurrency cap) is surfaced as an error instead of a false success.
  • /branch drops its altNames: ['fork'] so /fork is unambiguous; /branch keeps the copy-to-new-session behaviour.
  • Registered unconditionally as a built-in command (no feature flag) — directly usable.

Commit 2 — fix(cli): sanitize ANSI/control sequences in background-tasks dialog

  • The background-tasks dialog rendered entry labels/titles/activity/prompt without sanitization, while the sibling LiveAgentPanel and /tasks already strip control codes. Since /fork directives flow into an entry's description/prompt, a raw escape sequence could reach the terminal when the dialog opens. Wrapped those renders in escapeAnsiCtrlCodes. Benefits all background agents, not just forks.

Testing

  • forkCommand.test.ts (11) — guards, full-tool launch, inherited history, failed-launch detection.
  • BuiltinCommandLoader.test.ts/fork is always registered.
  • BackgroundTasksDialog.test.tsx — ANSI/control-sequence injection guard, plus existing render suite.
  • Manually verified via npm run dev: /fork launches a background agent that inherits the conversation, appears in the inline tracker (main / ○ fork: …), runs non-blocking, and reports its result back into the session.

Notes / follow-ups

  • Not in this PR: pressing Enter to switch the main viewport into a fork's live transcript, and @mention-ing an agent. These are tracked as follow-up UX work.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR introduces a /fork <directive> slash command that spawns a background agent inheriting the full conversation context, along with a security fix to sanitize ANSI/control sequences in the background-tasks dialog. The implementation is well-structured with comprehensive test coverage and thoughtful guard conditions. The ANSI sanitization fix is an important security hardening that prevents terminal injection attacks.

🔍 General Feedback

  • The PR cleanly separates concerns: /fork for background agents, /branch for session copying
  • Excellent test coverage across all modified components with 11 test cases for the new fork command
  • Good use of existing infrastructure (Agent tool's background path, BackgroundTaskRegistry) rather than introducing new complexity
  • The ANSI sanitization fix proactively addresses a security issue before it can be exploited
  • Code follows existing patterns and conventions in the codebase
  • Comments clearly explain the "why" behind implementation decisions

🎯 Specific Feedback

🟡 High

  • File: packages/cli/src/ui/commands/forkCommand.ts:72-77 - The history check has a try-catch that silently swallows all errors. If getHistory() throws for a specific reason (e.g., client not initialized), this masks the underlying issue. Consider logging the error or checking for client initialization explicitly before calling getHistory().

  • File: packages/cli/src/ui/commands/forkCommand.ts:127-138 - The failed status check uses (display as { status?: string }).status === 'failed' which is a bit awkward. Consider defining a proper type guard or interface for the return display to make this check more type-safe and readable.

🟢 Medium

  • File: packages/cli/src/ui/commands/forkCommand.ts:95-99 - The params object doesn't include subagent_type to select the implicit FORK_AGENT. This is documented in comments but could be made more explicit with a comment directly on the params object or by explicitly setting it to undefined with an inline comment explaining why.

  • File: packages/cli/src/ui/commands/forkCommand.ts:15 - The deriveForkDescription function truncates at 60 characters with ellipsis. Consider extracting the magic number 60 to a named constant like MAX_FORK_DESCRIPTION_LENGTH for clarity and maintainability.

  • File: packages/cli/src/ui/components/background-view/BackgroundTasksDialog.tsx:305 - The escapeAnsiCtrlCodes is applied to rowLabel(entry) which already does string formatting. Consider whether the sanitization should happen earlier in the rowLabel function itself to ensure all callers benefit from the protection, not just this dialog.

🔵 Low

  • File: packages/cli/src/ui/commands/forkCommand.ts:148 - The success message is quite long at ~180 characters. Consider shortening to something like "Forked to background agent. Track progress in the background tasks panel." Users can check the panel for details.

  • File: packages/cli/src/ui/commands/forkCommand.test.ts:159 - The test comment says "The Agent tool does not reject on a failed background launch" - this is important behavior that should probably be documented in the forkCommand.ts file as well for future maintainers.

  • File: packages/cli/src/ui/components/background-view/BackgroundTasksDialog.tsx:33 - The import of escapeAnsiCtrlCodes could be grouped with the other utility imports (formatDuration, formatTokenCount) for better organization.

✅ Highlights

  • Comprehensive guard conditions in forkCommand: empty directive, missing config/model, mid-stream/in-flight, no conversation history, and failed launch detection
  • Excellent test coverage with 11 test cases covering guards, successful launch, error handling, and edge cases like truncation
  • Proactive security fix for ANSI injection in the background-tasks dialog - this is exactly the kind of defense-in-depth thinking that prevents vulnerabilities
  • Clean separation of concerns between /fork (background agent) and /branch (session copy) by removing the alias
  • Good use of existing infrastructure - routes through Agent tool's background path rather than duplicating logic
  • Well-documented code with clear comments explaining the background agent flow and why certain design decisions were made
  • The ANSI sanitization benefits all background agents, not just forks - good generalization of the fix

qqqys and others added 2 commits June 4, 2026 21:02
Add a `/fork <directive>` slash command that spawns a background agent
inheriting the full conversation (system prompt, history, tools, model,
prompt-cache parity) to work the directive without blocking the main
conversation. It routes through the Agent tool's background path
(omitting subagent_type selects the implicit FORK_AGENT), so it reuses
the existing BackgroundTaskRegistry, live-activity tracking, JSONL
transcript, stop, and completion task-notification.

`/branch` drops its `fork` alias so `/fork` unambiguously means the
background-agent command; `/branch` keeps the copy-to-new-session
behaviour.

Guards: empty directive, missing config/model, mid-stream/in-flight,
no conversation history yet, and a non-throwing failed launch
(concurrency cap) is surfaced instead of a false success.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
The background-tasks dialog rendered agent entry labels, titles,
activity lines, and prompt text without sanitization, while the sibling
LiveAgentPanel and /tasks already strip control codes. With user-driven
`/fork` directives flowing into an entry's description/prompt, a raw
escape sequence (e.g. clear-screen) could reach the terminal when the
dialog is opened.

Wrap the description/title/activity/prompt renders in
`escapeAnsiCtrlCodes`, matching LiveAgentPanel. Benefits all background
agents, not just forks.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
const agentTool = config.getToolRegistry().getTool(ToolNames.AGENT);
if (!agentTool) {
return {
type: 'message',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Missing feature-gate check. The fork path in the Agent tool is gated on isForkSubagentEnabled(config) (agent.ts:1431-1432), which defaults to off (config.ts:1078). When the gate is off, omitting subagent_type silently falls back to DEFAULT_BUILTIN_SUBAGENT_TYPE — a general-purpose subagent that starts with zero conversation history. The success message at line 158 ("It inherits this conversation") is then factually false.

Suggested change
type: 'message',
if (!config.isForkSubagentEnabled?.()) {
return {
type: 'message',
messageType: 'error',
content: t(
'The /fork command requires the fork feature gate. Set QWEN_CODE_ENABLE_FORK_SUBAGENT=1 to enable it.',
),
};
}
const agentTool = config.getToolRegistry().getTool(ToolNames.AGENT);

— qwen3.7-max via Qwen Code /review

// Guard: a fork inherits the conversation history; there must be one.
let hasHistory = false;
try {
hasHistory = (config.getGeminiClient().getHistory(true) ?? []).length > 0;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] getHistory(true) triggers structuredClone of the entire curated conversation (see geminiChat.ts:2377-2383) just to check .length > 0. The codebase explicitly warns against this pattern at chatCompressionService.ts:358: "Avoid getHistory(true) here: long tool-heavy sessions can make a defensive deep clone larger than the remaining V8 heap headroom." For long sessions this can cause noticeable latency.

Suggested change
hasHistory = (config.getGeminiClient().getHistory(true) ?? []).length > 0;
hasHistory = (config.getGeminiClient().getHistoryShallow() ?? []).length > 0;

— qwen3.7-max via Qwen Code /review

};
}

if (!config.getModel()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Missing test for the !config.getModel() guard. All tests provide getModel: () => 'test-model', so this branch ("No model configured.") is never exercised. Consider adding a test with getModel: () => '' to cover this path.

— qwen3.7-max via Qwen Code /review

}

// Guard: a fork inherits the conversation history; there must be one.
let hasHistory = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Missing test for the catch block around getHistory(). No test makes getHistory() throw to exercise this error-recovery path. Consider adding a test where getHistory throws and assert the result is the "Cannot fork before the first conversation turn" error (not an unhandled rejection).

— qwen3.7-max via Qwen Code /review

maxWidth: number;
}> = ({ entry, maxHeight, maxWidth }) => {
const title = `${entry.subagentType ?? 'Agent'} \u203A ${buildBackgroundEntryLabel(entry, { includePrefix: false })}`;
const title = escapeAnsiCtrlCodes(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The new ANSI sanitization test only covers list-view rendering. Three detail-view sanitization sites added in this PR (title here, activity label at line 636, prompt lines at line 664) have no test coverage. A malicious /fork directive containing ANSI escape sequences could still inject raw terminal escapes when the user opens the detail view. Consider extending the test to call enterDetail() and assert the detail-mode frame does not contain the raw escape sequence.

— qwen3.7-max via Qwen Code /review

name: 'fork',
kind: CommandKind.BUILT_IN,
supportedModes: ['interactive'] as const,
get description() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Missing argumentHint. The command requires a <directive> argument (errors on empty input at line 53), but does not set argumentHint. Other commands that accept arguments consistently set this field — e.g., bugCommand has argumentHint: '<description>', approvalModeCommand has '<mode>', goalCommand has '[<condition> | clear]'. The completion menu reads this field for display.

Suggested change
get description() {
supportedModes: ['interactive'] as const,
argumentHint: '<directive>',

— qwen3.7-max via Qwen Code /review

// the synchronous launch/registration, so a fresh one is sufficient.
result = await agentTool
.build(params)
.execute(new AbortController().signal);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The fork launch calls agentTool.build(params).execute(...) directly, bypassing the tool scheduler. This means no functionCall/functionResponse entries are added to the Gemini client's conversation history. When the fork completes, the <task-notification> XML arrives as a user-role message, but the model has zero prior context — it never issued the tool_use or saw the launch confirmation. Every other notification path starts with a model-issued tool_use, so the model can correlate the completion.

Consider injecting a sentToModel: true history item after a successful launch (e.g., a user-role message like "User launched a background fork via /fork: ") so the model can contextualize the eventual notification.

— qwen3.7-max via Qwen Code /review

// ============================================================================
'Resume a previous session': '恢复先前会话',
'Fork the current conversation into a new session': '将当前对话分支到新会话',
'Spawn a background agent that inherits the full conversation':

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The description key is translated here, but the other 9 t() strings in forkCommand.ts are missing from both zh.js and zh-TW.js:

  • 'Please provide a directive. Usage: /fork <directive>'
  • 'Cannot fork while a response or tool call is in progress. …'
  • 'Cannot fork before the first conversation turn.'
  • 'The /fork command requires the fork feature gate. …'
  • 'The agent tool is unavailable; cannot fork.'
  • 'Failed to launch fork: {{error}}'
  • 'the background agent could not be started.'
  • 'User launched a background fork via /fork: {{directive}}'
  • 'Forked into a background agent. It inherits this conversation …'

The sibling branchCommand has all of its error keys translated in both locale files. Chinese-locale users will currently see these 9 messages in English (via the t() fallback).

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

✅ Local Verification Report — PR #4780

feat(cli): add /fork background-agent command

Environment

  • macOS Darwin 25.4.0, Node.js v22.17.0, npm workspaces monorepo
  • Branch: fork-cmd-prmain
  • Verified commits up to: 43aba99 (fix: translate fork command messages)

Build Verification

Step Result
npm run build (full monorepo) ✅ PASS (EXIT_CODE=0)
tsc --build (type-check) ✅ PASS (EXIT_CODE=0)

Test Results

Suite Result Details
forkCommand.test.ts ✅ 14/14 passed All guards, launch, truncation, error paths
branchCommand.test.ts ✅ All passed Including "no longer aliases /fork" assertion
BuiltinCommandLoader.test.ts ✅ All passed Including "/fork always registered" assertion
BackgroundTasksDialog.test.tsx ✅ All passed Including ANSI/control-sequence sanitization guard
PR-scoped total 62/62 passed 4 test files, 0 failures
CLI full suite ✅ EXIT_CODE=0 Pre-existing failures only (see below)

Note: initial run had 3 failures in PR-scoped tests due to stale .js compiled files resolving ahead of .ts sources (local env artifact). After removing stale .js, all 62 tests pass.

Pre-existing Failures (NOT introduced by this PR)

Test File Failures Cause
acpAgent.test.ts 20 ACP session/MCP tests (stale .js + recent main changes)
AuthDialog.test.tsx 1 Token Plan API key input timeout

None of these files are modified by this PR.

Review Issues — All Addressed

# Issue Fix
1 [Critical] Missing feature-gate check isForkSubagentEnabled() guard added (commit 8298434)
2 getHistory(true) deep clone perf Changed to getHistoryShallow() (commit 866706c)
3 Missing test for !config.getModel() guard Test added: "returns error when no model is configured"
4 Missing test for getHistory() catch block Test added: "refuses to fork when reading history fails"
5 ANSI detail-view test coverage Test covers both list-view and detail-view (enters detail at line 398)
6 Missing argumentHint Added argumentHint: '<directive>' (commit 1e8f661)
7 Record fork launches in history addHistory() call added (commit 945ca62)
8 Translate fork command messages All 11 strings in zh.js + zh-TW.js (commits cd9582b + 43aba99)

Code Review Summary

  • forkCommand.ts: Clean implementation with 6 guards (empty directive, no config, mid-flight, no model, no history, feature gate disabled), routes through Agent tool's background path, fire-and-forget launch, records in conversation history, proper error surfaces for both thrown and non-thrown failures.
  • branchCommand.ts: altNames: ['fork'] removed — /fork and /branch are now separate commands.
  • BackgroundTasksDialog.tsx: escapeAnsiCtrlCodes() applied to 4 render sites (list label, detail title, activity label, prompt lines). Test covers terminal injection prevention in both views.
  • BuiltinCommandLoader.ts: /fork registered unconditionally as built-in (gate check is inside the command action).

Verdict

✅ PASS — All 62 PR-scoped tests pass, no regressions, all 8 review issues addressed, clean architecture separating /fork (background agent) from /branch (session copy).


Verified by wenshao

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage gap: hasFailedDisplayStatus fallback branch (forkCommand.ts:160-161) — when returnDisplay.status === 'failed' but llmContent is empty/non-string — is untested. The existing test ('surfaces an error when the launch fails without throwing') only covers the case where llmContent is a non-empty string. Consider adding a test with mockExecute.mockResolvedValue({ returnDisplay: { status: 'failed' } }) (no llmContent) and asserting the error content contains 'the background agent could not be started.'. (Posted as body because the line is not in the PR diff.)

Comment thread packages/cli/src/i18n/locales/zh.js Outdated
'Resume a previous session': '恢复先前会话',
'Fork the current conversation into a new session': '将当前对话分支到新会话',
'Spawn a background agent that inherits the full conversation':
'启动继承完整对话的后台代理',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Translation inconsistency: "agent" is translated as "代理" in the new fork strings, but the established codebase convention for AI agents is "智能体" (40+ entries: 'Built-in Agents': '内置智能体', 'subagent': '子智能体', 'No agent selected': '未选择智能体', etc.). "代理" is used elsewhere only for network proxy ('Proxy': '代理').

Additionally, "fork" is left untranslated in several strings (e.g., "无法 fork", "启动 fork 失败", "已 fork 到后台代理"), while the existing entry 'Fork the current conversation into a new session' translates it as "分支" ('将当前对话分支到新会话').

Suggested fixes for zh.js:

  • '启动继承完整对话的后台代理''启动继承完整对话的后台智能体'
  • '后台代理无法启动。''后台智能体无法启动。'
  • '已 fork 到后台代理。...''已 fork 到后台智能体。...'
  • Consider translating "fork" consistently as "分支" (matching the existing convention) or leaving it as-is throughout (and updating the existing description to match).

Apply analogous changes in zh-TW.js using "智能體".

— qwen3.7-max via Qwen Code /review

parts: [
{
text: t('User launched a background fork via /fork: {{directive}}', {
directive,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] addHistory() is called outside the try/catch that wraps execute(). If addHistory() throws (e.g., Gemini client disconnected), the user gets an unhandled rejection even though the fork is already running in the background. The fork continues working, but the user sees a red error wall and may retry — creating a duplicate fork.

Suggested change
directive,
try {
config.getGeminiClient().addHistory({
role: 'user',
parts: [
{
text: t('User launched a background fork via /fork: {{directive}}', {
directive,
}),
},
],
});
} catch (error) {
debugLogger.debug('Failed to record fork event in history:', error);
}

— qwen3.7-max via Qwen Code /review

import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';

const debugLogger = createDebugLogger('FORK_COMMAND');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] debugLogger('FORK_COMMAND') is declared here but called exactly once in the entire file (line 94, in the getHistoryShallow catch). There are no debug logs at fork launch, success, failure, or guard returns. Compare with exportCommand (~20+ debug calls) or arenaCommand (5+ calls).

If /fork fails silently, there are no debug logs to trace what went wrong. Consider adding debugLogger.debug at key points: directive received (truncated), guard hit, launch result, returnDisplay shape, history recorded.

— qwen3.7-max via Qwen Code /review

'Resume a previous session': 'Resume a previous session',
'Fork the current conversation into a new session':
'Fork the current conversation into a new session',
'Spawn a background agent that inherits the full conversation':

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The 10 new fork-specific i18n keys added here (and to zh.js/zh-TW.js) are not in the MUST_TRANSLATE_KEYS array in packages/cli/src/i18n/mustTranslateKeys.ts. Without that, no automated test will fail if a future change accidentally removes a fork translation from a non-English locale — users would silently fall back to the English key string.

Consider adding all 10 new fork keys to MUST_TRANSLATE_KEYS.

— qwen3.7-max via Qwen Code /review

parts: [
{
text: t('User launched a background fork via /fork: {{directive}}', {
directive,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] addHistory() is called outside the try/catch that wraps execute(). If addHistory() throws (e.g., Gemini client disconnected), the user gets an unhandled rejection even though the fork is already running in the background. The fork continues working, but the user sees a red error wall and may retry — creating a duplicate fork.

Suggested change
directive,
try {
config.getGeminiClient().addHistory({
role: 'user',
parts: [
{
text: t('User launched a background fork via /fork: {{directive}}', {
directive,
}),
},
],
});
} catch (error) {
debugLogger.debug('Failed to record fork event in history:', error);
}

— qwen3.7-max via Qwen Code /review

import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';

const debugLogger = createDebugLogger('FORK_COMMAND');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] debugLogger('FORK_COMMAND') is declared here but called exactly once in the entire file (line 94, in the getHistoryShallow catch). There are no debug logs at fork launch, success, failure, or guard returns. Compare with exportCommand (~20+ debug calls) or arenaCommand (5+ calls).

If /fork fails silently, there are no debug logs to trace what went wrong. Consider adding debugLogger.debug at key points: directive received (truncated), guard hit, launch result, returnDisplay shape, history recorded.

— qwen3.7-max via Qwen Code /review

'Resume a previous session': 'Resume a previous session',
'Fork the current conversation into a new session':
'Fork the current conversation into a new session',
'Spawn a background agent that inherits the full conversation':

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The 10 new fork-specific i18n keys added here (and to zh.js/zh-TW.js) are not in the MUST_TRANSLATE_KEYS array in packages/cli/src/i18n/mustTranslateKeys.ts. Without that, no automated test will fail if a future change accidentally removes a fork translation from a non-English locale — users would silently fall back to the English key string.

Consider adding all 10 new fork keys to MUST_TRANSLATE_KEYS.

— qwen3.7-max via Qwen Code /review

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

R7 incremental review — 2 new commits since R6 (5f0cf139eb3ff829):

  • fix(cli): tolerate fork history recording failures — wraps addHistory() in try/catch with debugLogger fallback. New test verifies success despite recording failure. ✅ Addresses R6 suggestion.
  • test(cli): require fork command translations — adds 9 fork-specific keys to MUST_TRANSLATE_KEYS, a dedicated test, and translations for 6 locales (ca, de, fr, ja, pt, ru). ✅ Addresses R6 suggestion.

No new findings in this round. Deterministic analysis clean (tsc 0 errors, eslint 0 errors). All 84 related tests pass. The remaining R6 suggestion (debugLogger usage breadth) is partially addressed — now called in 2 error-path locations instead of 1.

Downgraded from Approve to Comment: CI still running. — qwen3.7-max via Qwen Code /review

@tanzhenxin tanzhenxin added the type/feature-request New feature or enhancement request label Jun 8, 2026
tanzhenxin
tanzhenxin previously approved these changes Jun 8, 2026

@tanzhenxin tanzhenxin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

The fork lifecycle and guard handling are correct and well-tested, and routing through the existing background-agent machinery instead of reimplementing session-copy is the right design call.

A few low-severity polish items remain, none blocking — good as follow-ups: the inherited-conversation promise drops the trailing user turn in the unusual case where history ends on a user message; the failed-launch fallback message ("the background agent could not be started.") is still unlocalized in ca/de/fr/ja/pt/ru and isn't on the required-translations list; and the background-task label truncates by code unit, so a multi-unit character on the cutoff can render as a broken glyph.

Verdict

APPROVE — solid, well-tested feature with the right architecture; remaining items are low-severity polish that can land as follow-ups.

@tanzhenxin tanzhenxin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Solid feature — the fork lifecycle and guards are correct and well-tested, and routing through the existing background-agent machinery instead of reimplementing session-copy is the right design. Approving.

A few low-severity, non-blocking items worth a follow-up:

  • Trailing user turn dropped on inherit — when the parent history ends on a user message (e.g. after a ! shell command, or an interrupted turn), that last turn is dropped even though the success message says the conversation was inherited.
  • Failed-launch fallback unlocalized — the "the background agent could not be started." message still renders in English for ca/de/fr/ja/pt/ru and isn't on the required-translations list, so the guard meant to catch this doesn't flag it.
  • Label truncates mid-character — the background-task label is cut by code unit rather than character, so a multi-unit character (e.g. an emoji) landing on the cutoff renders as a broken glyph.

Verdict

APPROVE — none of the above block; they can land as follow-ups.

@tanzhenxin tanzhenxin dismissed their stale review June 8, 2026 07:01

Duplicate of the review immediately following — dismissing to avoid a double approval.

@wenshao wenshao merged commit 90b4a4e into QwenLM:main Jun 8, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/feature-request New feature or enhancement request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cli): make /fork spawn a background fork agent (separate from /branch session copy)

3 participants