oh-my-pi 编码代理 glob 工具深度指南:模式匹配、内部 URL 与路径发现原理
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
本文围绕 oh-my-pi(⌥ Coding agent with the IDE wired in)编码代理内置的glob工具展开,以工具提示词文档 packages/coding-agent/src/prompts/tools/glob.md 为核心骨架,结合 GlobTool 实现、路径解析工具、原生 glob 编译实现 与测试用例,系统讲解glob工具的完整能力:如何用 glob 模式、文件、目录与内部 URL 做快速路径匹配,如何控制 gitignore/hidden 过滤,以及输出排序、超时降级、根目录保护等底层行为。读完本文,你将掌握在 oh-my-pi 会话中高效检索项目文件的全部参数用法与实现原理。
一、工具定位:agent 的文件系统"眼睛"
在 oh-my-pi 的编码代理工具集中,glob与grep、read、bash共同构成文件系统探索的基本盘。glob 负责"找到匹配路径",其提示词只有一句话点明职责:
Globs files, directories, and path-backed internal URLs with fast pattern matching.
对应到实现,GlobTool类(packages/coding-agent/src/tools/glob.ts)声明了三个关键属性:
approval = "read":属于只读操作,不触发写权限审批;loadMode = "essential":核心工具,随会话基础加载;strict = true:严格校验入参,非法输入直接报错。
它同时也是"find"类工具的前端别名之一——在 builtin-names.ts 中["find", "glob"]被归为一组,表明历史上 find 的职责已并入 glob。
二、path 参数:一个参数覆盖四种目标
提示词文档明确了path参数的取值范畴:glob 模式、文件、目录或 path-backed 内部 URL。Schema 定义见 glob.ts:
const findSchema = type({ "path?": type("string").describe( 'glob, file, or directory to search — a single path or a semicolon-delimited list ("src/**/*.ts; test/**/*.ts"). Omitted -> searches the workspace root (".")', ), "hidden?": type("boolean").describe("include hidden files"), "gitignore?": type("boolean").describe("respect gitignore"), "limit?": type("number").describe("max results"), });- 省略
path时等价于传入".",即搜索工作区根目录(见 glob.ts 的effectivePaths默认值逻辑); - 传入普通目录时,工具会递归匹配其下所有文件(
parseFindPattern将无 glob 字符的目录解析为basePath + "**/*"); - 传入单个文件路径时,通过
stat判断isFile()直接返回该文件,不做目录扫描(glob.ts)。
2.1 分号分隔多目标
提示词文档给出的关键语法是分号分隔:src/**/*.ts; test/**/*.ts。实现中的解析远比这丰富——path-utils.ts 的路径拆分器支持三种顶层分隔符:逗号,、分号;与空白,且具备花括号深度感知:{a,b}内部的逗号不会被误判为路径分隔符。因此以下写法均合法:
src/**/*.ts; test/**/*.ts // 分号分隔 src/**/*.ts, test/**/*.ts // 逗号分隔 src/**/*.ts test/**/*.ts // 空白分隔 *.{ts,tsx} // 花括号展开,内部逗号不参与拆分另外toPathList还支持 JSON 编码的字符串数组'["a.ts","b.ts"]'(path-utils.ts),保证模型输出数组形式入参时也能被正确解析。
2.2 多目标的部分缺失容忍
当一次调用传入多个路径、其中某些路径的基目录在磁盘上已不存在时,工具不会整体失败:它会跳过缺失项并继续扫描剩余路径,仅在所有目标全部缺失时才抛出Path not found错误(glob.ts)。被跳过的路径会以Skipped missing paths: ...的形式作为非致命警告附加在结果文本与 TUI 渲染中(GlobToolDetails.missingPaths字段)。这是刻意设计的容错:多路径调用中一个目录被删除不应拖垮整个检索。
2.3 模式解析规则
parseFindPattern(path-utils.ts)负责把用户输入拆成"搜索基目录 + glob 模式"两部分:
src/app/**/*.tsx → { basePath: "src/app", globPattern: "**/*.tsx", hasGlob: true } *.ts → { basePath: ".", globPattern: "**/*.ts", hasGlob: true } **/*.json → { basePath: ".", globPattern: "**/*.json", hasGlob: true } /abs/path/**/*.ts → { basePath: "/abs/path", globPattern: "**/*.ts", hasGlob: true } src/app → { basePath: "src/app", globPattern: "**/*", hasGlob: false }注意*.ts会被自动补成**/*.ts实现递归匹配——这意味着裸 glob 默认就是全子树递归。而显式给出目录前缀的src/*.ts则只在该目录层级匹配,不会穿透子目录。
三、gitignore 与 hidden:默认全开,按需关闭
提示词文档给出了两条关键默认值:
| 参数 | 默认值 | 作用 |
|---|---|---|
gitignore | true | 尊重.gitignore,跳过被忽略文件 |
hidden | true | 包含隐藏文件(点文件) |
对应实现见 glob.ts:
const includeHidden = hidden ?? true; const useGitignore = gitignore ?? true;由此可以推导出三种典型组合:
- 默认行为:
gitignore: true+hidden: true—— 跳过 git 忽略文件,但依然能搜到.env这类点文件(因为 hidden 默认开启); - 搜索被 git 忽略的文件(日志、构建产物):
{ path: ".env*", gitignore: false }—— 这是提示词文档与内置示例共同推荐的用法; - 搜索被 git 忽略的点文件:
{ path: ".cache", gitignore: false }—— 提示词文档特别强调"pair it withgitignore: falsefor ignored dotfiles",因为仅关 hidden 是不够的,.gitignore 规则可能同时覆盖这些文件。
内置示例(glob.ts)还包含一个目录匹配示范:{ path: "**/tests" }返回所有名为 tests 的目录(目录以/结尾标识)。
四、内部 URL:memory:// 与 ssh:// 的特殊规则
提示词文档关于内部 URL 的规则是本文最容易被忽略、也最重要的部分:
memory://glob 模式受支持:可以直接用memory://前缀配合 glob 通配符检索记忆资源;ssh://没有本地路径:glob 无法操作,必须改用read工具;- 其他内部 URL 只接受精确路径:带 glob 字符会直接报错。
这些规则在 glob.ts 中有着严格的类型化实现:
InternalUrlRouter.instance().canHandle(rawPattern)识别内部 URL 前缀;isSshUrl(rawPattern)命中时抛出明确的引导错误:
find cannot operate on a remote ssh:// path: <path>. ssh:// has no local file to glob; use `read <path>` to list or inspect the remote path.- 非 memory 内部 URL 若包含 glob 字符(
*?[{),抛出Glob patterns are not supported for internal URLs; memory://模式通过splitMemoryGlobPattern拆分为 baseUrl 与 glob 部分,经internalRouter.resolve(..., { pathOnly: true })解析出真实 backing 文件路径后,再将 glob 拼接上去执行扫描。通配符在拼接前会被转义(replace(/[*?[{]/g, "[$&]")),避免影响 base 部分。
五、输出格式:最新优先、按目录分组
提示词文档定义了输出契约:
Matches are newest-first and grouped by directory; directories end in
/.
实现上由三条链路共同保证:
- 原生扫描按 mtime 排序:调用
natives.glob时传入sortByMtime: true(glob.ts),让最近修改的文件排在最前; - 多根合并后再全局重排:每个目标的扫描并发执行、各自按 mtime 排名并截断到 limit,最后在 JS 层去重、按 mtime 降序合并(glob.ts),确保跨多个搜索根的整体 top-N 正确;
- 目录以
/结尾:formatMatchPath根据原生返回的FileType.Dir或路径自带尾斜杠决定是否补/(glob.ts),分组渲染由formatGroupedPaths完成。
输出对模型表现为普通文本(No files found matching pattern表示零命中);对 TUI 用户则由globToolRenderer渲染为内联状态行 + 文件树列表,支持折叠/展开与 OSC 8 超链接跳转(见 glob.ts)。
六、limit 与超时:宁可给部分结果,不抛空错误
limit参数有硬性上限:DEFAULT_LIMIT = 200、MAX_LIMIT = 200(glob.ts),有效值取Math.min(200, Math.max(1, floor(requestedLimit)))。非正数 limit 会抛出Limit must be a positive number。
超时方面,默认扫描超时为5000ms(DEFAULT_GLOB_TIMEOUT_MS,可在GlobToolOptions.timeoutMs中覆盖,仅测试用)。超时的行为设计非常讲究(glob.ts):
- 绝不把超时当作"无匹配"的证据:空结果 + 超时会附加
glob timed out after Ns before finding any matches — the scan is incomplete, NOT proof of absence,避免误导后续决策; - 有部分匹配时返回部分结果:把流式累积的匹配按 mtime 排序后返回,并注明
returning N partial matches — results are incomplete; - 提示正确补救方向:文档和实现都强调"walk cost 取决于目录树规模而非模式宽度",因此建议
sub/dir/*.ext而非在大根目录上写*.ext——这与 walk_depth_bound 的深度裁剪机制互为表里。
七、底层原理:原生 glob 的编译与快速路径
GlobTool默认调用natives.glob(@oh-my-pi/pi-natives的原生绑定),其匹配器位于 crates/pi-natives/src/glob_util.rs。三处关键设计:
- 模式规范化(build_glob_pattern):统一
/分隔符、按需补**/前缀、自动闭合未闭合的{花括号组; - 字面分隔符语义:编译时使用
literal_separator(true),保证*、?、[...]永不跨越/,避免模式意外匹配到深层子目录; - 快速路径分流(CompiledGlob::is_match):
GlobFastPath枚举覆盖All(**全匹配)、RootOnly(仅根层)、Extension/RootExtension(扩展名快判)、Basename/RootBasename(文件名快判)等常见形态,只有无法归类的模式才落到通用GlobSet匹配,兼顾速度与通用性。
另外GlobOperations接口(glob.ts)允许把exists/stat/glob三个操作注入为远程实现,注释明确说明这是为 SSH 等远程文件系统预留的扩展点;注入自定义操作时,工具会传入固定的忽略列表["**/node_modules/**", "**/.git/**"](glob.ts)。
八、安全护栏与取消语义
- 禁止根目录搜索:
path: "/"或"//"会直接抛出Searching from root directory '/' is not allowed(glob.ts,测试见 glob.test.ts),防止全盘扫描拖垮会话; - 准备阶段与扫描阶段分离取消:stat 准备阶段使用独立 AbortController,原生扫描启动后才把真实调用方信号接管过去(glob.ts),保证取消及时且不泄漏监听器;
- 超时与取消的竞态处理:测试明确验证了"超时后必须等原生扫描真正停止才结束调用""中止时必须等待所有并发扫描都 settle 后才抛出
ToolAbortError"两条不变量(glob.test.ts),避免悬挂的原生任务污染后续会话。
九、实战速查
| 场景 | 调用示例 |
|---|---|
| 递归找所有 TS 文件 | { "path": "src/**/*.ts" } |
| 多目录同时检索 | { "path": "src/**/*.ts; test/**/*.ts" } |
| 找被 git 忽略的 .env 文件 | { "path": ".env*", "gitignore": false } |
| 找被忽略的点文件目录 | { "path": ".cache", "gitignore": false } |
| 找同名目录 | { "path": "**/tests" } |
| 精确文件命中 | { "path": "src/main.ts" }(返回单个文件) |
| 限制返回数量 | { "path": "**/*.json", "limit": 50 } |
实践要点:裸 glob 默认递归全树且按 mtime 排序;大目录下优先把模式写深(sub/dir/*.ext)以避免超时;ssh://路径请改用read;搜索node_modules、构建产物等被忽略内容必须显式gitignore: false。
十、延伸阅读
- 工具提示词原文:packages/coding-agent/src/prompts/tools/glob.md
- 工具完整实现:packages/coding-agent/src/tools/glob.ts
- 模式与路径解析:packages/coding-agent/src/tools/path-utils.ts
- 原生匹配器:crates/pi-natives/src/glob_util.rs
- 行为测试:packages/coding-agent/test/tools/glob.test.ts
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考