pi-candy-undo
pi 的文件级撤销/重做插件 — 对标 Claude Code /rewind:按用户消息回退对话与 agent 改过的文件,支持 cwd 外文件、redo、i18n
Package details
Install pi-candy-undo from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-candy-undo- Package
pi-candy-undo- Version
1.0.0- Published
- Sep 20, 2026
- Downloads
- 230/mo · 7/wk
- Author
- candymuj
- License
- MIT
- Types
- extension
- Size
- 138.2 KB
- Dependencies
- 2 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./extensions"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-candy-undo 🍬
pi 的文件级撤销/重做插件,对标 Claude Code 的 /rewind:按用户消息回退对话与 agent 改过的文件。
/undo 选择一条历史消息 → 恢复代码 / 恢复对话 / 两者 / 摘要后回退
/redo 撤销最近一次 /undo
特性
- 文件级快照:只跟踪 agent 通过
write/edit改过的文件;从未被 agent 改过的文件,手动修改、bash修改天然不受影响(已被 agent 改过的文件则会在回退时被覆盖,见 「已知边界」) - 支持工作区外文件:agent 用绝对路径改过的文件同样可回退(工具级跟踪,无 cwd 限制)
- 对话与代码联动:对话回退走 pi 原生
navigateTree(分叉 + 原 prompt 回填编辑器),代码回退独立可选 - 绝对恢复 + 幂等:恢复是"把文件设为目标时刻的内容",已处于目标状态时跳过写入;重复
/undo、/redo安全 - redo 支持:每次
/undo前自动记录当前状态,/redo可原样找回 - fork / clone 迁移:新会话自动继承撤销历史(复制元数据 + 硬链接备份)
- i18n:菜单与提示支持中文 / 英文
安装
npm 安装
pi install npm:pi-candy-undo
pi -e npm:pi-candy-undo # 临时试用,不写入配置
本地安装
git clone https://github.com/CandyMuj/pi-candy-extensions.git
cd pi-candy-extensions/pi-candy-undo # 进入插件目录
npm install --omit=dev # 本地安装不会自动装依赖(npm 安装会自动);只装运行时依赖
pi install . # 安装当前目录
pi -e . # 临时试用,不写入配置
或在 settings.json 中直接声明:
{ "extensions": ["/absolute/path/to/pi-candy-undo/extensions"] }
若当前已有会话在运行:
pi install不会热加载,需执行/reload(或重启 pi)。插件从加载那一刻开始记录,加载之前的 agent 改动无法回退。
使用
/undo
- 弹出消息列表(最新在前),每条显示该消息时刻的文件改动统计
- 选中一条消息后选择操作(菜单标题以竖线引用块显示所选消息与相对时间:
│ 「修复登录页…」/│ (5 分钟前)):
| 有文件改动 | 无文件改动 |
|---|---|
| 恢复代码和对话 | 仅恢复对话 |
| 仅恢复对话 | 摘要并回退对话 |
| 仅恢复代码 | 自定义摘要并回退对话 |
| 摘要并回退对话 | 算了 |
| 自定义摘要并回退对话 | |
| 算了 |
- 执行后把操作推入 redo 栈
/redo
重做最近一次 /undo(文件与/或对话)。以下情况会清空 redo 栈:发送新的用户消息。
让文件改动可被跟踪(推荐)
本插件只能跟踪 agent 通过内置 write / edit 工具做的修改。如果模型改用脚本或命令行改文件(Python / Node / sed / shell / bash / PowerShell / cmd / bat 等),这些改动不会被跟踪,也就无法回退。
推荐把下面这条规则加进全局规则(~/.pi/agent/AGENTS.md)或项目 AGENTS.md,让模型默认走内置编辑工具:
创建或修改文件必须使用内置的 `write` / `edit` 工具;严格禁止使用 Python、Node、`sed`、`echo >`、`cat <<EOF`、shell / bash / PowerShell / cmd / bat 等脚本或命令行方式创建或修改文件(这类改动无法被撤销插件跟踪)。
临时使用也可以走启动参数:pi --append-system-prompt "<同样的规则>"。
配置
唯一配置入口是 pi 的 settings.json(全局 ~/.pi/agent/settings.json + 项目 .pi/settings.json,项目配置仅在项目被信任时读取)。不读取任何环境变量。
两处配置按字段级深合并:项目只需写要覆盖的字段,未写字段沿用全局,均为未写时使用内置默认值;数组整体替换(不追加、不合并);null 视为未设置。另注意 exclude 的两层语义 —— 全局与项目之间是“替换”,而内置默认值与你的 exclude 之间是“并集”(由 excludeDefaults 控制)。
{
"candyUndo": {
"enabled": true,
"language": "zh",
"storageDir": "~/.pi/file-history",
"exclude": [],
"excludeDefaults": true,
"trackedTools": ["write", "edit"],
"maxFileSizeMB": 100,
"maxSnapshotsPerSession": 200,
"maxRedoStackSize": 50,
"cleanupPeriodDays": 30,
"pickerLimit": 100,
"log": false
}
}
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled |
boolean | true |
总开关;非交互模式(-p / json)下不跟踪 |
language |
"zh" | "en" |
"zh" |
菜单与提示语言 |
storageDir |
string | "~/.pi/file-history" |
存储根目录,必须位于工作区外,否则禁用 |
exclude |
string[] | [] |
排除 glob(gitignore 风格,支持 ! 否定) |
excludeDefaults |
boolean | true |
是否并入内置默认排除(.git/**、node_modules/**、dist/**、build/**、**/.env*、*.lock、coverage/**) |
trackedTools |
string[] | ["write","edit"] |
跟踪哪些工具的写入 |
maxFileSizeMB |
number | 100 |
单文件大小上限,0 = 不限制 |
maxSnapshotsPerSession |
number | 200 |
每会话快照上限,超出淘汰最旧(redo 引用的快照受保护) |
maxRedoStackSize |
number | 50 |
redo 栈容量,0 = 禁用 redo |
cleanupPeriodDays |
number | 30 |
过期会话目录清理天数(按目录 mtime),0 = 禁用 |
pickerLimit |
number | 100 |
消息列表最多显示条数 |
log |
boolean | false |
写入 <storageDir>/undo.log 调试日志 |
排除匹配:cwd 内文件匹配相对路径,cwd 外文件匹配绝对路径;无 / 的模式按文件名匹配任意层级。
工作原理
- 跟踪时机:
tool_call事件在工具执行前触发,此时把文件"编辑前内容"存为不可变备份(每个文件首次出现时记录为originals) - 快照时机:每个 agent 操作开始前(
before_agent_start)记录所有被跟踪文件的当前状态 - 回退语义:选择消息 M = 恢复到 M 回合开始之前。注意这是"目标时刻"语义:选中较早的消息会一次性撤销其后所有回合的改动(快照式回退的固有行为)
- 恢复算法:目标快照有记录 → 用该备份(记录为"不存在"则删除文件);无记录 → 用
originals(该文件首次被 agent 编辑前的状态) - 跳过项:符号链接、硬链接、超过大小上限的文件不会被跟踪或恢复
存储布局
~/.pi/file-history/
<sessionId>/
state.json # 快照元数据 + originals + redo 栈(原子写)
backups/<sha256(路径)[:16]>@v<N> # 不可变内容副本(保留文件 mode)
undo.log
- 备份不可变:只新增版本,从不修改或删除既有备份(回退只读)
- 会话隔离:每个会话独立目录;fork/clone 时复制元数据并硬链接备份,redo 栈不迁移
- 清理:启动时删除 mtime 超过
cleanupPeriodDays的会话目录;快照超限时淘汰最旧快照,并回收无引用的备份文件
开发
npm install
npm test # node --test(77+ 用例,覆盖配置/排除/存储/跟踪/恢复/命令/入口)
npm run typecheck # tsc --noEmit
设计文档见 docs/design.md,Claude Code /rewind 调研纪要见 docs/research-cc.md。
已知边界
- 从插件加载后开始跟踪:加载之前 agent 已改过的文件没有历史数据,无法回退(这正是"装完立刻
/undo只能回退对话"的原因) - 选择"加载之前"的旧消息作为目标时,已跟踪文件会回落到"首次被 agent 编辑前"的内容,即把该文件在此之后的 agent 改动一并回退;选择器会先展示改动范围再确认
- 快照全自动生成,无需手动打点(不存在 checkpoint 类命令)
- 只跟踪
write/edit(可通过trackedTools扩展);bash与脚本造成的修改无法跟踪(可用全局规则强制模型只使用内置工具,见上文 「让文件改动可被跟踪」) - 手动修改会被覆盖:若某文件曾被 agent 改过(已跟踪),你之后手动改了它,
/undo会把它覆盖回目标时刻的内容(无脏检查,与 CC 一致);确认菜单会先显示将改动的文件数与 ±行数,可改选"仅恢复对话" - 把文件加入
exclude只影响后续跟踪:之前已跟踪的文件仍会被回退 - 二进制文件按字节读写(安全),但 diff 统计按 1 行计
- 符号链接 / 硬链接路径不恢复也不删除(避免越界写入)
- 与工作区 git 完全无关,不产生任何 git 操作
各场景下文件如何变化的完整矩阵(哪些文件会被跟踪、回退时逐文件如何变化,以及手动修改 /
exclude规则 / 外部 git 操作等组合场景)见设计文档 §3.1 文件变化矩阵。