import { afterEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { createExecApprovalForwarder } from "./exec-approval-forwarder.js"; const baseRequest = { id: "req-1", request: { command: "echo hello", agentId: "main", sessionKey: "agent:main:main", }, createdAtMs: 1000, expiresAtMs: 6000, }; afterEach(() => { vi.useRealTimers(); }); function getFirstDeliveryText(deliver: ReturnType): string { const firstCall = deliver.mock.calls[0]?.[0] as | { payloads?: Array<{ text?: string }> } | undefined; return firstCall?.payloads?.[0]?.text ?? ""; } describe("exec approval forwarder", () => { it("forwards to session target and resolves", async () => { vi.useFakeTimers(); const deliver = vi.fn().mockResolvedValue([]); const cfg = { approvals: { exec: { enabled: true, mode: "session" } }, } as OpenClawConfig; const forwarder = createExecApprovalForwarder({ getConfig: () => cfg, deliver, nowMs: () => 1000, resolveSessionTarget: () => ({ channel: "slack", to: "U1" }), }); await forwarder.handleRequested(baseRequest); expect(deliver).toHaveBeenCalledTimes(1); await forwarder.handleResolved({ id: baseRequest.id, decision: "allow-once", resolvedBy: "slack:U1", ts: 2000, }); expect(deliver).toHaveBeenCalledTimes(2); await vi.runAllTimersAsync(); expect(deliver).toHaveBeenCalledTimes(2); }); it("forwards to explicit targets and expires", async () => { vi.useFakeTimers(); const deliver = vi.fn().mockResolvedValue([]); const cfg = { approvals: { exec: { enabled: true, mode: "targets", targets: [{ channel: "telegram", to: "123" }], }, }, } as OpenClawConfig; const forwarder = createExecApprovalForwarder({ getConfig: () => cfg, deliver, nowMs: () => 1000, resolveSessionTarget: () => null, }); await forwarder.handleRequested(baseRequest); expect(deliver).toHaveBeenCalledTimes(1); await vi.runAllTimersAsync(); expect(deliver).toHaveBeenCalledTimes(2); }); it("formats single-line commands as inline code", async () => { vi.useFakeTimers(); const deliver = vi.fn().mockResolvedValue([]); const cfg = { approvals: { exec: { enabled: true, mode: "targets", targets: [{ channel: "telegram", to: "123" }], }, }, } as OpenClawConfig; const forwarder = createExecApprovalForwarder({ getConfig: () => cfg, deliver, nowMs: () => 1000, resolveSessionTarget: () => null, }); await forwarder.handleRequested(baseRequest); expect(getFirstDeliveryText(deliver)).toContain("Command: `echo hello`"); }); it("formats complex commands as fenced code blocks", async () => { vi.useFakeTimers(); const deliver = vi.fn().mockResolvedValue([]); const cfg = { approvals: { exec: { enabled: true, mode: "targets", targets: [{ channel: "telegram", to: "123" }], }, }, } as OpenClawConfig; const forwarder = createExecApprovalForwarder({ getConfig: () => cfg, deliver, nowMs: () => 1000, resolveSessionTarget: () => null, }); await forwarder.handleRequested({ ...baseRequest, request: { ...baseRequest.request, command: "echo `uname`\necho done", }, }); expect(getFirstDeliveryText(deliver)).toContain("Command:\n```\necho `uname`\necho done\n```"); }); it("uses a longer fence when command already contains triple backticks", async () => { vi.useFakeTimers(); const deliver = vi.fn().mockResolvedValue([]); const cfg = { approvals: { exec: { enabled: true, mode: "targets", targets: [{ channel: "telegram", to: "123" }], }, }, } as OpenClawConfig; const forwarder = createExecApprovalForwarder({ getConfig: () => cfg, deliver, nowMs: () => 1000, resolveSessionTarget: () => null, }); await forwarder.handleRequested({ ...baseRequest, request: { ...baseRequest.request, command: "echo ```danger```", }, }); expect(getFirstDeliveryText(deliver)).toContain("Command:\n````\necho ```danger```\n````"); }); });