import type { ExecApprovalDecision } from "../../infra/exec-approvals.js"; import type { ExecApprovalManager } from "../exec-approval-manager.js"; import { ErrorCodes, errorShape, formatValidationErrors, validateExecApprovalRequestParams, validateExecApprovalResolveParams, } from "../protocol/index.js"; import type { GatewayRequestHandlers } from "./types.js"; export function createExecApprovalHandlers(manager: ExecApprovalManager): GatewayRequestHandlers { return { "exec.approval.request": async ({ params, respond, context }) => { if (!validateExecApprovalRequestParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid exec.approval.request params: ${formatValidationErrors( validateExecApprovalRequestParams.errors, )}`, ), ); return; } const p = params as { id?: string; command: string; cwd?: string; host?: string; security?: string; ask?: string; agentId?: string; resolvedPath?: string; sessionKey?: string; timeoutMs?: number; }; const timeoutMs = typeof p.timeoutMs === "number" ? p.timeoutMs : 120_000; const explicitId = typeof p.id === "string" && p.id.trim().length > 0 ? p.id.trim() : null; if (explicitId && manager.getSnapshot(explicitId)) { respond( false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "approval id already pending"), ); return; } const request = { command: p.command, cwd: p.cwd ?? null, host: p.host ?? null, security: p.security ?? null, ask: p.ask ?? null, agentId: p.agentId ?? null, resolvedPath: p.resolvedPath ?? null, sessionKey: p.sessionKey ?? null, }; const record = manager.create(request, timeoutMs, explicitId); const decisionPromise = manager.waitForDecision(record, timeoutMs); context.broadcast( "exec.approval.requested", { id: record.id, request: record.request, createdAtMs: record.createdAtMs, expiresAtMs: record.expiresAtMs, }, { dropIfSlow: true }, ); const decision = await decisionPromise; respond( true, { id: record.id, decision, createdAtMs: record.createdAtMs, expiresAtMs: record.expiresAtMs, }, undefined, ); }, "exec.approval.resolve": async ({ params, respond, client, context }) => { if (!validateExecApprovalResolveParams(params)) { respond( false, undefined, errorShape( ErrorCodes.INVALID_REQUEST, `invalid exec.approval.resolve params: ${formatValidationErrors( validateExecApprovalResolveParams.errors, )}`, ), ); return; } const p = params as { id: string; decision: string }; const decision = p.decision as ExecApprovalDecision; if (decision !== "allow-once" && decision !== "allow-always" && decision !== "deny") { respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid decision")); return; } const resolvedBy = client?.connect?.client?.displayName ?? client?.connect?.client?.id; const ok = manager.resolve(p.id, decision, resolvedBy ?? null); if (!ok) { respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "unknown approval id")); return; } context.broadcast( "exec.approval.resolved", { id: p.id, decision, resolvedBy, ts: Date.now() }, { dropIfSlow: true }, ); respond(true, { ok: true }, undefined); }, }; }