# pi 版本兼容与升级指南

本文件记录 ygg-pi 与宿主 [pi coding agent](https://github.com/earendil-works/pi) 的版本适配关系、1.x 的行为变化、以及升级后必须真机验证的清单。

最后更新：2026-11-12 · 对应 ygg-pi 0.5.1

## 支持矩阵

| ygg-pi | pi 版本 | 状态 |
|---|---|---|
| 0.5.1 | `1.5.2` | **已升级验证**（2026-11-12：tsc + 832 项测试全绿）|
| 0.5.0 | `1.5.x` | 静态评估通过，代码无需改动 |
| 0.5.0 | `1.4.x` | 类型 + 测试验证通过 |
| 0.5.0 | `1.3.x` | 已真机验证（2026-10-19，见下文清单）|
| 0.4.0 | `1.4.x` | 已验证 |
| 0.4.0 | `0.84.x` | 兼容（回退路径），仅语法/单元级验证 |
| 0.3.0 | `1.4.x` | 已验证 |
| 0.1.2 及更早 | `0.84.x` | 已验证 |
| 0.1.2 及更早 | `>=1.2.0` | **不兼容**：`before_agent_start` 事件结构调整，`safety` 的工具拦截会抛异常 |

`package.json` 的 `peerDependencies` 对宿主包使用 `"*"` 并标记 optional（遵循 pi 官方 [packages 文档](https://pi.dev/docs/packages)），`engines.node` 为 `>=22.19.0`（1.3.0 / 1.4.0 / 1.5.0 要求一致）。

---

## pi 1.5.1 / 1.5.2 升级记录（2026-11-11）

已从 1.5.0 升到 1.5.2（全局 CLI + 项目 devDeps 同步）。`tsc --noEmit` 通过，
806 项断言全绿，**代码零改动**。

### 1.5.1 的破坏性变更

| 变更 | 本项目状态 |
|---|---|
| `CustomGrepOperations.readFile` 不再对 context lines 调用 | ✅ 未使用 |
| OpenAI 兼容模型不再发 `"tools": []`（修 vLLM 0.20+ 的 400） | ✅ 不命中（本项目只有一个工具集，从未全禁） |

### 1.5.2 的行为变更（需要真机看一下）

| 变更 | 影响面 |
|---|---|
| 会话目录不存在时，交互模式给出 **Continue / Fork / Cancel** 而不是只能 Continue | `session-mgr.ts` 调了 `ctx.switchSession()`。字段未变，只是 UI 多两个选项 —— 会话目录存在时行为不变 |

### 缓存保温（cacheWarming）：配了但不会跑

`CacheWarmer` 会在 prompt cache 过期前重发同一请求（`maxTokens: 1`）续 TTL。
实测探测结果：

```json
{
  "model": { "provider": "deepseek", "id": "deepseek-flash", "api": "openai-completions" },
  "promptCache": null,
  "cost": { "input": 0.3, "output": 1.2, "cacheRead": 0.006, "cacheWrite": 0 }
}
```

#### 设置层：默认是开着的

pi 1.5.2 的默认值（`settings-defaults.js:60`）是 **`idle`**，不是 `streaming`：

```js
cacheWarming: "idle"                    // 1.5.2
CACHE_WARMING_MODES = ["off", "streaming", "idle"]
```

项目与全局 `settings.json` 都没覆盖这个字段，所以**配置上是开着的**。
（1.5.0 时默认值确实是 `streaming`，1.5.2 改了。以 `c57f382` 的实测为准。）

#### 模型层：`promptCache` 为 null，直接停

```ts
const seconds = model.promptCache?.[retention];   // null → undefined
if (ttlMs === undefined) this.stop("cache lifetime unavailable");
```

**这是 pi 的明确设计，不是配置缺失。** 官方 settings 文档：

> Cache warming runs only when the model declares a cache lifetime and Pi
> estimates at least $0.05 in avoided cache-miss cost.

pi v0.86.0 发布说明说得更直白：

> Scope is deliberately narrow: warming needs a known cache lifetime for the
> model and retention tier, and **the built-in catalog annotates only direct
> Anthropic** … **OpenAI models are deliberately unannotated because a documented
> TTL alone does not establish full cache loss.**

实测 pi 1.5.2 打包的模型目录：**1,767 个模型定义，只有 18 个带 `promptCache`**，
全部是 Anthropic，值均为 `{short: 300, long: 3600}`（对应 Claude 的 5 分钟 / 1 小时）。
DeepSeek 官方 provider 下一个都没有。

#### DeepSeek 的 TTL 是公开的，但不可用作保温依据

DeepSeek 官方文档（`api-docs.deepseek.com/zh-cn/guides/kv_cache`）：

> 缓存构建耗时为秒级。**缓存不再使用后会自动被清空，时间一般为几个小时到几天**
>
> 缓存系统是“尽力而为”，**不保证 100% 缓存命中**

而且它的落盘机制是多单元前缀，不是单一前缀：

> 请求结束位置落盘：每次请求的用户输入结束位置与模型输出结束位置，会
> 产生两个缓存前缀单元。
> 按固定 token 间隔落盘：在长输入或长输出中，系统会以一定的 token 数量
> 为间隔，截取缓存前缀单元。

**“几小时到几天”加上“尽力而为”意味着连“缓存是否过期”本身都不确定** ——
保温的前提是能算出何时过期，而这里给的是区间不是定值。

（早期版本的本文档写过“DeepSeek 的缓存 TTL 不公开”，不准确，已修正。）

#### 就算手配了 TTL，经济门槛也过不了

```
250k 上下文：
  missCost = 250k × (0.3 − 0.006)/M ≈ $0.0735
  warmCost = 250k × 0.006/M + 1 token ≈ $0.0015
  空闲期 expectedSavings = 0.15 × 0.0735 − 0.0015 ≈ $0.0096
  pi 的门槛            = $0.05                         → 不刷
```

要真正跑起来，需要三件事同时做：

1. 往 `models.json` 给模型补 `promptCache: { short: <秒> }` —— 但这个值只能猜
2. 确认 `cacheWarming: "idle"`
3. 监听 `cache_warming_decision` 事件强制返回 `warm`，绕开 $0.05 门槛

**结论：不配。** 当前缓存命中 99.90% 说明 deepseek 自己的缓存已经在正常工作，
保温解决的是“隔很久回来”的场景，而实际使用是连续对话。
验证方式：`grep -c cache_warm <session>.jsonl` 应为 0（一次都没跑过）。

## pi 1.5.0 评估结论

基于 1.5.0 官方 release notes 逐条比对，**代码无需改动即可升级**。

### 破坏性变更：工具参数校验迁移到 pi-schema

| 1.5.0 变更 | 本项目状态 | 结论 |
|---|---|---|
| 参数校验改跑 `@earendil-works/pi-schema`；超出 JSON Schema 的 TypeBox 能力失效（`Type.Refine()` / `Type.BigInt()` / `Type.Undefined()` / codecs / `Format.Set()`） | 14 个工具仅用 `Type.Object` / `String` / `Number` / `Literal` / `Union` / `Array` / `Optional` 共 8 种，全在 JSON Schema 范围内 | ✅ 零改动 |
| `pi-ai` 不再 re-export TypeBox | `Type` 全部来自 `from "typebox"`（直接依赖，非 pi-ai）| ✅ 不命中 |
| 纯 JSON Schema/MCP tool 获得与 TypeBox 一致的 coercion（数字字符串→数字、单值→数组）| 仅 3 处 `Type.Number`（`maxResults` ×2、`maxLength` ×1），且代码侧均有 `\|\| 5` / `\|\| 50000` 兜底 | ✅ 从「报错」变「能用」|

### 其他相关变更

| 1.5.0 变更 | 对本项目的影响 |
|---|---|
| `tool_search` 非 ASCII 查询修复（#10816）| 🎁 14 个工具描述全中文，此前中文搜工具直接空结果 |
| `[Skill conflicts]` → `[Skill warnings]`（#11521）| 🎁 23 个内置 skill，减少误报 |
| `pi.setActiveTools()` 顺序变化导致系统提示冗余更新（#11495）| 本项目 `setActiveTools` **零调用**；但这正是 0.5.0 的 2.9 修复的同类问题（skill catalog 顺序）|
| `pi.exec()` 修 UTF-8 跨 chunk 截断损坏（#11312）| 相关：0.5.0 已将 `system-info.ts` 的 execSync 迁到 `pi.exec` |
| `pi.exec()` 新增 `maxBuffer` / `truncated`（#11360）| ⚠️ **1.4.0 没有**，`ExecOptions` 只有 `signal`/`timeout`/`cwd`。升级后可补 |
| 默认 OpenAI 模型 `gpt-5.5` → `gpt-6.1-sol`（#11442）| ⚠️ 0.5.0 已加 `model-guard` 提示模型配置缺失 |
| 模型目录升到 models.dev v2（realtime / image-only 不再作为 chat 模型）| ⚠️ 若用户模型 ID 已下架，`/model` 会找不到，`model-guard` 提示配置问题 |
| `pi -e npm:<pkg>` 每次启动重新解析版本（#11375）| `bin/ygg-pi.js` 用 `path.resolve` 本地绝对路径，不受影响 |
| 配置包安装失败不再崩启动（#11304）| 🎁 Extension 独立加载的容错更强 |
| 扩展更新日志可见性（`pi.changelogPath`）| 已配：`package.json` 的 `pi.changelogPath` = `./CHANGELOG.md`。⚠️ 本地 `-e` 直加载**看不到** Extension Updates，那是 npm 安装形态才有的 |

### typebox 双实例说明

本项目从 `typebox` 包直接导入（devDeps `^1.4.3`），pi 1.5.0 内部用 `pi-schema` 校验。schema 是纯 JSON Schema 数据，跨实例兼容，但 typebox 版本漂移时 `Type.Object` 生成的结构可能有细微差异。**升级 typebox 前先跑一轮工具调用验证。**

### 升级步骤

```bash
1. npm install -g @earendil-works/pi-coding-agent@1.5.0
2. package.json devDeps 四件套 ^1.4.0 → ^1.5.0
3. npx tsc --noEmit && npm test
4. 真机验证（见下）
5. 补 pi.exec 的 maxBuffer 支持（1.5.0 才有的 ExecOptions 字段）
```

### 1.5.0 真机验证清单

- [ ] 全新启动（非热重载），14 个工具全部注册成功
- [ ] 逐个工具跑一次带 Optional 参数的调用（`web_search` 带/不带 `maxResults`）
- [ ] 中文内容：`web_fetch` 中文页、`grammar_check` 中文文本、`doc_read` 中文文档
- [ ] `/model` 确认默认模型符合预期
- [ ] 长回答观察流式渲染（1.5.0 增量解析优化）
- [ ] `system_info` 在大仓库下不因 buffer 超限失败（验证 0.5.0 的 execSync → pi.exec）
- [ ] 中断-修正场景：ESC 中断后输入，应提示「上一条已中断」
- [ ] 命令说明行：`git status` 下方应显示「查看工作区状态」
- [ ] 工具输出默认展开（0.5.0 的 setToolsExpanded）

## pi 1.x 对本项目的影响

### 1. `before_agent_start` 事件变成 union（1.2.0 破坏性变更）

pi 1.2.0 起，`pi.sendMessage(..., { triggerTurn: true })` 触发的 run 也会走 `before_agent_start`，该分支**没有 `prompt` 字段**：

```ts
type BeforeAgentStartEvent = BeforeAgentStartUserEvent | BeforeAgentStartCustomEvent;
// user:   { messageType: "user";   prompt: string; images?: ImageContent[] }
// custom: { messageType: "custom"; message: CustomMessage }   ← 无 prompt
```

**本项目的处理**：`safety.ts` 在读取 `event.prompt` 时做了类型判断（custom run 视为空 prompt），避免 `undefined.includes()` 把 `tool_call` 安全拦截整个打挂。

### 2. 系统提示改为「增量注入」模型（1.2.0+）

官方推荐：改 `systemPromptOptions` 的 `sections` / `selectedTools` / `promptGuidelines`，而不是返回整个 `systemPrompt`。返回 `systemPrompt` 或设置 `forceSystemPrompt` 会替换整段提示，并让 provider 的 prompt cache 失效。

**本项目的处理**：`scene-router.ts` 走双路径——
- 检测到 `systemPromptOptions.sections` 是对象（pi ≥ 1.3）→ 把领域 prompt / 工作流 prompt / Skill 目录 / SYSTEM.md / 记忆 / 模式限制写成 6 个 section（`ygg-domain`、`ygg-workflow`、`ygg-skills`、`ygg-rules`、`ygg-memory`、`ygg-mode`），返回 `undefined`，让 pi 保留它自己的系统提示（工具说明、Guidelines）；
- 否则（pi ≤ 0.84）→ 回退到整体返回 `systemPrompt`，并手动把 `toolSnippets`（`Record<工具名, 说明>`）与 `promptGuidelines` 拼回去。

副作用：在 1.3.0 上，系统提示会比 0.84 上更长（pi 原生 + 本项目的 section 都在）。这是有意的——0.84 上本项目的整体替换会把 pi 原生的工具使用规范覆盖掉。

### 3. 默认进入全屏 TUI（1.0.0+）

`tuiMode` 默认值变为 `fullscreen`，footer 可用高度变小，本项目的多行状态栏（领域/工作流 + Context 进度条 + 工具统计 + 技能 + 任务列表 + Agent + 并行面板）可能挤压对话区。

**本项目的处理**：`status-bar.ts` 支持密度配置，在 `.ygg-pi/settings.json` 里：

```json
{ "statusBar": { "mode": "compact" } }
```

- `auto`（默认）：任务列表最多 6 行
- `compact`：任务列表最多 3 行，技能最多显示 2 个
- `full`：不限行数（0.84 时代的观感）

如果全屏模式观感不佳，也可以让 pi 回到普通模式：

```json
// .ygg-pi/settings.json 或 ~/.pi/agent/settings.json
{ "tuiMode": "regular" }
```

### 4. 会话上下文以 SessionManager 为准（0.87.0 破坏性变更）

0.87.0 起直接赋值 `session.agent.state.messages` 不再影响后续请求，该字段属框架内部实现，未出现在扩展类型定义中。

**本项目的处理**：新增 `extensions/utils/session-read.ts`，`parallel_execute` 与 `spawn_agent` 通过 `getSessionMessages()` 多路兜底读取（`agent.state.messages` → `sessionManager.getEntries()` → `agent.state.context.messages`），任何一路失败都返回空数组，不再抛异常。

### 5. models.json 相关变化

- 1.2.0 起发布配置 schema：`https://pi.dev/schemas/models.schema.json`。`bin/ygg-pi.js` 生成的 `models.json` 现已带 `$schema`（旧版本忽略该字段）。
- 1.3.0 起 `kimi-coding` 的 baseURL 覆盖必须带 `/v1`。`bin/ygg-pi.js` 会检测并自动补全，同时打印警告。
- 1.0.3 起 Azure provider 由 `azure-openai-responses` 更名为 `azure`。若 `~/.ygg-pi/settings.json` 的 `model.provider` 用了旧名，需要改。

### 6. 其他已核对为兼容的 API

逐签名比对 0.84.0 与 1.3.0 的 `dist/**/*.d.ts`，以下均无变化，无需改动：

`pi.on` / `pi.events.on|emit` / `pi.registerTool`（`execute` 五参数、`promptSnippet`、`promptGuidelines`）/ `pi.registerCommand`（`handler(args, ctx)`）/ `pi.sendMessage` / `pi.sendUserMessage`（1.2.0 起 idle 时需要 `deliverAs`，本项目已传）/ `pi.appendEntry` / `pi.setSessionName` / `pi.getCommands`（仍返回 `SlashCommandInfo{source}`）/ `ctx.ui.*`（`select`/`confirm`/`input`/`notify`/`setStatus`/`setWidget`/`setFooter`/`setHeader`/`custom`/`theme`/`addAutocompleteProvider`）/ `ctx.sessionManager.getSessionFile|getEntries` / `ctx.modelRegistry.getApiKeyAndHeaders|getProvider` / `ctx.newSession` / `ctx.switchSession` / `ctx.getContextUsage` / `createAgentSession` / `SessionManager.create|inMemory` / `ToolCallEventResult{block,reason}` / `ToolResultEvent{content,isError}` / `InputEventResult{action}` / CLI `-e/--extension`。

## 升级后的真机验证清单

### 已在 pi 1.4.0 完成静态验证（2026-10-26，macOS / pi 1.4.0）

1.4.0 是 minor 版本，**无破坏性变更**：

- `dist/index.d.ts` 逐字节一致——本项目只依赖 7 个顶层导出（`createAgentSession` / `DefaultResourceLoader` / `SessionManager` / `SettingsManager` / `ExtensionAPI` / `ExtensionContext` / `Theme`），签名全部未变。
- `core/sdk.d.ts`、`core/extensions/loader.d.ts`、`core/extensions/types.d.ts`（`ExtensionAPI` / `ExtensionContext` / 全部事件 payload）无变化。
- `exports` 子路径与 `engines.node`（`>=22.19.0`）无变化。
- `dist` 下 187 个文件有 diff，全部是内部实现：`interactive-mode` 的 dialog 栈重构（合并 `extensionSelector/Input/Editor` 为 `extensionDialogs`，均 private）、`compaction` 的 `reserveTokens` 由必填改可选（向后兼容）、`agent-session` 增加私有字段注释。

验证结果：`tsc --noEmit` 0 错误；`npm test`（5 个文件）**265 条断言全部通过**。

1.4.0 对本项目的正向影响：

- **扩展 dialog 堆叠修复**（#11166 / #9909）：旧版并发弹框时第二个 dialog 顶掉第一个，第一个 promise 永不 resolve。本项目的 `scene-router` / `commands` / `tools/config` / `safety` / `intent-classifier` 都用 `ctx.ui.select|confirm|input`，其中 `safety` 每次工具调用都可能弹 confirm，与其它扩展同时触发即死锁。
- **恢复会话保持扩展工具激活**（#11062）：`session-mgr` 用 `SessionManager` resume 时不再丢工具。
- **`grep` 工具加 `--no-config`**（#11203）：用户设置 `RIPGREP_CONFIG_PATH` 时语义不再被静默改变。

#### 1.4.0 暴露的语义澄清：`agent_end` ≠ 任务结束

1.4.0 文档明确 `agent_end` 之后仍可能因自动重试、recovery、compaction retry 或排队消息再起一次 agent run。本项目已把「任务收尾」类回调从 `agent_end` 迁到 `agent_settled`：

- `extensions/safety.ts`：`workMode` / `autopilot` 的重置。留在 `agent_end` 会让重试的那一轮丢失模式限制。
- `extensions/status-bar.ts`：`currentTask.phase = "done"` 与面板自动清除。

中断检测仍保留在 `agent_end`——它需要读 `event.messages` 判断 `stopReason === "aborted"`，而 `agent_settled` 的 payload 只有 `{ aborted }`。

### 已在 pi 1.3.0 实测通过的项（2026-10-19，Windows 11 / Node 22.23.2 / pi 1.3.0）

以下项用 `node <pi1.3>/dist/bundle/cli.js -p "..." <23个 -e 扩展> --no-mcp --session-dir <临时目录>` 跑过：

- [x] CLI 启动（`--version` 输出 `1.3.0`）
- [x] 23 个扩展文件（extensions/*.ts + extensions/tools/*.ts）全部加载，无加载失败
- [x] 模型调用正常，print 模式能拿到回复
- [x] **B 组系统提示注入**：`ygg-domain` 生效（模型准确复述 `configs/prompts/domains/code.md` 的角色定义）
- [x] **B 组系统提示注入**：`ygg-skills` 生效（模型正确列出技能目录前 3 个）
- [x] **E 组并行**：`parallel_execute` 在 1.3.0 下正常工作（两个子 session 并发、结果汇总正确）
- [x] **E 组会话读取**：`getSessionMessages()` 兜底在 1.3.0 上取到了子会话消息（否则统计与结果为空）
- [x] **D 组安全层**：`tool_call` 拦截正常，只读/项目内操作放行，无 TypeError
- [x] **修复验证**：`parallel_execute` 结束后 10 秒的延迟清理不再抛 stale-ctx 异常（修复前必崩）

### 仍需交互式 TUI 才能验证（print 模式无 UI 能力）

本机 npm 源指向内网（`whnpm.cestc.cn`，不可达），如需安装 1.3.0 请临时覆盖源：

```bash
npm i -g @earendil-works/pi-coding-agent@1.3.0 --registry=https://registry.npmjs.org
```

然后在交互模式下逐项确认：

以下项目**尚未实测**，请在能运行交互式 pi 的机器上逐项确认。

### A. 启动与加载
- [ ] `ygg-pi` 正常启动，12 个 extension + 11 个工具全部加载（无 `xxx 加载失败` 通知）
- [ ] `header.ts` 的启动面板正常渲染
- [ ] 全屏 / regular 两种 `tuiMode` 下都能启动

### B. 系统提示注入（本次改动重点）
- [ ] 首次对话后，模型能正确说出当前领域（问「你现在是什么角色」）
- [ ] `/domain` 切换领域后，新一轮对话使用的 prompt 已变化
- [ ] 记忆注入生效：`memory_save` 存一条偏好 → 下一轮对话模型能复述
- [ ] Skill 目录出现在上下文里：模型能列出可用技能名
- [ ] **cache 命中率**：连续两轮对话（仅追加用户消息）时，第二轮 input tokens 的缓存命中应显著高于 0.84 的整体替换方案（可用 `/session` 或状态栏的缓存百分比观察）

### C. 命令
- [ ] `/domain` `/workflow` `/autopilot` `/sessions`（list/new/switch/rename/delete）/`/config` `/init` `/skill` `/help`
- [ ] `/help` 里三组来源（扩展 / Prompt 模板 / Skill）分类正确
- [ ] `/autopilot` 启动后 `sendUserMessage` 不报错（1.2.0 起 idle 时必须有 `deliverAs`）

### D. 安全层（本次改动重点）
- [ ] 未命中任何层的 `bash`/`write`/`edit` 弹一次确认框；同意后同参数不再弹
- [ ] `web_search` / `task` / `memory_save` 等 13 个低风险工具**不**弹框
- [ ] `rm -rf` / `sudo` 触发危险确认
- [ ] 修改 `.env` / `package.json` 触发敏感文件确认
- [ ] debug 模式：诊断阶段禁写、修复阶段 ≤3 文件、大文件小改动不被误拦、超过 50 行被拦
- [ ] **重点**：让一个扩展执行 `pi.sendMessage(..., { triggerTurn: true })`，确认 `safety` 不再报 TypeError（这是 1.2.0 破坏点）

### E. 并行与子代理（本次改动重点）
- [ ] `parallel_execute` 3 个子任务：面板显示工具次数 / 思考轮数、结果文本非空
- [ ] `spawn_agent` 返回子代理正文，不返回「代理执行完成」兜底文案
- [ ] 并行执行中 Esc 能取消

### F. 状态栏与 UI
- [ ] 全屏模式下 footer 不溢出、不挤掉对话区；`statusBar.mode = compact` 时任务列表收敛到 3 行
- [ ] Context 进度条、工具统计、技能行、任务列表、Agent 行、并行面板依次出现
- [ ] 终端宽度变化（拖动窗口）不崩溃
- [ ] 两个 widget（当前任务统计 / Session 累计）位置正确
- [ ] `/model` 切换模型、`/thinking` 切换思考等级后状态栏同步

### G. 其他
- [ ] 多模态：`model.input: ["text","image"]` 下 `read` 图片成功
- [ ] `/compact` 后状态栏统计不异常
- [ ] 恢复历史会话（`/resume`）后 token 统计与工具统计正确

## 回滚

代码兼容两条路径，回滚宿主版本即可，无需改代码：

```bash
npm i -g @earendil-works/pi-coding-agent@0.84.0
```

若需要彻底退回旧行为（全屏/增量注入），把 `.ygg-pi/settings.json` 的 `tuiMode` 设为 `"regular"`，`statusBar.mode` 设为 `"full"`。
