Pi Extension 写完不等于可用:从类型检查到真实 Runtime 的证据阶梯
摘要
本文用一个 Context Audit Extension 展示 Pi Tool、Command、Event 与 UI Status 的职责, 并给出来源契约、官方类型检查、注册与 Mock、真实 Runtime、TUI/模型路径、生产安全六层 证据阶梯。文中真实验证到 Pi v0.82.1 RPC 加载、/audit 发现与执行、 context_audit 进入活动工具面;未把 TUI、模型自主调用或生产安全写成已通过。
关键词: Pi、Agent Harness、Extension、Runtime、RPC、证据分级
目录
- Tool、Command、Event 与状态栏怎样协作
- 为什么 Factory 与模式判断是工程边界
- 从官方类型检查到真实 RPC 的六层证据
- 观察事件、阻断事件和安全边界
- 可复跑验收合同与故障定位
一份 Extension 已经有 Tool、Slash Command、事件监听和状态栏,TypeScript 也没有报错。 它算"完成"了吗?不一定。
代码可能只是在手写类型桩下通过;Mock 可能从未经过真实加载器;命令可能已注册,但 Tool 没有进入活动工具面;状态栏可能在 TUI 中有效,却让 RPC 或 Print 模式崩溃。最危险的不是 代码有缺陷,而是把不同强度的证据合并成一句"实战已通过"。
本文用一个 Context Audit Extension 回答两个问题:完整扩展如何组织 Tool、Command、 Event 与 UI;又怎样把"源码对齐"一步步推进到可复现的真实 Runtime 证据。
核心结论是:Extension 不是按"能不能跑"二分,而要按证据层级验收。每一级只允许支撑 它亲自验证过的结论。

一、先定义一个能暴露边界的扩展
我们实现的 context-audit.ts 不负责业务写入,只读取当前 Runtime 的几个状态:工作目录、 Project Trust、Context 使用量、活动工具,以及可选的 System Prompt 预览。
它有四个表面:
text
context_audit Tool:模型可以自主调用
/audit Command:用户不经过模型直接触发
Lifecycle Events:观察 Session、Agent 与 Tool 执行阶段
Footer Status:把暂态运行状态显示给人
这四项看似都在"做审计",实际控制权不同。Tool 把动作交给模型;Command 把动作交给用户; Event 让 Runtime 在特定阶段通知扩展;Status 只是人类观察面。把它们混成一个功能,会掩盖 验收应该在哪一层发生。
二、Tool 与 Command 不是重复入口

Tool 需要名称、用途描述、参数 Schema、执行器和结构化结果。示例使用 TypeBox 描述可选参数:
typescript
parameters: Type.Object({
includeSystemPrompt: Type.Optional(Type.Boolean())
})
执行结果拆成两部分:content 是模型可读的观察结果,details 是应用可以继续消费的结构化 快照。不能为了方便调试,把整个 Runtime 对象或完整 System Prompt 全塞回 Context。
Command 则处理 /audit。用户想立即看状态时,不应先向模型发送"请判断要不要调用审计 工具"。固定版本 RPC 文档也明确,Extension Command 可以作为 Prompt 中的 Slash Command 立即执行。两条入口因此需要分别验收:
text
get_commands 能发现 /audit
getActiveTools 能看到 context_audit
/audit 能执行并返回可核对结果
模型是否会自主选择 context_audit,另做模型路径测试
前三项不需要消耗模型调用;最后一项才与模型、提示和 Provider 行为有关。
三、状态栏是观察面,不是任务真相

示例监听 session_start、agent_start、tool_execution_start、 tool_execution_end 与 agent_settled,并用同一个 Status Key 更新状态:
text
Audit: ready
Audit: agent running
Audit: bash
Audit: idle · 18.4%
这些文本帮助人理解 Runtime 正在做什么,但它们不会因为显示在页脚就自动进入模型 Context, 也不会成为持久化 Session 状态。需要恢复、审计或跨进程共享的数据,必须写入明确的状态合同, 不能把 Footer 当数据库。
同时,UI 能力不是每个模式都有。实现先检查 ctx.mode === "tui",通知则检查 ctx.hasUI。 这使同一扩展可以在 RPC 中完成无模型命令验证,而不依赖一个不存在的交互终端。
四、为什么 Tool Factory 比模块级全局变量更稳

早期示例为了让 Tool 访问 pi.getActiveTools(),把 ExtensionAPI 存进模块级变量。单实例运行 可能没问题,但 Reload、并行测试或同模块重复加载会让引用来源变得模糊。
更稳的写法是工厂:
typescript
function createAuditTool(pi: ExtensionAPI) {
return defineTool({
// schema and metadata
async execute(_id, params, _signal, _update, ctx) {
const activeTools = [...pi.getActiveTools()].sort();
// return bounded snapshot
},
});
}
export default function contextAuditExtension(pi: ExtensionAPI) {
pi.registerTool(createAuditTool(pi));
}
这不是风格偏好,而是把依赖绑定到 Extension Instance。测试时可以清楚知道 Tool 使用的是 哪个 API 实例,也避免模块状态替运行时生命周期背书。
五、六层证据阶梯
1. 来源契约
先固定版本、公开导出和事件定义。本文锁定 Pi v0.82.1 / b4f2936,只从公开包入口导入 defineTool、ExtensionAPI 与 ExtensionContext,并对照固定 Tag 的文档和官方示例。
这一层只能说明"我们理解了目标版本的合同",不能说明示例能编译。
2. 官方类型检查
Batch 08 原验证使用最小手写声明桩,能检查局部 TypeScript 逻辑,却不能证明真实 npm 包的 声明兼容。此次在临时目录安装 @earendil-works/pi-coding-agent@0.82.1、 @earendil-works/pi-ai@0.82.1 和 TypeScript 5.8.3,让示例直接连接官方声明执行严格检查。
结果通过。这里的结论是 VERIFIED_TYPECHECK,仍不能写成 Runtime 已加载。
3. 注册面与 Mock
Fake ExtensionAPI 可以记录注册了哪个 Tool、Command 和 Event,并直接测试 collectSnapshot()、renderSnapshot()、空 Context、输出截断等纯逻辑。
Mock 很适合快速回归,但它通常不会重现真实资源发现、TypeScript Loader、Project Trust、 模式能力和 Extension Runner。它证明的是注册意图和局部逻辑。
4. 真实 Runtime RPC

这是本次新增的关键证据。隔离目录中的 Node 为 v24.3.0,满足包要求的 >=22.19.0; 真实 CLI 返回 0.82.1。随后把扩展放入专用的 PI_CODING_AGENT_DIR,启动 RPC 且禁用 Session 持久化。
第一次请求 get_commands,Runtime 返回:
text
name: audit
source: extension
path: .../extensions/context-audit.ts
第二次通过 RPC 发送 /audit,扩展实际返回通知:
text
project trusted: true
context: 0/204800
context usage: 0.0%
active tools: bash, context_audit, edit, read, write
这证明了真实加载器接受该 TypeScript Extension、Command 已注册并执行、Tool 已进入活动工具面, 且非 TUI 模式没有因状态栏逻辑崩溃。整个路径不需要调用模型。


5. TUI 与模型路径
RPC 证据不能证明 Footer 在真实终端的视觉更新,也不能证明模型会在合适时机自主调用 Tool。 要升级这两项,需要分别记录:TUI 启动、命令补全、事件期间 Status 变化;以及真实 Provider 下 Tool Definition、Tool Call、Result 和 Agent 后续处理。
本次没有这些证据,因此保持 UNKNOWN,而不是从 RPC 推导通过。
6. 生产安全
能运行也不等于安全。Extension 与 Pi 进程拥有相同的宿主权限;Project Trust 控制项目资源 是否被加载,不是 OS Sandbox。System Prompt Preview 还可能暴露项目规则、Skill 描述、路径 或内部服务名。
生产级验收至少还需要输出脱敏、日志策略、依赖与来源固定、敏感目录权限、非交互默认行为、 错误与取消路径,以及独立隔离边界。完整威胁模型属于 PI-12,本文不借一个审计 Demo 宣称解决。
六、事件选择错误会让"观察"冒充"阻断"

tool_execution_start 与 tool_execution_end 适合观察实际执行阶段。如果目标是阻止危险 Tool,应该在 tool_call 阶段判断并返回 block。等到 execution end 再记录"危险命令已执行", 只获得审计记录,没有获得安全门禁。
字符串匹配同样不是完整权限模型。只拦截 rm -rf,无法识别 Python、脚本包装或等价系统 调用。Extension Gate 可以是纵深防御的一层,不能替代最小宿主权限、容器或虚拟机隔离。
七、把验收写成可复跑合同
一份可迁移的 Extension 验收表可以写成:
yaml
source_contract:
pi_version: 0.82.1
public_exports_only: true
typecheck:
official_package_types: passed
registration:
tool: context_audit
command: audit
runtime_rpc:
extension_loaded: passed
command_discovered: passed
command_executed: passed
active_tool_visible: passed
tui:
status_transition: unknown
model_path:
autonomous_tool_call: unknown
security:
production_isolation: unknown
价值不在 YAML,而在它禁止一句"全部通过"覆盖不同事实。升级 Pi、调整 Loader 或增加 UI 组件时,只重跑受影响的层;没有被新证据覆盖的未知项继续保留。
八、失败时如何定位,而不是直接重写代码

如果类型检查失败,先看公开类型与目标版本,不要用 as any 把合同抹掉。如果注册面通过、 真实 Runtime 找不到命令,检查 Extension 来源、Trust、显式 --extension 与发现范围。如果 RPC 可以执行但 TUI 崩溃,优先检查 ctx.mode、ctx.hasUI 和自定义组件能力。
如果 Tool 已出现在 Active Tools 中但模型不调用,也不要立刻改 Loader;问题可能在 Tool 描述、任务触发、模型工具能力或竞争 Tool。分层证据让故障域可定位,也避免每次失败都重写 整个 Extension。
九、最终能确认什么
截至 2026-08-11,这个 Context Audit Extension 已完成三项实质升级:真实官方类型检查、 固定 Pi Runtime 加载、RPC 下的命令发现与无模型执行。返回结果也证明 context_audit 已进入 活动工具集合。
仍不能确认的是:TUI Footer 的真实视觉变化、模型自主 Tool Call、长会话全部事件顺序、 Extension Reload 边界以及生产安全性。
这不是保守措辞,而是 Agent Harness 工程的核心能力:把"代码存在""接口一致""真实加载" 和"生产可靠"分开,系统才知道下一份证据应该补在哪里。