From e35845dd49cefb2aca9b02cefa9ab7c698b470fa Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 8 Jan 2026 00:17:44 +0000 Subject: [PATCH] fix(nodes-tool): add run invoke timeout (PR #433, thanks @sircrumpet) --- CHANGELOG.md | 1 + docs/tools/index.md | 1 + src/agents/clawdbot-tools.camera.test.ts | 46 ++++++++++++++++++++++++ src/agents/tools/nodes-tool.ts | 7 ++++ 4 files changed, 55 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a4f9b463..f410a1f0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - Tools: add Telegram/WhatsApp reaction tools (with per-provider gating). Thanks @zats for PR #353. - Tools: flatten literal-union schemas for Claude on Vertex AI. Thanks @carlulsoe for PR #409. - Tools: keep tool failure logs concise (no stack traces); full stack only in debug logs. +- Tools: add nodes tool run invoke-timeout support. Thanks @sircrumpet for PR #433. - Tools: unify reaction removal semantics across Discord/Slack/Telegram/WhatsApp and allow WhatsApp reaction routing across accounts. - Android: fix APK output filename renaming after AGP updates. Thanks @Syhids for PR #410. - Android: rotate camera photos by EXIF orientation. Thanks @fcatuhe for PR #403. diff --git a/docs/tools/index.md b/docs/tools/index.md index 6e9d14daa..75c5bdb67 100644 --- a/docs/tools/index.md +++ b/docs/tools/index.md @@ -109,6 +109,7 @@ Core actions: - `status`, `describe` - `pending`, `approve`, `reject` (pairing) - `notify` (macOS `system.notify`) +- `run` (macOS `system.run`) - `camera_snap`, `camera_clip`, `screen_record` - `location_get` diff --git a/src/agents/clawdbot-tools.camera.test.ts b/src/agents/clawdbot-tools.camera.test.ts index 77abff5f5..35e12b131 100644 --- a/src/agents/clawdbot-tools.camera.test.ts +++ b/src/agents/clawdbot-tools.camera.test.ts @@ -88,3 +88,49 @@ describe("nodes camera_snap", () => { }); }); }); + +describe("nodes run", () => { + beforeEach(() => { + callGateway.mockReset(); + }); + + it("passes invoke and command timeouts", async () => { + callGateway.mockImplementation(async ({ method, params }) => { + if (method === "node.list") { + return { nodes: [{ nodeId: "mac-1" }] }; + } + if (method === "node.invoke") { + expect(params).toMatchObject({ + nodeId: "mac-1", + command: "system.run", + timeoutMs: 45_000, + params: { + command: ["echo", "hi"], + cwd: "/tmp", + env: { FOO: "bar" }, + timeoutMs: 12_000, + }, + }); + return { + payload: { stdout: "", stderr: "", exitCode: 0, success: true }, + }; + } + throw new Error(`unexpected method: ${String(method)}`); + }); + + const tool = createClawdbotTools().find( + (candidate) => candidate.name === "nodes", + ); + if (!tool) throw new Error("missing nodes tool"); + + await tool.execute("call1", { + action: "run", + node: "mac-1", + command: ["echo", "hi"], + cwd: "/tmp", + env: ["FOO=bar"], + commandTimeoutMs: 12_000, + invokeTimeoutMs: 45_000, + }); + }); +}); diff --git a/src/agents/tools/nodes-tool.ts b/src/agents/tools/nodes-tool.ts index b9d4debff..c8c648581 100644 --- a/src/agents/tools/nodes-tool.ts +++ b/src/agents/tools/nodes-tool.ts @@ -158,6 +158,7 @@ const NodesToolSchema = Type.Union([ cwd: Type.Optional(Type.String()), env: Type.Optional(Type.Array(Type.String())), commandTimeoutMs: Type.Optional(Type.Number()), + invokeTimeoutMs: Type.Optional(Type.Number()), needsScreenRecording: Type.Optional(Type.Boolean()), }), ]); @@ -534,6 +535,11 @@ export function createNodesTool(): AnyAgentTool { Number.isFinite(params.commandTimeoutMs) ? params.commandTimeoutMs : undefined; + const invokeTimeoutMs = + typeof params.invokeTimeoutMs === "number" && + Number.isFinite(params.invokeTimeoutMs) + ? params.invokeTimeoutMs + : undefined; const needsScreenRecording = typeof params.needsScreenRecording === "boolean" ? params.needsScreenRecording @@ -548,6 +554,7 @@ export function createNodesTool(): AnyAgentTool { timeoutMs: commandTimeoutMs, needsScreenRecording, }, + timeoutMs: invokeTimeoutMs, idempotencyKey: crypto.randomUUID(), })) as { payload?: unknown }; return jsonResult(raw?.payload ?? {});