尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

oh-my-openagent prompt-async-gate 路径兼容性修复实录:从 `got undefined` 生产故障到双端回归验证

oh-my-openagent prompt-async-gate 路径兼容性修复实录:从 `got undefined` 生产故障到双端回归验证 oh-my-openagent prompt-async-gate 路径兼容性修复实录从got undefined生产故障到双端回归验证【免费下载链接】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/evidence/20260804-prompt-async-gate-undefined/README.md 及其配套证据 TESTIMONY-USER-SESSION.md完整还原一次真实生产会话驱动的缺陷定位与修复过程OpenCode 会话中edit/task工具持续抛出The path property must be of type string, got undefined根因是 prompt-async-gate 的路径类型兼容守卫只识别got object一种错误形态。读完本文你将掌握该门闩gate的双路径调度原理、错误形状守卫的判定逻辑、最小修复方式以及一套真实 harness 回归 单元测试覆盖 数据隔离证明的可复用 QA 方法。一、事件背景一次真实发生的got undefined故障1.1 故障现场2026 年 8 月 4 日oh-my-openagent 提交 PR #6583对应 Issue #6582提交938d35003修复 prompt-async-gate 的路径兼容性缺陷。这不是一个合成复现故障首次出现在真实 OpenCode 用户会话中会话 ID 为ses_038a0d10dffe7hHQyjy3ndVIgF运行于 2026-08-03累计 494 条消息涉及openproject-updater、Sisyphus - ultraworker、code、Prometheus - Plan Builder、Atlas - Plan Executor、compaction等多个 agent。实际驱动工具调用的模型栈为重负载9router5.6 terra故障 agent 为 oh-my-openagent 插件自带的Atlas运行在$start-work计划执行流程下。关键事实链故障工具edit与task每次调用都报同一错误正常工具read、write、bash、glob/grep、lsp_*、envsitter_*均不受影响失败事件时间戳ms epoch → UTC1785791986035edit作用于.../FakeAppSettings.cs返回{status:error, input:{filePath:..., oldString:...undefined}}1785792261985edit作用于/tmp/edit_probe.txt返回{status:error, error:The \path\ property must be of type string, got undefined}1785791698122task多参数返回同款got undefined错误1785795757766edit以最小参数作用于/tmp/edit_probe.txt仍报got undefined。最小复现的意义在于/tmp/edit_probe.txt文件真实存在edit依然失败说明故障发生在插件调度层而非编辑内容或 schema 校验本身。1.2 现场诊断手段会话内诊断故障 agent 自身的 bash 转录直接在已安装产物上搜索错误特征grep -n got undefined\|got object\|required error\|AggregateError\|must be of type string \ /home/allmaker/.cache/opencode/packages/oh-my-openagentlatest/node_modules/oh-my-openagent/dist/index.js # 11453: return message.includes(The path property must be of type string) message.includes(got object);在 oh-my-openagent 的 QA 方法论中这类指向精确安装产物 精确行号的 grep 是定位已发布 bug 的第一步证据链完整记录在 TESTIMONY-USER-SESSION.md。二、根因错误形状守卫与真实错误形态的错配2.1 已安装产物的守卫逻辑安装的插件版本为oh-my-openagentlatestv4.19.4打包产物dist/index.js:11453中守卫函数如下修复前状态function isObjectPathTypeError(error) { const message ...; return message.includes(The path property must be of type string) message.includes(got object); }该守卫驱动dispatchWithPathCompatibility的重试决策只有错误消息同时包含The path property must be of type string与got object时才会认为这是对象形态的session.promptAsync({path:{id}})需要兼容重试。2.2 真实错误形态got undefined但真实 harness 产生的是got undefined——即会话路径在调度时已经为 undefined。此时守卫判定失败dispatchWithPathCompatibility直接重抛原始错误原始错误冒泡到用户端表现为edit/task全部不可用。在 QA 主机上对同一已安装插件产物独立验证/home/allmaker/.cache/opencode/packages/oh-my-openagentlatest/node_modules/oh-my-openagent/dist/index.js:11453确实仍只包含message.includes(got object)——即修复前的 bug 形态。2.3 为什么真实会话证据不可替代测试文件 TESTIMONY-USER-SESSION.md 明确阐述了该证据的独特价值它是真实 harness 中的可观测行为真实模型5.6 terra、真实 agent来自同一插件的Atlas、真实会话path生命周期共同产出了got undefined这一变体——这是 SDK/服务端加插件路径的单元 mock 无法覆盖的组合它精确定位到安装产物与行号修复方案got object或got undefined正是让dispatchWithPathCompatibility重试这一真实生产错误形状的最小改动与 README 中的合成 harness 运行全新安装 真实 OpenCode CLI 干净驱动edit和单元测试一起构成复现 → 根因 → 修复 → 回归覆盖的完整闭环。2.4 证据来源provenance会话ses_038a0d10dffe7hHQyjy3ndVIgF的 part 行通过生产 OpenCode 数据库查询取得SELECT ... FROM part WHERE session_id? AND data LIKE %got undefined%数据库路径~/.local/share/opencode/opencode.db。最小自包含的失败用例edit /tmp/edit_probe.txt在文件存在的前提下仍以got undefined失败进一步证明故障位于插件调度层。三、修复后的源码剖析dispatchWithPathCompatibility的双路径调度修复后的核心实现在 packages/utils/src/prompt-async-gate.ts同时以镜像形式存在于 packages/omo-opencode/src/shared/prompt-async-gate.ts后者为生产插件侧的主实现。3.1 对象形态路径的判定type ObjectPathPromptInput { readonly path?: { readonly id?: string } | string readonly [key: string]: unknown } function hasObjectSessionPath(input: unknown): input is ObjectPathPromptInput { readonly path: { readonly id: string } } { return typeof input object input ! null path in input typeof input.path object input.path ! null id in input.path typeof input.path.id string }hasObjectSessionPath是一个类型守卫只有当input是对象、包含path键、path本身是对象、且path.id是字符串时才认为这是对象形态会话路径——即session.promptAsync({ path: { id: ses_... }, ... })这种调用形态。3.2 错误形状守卫最小修复点function isObjectPathTypeError(error: unknown): boolean { const message error instanceof Error ? error.message : typeof error string ? error : return message.includes(The path property must be of type string) (message.includes(got object) || message.includes(got undefined)) }修复内容就是最后一行的|| message.includes(got undefined)。该函数同时兼容Error实例与裸字符串错误并容忍空消息。3.3 兼容重试调度器async function dispatchWithPathCompatibilityTInput( dispatch: (dispatchInput: TInput) Promiseunknown, input: TInput, ): Promiseunknown { try { return await dispatch(input) } catch (error) { if (!isObjectPathTypeError(error) || !hasObjectSessionPath(input)) { throw error } const retryInput { ...input, path: input.path.id, } as TInput return dispatch(retryInput) } }逻辑十分清晰先以原始input调用底层dispatch若抛出错误且同时满足path 类型错误含got object或got undefined与输入为对象形态路径则将path从{ id: ses_... }摊平为字符串ses_...后重试一次其余任何错误非 path 类型错误、或输入本身不是对象形态路径一律原样重抛不掩盖真实失败。这一先试对象形态、失败再降级为字符串形态的机制兼容了 OpenCode SDK 不同版本对session.promptAsync/session.prompt入参的差异。3.4 在主调度流程中的接入点dispatchInternalPrompt是门闩对外暴露的唯一公共调度入口其内部按modeasync/sync分别绑定session.promptAsync/session.prompt并按queueBehaviordefer/ 队列 / 直接走三条路径三条路径最终都经过dispatchWithPathCompatibility包装defer模式dispatchAfterSessionIdle内dispatch: (dispatchInput) dispatchWithPathCompatibility(dispatch, dispatchInput)见 prompt-async-gate.ts队列模式enqueueInternalPrompt内dispatch: async (_dispatchInput) dispatchWithPathCompatibility(dispatch, input)直接模式同样经dispatchWithPathCompatibility包装L281-L297。这意味着无论调用方走哪条注入路径路径兼容重试都生效。另外当resolved.route live且发生发送前连接失败isPreSendConnectionFailure时还会回退到调用方传入的originalSession上的原始promptAsync/prompt再次尝试——这是与路径兼容正交的另一层容错。3.5 门闩的宏观定位ADR 背景prompt-async-gate 的完整设计见 docs/reference/prompt-async-gate-rfc.mdADRv4.2.0 引入其起因是 Issue #4012 的重复流式输出——OMO 的 13 内部 hook 调用方后台任务父唤醒、运行时回退重试、模型建议重试、团队邮箱实时投递、会话恢复续写、todo 续接、CLI run 恢复、Claude Code hook 注入、同步/后台子 agent prompt 等各自判断空闲/完成/错误边缘可能对同一会话重复注入 prompt。门闩以MapsessionID, Reservation的模块级保留表保证每个会话同一时刻只有一个注入赢家默认 post-dispatch hold 为DEFAULT_PROMPT_ASYNC_POST_DISPATCH_HOLD_MS 2_000msv4.2.3 起由 250 ms 提升 8 倍默认调度超时DEFAULT_PROMPT_DISPATCH_TIMEOUT_MS 30_000ms。原始session.prompt/session.promptAsync调用被 packages/omo-opencode/src/shared/prompt-async-route-audit.test.ts 的 TypeScript AST 审计禁止非正则可捕获解构、括号访问、可选链、别名/断言访问等绕过形态。四、回归测试双包镜像覆盖三种调度场景4.1 测试位置与断言修复配套的单元测试分别位于packages/utils/src/prompt-async-gate-path-compat.test.tspackages/omo-opencode/src/shared/prompt-async-gate-path-compat.test.ts生产侧镜像。测试桩createPathSensitivePrompt(errorKind)构造一个 mock prompt只要收到非字符串path就抛出TypeError: The path property must be of type string, got ${errorKind}并记录每次调用的入参。三个用例分别覆盖同步prompt拒绝对象形态路径期望dispatchInternalPrompt({ mode: sync, ... })两次调用底层 prompt第一次入参path: { id: ses_sync_path_compat }第二次降级为ses_sync_path_compat最终result.status dispatched异步promptAsync拒绝对象形态路径同上语义走mode: async异步promptAsync拒绝并报got undefined即本 PR 修复的真实生产错误形状期望同样重试成功。关键断言为calls.map((call) call.path)的两次调用形态[{ id }, id]直接验证先对象后字符串的降级顺序。4.2 运行结果README 记录的回归运行结果$ bun test src/prompt-async-gate-path-compat.test.ts # packages/utils 3 pass, 0 fail, 6 expect() calls $ bun test src/shared/prompt-async-gate-path-compat.test.ts # packages/omo-opencode 3 pass, 0 fail, 6 expect() calls两个包各 3 个用例、6 次expect()全部通过。新用例精确断言isObjectPathTypeError接受生产错误消息原文The path property must be of type string, got undefined。五、真实 harness 验证驱动真实 OpenCode CLI 走通 edit 工具5.1 环境与命令合成 harness 在全新插件构建上运行环境如下OpenCode1.18.11模型9router/LightQA 目录/tmp/oh-my-openagent真实 OpenCode 数据库会话数QA 前后25/25驱动命令真实安装的 OpenCode CLI、SDK/插件工具调度、edit工具路径全链路opencode run Use the edit tool to append a comment // QA: tool dispatch test to packages/utils/src/prompt-async-gate.ts. Confirm TOOL_QA_OK. -m 9router/Light --format json --dir /tmp/oh-my-openagent --print-logs5.2 观测到的结构化事件step_starttool_usetool: readstate.status: completedtool_usetool: editstate.status: completedfilediff.additions: 1filediff.deletions: 0最终textTOOL_QA_OK最终step_finishreason: stop。全程未出现任何path类型错误。QA 标记随后立即回滚避免污染跟踪源码git checkout packages/utils/src/prompt-async-gate.ts原始完整 JSON 输出捕获于/tmp/opencode-qa-output.log。六、隔离性/回归证明数据库会话数不变QA 运行前后分别查询真实 OpenCode 数据库的会话表$ opencode db SELECT count(*) AS cnt FROM session 25 $ opencode db SELECT count(*) AS cnt FROM session # after QA 25结论真实 DB 会话数保持25不变QA 使用仓库本地目录且标记回滚后未改动任何受跟踪源文件。这证明验证过程本身无副作用是数据隔离 工作区隔离双保险的完整示范。七、遗留事项Caveat仓库级构建虽然到达了生成的插件 bundle但在无关的build:senpi-plugin元数据生成阶段停止packages/omo-senpi/plugin/extensions/omo.js.meta.json缺失。README 明确说明这不影响真实安装的 OpenCode CLI 运行结果也不影响上述针对性单元测试。这一细节也提醒读者在评估 QA 证据时应区分被验证子系统与无关的构建阻断项。八、经验总结与复用建议错误形状守卫要覆盖所有真实生产变体isObjectPathTypeError从只认got object扩展为got object || got undefined是最小且充分的修复。任何基于错误消息字符串的兼容层都应从真实日志中收集全部变体后再收敛判定条件。合成 harness 与真实会话证据互补README合成、可控、可重复与 TESTIMONY真实、不可伪造、暴露 mock 覆盖不到的 SDK 路径各自回答不同问题发布前两者都应保留。验证必须可隔离QA 前后opencode db会话数不变 git checkout回滚标记是证明验证本身干净的模板。测试断言要验证调度顺序calls.map((call) call.path)断言[{ id }, id]比只断言最终成功更能防止未来有人破坏降级重试逻辑。参考路径速查证据文档.omo/evidence/20260804-prompt-async-gate-undefined/README.md、.omo/evidence/20260804-prompt-async-gate-undefined/TESTIMONY-USER-SESSION.md修复实现packages/utils/src/prompt-async-gate.tsisObjectPathTypeError/hasObjectSessionPath/dispatchWithPathCompatibility/dispatchInternalPrompt、packages/omo-opencode/src/shared/prompt-async-gate.ts回归测试packages/utils/src/prompt-async-gate-path-compat.test.ts、packages/omo-opencode/src/shared/prompt-async-gate-path-compat.test.ts设计 ADRdocs/reference/prompt-async-gate-rfc.md变更记录CHANGELOG.mdPR #6583 合并记录9ffcab37f【免费下载链接】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),仅供参考
返回列表