qwen-code 守护进程工作区运行时 Skills 管理:config/skills 与 runtime/skills 双目录机制解析
2026/9/15 14:17:49 网站建设 项目流程

qwen-code 守护进程工作区运行时 Skills 管理:config/skills 与 runtime/skills 双目录机制解析

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

导读

本文围绕 docs/design/daemon-workspace-runtime-skills.md 这一设计文档,深入讲解 qwen-code 守护进程(daemon)如何在不依赖聊天会话、不将内部快照缓存暴露为 API 状态的前提下,实现 Skills(技能)管理的"工作区感知"(workspace-aware)。你将掌握config/skills(守护进程本地持久清单)与runtime/skills(活跃工作区运行时目录)的职责划分、runtimeEpochrevision的新鲜度判定规则、workspace_skills_config_runtime能力通告机制,以及 Web Shell 端在 Skills 管理页和新会话编排中的实际加载流程。文中所有结论均以当前仓库源码与测试用例为依据,可直接对照验证。


一、设计目标:Skills 管理的工作区感知

传统模式下,Skills 目录的读取与刷新强绑定在某个 ACP(Agent Client Protocol)会话上下文中:要么需要先建立一个聊天会话才能查询 Skills,要么直接依赖 daemon 内部快照缓存,导致客户端看到的"目录状态"与真实运行状态之间存在隐藏的耦合。

该设计文档给出的目标非常明确:

  1. Skills 管理做到工作区感知(workspace-aware),而不必先建立聊天会话;
  2. 不把 daemon 内部的快照缓存暴露成公开 API 状态——快照缓存只是实现细节,公开协议上不新增任何 cache/source 状态。

换句话说,Skills 的"配置事实"与"运行时事实"被彻底分层:配置层回答"这个工作区装了哪些 Skill、启用了哪些",运行时层回答"当前活跃的 ACP 运行时实际加载了哪些 Skill"。这两层各自独立可读、可验证,且在运行时能力就绪后按明确的规则合并。


二、所有权与数据分层:config 与 runtime 两条目录

设计文档将 Skills 状态明确划分为两个所有权边界:

层面数据来源读取代价对应 API
config/skillsdaemon 本地、持久化的 Skills 清单不启动、不查询 ACP,纯本地读取GET /workspace/config/skills
runtime/skills选中的活跃工作区运行时返回的目录由运行时产生,携带产生它的runtimeEpochGET /workspace/runtime/skills
  • config/skills是 daemon 本地的持久清单(durable inventory),例如.qwen/skills下的已安装 Skill 目录、skills.disabled/skills.enabled设置项等。读取它不会拉起 ACP 子进程。
  • runtime/skills选中的活跃工作区运行时返回的目录快照。它必须携带产生该目录时的运行时纪元(runtimeEpoch),用于判断目录是否仍然"当前"。

运行时协调器的职责:工作区运行时协调器(Workspace Runtime Coordinator)负责运行时的准备(preparation)与对账(reconciliation)。其 Skills 能力项对外报告三个字段——statenot_started/starting/ready/stale/error)、revisionruntimeEpoch

变更路由规则(这是工作区感知的核心):

  • 用户级(user-global)变更单数工作区路由/workspace/...),提交后对所有可信的托管运行时做对账(reconcile);
  • 项目级(project)变更与开关(toggle)限定工作区路由/workspaces/:workspace/...),只对该运行时做对账。

从源码看,这一规则在 packages/cli/src/serve/routes/workspace-skills.ts 的reconcileSkills函数中实现:它遍历运行时列表,对每个运行时先调用invalidateWorkspaceSkillsStatus()使旧目录失效,再对可信运行时调用协调器的reconcileSkillsConfiguration()。测试用例commits global config without using the legacy runtime refresh验证了用户级安装提交后,invalidateSkillsConfigStatus会同时作用于两个工作区(/workspace/workspace-2),见 packages/cli/src/serve/routes/workspace-skills.test.ts。


三、新鲜度规则:epoch 相等才能合并,revision 只用于进程内排序

设计文档对"什么时候可以把运行时目录合并给消费者看"给出了严格的判定条件,这也是整套机制里最容易被误解的部分:

消费者只有在 Skills 能力状态为ready能力自身与目录的runtimeEpoch都等于当前运行时的runtimeEpoch时,才能合并运行时独有(runtime-only)的 Skills;否则一律回退使用 config 清单。

两个概念必须分清:

  • runtimeEpoch(运行时纪元):标识产生目录的运行时代次。ACP 子进程每次重启(如信任策略变更、运行时重建)都会产生新纪元。目录与能力的 epoch 与当前运行时 epoch 不一致,即代表"过时"。
  • revision(修订号):仅在单个 daemon 进程内为变更排序,不持久化,消费者也不得持久化它。它不能跨进程、跨 epoch 比较。

在 packages/cli/src/serve/workspace-runtime-coordinator.ts 中可以看到协调器初始状态为{ state: 'not_started', revision: 0 }status()方法(L155-L185)在能力已有runtimeEpoch但运行时未存活、或 epoch 与当前快照不一致时,会把该能力状态降级标记为stale,同时保留其revision与旧runtimeEpoch供诊断。而在ensure()的准备流程中,skillsReady的判定正是"state === 'ready'capabilities.skills.runtimeEpoch === status.runtimeEpoch"(L233-L239)。

设计文档还特别强调:daemon 既有的快照缓存仍然是实现细节,不新增任何公开的 cache/source 状态。这保证了 API 面的简洁性——客户端永远只需要关心 config 与 runtime 两个显式来源。


四、能力通告与路由契约:workspace_skills_config_runtime

该特性的对外开关是能力标签workspace_skills_config_runtime。在 packages/cli/src/serve/capabilities.ts 的能力注册表中登记,且属于条件性通告(conditional)能力:只有当toggles.workspaceRuntimeAvailable === true(即每个活跃工作区都支持权威的运行时生命周期)时才在/capabilities中宣告,见 CONDITIONAL_SERVE_FEATURES。客户端必须先 pre-flight 该标签,再决定是否调用拆分路由——这是 qwen-code serve 协议"标签存在 = 行为开启"的一贯约定。

4.1 拆分后的路由面

依据 docs/developers/qwen-serve-protocol.md 的协议描述,拆分后的核心路由如下:

读取(config 面,纯本地、无需运行时存活)

  • GET /workspace/config/skills:读取 daemon 本地全局配置所有者;
  • GET /workspaces/:workspace/config/skills:读取指定注册工作区的配置,不要求运行时存活或可信

响应为标准 Skills 状态形状,不要求runtimeEpoch

{ "v": 1, "workspaceCwd": "/work/project", "initialized": true, "skills": [] }

读取(runtime 面,不启动运行时)

  • GET /workspace/runtime/skillsGET /workspaces/:workspace/runtime/skills:读取选中的可信运行时目录而不启动它。活跃目录携带产生它的运行时纪元:
{ "v": 1, "workspaceCwd": "/work/project", "initialized": true, "runtimeEpoch": 4, "skills": [] }

写入(config 面)

  • 用户级:POST /workspace/config/skills/installDELETE /workspace/config/skills/:name?scope=global
  • 工作区级:POST /workspaces/:workspace/config/skills/installDELETE /workspaces/:workspace/config/skills/:name?scope=workspacePOST /workspaces/:workspace/config/skills/:name/enable

4.2 激活语义:deferred 与 reconciling

config 写入总是先提交持久状态、再调度运行时对账。响应中的activation字段语义如下:

  • deferred:当前没有可更新的运行时(对账被延后);
  • reconciling:对账请求已排队,正在协调器执行;
  • 无操作(no-op)的开关保持门面已有的激活值,不得谎称发生了运行时刷新

对应源码中reconcileSkills的返回逻辑(workspace-skills.ts L65-L80):只要任一可信运行时的协调器返回'reconciling',整体就是reconciling;否则为deferred。测试reports a live qualified toggle as reconciling(workspace-skills.test.ts L365-L375)与commits global config without using the legacy runtime refresh(断言activationdeferred)分别覆盖了两种分支。

4.3 结构化错误码

从路由实现与测试中可归纳出以下关键错误码(均以{error, code}结构返回):

错误码HTTP 状态触发场景
skills_config_unavailable503config 枚举失败时,删除/变更操作不给出确定性的 not-found,而是报配置不可用
skill_not_found404目标 Skill 不存在(含ENOENT映射)
skill_not_managed409大小写不敏感删除存在歧义,无法唯一确定目标
global_scope_requires_singular_owner400在限定工作区路由上使用scope=global
workspace_scope_requires_qualified_workspace400在单数路由上使用scope=workspace
untrusted_workspace403对非可信工作区执行限定写操作
workspace_runtime_not_supported501旧式注入桥不支持运行时协调
invalid_skill_name/invalid_skill_names400名称超长(256 字符上限)、为空或非法
invalid_enabled_flag400enabled缺失或非布尔值
workspace_runtime_unavailable503变更期间工作区代次关闭(workspace_generation_closed

此外,批量开关(POST /workspace/skills/enable)的单次上限为100 个名称,且在去重前计数(重复项无法绕过上限),见 workspace-skills.ts L35 与测试validates Skill batch request shape before calling the service(workspace-skills.test.ts L759-L825)。


五、Web Shell 端行为:从列表页到新会话编排

5.1 Skills 管理页

当能力标签workspace_skills_config_runtime被通告时,Web Shell 的 Skills 页面(packages/web-shell/client/components/plugins/PluginManagerPage.tsx)采用以下流程:

  1. 先加载 config:立即展示选中工作区的配置 Skills(快、纯本地);
  2. 后台确保运行时:再在后台调用ensureRuntime()确保选中运行时,然后读取该运行时的目录;
  3. 多工作区选择器:当注册了多个工作区时,列表页展示工作区选择器,详情页展示同一个但禁用的选择器(防止在详情视图中切换上下文),见 PluginManagerPage.tsx L95-L109;
  4. 无该特性时回退:保持旧的"主工作区(primary workspace)"路由,并且为 Skills 而 ensure 运行时。

5.2 新会话编排与延迟会话引导

新会话 composer 与延迟会话引导(deferred session bootstrap)的拆分读取同样以workspace_skills_config_runtime为门槛,二者逻辑一致:

  1. 先展示选中工作区的 config Skills(立即可用);
  2. 在后台 ensure 该运行时;
  3. 运行时就绪后,用当前 epoch 的运行时目录替换 config 目录。

该流程在 packages/web-shell/client/App.tsx 的reloadLoadedSkills中落地:forNewSession且支持拆分特性时,先workspaceConfigSkills()填充loadedSkills,随后ensureRuntime()+loadReadyWorkspaceSkills(),用运行时目录二次覆盖。每次异步返回前都会用请求序号(loadedSkillsRequestRef)做竞态检查,丢弃过期结果。

与之配套的测试在 packages/web-shell/client/daemon/session/DaemonSessionProvider.test.tsx:uses the Skills runtime API for a new task when advertised断言了workspaceConfigSkillsensureRuntimeruntimeStatusworkspaceRuntimeSkills各调用一次,且不再调用旧的workspaceSkills/workspaceAcpStatus/workspaceAcpPreheat;而skips all Skill preparation when prefetch is disabled则验证了关闭预取后,无论是否通告拆分特性,所有 Skills 相关调用都会被跳过。

其他消费者不使用该特性——设计文档明确限定只有 Skills 管理页与新会话编排接入,避免扩大行为面。


六、兼容性策略:新旧路由并存

设计文档的兼容性要求有两层:

  1. 旧 Skills 路由保持同步刷新行为不变GET /workspace/skillsPOST /workspace/skills/installPOST /workspace/skills/enable等旧路由(能力标签workspace_skills)继续工作,老客户端不受影响。测试invalidates config status after qualified legacy mutations(workspace-skills.test.ts L584-L611)验证了四条旧式限定路由在变更后都会使 config 状态失效。
  2. 新 config 路由禁用旧式刷新,并委托恰好一次运行时对账:新的 config 写入不再走旧的"读目录→刷新运行时"链路,而是由协调器统一调度reconcileSkillsConfiguration()。协调器内部通过skillsRevision递增 + 修订号比对来去重(if (revision !== this.skillsRevision) return;,见 workspace-runtime-coordinator.ts L314-L316),保证同一代次的变更只被处理一次。

这种"新路由委托、旧路由保留"的渐进式迁移,让支持多工作区的 daemon 与旧式主工作区客户端可以在同一部署中共存。


七、源码级验证:从测试用例看契约边界

除上文引用的测试外,以下测试进一步固化了设计文档中的契约,值得读者对照阅读:

  • packages/cli/src/serve/routes/workspace-skills.test.tskeeps config reads daemon-local and runtime reads explicit):GET /workspace/config/skills只调用getSkillsConfigStatus调用运行时状态;GET /workspace/runtime/skills恰好调用一次getWorkspaceSkillsRuntimeStatus——这正是"config 纯本地、runtime 显式读取"的直接验证。
  • L220-L237does not report a missing Skill when config enumeration fails):config 枚举失败时删除操作返回 503skills_config_unavailable,且不触碰服务层。
  • L239-L302(大小写敏感/歧义删除):config 清单中存在多个仅大小写不同的名称时,删除返回 409skill_not_managed,防止误删。
  • L304-L337(限定读取兼容非可信与替换中工作区):GET /workspaces/:workspace/config/skills对非可信、以及正在替换(transitioning)的工作区仍可读,且全局安装被拒绝。
  • L377-L395(旧式桥返回 501):限定 config 写入在旧式注入桥(无生命周期快照能力)上返回workspace_runtime_not_supported

八、总结

qwen-code 的守护进程工作区运行时 Skills 设计,本质上是一次"状态来源分层"的工程实践:

  • config/skills 负责"事实":daemon 本地持久、随时可读、不依赖 ACP;
  • runtime/skills 负责"运行真相":由活跃运行时产生,用runtimeEpoch标注代次;
  • 协调器负责"对账":以revision在进程内排序变更、以stale状态表达代次失配,将"配置变更 → 运行时生效"收敛为一次权威调度;
  • Web Shell 负责"渐进呈现":先给 config,再在后台补 runtime,保证 UI 永远有东西可看、且最终收敛到当前代次的真实目录。

对于希望为多工作区 daemon 扩展管理面的开发者,这套"能力标签 + 条件通告 + 拆分路由 + 唯一对账入口"的模式本身就是一个可复用的参考模板:新增面向工作区的管理路由时,优先考虑 config/runtime 分层与runtimeEpoch校验,而不是把快照缓存直接暴露给客户端。

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询