资讯详情

oh-my-opencode-slim 文件操作后委托提醒钩子(post-file-tool-nudge)源码级解析

📅 2026/9/25 2:15:32 | 华诺云谱 👁 阅读
oh-my-opencode-slim 文件操作后委托提醒钩子(post-file-tool-nudge)源码级解析
人工智能AI AgentAgent 编排AI 技能【免费下载链接】oh-my-opencode-slimLean, fine tuned Opencode multi agent suite · Mix any models · Auto delegate tasks项目地址https://gitcode.com/gh_mirrors/oh/oh-my-opencode-slim点击查看免费下载本篇文章围绕 oh-my-opencode-slim 仓库中的src/hooks/post-file-tool-nudge/模块展开深入讲解它在 Agent 读完或写完文件后、向 orchestrator 下一轮消息注入委托提醒delegation reminder的实现原理。读完本文你将掌握该钩子的完整工作流tool.execute.after记录标记 →experimental.chat.messages.transform注入提醒、它与phase-reminder钩子的去重协作、缓存安全注入机制的底层约束以及如何通过shouldInject与coordinator选项做会话级过滤并能在自己的 OpenCode 插件中复刻这一模式。一、模块职责纠正读完文件就自己动手的反模式在多 Agent 协作架构中orchestrator主协调 Agent最常见的失效模式是先用 Read/Write 类文件工具读取源码、理解实现随后放弃委托直接自己动手修改文件违反了 OpenCode 架构的委托原则——应当把实现交给专门的子 Agent如 fixer、designer、explorer去完成。该模块的职责正是捕获这一反模式在文件类工具执行完成后排队一个委托提醒delegation reminder并在下一个符合条件的 orchestrator 轮次把它作为合成消息片段synthetic message part注入请求负载。提醒是临时ephemeral的——记录在工具执行时、注入在消息变换时、且只消费一次文件工具的原始输出与用户自己撰写的文本始终保持不被修改。二、钩子总体设计工厂模式 双生命周期处理器createPostFileToolNudgeHook()返回一个包含两个生命周期处理器的对象index.ts处理器作用阶段行为tool.execute.after工具执行完成后若是文件工具且有 sessionID调用coordinator.markPending(sessionID)记录待注入标记experimental.chat.messages.transform消息发送到 API 之前找到符合条件的 orchestrator 用户消息消费标记并追加合成提醒片段设计上有三个关键点条件注入Conditional Injection通过shouldInject?: (sessionID: string) boolean选项过滤会话只有返回true的会话才会真正收到提醒测试中通过它演示被拒绝的轮次不注入、待处理标记被保留的行为基于 Set 的工具名过滤Set-based Tool Filteringconst FILE_TOOLS new Set([Read, read, Write, write])O(1) 查找同时覆盖了工具名的大小写两种变体index.ts会话生命周期管理若传入coordinatorSessionLifecycle实例钩子会注册onSessionDeleted回调在会话被删除时调用clearSession(sid)清理待处理标记避免内存泄漏index.ts。SessionLifecycle原子化的待处理标记容器SessionLifecyclesession-lifecycle.ts内部维护一个#pendingSessionIdsSet提供四个方法markPending(sessionId)追加会话 ID 到待处理集合consumePending(sessionId)原子消费——has检查后立即delete保证同一标记只有一个调用者能拿到true从而避免多个变换钩子重复注入clearSession(sessionId)会话删除时清理标记onSessionDeleted(cb)/dispatchSessionDeleted(sessionId)注册与派发会话删除事件逐个调用清理回调并对异常做日志隔离。三、触发阶段tool.execute.after只负责记账tool.execute.after的实现极简index.tstool.execute.after: async ( input: { tool: string; sessionID?: string; callID?: string }, _output: unknown, ): Promisevoid { if (!FILE_TOOLS.has(input.tool) || !input.sessionID) return; coordinator?.markPending(input.sessionID); },两个静默退出的条件意味着非文件工具如bash、grep完全不参与记账工具调用缺少sessionID例如会话信息不可用时同样忽略因为后续注入需要以会话为维度定位消息。它不修改工具输出也不做任何消息变换只负责记账——这保证了工具输出链路始终干净test 断言hook[experimental.chat.system.transform]为undefined即该钩子不注册系统提示变换见 index.test.ts。四、注入阶段experimental.chat.messages.transform与合格消息判定消息变换处理器是核心逻辑所在index.ts执行顺序为无coordinator直接返回没有记账容器就无法消费从output.messages中取数组用findLatestUserMessage找到最后一条用户消息types.ts 中从数组尾部向前扫描调用getEligibleMessage判定合格性不合格则返回依次执行shouldInject(sessionID)过滤 →consumePending(sessionID)原子消费 →hasPhaseReminder去重检查 →appendTaggedSyntheticPart追加提醒。getEligibleMessage什么样的消息才是合格注入点getEligibleMessageindex.ts要求同时满足四个条件消息是role: user且带parts数组isUserMessageWithPartsmessage.info.sessionID存在message.info.agent orchestrator——只对 orchestrator 轮次注入子 Agentexplorer、fixer 等的轮次不注入消息中有一条非空的 text part且该 part 不是内部发起者片段isInternalInitiatorPart用于排除插件内部自动生成的提示消息。测试矩阵完整覆盖了这些反例index.test.ts错误会话、空消息数组、非 orchestrator 轮次、无 sessionID 的轮次、纯附件image轮次、内部发起者轮次——这些场景下钩子都不会消费待处理标记标记会保留到下一个合格轮次。五、与 phase-reminder 的协作元数据去重而非文本去重提醒文本本身复用了PHASE_REMINDER常量constants.ts它是一个用system-reminder包裹的工作流提示!IMPORTANT! Scheduler workflow: First choose the lightest workflow that fits the work. If direct execution is justified, complete it and verify proportionately. Otherwise: plan lanes/dependencies → dispatch background specialists → track task IDs → wait for hook-driven completion → reconcile terminal results → verify. !END!PHASE_REMINDER_METADATA_KEY定义为oh-my-opencode-slim.phaseReminderphase-reminder/index.ts注入的片段形如{ type: text, synthetic: true, text: PHASE_REMINDER, metadata: { oh-my-opencode-slim.phaseReminder: true }, }两个钩子共享同一元数据键因此hasPhaseReminderisTaggedPart(part, PHASE_REMINDER_METADATA_KEY)要求synthetic true且 metadata 命中用于检查消息中是否已存在提醒post-file-tool-nudge 必须先于 phase-reminder 运行源码注释明确说明 This transform must run before phase-reminder so this metadata deduplicates见 index.tsphase-reminder 看到已打标的片段后即跳过避免重复注入。组合测试验证了这一点index.test.ts文件操作后先跑 nudge 再跑 phase-reminder最终只有 1 条提醒而普通的新轮次 phase-reminder 仍能独立注入自己的提醒。六、缓存安全注入为什么追加在消息尾部注入不是简单地拼接文本而是通过appendTaggedSyntheticPartcache-safe-injection.ts完成。该模块是整个插件唯一被允许向出站请求负载追加内容的通道其设计约束来自 LLM Provider 的提示缓存机制Provider 缓存基于渲染后请求tools → system → messages的精确字节前缀匹配任何改写或重排早期对话内容的变换都会让缓存从第一个变更字节起全部失效后续每次请求都要重新支付完整输入成本与延迟因此appendTaggedSyntheticPart只允许在已有消息的尾部追加确定性内容——下一轮重跑变换时在同一位置复现相同字节缓存前缀保持稳定。由此也解释了本钩子的两个设计决策提醒作为独立的消息 part 追加而不是改写用户文本——用户自己写的 text part 保持原样测试断言message.parts[0].text仍是hello不注入时间戳或随机内容——提醒文本是会话稳定的纯函数输出满足缓存安全性质测试cache-safety.property.test.ts将postFileToolNudge列为必须通过缓存安全约束的变换之一见 cache-safety.property.test.ts。七、在插件中的注册与调用顺序钩子在插件入口统一创建与注册index.tspostFileToolNudge createPostFileToolNudgeHook({ shouldInject: shouldInjectOrchestratorReminder, coordinator: sessionLifecycle, });shouldInject复用 orchestrator 提醒的会话判定函数实现会话级过滤的统一入口coordinator传入全局sessionLifecycle实例与其他钩子共享同一待处理标记容器。它的两个处理器在插件生命周期中的位置index.tstool.execute.after阶段被wrapPostToolHook(post-file-tool-nudge, ...)包裹后注册错误隔离包装避免单个钩子异常拖垮整条工具链与其他后置钩子json-error-recovery、tool-loop-guard、task-session-manager串行执行experimental.chat.messages.transform阶段按固定顺序执行taskSessionManager→postFileToolNudge→phaseReminder→filterAvailableSkills→ 后台任务看板注入。这个顺序是刻意安排的任务会话管理器先修复会话映射nudge 先注入带元数据的提醒片段供 phase-reminder 去重最后阶段性的 volatile 内容后台任务看板才追加到负载尾部。钩子由 hooks/index.ts 统一对外导出export { createPostFileToolNudgeHook } from ./post-file-tool-nudge;。八、可复制的使用示例与选项说明结合源码中定义的PostFileToolNudgeOptionsindex.ts最小可用示例import { createPostFileToolNudgeHook } from ./src/hooks; import { SessionLifecycle } from ./src/hooks/session-lifecycle; const sessionLifecycle new SessionLifecycle((msg, meta) console.log(msg, meta), ); const hook createPostFileToolNudgeHook({ // 只有满足条件的会话才注入提醒 shouldInject: (sessionID) sessionID.includes(user-requested), // 传入 coordinator 以启用 pending 记账、原子消费与会话清理 coordinator: sessionLifecycle, }); // 插件初始化时注册两个处理器 hooks.register(tool.execute.after, hook[tool.execute.after]); hooks.register( experimental.chat.messages.transform, hook[experimental.chat.messages.transform], );选项说明选项类型作用缺省行为shouldInject(sessionID: string) boolean会话级过滤在消费标记之前求值不提供则所有会话都注入coordinatorSessionLifecycle提供待处理标记容器、原子消费与会话删除清理不提供则tool.execute.after不记账、transform 直接返回从源码可以推断的几条行为保证多文件操作折叠为一条提醒markPending对同一会话的多次 Read/Write 只维护一个 Set 条目consumePending原子消费后清空因此连续 3 次文件操作最终只注入 1 条提醒index.test.ts只注入最后一条用户消息findLatestUserMessage从尾部扫描中间即使夹着 assistant 消息也不受影响index.test.ts会话间隔离不同 sessionID 的 pending 标记互不干扰各自的轮次各自注入index.test.ts拒绝的轮次保留标记shouldInject返回false时标记未被消费之后合格轮次仍能正常注入index.test.ts会话删除即清理dispatchSessionDeleted触发clearSession删除后的轮次不再注入index.test.ts。九、反模式预防inspect → delegate → implement 的强化回路回到模块设计的初衷。该钩子防御的典型失败链是Agent 读完文件 → 理解了实现 → 忍不住亲自上手改代码而不是把实现委托给专门的子 Agent。其危害在于破坏 OpenCode 架构的职责划分——orchestrator 应当调度而非执行。本钩子通过一条记账-消费-注入回路强化预期工作流inspect用 Read 理解 → delegate把实现委托给专门 Agent → implement由子 Agent 完成每当文件工具执行完成后orchestrator 的下一轮请求负载尾部就会出现那条system-reminder调度提醒且不会污染用户文本、不破坏提示缓存、不重复出现。配合 phase-reminder 的常规注入两套机制共同保证文件操作触发的强提醒nudge与每轮例行的工作流提醒phase-reminder各司其职、互不重复。这一模式同样适用于任何需要行为纠正型提示的插件场景——用工具执行事件做记账、用消息变换做注入、用元数据做去重是既缓存安全又可精确控制作用域的通用实现路径。赞分享人工智能AI AgentAgent 编排AI 技能【免费下载链接】oh-my-opencode-slimLean, fine tuned Opencode multi agent suite · Mix any models · Auto delegate tasks项目地址https://gitcode.com/gh_mirrors/oh/oh-my-opencode-slim点击查看免费下载相关推荐Mac Mouse Fix 体验指南10美元鼠标如何超越苹果触控板Mac Mouse Fix 体验指南10美元鼠标如何超越苹果触控板 第一次把 Windows 上用了三年的鼠标插进 MacBook我就知道自己错了。滚轮一转人工智能AI AgentAgent 编排AI 技能oh-my-opencode高级用法自定义钩子和插件系统详解oh my opencode高级用法自定义钩子和插件系统详解 想要充分发挥 oh my opencode 的强大功能吗掌握自定义钩子和插件系统就是关键作为人工智能AI Agent代码智能体多智能体MCP ClientsAgent 编排3分钟掌握WindowResizer彻底解决Windows顽固窗口大小限制难题3分钟掌握WindowResizer彻底解决Windows顽固窗口大小限制难题 你是否曾遇到过这样的情况某个应用程序窗口无论如何拖拽边框都无法改变大小老旧人工智能AI AgentAgent 编排AI 技能上一篇开源项目推荐Tensorflow Speech Recognition下一篇如何在NPU上高效运行Dolphin-2.9.2-Phi-3-Medium硬件加速与性能调优完整指南 创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑