openclaw / src /cli /nodes-cli /register.invoke.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f778c12 verified
Raw
History Blame Contribute Delete
2.86 kB
// Generic node.invoke command with shell-exec commands intentionally blocked.
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "@openclaw/normalization-core/string-coerce";
import type { Command } from "commander";
import { randomIdempotencyKey } from "../../gateway/call.js";
import { defaultRuntime } from "../../runtime.js";
import { runNodesCommand } from "./cli-utils.js";
import {
callNodesGatewayCli,
nodesCallOpts,
parseOptionalNodePositiveInteger,
resolveCliNodeId,
} from "./rpc.js";
import type { NodesRpcOpts } from "./types.js";
const BLOCKED_NODE_INVOKE_COMMANDS = new Set(["system.run", "system.run.prepare"]);
function parseNodeInvokeParams(value = "{}"): unknown {
try {
return JSON.parse(value) as unknown;
} catch {
throw new Error("--params must be valid JSON.");
}
}
/** Register direct node command invocation. */
export function registerNodesInvokeCommands(nodes: Command) {
nodesCallOpts(
nodes
.command("invoke")
.description("Invoke a command on a paired node")
.requiredOption("--node <idOrNameOrIp>", "Node id, name, or IP")
.requiredOption("--command <command>", "Command (e.g. canvas.navigate)")
.option("--params <json>", "JSON object string for params", "{}")
.option("--invoke-timeout <ms>", "Node invoke timeout in ms (default 15000)", "15000")
.option("--idempotency-key <key>", "Idempotency key (optional)")
.action(async (opts: NodesRpcOpts) => {
await runNodesCommand("invoke", async () => {
const nodeQuery = normalizeOptionalString(opts.node) ?? "";
const command = normalizeOptionalString(opts.command) ?? "";
if (!nodeQuery || !command) {
throw new Error("--node and --command required");
}
if (BLOCKED_NODE_INVOKE_COMMANDS.has(normalizeLowercaseStringOrEmpty(command))) {
throw new Error(
`command "${command}" is reserved for shell execution; use the exec tool with host=node instead`,
);
}
const params = parseNodeInvokeParams(opts.params);
const timeoutMs = parseOptionalNodePositiveInteger(
opts.invokeTimeout,
"--invoke-timeout",
);
const nodeId = await resolveCliNodeId(opts, nodeQuery);
const invokeParams: Record<string, unknown> = {
nodeId,
command,
params,
idempotencyKey: opts.idempotencyKey ?? randomIdempotencyKey(),
};
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
invokeParams.timeoutMs = timeoutMs;
}
const result = await callNodesGatewayCli("node.invoke", opts, invokeParams);
defaultRuntime.writeJson(result);
});
}),
{ timeoutMs: 30_000 },
);
}