pi-verdict 0.12.1 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  **pi-verdict is a minimal permission gate for [pi](https://pi.dev), inspired by Claude Code's auto mode: every tool call gets checked before it runs — allow, deny, or ask you first.**
10
10
 
11
- - Minimal — a ~2k-line single-file core (plus a small bundled jev adapter)
11
+ - Minimal — a ~2k-line single-file core (the classifier rides pi's native `classify()` since 0.13)
12
12
  - Built-in danger rules and your own allow/deny rules settle the clear cases first, at zero latency
13
13
  - Everything else goes to a model classifier that sees the conversation context
14
14
  - Any uncertainty or failure fails closed; nothing ever runs silently
@@ -58,13 +58,13 @@ pi --extension ./extensions/pi-verdict.ts
58
58
 
59
59
  ```
60
60
 
61
- Requires pi ≥ 0.84. Works in interactive and non-interactive (`-p`/json/rpc) sessions; in non-interactive modes `ask` degrades to `deny`.
61
+ Requires pi ≥ 0.99. Works in interactive and non-interactive (`-p`/json/rpc) sessions; in non-interactive modes `ask` degrades to `deny`.
62
62
 
63
63
  ### Hosts
64
64
 
65
- pi-verdict runs on both [pi](https://github.com/badlogic/pi-mono) and [oh-my-pi](https://github.com/can1357/oh-my-pi) (omp) — it self-anchors to whichever agent tree it is installed in, and follows the extension copy's own location on dual-install machines. On omp 18 the classifier's completion call falls back to the pi-ai compat API (still fail-closed). Details: [docs/configuration.md](docs/configuration.md#host-notes-pi-and-oh-my-pi).
65
+ pi-verdict 0.13+ requires **pi ≥ 0.99** and runs on pi only (native classifier support, [ADR-0005](docs/adr/0005-native-classifier-migration.md)). Older hosts — pi < 0.99 and [oh-my-pi](https://github.com/can1357/oh-my-pi) (omp) — keep using the **0.12.x** line from npm (old hosts run old extensions). The 0.12 line still self-anchors to whichever agent tree it is installed in and follows the extension copy's own location on dual-install machines; its classifier completion falls back to the pi-ai compat API on omp 18 (still fail-closed). Details: [docs/configuration.md](docs/configuration.md#host-notes-pi-and-oh-my-pi).
66
66
 
67
- | | pi | omp |
67
+ | | pi | omp (0.12.x line) |
68
68
  |---|---|---|
69
69
  | install | `pi install npm:pi-verdict` | `omp plugin install npm:pi-verdict` |
70
70
  | extension copy | `~/.pi/agent/extensions/` | `~/.omp/plugins/node_modules/pi-verdict/` (omp 18.1+; ≤18.0: under `agent/`) |
@@ -119,33 +119,45 @@ pi-verdict runs on both [pi](https://github.com/badlogic/pi-mono) and [oh-my-pi]
119
119
 
120
120
  - `allow`/`deny` are JS regex arrays; **`deny` wins over `allow`**, both beat the classifier
121
121
  - `denyPaths` are plain paths you declare **protected** — touches trigger a terminal ask you adjudicate (non-interactive → deny); the classifier never learns the paths themselves, only that they exist. `grep`/`find`/`ls` compare their whole **search scope**: an omitted `path` (pi's default: the current directory) or a parent directory of a declared path triggers the ask as well. A fresh install pre-fills a **starter list** (`~/.ssh/`, `~/.gnupg`, `~/.mc`, shell rc/profile files)
122
- - `ignoreTools` names uncovered tools (`todo`, `web_search`, MCP/custom tools) that skip adjudication — **allow with zero model calls**; entries naming covered tools (`bash`/`read`/`write`/`edit`/`grep`/`find`/`ls`/`powershell`) are inert: those stay governed by the deny floor and your allow/deny rules, and the self-protection layer always runs first. A fresh install pre-fills a **starter list** (`todo`, `ask_user_question`, `memory_write`, `memory_search` — observed harmless across the 1265-verdict production audit). Caveat: an exempted tool loses the classifier's `denyPaths` existence-hint vigilance (uncovered tools never hit the path extractor anyway)
122
+ - `ignoreTools` names uncovered tools (`todo`, `web_search`, MCP/custom tools) that skip adjudication — **allow with zero model calls**; entries naming covered tools (`bash`/`read`/`write`/`edit`/`grep`/`find`/`ls`/`powershell`) are inert: those stay governed by the deny floor and your allow/deny rules, and the self-protection layer always runs first. A fresh install pre-fills a **starter list** (`todo`, `ask_user_question`, `memory_write`, `memory_search` — observed harmless across the 1265-verdict production audit). Caveat: an exempted tool loses the classifier's `denyPaths` existence-hint vigilance (uncovered tools never hit the path extractor anyway); MCP tool names are normalized before matching — see [codemode & MCP](#pi-099-codemode--mcp-indirect-calls-are-still-gated)
123
123
  - `builtinDenyFloor: false` turns off the built-in danger/path floor (your risk; the self-protection layer below always stays on)
124
124
  - `classifierModel` pins the classifier model, e.g. `"zai/glm-5.3-flash:low"` (thinking suffix supported; default: session model with thinking off)
125
- - `classifierModel: "typesafe/jev-latest"` opts into the bundled **jev decisions adapter** — gray-zone verdicts via TypeSafe's jev (OpenRouter by default, or TypeSafe's official API directly with `PI_VERDICT_JEV_TRANSPORT=typesafe`); experimental, see [ADR-0003](docs/adr/0003-jev-decisions-adapter.md)
125
+ - `classifierModel: "typesafe/jev-latest"` opts into the **native jev classifier** — one structured `classify()` call per gray-zone verdict via pi's built-in classifier catalog (TypeSafe direct, or Jev on OpenRouter/OpenCode/Cloudflare/Vercel); see [ADR-0005](docs/adr/0005-native-classifier-migration.md)
126
126
  - `audit: true` records every **gray-zone adjudication** (the full transcript sent to the classifier, its raw response, the parsed verdict) as JSONL under `~/.pi/agent/verdicts/<sessionId>.jsonl` — one file per session, the 20 most recent kept. Interactive asks also record your answer (`userAnswer` ground truth, written after the confirm resolves), and protected-path asks are recorded too (#62); rule allow/deny stays unaudited. Local-only and full-fidelity (protected-path plaintext may appear — it never leaves your machine; [ADR-0002](docs/adr/0002-deny-paths-deterministic-ask.md) boundary note); the agent can neither read nor write the directory. `/automode` shows the audit state and path while on
127
127
  - `notifyAllows: true` notifies on every **classifier allow** (reason + action line — e.g. jev's probability breakdown); default `false` keeps passes silent. Mechanical passes (your own allow rules, protected-path confirms) never notify; with both switches on the notification appears once
128
- - `classifierMinConfidence` (optional, [ADR-0004](docs/adr/0004-classifier-fallback-cascade.md)) sets the **confidence floor**: a jev verdict below it is demoted — cascaded to `classifierFallbackModel` if set (`enforce`, the default = the second layer adjudicates; a demoted **deny or ask** can never be auto-relaxed to an allow; a fail-closed layer emitted no verdict, so its rescue stands; `shadow` = records its opinion only and you are asked — `/automode` hints the activation switch), otherwise asked of you directly. At/above the floor the first layer is autonomous. The floor applies to decisions models only — with an LLM classifier it is inert, and a one-time warning says so. A natural pairing: jev first + a haiku/flash-class fallback
128
+ - `classifierMinConfidence` (optional, [ADR-0004](docs/adr/0004-classifier-fallback-cascade.md)) sets the **confidence floor**: a native-classifier verdict below it is demoted — cascaded to `classifierFallbackModel` if set (`enforce`, the default = the second layer adjudicates; a demoted **deny or ask** can never be auto-relaxed to an allow; a fail-closed layer emitted no verdict, so its rescue stands; `shadow` = records its opinion only and you are asked — `/automode` hints the activation switch), otherwise asked of you directly. At/above the floor the first layer is autonomous. The floor applies to native classifier models only (protocol-native confidence) — with a chat/LLM classifier it is inert, and a one-time warning says so. A natural pairing: jev first + a haiku/flash-class fallback
129
129
 
130
130
  No built-in allowlist — every "always allow" claim is yours ([why](docs/configuration.md#why-no-built-in-allowlist)). Full reference: [docs/configuration.md](docs/configuration.md).
131
131
 
132
- ### Jev decisions backend (experimental — [ADR-0003](docs/adr/0003-jev-decisions-adapter.md))
132
+ ### Native jev classifier ([ADR-0005](docs/adr/0005-native-classifier-migration.md))
133
133
 
134
- 1. Install a version that ships the adapter (v0.8+): `pi install npm:pi-verdict`
135
- 2. Pick a transport (both serve the same decisions wire contract):
136
- - **OpenRouter (default)**: run `/login openrouter` inside pi, or `export OPENROUTER_API_KEY=sk-or-v1...` in your shell
137
- - **TypeSafe direct (official v1 API)**: grab a self-service key at console.typesafe.ai, then `export TYPESAFE_API_KEY=apikey_...` and `export PI_VERDICT_JEV_TRANSPORT=typesafe`
134
+ 1. Install: `pi install npm:pi-verdict` (v0.13+; pi ≥ 0.99 required — older hosts keep 0.12.x)
135
+ 2. Ensure a credential for one of the built-in Jev transports:
136
+ - **TypeSafe direct** (`typesafe/jev-latest`): `export TYPESAFE_API_KEY=apikey_...` (self-service at console.typesafe.ai)
137
+ - **OpenRouter** (`openrouter/~typesafe/jev-latest`, `openrouter/typesafe/jev-1.13`): `/login openrouter` inside pi, or `export OPENROUTER_API_KEY=sk-or-v1...`
138
+ - also served on OpenCode Zen, Cloudflare Workers AI, and Vercel AI Gateway with each provider's login; llama.cpp chat models double as free local classifiers (`model.type: "classifier"` siblings)
138
139
  3. Point the classifier at jev (applies to new sessions)
139
140
  - persistent: edit `~/.pi/agent/config/pi-verdict.json` outside pi and set `{ "classifierModel": "typesafe/jev-latest" }`
140
141
  - or try it once: `PI_AUTO_MODE_MODEL=typesafe/jev-latest pi`
141
142
 
142
- **Limits**:
143
- - **Transports**: OpenRouter decisions (default) or TypeSafe direct — on the TypeSafe transport per-call cost shows $0 (its API does not report it)
144
- - **Hosts**: pi only. On omp the setting warns and falls back to the session model; and it must never be selected as the session model (no text generation — selecting it warns)
145
- - **Escape hatch**: `PI_VERDICT_JEV_URL` overrides the active transport's endpoint (OpenRouter's is an alpha API)
143
+ **Notes**:
144
+ - Verdicts are structured `classify()` answers (choice + probabilities + confidence); the reason line keeps the historical `jev:` probability breakdown (tag-free — the `<verdict>` prefix survives only in the audit record's rawResponse), other classifier APIs render `classifier:`
145
+ - Classifier specs resolve through pi's classifier catalog first (`findOfType`), chat registry second; on same-id dual listings (llama.cpp) the native entry wins; thinking suffixes on a classifier spec warn once and drop (recorded `thinking: null`)
146
+ - Custom endpoints: override the provider's `baseUrl` in models.json (the 0.12 `PI_VERDICT_JEV_URL` escape hatch is gone, as is `PI_VERDICT_JEV_TRANSPORT` — transport choice is now the spec itself)
147
+ - Carried-over limit: the denyPaths existence hint still does not reach classifier-typed models ([ADR-0005](docs/adr/0005-native-classifier-migration.md)); on the TypeSafe direct transport per-call cost shows $0 (its API does not report it)
146
148
 
147
149
  jev's calibrated confidence is exactly what the confidence floor keys on — pair it with a second layer (`"classifierMinConfidence", "classifierFallbackModel"`) so its low-confidence calls go to a deeper model instead of standing ([ADR-0004](docs/adr/0004-classifier-fallback-cascade.md)).
148
150
 
151
+ ### pi 0.99 codemode & MCP: indirect calls are still gated
152
+
153
+ pi 0.99 can run model-written JavaScript in a QuickJS sandbox (`codemode`) that calls pi's tools, and MCP servers register tools as `mcp__<server>__<tool>`. Neither surface bypasses this gate:
154
+
155
+ - **Nested calls are gated exactly like direct ones** — pi routes every tool call a codemode script makes through the same `tool_call` pipeline (tagged `parentToolCallId`, ids `<parent>/<n>`); a blocked call returns as an error to the script, which the model sees
156
+ - **MCP tools land in the gray zone** — the rule layer covers the built-in command/file tools only; each `mcp__*` call is classified, fail-closed included
157
+ - **`ignoreTools` and MCP names**: tool names are normalized — every character outside `[A-Za-z0-9_]` becomes `_` (`mcp__dev-radius__x` → `mcp__dev_radius__x`); exemption entries must use the normalized form
158
+ - **Cost amplification**: one script may issue up to 256 nested calls; gray-zone calls classify one by one, so a slow LLM classifier multiplies per-call latency
159
+ - **Exposure boundary**: adding an MCP server auto-enables codemode, and `pi --no-extensions -e builtin:mcp` runs MCP tools with no extensions loaded — i.e. without this gate. The gate is itself an extension, so it cannot be active in a session that loads none; the boundary is inherent to pi's extension model, stated here rather than papered over
160
+
149
161
  ### Self-protection (the gate guards itself — [ADR-0001](docs/adr/0001-self-protection-layer.md))
150
162
 
151
163
  The gate's own files — the config and the installed extension copy — are **user-editable only**: writes from inside the gate hard-deny (reads pass); your editor never passes through the gate, the sudoers/visudo precedent.
@@ -193,7 +205,9 @@ tool_call
193
205
  ├─ 2. Gray zone → model classifier (defaults to session model — "self-reflection")
194
206
  │ ├─ input: CC-style <transcript> — recent user intent + tool calls,
195
207
  │ │ action under review always last
196
- │ └─ output contract: <verdict>allow|ask|deny</verdict> prefix-anchored
208
+ │ └─ output: native classify() answers (choice + probabilities + confidence;
209
+ │ tag-free reason, full contract line in the audit rawResponse) or,
210
+ │ on the chat path, <verdict>…</verdict> prefix-anchored free text
197
211
  │
198
212
  └─ 3. Three-state adjudication
199
213
  ├─ allow → pass
@@ -221,6 +235,7 @@ Design decisions here are settled by measurement, and the lab notes ship with th
221
235
  - no built-in allowlist by design (see the [bypass writeup](research/rule-layer-security-audit.md)); with an empty `allow` config most commands go to the classifier — point `--auto-mode-model` at a fast model if per-call latency matters
222
236
  - the path sensitivity floor applies to file tools only: bash command strings are matched by the danger regexes alone, so e.g. `cat ~/.ssh/id_rsa` goes to the classifier rather than the deterministic S0 deny (the file-tool spelling `read ~/.ssh/id_rsa` does deny)
223
237
  - on Windows the built-in floor covers bash-shaped patterns only — PowerShell-native dangerous commands (`Remove-Item -Recurse -Force`, `Invoke-Expression`, `Set-ExecutionPolicy`, …) rely on the classifier (fail-closed)
238
+ - on macOS the per-user temp tree (`$TMPDIR`, the `/var/folders/…/T` confstr dir) is exempt from the system-directory floor: reads allow at zero cost, writes adjudicate as ordinary outside-project writes (classifier). The exemption anchors to the runtime-resolved confstr family — a hand-set `TMPDIR` lifts nothing — and `/var/tmp` (POSIX shared temp) stays denied; symlink spellings whose real form escapes the temp tree still hit S1
224
239
  - AGENTS.md is not passed to the classifier as downweighted intent evidence (Claude Code does this)
225
240
  - parallel gray-zone calls are adjudicated serially
226
241
  - self-reflection means the session model adjudicates — point `--auto-mode-model` at a lighter model if verdict latency/cost matters (open question tracked in the issue tracker)
package/README.zh-CN.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  **pi-verdict 是 [pi](https://pi.dev) 的极简权限门禁,灵感来自 Claude Code 的 auto mode:每次工具调用执行前先过检查——放行、拦截,或先问你。**
10
10
 
11
- - 极简——核心单文件约 2k 行(另含一个小型 jev 适配器)
11
+ - 极简——核心单文件约 2k 行(0.13 起分类器走 pi 原生 `classify()`)
12
12
  - 内置危险规则与你的 allow/deny 规则以零延迟先行裁决明确情形
13
13
  - 其余交给携带会话上下文的模型分类器
14
14
  - 任何不确定或失败一律 fail-closed,绝不静默放行
@@ -58,13 +58,13 @@ pi --extension ./extensions/pi-verdict.ts
58
58
 
59
59
  ```
60
60
 
61
- 需要 pi ≥ 0.84。交互与非交互(`-p`/json/rpc)会话均支持;非交互模式下 `ask` 降级为 `deny`。
61
+ 需要 pi ≥ 0.99。交互与非交互(`-p`/json/rpc)会话均支持;非交互模式下 `ask` 降级为 `deny`。
62
62
 
63
63
  ### 宿主
64
64
 
65
- pi-verdict 同时支持 [pi](https://github.com/badlogic/pi-mono) 与 [oh-my-pi](https://github.com/can1357/oh-my-pi)(omp)——扩展按自身安装位置自锚定到所在宿主的目录树,双宿主并存的机器上跟随扩展副本自身的位置。omp 18 下分类器的模型调用回退到 pi-ai compat API(仍然 fail-closed)。细节见 [docs/configuration.md](docs/configuration.md#host-notes-pi-and-oh-my-pi)。
65
+ pi-verdict 0.13+ 需 **pi ≥ 0.99**,仅支持 pi(原生分类器接入,[ADR-0005](docs/adr/0005-native-classifier-migration.md))。老宿主——pi < 0.99 与 [oh-my-pi](https://github.com/can1357/oh-my-pi)(omp)——继续使用 npm 上的 **0.12.x** 线(老宿主配老版本扩展)。0.12 线仍按自身安装位置自锚定到所在宿主的目录树,双宿主并存的机器上跟随扩展副本自身的位置;其在 omp 18 下的分类器模型调用回退 pi-ai compat API(仍然 fail-closed)。细节见 [docs/configuration.md](docs/configuration.md#host-notes-pi-and-oh-my-pi)。
66
66
 
67
- | | pi | omp |
67
+ | | pi | omp(0.12.x 线) |
68
68
  |---|---|---|
69
69
  | 安装 | `pi install npm:pi-verdict` | `omp plugin install npm:pi-verdict` |
70
70
  | 扩展副本 | `~/.pi/agent/extensions/` | `~/.omp/plugins/node_modules/pi-verdict/`(omp 18.1+;≤18.0 在 `agent/` 下) |
@@ -119,33 +119,45 @@ pi-verdict 同时支持 [pi](https://github.com/badlogic/pi-mono) 与 [oh-my-pi]
119
119
 
120
120
  - `allow`/`deny` 为 JS 正则数组;**`deny` 优先于 `allow`**,两者都优先于分类器
121
121
  - `denyPaths` 是你声明**受保护**的普通路径列表:触碰触发**终局 ask** 由你裁决(非交互降级 deny);分类器只被告知路径**存在**,路径明文永不出本机。`grep`/`find`/`ls` 按**整个搜索范围**比较:省略 `path`(pi 默认:当前目录)或传入位于声明路径之上的父目录,同样触发 ask。全新安装会预填一份**入门列表**(`~/.ssh/`、`~/.gnupg`、`~/.mc`、shell rc/profile 文件)
122
- - `ignoreTools` 列出规则未覆盖的工具(`todo`、`web_search`、MCP/自定义工具):**直接放行、零模型调用**;列出已覆盖工具(`bash`/`read`/`write`/`edit`/`grep`/`find`/`ls`/`powershell`)的条目无效:它们仍受 deny floor 与你的 allow/deny 规则约束,自保护层也永远先行。全新安装会预填一份**入门列表**(`todo`、`ask_user_question`、`memory_write`、`memory_search`——来自项目 1265 条生产审计的观察)。注意:被豁免的工具失去分类器对 `denyPaths` 的存在性话术警戒(未覆盖工具本就不进路径提取器)
122
+ - `ignoreTools` 列出规则未覆盖的工具(`todo`、`web_search`、MCP/自定义工具):**直接放行、零模型调用**;列出已覆盖工具(`bash`/`read`/`write`/`edit`/`grep`/`find`/`ls`/`powershell`)的条目无效:它们仍受 deny floor 与你的 allow/deny 规则约束,自保护层也永远先行。全新安装会预填一份**入门列表**(`todo`、`ask_user_question`、`memory_write`、`memory_search`——来自项目 1265 条生产审计的观察)。注意:被豁免的工具失去分类器对 `denyPaths` 的存在性话术警戒(未覆盖工具本就不进路径提取器);MCP 工具名匹配前会归一化——见 [codemode 与 MCP](#pi-099-codemode-与-mcp间接调用同样受门禁)
123
123
  - `builtinDenyFloor: false` 整体关闭内置危险/路径拦截(风险自担;下方自保护层永远开启)
124
124
  - `classifierModel` 指定分类器模型,如 `"zai/glm-5.3-flash:low"`(支持思考后缀;缺省 = 会话模型且显式关思考)
125
- - `classifierModel: "typesafe/jev-latest"` 启用随包的 **jev 决策适配器**——灰区裁决经 TypeSafe jev 完成(默认 OpenRouter,或 `PI_VERDICT_JEV_TRANSPORT=typesafe` 直连官方 API);实验性质,详见 [ADR-0003](docs/adr/0003-jev-decisions-adapter.md)
125
+ - `classifierModel: "typesafe/jev-latest"` 启用**原生 jev 分类器**——每次灰区裁决经 pi 内置分类器目录发一次结构化 `classify()` 调用(TypeSafe 直连,或 OpenRouter/OpenCode/Cloudflare/Vercel 上的 Jev);详见 [ADR-0005](docs/adr/0005-native-classifier-migration.md)
126
126
  - `audit: true` 把每次**灰区裁决**(发给分类器的完整转录、其原始响应、解析出的裁决)以 JSONL 记录到 `~/.pi/agent/verdicts/<sessionId>.jsonl`——按会话一分文件,保留最近 20 个。交互式 ask 还会记录你的应答(`userAnswer` ground truth,确认结束后落盘),protected-path ask 也入审计(#62);规则 allow/deny 仍不入。仅存本机且全保真(受保护路径明文可能出现——永不出本机;[ADR-0002](docs/adr/0002-deny-paths-deterministic-ask.md) 边界注);agent 对该目录读写双拒。开启时 `/automode` 会显示审计状态与路径
127
127
  - `notifyAllows: true` 对每次 **classifier 放行**发通知(reason + action 行——如 jev 的概率分解);默认 `false` 保持放行静默。机械放行(你自己的 allow 规则、protected-path 确认)永不通知;两开关同开时通知只出现一次
128
- - `classifierMinConfidence`(可选,[ADR-0004](docs/adr/0004-classifier-fallback-cascade.md))设定**置信地板**:低于它的 jev 裁决被降级——配置了 `classifierFallbackModel` 则级联(`enforce`,默认 = 第二层全权裁决;例外:降级的 **deny 与 ask** 永不被自动放宽为 allow;fail-closed 未产生裁决,其获救裁决照常生效;`shadow` = 只记录意见、由你裁决——`/automode` 会提示激活开关),否则直接问你。不低于地板时第一层自主。地板仅作用于 decisions 模型——LLM 分类器下不生效,会有一次中性警告提示。天然搭配:jev 在前 + haiku/flash 级回退
128
+ - `classifierMinConfidence`(可选,[ADR-0004](docs/adr/0004-classifier-fallback-cascade.md))设定**置信地板**:低于它的原生分类器裁决被降级——配置了 `classifierFallbackModel` 则级联(`enforce`,默认 = 第二层全权裁决;例外:降级的 **deny 与 ask** 永不被自动放宽为 allow;fail-closed 未产生裁决,其获救裁决照常生效;`shadow` = 只记录意见、由你裁决——`/automode` 会提示激活开关),否则直接问你。不低于地板时第一层自主。地板仅作用于原生分类器模型(协议原生置信度)——chat/LLM 分类器下不生效,会有一次中性警告提示。天然搭配:jev 在前 + haiku/flash 级回退
129
129
 
130
130
  没有内置白名单——每一条「永远放行」声明都归你([为什么](docs/configuration.md#why-no-built-in-allowlist))。完整参考:[docs/configuration.md](docs/configuration.md)。
131
131
 
132
- ### Jev 决策后端(实验性——[ADR-0003](docs/adr/0003-jev-decisions-adapter.md))
132
+ ### 原生 jev 分类器([ADR-0005](docs/adr/0005-native-classifier-migration.md))
133
133
 
134
- 1. 安装含适配器的版本(v0.8 及以上):`pi install npm:pi-verdict`
135
- 2. 选一条 transport(两条走同一 decisions wire 契约):
136
- - **OpenRouter(默认)**:pi 内执行 `/login openrouter`,或 shell 里 `export OPENROUTER_API_KEY=sk-or-v1...`
137
- - **TypeSafe 直连(官方 v1 API)**:在 console.typesafe.ai 自助发 key,然后 `export TYPESAFE_API_KEY=apikey_...` 并 `export PI_VERDICT_JEV_TRANSPORT=typesafe`
134
+ 1. 安装:`pi install npm:pi-verdict`(v0.13+;需 pi ≥ 0.99——老宿主继续用 0.12.x)
135
+ 2. 为任一内置 Jev transport 准备好凭证:
136
+ - **TypeSafe 直连**(`typesafe/jev-latest`):`export TYPESAFE_API_KEY=apikey_...`(console.typesafe.ai 自助发 key)
137
+ - **OpenRouter**(`openrouter/~typesafe/jev-latest`、`openrouter/typesafe/jev-1.13`):pi 内 `/login openrouter`,或 `export OPENROUTER_API_KEY=sk-or-v1...`
138
+ - 亦经 OpenCode Zen、Cloudflare Workers AI、Vercel AI Gateway 提供(各自登录);llama.cpp chat 模型自带免费本地分类器形态(同 id 的 `model.type: "classifier"` 孪生条目)
138
139
  3. 将分类器指向 jev(新会话生效)
139
140
  - 持久:在 pi 之外编辑 `~/.pi/agent/config/pi-verdict.json` 并设置 `{ "classifierModel": "typesafe/jev-latest" }`
140
- - 或者临时试用一次:`PI_AUTO_MODE_MODEL=typesafe/jev-latest pi`
141
+ - 或者临时试用一次:`PI_AUTO_MODE_MODEL=typesafe/jev-latest pi`
141
142
 
142
- **限制**:
143
- - **Transport**:OpenRouter decisions(默认)或 TypeSafe 直连——TypeSafe 侧单次成本显示 $0(其 API 不返回 cost)
144
- - **宿主**:仅支持 pi。omp 上该设置会警告并回退会话模型。也绝不能选作会话主模型(不生成文本,选中即警告)
145
- - **逃生口**:`PI_VERDICT_JEV_URL` 可覆盖当前 transport 的端点(OpenRouter 侧为 alpha 接口)
143
+ **说明**:
144
+ - 裁决为结构化 `classify()` 应答(choice + probabilities + confidence);reason 行保留历史 `jev:` 概率分解(无标签——`<verdict>` 前缀只存活于审计记录的 rawResponse),其余分类器 API 渲染 `classifier:`
145
+ - 分类器 spec 先经 pi 分类器目录解析(`findOfType`)、chat 注册表兜底;同 id 双型并存(llama.cpp)时原生条目优先;分类器 spec 上的思考后缀警告一次后丢弃(审计 `thinking` 记为 `null`)
146
+ - 自定义端点:在 models.json 覆盖 provider 的 `baseUrl`(0.12 的 `PI_VERDICT_JEV_URL` 逃生口与 `PI_VERDICT_JEV_TRANSPORT` 均已移除——transport 选择即 spec 本身)
147
+ - 沿袭限制:denyPaths 存在性话术仍不达分类器形态模型([ADR-0005](docs/adr/0005-native-classifier-migration.md));TypeSafe 直连的单次成本显示 $0(其 API 不返回 cost)
146
148
 
147
149
  jev 的校准 confidence 正是置信地板的判定依据——搭配第二层使用(`"classifierMinConfidence", "classifierFallbackModel"`),让低置信调用交由更深的模型复裁,而非就地生效([ADR-0004](docs/adr/0004-classifier-fallback-cascade.md))。
148
150
 
151
+ ### pi 0.99 codemode 与 MCP:间接调用同样受门禁
152
+
153
+ pi 0.99 可以在 QuickJS 沙箱(`codemode`)里运行模型写的 JavaScript 去调用 pi 的工具,MCP 服务器则以 `mcp__<server>__<tool>` 注册工具。这两个新增面都不会绕过本门禁:
154
+
155
+ - **嵌套调用与直接调用同样过门**——pi 把 codemode 脚本发起的每一次工具调用都路由到同一条 `tool_call` 管线(带 `parentToolCallId`,id 形如 `<父id>/<n>`);被拦截的调用以错误形式回传脚本,模型可见
156
+ - **MCP 工具落入灰区**——规则层只覆盖内置命令/文件工具;每次 `mcp__*` 调用都走分类器,含 fail-closed
157
+ - **`ignoreTools` 与 MCP 工具名**:工具名会归一化——`[A-Za-z0-9_]` 之外的字符统一变 `_`(`mcp__dev-radius__x` → `mcp__dev_radius__x`);豁免条目必须写归一化后的形态
158
+ - **成本放大**:单个脚本最多可发 256 次嵌套调用,灰区调用逐个分类,慢的 LLM 分类器会把单次延迟成倍放大
159
+ - **暴露边界**:添加 MCP 服务器会自动开启 codemode,而 `pi --no-extensions -e builtin:mcp` 可在不加载任何扩展(即无本门禁)的情况下启用 MCP 工具。门禁自身即扩展,在完全不加载扩展的会话中无法生效;此边界为 pi 扩展模型的固有属性,此处显式陈述而非掩饰
160
+
149
161
  ### 自保护(门禁守护自身——[ADR-0001](docs/adr/0001-self-protection-layer.md))
150
162
 
151
163
  门禁自身的文件——配置与扩展安装副本——**仅用户可改**:门禁之内的写入一律硬 deny(读放行);你的编辑器修改不经门禁,同类先例是 sudoers 必须经 visudo。
@@ -221,6 +233,7 @@ tool_call
221
233
  - 设计上无内置白名单(见[绕过测试](research/rule-layer-security-audit.md)与[用户自定义规则](#用户自定义规则pi-verdictjson));allow 配置为空时大多数命令进分类器 —— 延迟敏感可 `--auto-mode-model` 指向轻量模型
222
234
  - 路径敏感度 floor 只作用于文件类工具:bash 命令串仅匹配危险正则——`cat ~/.ssh/id_rsa` 走分类器而非确定性 S0 拦截(文件工具拼写 `read ~/.ssh/id_rsa` 会拦截)
223
235
  - Windows 下内置 floor 仅覆盖 bash 形态模式——PowerShell 原生危险命令(`Remove-Item -Recurse -Force`、`Invoke-Expression`、`Set-ExecutionPolicy` 等)依赖分类器兜底(fail-closed)
236
+ - macOS 下 per-user 临时目录(`$TMPDIR`,`/var/folders/…/T` confstr 目录)豁免于系统目录 floor:读零成本放行,写按普通项目外写交分类器裁决。豁免锚定运行时解析的 confstr 族——手工设置 `TMPDIR` 不会解除任何保护;`/var/tmp`(POSIX 共享临时目录)维持拦截;真实形态逃逸临时树的符号链接拼写仍命中 S1
224
237
  - AGENTS.md 未作为降权意图证据传入分类器(Claude Code 有此设计)
225
238
  - 并行灰区调用串行裁决
226
239
  - 自省意味着会话模型自身裁决 —— 若延迟/成本敏感,用 `--auto-mode-model` 指向轻量模型(开放问题见 issue tracker)