dsh: 权限档位接线 — presets.mount 注册工具 + 三档 sandbox/approval 映射 + 心跳报 native
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 测试全过
This commit is contained in:
@ -421,6 +421,9 @@ export function apply(ctx: any, config: PluginConfig): void {
|
||||
|
||||
async function beat(): Promise<void> {
|
||||
const body: Record<string, unknown> = {};
|
||||
// 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<string, string> = {
|
||||
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<string, string> = {
|
||||
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 不过之类仍会抛
|
||||
|
||||
Reference in New Issue
Block a user