oh-my-openagent 配置迁移缺陷排查:2026-08-reasoning-unification 与模型配置链的一致性修复
【免费下载链接】oh-my-openagentOmO: Just type "mass ulw" keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent
本文基于 oh-my-openagent(OmO)仓库中的 Discord 配置迁移分诊记录(triage 文档),完整还原一次"迁移写入的配置被运行时 Schema 拒绝"的仓库自有缺陷(repository-owned defect)的定位过程:从 Discord 报告、GitHub issue #6567 的表象,到2026-08-reasoning-unification迁移的源码级实现、AgentOverrideConfigSchema的校验缺口、config-chain 模型输入的遗漏,再到测试验证与修复边界。读完本文,你将掌握 OmO 配置迁移引擎的完整调用链、reasoning 字段统一化的归一化规则,以及如何通过 fixture 测试锁定这类"迁移端与校验端不一致"的回归。
问题表象:迁移之后配置失效、委派不再遵循模型
2026-08-05(KST)的分诊记录显示,用户通过 Discord 报告(报告 ID1534283281300979743)反馈了与配置迁移/委派相关的异常,核心现象有两个:
- 迁移会留下非法配置:
2026-08-reasoning-unification迁移把规范形态(canonical)写入agents.*.models,但当时origin/dev的AgentOverrideConfigSchema仍然拒绝该键; - 委派停止遵循已配置的模型:由于
models键被从 config-chain 的模型输入中省略,运行时拿不到迁移写入的模型链,导致 delegation 不再按用户配置选择模型。
分诊小组检查了关联的 Discord 消息及周边迁移/委派讨论、GitHub issue #6567 与相关 PR #6560、#6516,并核对了最新origin/dev上的迁移、插件 schema、config-chain、doctor 与运行时加载路径,最终给出所有权判定:PASS,这是仓库自有缺陷。
OmO 配置迁移体系全貌:发现、计划、执行、转换
要理解这个缺陷,先看2026-08-reasoning-unification在迁移体系中的位置。根据 config-migration/AGENTS.md,该模块负责:
- 发现(discovery):定位遗留配置文件(
oh-my-opencode/oh-my-openagentJSON[C]、~/.omo/config.jsonc),由 discovery.ts 及discovery-roots.ts、discovery-paths.ts实现,涉及OPENCODE_CONFIG_MIGRATION_ID与CONFIG_JSONC_MIGRATION_ID两个组; - 计划(planning):由 migration-plans.ts 的
createLegacyConfigMigrationPlans构建迁移计划; - 执行(execution):由 migration-executor.ts 的
executeLegacyConfigMigrationPlan落地,支持 dry-run、备份与日志; - 内容转换(transform):
transform-opencode.ts/transform-config-jsonc.ts,以及本次缺陷核心的 reasoning-unification.ts。
关键约定(见 AGENTS.md):
- 转换是纯函数:transform 只把加载的源返回为
ConfigMigrationTransformResult,所有文件系统副作用都经由 omo-config-core 的迁移引擎完成,绝不在转换层直接写配置; - 迁移 ID 必须稳定:每个迁移以固定 id 记录在目标文件的
_migrations数组中,已发布的 id 不得复用或改名,历史 id 追加到legacy-history.ts; - no-clobber 语义:
deep-diff.ts保证已存在的目标值总是优先于迁移值。
迁移引擎本体位于packages/omo-config-core/src/migration/。以 commit.ts 为例,提交阶段会把_migrations标记写入目标文档(const document = { ...merged.merged, _migrations: marker }),使迁移具备幂等性:predicate.ts检查目标中的标记,已应用过的迁移不会重复执行。整个链路在 OpenCode 插件启动、Senpi 启动、安装流程与 CLI 中都会触发(详见 configuration.md 对两个迁移组的说明)。
2026-08-reasoning-unification迁移实现剖析
迁移的 ID 常量定义在 reasoning-unification.ts:
export const REASONING_UNIFICATION_MIGRATION_ID = "2026-08-reasoning-unification"入口transformReasoningUnification(document)(L190-L197)返回{ diagnostics, document },其中 diagnostics 收集冲突告警,document 是规范化后的配置树。转换逻辑对根级categories、agents、models、[senpi]/[codex]/[opencode]分块以及嵌套profiles做递归处理。
底层归一化:legacy 模型字段 → canonical 字段
迁移的核心归一化来自 omo-config-core 的 fallback-models.ts:
normalizeLegacyModelFields(L38-L72)删除variant、reasoningEffort、thinking、textVerbosity、maxTokens、providerOptions等 legacy 键,并按下述优先级收敛出规范reasoning:reasoning > reasoningEffort > variant > (thinking.type === "disabled" ? "off")即:显式
reasoning优先,其次reasoningEffort,再次variant;若只写了thinking: { type: "disabled" },则归一化为reasoning: "off"。thinking: { type: "enabled", budgetTokens }与textVerbosity被并入provider_options;maxTokens(camelCase)归一化为max_tokens;canonicalModelString(L24-L36)把三种模型字符串写法统一为provider/model:reasoning冒号后缀形式:"p/m(xhigh)"括号后缀 →p/m:xhigh"p/m high"空格后缀 →p/m:high- 已带冒号后缀的
p/m:minimal保持不变
迁移的八条规则与 fixture 验证
迁移测试 reasoning-unification.test.ts 的第一个用例声明"rules one through eight match exact expected output",使用仓库内置 fixture 对拍:
- 输入 fixture:2026-08-reasoning-unification/fixture-input.json
- 期望输出:2026-08-reasoning-unification/fixture-expected.json
以categories.quick为例,输入为:
{ "model": "apitopia/kimi-for-coding-highspeed-unlocked", "reasoningEffort": "minimal", "fallback_models": [ { "model": "apitopia/z-ai/glm-5.2-ultrafast-unlocked", "reasoningEffort": "none" }, "openai-codex/gpt-5.6-luna-fast minimal" ], "maxTokens": 8192 }迁移后变为:
{ "models": [ { "model": "apitopia/kimi-for-coding-highspeed-unlocked", "reasoning": "minimal" }, { "model": "apitopia/z-ai/glm-5.2-ultrafast-unlocked", "reasoning": "off" }, "openai-codex/gpt-5.6-luna-fast:minimal" ], "max_tokens": 8192 }可归纳出如下规则:
- 模型链合并:
model(主模型)+ 已有models+fallback_models(后备模型)合并进统一的models数组,首项为主模型,其余为后备;随后删除顶层model与fallback_models; reasoningEffort→reasoning:对象项内的 legacy 推理级别归一为reasoning("minimal"、"none"→"off"等);- 模型字符串后缀归一:
"gpt-5.6-luna-fast minimal"这类空格后缀改写为冒号形式"gpt-5.6-luna-fast:minimal"; maxTokens→max_tokens:snake_case 规范化;- 无
fallback_models且无冲突时保留单模型形态:如categories.deep仅model + variant,迁移后为{ "model": "...", "reasoning": "medium" }; - 冲突诊断:同一实体同时出现
reasoning、reasoningEffort、variant等多个推理字段时,按优先级保留一个,其余写入 diagnostics,如conflict: categories.conflict dropped variant="high" kept reasoningEffort="xhigh"; [opencode]分块单独处理:normalizeOpenCodeBlock只重写 agent/category 已知键,native、provider等无关结构原样保留(见 L174-L188);[senpi]/[codex]则复用同一normalizeTypedBlock递归(L158-L160);profiles递归:profile 内的整块配置继续递归规范化(L164-L170)。
注意第 7 条是缺陷的关键背景:迁移对[opencode]块同样会写入规范形态(如把variant: "high"变成reasoning: "high"、把model + fallback_models合并为models数组),而 OpenCode 侧的校验 Schema 尚未同步放开。
缺陷定位:Schema 拒绝、config-chain 遗漏、doctor 告警
分诊确认了三个相互咬合的问题点:
1.AgentOverrideConfigSchema拒绝models键
OpenCode 侧的 agent 覆盖 Schema 定义在 agent-overrides.ts。该 Schema 明确允许:
model(标为 deprecated,注释说明"模型应从 category 默认值继承")models(有序模型链,首项主模型、其余后备)
……但分诊指出,最新origin/dev的AgentOverrideConfigSchema仍拒绝迁移写入的agents.*.models键——即 Schema 的z.object默认 strict/未知键剔除行为把合法迁移结果拦在了校验层之外,配置在校验阶段即被判为非法,这正是"迁移留下 invalid config"的直接原因。
2. config-chain 模型输入遗漏models
即使校验放行,config-chain 构造模型输入时也遗漏了models键:迁移合并出的完整模型链没有进入运行时模型解析的输入,导致 delegation 解析不到配置的模型,行为回退到默认值。这与 Discord 报告中"委派停止遵循配置模型"的现象完全吻合。
3.[opencode]的variant/fallback_models兼容告警
分诊还观察到,doctor/校验链路对[opencode]块中仍然支持的 legacy 键(variant、fallback_models)发出警告。也就是说,迁移端已经完成variant→reasoning、fallback_models→models的统一化,而运行时校验端仍处于"兼容旧键"阶段,两端语义不一致:迁移改写后variant消失、models出现,但校验端还在期待旧键、拒绝新键。
三个问题叠加,形成完整闭环:迁移(写 canonical)→ Schema(拒 canonical)→ config-chain(漏 canonical)→ doctor(报 legacy 兼容警告),四个环节步调不一。
测试验证:用 fixture 与引擎 E2E 锁定行为
迁移测试 reasoning-unification.test.ts 提供了四层验证:
- 纯转换对拍:
transformReasoningUnification(fixtureInput)的输出与fixtureExpected完全相等,并断言 diagnostics 包含冲突记录conflict: categories.conflict dropped variant="high" kept reasoningEffort="xhigh"; - 优先级语义:输入
{ reasoning: "low", reasoningEffort: "xhigh", variant: "high" }时,迁移结果保留reasoning: "low",且 journal 中不会出现kept reasoningEffort="xhigh"——验证 canonical 键在冲突中的胜出; - 引擎级 E2E:在临时 HOME 下写入
.omo/omo.jsonc,经createLegacyConfigMigrationPlans找到 reasoning 迁移计划后依次执行 dry-run(返回status: "planned"且 preview 与期望一致)与正式执行(返回status: "migrated"),最终文件等于fixtureExpected + { _migrations: ["2026-08-reasoning-unification"] },journal diagnostics 正确落盘,且目录下出现omo.jsonc.bak.*备份——印证了 dry-run、备份、journal、原子提交、幂等标记五件套; - typed block 与 opencode 边界:
[senpi]/[codex]递归规范化、profiles递归、[opencode]只改已知 agent/category 键、native/provider无关结构不被触碰。
测试还关联了启动路径:packages/omo-opencode/src/startup-migration.test.ts与packages/omo-senpi/src/components/config-startup/index.test.ts均涉及该迁移 ID,说明转换确实在 OpenCode/Senpi 启动期被拉起。
修复边界与 PR 状态:只合入可复现、已测试的子集
分诊对相关 PR 做了明确切割:
- PR #6560:修复的是生成 JSON Schema 的 identity/required-array 问题,与本次运行时迁移路径无关,保持独立、另行合入;
- PR #6516:目标是规范化的运行时模型链,但它是无专属测试的草稿外部 PR,且包含更广的 fallback 改动,风险不可控;
- 本次分支:仅实现当前
origin/dev上可复现、有测试覆盖的缺陷子集——即修复AgentOverrideConfigSchema对models的接纳、补上 config-chain 模型输入中的models传递、同步[opencode]的 legacy 键告警策略,不夹带 PR #6516 的宽泛改动。
这一策略体现了分诊的工程纪律:修复范围与证据范围严格对齐,未经验证的改动不进主线。
分诊流程规范:证据留存与清理
triage 文档还记录了缺陷处置的流程性内容,值得作为团队协作参考:
- 来源证据:保留 issue #6567、PR #6560、PR #6516 的引用作为审计线索;
- 信息脱敏(Omitted or sanitized):Discord 凭证、token、认证头与原始凭证文件一律不落入文档;无关频道消息与 issue/PR JSON 被剔除;缺失的 Discord 可视化负载不臆造,由文本、issue、代码与运行时证据独立确立所有权;
- Triage cleanup:确认无残留的 Discord 浏览器标签、桌面窗口、服务器进程、端口、临时文件或登录会话,避免敏感会话泄漏。
总结:从一条 Discord 报告到一次体系级修复
这次缺陷的本质,是迁移端与校验端对"canonical 配置形态"的定义不同步:2026-08-reasoning-unification按照"统一reasoning、合并models链"的既定方向改写配置,而AgentOverrideConfigSchema、config-chain 与 doctor 仍停留在 legacy 键时代,导致同一份配置在"写入"与"读取"两个阶段被不同对待。修复路径由此清晰:让校验层与运行时接受迁移产物(agents.*.models、canonicalreasoning),同时把[opencode]的 legacy 键兼容告警收敛到与新形态一致。
对使用者而言,如果遇到"升级后配置看似迁移成功、但委派模型不生效"的问题,可先检查~/.omo/omo.jsonc是否已出现_migrations: ["2026-08-reasoning-unification"]标记(代表迁移已应用),再核对agents.*.models是否被运行时接受;对维护者而言,本文梳理的 reasoning-unification.ts、agent-overrides.ts、fallback-models.ts 与 fixture 对 四件套,是复现与回归验证该缺陷的完整入口。
【免费下载链接】oh-my-openagentOmO: Just type "mass ulw" keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考