
qwen-code Daemon Skill 批量启停接口设计解析从 Settings 所有权模型到集合级路由【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code在 qwen-code 的 daemon 架构中Skill 的启用与停用并不等同于安装或卸载它是对工作区skills.disabled/skills.enabled/skills.defaultDisabled声明列表的一次持久化写入。daemon-skill-batch-toggle设计文档docs/design/daemon-skill-batch-toggle.md正式提出了集合级collection-level批量变更路由让远程 Skill 管理器可以像写工作区设置一样一次性提交最多 100 个 Skill 的启用/停用声明。读完本文你将掌握批量 toggle 的完整 HTTP 契约、请求校验与响应语义、enable/disable 在不同声明状态下的分支行为以及如何通过能力标签预检和 TypeScript SDK 安全调用该接口。一、设计动机为什么运行时快照不能充当 Settings 的所有权来源文档开篇即点明核心问题Remote Skill managers need both single and batch mutations to behave like workspace settings writes. A runtime Skill snapshot is not an ownership source forskills.disabledorskills.enabled.这句话有两层含义行为对齐远程 Skill 管理器如 Web Shell 的 Skills 管理页、SDK 客户端对 Skill 的启停操作本质上是写工作区设置而不是操作运行时进程。因此接口的语义必须与设置写入一致——声明可能先于安装存在也可能在目录卸载后依然有意存活。所有权边界当前加载的运行时 Skill 目录runtime snapshot不是skills.disabled/skills.enabled的所有权来源。被禁用的条目disabled以及可选的默认禁用defaultDisabledopt-in 声明完全可以在 Skill 安装之前就写入并且有意地比当前加载的目录活得久。从源码看这一所有权模型落实在 packages/cli/src/config/skill-settings.ts 中。该模块定义了SkillSettingListKey disabled | defaultDisabled | enabled三种列表键并提供了两个核心工具函数normalizeSkillNames对任意值做 trim、小写、去空过滤产出规范化后的名字集合updateWorkspaceSkillSettingLists给定{disabled, enabled}两组列表与目标名、目标状态返回启用则从 disabled 移除并在 enabled 中补入、停用则反向操作的新列表。这意味着 daemon 侧在持久化层面始终围绕声明列表做纯函数式更新与此刻哪个 Skill 已加载、是否可调用完全解耦。二、API 总览两条集合级路由文档定义了两条等价的集合级变更路由分别对应当前工作区与指定工作区两种寻址方式路由说明POST /workspace/skills/enable作用于当前绑定的工作区POST /workspaces/:workspace/skills/enable通过:workspace参数作用于工作区注册表中的指定工作区它们在 packages/cli/src/serve/routes/workspace-skills.ts 中注册registerWorkspaceSkillsRoutes挂载/workspace/skills/enable第 548 行起registerWorkspaceQualifiedSkillsRoutes挂载/workspaces/:workspace/skills/enable第 889 行起。两条路由都先经过deps.mutate({ strict: true })严格变更门再解析请求体最终统一调用workspaceService.setWorkspaceSkillsEnabled(...)。请求体格式如下{ skillNames: [review, deploy, missing], enabled: false }2.1 skillNames 校验规则parseSkillBatchToggleRequestworkspace-skills.ts实现了文档声明的全部约束并有源码级的常量背书非空字符串数组Array.isArray且长度大于 0数组中每一项必须是字符串否则直接返回 HTTP 400上限 100 条MAX_WORKSPACE_SKILL_BATCH_SIZE 100文件顶部常量超出即 400逐项 trim每个名字先trim()trim 后为空字符串同样报 400大小写不敏感去重、保留首次出现顺序用toLowerCase()归一化后放入seenSet 去重仅保留首次出现的原始拼写单名长度上限复用MAX_WORKSPACE_SKILL_NAME_LENGTH校验超长返回invalid_skill_nameenabled 必须为布尔值parseEnabledFlag校验body[enabled]必须是boolean否则返回invalid_enabled_flag。任何畸形请求都会整体失败并返回 HTTP 400不会出现部分成功。三、响应结构逐 Skill 结果与批量级激活状态一次成功结构有效的批量 toggle 返回如下响应示例为全部 disable 且命中声明变更{ enabled: false, activation: applied, sessionsRefreshed: 2, sessionsFailed: 0, results: [ { skillName: review, enabled: false, changed: true }, { skillName: deploy, enabled: false, changed: true }, { skillName: missing, enabled: false, changed: true } ], errors: [] }3.1 字段语义enabled回显请求中的目标状态activation批量级激活结果取值包括applied已应用、deferred延迟生效、partial部分失败、reconciling协调中见下等sessionsRefreshed/sessionsFailedlive session 刷新统计。daemon 在有实际变更时会对活跃 ACP session 做一次共享刷新workspaceSkillsRefreshreason: settingsresults逐 Skill 结果数组严格保留请求顺序每项含skillName/enabled/changederrors为线上兼容而保留的字段结构有效的名字数组下恒为空数组。3.2 activation 与 changed 的独立语义文档特别强调批量级activation独立于每个 result 的changed标志它反映的是子进程活性child liveness与所需共享刷新的完成情况而不是有没有声明被改。因此一个all-no-op 批次没有任何声明被变更的activation可能是applied或deferred且不会触发任何刷新只要存在一个changed: true且通道存活daemon 就会发起一次共享刷新sessionsRefreshed/sessionsFailed才可能非零。这一逻辑在 packages/cli/src/serve/workspace-service/index.ts 的单 Skill 实现setWorkspaceSkillEnabled中有清晰体现persisted.changed为真时才invalidateWorkspaceSkillsSnapshot()并触发刷新刷新失败时若异常是SessionNotFoundError/BridgeChannelClosedError则回退deferred否则置partial并累计sessionsFailed。四、enable / disable 的分支语义一次加锁写入 一次刷新daemon不读取也不校验运行时 Skill 状态The daemon does not read or validate against runtime Skill status。它把所有声明变更合并在至多一次加锁的设置写入中完成并在确有变更时执行一次 live-session 刷新。4.1 enable 的三条路径对某个 Skill 执行 enableenabled: true时按现有声明状态分三种情况存在 workspaceskills.disabled条目移除该匹配条目已存在 workspaceskills.enabled声明保留并规范化该声明trim 大小写归一化去重存在生效的skills.defaultDisabled条目且无 workspace 声明记录一次 opt-in写入skills.enabled从而覆盖默认禁用。若既无 workspace 声明、又无生效的defaultDisabled条目enable 是no-op该条结果changed: false。4.2 disable 与不设防的名字disableenabled: false则直接写入skills.disabled未知名字、非用户可调用non-user-invocable的名字、处于 inactive 状态的 Extension 提供的名字、以及被更高作用域禁用的名字——全部走同一条 settings 写入路径不会因为运行时查无此 Skill 而报错或跳过更高作用域如 system / user仍然在 settings 合并后决定有效可用性但这不妨碍 workspace 作用域记录自己的声明。也就是说即使某个 Skill 在 system 作用域被硬禁用hard禁用见skill-settings.ts中SkillDisablement.reasonworkspace 依然可以写入自己的声明最终生效与否由合并结果裁决。4.3 失败语义意外的持久化失败与 runtime-generation 失败会让整个请求失败不是部分成功。源码中路由层在 catch 分支统一invalidateConfigStatus后走sendBridgeError保证异常场景下响应是整体性错误而非零散结果。4.4 信任与鉴权门workspace 信任trust、认证authentication、客户端身份client identity与 generation 所有权generation ownership四道门与单 Skill 路由完全一致。从实现看两条集合路由都经requireTrustedWorkspaceRuntime未信任的工作区返回未信任响应客户端身份通过parseAndValidateClientId/parseAndValidateWorkspaceClientId校验限定路径/workspaces/:workspace/...额外经过resolveWorkspaceRuntimeFromParam解析且需要requireRuntimeCoordinator不支持运行时生命周期协调时返回 501workspace_runtime_not_supported。五、兼容性与能力标签先预检、再调用文档给出了明确的兼容性约定新能力标签workspace_skill_settings_batch_toggle与workspace_skill_settings_toggle分开广告分别对应批量路由与单 Skill 路由这两个标签取代已退役的workspace_skill_batch_toggle与workspace_skill_toggle——旧标签对应的目录校验型契约catalog-validated与新的纯设置写入不兼容必须整体更换客户端必须先预检设置能力pre-flight再调用不变的路由。在 packages/cli/src/serve/capabilities.ts 中可以看到两个新标签均以since: v1注册workspace_skill_settings_toggle: { since: v1 }, workspace_skill_settings_batch_toggle: { since: v1 },packages/cli/src/serve/server.test.ts 的标签清单测试也同时断言了这两个能力的存在。5.1 单 Skill 路由的返回名约定单 Skill 路由POST /workspace/skills/:name/enable以及限定版POST /workspaces/:workspace/skills/:name/enable返回的是trim 后的请求名而不是目录中的规范拼写——因为设置写入路径没有目录查找无法从 catalog 反查 canonical spelling。这要求客户端自行保证传入名字的拼写准确性。5.2 HTTP-onlyACP 面保持只读集合路由是HTTP-only的ACP 的_qwen/workspace/skillsdispatch 面保持只读与单 Skill toggle 的定位一致。即 Skill 启停这类设置变更不通过 ACP 工具调用面暴露只能走 daemon 的 HTTP REST 层。六、TypeScript SDK如何安全调用批量接口packages/sdk-typescript/src/daemon/DaemonClient.ts 提供了两个对称的封装方法setWorkspaceSkillEnabled(skillName, enabled, opts?)第 4432 行——单 Skill 版本文档注释明确要求调用前预检caps.features.includes(workspace_skill_settings_toggle)setWorkspaceSkillsEnabled(skillNames, enabled, opts?)第 4466 行——批量版本注释明确要求预检caps.features.includes(workspace_skill_settings_batch_toggle)内部发往POST /workspace/skills/enable返回DaemonSkillBatchToggleResult。典型的调用序列Web Shell Skills 管理页与 SDK 客户端通用const caps await client.getCapabilities(); if (caps.features.includes(workspace_skill_settings_batch_toggle)) { const result await client.setWorkspaceSkillsEnabled( [review, deploy, missing], false, ); // result.activation: applied | deferred | partial | ... // result.results: 逐 Skill 变更结果顺序与请求一致 }同样地限定工作区版本的 SDK 方法setWorkspaceSkillEnabled/setWorkspaceSkillsEnabled第 7638 / 7651 行将请求发往/workspaces/:workspace/skills/:name/enable与/workspaces/:workspace/skills/enable适用于多工作区场景。Web Shell 前端的能力预检也可以从 packages/web-shell/client/components/skills/SkillsManagerPage.tsx 及其测试SkillsManagerPage.test.tsx中看到实际用法先检查workspace_skill_settings_toggle特性再展示/启用对应控件。七、测试覆盖契约与语义的落地验证批量 toggle 的契约在仓库中有多层测试保障packages/cli/src/serve/routes/workspace-skills.test.ts路由层测试覆盖POST /workspace/skills/enable与限定工作区版本的请求解析空数组、超 100 条、非法 enabled 类型等 400 分支及成功响应结构packages/cli/src/serve/workspace-qualified-rest.test.ts限定工作区 REST 路由的端到端行为验证packages/cli/src/serve/workspace-service/tests/facade.test.ts 与 packages/cli/src/serve/workspace-skills-status.test.tsservice 层对声明合并与状态失效的验证packages/cli/src/config/skill-settings.test.tsupdateWorkspaceSkillSettingLists/computeWorkspaceSkillListUpdates等纯函数的列表更新语义测试packages/sdk-typescript/test/unit/DaemonClient.test.ts 与 daemon-public-surface.test.tsSDK 方法与能力标签的公共面测试。八、小结daemon-skill-batch-toggle的设计核心可以概括为一句话Skill 启停是设置写入不是运行时操作。围绕这一原则批量接口在 HTTP 层做到了声明与目录的解耦——运行时快照不拥有disabled/enabled/defaultDisabled声明未知与不可调用的名字照常写入更高作用域仅影响合并后的有效可用性而不阻止 workspace 声明在一致性层做到了一次加锁写入 一次共享刷新的原子语义results严格保序、activation独立反映子活性在演进层面用workspace_skill_settings_batch_toggle/workspace_skill_settings_toggle两个新能力标签取代旧目录校验型标签并要求客户端先预检再调用。对于需要批量编排 Skill 状态的远程管理器Web Shell、SDK 客户端、自动化脚本这套契约提供了可预检、可拆分、可重试的稳定入口。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考