Repository navigation
fix(webui): Make cross-session switching transactional - #8882
Conversation
E2E verification reportValidated the final PR head BaselineOn pre-change main, both action-driven and controlled A→B switches detached A, aborted its SSE, cleared its transcript, and exposed B/loading before B settled. Rejecting B left an empty B/error state, and a same-workspace controlled target remounted the session provider. Real-daemon scenarios
Result: 2/2 passed. Additional verification
The test environment emitted existing React |
175640d to
a6cdc44
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
Co-authored-by: Qwen-Coder <[email protected]>
Review follow-up
Verification on
|
|
Handled the second review round on head e3e0825.\n\n| Item | Disposition |\n| --- | --- |\n| R2-1 | Fixed: plan preparation uses an operation token; same-ID and A→B→A regressions covered. |\n| R2-2 | Fixed: model select/delete share an operation token; same-ID and ABA regressions covered. |\n| R2-3 | Fixed: blocked turn-error retry no longer consumes its latch and remains usable after unblock. |\n| R2-4 | Fixed: same-logical legacy loads cannot restart the runner while a transactional target is pending. |\n| R2-7, R2-8, R2-10, R2-11, R2-12, R2-13 | Deferred as Suggestions under the repository policy for mature PRs after roughly five review rounds; each thread records the focused follow-up scope. |\n\nVerification:\n- WebUI actions + Provider: 291 tests passed.\n- WebShell App + WorkspaceSessionProvider: 380 tests passed.\n- Six focused same-ID/ABA/write-block regressions passed.\n- Independent Provider probe confirmed A stays connected, B stays preparing, and only B restore runs.\n- WebUI and WebShell typecheck passed.\n- WebUI build, affected ESLint, formatting, and diff checks passed.\n- Two consecutive clean audits found no remaining actionable correctness issue. |
yiliang114
left a comment
There was a problem hiding this comment.
LGTM. The transactional core holds up under an adversarial pass: commitCrossSession is a fully synchronous ownership swap (microtask-batched store notification, one React batch — no observer-visible intermediate state), the arbitration (intent identity, lifecycle, env generation, deadline, source logical identity) re-runs inside the commit with no await between staging and commit, discard paths only retire the candidate without touching the current session's store/refs/stream, and the coordinator serializes with latest-queued-only and identical-target coalescing. No sessionId-only ownership is reintroduced, double-commit and commit-after-discard are structurally impossible, and all prior rounds' Criticals (round-2's seven, round-3's four, plus round-1's 89 broken unit tests) are verifiably fixed at head with 96 new cases and a real-daemon E2E. CI green on head.
Two P2 hardening items for fast-follow, not blockers: (1) the pump accepts a candidate attachment whose clientId differs from the requested id (DaemonSessionProvider.tsx:3440-3448 never compares candidate.clientId to requestClientId, and persistStableClientId persists the echoed one) — that weakens the #8833 exact-attachment fencing to 'some attachment identity' if a daemon ever echoes a different id; reject or persist the requested id and pin with a test. (2) 'bounded staging' only bounds side queues: the staging store is created with maxBlocks MAX_SAFE_INTEGER and the replay is synchronous, so an incident-class 76 MiB target transiently holds ~3x block arrays and blocks the main thread; deliberate and pinned by tests, not a regression vs legacy, but cap staging at maxBlocks or chunk across macrotasks and document the peak cost. Plus the known-deferred P3s (silent pump stall when client_identity capability is missing at pump time until the watchdog fires; releaseSession only cancels a pending transition when the current session matches). Ship it.
|
@qwen-code /triage |
Co-authored-by: Qwen-Coder <[email protected]>
e3e0825 to
824dfa5
Compare
|
Rebased the PR onto current main at a863f6a and resolved the single semantic conflict in the session-actions test harness by preserving both upstream restore-session injection and this PR's prompt-status injection.\n\nPost-rebase verification on head 824dfa5:\n- WebUI actions + Provider: 292 tests passed.\n- WebShell App + WorkspaceSessionProvider + queued-prompts DOM: 423 tests passed.\n- WebUI and WebShell typecheck passed.\n- WebUI build, affected ESLint, conflict-marker scan, formatting, and diff checks passed.\n- Range-diff and two clean audits found no unintended semantic change. |
Co-authored-by: Qwen-Coder <[email protected]>
824dfa5 to
5651663
Compare
|
Rebased again onto current main ( The integration audit found and fixed three merge-specific Critical races: a stable-ID mid-turn payload can no longer disappear during same-session reattachment; old-owner reconciliation cannot write into the replacement attachment or fall through after the transition write gate closes; and pending payloads remain scoped to their workspace across A→B→A while authoritative snapshots/events retire them so they cannot be resurrected. Post-rebase verification on head
|
ytahdn
left a comment
There was a problem hiding this comment.
Review — transactional cross-session switching
Reviewed range 1a2c5026b..5651663d8 (4 commits). The transactional-switch machinery is well-engineered and holds up under close inspection: the commit is a single synchronous ownership handoff (re-validating desired-intent identity / lifecycle / environment generation / absolute deadline immediately before install), A's buffered events cannot contaminate B's store, late results are fenced by exact attachment + environment identity, unmount fencing is careful, and the WebShell owner-guard sweep is consistent across navigation paths. No Critical issues found.
One Important and three Minor items are inline:
- actions.ts:1549 (Important) —
branchSession's in-flight flag is cleared only when the raw request settles;withActionTimeoutdoes not abort it, so a slow branch blocks everyrequireStableSessionaction (prompt / model / rename / recap …) for up to the SDK's 30 s fetch timeout. Clear the flag in a whole-operationfinally. - useQueuedPrompts.ts:625 — dropped mid-turn admissions leak in
pendingMidTurnAdmissionsRef(one entry per interrupted mid-turn submission, app lifetime). - ChatPane.tsx:483 — this
useQueuedPromptsinstance lackswriteBlocked; mid-switch submits fail at the actions layer and land in the queue as failed instead of being rejected at the composer. - DaemonSessionProvider.tsx:3300 — a throwing
onSessionTransitionCommitis swallowed after B is installed; the self-heal-on-next-render contract deserves a comment or a retry on failure.
Scope note: the acp-bridge / cli-serve / SDK daemon changes described in the PR body are not part of this diff (they landed in the base via #8691/#8833); this review covers the 23 files actually changed here.
中文说明
审查范围 1a2c5026b..5651663d8(4 个 commit)。事务化切换机制本身扎实:commit 是单次同步 ownership 交接(安装前重新校验 desired-intent 身份 / 生命周期 / 环境代数 / 绝对截止时间),A 的缓冲事件不会污染 B 的 store,迟到结果按精确 attachment + 环境身份隔离,卸载 fencing 严谨,WebShell owner-guard 扫描一致。无 Critical。
Important 1 项 + Minor 3 项见行内评论:branchSession in-flight 标志的无限期延迟耦合;pendingMidTurnAdmissionsRef 泄漏;ChatPane 未传 writeBlocked;抛异常的 onSessionTransitionCommit 被吞。另说明:PR 描述中提到的 acp-bridge / cli-serve / SDK daemon 改动不在本 diff 内(经 #8691/#8833 已合入 base),本审查只覆盖实际改动的 23 个文件。
yiliang114
left a comment
There was a problem hiding this comment.
Re-approving at head 5651663 after the two rebases: commitCrossSession is byte-identical to the head I approved, the arbitration/discard/coordinator code is untouched, no tests were removed, and the genuine new work checks out — the O(B) single-dispatch replay (fixing my P2-2's multiplicative transient allocation, with a batch-size regression test), the pendingMidTurnAdmissionsRef ledger with workspace-scoped owner-token identity and fenced old-owner reconciles (14 new tests), and the #8883 adaptation clearing failed loads without auto-fresh attach.
Remaining, all bounded fast-follows: my P2-1 (candidate clientId still accepted on truthiness without equality vs requestClientId, and commit persists the candidate's id) is open but re-scoped — types.ts now documents clientId as not an ownership boundary for stable-id mid-turn queues and the design doc defers client-id replacement; still a cheap unperformed check. New P2 from ytahdn verified: branchSession's setSourceBoundOperationInFlight clears only when the raw branch request settles and withActionTimeout is a Promise.race, so a hung branch serializes every requireStableSession action for up to the 30s fetch timeout — a whole-operation finally fixes it. P3s: pendingMidTurnAdmissionsRef never deletes non-matching entries on post-switch rejection (one orphaned entry per interrupted submission), ChatPane's useQueuedPrompts lacks writeBlocked so mid-switch pane submits land as failed queue entries instead of composer rejection, and a throwing onSessionTransitionCommit after B install self-heals one frame late. CI green on head, identical skip profile to the old head. Ship it with the fast-follows.
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
ytahdn
left a comment
There was a problem hiding this comment.
✅ Approved
Approved after the review discussion resolved on the four inline threads: three suggestions were withdrawn (the branch in-flight interlock, the mid-turn admission retention, and the commit-observer boundary are all intentional and correctly reasoned), and the remaining ChatPane writeBlocked follow-up is accepted as a tracked deferral per the review-round policy. No blockers remain.
Note: Test (macos/windows) and Integration Tests (CLI, No Sandbox) are skipped on this head (the existing repo-wide pattern); the jobs that ran — Test (ubuntu), Desktop Shell (ubuntu/windows), web-shell E2E Smoke, visuals — are green.
中文说明
审查通过。4 条行内评论线程已闭环:3 条撤回(branch in-flight 互锁、mid-turn admission 保留、commit observer 边界均为有意设计且论证成立),剩余 ChatPane writeBlocked 一条作为延后项记录(符合审查轮次策略)。无遗留 blocker。说明:head 上 macos/windows 测试与 Integration Tests 被跳过(仓库既有模式),实际运行的 ubuntu Test、Desktop Shell、web-shell E2E 与 visuals 均绿。
Co-authored-by: Qwen-Coder <[email protected]>
5651663 to
b700a4c
Compare
|
Conflict resolution update:
The PR is mergeable with no unresolved current review threads. Checks for the rebased SHA are still finishing; the zero-duration red entries belong to a superseded workflow run that GitHub marked |
ytahdn
left a comment
There was a problem hiding this comment.
✅ Approved (re-approved after rebase)
Re-reviewed the updated head b700a4cef (rebase onto the newer main + one new commit):
- The rebase is content-clean — every PR-specific file (
DaemonSessionProvider,actions,types,useQueuedPrompts,WorkspaceSessionProvider,useBackgroundTasks,useSessionArtifacts,midTurnDedup, the daemon side channels, integration test, design doc) is byte-identical to the previously approved head; the diff on the sharedApp.tsx/ChatPanefiles is entirely the newer base's own progress (#8891/#8914), not conflict-resolution churn. - The new commit
b700a4cefis sound — it moves theowner.isCurrent()gate in the rename flow to run after the catalog reconciliation and before the local status notice / error report, so a confirmed server-side rename still reconciles into the catalog after its source attachment was replaced, with a dedicated regression test (reconciles a confirmed rename after its source attachment is replaced). Matches its stated purpose. - Prior discussion conclusions are unchanged: three suggestions withdrawn (branch in-flight interlock, mid-turn admission retention, commit-observer boundary), one accepted as a tracked deferral (
ChatPanewriteBlocked).
CI on the new head: no failures (checks re-running after the rebase). The Test (macos/windows) / Integration Tests (CLI, No Sandbox) skip pattern remains the pre-existing repo-wide one; Test (ubuntu), Desktop Shell, web-shell E2E Smoke, and visuals were green.
中文说明
已针对更新后的 head b700a4cef(rebase 到新 main + 一个新提交)重新审查后再次通过:
- rebase 内容干净——本 PR 专属文件(DaemonSessionProvider、actions、types、useQueuedPrompts、WorkspaceSessionProvider、useBackgroundTasks、useSessionArtifacts、midTurnDedup、daemon 侧通道、集成测试、设计文档)与此前 approved 的 head 逐字节一致;共享文件(App.tsx/ChatPane)的差异全部来自新 base 自身的进度(#8891/#8914),不是 rebase 冲突改动。
- 新提交 b700a4c 合理——把 rename 流程中的
owner.isCurrent()门控移到 catalog reconcile 之后、本地状态通知/错误上报之前:确认过的服务端 rename 在 source attachment 被替换后仍会 reconcile 进 catalog,并有专属回归测试。与提交目的一致。 - 此前的讨论结论不变:3 条撤回(branch in-flight 互锁、mid-turn admission 保留、commit observer 边界),1 条接受为延后项(ChatPane
writeBlocked)。
新 head 的 CI 无失败(rebase 后检查正在重跑);macos/windows 与 Integration Tests 的跳过仍是仓库既有模式,实际运行的 ubuntu Test、Desktop Shell、web-shell E2E 与 visuals 此前均绿。
|
@qwen-code /triage |
…he loading-skeleton model (QwenLM#9129) The transactional cross-session switching from QwenLM#8882 staged a handoff and kept the old attachment live until the target load committed. It added a large transition state machine (intent staging, same-session capture, watchdog deadlines, controlled rebind) across the daemon session layer and the web-shell provider, and left the UI pinned to the previous session while a switch prepared. Restore the loading-skeleton model: switching a session clears the transcript, shows the loading skeleton, and waits for the load result. - Remove sessionTransition state, onSessionTransitionCommit and the transactional target logic from WorkspaceSessionProvider. - Strip the transition state machine from DaemonSessionProvider and restore single-session restores: restore_in_progress retries stay bounded by the existing watchdog, and the skeleton UI keys on loadingTranscript. - Move useDaemonSessionOwnerGuard back under the daemon index export. - Delete the transactional design docs and both daemon integration tests; the restored behavior is covered by unit tests. - Drop the dead desiredSessionTargetPending prop (write gating now keys on loadingTranscript alone) and stop a failed switch's target workspace from leaking into the next workspace-less load. Co-authored-by: 钉萁 <[email protected]> Co-authored-by: Qwen-Coder <[email protected]>
What this PR does
This PR makes modern WebUI load/resume switches to a different logical session transactional. The current session remains the visible owner while the target restores and is replayed into an isolated, bounded staging store; only a fully staged target that still wins the lifecycle/deadline arbitration is committed. Failed, timed-out, superseded, malformed, or stale targets are discarded and detached best-effort without clearing the current transcript, stopping its event stream, or replacing its connection state.
The restore coordinator permits one ordinary restore RPC at a time, coalesces identical targets, retains only the latest queued target, and fences late results by exact attachment and environment identity. Commit installs the target transcript, session/client/workspace references, connection state, staged notices and side channels in one synchronous ownership handoff before the public load promise resolves, then starts the prepared target runner without issuing a second restore.
The main WebShell provider now stays mounted across modern controlled session/workspace changes. Navigation paths share transactional ownership fences and write gates, preserve source metadata while a target is pending or fails, hide stale metadata on the first committed target frame, and delay the scheduled-run catch-up timeout until restore has committed. Daemons that explicitly lack
client_identitykeep the existing keyed, detach-first compatibility behavior; unknown capabilities and malformed modern ownership fail closed.Why it's needed
Before this change, selecting another session immediately detached the current session, aborted its event stream, cleared its transcript, and published the target as loading before the target restore had succeeded. A large-session timeout or other restore failure therefore left the user on an empty/error target and stopped the conversation that had been usable. PR #8691 made restore deadlines safe and observable, and PR #8833 fenced late attachment work; this PR adds the missing client-side transaction boundary for ordinary and controlled cross-session load/resume.
Reviewer Test Plan
How to verify
Start with session A connected and containing a visible transcript, then delay the completed response for loading session B. Confirm that A stays connected, retains its transcript, continues receiving live events, and still accepts existing control operations while B is pending. Release B and confirm that the first committed state consistently contains B's session, client, workspace, and replay, with A detached only after commit.
Repeat with B returning a structured HTTP 504 and with B being superseded by later targets. Confirm that A remains usable after failure, only one ordinary restore is in flight, an intermediate queued target is never sent, and a late result cannot replace the latest owner. For a controlled workspace target, confirm that unresolved/failed workspace resolution keeps A mounted and rolls the host back once without a retry loop.
The focused regression suites cover WebUI restore arbitration, pure staging, attachment cleanup, controlled transitions, WebShell owner fences, navigation, queued prompts, background tasks, and artifacts. The real-daemon integration test exercises delayed-success and structured-504 scenarios through
qwen serve.Evidence (Before & After)
Before: deterministic baseline runs on pre-change
mainshowed action-driven and controlled switches detach A, abort A's stream, clear its transcript, and expose B/loading before B settled; a rejected B left an empty B/error state. A same-workspace controlled target also remounted the session provider.After: the real-daemon integration test completed B's restore but withheld the response for about five seconds. During that gate A remained connected, accepted a control request, and received a new live event; releasing the response atomically installed B. A structured B restore 504 preserved A and surfaced
session_restore_timeoutwith HTTP status 504. Focused verification passed 279 WebUI action/provider tests and 418 affected WebShell tests; repository build, typecheck, bundle, and focused ESLint also passed.Tested on
Environment (optional)
macOS Darwin 25.4.0 arm64, Node.js v22.22.3, npm 10.9.8. Real-daemon integration used
QWEN_SANDBOX=falsewith the builtqwen servebundle and a mock ACP child.Risk & Scope
client_identityretain the prior destructive switching behavior.Linked Issues
Refs #8678
中文说明
本 PR 做了什么
本 PR 将现代 WebUI 中切换到不同逻辑会话的 load/resume 改为事务化。目标会话恢复并在隔离、有界的 staging store 中重放期间,当前会话仍然是可见 owner;只有完成全部 staging 且仍通过生命周期/截止时间仲裁的目标才会提交。失败、超时、被替换、格式错误或过期的目标会被丢弃并尽力 detach,不会清空当前 transcript、停止其事件流或替换其连接状态。
restore coordinator 同时只允许一个普通 restore RPC,合并相同目标,只保留最新排队目标,并使用精确 attachment 与环境 identity 隔离迟到结果。commit 在公开 load promise resolve 前,通过一次同步 ownership handoff 安装目标 transcript、session/client/workspace 引用、connection state、已 staging 的 notices 和 side channels,然后直接启动准备好的目标 runner,不会再次发起 restore。
现代受控 session/workspace 变化期间,主 WebShell provider 现在保持挂载。各导航入口共享事务化 ownership fence 和写入 gate,目标 pending 或失败时保留源会话 metadata,目标提交的第一帧隐藏过期 metadata,并将 scheduled-run 的 catch-up timeout 延后到 restore commit 之后。明确缺少
client_identity的 daemon 继续使用现有 keyed、detach-first 兼容行为;capability 未知和现代 ownership 格式错误则 fail-closed。为什么需要
变更前,选择另一个会话会在目标 restore 成功之前立即 detach 当前会话、终止其事件流、清空 transcript,并把目标发布为 loading。因此大型会话超时或其他 restore 失败会让用户停留在空白/错误目标,同时停止原本可用的会话。PR #8691 已使 restore deadline 安全且可观测,PR #8833 已隔离迟到 attachment 工作;本 PR 补上普通及受控跨会话 load/resume 所缺少的客户端事务边界。
Reviewer 测试计划
如何验证
先连接 session A 并确保存在可见 transcript,然后延迟 session B 已完成 load 的响应。确认 B pending 期间 A 仍保持 connected、保留 transcript、继续接收实时事件,并且现有控制操作仍能使用。释放 B 后,确认第一个 committed 状态中的 session、client、workspace 和 replay 全部一致属于 B,并且 A 只在 commit 后才 detach。
再让 B 返回结构化 HTTP 504,并让 B 被后续目标替换。确认失败后 A 仍可用、同时只有一个普通 restore 在运行、中间排队目标不会发送、迟到结果不能替换最新 owner。对于受控 workspace 目标,确认 workspace 解析未完成或失败时 A 保持挂载,host 只回滚一次且不会形成重试循环。
focused regression suites 覆盖 WebUI restore 仲裁、pure staging、attachment 清理、受控切换,以及 WebShell owner fence、导航、queued prompts、background tasks 和 artifacts。真实 daemon integration test 通过
qwen serve覆盖 delayed-success 与结构化 504 场景。证据(变更前后)
变更前:在变更前
main上的确定性 baseline 运行表明,action-driven 和 controlled 切换会在 B settle 前 detach A、终止 A 的 stream、清空 transcript 并显示 B/loading;B reject 后停留在空白 B/error 状态。同 workspace 的受控目标还会 remount session provider。变更后:真实 daemon integration test 完成了 B restore,但将 response 暂停约五秒。在 gate 期间 A 保持 connected、接受 control request 并收到新的 live event;释放 response 后原子安装 B。B 的结构化 restore 504 保留 A,并显示
session_restore_timeout与 HTTP 504。focused verification 通过 279 个 WebUI action/provider 测试和 418 个受影响 WebShell 测试;仓库 build、typecheck、bundle 与 focused ESLint 也全部通过。测试平台
环境(可选)
macOS Darwin 25.4.0 arm64,Node.js v22.22.3,npm 10.9.8。真实 daemon integration 使用
QWEN_SANDBOX=false、已构建的qwen servebundle 和 mock ACP child。风险与范围
client_identity的 daemon 保留原有 destructive switching 行为。关联 Issue
Refs #8678