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、命令给用户、状态持久化进会话历史------分支对齐和重启恢复都是白送的。

相关推荐
后端小肥肠13 分钟前
还在找PPT 生成工具?我集成了 GitHub 高星 Skill,自动匹配最优方案
人工智能·aigc·agent
小哈里19 分钟前
【执行】个人操作系统架构图 v1(硬件层,OS层,软件层,Agent层,横向控制面)
系统架构·操作系统·agent·架构图·执行
LiaCode31 分钟前
别让 AI 把仓库写成“代码平行宇宙”:从 Deslop 看生成前查重
前端·后端·github
nice先生的狂想曲42 分钟前
javascript高级程序设计(六)——2026.9.5
前端·javascript
光影少年1 小时前
如何做大型React+RN 项目架构设计、目录规范
前端·react native·react.js
一位正在转型AI全栈的前端工程师1 小时前
AI 全栈学习之旅 -Week 8:从单 Agent 到多 Agent 协作:LangGraph 实战与记忆持久化
前端·python
用户921080262861 小时前
Promise 和 async/await:从用途到执行机制
前端
yyt3630458411 小时前
KLineChartQuant:GLSL 的精度问题及 RTC 解法
前端·vue.js·react.js·交互·图形渲染·webgl·数据可视化
HarmonLTS1 小时前
智盾 WAF v8.2 Ultra|下一代 Web 应用防火墙
前端
এ慕ོ冬℘゜1 小时前
前端实战:基于jQuery递归实现通用树形组织菜单(可直接复用)
前端·javascript·jquery