From 0c98fab4d555ed33837acbce564bc3a2d6546485 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 6 Sep 2026 19:19:42 +0800 Subject: [PATCH] =?UTF-8?q?pi:=20=E6=A1=A3=E4=BD=8D=E5=88=A4=E5=AE=9A=20to?= =?UTF-8?q?ol=5Fcall=20hook=20+=20=E5=BF=83=E8=B7=B3=E6=8A=A5=20native?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - worker.mjs: mailContext 加 permissionMode 字段(从 SSE payload 读入) - tool_call hook 增加档位判定: full → 不拦截任何工具(直接 return) plan → 被守卫工具(bash/write/edit)一律 block + 返回原因说明 workspace → 走原有问人流程(不变) - index.mjs 心跳上报 mode_enforcement: 'native' - 导入 normalizeMode/MODE_FULL/MODE_PLAN 从 lib/permission-mode.js - 377 测试全过 --- plugins/pi-mail-bridge/src/index.mjs | 1 + plugins/pi-mail-bridge/src/worker.mjs | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/plugins/pi-mail-bridge/src/index.mjs b/plugins/pi-mail-bridge/src/index.mjs index eb083ae..e38ff57 100644 --- a/plugins/pi-mail-bridge/src/index.mjs +++ b/plugins/pi-mail-bridge/src/index.mjs @@ -322,6 +322,7 @@ async function main() { const beat = async () => { const [platform_sessions, models] = await Promise.all([reportSessions(), reportModels()]); const body = {}; + body.mode_enforcement = 'native'; // tool_call hook 能 block bash/write/edit if (platform_sessions) body.platform_sessions = platform_sessions; if (models) body.models = models; try { diff --git a/plugins/pi-mail-bridge/src/worker.mjs b/plugins/pi-mail-bridge/src/worker.mjs index 60eb2af..75c1f27 100644 --- a/plugins/pi-mail-bridge/src/worker.mjs +++ b/plugins/pi-mail-bridge/src/worker.mjs @@ -54,6 +54,7 @@ import { explicitSends, shouldSkipAutoRelay } from '../lib/relay-dedup.js'; import { autoRelayDecision } from '../lib/relay-policy.js'; import { adoptedSessionID, adoptMissingMessage } from '../lib/adopt.js'; import { isApproval, isAlwaysDecision } from '../lib/permission-grants.js'; +import { normalizeMode, MODE_FULL, MODE_PLAN } from '../lib/permission-mode.js'; import { clampRelayKey, isPermanentFailure } from '../lib/relay-key.js'; // ─── 与主进程的通道 ─── @@ -90,7 +91,7 @@ let job = null; let client = null; let modelRuntime = null; let piSessionId = ''; -let mailContext = { replyTo: '', subject: '', mailID: '' }; +let mailContext = { replyTo: '', subject: '', mailID: '', permissionMode: 'workspace' }; let lastSyncedName = ''; let relayedKey = ''; let finished = false; @@ -110,6 +111,18 @@ function permissionExtension() { return (pi) => { pi.on('tool_call', async (event, ctx) => { + // ── 档位判定 ── + // plan: 被守卫的工具一律直接拒绝(该档语义是「不动手」,没什么可问人的) + // full: 不拦截(已声明全权) + // workspace: 走原有问人流程 + const mode = normalizeMode(mailContext.permissionMode); + if (mode === MODE_FULL) return; // full 档不拦任何工具 + if (mode === MODE_PLAN && GUARDED.has(event.toolName)) { + return { + block: true, + reason: `plan 档下不允许执行 ${event.toolName}。本档只允许读与查,请把方案写在回信里。如需动手请让发件人把档位改成 workspace。`, + }; + } if (!GUARDED.has(event.toolName)) return; const sid = ctx?.sessionManager?.getSessionId?.() || ''; @@ -559,6 +572,7 @@ process.on('message', (msg) => { replyTo: msg.data?.from_name || '', subject: msg.data?.subject || '', mailID: msg.data?.mail_id || '', + permissionMode: msg.data?.permission_mode || 'workspace', }; lastSyncedName = msg.lastSyncedName || ''; for (const t of msg.grants || []) grants.add(t);