Qwen Code 跨包契约治理指南:9151 单所有权架构、共享常量与源码级验证
Qwen Code 跨包契约治理指南9151 单所有权架构、共享常量与源码级验证【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code本指南聚焦 Qwen Code 开源仓库中由 Issue #9151 驱动的跨包契约所有权Cross-package contract ownership治理设计。多包仓库monorepo中同一个语义常量或类型往往被多个包引用一旦各自维护一份拷贝就会出现同一配置多份事实的漂移风险。读完本文你将掌握该仓库为这类契约建立的**单一所有者single owner**原则、LIVE_TASK_TOOL_NAMES与MAX_SUB_SESSION_PROMPT_CHARS等共享常量的具体归属与导入路径以及如何用表驱动测试把这些契约钉死在源码层面。背景Issue #9151 识别出的契约漂移问题Qwen Code 是一个多包架构的终端 AI 编程代理其仓库根目录下的 packages 内部分布着acp-bridge、cli、core等多个独立 npm 包。跨包协作时某些消费方必须达成一致的值例如子会话 prompt 的字符上限、Live 任务工具白名单如果被各包各自复制一份就会出现两类典型故障事实分裂A 包把上限调成100_000B 包忘了同步边界行为不一致信任边界失效安全相关的阈值如果只是恰好相同一旦漂移就会给跨进程调用留下可利用的间隙。Issue #9151 正是针对这类跨越包边界、但当前各自持有独立拷贝的值给出了明确的收敛决策。该设计文档即仓库中的 docs/design/9151-cross-package-contracts.md它同时配套了源码测试 scripts/tests/cross-package-contracts.test.js 作为落地验证。决策一LIVE_TASK_TOOL_NAMES由 acp-bridge 独占所有单一所有者的确定文档明确acp-bridge拥有LIVE_TASK_TOOL_NAMESCLI 通过其已有的对 bridge 包的依赖来导入该值与派生类型。也就是说这一常量不再在 CLI 侧保留拷贝而是作为 bridge 包对外发布的契约。实际定义位于 packages/acp-bridge/src/bridgeOptions.tsexport const LIVE_TASK_TOOL_NAMES [ list_threads, read_thread, wait_threads, send_message_to_thread, create_thread, ] as const; export type LiveTaskToolName (typeof LIVE_TASK_TOOL_NAMES)[number];这是 5 个Live 任务工具的固定白名单list_threads列出任务、read_thread读取任务状态与回合摘要、wait_threads等待任务完成或需要关注、send_message_to_thread向既有任务发送后续 prompt、create_thread创建独立任务。as const断言使该数组在类型层面收窄为字面量元组从而派生出LiveTaskToolName联合类型——值即类型两个消费方不可能出现值同步了但类型不同步的错位。CLI 侧的消费方式CLI 的两个消费方均通过qwen-code/acp-bridge/bridgeOptions子路径导入例如 packages/cli/src/acp-integration/live/live-task-tools.tsimport { LIVE_TASK_TOOL_NAMES, type LiveTaskToolName, } from qwen-code/acp-bridge/bridgeOptions;该文件随后用这一常量集驱动createLiveTaskTools(execute)将 5 个工具规范LiveTaskToolSpec含name、displayName、description、kind、parametersJSON Schema逐一实例化为LiveTaskTool继承BaseDeclarativeTool。也就是说白名单与工具注册表之间是同一事实源工具名定义在 bridge注册与执行在 CLI两者通过类型系统对齐。LIVE_TASK_TOOL_NAMES还在 bridge 包内部被信任边界使用在 packages/acp-bridge/src/bridgeClient.ts 的handleLiveTaskTool中来自 ACP 子进程的extMethod请求会被校验callerSessionId归属以及name是否落在LIVE_TASK_TOOL_NAMES.includes(...)白名单内非法名字直接拒绝——同一常量既约束了能注册什么工具又约束了能调用什么工具。决策二MAX_SUB_SESSION_PROMPT_CHARS由 core 在轻量子路径发布归属与发布方式第二个共享契约是子会话 prompt 的字符上限。文档决定core在一个轻量公开子路径中拥有MAX_SUB_SESSION_PROMPT_CHARScore 工具与 ACP 信任边界检查各自导入该值同时保留各自独立的执行检查点。值定义位于 packages/core/src/tools/sub-session-constants.tsexport const MAX_SUB_SESSION_PROMPT_CHARS 100_000;注释点明了它的双重身份这是委托 prompt 的天花板core 与 ACP 在各自的边界独立执行该共享值。core 侧的执行点在 packages/core/src/tools/create-sub-session.tsif (params.prompt.length MAX_SUB_SESSION_PROMPT_CHARS) { return Parameter prompt exceeds the ${MAX_SUB_SESSION_PROMPT_CHARS}-character limit.; }该文件同样通过export { MAX_SUB_SESSION_PROMPT_CHARS } from ./sub-session-constants.js;再导出让契约能沿 core 的公开子路径继续外泄。bridge 作为第二道独立执行点ACP 信任边界侧的检查位于 packages/acp-bridge/src/bridgeClient.ts。其注释明确说明了为何这里必须独立执行子进程是独立进程这是一条信任边界——若不设上限子进程可以把多 MB 的字符串交给守护进程去反序列化、复制用于显示名并派发进新会话。值得注意的两个细节bridge 通过qwen-code/qwen-code-core/subSessionConstants子路径导入该值见 packages/acp-bridge/src/bridgeOptions.ts并在文件末尾export { MAX_SUB_SESSION_PROMPT_CHARS };再次转发packages/acp-bridge/src/bridgeOptions.ts使 bridge 的调用方也能访问对定时任务运行来源SCHEDULED_TASK_RUN_SOURCE_TYPE的 prompt上限会放宽SCHEDULED_TASK_RUN_CONTEXT_HEADROOM_CHARS的余量普通子会话则严格卡在100_000。可以推断这种一个事实源 多点独立执行的结构是本文档刻意保留的共享的是数值而不是把两处校验合并成一处——进程边界决定了检查点必须各自存在契约治理只负责让它们永远读到同一个数。决策三与四两个命名与归属的边界裁定除了两个共享常量文档还对两个易混淆的符号做了边界裁定getSanitizedExtensionDisplayName归 CLIgetExtensionDisplayName留 core扩展显示名存在两条语义不同的路径本地化展示与未受信 prompt 文本的准备。文档裁定CLI 的扩展辅助函数命名为getSanitizedExtensionDisplayName因为它负责准备未受信的 prompt 文本core 的getExtensionDisplayName保留为感知本地化的显示解析器。core 的 packages/core/src/extension/i18n.ts 定义getExtensionDisplayName承担 locale-aware 的显示解析CLI 的 packages/cli/src/utils/extension-mention.ts 定义getSanitizedExtensionDisplayName先经sanitizeDisplayText处理剥离终端控制序列stripTerminalControlSequences、移除双向文本控制符BIDI_CONTROL_RE防 bidi 欺骗注入、压缩空白并 trim最终在buildExtensionContextText中作为--- Extension: name (untrusted third-party content) ---上下文注入。命名上的区分让哪一段文本会被当作文本送入模型与哪一段只用于界面显示一目了然。writeStderrLine保持包内私有文档同时记录了一个维持现状的决定writeStderrLine因为没有既有的共享归宿、且 Issue 将其标记为可选因此留在包内不提升为跨包契约。这是治理边界的重要反面样本——并非所有重复代码都必须收敛只有当消费方必须一致或漂移会造成错误时才值得引入共享契约的维护成本。验证用表驱动源码测试钉死单一所有权设计文档的Verification一节承诺一张表驱动源码测试钉住每个共享契约的单一所有者与导入路径既有行为测试继续覆盖不变的值与执行边界。落地实现正是 scripts/tests/cross-package-contracts.test.js其核心机制值得完整拆解。单一所有者测试测试先通过git grep --untracked -l -E pattern -- packages见definitionFilesscripts/tests/cross-package-contracts.test.js扫描全部包再断言定义该符号的文件恰好只有一个且正是声明的 ownerconst definitions [ { symbol: LIVE_TASK_TOOL_NAMES, pattern: ^(export )?(const|let|var) LIVE_TASK_TOOL_NAMES[[:space:]]*[:], owner: packages/acp-bridge/src/bridgeOptions.ts, }, { symbol: LiveTaskToolName, pattern: ^(export )?type LiveTaskToolName[[:space:]]*([^])?[[:space:]]*, owner: packages/acp-bridge/src/bridgeOptions.ts, }, { symbol: MAX_SUB_SESSION_PROMPT_CHARS, pattern: ^(export )?(const|let|var) MAX_SUB_SESSION_PROMPT_CHARS[[:space:]]*[:], owner: packages/core/src/tools/sub-session-constants.ts, }, ]; it.each(definitions)($symbol has one owner, ({ pattern, owner }) { expect(definitionFiles(pattern)).toEqual([owner]); });正则锚定在行首^能精确匹配顶层声明而不会误伤导入语句或注释。只要有人在新位置重新声明同名常量definitionFiles就会返回多于一个文件toEqual([owner])立即失败——重复拷贝在合入前就被拦截。导入路径测试随后测试逐条断言每个契约的每个消费方必须从指定来源导入scripts/tests/cross-package-contracts.test.jsconst imports [ [LIVE_TASK_TOOL_NAMES, packages/acp-bridge/src/bridgeClient.ts, ./bridgeOptions.js], [LIVE_TASK_TOOL_NAMES, packages/cli/src/acp-integration/live/live-task-tools.ts, qwen-code/acp-bridge/bridgeOptions], [LIVE_TASK_TOOL_NAMES, packages/cli/src/serve/live/live-task-service.ts, qwen-code/acp-bridge/bridgeOptions], [LiveTaskToolName, packages/cli/src/acp-integration/live/live-task-tools.ts, qwen-code/acp-bridge/bridgeOptions], [LiveTaskToolName, packages/cli/src/serve/live/live-task-service.ts, qwen-code/acp-bridge/bridgeOptions], [MAX_SUB_SESSION_PROMPT_CHARS, packages/core/src/tools/create-sub-session.ts, ./sub-session-constants.js], [MAX_SUB_SESSION_PROMPT_CHARS, packages/acp-bridge/src/bridgeOptions.ts, qwen-code/qwen-code-core/subSessionConstants], [MAX_SUB_SESSION_PROMPT_CHARS, packages/acp-bridge/src/bridgeClient.ts, ./bridgeOptions.js], ];测试读取每个文件、剥离//注释后提取import ...;语句再同时匹配符号名与from 来源。可以看到CLI 侧一律走qwen-code/acp-bridge/bridgeOptions子路径导入 Live 工具契约core 内部走相对路径而 bridge 则通过qwen-code/qwen-code-core/subSessionConstants消费 core 的契约——整张表就是一份机器可读的依赖方向图。命名与子路径的补充断言测试还额外钉住了两个容易回归的点语义区分getExtensionDisplayName只允许存在于packages/core/src/extension/i18n.tsgetSanitizedExtensionDisplayName只允许存在于packages/cli/src/utils/extension-mention.tsscripts/tests/cross-package-contracts.test.js——防止有人顺手在两个包里各放一个同名函数公开子路径发布packages/core/package.json的exports[./subagentRuntime]必须指向dist/src/subagent-runtime.{d.ts,js}对应入口 barrel 必须转发ExternalAgentExecutor与AgentEventEmitter且packages/cli/vitest.config.ts与packages/cli/tsconfig.json中的qwen-code/qwen-code-core/subagentRuntime别名必须解析到../core/src/subagent-runtime.tsscripts/tests/cross-package-contracts.test.js——验证跨包契约通过声明的子路径对外发布这一整体机制。运行时行为测试的兜底契约测试只负责所有权与导入路径不变语义本身仍由各自包内的行为测试兜底。例如LIVE_TASK_TOOL_NAMES的注册与执行行为由 packages/cli/src/acp-integration/live/live-task-tools.test.ts 与 packages/cli/src/serve/live/live-task-service.test.ts 覆盖MAX_SUB_SESSION_PROMPT_CHARS在 core 的 packages/core/src/tools/create-sub-session.test.ts 与 bridge 的 packages/acp-bridge/src/bridgeClient.test.ts 中分别验证各自边界的超限拒绝行为。这正是设计文档Existing behavioral tests continue to cover the unchanged values and enforcement boundaries的落实契约测试管事实唯一行为测试管行为正确。实践启示何时该为跨包值引入单一所有者从 #9151 的四个决策可以提炼出可复用的判定标准是否有多包消费方且必须一致LIVE_TASK_TOOL_NAMES被 bridge 的信任校验与 CLI 的工具注册同时消费MAX_SUB_SESSION_PROMPT_CHARS被 core 工具与 ACP 信任边界同时执行——二者都满足漂移即出错归属应落在语义上最内聚的包Live 工具白名单属于 bridge它是工具请求的入口子会话 prompt 上限属于 core它是工具的参数校验方CLI 只是消费方不持有事实值共享 ≠ 检查点合并进程边界要求各自独立执行契约治理只保证各方读到同一数值命名即文档getExtensionDisplayName显示解析与getSanitizedExtensionDisplayName未受信文本准备的命名区分比注释更持久地传达了语义差异非所有重复都该收敛writeStderrLine这类没有共享归宿、漂移无碍的值保留包内私有反而是更低成本的选择测试要钉所有权而不仅是行为表驱动测试把单一 owner 规定导入路径变成可自动回归的硬约束让未来的重构在合并前就能发现契约漂移。如果你正在参与 Qwen Code 的跨包开发请优先查阅上述契约测试表确认每个共享符号的归属如果你在自己的 monorepo 中遇到类似问题这份设计从识别 → 裁定 → 落测试的完整链路也值得直接借鉴。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考