omo-opencode 的 delegate-task 常量模块拆分实战:从 654 行 constants.ts 到单一职责模块与桶式再导出
本文以仓库中一份真实的 PR 描述文档(pr-description.md)为主体,完整还原 omo/lazycodex(oh-my-openagent)项目中一次典型的大型常量文件重构:如何把 654 行的 delegate-task/constants.ts 拆分为 4 个单一职责模块,同时通过 re-export barrel 保证 13 处消费方的导入路径零改动,并给出可复制的验证流程。读完后你可以掌握"大文件拆分 + 桶文件兼容"这套在复杂代码库中安全重构的完整方法论,并理解 task 委派工具的类别(category)与 plan agent 提示词在源码中的真实落地位置。
一、重构背景:654 行 constants.ts 违反了什么规则
omo-opencode 的 task 工具位于 delegate-task 目录,负责把工作委派给子代理(subagent),解析类别、模型与技能(skill),并管理后台/同步两种执行流。拆分前的 constants.ts 有 654 行,捆绑了 4 个互不相关的职责:
| 职责 | 内容 |
|---|---|
| 类别模型配置 | DEFAULT_CATEGORIES、CATEGORY_DESCRIPTIONS 等内置类别记录 |
| 类别提示词文本 | 8 个 *_PROMPT_APPEND 常量及 CATEGORY_PROMPT_APPENDS 记录 |
| plan agent 提示词 | plan 系统提示词常量与 buildPlanAgentSystemPrepend() |
| plan agent 名称工具 | PLAN_AGENT_NAMES、isPlanAgent、PLAN_FAMILY_NAMES、isPlanFamily |
PR 文档指出的核心问题是:654 行违反了项目 200 LOC 软限制(modular-code-enforcement.md 规则)。这条规则在仓库根目录的 AGENTS.md 中有明确记载——模块结构要求 "index.ts barrel exports, no catch-all files (utils.ts, helpers.ts, service.ts banned), 200 LOC soft limit per file"。也就是说,"禁止大杂烩文件 + 每文件 200 行软上限"是项目级的代码组织约束,本次拆分正是对该约束的一次落地。
二、拆分方案:4 个单一职责模块 + 4 行 barrel
PR 的变更清单(Changes)如下,完整继承了原始 PR 描述:
| 新文件 | 职责 | LOC |
|---|---|---|
default-categories.ts |
DEFAULT_CATEGORIES、CATEGORY_DESCRIPTIONS |
~25 |
category-prompt-appends.ts |
8 个 *_PROMPT_APPEND 常量 + CATEGORY_PROMPT_APPENDS 记录 |
~300(prompt-exempt) |
plan-agent-prompt.ts |
Plan 系统提示词常量 + buildPlanAgentSystemPrepend() |
~250(prompt-exempt) |
plan-agent-names.ts |
PLAN_AGENT_NAMES、isPlanAgent、PLAN_FAMILY_NAMES、isPlanFamily |
~30 |
constants.ts(更新后) |
4 行 re-export barrel | 4 |
两个值得注意的细节:
- prompt-exempt 标记。两个提示词文件虽然各有约 300 行和约 250 行,超过了 200 LOC 软限制,但被标注为 "prompt-exempt"(提示词内容豁免行数限制)。从源码结构看,这是合理的:提示词是整体语义单元,强行按行数切断字符串模板反而损害可读性与可维护性。行数规则针对的是逻辑复杂度堆积,而不是不可分割的文本资产。
- 拆分的粒度标准是"单一职责"而非"等分行数"。类别配置(~25 行)、提示词追加(~300 行)、plan 提示词(~250 行)、名称判定(~30 行)各自独立成文件,每个文件只回答一个问题。
三、向后兼容:re-export barrel 与导入链
PR 文档的 Backward Compatibility 一节给出了兼容策略的全部要点:
全部 13 处消费方(13 consumers)继续从
"./constants"或"../tools/delegate-task/constants"导入,零改动。再导出链为:新模块 ->constants.ts->index.ts-> 外部消费方。
这条两级再导出链在当前仓库中依然成立,可以直接从源码验证:
- constants.ts 顶部将类别记录类符号整体 re-export 出去(
export { ... } from "./builtin-categories"),同时保留自身的 plan agent 提示词导出; - index.ts 仅 4 行,其中
export * from "./constants"把整个常量面暴露给包外。
外部消费方的例子:builtin-agents.ts 第 22 行 import { CATEGORY_DESCRIPTIONS } from "../tools/delegate-task/constants"——无论 CATEGORY_DESCRIPTIONS 实际定义在哪个子模块里,这条导入路径始终有效。这正是 barrel 模式的价值:拆分是文件组织层面的动作,对导入方的 API 契约零影响,也因此才能做到 PR 摘要里所说的"Zero import changes across the codebase (6 external + 7 internal consumers verified)"。
CATEGORY_MODEL_REQUIREMENTS 的归属澄清
PR 文档专门有一节 Note 澄清了一个常见误区:
CATEGORY_MODEL_REQUIREMENTS已经位于src/shared/model-requirements.ts,无需移动;AGENTS.md 中关于它在constants.ts的说法已过时。
这类"文档与现实漂移"在多模块重构中非常典型——拆分前值得先确认每个符号的真实出处,避免把"文档说它在哪"当成"它实际在哪"。从当前源码结构看,类别定义已按提供商(Google/OpenAI/Anthropic/Kimi)进一步拆到 anthropic-categories.ts 等文件并汇总于 builtin-categories.ts,后者通过 buildCategoryRecord() 统一派生出 DEFAULT_CATEGORIES、CATEGORY_PROMPT_APPENDS、CATEGORY_DESCRIPTIONS 等记录——可以推断,这是 PR 拆分之后的继续演化:常量层被进一步下沉为"定义 + 派生"的结构,而 constants.ts 的 barrel 角色得以保留。
四、源码纵深:plan agent 提示词常量在 constants.ts 中的实现
拆分方案中 plan-agent-prompt.ts 承载的内容,在仓库中可以从 constants.ts 直接读到完整实现,这也是 654 行中"约 250 行 prompt-exempt"部分的主体:
PLAN_AGENT_SYSTEM_PREPEND_STATIC_BEFORE_SKILLS(第 21 行起):plan agent 的系统提示词前段。它强制要求 plan agent 在规划前执行"MANDATORY CONTEXT GATHERING PROTOCOL"——先用后台explore/librarian代理收集上下文,再输出用户请求摘要、不确定点与澄清问题,迭代到需求 100% 清晰;随后是三个强制输出章节:任务依赖图、并行执行波次(Parallel Execution Waves)、每个任务的 Category + Skills 推荐。PLAN_AGENT_SYSTEM_PREPEND_STATIC_AFTER_SKILLS:提示词后段,规定计划的精确 Markdown 响应格式、<plan>...</plan>交付物信封(Deliverable Envelope)规则——调用方靠这个信封区分最终交付物与探索性消息。buildPlanAgentSystemPrepend(categories, skills)(第 332 行起):将"前段静态文本 + 动态渲染的 AVAILABLE CATEGORIES / AVAILABLE SKILLS 表格 + 后段静态文本"三段拼接为最终系统提示词,其中renderPlanAgentCategoryRows/renderPlanAgentSkillRows负责把运行时解析出的类别与技能渲染成 Markdown 表格行。
与之配套的"名称工具"部分(对应拆分方案中的 plan-agent-names.ts)也在同一文件中可见:
PLAN_AGENT_NAMES = ["plan"]与isPlanAgent()(第 347-356 行):大小写不敏感判定某 agent 是否应注入 plan 系统提示词;PLAN_DELIVERABLE_TAG = "plan"与getDeliverableTag():plan agent 交付物信封标签,与提示词中"必须用<plan>包裹"的指令一一对应——源码注释明确说明"与注入信封指令使用同一谓词门控,因此提取端只在提示词要求过标签时才期望它";PLAN_FAMILY_NAMES = ["plan", "prometheus"]与isPlanFamily()(第 378-389 行):plan 家族共享"互斥委派阻塞 + task 工具权限",但不共享系统提示词——注释特别强调"只有isPlanAgent控制提示词注入"。这正是把"名称判定"与"提示词文本"拆到不同模块的语义依据:两者的消费者和作用范围不同。
五、验证流程:拆分类重构的最小验证集
PR 的 Testing 一节给出了拆分类重构的验证标准,这套流程对所有"纯移动、不改语义"的重构都适用:
bun run typecheck通过——类型系统会捕获拆分中的漏导出、类型路径错误;bun test src/tools/delegate-task/通过——该目录下的既有测试(如 category-resolver.test.ts、prompt-builder.test.ts 等)一行未动却全部通过,本身就是"行为零变化"的最强证据:测试断言的是模块行为,导入路径变了但行为必须一致;bun run build成功——打包产物无断裂。
值得强调的是 PR 摘要中"all existing tests untouched"这个表述的方法论意义:如果一次纯移动重构需要修改任何测试,就说明改动已经越过"组织层面"进入了"行为层面",应当停下来审查。
六、可复用的重构模式总结
把这份 PR 抽象出来,就得到一套在大型 TypeScript 代码库中安全拆分"巨型 constants 文件"的标准动作:
- 先盘点职责:把大文件里的符号按"它服务哪个特性"分组,每组一个候选模块;
- 保留原文件名作 barrel:原
constants.ts退化为纯 re-export(本次为 4 行),所有既有导入路径原样保留; - 明确豁免项:提示词等不可分割的文本资产可以豁免行数规则,但要显式标注(prompt-exempt),而不是让规则形同虚设;
- 核对符号真实出处:拆分前确认每个符号实际定义位置,顺手清理过期文档声明(如
CATEGORY_MODEL_REQUIREMENTS的归属); - 以"测试零改动 + typecheck + build"作为验收线:13 处消费方零导入改动、既有测试原样通过,构成完整的兼容性证据链。
这套模式在当前仓库的 delegate-task 模块中已经历了进一步演化(内置类别定义下沉到按提供商拆分的定义文件 + builtin-categories.ts 派生记录),而 barrel 兼容层始终未被破坏——这正是桶式再导出作为"重构安全网"的长期价值所在。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00