From 0564482abaf3e849423cd02c7db5f77681e7c6c0 Mon Sep 17 00:00:00 2001 From: Ross Date: Fri, 30 Jan 2026 13:41:23 +0000 Subject: [PATCH] fix(nodes-tool): add exec approval flow for agent tool run action The agent tool's run action was directly calling node.invoke with system.run without going through the exec approval flow. When the node requires approval (ask=on-miss and allowlist miss), it immediately denied the request with SYSTEM_RUN_DENIED. The CLI path (nodes run) already handled this correctly by calling exec.approval.request first, waiting for the user decision, then retrying with the approved flag. This commit adds the same flow to the agent tool: 1. Try system.run without approval flags 2. If denied with 'approval required', call exec.approval.request on the gateway to create a pending approval and wait for user 3. On approve, retry system.run with approved=true + approvalDecision 4. On deny/timeout, throw a clear error This fixes the approval UI not showing pending requests when the agent tool triggers a node run command. --- src/agents/tools/nodes-tool.ts | 68 ++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/src/agents/tools/nodes-tool.ts b/src/agents/tools/nodes-tool.ts index 77b737876..882971826 100644 --- a/src/agents/tools/nodes-tool.ts +++ b/src/agents/tools/nodes-tool.ts @@ -423,17 +423,71 @@ export function createNodesTool(options?: { typeof params.needsScreenRecording === "boolean" ? params.needsScreenRecording : undefined; + const runParams = { + command, + cwd, + env, + timeoutMs: commandTimeoutMs, + needsScreenRecording, + agentId, + sessionKey, + }; + + // First attempt without approval flags. + try { + const raw = (await callGatewayTool("node.invoke", gatewayOpts, { + nodeId, + command: "system.run", + params: runParams, + timeoutMs: invokeTimeoutMs, + idempotencyKey: crypto.randomUUID(), + })) as { payload?: unknown }; + return jsonResult(raw?.payload ?? {}); + } catch (firstErr) { + const msg = firstErr instanceof Error ? firstErr.message : String(firstErr); + if (!msg.includes("SYSTEM_RUN_DENIED: approval required")) { + throw firstErr; + } + } + + // Node requires approval – create a pending approval request on + // the gateway and wait for the user to approve/deny via the UI. + const APPROVAL_TIMEOUT_MS = 120_000; + const cmdText = command.join(" "); + const approvalResult = (await callGatewayTool( + "exec.approval.request", + { ...gatewayOpts, timeoutMs: APPROVAL_TIMEOUT_MS + 5_000 }, + { + command: cmdText, + cwd, + host: "node", + agentId, + sessionKey, + timeoutMs: APPROVAL_TIMEOUT_MS, + }, + )) as { decision?: string; id?: string } | null; + + const decision = + approvalResult && typeof approvalResult === "object" + ? (approvalResult.decision ?? null) + : null; + + if (!decision || decision === "deny") { + throw new Error( + decision === "deny" + ? "exec denied: user denied" + : "exec denied: approval timed out", + ); + } + + // Retry with the approval decision. const raw = (await callGatewayTool("node.invoke", gatewayOpts, { nodeId, command: "system.run", params: { - command, - cwd, - env, - timeoutMs: commandTimeoutMs, - needsScreenRecording, - agentId, - sessionKey, + ...runParams, + approved: true, + approvalDecision: decision, }, timeoutMs: invokeTimeoutMs, idempotencyKey: crypto.randomUUID(),