feat(serve): add extension management v2 - #6825
Conversation
Mirror the daemon extension-management REST surface to per-workspace routes, reusing the Phase 3 runtime resolver and trust gate. Extract a per-workspace extensions controller so the primary workspace shares one install queue, operation history, and status cache across the legacy and workspace-qualified routes. Reads resolve the target runtime only; mutations require a trusted workspace. Advertise a new baseline capability so clients can discover the surface, and add matching SDK client methods. Refs QwenLM#6378.
Align the new extensions controller file's copyright year with the other new files added in this change.
Redact credentials from the extension source on the two success-path fan-outs (session refresh and refresh-failure broadcast), matching the operation record and failure broadcast. Document the non-cancellation semantics of the extension timeout wrapper.
Share the queue-full sentinel message via an exported constant so the throw site (controller) and the 429 match site (routes) cannot drift after the module split. Include the bound workspace in the extension operation log prefixes so concurrent per-workspace controllers are distinguishable in stderr.
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>
Keep both extension_management_v2 (PR branch) and workspace_qualified_acp (origin/main) in SERVE_CAPABILITY_REGISTRY and EXPECTED_REGISTERED_FEATURES. Both are independent features added at the same insertion point. 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>
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>
|
⏸️ Re-run complete — all three stages updated in place. No blocking issues found. This PR was already reviewed and approved by @wenshao (maintainer) at Re-run assessment: build ✅, typecheck ✅, 254+ tests ✅, daemon smoke ✅, code quality high. Scale (7,791 production lines in core) warrants maintainer awareness, which has been provided. |
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
@qwen-code /triage |
✅ Maintainer local re-verification at head
|
| Area | Fix in the 7 new commits |
|---|---|
zip-extraction.ts |
Per-segment symlink/non-directory rejection while creating the destination tree (catches a symlink planted mid-path, not just the final realpath); error messages now strip ANSI/control chars and are length-bounded (anti log-injection). |
archive-safety.ts |
Tar link-limit is now enforced deterministically — the "too many links" error is captured and thrown after parsing completes, instead of from inside the tar onReadEntry callback where it could be swallowed. |
github.ts |
A public-policy git subprocess now receives a minimal allowlisted env (PATH + Windows/temp dirs) instead of the entire process.env — no secret/token leakage into an untrusted clone. |
npm.ts |
The ambient NPM_TOKEN is sent only when the target registry origin matches the configured registry origin — stops the token being exfiltrated to an override registryUrl. |
network-policy.ts |
SSRF guard now blocks 3fff::/20 (RFC 9637 documentation range). |
extensionSettings.ts |
Legacy sensitive-setting sync writes through the current keychain backend. |
acp-bridge / acpAgent / daemon-worker |
De-dup of in-flight workspaceExtensionsRefresh per session; dropped a redundant skill-cache refresh; single-workspace daemons use the classic path (multi-workspace matching only when >1 registered). |
workspace-extensions-controller.ts |
Operation status now distinguishes queued (pending prep) from running (active prep) and clears phase on every terminal state. |
DaemonClient.ts |
Workspace-extension + catalog requests pinned to REST transport mode. |
1) Build · Typecheck · Lint
| Step | Command | Result |
|---|---|---|
| Build | npm run build |
✅ all packages compiled (exit 0) |
| Typecheck | npm run typecheck |
✅ 9 workspaces (core · cli · sdk · acp-bridge · web-shell · webui · …) exit 0 |
| Lint | eslint <85 PR-changed .ts/.tsx> --max-warnings 0 |
✅ exit 0, no errors/warnings |
2) Real test execution — every PR-touched suite (vitest run)
| Package / area | Files | Tests | Result |
|---|---|---|---|
core · src/extension/ (store · manager · settings · github · npm · network-policy · archive-safety · zip-extraction · converters) |
12 | 474 | ✅ |
cli · serve routes · ws-qualified ext · op-scheduler · run-qwen-serve · server · ext cmds/TUI · daemon-worker |
20 | 1,342 | ✅ |
sdk-typescript · DaemonClient catalog/mutation/polling/projection |
1 | 254 | ✅ |
| acp-bridge · bridge session / extension-refresh dedup | 1 | 393 | ✅ |
| Total (V2 surface) | 34 | 2,463 | ✅ 0 failures |
Scoped to exactly the files this PR touches — I deliberately excluded the unrelated
commands/extensions/list.test.ts, which fails only on this machine's zh-CN system locale (it hard-codes the English"No extensions installed."string while the CLI localizes it) and is byte-identical tomain.
3) Security-hardening delta — the 7 new commits, covered & passing
A focused verbose re-run of the six core suites hardened by the new commits (6 files / 188 tests), so the new fixes are legible and demonstrably tested — e.g. rejects blocked literal address https://[3fff::1]/archive, does not create directories through an existing symbolic link, sanitizes and bounds entry names in errors, rejects a large link set without throwing outside the promise, does not send the ambient npm token to an override registry / sends the ambient npm token to the configured registry origin.
4) Live smoke — bundled qwen serve with two registered workspaces
Booted the real built daemon (node packages/cli/dist/index.js serve --port 0 --workspace wsA --workspace wsB) against an isolated QWEN_HOME with a bearer token, and drove the V2 surface over real HTTP — 17/17 checks pass:
- Capabilities advertise
extension_management_v2and the legacyworkspace_extensionscompat tag, plus exactly 2 workspaces. ✔ - Global catalog
GET /extensions→200 {generation:0, extensions:[]}; both workspaces project with distinctworkspaceId. ✔ - Install security boundaries — no
consent→400; non-httpssource →400; a local-path source is accepted async then fails with "Only GitHub, Git, and npm … supported over the daemon endpoint" (a remote client cannot make the daemon install from an arbitrary host path). ✔ - Async operation lane end-to-end —
202+Location+operationId, polled to a terminal state; the terminal op carries no leftoverphase(the new controller phase-clear fix); after the failed install the catalog is still empty atgeneration:0(transactional — no partial mutation). ✔ - Guards — unregistered workspace →
400 workspace_mismatch; invalid id →400 invalid_extension_id; invalid state →400 invalid_extension_activation. ✔ - Legacy
GET /workspace/extensionsstill200. ✔
Scope note (honest boundaries)
- I ran the targeted V2 test surface (the suites this PR touches), not the entire repo suite.
- The live smoke is hermetic — it exercises capabilities · catalog · dual-workspace projection · install security boundaries · the async operation lane · phase-clearing · transactional rollback-on-failure · guards · legacy compat. It does not drive the remote network install path (git/npm download+extract) or the secure-storage secret-bundle path live — those stay covered by unit tests (
npm.test.ts,github.test.ts,network-policy.test.ts,zip-extraction.test.ts,archive-safety.test.ts,extensionSettings.test.ts,extension-store.test.ts), all green above. (My first run additionally drove activation-policy precedence with a seeded local extension.) - The branch is currently 17 commits behind
main(114 ahead); a merge/rebase before landing is advisable, but it builds, typechecks, lints, and tests cleanly on its own head.
Verdict: build, typecheck, lint, and the full targeted test surface (2,463 tests, 0 failures) all pass at 425d9df9; the seven new security-hardening fixes are individually tested and green; and the V2 daemon serves correctly against a live two-workspace instance. The delta since my last sign-off is well-scoped and strengthens the download/extraction safety. 👍 Still good as a merge reference.
Re-verified from a fresh isolated worktree at head 425d9df9; screenshots are rendered from the actual command output and real HTTP responses captured during this run.
🇨🇳 中文版本(点击展开)
✅ 维护者本地三次验证(head 425d9df9)—— 构建 · 类型检查 · lint · 2,463 个目标用例 · 双 workspace daemon 实测
本条更新我此前的两次验证(15b158fa9 与 604ff49a5)。自第二次以来,分支新增了 7 个提交(604ff49a5..425d9df9,24 个文件,+852/−121)—— 全部是 review 反馈 / CI 修复,且大多是扩展下载与解压路径上的真实安全加固。正因为这些改动恰好落在高风险面上,我在全新的隔离 worktree 中基于当前 head 从头重跑了全部验证。全部通过。
环境: macOS (darwin 24.6.0) · Node v22.23.1 · vitest v3.2.4 · 测试前先做全新 npm install + npm run build · 隔离的 QWEN_HOME。
自上次验证以来的改动(以及为何值得重跑)
| 领域 | 7 个新提交中的修复 |
|---|---|
zip-extraction.ts |
在创建目标目录树时逐段拒绝符号链接/非目录(可捕获中间路径段上植入的软链,而不仅是最终的 realpath);错误信息会剥离 ANSI/控制字符并做长度限制(防日志注入)。 |
archive-safety.ts |
tar 软链数量上限现在确定性生效 —— “链接过多”错误被捕获后在解析完成之后抛出,而不再在 tar 的 onReadEntry 回调里抛出(那里可能被吞掉)。 |
github.ts |
public 策略下的 git 子进程现在只收到最小白名单环境(PATH + Windows/temp 目录),而非整个 process.env —— 不会把密钥/token 泄漏进不受信任的 clone。 |
npm.ts |
仅当目标 registry 的 origin 与配置的 registry origin 一致时才发送环境变量 NPM_TOKEN —— 避免 token 被外泄到被覆盖的 registryUrl。 |
network-policy.ts |
SSRF 防护新增拦截 3fff::/20(RFC 9637 文档保留段)。 |
extensionSettings.ts |
旧版敏感设置同步改为写入当前 keychain 后端。 |
acp-bridge / acpAgent / daemon-worker |
对同一 session 的在途 workspaceExtensionsRefresh 去重;移除 reconcile 路径上多余的 skill 缓存刷新;单 workspace daemon 走经典路径(仅当注册数 >1 才做多 workspace 匹配)。 |
workspace-extensions-controller.ts |
operation 状态区分 queued(等待准备)与 running(准备中),并在每个终态清空 phase。 |
DaemonClient.ts |
workspace-extension 与 catalog 请求固定走 REST 传输模式。 |
1)构建 · 类型检查 · lint
| 步骤 | 命令 | 结果 |
|---|---|---|
| 构建 | npm run build |
✅ 全部 package 编译通过(exit 0) |
| 类型检查 | npm run typecheck |
✅ 9 个 workspace(core · cli · sdk · acp-bridge · web-shell · webui · …)exit 0 |
| Lint | eslint <85 个 PR 改动的 .ts/.tsx> --max-warnings 0 |
✅ exit 0,无 error/warning |
2)真实测试执行 —— 覆盖本 PR 改动的每个套件(vitest run)
| Package / 领域 | 文件 | 用例 | 结果 |
|---|---|---|---|
core · src/extension/(store · manager · settings · github · npm · network-policy · archive-safety · zip-extraction · converters) |
12 | 474 | ✅ |
cli · serve 路由 · workspace 限定扩展 · op-scheduler · run-qwen-serve · server · 扩展命令/TUI · daemon-worker |
20 | 1,342 | ✅ |
sdk-typescript · DaemonClient catalog/mutation/轮询/projection |
1 | 254 | ✅ |
| acp-bridge · bridge session / 扩展刷新去重 | 1 | 393 | ✅ |
| 合计(V2 相关面) | 34 | 2,463 | ✅ 0 失败 |
(对应上方第一张截图)
严格限定为本 PR 实际改动的文件 —— 我特意排除了无关的
commands/extensions/list.test.ts,它仅因本机 zh-CN 系统 locale 而失败(它硬编码了英文串"No extensions installed.",而 CLI 会将其本地化),且与main逐字节一致。
3)安全加固增量 —— 7 个新提交,均有测试覆盖且通过
对新提交加固的 6 个 core 套件做了一次 verbose 重跑(6 个文件 / 188 个用例),让新修复清晰可见且确有测试 —— 例如 rejects blocked literal address https://[3fff::1]/archive、does not create directories through an existing symbolic link、sanitizes and bounds entry names in errors、rejects a large link set without throwing outside the promise、does not send the ambient npm token to an override registry / sends the ambient npm token to the configured registry origin。
(对应上方第二张截图)
4)实测 —— 打包后的 qwen serve 注册两个 workspace
用构建产物启动真实 daemon(node packages/cli/dist/index.js serve --port 0 --workspace wsA --workspace wsB),指向隔离的 QWEN_HOME 并带 bearer token,通过真实 HTTP 端到端驱动 V2 接口 —— 17/17 检查通过:
- capabilities 同时声明
extension_management_v2和旧的workspace_extensions兼容标签,并暴露恰好 2 个 workspace。✔ - 全局 catalog
GET /extensions→200 {generation:0, extensions:[]};两个 workspace 都能投影,workspaceId各不相同。✔ - 安装安全边界 —— 无
consent→400;非https源 →400;本地路径源会先异步受理再失败,报 "Only GitHub, Git, and npm … supported over the daemon endpoint"(远程客户端无法让 daemon 从任意主机路径安装)。✔ - 异步 operation lane 端到端 ——
202+Location+operationId,轮询到终态;终态 operation 不残留phase(新的 controller phase 清理修复);失败安装后 catalog 仍为generation:0空(事务性 —— 无部分写入)。✔ - 防护 —— 未注册 workspace →
400 workspace_mismatch;非法 id →400 invalid_extension_id;非法 state →400 invalid_extension_activation。✔ - 旧兼容路由
GET /workspace/extensions仍200。✔
(对应上方第三张截图)
范围说明(诚实边界)
- 我跑的是目标 V2 测试面(本 PR 改动涉及的套件),并非整个仓库。
- 实测是自包含的 —— 覆盖 capabilities · catalog · 双 workspace projection · 安装安全边界 · 异步 operation lane · phase 清理 · 失败回滚的事务性 · 防护 · 旧兼容层;未实测远程网络安装路径(git/npm 下载+解压)与安全存储 secret bundle 的实时路径 —— 这些由单测覆盖(
npm.test.ts、github.test.ts、network-policy.test.ts、zip-extraction.test.ts、archive-safety.test.ts、extensionSettings.test.ts、extension-store.test.ts),上文均已通过。(我第一次那次还额外用预置本地扩展验证过 activation 优先级。) - 该分支目前落后
main17 个提交(领先 114);建议合并前先 rebase/merge,但它在自身 head 上可正常构建、类型检查、lint 与测试。
结论: 在 425d9df9 上构建、类型检查、lint 与完整目标测试面(2,463 个用例,0 失败)全部通过;7 个新的安全加固修复各自有测试且为绿;V2 daemon 在真实双 workspace 实例上对外服务正确。相较上次签署,本次增量范围清晰,并强化了下载/解压的安全性。👍 作为合并参考,仍然可以合入。
|
⏸️ Deferring to @wenshao — Stage 0 large-PR advisory (7,796 production lines in core) requires maintainer awareness, which has been provided. @wenshao has already approved after extensive verification (2,483 tests, live daemon smoke, independent code read). This re-run found two minor concerns worth a maintainer's second look before merge:
Neither concern blocks the PR — flagging for the maintainer's awareness. |
|
@qwen-code /resolve |
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Qwen Code resolved the merge conflicts and pushed the branch update. Merge Conflict Resolution SummaryPR: #6825 — feat(serve): add extension management v2 Conflicted Files1.
|
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>



What this PR does
This PR introduces Extension Management V2 for
qwen serveunder the additiveextension_management_v2capability. Installed extension artifacts remain user-level and shared across all hosted workspaces, while activation becomes policy: a global default plus optional exact workspace overrides. The daemon exposes a global catalog and mutation surface for install, update checks, update, uninstall, and default activation, together with workspace-qualified projection, activation, and refresh routes. The TypeScript SDK includes matching catalog, mutation, polling, and workspace-projection helpers.Extension mutations now use a transactional store as the single writer of final extension directories and activation state. Preparation runs outside the final artifact directory, commits are serialized, state advances through a monotonic generation, and journal-based recovery handles interrupted artifact swaps. A daemon-wide scheduler allows up to two concurrent preparation tasks and serializes durable artifact and state writes in preparation-completion order. The commit lane is released immediately after those writes become durable, so runtime refresh and cleanup can continue without blocking the next durable commit. Successful global changes reconcile every hosted runtime, while workspace activation changes reconcile only their target. Runtime reconciliation refreshes extension and skill caches, extension tools, hierarchical memory, active chat system instructions, and available commands. Post-commit refresh failures are surfaced as warnings through daemon operation results, legacy CLI commands, and the TUI without rolling back committed state.
Sensitive user settings now participate in the artifact transaction without placing secrets in the artifact itself. Preparation writes one revisioned secret bundle to the selected secure-storage backend and places only a backend-and-revision selector in the staged artifact. The artifact commit atomically activates that complete bundle; cancellation discards the unselected bundle, a successful commit removes the previously selected bundle, and later legacy per-key synchronization is outside the durable lane. A crash can leave only an unreachable orphan bundle, never a new artifact paired with old or partially written credentials. Post-commit settings or cleanup failures are returned with their
codeanderrorthrough daemon operations and update callers, usingupdated with warnings; only reload or runtime-refresh failures useupdated, needs restart.Daemon shutdown now centralizes runtime-owned cleanup against the actual runtime app, including deferred apps that finish after shutdown has started. Scheduled-task keepalive, workspace Git state, sub-session launchers, extension reconciliation, device flow, ACP, rate limiting, event-loop monitoring, and metrics sampling are disposed idempotently with per-resource error isolation.
The existing
workspace_extensionscapability and/workspace/extensions/*routes remain available as a primary-workspace compatibility adapter. Legacy activation rules are migrated and projected back to the existing enablement format, while the extension watcher observes store generations so other daemon processes converge after external commits.Why it's needed
Extension artifacts are stored globally under one Qwen home even when a daemon hosts multiple workspaces. Treating extension mutations as independent per-workspace operations allows concurrent requests to race against the same artifact directory and can leave other workspaces running stale extension state. Extension Management V2 makes artifact ownership and mutation explicitly global while preserving per-workspace activation, providing a consistent transaction boundary, deterministic serialization, targeted runtime reconciliation, and backward compatibility for existing clients. The follow-up transaction and shutdown changes ensure that committed artifacts select one complete credential revision, post-commit failures remain actionable, and late-created runtime resources cannot outlive daemon shutdown.
Reviewer Test Plan
How to verify
Start a daemon with at least two registered workspaces and confirm that capabilities advertise both
extension_management_v2and the existingworkspace_extensionscompatibility surface. Install a remote extension through/extensions/installwith explicit consent and either user or workspace initial activation, poll the returned operation to completion, and confirm that the artifact appears once in the global catalog while each workspace projection reports the expected default, override, effective activation, desired generation, and applied generation.Exercise default activation, exact workspace activation, clearing a workspace override, update checks, update, uninstall, and explicit workspace refresh. Confirm that global artifact/default changes reconcile all hosted runtimes, workspace activation changes reconcile only the trusted target, reads remain available for an untrusted registered workspace, and workspace mutations reject untrusted or unknown targets. For an extension with a context file, change activation while a session is already running and confirm that the same session's next model turn uses the rebuilt system instruction without the disabled extension context. Confirm that the legacy
/workspace/extensions/*routes continue to work for the primary workspace and expose compatible operation results.Submit overlapping installs or updates and confirm that preparation admits at most two active tasks, durable commits remain single-lane FIFO by preparation completion, the lane releases after artifact and state writes are durable, runtime refresh or cleanup may continue afterward without blocking the next durable commit, queued preparation can be aborted without releasing an active non-cooperative task early, and admission rejects more than ten unfinished operations. Simulate a runtime refresh failure after commit and confirm that the operation finishes with warnings while the committed generation and artifact remain authoritative. Restart after prepared, artifact-swapped, and state-committed transaction states and confirm recovery rolls back before the state commit point and completes cleanup after it.
For an extension with sensitive settings, prepare an update and confirm that the installed extension continues reading the old settings until the artifact is committed, then reads the complete new bundle immediately after commit even before legacy per-key synchronization finishes. Cancel another prepared update and confirm that its unselected bundle is removed; commit a replacement and confirm that the previous bundle and overrides are removed. Force post-commit settings synchronization to fail and confirm that the durable lane is already available to the next commit and that callers receive the exact warning rather than a restart-only status. Start deferred runtime construction, close the daemon before it mounts, allow the late runtime app to finish, and confirm that every runtime producer and monitor is disposed even if one cleanup callback throws.
Focused unit coverage exercises store migration and recovery, activation precedence, concurrent store instances, stale-update conflicts, scheduler behavior and durable-commit lane release, revisioned settings activation and cleanup, late-runtime shutdown, route trust and reconciliation, watcher convergence, committed-warning propagation through CLI and TUI surfaces, direct extension TUI actions, SDK paths, and operation polling. Build, typecheck, targeted package tests, and a bundled two-workspace daemon smoke were run during development.
Evidence (Before & After)
Before: If an extension mutation committed successfully but runtime refresh failed, CLI and TUI callers could report only success or suppress the refresh error, leaving users without a clear indication that the persisted state and running runtime had diverged.
After: The durable mutation remains authoritative, while daemon operation results, legacy CLI commands, automatic updates, and TUI extension actions display the exact post-commit warning. Settings-only and cleanup warnings use
updated with warnings, while reload/runtime failures retain the restart guidance.Tested on
Environment (optional)
Node.js 22, Vitest package suites, repository build and typecheck, and a local bundled
qwen servetwo-workspace smoke.Risk & Scope
extension_management_v2is additive, the existing capability and primary-workspace routes remain supported, and existing enablement rules are migrated with a backward-compatible projection.Linked Issues
Refs #6378. Builds on #6567. Supersedes #6638.
中文说明
这个 PR 做了什么
本 PR 在新增的
extension_management_v2capability 下,为qwen serve引入 Extension Management V2。已安装扩展的 artifact 仍然是用户级资源,并由所有托管 workspace 共享;activation 则作为策略管理:一个全局默认值,加上可选的精确 workspace override。daemon 新增全局 catalog 与 mutation 接口,用于安装、检查更新、更新、卸载和默认 activation,同时提供按 workspace 限定的 projection、activation 与 refresh 路由。TypeScript SDK 补充了对应的 catalog、mutation、轮询和 workspace projection 辅助方法。扩展 mutation 现在由事务式 store 统一写入最终扩展目录和 activation 状态。准备阶段在最终 artifact 目录之外执行,commit 串行化,状态通过单调递增的 generation 推进,并通过 journal 恢复被中断的 artifact 交换。daemon 级调度器允许最多两个并发准备任务,并按照准备完成顺序串行执行 artifact 与状态的持久化写入。这些写入持久化后会立即释放 commit lane,因此 runtime refresh 和清理可以继续执行,而不会阻塞下一次持久化 commit。成功的全局变更会 reconcile 所有托管 runtime,workspace activation 变更只 reconcile 目标 runtime。runtime reconciliation 会刷新 extension 与 skill cache、extension tool、分层 memory、运行中 chat 的 system instruction 以及可用命令;commit 后的 refresh 失败会通过 daemon operation 结果、旧 CLI 命令和 TUI 显式展示为 warning,不会回滚已提交状态。
敏感的用户设置现在也参与 artifact 事务,但 secret 本身不会写入 artifact。准备阶段会向选定的安全存储后端写入一个带 revision 的完整 secret bundle,并只在 staged artifact 中放入后端与 revision selector。artifact commit 会原子激活这个完整 bundle;取消操作会删除未选中的 bundle,成功 commit 会删除之前选中的 bundle,而旧版逐 key 同步则在 durable lane 之外执行。进程崩溃最多留下一个不可达的孤立 bundle,不会出现新 artifact 搭配旧凭据或部分写入凭据的状态。commit 后的设置或清理失败会通过 daemon operation 和更新调用方返回原始
code与error,并使用updated with warnings;只有 reload 或 runtime refresh 失败才使用updated, needs restart。daemon shutdown 现在会针对实际 runtime app 统一清理 runtime 所有资源,包括 shutdown 开始后才完成构建的 deferred app。scheduled-task keepalive、workspace Git state、sub-session launcher、extension reconciliation、device flow、ACP、rate limiting、event-loop monitor 和 metrics sampler 都会幂等释放,并且每项清理互相隔离错误。
现有的
workspace_extensionscapability 和/workspace/extensions/*路由继续作为 primary workspace 的兼容适配层保留。旧 activation 规则会被迁移,并投影回原有 enablement 格式;扩展 watcher 观察 store generation,使其他 daemon 进程能够在外部 commit 后收敛。为什么需要
即使一个 daemon 托管多个 workspace,扩展 artifact 仍然存放在同一个 Qwen home 下。若把扩展 mutation 当作相互独立的 workspace 操作,并发请求就可能竞争同一个 artifact 目录,并让其他 workspace 继续运行过期的扩展状态。Extension Management V2 明确区分全局 artifact 所有权和 mutation 与按 workspace activation,在保持现有客户端兼容性的同时,提供一致的事务边界、确定性的串行化、针对性的 runtime reconciliation。后续的事务与 shutdown 调整进一步保证已提交 artifact 只选择一个完整凭据 revision、commit 后失败保持可操作性,并阻止晚创建的 runtime 资源在 daemon shutdown 后继续存活。
Reviewer Test Plan
如何验证
启动一个注册了至少两个 workspace 的 daemon,确认 capabilities 同时声明
extension_management_v2和既有的workspace_extensions兼容接口。通过/extensions/install安装一个远程扩展,显式提供 consent,并选择 user 或 workspace 初始 activation;轮询返回的 operation 直到完成。确认 artifact 在全局 catalog 中只出现一次,同时每个 workspace projection 都返回预期的默认值、override、有效 activation、目标 generation 和已应用 generation。验证默认 activation、精确 workspace activation、清除 workspace override、检查更新、更新、卸载和显式 workspace refresh。确认全局 artifact 或默认值变更会 reconcile 所有托管 runtime,workspace activation 变更只 reconcile 受信任的目标;未受信任但已注册的 workspace 仍可读取状态,而针对未受信任或未知目标的 workspace mutation 会被拒绝。对于带 context file 的扩展,在 session 已经运行时修改 activation,确认同一个 session 的下一次模型调用会使用重建后的 system instruction,且不再包含已禁用扩展的上下文。确认旧
/workspace/extensions/*路由继续对 primary workspace 生效,并返回兼容的 operation 结果。提交重叠的安装或更新操作,确认准备阶段最多允许两个活跃任务,持久化 commit 始终保持单 lane 并按准备完成顺序执行;artifact 与状态写入持久化后 lane 会立即释放,后续 runtime refresh 或清理可以继续执行且不会阻塞下一次持久化 commit;排队中的准备任务可以被 abort,而正在运行但不响应 abort 的任务不会提前释放槽位;超过十个未完成 operation 时 admission 会拒绝新请求。模拟 commit 后的 runtime refresh 失败,确认 operation 以 warning 完成,已提交 generation 和 artifact 仍是权威状态。分别在 prepared、artifact-swapped 和 state-committed 事务状态后重启,确认恢复逻辑在状态 commit 点之前执行回滚,在 commit 点之后完成清理。
对于带敏感设置的扩展,prepare 一次更新并确认已安装扩展在 artifact commit 前仍读取旧设置;commit 后即使旧版逐 key 同步尚未完成,也会立即读取完整的新 bundle。取消另一个 prepared update 并确认其未选中 bundle 被删除;commit 替换版本并确认旧 bundle 与 override 被删除。强制 commit 后设置同步失败,确认 durable lane 已可供下一次 commit 使用,而且调用方拿到准确 warning,而不是只有 restart 状态。启动 deferred runtime 构建,在其 mount 前关闭 daemon,再允许晚到的 runtime app 完成,确认即使某个 cleanup callback 抛错,所有 runtime producer 与 monitor 仍被释放。
聚焦单测覆盖 store 迁移与恢复、activation 优先级、并发 store 实例、过期更新冲突、调度行为与持久化 commit lane 释放、带 revision 的设置激活与清理、晚到 runtime shutdown、路由 trust 与 reconciliation、watcher 收敛、committed warning 在 CLI/TUI 界面的传播、扩展 TUI action 的直接交互、SDK 路径和 operation 轮询。开发过程中已运行构建、类型检查、目标 package 测试以及打包后的双 workspace daemon smoke。
证据(Before & After)
Before: 扩展 mutation 已成功提交但 runtime refresh 失败时,CLI 和 TUI 调用方可能只报告成功或吞掉 refresh 错误,用户无法明确得知持久化状态与正在运行的 runtime 已出现偏差。
After: 持久化 mutation 仍然是权威状态,同时 daemon operation 结果、旧 CLI 命令、自动更新和 TUI 扩展操作会显示准确的 post-commit warning。仅设置或清理 warning 使用
updated with warnings,reload/runtime 失败继续保留 restart 引导。测试平台
环境(可选)
Node.js 22、Vitest package 测试、仓库构建与类型检查,以及本地打包后的
qwen serve双 workspace smoke。风险与范围
extension_management_v2是增量 capability,既有 capability 和 primary workspace 路由继续受支持,现有 enablement 规则会迁移并保留向后兼容的 projection。关联 Issue
Refs #6378。基于 #6567。 替代 #6638。