omo/lazycodex 常量文件拆分实战:delegate-task constants.ts 重构执行计划深度解析
在 omo/lazycodex(oh-my-openagent)这个由大量 TypeScript 包组成的 agent harness 中,task 委托工具的 constants.ts 曾是一个 654 行、身兼 6 种职责的"上帝常量文件"。本文基于仓库中一份真实的重构执行计划(execution-plan),完整还原"如何安全拆分一个被 10+ 处内部/外部模块重度依赖的常量文件"的全流程:从预检分析、职责盘点、导入依赖映射,到逐文件拆分、桶(barrel)重导出与零消费者变更的提交策略,并结合当前仓库源码验证该计划的实际落地形态。读完本文,你能掌握一套可迁移到任何中大型 TypeScript 项目的"零消费者破坏"模块拆分方法论。
一、计划背景:为什么要拆 constants.ts
执行计划的 Context 部分给出了触发拆分的三个硬事实:
src/tools/delegate-task/constants.ts当时为 654 行,承载 6 种不同职责,违反了项目的 200 LOC 模块代码约束规则(modular-code-enforcement);- 其中被普遍引用的
CATEGORY_MODEL_REQUIREMENTS实际并不在constants.ts中,而是在src/shared/model-requirements.ts(当时 311 行,同样违反 200 LOC 规则); - 因此本次重构是"双文件拆分":拆分
constants.ts本体,同时把CATEGORY_MODEL_REQUIREMENTS从model-requirements.ts中剥离。
这个文件之所以危险,是因为它集中了 task 委托工具的全部静态知识:内置分类的默认配置、分类描述、分类专属 prompt 附加段、plan 智能体的系统提示词与身份判定逻辑。任何一个拼写错误或误删导出,都会波及整个委托执行链路。
二、预检分析:职责盘点与 LOC 核算
执行计划的第一步不是动手,而是把两个文件的所有职责逐项列出并估算行数:
constants.ts 的 6 项职责
| # | 职责 | 内容 | 规模 |
|---|---|---|---|
| 1 | Category prompt appends | 8 个模板字符串常量 | 约 274 行 prompt 文本 |
| 2 | DEFAULT_CATEGORIES |
Record<string, CategoryConfig> |
约 10 行 |
| 3 | CATEGORY_PROMPT_APPENDS |
分类 → prompt 映射 | 约 10 行 |
| 4 | CATEGORY_DESCRIPTIONS |
分类 → 描述映射 | 约 10 行 |
| 5 | Plan agent prompts | 2 个模板字符串 + 4 个构建函数 | 约 250 行 prompt 文本 |
| 6 | Plan agent identity utils | isPlanAgent、isPlanFamily |
约 30 行 |
model-requirements.ts 的 3 项职责
- 类型定义(
FallbackEntry、ModelRequirement); AGENT_MODEL_REQUIREMENTS(约 146 行);CATEGORY_MODEL_REQUIREMENTS(约 148 行)。
值得注意:prompt 文本类代码虽然行数巨大,但在 modular-code-enforcement 规则下豁免 200 LOC 限制——这一点在计划中针对 2c(约 280 行)与 2d(约 270 行)两个新文件被明确标注。也就是说,拆分目标不是机械地把行数砍到 200 以下,而是按职责边界切分,prompt 文本天然聚类的文件允许超限。
三、导入依赖映射:拆分安全性的前提
计划的核心洞察是:只要保留桶文件(barrel)重导出,所有消费者就一行都不用改。但前提是先把依赖面摸清楚。计划列出了三类消费者:
内部消费者(delegate-task/ 目录内)
| 文件 | 导入符号 |
|---|---|
categories.ts |
DEFAULT_CATEGORIES、CATEGORY_PROMPT_APPENDS |
tools.ts |
CATEGORY_DESCRIPTIONS |
tools.test.ts |
DEFAULT_CATEGORIES、CATEGORY_PROMPT_APPENDS、CATEGORY_DESCRIPTIONS、isPlanAgent、PLAN_AGENT_NAMES、isPlanFamily、PLAN_FAMILY_NAMES |
prompt-builder.ts |
buildPlanAgentSystemPrepend、isPlanAgent |
subagent-resolver.ts |
isPlanFamily |
sync-continuation.ts |
isPlanFamily |
sync-prompt-sender.ts |
isPlanFamily |
index.ts |
export * from "./constants"(桶文件) |
外部消费者(import from "../../tools/delegate-task/constants")
| 文件 | 导入符号 |
|---|---|
agents/atlas/prompt-section-builder.ts |
CATEGORY_DESCRIPTIONS |
agents/builtin-agents.ts |
CATEGORY_DESCRIPTIONS |
plugin/available-categories.ts |
CATEGORY_DESCRIPTIONS |
plugin-handlers/category-config-resolver.ts |
DEFAULT_CATEGORIES |
shared/merge-categories.ts |
DEFAULT_CATEGORIES |
shared/merge-categories.test.ts |
DEFAULT_CATEGORIES |
CATEGORY_MODEL_REQUIREMENTS 的消费者
| 文件 | 导入路径 |
|---|---|
tools/delegate-task/categories.ts |
../../shared/model-requirements |
这些依赖在当前仓库中依然可以逐一验证,说明该计划的预检映射是准确的:
- prompt-builder.ts 中
import { buildPlanAgentSystemPrepend, isPlanAgent } from "./constants",并在 prompt 组装时以isPlanAgent(agentName)为开关注入 plan 系统提示词; - sync-continuation.ts 与 sync-prompt-sender.ts 都从
./constants引入isPlanFamily,分别用于"恢复会话时是否允许 task 工具"(const allowTask = isPlanFamily(resumeAgent))和"同步 prompt 路由时是否放行 task"; - 外部消费者 builtin-agents.ts、available-categories.ts、atlas/prompt-section-builder.ts 至今仍从
../tools/delegate-task/constants导入CATEGORY_DESCRIPTIONS,category-config-resolver.ts 导入DEFAULT_CATEGORIES并实现"用户自定义分类优先、内置分类兜底"(userCategories?.[categoryName] ?? DEFAULT_CATEGORIES[categoryName])。
依赖图还揭示了一个跨层语义:isPlanFamily 不只是提示词注入开关,它同时控制"互斥委托阻断"与"task 工具权限"——plan 家族(plan + prometheus)是编排者,可以下发 task 调用,这正是后续 constants.ts 中 PLAN_FAMILY_NAMES = ["plan", "prometheus"] 与 COORDINATOR_AGENT_NAMES 守卫存在的根源。
四、分步执行:从建分支到提 PR
Step 1:创建分支
git checkout -b refactor/split-category-constants dev
分支命名直接体现重构意图与目标(refactor/split-*),基线为 dev。
Step 2:把 constants.ts 拆分为 5 个职责单一的文件
2a. default-categories.ts
- 移入
DEFAULT_CATEGORIESrecord; - 从 config schema 导入
CategoryConfig类型; - 约 15 行。
2b. category-descriptions.ts
- 移入
CATEGORY_DESCRIPTIONSrecord; - 无依赖;
- 约 12 行。
2c. category-prompt-appends.ts
- 移入全部 8 个
*_CATEGORY_PROMPT_APPEND模板字符串常量; - 移入
CATEGORY_PROMPT_APPENDS映射 record; - 无依赖(全部是自带上下文、自包含的模板字符串);
- 约 280 行(以 prompt 文本为主,豁免 200 LOC 限制)。
2d. plan-agent-prompt.ts
- 移入
PLAN_AGENT_SYSTEM_PREPEND_STATIC_BEFORE_SKILLS; - 移入
PLAN_AGENT_SYSTEM_PREPEND_STATIC_AFTER_SKILLS; - 移入
renderPlanAgentCategoryRows()、renderPlanAgentSkillRows(); - 移入
buildPlanAgentSkillsSection()、buildPlanAgentSystemPrepend(); - 依赖:来自 agents 的
AvailableCategory、AvailableSkill类型,来自 shared 的truncateDescription; - 约 270 行(以 prompt 文本为主,豁免)。
2e. plan-agent-identity.ts
- 移入
PLAN_AGENT_NAMES、isPlanAgent(); - 移入
PLAN_FAMILY_NAMES、isPlanFamily(); - 无依赖;
- 约 35 行。
拆分顺序暗含依赖考量:2a–2c、2e 完全无依赖、可独立验证;2d 是唯一带跨模块类型依赖的文件,因此放在最后,且计划中显式标注了它需要导入的类型来源。
Step 3:把 constants.ts 改写为桶重导出文件
原文件全部内容替换为对 5 个新文件的 re-export。这一步是整个计划的枢纽:它让所有既有导入者保持 100% 向后兼容——内部消费者、外部消费者、桶文件 index.ts 的 export * 全部照常工作。
Step 4:拆分 model-requirements.ts
4a. 新建 src/shared/category-model-requirements.ts
- 移入
CATEGORY_MODEL_REQUIREMENTSrecord; - 从
./model-requirements导入ModelRequirement类型; - 约 150 行。
4b. 更新 src/shared/model-requirements.ts
- 删除
CATEGORY_MODEL_REQUIREMENTS; - 增加重导出:
export { CATEGORY_MODEL_REQUIREMENTS } from "./category-model-requirements"; - 保留类型(
FallbackEntry、ModelRequirement)与AGENT_MODEL_REQUIREMENTS; - 收敛到约 165 行(低于 200 行红线)。
Step 5:验证导入无破坏
bun run typecheck—— 确认所有 import 可解析;bun test—— 确认无行为回归;bun run build—— 确认构建成功。
Step 6:LSP 诊断检查
对所有新建与修改文件检查 lsp_diagnostics 是否为空。这一条把验证从"编译器/测试通过"进一步收紧到"无诊断告警",防止死代码、未使用导出等隐性问题进入主干。
Step 7:提交并创建 PR
- 单个原子提交:
refactor: split delegate-task constants and category model requirements into focused modules; - 附 PR 描述。
原子提交保证 code review 时 diff 只有"移动 + 重导出",没有任何行为混入,reviewer 可以确信这是一次纯结构性重构。
五、文件变更清单与"零消费者变更"原则
| 文件 | 动作 |
|---|---|
src/tools/delegate-task/constants.ts |
改写为桶重导出 |
src/tools/delegate-task/default-categories.ts |
新增 |
src/tools/delegate-task/category-descriptions.ts |
新增 |
src/tools/delegate-task/category-prompt-appends.ts |
新增 |
src/tools/delegate-task/plan-agent-prompt.ts |
新增 |
src/tools/delegate-task/plan-agent-identity.ts |
新增 |
src/shared/model-requirements.ts |
删除 CATEGORY_MODEL_REQUIREMENTS,增加重导出 |
src/shared/category-model-requirements.ts |
新增 |
对任何消费者文件零修改。 全部既有导入经由桶重导出继续生效。这是本文最值得内化的一条原则:重构的安全边界不靠小心修改消费者来维持,而靠重导出契约来维持——消费者越多,这一原则的价值越大。
六、当前仓库验证:计划的实际落地形态
从源码结构看,该执行计划在 omo/lazycodex 仓库中已被(部分)落地,且实际演化与计划同构但略有差异,这本身就是"计划驱动重构 + 后续持续演化"的真实样本:
-
分类 record 已迁出 constants.ts。当前 constants.ts 只有 414 行(从 654 行收敛),其开头就是标准的桶重导出:
export { BUILTIN_CATEGORY_REQUIRES_MODEL, CATEGORY_DESCRIPTIONS, CATEGORY_PROMPT_APPENDS, CATEGORY_PROMPT_APPEND_RESOLVERS, DEFAULT_CATEGORIES, } from "./builtin-categories"DEFAULT_CATEGORIES、CATEGORY_PROMPT_APPENDS、CATEGORY_DESCRIPTIONS等 record 已迁入 builtin-categories.ts。值得注意的是,实际实现没有把三份 record 机械拆成三个文件,而是收敛为一份BUILTIN_CATEGORY_DEFINITION[](按 Google/OpenAI/Anthropic/Kimi 分组),再通过统一的buildCategoryRecord()工厂派生出各 record——从源码结构看,这是比计划更进一步的"单一数据源"设计,消除了计划中三份 record 各自维护的潜在漂移风险。 -
constants.ts 保留了计划 2d/2e 对应的职责:
PLAN_AGENT_SYSTEM_PREPEND_STATIC_BEFORE_SKILLS/AFTER_SKILLS两大模板字符串、renderPlanAgentCategoryRows、renderPlanAgentSkillRows、buildPlanAgentSkillsSection、buildPlanAgentSystemPrepend,以及PLAN_AGENT_NAMES、isPlanAgent、PLAN_FAMILY_NAMES、isPlanFamily等身份工具仍留在该文件中。plan agent 的系统提示词本身就是"先派发 explore/librarian 智能体收集上下文、再输出依赖图 + 并行执行波次 + 分类/技能推荐"的强制协议——这也解释了为什么这类 prompt 文本天然豁免 200 LOC 限制。 -
model-requirements 拆分已完成并进一步下沉到共享包。当前 shared/model-requirements.ts 已是一个仅 5 行的重导出垫片(shim),把
FallbackEntry、ModelRequirement类型与AGENT_MODEL_REQUIREMENTS、CATEGORY_MODEL_REQUIREMENTS全部转发到@oh-my-opencode/model-core。而 packages/model-core/src/category-model-requirements.ts(131 行)正是计划 Step 4a 所描述的新家:CATEGORY_MODEL_REQUIREMENTSrecord 按分类提供fallbackChain,例如visual-engineering分类的降级链为claude-opus-5(max)→kimi-k3(max)→glm-5.2(max)→gpt-5.6-sol(medium),每项都带 providers 白名单与推理档位(variant)。类型定义则落在 model-requirement-types.ts。消费端 categories.ts 仍然按计划中的路径../../shared/model-requirements导入CATEGORY_MODEL_REQUIREMENTS并在分类解析时查表(const categoryReq = CATEGORY_MODEL_REQUIREMENTS[categoryName])——消费者零变更原则在真实代码中得到兑现。 -
委托链文档与计划相互印证。delegate-task 目录的 AGENTS.md 描述了该目录的双执行模式(background/sync)、同步执行链(
sync-task.ts → sync-session-creator.ts → sync-prompt-sender.ts → sync-session-poller.ts → sync-result-fetcher.ts)与分类解析流程(用户自定义分类优先,回退到内置分类)。计划中依赖映射涉及的sync-prompt-sender.ts、sync-continuation.ts、categories.ts都是这条链上的一环,说明拆分所触及的正是委托执行链的核心静态层。
七、方法论提炼:如何安全拆分一个"上帝常量文件"
把这份执行计划抽象成可复用流程,共五步:
- 先盘点职责,再动手:为源文件逐条列出职责与行数,明确哪些内容豁免行数规则(如 prompt 文本),避免按行数机械切分导致语义割裂;
- 画完整导入依赖图:区分内部消费者、外部消费者、桶文件三类,按符号粒度记录(哪个文件 import 了哪些导出)。这一步决定了桶文件必须保留哪些重导出;
- 按职责建文件,无依赖者优先:无外部依赖的 record/常量先拆(可独立验证),带跨模块类型依赖的模块最后拆,并在计划中显式列出依赖来源;
- 桶重导出兜底:源文件改写为 re-export 层,实现"消费者零变更";被拆分出的大块(如
CATEGORY_MODEL_REQUIREMENTS)同样在新家加类型导入、在旧位置加重导出; - 三层验证 + LSP 诊断:
typecheck(导入可解析)→test(行为无回归)→build(构建成功)→lsp_diagnostics(无隐性问题),全部通过后再以单个原子提交 + PR 描述收尾。
这套流程在 omo/lazycodex 的实际演化中经受住了检验:即使后续架构调整(内置分类改为按 provider 分组的定义数组、模型需求下沉到 model-core 共享包)偏离了原计划的文件名与文件数,重导出契约始终保护着所有消费者——这正是"以文档为计划、以源码为契约"的重构工程实践。
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