feat(pi-permission-system): AutoReview 改为跟随主模型的对话式工具调用审查

AutoReview:

- 审查器回到 Codex Guardian 风格的对话式审查,默认跟随会话当前主模型
  (每次审查实时读取 ctx.model);provider/model 必须同时设置才固定审查
  模型,只设其一视为配置错误并转人工。
- 结论只通过 approve / reject 两个工具给出,参数仅 reason;不强制
  toolChoice,正文文本(即使是合法 JSON)一律忽略。
- approve 自动放行;reject 转人工并在弹窗显示理由(标注模型生成)。
  超时、Provider 错误、截断、未调用工具、多次调用、参数无效等异常同样
  转人工,并附固定的系统说明;用户按 Esc 中止时直接拒绝。
- 模型调用改为 modelRegistry.streamSimple(...).result():Pi 1.3.0 面向
  扩展的 ModelRegistry 没有 completeSimple,原写法在真实环境中每次都会
  转人工。
- 对话记录保留可信度标注;工具结果在有更短的 pi-compaction 摘要时替换
  为摘要;token 估算改为非 ASCII 字符按 1 token 计;预算可配置。
- 同一条用户消息内完全相同的请求只缓存 approve 结论。
- 默认 reasoning low、总超时 30 秒、最多 2 次尝试、maxOutputTokens 4000。

删除:

- 未发布过的 TypeSafe Jev 分类器实现、DenialCircuitBreaker。
- 整个 annotator 机制(model-explanation、注册表、registerAnnotator、
  provider-timeout);annotators 配置键保留为已弃用并忽略,避免旧配置
  被拒绝。

测试:

- 新增 reviewer / transcript / 弹窗说明 / 默认配置测试;清理重复、恒真
  或只测实现细节的旧测试。
- 使用 deepseek-v4.1-flash 实测 5 个场景各 5 次,25 次结论全部符合预期。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
云服务部-叶林立
2026-10-19 01:54:17 -07:00
co-authored by Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent dec48dab6d
commit 7e6a8faae1
87 changed files with 2831 additions and 2658 deletions
@@ -53,23 +53,14 @@ const normalizer = new PathNormalizer(posixPathFlavor, "/test/project");
// ── tests ──────────────────────────────────────────────────────────────────
describe("describeToolGate", () => {
it("returns descriptor with tool name as surface for standard tools", () => {
const desc = describeToolGate(
makeTcc({ toolName: "read" }),
makeCheckResult("ask"),
makeFormatter(),
);
expect(desc.surface).toBe("read");
expect(desc.decision.surface).toBe("read");
});
it("returns descriptor with tool name as decision value for standard tools", () => {
it("gates a standard tool on its own surface and name", () => {
const desc = describeToolGate(
makeTcc({ toolName: "write" }),
makeCheckResult("ask"),
makeFormatter(),
);
expect(desc.decision.value).toBe("write");
expect(desc.surface).toBe("write");
expect(desc.decision).toEqual({ surface: "write", value: "write" });
});
it("returns bash surface with command in decision.value for bash tools", () => {
@@ -85,6 +76,7 @@ describe("describeToolGate", () => {
expect(desc.surface).toBe("bash");
expect(desc.decision.surface).toBe("bash");
expect(desc.decision.value).toBe("git status");
expect(desc.payload.request.value).toBe("git status");
});
it("gates an aliased shell tool on the bash surface while keeping its tool name in logs (#574)", () => {
@@ -177,42 +169,6 @@ describe("describeToolGate", () => {
expect(desc.payload.request.requester.agentName).toBeNull();
});
it("names the requesting agent on the payload when provided", () => {
const check = makeCheckResult("ask", { toolName: "read" });
const desc = describeToolGate(
makeTcc({ agentName: "my-agent" }),
check,
makeFormatter(),
);
expect(desc.payload.request.requester.agentName).toBe("my-agent");
});
it("carries the command as the decision-relevant value for a bash ask", () => {
const check = makeCheckResult("ask", { toolName: "bash", command: "ls" });
const desc = describeToolGate(
makeTcc({ toolName: "bash", input: { command: "ls" } }),
check,
makeFormatter(),
);
expect(desc.payload.kind).toBe("bash");
expect(desc.payload.request.value).toBe("ls");
});
it("populates sessionApproval via suggestSessionPattern", () => {
const check = makeCheckResult("ask", {
toolName: "bash",
command: "git status",
});
const desc = describeToolGate(
makeTcc({ toolName: "bash", input: { command: "git status" } }),
check,
makeFormatter(),
);
expect(desc.sessionApproval).toBeDefined();
expect(desc.sessionApproval?.surface).toBe("bash");
expect(desc.sessionApproval?.representativePattern).toBeDefined();
});
it("binds a current-directory file's session approval to the cwd subtree", () => {
const check = makeCheckResult("ask", { toolName: "edit" });
const desc = describeToolGate(
@@ -275,7 +231,6 @@ describe("describeToolGate", () => {
toolCallId: "tc-42",
toolName: "read",
});
expect(desc.promptDetails.sessionLabel).toBeDefined();
});
it("carries the AccessPath's facts on promptDetails for a path-bearing tool", () => {
@@ -328,15 +283,4 @@ describe("describeToolGate", () => {
});
expect(desc.logContext.command).toBe("ls");
});
it("uses toolName as input for checkPermission surface", () => {
const desc = describeToolGate(
makeTcc({ toolName: "edit", input: { path: "/a.ts" } }),
makeCheckResult("ask", { toolName: "edit" }),
makeFormatter(),
normalizer.forPath("/a.ts"),
);
expect(desc.surface).toBe("edit");
expect(desc.input).toEqual({ path: "/a.ts" });
});
});