feat(serve): support runtime workspace removal - #6745
Conversation
Local verification reportValidated on macOS with Node.js v22.22.3 after rebasing onto
The focused coverage includes capability/removable metadata, primary/static protection, busy and force removal, persistence alias deletion and rollback, post-commit cleanup convergence, add/remove serialization, channel-worker routing and pidfile convergence, bridge shutdown reason/memoization/re-entry, ACP/memory drain behavior, SDK DELETE serialization, untrusted Web Shell removal, busy activity rendering, force guard, mismatch reconciliation, and same-path runtime recreation. Not run locally: the full sandbox-none CLI/interactive integration suites, manual Windows/Linux verification, live external-model integration, and browser screenshot E2E. The Qwen review plugin was started but produced no result within 60 minutes and was terminated; two open-ended manual audits completed with no remaining actionable findings. |
|
Re-run triage after 20+ review feedback commits. Template ✓ — all required sections present with thorough bilingual reviewer test plan. Problem: real operational gap. Dynamically registered workspaces had no runtime removal path — only restart could unload them. This is a natural lifecycle completion for the dynamic registration feature shipped in earlier PRs. Not theoretical. Direction: aligned. Workspace runtime removal is a necessary companion to dynamic workspace addition (#6716, #6635). The capability advertisement, optional Size: ~2,496 production additions, ~396 deletions (2,892 total). ~3,192 test lines. ~75 docs lines. No core paths touched — all changes are in Approach: the scope is driven by the number of subsystems participating in workspace teardown (sessions, prompts, ACP, memory, channel workers, bridge, persistence). Each subsystem needs its own drain/rollback/cleanup adapter. The scope feels proportional — not scope creep. The one-line import fix for the rebased branch is called out transparently in the PR body. Moving on to code review. 🔍 中文说明在 20 多条 review 反馈提交后重新进行 triage。 模板 ✓ — 所有必需部分齐全,包含详尽的双语 reviewer 测试计划。 问题:真实的运维缺口。动态注册的 workspace 没有运行时移除路径——只能重启 daemon 才能卸载。这是早期 PR 中已交付的动态注册功能的自然生命周期补全,非理论性问题。 方向:对齐。workspace 运行时移除是动态添加(#6716、#6635)的必要配套。capability 发布、可选 规模:约 2,496 行生产代码新增,约 396 行删除(共 2,892 行)。约 3,192 行测试。约 75 行文档。未触及核心路径——所有变更位于 方案:范围由参与 workspace 拆卸的子系统数量决定(session、prompt、ACP、memory、channel worker、bridge、persistence),每个子系统需要自己的 drain/rollback/cleanup 适配器。范围与目标成比例,非 scope creep。rebase 后分支所需的一行 import 修复已在 PR 正文中透明说明。 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal: I would have added a Comparison with the diff: The PR's approach matches and exceeds this baseline. Key design choices are sound:
No critical correctness issues found. Reuse check: reuses existing Build & Test ResultsReal-Scenario TestingStarted daemon from PR bundle, exercised removal flow end-to-end:
Removal response: 中文说明代码审查PR 方案匹配并超越了独立基线。关键设计合理:按 cwd 序列化、两阶段 drain 带回滚、持久化作为提交点、WeakMap memoize 防止双重清理、bridge shutdown reason 传播。未发现关键正确性问题。 构建与测试全部通过:build、typecheck、lint 以及 979 个单元测试。 真实场景测试从 PR bundle 启动 daemon,端到端验证:capabilities 发布 — Qwen Code · qwen3.7-max |
|
Re-run after the author addressed 20+ rounds of review feedback. Everything checks out. The PR fills a real operational gap — dynamic workspace addition without runtime removal leaves operators stuck restarting the daemon. The implementation handles the hard parts correctly: concurrent mutation serialization, two-phase drain with rollback, persistence-as-commit-point, memoized cleanup, and clean subsystem-specific drain adapters. Scope is large (~2,900 production lines) but proportional — each subsystem owning workspace-scoped state needs drain/cleanup integration. Test coverage (979 tests, 13 files, all passing) matches the complexity. Real-scenario testing confirms the end-to-end flow works. Previous Approval guardrail: 中文说明在作者处理了 20 多轮反馈后重新 triage。一切通过。 PR 填补了真实运维缺口。实现正确处理了并发变更序列化、两阶段 drain、持久化提交点、memoize 清理和子系统级适配器。规模较大但与目标成比例。979 个测试全部通过。真实场景测试确认端到端流程正常。之前的 CHANGES_REQUESTED 已解决。批准 ✅ — Qwen Code · qwen3.7-max |
|
⏸️ Deferring to maintainer for final review. The triage pipeline completed all three stages with no blocking findings — code review clean, 907 tests pass, build/typecheck/lint all green, direction aligned with the workspace lifecycle roadmap. The implementation is sound and the recommendation is to approve. However, the PR touches ~2,300 production lines across 5 packages in serve infrastructure, which the Stage 0 gate flags for maintainer awareness. A human should verify the architectural choices around the two-phase drain, registry state machine (active → draining → removed), and the cross-package contract before merge. @doudouOUC — thanks for the thorough PR and design doc. This is queued for maintainer review. 中文说明⏸️ 转交 maintainer 进行最终审查。 Triage 管线三个阶段全部完成,未发现阻塞问题——代码审查无问题,907 个测试通过,build/typecheck/lint 全部通过,方向与 workspace 生命周期路线图一致。实现合理,建议批准。 但 PR 涉及跨 5 个包的 serve 基础设施约 2,300 生产行,Stage 0 门禁标记需 maintainer 关注。建议人工验证两阶段 drain、registry 状态机(active → draining → removed)和跨包契约的架构选择。 — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
|
@qwen-code /resolve |
|
Qwen Code resolved the merge conflicts and pushed the branch update. Merge conflict resolution summary — PR #6745Conflicted files (3)1.
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
|
@qwen-code /resolve |
a0b518d to
8c6489a
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)为单个提交。 |
|
Qwen Code resolved the merge conflicts, but the head branch changed while resolving, so the update was not pushed. Re-run /resolve. The resolved diff is attached as the Merge Conflict Resolution SummaryPR: #6745 — feat(serve): support runtime workspace removal Conflicted File
ConflictBoth branches added new error handlers at the top of
ResolutionKept both sides — all four error handlers are independent and non-overlapping. The PR's |
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
wenshao
left a comment
There was a problem hiding this comment.
Review Summary
One new Critical finding plus four Suggestions. Build and typecheck pass cleanly. Full test coverage verified (55 behaviors).
Critical (1)
workspace-remember.ts:261—failRunningTaskAfterRemovaloverwrites completed task state (data loss)
Suggestions (4)
workspace-registration-store.ts:393— Error handling masks root cause on dual failureWebShellSidebar.tsx:1490— Removal polling dialog cannot be dismissedWorkspaceSection.tsx:119— Focus not restored on pointer-outside menu closerun-qwen-serve.test.ts:5252— Test assertion does not verify removeByIds arguments
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>
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>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
6802b5a to
d509ff7
Compare
wenshao
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
— qwen3.7-max via Qwen Code /review
✅ Local verification report — round 3 (maintainer merge reference)Re-verification of the current head Environment: macOS (Darwin 24.6.0) · Node.js v22.23.1 · built from source ( 1. Repository quality gates
2. Focused Vitest — every changed test file in the PR
Total focused: 1896 passed · 1 skipped · 0 failed.
3. Live end-to-end — real daemon, real
|
| 门禁 | 结果 | 说明 |
|---|---|---|
npm run build |
✅ 通过 | 所有 workspace 包编译成功(tsc --build) |
npm run typecheck |
✅ 通过 | cli · core · sdk · web-shell · webui(tsc --noEmit) |
npm run lint |
✅ 通过 | eslint . + integration-tests · 0 问题 |
npm run bundle |
✅ 通过 | esbuild 打包 + 全部资源复制到 dist/ |
2. 定向 Vitest — PR 中每一个改动的测试文件
| 包 | 测试套件 | 结果 |
|---|---|---|
cli |
serve/* — workspace-management、run-qwen-serve、channel-worker-group、channel-worker-manager、workspace-qualified-acp、workspace-registration-store、workspace-registry、workspace-remember、total-session-admission、workspace-git-state、routes/workspace-git、server(12 个文件) |
✅ 1089 通过 · 1 跳过 |
acp-bridge |
bridge.test.ts(shutdown reason + memoization) |
✅ 392 通过 |
sdk-typescript |
daemon/DaemonClient.test.ts |
✅ 236 通过 |
web-shell |
sidebar/WebShellSidebar.workspace-removal.test.tsx |
✅ 4 通过 |
webui |
DaemonSessionProvider · workspace/actions · session/actions |
✅ 175 通过 |
定向测试合计:1896 通过 · 1 跳过 · 0 失败。
关于 web-shell 数量相较第 2 轮的变化(95 → 4):main 的 #6804 删除了原来的巨型
WebShellSidebar.test.tsx;本 PR 现在只保留聚焦的移除测试。这 4 个测试仍覆盖了此处关键的行为——feature 门控、untrusted 但可移除的操作、移除后回退到 primary,以及当前 session 所属 workspace 的 force 保护。
3. 端到端 — 真实 daemon、真实 DELETE /workspaces/:workspace
我用一个 primary + 一个启动参数配置的 workspace 启动了打包后的 dist/index.js serve,用 fetch 驱动真实 HTTP 接口并对每个响应做断言(共 26 条断言),覆盖 PR 描述的完整生命周期——capability 发布、两阶段 drain、busy 409 / force 路径、持久化 commit-point,以及同路径重新添加:
- A — capability 与标志位:
GET /capabilities发布workspace_runtime_removal;primary 与启动配置的 workspace 均为removable:false。 - B — 动态添加 + 持久化:
POST /workspaces {persist:true}→201,secondary 显示removable:true,并将持久化注册写入磁盘。 - C — 保护: 对 primary 执行
DELETE→409 primary_workspace_removal_forbidden;对启动 workspace →409 static_workspace_removal_forbidden。 - D — busy → force: 在保持一个真实 ACP 连接时,
DELETE(非 force)→409 workspace_busy {acpConnections:1};DELETE {force:true}→200 removed:true forced:true,同时移除持久化注册。 - E — 干净释放: secondary 从 capabilities 消失并从磁盘擦除,而 primary daemon 继续返回
200。 - F — 重新添加 + 空闲删除: 相同路径作为全新 removable runtime 重新添加;空闲
DELETE返回200 removed:true forced:false,所有 activity 计数为零。SIGTERMdrain 后退出码0。
结果:26 通过 · 0 失败,连续 3 / 3 次运行稳定——PR 标注的主要风险(并发清理)路径未见抖动。
4. Web Shell 移除交互 — 真实组件截图(本轮新增)
由于 sidebar 本轮被重构,我从真实的 WebShellSidebar 组件(通过 Playwright 驱动 mock daemon)捕获了实际的移除弹窗,而非仅依赖组件测试。首次确认执行非 force 移除,并提示文件/设置/历史会被保留;409 workspace_busy 会渲染被冻结的 activity 快照(全部 6 个计数)以及明确的 Force remove 操作。在浅色与深色主题下均渲染正确。
本地未重复覆盖的范围
与作者注明的范围外一致:手工 Windows/Linux 运行、真实外部模型集成,以及完整的 sandbox-none 集成套件。(Web Shell 弹窗已在上方做了可视化验证;当前 session 的 force 保护仍由聚焦组件测试覆盖。)
建议
验证在当前 head d509ff713 上干净且可复现。构建 / 测试 / 运行时行为以及 Web Shell 移除交互均通过;从技术角度看,这仍是一个可靠的合并候选。剩余的门禁属于流程层面——一次正式的非作者审批,以及最终的 main 集成 /resolve。
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
| } | ||
| registryDraining = false; | ||
| controllerDraining = false; | ||
| acpDraining = false; |
There was a problem hiding this comment.
[Suggestion] The 'forget' operation type falls into the "registration in progress" branch of this ternary, producing the misleading message "Workspace registration is in progress" when another forget is in flight for the same cwd. Consider adding a 'forget' branch or using a generic message:
| acpDraining = false; | |
| error: | |
| operation === 'removal' | |
| ? 'Workspace removal is in progress' | |
| : operation === 'forget' | |
| ? 'Workspace forget is in progress' | |
| : 'Workspace registration is in progress', |
— qwen3.7-max via Qwen Code /review
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅



What this PR does
This PR adds synchronous runtime removal for removable secondary workspaces. Production daemons advertise
workspace_runtime_removal, capabilities identify removable runtimes, andDELETE /workspaces/:workspaceperforms a two-phase drain with an optional force mode, removes every known persistent registration alias, tears down workspace-owned sessions, prompts, pending starts, ACP connections, memory tasks, sub-session launchers, bridges, and channel workers, then releases the workspace path for a clean re-add.The implementation keeps primary and explicitly configured startup workspaces non-removable, preserves the existing forget-only registration endpoint, shares the same memoized disposer between hot removal and daemon shutdown, and rolls every admission/ACP/worker gate back if a non-force removal finds activity or persistence fails. It also adds the typed SDK operation, WebUI action, Web Shell confirmation and busy/force flow, protocol documentation, and focused concurrency and lifecycle coverage.
The latest main branch moved composer tag helpers but left one stale import in the scheduled-task dialog; this PR includes the one-line import correction required for the rebased branch to build.
Why it's needed
Dynamic workspace registration and persistent restoration can add secondary runtimes without restarting the daemon, but those runtimes could not previously be unloaded until process restart. Operators therefore could not immediately release sessions, ACP mounts, memory lanes, bridge children, or channel-worker routing, and deleting a persistent registration only affected the next start. Runtime drain completes that lifecycle while retaining project files, settings, transcripts, archives, and other workspace data.
Reviewer Test Plan
How to verify
workspace_runtime_removalwithremovable: trueonly on the secondary runtime.409 workspace_busywith accurate activity counts, then force removal terminates only the target workspace resources while the primary daemon and sessions remain usable.npm run build,npm run typecheck,npm run lint, andnpm run bundle; all should pass.Evidence (Before & After)
Before: dynamically added or restored workspaces had no runtime removal action and could only be unloaded by restarting the daemon; forgetting persistence left the active runtime mounted.
After: removable workspace rows expose a separate Remove action. Idle removal completes immediately; busy removal presents the frozen activity counts and an explicit Force Remove confirmation. Component coverage verifies the untrusted-workspace action, busy details, current-session force guard, concurrent-removal state, mismatch reconciliation, and primary fallback.
Tested on
Environment (optional)
macOS, Node.js v22.22.3, local HTTP daemon, sandbox disabled for daemon lifecycle tests.
Risk & Scope
removablefield, DELETE route, result types, and bridge shutdown option are additive; existing clients and the forget-only registration endpoint retain their behavior.Linked Issues
Related to #6378. Builds on #6716 and #6635.
中文说明
本 PR 做了什么
本 PR 为可移除的 secondary workspace 增加同步运行时移除能力。生产 daemon 会发布
workspace_runtime_removal,capabilities 会标识可移除 runtime,DELETE /workspaces/:workspace通过可选 force 模式执行两阶段 drain,删除所有已知的持久化注册别名,清理该 workspace 所有的 session、prompt、待启动 reservation、ACP 连接、memory task、sub-session launcher、bridge 与 channel worker,最后释放 workspace 路径以支持干净地重新添加。实现保持 primary 与通过启动参数显式配置的 workspace 不可移除,保留现有 registration forget-only 接口,并让热移除与 daemon shutdown 复用同一个 memoized disposer。如果非 force 删除发现活动资源或持久化写入失败,session admission、ACP 与 worker 的所有 gate 都会完整回滚。此外还新增了类型化 SDK 操作、WebUI action、Web Shell 确认与 busy/force 交互、协议文档,以及针对并发和生命周期的定向测试。
最新 main 已将 composer tag helper 迁移到新位置,但 scheduled-task dialog 仍保留一个旧 import;本 PR 包含让 rebase 后分支恢复构建所必需的一行 import 修正。
为什么需要
动态 workspace 注册与持久化恢复已经可以在不重启 daemon 的情况下添加 secondary runtime,但这些 runtime 以前只能等进程重启才能卸载。因此操作者无法立即释放 session、ACP mount、memory lane、bridge 子进程或 channel-worker 路由,而删除持久化注册也只会影响下一次启动。Runtime drain 补齐了这一生命周期,同时保留项目文件、设置、transcript、归档及其他 workspace 数据。
Reviewer 测试计划
如何验证
workspace_runtime_removal,并且只有 secondary runtime 的removable为true。409 workspace_busy,随后 force 删除只终止目标 workspace 资源,primary daemon 与 session 继续可用。npm run build、npm run typecheck、npm run lint、npm run bundle;所有命令均应通过。证据(修改前与修改后)
修改前:动态添加或恢复的 workspace 没有运行时移除操作,只能通过重启 daemon 卸载;forget persistence 后活动 runtime 仍保持挂载。
修改后:可移除 workspace 行提供独立 Remove 操作。空闲删除立即完成;繁忙删除显示冻结的 activity 数量并提供明确的 Force Remove 二次确认。组件测试覆盖 untrusted workspace 操作、busy 详情、当前 session force 保护、并发删除状态、mismatch 收敛与 primary fallback。
测试平台
环境(可选)
macOS,Node.js v22.22.3,本地 HTTP daemon,daemon 生命周期测试禁用 sandbox。
风险与范围
removable字段、DELETE route、结果类型与 bridge shutdown option 均为增量变更;现有客户端与 forget-only registration 接口保持原有行为。关联 Issue
关联 #6378。基于 #6716 与 #6635。