Skip to content

feat(web-shell): Add standalone chats - #10514

Merged
doudouOUC merged 13 commits into
mainfrom
docs/standalone-pr6-webshell-ui-plan
Sep 1, 2026
Merged

feat(web-shell): Add standalone chats#10514
doudouOUC merged 13 commits into
mainfrom
docs/standalone-pr6-webshell-ui-plan

Conversation

@doudouOUC

@doudouOUC doudouOUC commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Implements the approved PR6 plan and makes standalone chats a first-class WebShell product context. Global New task creates an exact standalone session when standalone_sessions_v1 is available, while project, Goals, and Git entry points remain workspace-scoped and older daemons retain the legacy primary-workspace route only when the capability is absent. Explicit standalone and Live navigation never falls back to the primary workspace.

Adds top-level standalone Recents with active and archived pagination plus rename, export, archive, unarchive, and delete actions. Deep links carry an explicit context, validate the exact standalone session before mounting the provider, unarchive archived sessions before attach, and preserve standalone recovery UI for missing, compromised, recreated-directory, cleanup-pending, and outcome-unknown states. Standalone creation is not automatically retried or hidden behind the generic action timeout.

Separates workspace, standalone, and Live UI behavior. Non-workspace chats skip project-only navigation, workspace settings, Git state, split view, workspace invalidation, file references, uploads, and Web Terminal surfaces. Attached standalone sessions still expose their daemon-supported slash commands, skills, Shell, and ordinary tools; only workspace-dependent surfaces are removed. Composer drafts and history are isolated by product context, stale transitions cannot publish data into the newly selected context, and the public React entry point can receive and report sessionContext for host routing.

Why it's needed

The earlier standalone sequence delivered daemon routes, capability advertisement, SDK methods, and explicit React provider contexts, but users still had no WebShell path to create, find, reopen, or manage a workspace-independent chat. This change completes the user-facing slice while preserving old-daemon compatibility and the existing project workflow. It also keeps failure semantics explicit: capability loading fails closed, uncertain creation remains recoverable by exact session ID, directory recovery is typed, and standalone routes never leak an internal cwd into product workspace state.

Reviewer Test Plan

How to verify

  1. With standalone_sessions_v1, use global New task and confirm one exact standalone session is created, the URL carries context=standalone, no workspace parameter is added, and the session appears under top-level Recents.
  2. Without the capability, confirm global New task follows the existing primary-workspace behavior; with capability loading or capability lookup failure, confirm creation waits or fails closed rather than falling back.
  3. Open, archive, unarchive, rename, export, and delete standalone rows, including archived pagination, and confirm every action targets the exact standalone route while child sessions are excluded.
  4. Open standalone and Live chats and confirm project-only navigation, Git/settings controls, split view, attachments, file references, uploads, and Web Terminal are unavailable. In an attached standalone session, confirm session-supported commands, skills, Shell, and ordinary tools still work.
  5. Exercise exact standalone deep links for active, archived, creating, missing, compromised, recreated-directory, cleanup-pending, and outcome-unknown sessions. Confirm archived sessions unarchive before attach, uncertain creation is never retried automatically, and navigation during an in-flight transition cannot publish stale transcript or recovery state.
  6. Verify project New task, Goals, Git, and workspace session links remain exact-workspace flows, while the public callback and root URL preserve explicit standalone and Live context.

Evidence (Before & After)

Before After
Global New task Started the legacy primary-workspace flow. Creates an exact standalone session when capability-gated, with legacy behavior only when the capability is absent.
Session discovery No product-level standalone catalog or lifecycle UI. Top-level active and archived Recents expose exact standalone lifecycle actions.
Routing and recovery Standalone IDs had no explicit WebShell routing or typed recovery surface. Context-explicit deep links validate exact state and render bounded recovery without workspace fallback.
Product isolation Workspace-only controls could remain reachable from non-workspace contexts. Standalone and Live hide workspace-only surfaces while retaining session-supported commands and tools.

Automated evidence: the full WebShell unit coverage run passed 5,290 tests; the full Playwright suite passed 66 tests with 1 intentional skip; after rebasing onto current main, 941 focused unit tests and all 6 standalone/split E2E tests passed again.

Tested on

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

Environment (optional)

macOS with Node.js 22.22.3, Vitest, Playwright Chromium, and the deterministic mock daemon. Repository npm run build and npm run typecheck passed after the final rebase; WebShell lint passed with zero warnings, and every changed file passed Prettier. The package-wide format check still reports nine unchanged pre-existing files, so this PR does not rewrite unrelated formatting.

Risk & Scope

  • Main risk or tradeoff: product-context orchestration touches the central WebShell application and session provider, so regressions could affect navigation or stale async transitions; focused ownership, failure-path, public-surface, browser-bundle, unit, and E2E coverage guard those boundaries.
  • Not validated / out of scope: no real-daemon manual UI recording, Windows or Linux local run, Global Recents beyond standalone sessions, lifecycle menus outside standalone Recents, standalone uploads, durable scheduling changes, daemon lifecycle changes, or the separate WebShell cutover tracked by refactor(vscode-ide-companion): migrate chat to WebShell and qwen serve #9811.
  • Breaking changes / migration notes: none. The public context input is optional, older daemons keep their legacy route when the standalone capability is absent, and explicit non-workspace contexts deliberately fail closed rather than falling back to a workspace.

Linked Issues

Refs #8908

中文说明

本 PR 内容

按照已通过评审的 PR6 方案完成实现,使 standalone 聊天成为 WebShell 中的一等产品上下文。当 daemon 提供 standalone_sessions_v1 capability 时,全局 New task 会创建一个精确的 standalone 会话;项目、Goals 和 Git 入口仍严格绑定 workspace;只有 capability 确实缺失时,旧 daemon 才继续走原有的主 workspace 路由。显式 standalone 和 Live 导航绝不会回退到主 workspace。

新增顶层 standalone Recents,支持 active/archived 分页,以及重命名、导出、归档、取消归档和删除。深链携带显式上下文,在挂载 provider 前校验精确 standalone 会话,对已归档会话先取消归档再 attach,并为会话缺失、目录受损、目录已重建、清理待完成和创建结果未知等状态保留 standalone 恢复界面。standalone 创建不会自动重试,也不会被通用 action timeout 掩盖。

明确区分 workspace、standalone 和 Live 的 UI 行为。非 workspace 聊天会跳过项目专属导航、workspace 设置、Git 状态、分屏、workspace 失效刷新、文件引用、上传和 Web Terminal。已 attach 的 standalone 会话仍会暴露 daemon 支持的 slash commands、skills、Shell 和普通工具;移除的只是依赖 workspace 的界面。输入草稿与历史按产品上下文隔离,过期异步切换不能把数据发布到新选择的上下文,公共 React 入口也可接收并回报 sessionContext,供宿主完成路由。

背景与动机

此前的 standalone 序列已经交付 daemon 路由、capability 广告、SDK 方法和显式 React provider 上下文,但用户仍无法在 WebShell 中创建、发现、重新打开或管理一个不依赖 workspace 的聊天。本改动完成面向用户的最后一段,同时保留旧 daemon 兼容性和现有项目工作流。失败语义也保持显式:capability 加载失败时 fail closed,创建结果不确定时可通过精确 session ID 恢复,目录恢复使用类型化状态,standalone 路由绝不会把内部 cwd 泄露成产品 workspace 状态。

评审验证方式

如何验证

  1. 在具备 standalone_sessions_v1 的 daemon 上使用全局 New task,确认只创建一个精确 standalone 会话,URL 包含 context=standalone,不附带 workspace 参数,并且会话出现在顶层 Recents 中。
  2. 在 capability 缺失时,确认全局 New task 保持原有主 workspace 行为;在 capability 正在加载或查询失败时,确认创建会等待或 fail closed,而不是回退。
  3. 对 standalone 列表执行打开、归档、取消归档、重命名、导出和删除,并覆盖 archived 分页;确认每个操作都命中精确 standalone 路由,且子会话不会进入目录。
  4. 打开 standalone 和 Live 聊天,确认项目专属导航、Git/设置控件、分屏、附件、文件引用、上传和 Web Terminal 均不可用;在已 attach 的 standalone 会话中,确认会话支持的命令、skills、Shell 和普通工具仍可运行。
  5. 分别打开 active、archived、creating、missing、compromised、目录已重建、清理待完成和创建结果未知状态的精确 standalone 深链;确认 archived 会话先取消归档再 attach,创建不确定时不会自动重试,并且在异步切换期间导航不会发布过期 transcript 或恢复状态。
  6. 验证项目 New task、Goals、Git 和 workspace 会话链接仍走精确 workspace 流程,同时公共回调与根 URL 会保留显式 standalone 和 Live 上下文。

证据(改动前后)

改动前 改动后
全局 New task 启动旧的主 workspace 流程。 capability 满足时创建精确 standalone 会话;仅 capability 缺失时保留旧行为。
会话发现 没有产品级 standalone 目录或生命周期 UI。 顶层 active/archived Recents 提供精确 standalone 生命周期操作。
路由与恢复 standalone ID 没有显式 WebShell 路由或类型化恢复界面。 携带上下文的深链校验精确状态,并在不回退 workspace 的前提下提供有界恢复。
产品隔离 非 workspace 上下文仍可能触达 workspace 专属控件。 standalone 和 Live 隐藏 workspace 专属界面,同时保留会话支持的命令与工具。

自动化证据:完整 WebShell 单元覆盖运行通过 5,290 项测试;完整 Playwright 套件通过 66 项,另有 1 项预期跳过;rebase 到当前 main 后再次通过 941 项关键单元测试以及全部 6 项 standalone/split E2E 测试。

测试平台

操作系统 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

macOS、Node.js 22.22.3、Vitest、Playwright Chromium,以及确定性的 mock daemon。最终 rebase 后,仓库级 npm run buildnpm run typecheck 均通过;WebShell lint 零告警,所有本次改动文件均通过 Prettier。package 全量 format check 仍报告 9 个未改动的历史文件,因此本 PR 不会为格式化而重写无关代码。

风险与范围

  • 主要风险或取舍:产品上下文编排触及 WebShell 中央应用和 session provider,因此潜在回归集中在导航和过期异步切换;针对 ownership、失败路径、公共接口、浏览器 bundle、单元和 E2E 的覆盖用于守住这些边界。
  • 未验证/超出范围:未进行真实 daemon 的手工 UI 录屏、Windows 或 Linux 本地运行;不包含 standalone 以外的 Global Recents、standalone Recents 之外的生命周期菜单、standalone 上传、持久调度改动、daemon 生命周期改动,也不包含 refactor(vscode-ide-companion): migrate chat to WebShell and qwen serve #9811 跟踪的独立 WebShell cutover。
  • 破坏性变更/迁移说明:无。公共上下文输入为可选项;旧 daemon 在 capability 缺失时继续使用旧路由;显式非 workspace 上下文会有意 fail closed,而不会回退到 workspace。

关联问题

Refs #8908

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

⚠️ Qwen Triage ended earlyview run. It stopped before finishing; check the run log.

⚠️ Qwen Triage 提前结束 —— 查看运行。未跑完,请查看运行日志。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Gate: pass (re-run) — docs-only plan, template complete, no duplicate.

中文说明

门禁:通过(re-run)—— 纯文档计划,模板完整,无重复。

Qwen Code · qwen3.8-max

Reviewed at 21074a381f360ec7ad73978b6d4ff53787208133 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Re-run: plan re-verified against current main. Everything substantive still checks out, but main moved under this PR since it was written — two freshness gaps below, both caused by the WebShell UI cutover (#9811, fe34a5cf22), which landed a few hours after this plan.

Still accurate on current main (re-verified, not carried over):

  • Daemon routes: registerStandaloneSessionRoutes at packages/cli/src/serve/routes/standalone-sessions.ts:247 — exact line.
  • Capability: standalone_sessions_v1 registered at capabilities.ts:136 and conditional on standaloneSessionsAvailable in CONDITIONAL_SERVE_FEATURES — the "absent vs. failing" premise of the dual-behavior rule holds.
  • SDK: STANDALONE_SESSIONS_CAPABILITY at standalone-sessions.ts:16; the full standalone method family on DaemonClient; DaemonSessionClient statics at :444–485 and the restoreStrategy.kind === 'standalone' dispatch at ~:710 — all present.
  • Provider types: DaemonProductSessionContext (types.ts:71), the sessionContext prop (:160), action options (:439/:451/:472), connection state (:94) — all verified at the provider's new location (see below).
  • Sidebar anchors are exact: global handleNewSession() at WebShellSidebar.tsx:5097, project-scoped handleNewSession(wsCwd) at :5514, action-item type at :270. SessionCatalogQuery { routeKind, workspaceCwd } at session-catalog-store.ts:11; deep-link helpers parseSessionId/buildSessionPathname in utils/sessionPath.ts.
  • App.tsx still has zero sessionContext references — the product-wiring premise holds.
  • The design doc docs/design/standalone-daemon-sessions.md §PR6 routing table matches the plan's table row-for-row, including Live Voice → live.

Findings (non-blocking, freshness — the plan was accurate when written):

  1. Provider location moved. packages/webui/src/daemon/session no longer exists; the cutover moved the PR5 provider surface to packages/web-shell/client/daemon/session/ (every types.ts anchor above re-verified there). Three plan passages need updating: the "WebUI provider" section heading, the goal paragraph's "only where a typed hole is found, packages/webui", and the compatibility note about @qwen-code/webui re-exporting standalone types. The substantive upshot is good news: PR6's implementation is now fully contained in packages/web-shell — simpler than the plan describes.
  2. App.tsx line anchors drifted ~+80 to +130 lines (createNewSession 8543→8641, ensureSessionForPrompt 6095→6179, switchWorkspace 8605→8703, resolveSessionForWorkspace 6574→6661, loadSidebarSession 9016→9114, onCreateGoal 13080→13212, gitModeIntent 6060→6142, isKnownLiveWorkspaceCwd 2330→2405). Every symbol still exists and is unambiguous — the numbers are as-of-writing pointers; re-resolve at implementation time.

Neither finding changes the plan's design, and line numbers in a plan doc are inherently transient — flagging so the author can refresh the anchors (or the implementer can simply ignore line numbers and follow the symbols).

Open decision (unchanged from the prior run): current-session New Chat from a Live session maps to standalone. The reasoning is sound (the provider deliberately does not create Live sessions; a fresh text chat from a voice session is projectless), but it is a product call — flagged for maintainer confirmation before PR6 implementation begins. It does not block merging the plan.

Test evidence (from the PR's own CI on 21074a3 — no PR code was executed; docs-only diff, so code-path checks were skipped by design):

Check Conclusion
Test (ubuntu-latest, Node 22.x) success
Desktop Shell (ubuntu-22.04) success
Desktop Shell (windows-2022) success
Integration Tests (no-AK, No Sandbox) success
Secret scan (TruffleHog) success
Dependency CVE audit success
Classify PR / label / assign / authorize success
Test (macos-latest, Node 22.x) skipped (docs-only)
Test (windows-latest, Node 22.x) skipped (docs-only)
Integration Tests (CLI, No Sandbox) skipped (docs-only)
web-shell E2E Smoke (ubuntu-latest, Node 22.x) skipped (docs-only)
tmux-testing / verify skipped (docs-only)

All checks on this commit are complete; nothing failed, nothing pending.

Real-scenario testing: N/A — docs-only change, nothing user-visible to drive (and this is an unattended CI run).

中文说明

Re-run:已对照当前 main 重新核实计划。 实质性内容全部仍然成立,但本计划写就之后 main 又前进了——以下两处时效性缺口均由 WebShell UI 切换(#9811fe34a5cf22)引起,该提交在计划写就数小时后合并。

在当前 main 上仍然准确(本次重新核实,非沿用旧结论): daemon 路由 registerStandaloneSessionRoutesstandalone-sessions.ts:247,行号精确);capability standalone_sessions_v1capabilities.ts:136,条件广告于 standaloneSessionsAvailable);SDK 常量(standalone-sessions.ts:16)、DaemonClient 全套 standalone 方法、DaemonSessionClient statics(:444–485)与 restoreStrategy 分发(~:710);provider 类型锚点 types.ts:71/:160/:439/:451/:472/:94 均在新位置核实通过;sidebar 锚点精确(:5097 全局 / :5514 项目内 / :270 动作项类型);SessionCatalogQuery { routeKind, workspaceCwd }session-catalog-store.ts:11)与深链辅助函数均在;App.tsx sessionContext 引用;设计文档 §PR6 路由表与计划逐行一致(含 Live Voice → live)。

发现(不阻塞,属时效性问题——计划写就时这些信息是准确的):

  1. provider 位置已迁移。 packages/webui/src/daemon/session 已不存在;切换提交将 PR5 provider 移至 packages/web-shell/client/daemon/session/(上述 types.ts 锚点均已在新位置复核)。计划中三处需更新:"WebUI provider" 小节标题、目标段中 "仅在发现类型缺口时改 packages/webui"、以及关于 @qwen-code/webui 再导出 standalone 类型的兼容性说明。实质影响是好消息:PR6 实现现已完全包含在 packages/web-shell 内——比计划描述的更简单。
  2. App.tsx 行号锚点漂移约 +80 至 +130 行createNewSession 8543→8641、ensureSessionForPrompt 6095→6179、switchWorkspace 8605→8703、resolveSessionForWorkspace 6574→6661、loadSidebarSession 9016→9114、onCreateGoal 13080→13212、gitModeIntent 6060→6142、isKnownLiveWorkspaceCwd 2330→2405)。所有符号依然存在且无歧义——行号是写就时点的指针,实现时按符号重新定位即可。

两项发现均不改变计划设计,且计划文档中的行号本就随时效漂移——提出来供作者刷新锚点(或实现者直接忽略行号、按符号定位)。

开放决策(与上次运行一致):Live 会话发起当前会话 New Chat 映射为 standalone。理由成立,但属产品判断——在 PR6 实现开始前标注以待 maintainer 确认,不阻塞计划合并。

测试证据: 来自本提交(21074a3)自身 CI,未执行任何 PR 代码;纯文档 diff,代码路径检查按设计跳过。该提交上所有检查已完成:无失败、无进行中。

真实场景测试: N/A——纯文档变更,无用户可见行为可驱动(且本次为无人值守 CI 运行)。

Qwen Code · qwen3.8-max

Reviewed at 21074a381f360ec7ad73978b6d4ff53787208133 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — re-verified clean on current main; the two findings are anchor-freshness drift from a cutover that landed after the plan was written, not design problems.

Verdict: approve. The re-run changed my read of the details, not of the PR:

  • The substance re-checks against current main: daemon routes, capability gating, SDK surface, provider types (at their new home), sidebar entry points, the zero-sessionContext product premise, and row-for-row agreement with the design doc's §PR6 routing table.
  • Since the plan was written, the WebShell UI cutover (refactor(vscode-ide-companion): migrate chat to WebShell and qwen serve #9811) moved the PR5 provider surface from packages/webui into packages/web-shell/client and shifted App.tsx line numbers by ~80–130 lines. Every symbol the plan names still exists and is unambiguous; the affected passages are listed in Stage 2. These are editorial fixes the author can fold in with the next touch — worth doing before PR6 implementation starts, but not worth blocking a planning doc on.
  • If anything, the cutover strengthens the plan: PR6 is now fully contained in packages/web-shell, with no cross-package webui hole-patching to budget for.
  • The one open product decision (Live → standalone New Chat inheritance) stays flagged for maintainer confirmation before PR6 implementation; it does not gate this plan.
  • CI on this commit is fully settled — green where applicable, skipped by design where the docs-only diff makes a check inapplicable.

The prior run's approval is already pinned to exactly this commit (21074a3), and the head has not moved, so this re-run refreshes the review comments without stacking a duplicate approval.

@doudouOUC — still a solid, well-grounded plan; if you get a chance, refresh the provider-location references and the App.tsx anchors against the post-cutover tree before starting PR6.

中文说明

置信度:4/5 —— 已在当前 main 上复核通过;两项发现是切换提交(在计划写就之后合并)造成的锚点时效漂移,不是设计问题。

结论:批准。 re-run 改变的是我对细节的判断,而不是对这个 PR 的判断:

  • 实质内容在当前 main 上复核通过:daemon 路由、capability 门控、SDK 接口、provider 类型(位于新位置)、sidebar 入口、产品代码零 sessionContext 的前提,以及与设计文档 §PR6 路由表逐行一致。
  • 计划写就之后,WebShell UI 切换(refactor(vscode-ide-companion): migrate chat to WebShell and qwen serve #9811)把 PR5 provider 从 packages/webui 移入 packages/web-shell/client,并使 App.tsx 行号漂移约 80–130 行。计划点名的每个符号依然存在且无歧义;受影响段落已在 Stage 2 列出。这些是作者下次改动时可顺带修正的编辑性问题——值得在 PR6 实现开始前做,但不值得因此阻塞一份计划文档。
  • 如果说有变化,切换反而强化了该计划:PR6 现在完全包含在 packages/web-shell 内,无需再为跨包修补 webui 预留工作量。
  • 唯一开放的产品决策(Live → standalone 的 New Chat 继承)继续标注,待 PR6 实现前由 maintainer 确认;不阻塞本计划。
  • 该提交的 CI 已全部落定——适用项全绿,纯文档 diff 不适用的检查按设计跳过。

上次运行的批准已精确钉在本提交(21074a3)上,且 head 未移动,因此本次 re-run 只刷新评审评论,不叠加重复批准。

@doudouOUC —— 依然是扎实、有据可依的计划;如有时间,请在开始 PR6 之前按切换后的代码树刷新 provider 位置引用与 App.tsx 锚点。

Qwen Code · qwen3.8-max

Reviewed at 21074a381f360ec7ad73978b6d4ff53787208133 · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot 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.

LGTM, looks ready to ship. Docs-only plan; every referenced API/anchor verified against main. One flagged open decision (Live -> standalone New Chat inheritance) noted for maintainer confirmation before PR6 implementation. ✅

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

📄 Docs-only change (superseded) — this badge no longer reflects the current review state of this PR. See the latest review activity on this PR.

中文说明

📄 纯文档变更 (已失效) —— 该徽章已不再反映本 PR 当前的评审状态。请以本 PR 上最新的评审动态为准。

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ⚠️ not run — n/a - workflow run

This PR changes documentation/assets only — there is no code to execute, so a sandboxed verification has nothing to verify.

中文 — 判定:⚠️ 未运行 · 不适用

该 PR 仅改动文档/静态资源,没有可执行的代码,沙箱验证没有验证对象。

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

The bot already has a review of its own on 21074a381f360ec7ad73978b6d4ff53787208133, which still stands.

机器人在 21074a381f360ec7ad73978b6d4ff53787208133 上已有自己的评审,且仍然有效。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The overall PR6 direction is sound, but the plan is not implementation-ready yet. The inline comments below cover the concrete routing, failure-state, and context-boundary gaps that should be resolved before implementation.

Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The previous seven threads are addressed on this head. A fresh audit against current main found six remaining implementation-boundary gaps below: four correctness/context issues and two lifecycle/catalog issues. These should be resolved before treating the plan as implementation-ready.

Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
@doudouOUC doudouOUC self-assigned this Aug 30, 2026
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code  /triage

@doudouOUC
doudouOUC enabled auto-merge August 30, 2026 12:25

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

Scan — docs-only plan (docs/plans/2026-08-29-standalone-pr6-webshell-ui.md, +455/-0)


No blocking findings

Triage: Documentation-only; one new file. No code paths changed. Reviewed at Scan depth: contract claims vs. sources on main, routing table vs. umbrella design doc, referenced commit SHAs.

Merge-contract SHAs verified:

  • PR3 34a6e918b6feat(cli): Add standalone daemon session API (#10179)
  • PR4 7357136dd1feat(sdk): Add standalone session APIs (#10294)
  • PR5 ac761e11c2feat(web-shell): Add explicit daemon session contexts (#10418)

Routing table — matches docs/design/standalone-daemon-sessions.md exactly:

Entry point Plan Design doc
Home / global New Chat standalone standalone
New Chat within selected/locked project workspace workspace
Goals and Git entry points workspace workspace
Current-session New Chat Inherit explicit context Inherit explicit context ✓
Live Voice live live

Prior review findings — all 13 threads confirmed adopted at current head (85d7e94b5e):

  • P1: standalone creation branch omits workspace-only fields (sourceType, worktree, branch) ✓
  • P1: capability tri-state (loading waits, absent → legacy, error → fail-closed) ✓
  • P1: pending context stays authoritative until attach succeeds ✓
  • P1: Live New Chat routes through startLive('new'), not standalone ✓
  • P1: repair offered only for working_directory_missing; compromised is fail-closed ✓
  • P1: split view defers standalone — workspace-only in PR6 ✓
  • P1: attachments cleared on context switch + submit-time guard ✓
  • P1: missing-directory recovery captures typed code, owner-guards repair, reloads on success ✓
  • P1: stale deep-link creation poll canceled by App-level route-resolution token ✓
  • P2: standalone Recents view model drops internal workspaceCwd entirely ✓
  • P2: archived standalone lane added to lazy Archived section with explicit refresh ✓
  • P2: partial-success batch envelopes interpreted per session ID ✓
  • P2: creating deep link polls to terminal state with bounded backoff + Retry action ✓

Scope boundary: uploads, session migration, durable scheduling all explicitly excluded ✓

Open decision: Live → standalone remap removed; Live current-session New Chat routes through existing startLive('new') path ✓

CI: build-cli SKIPPED — expected for a docs-only PR with no compilable changes.


Clean result at this head. The plan is internally consistent, the umbrella contract row is satisfied, all prior issues are resolved, and the three referenced merge commits are real.

Reviewed with AI assistance.

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fresh reverse audit at the latest head found six remaining implementation-boundary gaps. The prior review fixes are adopted; the comments below cover only new or incomplete routing, recovery, and isolation issues.

Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The latest head addresses the first four prior comments in direction, but two resolved-without-reply gaps remain and the new wording leaves two implementation ambiguities. The inline comments below are limited to those four items.

Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md Outdated
@doudouOUC
doudouOUC requested a review from chiga0 August 30, 2026 15:16

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The d4c28ec update fixes the previous archived-summary, Split View, intent-example, and composer-key issues in direction. A fresh pass against current main found three remaining context-isolation paths below; all are correctness blockers rather than additional scope.

Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Two remaining context-isolation gaps need to be explicit before implementation.

Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
Comment thread docs/plans/2026-08-29-standalone-pr6-webshell-ui.md
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@doudouOUC
doudouOUC requested a review from wenshao August 30, 2026 17:31

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Re-reviewed current head 62173db0b394be5d4542a4d208abb97879403ed9 after the latest plan and thread updates.

The latest revision resolves the Web Terminal boundary correctly: standalone sessions keep ordinary Shell/tool execution in their private working directory, while the separate workspace Web Terminal is limited to explicit workspace contexts. Pending and attached standalone/Live contexts create no terminal WebSocket, inherited terminal tabs are discarded when entering a non-workspace context, and workspace terminal behavior stays unchanged.

I also re-audited context routing, capability loading and failure behavior, Recents and lifecycle actions, deep links, Split View ingress, composer/history/attachment isolation, artifact workspace authority, approval mode, and outcome-unknown recovery. I found no new correctness blockers on this head. The plan is ready for implementation under the documented scope and verification matrix.

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ⚠️ not run — n/a - workflow run

This PR changes documentation/assets only — there is no code to execute, so a sandboxed verification has nothing to verify.

中文 — 判定:⚠️ 未运行 · 不适用

该 PR 仅改动文档/静态资源,没有可执行的代码,沙箱验证没有验证对象。

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ⚠️ not run — n/a - workflow run

This PR changes documentation/assets only — there is no code to execute, so a sandboxed verification has nothing to verify.

中文 — 判定:⚠️ 未运行 · 不适用

该 PR 仅改动文档/静态资源,没有可执行的代码,沙箱验证没有验证对象。

Qwen Code · sandboxed verification

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

Round 2 — re-review against head 62173db0 (5 commits ahead of round-1 at 85d7e94b)


No blocking findings

New commits since round 1: e1ea85a, d4c28ec, 8c1f2c1, 3e14523, 62173db

5 new P1 issues identified and adopted in this range — all confirmed addressed:

Issue Adopted in
Input-history fallback: standalone history gets no legacy qwen-web-shell-history fallback 8c1f2c1
ScheduledTasksDialog.onCreateViaChat forces { kind: 'workspace', cwd } — standalone never inherits 8c1f2c1
/branch and Branch action excluded from standalone (route registered with rejectStandalone: true); /fork stays available 3e14523
SessionOverviewPanel / ResumeDialog ingresses gated — bare /resume, /delete, /release hidden/blocked from non-workspace contexts; standalone-targeted /resume <id> routes only through explicit standalone context 3e14523
Workspace Web Terminal gated by effectiveContext.kind === 'workspace'; inherited terminal tabs discarded when entering non-workspace context; pending/attached standalone and Live contexts create no terminal WebSocket 62173db

Prior round issues (13 threads) remain adopted as confirmed in round 1.

Reviewer (doudouOUC) re-audited at 62173db (17:31 UTC):

"I found no new correctness blockers on this head. The plan is ready for implementation under the documented scope and verification matrix."

Re-checked from the new content: archived exact-lookup branches on both cold-link and outcome-recovery paths, outcome-unknown state machine (all 4 transitions: creating/existing/absent/unknown), Split View bare-id classifier, ScheduledTasksDialog, /branch//fork separation, Web Terminal tab cleanup. All internally consistent.

CI: build-cli SKIPPED — expected (docs-only PR).


Reviewed with AI assistance.

doudouOUC and others added 9 commits September 1, 2026 17:02
…classifier review

- Branch cold-link and recovery flows on isArchived: render archived
  state, unarchive with per-id success verification, then load
- Isolate composer commands/skills/atWorkspaceCwd and draft/history
  identity for standalone drafts; no primary fallback
- Specify the fail-closed Split View bare-id classifier before panes mount
- Rewrite project/Goals examples to the explicit intent contract
- Gate workspace effects, command discovery, and submit-time handlers on
  effectiveContext.kind === 'workspace', not only rendered controls
- Disable the legacy global input-history fallback for standalone
  identities
- Give ScheduledTasksDialog.onCreateViaChat an explicit workspace intent
- Exclude generic transcript branching (/branch, per-message Branch) from
  standalone contexts; the daemon route is rejectStandalone
- Gate session-overview, resume, delete, and release ingresses from
  non-workspace contexts; standalone /resume routes via explicit context

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Terminal availability and the open handler require an explicit workspace
context; inherited terminal tabs are discarded when entering standalone or
Live, and ordinary shell/tool commands in the private directory remain
governed by the session permission pipeline.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
chiga0
chiga0 previously approved these changes Sep 1, 2026

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

Round 5 — re-review against head cb521a13 (new commit fix(web-shell): address PR review feedback on top of 49fad237)


CI at this head

Check Result
Test (ubuntu-latest, Node 22.x) FAILURE — unrelated to PR
Test (macos-latest, Node 22.x) SKIPPED
Test (windows-latest, Node 22.x) SKIPPED
build-cli SKIPPED
Integration Tests (CLI, No Sandbox) SKIPPED
Integration Tests (no-AK, No Sandbox) SUCCESS
Desktop Shell ubuntu / windows SUCCESS
web-shell E2E Smoke SUCCESS

The ubuntu test failure is in step "Run .github/scripts helper tests", test case autofix-status-heartbeat loop > a planted FIFO at heartbeat-tick-inflight cannot block the loop past the bounded write (.github/scripts/autofix-status-heartbeat.test.mjs). This file is not in the PR diff — all 27 PR files are under packages/web-shell/client/. The failure is a flaky FIFO-based concurrency test in a CI helper script, pre-existing and unrelated to these changes. The actual web-shell vitest suite is not the failing step.

macOS, Windows, build-cli and Integration Tests (CLI) are SKIPPED — expected for a web-shell-only PR with no platform-sensitive or CLI path changes. darwin filesystem case-sensitivity and Node/Windows path behaviour remain unreviewed.


Verified FIXED — new commit cb521a13

Finding Fix
R2-18: stale result applied after component re-keyed — unarchiveStandaloneSessions .then()/.catch() had no generation check Added generation guard before every setResolution call in both callbacks
R2-21: daemon response IDs compared case-sensitively against a mixed-case input normalizedSessionId = requestedSessionId.toLowerCase() used for all result comparisons
R2-13: untrusted-workspace cleanup effect fired during standalone context, potentially overwriting pending non-workspace context Added !workspaceContextActive guard: effect returns early when session context is not workspace
R2-3: suggestion mode read pendingSessionContextRef while a locked workspace cwd was active Added !lockedWorkspaceCwd guard on the suggestion-mode branch
R2-34: startLive success returned true unconditionally, ignoring superseded invocations Return now checks sessionOpenInvocationRef.current === invocation; error path gained a matching guard

Verified FIXED — full PR diff at this head

Finding Evidence
R1-4: "goal" absent from NON_WORKSPACE_BLOCKED_COMMANDS "goal" present in the array
R1-5: sidebar state not reset when projectFeaturesEnabled flips false Effect added: clears collapsedSessionsOpen, searchOpen, searchQuery, and calls cancelRename
R1-6: single try/catch mixed unarchive and load errors, wrong recovery state set Split into two separate try/catch blocks: unarchive failure sets "archived", load failure sets "unknown"
R1-10: loadSidebarSession restore-on-failure could stomp a newer pending context Dual guard: sessionOpenInvocationRef.current === invocation && pendingSessionContextRef.current === targetContext on both success-clear and failure-restore

Surviving minor findings from round 4

R4-1 and R4-2 were not touched by the new commit and still stand. See round-4 inline comments for detail.


Open items — not filed as inline (round-5 floor; one link unread)

R1-3 — ordinaryWorkspaces cleanup effect: possible overwrite of pending workspace navigation

The cleanup effect has workspaceContextActive in its dep array. When a user transitions from standalone back to workspace context via loadSidebarSession, which sets pendingSessionContext = { kind: "workspace", cwd: X }, workspaceContextActive flips false→true, triggering the effect. At that point selectedWorkspaceCwd may still hold a previously-selected non-primary workspace (e.g. "/Y"), causing the effect to call setPendingSessionContext({ kind: "workspace", cwd: primaryCwd }) and overwrite the intended target before the session settles.

One unread link: whether setSelectedWorkspaceCwd(undefined) is called on standalone entry. The only call site in the diff is after creating a new workspace session (App.tsx around the creationSessionContext?.kind === "workspace" guard at line ~4415). If it is not cleared on standalone entry, the race fires whenever the user switches standalone to workspace on a host that previously had a non-primary workspace selected. Suggest verifying and adding a !pendingSessionContextRef.current guard or an explicit setSelectedWorkspaceCwd(undefined) on context kind switch.

R1-8 — workspace.client stability in StandaloneSessionGate resolution effect

workspace.client is listed as a dep of the resolution effect. Stability of that reference is not verifiable from this PR's diff (depends on DaemonWorkspaceProvider internals not present here). Practical harm is bounded by the generation counter. Low risk, disclosed for completeness.


Scope

Reviewed: 27 files in diff, merge-base 3a0c4c61..cb521a13. App.tsx and WebShellSidebar.tsx mutation matrix not re-run at this head (files changed, but the new commit's 4 changed files cover the fixes above; surviving mutant sites are unchanged). No Playwright host for E2E.

No blocking findings. Approving. R4-1 and R4-2 are the only surviving findings (both minor, not must-fix). CI failure confirmed unrelated to this PR.

Reviewed with AI assistance.

@doudouOUC
doudouOUC force-pushed the docs/standalone-pr6-webshell-ui-plan branch from cb521a1 to bdfffc2 Compare September 1, 2026 09:09
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Rebased PR #10514 onto origin/main at cf6c9bbec2 and force-pushed bdfffc26b4, resolving the App.tsx conflict by retaining both the upstream reasoning-controls type and this PR's product-session-context type.

Action Result
Review feedback Fresh paginated audit found 142/142 threads resolved; the new round-5 review approves the prior head with no blocking findings. No thread needed a reply in this cycle.
CI diagnosis The old-head Ubuntu failure was an unrelated FIFO helper-test cleanup race (ENOTEMPTY in .github/scripts/autofix-status-heartbeat.test.mjs); the coverage-comment failure was only the missing downstream artifact. No old-head rerun was started because the rebase created a new SHA.
Conflict Rebased 13 commits onto current main; the only content conflict was mechanically resolved in packages/web-shell/client/App.tsx.
Verification Node 22 Web Shell build and typecheck passed; App.test.tsx passed 639/639; focused standalone/composer/session tests passed 372/372; App ESLint and Prettier checks passed; range-diff, conflict-marker, and clean-diff audits passed. The repository-wide build reached the CLI package and then hit a stale local dependency checkout (remend missing), so the new-head CI run remains the broad clean-environment gate.

Resolved 0/0 newly handled review threads in this cycle; all 142 existing review threads remain resolved.

@wenshao

wenshao commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Round 3 — re-validated on cb521a1322, and on the result of merging it into current main

Re-ran the harness against the new head, and then built a third arm from the conflict-resolved merge with current main (cf6c9bbec2), because the PR now shows as CONFLICTING. Three Web Shell builds from source in one environment — previous head, new head, merged — against the same real qwen serve daemon with 61 active / 59 archived real standalone conversations on disk.

summary

Headline

One of the five fixes closes a bug the previous review round dismissed as a false positive — and I can show it failing. The rest of the batch checks out, the merge conflict is trivial, and the merged result is type-clean and behaves identically to the new head on every scenario I re-ran.

1 · R2-3 was real — upper-case archived deep links reported failure on a successful unarchive

Round 2 concluded R2-3 was "likely a false positive" on the premise that the daemon rejects non-lower-case session ids up front. Probed directly, it does not: it accepts mixed case on every standalone route and answers in lower case.

POST /standalone/sessions/unarchive  {"sessionIds":["E5E51D0F-CC54-4276-8765-8F30E599EA66"]}
  -> 200 {"unarchived":["e5e51d0f-cc54-4276-8765-8f30e599ea66"], ...}
POST /standalone/sessions/<UPPER>/load     -> 200
GET  /standalone/sessions/<UPPER>          -> 200
PATCH .../<UPPER>/metadata                 -> 200

So on the previous head, opening /session/<UPPER-CASE-ID>?context=standalone and clicking Unarchive produced this: the daemon unarchived the conversation, the client compared the upper-case request id against the lower-case echo, fell through to the error branch, and told the user it had failed — with no follow-up load, leaving them stuck on a conversation that was in fact already open.

r2-3

previous head 49fad237e2 new head cb521a1322 merged with main
POST /standalone/sessions/unarchive 200 200 200
daemon state afterwards unarchived unarchived unarchived
what the client shows "Failed to unarchive this conversation." attaches and opens the chat attaches and opens the chat
follow-up load none — dead end 200 200
resulting URL stays upper-case normalized to lower case normalized to lower case

The toLowerCase() normalization in WorkspaceSessionProvider.tsx:571 is therefore load-bearing, not cosmetic. Good catch by the author, and worth correcting the record on.

2 · The other four changes

  • App.tsx:2702 — the stale-workspace cleanup effect now bails unless workspaceContextActive. This closes R2-1 at the source. I had already shown R2-1 was not reachable from the UI, and it is still clean on the new head: with ws2 armed, removed by another client, and the removal genuinely observed by the page, workspace-only surfaces stay at zero and /new still issues POST /standalone/sessions with zero POST /session. The guard is a correct belt-and-braces; the legitimate fallback path is preserved by construction, since workspaceContextActive is true exactly when the picker is armed.
  • WorkspaceSessionProvider.tsx:571 — the generation guard on the unarchive completion is the R2-2 fix; I could not stage a stale resolution deterministically, so this rests on code reading, as it did last round.
  • App.tsx:9215 (superseded Live-start completions) and App.tsx:9691 (locked-workspace precedence) — not testable here: Live Voice is darwin-gated on this box, and I did not stage a locked-workspace host. Flagging them as unverified rather than claiming coverage.

3 · The merge conflict is one import line, and the merge is type-clean

git merge against cf6c9bbec2 produces exactly one conflict, in the type-import list at the top of App.tsx: main added type DaemonReasoningControls (#10489) and this PR added type DaemonProductSessionContext in the same position. The resolution is to keep both lines.

After resolving it, in a single worktree:

result
web-shell tsc --noEmit on main alone 19 errors (all stale SDK dist in the worktree)
same tsc on the merged tree 19 errors, the same set once line numbers are normalized
new type errors introduced by the merge 0
vite build of the merged tree ok

And re-running the real-daemon scenarios on the merged build: global New task → one POST /standalone/sessions, zero legacy POST /session; product isolation clean with nothing leaking into the standalone context; the upper-case archived deep link unarchives, loads and attaches; the R2-1 scenario stays on the standalone route. So the conflict is a rebase chore, not a design collision.

4 · The three deferred items are unchanged

still-open

Re-measured on the merged arm, all three behave exactly as in round 2: top-level Recents still collapse to page 1 on the first hop into a standalone chat (61 → 44 rows, with the just-opened session missing from its own list); /resume <workspace-id> from a standalone chat still produces four 500 session_workspace_conflict responses, no toast, and a blank New session in place of the chat the user was in; and a standalone deep link still issues exactly one /capabilities request and sits on Loading… forever where a workspace deep link retries five times and self-heals. All three are fail-closed and none corrupts state — I agree they are reasonable follow-ups rather than another round.

F1 is unchanged and remains the release gate: on unpatched code the daemon still cannot create a standalone session at all (acpAgent.ts dereferences config.getContentGeneratorConfig() after deliberately skipping auth for a provisional standalone session). Not this PR's code. All three arms in this round ran with the same one-line local patch, so no comparison here is biased by it. F4 and the daemon-restart behaviour also reproduce unchanged, and both are identical on the base arm.

Recommendation

Resolve the one-line import conflict and merge. The new batch is a genuine improvement — it fixes a user-visible archived-deep-link bug that two review rounds had written off — the merge with current main is type-clean and behaviourally identical, and nothing outstanding corrupts state or blocks the product contract. The remaining three items are worth follow-up issues, and F1 still needs to land before the feature is announced.

中文版

第三轮 —— 在 cb521a1322 上重新验证,并验证它与当前 main 合并后的结果

在新 head 上重跑了 harness,并且因为 PR 现在显示 CONFLICTING,我又用解决冲突后与当前 maincf6c9bbec2)的合并结果构建了第三个 arm。同一环境下从源码构建三份 Web Shell —— 上一个 head、新 head、合并后 —— 跑在同一个真实 qwen serve daemon 上,磁盘上有 61 个 active / 59 个 archived 真实 standalone 会话。

结论要点

五个修复中有一个,堵上了上一轮评审判定为「误报」的真实 bug —— 而且我能把它复现出来。 其余改动经检查无误,合并冲突很轻,合并结果类型干净,在我重跑的所有场景上与新 head 行为一致。

1 · R2-3 是真的 —— 大写归档深链会把成功的 unarchive 报成失败

第二轮的结论是 R2-3「大概率是误报」,前提是 daemon 会在入口就拒绝非小写的 session id。我直接探测后发现并非如此:daemon 在所有 standalone 路由上都接受混合大小写,并以小写作答

POST /standalone/sessions/unarchive  {"sessionIds":["E5E51D0F-CC54-4276-8765-8F30E599EA66"]}
  -> 200 {"unarchived":["e5e51d0f-cc54-4276-8765-8f30e599ea66"], ...}
POST /standalone/sessions/<UPPER>/load     -> 200
GET  /standalone/sessions/<UPPER>          -> 200
PATCH .../<UPPER>/metadata                 -> 200

于是在上一个 head 上,打开 /session/<大写-ID>?context=standalone 并点击 Unarchive 的结果是:daemon 确实把会话取消归档了,但客户端拿大写的请求 id 去比对小写的回显,落进了错误分支,向用户报告失败 —— 并且不会发起后续 load,把用户卡在一个其实已经打开的会话上。

上一个 head 49fad237e2 新 head cb521a1322 main 合并后
POST /standalone/sessions/unarchive 200 200 200
daemon 侧状态 已取消归档 已取消归档 已取消归档
客户端展示 「取消归档失败」 正常 attach 并打开会话 正常 attach 并打开会话
后续 load 无 —— 死路 200 200
最终 URL 保持大写 归一化为小写 归一化为小写

因此 WorkspaceSessionProvider.tsx:571 里的 toLowerCase() 归一化是有实际承载的,不是修饰。作者这一手抓得好,也值得把之前的判断纠正过来。

2 · 其余四处改动

  • App.tsx:2702 —— 过期 workspace 清理 effect 现在在 workspaceContextActive 为假时直接返回,从源头堵上了 R2-1。我此前已经证明 R2-1 在 UI 上不可达,新 head 上依然干净:锚定 ws2、由另一客户端移除、且页面确实观察到了这次移除之后,workspace 专属界面数量仍为 0,/new 仍发出 POST /standalone/sessionsPOST /session 为零。这个守卫是正确的纵深防御;合法的回退路径也天然保留 —— 因为选择器被锚定时 workspaceContextActive 恰好为真。
  • WorkspaceSessionProvider.tsx:571 —— unarchive 完成回调上的 generation 守卫即 R2-2 的修复;我无法确定性地构造出过期 resolution,所以这一条与上轮一样只基于代码阅读。
  • App.tsx:9215(丢弃被取代的 Live 启动完成)与 App.tsx:9691(locked-workspace 优先级)—— 本环境无法验证:Live Voice 在这台机器上仅限 darwin,我也没有构造 locked-workspace 宿主。如实标注为未验证,而不是声称已覆盖。

3 · 合并冲突只有一行 import,合并后类型干净

cf6c9bbec2 合并只产生一处冲突,位于 App.tsx 顶部的类型 import 列表:main 加了 type DaemonReasoningControls#10489),本 PR 加了 type DaemonProductSessionContext,位置相同。解决方式就是两行都保留。

解决之后,在同一个 worktree 内:

结果
main 上跑 web-shell tsc --noEmit 19 个错误(全部来自 worktree 内陈旧的 SDK dist
合并后跑同一条 tsc 19 个错误,归一化行号后是同一批
合并引入的新类型错误 0
合并后 vite build 通过

用合并后的产物重跑真实 daemon 场景:全局 New task → 一次 POST /standalone/sessions、零次旧的 POST /session;产品隔离干净、没有任何控件反向泄漏进 standalone;大写归档深链能取消归档、加载并 attach;R2-1 场景仍稳定停在 standalone 路由。所以这个冲突是一次 rebase 杂务,不是设计层面的碰撞。

4 · 三个被推迟的问题原样保留

在合并后的 arm 上重新测量,三条的表现与第二轮完全一致:首次从常规视图点进 standalone 聊天时,顶层 Recents 仍会塌回第一页(61 → 44 行,且刚打开的那个会话不在自己的列表里);在 standalone 聊天里执行 /resume <workspace-id> 仍然产生四次 500 session_workspace_conflict、没有任何 toast,并把用户所在的聊天替换成空白 New session;standalone 深链在 /capabilities 失败时仍然只发一次请求并永远停在 Loading…,而 workspace 深链会重试五次并自愈。三条都是 fail-closed 且不损坏状态 —— 我同意把它们作为后续跟进,而不是再开一轮。

F1 未变,仍是发布门槛:在未打补丁的代码上,daemon 依然完全无法创建 standalone 会话(acpAgent.ts 在对 provisional standalone 会话有意跳过认证之后,仍然解引用了 config.getContentGeneratorConfig())。这不是本 PR 的代码。本轮三个 arm 都施加了同一处一行补丁,因此不会影响任何对比结论。F4 以及 daemon 重启的行为也原样复现,且在 base arm 上完全一致。

建议

解决这一行 import 冲突,然后合并。 这一批改动是实实在在的改进 —— 它修好了一个被两轮评审写掉的、用户可见的归档深链 bug;与当前 main 的合并类型干净、行为一致;剩下的问题没有一条会损坏状态或破坏产品契约。余下三项值得开 follow-up issue,而 F1 仍需在该功能对外宣布之前落地。

@doudouOUC
doudouOUC requested a review from ytahdn September 1, 2026 09:17

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

Round 6 — re-review against head bdfffc26 (new commit fix(web-shell): address PR review feedback on top of 9b4cd0a7)


CI at this head

Check Result
Test (ubuntu-latest, Node 22.x) IN PROGRESS
Test (macos-latest, Node 22.x) SKIPPED
Test (windows-latest, Node 22.x) SKIPPED
Integration Tests (no-AK, No Sandbox) SUCCESS
Desktop Shell ubuntu / windows SUCCESS

The ubuntu test is still running at review time (run 33490751774, started 2026-09-01T09:09:40Z). Previous round-5 ubuntu failure was confirmed unrelated to this PR (flaky .github/scripts/autofix-status-heartbeat.test.mjs). macOS and Windows tests remain SKIPPED — expected.


New commit bdfffc26 — what changed and verification

4 files changed: App.tsx (+12/-2), App.test.tsx (+247), WorkspaceSessionProvider.tsx (+10/-5), WorkspaceSessionProvider.test.tsx (+157)

R1-3 — FIXED

The trusted-workspace validation effect in App.tsx now has a !workspaceContextActive guard (parallel to the R2-13 fix in round 5 for the untrusted-workspace cleanup effect). Previously: when a user was in a settled standalone session and the secondary workspace they had previously selected became untrusted (workspace list refreshed), the effect would fire — workspaceContextActive flipping false→true was in the dep array — and overwrite the current session context with primaryCwd.

The fix adds !workspaceContextActive to the early-return guard and adds workspaceContextActive as a dep, so the effect cannot fire when the active session context is not workspace.

Test added and verified: it('does not retarget a settled standalone session when a stale workspace selection becomes untrusted', ...) — renders with sessionContext: { kind: 'standalone' }, makes the secondary workspace trusted→untrusted, asserts composerScopeKey: 'standalone' and workspaceFeaturesEnabled: false remain unchanged. Deleting the guard would cause workspaceFeaturesEnabled to flip true (effect fires → sets workspace pending context), failing the assertion. The test pins the fix.

R2-18 + R2-21 — additional test coverage added

The WorkspaceSessionProvider.tsx change refactors the unarchive onAction callback to capture requestedSessionId, normalizedSessionId, and generation at click time (before the async call), ensuring the .then()/.catch() handlers reference stable captured values. Two new tests:

  • it('accepts normalized daemon ids when unarchiving a mixed-case deep link', ...) — sends sessionId = "Standalone-A", daemon returns lowercase "standalone-a" in the unarchived array; asserts the provider reaches ready state without remounting. Pins R2-21 case-normalization.
  • it('ignores a stale unarchive result after navigating to another standalone session', ...) — suspends the unarchive promise, navigates to standalone-b while unarchive for standalone-a is in flight, then resolves; asserts standalone-a's result is discarded and standalone-b's resolution proceeds. Pins R2-18 generation guard.

R2-3 + R2-34 — tests added

Two more tests pin the fixes from round 5 (confirmed re-applied correctly after rebase):

  • it('uses the locked workspace path when accepting a Live-context new-topic suggestion', ...) — with lockedWorkspaceCwd set, accepts a new_session suggestion; asserts startLive was NOT called and sendPrompt was called instead. Pins R2-3.
  • it('cancels an accepted Live new-topic suggestion when a newer global new-chat request wins', ...)createNewSession races with an in-flight startLive; asserts sendPrompt was NOT called. Pins R2-34.

Surviving minor findings

R4-1 and R4-2 are unchanged (files not touched by any commit since round 4). Still stand.


Open items

R1-8 (workspace.client stability in StandaloneSessionGate resolution effect dep array) remains unresolvable from this diff; generation counter continues to bound worst-case harm.


No blocking findings at round 6. R1-3 verified fixed. Approving.

Reviewed with AI assistance.

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 57 passed · 0 failed · 57 total

Flakiness gate: ✅ 10 changed test file(s) x 5 identical rounds, no divergence

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:57 通过 · 0 失败 · 57 总计

抖动门:✅ 10 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR 10514 — feat(web-shell): Add standalone chats

Verdict: merge-ready — 57/57 scripted assertions passed, 0 unexpected failures. Verified head: bdfffc26b489c48a9b37df77cae91bf1ef3d56b0 (merge ref 5b94783, base cf6c9bb = origin/main tip at snapshot, so the merge is against current main — no stale-base concern).

中文摘要
  • 结论: merge-ready。57 项脚本化断言全部通过,0 项意外失败。
  • A/B 结论: 中心声明(load-bearing)被证明。wire oracle 在真实回环 HTTP 上驱动真实 sessionPreparation.ts + 真实 createDaemonSessionActions + 真实 SDK DaemonClient:head 的 standalone 意图精确命中 POST /standalone/sessions,请求体仅含 {sessionId, approvalMode},零 workspace 字段泄露;同一输入在 base 版 sessionPreparation 下静默创建 workspace 会话(POST /session 携带泄露的 cwd/sourceType/worktree/branch)——即本 PR 修复的缺陷在 base 上被复现,在 head 上消失(见 01-wire-oracle-ab.png)。capability 三态(存在→standalone、加载/失败→fail closed、缺失→legacy)在 App 层由变异矩阵证明:关闭 standalone 分支后 3 个 gating 测试变红。
  • 变异矩阵: 5 个守卫逐一还原,全部被各自的新测试捕获(1/3/3/1/2 红),无存活者(见 02-mutation-matrix.png)。
  • 门禁: web-shell 全量单元测试 242 文件 / 5497 测试全绿(见 03-webshell-gate-head.png)。
  • Findings: 无阻塞项。两条非阻塞观察见正文。
  • 未覆盖: 每提交归因(shallow depth 2,元数据 13 个提交仅 1 个可达)、Playwright E2E(容器无浏览器,PR 自身 CI 覆盖)、真实 daemon 手工 UI、StandaloneRecents 生命周期菜单与 composer 附件隔离仅由套件绿覆盖(未变异)。

Central claim and A/B proof

Central claim: with standalone_sessions_v1 advertised, global New task creates an exact standalone session (no workspace-only fields on the wire); legacy primary-workspace behavior only when the capability is loaded-absent; fail-closed while capabilities load or fail.

The wire oracle (harness/wire-oracle.verify.test.ts, run under vitest/jsdom) drives the real changed modules — createAndAttachSessionForPrompt, createDaemonSessionActions wired exactly as DaemonSessionProvider.tsx does, and the built @qwen-code/sdk/daemon DaemonClient/DaemonSessionClient — against a real loopback HTTP fake daemon whose route semantics are lifted from packages/cli/src/serve/routes/standalone-sessions.ts (strict requireExactBody on POST /standalone/sessions → unknown fields 400; batch envelopes echo received ids; standalone_session_not_found 404 code). The only injected seams are attachSession/setModel recorders, outside this PR's changed surface.

cell build / input oracle (requests the fake daemon received) result
H1 head-standalone head sessionPreparation, intent standalone + workspaceCwd/worktree/branch/sourceType all set to must-not-leak POST /standalone/sessions, body keys exactly {approvalMode, sessionId}, approvalMode=yolo, lowercased UUID; 0× POST /session pass
C1 control (base sessionPreparation) same input 0× standalone; 1× POST /session with cwd=/must-not-leak, sourceType, worktree, branch; connection context becomes {kind:'workspace', cwd:'/must-not-leak'} pass (base misroutes silently, as predicted)
H2 head-workspace intent {kind:'workspace',cwd:'/primary'} + all workspace fields 0× standalone; 1× POST /session carrying cwd, sourceType, worktree, branch, approvalMode pass (workspace path unregressed)
H3 layered guard direct actions.createSession({sessionContext:standalone, worktree}) rejects with "Standalone session creation does not support sourceType, worktree, or branch options" before any wire call pass
H4 capability gate daemon without standalone_sessions_v1 createStandaloneSession rejects naming the capability; 0 create attempts pass

Witness: evidence/01-wire-oracle-ab.png (8/8 tests green). The H1/C1 pair is the load-bearing flip: the PR's hunk converts a silent workspace misroute into an exact standalone create; the real daemon's strict body validation means any leaked field would have been a 400, so "body keys exactly {approvalMode, sessionId}" is the strongest form of the no-leak claim.

Secondary claim — deep-link gate: StandaloneSessionGate validates the exact session via GET /standalone/sessions/:id before mounting the provider; archived → unarchive-before-attach with per-id success verification; not-found → recovery without workspace fallback. Wire cells (lookup resolves; 404-with-code classifies as not-found while generic 404/500 do not; keepWorkspaceSplitSessionIds over real HTTP keeps only ids proven workspace-only and fails closed on unresolved capabilities; unarchive envelope ids round-trip lowercased exactly as the gate compares) all pass in the same capture. Mutation M3 (gate skips the lookup) turns the exact-check / unarchive-before-attach / missing tests red (3 failed), proving the gate load-bearing.

Secondary claim — capability tri-state at App level: mutation M2 (standalone branch of createNewSession replaced by if (false)) turns the three gating tests red (reports a pending standalone context after the current session clears, keeps a newer standalone draft when an older session load fails, does not expose stale workspace session commands in a pending standalone draft), i.e. the capability-present → standalone decision is pinned.

Mutation / vacuity matrix

harness/run-mutation-matrix.sh reverts one guard at a time in a scratch copy, runs the targeted tests, restores. Witness: evidence/02-mutation-matrix.png; raw log logs/mutation-matrix.log.

row reverted guard suite expected red observed classification
M1 sessionPreparation.ts standalone branch (→ base) utils/sessionPreparation.test.ts 1 1 failed | 20 passed caught
M2 App createNewSession capability gate App.test.tsx (3 gating tests) 3 3 failed | 636 skipped caught
M3 StandaloneSessionGate exact lookup WorkspaceSessionProvider.test.tsx (3 tests) 3 3 failed | 18 skipped caught
M4 keepWorkspaceSplitSessionIds classification utils/standalone-session-routing.test.ts 1 1 failed | 2 passed caught
M5 publishStandaloneWorkingDirectoryError actions.test.ts (publish pair) 2 2 failed | 141 skipped caught

Matrix failures: 0 — every mutation was caught; the adjacent green counts (20/2/636-skipped etc.) attribute each red to the mutated hunk. Positive control: every row is itself an expected-caught mutation and was caught.

Layered-guard analysis (no combination row needed, documented instead): the "workspace-only fields leaking into standalone creation" hazard is guarded by two hunks from different PRs — this PR's sessionPreparation omission and the base's actions.createSession throw. Wire cells H1/C1/H3 show they defend different call shapes: the PR hunk fixes the PR's own call shape (C1 shows the base shape misroutes silently), while the base throw only fires for callers that pass sessionContext and workspace fields together (loud reject). Neither is redundant: removing the PR hunk yields silent misroute, not a loud error.

Targeted gate

evidence/03-webshell-gate-head.png, raw log logs/gate-head.log: full web-shell unit suite at head — 242 files / 5497 tests, 0 failures (67.98 s). The PR body claims 5,290 on its own rebase; the merge head carries additional main-side tests, hence the higher count.

Reviewer Test Plan walk-through

step status at head
1. Global New task → exact standalone, context=standalone, no workspace param, appears in Recents verified at wire level (H1: exact standalone create, no workspace fields) and at App level (M2-targeted tests); URL handling (replaceStandaloneSessionUrl sets context, deletes workspace) covered by suite green, not mutated
2. No capability → legacy; loading/lookup failure → wait/fail closed verified: H4 (missing capability → SDK rejects, no create), M2 (gate load-bearing), WorkspaceSessionProvider "never calls a standalone route when the capability is absent" green; loading-toast arm covered by suite only
3. Recents lifecycle actions target exact standalone routes, children excluded covered by StandaloneRecents.test.tsx (suite green); not mutated — see Not covered
4. Standalone/Live hide workspace-only surfaces; session-supported commands/skills remain covered by App.test.tsx isolation tests (suite green, incl. the three M2-adjacent tests); not mutated
5. Deep links for active/archived/creating/missing/compromised/recreated/cleanup-pending/unknown; unarchive-before-attach; no auto-retry; no stale publish verified for lookup/archived/missing at wire + mutation level (M3, H-cells); compromised/recreated/cleanup-pending recovery states covered by suite green only
6. Project/Goals/Git/workspace links stay exact-workspace; public callback and root URL preserve context verified H2 (workspace create carries all fields) and "reports explicit non-workspace contexts to the host" green; Goals/Git flows covered by suite

Findings

No blocking findings. Two non-blocking observations:

  1. Base bug confirmed as silent (informational, this is what the PR fixes): C1 shows that before this PR a standalone product intent produced a workspace session bound to whatever cwd the caller held, with no error anywhere — the quiet variant of the bug class, now eliminated at head.
  2. Unarchive success comparison depends on the daemon echoing received ids (low, contract currently satisfied): StandaloneSessionGate compares result.unarchived.includes(sessionId.toLowerCase()); the SDK lowercases ids on send (standaloneBatchRequest) and standalone-session-service.ts#unarchiveMany pushes the id exactly as received, so both ends agree today. A future daemon that re-normalizes ids differently would make a successful unarchive read as a failure (error state, retryable) — fail-closed, not silent, so non-blocking.

Not covered

  • Per-commit attribution: the checkout is depth 2; the metadata lists 13 commits but only bdfffc2 is reachable (git rev-list HEAD^1..HEAD^2 = 1 at the shallow boundary). The aggregate HEAD^1..HEAD diff was verified; per-commit claims were not individually exercised.
  • Playwright E2E (client/e2e/web-shell.standalone.spec.ts, 66-test suite): no Playwright browser is installed in this container (~/.cache/ms-playwright absent); the PR's own CI runs it. The wire oracle covers the same route semantics at the HTTP level instead.
  • Real-daemon manual UI: all daemon interaction went through a loopback fake encoding the real route validation; no recording against a live daemon.
  • Unmutated surfaces: StandaloneRecents lifecycle menus, composer attachment clearing on context switch, and the compromised/recreated/cleanup-pending recovery states are covered only by the green suite — the suites pin them by construction, but no mutation run proved it here.
  • Windows/macOS: container is Linux only.

Methodology

Environment: CI verify container (node:22, merge-ref checkout, npm ci + npm run build pre-run at head). The wire oracle ran the real client chain (sessionPreparation → actions → SDK) in vitest/jsdom against a node:http loopback daemon whose validation rules were lifted verbatim from packages/cli/src/serve/routes/standalone-sessions.ts; requests were recorded and asserted per method/path/body. The mutation matrix mutated working-tree copies and restored from backups/ after each row (final git status clean). Raw logs in logs/, harnesses in harness/, captures in evidence/. Assertion counts: 51 wire-oracle expects + 5 matrix row assertions + 1 gate assertion = 57 executed, 57 passed, 0 failed.

Flakiness gate log

e2e suite, out of gate scope: packages/web-shell/client/e2e/web-shell.standalone.spec.ts
rounds=5 files=10 skipped=1
file packages/web-shell/client/App.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/App.test.tsx
file packages/web-shell/client/components/ChatEditor.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/ChatEditor.test.tsx
file packages/web-shell/client/components/WorkspaceSessionProvider.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/WorkspaceSessionProvider.test.tsx
file packages/web-shell/client/components/sidebar/StandaloneRecents.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/sidebar/StandaloneRecents.test.tsx
file packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx
file packages/web-shell/client/daemon/session/actions.test.ts: (cd packages/web-shell) npx --no-install vitest run ./client/daemon/session/actions.test.ts
file packages/web-shell/client/hooks/useComposerCore.dom.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/hooks/useComposerCore.dom.test.tsx
file packages/web-shell/client/main.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/main.test.tsx
file packages/web-shell/client/utils/sessionPreparation.test.ts: (cd packages/web-shell) npx --no-install vitest run ./client/utils/sessionPreparation.test.ts
file packages/web-shell/client/utils/standalone-session-routing.test.ts: (cd packages/web-shell) npx --no-install vitest run ./client/utils/standalone-session-routing.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/web-shell/client/App.test.tsx: PPPPP
  packages/web-shell/client/components/ChatEditor.test.tsx: PPPPP
  packages/web-shell/client/components/WorkspaceSessionProvider.test.tsx: PPPPP
  packages/web-shell/client/components/sidebar/StandaloneRecents.test.tsx: PPPPP
  packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: PPPPP
  packages/web-shell/client/daemon/session/actions.test.ts: PPPPP
  packages/web-shell/client/hooks/useComposerCore.dom.test.tsx: PPPPP
  packages/web-shell/client/main.test.tsx: PPPPP
  packages/web-shell/client/utils/sessionPreparation.test.ts: PPPPP
  packages/web-shell/client/utils/standalone-session-routing.test.ts: PPPPP

verdict: pass
summary: 10 changed test file(s) x 5 identical rounds, no divergence

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/ChatEditor.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/WorkspaceSessionProvider.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/sidebar/StandaloneRecents.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/daemon/session/actions.test.ts: P (exit 0)
round 1 · packages/web-shell/client/hooks/useComposerCore.dom.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/main.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/utils/sessionPreparation.test.ts: P (exit 0)
round 1 · packages/web-shell/client/utils/standalone-session-routing.test.ts: P (exit 0)
round 2 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/ChatEditor.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/WorkspaceSessionProvider.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/StandaloneRecents.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/daemon/session/actions.test.ts: P (exit 0)
round 2 · packages/web-shell/client/hooks/useComposerCore.dom.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/main.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/utils/sessionPreparation.test.ts: P (exit 0)
round 2 · packages/web-shell/client/utils/standalone-session-routing.test.ts: P (exit 0)
round 3 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/ChatEditor.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/WorkspaceSessionProvider.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/StandaloneRecents.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/daemon/session/actions.test.ts: P (exit 0)
round 3 · packages/web-shell/client/hooks/useComposerCore.dom.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/main.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/utils/sessionPreparation.test.ts: P (exit 0)
round 3 · packages/web-shell/client/utils/standalone-session-routing.test.ts: P (exit 0)
round 4 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/ChatEditor.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/WorkspaceSessionProvider.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/StandaloneRecents.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/daemon/session/actions.test.ts: P (exit 0)
round 4 · packages/web-shell/client/hooks/useComposerCore.dom.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/main.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/utils/sessionPreparation.test.ts: P (exit 0)
round 4 · packages/web-shell/client/utils/standalone-session-routing.test.ts: P (exit 0)
round 5 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/ChatEditor.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/WorkspaceSessionProvider.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/StandaloneRecents.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/daemon/session/actions.test.ts: P (exit 0)
round 5 · packages/web-shell/client/hooks/useComposerCore.dom.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/main.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/utils/sessionPreparation.test.ts: P (exit 0)
round 5 · packages/web-shell/client/utils/standalone-session-routing.test.ts: P (exit 0)

Evidence images

01-wire-oracle-ab

02-mutation-matrix

03-webshell-gate-head

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

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

What this PR does / PR 主旨

This PR makes standalone chats a first-class WebShell product surface: a capability-gated global "New task" that creates an exact standalone session (legacy primary-workspace route only when the capability is absent), a context-explicit deep-link router with a StandaloneSessionGate (bounded creating-poll, archived/unarchive, typed recovery states), top-level Recents with an archived lane + pagination and rename/export/archive/unarchive/delete actions, and product-context isolation (workspaceContextActive as the single source of truth) so workspace-only navigation, Git/settings, split view, attachments, uploads and Web Terminal are hidden for standalone/Live chats while session-supported commands, skills, Shell and tools remain.

本 PR 将 standalone chats 提升为 WebShell 一等公民:能力门控的全局 New task 精确创建 standalone 会话(能力缺失时才回退旧主工作区路由)、带上下文的深链路由与 StandaloneSessionGate(有界 creating 轮询、归档/解档、类型化恢复态)、顶层 Recents 归档泳道+分页与重命名/导出/归档/解档/删除,并以 workspaceContextActive 为单一真源做产品上下文隔离——standalone/Live 隐藏仅工作区可用的导航、Git/设置、分屏、附件、上传与 Web 终端,同时保留会话支持的命令、技能、Shell 与工具。


Fourth-round incremental re-review, run purely statically at head bdfffc26b489c48a9b37df77cae91bf1ef3d56b0 (no tests/builds executed). The branch was rebased again, so my previous head 49fad237e2 is not an ancestor of the current one — I re-verified every item against the code at this head, not by commit id. The increment under review is the author's four fix(web-shell): address PR review feedback commits (≈550 non-test lines across App.tsx / WorkspaceSessionProvider.tsx / StandaloneRecents.tsx / ChatEditor.tsx / WebShellSidebar.tsx).

Round-3 blockers — all five genuinely fixed at head / 上轮五条阻塞项逐条复验

  1. Pending-context re-arm flipping a settled standalone chat into workspace mode — FIXED. The cleanup effect is now gated by !workspaceContextActive (App.tsx:2721-2727): a settled standalone/live chat (effective context kind ≠ workspace) early-returns before the stale draft-pick re-arm can set pendingSessionContext = {kind:'workspace'}. Legitimate cleanup still runs whenever the effective context genuinely is workspace. Pinned by "does not retarget a settled standalone session when a stale workspace selection becomes untrusted".
  2. Unarchive handler missing the generation guard — FIXED. WorkspaceSessionProvider.tsx:576 captures generation at click; both .then (:580) and .catch (:598) return early when resolutionGenerationRef.current has advanced. The token is bumped in the effect body (:398-399), the cleanup (:456-460), and sessionId is an effect dep — so an A→B switch can never let a stale unarchive write B's resolution. Pinned by "ignores a stale unarchive result after navigating to another standalone session".
  3. Case-sensitive unarchive result comparison — FIXED. The compared value is normalized with requestedSessionId.toLowerCase() (:574-575) across unarchived/alreadyActive/errors[].sessionId, and a notFound branch was added; outbound is already lowercased by the SDK (DaemonClient.ts:2713/:2780). Pinned by "accepts normalized daemon ids when unarchiving a mixed-case deep link".
  4. Suggestion-accept wedge under lockedWorkspaceCwd — FIXED. mode now forces 'cleared' under a locked shell (App.tsx:9750-9755), matching the end state that createNewSession({kind:'inherit'}) produces (never startLive); every flush exit path resets isStartingNewSessionSuggestion. Pinned by "uses the locked workspace path when accepting a Live-context new-topic suggestion".
  5. New Chat during the startLive round trip — CLOSED. createNewSession and loadSidebarSession now share sessionOpenInvocationRef with per-invocation guards that reset stale writes, so a superseded request cannot leave the composer wedged. Pinned by "cancels an accepted Live new-topic suggestion when a newer global new-chat request wins" — and the drop in favour of the newer request is the intended behavior, not a leak.

Other changes reviewed / 其余改动的核对

  • The resolveWorkspaceMaintenanceTargetCwd / trustedPrimaryWorkspaceCwd trust filter, applied to the cron/goal/maintenance sites: the resulting silent no-op when only an untrusted primary resolves is a deliberate trust-boundary hardening and is explicitly tested ("does not create a goal session in an untrusted primary workspace", "does not start task creation in an untrusted primary workspace") — not a defect.
  • The splitClassificationGenerationRef bumps added to panel/navigation handlers are intended suppression of stale in-flight split classification, covered by "does not reopen a controlled split after exit while classification is pending" and "keeps controlled split ids while a standalone navigation can still fail".
  • Removing the key={sessionId} remount in favour of the resolutionSessionId + attachedSessionId render gate is sound: the provider's render-phase reset of attachedStandaloneSessionIdRef keeps the spinner up on a standalone→standalone switch, so I could not construct the one-paint stale-panel flash a fresh reading might suspect.
  • ChatEditor.tsx disableLegacyHistoryFallback now keys off composerScopeKey === 'standalone' instead of !workspaceFeaturesEnabled — more precise and consistent with Live scope keeping legacy history (pinned).

Non-blocking follow-ups (optional) / 不阻塞的小项(可选)

  • Optimistic rename (StandaloneRecents.tsx:531-553) sets both lists but a concurrent already-in-flight mergeRefreshedPage can overwrite it until the next load (self-heals; server is source of truth).
  • Round-3 residual unchanged: an archived-lane fetch failure still renders the empty state alongside the error toast (no failure flag). Cosmetic.

Verdict / 结论

💚 Approve. All five round-3 blockers are fixed with targeted, well-scoped changes and each is pinned by a dedicated test; the trust-boundary and split-suppression changes are intentional and tested. I found no new Critical and no surviving Important at this head, and the review bot independently approved bdfffc26b4. The two items above are optional polish, not gates.

结论:批准。上轮五条阻塞项全部以针对性改动修复且各有一致测试钉住;信任边界与分屏抑制改动均为有意且有测试覆盖。本 head 上我未发现新的 Critical,也没有经得起复核的 Important,审查机器人亦已独立批准 bdfffc26b4。上面两条为可选打磨,非门槛。

Process facts at publish time / 发布时事实: Test (ubuntu), Serve A/B and review-pr were still in progress and several checks from the pre-rebase head were cancelled by the push; no CI polling was performed and this round ran no tests or builds — purely static.

@doudouOUC
doudouOUC dismissed qwen-code-ci-bot’s stale review September 1, 2026 09:43

Already have 2 approves,3ks.

@doudouOUC
doudouOUC added this pull request to the merge queue Sep 1, 2026
Merged via the queue into main with commit 6e99d02 Sep 1, 2026
83 of 98 checks passed
@doudouOUC
doudouOUC deleted the docs/standalone-pr6-webshell-ui-plan branch September 1, 2026 09:44
wenshao added a commit that referenced this pull request Sep 1, 2026
Resolves the #10514 (standalone chats) overlap: the sidebar's
reworked workspace-header region keeps main's structure with this
branch's removal-controller names re-applied, the Manage workspaces
row and the opt-in footer entry move inside the new wrappers (the
footer entry now also honours projectFeaturesEnabled like its
peers), and the Workspaces panel's New task adapts to the
NewSessionIntent API through a stable per-workspace handler so the
primary row keeps creating a workspace draft rather than a
standalone chat.
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.

6 participants