pi 扩展开发实战手册
摘要
这是一本面向开发者的 cookbook,把 pi(earendil-works/pi)的四层扩展系统(skill / prompt template / extension / package)拆成”先讲机制 → 给最小可运行示例 → 列常见坑 → 推荐进阶”四步式章节,目标是让你看完就能动手写并发布。
本手册覆盖的 pi 版本:@earendil-works/pi-coding-agent 0.74.x – 0.78.x(manifest 字段、生命周期事件、CLI 子命令均按这一带版本)。已有的两份姊妹笔记 pi-deep-dive.md(深度研究 925 行)和 pi-agent-vs-claude-code-codex.md(横向对比)作为背景,本手册不再重复综述内容。
撰写过程同步抓取了官方 docs(packages/coding-agent/docs/*.md)、src/core/extensions/types.ts、loader 源码,以及社区扩展 pi-mcp-adapter 与 pi-subagents 的真实源码,每节”机制”部分尽量贴官方原文,可粘贴可运行的示例则按 docs 与社区代码风格组合而成。
2026-06-05 复核(共 9 处):原稿中标注的不确定点已通过抓取
src/core/extensions/types.ts、src/core/package-manager.ts、src/core/slash-commands.ts、src/core/prompt-templates.ts、src/cli/args.ts、src/package-manager-cli.ts、packages/ai/src/api-registry.ts、docs/skills.md、docs/packages.md、docs/extensions.md、docs/containerization.md进行 verbatim 核实。第一轮 5 处关键修正:
tool_callevent 没有replaceWith字段(只能block + reason+ 直接改event.input);pi pkg create/pi extension dev子命令确认不存在;getSystemPromptOptions()是真实 API 但只在ExtensionCommandContext上;ToolDefinition用executionMode: "sequential" | "parallel"而不是分开两个字段。详见 §6.3 / §6.4 / §6.6 / §7.3。第二轮 4 处补充修正:(1) prompt template 替换还支持
${@:N}${@:N:L}bash 切片语法,并按顺序替换防递归——详见 §5;(2) slash command 没有显式优先级常量,SlashCommandSource仅有"extension" | "prompt" | "skill"三类来源 + 21 个内置命令;真正的 resolve 在调用链推断——详见 §5.5;(3) 自定义 provider 的真实 API 是registerApiProvider而非registerProvider,定义在packages/ai/src/api-registry.ts,用 Map 实现而非 class;详见 §11.x;(4)context-mode≠ pi 扩展——它是mksglu/claude-context-mode(Claude Code 的 MCP 插件,Elastic License 2.0),原稿 §8.3 把它当 pi 扩展拆解是错误前提,已修正。
1. 导言:本手册写给谁
读完你能:
- 在 30 分钟内写一个 skill(最简单的层),给 pi 增加一段”按需加载”的工作流。
- 写一个 prompt template,把”你常打的那段话”变成一个
/foo斜杠命令。 - 写一个 extension,注册自定义工具、监听 agent 生命周期、画 TUI panel、加快捷键、做进程间 RPC。
- 把上述三者打成一个 npm package,发布给别人
pi install npm:your-package一键装上。 - 调试上面这套东西、知道哪里看 log、哪里 hot reload。
不写:模型 / provider 选型,session 文件格式,theme 调色盘,CLI 内部架构(这些去看 pi-deep-dive.md)。
前置假设:
- 你写过一点 TypeScript(不一定精,但能读
import type { ... } from "...")。 - 你装过 npm 全局包,知道
~/是 home 目录。 - 你大致知道”agent loop = 模型生成 → 工具调用 → 把结果塞回去再生成”是怎么回事。
2. 预备知识与环境搭建
2.1 安装 pi
1 | npm install -g --ignore-scripts @earendil-works/pi-coding-agent |
--ignore-scripts 是官方 quickstart 推荐写法,避免依赖在 install 时跑生命周期脚本。装完直接 pi 启动。
2.2 登录 / 配 key
1 | # 选项 1:subscription(启动后 /login 选 Claude Pro / ChatGPT Plus / Copilot) |
凭据落到 ~/.pi/agent/auth.json。
2.3 目录结构(你只需要记三个位置)
1 | ~/.pi/agent/ # 全局 |
优先级:项目级 > 全局;同名 skill / extension 项目级覆盖全局。源码 core/package-manager.ts 中的 dedupePackages() 实现了这条规则——见 §7.7。
2.4 IDE 配置建议
1 | // .vscode/settings.json |
TypeScript 版本:5.4+ 即可(pi 内部用 jiti 加载 .ts,所以不需要你自己 tsc 编译;jiti 直接吃源码)。
2.5 本地 dev loop(最重要的一节)
三种 loop,从快到慢:
| 场景 | 命令 | 何时用 |
|---|---|---|
| 单文件试 | pi -e ./my-extension.ts |
写第一版、还没决定要不要发布 |
| 项目内安装 | 放进 <project>/.pi/extensions/<name>/ |
跟项目绑定的 ext |
| 全局安装 | 放进 ~/.pi/agent/extensions/<name>/ |
自己日常用的 ext |
热重载:在 pi 里敲 /reload,会重新发现并加载所有 extensions / skills / prompts,不会丢当前 session。
没有
pi extension dev/pi pkg create这种子命令。pi CLI 截至 v0.78.x 的全部子命令是install/remove/uninstall/update/list/config(见src/package-manager-cli.ts的PackageCommand联合类型)。dev loop 就是pi -e <path>、改文件、/reload。
2.6 调试
1 | /debug # 启用 debug 输出 |
console.log / console.error 在 extension 里是有效的,会进 pi 的 log 通道(具体落点取决于运行模式:TUI 模式下被 capture,--mode json / --mode rpc 下被框成事件)。
3. 四层扩展系统速查
| 层 | 文件类型 | 写起来要 | 何时用 | 不能做什么 |
|---|---|---|---|---|
| skill | Markdown(SKILL.md) |
5 分钟 | 把”复杂任务的步骤、checklist、参考代码”按需投喂给模型 | 不能跑代码、不能拦事件 |
| prompt template | Markdown(*.md) |
2 分钟 | 把”你常打的那一大段 prompt”变成 /foo arg1 arg2 |
不能拦事件、不能注册工具 |
| extension | TypeScript 模块 | 30 分钟~几天 | 注册新工具、拦截 agent loop、画 TUI、做 RPC | 写起来重,不适合纯 prompt 工程 |
| package | npm package | 1 小时 | 把上面 1~3 打包发布 | 它本身只是容器 |
口诀:能用 skill 就别写 extension;能用 prompt template 就别写 skill;能 hardcode 就别拆 package。
4. 写一个 Skill
4.1 机制
skill 走的是 progressive disclosure:
- pi 启动时扫描所有 skill 位置(
~/.pi/agent/skills/、<project>/.pi/skills/、packages、settings、CLI flag),只读SKILL.md的 frontmatter(name + description)。 - 把所有 skill 的
name + description以 XML 格式拼进 system prompt,让模型”看到目录”。 - 模型决定要用某个 skill 时,再加载完整的
SKILL.mdbody 与配套文件。 - 用户也可以用
/skill:<name>显式触发。
这是为了省 token:100 个 skill 摘要 ≈ 10K token,全文加载 ≈ 1M token,差一个数量级。
4.2 文件位置与命名
1 | ~/.pi/agent/skills/ |
frontmatter 字段(来自 docs/skills.md 的 Frontmatter 表,verbatim):
| 字段 | 必填 | 约束 |
|---|---|---|
name |
✅ | Max 64 chars. Lowercase a-z, 0-9, hyphens. Pi does not require this to match the parent directory(标准要求一致,pi 故意放宽)。 |
description |
✅ | Max 1024 chars. What the skill does and when to use it. |
license |
❌ | License name or reference to bundled file. |
compatibility |
❌ | Max 500 chars. Environment requirements. |
metadata |
❌ | Arbitrary key-value mapping. |
allowed-tools |
❌ | Space-delimited list of pre-approved tools (experimental)。 |
disable-model-invocation |
❌ | When true, skill 从 system prompt 隐藏,必须 /skill:name 才能用。 |
metadata字段的语义已核实:docs 写明 *”Arbitrary key-value mapping”*,源码(core/skills.ts的SkillFrontmatter与Skill接口)把它存为 frontmatter 透传字段,未知字段统一忽略——意思是 pi 自身不读这块,留给 skill 作者 / 三方扩展自由用。如果你的 extension 想消费它,需要自己读SKILL.mdfrontmatter。
pi 比 Agent Skills 标准更宽容:skill name 可以与目录名不同(虽然 docs 说 “suboptimal for shared skill directories”),description 缺失才会拒绝加载,其他违规只 warn。
4.3 最小示例:code review checklist
1 | <!-- ~/.pi/agent/skills/code-review-checklist/SKILL.md --> |
[severity] file:line — short description
why it matters
suggested fix
1 |
|
运行:
1 | pi --skill ~/.pi/agent/skills/code-review-checklist |
或者放进 .pi/skills/ 后直接:
1 | pi |
4.4 进阶:与 prompt template 组合
skill 是”参考资料”,prompt template 是”启动咒语”。把 /review template 的 body 写成 “Use the code-review-checklist skill to review $@“,就能把模型的入口点固定下来:
1 | <!-- ~/.pi/agent/prompts/review.md --> |
调用:/review src/api/auth.ts src/api/users.ts。
4.5 常见坑
- 命名冲突:同名 skill,pi 只保留先扫到的那个并 warn。项目级先扫,所以项目级 wins。但若两个全局位置(
~/.pi/agent/skills/与~/.agents/skills/)都有同名 skill,行为依赖发现顺序,最好别撞名。 - description 太长:>1024 字符 pi 会 warn,且摘要会被截断进 system prompt,一旦截在关键词中间,模型可能就不调你了。
- token 预算:每个 skill 摘要 ~50-200 token,装 200 个 skill 就吃掉一两万 token。考虑用 settings 的 glob filter 关掉不用的。
- 加载顺序:skill body 是按需加载的,但摘要在 startup 时就一次扫完。新加 skill 必须
/reload或重启。 - disable-model-invocation 用错地方:如果你只是想让 skill 的描述不出现在 system prompt 里、节省 token,但还希望模型能间接调到——做不到。设了这个标志后必须
/skill:name显式触发。
4.6 推荐进阶
- 翻 Anthropic 的 agent-skills repo 与 pi 自己 collection 看真实模板。
- 把”长 reference 文档”放进 skill 子目录而不是 SKILL.md 主体,让模型按需
read而不是一次塞光。 - 在
allowed-tools字段里只暴露read/grep,做”只读 skill”,避免 agent 写文件。
5. 写一个 Prompt Template
5.1 机制
prompt template 是纯文本宏:
- Markdown 文件,文件名 = 命令名(
review.md→/review)。 - 调用时 pi 把 frontmatter 之外的 body 展开成一条 user message 送给模型。
- 支持参数替换:
$1、$2、…、$@(全部)、$ARGUMENTS(同$@)、${@:N}(从第 N 个开始)、${@:N:L}(从第 N 个起 L 个)。 - 没有任何拦截 / 注册能力,纯展开。
防注入机制(2026-06-05 实测自 core/prompt-templates.ts):substituteArgs() 是纯字符串顺序替换,没有 shell escape、没有 HTML escape。模块对”参数值里恰好包含 $1 这种 pattern 的字符串”做了顺序处理防递归——一次替换完不会再扫第二遍,所以参数值里的 $2 不会触发对第二个参数的二次替换。但对 prompt 注入完全无防护:如果用户把 "; rm -rf /" 当 $1 传进来,pi 原样塞给模型;写 template 时不要把 $@ 直接喂进 shell 类指令的反引号,要么自己加引号,要么改用 skill 让模型显式 bash 调用,由 bash 工具做参数转义。
参数解析也在 core/prompt-templates.ts 的 parseCommandArgs() 里做:支持 bash-style 单/双引号,按空白分词。所以 /refactor "user account" "too long" 会得到 $1 = "user account"、$2 = "too long",而不是 4 个参数。
5.2 加载位置
1 | ~/.pi/agent/prompts/*.md # 全局,非递归 |
子目录里的 template 默认不会被发现,要在 settings 或 package manifest 里显式列出。
5.3 何时用 template、何时用 skill
| 场景 | 选 |
|---|---|
我要把”那段我每天打 5 次的 prompt”变成 /foo |
template |
| 我要给模型一份 checklist / 参考资料、希望它自己决定何时用 | skill |
我要传参数(<path>、<branch>) |
template |
| 我要附带辅助脚本 / reference 文档 | skill |
| 我要拦截事件 / 改 tool result / 画 UI | extension |
5.4 最小示例:/refactor <function-name>
1 | <!-- ~/.pi/agent/prompts/refactor.md --> |
调用:
1 | /refactor parseConfig "too long, hard to test" |
替换结果(送给模型的):
1 | Refactor the function named `parseConfig` in the current project. |
运行命令:
1 | pi --prompt-template ~/.pi/agent/prompts/refactor.md |
5.5 常见坑
- 文件名带连字符 / 大写:
Refactor-Func.md会变成命令/Refactor-Func,pi 不会自动 lower-case。建议全 lower-snake-or-kebab。 - frontmatter 缺 description:依然能加载,但
/自动补全里没有提示,别人不知道这命令干嘛——docs/prompt-templates.md写明 *”if omitted, the first non-empty line serves this purpose”*。 $@没人传参:会展开成空字符串,模型可能困惑。给个if empty, use ...的 fallback 句子。- 递归发现问题:把 template 塞
prompts/team/code/refactor.md默认发现不到,要么平铺,要么在 settings.json 配 path。 - 跟 skill 重名 / 与内置斜杠命令同名时的优先级(2026-06-05 复核):
core/slash-commands.ts只定义SlashCommandSource = "extension" | "prompt" | "skill"三类来源 + 21 条内置命令固定清单(new/resume/clone/fork/tree/settings/model/scoped-models/login/logout/export/import/share/copy/session/changelog/hotkeys/reload/quit等),但没有公开的 priority 数值常量——优先级是按”内置 → extension → prompt → skill (/skill:命名空间)”的注册/查表顺序隐式决定的。/skill:前缀因为是命名空间不会跟 template 直接撞名;同名 prompt template 与 extension command 同时存在时,后注册者覆盖前者(基于Map.set语义)。建议加项目前缀/myproj-refactor避免歧义——尤其是当你的项目命令撞上未来 pi 新增的内置命令时(21 条内置清单可能在 v0.79+ 扩张)。
5.6 推荐进阶
- 把 template 放到 package 里跟 extension 一起发;package 的
pi.prompts字段可以指 prompt 目录。 - 用 template 做”对话状态启动器”:body 里写 “你现在是 X 角色,第一句话先问我 Y”。
- 写一个 meta template
/template-new把生成新 template 的过程也自动化。
6. 写一个 Extension(核心章节)
这是 pi 扩展系统真正强大的地方。skill / template 都是给模型读的文本,extension 是 TypeScript 代码,能拦截 agent loop 的每一步、注册新工具、画 UI、跟外部进程做 RPC。本节会把官方 docs 与
src/core/extensions/types.ts里的事件 / API 表完整列出,再给两个完整可运行例子。
6.1 Extension 的 3 种形态
| 形态 | 文件结构 | 何时用 |
|---|---|---|
| 单文件 | ~/.pi/agent/extensions/foo.ts |
写脚本式逻辑 |
| 目录 | ~/.pi/agent/extensions/foo/index.ts |
拆成多文件 |
| 带依赖的 npm 包 | foo/package.json 里有 "pi": { "extensions": ["./index.ts"] } |
要 import 第三方依赖 |
加载机制(来自 src/core/extensions/loader.ts):
- 用 jiti 动态 import,所以不需要预编译 TS。
- 入口必须
export default function (pi: ExtensionAPI) { ... }。 - 三个发现位置:项目级
<cwd>/.pi/extensions/、全局agentDir/extensions/、settings 配的 path。 - 入口规则(来自
core/package-manager.ts的resolveExtensionEntries()):先看package.json里的pi.extensions字段;找不到就回退到index.ts/index.js。
6.2 Manifest 字段(package.json pi 块)
源码 core/package-manager.ts 的 PiManifest interface(v0.78.x verbatim):
1 | interface PiManifest { |
社区扩展 pi-mcp-adapter 的实例:
1 | { |
pi-subagents 把三层都打包:
1 | { |
注意:
PiManifest源码里没有video/image字段——这两个是 gallery 元数据,被 npm 读到pi.video/pi.image即可,pi 自身的资源加载器忽略它们。核心 deps 要放 peerDependencies:pi-coding-agent 自己管
@earendil-works/pi-coding-agent/pi-agent-core/pi-ai,extension 不要 bundle,否则版本错位时会出现两份运行时。
6.3 完整生命周期事件表(含返回值精确 TS 类型)
本节所有 return 类型直接抓自
packages/coding-agent/src/core/extensions/types.ts(v0.78.x 时点)。最重要的修正:
tool_callevent 没有replaceWith字段!只能block + reason;要修改工具入参的话,直接 mutateevent.inputin place(types.ts 注释原文:”event.inputis mutable. Mutate it in place to patch tool arguments before execution”)。tool_resultevent 的返回值是{ content?, details?, isError? },不是replaceWith。inputevent 用 tagged union{ action: "continue" | "transform" | "handled" }。user_bashevent 是{ operations?, result? },没有cancel。message_end用{ message? }(替换整条 message 必须保留原 role)。
| 事件 | 触发时机 | 精确 return 类型(types.ts) | 行为 |
|---|---|---|---|
resources_discover |
启动扫描 skills / prompts / themes 时 | ResourcesDiscoverResult { skillPaths?, promptPaths?, themePaths? } |
追加额外资源路径 |
session_start |
session 开始(含 fork、resume 后) | — (handler 不返回) | 只读 |
session_before_switch |
准备切到另一个 session | SessionBeforeSwitchResult { cancel?: boolean } |
可阻断 |
session_before_fork |
准备 fork | SessionBeforeForkResult { cancel?, skipConversationRestore? } |
可阻断 |
session_before_compact |
准备压缩对话历史 | SessionBeforeCompactResult { cancel?, compaction? } |
可阻断 / 可整段替 |
session_compact |
compact 实际发生 | — | 只读 |
session_before_tree |
进 session tree 视图前 | SessionBeforeTreeResult { cancel?, summary?, customInstructions?, replaceInstructions?, label? } |
可阻断 / 可改参 |
session_tree |
进入后 | — | 只读 |
session_shutdown |
session 结束 | — | 做清理 |
context |
准备发 LLM 时 | ContextEventResult { messages? } |
替换 messages 数组 |
before_provider_request |
即将向 provider 发请求 | BeforeProviderRequestEventResult = unknown |
改请求体 |
after_provider_response |
provider 响应回来后 | — | 只读 |
before_agent_start |
agent loop 即将开始 | BeforeAgentStartEventResult { message?, systemPrompt? } |
注入 message / 替 system prompt(多扩展会被 chain) |
agent_start / agent_end |
agent loop 起止 | — | 只读 |
turn_start / turn_end |
turn 起止 | — | 只读 |
message_start / message_update |
模型流式 chunk | — | 只读 |
message_end |
一条 message 流完 | MessageEndEventResult { message? } |
替换整条 message(必须保留 role) |
tool_call |
执行前模型决定调一个工具 | ToolCallEventResult { block?, reason? } |
改入参靠 mutate event.input,不靠 return |
tool_result |
工具执行完毕 | ToolResultEventResult { content?, details?, isError? } |
改写结果 |
tool_execution_start/update/end |
工具实际执行的 3 个时点 | — | 只读 |
model_select |
用户 / 系统选模型 | — (无 ResultType;返回值被忽略) | 只读 / 监听 |
thinking_level_select |
调 thinking level | — | 同上 |
user_bash |
用户 !cmd 跑 shell |
UserBashEventResult { operations?, result? } |
换执行后端或直接给结果 |
input |
用户提交 message(未送 agent) | InputEventResult = { action: "continue" } | { action: "transform"; text; images? } | { action: "handled" } |
拦 / 改 / 完全接管 |
要点:
- 能阻断 + 能改写的事件主要是
tool_call(block)/input(handled / transform)/user_bash(result)/before_agent_start(systemPrompt)。 - 改 tool 入参必须用 in-place mutation:
event.input.command = sanitize(event.input.command),不是return { input: ... }。 - 改 tool 输出用
tool_result的content/details/isError。 - 上下文型事件(
context/before_agent_start/resources_discover)的返回值是追加(多扩展会被 chain)而不是替换。
ToolCallEvent 是按工具名分支的 union(来自 types.ts):
1 | export type ToolCallEvent = |
所以你做 event.toolName === "bash" 判断后能拿到带 typed event.input(如 BashToolCallEvent.input.command)。
6.4 ctx:你能拿到什么
来自 types.ts 的 ExtensionContext(v0.78.x,verbatim):
1 | export interface ExtensionContext { |
getSystemPromptOptions() 在哪? 它是真实存在的 API,但只挂在 ExtensionCommandContext 上(命令 handler 拿到的、ExtensionContext 的超集)。verbatim:
1 | export interface ExtensionCommandContext extends ExtensionContext { |
差别:
getSystemPrompt()给你已组装好的最终字符串,事件 handler 与命令 handler 都能调。getSystemPromptOptions()给你还没组装的结构化选项(BuildSystemPromptOptions来自core/system-prompt.ts),用来在before_agent_start之类的事件里看 / 改 system prompt 的”原料”——但只能在命令 handler 里调。在事件 handler 里要拿原料,看BeforeAgentStartEvent.systemPromptOptions(事件直接给你了,types.ts 里这条 event 自带systemPromptOptions: BuildSystemPromptOptions字段)。
ExtensionCommandContext 比 ExtensionContext 多的 methods(命令 handler 专属):
getSystemPromptOptions(): BuildSystemPromptOptionswaitForIdle(): Promise<void>newSession(options?)/fork(options?)/switchSession(...)/navigateTree(...)reload()—— 重新发现 / 加载所有扩展
6.5 ExtensionAPI 注册接口(pi 参数)
来自 types.ts(v0.78.x verbatim 摘录):
1 | export interface ExtensionAPI { |
6.6 注册自定义工具
ToolDefinition 接口(v0.78.x verbatim 摘自 types.ts):
1 | export interface ToolDefinition< |
关键修正:并发控制是单一字段
executionMode: "sequential" | "parallel",不是两个分开的sequential/parallelboolean。原稿里描述错了。
具体用法:
1 | import { Type } from "typebox"; |
注意:
- 参数 schema 用 TypeBox,不是 zod。pi 内部把 TypeBox schema 转成 JSON Schema 喂给 LLM。如果你需要 enum,用
StringEnumfrom@earendil-works/pi-ai而不是Type.Union(Type.Literal(...)),这是 docs 强调的兼容性细节(Google API 会拒绝某些 OneOf)。 - 输出截断是必须的,docs 写明 50KB 或 2000 行上限——超过会破坏 compaction 逻辑、把 context 撑爆。
- 工具与原子工具(read/write/edit/bash)共存,名字别冲突;要替换 read/write 行为推荐用
tool_callevent 拦截而不是同名 register。 ctx在 execute 里跟事件 handler 拿到的ExtensionContext同接口(不是ExtensionCommandContext)。
6.7 添加 UI
1 | // 在 handler 或命令里 |
ExtensionUIContext 关键 method(types.ts 摘录):select / confirm / input / notify / setStatus / setWorkingMessage / setWidget / setFooter / setHeader / setTitle / custom / pasteToEditor / setEditorText / getEditorText / editor / addAutocompleteProvider / setEditorComponent / theme(只读)/ getAllThemes / setTheme / getToolsExpanded / setToolsExpanded。
@earendil-works/pi-tui 里能 import 的内置组件:Text、Box、Container、SelectList、SettingsList、Markdown、按键工具 matchesKey / Key、宽度工具 visibleWidth / truncateToWidth / wrapTextWithAnsi。
守则:自定义渲染必须用 callback 里的
theme,不要直接import { theme },否则用户切主题你不会跟着变。
6.8 添加 slash commands & 快捷键
1 | pi.registerCommand("hello", { |
跟内置命令同名时用户级 keybindings.json 优先级最高,extension 注册的会被 shadow——这是 docs/keybindings.md 说的”用户可改任何 binding”的副作用。
6.9 RPC:扩展跟外部进程通信
pi 的 RPC 主要面向外部客户端控制 pi(headless mode,stdin/stdout 走 JSON),不是 extension 之间互相喊话。但 extension 在 RPC 模式下可以发起 extension_ui_request 让外部 UI 帮你画对话框(参见 docs/rpc.md)。
extension ↔ 外部进程的常见做法:
- extension 里
child_process.spawn起一个本地进程(比如 MCP server、prettier daemon)。 - session 结束时在
session_shutdownhandler 里 graceful kill。 - 跨 extension 共享状态用
pi.appendEntry()写 session log,下次读回。
pi-mcp-adapter 的真实做法(节选自 index.ts):
1 | pi.on("session_start", async (_event, ctx) => { |
lifecycleGeneration 这个 monotonic counter 是经典做法:在 session 反复重启时丢弃”已经过期”的初始化结果,避免 race。
6.10 完整最小示例:auto-format on save
需求:每次 agent 用 write 或 edit 修改 .ts / .tsx / .js / .jsx / .json / .md 文件后,自动跑 prettier --write,把格式化结果当成”额外的工具结果”显示出来。
1 | // ~/.pi/agent/extensions/auto-format/index.ts |
运行:
1 | pi -e ~/.pi/agent/extensions/auto-format/index.ts |
或者放进 ~/.pi/agent/extensions/auto-format/(把上面文件命名为 index.ts)然后 pi 直接启动。
测试:在 pi 里说 “create a file tmp.ts that prints ‘hi’ badly indented”,等模型 write 完,你会看到右下角 notify 弹”prettier: formatted tmp.ts”,文件被自动格式化。
6.11 进阶示例:简化版 plan-mode
需求:拦截所有写操作(write / edit / bash),先把”接下来要做什么”以 plan 形式打出来,等用户在 UI 里确认才放行。
修正(2026-06-05):原稿这里用了
tool_call的replaceWith字段——但ToolCallEventResult没有这个字段(types.ts 只定义了block?: boolean和reason?: string)。要给模型一个”被拦了为什么”的反馈,靠reason字符串。要把”被拦”显示成更复杂的 message,可以在拦截后调pi.sendMessage()注入一条 custom message。
1 | // ~/.pi/agent/extensions/plan-gate/index.ts |
运行:
1 | pi -e ~/.pi/agent/extensions/plan-gate/index.ts |
这就是为什么 pi 把”plan mode”故意没做成内置——50 行 extension 就能自己写一个,且行为完全可控。
顺手提一句:要改 tool 入参而不是拦截,直接 mutate
event.input:
1
2
3
4
5 pi.on("tool_call", (event) => {
if (event.toolName === "bash") {
event.input.command = event.input.command.replace(/rm -rf \//g, "echo blocked");
}
});
6.12 常见坑
- handler 阻塞 agent loop:
tool_callhandler 是await的,handler 里 sleep 30 秒,agent 就卡 30 秒。长任务请setImmediate(() => ...),把”通知 / 写日志”挪到后台。 - 拿
tool_call的replaceWith/cancel:没有这俩字段!只能{ block: true, reason: "..." }。要改入参:mutateevent.input。要替换”被拦后给模型的反馈”:自己pi.sendMessage一条 custom message。 ctx.signal不监听:用户 ctrl-C,你的工具继续跑、UI 还在转圈。execute 长任务必须周期性if (signal?.aborted) throw new Error("aborted");。- session 切换时残留状态:如 6.9 节展示的
lifecycleGeneration模式,在session_start里要 nuke 之前的状态、并防止”上次的 init 结果回来后污染本次”。 tool_result改写 size:能减小、不能增大太多,否则触发 token 截断逻辑出怪现象(docs 强调 50KB / 2000 行)。- TS 编译陷阱:jiti 跑你的源码,不会做 strict 类型检查。意思是
as any和 implicit any 都过;自己 IDE 配tsc --noEmit做静态检查,否则 bug 很难发现。 - 依赖装错位置:
@earendil-works/pi-coding-agent应该在 peerDependencies 而不是 dependencies;放 dependencies 用户装包时会拉一份独立 runtime。 registerProvider顺序:provider 必须在 session_start 之前注册;registerProvider调用本身可以放在 factory 同步代码里,但生效要等 ctx 起来。getSystemPromptOptions()在事件 handler 里报 undefined:那是因为它只挂在ExtensionCommandContext上。事件 handler 里要拿 system prompt 原料,看BeforeAgentStartEvent.systemPromptOptions。
6.13 推荐进阶
- 读
pi-mcp-adapter的index.ts(约 250 行)学:lifecycle generation、direct tool 注册、proxy tool 模式、CLI flag 注入。 - 读
pi-subagents的src/extension/index.ts(约 22 KB)学:怎么把”subagent 任务”做成自定义 tool(一个 tool 名subagent,靠参数分single/chain/parallel/list/get/create/update/delete/status/interrupt/resume/doctor多种动作)、注册三个 message renderer、监听pi.events.on(SUBAGENT_ASYNC_*)、做 slash bridge。 - 给你的 extension 加
ctx.appendEntry()持久化,这样 pi 重启之后 todos / 计数器还在。
7. 写一个 Package
7.1 目录结构
一个 package 同时可以含 extensions / skills / prompt templates / themes:
1 | my-pi-pack/ |
package.json manifest(手写):
1 | { |
如果你不写 pi 字段,pi 会自动发现 extensions/、skills/、prompts/、themes/ 这些约定目录(来自 core/package-manager.ts 的 collectPackageResources() fallback 分支)。所以小包可以直接省略 manifest。
7.2 4 种分发方式(直接抓自 docs/packages.md)
1 | # 1) npm 注册表 |
加 -l flag 表示项目级安装(落 <project>/.pi/npm/),不加就是全局(落 ~/.pi/agent/npm/)。源码 core/package-manager.ts 的 getManagedNpmInstallPath():
1 | private getManagedNpmInstallPath(source: NpmSource, scope: SourceScope): string { |
7.3 管理命令(v0.78.x verbatim 抓自 src/package-manager-cli.ts)
1 | export type PackageCommand = "install" | "remove" | "update" | "list"; |
加上 alias uninstall 和独立的 config 命令,CLI 子命令的完整列表就是这些(args.ts 的 help 文本同步):
1 | pi install <source> [-l] # 装 |
经源码核实,pi 当前 (v0.78.x) 没有以下子命令:
- ❌
pi pkg create—— 不存在。用npm init -y起项目,按 §7.1 写package.json的pi字段即可。- ❌
pi pkg publish—— 不存在。**用npm publish**。- ❌
pi extension dev—— 不存在。**dev loop 是pi -e <path>+/reload**(见 §2.5)。- ❌
pi pkg <anything>—— 整个pkg子命令族不存在。这是用户 brief 里的笔误或外部资料过时;以本节为准。
7.4 临时加载(不安装)
1 | pi -e npm:@foo/bar # 单次启动加载 npm 包里的 extension |
适合 demo / CI / 临时调试。
7.5 版本管理 & 兼容性
pi-coding-agent主版本 0.x 阶段频繁 break:events 的 ctx shape、tool 注册字段、内置组件 API 都可能改名。把peerDependencies写成>=0.74.0这种宽松约束,CI 跑测试,挂了就出新 minor。- pi 安装 npm 包时
dependencies会被装到 package 自己的node_modules;bundledDependencies里列出的会一起 publish 进 tarball(适合 fork 出来的子模块)。peerDependencies不会被装——意思是 pi 自己作为 peer 提供。 pi-package这个 keyword 是 gallery 收录的硬性条件(也是 docs 推荐的)。
7.6 最小示例:把第 6 节的 auto-format 发成 npm package
1 | # 1) 起项目 |
7.7 加载顺序与覆盖规则(核心源码精确版)
本节所有结论直接来自
core/package-manager.ts的dedupePackages()/resourcePrecedenceRank()/collectPackageResources()/applyPatterns()。
Package 维度(同源覆盖):
1 | // core/package-manager.ts dedupePackages() |
identity 的算法:
1 | private getPackageIdentity(source, scope?) { |
Resource 维度(同名资源覆盖):
1 | // resourcePrecedenceRank(),越小越优先 |
也就是说:
- 项目 settings 显式列出 > 项目 auto > 全局 settings 显式列出 > 全局 auto > package。
- 同 rank 同名时先扫到的 wins(loader.ts / skills.ts 里 “first wins” 的实现一致)。
Package filtering(pi config / settings 里的 packages 数组对象形式):
1 | // settings.json |
applyPatterns() 的精确规则(源码注释 verbatim):
Pattern types:
- Plain patterns: include matching paths
!pattern: exclude matching paths+path: force-include exact path (overrides exclusions)-path: force-exclude exact path (overrides force-includes)
pi.skills manifest 指什么(来自 core/package-manager.ts 的 collectResourceFiles() → collectSkillEntries(dir, "pi")):
- 指目录(
"./skills"或"./skills/foo"):递归扫描,遇到SKILL.md就停止下钻(把那个目录当作 skill root),同时根目录下的.md文件也直接当作单文件 skill 加载。 - 直接指 SKILL.md 文件(
"./skills/foo/SKILL.md"):把那一个 skill 加进来。 - 指中间目录但目录里没有
SKILL.md、只有 .md 文件:根目录的 .md 文件被当成 skill;若该目录仅是”装着多个子 skill 目录”的容器,行为正确——逐个递归找 SKILL.md。
简单说:指目录最稳,无论目录里是直接放 SKILL.md 还是嵌套了多个子 skill 目录都行。
8. 真实案例拆解
8.1 pi-mcp-adapter(月下载 99.2K)
职责:把任意 MCP server 接入 pi。
关键设计:
- proxy tool 模式:默认只注册一个
mcp工具(~200 token),通过参数(tool/connect/describe/search)把上百个 MCP tool 隐藏在后面。 - direct tool 模式:用户在
.mcp.json里把高频 tool 标记为 direct,启动时把这些注册成一等公民工具(每个 ~200 token)。MCP_DIRECT_TOOLS=__none__完全禁用、缺省自动选。 - lazy connect:MCP server 不在启动时全连,模型调到时才 spawn(节省内存、更快启动)。
- lifecycleGeneration:抗 session 反复重启的 race。
- OAuth flow:跟 MCP server 走 OAuth 时,需要 extension 自己起 callback 端口;在
session_start调initializeOAuth(),session_shutdown里shutdownOAuth()。
注册的工具与命令:
| 名字 | 类型 | 干嘛 |
|---|---|---|
mcp |
tool(proxy) | 让 LLM 调任意 MCP tool |
<prefix>__<tool> |
tool(direct) | 高频 MCP tool 直接暴露 |
/mcp |
command | 状态面板(reconnect / tools / setup / logout / status) |
/mcp-auth |
command | 触发 OAuth |
怎么读它的源码:
- 入口
index.ts是粘合层。 - 业务逻辑在
init.ts(启动)、commands.ts(slash 命令)、proxy-modes.ts(proxy tool 的几种模式)、direct-tools.ts(直接 tool 注册)、tool-result-renderer.ts(自定义渲染)。 - 配置:
config.ts读.mcp.json(兼容 Claude Desktop / Cursor 的格式)。
对你的启发:写”adapter 类”扩展(连外部协议进 pi)的标准模板就是它。
8.2 pi-subagents(月下载 103.2K)
职责:在 pi 里实现 sub-agent,能 sequential / parallel / background 跑多个内嵌 agent。
关键设计(从 src/extension/index.ts 实抓):
- 单工具多动作:只注册一个
subagent工具,靠参数 schema 分支({ agent, task? }/{ chain: [...] }/{ tasks: [...] }三种主模式 +list/get/create/update/delete/status/interrupt/resume/doctor这套管理动作)。 - 三个 message renderer:分别处理
SLASH_RESULT_TYPE、subagent-notify、SUBAGENT_CONTROL_MESSAGE_TYPE,定制 TUI 显示。 - 基于 EventBus 的 async 框架:
pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT)/SUBAGENT_ASYNC_COMPLETE_EVENT/SUBAGENT_CONTROL_EVENT,让 background subagent 能跨 turn 通知主 session。 - session bridge:
registerSlashSubagentBridge、registerPromptTemplateDelegationBridge、registerFanoutChildSubagentExtension——把 slash command 和 prompt template 接进 subagent 体系。 - 包结构(package.json):
1 | "pi": { |
三层都用:
- extension 注册
subagent工具与/subagent//subagent-status等命令。 - skills 提供”如何使用 subagent 框架”的 progressive disclosure 文档。
- prompts 提供
/scout、/plan这类启动咒语。
对比 Claude Code 内置 subagent:
| 维度 | Claude Code | pi-subagents |
|---|---|---|
| 实现位置 | 内核 | 第三方 extension |
| 可定制性 | 受限于官方暴露的 hook | 全部 TS 源码 |
| chain 表达力 | 简单 | parallel / collect / chain 三种 |
| 隔离粒度 | 子任务 | 子 session(含独立 context window) |
| 持久化 | 跟主 session 绑 | markdown / json artifact |
这正体现 pi 的哲学:核心不内置,社区能写得比内置更好——因为你看得到全部代码。
8.3 context-mode(月下载 121.5K)— ⚠️ 不是 pi extension
2026-06-05 复核更正:原稿把 context-mode 当 pi 社区扩展拆解是错误前提。npm registry 显示该包的 repository 字段指向 mksglu/claude-context-mode,作者 Mert Koseoğlu (mksglu),Elastic License 2.0(不是 MIT),最新版 1.0.162。它实际是Claude Code / Gemini CLI / Cursor / Codex CLI 等多平台的 MCP 插件,安装方式是:
1 | # Claude Code |
官方明确支持的平台清单(README 摘录):
- Full hooks(routing enforcement + session continuity):Claude Code、Gemini CLI、VS Code Copilot、JetBrains Copilot、OpenCode、OpenClaw、Codex CLI、OMP
- Partial / no hooks(约 60% compliance):Cursor、Antigravity、Zed、Kiro
- 未提及 pi——所以”pi 月下载 121.5K 的最热扩展”这一说法源自 npm 全局下载量,不能等同于 pi extension 圈的真实份额。
功能(README 原文):解决”context window crisis”——把 raw data 隔离到 sandbox 跑代码、用 SQLite + FTS5 + BM25 做长程检索、强制模型生成分析脚本而非直接看数据。核心工具:
| Tool | 功能 | Context 节省 |
|---|---|---|
ctx_execute |
在 12 种语言的 sandbox 跑代码,只回 stdout | 56 KB → 299 B |
ctx_batch_execute |
多命令 + 多查询合一调 | 986 KB → 62 KB |
ctx_index / ctx_search |
FTS5 + BM25 检索 | 60 KB → 40 B |
ctx_fetch_and_index |
抓 URL 切片建索引(24h 缓存) | 60 KB → 40 B |
ctx_execute_file |
sandbox 内处理大文件 | 45 KB → 155 B |
对 pi 用户的启示:
- 想要类似能力,可以通过
pi-mcp-adapter把context-mode接进 pi——pi-mcp-adapter 本身就是 MCP 桥,理论上 context-mode 这个 MCP server 能挂上去(pi 文档与 README 都未明确测试,需自验)。 - context-mode 的”代码代替数据”范式仍然有借鉴价值:写真正的 pi extension 时,用 sandboxed
bash+ 临时文件路径回传(pi 的 bash 工具默认行为)已经能实现 70% 等效效果,无需引入新工具。 - License 差异:Elastic License 2.0 禁止”作为托管服务转售”,企业内部使用 OK;写到 pi-package 生态时不要复制 context-mode 源码——只能通过 MCP 调用。
9. 从 Claude Code / Codex 迁移
9.1 概念映射
| Claude Code | Codex | pi |
|---|---|---|
| Skills | (无原生) | skills(同协议) |
| MCP servers | MCP servers | pi-mcp-adapter(社区) |
| Hooks(PreToolUse / PostToolUse) | (Codex 内置较少) | extension events(tool_call / tool_result) |
| Subagents(内置) | (无原生) | pi-subagents(社区) |
/commands |
--prompt flag |
prompt templates |
| Plan mode(内置) | (无) | DIY,参考 6.11 |
| Permissions | sandbox(OpenShell) | DIY + pi-OpenShell / gondolin |
9.2 Skills:直接挪过来
Claude Code 的 skill 协议跟 pi 的几乎完全一致(pi 实现的就是 Agent Skills 标准),把 ~/.claude/skills/foo 复制到 ~/.pi/agent/skills/foo 多半能用。差异:
- pi 允许 name 跟目录名不同。
- pi 的 frontmatter 多了
compatibility/metadata/disable-model-invocation,少了 Claude 特有字段(如果有)。 allowed-tools列表里写的工具名要对应 pi 的工具名(bash/read/write/edit),不是 Claude Code 的Bash/Read(首字母大小写不同)。
docs/skills.md 给出现成的”借用其他 harness 的 skills”配置:
1 | { |
9.3 MCP:装个 adapter
1 | pi install npm:pi-mcp-adapter |
把 Claude Desktop / Cursor 的 mcp.json 直接放 <project>/.mcp.json 或 ~/.config/mcp/mcp.json,pi 自动读。OAuth flow 走 /mcp-auth。
9.4 Hooks → events
| Claude Code Hook | pi event |
|---|---|
PreToolUse |
tool_call(return { block: true, reason } 等于拒绝;mutate event.input 等于改入参) |
PostToolUse |
tool_result(return { content?, details?, isError? } 等于 transform) |
UserPromptSubmit |
input(return { action: "transform", text, images? } 改写;{ action: "handled" } 完全接管) |
Stop |
agent_end |
SubagentStop |
(pi 里没 subagent 内置;用 pi-subagents 自己监听) |
写法上 pi 是 TS 里的函数,Claude Code 是 JSON 配命令;pi 灵活得多但要写代码。
9.5 Codex sandbox → pi(来自 docs/containerization.md 抓取)
docs/containerization.md 列了三种容器化模式,verbatim 引用:
| Pattern | What is isolated | Best for | Notes |
|---|---|---|---|
| OpenShell | Whole pi process in a policy-controlled sandbox |
Local or remote managed sandbox | Requires an OpenShell gateway |
| Gondolin extension | Built-in tools and ! commands |
Local micro-VM isolation while keeping auth on host | See examples/extensions/gondolin/ |
| Plain Docker | Whole pi process in a local container |
Simple local isolation | Provider API keys enter the container |
OpenShell(NVIDIA OpenShell)
进 sandbox 内整体跑 pi:
1 | openshell gateway add <gateway-url> --name <name> |
如果 gateway 是远端的,项目文件不会被 bind-mount,要么 sandbox 里 clone repo,要么用 openshell sandbox upload / download。OpenShell 还能做”原始 API key 不进 sandbox、由 gateway 注入”的 inference routing(沙箱里调 https://inference.local)。
Gondolin(earendil-works 自家本地 micro-VM)
1 | cp -R packages/coding-agent/examples/extensions/gondolin ~/.pi/agent/extensions/gondolin |
行为:把 host cwd 挂到 VM 的 /workspace,override 内置 read / write / edit / bash / grep / find / ls 七个工具,并把 ! 命令也路由进 VM。文件改动通过 /workspace 写回 host。需要 Node ≥ 23.6.0 和 QEMU。
Plain Docker(最简单)
1 | FROM node:24-bookworm-slim |
1 | docker build -t pi-sandbox -f Dockerfile.pi . |
关键差别:OpenShell / Plain Docker 是整 pi 进容器,凭据在容器内;Gondolin 是 pi 在 host、工具调用进 VM,auth 留在 host。要纯审计沙箱选 OpenShell,要本地隔离选 Gondolin,要 demo 选 Docker。
10. 调试与发布
10.1 本地 dev loop
1 | # 写代码 → 跑 pi -e ./your-ext.ts |
console.log / console.error 可用。在 TUI 模式下 stdout 被 capture,进 log 文件而不是终端。
10.2 单元测试
pi 没有官方 extension test harness(截至 v0.78.x 我看到的 docs / repo)。建议方案:
1 | // tests/auto-format.test.ts |
集成测试:跑 pi --mode json -p "...",把 JSON 事件流抓下来,断言事件序列。这条路 pi-subagents 项目里有 test:integration script,可以参考它的实现。
10.3 发布
1 | # 普通 npm 流程 |
发布之前 checklist:
-
package.json有pi-packagekeyword -
peerDependencies写明 pi 兼容版本范围 -
files字段包含 extensions / skills / prompts / themes / README - README 里有「装 / 配 / 用 / 卸」四段
- 至少跑过一次
pi -e .不报错 - 至少有一个 demo gif 或 mp4
11. 未来与限制
仍在 unstable / 短期可能变的:
ctx.mode的取值集合(tui/rpc/json/print)—— SDK mode 是否会进来还不确定。- TypeBox 是 pi 选的,但 0.x 内随时可能换 schema 库(Claude Code / OpenAI 都各有偏好)。
- Provider registration API:
ExtensionContext.registerProvider(name, config)是包装层;底层是packages/ai/src/api-registry.ts的registerApiProvider(provider, sourceId?)(Map 实现,非 class,不存在ApiRegistry类)。ApiProvider<TApi, TOptions>接口字段是{ api, stream, streamSimple }。**ProviderConfig这个名字在pi-ai/src/types.ts中并不存在——extension docs 用ProviderConfig是把ApiProvider包了一层(具体包装在core/extensions/wrapper.ts,本次未深入)。社区扩展自定义 OpenAI 兼容 endpoint 时,目前最可靠路径仍是走pi-ai内置的openai-completionsprovider 加baseURL配置**,而不是手写 ApiProvider。 disable-model-invocation这个 skill 字段的执行细节。ToolCallEventResult的字段集(block+reason)非常窄;社区有需求要transformInput这种正式字段(目前靠 mutateevent.input)。
已知限制:
- extension 之间没有标准化的 shared state 总线,要么
appendEntry、要么自己开 socket。 tool_call不能在执行后改 result(要tool_result才能改)。- skill 的”按需加载”是模型自己决定,不是确定性触发——遇到模型偷懒不调,只能改 description 或显式
/skill:name。 - prompt template 的参数替换没有自动 escape,命令注入风险靠开发者自己防。
- pi 的 RPC mode 是 stdio JSON,没有 WebSocket(不适合跨主机集成;要远程就上
gondolin)。
12. 不确定性(2026-06-05 两轮复核后留下的)
12.1 第一轮 5 处复核(已闭合)
5 个原始不确定点的核实结果:
| # | 原疑问 | 结论 |
|---|---|---|
| 1 | ctx.getSystemPromptOptions() |
✅ 真实存在,但只挂在 ExtensionCommandContext(命令 handler 拿到的超集)。事件 handler 拿不到,要看 BeforeAgentStartEvent.systemPromptOptions。 |
| 2 | pi pkg create / pi extension dev |
❌ 确认不存在。CLI 子命令完整集合是 install / remove / uninstall / update / list / config(来自 src/package-manager-cli.ts 的 PackageCommand 联合类型 + args.ts 的 help 文本)。 |
| 3 | tool_call 的 replaceWith schema |
❌ 不存在!ToolCallEventResult 仅有 { block?, reason? }。改入参是 mutate event.input in place。改 tool 结果要在 tool_result 事件里返回 { content?, details?, isError? }。 |
| 4 | skill metadata 字段 |
✅ docs 写明 *”Arbitrary key-value mapping”*;pi 自身只透传不读,给三方 extension 作元数据用。 |
| 5 | 全局 + 项目同名 package、pi config 部分关闭时的覆盖规则 |
✅ 见 §7.7:dedupePackages 项目 wins、resourcePrecedenceRank 给出 5 级精确顺序、applyPatterns 处理 plain / ! / + / - 四类 filter。 |
12.2 第二轮 4 处补充复核(已闭合)
| # | 原疑问 | 结论 |
|---|---|---|
| 6 | slash command / prompt template / skill 同名优先级常量 | ⚠️ 没有显式 priority 常量。core/slash-commands.ts 只定义 SlashCommandSource = "extension" | "prompt" | "skill" + 21 个内置命令固定清单(new/resume/clone/fork/tree/settings/model/scoped-models/login/logout/export/import/share/copy/session/changelog/hotkeys/reload/quit 等)。优先级靠”内置 → extension → prompt → skill /skill:“的注册顺序隐式决定,同源后注册者覆盖前者(Map.set 语义)。建议永远加项目前缀避免歧义。 |
| 7 | prompt template 参数替换的 sanitization 边界 | ⚠️ 完全无防护。core/prompt-templates.ts 的 substituteArgs() 是纯字符串顺序替换,只防递归不防注入;parseCommandArgs() 支持 bash-style 单/双引号,按空白分词。命令注入风险全部转嫁给 template 作者。 |
| 8 | registerProvider 的 ProviderConfig schema |
⚠️ **API 真名是 registerApiProvider**(packages/ai/src/api-registry.ts,Map 实现)。ApiProvider 接口是 { api, stream, streamSimple }。ProviderConfig 名字仅出现在 ExtensionContext.registerProvider 的包装层,未在 pi-ai/src/types.ts 中独立定义;自定义 OpenAI 兼容 endpoint 推荐走内置 openai-completions + baseURL 配置。详见 §6.4 注释与 §11。 |
| 9 | context-mode 源码地址与 pi 关系 |
❌ 不是 pi extension!npm 包 context-mode 的 repository 是 mksglu/claude-context-mode,Elastic License 2.0,是 Claude Code / Gemini CLI / Cursor / Codex CLI 的多平台 MCP 插件,README 明确未列 pi。原稿 §8.3 已重写。 |
12.3 仍未闭合的派生疑点
ApiProvider包装层 wrapper.ts:ExtensionContext.registerProvider(name, config)把name与ProviderConfig转成ApiProvider的具体路径未抓 verbatim 源码。/skill:name命名空间冲突:当一个 package 与一个 extension 同时注册同名 skill,/skill:name解析到哪个,源码无明确测试。- slash command 21 条内置清单的稳定性:
core/slash-commands.ts内置数组在 v0.79+ 是否会扩张/重命名(用户在 prompt template / extension 里撞上同名时会被静默覆盖)。 registerApiProvider的sourceId参数:用于unregisterApiProviders(sourceId)时按源批量清理,但 extension 调用ctx.registerProvider时这个 sourceId 是否自动塞为 extension name,未抓代码确认。
13. 来源
直接拉取的源码 / docs(raw URL,时点 main 分支 v0.78.x 附近,2026-06-05 抓取):
- pi extensions docs
- pi skills docs
- pi prompt-templates docs
- pi packages docs
- pi quickstart docs
- pi extensions types.ts — 5 处不确定点的主要来源
- pi extensions loader.ts
- pi skills.ts
- pi package-manager.ts — §7.7 覆盖规则的源码出处
- pi package-manager-cli.ts —
PackageCommand联合类型,证实pkg create不存在 - pi cli/args.ts — CLI 子命令完整列表
- pi cli.ts
- pi tui docs
- pi keybindings docs
- pi rpc docs
- pi development docs
- pi settings docs
- pi containerization docs — §9.5 OpenShell / Gondolin / Docker 三模式来源
- pi slash-commands.ts — §5.5 / §12.2 优先级核实,21 条内置命令清单出处
- pi prompt-templates.ts — §5.1 / §12.2 sanitization & 切片语法核实
- pi-ai api-registry.ts — §11 / §12.2
registerApiProvider真实签名出处 - pi-ai types.ts — 确认 ProviderConfig 名字不在 pi-ai/types.ts 中独立定义
- pi-ai index.ts
- claude-context-mode (mksglu) repo — §8.3 / §12.2 修正 context-mode 不是 pi extension 的依据
- context-mode npm registry —
repository字段确认 - pi-mcp-adapter index.ts
- pi-mcp-adapter package.json
- pi-mcp-adapter README
- pi-subagents index.ts — §6.13 / §8.2 拆解依据
- pi-subagents repository
- Gondolin repository
姊妹笔记(同 repo):
research/notes/pi-deep-dive.mdresearch/notes/pi-agent-vs-claude-code-codex.md