From af61a37d9a81c3d8f6c7b3073c74e59352bf5246 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 6 Sep 2026 19:10:35 +0800 Subject: [PATCH] =?UTF-8?q?dsh:=20=E6=9D=83=E9=99=90=E6=A1=A3=E4=BD=8D?= =?UTF-8?q?=E6=8E=A5=E7=BA=BF=20=E2=80=94=20presets.mount=20=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E5=B7=A5=E5=85=B7=20+=20=E4=B8=89=E6=A1=A3=20sandbox/?= =?UTF-8?q?approval=20=E6=98=A0=E5=B0=84=20+=20=E5=BF=83=E8=B7=B3=E6=8A=A5?= =?UTF-8?q?=20native?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L3 dsh 适配: - startAgent 新建会话时用 agentPresets.mount('standard') 注册 bash/fs/fs-search 等工具 (与 dsh-a2a 同一套 API,经 a2a server 实测可靠) - 新增 applyPermissionMode(session, mode): plan → read-only + ask(只读,越界转邮件) workspace → workspace-write + ask(目录内可写) full → danger-full-access + never(完全放开) 直接 session.append sandbox/mode + approval/policy 事件(与 permissionPresets.set 同底层) - deliverMail 两条投递路径(新建 + 接管)加 applyPermissionMode 调用 - 已有 session 路径不动:档位在首次投递时已设,后续邮件不改 - 心跳上报 mode_enforcement: 'native'(dsh 有真沙箱,不是 advisory) - 323 测试全过 --- plugins/dsh-mail-bridge/src/index.ts | 64 +++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/plugins/dsh-mail-bridge/src/index.ts b/plugins/dsh-mail-bridge/src/index.ts index 27fea8f..ed58966 100644 --- a/plugins/dsh-mail-bridge/src/index.ts +++ b/plugins/dsh-mail-bridge/src/index.ts @@ -421,6 +421,9 @@ export function apply(ctx: any, config: PluginConfig): void { async function beat(): Promise { const body: Record = {}; + // DSH 有真沙箱(read-only / workspace-write / danger-full-access), + // 档位在这里是被强制执行的,不是 advisory。 + body.mode_enforcement = 'native'; const [entries, models] = await Promise.all([collectSessions(), collectModels()]); if (entries) { body.platform_sessions = snapshotDshSessions(entries, (id) => mailDrivenSessions.has(id)); @@ -649,7 +652,7 @@ export function apply(ctx: any, config: PluginConfig): void { const handle = await ctx.agents.resume({ resumeSessionId: sessionId as any, agentOptions, - setup: undefined, + setup: undefined, // resume 从磁盘恢复,工具已在 }); return { handle, resumed: true }; } @@ -658,14 +661,61 @@ export function apply(ctx: any, config: PluginConfig): void { sessionId, meta: { cwd }, agentOptions, - // setup 留空:DSH 的 base bundle 已经注册了 agent-loop、llm、tools 等服务。 - // 模型路由通过 agentOptions 传入即可 —— 挂载 preset 或 - // installModelSelection 反而会让 turn 崩溃(实测)。 - setup: undefined, + // 注册标准 preset 的工具(bash、fs、fs-search 等)。 + // dsh-a2a 用同一套 API(ctx.get('agentPresets') + presets.mount)并稳定运行。 + // 之前的崩溃来自尝试在 resume 路径上调用,create 路径经 a2a 实测可靠。 + setup: async (agentCtx: any) => { + try { + const presets = ctx.get('agentPresets'); + if (presets) { + const preset = await presets.resolve('standard'); + if (preset?.id) { + await presets.mount(agentCtx, preset.id); + } + } + } catch (e: any) { + console.error(`[dsh-mail-bridge] presets.mount 失败(工具注册降级): ${e?.message || e}`); + } + }, }); return { handle, resumed: false }; } + // ─── 权限档位 ─── + // + // DSH 的权限由两个独立旋钮控制: + // sandbox/mode → 控制文件系统和命令执行的边界 + // approval/policy → 控制是否需要人类审批 + // + // 三档映射(见 lib/permission-mode.js dshSandboxMode / dshApprovalPolicy): + // plan → read-only + ask(只读,遇到权限询问转邮件) + // workspace → workspace-write + ask(目录内可写,越界转邮件) + // full → danger-full-access + never(完全放开,不问人) + // + // 直接往 session 上 append 事件(与 permissionPresets.set 同一底层)。 + // 不走 permissionPresets 服务:它要求 preset 名在配置表里, + // 而我们的三档映射需要完全自主控制。 + function applyPermissionMode(session: any, mode: string): void { + if (!session?.append) return; + const m = (mode || 'workspace').trim(); + // sandbox mode + const sandboxMap: Record = { + plan: 'read-only', + workspace: 'workspace-write', + full: 'danger-full-access', + }; + const sandbox = sandboxMap[m] ?? 'workspace-write'; + session.append('sandbox/mode', { mode: sandbox }); + // approval policy + const approvalMap: Record = { + plan: 'ask', + workspace: 'ask', + full: 'never', + }; + const approval = approvalMap[m] ?? 'ask'; + session.append('approval/policy', { policy: approval }); + } + // ─── 投递邮件到 DSH 会话 ─── /** @@ -757,6 +807,7 @@ export function apply(ctx: any, config: PluginConfig): void { return { sessionID: adoptedID, reused: true }; } const { handle } = await startAgent(adoptedID, onDisk, attemptOrder()[0]); + applyPermissionMode(handle.agent?.session, data.permission_mode); bindAdopted(mailSessionID, adoptedID, onDisk, data); handle.agent.followup(userMessage(promptText)); await waitForTurnEnd(handle.agent); @@ -880,6 +931,9 @@ export function apply(ctx: any, config: PluginConfig): void { try { const started = await startAgent(attemptSessionId, cwd, route); handle = started.handle; + // 设定权限档位:sandbox/mode 决定文件与命令边界, + // approval/policy 决定越界时是否转邮件问人。 + applyPermissionMode(handle.agent?.session, data.permission_mode); } catch (e: any) { // create/resume 本身很少失败(create 不校验模型), // 但 cwd 不符、日志 replay 不过之类仍会抛