Agents: add nodes invoke action
This commit is contained in:
committed by
Mariano Belinky
parent
a4382607d7
commit
d9cadf9737
@@ -133,3 +133,52 @@ describe("nodes run", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("nodes invoke", () => {
|
||||
beforeEach(() => {
|
||||
callGateway.mockReset();
|
||||
});
|
||||
|
||||
it("invokes arbitrary commands with params JSON", async () => {
|
||||
callGateway.mockImplementation(async ({ method, params }) => {
|
||||
if (method === "node.list") {
|
||||
return { nodes: [{ nodeId: "ios-1" }] };
|
||||
}
|
||||
if (method === "node.invoke") {
|
||||
expect(params).toMatchObject({
|
||||
nodeId: "ios-1",
|
||||
command: "device.info",
|
||||
params: { includeBattery: true },
|
||||
timeoutMs: 12_000,
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
nodeId: "ios-1",
|
||||
command: "device.info",
|
||||
payload: { deviceName: "iPhone" },
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected method: ${String(method)}`);
|
||||
});
|
||||
|
||||
const tool = createOpenClawTools().find((candidate) => candidate.name === "nodes");
|
||||
if (!tool) {
|
||||
throw new Error("missing nodes tool");
|
||||
}
|
||||
|
||||
const result = await tool.execute("call1", {
|
||||
action: "invoke",
|
||||
node: "ios-1",
|
||||
invokeCommand: "device.info",
|
||||
invokeParamsJson: JSON.stringify({ includeBattery: true }),
|
||||
invokeTimeoutMs: 12_000,
|
||||
});
|
||||
|
||||
expect(result.details).toMatchObject({
|
||||
ok: true,
|
||||
nodeId: "ios-1",
|
||||
command: "device.info",
|
||||
payload: { deviceName: "iPhone" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@ const NODES_TOOL_ACTIONS = [
|
||||
"screen_record",
|
||||
"location_get",
|
||||
"run",
|
||||
"invoke",
|
||||
] as const;
|
||||
|
||||
const NOTIFY_PRIORITIES = ["passive", "active", "timeSensitive"] as const;
|
||||
@@ -84,6 +85,9 @@ const NodesToolSchema = Type.Object({
|
||||
commandTimeoutMs: Type.Optional(Type.Number()),
|
||||
invokeTimeoutMs: Type.Optional(Type.Number()),
|
||||
needsScreenRecording: Type.Optional(Type.Boolean()),
|
||||
// invoke
|
||||
invokeCommand: Type.Optional(Type.String()),
|
||||
invokeParamsJson: Type.Optional(Type.String()),
|
||||
});
|
||||
|
||||
export function createNodesTool(options?: {
|
||||
@@ -99,7 +103,7 @@ export function createNodesTool(options?: {
|
||||
label: "Nodes",
|
||||
name: "nodes",
|
||||
description:
|
||||
"Discover and control paired nodes (status/describe/pairing/notify/camera/screen/location/run).",
|
||||
"Discover and control paired nodes (status/describe/pairing/notify/camera/screen/location/run/invoke).",
|
||||
parameters: NodesToolSchema,
|
||||
execute: async (_toolCallId, args) => {
|
||||
const params = args as Record<string, unknown>;
|
||||
@@ -438,6 +442,31 @@ export function createNodesTool(options?: {
|
||||
});
|
||||
return jsonResult(raw?.payload ?? {});
|
||||
}
|
||||
case "invoke": {
|
||||
const node = readStringParam(params, "node", { required: true });
|
||||
const nodeId = await resolveNodeId(gatewayOpts, node);
|
||||
const invokeCommand = readStringParam(params, "invokeCommand", { required: true });
|
||||
const invokeParamsJson =
|
||||
typeof params.invokeParamsJson === "string" ? params.invokeParamsJson.trim() : "";
|
||||
let invokeParams: unknown = {};
|
||||
if (invokeParamsJson) {
|
||||
try {
|
||||
invokeParams = JSON.parse(invokeParamsJson);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(`invokeParamsJson must be valid JSON: ${message}`);
|
||||
}
|
||||
}
|
||||
const invokeTimeoutMs = parseTimeoutMs(params.invokeTimeoutMs);
|
||||
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
|
||||
nodeId,
|
||||
command: invokeCommand,
|
||||
params: invokeParams,
|
||||
timeoutMs: invokeTimeoutMs,
|
||||
idempotencyKey: crypto.randomUUID(),
|
||||
});
|
||||
return jsonResult(raw ?? {});
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown action: ${action}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user