Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pi Agent(智能体) 扩展体系详解

Pi 的核心设计哲学是“极简核心 + 强力扩展“。它主动不做许多功能(MCP(模型上下文协议)、子智能体、Plan 模式等),而是提供 4 层扩展机制,让用户按需构建工作流。

扩展体系总览

层级能力格式加载位置适用场景
Extensions自定义工具、命令、事件处理、UI 组件TypeScript~/.pi/agent/extensions/ / .pi/extensions/深度定制,需要编程能力的扩展
Skills按需系统指令注入SKILL.md(YAML frontmatter + 指令)~/.pi/agent/skills/ / .pi/skills/添加领域知识、角色设定、规则集
Prompt Templates可复用提示词模板Markdown(YAML frontmatter + 模板)~/.pi/agent/prompts/ / .pi/prompts/常用指令模版
ThemesUI 主题定制JSON~/.pi/agent/themes/ / settings.json配色和外观定制

此外还有 Pi Packages 作为打包分发机制,可将上述四种扩展打包为一个 npm 可分发的单元。


Extensions(TypeScript 插件系统)

Extensions 是 Pi 最强大的扩展层,允许通过 TypeScript 编写自定义工具、命令、事件处理器、覆盖层和 UI 组件。

架构模型

Extensions 在 Pi 的 Agent 运行时中注册钩子,可以:

  • 注册自定义工具:新增 LLM 可调用的工具函数
  • 注册自定义命令:新增 Slash 命令(/mycommand)
  • 替换内置工具:例如 Gondolin extension 将 read/write/edit/bash 路由到微 VM 中执行
  • 监听事件:Agent 生命周期的各类事件(上下文准备、工具调用前/后 等)
  • 添加 UI 组件:自定义 Widget、状态行、覆盖层(Overlay)
  • 替换编辑器:自定义 TUI 编辑器组件

编写 Extension

Extensions 是标准的 TypeScript 文件,放置在 ~/.pi/agent/extensions/ 或 .pi/extensions/ 目录:

// ~/.pi/agent/extensions/my-extension.ts
import type { ExtensionAPI } from "@earendil-works/pi-agent-core";

export default function (pi: ExtensionAPI) {
  pi.registerTool({
    name: "my_tool",
    description: "我的自定义工具",
    parameters: { /* TypeBox schema */ },
    execute: async (args) => {
      return "工具执行结果";
    },
  });

  pi.registerCommand({
    name: "mycommand",
    description: "我的自定义命令",
    execute: async (args) => {
      return "命令执行结果";
    },
  });
}

内置 Extension 示例

Pi 自带 3 个示例 Extension:

Extension功能
prompt-url-widget.ts编辑器中的 @url 自动补全,解析 URL 内容后提交给 LLM
redraws.tsTUI 屏幕重绘事件处理
tps.ts在状态行显示 Token Per Second 实时速率

完整的 Extension API 参考见 Pi 官方文档:pi.dev/docs/latest/extensions

ExtensionAPI 类型速查表

Extensions 使用 TypeScript 类型系统,以下为核心类型速查:

类型用途关键方法/属性
ExtensionAPIExtension 工厂函数接收的 API 对象registerTool(), registerCommand(), on(), sendMessage()
ToolDefinition工具定义,描述 LLM 可调用的函数name, description, parameters, execute()
RegisteredCommandSlash 命令定义name, description, handler()
ExtensionContext事件处理器和工具执行上下文ctx.ui, ctx.cwd, ctx.sessionManager, ctx.model
ExtensionCommandContext命令上下文(扩展 ExtensionContext)waitForIdle(), newSession(), fork(), reload()
ToolCallEvent工具调用事件toolName, input, args
KeyId键盘快捷键标识如 "ctrl+x", "ctrl+shift+d"

生命周期事件详表

Extensions 可监听的关键事件,覆盖 Agent 完整生命周期:

事件触发时机可阻断/修改
project_trust项目信任决策时✅ 返回 trusted: "yes"/"no"
session_startSession 启动/重载/切换时❌ 通知型
session_shutdownSession 关闭时❌ 清理资源
resources_discover资源发现时✅ 贡献额外 skill/prompt/theme 路径
before_agent_start用户提交 prompt 后、Agent 循环开始前✅ 注入消息/修改 system prompt
agent_start / agent_endAgent 每次响应开始/结束❌
turn_start / turn_end每一轮 LLM 交互开始/结束❌
message_end消息完成时✅ 可替换消息内容
tool_call工具调用前✅ 可阻断或修改参数
tool_result工具返回结果后✅ 可修改结果
input用户输入时✅ 可拦截或转换
session_before_switchSession 切换前✅ 可取消
session_before_compact上下文压缩前✅ 提供自定义摘要

进阶 Extension 模式

异步工厂:当 Extension 需要初始化(如获取远程配置)时返回 Promise:

export default async function (pi: ExtensionAPI) {
  const response = await fetch("http://localhost:1234/v1/models");
  const payload = await response.json();
  pi.registerProvider("local-openai", {
    baseUrl: "http://localhost:1234/v1",
    apiKey: "$LOCAL_OPENAI_API_KEY",
    api: "openai-completions",
    models: payload.data.map((model) => ({
      id: model.id, name: model.name ?? model.id,
      reasoning: false, input: ["text"],
      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      contextWindow: model.context_window ?? 128000,
      maxTokens: model.max_tokens ?? 4096,
    })),
  });
}

长时间资源管理:不要在 factory 中启动后台资源(进程、socket、文件监听)。改为在 session_start 中延迟初始化,session_shutdown 中清理:

export default function (pi: ExtensionAPI) {
  let watcher: FileWatcher | null = null;

  pi.on("session_start", async (_event, ctx) => {
    watcher = startWatching(ctx.cwd);
  });

  pi.on("session_shutdown", async () => {
    watcher?.close();
    watcher = null;
  });
}

测试与调试 Extensions

Pi 没有专用的 Extension 测试框架,但有多种成熟的调试方式:

1. console.log + 调试日志

pi --log-level debug

Extension 中的 console.log 输出会显示在调试日志中。

2. 热重载(/reload)

将 Extension 放在自动发现目录(~/.pi/agent/extensions/),修改后用 /reload 热重载,无需重启 Pi。注意 ctx.reload() 调用后应当立即 return,因旧上下文仍在执行。

3. 用 -e 标志快速测试

pi -e ./my-extension.ts

无需将 Extension 复制到自动发现目录即可验证加载和基本功能。

4. Extension 样式选择

样式适用场景
单文件 my-ext.ts小型扩展(100 行以内)
目录 + index.ts多文件组织
Package(含 package.json)需要 npm 第三方依赖

含依赖的 Extension 结构:

my-extension/
├── package.json       # 声明 dependencies
├── package-lock.json
├── node_modules/
└── src/
    └── index.ts       # export default function(pi: ExtensionAPI)

5. 错误边界

Extension 的加载错误不会导致 Pi 崩溃——Pi 捕获异常后记录日志并继续运行。但强烈建议在 execute() 中自行捕获异常:

execute: async (args) => {
  try {
    return { content: [{ type: "text", text: result }], details: {} };
  } catch (err) {
    return {
      content: [{ type: "text", text: `错误: ${err.message}` }],
      details: { isError: true },
    };
  }
}

6. 常见陷阱

陷阱说明解决方案
缺少 typebox 依赖Tool 参数定义需要 TypeBoxnpm install typebox(注意包名不带 @sinclair/)
导入路径错误使用旧包名 @mariozechner/*改为 @earendil-works/*
异步错误未捕获Promise reject 不被外部捕获在 execute() 内 try/catch
在 factory 中启动后台资源session 未启动时资源已创建推迟到 session_start 事件中
忽略 Print/RPC 模式某些 ctx.ui 方法在非交互模式不可用先检查 ctx.hasUI
reload 后继续使用旧状态旧引用已失效await ctx.reload(); return;

Skills(Agent Skills 标准)

Skills 遵循 agentskills.io 标准,以 SKILL.md 文件提供按需加载的系统指令。

Skill(技能) 文件格式

---
name: add-llm-provider
description: 添加自定义 LLM Provider 配置
---

你是一个 LLM Provider 配置专家。你的职责是:

1. 读取当前的 Provider 配置文件
2. 根据用户提供的 API 地址和密钥信息添加新 Provider
3. 验证配置是否正确加载

Skills 使用 YAML frontmatter 声明 name 和 description,正文作为系统指令注入到 Agent 的上下文中。

加载位置与优先级

Skills 从多个位置加载:

位置作用域
~/.pi/agent/skills/全局技能
.pi/skills/项目级技能
通过 pi packages install 安装包内技能

支持 .gitignore 风格的忽略文件(SKILL.md.ignore)。

调用方式

通过 /skill:name 在编辑器中调用已识别到的 Skill。Pi 的 harness 层(formatSkillsForSystemPrompt)将 Skill 内容格式化为 XML 块注入到系统提示中:

<skill name="add-llm-provider">
你是一个 LLM Provider 配置专家。你的职责是:
...
</skill>

Prompt(提示词) Templates(提示词模板)

Prompt Templates 是命名后可复用的提示词片段。它们与 Skills 的区别在于:

  • Skills 是系统性指令,注入到 Agent 的角色定义中,长期有效
  • Prompt Templates 是单次调用模板,通过 /name 快捷注入到当前消息

模板文件格式

---
name: cl
description: 生成 Conventional Commit 格式的提交信息
---

请为当前变更生成一个 Conventional Commit 格式的提交信息。

格式要求:
<type>(<scope>): <description>

<body>

<footer>

类型包括:feat、fix、docs、style、refactor、test、chore

内置模板

Pi 自带以下 Prompt Templates:

模板文件功能
/cl.pi/prompts/cl.md生成 Conventional Commit 提交信息
/is.pi/prompts/is.md生成 Issue 模板
/pr.pi/prompts/pr.md生成 PR 描述模板

在编辑器中输入 /cl 即可调用模板,模板内容会自动填充到当前消息中,用户可以在此基础上编辑。

加载位置

位置作用域
~/.pi/agent/prompts/全局
.pi/prompts/项目级

Themes(主题系统)

Pi 的 TUI 支持热重载主题配置:

{
  "theme": "dark",
  "colors": { ... }
}

通过 /settings 在运行中切换主题,无需重启。支持 dark、light 以及完全自定义的主题 JSON。


Context(上下文) Files(上下文文件)

Pi 支持多层级上下文文件配置:

文件作用优先级
AGENTS.md项目级指令文件高(项目级最高)
SYSTEM.md添加到系统提示中中
APPEND_SYSTEM.md追加到系统提示末尾中

文件加载位置:

位置作用域说明
~/.pi/agent/AGENTS.md全局所有项目生效
~/.pi/agent/SYSTEM.md全局所有项目生效
.pi/AGENTS.md项目级覆盖全局
.pi/SYSTEM.md项目级覆盖全局
项目根 AGENTS.md项目级仅当前项目

--no-context-files 标志可禁用所有上下文文件加载。


Pi Packages(扩展打包分发)

Pi Packages 是将 Extensions、Skills、Prompt Templates、Themes 打包为可分发 npm 包的机制。

Package 结构

my-pi-package/
├── package.json           # 包含 "pi" 字段声明包类型
├── extensions/
│   └── my-extension.ts
├── skills/
│   └── my-skill.md
├── prompts/
│   └── my-prompt.md
└── themes/
    └── my-theme.json

package.json 中的 pi 字段:

{
  "name": "my-pi-package",
  "version": "1.0.0",
  "pi": {
    "extensions": ["extensions/my-extension.ts"],
    "skills": ["skills/my-skill.md"],
    "prompts": ["prompts/my-prompt.md"],
    "themes": ["themes/my-theme.json"]
  }
}

安装与分发

# 从 npm 安装
pi packages install my-pi-package

# 从本地路径安装
pi packages install ./path/to/my-pi-package

通过 pi-build CLI 工具打包。分发方式包括 npm 仓库、Git 仓库或直接本地路径安装。


容器化部署

Pi 默认以当前用户权限运行所有工具。在生产环境、多租户或安全敏感场景下,建议将 Pi 放入隔离环境。Pi 提供三种容器化模式:

Gondolin(微 VM 方案)

Gondolin 是本地 Linux 微 VM,仅隔离内置工具的执行,API Key 等凭据保留在宿主机上:

cp -R examples/extensions/gondolin ~/.pi/agent/extensions/gondolin
cd ~/.pi/agent/extensions/gondolin && npm install --ignore-scripts
cd /path/to/project && pi -e ~/.pi/agent/extensions/gondolin

Extension 将 read/write/edit/bash/grep/find/ls 路由到 VM 内执行,宿主机 cwd 挂载为 VM 的 /workspace。要求 Node.js >= 23.6.0 和 QEMU。

Docker(全进程隔离)

将整个 Pi 进程运行在 Docker 容器中:

FROM node:24-bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
  bash ca-certificates git ripgrep && rm -rf /var/lib/apt/lists/*
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent
WORKDIR /workspace
ENTRYPOINT ["pi"]
docker build -t pi-sandbox -f Dockerfile.pi .
docker run --rm -it \
  -e ANTHROPIC_API_KEY \
  -v "$PWD:/workspace" \
  -v pi-agent-home:/root/.pi/agent \
  pi-sandbox

建议:避免挂载宿主机 ~/.pi/agent,改用命名卷 pi-agent-home,防止 Session 和凭据泄露。

OpenShell(策略控制沙箱)

适用于需要细粒度安全策略的远程或本地沙箱:

openshell gateway add <gateway-url> --name my-gateway
openshell gateway select my-gateway
openshell sandbox create --name pi-sandbox --from pi -- pi

OpenShell 网关可注入推理凭据,沙箱内代码只需调用 https://inference.local 即可使用配置好的 Provider。

Containerization vs Extension Tool Routing

模式隔离粒度凭据安全适用场景
Gondolin仅内置工具✅ 凭据在宿主机本地开发隔离
Docker全进程⚠️ 凭据需传入容器CI/CD、简单隔离
OpenShell全进程 + 策略✅ 网关注入凭据远程/多租户沙箱

安全考量

项目信任(Project Trust)

Pi 的信任机制控制项目级配置和扩展的自动加载,而非运行时的权限沙箱:

  • 当启动目录存在 .pi/extensions、.pi/skills 等资源时,Pi 询问是否信任该项目
  • 信任决定存储在 ~/.pi/agent/trust.json 中
  • 信任前仅加载全局 Extension 和 CLI -e Extension
  • 可通过 --approve/-a 或 --no-approve/-na 覆盖一次运行的决定
  • defaultProjectTrust 可设为 "ask"(默认)、"always" 或 "never"

信任的作用范围:允许加载项目级 Extension、Skills、配置。AGENTS.md 等上下文文件不受信任机制限制。

Extension 权限

Extensions 是 TypeScript 模块,拥有宿主进程的完整权限:

  • 可以读写任意文件
  • 可以执行任意 Shell 命令
  • 可以访问所有环境变量

因此,只从信任的来源安装 Extension。通过 pi packages install 安装的包在生产模式下使用 npm install --omit=dev,确保非开发依赖被正确隔离。

安全实践建议

  1. 最小权限原则:Extension 应只请求所需权限。对危险 bash 命令添加确认门禁(参考 permission-gate.ts 示例 Extension)
  2. API Key 管理:使用环境变量而非硬编码。非交互模式(-p、--mode json、--mode rpc)不显示信任提示,确保 CI 中信任策略明确
  3. 不受信任的仓库:在容器中运行 Pi,仅挂载所需工作路径,避免挂载凭据
  4. 审计日志:通过 session_start/session_shutdown 事件的自定义 Extension 记录操作日志

扩展体系对比

维度Pi Agent ExtensionsOpenCode Plugin(插件)Claude Code Hooks
语言TypeScriptTypeScriptNode.js / Shell
工具注册pi.registerTool()definePlugin()CLAUDE.md 自定义命令
事件钩子生命周期事件20+ Hook 点Hook 系统
UI 定制Widget / Overlay / 编辑器替换有限不支持
打包分发Pi Packages (npm)npm无标准机制

测试与调试

Extension 热重载测试

Pi 支持 /reload 命令热重载 Extensions,修改 TypeScript 源码后无需重启 Agent:

# 编辑 Extension 后,在 Pi 会话中执行
/reload

快速测试单个 Extension 用 -e 标志:

pi -e ./my-extension.ts

放在 ~/.pi/agent/extensions/ 或 .pi/extensions/ 目录的 Extension 支持自动发现和热重载;其他路径只能用 -e 单次加载,不支持 /reload。

调试技巧

  • /debug 命令:写入 ~/.pi/agent/pi-debug.log,包含 TUI 渲染行和最近发送给 LLM 的消息全文
  • console.log:Extension 中的 console.log 输出到 pi 的 stderr,正常启动终端即可看到
  • 注册验证:pi.registerTool() 后调用 pi.getAllTools() 检查工具是否注册成功
  • 零构建:Pi 使用 jiti 即时代译 TypeScript,无需构建步骤,修改保存后 /reload 立即生效

常见陷阱

陷阱现象解决方案
ExtensionAPI 版本不匹配方法报 undefined确认 @earendil-works/pi-agent-core 版本与安装的 Pi 版本一致
异步错误被吞Extension 加载失败无提示顶层用 try-catch,捕获后 console.error 输出错误详情
工具名冲突注册的工具不生效Pi 的策略是“先注册者保留“,用 pi.getActiveTools() 检查实际生效的工具列表
依赖缺失Extension 内 import 报错在 Extension 所在目录运行 npm install,或创建 package.json 声明依赖

CI 集成

Extensions 是标准 TypeScript,可用 node:test / Vitest 编写测试:

npm test

Pi 官方推荐在 CI 中跑非 LLM 测试(./test.sh 或 npm test),验证 Extension 加载、工具注册和事件响应。发布 Pi Package 前用 npm pack 验证打包完整性。


→ Pi Agent(智能体) 生态参考 涵盖 Provider 生态、程序化集成方式和容器化方案 → Pi Agent(智能体) SDK 与程序化集成 涵盖程序化集成、Agent Session API 和 RPC 模式


常见反模式

用 Extension 做本应由 Skill 完成的事

Extension 是强大的 TypeScript 编程工具,但许多开发者用它来实现简单的指令集(比如“审查代码时关注安全问题“)。这种场景完全可以用 Skill 实现——一个 SKILL.md 文件就能定义角色和规则,不需要编写任何 TypeScript 代码。用 Extension 做 Skill 的事不仅增加了维护成本,还因为 Extension 的加载和初始化开销拖慢了会话启动速度。

区分标准是:如果你需要的是“系统指令注入“(告诉 Agent 怎么做),用 Skill;如果你需要的是“新能力“(让 Agent 能做之前做不到的事),用 Extension。Skill 是声明式的,Extension 是编程式的。声明式能解决的问题不要升级到编程式。

Skill 的 description 写得过于笼统

Pi 的 Skill 通过 description 字段让 LLM 判断何时激活该 Skill。如果描述写成“一个有用的 Skill“或“处理各种任务“,LLM 几乎会在所有场景下激活它,导致系统提示词膨胀、响应延迟增加,且 Skill 的专业指令被稀释在大量无关上下文中。

每个 Skill 的 description 应该明确描述触发条件。例如“当用户需要审查代码安全性时使用“优于“代码审查 Skill“。好的描述让 LLM 能精确判断何时需要、何时不需要,避免不必要的激活。

Pi Package 打包时包含不必要的依赖

通过 pi packages install 安装的 Extension 使用 npm install --omit=dev 安装依赖,确保只包含生产依赖。但许多开发者在打包 Pi Package 时没有清理 node_modules 中的开发依赖(TypeScript、测试框架、linter),导致安装包体积膨胀数倍。

使用 pi-build CLI 工具打包前,确保 package.json 的 dependencies 只包含运行时必需的包,devDependencies 包含开发工具。打包后用 npm pack --dry-run 检查包内容,确认不包含源码测试文件和开发配置。

适用场景与限制

Skills 不支持运行时动态内容注入

Skills 通过 SKILL.md 的 YAML frontmatter 和正文提供静态指令。虽然 Pi 支持 !`command` 语法在 Skill 加载时执行 Shell 命令并注入输出,但这种注入是一次性的——Skill 加载后内容就固定了,不会随着上下文变化而更新。

如果需要动态内容注入(比如在每次工具调用前注入最新的 Git 状态),应该用 Extension 的事件监听器实现。Extension 可以在 before_agent_start、tool_call 等事件中动态构造和注入内容,灵活性远高于 Skill。

Prompt Templates 的命名空间是全局的

Pi 的 Prompt Templates 通过文件名作为命令名(如 /cl、/pr)。如果两个 Pi Package 定义了同名的 Prompt Template,后加载的会覆盖先加载的,且没有冲突提示。这在安装多个第三方 Package 时可能导致意外行为。

安装新 Package 后,检查它提供的 Prompt Templates 是否与你的自定义模板冲突。对于团队共享的模板,使用明确的命名约定(如项目前缀 myproject-cl)避免冲突。Pi 的 Prompt Templates 没有版本控制机制,Package 升级时模板可能被静默替换。

Theme 系统只支持颜色定制

Pi 的 Theme 系统通过 JSON 配置 TUI 的颜色方案,支持 dark、light 和自定义主题。但它不支持布局定制(如修改面板大小、调整侧边栏位置)或字体配置。对于需要深度 UI 定制的场景,Theme 系统的能力有限。

如果需要修改 TUI 的布局或组件结构,需要通过 Extension 的 UI 定制能力(Widget、Overlay、编辑器替换)实现。Theme 只是 Pi UI 定制的最浅层,更深层次的定制需要编写 TypeScript Extension。

常见失败与陷阱

Extension 热重载后旧状态残留

使用 /reload 热重载 Extension 时,Pi 会重新加载所有 Extension 文件。但如果旧 Extension 在模块级维护了状态(全局变量、缓存、连接池),这些状态不会被清理。新 Extension 代码可能意外引用了旧状态,产生“明明改了代码但行为没变“的困惑。

热重载后立即验证 Extension 的行为是否符合预期。在开发阶段,使用 pi --log-level debug 查看 Extension 的加载和初始化日志。避免在 Extension 中使用模块级可变状态,改为在事件处理器中使用局部变量或通过 ctx 传递状态。

Skills 的 SKILL.md.ignore 文件不生效

Pi 支持 .gitignore 风格的忽略文件(SKILL.md.ignore)来排除特定 Skill。但如果忽略文件的路径或格式不正确(比如放在了错误的目录层级,或者使用了不支持的 glob 模式),Skill 不会被排除,仍然会被加载和注入到系统提示中。

创建忽略文件后,用 /skills 命令检查 Skill 列表,确认被忽略的 Skill 不在列表中。忽略文件必须放在 Skills 目录的根层级(如 ~/.pi/agent/skills/SKILL.md.ignore),且每行一个 glob 模式。

多个 Extension 注册同名事件处理器的执行顺序不确定

当多个 Extension 监听同一个事件(如 tool_call)时,Pi 不保证执行顺序。如果你的两个 Extension 都监听 tool_call 事件并返回 { block: true },哪个先执行取决于 Extension 的加载顺序,而加载顺序可能因文件系统遍历顺序而不同。

不要依赖多个 Extension 对同一事件的处理顺序。每个 Extension 的事件处理器应该独立工作,不假设自己是第一个或最后一个执行的。如果需要有序处理,考虑将多个逻辑合并到一个 Extension 中,用内部的优先级队列控制执行顺序。

关联章节