fix(nodes-tool): add missing 'run' action to execute commands on paired nodes with optional parameters as defined in CLI

This commit is contained in:
SirCrumpet
2026-01-08 09:18:53 +10:00
committed by Peter Steinberger
parent 145fe1cec7
commit b34fc0aaed

View File

@@ -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<string, unknown>;
@@ -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<string, string> | undefined =
Array.isArray(envRaw) && envRaw.length > 0
? (() => {
const parsed: Record<string, string> = {};
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}`);
}