Pi 插件解剖|todo.ts:297 行,拼出工具+命令+状态的完整插件

一句话定位

给 pi 加一个待办列表:LLM 通过 todo 工具增删改查,用户通过 /todos 命令查看------297 行,完整插件的四件套范本。

作用(为什么存在)

前两期拆的都是「单点能力」插件(拦截、快照),这期是完整插件:工具 + 命令 + 状态持久化 + 自定义渲染,四件套全齐。

它最值钱的地方是状态怎么存 :把状态写进工具结果的 details 字段,持久化到会话历史。这带来一个白送的能力------fork 后状态自动对齐分支,重启也不丢。你不需要自己维护任何文件或存储。

关键信息

内容
源码位置 examples/extensions/todo.ts(297 行)
核心 API registerTool + registerCommand + ctx.ui.custom + ctx.sessionManager.getBranch() + renderCall/renderResult
插件类型 工具 + 命令 + 状态

触发流程 / 数据流

css 复制代码
LLM 调 todo 工具(add/toggle/clear/list)
  → execute 返回 content(给 LLM)+ details(完整状态快照:todos + nextId)
  → toolResult 消息 append 进 session.jsonl
  → session_start / session_tree 触发 reconstructState
      → getBranch() 遍历当前分支 → 筛 toolResult && toolName==="todo"
      → 最后一条 details 胜出 → 内存重建
用户 /todos → ctx.ui.custom 渲染 TodoListComponent

架构 / 流程

关键代码解读

typescript 复制代码
// 状态重建:从当前分支历史重放(session-manager 的 getBranch 返回分支 entries)
const reconstructState = (ctx) => {
  todos = [];          // ① 清空内存(可能已过期,不能信任)
  nextId = 1;
  for (const entry of ctx.sessionManager.getBranch()) {  // ② 遍历当前分支
    if (entry.type !== "message") continue;
    const msg = entry.message;
    if (msg.role !== "toolResult" || msg.toolName !== "todo") continue;  // ③ 只筛本工具
    const details = msg.details;
    if (details) { todos = details.todos; nextId = details.nextId; }     // ④ 最后一条胜出
  }
};

// 工具返回:状态在 details 里(完整快照)
async execute(_toolCallId, params, ...) {
  // add 分支
  const newTodo = { id: nextId++, text: params.text, done: false };
  todos.push(newTodo);
  return {
    content: [{ type: "text", text: `Added todo #${newTodo.id}: ${newTodo.text}` }],
    details: { action: "add", todos: [...todos], nextId },  // ← 完整状态快照
  };
}

亮点 / 踩坑

亮点 1:状态持久化到会话历史,分支对齐是白送的。 状态跟着工具调用走(每次返回完整快照),getBranch() 只返回当前分支的 entries,所以 fork 后重建出的就是那个分支的状态------不用自己处理"分支里状态该是什么"。

亮点 2:渲染分层。 content 给 LLM(纯文本语义)、details 存结构化数据、renderCall/renderResult 管 TUI 展示。展示层读 result.details 而不是解析 content 文本。

踩坑提示:跨机器会丢。 session 文件在 ~/.pi/agent/sessions/(全局目录),不在项目仓库------git 不跟踪、常规迁移不带。换机器后扫不到任何 todo 结果,状态变空。

边界(Limitations)

维度 表现
fork/分支 ✅ 状态在会话历史,分支自动对齐
进程重启 ✅ 落盘 session,启动重建
跨机器 ❌ session 文件在全局目录,git 不跟踪、迁移不带
状态粒度 每次完整快照、最后一条胜出;清空重扫防状态穿越

场景(Scenarios)

  • 能恢复:同机重启 / fork 到历史点 → 重放当前分支 → 重建对应状态
  • 静默丢失:换机器(session 目录空)/ 删 session 文件 → 重扫不到 → 空列表

可借鉴的模式

  1. 状态持久化到会话历史 :借用工具结果 details 把状态写进会话历史,状态跟会话走、分支对齐白送,免去单独维护存储。
  2. 完整快照而非增量 :每次返回整份 todos + nextId,让历史可重放------最后一份就是状态。增量记录做不到从历史重建。
  3. 清空重扫而非增量累加:内存可能过期(fork 切换后残留旧分支状态),直接丢弃内存、以历史重放为准。
  4. 双入口分工:工具管增删改查(唯一写入方),命令只读查看,避免多写入方竞争。
  5. 渲染分层content 给 LLM、details 结构化、renderCall/renderResult 管 TUI,展示层直接拿结构化数据。

一句话总结

297 行告诉你完整插件长什么样:工具给 LLM、命令给用户、状态持久化进会话历史------分支对齐和重启恢复都是白送的。

相关推荐
12.=0.1 小时前
【REVIEW_C】【持续更新】
服务器·前端·javascript
明月_清风1 小时前
开发者写PPT自救指南:4类对接场景,把技术讲清楚
前端·后端·面试
探索前端1 小时前
3dtiles加载时被地形遮挡问题研究及处理思路
前端·3d·cesium
不一样的少年_1 小时前
我让 AI Agent 先别改代码,它怎么还是动手了?
人工智能·agent·ai编程
Htr_1 小时前
Vercel 使用指南:框架、工作流与基础设施一体化的现代 Web 部署平台
前端
樊小肆1 小时前
DeepSeeker-Code源码导读04-上下文压缩
人工智能·agent
weixin_471383031 小时前
17 Self-RAG —— 幻觉检测 + 答案质量评估
python·agent
一颗烂土豆1 小时前
ECharts 太平面?试试这款 Vue 3D 图表库
前端·vue.js·echarts
anyup1 小时前
迁移uni-app x,我是如何让 AI 把我一步步搞崩溃的...
前端·uni-app·trae