diff --git a/src/agents/tools/nodes-tool.ts b/src/agents/tools/nodes-tool.ts index f108e471d..b9d4debff 100644 --- a/src/agents/tools/nodes-tool.ts +++ b/src/agents/tools/nodes-tool.ts @@ -148,6 +148,18 @@ const NodesToolSchema = Type.Union([ ]), ), }), + Type.Object({ + action: Type.Literal("run"), + gatewayUrl: Type.Optional(Type.String()), + gatewayToken: Type.Optional(Type.String()), + timeoutMs: Type.Optional(Type.Number()), + node: Type.String(), + command: Type.Array(Type.String()), + cwd: Type.Optional(Type.String()), + env: Type.Optional(Type.Array(Type.String())), + commandTimeoutMs: Type.Optional(Type.Number()), + needsScreenRecording: Type.Optional(Type.Boolean()), + }), ]); export function createNodesTool(): AnyAgentTool { @@ -155,7 +167,7 @@ export function createNodesTool(): AnyAgentTool { label: "Nodes", name: "nodes", description: - "Discover and control paired nodes (status/describe/pairing/notify/camera/screen/location).", + "Discover and control paired nodes (status/describe/pairing/notify/camera/screen/location/run).", parameters: NodesToolSchema, execute: async (_toolCallId, args) => { const params = args as Record; @@ -478,6 +490,68 @@ export function createNodesTool(): AnyAgentTool { })) as { payload?: unknown }; return jsonResult(raw?.payload ?? {}); } + case "run": { + const node = readStringParam(params, "node", { required: true }); + const nodeId = await resolveNodeId(gatewayOpts, node); + const commandRaw = params.command; + if (!commandRaw) { + throw new Error( + "command required (argv array, e.g. ['echo', 'Hello'])", + ); + } + if (!Array.isArray(commandRaw)) { + throw new Error( + "command must be an array of strings (argv), e.g. ['echo', 'Hello']", + ); + } + const command = commandRaw.map((c) => String(c)); + if (command.length === 0) { + throw new Error("command must not be empty"); + } + const cwd = + typeof params.cwd === "string" && params.cwd.trim() + ? params.cwd.trim() + : undefined; + const envRaw = params.env; + const env: Record | undefined = + Array.isArray(envRaw) && envRaw.length > 0 + ? (() => { + const parsed: Record = {}; + for (const pair of envRaw) { + if (typeof pair !== "string") continue; + const idx = pair.indexOf("="); + if (idx <= 0) continue; + const key = pair.slice(0, idx).trim(); + const value = pair.slice(idx + 1); + if (!key) continue; + parsed[key] = value; + } + return Object.keys(parsed).length > 0 ? parsed : undefined; + })() + : undefined; + const commandTimeoutMs = + typeof params.commandTimeoutMs === "number" && + Number.isFinite(params.commandTimeoutMs) + ? params.commandTimeoutMs + : undefined; + const needsScreenRecording = + typeof params.needsScreenRecording === "boolean" + ? params.needsScreenRecording + : undefined; + const raw = (await callGatewayTool("node.invoke", gatewayOpts, { + nodeId, + command: "system.run", + params: { + command, + cwd, + env, + timeoutMs: commandTimeoutMs, + needsScreenRecording, + }, + idempotencyKey: crypto.randomUUID(), + })) as { payload?: unknown }; + return jsonResult(raw?.payload ?? {}); + } default: throw new Error(`Unknown action: ${action}`); }