- 人工智能
- AI Agent
- Agent 编排
- AI 技能
【免费下载链接】oh-my-opencode-slim
Lean, fine tuned Opencode multi agent suite · Mix any models · Auto delegate tasks
导读
在 OpenCode 多智能体插件体系里,grep与glob是 Agent 检索仓库信息最频繁调用的宿主工具,而一旦路径参数指向不存在或结构非法的位置,宿主只会抛出一条晦涩难懂的ripgrep execution failed错误,让排查无从下手。oh-my-opencode-slim 通过src/hooks/search-path-guard/这个tool.execute.before钩子,在搜索工具真正执行之前完成路径解析、文件系统校验与分级错误报告,把"宿主失败"提前转化为"可行动的提示"。读完本文,你将掌握该钩子的设计骨架、路径解析的宿主语义细节、错误分类策略,以及它如何在插件主流程中与其他钩子协同,并可通过仓库中的完整测试用例复现每一种拦截与放行行为。
钩子的职责边界:只服务 grep 与 glob
search-path-guard 的全部职责可以浓缩为一句话:在宿主工具执行前校验搜索路径,防止 ripgrep 执行失败。它挂载在tool.execute.before阶段,拦截范围被刻意收窄为grep与glob两类工具(源码见 index.ts 中的if (input.tool !== 'grep' && input.tool !== 'glob') return;),从而实现对搜索操作的高精度路径校验,同时避免干扰其他工具类别——例如read、bash、write等工具的路径参数完全不受影响,这一点在测试中也有明确覆盖。
钩子要解决的三类痛点非常具体:
- 路径存在性:解析后的路径必须真实存在,
ENOTDIR(路径某个组成部分不是目录)这类非法路径要给出可行动的报错,而不是让宿主把原始错误抛给调用者; - 错误可读性:缺失路径要上报"带指引"的帮助信息,替代晦涩的
ripgrep execution failed; - 宿主语义对齐:路径解析必须严格镜像宿主工具各自的语义——v1 的 grep 使用
path.join,v1 的 glob 使用path.resolve,而 v2 宿主对两者都使用path.resolve。
核心架构:工厂函数 + 可注入路径操作
钩子的整体结构是标准插件钩子工厂模式(codemap.md):
createSearchPathGuardHook(ctx):接收PluginInput(含ctx.directory与可选的ctx.hostFlavor),返回tool.execute.before处理器;resolveSearchPath():实现与宿主工具逐字节对齐的路径解析规则;- Path 参数提取:只从工具调用的
args.path中取值; - Stat 校验:通过
fs.statSync做文件系统检查,并按错误码分派处理; - 错误上报:依据错误类型给出带实操指引的提示。
值得强调的是"无外部配置"的设计取向:行为完全由宿主 flavor 探测驱动,不需要用户维护任何额外配置文件。两个可注入/可探测的入口分别是hostFlavor(可选参数,用于 v1/v2 宿主差异)与pathOperations(注入的路径工具,专为确定性测试设计)。
resolveSearchPath 的精确实现
路径解析是全部语义的核心,源码实现(index.ts)如下:
export function resolveSearchPath( tool: string, hostFlavor: string | undefined, directory: string | undefined, raw: string, pathOperations: PathOperations = path, ): string | null { if (!directory) return null; if (pathOperations.isAbsolute(raw)) return raw; if (hostFlavor === 'v2' || tool === 'glob') { return pathOperations.resolve(directory, raw); } return pathOperations.join(directory, raw); }几个关键设计决策:
- 无基准目录则保守放行:
ctx.directory不可用时返回null,上层据此"绝不拦截"(注释明确写着 "Without a resolution base, never block (conservative fallback)"),避免在缺少工作区上下文时误伤正常调用; - 绝对路径快速通道:
isAbsolute(raw)为真时原样返回,跳过拼接逻辑——这既是性能优化,也保证绝对路径不会因 join/resolve 的语义差异而被错误改写; - v1 与 v2 的差异被精确复刻:v1 grep 走
join、v1 glob 走resolve、v2 一律走resolve。这个区别在 Windows 上尤其重要,因为join与resolve对驱动器相对路径(如C:src)的处理结果完全不同。源码注释明确点出:"Without a resolution base, never block" 之外,还特别提到这是为了对齐宿主:"Mirror each host tool's resolution rule exactly... that distinction matters for relative paths on Windows, including drive-relative paths such asC:src."
错误处理策略:ENOENT 上报、ENOTDIR 阻断、其余透传
钩子的错误分类是它区别于"一刀切拦截"的关键(对应 codemap.md 的 Error Handling Strategy 与 Error Reporting Flow):
| 错误码 | 语义 | 钩子行为 | 错误消息示例 |
|---|---|---|---|
ENOENT | 路径(含断链的软链接)确实不存在 | 上报缺失,抛出带指引的错误 | Search path does not exist: {resolved} (from "{raw}"). ... Verify the target path, or list its parent directory to find the correct location. |
ENOTDIR | 路径的某个组成部分不是目录 | 阻断非法路径,抛出明确错误 | Search path is invalid: {resolved} (from "{raw}"). A path component is not a directory (ENOTDIR), so the {tool} search was blocked before ripgrep ran. ... |
| 其他(权限、I/O 等) | stat 失败但语义不属于上述两类 | 透传,保持原始含义,绝不误诊 | 不修改、不追加,仅记录日志 |
这个"三态分流"非常克制:ENOTDIR与ENOENT是 Agent 最容易踩中的两类错误(路径写错、把文件当目录用),因此值得在宿主执行前拦截并改写为可行动的提示;而权限问题(EACCES 等)可能是真实的宿主环境限制,透传比武断阻断更诚实。源码中的注释佐证了这一点:"Any other stat failure (permissions or I/O) keeps its original meaning: pass through and never misdiagnose."
钩子执行流程总览
原文档给出的完整流程,自上而下贯穿一次 grep/glob 调用的生命周期:
Tool execution (grep/glob) ↓ Tool execute before hook ↓ Validate tool type (grep/glob only) ↓ Extract path argument from tool args ↓ Resolve path using host-appropriate resolution ↓ Validate path exists and is accessible ├─ ENOENT → report missing path ├─ ENOTDIR → block invalid path └─ other error → pass through ↓ Proceed with original tool execution (success case)路径解析子流程(即上文的resolveSearchPath):
Input: raw path, directory, hostFlavor ↓ Check if absolute path (return as-is) ↓ Resolve using host-specific logic: - v2 or glob → path.resolve(directory, raw) - v1 grep → path.join(directory, raw) ↓ Return resolved path or null if directory unavailable主流程中的注册顺序:一次深思熟虑的编排
search-path-guard 并非孤立运行,它在插件主入口 src/index.ts 中的注册位置体现了严格的时序考量。createSearchPathGuardHook(ctx)在插件初始化时创建(src/index.ts),随后被组装进tool.execute.before复合处理器(src/index.ts):
'tool.execute.before': async (input, output) => { await applyPatch'tool.execute.before'; // Rewrite guessed non-existing absolute paths BEFORE the search guard: // the guard blocks grep/glob on missing paths, so running the rescue // after it would never see a rescuable path (#1143). await absolutePathRescue'tool.execute.before'; await searchPathGuard'tool.execute.before'; await taskSessionManagerHook'tool.execute.before'; // Record a call only after all rejecting before-hooks have accepted it. // In particular, search-path-guard can reject grep/glob before the host // emits tool.execute.after; running the loop guard first would leave a // pending call-key entry with no completion to consume it. await toolLoopGuard'tool.execute.before'; },这里透露出两个关键协作细节:
- absolute-path-rescue 必须先于 search-path-guard 执行:absolute-path-rescue 负责把 Agent 猜错的绝对路径重锚定到工作区真实位置,而 guard 会直接阻断不存在的路径。如果两者顺序颠倒,被 rescue 视为可挽救的路径在 guard 阶段就会被拦下,永远没有机会被改写(对应 issue #1143 的教训,注释明确说明了这一点);
- tool-loop-guard 必须后于 search-path-guard 执行:tool-loop-guard 负责记录工具调用键,而 search-path-guard 可能在宿主发出
tool.execute.after之前就拒绝 grep/glob。若 loop guard 先记录,就会留下一个没有完成事件来消费的悬空调用键(pending call-key),因此调用记录必须放在所有可能拒绝的 before-hook 接受之后。
此外,钩子整体通过 src/hooks/index.ts 的 barrel 导出(export { createSearchPathGuardHook } from './search-path-guard';)提供给主入口,与 apply-patch、absolute-path-rescue、tool-loop-guard 同属"工具拦截"这一钩子类别(见 hooks/codemap.md 的 Hook Categories 表)。
细节语义:字面量、空白与 Windows 驱动路径
源码注释与测试共同揭示了几个容易被忽略、但直接影响 Agent 行为的边界语义(index.ts):
- 宿主不做 trim:路径参数按原样使用,前后空白是有效字符。因此
' padded-missing-dir '会被当作真实路径名参与解析并最终因不存在而被阻断——这符合"镜像宿主"原则,因为宿主从不裁剪路径。测试同时验证了带空白前缀的目录名可以正常通过('spaced dir '目录存在时放行); - 字面量
'undefined'/'null'是普通相对路径:上游宿主把它们当作普通相对路径解析(schema 描述中的说明并不代表运行时特殊处理),因此钩子也按普通路径对待——指向不存在的'undefined'路径时同样会触发 ENOENT 阻断,测试用path.join(tempRoot, 'undefined')断言了这一行为; - 空字符串与缺失/非字符串参数一律放行:
args缺失、path缺失、path: 42、path: null、path: ''都不会被拦截。其中空字符串解析结果等价于实例目录本身,没有可阻断的对象; - ENOENT 涵盖断链软链接:源码注释指出 broken symlink 属于"genuine missing path",同样上报缺失而非透传,这与上游正在修复的行为保持一致。
测试验证:18 个场景覆盖全部语义分支
index.test.ts是理解钩子行为的另一份权威文档,它通过mkdtemp构造真实临时目录、注入 fakedirectory/hostFlavor,并借助path.win32注入模拟 Windows 语义(这正是pathOperations可注入设计的价值——没有 Windows CI 也能确定性验证 Windows 行为)。测试矩阵(index.test.ts)覆盖:
- 缺失路径阻断:绝对路径不存在(grep)、相对路径不存在于目录下(glob)均抛出
Search path does not exist且消息包含解析后的完整路径; - 有效路径放行:存在的绝对文件(grep/glob)、存在的相对目录(glob)均无异常;
- 目录缺失保守放行:
directory为假值时绝不阻断相对路径; - 工具过滤:
read、bash等非 grep/glob 工具即使路径缺失也直接忽略; - 参数形状容错:
undefined、空对象、非字符串、null、空字符串均忽略; - ENOTDIR 阻断:把普通文件当路径组成部分(
'reg-file.txt/child'),grep 与 glob 均抛出包含not a directory与ENOTDIR的可行动错误; - join/resolve 语义分离:同一相对路径下,grep 解析为
path.join(directory, raw)而 glob 解析为path.resolve(directory, raw),测试分别断言; - Windows 驱动相对路径:在 win32 平台上,
'C:missing-search-path'按各自语义解析;v2 的 grep 对'C:src'使用 win32resolve语义,v1 使用join语义(通过path.win32注入验证,即使跑在非 Windows CI 上)。
依赖、性能与可观测性
依赖清单
钩子的依赖极简(codemap.md):
- Node.js
fs.statSync:文件系统校验; node:path:isAbsolute/join/resolve路径解析(且类型上仅Pick这三个方法,约束了注入面的范围);- 结构化日志器:来自 src/utils/logger.ts 的
log(),记录验证决策与错误分类;该日志器在落盘前统一经过redactSecretsForLog脱敏,保证路径等上下文不会泄露凭据; - 插件 SDK:
PluginInput类型用于钩子注册与hostFlavor/directory上下文获取。
性能设计
- 早期过滤:只处理 grep/glob,其余工具零开销;
- 单次 stat:每次校验最多一次
statSync,无额外系统调用; - 绝对路径快速通道:跳过拼接、直接返回;
- 确定性可注入:
pathOperations让测试无需真实文件系统也能覆盖平台差异,生产中则使用 Node 原生路径实现,与宿主完全一致。
可观测性与无副作用承诺
- 日志追踪:阻断非法路径、透传 stat 错误、上报缺失路径时都会记录带上下文的日志条目(工具名、原始路径、解析路径、错误码);
- 错误消息即指引:两类错误文案都包含"验证目标路径 / 列出父目录寻找正确位置 / 检查每个父组件是否为目录"等可执行建议;
- 只拦截不改写:钩子对有效路径不做任何修改,唯一副作用是"提前阻断无效调用",因此对宿主行为是透明且安全的。
总结:从"宿主报错"到"钩子指引"的范式转变
search-path-guard 的价值不在于发明新的搜索能力,而在于把失败边界前移:与其让 Agent 面对ripgrep execution failed这类无法归因的宿主错误,不如在tool.execute.before阶段就用一次 stat 调用判断出"路径不存在(ENOENT)"还是"路径结构非法(ENOTDIR)",并分别给出可行动的修复指引;对权限/IO 等不属于路径问题的错误则保持克制、原样透传。它与 absolute-path-rescue(先改写猜测路径)、tool-loop-guard(后记录调用键)的注册顺序,以及 join/resolve 的宿主语义镜像,共同构成了一个严谨、可测试、低开销的工具调用前校验层——任何在 OpenCode 生态中构建插件的开发者,都可以把这套"错误分类 + 语义镜像 + 注入式测试"的方法论直接迁移到自己的钩子设计中。
- 人工智能
- AI Agent
- Agent 编排
- AI 技能
【免费下载链接】oh-my-opencode-slim
Lean, fine tuned Opencode multi agent suite · Mix any models · Auto delegate tasks
相关推荐
oh-my-opencode-slim 的 Absolute Path Rescue Hook:Agent 绝对路径猜测错误的自动纠正机制
oh my opencode slim 的 Absolute Path Rescue Hook:Agent 绝对路径猜测错误的自动纠正机制 导读 本篇文章围绕
人工智能AI AgentAgent 编排AI 技能oh-my-opencode-slim 的 OpenCode Go 预设(opencode-go preset):将 Pantheon 多智能体迁往 OpenCode Go 模型
oh my opencode slim 的 OpenCode Go 预设(opencode go preset):将 Pantheon 多智能体迁往 OpenC
人工智能AI AgentAgent 编排AI 技能mise bootstrap dotfiles exclude:用 glob 规则精准拦截 dotfile 捕获
mise bootstrap dotfiles exclude:用 glob 规则精准拦截 dotfile 捕获 mise bootstrap dotfiles
开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考