我又给 Pi Agent 做了个 zvec-grep 扩展
摘要
承接 dsh-zvec-grep 的开发,聊聊 pi-zvec-grep 如何在命令行 Agent 中自动建立和更新代码索引,以及它与 DSH 插件在运行机制上的差别。
上一篇《我给 DeepSeek Harness 做了个 zvec-grep 插件》发布以后,我继续把同一套代码语义搜索能力接入了另一个 Agent:Pi。
这次做出来的扩展叫 pi-zvec-grep。目标没有变:安装一次,以后进入任何项目启动 Pi,索引都在后台自动建立和更新,不需要再运行 zg install、zg index 或 zg serve。
不过,Pi 是一个可以从任意目录启动的命令行 TUI,DSH 则有服务端、Web 客户端和明确的 Session workspace。相同的索引能力放进这两个宿主,生命周期、状态展示和文件监听都需要重新适配。这篇我会把 pi-zvec-grep 的工作机制,以及它和 DSH 版本的主要差别写清楚。期望对大家有所帮助。
安装以后直接启动 Pi
pi-zvec-grep 已经发布到 npm。全局安装只需要一条命令:
pi install npm:@sugarforever/pi-zvec-grep
以后进入项目,照常启动 Pi:
cd /path/to/project
pi
如果希望把扩展作为项目配置分享给其他开发者,也可以写进仓库的 .pi/settings.json:
{
"packages": ["npm:@sugarforever/pi-zvec-grep"]
}
Pi 在受信任的项目中发现缺少这个 package 时,会自动安装。无论采用哪种方式,用户都不需要为每个项目单独初始化索引。
扩展源码在 sugarforever/yummy-pi-extensions,npm package 是 @sugarforever/pi-zvec-grep。代码、测试、README 和自动发布 workflow 都放在同一个目录中。
Pi 从当前目录得到 workspace
DSH 的插件可以从 session.header.cwd 取得 Session 对应的 workspace。Pi 的入口不一样:用户可能在仓库根目录启动 TUI,也可能在 packages/app 这样的子目录里启动。
pi-zvec-grep 会在 session_start 阶段读取 ctx.cwd,先转换成真实路径,再查询所在的 Git worktree。如果当前目录属于 Git 仓库,就以 worktree 根目录作为索引边界;如果不属于 Git,就使用 Pi 的启动目录。
Pi 启动目录
-> realpath
-> Git worktree 根目录,如果存在
-> 否则使用当前目录
-> createZvecGrep({ root })
因此,从 repo/packages/app 启动 Pi,索引的仍然是整个 repo。不同 Git worktree 的文件内容可能不同,所以它们分别维护自己的 .zvec-grep/ 索引。
和 DSH 版本一样,这里没有包装 zg CLI,也没有启动 MCP Server。扩展直接调用 zvec-grep 导出的 createZvecGrep()、index() 和 context()。文件扫描、ignore 规则、代码切分、embedding、BM25、向量检索与结果融合仍然由 zvec-grep 完成;Pi 扩展负责把这些能力接入自己的 Session 生命周期。
首次索引不会阻塞 TUI
workspace 确定以后,扩展立即创建一个 WorkspaceRuntime,在后台调用 index() 完成一次完整校准,同时启动文件 watcher。
这个过程不会阻塞 session_start。大型仓库第一次建立索引,或者本地 embedding 模型首次下载时,用户仍然可以继续使用 Pi。
此时如果 Agent 调用 zvec_search,工具也不会一直等待索引结束。它会立即返回结构化状态:
{
"status": "indexing",
"retryable": true,
"root": "/path/to/workspace",
"message": "The workspace index is not ready. Choose whether to use grep/read now or retry zvec_search later."
}
这不是一次工具错误。Agent 可以根据当前任务决定稍后重试,继续处理其他内容,或者改用精确搜索。异常情况下,即使索引任务迟迟没有完成,也不会把整个 Agent turn 卡在一次工具调用里。
扩展还会在每轮 Agent 开始前加入一小段工具分工说明:不知道准确措辞、需要理解架构或跨文件关系时,使用 zvec_search;已知函数名、配置项、字面量或正则时,继续使用 grep。搜索结果只向模型返回路径、行号、内容、匹配方式和分数,不会把底层完整的 diagnostics 一起放进上下文。
文件变化如何进入索引
Pi 运行期间,watcher 会监听 workspace 内的文件变化。短时间内连续出现的事件先进入 ChangeBatcher,相同路径会被去重;停止写入 750ms 后,整批路径通过 index({ changedPaths }) 交给 zvec-grep。持续写入超过 5 秒时也会强制提交一次,避免索引一直等不到安静窗口。
文件新增、修改或删除
-> watcher
-> 路径去重与 debounce
-> changedPaths 增量索引
Pi 每次启动 + 每小时定时任务
-> 完整校准
watcher 只负责及时发现变化,不是正确性的唯一来源。操作系统可能合并或丢弃文件事件,Linux 也可能遇到 inotify 数量限制。因此,扩展在每次启动时重新校准,并默认每小时再做一次完整校准。即使 Pi 关闭期间文件发生变化,下次启动也会修复索引。
macOS 和 Windows 支持递归 fs.watch 时,扩展只需要监听 workspace 根目录。Linux 则为允许进入索引的目录分别建立 watcher,同时跳过 .git、.zvec-grep、node_modules 和常见构建目录。watcher 报错后会请求完整校准,并尝试恢复监听。
命令行里的状态显示
DSH 版本有 Web 客户端,所以我为它实现了服务端状态接口和一个可以展开的状态胶囊。客户端需要定时读取状态,还要处理 Session 切换以后旧请求晚到的问题。
Pi 不需要这层结构。扩展可以直接调用 ctx.ui.setStatus(),把状态写进 TUI footer。WorkspaceRuntime 在状态变化时主动通知扩展,不需要浏览器轮询。
用户可能看到这些状态:
zvec: indexing- 正在建立首次索引。zvec: updating 4 files- watcher 发现变化,等待或正在增量更新。zvec: ready- 索引可用,watcher 正常运行。zvec: degraded- 现有索引仍可搜索,但 watcher 正在恢复。zvec: error- 索引操作失败。
这里特意区分了 degraded 和 error。watcher 暂时失效,并不代表磁盘上的现有索引也不可用。除了 footer,用户还可以运行 /zvec-status 查看 workspace 和当前状态;/zvec-reindex 可以手动要求完整校准,但正常使用不需要执行它。
Pi 与 DSH 的运行机制不同
两个版本共享同一个基本判断:索引自动建立,文件变化自动更新,耗时准备不阻塞搜索调用,语义搜索不替代精确 grep。真正不同的是宿主提供的边界。
DSH 服务端可能同时管理多个 workspace 和多个 Session。因此,dsh-zvec-grep 使用 workspace map,让相同目录下的 Session 共享 runtime,也允许多个目录同时保持活动。状态从服务端通过接口提供给 Web 客户端,再由状态胶囊显示。
Pi 的一个进程在同一时刻只有一个当前 Session runtime。执行 /new、/resume、/fork 或 /reload 时,扩展关闭旧 runtime,再按照新 Session 的 ctx.cwd 创建一个。这里不需要复制 DSH 的多 workspace map,一个进程对应一个 runtime 反而更符合 Pi 的使用方式。
DSH
多个 Session
-> workspace map
-> 每个 workspace 一套 runtime
-> 服务端状态接口
-> Web 状态胶囊
Pi
当前 Session
-> 一个 runtime
-> session_start / session_shutdown 管理生命周期
-> runtime 主动通知状态变化
-> TUI footer
文件监听也有所不同。DSH 插件运行在它固定的 Node 服务端环境中,watcher 实现可以相对集中。Pi 是直接分发给用户的命令行扩展,需要面对 macOS、Windows 和 Linux 的不同 fs.watch 行为,因此 Pi 版本增加了按平台选择递归监听或逐目录监听的逻辑。
关闭方式同样服从宿主生命周期。Pi 切换 Session 或退出时,runtime 会停止 watcher 和定时任务,通过 AbortController 取消正在执行的索引,并为关闭过程设置等待上限。debounce 队列里尚未提交的路径不会在退出时启动一轮新索引,它们会由下次启动的完整校准处理。
DSH 与 Pi 最终调用的是同一个 zvec-grep 引擎,但插件并不是把同一份代码换一个注册入口。宿主怎样定义 workspace,能否同时保留多个 Session,状态出现在哪里,关闭事件什么时候发生,这些条件共同决定了扩展应该怎样工作。
默认 embedding 仍然是本地的 local/potion-code-16m-v2。项目内容、模型推理和索引可以留在本机,索引保存在 workspace 的 .zvec-grep/ 目录。如果 watcher 暂时漏掉事件,下一次启动或定时校准会继续修复。
相关文章
2026年9月16日
fx:把 coding agent 做成一把瑞士军刀
fx 是一个用 Zig 写的原生 coding agent,二进制只有 6MB 出头。它能当终端 agent 用,也能作为 WASM 内核嵌进浏览器和 Node 应用。这篇文章拆解它的工具、权限、子 agent、上下文和嵌入设计,以及它和主流 coding agent 的差别。
2026年9月15日
一天多了 1,796 颗星,阿里把代码审查里的 Agent 关进了确定性流程
OpenCodeReview 在 GitHub Today 获得 +1,796 stars,并于同日发布 v1.12.1。它不是让 Agent 自由浏览代码,而是把文件选择、规则匹配、分组和评论校验交给确定性程序,再把真正需要判断的部分留给模型。
2026年9月12日
一天多了 545 颗星,PI-Desktop 想把 Coding Agent 从终端搬进可控的桌面
PI-Desktop 在 GitHub Today 拿到 +545 stars,当天又发布 v0.14.7-beta.1。它把多模型、技能、MCP、子 Agent 和权限确认收进本地桌面,但仍是一款需要审慎安装的早期预览软件。
最近一封 · Sample
OpenRig 冲上 Trending:把 Claude Code 和 Codex 编成一支队伍
“OpenRig 在 GitHub Trending Today 获得 622 个 stars 的窗口信号。它用可恢复的队列、席位与权限记录,把 Claude Code 和 Codex 放进同一套多 Agent 工程流程。”
—— william
来信
里面装的是
- 新文章 — 写完一篇就寄一封,不攒货
- 这周读到的、看到的、好用的工具
- 正在折腾的实验,附带翻车记录
约莫 1–2 周一封 · 随时退订
合作伙伴
CompeteMap — 英国及爱尔兰学生竞赛一站式搜索
数学、编程、科学、写作等各类竞赛信息汇总,支持按年龄和科目筛选,再也不错过报名截止日。