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.
This commit is contained in:
Ross 2026-01-30 13:41:23 +00:00
parent da71eaebd2
commit 0564482aba

View File

@ -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(),