# pi 版本兼容与升级指南

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

最后更新：2026-10-26 · 对应 ygg-pi 0.3.0

## 支持矩阵

| ygg-pi | pi 版本 | 状态 |
|---|---|---|
| 0.3.0 | `1.4.x` | 目标版本，类型 + 测试验证通过，**待真机 TUI 验证**（见下文 1.4.0 段） |
| 0.3.0 | `1.3.x` | 已真机验证（2026-10-19，见下文清单） |
| 0.3.0 | `0.84.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 要求一致）。

## 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"`。
