oh-my-openagent delegate-task 常量模块拆分重构:Barrel 再导出、类别配置与模型要求提取
本文以仓库中的一份重构方案文档为核心,完整解读 oh-my-openagent 项目里 delegate-task(任务委派工具)模块的常量拆分实践:如何把约 654 行的 constants.ts 按职责拆分为类别配置、类别描述、提示词附加段、Plan 代理提示词与身份判定等多个独立文件,并通过 Barrel 再导出保持零消费者改动的向后兼容。读完后你将掌握该重构的逐文件设计、CATEGORY_MODEL_REQUIREMENTS 回退链的数据结构,以及这一方案在当前仓库源码中的落地形态。
1. 重构背景:为什么拆分 constants.ts
delegate-task 模块是 omo/lazycodex 的子代理委派体系(task 工具)的核心,负责按“类别(category)”把任务路由到合适的模型、附加类别专属提示词,并为 Plan 代理注入规划系统提示词。重构前,这些内容全部集中在一块:
- 类别默认配置(
DEFAULT_CATEGORIES)、类别描述(CATEGORY_DESCRIPTIONS)、8 组类别提示词附加段(CATEGORY_PROMPT_APPENDS); - Plan 代理系统提示词的静态模板与动态组装函数;
- Plan 代理身份判定(
isPlanAgent/isPlanFamily)。
方案文档(code-changes.md)给出的目标是:按单一职责拆分文件,constants.ts 改写为 Barrel 再导出文件,CATEGORY_MODEL_REQUIREMENTS 从共享模块中抽出独立文件,消费者文件零改动。该文档源自仓库的 PR 工作区评估(eval_metadata.json 中 eval_name 为 refactor-split-constants),是一份逐文件的重构规格。
路径说明:文档中写的
src/tools/delegate-task/...相对路径,对应当前仓库中的 packages/omo-opencode/src/tools/delegate-task/;共享的模型要求则对应 packages/model-core/src/。
2. 目标文件全景:变更汇总表
方案给出的变更清单如下(行数为方案撰写时的估计值):
| 文件 | 变更前行数 | 变更后行数 | 动作 |
|---|---|---|---|
constants.ts |
654 | ~25 | 改写为 Barrel 再导出 |
default-categories.ts |
- | ~15 | 新增 |
category-descriptions.ts |
- | ~12 | 新增 |
category-prompt-appends.ts |
- | ~280 | 新增(大部分为提示词文本) |
plan-agent-prompt.ts |
- | ~270 | 新增(大部分为提示词文本) |
plan-agent-identity.ts |
- | ~35 | 新增 |
model-requirements.ts |
311 | ~165 | 移除 CATEGORY_MODEL_REQUIREMENTS |
category-model-requirements.ts |
- | ~150 | 新增 |
方案在结尾明确:“Zero consumer files modified. Backward compatibility maintained through barrel re-exports.”——没有任何消费方文件需要修改,兼容性完全靠 Barrel 再导出维持。
3. 类别配置层拆分
3.1 default-categories.ts:DEFAULT_CATEGORIES
类别是委派系统的核心抽象。每个类别绑定一个首选模型与可选的 reasoning variant,方案文档中给出的完整定义为:
import type { CategoryConfig } from "../../config/schema"
export const DEFAULT_CATEGORIES: Record<string, CategoryConfig> = {
"visual-engineering": { model: "google/gemini-3.1-pro", variant: "high" },
ultrabrain: { model: "openai/gpt-5.4", variant: "xhigh" },
deep: { model: "openai/gpt-5.5-codex", variant: "medium" },
artistry: { model: "google/gemini-3.1-pro", variant: "high" },
quick: { model: "anthropic/claude-haiku-4-5" },
"unspecified-low": { model: "anthropic/claude-sonnet-4-6" },
"unspecified-high": { model: "anthropic/claude-opus-4-6", variant: "max" },
writing: { model: "kimi-for-coding/k2p5" },
}
8 个内置类别的语义分工(来自同一文档的 CATEGORY_DESCRIPTIONS,完整继承):
export const CATEGORY_DESCRIPTIONS: Record<string, string> = {
"visual-engineering": "Frontend, UI/UX, design, styling, animation",
ultrabrain: "Use ONLY for genuinely hard, logic-heavy tasks. Give clear goals only, not step-by-step instructions.",
deep: "Goal-oriented autonomous problem-solving. Thorough research before action. For hairy problems requiring deep understanding.",
artistry: "Complex problem-solving with unconventional, creative approaches - beyond standard patterns",
quick: "Trivial tasks - single file changes, typo fixes, simple modifications",
"unspecified-low": "Tasks that don't fit other categories, low effort required",
"unspecified-high": "Tasks that don't fit other categories, high effort required",
writing: "Documentation, prose, technical writing",
}
注意:文档中的模型名(
gemini-3.1-pro、gpt-5.4、gpt-5.5-codex、k2p5等)反映的是重构方案撰写时的模型目录。当前仓库的模型目录已演进(见第 6 节),但“类别 → 模型/variant”的数据形状保持不变。
3.2 category-prompt-appends.ts:类别提示词附加段
每个类别携带一段 *_CATEGORY_PROMPT_APPEND 提示词,用 <Category_Context> 标签包裹,注入被委派子代理的系统提示,使其行为风格与任务类型匹配。方案文档中列出了 8 个导出常量及汇总映射:
export const VISUAL_CATEGORY_PROMPT_APPEND = `<Category_Context>
You are working on VISUAL/UI tasks.
...
</Category_Context>`
export const ULTRABRAIN_CATEGORY_PROMPT_APPEND = `<Category_Context>
You are working on DEEP LOGICAL REASONING / COMPLEX ARCHITECTURE tasks.
...
</Category_Context>`
export const ARTISTRY_CATEGORY_PROMPT_APPEND = `<Category_Context>
You are working on HIGHLY CREATIVE / ARTISTIC tasks.
...
</Category_Context>`
export const QUICK_CATEGORY_PROMPT_APPEND = `<Category_Context>
You are working on SMALL / QUICK tasks.
...
</Caller_Warning>`
export const UNSPECIFIED_LOW_CATEGORY_PROMPT_APPEND = `<Category_Context>
You are working on tasks that don't fit specific categories but require moderate effort.
...
</Caller_Warning>`
export const UNSPECIFIED_HIGH_CATEGORY_PROMPT_APPEND = `<Category_Context>
You are working on tasks that don't fit specific categories but require substantial effort.
...
</Category_Context>`
export const WRITING_CATEGORY_PROMPT_APPEND = `<Category_Context>
You are working on WRITING / PROSE tasks.
...
</Category_Context>`
export const DEEP_CATEGORY_PROMPT_APPEND = `<Category_Context>
You are working on GOAL-ORIENTED AUTONOMOUS tasks.
...
</Category_Context>`
export const CATEGORY_PROMPT_APPENDS: Record<string, string> = {
"visual-engineering": VISUAL_CATEGORY_PROMPT_APPEND,
ultrabrain: ULTRABRAIN_CATEGORY_PROMPT_APPEND,
deep: DEEP_CATEGORY_PROMPT_APPEND,
artistry: ARTISTRY_CATEGORY_PROMPT_APPEND,
quick: QUICK_CATEGORY_PROMPT_APPEND,
"unspecified-low": UNSPECIFIED_LOW_CATEGORY_PROMPT_APPEND,
"unspecified-high": UNSPECIFIED_HIGH_CATEGORY_PROMPT_APPEND,
writing: WRITING_CATEGORY_PROMPT_APPEND,
}
(文档注明:每个 *_CATEGORY_PROMPT_APPEND 的模板正文在方案中做了省略,实际代码包含完整提示词。)
拆分这个文件的价值在于:约 280 行中绝大部分是提示词文本而非逻辑代码,与数据定义、身份判定逻辑放在一起会严重干扰可读性。从当前仓库源码结构看,这一思路已经落地为更进一步的形态——builtin-categories.ts 把 Google/OpenAI/Anthropic/Kimi 四个厂商的类别定义分别放在 google-categories.ts、openai-categories.ts 等文件中,再由 buildCategoryRecord 统一构建 DEFAULT_CATEGORIES、CATEGORY_PROMPT_APPENDS、CATEGORY_DESCRIPTIONS 三个 Record,即“数据源与索引构建”的二次拆分。
4. Plan 代理层拆分
4.1 plan-agent-prompt.ts:系统提示词的三段式组装
Plan 代理(规划代理)在被委派时会收到一段强制性的系统提示词,要求先收集上下文、输出任务依赖图、并行执行波次、类别与技能推荐。方案将其组织为“静态前段 + 动态技能区 + 静态后段”:
import type {
AvailableCategory,
AvailableSkill,
} from "../../agents/dynamic-agent-prompt-builder"
import { truncateDescription } from "../../shared/truncate-description"
export const PLAN_AGENT_SYSTEM_PREPEND_STATIC_BEFORE_SKILLS = `<system>
BEFORE you begin planning, you MUST first understand the user's request deeply.
...
</CRITICAL_REQUIREMENT_DEPENDENCY_PARALLEL_EXECUTION_CATEGORY_SKILLS>
<FINAL_OUTPUT_FOR_CALLER>
...
</FINAL_OUTPUT_FOR_CALLER>
`
export const PLAN_AGENT_SYSTEM_PREPEND_STATIC_AFTER_SKILLS = `### REQUIRED OUTPUT FORMAT
...
`
function renderPlanAgentCategoryRows(categories: AvailableCategory[]): string[] {
const sorted = [...categories].sort((a, b) => a.name.localeCompare(b.name))
return sorted.map((category) => {
const bestFor = category.description || category.name
const model = category.model || ""
return `| \`${category.name}\` | ${bestFor} | ${model} |`
})
}
function renderPlanAgentSkillRows(skills: AvailableSkill[]): string[] {
const sorted = [...skills].sort((a, b) => a.name.localeCompare(b.name))
return sorted.map((skill) => {
const domain = truncateDescription(skill.description).trim() || skill.name
return `| \`${skill.name}\` | ${domain} |`
})
}
export function buildPlanAgentSkillsSection(
categories: AvailableCategory[] = [],
skills: AvailableSkill[] = []
): string {
const categoryRows = renderPlanAgentCategoryRows(categories)
const skillRows = renderPlanAgentSkillRows(skills)
return `### AVAILABLE CATEGORIES
| Category | Best For | Model |
|----------|----------|-------|
${categoryRows.join("\n")}
### AVAILABLE SKILLS (ALWAYS EVALUATE ALL)
Skills inject specialized expertise into the delegated agent.
YOU MUST evaluate EVERY skill and justify inclusions/omissions.
| Skill | Domain |
|-------|--------|
${skillRows.join("\n")}`
}
export function buildPlanAgentSystemPrepend(
categories: AvailableCategory[] = [],
skills: AvailableSkill[] = []
): string {
return [
PLAN_AGENT_SYSTEM_PREPEND_STATIC_BEFORE_SKILLS,
buildPlanAgentSkillsSection(categories, skills),
PLAN_AGENT_SYSTEM_PREPEND_STATIC_AFTER_SKILLS,
].join("\n\n")
}
两段静态模板之间的动态区(buildPlanAgentSkillsSection)按名称排序后渲染成 Markdown 表格,让 Plan 代理“看到”当前可用的全部类别与技能。
当前仓库中的完整提示词文本可以直接查看:constants.ts 中 PLAN_AGENT_SYSTEM_PREPEND_STATIC_BEFORE_SKILLS(L21 起)强制要求 Plan 代理先以 explore / librarian 子代理收集上下文,再依次输出 SECTION 1 任务依赖图、SECTION 2 并行执行波次图、SECTION 3 类别+技能推荐;PLAN_AGENT_SYSTEM_PREPEND_STATIC_AFTER_SKILLS(L129 起)则规定了逐任务的 Delegation Recommendation 块、<plan> 交付物信封(Deliverable Envelope)以及供调用方直接照抄的按波次划分的 TODO 列表格式。buildPlanAgentSystemPrepend 的三段式拼接与方案文档逐行对应(constants.ts)。
4.2 plan-agent-identity.ts:Plan 代理身份判定
方案将“谁是 Plan 代理”与“谁是 Plan 家族”分离为一组独立常量与谓词:
/**
* List of agent names that should be treated as plan agents (receive plan system prompt).
* Case-insensitive matching is used.
*/
export const PLAN_AGENT_NAMES = ["plan"]
/**
* Check if the given agent name is a plan agent (receives plan system prompt).
*/
export function isPlanAgent(agentName: string | undefined): boolean {
if (!agentName) return false
const lowerName = agentName.toLowerCase().trim()
return PLAN_AGENT_NAMES.some(name => lowerName === name || lowerName.includes(name))
}
/**
* Plan family: plan + prometheus. Shares mutual delegation blocking and task tool permission.
* Does NOT share system prompt (only isPlanAgent controls that).
*/
export const PLAN_FAMILY_NAMES = ["plan", "prometheus"]
export function isPlanFamily(category: string | undefined): boolean {
if (!category) return false
const lowerCategory = category.toLowerCase().trim()
return PLAN_FAMILY_NAMES.some(
(name) => lowerCategory === name || lowerCategory.includes(name)
)
}
两个谓词的分工是有意为之的:isPlanAgent 只决定系统提示词注入,而 isPlanFamily 决定互相委派的阻断与 task 工具权限(plan 与 prometheus 共享)。注释里一句 “Does NOT share system prompt” 点明了这种非对称设计。从当前源码结构看,这套语义已完整保留在 constants.ts:isPlanAgent 在此基础上还引入了 getAgentConfigKey 归一化与精确匹配(L352-L356),并新增了 PLAN_DELIVERABLE_TAG / getDeliverableTag(L363-L372)用于配合 <plan> 信封的产物提取,以及 COORDINATOR_AGENT_NAMES = ["prometheus"] 的协调者防误委派守卫(L404-L414,注释中引用了 issue #4027 与 PR #4065 的来龙去脉)——这些都可以推断为方案落地后的持续演化。
5. Barrel 再导出:constants.ts 的最终形态
拆分完成后,constants.ts 收缩为纯再导出文件,保证所有原有 import { ... } from "./constants" 的消费者不受影响:
export { DEFAULT_CATEGORIES } from "./default-categories"
export { CATEGORY_DESCRIPTIONS } from "./category-descriptions"
export {
VISUAL_CATEGORY_PROMPT_APPEND,
ULTRABRAIN_CATEGORY_PROMPT_APPEND,
ARTISTRY_CATEGORY_PROMPT_APPEND,
QUICK_CATEGORY_PROMPT_APPEND,
UNSPECIFIED_LOW_CATEGORY_PROMPT_APPEND,
UNSPECIFIED_HIGH_CATEGORY_PROMPT_APPEND,
WRITING_CATEGORY_PROMPT_APPEND,
DEEP_CATEGORY_PROMPT_APPEND,
CATEGORY_PROMPT_APPENDS,
} from "./category-prompt-appends"
export {
PLAN_AGENT_SYSTEM_PREPEND_STATIC_BEFORE_SKILLS,
PLAN_AGENT_SYSTEM_PREPEND_STATIC_AFTER_SKILLS,
buildPlanAgentSkillsSection,
buildPlanAgentSystemPrepend,
} from "./plan-agent-prompt"
export {
PLAN_AGENT_NAMES,
isPlanAgent,
PLAN_FAMILY_NAMES,
isPlanFamily,
} from "./plan-agent-identity"
当前仓库的 constants.ts 呈现了同样的 Barrel 结构——头部即是对 ./builtin-categories 的再导出:
export {
BUILTIN_CATEGORY_REQUIRES_MODEL,
CATEGORY_DESCRIPTIONS,
CATEGORY_PROMPT_APPENDS,
CATEGORY_PROMPT_APPEND_RESOLVERS,
DEFAULT_CATEGORIES,
} from "./builtin-categories"
对比方案可见两点演化:一是类别数据源从“8 个类别平铺在一个 Record”收敛为“按厂商组织的 BuiltinCategoryDefinition[] 数组”(见 builtin-category-definition.ts);二是新增了 CATEGORY_PROMPT_APPEND_RESOLVERS(按模型动态解析附加段)与 BUILTIN_CATEGORY_REQUIRES_MODEL(类别级模型门)。Barrel 的兼容性策略则一以贯之:消费者仍只从 constants.ts 取用。
6. 模型要求拆分:CATEGORY_MODEL_REQUIREMENTS 与回退链
6.1 数据结构
方案把 CATEGORY_MODEL_REQUIREMENTS 从 model-requirements.ts 移入新文件 category-model-requirements.ts,其类型形状为:
import type { ModelRequirement } from "./model-requirements"
export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
"visual-engineering": {
fallbackChain: [
{
providers: ["google", "github-copilot", "opencode"],
model: "gemini-3.1-pro",
variant: "high",
},
{ providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
{
providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-opus-4-6",
variant: "max",
},
{ providers: ["opencode-go"], model: "glm-5" },
{ providers: ["kimi-for-coding"], model: "k2P5" },
],
},
ultrabrain: { fallbackChain: [/* 与原文件一致的回退链 */] },
deep: {
fallbackChain: [/* 与原文件一致的回退链 */],
requiresModel: "gpt-5.5-codex",
},
artistry: {
fallbackChain: [/* 与原文件一致的回退链 */],
requiresModel: "gemini-3.1-pro",
},
/* quick / unspecified-low / unspecified-high / writing 同理,省略 */
}
每个 fallbackChain 条目按 providers(可连接的服务商列表)+ model + 可选 variant 描述一个候选;requiresModel 则是“类别激活门”——例如 deep 类别只在 gpt-5.5-codex(方案时点)可用时才参与路由。
该结构的正式类型定义现在位于 model-requirement-types.ts:
export type FallbackEntry = {
providers: string[];
model: string;
variant?: string; // Entry-specific variant (e.g., GPT->high, Opus->max)
/* 以及 reasoning、temperature、top_p、maxTokens、thinking 等可选字段 */
};
export type ModelRequirement = {
fallbackChain: FallbackEntry[];
variant?: string; // Default variant (used when entry doesn't specify one)
requiresModel?: string; // If set, only activates when this model is available (fuzzy match)
requiresAnyModel?: boolean;
requiresProvider?: string[];
};
与方案中的 ModelRequirement 相比,字段完全兼容且略有扩展(reasoning、requiresAnyModel 等)。
6.2 model-requirements.ts 的收缩
方案对 model-requirements.ts 的改动只有两处:删除文件主体中的 CATEGORY_MODEL_REQUIREMENTS,保留 AGENT_MODEL_REQUIREMENTS(代理级要求)与类型定义,并在文件末尾追加一条再导出:
export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
// ... unchanged, full agent entries stay here
};
export { CATEGORY_MODEL_REQUIREMENTS } from "./category-model-requirements"
“类别级”与“代理级”两套模型要求从此分文件维护,而既有 from "./model-requirements" 的导入路径依旧有效。
6.3 当前仓库中的落地形态
在当前仓库中,这份数据已经迁移到独立的共享包 packages/model-core/src/category-model-requirements.ts,8 个类别各带一条完整回退链(例如 visual-engineering 在 L4-L23 依次尝试 claude-opus-5/max → kimi-k3/max → glm-5.2/max → gpt-5.6-sol/medium;quick 在 L59-L78 有 8 级回退,从 kimi-for-coding-highspeed 一路到 claude-haiku-4-5/off)。类别模型目录随版本更新,但“类别 → 多级 provider/model/variant 回退链”的结构自方案确立以来未变。
两处源码证据印证了方案中的“门”语义:
- builtin-categories.ts 的注释明确写道:“CATEGORY_MODEL_REQUIREMENTS (model-core) wins when it carries its own requiresModel; this record covers builtin categories whose gate is declared only on the definition (deep)”——即 model-core 中
requiresModel优先,定义侧的requiresModel(如deep)作为补充,两者合并为BUILTIN_CATEGORY_REQUIRES_MODEL; - 该包内的 category-routing-policy.test.ts 对“类别路由策略”(模型可用性决定哪些类别可路由)做了针对性测试,可视为该数据结构的回归验证入口。
7. 重构要点复盘
从这份方案与当前仓库的对照中可以提炼出几条可直接复用的工程经验:
- Barrel 再导出实现零破坏拆分:654 行 → ~25 行的纯 re-export 文件,使“拆分”与“消费者迁移”解耦,PR 可以一次完成而无需协调任何导入方改动。当前 constants.ts 仍维持这一形态。
- 数据、文本、逻辑三分:类别配置(小数据)、提示词附加段(大文本)、Plan 提示词组装与身份判定(纯逻辑)各归其位,
category-prompt-appends.ts这类“几乎全是字符串”的文件从常量逻辑中剥离后,diff 审查与测试都更聚焦。 - 模型要求按“类别/代理”两个维度分文件:
CATEGORY_MODEL_REQUIREMENTS与AGENT_MODEL_REQUIREMENTS各自独立维护、共享ModelRequirement类型,且通过再导出保持旧路径可用;当前形态进一步演进为独立共享包 model-core,被 omo-opencode 与 senpi-task 等多个包复用(见 model-core 的 AGENTS.md)。 - 语义谓词显式分离:
isPlanAgent(提示词注入)与isPlanFamily(权限与阻断)名称、注释、职责边界清晰,避免了“一个布尔值管三件事”的隐式耦合。 - 验证策略:方案配套的评估断言(eval_metadata.json)要求 git worktree 隔离、2+ 原子提交、Barrel 兼容、三门验证循环,以及“引用真实的 constants.ts 及其导出”——对一次多文件重构而言,这些约束共同保证了改动可回滚、可审查。
需要说明的适用前提:方案文档中的具体模型名(gemini-3.1-pro、gpt-5.4、kimi k2p5 等)是撰写时点的目录快照,当前仓库已切换到新目录(如 claude-opus-5、gpt-5.6-sol、kimi-k3,见 category-model-requirements.ts);阅读该文档时应把它视为“结构与流程的依据”,模型取值以当前源码为准。
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