feat(web-shell): Add standalone chats - #10514
Conversation
|
Gate: pass (re-run) — docs-only plan, template complete, no duplicate.
中文说明门禁:通过(re-run)—— 纯文档计划,模板完整,无重复。
— Qwen Code · qwen3.8-max Reviewed at |
|
Re-run: plan re-verified against current Still accurate on current
Findings (non-blocking, freshness — the plan was accurate when written):
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
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:已对照当前 在当前 发现(不阻塞,属时效性问题——计划写就时这些信息是准确的):
两项发现均不改变计划设计,且计划文档中的行号本就随时效漂移——提出来供作者刷新锚点(或实现者直接忽略行号、按符号定位)。 开放决策(与上次运行一致): 从 Live 会话发起当前会话 New Chat 映射为 standalone。理由成立,但属产品判断——在 PR6 实现开始前标注以待 maintainer 确认,不阻塞计划合并。 测试证据: 来自本提交( 真实场景测试: N/A——纯文档变更,无用户可见行为可驱动(且本次为无人值守 CI 运行)。 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — re-verified clean on current Verdict: approve. The re-run changed my read of the details, not of the PR:
The prior run's approval is already pinned to exactly this commit ( @doudouOUC — still a solid, well-grounded plan; if you get a chance, refresh the provider-location references and the 中文说明置信度:4/5 —— 已在当前 结论:批准。 re-run 改变的是我对细节的判断,而不是对这个 PR 的判断:
上次运行的批准已精确钉在本提交( @doudouOUC —— 依然是扎实、有据可依的计划;如有时间,请在开始 PR6 之前按切换后的代码树刷新 provider 位置引用与 — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
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 /triage |
|
Sandboxed verification: This PR changes documentation/assets only — there is no code to execute, so a sandboxed verification has nothing to verify. 中文 — 判定:
|
|
Triage re-run completed without a new review. The bot already has a review of its own on 机器人在 The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
doudouOUC
left a comment
There was a problem hiding this comment.
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.
doudouOUC
left a comment
There was a problem hiding this comment.
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.
|
@qwen-code /triage |
chiga0
left a comment
There was a problem hiding this comment.
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
34a6e918b6→feat(cli): Add standalone daemon session API (#10179)✓ - PR4
7357136dd1→feat(sdk): Add standalone session APIs (#10294)✓ - PR5
ac761e11c2→feat(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;compromisedis 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
workspaceCwdentirely ✓ - P2: archived standalone lane added to lazy Archived section with explicit refresh ✓
- P2: partial-success batch envelopes interpreted per session ID ✓
- P2:
creatingdeep 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
left a comment
There was a problem hiding this comment.
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.
doudouOUC
left a comment
There was a problem hiding this comment.
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.
doudouOUC
left a comment
There was a problem hiding this comment.
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.
doudouOUC
left a comment
There was a problem hiding this comment.
Two remaining context-isolation gaps need to be explicit before implementation.
|
@qwen-code /triage |
doudouOUC
left a comment
There was a problem hiding this comment.
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.
|
@qwen-code /triage |
|
Sandboxed verification: This PR changes documentation/assets only — there is no code to execute, so a sandboxed verification has nothing to verify. 中文 — 判定:
|
|
Sandboxed verification: This PR changes documentation/assets only — there is no code to execute, so a sandboxed verification has nothing to verify. 中文 — 判定:
|
chiga0
left a comment
There was a problem hiding this comment.
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.
…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
left a comment
There was a problem hiding this comment.
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.
cb521a1 to
bdfffc2
Compare
|
Rebased PR #10514 onto
Resolved 0/0 newly handled review threads in this cycle; all 142 existing review threads remain resolved. |
Round 3 — re-validated on
|
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 unlessworkspaceContextActive. This closesR2-1at the source. I had already shown R2-1 was not reachable from the UI, and it is still clean on the new head: withws2armed, removed by another client, and the removal genuinely observed by the page, workspace-only surfaces stay at zero and/newstill issuesPOST /standalone/sessionswith zeroPOST /session. The guard is a correct belt-and-braces; the legitimate fallback path is preserved by construction, sinceworkspaceContextActiveis true exactly when the picker is armed.WorkspaceSessionProvider.tsx:571— the generation guard on the unarchive completion is theR2-2fix; 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) andApp.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
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,我又用解决冲突后与当前 main(cf6c9bbec2)的合并结果构建了第三个 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/sessions且POST /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 仍需在该功能对外宣布之前落地。
chiga0
left a comment
There was a problem hiding this comment.
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', ...)— sendssessionId = "Standalone-A", daemon returns lowercase"standalone-a"in theunarchivedarray; asserts the provider reachesreadystate without remounting. Pins R2-21 case-normalization.it('ignores a stale unarchive result after navigating to another standalone session', ...)— suspends the unarchive promise, navigates tostandalone-bwhile unarchive forstandalone-ais in flight, then resolves; assertsstandalone-a's result is discarded andstandalone-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', ...)— withlockedWorkspaceCwdset, accepts anew_sessionsuggestion; assertsstartLivewas NOT called andsendPromptwas called instead. Pins R2-3.it('cancels an accepted Live new-topic suggestion when a newer global new-chat request wins', ...)—createNewSessionraces with an in-flightstartLive; assertssendPromptwas 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.
|
@qwen-code /triage |
|
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 reportPR 10514 — feat(web-shell): Add standalone chatsVerdict: 中文摘要
Central claim and A/B proofCentral claim: with The wire oracle (
Witness: Secondary claim — deep-link gate: Secondary claim — capability tri-state at App level: mutation M2 (standalone branch of Mutation / vacuity matrix
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 Targeted gate
Reviewer Test Plan walk-through
FindingsNo blocking findings. Two non-blocking observations:
Not covered
MethodologyEnvironment: CI verify container ( Flakiness gate logEvidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
ytahdn
left a comment
There was a problem hiding this comment.
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 / 上轮五条阻塞项逐条复验
- 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 setpendingSessionContext = {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". - Unarchive handler missing the generation guard — FIXED.
WorkspaceSessionProvider.tsx:576capturesgenerationat click; both.then(:580) and.catch(:598) return early whenresolutionGenerationRef.currenthas advanced. The token is bumped in the effect body (:398-399), the cleanup (:456-460), andsessionIdis 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". - Case-sensitive unarchive result comparison — FIXED. The compared value is normalized with
requestedSessionId.toLowerCase()(:574-575) acrossunarchived/alreadyActive/errors[].sessionId, and anotFoundbranch 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". - Suggestion-accept wedge under
lockedWorkspaceCwd— FIXED.modenow forces'cleared'under a locked shell (App.tsx:9750-9755), matching the end state thatcreateNewSession({kind:'inherit'})produces (neverstartLive); every flush exit path resetsisStartingNewSessionSuggestion. Pinned by "uses the locked workspace path when accepting a Live-context new-topic suggestion". - New Chat during the
startLiveround trip — CLOSED.createNewSessionandloadSidebarSessionnow sharesessionOpenInvocationRefwith 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/trustedPrimaryWorkspaceCwdtrust 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
splitClassificationGenerationRefbumps 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 theresolutionSessionId+attachedSessionIdrender gate is sound: the provider's render-phase reset ofattachedStandaloneSessionIdRefkeeps 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.tsxdisableLegacyHistoryFallbacknow keys offcomposerScopeKey === '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-flightmergeRefreshedPagecan 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.
Already have 2 approves,3ks.
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.






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_v1is 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
sessionContextfor 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
standalone_sessions_v1, use global New task and confirm one exact standalone session is created, the URL carriescontext=standalone, no workspace parameter is added, and the session appears under top-level Recents.Evidence (Before & After)
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
Environment (optional)
macOS with Node.js 22.22.3, Vitest, Playwright Chromium, and the deterministic mock daemon. Repository
npm run buildandnpm run typecheckpassed 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
Linked Issues
Refs #8908
中文说明
本 PR 内容
按照已通过评审的 PR6 方案完成实现,使 standalone 聊天成为 WebShell 中的一等产品上下文。当 daemon 提供
standalone_sessions_v1capability 时,全局 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 状态。
评审验证方式
如何验证
standalone_sessions_v1的 daemon 上使用全局 New task,确认只创建一个精确 standalone 会话,URL 包含context=standalone,不附带 workspace 参数,并且会话出现在顶层 Recents 中。证据(改动前后)
自动化证据:完整 WebShell 单元覆盖运行通过 5,290 项测试;完整 Playwright 套件通过 66 项,另有 1 项预期跳过;rebase 到当前
main后再次通过 941 项关键单元测试以及全部 6 项 standalone/split E2E 测试。测试平台
环境(可选)
macOS、Node.js 22.22.3、Vitest、Playwright Chromium,以及确定性的 mock daemon。最终 rebase 后,仓库级
npm run build和npm run typecheck均通过;WebShell lint 零告警,所有本次改动文件均通过 Prettier。package 全量 format check 仍报告 9 个未改动的历史文件,因此本 PR 不会为格式化而重写无关代码。风险与范围
关联问题
Refs #8908