news 2026/9/14 6:29:46

qwen-code 声明式 Agent 移植:Claude Code Agent 文件 Schema 兼容方案与源码实现详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
qwen-code 声明式 Agent 移植:Claude Code Agent 文件 Schema 兼容方案与源码实现详解

qwen-code 声明式 Agent 移植:Claude Code Agent 文件 Schema 兼容方案与源码实现详解

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

本文基于 qwen-code 仓库的设计文档 declarative-agents-port.md,完整解析如何把 Claude Code 2.1.168 的声明式 Agent 定义(Markdown + YAML frontmatter)移植到 qwen-code 子代理体系中:16 个 frontmatter 字段的反向工程结论、字段级解析与桥接规则、permissionMode → approvalMode映射、per-agentmcpServers/hooks的运行时接线,以及五级存储层级的优先级解析。读完你可以直接理解 qwen-code 中.qwen/agents/*.md文件的完整字段语义、宽松解析(lenient parse)行为,以及为什么某些字段被推迟实现。

一、移植目标与实现状态

该移植工作的目标(对应上游 issue #4821 与 #4721)是让 Claude Code 的.claude/agents/*.md声明式 Agent 文件能够原样放入 qwen-code 的.qwen/agents/并被正确解析。整个移植采用"垂直切片"(vertical-sliced)方式交付:

  • 第一个 PR(#4842)带来了核心字段并打通端到端运行时路径(permissionModemaxTurnscolor白名单);
  • 第二个 PR(#4870)把 YAML 解析器替换为支持块标量的实现(参见 yaml-parser-replacement.md);
  • 后续 PR 把mcpServershooks表面化到SubagentConfig上,并接线到运行时,使 per-agent 的 MCP 服务器与 hooks 在子代理运行时真正生效。

当前各字段的落地状态如下(引自设计文档的 Implementation status 表):

字段状态说明
permissionMode已交付解析期桥接到 qwen 现有的approvalMode
maxTurns已交付接入现有runConfig.max_turns运行时路径
color白名单已交付收紧为 Claude Code 的_Y颜色集,保留auto旧哨兵
mcpServers已交付(后续 PR)通过 eemeli/yaml序列化保证嵌套 YAML 往返安全;运行时通过子代理 Config 包装器合并会话级 + Agent 级服务器,并强制工具注册表重建
hooks已交付(后续 PR)临时 HookRegistry 条目在子代理派生时注册、onStop时移除;v1 全局触发(尚无 Agent 级作用域过滤)
effort推迟qwen 各 provider 尚不存在模型层effort参数
memory推迟qwen 的 auto-memory 尚无user/project/local作用域区分
isolation推迟运行时归 workflow 移植(PR #4732)所有,per-agent 默认值随其落地
initialPrompt推迟需要--agentCLI 标志(qwen 尚无主会话 Agent 基础设施)
skills推迟需要 SkillManager 消费config.skills

二、反向工程:Claude Code 2.1.168 的 Agent Schema

设计文档的 Phase 1 通过对 Claude Code 2.1.168 原生二进制的字符串提取(约 342k 行)做了对抗性证伪式的反向工程。关键结论是:Agent frontmatter 存在两套 schema——影子 schemaIg5(仅用于遥测)与生产加载器DL7parseAgentFromMarkdown,后者是逐字段手工校验、带自定义错误信息的宽松实现;另有一套更紧的 JSON 形式 schemaJL7(供--agents <json>settings.agents使用)。

2.1 15 + 1 个 frontmatter 字段

#字段类型必填默认枚举 / 约束
1namestring,非空缺失直接返回 null
2descriptionstring,非空Description cannot be empty
3modelstringundefinedinherit(大小写不敏感)归一化为字面量"inherit",否则 trim 后透传
4toolsstring | arrayundefined单 token*→ undefined(表示"继承全部")
5disallowedToolsstring | arrayundefined"若设置了tools则被忽略"(由调用方强制)
6effortstring | integerundefined枚举["low","medium","high","xhigh","max"]或整数;别名med → medium
7permissionModestringundefined6 值枚举:acceptEdits/auto/bypassPermissions/default/dontAsk/plan
8mcpServersrecordundefined每项为 string 或record(string, MCPServerSpec),逐条safeParse
9hooksrecordundefined运行时按 settings.json hooks 形状惰性校验
10maxTurnsnumber | string | nullundefined正整数,数字或数字字符串均可
11skillsstring | array[]会输出逗号串归一化;无*通配
12initialPromptstringundefined纯空白 → undefined;仅当 Agent 为主会话时自动提交
13memorystringundefined枚举["user","project","local"]
14backgroundstring | boolundefined接受true/false/"true"/"false";仅真值归一化为true
15isolationstringundefined枚举["worktree"](不含none——"无隔离"即省略字段)
16colorstringundefined8 色枚举;超出值在解析期被静默丢弃;标注为@internal(仅 UI 展示色)

一个经对抗性验证的细节:skills虽为可选,但DL7的输出子句会在 frontmatter 省略该字段时仍然输出skills: []——这会影响下游的相等性检查,是移植时必须留意的行为。

2.2 解析器与热加载生命周期

Claude Code 的 frontmatter 解析不依赖gray-matter/js-yaml,而是手写分片器 +Bun.YAML.parse

  • 分割正则为/^---\s*\n([\s\S]*?)---\s*\n?/
  • YAML 解析失败时先做 tab→2 空格归一化重试,仍失败则记 warn 日志并返回{frontmatter: {}, content: body}从不抛异常
  • 该分片器被 agents / skills / commands / output-styles 四类加载器共享;
  • schema 校验是"影子"模式:Zod v4 的strict().safeParse()结果只用于遥测(tengu_frontmatter_shadow_unknown_key/_mismatch),生产路径完全以DL7的逐字段宽松校验为准。

热加载方面,Claude Code 使用 chokidar watcher 监听.claude/agents(用户 + 项目两级),300ms 去抖后调用clearAgentDefinitionsCache失效缓存;活跃期轮询间隔 2000ms,空闲(60s 无交互)降为 30000ms,切换时重建 watcher 实例。插件来源的 Agent 不被 watcher 覆盖,需靠/reload-plugins重新装配。

2.3 优先级决议

Claude Code 的DS()getActiveAgentsFromList)按固定顺序把 6 个来源桶迭代进一个以agentType为键的MapMap.set覆盖语义使最后写入的桶获胜

[built-in, plugin, userSettings, projectSettings, flagSettings, policySettings] ^ 最高优先级

即 policySettings(系统托管目录)最高,built-in 最低;同名冲突被静默解决,只触发tengu_plugin_name_collision遥测。一个反直觉的行为:项目级目录树内层目录先 push、但 last-wins,导致外层.claude/agents/压过内层——设计文档明确标记这是"陷阱",qwen-code 移植决定不镜像该行为。

三、qwen-code 现状与架构决策

3.1 移植前的基础设施

qwen-code 在移植前已具备可观的子代理基础设施:

  • SubagentManager 对.qwen/agents/(项目级)与~/.qwen/agents/(用户级)中的 markdown + YAML frontmatter 文件提供 CRUD;
  • SubagentConfig 已有namedescriptiontoolsdisallowedToolsapprovalModesystemPromptmodelrunConfigcolorbackground
  • SubagentLevel 已是五级作用域session > project > user > extension > builtin
  • Agent 工具(packages/core/src/tools/agent/agent.ts)声明subagent_type参数并动态刷新 schema 枚举。

3.2 关键架构决策(D1–D7)

决策内容
D1复用现有 yaml-parser.ts 处理 frontmatter——与 Claude Code 共享解析器的架构模式一致,不引入gray-matter/js-yaml新依赖
D2优先级沿用 qwen-code 现有五级session > project > user > extension > builtin,v1 不镜像 Claude Code 的flagSettings/policySettings桶(企业管理目录是 qwen 没有的场景)
D3扩展现有SubagentValidator手工校验,不引入 zod——与 Claude Code 影子 schema + 手工校验的形态对齐,保持错误信息可读
D4v1 不交付 chokidar 热加载,依赖冷加载 + 显式失效(changeListener
D5交付--agent <name>CLI 标志;不采用 Claude Code 的CLAUDE_CODE_AGENT环境变量间接层,Config对象直接承载
D6对 workflow 移植暴露稳定的 resolver 接口契约:frontmattername即 workflow 的agentType字符串(键相等、大小写敏感);workflow 硬编码的disallowedTools地板[SEND_MESSAGE, EXIT_PLAN_MODE]与 agent 级disallowedTools取并集,地板恒生效
D7permissionModeapprovalMode双收桥接:解析期把permissionMode映射到approvalMode;两者同时存在时approvalMode获胜(对 qwen 更具体),并发出"双写"遥测

D6 同时解决了 #4821("设置了tools则忽略disallowedTools")与 #4721("与 workflow 地板取并集")的表面矛盾:注册表是"笨数据载体",两个字段始终独立携带,优先级规则放在派发点(Agent 工具 / workflow)而非解析期。

3.3 字段映射表(Claude Code 2.1.168 → qwen-code)

Claude Code 字段qwen-code 字段适配
name/description同名完全一致,必填
modelmodel接受inheritfast、具体 model-id、authType:model-id
toolstoolsstring | array;逗号串切分;*→ undefined(继承全部)
disallowedToolsdisallowedToolsstring | array;与tools独立携带
permissionModepermissionMode+ 桥接approvalMode6 值枚举,映射表见下节
maxTurnsmaxTurns(新顶层字段)正整数,兼容数字字符串;由runConfig.max_turns提升而来,旧嵌套形式保留为弃用别名
mcpServersmcpServersrecord-of-records 浅校验;逐 spec 深校验推迟到运行时 MCP loader
hookshooksrecord-of-arrays 浅校验;逐 matcher 校验由SessionHooksManager负责
backgroundbackground接受 bool 或"true"/"false"字符串,仅真值 →true
color(未文档化 #16)color8 色白名单 + qwen 旧auto哨兵;超界值静默丢弃
isolation推迟枚举仅["worktree"],运行时归 workflow PR
effort/memory/skills/initialPrompt推迟见第一节状态表

四、源码实现:字段解析与桥接

4.1 枚举常量单一事实源

设计文档要求的新模块 agent-frontmatter-schema.ts 是枚举常量的单一事实源,逐字镜像 Claude Code 2.1.168:

  • PERMISSION_MODE_VALUES:acceptEditsautobypassPermissionsdefaultdontAskplan
  • COLOR_VALUES:redbluegreenyellowpurpleorangepinkcyan
  • parseMaxTurns:接受正整数 number 或数字字符串,其余一律返回 undefined(对应 CC 的W46)。

permissionMode → approvalMode映射表(claudePermissionModeToApprovalMode)值得细看:

permissionModeapprovalMode语义说明
defaultdefault工具调用需确认
planplan计划模式
acceptEditsauto-edit自动批准编辑
autoauto-edit同上
bypassPermissionsyolo全部自动批准
dontAskdefaultClaude 侧dontAsk语义偏限制(会弹窗的调用一律拒绝),因此映射到同样需要确认的default而非自动批准的auto-edit,保留限制意图

实现上特意用Map而非普通Record,防止调用方传入'__proto__'/'constructor'沿原型链取到非字符串值——这是从源码结构中可见的防御性细节。

4.2 宽松解析(lenient parse)的逐字段行为

核心解析逻辑在 parseSubagentContent。frontmatter 分割用正则/^---\n([\s\S]*?)\n---\n([\s\S]*)$/,随后逐字段处理:

  • permissionMode:仅当值命中 6 值枚举才保留,否则 warn 并丢弃(Agent file <path> has invalid permissionMode '<x>'. Dropping field.);仅当approvalMode未设置时才桥接,即effectiveApprovalMode = approvalMode ?? bridgedApprovalMode——与 D7 "双写时 qwen 字段赢"的决策完全一致;
  • maxTurns:走parseMaxTurns,非法值 warn 并丢弃;
  • color:白名单内或遗留auto哨兵保留,其余静默丢弃并记 warn;
  • background"true"/truetrue"false"/falsefalse,其余 warn 并置 undefined;
  • mcpServers:parseAgentMcpServers 做 record-of-records 浅校验——非对象整体丢弃、值为标量/数组/null 的条目逐条丢弃,并返回空原型对象Object.create(null))避免 YAML 键字面量__proto__触发原型链污染;
  • hooks:parseAgentHooks 仅保留值为数组的条目,逐 matcher 的深校验推迟到SessionHooksManager

这套"丢弃而非抛错"(drop-the-whole-field)姿态与 Claude CodeDL7的宽松语义一致:一个写坏的mcpServers块不会杀死整个 Agent 定义。源码注释明确说明这与 qwen 早期字段(如approvalMode,非法值直接抛错拒绝加载)的严格姿态有意区分,以保护存量.qwen/agents/*.md文件。

值得注意的是,qwen-code 在此镜像 schema 之外增加了一个executor扩展字段(SubagentExecutorSpec):声明kind: 'acp' | 'codex'commandargs?,让子代理的 turn 交给外部 Agent 进程执行。它与mcpServers/hooks的宽松姿态刻意相反——解析失败时拒绝加载整个定义(named executor refusal),因为丢弃该字段会让任务静默地以 Qwen 进程内模型代跑,形成"静默替换"。这一 fail-closed 设计在 subagent-manager.ts 中有大量防御注释,包括用 YAML 真实 AST(parseDocument)而非宽松行式解析探测executor:声称,避免块标量中的散文误判。

4.3 校验器

SubagentValidator 保留了手工校验路线(D3 决策的落实):

  • 名字校验(validateName):2–50 字符、仅字母数字连字符下划线、不能以-/_开头结尾,且拒绝保留名self/system/user/model/tool/config/default/mainmain/stats归因管线的哨兵,子代理占用会静默并入主会话桶);
  • description必填非空,超过 1000 字符仅告警;
  • systemPrompt至少 10 字符,超过 10000 字符告警;
  • modelresolveModelId解析校验,显式写inherit会收到"省略即可"的提示;
  • runConfig.max_turns必须是正整数,超过 100 告警;max_time_minutes超过 60 告警。

五、运行时接线:per-agent MCP 服务器与 hooks

设计文档声明mcpServershooks已从"仅携带元数据"升级为真正生效。源码中的落地路径:

5.1mcpServers:Config 包装器 + 强制注册表重建

在 buildSubagentContextOverride 中:

  1. deriveConfig派生一个薄的 Config 包装器作为子代理运行时上下文(顺带让子代理获得独立的FileReadCache,避免继承父进程的已读记录而削弱"先读后写"约束);
  2. 若 frontmatter 声明了mcpServers,则sessionServersconfig.mcpServers按键合并——同名键时 Agent 级覆盖会话级(more-specific-wins,对齐 Claude Code 的scope: 'agent'语义);
  3. 只要存在 per-agent 服务器就绕过hasRebuiltToolRegistry跳过优化,强制rebuildToolRegistryOnOverride——否则父级注册表缓存的McpClientManager永远看不到合并后的服务器集合,per-agent 发现会静默空转;
  4. 新注册表以skipDiscovery: true构造后从父级回填工具,因此 per-agent 服务器需要显式discoverToolsForServer,且用Promise.allSettled并行发现——单个挂死的 stdio 服务器不会串行拖慢整个子代理派生,单个失败仅记 warn 不阻塞其他服务器;
  5. 返回的cleanup回调持有新注册表(其 stdio 子进程/ socket 是父级Config.shutdown够不到的),由createAgentHeadlessdispose闭包在子代理终止时执行。

5.2hooks:临时注册 + 作用域标注 + 信任门禁

在 createAgentHeadless 中:

  • 子代理派生时以agent:<name>:<uuid>作为作用域,调用hookRegistry.addAgentHooks注册 frontmatter hooks,返回的 unregister 回调挂到dispose上(构造失败路径也会执行,防泄漏);
  • 项目级 Agent 的 hooks 受信任门禁:非受信目录(untrusted folder)中的项目 Agent 可以只读加载,但其 hooks 属于仓库提供的代码执行,与Config.getProjectHooks()对 settings 文件 hooks 的门禁一致,直接忽略并 warn;
  • 宿主没有 HookSystem 时也仅 warn 忽略,不抛错;
  • v1 局限(与设计文档一致):条目驻留注册表期间对同类型的所有事件全局触发,尚无 per-agent 作用域过滤。

5.3 模型路由

model选择器经resolveModelId/buildRuntimeContentGeneratorView解析:当选择器指向不同 provider 或调用方要求 per-agent 推理强度时,构建专属 ContentGenerator + 视图,经 AsyncLocalStorage 在运行期生效,不影响父进程——这与 SubagentConfig.model 文档的inherit/fast/model-id/authType:model-id四种选择器语义对应。

六、存储层级与优先级解析

qwen-code 沿用了 D2 决策的五级结构,loadSubagent的解析顺序(subagent-manager.ts)为:session → project → user → extension → builtin,逐级回退;每级之间还插入throwRecordedExecutorRefusal检查——若某级存在一个声明了executor但加载失败的同名文件,按名派生直接抛错拒绝,而不是回退到低优先级的进程内定义。这正是 4.2 节所述"静默替换"防护的落点。

项目级目录内嵌套目录的冲突,qwen-code 明确选择innermost-wins(最内层获胜),不镜像 Claude Code 意外形成的 outer-wins 行为(设计文档 Q5/R6 的显式决策)。

Agent 工具侧,subagent_type参数(agent.ts)省略时回退到 general-purpose Agent;builtin-agents.ts 提供内建 Agent(如Explore,且其模型可被agents.builtin.exploreModel设置覆盖,见 applyBuiltinSettings)。

七、测试与验证路径

设计文档给出了 TDD 计划,仓库中对应落地的测试文件:

测试文件覆盖
agent-frontmatter-schema.test.ts枚举常量快照(与 Claude Code 2.1.168 逐字节对齐)、parseMaxTurns/parseAgentMcpServers/parseAgentHooks/parseAgentExecutorgetAgentByName风格的 resolver 导出(供 workflow 消费)
subagent-manager.test.ts全字段往返解析、必填字段缺失、非法枚举值的 warn + 丢弃、宽松字段类型(background: "true"maxTurns: "5")、color 白名单、优先级决议
validation.test.ts校验器错误与告警路径
subagent-manager-override.test.tsper-agent MCP 覆盖与注册表重建
packages/core/src/tools/agent/agent.test.tssubagent_type解析与运行时字段管道

八、风险与推迟字段的取舍

设计文档 Phase 4 列出的风险中,与实现直接相关的取舍:

  • R2(maxTurns提升是破坏性变更):旧嵌套形式runConfig.max_turns保留为弃用别名,两者同设时顶层maxTurns获胜(见 SubagentConfig.maxTurns 注释),解析时发 warn,记入 CHANGELOG;
  • R4(携带但无运行时效果的字段)effortmemoryskillsinitialPrompt在 v1 只做"携带",设计上要求文档明确 v1 边界,避免用户设置后静默无效;
  • R7(color是 Claude 的@internal字段):照搬但同样标记 internal,不作为用户面向文档内容,仅 UI 展示用。

推迟理由均可从源码结构印证:effort需要 provider 层的推理强度参数(qwen 已有reasoningEffort概念,见 buildRuntimeContentGeneratorView 中的modelConfig.reasoningEffort,但 frontmatter 尚未接通该旋钮);isolation的 worktree 运行时归 workflow 移植所有;initialPrompt依赖尚不存在的--agent主会话选择器。

九、小结

qwen-code 的声明式 Agent 移植是一次"schema 逐字镜像 + 运行时按需接线"的工程:frontmatter 的 16 字段语义、枚举常量、宽松解析姿态与 Claude Code 2.1.168 保持逐字节对齐,使.claude/agents/*.md可以原样放入.qwen/agents/permissionMode → approvalMode桥接、mcpServers的合并 + 强制注册表重建、hooks的临时注册 + 信任门禁,则把"仅携带"的字段推进为真正生效的运行时能力;而 qwen-code 独有的executor扩展用 fail-closed 拒绝加载替代宽松丢弃,划定了镜像与扩展之间的边界。对维护者而言,枚举事实源在 agent-frontmatter-schema.ts、解析行为在 parseSubagentContent、运行时接线在 buildSubagentContextOverride,三处即可覆盖该特性的全部实现面。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 6:28:55

Mac SSH客户端termcc深度体验:串口调试与密钥认证全攻略

搞了十几年网络设备和服务器运维&#xff0c;我对Mac上的SSH客户端工具一直有种“找不到趁手家伙”的无力感。Windows时代有SecureCRT、Xshell&#xff0c;切换设备、保存会话都很顺手&#xff0c;但到了Mac上&#xff0c;要么是iTerm2配命令行&#xff0c;要么是各种重型的现代…

作者头像 李华
网站建设 2026/9/14 6:27:55

大模型隐私数据删除技术:PrivacyScalpel原理与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 6:25:37

Zulip 升级失败后如何回滚到之前的版本

Zulip 升级失败后如何回滚到之前的版本 【免费下载链接】zulip Zulip server and web application. Open-source team chat that helps teams stay productive and focused. 项目地址: https://gitcode.com/GitHub_Trending/zu/zulip 在自托管的 Zulip 服务器上执行 upg…

作者头像 李华
网站建设 2026/9/14 6:24:07

自然研学活动设计:色彩与气味的感官教育实践

1. 六亩半田埂上的研学构想在乌鲁木齐城郊一片六亩半的农田边缘&#xff0c;我萌生了做一场特殊研学活动的念头。这片田地不算大&#xff0c;但足够让孩子们奔跑嬉戏&#xff1b;不算规整&#xff0c;却正好保留着最自然的农耕肌理。每当春风吹过&#xff0c;田埂上的野花就摇曳…

作者头像 李华
网站建设 2026/9/14 6:23:57

OpenSpec+Superpowers实现契约驱动开发工作流

1. 这不是又一个“AI工作流”概念秀&#xff0c;而是真正能落地的工程化协作范式 OpenSpec Superpowers 搭建 SDDTDD 工作流——光看标题&#xff0c;很多人第一反应是&#xff1a;“又是套新词包装的老东西&#xff1f;”但如果你真花30分钟跑通这个组合&#xff0c;会发现它…

作者头像 李华
网站建设 2026/9/14 6:22:26

Qt图书管理系统课设全解析:界面分层、SQLite事务与QCustomPlot绘图实战

简介&#xff1a;这是一份面向高校计算机相关专业课程设计或毕业设计的图书管理系统完整方案&#xff0c;基于Qt框架与SQL数据库实现&#xff0c;适合具备C与数据库基础、希望快速搭建可演示项目或系统学习桌面应用开发的学习者。压缩包共一百三十二个文件&#xff0c;约六点六…

作者头像 李华