Pi 插件解剖|git-checkpoint.ts:只用 53 行,让 fork 恢复代码状态

一句话定位

每个 agent 回合前自动 git stash create 拍代码快照,/fork 时按历史点恢复代码的工作流增强插件。

作用(为什么存在)

它解决的是「会话历史」和「代码状态」错位的问题。 你在 pi 里 fork 一个会话到某个历史点继续对话,但工作区的代码还是最新状态------会话回到了过去,代码却没跟上。

git-checkpoint 用 53 行解决这个错位:fork 到哪个历史点,代码就恢复到哪个点的快照

注意一个容易混淆的点:它不是 「LLM 改坏了代码,撤销一下」的 undo 工具------触发者是 /fork 操作,语义是「新分支的代码起点对齐」,不是「回退开关」。

关键信息

内容
源码位置 examples/extensions/git-checkpoint.ts(53 行)
核心 API on("tool_result") / on("turn_start") / on("session_before_fork") / on("agent_end") + pi.exec + ctx.sessionManager.getLeafEntry() + ctx.ui.select
插件类型 事件钩子型(生命周期)

触发流程 / 数据流

csharp 复制代码
tool_result(工具结果回传)
  → getLeafEntry() → currentEntryId = 本轮树尖 entry      ← 记落点
turn_start(下一轮开始前)
  → git stash create → 快照 ref → checkpoints.set(entryId, ref)  ← 拍快照
session_before_fork(用户 /fork 时)
  → checkpoints.get(event.entryId) → 命中则确认恢复        ← 按坐标取快照
agent_end(整轮 run 结束)
  → checkpoints.clear()                                    ← 清理

核心设计:用 pi 会话树的 entryId 当 key,把「树上的位置」和「git 里的快照」绑定tool_result 记落点,turn_start 用上一个落点建快照,session_before_fork 按 fork 起点查快照。

架构 / 流程

flowchart TD subgraph S1[&#34;每轮交互&#34;] TR[&#34;tool_result 事件<br/>记录 leaf.id&#34;] --> ID[&#34;存 currentEntryId&#34;] TS[&#34;turn_start 事件<br/>git stash create&#34;] --> HAS{&#34;有 ref?&#34;} HAS -- 是 --> STORE[&#34;checkpoints.set(currentEntryId, ref)&#34;] HAS -- 否 --> SKIP[&#34;跳过本轮&#34;] end subgraph S2[&#34;fork 时&#34;] BF[&#34;session_before_fork 事件&#34;] --> GET{&#34;checkpoints.get(entryId)?&#34;} GET -- 无 ref --> RET[&#34;返回,不恢复&#34;] GET -- 有 ref --> UI{&#34;有 UI?&#34;} UI -- 否 --> RET2[&#34;不自动恢复&#34;] UI -- 是 --> CH[&#34;ui.select 询问恢复?&#34;] CH -- &#34;Yes&#34; --> APPLY[&#34;git stash apply ref<br/>notify 提示&#34;] CH -- &#34;No&#34; --> KEEP[&#34;保留当前代码&#34;] end subgraph S3[&#34;收尾&#34;] AE[&#34;agent_end 事件&#34;] --> CLEAR[&#34;checkpoints.clear&#34;] end

关键代码解读

typescript 复制代码
pi.on("turn_start", async () => {
  // ① 在 LLM 改代码之前,git stash create 拍快照
  //    create 只创建对象,不写 ref、不改工作区(区别于 git stash 入栈)
  const { stdout } = await pi.exec("git", ["stash", "create"]);
  const ref = stdout.trim();
  if (ref && currentEntryId) {          // ② 有脏文件 且 有落点,才存
    checkpoints.set(currentEntryId, ref);
  }
});

pi.on("session_before_fork", async (event, ctx) => {
  const ref = checkpoints.get(event.entryId);  // ③ 按 fork 起点坐标查快照
  if (!ref) return;                             // ④ 无快照 → 静默跳过
  if (!ctx.hasUI) return;                       // ⑤ 无 UI → 保守不恢复
  const choice = await ctx.ui.select("Restore code state?", [
    "Yes, restore code to that point",
    "No, keep current code",
  ]);
  if (choice?.startsWith("Yes")) {
    await pi.exec("git", ["stash", "apply", ref]);  // ⑥ 参数数组,防注入
    ctx.ui.notify("Code restored to checkpoint", "info");
  }
});

亮点 / 踩坑

亮点 1:事件是「时机」,不是只能「拦截」。 session_before_fork 返回 undefined(不拦 fork),只利用「fork 前」这一刻挂自己的逻辑。对比上期的 permission-gate:两者都是可拦截的 before 型事件 ------tool_call 返回 {block:true} 拦截,session_before_fork 返回 undefined 挂副逻辑。事件不同,但「拦截型事件既能拦也能挂逻辑」的用法模式相同

亮点 2:git stash create 不污染 git history。 它创建的是 dangling 对象(无 ref 指向),不会进 HEAD 历史、不会被 push。代价是对象躺在 .git 里等 gc 回收(默认 2 周 prune)------轻量且安全。

踩坑提示:检查点只覆盖「未提交(dirty)」变更。 已 commit 的代码不受管理;工作区干净时 stash create 返回空串,直接跳过。

边界(Limitations)

边界 表现
进程级 checkpoints Map 是纯内存,重启进程即失效
机器级 dangling 对象不推送,换机器 clone 后 fork 不恢复代码
覆盖范围 只对 dirty 变更生效
首次 turn 第一个 turn_start 时 currentEntryId 为 undefined,第一轮无检查点
粒度 currentEntryId 只在 tool_result 更新,纯对话轮不产生检查点

场景(Scenarios)

能恢复:同一进程内 fork 到「工具调用后的历史点」→ 命中 Map → 确认后恢复。

静默跳过 (不报错、不恢复):fork 到第一轮之前、干净工作区、无 UI(CI/headless)、agent_end 之后、重启/换机器。

可借鉴的模式

  1. 状态生命周期配对turn_start 建 → agent_end 清。内存状态必须有清理时机(类比 React useEffect setup/cleanup)。
  2. 事件即时机 :拦截型事件返回 undefined 也能挂副逻辑。
  3. 命令参数化防注入pi.exec("git", ["stash", "apply", ref]) 用数组而非字符串拼接。
  4. 快照-恢复模式:改前存快照 → 关联坐标 → 确认恢复 → 清理。可复用到任何「Agent 改工作区」的插件(自动备份/工作区保护)。
  5. 无 UI 保守降级!ctx.hasUI 时不擅自动作。

一句话总结

53 行把「会话树的坐标」和「git 快照」绑定起来,让 fork 具备代码恢复能力------快照-恢复模式的最小范本。

相关推荐
大家的林语冰1 小时前
✌️ 让 Rust 再次伟大,pnpm 12 抛弃 TypeScript,移植 Rust 原地起飞!
前端·javascript·node.js
安逸sgr2 小时前
循环神经网络 RNN、LSTM、GRU 是什么?为什么能处理序列?
人工智能·ai·大模型·agent·智能体
XPoet2 小时前
AI 编程工程化:实战——从 0 到 1 搭建 AI 编程工作流
前端·后端·ai编程
-今昭-3 小时前
Logstash 管理
java·服务器·前端
名字还没想好☜3 小时前
React 用自定义 useDebounce Hook 治好搜索框频繁请求:防抖、竞态取消与卸载清理
前端·javascript·react.js·react·hooks
杉氧3 小时前
Flutter 跨平台多端适配与 Android/iOS 一键自动化打包发布
android·前端·flutter
障碍的枫子3 小时前
xss和CSRF的攻击和防御
前端·xss·csrf
像颗糖3 小时前
AG-UI:把 Agent 与前端之间的“私有暗号”变成标准协议
python·agent·ai编程
用户921080262863 小时前
3. 给 Bubble 新增一个 Agent Variant
前端