插件与扩展¶
Pi 的扩展系统是它的核心产品哲学:核心保持相对小,把很多工作流能力留给 extension 和 package。这里的“插件”主要对应源码里的 extensions 和 pi packages。
本章涉及的 loader、runner、hook、resource loader 和 package manager 行号见源码索引。
关键源码:
packages/coding-agent/src/core/extensions/types.tspackages/coding-agent/src/core/extensions/loader.tspackages/coding-agent/src/core/extensions/runner.tspackages/coding-agent/src/core/resource-loader.tspackages/coding-agent/examples/extensions/*
扩展能做什么¶
扩展是 TypeScript 模块,默认导出一个 factory:
export default function (pi: ExtensionAPI) {
pi.registerTool({ name: "deploy", ... });
pi.registerCommand("stats", { ... });
pi.on("tool_call", async (event, ctx) => { ... });
}
它可以:
- 注册工具。
- 注册 slash command。
- 注册快捷键和 CLI flag。
- 监听 agent/session/tool/provider 事件。
- 改写输入、上下文、工具结果、message。
- 注入 custom message。
- 注册 provider。
- 请求 UI:select、confirm、input、notify、自定义组件、footer/header/editor。
- 发现额外 skills、prompts、themes。
- 实现权限门、路径保护、git checkpoint、sub-agent、plan mode、MCP adapter 等上层工作流。
加载路径¶
DefaultResourceLoader 负责发现扩展。典型来源:
- 用户全局目录:
~/.pi/agent/extensions/ - 项目目录:
<cwd>/.pi/extensions/ - settings 中声明的 extension source
- pi package 中的
pi.extensions - SDK 传入的
additionalExtensionPaths或extensionFactories
项目扩展受 project trust 控制。未信任项目时,Pi 只加载较安全的上下文文件、用户/全局扩展和 CLI 显式扩展,让它们有机会处理 project_trust 事件;项目本地扩展要等信任后加载。
Loader 设计¶
loader.ts 做两件事:
- 用
jiti加载 TypeScript/JavaScript extension module。 - 创建
ExtensionAPI,把注册方法写入当前 extension 对象,把动作方法委托给共享 runtime。
注册方法包括:
on(event, handler)registerTool(tool)registerCommand(name, options)registerShortcut(shortcut, options)registerFlag(name, options)registerMessageRenderer(customType, renderer)registerProvider(name, config)
动作方法包括:
sendMessagesendUserMessageappendEntrysetSessionNamesetLabelsetActiveToolssetModelsetThinkingLevelexec
这个分离很重要:扩展加载期只能登记能力;绑定到具体 session/runtime 后,动作方法才有真实实现。
Runner 设计¶
ExtensionRunner 是扩展执行器。它知道当前 extensions、runtime、UI context、session manager、model registry,并负责事件分发。
事件不是一律广播。不同事件有不同合并规则:
| 事件 | 合并/短路规则 |
|---|---|
project_trust |
第一个返回 yes/no 的 handler 胜出,undecided 继续 |
session_before_switch/fork/compact/tree |
handler 可 cancel,cancel 后短路 |
message_end |
handler 可返回同 role replacement,串行传递给下一个 handler |
tool_call |
handler 可 block,block 后短路 |
tool_result |
handler 可改 content/details/isError,串行累积 |
context |
handler 可替换 messages,串行传递 |
before_agent_start |
handler 可追加 custom message 或修改 system prompt |
| 普通 lifecycle event | 通知式分发,错误被记录而不是打崩主流程 |
这比简单 event emitter 更适合 agent,因为不同插入点的安全性和语义完全不同。
扩展上下文失效¶
AgentSessionRuntime 在 new session、switch session、fork、reload 时会 dispose 旧 session,并让旧 runner/context 失效。源码中对 stale context 有明确错误信息,要求扩展在 withSession 中使用新 context。
这是处理插件系统时很容易忽略的点:扩展闭包可能捕获旧 session,如果不显式失效,就会写错会话或操作已释放资源。
UI Context¶
扩展不直接依赖交互式 TUI。它通过 ExtensionUIContext 请求能力:
- 交互模式提供真实 UI。
- RPC 模式把 UI 请求编码成 JSONL
extension_ui_request。 - print/headless 模式使用 no-op UI。
这让同一个扩展可以在不同运行模式下退化运行,而不是只能绑定终端 UI。
Pi Package¶
Pi package 是对扩展、skills、prompts、themes 的分发格式。package.json 可声明:
{
"pi": {
"extensions": ["./extensions"],
"skills": ["./skills"],
"prompts": ["./prompts"],
"themes": ["./themes"]
}
}
没有 manifest 时,Pi 会按约定目录自动发现。包可从 npm、git、https、ssh 安装。项目本地安装和全局安装分开,便于团队共享工作流。
设计启发¶
- 扩展系统不只是 hook,要同时有注册表、资源发现、UI 抽象和 session 生命周期。
- 每类事件都应定义自己的合并/短路语义。
- 插件上下文需要可失效,尤其是支持 session replacement 的应用。
- 扩展 UI 用能力接口抽象,可以同时支持 TUI、RPC 和 headless。