feat(serve): manage skills per workspace runtime - #7311
Conversation
|
Thanks for the PR — this is a substantial piece of the workspace runtime stack. Template looks good ✓ Problem: This is a feature PR (stack 3/4: #7308 → #7309 → #7310 → #7311), not a bug fix. The need is clearly stated: extension management must target the selected workspace runtime without changing the persistence format or coupling refresh to an active session. The stack is well-structured, each PR building on the previous one. Direction: Aligned. Workspace-scoped extension management is a natural extension of the runtime foundation (#7308) and MCP management (#7309). The split into global config routes ( Size: No core paths touched. Production logic: ~1,799 lines (additions + deletions across 9 production files). Test code: ~1,609 lines (4 test files). This is a large PR — consider whether it could be split further, though the stack structure already provides some decomposition. Approach: The scope feels right for the stated goal — backend controller refactoring, SDK client methods, Web UI actions, and the management page all need to change together to support workspace-scoped extension operations. Two things worth noting:
Moving on to code review. 🔍 中文说明感谢贡献!这是 Workspace Runtime 堆叠 PR 中的一个重要部分。 模板完整 ✓ 问题: 这是一个功能 PR(堆叠 3/4:#7308 → #7309 → #7310 → #7311),不是 bug 修复。需求描述清晰:拓展管理必须作用于所选工作区运行时,同时不改变持久化格式,也不让刷新依赖活动 Session。堆叠结构良好,每个 PR 基于前一个构建。 方向: 对齐。工作区作用域的拓展管理是 Runtime 基础架构(#7308)和 MCP 管理(#7309)的自然延伸。全局配置路由( 规模: 未触及核心路径。生产逻辑约 1,799 行(9 个生产文件的增删合计),测试代码约 1,609 行(4 个测试文件)。PR 较大——可以考虑是否能进一步拆分,不过堆叠结构已经提供了一定的分解。 方案: 范围与目标匹配——后端控制器重构、SDK 客户端方法、Web UI actions 和管理页面需要一起改动以支持工作区作用域的拓展操作。两点值得注意:
进入代码审查 🔍 — Qwen Code · qwen3.7-max Reviewed at |
Code ReviewIndependent proposal: Given "manage skills per workspace runtime", I would split the skills API into a config-only inventory (loadable without a live runtime) and a runtime-enriched catalog (requiring ACP), add workspace-qualified routes mirroring the primary ones, split the webui hook into two resources, and merge the two inventories in the UI with config as source of truth. Comparison: The PR matches this approach closely. The architecture is sound and the implementation is clean. Findings — no critical blockers. The route layer correctly separates config reads (no trust required) from mutations (trust-gated). Scope guards ( The The In The SDK additions ( sequenceDiagram
participant P1 as SkillsManagerPage
participant P2 as useDaemonSkills
participant P3 as DaemonWorkspaceActions
participant P4 as DaemonClient
P1->>P2: mount (autoLoad)
P2->>P3: loadSkillsConfigStatus
P3->>P4: workspaceConfigSkills
P4-->>P1: config inventory (immediate)
P1->>P2: ensureRuntime (on demand)
P2->>P3: ensureRuntime
P3->>P4: workspaceRuntimeEnsure
P4-->>P2: prepared status
P2->>P3: loadSkillsStatus(preparedStatus)
P3->>P4: workspaceRuntimeSkills
P4-->>P1: merged inventory (config + runtime)
Files changed (26 of 26 shown)
Real-Scenario TestingBuild timed out on this CI runner (npm install and TypeScript compilation both exceeded the 2-3 minute limit), so the daemon could not be started for live API testing. The web-shell App.test.tsx and webui useDaemonSkills.test.tsx also require built cross-package dependencies that were unavailable. Unit tests verified from the main working tree (all passing): The PR author reports 129 CLI Skills tests, 221 Web UI tests, and 149 Web Shell tests passing locally, plus type checks across all four packages. The test coverage in the diff is comprehensive — every new route, action, hook, and merge path has corresponding test cases. 中文说明代码审查独立方案: 给定"按 Workspace Runtime 管理 Skills",我会将 Skills API 拆分为仅配置清单(无需活跃 Runtime 即可加载)和 Runtime 丰富目录(需要 ACP),添加镜像主路由的 Workspace 限定路由,将 webui hook 拆分为两个资源,并在 UI 中以配置为真实来源合并两个清单。 对比: PR 与此方案高度一致。架构合理,实现干净。 发现——无关键阻塞项。 路由层正确地将配置读取(无需信任)与变更(需信任门控)分离。作用域守卫正确强制全局作用域变更通过主端点、Workspace 作用域通过限定端点。
sequenceDiagram
participant P1 as SkillsManagerPage
participant P2 as useDaemonSkills
participant P3 as DaemonWorkspaceActions
participant P4 as DaemonClient
P1->>P2: 挂载 (autoLoad)
P2->>P3: loadSkillsConfigStatus
P3->>P4: workspaceConfigSkills
P4-->>P1: 配置清单 (立即)
P1->>P2: ensureRuntime (按需)
P2->>P3: ensureRuntime
P3->>P4: workspaceRuntimeEnsure
P4-->>P2: 准备状态
P2->>P3: loadSkillsStatus(preparedStatus)
P3->>P4: workspaceRuntimeSkills
P4-->>P1: 合并清单 (配置 + Runtime)
真实场景测试构建在此 CI 运行器上超时(npm install 和 TypeScript 编译均超过 2-3 分钟限制),因此无法启动守护进程进行实时 API 测试。 从主工作树验证的单元测试(全部通过):CLI 路由 5 个、限定 REST 25 个、facade 87 个、SDK 279 个、webui actions 8 个、web-shell logic 3 个。PR 作者报告本地 129 个 CLI Skills 测试、221 个 Web UI 测试和 149 个 Web Shell 测试通过。 — Qwen Code · qwen3.7-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — build/test agent did not complete. Not reviewed: chunk 2, chunk 5, chunk 11, chunk 3, chunk 14, chunk 10, chunk 9, chunk 6, chunk 7, chunk 13, chunk 1, chunk 16, chunk 15, chunk 4, chunk 12, chunk 8 — launched with a prompt that is not the one the CLI built. Not reviewed: Agent 0: Issue fidelity & root-cause ownership, Test coverage matrix (whole-diff), Agent 1b: Removed-behavior audit, Agent 1c: Cross-file tracer, Agent 7: Build & test verification, Invariant agent A: state, timers, collections — packages/cli/src/serve/routes/workspace-skills.ts, Invariant agent B: counters, return values, error taxonomies — packages/cli/src/serve/routes/workspace-skills.ts, Invariant agent C: config fields, early returns — packages/cli/src/serve/routes/workspace-skills.ts — its prompt was built, but no agent on record was launched with it. Not reviewed: reverse audit — no auditor was launched with a prompt this skill builds — the pass that hunts what the rest of the review missed ran, if at all, without the method its brief carries. Not reviewed: verification — the review posts findings, but no verifier was launched with a prompt this skill builds — they were ruled on, if at all, without the verdict bar its brief carries.
— qwen3.7-max via Qwen Code /review
| app.post( | ||
| '/workspace/config/skills/install', | ||
| deps.mutate({ strict: true }), | ||
| async (req, res) => { | ||
| const input = parseSkillInstallRequest(req, res, deps.safeBody); |
There was a problem hiding this comment.
[Critical] Missing requireTrustedWorkspaceRuntime trust-boundary check on both new singular config mutation routes (POST /workspace/config/skills/install and DELETE /workspace/config/skills/:name).
Failure scenario: Every existing singular mutation route (POST /workspace/skills/install at line 258, DELETE /workspace/skills/:name at line 292, POST /workspace/skills/:name/enable at line 337) calls requireTrustedWorkspaceRuntime(deps.workspaceRuntime, res) as its first statement. The qualified config routes (lines 617, 652, 685) also enforce it. But these two singular config routes omit the check entirely. When deps.workspaceRuntime is untrusted, an untrusted workspace can install or delete globally-scoped skills — operations that propagate to every managed runtime via affectedSkillRuntimes / invalidateSkillsInventory / scheduleSkillsConfiguration — while peer routes correctly return 403.
| app.post( | |
| '/workspace/config/skills/install', | |
| deps.mutate({ strict: true }), | |
| async (req, res) => { | |
| const input = parseSkillInstallRequest(req, res, deps.safeBody); | |
| app.post( | |
| '/workspace/config/skills/install', | |
| deps.mutate({ strict: true }), | |
| async (req, res) => { | |
| if (!requireTrustedWorkspaceRuntime(deps.workspaceRuntime, res)) return; | |
| const input = parseSkillInstallRequest(req, res, deps.safeBody); |
— qwen3.7-max via Qwen Code /review
| const skill = status?.runtimeSkills?.find( | ||
| (candidate) => candidate.name === skillName, | ||
| ); |
There was a problem hiding this comment.
[Suggestion] isSkillRuntimeConfirmed uses case-sensitive name matching (===), while mergeSkillsInventory and isSkillInConfigInventory both match by name.toLowerCase(). If config and runtime report different casing for the same skill name, the confirmation check will fail to find the skill even though the merge matched it correctly.
Concrete cost: After a successful toggle with activation: 'applied', isSkillRuntimeConfirmed searches runtimeSkills with candidate.name === skillName — no match on casing mismatch. The function returns false, and SkillsManagerPage.tsx overwrites the success presentation with { messageKey: 'skills.runtimeNotConfirmed', error: true }, showing the user an error despite the toggle having actually taken effect.
| const skill = status?.runtimeSkills?.find( | |
| (candidate) => candidate.name === skillName, | |
| ); | |
| const normalizedName = skillName.toLowerCase(); | |
| const skill = status?.runtimeSkills?.find( | |
| (candidate) => candidate.name.toLowerCase() === normalizedName, | |
| ); |
— qwen3.7-max via Qwen Code /review
| vi.mock('../DaemonWorkspaceProvider.js', () => ({ | ||
| useDaemonWorkspaceActions: () => actions, | ||
| })); |
There was a problem hiding this comment.
[Suggestion] useDaemonSkills accepts a workspaceCwdOverride parameter (used by SkillsManagerPage to scope operations to the selected workspace), but the hook's tests never pass this parameter and the mock of useDaemonWorkspaceActions discards its argument, so the override forwarding path is entirely untested.
Concrete cost: If a future refactor drops the workspaceCwdOverride argument in the useDaemonSkills → useDaemonWorkspaceActions call chain, the Skills manager page would silently operate on the primary workspace instead of the selected one — installs, deletes, and toggles would affect the wrong workspace with no test failure to detect it.
— qwen3.7-max via Qwen Code /review
| expect(configToggle.body).toMatchObject({ | ||
| skillName: 'review', | ||
| enabled: true, | ||
| }); |
There was a problem hiding this comment.
[Suggestion] The qualified config toggle test is exercised, but the adjacent qualified config delete route (DELETE /workspaces/:workspace/config/skills/:name through a trusted workspace) has no trusted happy-path test — only the untrusted rejection (403) is verified.
Concrete cost: A registration error in the DELETE /workspaces/:workspace/config/skills/:name route (e.g. a missing rejectQualifiedGlobalScope guard, or incorrect route pattern) would go undetected because the only trusted qualified config mutation tests cover install and toggle, not delete.
— qwen3.7-max via Qwen Code /review
|
@qwen-code /takeover |
🔍 Local Verification Report — PR #7311Branch: Test Results
tmux CLI Startup✅ 构建 core 后 CLI 正常启动(v0.20.0) Architecture Review论点: Skill 管理完全复用 #7308 所有权模型和 #7310 的 activation 模式——scope-based routing + coordinator 收敛 + 持久化/运行时分离。 论据:
论证:
Verdict✅ 412/412 测试全部通过,CLI 正常启动。设计与 #7308/#7310 模式一致,建议合并。 Verified locally: unit tests + CLI startup on macOS |
wenshao
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 2 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here. Unresolved, please confirm: [Critical] packages/cli/src/serve/routes/workspace-skills.ts:361 — existing blocker (comment 3613300355): missing requireTrustedWorkspaceRuntime on singular config routes. Mechanism is present, but the test explicitly asserts 200 for untrusted config install, and the docs say Read-only for untrusted workspaces — cannot determine whether this is deliberate design or a codified bug without author confirmation. Not reviewed: reverse audit of chunk 4 — the auditor failed to execute (system error). Not reviewed: reverse audit of chunk 13 — the auditor failed to execute (system error). Not reviewed: reverse audit of chunk 14 — the auditor failed to execute (system error). Not reviewed: reverse audit of chunk 16 — the auditor failed to execute (system error). Not reviewed: reverse audit — an auditor ran and opened its brief, but no agent was launched with the prompt the CLI built — the launch was written by hand, and what the agent was actually asked is not what this skill certifies.
中文说明
已审查。 建议见行内评论。 2 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。 未决,请确认:[Critical] packages/cli/src/serve/routes/workspace-skills.ts:361 — existing blocker (comment 3613300355): missing requireTrustedWorkspaceRuntime on singular config routes. Mechanism is present, but the test explicitly asserts 200 for untrusted config install, and the docs say Read-only for untrusted workspaces — cannot determine whether this is deliberate design or a codified bug without author confirmation. 未审查:reverse audit of chunk 4 — the auditor failed to execute (system error)。 未审查:reverse audit of chunk 13 — the auditor failed to execute (system error)。 未审查:reverse audit of chunk 14 — the auditor failed to execute (system error)。 未审查:reverse audit of chunk 16 — the auditor failed to execute (system error)。 未审查:反向审计——有审计 agent 运行并打开了自己的 brief,但没有 agent 是用 CLI 构建的 prompt 启动的——启动 prompt 是手写的,agent 实际被要求做的并不是本 skill 所认证的内容。
— qwen3.8-max-preview via Qwen Code /review
| harness.invalidateSecondarySkillsStatus.mockImplementationOnce(() => { | ||
| throw new Error('runtime removed'); | ||
| }); |
There was a problem hiding this comment.
[Suggestion] The test sets up invalidateSecondarySkillsStatus.mockImplementationOnce(() => { throw ... }) but never asserts the mock was called, so the invalidation-failure precondition is unverified. — Failure scenario: if a future refactor removes the invalidateSkillsInventory(runtimes) call, the mock throw is never triggered, the route still returns 200, and every assertion passes — silently dropping cross-workspace invalidation.
Add after the existing assertions:
expect(harness.invalidateSecondarySkillsStatus).toHaveBeenCalledOnce();中文说明
[Suggestion] 测试设置了 invalidateSecondarySkillsStatus.mockImplementationOnce(() => { throw ... }),但从未断言该 mock 被调用过,因此失效失败的前提条件未被验证。 — 失败场景:如果未来的重构移除了 invalidateSkillsInventory(runtimes) 调用,mock 的 throw 永远不会触发,路由仍返回 200,所有断言都会通过——静默丢弃跨工作区失效逻辑。
在现有断言后添加:
expect(harness.invalidateSecondarySkillsStatus).toHaveBeenCalledOnce();— qwen3.8-max-preview via Qwen Code /review
| const input = parseSkillToggleRequest(req, res, deps.safeBody); | ||
| if (!input) return; | ||
| const configRoute = |
There was a problem hiding this comment.
[Suggestion] The qualified enable route validates the skill name only via parseSkillToggleRequest (trim + length), while the sibling delete route validates via validateWorkspaceSkillName (trim + length + [A-Za-z0-9._-]+ allowlist), producing inconsistent error responses for the same invalid input. — Failure scenario: a name like ../../etc via POST enable returns 404 skill_not_found; the same name via DELETE returns 400 invalid_skill_name. Two different status codes for the same malformed name across sibling routes added in the same diff.
| const input = parseSkillToggleRequest(req, res, deps.safeBody); | |
| if (!input) return; | |
| const configRoute = | |
| const input = parseSkillToggleRequest(req, res, deps.safeBody); | |
| if (!input) return; | |
| try { | |
| input.skillName = validateWorkspaceSkillName(input.skillName); | |
| } catch (error) { | |
| sendSkillManagementError(res, error); | |
| return; | |
| } | |
| const configRoute = |
中文说明
[Suggestion] 限定的 enable 路由仅通过 parseSkillToggleRequest(trim + 长度)验证技能名称,而同级的 delete 路由通过 validateWorkspaceSkillName(trim + 长度 + [A-Za-z0-9._-]+ 白名单)验证,对相同的无效输入产生不一致的错误响应。 — 失败场景:类似 ../../etc 的名称通过 POST enable 返回 404 skill_not_found;相同名称通过 DELETE 返回 400 invalid_skill_name。同一 diff 中添加的同级路由对相同的格式错误名称返回两种不同的状态码。
— qwen3.8-max-preview via Qwen Code /review
| await expect(actions.reloadMcp()).resolves.toMatchObject({ | ||
| capabilities: { mcp: { state: 'ready' } }, | ||
| }); | ||
| expect(reloadWorkspaceRuntimeMcp).toHaveBeenCalledWith(65_000); |
There was a problem hiding this comment.
[Suggestion] Exact timeout assertion expect(reloadWorkspaceRuntimeMcp).toHaveBeenCalledWith(65_000) without fake timers is sensitive to real-clock drift between two Date.now() calls in the implementation. — Failure scenario: under CI load, Date.now() advances 1ms between the deadline computation and the remaining-timeout calculation, making the argument 64_999 and failing the test. Every other test in this diff that asserts exact timeout values uses vi.useFakeTimers().
| expect(reloadWorkspaceRuntimeMcp).toHaveBeenCalledWith(65_000); | |
| expect(reloadWorkspaceRuntimeMcp).toHaveBeenCalledWith(expect.any(Number)); |
Or add vi.useFakeTimers() at the top of this test (the afterEach already restores real timers).
中文说明
[Suggestion] 精确的超时断言 expect(reloadWorkspaceRuntimeMcp).toHaveBeenCalledWith(65_000) 未使用 fake timers,对实现中两个 Date.now() 调用之间的实际时钟漂移敏感。 — 失败场景:在 CI 负载下,Date.now() 在截止时间计算和剩余超时计算之间前进 1ms,使参数变为 64_999 并导致测试失败。此 diff 中其他所有断言精确超时值的测试都使用了 vi.useFakeTimers()。
— qwen3.8-max-preview via Qwen Code /review
| if (rejectQualifiedGlobalScope(input.scope, res)) return; | ||
| const configRoute = 'POST /workspaces/:workspace/config/skills/install'; |
There was a problem hiding this comment.
[Suggestion] The rejectQualifiedGlobalScope guard on workspace-qualified Skills install/delete routes has no test; the analogous guard for extensions IS tested (workspace-qualified-extensions.test.ts:347). — Failure scenario: if a future refactor drops one of these calls, a client could install or delete a process-global Skill through a workspace-qualified endpoint, and every existing test stays green.
Suggested fix: add a test that POST /workspaces/:id/config/skills/install with { scope: "global", ... } returns 400 with body.code === "global_scope_requires_singular_owner".
中文说明
[Suggestion] 工作区限定的 Skills install/delete 路由上的 rejectQualifiedGlobalScope 守卫没有测试;extensions 的类似守卫已有测试(workspace-qualified-extensions.test.ts:347)。 — 失败场景:如果未来的重构移除了这些调用之一,客户端可以通过工作区限定端点安装或删除进程全局 Skill,而所有现有测试保持绿色。
建议修复:添加测试,验证 POST /workspaces/:id/config/skills/install 带 { scope: "global", ... } 返回 400 且 body.code === "global_scope_requires_singular_owner"。
— qwen3.8-max-preview via Qwen Code /review
doudouOUC
left a comment
There was a problem hiding this comment.
中文说明
— qwen3.7-max via Qwen Code /review
| void reloadAfterMutation(mutation.activation).then(({ refreshError }) => { | ||
| if (refreshError === undefined) return; | ||
| setListNotice({ |
There was a problem hiding this comment.
[Suggestion] installSkill uses fire-and-forget void reloadAfterMutation(...).then(...) while deleteSkill properly awaits the same call. The .then callback unconditionally overwrites listNotice on error.
Failure scenario: User installs skill A — success notice appears, background reload starts. User installs skill B before A's reload completes — B's success notice replaces A's. A's reload then fails — the error notice for skill A overwrites B's success notice, showing the user a confusing error about a skill they are no longer focused on.
| void reloadAfterMutation(mutation.activation).then(({ refreshError }) => { | |
| if (refreshError === undefined) return; | |
| setListNotice({ | |
| const { refreshError } = await reloadAfterMutation(mutation.activation); | |
| if (refreshError !== undefined) { | |
| setListNotice({ | |
| text: `${t('skills.install.succeeded', { | |
| name: request.name.trim(), | |
| })} ${t(presentation.messageKey, { | |
| activation: mutation.activation ?? 'unknown', | |
| })} ${refreshFailureMessage(refreshError)}`, | |
| error: true, | |
| }); | |
| } |
中文说明
installSkill 使用 fire-and-forget 模式 void reloadAfterMutation(...).then(...),而 deleteSkill 正确地 await 了同一调用。.then 回调在出错时会无条件覆盖 listNotice。
故障场景: 用户安装技能 A — 成功通知出现,后台重新加载开始。用户在 A 重新加载完成前安装技能 B — B 的成功通知替换了 A 的。A 的重新加载随后失败 — A 的错误通知覆盖了 B 的成功通知,向用户显示一个不再关注的技能的错误信息。
— qwen3.7-max via Qwen Code /review
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here. Not reviewed: every dimension — none of the 24 required agents is on record as launched with a prompt this skill built, so this diff was reviewed, if at all, from prompts the run wrote for itself: no record shows the severity bar, the finding format or this project's own rules reaching an agent. Not reviewed: verification and reverse audit — each ran and opened its brief, but neither was launched with the prompt the CLI built — the launches were written by hand, so the posted findings cannot be counted as verified, and what the agents were actually asked is not what this skill certifies.
中文说明
已审查。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。 未审查:所有维度——24 个必需 agent 中没有任何一个有记录表明是用本 skill 构建的 prompt 启动的,这个 diff 即便被审查过,也是基于这次 run 自行编写的 prompt:没有记录表明严重级别标准、发现格式或本项目自己的规则到达过任何 agent。 未审查:验证与反向审计——两者都运行并打开了各自的 brief,但都不是用 CLI 构建的 prompt 启动的——启动 prompt 是手写的,发布的发现不能算作已验证,agent 实际被要求做的也不是本 skill 所认证的内容。
— qwen3.7-max via Qwen Code /review
| if (!effectWorkspaceCwd) { | ||
| throw new Error('Daemon workspace is not connected'); |
There was a problem hiding this comment.
[Critical] New throw creates an infinite retry loop when workspace CWD is missing in the deferred connect path — Failure scenario: effectWorkspaceCwd is optional (workspaceCwd?: string in capabilities type). When falsy, this throw is caught by the outer catch block which classifies it as retriable (not auth/terminal). With autoReconnect=true (default), the while loop retries after backoff, hits the same throw, and loops forever. The old code gracefully fell back to top-level client methods.
| if (!effectWorkspaceCwd) { | |
| throw new Error('Daemon workspace is not connected'); | |
| if (!effectWorkspaceCwd) { | |
| setConnection((current) => ({ | |
| ...current, | |
| status: 'error', | |
| error: 'Daemon workspace is not connected', | |
| })); | |
| return; | |
| } |
中文说明
[Critical] 新增的 throw 在工作区 CWD 缺失时会造成无限重试循环 — 失败场景:effectWorkspaceCwd 是可选的(capabilities 类型中 workspaceCwd?: string)。当值为假时,这个 throw 被外层 catch 捕获并归类为可重试错误(非 auth/terminal)。在 autoReconnect=true(默认值)的情况下,while 循环在退避后重试,再次触发相同的 throw,导致无限循环。旧代码会优雅地回退到顶层客户端方法。
— qwen3.7-max via Qwen Code /review
What this PR does
This PR moves Skills configuration and live validation onto the selected workspace runtime, refreshes new-task Skills and slash commands after management changes, and completes the shared management-page workspace scoping.
This is stack 4/4 and contains one commit on top of
codex/workspace-runtime-extensions.Why it's needed
Skills shown in management and new-task flows must reflect the selected workspace even when no session exists. Configuration should load immediately, while runtime validation can reconcile in the background without blocking the page.
Reviewer Test Plan
How to verify
Open Skills management without a session, verify configuration appears before background runtime reconciliation completes, then install, remove, enable, or disable a Skill. Open a new task and confirm the latest Skills and slash commands are available.
Evidence (Before & After)
Before: Skills and slash commands could remain stale until a session refreshed them. After: configuration loads immediately and workspace runtime reconciliation refreshes live validation and new-task commands.
Tested on
Environment (optional)
Local npm workspace. CLI, SDK, Web UI, and Web Shell type checks passed; 129 CLI Skills tests, 221 Web UI tests, and 149 Web Shell tests passed. End-to-end validation was not run.
Risk & Scope
Linked Issues
Stack 4/4: #7308 Runtime foundation → #7309 MCP → #7310 Extensions → #7311 Skills.
中文说明
本 PR 的改动
本 PR 将 Skills 配置和实时校验迁移到所选 Workspace Runtime,在管理页面变更后刷新新建任务的 Skills 和斜杠命令,并完成管理页面共享的工作区作用域处理。
这是堆叠 PR 的第 4/4 个,仅包含基于
codex/workspace-runtime-extensions的一个 commit。为什么需要
即使没有 Session,管理页和新建任务流程展示的 Skills 也必须反映所选工作区。配置应立即加载,Runtime 校验则可以在后台协调而不阻塞页面。
审查测试计划
如何验证
在没有 Session 时打开 Skills 管理页,确认配置会先于后台 Runtime 协调显示,然后安装、删除、启用或禁用 Skill。打开新建任务,确认最新 Skills 和斜杠命令可用。
前后对比证据
改动前:Skills 和斜杠命令可能一直过期,直到 Session 刷新。改动后:配置立即加载,Workspace Runtime 协调会刷新实时校验和新建任务命令。
测试平台
环境(可选)
本地 npm workspace。CLI、SDK、Web UI 和 Web Shell 类型检查通过;129 个 CLI Skills 测试、221 个 Web UI 测试和 149 个 Web Shell 测试通过;未执行端到端验证。
风险与范围
关联事项
堆叠 PR 第 4/4 个:#7308 Runtime 基础架构 → #7309 MCP → #7310 拓展 → #7311 Skills。