omp packages

pi-tool-supervisor0.11.0

Pi tool supervisor extension with configurable lifecycle review for every tool and interactive configuration

GitHub Actions Published 2 days ago MIT ~1.3K downloads/mo

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.

Did it work for you?

Result

One report per person. A new report replaces your old one.

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 审计条目,不会阻断工具,与对话模型审查失败时的行为一致。

怎么启用

  1. 确保装的是带本后端的版本(pi update --extensions)。
  2. 在 config.json 顶层加 TypeSafe 连接设置(endpoint 可省略,默认官方地址):
"typesafe": {
  "apiKey": "<your-typesafe-api-key>"
}
  1. 把要切换的 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 或更高版本。
  • 每个启用的 model reviewer 需要一个已配置的 Pi 模型。
  • 每个启用的 typesafe reviewer 需要一个 TypeSafe API Key:优先读 config.json 里的 typesafe.apiKey,未配置时读 TYPESAFE_API_KEY。
  • 需要提供描述项目级检查项的规则文件。

许可证

MIT

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,不能把它当作审查通过或操作系统权限隔离。