← 返回更新日志

Pi v0.31.0

2026-01-02 · GitHub 原文
本次更新引入了用于就地分支的会话树、钩子(Hooks)和自定义工具的重大 API 变更,以及带有文件追踪的结构化压缩功能。 ### 会话树 会话现在使用带有 `id`/`parentId` 字段的树形结构。这实现了就地分支功能:使用 `/tree` 命令导航到任意历史节点,从该处继续操作,并在单文件中保留所有历史记录的同时在分支间切换。 **现有会话将在首次加载时自动迁移**(v1 → v2)。无需手动操作。 新增条目类型:`BranchSummaryEntry`(来自已放弃分支的上下文)、`CustomEntry`(钩子状态)、`CustomMessageEntry`(钩子注入的消息)、`LabelEntry`(书签)。 文件格式与 `SessionManager` API 请参阅 [docs/session.md](docs/session.md)。 ### 钩子迁移 钩子 API 已重构,提供更细粒度的事件和更好的会话访问能力。 **类型重命名:** - `HookEventContext` → `HookContext` - `HookCommandContext` 现已成为扩展 `HookContext` 并新增会话控制方法的新接口 **事件变更:** - 原有的单一 `session` 事件已拆分为细粒度事件:`session_start`、`session_before_switch`、`session_switch`、`session_before_branch`、`session_branch`、`session_before_compact`、`session_compact`、`session_shutdown` - `session_before_switch` 和 `session_switch` 事件现在包含 `reason: "new" | "resume"` 以区分 `/new` 和 `/resume` - 新增 `session_before_tree` 和 `session_tree` 事件用于 `/tree` 导航(钩子可提供自定义分支摘要) - 新增 `before_agent_start` 事件:在智能体循环开始前注入消息 - 新增 `context` 事件:在每次 LLM 调用前非破坏性地修改消息 - 事件中将不再传递会话条目。请改用 `ctx.sessionManager.getEntries()` 或 `ctx.sessionManager.getBranch()` **API 变更:** - `pi.send(text, attachments?)` → `pi.sendMessage(message, triggerTurn?)`(创建 `CustomMessageEntry`) - 新增 `pi.appendEntry(customType, data?)` 用于钩子状态持久化(不进入 LLM 上下文) - 新增 `pi.registerCommand(name, options)` 用于自定义斜杠命令(处理器接收 `HookCommandContext`) - 新增 `pi.registerMessageRenderer(customType, renderer)` 用于自定义 TUI 渲染 - 新增 `ctx.isIdle()`、`ctx.abort()`、`ctx.hasQueuedMessages()` 用于获取智能体状态(所有事件均可用) - 新增 `ctx.ui.editor(title, prefill?)` 用于多行文本编辑,支持 Ctrl+G 调用外部编辑器 - 新增 `ctx.ui.custom(component)` 用于完整 TUI 组件渲染及键盘焦点控制 - 新增 `ctx.ui.setStatus(key, text)` 用于在页脚设置持久化状态文本(多个钩子可分别设置) - 新增 `ctx.ui.theme` 属性用于使用主题颜色设置文本样式 - `ctx.exec()` 移至 `pi.exec()` - `ctx.sessionFile` → `ctx.sessionManager.getSessionFile()` - 新增 `ctx.modelRegistry` 和 `ctx.model` 用于 API 密钥解析 **HookCommandContext(仅限斜杠命令):** - `ctx.waitForIdle()` - 等待智能体完成流式输出 - `ctx.newSession(options?)` - 创建新会话,可选设置回调函数 - `ctx.branch(entryId)` - 从特定条目创建分支 - `ctx.navigateTree(targetId, options?)` - 导航会话树 这些方法仅存在于 `HookCommandContext` 中(而非 `HookContext`),因为如果在运行于智能体循环内的事件处理器中调用,可能会导致死锁。 **已移除:** - `hookTimeout` 配置项(钩子不再支持超时设置;请使用 Ctrl+C 中止) - `resolveApiKey` 参数(请改用 `ctx.modelRegistry.getApiKey(model)`) 当前 API 请参阅 [docs/hooks.md](docs/hooks.md) 和 [examples/hooks/](examples/hooks/)。 ### 自定义工具迁移 自定义工具 API 已重构,采用与钩子类似的上下文对象模式。 **类型重命名:** - `CustomAgentTool` → `CustomTool` - `ToolAPI` → `CustomToolAPI` - `ToolContext` → `CustomToolContext` - `ToolSessionEvent` → `CustomToolSessionEvent` **执行签名变更:** ```typescript // Before (v0.30.2) execute(toolCallId, params, signal, onUpdate) // After execute(toolCallId, params, onUpdate, ctx, signal?) ``` 新的 `ctx: CustomToolContext` 提供了 `sessionManager`、`modelRegistry`、`model` 及智能体状态方法: - `ctx.isIdle()` - 检查智能体是否正在流式输出 - `ctx.hasQueuedMessages()` - 检查用户是否有排队消息(跳过交互式提示) - `ctx.abort()` - 中止当前操作(异步触发,不等待结果) **会话事件变更:** - `CustomToolSessionEvent` 现在仅包含 `reason` 和 `previousSessionFile` - 事件中将不再包含会话条目。请使用 `ctx.sessionManager.getBranch()` 或 `ctx.sessionManager.getEntries()` 重建状态 - 原因值:`"start" | "switch" | "branch" | "tree" | "shutdown"`(不再有独立的 `"new"` 原因;`/new` 会触发 `"switch"`) - 移除 `dispose()` 方法。请使用 `onSession` 配合 `reason: "shutdown"` 进行清理 当前 API 请参阅 [docs/custom-tools.md](docs/custom-tools.md) 和 [examples/custom-tools/](examples/custom-tools/)。 ### SDK 迁移 **类型变更:** - `CustomAgentTool` → `CustomTool` - `AppMessage` → `AgentMessage` - `sessionFile` 返回 `string | undefined`(原为 `string | null`) - `model` 返回 `Model | undefined`(原为 `Model | null`) - 移除 `Attachment` 类型。请改用 `@mariozechner/pi-ai` 中的 `ImageContent`。直接将图片添加到消息内容数组中。 **AgentSession API:** - `branch(entryIndex: number)` → `branch(entryId: string)` - `getUserMessagesForBranching()` 返回 `{ entryId, text }` 而非 `{ entryIndex, text }` - `reset()` → `newSession(options?)`,其中 options 包含可选的 `parentSession` 用于追踪会话谱系 - `newSession()` 和 `switchSession()` 现在返回 `Promise<boolean>`(若被钩子取消则返回 false) - 新增 `navigateTree(targetId, options?)` 用于就地树形导航 **钩子集成:** - 新增 `sendHookMessage(message, triggerTurn?)` 用于钩子消息注入 **SessionManager API:** - 方法重命名:`saveXXX()` → `appendXXX()`(例如 `appendMessage`、`appendCompaction`) - `branchInPlace()` → `branch()` - `reset()` → `newSession(options?)`,新增可选参数 `parentSession` 用于追踪会话谱系 - `createBranchedSessionFromEntries(entries, index)` → `createBranchedSession(leafId)` - `SessionHeader.branchedFrom` → `SessionHeader.parentSession` - `saveCompaction(entry)` → `appendCompaction(summary, firstKeptEntryId, tokensBefore, details?)` - `getEntries()` 现在不再包含会话头(需单独使用 `getHeader()`) - `getSessionFile()` 返回 `string | undefined`(内存会话返回 `undefined`) - 新增树操作方法:`getTree()`、`getBranch()`、`getLeafId()`、`getLeafEntry()`、`getEntry()`、`getChildren()`、`getLabel()` - 新增追加方法:`appendCustomEntry()`、`appendCustomMessageEntry()`、`appendLabelChange()` - 新增分支方法:`branch(entryId)`、`branchWithSummary()` **ModelRegistry(新增):** `ModelRegistry` 是一个新类,用于管理模型发现与 API 密钥解析。它将内置模型与 `models.json` 中的自定义模型结合,并通过 `AuthStorage` 解析 API 密钥。 ```typescript import { discoverAuthStorage, discoverModels } from "@mariozechner/pi-coding-agent"; const authStorage = discoverAuthStorage(); // ~/.pi/agent/auth.json const modelRegistry = discoverModels(authStorage); // + ~/.pi/agent/models.json // 获取所有模型(内置 + 自定义) const allModels = modelRegistry.getAll(); // 仅获取拥有有效 API 密钥的模型 const available = await modelRegistry.getAvailable(); // 查找特定模型 const model = modelRegistry.find("anthropic", "claude-sonnet-4-20250514"); // 获取模型的 API 密钥 const apiKey = await modelRegistry.getApiKey(model); ``` 此功能取代了旧的 `resolveApiKey` 回调模式。Hooks 与自定义工具通过 `ctx.modelRegistry` 访问它。 **重命名导出:** - `messageTransformer` → `convertToLlm` - 移除 `SessionContext` 的别名 `LoadedSession` 请参阅 [docs/sdk.md](docs/sdk.md) 和 [examples/sdk/](examples/sdk/) 获取当前 API 文档。 ### RPC 迁移 **会话命令:** - `reset` 命令 → `new_session` 命令,新增可选 `parentSession` 字段 **分支命令:** - `branch` 命令:`entryIndex` → `entryId` - `get_branch_messages` 响应:`entryIndex` → `entryId` **类型变更:** - 消息类型现改为 `AgentMessage`(原为 `AppMessage`) - `prompt` 命令:`attachments` 字段替换为使用 `ImageContent` 格式的 `images` 字段 **压缩事件:** - `auto_compaction_start` 现包含 `reason` 字段(`"threshold"` 或 `"overflow"`) - `auto_compaction_end` 现包含 `willRetry` 字段 - `compact` 响应现包含完整的 `CompactionResult`(`summary`、`firstKeptEntryId`、`tokensBefore`、`details`) 请参阅 [docs/rpc.md](docs/rpc.md) 获取当前协议文档。 ### 结构化压缩 压缩与分支摘要现采用结构化输出格式: - 清晰的分节:目标、进度、关键信息、文件操作 - 文件追踪:`details` 中的 `readFiles` 和 `modifiedFiles` 数组,跨压缩操作累积 - 在摘要生成前,对话会被序列化为文本,以防止模型“续写”对话 `before_compact` 和 `before_tree` 钩子事件允许自定义压缩实现。请参阅 [docs/compaction.md](docs/compaction.md)。 ### 交互模式 **`/tree` 命令:** - 原地导航完整的会话树 - 通过输入文本搜索,使用 ←/→ 翻页 - 过滤模式(Ctrl+O):默认 → 无工具 → 仅用户 → 仅带标签 → 全部 - 按 `l` 将条目标记为书签 - 选择分支将切换上下文,并可选择注入被放弃分支的摘要 **条目标签:** - 通过 `/tree` → 选择 → `l` 为任意条目添加书签 - 标签将显示在树视图中,并持久化为 `LabelEntry` **主题变更(对自定义主题不兼容):** 自定义主题必须添加以下新颜色令牌,否则将无法加载: - `selectedBg`:树选择器及其他组件中选中的/高亮项的背景色 - `customMessageBg`:钩子注入消息(`CustomMessageEntry`)的背景色 - `customMessageText`:钩子消息的文本颜色 - `customMessageLabel`:钩子消息的标签颜色(即 `[customType]` 前缀) 颜色总数从 46 增加到 50 个。请参阅 [docs/theme.md](docs/theme.md) 获取完整颜色列表,并可从内置的深色/浅色主题中复制值。 **设置:** - `enabledModels`:在 `settings.json` 中设置允许使用的模型列表(格式与 `--models` CLI 参数相同) ### 新增 - 新增 `ctx.ui.setStatus(key, text)`,供 Hooks 在底部显示持久化状态文本([#385](https://github.com/badlogic/pi-mono/pull/385) by [@prateekmedia](https://github.com/prateekmedia)) - 新增 `ctx.ui.theme` 获取器,用于使用主题颜色对状态文本及其他输出进行样式设置 - 新增 `/share` 命令,可将会话上传为 GitHub 私密 Gist,并通过 shittycodingagent.ai 获取可分享链接([#380](https://github.com/badlogic/pi-mono/issues/380)) - HTML 导出现包含树状可视化侧边栏,用于导航会话分支([#375](https://github.com/badlogic/pi-mono/issues/375)) - HTML 导出支持键盘快捷键:Ctrl+T 切换思考块,Ctrl+O 切换工具输出 - HTML 导出支持通过主题 JSON 中可选的 `export` 部分配置背景色([#387](https://github.com/badlogic/pi-mono/pull/387) by [@mitsuhiko](https://github.com/mitsuhiko)) - HTML 导出语法高亮现使用主题颜色,并与 TUI 渲染保持一致 - **贪吃蛇游戏示例 Hook**:演示 `ui.custom()`、`registerCommand()` 及会话持久化。请参阅 [examples/hooks/snake.ts](examples/hooks/snake.ts)。 - **`thinkingText` 主题令牌**:可配置思考块文本的颜色。([#366](https://github.com/badlogic/pi-mono/pull/366) by [@paulbettner](https://github.com/paulbettner)) ### 已修改 - **Entry IDs**: 会话条目现在使用简短的 8 位十六进制 ID,替代完整的 UUID - **API key priority**: `ANTHROPIC_OAUTH_TOKEN` 现在优先于 `ANTHROPIC_API_KEY` - HTML 导出模板已拆分为独立文件(template.html、template.css、template.js),便于维护 ### 修复 - HTML 导出现在能正确清理包含 `<style>` 等可能破坏 DOM 渲染的 HTML 标签的用户消息 - 显示包含 U+0600-U+0604 等 Unicode 格式字符的 bash 输出时崩溃 ([#372](https://github.com/badlogic/pi-mono/pull/372) by [@HACKE-RC](https://github.com/HACKE-RC)) - **Footer shows full session stats**: Token 使用量和成本现在包含所有消息,而不仅是压缩后的消息。 ([#322](https://github.com/badlogic/pi-mono/issues/322)) - **Status messages spam chat log**: 快速更改设置(例如通过 Shift+Tab 切换思考级别)会添加多条状态行。连续的状态更新现在会合并为单行。 ([#365](https://github.com/badlogic/pi-mono/pull/365) by [@paulbettner](https://github.com/paulbettner)) - **Toggling thinking blocks during streaming shows nothing**: 在流式传输时按 Ctrl+T 会隐藏当前消息,直到流式传输完成。 - **Resuming session resets thinking level to off**: 初始模型和思考级别未保存到会话文件中,导致 `--resume`/`--continue` 默认为 `off`。 ([#342](https://github.com/badlogic/pi-mono/issues/342) by [@aliou](https://github.com/aliou)) - **Hook `tool_result` event ignores errors from custom tools**: 当工具抛出错误时,`tool_result` hook 事件从未触发,且成功执行时始终为 `isError: false`。现在在成功和错误情况下都会触发该事件,并携带正确的 `isError` 值。 ([#374](https://github.com/badlogic/pi-mono/issues/374) by [@nicobailon](https://github.com/nicobailon)) - **Edit tool fails on Windows due to CRLF line endings**: 当 LLM 发送仅含 LF 的文本时,包含 CRLF 换行符的文件现在能正确匹配。匹配前会标准化换行符,写入时恢复为原始样式。 ([#355](https://github.com/badlogic/pi-mono/issues/355) by [@Pratham-Dubey](https://github.com/Pratham-Dubey)) - **Edit tool fails on files with UTF-8 BOM**: 含 UTF-8 BOM 标记的文件可能导致“未找到文本”错误,因为 LLM 不包含不可见的 BOM 字符。匹配前现在会剥离 BOM,写入时恢复。 ([#394](https://github.com/badlogic/pi-mono/pull/394) by [@prathamdby](https://github.com/prathamdby)) - **Use bash instead of sh on Unix**: 修复了 Unix 系统上 shell 命令使用 `/bin/sh` 而非 `/bin/bash` 的问题。 ([#328](https://github.com/badlogic/pi-mono/pull/328) by [@dnouri](https://github.com/dnouri)) - **OAuth login URL clickable**: 使终端中的 OAuth 登录 URL 可点击。 ([#349](https://github.com/badlogic/pi-mono/pull/349) by [@Cursivez](https://github.com/Cursivez)) - **Improved error messages**: 当缺少 `apiKey` 或 `model` 时提供更清晰的错误信息。 ([#346](https://github.com/badlogic/pi-mono/pull/346) by [@ronyrus](https://github.com/ronyrus)) - **Session file validation**: `findMostRecentSession()` 现在在返回前验证会话头,防止加载非会话 JSONL 文件 - **Compaction error handling**: `generateSummary()` 和 `generateTurnPrefixSummary()` 在遇到 LLM 错误时现在会抛出异常,而非返回空字符串 - **Compaction with branched sessions**: 修复了压缩错误地包含已放弃分支的条目,从而导致 token 溢出错误的问题。压缩现在使用 `sessionManager.getPath()` 仅在当前分支路径上操作,消除了 `prepareCompaction()` 和 `compact()` 之间 80 多行重复的条目收集逻辑 - **enabledModels glob patterns**: `--models` 和 `enabledModels` 现在支持 `github-copilot/*` 或 `*sonnet*` 等 glob 模式。此前,模式仅支持字面匹配或子字符串搜索。 ([#337](https://github.com/badlogic/pi-mono/issues/337))