fix(gateway): bind system.run approvals to exec approvals

This commit is contained in:
Peter Steinberger
2026-02-14 13:02:48 +01:00
parent 233483d2b9
commit 318379cdba
12 changed files with 437 additions and 3 deletions

View File

@@ -135,6 +135,7 @@ describe("nodes run", () => {
it("requests approval and retries with allow-once decision", async () => {
let invokeCalls = 0;
let approvalId: string | null = null;
callGateway.mockImplementation(async ({ method, params }) => {
if (method === "node.list") {
return { nodes: [{ nodeId: "mac-1", commands: ["system.run"] }] };
@@ -149,6 +150,7 @@ describe("nodes run", () => {
command: "system.run",
params: {
command: ["echo", "hi"],
runId: approvalId,
approved: true,
approvalDecision: "allow-once",
},
@@ -157,10 +159,15 @@ describe("nodes run", () => {
}
if (method === "exec.approval.request") {
expect(params).toMatchObject({
id: expect.any(String),
command: "echo hi",
host: "node",
timeoutMs: 120_000,
});
approvalId =
typeof (params as { id?: unknown } | undefined)?.id === "string"
? ((params as { id: string }).id ?? null)
: null;
return { decision: "allow-once" };
}
throw new Error(`unexpected method: ${String(method)}`);

View File

@@ -467,10 +467,12 @@ export function createNodesTool(options?: {
// the gateway and wait for the user to approve/deny via the UI.
const APPROVAL_TIMEOUT_MS = 120_000;
const cmdText = command.join(" ");
const approvalId = crypto.randomUUID();
const approvalResult = await callGatewayTool(
"exec.approval.request",
{ ...gatewayOpts, timeoutMs: APPROVAL_TIMEOUT_MS + 5_000 },
{
id: approvalId,
command: cmdText,
cwd,
host: "node",
@@ -502,6 +504,7 @@ export function createNodesTool(options?: {
command: "system.run",
params: {
...runParams,
runId: approvalId,
approved: true,
approvalDecision,
},