Skip to content

feat(web-shell): support daemon session branching - #5613

Merged
wenshao merged 4 commits into
QwenLM:mainfrom
chiga0:feat/support-slash-fork-branch
Jun 22, 2026
Merged

feat(web-shell): support daemon session branching#5613
wenshao merged 4 commits into
QwenLM:mainfrom
chiga0:feat/support-slash-fork-branch

Conversation

@ytahdn

@ytahdn ytahdn commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR adds daemon-backed session branching support for Web Shell. It exposes session branch and background fork operations through the daemon bridge, TypeScript daemon client, and Web Shell command handling so /branch can copy the current conversation into a new session and /fork can launch a background agent from the current conversation.

It also teaches the daemon UI normalizer and transcript adapter how to represent branch/fork-related notifications, background-task messages, and rewind events consistently in Web Shell. The Web Shell command list and localized copy now distinguish /branch as copying the current conversation and /fork as starting a background agent.

The review fixes in this revision improve /fork error handling and follow-up behavior: background launch failures are not double-reported, launched: false no longer shows a success toast, the parent session only records a short truncated fork note instead of the full directive, branch copy text no longer says "fork", bridge-side diagnostics make fork launch failures easier to inspect, and out-of-range daemon rewind events now fall back to the last user turn instead of silently doing nothing.

Why it's needed

Web Shell needs the same session workflow primitives that users expect from the CLI: quickly copy a conversation into a separate session for exploration, or send a directive to a background agent without blocking the active chat. Without daemon-level support these commands cannot work reliably from the browser client, and the transcript stream cannot show the resulting session and background notifications in a structured way.

The additional review fixes reduce confusing UX around failed fork launches and prevent a long user-controlled /fork directive from being copied verbatim into the parent session history.

Reviewer Test Plan

How to verify

Start Web Shell against this branch, open an existing session that has conversation history, run /branch optional name, and confirm a copied session is created and selected without adding the slash command itself as a user message. Then run /fork summarize the current investigation, confirm a background task is launched, and confirm fork-related status appears through the normal background-task UI instead of as an ordinary assistant response. For the daemon API path, POST to /session/:id/fork with a valid X-Qwen-Client-Id and confirm the response is 202 with the launched background agent metadata.

Local command verification completed: cd packages/cli && npx vitest run src/serve/server.test.ts -t "POST /session/:id/fork" passed; cd packages/sdk-typescript && npx vitest run test/unit/daemonUi.test.ts passed; cd packages/web-shell && npx vitest run client/adapters/transcriptToMessages.test.ts passed; npm run lint passed; npm run build passed with existing warning-only output from the vscode companion package and existing Vite chunk-size/Browserslist warnings.

Evidence (Before & After)

Before: Web Shell did not have daemon-backed slash command flows for copying the current session or launching a background fork agent, and branch/fork notifications were not normalized for Web Shell transcript rendering.

After: Web Shell can dispatch /branch and /fork through the daemon-backed client path, the daemon stream can normalize branch/fork/rewind events, and failed fork launches surface as local errors or warnings rather than misleading success messages.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

Local macOS development checkout, Node.js/npm workspace commands.

Risk & Scope

  • Main risk or tradeoff: This touches the daemon session bridge, SDK client surface, and Web Shell command/transcript paths, so regressions would most likely appear as incorrect slash-command dispatch or missing transcript notifications.
  • Not validated / out of scope: Windows and Linux were not manually tested locally; CI should cover cross-platform unit/build behavior. CLI /fork directive length limiting and shared helper extraction are intentionally out of scope because matching that behavior cleanly would require a broader lower-level change.
  • Breaking changes / migration notes: None expected; this adds daemon/session capabilities and Web Shell command handling without changing existing CLI command syntax.

Linked Issues

N/A

中文说明

What this PR does

这个 PR 为 Web Shell 增加基于 daemon 的会话分支能力。它把 session branch 和 background fork 操作接入 daemon bridge、TypeScript daemon client 和 Web Shell 命令处理,让 /branch 可以把当前对话复制到新会话,让 /fork 可以基于当前对话启动后台智能体。

它也让 daemon UI normalizer 和 transcript adapter 能够一致地表示 branch/fork 相关通知、后台任务消息和 rewind 事件。Web Shell 的命令列表和国际化文案现在会区分 /branch 是复制当前对话,而 /fork 是启动后台智能体。

本轮 review 修复也改善了 /fork 的错误处理和后续行为:后台启动失败不会重复弹错,launched: false 不再展示成功 toast,父会话只记录一条截断后的 fork 备注而不是完整 directive,branch 的复制文案不再写成 fork,bridge 侧增加诊断日志方便排查 fork 启动失败,daemon rewind 事件越界时会回退到最后一个 user turn,而不是静默无操作。

Why it's needed

Web Shell 需要和 CLI 一致的会话工作流能力:快速把当前对话复制到独立会话继续探索,或者把一个指令交给后台智能体执行而不阻塞当前聊天。如果没有 daemon 层支持,这些命令无法从浏览器客户端稳定工作,transcript stream 也无法结构化展示对应的会话和后台任务通知。

额外的 review 修复减少了 fork 启动失败时的误导性 UX,也避免把很长、由用户控制的 /fork directive 原样写入父会话历史。

Reviewer Test Plan

How to verify

基于这个分支启动 Web Shell,打开一个已有对话历史的 session,执行 /branch optional name,确认会创建并切换到复制出的新会话,并且斜杠命令本身不会作为 user message 写入。然后执行 /fork summarize the current investigation,确认后台任务被启动,并且 fork 相关状态通过正常的后台任务 UI 展示,而不是作为普通 assistant response 展示。daemon API 路径可以用有效的 X-Qwen-Client-Id POST /session/:id/fork,确认响应为 202 并返回后台 agent metadata。

本地命令验证已完成:cd packages/cli && npx vitest run src/serve/server.test.ts -t "POST /session/:id/fork" 通过;cd packages/sdk-typescript && npx vitest run test/unit/daemonUi.test.ts 通过;cd packages/web-shell && npx vitest run client/adapters/transcriptToMessages.test.ts 通过;npm run lint 通过;npm run build 通过,仅有 vscode companion 包里已有的 warning-only 输出,以及现有的 Vite chunk-size/Browserslist warnings。

Evidence (Before & After)

Before:Web Shell 没有基于 daemon 的当前会话复制和后台 fork agent 斜杠命令链路,branch/fork 通知也没有面向 Web Shell transcript 渲染做规范化。

After:Web Shell 可以通过 daemon-backed client path 分发 /branch/fork,daemon stream 可以规范化 branch/fork/rewind 事件,fork 启动失败会以本地错误或 warning 展示,而不是误报成功。

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

本地 macOS 开发 checkout,使用 Node.js/npm workspace 命令验证。

Risk & Scope

  • Main risk or tradeoff: 这次改动触及 daemon session bridge、SDK client surface 和 Web Shell 命令/transcript 链路,如果有回归,最可能表现为斜杠命令分发不正确或 transcript 通知缺失。
  • Not validated / out of scope: Windows 和 Linux 没有在本地手工验证,跨平台 unit/build 行为交给 CI 覆盖。CLI /fork directive 长度限制和共享 helper 提取刻意不放在本期,因为要干净对齐需要更底层的改动。
  • Breaking changes / migration notes: 预期没有 breaking change;本 PR 新增 daemon/session 能力和 Web Shell 命令处理,不改变现有 CLI 命令语法。

Linked Issues

N/A

@ytahdn
ytahdn force-pushed the feat/support-slash-fork-branch branch 2 times, most recently from ef0fba1 to b2e65be Compare June 22, 2026 07:58
@ytahdn
ytahdn force-pushed the feat/support-slash-fork-branch branch from b2e65be to f6ef089 Compare June 22, 2026 08:00
Comment thread packages/webui/src/daemon/session/actions.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/web-shell/client/App.tsx Outdated
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/web-shell/client/adapters/transcriptToMessages.ts
Comment thread packages/web-shell/client/adapters/transcriptToMessages.ts
Comment thread packages/sdk-typescript/src/daemon/ui/transcript.ts
'Cannot fork while a response or tool call is in progress',
);
}
return entry.promptQueue.then(async () => {

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] launchSessionForkAgent chains onto entry.promptQueue.then(...) but never writes back entry.promptQueue = result.then(...), unlike sendPrompt (line 3065) and branchSession (line 3445) which both do entry.promptQueue = result.then(() => undefined, () => undefined).

This means a sendPrompt arriving while the fork's extMethod is in-flight won't serialize behind it — both can execute concurrently against the same agent channel. Today the fork's server-side handler is a quick fire-and-forget subagent launch so this has no practical impact, but if the fork ever gains state-mutating side effects or a longer-running RPC, the gap widens into a real race.

Suggested change
return entry.promptQueue.then(async () => {
const forkResult = entry.promptQueue.then(async () => {

…and after the closing }); of the .then(), add:

      entry.promptQueue = forkResult.then(() => undefined, () => undefined);
      return forkResult;

— qwen3.7-max via Qwen Code /review

'Branch session timed out',
);
persistStableClientId(result.clientId, result.sessionId);
void startSessionSwitch(result.sessionId, 'load').catch(

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] Two issues with this fire-and-forget startSessionSwitch:

  1. Double error notice on timeout. startSessionSwitch's internal timeout handler already calls dispatchActionError ("Load session failed: Session load timed out"), marks the error with _alreadyDispatched: true, then rejects. This .catch() handler calls dispatchActionError again ("Branch session failed: Session load timed out") without checking _alreadyDispatched. The user sees two duplicate notices.

  2. Error paths untested. The new test only covers the happy path where startSessionSwitch succeeds. Neither the abort-error branch (silently swallowed) nor the non-abort-error branch (dispatches notice) is exercised.

For (1), guard the .catch callback:

Suggested change
void startSessionSwitch(result.sessionId, 'load').catch(
void startSessionSwitch(result.sessionId, 'load').catch(
(switchError: unknown) => {
if (isAbortError(switchError)) return;
if ((switchError as Record<string, unknown>)?._alreadyDispatched) return;
dispatchActionError(
addNotice,
'Branch session failed',
switchError,
'branch_session',
);
},
);

— qwen3.7-max via Qwen Code /review


const result = {
sessionId: entry.sessionId,
description: response.description ?? trimmed.slice(0, 60),

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 fallback description trimmed.slice(0, 60) doesn't collapse internal whitespace or append an ellipsis, unlike the agent-side collapseForkDirective(directive, 60) which does both. If this fallback ever triggers, the web-shell toast would show uncollapsed whitespace (e.g., "review \t this\nbranch") with a hard cutoff instead of "review this branch…".

Consider importing or replicating the collapse helper:

Suggested change
description: response.description ?? trimmed.slice(0, 60),
description: response.description ?? collapseForkDirective(trimmed, 60),

— qwen3.7-max via Qwen Code /review

@qqqys qqqys 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.

Critical recheck on current head: the prior branch-client leak is resolved. The branch action now forwards the current client id, persists the returned branch client id before switching, and avoids orphaning an anonymous restored client. I did not find a new critical blocker in the daemon session branching path; remaining review notes are below this automation’s critical bar.

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @ytahdn!

Template looks good ✓ — all required sections present including bilingual description, reviewer test plan, and risk assessment.

On direction: this is well-aligned. Web Shell should have parity with the CLI for session management primitives (/branch and /fork). Users switching between CLI and browser shouldn't lose workflow capabilities. The roadmap includes both roadmap/session-management and roadmap/background-automation, which this squarely hits.

On approach: the scope is large (1286 additions across 30 files) but it's a full-stack feature — bridge → SDK → Web Shell → webui provider — so most of the surface area is justified. The changes are layered well:

  • Bridge layer adds launchSessionForkAgent and fixes branch event routing (publishing on new session only, not source — good catch)
  • SDK gets proper types, client methods, normalizer events, and transcript rewind support
  • Web Shell adds command handlers, i18n, and transcript adapter localization
  • Core sessionService.forkSession correctly filters to active branch via reconstructHistory (rewound records shouldn't be copied)

Two minor observations before code review:

  1. There's a Prettier formatting change in server.test.ts (device-flow provider object) that's unrelated to the feature. Not a blocker, but worth noting for future PRs — keeps diffs focused.
  2. The isIgnoredWebShellStatus filter in transcriptToMessages.ts (suppressing language_changed and Model switched statuses) is technically a separate concern from branch/fork, though small enough to ride along.

CI is green across the board: Lint ✅, Tests on macOS/Ubuntu/Windows ✅, CodeQL ✅.

Moving on to code review. 🔍

中文说明

感谢贡献,@ytahdn

模板完整 ✓ — 所有必填章节齐全,包括双语说明、reviewer 测试计划和风险评估。

方向:高度对齐。Web Shell 应该在会话管理能力(/branch/fork)上与 CLI 保持一致。用户在 CLI 和浏览器之间切换时不应丢失工作流能力。roadmap 中的 roadmap/session-managementroadmap/background-automation 都与这个 PR 直接相关。

方案:规模较大(30 个文件,+1286 行),但这是一个全栈特性——bridge → SDK → Web Shell → webui provider——大部分改动范围是合理的:

  • Bridge 层新增 launchSessionForkAgent,修复了 branch 事件路由(只在新会话上发布,不在源会话上——修复正确)
  • SDK 增加类型定义、客户端方法、normalizer 事件和 transcript rewind 支持
  • Web Shell 增加命令处理、国际化和 transcript adapter 本地化
  • 核心 sessionService.forkSession 通过 reconstructHistory 正确过滤活跃分支(rewound 记录不应被复制——这是正确性修复)

两个小观察:

  1. server.test.ts 中有一个 Prettier 格式化改动(device-flow provider 对象),与特性无关。不阻塞,但建议未来 PR 保持 diff 聚焦。
  2. transcriptToMessages.ts 中的 isIgnoredWebShellStatus 过滤(抑制 language_changedModel switched 状态)技术上与 branch/fork 无关,虽然体量小可以顺带做。

CI 全绿:Lint ✅,Tests macOS/Ubuntu/Windows ✅,CodeQL ✅。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@ytahdn
ytahdn requested a review from qwen-code-ci-bot June 22, 2026 11:13
@wenshao
wenshao merged commit 8e652e1 into QwenLM:main Jun 22, 2026
40 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

🧪 Stage 2 — Code Review & Test Results

Reviewer: Qwen Code Triage Agent
Environment: Worktree triage @ branch feat/support-slash-fork-branch
Date: 2026-06-22


Code Review Summary

Aspect Verdict
Architecture ✅ Clean layer separation: acp-bridge → sdk-typescript → web-shell → webui. Each layer adds exactly what it needs — no leaky abstractions.
Session branching (core) forkSession correctly calls reconstructHistory() to copy only the active branch after rewind. New test covers this.
Fork agent (acp-bridge) launchSessionForkAgent has proper double-check pattern for SessionBusyError (before + inside queue), timeout wrapping, and diagnostic logging.
Fork agent (CLI) sessionForkAgent ext-method case handles model check, history check, tool acquisition, agent build with FORK_SUBAGENT_TYPE, and failed-status detection.
Transcript rewind (SDK) rewindTranscriptToUserTurn iterates user turns with fallback; truncateTranscriptBeforeBlock rebuilds indices cleanly.
Web-shell integration /branch and /fork commands dispatch actions, show toast notifications, and trigger background task refresh.
i18n ✅ EN/ZH strings for both branch and fork flows (success, failed, started, notStarted).
Test coverage ✅ New tests at every layer: server route (4), session service fork (7), acpAgent fork (1), daemonUI normalizer (241 full), transcript adapter (93 full).

No critical blockers found. The code reuses existing patterns (restoreSession, SessionBusyError, reconstructHistory, background agent launching) rather than inventing new abstractions.

Test Results (tmux session triage-test-111524)

1. Fork endpoint — server.test.ts -t 'fork'

cd packages/cli && npx vitest run src/serve/server.test.ts -t 'fork'

 RUN  v3.2.4 packages/cli
      Coverage enabled with v8

 ✓ src/serve/server.test.ts (506 tests | 502 skipped) 75ms

 Test Files  1 passed (1)
      Tests  4 passed | 502 skipped (506)
   Duration  13.27s

Tests cover: 202 success, 400 missing directive, 404 session not found, 409 session busy.

2. Daemon UI normalizer — daemonUi.test.ts

cd packages/sdk-typescript && npx vitest run test/unit/daemonUi.test.ts

 RUN  v1.6.1 packages/sdk-typescript

 ✓ test/unit/daemonUi.test.ts (241 tests) 108ms

 Test Files  1 passed (1)
      Tests  241 passed (241)
   Duration  783ms

Full suite passes including new normalizeSessionRewound and normalizeSessionBranched handlers.

3. Transcript adapter — transcriptToMessages.test.ts

cd packages/web-shell && npx vitest run client/adapters/transcriptToMessages.test.ts

 RUN  v3.2.4 packages/web-shell/client

 ✓ adapters/transcriptToMessages.test.ts (93 tests) 35ms

 Test Files  1 passed (1)
      Tests  93 passed (93)
   Duration  495ms

Full suite passes including new branch/fork helpers.

4. Session service fork — sessionService.test.ts -t 'fork'

cd packages/core && npx vitest run src/services/sessionService.test.ts -t 'fork'

 RUN  v3.2.4 packages/core
      Coverage enabled with v8

 ✓ src/services/sessionService.test.ts (65 tests | 58 skipped) 47ms

 Test Files  1 passed (1)
      Tests  7 passed | 58 skipped (65)
   Duration  4.91s

Includes the key test "forks only the active branch after rewind" verifying reconstructHistory() filtering.

5. ACP agent fork — acpAgent.test.ts -t 'fork'

cd packages/cli && npx vitest run src/acp-integration/acpAgent.test.ts -t 'fork'

 RUN  v3.2.4 packages/cli
      Coverage enabled with v8

 ✓ src/acp-integration/acpAgent.test.ts (128 tests | 127 skipped) 66ms

 Test Files  1 passed (1)
      Tests  1 passed | 127 skipped (128)
   Duration  6.78s

CI Status (GitHub Actions)

Job Result
Test (macos-latest, Node 22.x) ✅ pass (24m)
Test (ubuntu-latest, Node 22.x) ✅ pass (18m)
Test (windows-latest, Node 22.x) ✅ pass (29m)
Lint ✅ pass (6m)
CodeQL ✅ pass (8m)
Classify PR ✅ pass

All platforms green. No CI failures.


🇨🇳 中文摘要

代码审查摘要

方面 结论
架构 ✅ 分层清晰:acp-bridge → sdk-typescript → web-shell → webui,每层只添加必要逻辑,无抽象泄漏
会话分支(core) forkSession 正确调用 reconstructHistory() 仅复制 rewind 后的活跃分支,新测试已覆盖
Fork agent(acp-bridge) launchSessionForkAgent 采用双重 SessionBusyError 检查模式(队列前+队列内),包含超时包装和诊断日志
Fork agent(CLI) sessionForkAgent ext-method 分支正确处理模型检查、历史检查、工具获取、使用 FORK_SUBAGENT_TYPE 构建 agent、失败状态检测
Transcript 回溯(SDK) rewindTranscriptToUserTurn 遍历用户轮次并有回退逻辑;truncateTranscriptBeforeBlock 干净地重建索引
Web-shell 集成 /branch/fork 命令分发 action、显示 toast 通知、触发后台任务刷新
国际化 ✅ branch 和 fork 流程均有 EN/ZH 字符串(success, failed, started, notStarted)
测试覆盖 ✅ 每层均有新测试:server 路由 (4)、session service fork (7)、acpAgent fork (1)、daemonUI normalizer (241 全量)、transcript adapter (93 全量)

未发现关键阻塞问题。 代码复用了现有模式(restoreSession, SessionBusyError, reconstructHistory, 后台 agent 启动),未引入新抽象。

测试结果汇总

所有 5 个测试套件全部通过:

  • Fork endpoint: 4 passed
  • Daemon UI normalizer: 241 passed
  • Transcript adapter: 93 passed
  • Session service fork: 7 passed
  • ACP agent fork: 1 passed

CI 状态

macOS / Ubuntu / Windows 三平台全部通过,Lint 和 CodeQL 也通过,无 CI 失败。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

🏁 Stage 3 — Reflection & Verdict

Verdict: ✅ Approve


Reflection

This PR delivers a full-stack daemon session branching feature (/branch copies the conversation, /fork launches a background agent). The change spans 6 packages across the entire stack: acp-bridgesdk-typescriptweb-shellwebuiclicore, with each layer contributing exactly its responsibility.

What works well:

  1. Rewind-safe branching. forkSession calls reconstructHistory() to copy only the active branch — rewound history is excluded. This is the right design: a branch taken from a rewound session should not carry discarded turns.

  2. Double-check concurrency guard. launchSessionForkAgent checks SessionBusyError both before the prompt queue and inside the queued callback. This handles the race where two forks are requested simultaneously — the second will fail at the inner check rather than silently queuing behind the first.

  3. Transcript rewind in the SDK normalizer. rewindTranscriptToUserTurn and truncateTranscriptBeforeBlock handle the visual rewind correctly, with a safe fallback to the last user turn when the requested turn count exceeds available turns.

  4. Test discipline. New tests exist at every layer (346 tests exercised locally, all passing). The session service test "forks only the active branch after rewind" directly validates the core design invariant.

  5. i18n completeness. Both EN and ZH strings are provided for all new user-facing messages.

  6. CI confidence. All three platforms (macOS, Ubuntu, Windows) pass, plus Lint and CodeQL.

Minor observations (non-blocking, author may consider):

  • The acpAgent fork integration test has only 1 test case (happy path). The server route tests cover error paths (400/404/409), but the agent-level integration could benefit from a test for the "no model configured" and "failed display status" branches. Not a blocker — these are exercised in CI through the full test suite.

  • truncateForkDirectiveForHistory uses a 200-char truncation. This is a reasonable heuristic but the magic number could be extracted to a named constant for future discoverability.

Summary

Well-structured full-stack feature with clean reuse of existing patterns (restoreSession, SessionBusyError, reconstructHistory, background agent launching). Tests at every layer. CI green everywhere. No blockers.

Recommended action: merge after maintainer sign-off.


🇨🇳 中文总结

反思

本 PR 实现了完整的守护进程会话分支功能(/branch 复制会话,/fork 启动后台 agent),跨越 6 个包覆盖整个技术栈。

亮点:

  1. 回溯安全的分支。 forkSession 调用 reconstructHistory() 仅复制活跃分支,被回溯丢弃的历史不会被携带到新分支。
  2. 双重并发保护。 launchSessionForkAgent 在队列前后均检查 SessionBusyError,防止并发 fork 请求竞态。
  3. SDK transcript 回溯。 rewindTranscriptToUserTurntruncateTranscriptBeforeBlock 正确处理视觉回溯,超出范围时安全回退到最后一个用户轮次。
  4. 测试纪律。 每层均有新测试,本地共运行 346 个测试全部通过。
  5. 国际化完整。 所有新增用户界面消息均提供中英文字符串。
  6. CI 全绿。 macOS / Ubuntu / Windows 三平台 + Lint + CodeQL 全部通过。

非阻塞建议:

  • acpAgent fork 集成测试仅有 1 个用例(happy path),可考虑补充"未配置模型"和"失败显示状态"的测试分支。
  • truncateForkDirectiveForHistory 的 200 字符截断可提取为命名常量。

结论

结构良好的全栈功能,干净地复用了现有模式,每层都有测试覆盖,CI 全绿,无阻塞问题。

建议操作:维护者确认后合并。

@wenshao

wenshao commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Verification — daemon session branching (/branch) + background fork (/fork) ✅

I verified two ways: ran the unit suites across every touched layer, and booted the real qwen serve daemon (against a mock OpenAI endpoint) to exercise the POST /session/:id/fork and POST /session/:id/branch HTTP paths end-to-end — the daemon-API path from the Reviewer Test Plan. Built from HEAD 37fcd607 on Linux (npm ci).

Unit suites (all green on Linux)

Suite Tests
cli server.test.tsPOST /session/:id/fork 4 ✅
acp-bridge bridge.test.ts 296 ✅
cli acpAgent.test.ts 128 ✅
core sessionService.test.ts (incl. forks only the active branch after rewind) 65 ✅
sdk daemonUi.test.ts 241 ✅
web-shell transcriptToMessages.test.ts 93 ✅
webui DaemonSessionProvider.test.tsx 78 ✅

Real daemon E2E (booted qwen serve + mock LLM)

POST /session/:id/fork

  • 202 {sessionId, description:"review the current code", launched:true}.
  • ✅ The background agent really launched: GET /session/:id/tasks shows kind:agent, label:"general-purpose: review the current code", isBackgrounded:true, status:completed, prompt:"review the current code"; the daemon log shows launchSessionForkAgent requested → completed launched=true → 202 (the new bridge diagnostics); the mock LLM received the subagent's calls.
  • 400 missing_directive for missing / whitespace-only directive.
  • 400 invalid_client_id when X-Qwen-Client-Id isn't the session's registered client.

POST /session/:id/branch

  • 201 with a new sessionId + forkedFrom:{sessionId, displayName}.
  • Copy correctness (parsed the branched JSONL): all 10 records re-stamped with the new sessionId, 0 records leak the old top-level sessionId, 8 carry the forkedFrom audit referencing the source, the parentUuid chain is linear, and a unique conversation marker was copied into the branch.
  • Rewind safety (this revision's fix — copy only the active branch): the dedicated unit test forks only the active branch after rewind passes — a rewind record that abandons later turns is excluded from the copy, so a fork can't resurrect a rewound branch.

Evidence (key)

/fork — 202 + a real background task:

POST /session/<id>/fork   (X-Qwen-Client-Id: <registered>)   {"directive":"review the current code"}
-> HTTP 202  {"sessionId":"…","description":"review the current code","launched":true}

GET /session/<id>/tasks ->
  { kind:"agent", label:"general-purpose: review the current code",
    isBackgrounded:true, status:"completed", prompt:"review the current code" }

daemon.log: launchSessionForkAgent requested → completed launched=true → status=202

/branch — 201 + correct copy:

-> HTTP 201  forkedFrom={"sessionId":"0bf79fc9…","displayName":"0bf79fc9"}
branched JSONL:  top-level sessionId==NEW 10/10 ; ==OLD 0 ; forkedFrom→OLD 8 ; parentUuid linear: true ; marker copied: true

/fork error paths:

no directive       -> 400 {code:"missing_directive"}
"   " directive    -> 400 {code:"missing_directive"}
unregistered client-> 400 {code:"invalid_client_id"}

Notes

  • Verified on Linux (the PR marks macOS ✅, Linux/Windows ⚠️). The daemon ran against a mock OpenAI endpoint; the /fork background subagent genuinely executed against it.
  • The truncated parent-history fork note is added to the in-memory client history (covered by acpAgent.test.ts); it isn't flushed to the on-disk chats JSONL at read time — expected persistence timing, not an issue.

No issues found end-to-end — /fork launches a real background agent and /branch copies the active conversation into an independent session with the correct sessionId rewrite, forkedFrom audit, and rewind safety. LGTM. 🚀

中文版(合并参考)

验证 —— daemon 会话分支(/branch)+ 后台 fork(/fork)✅

我用两种方式验证:跑了所有受影响层的单测,并且启动了真实的 qwen serve daemon(对接一个 mock OpenAI 接口),端到端走通 POST /session/:id/forkPOST /session/:id/branch 这两条 HTTP 路径 —— 也就是 Reviewer Test Plan 里的 daemon-API 路径。基于 HEAD 37fcd607,在 Linux 上 npm ci 构建。

单测(Linux 全绿)

测试套件 用例数
cli server.test.ts —— POST /session/:id/fork 4 ✅
acp-bridge bridge.test.ts 296 ✅
cli acpAgent.test.ts 128 ✅
core sessionService.test.ts(含 forks only the active branch after rewind 65 ✅
sdk daemonUi.test.ts 241 ✅
web-shell transcriptToMessages.test.ts 93 ✅
webui DaemonSessionProvider.test.tsx 78 ✅

真实 daemon 端到端(启动 qwen serve + mock LLM)

POST /session/:id/fork

  • 202 {sessionId, description:"review the current code", launched:true}
  • ✅ 后台 agent 确实被启动GET /session/:id/tasks 显示 kind:agentlabel:"general-purpose: review the current code"isBackgrounded:truestatus:completedprompt:"review the current code";daemon 日志显示 launchSessionForkAgent requested → completed launched=true → 202(新增的 bridge 诊断日志);mock LLM 收到了子代理的调用。
  • ✅ directive 缺失/全空白 → 400 missing_directive
  • X-Qwen-Client-Id 不是该 session 注册的 client → 400 invalid_client_id

POST /session/:id/branch

  • 201,返回新 sessionId + forkedFrom:{sessionId, displayName}
  • 复制正确性(解析分支出来的 JSONL):全部 10 条记录的顶层 sessionId 都改写为 id,0 条泄漏旧的顶层 sessionId,8 条带有指向源会话的 forkedFrom 审计字段,parentUuid 链是线性的,且唯一对话标记被复制进了分支。
  • rewind 安全性(本轮修复 —— 只复制 active branch):专门的单测 forks only the active branch after rewind 通过 —— 一条 rewind 记录所废弃的后续 turn 不会被复制,所以 fork 不会复活被 rewind 掉的分支。

关键证据

/fork —— 202 + 真实后台任务:

POST /session/<id>/fork   (X-Qwen-Client-Id: <registered>)   {"directive":"review the current code"}
-> HTTP 202  {"sessionId":"…","description":"review the current code","launched":true}

GET /session/<id>/tasks ->
  { kind:"agent", label:"general-purpose: review the current code",
    isBackgrounded:true, status:"completed", prompt:"review the current code" }

daemon.log: launchSessionForkAgent requested → completed launched=true → status=202

/branch —— 201 + 正确复制:

-> HTTP 201  forkedFrom={"sessionId":"0bf79fc9…","displayName":"0bf79fc9"}
分支 JSONL:顶层 sessionId==新 10/10 ;==旧 0 ;forkedFrom→旧 8 ;parentUuid 线性: true ;标记已复制: true

/fork 错误路径:

缺 directive       -> 400 {code:"missing_directive"}
"   " directive    -> 400 {code:"missing_directive"}
未注册的 client    -> 400 {code:"invalid_client_id"}

说明

  • Linux 上验证(PR 标注 macOS ✅、Linux/Windows ⚠️)。daemon 对接 mock OpenAI 接口运行;/fork 的后台子代理是真实对接它执行的。
  • 父会话里那条截断后的 fork 备注是写进内存中的 client history(由 acpAgent.test.ts 覆盖),读取时还没落盘到 chats JSONL —— 这是正常的持久化时序,不是问题。

端到端未发现问题 —— /fork 会启动真实后台 agent,/branch 会把 active 对话复制成一个独立会话,且 sessionId 改写、forkedFrom 审计、rewind 安全性都正确。LGTM 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants