
Qwen Code Workspace Skills installedPath 字段设计技能安装路径的契约、数据流与脱敏边界【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读本文围绕 docs/design/2026-07-13-workspace-skills-installed-path.md 设计文档系统讲解 Qwen Codeqwen-code工作区技能Workspace Skills状态接口中新增的installedPath字段它如何把每个技能SKILL.md的绝对路径从SkillConfig原样映射到状态响应、如何在 live ACP 快照与 daemon 本地回退两条数据链路中保持一致以及为什么它被严格限定在元数据 allowlist 内而不泄露技能正文。读完本文你将掌握该字段的完整契约、兼容性策略、底层映射实现与脱敏边界并能在自己的客户端中安全地消费该字段。一、背景为什么状态接口需要暴露技能安装路径在 Qwen Code 中技能Skill以目录形式存在于磁盘上每个技能目录内有一个带 YAML frontmatter 的SKILL.md文件。核心类型SkillConfig在 packages/core/src/skills/types.ts 中定义其中filePath: string—— Absolute path to the skill directory containing SKILL.md即指向SKILL.md文件的绝对路径level: SkillLevel—— 存储层级project/user/extension/bundledskillRoot?: string—— 技能根目录用于向技能 hooks 注入QWEN_SKILL_ROOT环境变量body: string—— frontmatter 之后的 Markdown 正文。当客户端通过工作区状态接口枚举技能时得到的每条技能状态记录里只有名称、描述、层级、是否可被模型调用等元数据却缺少这个技能文件到底装在哪里这一信息。installedPath字段的引入就是为了把SkillConfig.filePath这份安装路径显式地带到状态响应中让客户端无需猜测技能文件的落盘位置即可定位、校验乃至管理对应的SKILL.md。值得注意的设计取舍是值按原样复制copied as stored状态层不做二次 symlink 解析也不做路径规范化。也就是说filePath中如果本身是符号链接或带有未规范化的成分installedPath会原封不动地呈现客户端拿到的是SkillManager 眼中的原始路径而不是经过realpath之后的物理路径。这保证了状态输出与内部SkillConfig记录之间可精确互推避免了在不同层各自 canonicalize 导致的两份路径不一致。二、契约Contract两条路由、一个必填字段设计文档明确约定GET /workspace/skills与GET /workspaces/:workspace/skills返回的每个技能条目都必须包含installedPath其值就是该技能SkillConfig.filePath的现有绝对路径指向其SKILL.md文件。从当前仓库的路由实现看技能状态实际上以两种视图暴露见 packages/cli/src/serve/routes/workspace-skills.ts配置视图configGET /workspace/config/skills与GET /workspaces/:workspace/config/skills返回技能配置状态快照运行时视图runtimeGET /workspace/runtime/skills与GET /workspaces/:workspace/runtime/skills返回运行时会话视角的技能状态。两种视图最终共享同一份由 mapper 产出的技能状态数组详见下文数据流因此无论客户端从哪条路由读取技能条目的字段形状都完全一致。一个典型的响应条目形如{ kind: skill, status: ok, name: review, description: Review changed code, level: bundled, modelInvocable: true, argumentHint: [pr-number], installedPath: /path/to/qwen-code/packages/core/src/skills/bundled/review/SKILL.md }契约还规定installedPath是逐条技能携带的字段不存在时该技能条目应被认定为不在可管理范围内见下文删除流程对它的依赖。三、兼容性Compatibility附加型 v1 字段客户端侧保持可选installedPath是附加型additivev1 字段其兼容性策略在协议层非常克制Daemon 侧总是发出当前版本的 daemonqwen serve始终输出该字段不设开关、不依赖能力协商ACP bridge 与 TypeScript SDK 侧保持可选公共状态类型中该字段被声明为installedPath?: string即使连接的是旧版 daemon不输出该字段新客户端也不会因字段缺失而解析失败协议版本与能力列表均不变STATUS_SCHEMA_VERSION不升版capability 列表不新增条目——这是一次纯粹加字段的演进对既有消费者完全向后兼容。这一策略在类型定义中有直接体现ACP bridge 侧packages/acp-bridge/src/status.ts 中ServeWorkspaceSkillStatus将installedPath?: string声明为可选并注释其语义为技能SKILL.md的安装路径TypeScript SDK 侧packages/sdk-typescript/src/daemon/types.ts 中DaemonWorkspaceSkillStatus同样以installedPath?: string可选字段暴露。对 SDK 消费者而言正确的消费姿势是把它当作存在性可空字段处理import type { DaemonWorkspaceSkillStatus } from qwen-code/sdk-typescript/daemon/types; function skillMarkdownPath(skill: DaemonWorkspaceSkillStatus): string | null { return skill.installedPath ?? null; // 旧 daemon 不输出该字段按 null 处理 }四、数据流Data Flow一条 mapper两个出口installedPath的产生链路非常短但覆盖了两条关键数据出口。核心实现位于 packages/cli/src/runtime/workspace-skills-mapping.tsexport function mapSkillConfigToStatus( skill: SkillConfig, disablements: ReadonlyMapstring, SkillDisablement new Map(), opts: { disabled?: boolean; enabled?: boolean } {}, ): ServeWorkspaceSkillStatus { // ... return { kind: skill, status: disabledReason ? disabled : ok, name: skill.name, description: skill.description, level: skill.level, modelInvocable, // ... installedPath: skill.filePath, // ← 安装路径原样拷贝 // ... }; }整个数据流可以拆成四步数据源SkillManager.listSkills()返回SkillConfig记录数组。SkillManager由qwen-code/qwen-code-core提供负责按层级扫描技能目录、解析SKILL.mdfrontmatter、编译 glob 规则映射共享的mapSkillConfigToStatus()把每条SkillConfig转成ServeWorkspaceSkillStatus其中filePath被直接拷贝为installedPath两个出口live ACP 快照ACP child 的buildWorkspaceSkillsStatus与 daemon 本地回退workspace-skills-statusprovider使用同一个 mapper 函数因此 project、user、bundled、extension、inactive-extension、disabled 等各种来源的技能产出的是完全一致的形状统一转发workspace status service 把这份共享结果同时转发给GET /workspace/skills与GET /workspaces/:workspace/skills两条路由形态。两个出口共用 mapper不是巧合而是刻意设计。mapper 源码头部的注释明确写道它被 ACP child 的buildWorkspaceSkillsStatus与 daemon 本地的workspace-skills-statusprovider 共享so the two skill listings can never drift in shape两份技能列表的形状永远不会漂移。这正是本项目对同一事实、多视图呈现的一贯做法单一映射函数 多路复用杜绝两份实现各自演化导致的字段差异。4.1 出口一live ACP 快照在正常会话生命周期中ACP child 进程持有活的SkillManager由它回答技能状态。child 内通过buildWorkspaceSkillsStatus位于 packages/cli/src/acp-integration/acpAgent.ts调用同一个 mapper此时installedPath反映的是会话内真实加载的技能文件路径。4.2 出口二daemon 本地回退当 ACP child 尚未就绪时例如冷启动阶段会话尚未创建、preheat 握手超时/workspace/skills由 daemon 本地 provider 应答。该 provider 位于 packages/cli/src/serve/workspace-skills-status.ts其工作方式值得展开它不启动 child、不初始化 MCP仅从文件系统直接枚举技能因此 daemon 可以瞬时应答SkillManager.listSkills()实际上只读取少量ConfiggetterisSafeMode、getBareMode、getProjectRoot、getActiveExtensions、getDisabledSkillLevels于是 provider 用PickConfig, ...构造一个轻量 config shim避免完整构造Config也就避免了initialize()的副作用每个 workspace 复用同一个SkillManager实例managersMap 缓存重复查询走内存缓存而不是重新扫描磁盘回退视图刻意省略 extension 提供的技能child 之外没有活跃扩展上下文这些技能在会话建立后由会话快照呈现捕获异常时返回initialized: false的空状态并输出 stderr 诊断而不是崩溃——此时技能列表交由 live child 全权负责。无论走哪条出口每条技能记录里的installedPath都来自同一 mapper、同一份SkillConfig.filePath形状完全一致。五、脱敏边界Redaction Boundary显式元数据 allowlistinstalledPath的加入没有扩大状态接口的信息暴露面。mapper 本身就是一个显式的元数据 allowlist它只挑选name、description、level、modelInvocable、installedPath、argumentHint、model、extensionName、extensionDisplayName等元数据字段输出而明确不暴露bodySKILL.md的 Markdown 正文hooks技能注册的 hooks 配置可能携带命令与脚本细节skillRoot技能根目录用于注入QWEN_SKILL_ROOT环境变量其他任何未列入 allowlist 的技能配置。从实现看packages/cli/src/runtime/workspace-skills-mapping.ts 的返回对象是逐字段手写的字面量而不是把整个SkillConfig展开后剔除字段——这正是显式 allowlist而非黑名单的代码级体现新增字段必须显式列出才会进入状态未来任何技能配置新增项默认都不会泄漏到状态接口。设计文档同时明确本次变更不添加任何 UI 行为纯属协议/接口层的补充。对安全审计而言这条边界的意义在于客户端虽然能知道技能文件装在哪个路径但无法通过状态接口读取技能内容本身技能正文的读取仍然走文件系统自身的权限与 trust 体系与状态接口无关。六、实战客户端如何消费 installedPath6.1 定位技能文件拿到installedPath后客户端可以直接定位SKILL.md并读取 frontmatter 做展示或把技能条目与编辑器文件树关联起来const res await fetch(http://127.0.0.1:PORT/workspace/config/skills); const status await res.json(); // ServeWorkspaceSkillsStatus for (const skill of status.skills) { // skill.installedPath 形如 /path/to/workspace/.qwen/skills/review/SKILL.md console.log(${skill.name} (${skill.level}) - ${skill.installedPath}); }6.2 删除技能时以 installedPath 定位管理对象installedPath并非只读展示字段——它在删除流程中承担了判定技能是否可管理的职责。在 packages/cli/src/serve/routes/workspace-skills.ts 的deleteConfiguredSkill中先按名称大小写不敏感在状态列表中匹配候选技能再按scope对应的期望层级workspace→projectglobal→user过滤若匹配到的技能没有installedPath直接抛skill_not_managedHTTP 409——即该技能不在请求作用域的可管理范围内有路径时把installedPath连同workspaceCwd、scope、name一并交给deleteSkillConfig执行实际删除。也就是说installedPath从设计上就服务于状态枚举 → 管理操作这条闭环先枚举得到路径再用路径定位并删除。客户端在调用删除接口前可以从状态响应中确认目标技能的installedPath是否存在以此预判 409 风险async function deleteSkill(name: string, scope: workspace | global) { const res await fetch( http://127.0.0.1:PORT/workspace/config/skills/${encodeURIComponent(name)}?scope${scope}, { method: DELETE }, ); if (res.status 409) { // 技能不在请求 scope 的可管理范围内installedPath 缺失 } }6.3 注意旧 daemon 兼容由于 ACP bridge 与 SDK 类型中installedPath均为可选任何依赖路径存在的逻辑都必须做存在性判断否则在连接旧版 daemon 时会出现undefined访问错误。七、测试验证mapper 行为有据可查installedPath的映射行为并非口头约定而是有单元测试钉死的。见 packages/cli/src/runtime/workspace-skills-mapping.test.ts正常技能映射时断言输出对象精确等于包含installedPath: /skills/review/SKILL.md的完整对象toEqual严格比对多一个字段都会失败disableModelInvocation: true的技能仍保持status: ok但modelInvocable: falseinstalledPath不受影响userInvocable: false仅在手动调用被禁用时才输出属条件字段settings 禁用disabledReason: hard、lockedScope: user与强制禁用inactive_extension场景下状态与禁用原因正确联动extension 技能只有在level extension时才输出extensionName/extensionDisplayName/model非 extension 技能即使携带这些字段也被剔除——再次印证 allowlist 的显式列举语义。这些用例同时锁定了installedPath的存在性与其余字段的按需输出行为是接口演进过程中防止回归的第一道防线。八、小结installedPath是 Qwen Code 工作区技能状态接口上一次小而完整的协议演进契约明确GET /workspace/skills与GET /workspaces/:workspace/skills的每个技能条目都携带指向SKILL.md的绝对路径值按SkillConfig.filePath原样拷贝、不解析 symlink、不二次规范化演进克制daemon 侧恒输出、桥与 SDK 侧可选、协议版本与能力列表不动旧客户端零破坏实现收敛live ACP 快照与 daemon 本地回退共用 mapSkillConfigToStatus 单一映射函数六类技能来源形状完全一致且有单元测试兜底边界清晰状态层是显式元数据 allowlist暴露路径、不暴露正文与 hooks且不引入任何 UI 变化。对于希望在客户端中展示、定位或管理工作区技能的开发者来说installedPath提供了一个稳定、可验证、向后兼容的入口——只要记住它是可选字段、按原值透传这两条使用前提即可。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考