omp packages

pi-agent-edit0.2.14

An agent-built edit tool for Pi: lossless tolerant matching, actionable ambiguity recovery, stale-read guards, and recoverable file writes.

bjstan1979 Published 2 days ago MIT ~1.6K downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

pi-agent-edit

English | 简体中文

一个由 agent 自己开发、为 agent 使用而设计的 edit 工具。 在人类维护者的指导和审查下,AI agent 参与实现、测试与迭代。参与开发的 AI 模型(按维护者提供的名称列出):claude-opus-5.5、gemini-4-Argon、gpt-6.1-sol。这些名称是开发贡献说明,不表示模型提供商的官方背书。

为 agent 设计的 Pi edit 工具替代品。注册同名工具 edit 覆盖内置版本,并包装官方 read 以捕获真实读取版本(Pi 加载时会提示 shadow),参数完全向后兼容 { path, edits: [{ oldText, newText }] }。

解决内置 edit 的哪些问题

内置 edit 的不足 本扩展的做法
一旦某条 edit 走了模糊匹配,被触及行上的全角中文标点、弯引号会被静默改成 ASCII(earendil-works/pi#10655) 归一化只用于定位,替换时原文件中匹配区之外的字符一个都不改
模糊匹配不处理缩进差异、连续空白长度差异(#7836) 四级匹配:exact → 忽略行尾空白 → 忽略缩进(按 oldText 与文件逐行的缩进对应关系重建 newText 的缩进,统一使用文件自己的 tab/空格风格,不会产生混排)→ 宽松(空白折叠 + NFKC/引号/破折号/全角归一)
模型从报错或 read 结果复制时常误带 12│ 行号前缀 当常规匹配失败时自动识别并剥离 oldText/newText 的行号前缀(包括本工具 diff 里的 + 12│ 行;newText 中混有手写新行时只剥离带前缀的行)
匹配失败只回报"找不到",模型通常要重新 read 整个文件 返回最相似的候选区域(带行号和相似度,惰性缓存 + 提前剪枝加速)、逐行"file vs yours"差异;若 newText 在文件中唯一存在且不是 oldText 的一部分,提示"可能已应用"(同时仍给出候选区,避免误报掩盖真实问题)
oldText 出现多次只能报错,模型只能重新 read 报错里给出最多 5 处出现的带行号上下文,并在最多扩展 6 行、400 字符内尝试给出“唯一 oldText”(从文件原样截取,可直接复制使用);报错中最多列出 12 个匹配行号,可以立即作为 nearLine 重试(同一行多个匹配无法靠行号区分)——即使已读视图已过期,只要文件内容没变就接受;也支持 replaceAll 全部替换
只能按文本替换 新增 startLine/endLine 区间替换/删除、afterLine 插入(0 = 文件开头,精确处理空文件)
— 行号操作有陈旧保护:read 记录工具实际读到的字节版本,write 记录输入内容的版本;每个改动块都没改变行数时保留原有行号视图。发生行数变化后,仍可直接使用最新成功 edit 报告中实际展示的新文件行号,省略区域和跨越未展示区域的范围不授权。nearLine 在多处匹配中选择时也受保护。会话切换、分叉、树跳转和压缩会清空视图
结果反馈不利于继续编辑 返回紧凑的带新行号的 diff(含上下文),模型一般不需要再 read
— dryRun 预览;JSON 文件被改坏时给出警告
— 原子写入(临时文件 + fsync + rename,多硬链接文件自动原地写保护 inode),保留 BOM、CRLF、文件权限、属主/属组(无法恢复时改为原地写)、符号链接;只有目录不可写这类错误才回退原地写,磁盘满等错误直接失败、原文件不动;多条 edit 全有或全无(自动幂等去重完全相同的重复项)
— 并发修改检测:rename 前(原地写则在同一文件描述符上写入前)再次核对文件内容是否仍是本次编辑读取时的版本,被其他进程改过就拒绝写入,并要求重新读取
— 原地写可恢复:不用 O_TRUNC,先覆盖再截断,原始内容保留在内存并备份到系统临时目录;写到一半失败会自动写回原文;连回滚也失败时,报错给出备份文件路径
容错匹配会把未改动行的空白、引号一起改掉 非精确匹配时,newText 中与 oldText 完全相同的行视为未改动,原样保留文件中的那一行(字符串里的空白、Makefile 的 tab、对齐空格、弯引号都不动);只有真正改动的行才会重缩进
— Python、YAML、Makefile 等空白敏感文件:容错匹配、tab/空格混用、YAML 中的 tab 缩进、Makefile 中用空格开头的 recipe 行会在结果开头给出 WARNING(文本检查,不是语法分析)
— 非 UTF-8 文件(GBK、Latin-1 等)直接拒绝编辑,而不是把全文的非 ASCII 字节替换成乱码;混合换行文件中被改动的 CRLF 行保持 CRLF
部分模型会发 old_str/new_str/start_line、字符串形式的 edits prepareArguments 兼容所有参数的 camelCase 与 snake_case 别名;行号参数接受 "12" 这样的整数字符串,null 视为未设置

参数

{
  "path": "src/app.ts",
  "dryRun": false, // 可选
  "edits": [
    // 每项只能用一种定位方式:
    { "oldText": "foo(1)", "newText": "foo(2)" },                 // 文本替换
    { "oldText": "x = 1", "newText": "x = 2", "nearLine": 120 },  // 多处出现时取最接近 120 行的
    { "oldText": "oldName", "newText": "newName", "replaceAll": true }, // 全部替换
    { "startLine": 10, "endLine": 12, "newText": "..." },         // 替换 10–12 行(newText 为空则删除)
    { "afterLine": 0, "newText": "import x from 'x';" }           // 在第 0 行后(文件开头)插入
  ]
}

所有 edit 都基于调用前的原始文件定位,不能重叠。endLine 只能和 startLine 搭配,replaceAll/nearLine 只能用于 oldText,混用会报错而不是被静默忽略。同一调用里如果还有别的真实修改,oldText 与 newText 完全相同的条目会被跳过并在结果里注明;如果每一条都是空改,整次调用失败、不写文件。追加到文件末尾时 afterLine 等于当前行数,不是行数加一。path 支持模型常写的 @src/app.ts 形式(同名的字面 @ 文件优先)。

新建文件与连续行号编辑

  • 通过成功的 write 创建文件后,不用额外 read 就能按行编辑。通过 shell 创建的文件没有工具视图,需先 read;上下文压缩或切换后也一样。如果宿主的 write + then_run 已写入文件,但后续命令失败导致整体工具报错,也不会建立视图,需先 read,不能仅凭文件存在推断写入已成功完成。
  • read 的 offset/limit 可建立当前版本视图:hash 来自底层实际读取的 Buffer,不是截取后的显示文本,也不事后重读磁盘。这是版本校验,不代表每一行都已显示,仍需确认目标坐标。
  • 编辑增删行后,只认最新成功报告实际打印的新行号:删除行的旧行号、省略的改动块、截断的中间区域都不能作为授权;dryRun 不建立新视图。
  • 外部内容变化立即使上述坐标失效,即使文件大小和修改时间都被保留。此时重新 read,或改用 oldText。

安装

已发布到 npm,并收录于 Pi 包画廊:

pi install npm:pi-agent-edit
# 或仅在当前项目安装
pi install -l npm:pi-agent-edit
# 或仅试用一次
pi -e npm:pi-agent-edit

本地源码也可使用 pi install ./pi-agent-edit 安装。然后重新启动 Pi 或执行 /reload。

本扩展有意覆盖 edit 并包装官方 read 工具;加载时的 shadow 提示是预期行为。不要同时加载另一套 edit 或 read 替代工具。只保留一个安装来源,避免同时从本地扩展目录和 npm 重复加载。卸载使用 pi remove npm:pi-agent-edit,然后重启或 /reload。

请使用当前版本的 Pi(@earendil-works/pi-coding-agent)。包测试对着 1.3.0 的 peer 运行;已加载的工具在 Pi 1.5.2 上实际用过。替换内置 edit 的能力以及具体参数格式取决于宿主版本,不保证兼容所有旧版 Pi 或第三方分支。

测试

engine.ts 和 writer.ts 不依赖 Pi,可独立测试:

npm i -D tsx
npx tsx --test test/engine.test.ts test/regression.engine.test.ts test/hardening.engine.test.ts test/writer.test.ts

test/*extension.test.ts 需要安装 Pi(peer 依赖),且 Pi 要求 Node >= 22.19.0(它用了 node:fs 的 globSync),Node 20 上这些用例连模块都加载不了。装好后 npm test 跑全部测试,npm run typecheck 做类型检查(已内置本地 typescript devDependency,不依赖全局 tsc);两者在 CI(.github/workflows/pi-agent-edit.yml,Node 22/24)中对 pi-agent-edit/** 的改动自动运行。regression.*.test.ts 是 0.2.1 修复的回归用例,hardening.*.test.ts 和 writer.test.ts 是 0.2.2/0.2.3 的用例(writer.test.ts 用故障注入覆盖 rename/fsync 失败、原地写半途失败与回滚失败、并发修改)。属主相关的几条需要 root 权限,否则自动跳过。

许可证

MIT。本扩展在本地运行,不需要独立的 API key 或模型服务;AI 模型参与开发,而不是在每次编辑时由扩展调用。

仍然存在的限制

  • 检查与 rename 之间仍有极短的竞态窗口。 POSIX 没有“比较后再 rename”的原子操作;本扩展把检查放在 rename 之前的最后一步,窗口是微秒级,但不为零。它也不是文件锁:不会阻止别的编辑器之后覆盖本次修改。
  • 原地写不是原子的,只是可恢复的。 进程在写入中途被强杀(kill -9、断电)时没有机会回滚,文件可能是新旧内容的混合;备份文件只在本次调用期间存在。正常的 I/O 错误会被回滚。
  • 网络盘和特殊挂载无法由本地测试证明。 NFS/SMB 上 rename、fsync 的语义取决于服务端和挂载参数;对目录 fsync 不被支持时会静默跳过。
  • 空白检查是文本启发式,不是语法分析。 关键修改仍应看 diff、跑编译或测试。