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",
|
"screen_record",
|
||||||
"location_get",
|
"location_get",
|
||||||
"run",
|
"run",
|
||||||
|
"invoke",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const NOTIFY_PRIORITIES = ["passive", "active", "timeSensitive"] as const;
|
const NOTIFY_PRIORITIES = ["passive", "active", "timeSensitive"] as const;
|
||||||
@@ -84,6 +85,9 @@ const NodesToolSchema = Type.Object({
|
|||||||
commandTimeoutMs: Type.Optional(Type.Number()),
|
commandTimeoutMs: Type.Optional(Type.Number()),
|
||||||
invokeTimeoutMs: Type.Optional(Type.Number()),
|
invokeTimeoutMs: Type.Optional(Type.Number()),
|
||||||
needsScreenRecording: Type.Optional(Type.Boolean()),
|
needsScreenRecording: Type.Optional(Type.Boolean()),
|
||||||
|
// invoke
|
||||||
|
invokeCommand: Type.Optional(Type.String()),
|
||||||
|
invokeParamsJson: Type.Optional(Type.String()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function createNodesTool(options?: {
|
export function createNodesTool(options?: {
|
||||||
@@ -99,7 +103,7 @@ export function createNodesTool(options?: {
|
|||||||
label: "Nodes",
|
label: "Nodes",
|
||||||
name: "nodes",
|
name: "nodes",
|
||||||
description:
|
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,
|
parameters: NodesToolSchema,
|
||||||
execute: async (_toolCallId, args) => {
|
execute: async (_toolCallId, args) => {
|
||||||
const params = args as Record<string, unknown>;
|
const params = args as Record<string, unknown>;
|
||||||
@@ -438,6 +442,31 @@ export function createNodesTool(options?: {
|
|||||||
});
|
});
|
||||||
return jsonResult(raw?.payload ?? {});
|
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:
|
default:
|
||||||
throw new Error(`Unknown action: ${action}`);
|
throw new Error(`Unknown action: ${action}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user