pi-tool-supervisor0.11.0
Pi tool supervisor extension with configurable lifecycle review for every tool and interactive configuration
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
pi-tool-supervisor
pi-tool-supervisor 是 Pi 的可配置工具生命周期审查扩展。它可以在选定工具执行前后按规则审查,并对 edit、write 使用真实文件变化。
解决什么问题
编辑工具成功执行,并不代表结果符合项目约定、架构边界、安全规则或任务要求。对真实的前后文件 diff 执行模型审查,可以及时发现这些问题,同时把审查策略保留在项目可配置的规则文件中。
工作方式
- 每个 reviewer 可选择
tools、trigger,并可选配置本地condition模块;省略时保持旧默认:edit/write+after,"*"匹配全部内建和自定义工具。 - before reviewer 审查执行前输入,明确拒绝会通过 Pi 原生机制阻断调用;模型审查失败仍 fail-open,但不会把错误追加给 Agent,审计卡片仍保留失败状态;condition 模块加载或执行失败会阻断 before 调用。
- 在
edit/write前捕获文件状态,在工具返回后读取实际文件状态。 - 将带真实行号的修改后文件与 diff 一起发送;超过
maxFileContextChars时,截取首次和末次变更附近并明确标记截断。 - 构建 diff,并只选择规则文件匹配当前变更文件的 reviewer。
- 支持多个 reviewer 并行执行,每个 reviewer 可以使用自己的模型和一个或多个规则文件。
- 读取规则文件可选的 front matter:
enabled、filePatterns、complexity和consumers。 - 返回
passed、rejected、failed或skipped状态,以及结论、发现、规则组和耗时。 - 只在审查结论自洽时才采纳拒绝:
passed: false但没有任何 finding 时会降级为通过并附上说明,避免自相矛盾的审查阻断没有可修项的编辑。 - 原样透传工具结果,不截断,也不把工具输出写入临时文件;输出控制由 Pi 或其他扩展负责。
- 每次工具调用都重新读取配置,因此配置修改会在下一次匹配操作立即生效。
- 当前 Pi 展示中间件可用时显示审计卡片,否则使用 fallback renderer。展示协议由公共运行库
pi-extensions-tool-display提供。
它监听 Pi 原生事件,不会注册替代版 edit 或 write 工具。
审查引擎
每个 reviewer 用 backend 选择审查引擎;省略时保持原有行为。
backend |
由谁判断 | 开销 | 结果形态 |
|---|---|---|---|
model(默认) |
一个 Pi 对话模型,把全部规则和 diff 放进一次 prompt 里读 | 每个 reviewer 一次对话补全 | 自由文本 JSON:passed、summary、findings |
cloudflare |
Workers AI Clef Flash 或 Clef 的原生 Noul / Choice | 取决于模型、状态与批次数 | 使用现有阈值的条款概率与 findings |
typesafe |
TypeSafe System One,一次批量请求里每条规则一个 typed question | 实测 3 条规则的 reviewer:约 1-2 秒、约 1,400 input token、每次审查约 US$0.00006 | 每条规则一个校准过的 noul 概率;阈值由代码判定 |
Cloudflare Clef 引擎
设置 backend: "cloudflare" 直接使用 Workers AI。cloudflareModel 默认 clef-flash,也可选较大的 clef。规则判断(Noul)和行定位(Choice)均使用所选 backend 与模型,复用 TypeSafe 的编号条款、阈值与 findings。旧 reviewer 的 model / typesafe 行为保持兼容,不自动回退或迁移配置。
{
"enabled": true,
"timeoutSeconds": 10,
"cloudflare": {
"baseUrl": "https://api.cloudflare.com/client/v4",
"accountIdEnv": "CLOUDFLARE_ACCOUNT_ID",
"tokenEnv": "CLOUDFLARE_API_TOKEN"
},
"reviewers": [{
"name": "code-quality",
"backend": "cloudflare",
"cloudflareModel": "clef-flash",
"rulesFiles": ["./rules/code-quality.md"],
"tools": ["edit", "write"],
"trigger": "after"
}]
}
由可信启动层向 Pi 进程注入 CLOUDFLARE_ACCOUNT_ID 和 CLOUDFLARE_API_TOKEN。token 需要 Workers AI 权限,参见 Cloudflare REST 设置。配置文件与面板只保存环境变量名,不保存 token 原值。可选的 accountId 是非密钥显式账号 ID,优先于 accountIdEnv;账号 ID 必须是 32 位十六进制标识。tokenEnv 可指定其它变量,包括某些启动层沿用的 CLOUDFARE_WORKER_AI_API_KEY 历史拼写。其它插件可用的凭证不会自动传给 Pi。
/config:tool-supervisor 提供全部四个连接字段、backend 与 Cloudflare 模型选择;修改立即作用于下一次匹配工具调用,无需重载。审计标签为 cloudflare/clef-flash 或 cloudflare/clef。缺凭证、失败的 REST 包装(包括 HTTP 200 且 success: false)、错误或不完整的答案、非法概率均记为 failed。失败不会额外阻断 before;明确 rejected 才阻断执行,after 保留原工具结果。行定位失败保留已确认的规则拒绝,并添加审计警告。
使用 Clef / Clef Flash REST 端点。规则判断与行定位超过 64 个问题时均顺序分批;非法或过长问题 ID 无碰撞映射,生成 findings 前还原,不丢弃条款。Cloudflare 的 timeoutSeconds 由判断、定位、全部批次、响应读取与重试共同使用。每批网络故障或 HTTP 429/500/502/503/504 最多尝试三次并有界退避;401/403/422/451 等明确拒绝不重试。父任务取消后停止后续请求。响应最大 1 MiB;错误只展示 HTTP 状态、Cloudflare 数字错误码和安全原因,不展示原始响应内容。
baseUrl 默认 https://api.cloudflare.com/client/v4;自定义远程网关须为不含 URL 凭证、查询参数或片段的 HTTPS,本机测试才允许 HTTP。拒绝重定向以防 Authorization 转发。Clef 可能按上下文窗口截断过长文本 state;拆小改动并用 maxFileContextChars 控制审查上下文。本地 HTTP 确定性测试不能证明线上模型质量。生产启用前应在有授权的凭证运行层用合成输入验证 Noul 和 Choice。消费内置 supervisor 的 Bridge 还需独立更新版本、注入凭证及部署。
TypeSafe 引擎
typesafe 把规则文件里的每一条编号条款变成一条 Noul 问题——“diff 中新增或修改的代码是否违反这一条?”——答案是概率。同一 reviewer 的全部条款放在一次请求里问,因为 TypeSafe 会对同一份 state 并行回答所有问题。剩下的都由代码控制:threshold 决定算不算命中,命中后用一次更小的 Choice 请求把问题定位到 diff 真正新增的那一行。
规则文件是引擎无关的:换 backend 只改一行 config,规则文件一个字符都不用动。 不需要 ## 判据 段落,不需要声明级别,不需要分块标题。
API Key 优先从 config.json 顶层的 typesafe.apiKey 读取,没配时回退到 TYPESAFE_API_KEY 环境变量;typesafe.endpoint 同理,没配时回退到 TYPESAFE_ENDPOINT,都没有就用官方地址。key 缺失、请求失败或规则文件切不出条款都会产生可见的 failed 审计条目,不会阻断工具,与对话模型审查失败时的行为一致。
怎么启用
- 确保装的是带本后端的版本(
pi update --extensions)。 - 在
config.json顶层加 TypeSafe 连接设置(endpoint可省略,默认官方地址):
"typesafe": {
"apiKey": "<your-typesafe-api-key>"
}
- 把要切换的 reviewer 改成
typesafe引擎(model字段要去掉):
{
"name": "code-taste",
"backend": "typesafe",
"typesafeModel": "jev-latest",
"rulesFiles": ["/absolute/path/to/javascript-typescript.md"],
"tools": ["edit", "write"],
"trigger": "after"
}
规则文件不用动。改完后重启 Pi 会话(或 /reload)生效;先挑一个规则文件试几天,看误报和漏报再扩到其他 reviewer。
它怎么读你的规则文件
---
name: javascript-typescript
filePatterns: ["**/*.ts"]
---
# JavaScript / TypeScript 规则
## 归属(优先于以上条款)
1. `ruleGroup` 只能填本文件里原样出现的条款名或编号。 ← 报告要求,不参与判断
2. 禁止越界:不要报本文件没写的规则。 ← 报告要求,不参与判断
## 必须遵守
1. **禁止魔法值**:业务逻辑中的非显而易见数字必须提取为有语义的 `const` 或集中配置。
2. **最多 3 个参数**:声明中出现 4 个或更多参数时必须改为参数对象。
3. **禁止 any**:优先使用具体类型;无法确定时使用 `unknown` 并在边界处收窄。
不要用 `as any` 或 `any[]` 绕过类型检查。
切成 3 条独立规则:
| 判断 id | 规则名(就是 finding 里的 ruleGroup) |
判据 | 命中 | 结果 |
|---|---|---|---|---|
rule_1 |
必须遵守 1 禁止魔法值 |
条款原文 | 0.52 | 未命中 |
rule_2 |
必须遵守 2 最多 3 个参数 |
条款原文 | 0.03 | 未命中 |
rule_3 |
必须遵守 3 禁止 any |
条款原文 | 0.97 | 阻断,定位到第 3 行 |
一条条款的正文既是判据也是 finding 文案。三条条款是三个独立的 Noul 问题,在同一次请求里并行回答。
切分规则
| 你写的东西 | 它怎么处理 |
|---|---|
1. 正文 编号条款 |
一条规则;缩进的续行拼进同一条 |
| 以「归属」开头的段落里的编号 | 不参与判断——那是「怎么报告」,不是「什么代码算违规」 |
条款开头历史遗留的 [error] / [warning] |
会从判据里剔掉;没有分级,命中即阻断(老规则文件不用改) |
条款里的 **粗体** |
当规则名,拼成 {段落} {编号} {标题},就是 finding 里的 ruleGroup |
front matter 的 threshold |
该文件全部条款的命中阈值;不写默认 0.85 |
| 一个 reviewer 挂多个规则文件 | 判断 id 加文件序号前缀(f1_rule_1);跨文件重号是常态,不告警,同一个文件里编号重复才告警 |
其他散文、## 输出 这类段落 |
不影响判断 |
| 放行条件的编号列表(「只读」「只跑脚本」这类) | 仍会一条一条切成规则,但问的方向是反的——见下文 |
以「归属」开头的段落名(常见规则文件里写作 ## 归属与 severity…)在判定时一律跳过,所以「
ruleGroup 只能填本文件里原样出现的条款名」这类报告要求不会变成代码规则。
编号条款必须写「什么代码算违规」。把放行条件(「只读」「只跑脚本或模块」「拿不准就判 passed」)写成编号列表,它们不会消失:每条都会变成一条规则,被问成「这段代码是否违反『只读』这条规则」,语义正好反过来;而真正的禁令如果写成了无序列表或散文,反而一条都不会被问。切得出条款不等于切对了条款,所以下面「缺条款时报错」那道闸门拦不住这种文件:它确实切得出条款,只是切出来的不是禁令。条款一律写禁令,豁免条件写进被豁免的那条条款里。
不通过时能看出是哪一条
每条命中的条款各产生一条 finding,规则名就是文件里那条条款的叫法:
发现 2 处必须修正的问题
- [必须遵守 1 禁止魔法值] 第 5 行:业务逻辑中的非显而易见数字必须提取为有语义的 `const`…
命中代码:const fallback = 30000;
- [必须遵守 4 禁止 any] 第 3 行:优先使用具体类型;无法确定时使用 `unknown`…
命中代码:const raw = (config as any).timeout;
同一个 reviewer 的 summary 也逐条列出 noul,形如 2 条规则命中:必须遵守 1 禁止魔法值=0.92, 必须遵守 4 禁止 any=0.97。
一次请求问 N 条,所以条款变多几乎不增加延迟:实测 6 条条款(2,438 input token)和 10 条规则批量一次 1.1 秒 / 1,351 input token;拆成 10 次请求则是 4.3 秒 / 7,525 input token。
缺条款时报错,不静默通过
规则文件里一条编号条款都切不出来(比如整份都是散文、或者编号写成了无序列表)时报 failed 并带文件路径,不会被当成通过。否则启用 typesafe 后规则会静默失效。
限制
把 typesafe reviewer 当门禁之前需要知道的限制:
- 条款写得多具体,判得就多准。 概率判不准的时候,该改的是那句话怎么写(把豁免条件列清楚),不是换格式。实测:「非显而易见数字必须提取为有语义的常量」里的「有语义」太主观,
const fallback = 30000;只给 0.52;而「禁止any」这种边界清楚的条款给 0.97。 - 阈值按文件调。 目前
threshold是文件级。同一个文件里条款的好坏差异大时,只能靠把条款写清楚,不能逐条调。 - 没有分级。 所有后端一致:条款命中就阻断。规则文件里历史遗留的
[error]/[warning]标注会被剔掉,不改变判定。 - 行号定位需要修改后的文件内容。
before审查只有write拿得到,所以edit的 before 审查只报规则级问题、不给行号。 - diff 新增超过 40 行时会跳过行定位,并在审计里给出警告;规则级问题仍然照常报告。
- 会把整份修改后文件作为上下文发出,受
maxFileContextChars限制;相比只发 diff,输入 token 大约翻倍。 - 会把每条条款的原文都发出去,所以条款越详细输入 token 越高(实测 6 条约 2.4k input token)。
- 概率是校准过的判断,不是事实保证。
安装
pi install npm:pi-tool-supervisor
包清单会把共享依赖 pi-extensions-tool-display 作为一个扩展入口加载,不需要额外安装宿主包。
安装后重新加载 Pi:
/reload
使用交互式配置命令:
/config:tool-supervisor
它会打开配置面板:顶层是总开关、超时、文件上下文与规则行数上限、TypeSafe 与 Cloudflare 连接,然后是每个 reviewer 一行。选中某个 reviewer 打开它的字段页(启用、名称、审查引擎、审查模型、规则文件、工具范围、触发阶段、条件模块、文件匹配)以及删除动作。审查模型那一行回车打开本会话可用模型的可搜索列表。每改一项立即写盘,无需 /reload。
配置
默认配置路径:
~/.pi/agent/extensions/pi-tool-supervisor/config.json
可以从 config.example.json 开始:
{
"enabled": true,
"timeoutSeconds": 10,
"maxFileContextChars": 50000,
"maxRuleLines": 100,
"typesafe": {
"apiKey": "<your-typesafe-api-key>",
"endpoint": "https://api.typesafe.ai/v1/systemone"
},
"reviewers": [
{
"name": "project-rules",
"model": "provider/model",
"rulesFiles": [
"/absolute/path/to/rules.md"
],
"tools": ["edit", "write"],
"trigger": "after",
"condition": "/absolute/path/to/condition.ts"
},
{
"name": "code-taste",
"backend": "typesafe",
"typesafeModel": "jev-latest",
"rulesFiles": [
"/absolute/path/to/javascript-typescript.md"
],
"tools": ["edit", "write"],
"trigger": "after"
}
]
}
每个 reviewer 必须提供 provider/model 格式的模型(model 引擎),或选择 typesafe / cloudflare 及各自模型名(可使用默认值),并提供 rulesFile 或 rulesFiles。相对规则文件和 condition 模块路径按当前项目工作目录解析。
| 配置项 | 含义 |
|---|---|
enabled |
启用或关闭审查层。 |
timeoutSeconds |
每个 reviewer 模型调用的最长等待时间;Cloudflare 的判断、定位、批次与重试共用此预算。 |
maxFileContextChars |
发送给 reviewer 的修改后文件上下文上限,默认 50,000 字符;超大文件仅发送首次和末次变更附近的有界片段并明确标记。 |
maxRuleLines |
单条审查规则允许读取的最大行数。 |
typesafe |
TypeSafe 引擎的连接设置;有 apiKey 和 endpoint 两个字段,都可省略并回退到同名环境变量。 |
cloudflare |
连接设置:baseUrl、可选显式 accountId、accountIdEnv(默认 CLOUDFLARE_ACCOUNT_ID)、tokenEnv(默认 CLOUDFLARE_API_TOKEN)。 |
cloudflareModel |
仅 cloudflare 使用,clef-flash(默认)或 clef。 |
backend |
model(默认)、typesafe 或 cloudflare,选择审查引擎。 |
typesafeModel |
typesafe 引擎使用的 TypeSafe 模型名,默认 jev-latest。 |
reviewers |
reviewer 名称、模型、规则文件、tools、trigger 和可选的 condition 模块;省略生命周期字段时保持旧的 edit/write + after 行为。 |
condition |
可选的本地 TypeScript/ESM 模块路径。默认导出函数会收到 Pi 原生工具事件、ExtensionContext 和 ToolConditionHelpers;返回 false 时跳过该 reviewer,不调用模型。 |
规则文件可以通过 front matter 限定适用文件或消费者:
---
name: TypeScript safety
enabled: true
filePatterns:
- "**/*.ts"
complexity: local
consumers:
- editor-review
---
filePatterns 使用简化 glob:* 不跨 /,** 可以跨目录,任意位置的 **/ 都可匹配零层或多层目录。反斜杠会归一化为 /,开头的 ./ 会被忽略。
threshold 是 typesafe 与 cloudflare 引擎使用的可选 front matter 字段,默认 0.85;model 引擎忽略它。
Condition 模块
reviewer 可以将 condition 设置为本地 TypeScript 或 ESM 模块路径。相对路径按当前项目工作目录解析,~ 会按 Pi home 目录展开。模块必须默认导出一个同步或异步函数:
import type {
ExtensionContext,
ToolCallEvent,
} from "@earendil-works/pi-coding-agent";
import type { ToolConditionHelpers } from "pi-tool-supervisor";
export default function condition(
event: ToolCallEvent,
ctx: ExtensionContext,
helpers: ToolConditionHelpers,
): boolean {
if (event.toolName !== "bash") return false;
const command = event.input.command;
if (typeof command !== "string") return false;
// 可以直接使用 Pi 原生 event/context;解析器是可选辅助。
const ast = helpers.parseBash(command);
return ast.errors?.length === 0 && ast.commands.some((statement) =>
statement.command.type === "Command" && statement.command.name?.value === "mvn",
);
}
第一个参数是 before reviewer 收到的原始 tool_call 事件,或 after reviewer 收到的原始 tool_result 事件。第二个参数是原生 ExtensionContext,condition 模块可以使用其他 Pi 插件能使用的 context 能力。第三个参数提供 parseBash(source)。
condition 返回 false 时跳过该 reviewer,不读取其规则文件,也不调用模型。模块加载失败、执行失败、返回非 boolean 或超时会生成可见错误;before 会将其视为审查门禁失败并阻断工具。需要阻断工具时使用 trigger: "before",after 仍然只提供诊断。
审查语义
- before reviewer 明确拒绝会阻断 Pi 原生工具调用,并把完整 reason 展示给 Agent;模型失败/跳过会放行,但失败信息只保留在审计详情中,不追加到 tool result。condition 模块加载或执行失败会阻断 before 调用。
- after 拒绝只提供诊断并展示给 Agent,不回滚已完成的工具调用;after 模型失败只保留在审计详情中,不追加错误诊断;工具失败时跳过 after 审查并保留原始错误。
- 审查拒绝和配置警告通过 Pi 的
ctx.ui.notify展示;审查失败保留在审计卡片中,不向 Agent 注入错误文本;扩展不直接调用console.warn或console.error。 - 如果用户打断上级 Agent 请求,所有尚未完成的 reviewer 模型请求会一起取消;尚未发起的 reviewer 会跳过;上级中断记为 skipped,而不是模型调用失败。
- 工具调用失败或文件内容没有变化时跳过审查。
- 扩展不会回滚编辑、阻断操作系统,也不替代 Pi 的权限与沙箱控制。
从 pi-file-edit-review 升级时,如果新配置不存在,扩展会读取旧配置;通过 /config:tool-supervisor 保存后会写入新的配置路径。/pi-tool-supervisor 仍作为兼容别名保留。
要求
- Node.js 22 或更高版本。
- 每个启用的
modelreviewer 需要一个已配置的 Pi 模型。 - 每个启用的
typesafereviewer 需要一个 TypeSafe API Key:优先读config.json里的typesafe.apiKey,未配置时读TYPESAFE_API_KEY。 - 需要提供描述项目级检查项的规则文件。
许可证
Cloudflare reviewer 还需可信运行层向 Pi 进程注入所选账号与 token 环境变量;详见上面的 Cloudflare 引擎说明。
OpenAI Decisions 引擎
把 reviewer 的 backend 设为 openai-decisions,即可使用原生 Decisions API,默认模型为 gpt-6-luna。连接设置放在顶层 openaiDecisions,由所有 reviewer 共用;规则和模型仍由各 reviewer 选择。
{
"openaiDecisions": {
"baseUrl": "https://api.openai.com/v1",
"apiKeyEnv": "OPENAI_API_KEY"
},
"reviewers": [{
"name": "decisions-quality",
"backend": "openai-decisions",
"openaiDecisionsModel": "gpt-6-luna",
"rulesFiles": ["./rules/code-quality.md"],
"tools": ["edit", "write"],
"trigger": "after"
}]
}
通过 /config:tool-supervisor 编辑连接和审查引擎,下一次工具调用立即使用新配置,无需重新加载。baseUrl 接受 API 版本前缀或完整的 /decisions 地址,并保留自定义网关的路径前缀。apiKeyEnv 只填写可信启动层提供的环境变量名,不填写密钥内容。省略连接设置时使用 OpenAI 公网和 OPENAI_API_KEY;公共包没有公司专用网关默认值。
规则判断转换成 predicate,代码行定位转换成 choice,保留原生概率。每批最多 200 道问题;批次、响应读取、重试和代码行定位共用一次审查的截止时间。临时服务错误会在预算内重试;鉴权错误、重定向、拒答和无效答案明确报告失败。响应上限为 1 MiB,错误提示不包含服务端正文。审查失败仍沿用现有行为:记录为 failed,不能把它当作审查通过或操作系统权限隔离。