From b8373eaddce2243b34bff46ad5518933751d4561 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Fri, 27 Feb 2026 09:53:08 +0530 Subject: [PATCH] fix(nodes): reject facing=both when camera deviceId is set --- src/agents/openclaw-tools.camera.test.ts | 11 ++++++++ src/agents/tools/nodes-tool.ts | 3 +++ src/cli/nodes-cli/register.camera.ts | 3 +++ src/cli/program.nodes-media.test.ts | 32 ++++++++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/src/agents/openclaw-tools.camera.test.ts b/src/agents/openclaw-tools.camera.test.ts index 0101d5f2d3f..171788e5350 100644 --- a/src/agents/openclaw-tools.camera.test.ts +++ b/src/agents/openclaw-tools.camera.test.ts @@ -136,6 +136,17 @@ describe("nodes camera_snap", () => { deviceId: "cam-123", }); }); + + it("rejects facing both when deviceId is provided", async () => { + await expect( + executeNodes({ + action: "camera_snap", + node: NODE_ID, + facing: "both", + deviceId: "cam-123", + }), + ).rejects.toThrow(/facing=both is not allowed when deviceId is set/i); + }); }); describe("nodes notifications_list", () => { diff --git a/src/agents/tools/nodes-tool.ts b/src/agents/tools/nodes-tool.ts index b16744057d1..bec518feeb7 100644 --- a/src/agents/tools/nodes-tool.ts +++ b/src/agents/tools/nodes-tool.ts @@ -248,6 +248,9 @@ export function createNodesTool(options?: { typeof params.deviceId === "string" && params.deviceId.trim() ? params.deviceId.trim() : undefined; + if (deviceId && facings.length > 1) { + throw new Error("facing=both is not allowed when deviceId is set"); + } const content: AgentToolResult["content"] = []; const details: Array> = []; diff --git a/src/cli/nodes-cli/register.camera.ts b/src/cli/nodes-cli/register.camera.ts index c93f63cf133..e86ab854650 100644 --- a/src/cli/nodes-cli/register.camera.ts +++ b/src/cli/nodes-cli/register.camera.ts @@ -121,6 +121,9 @@ export function registerNodesCameraCommands(nodes: Command) { const quality = opts.quality ? Number.parseFloat(String(opts.quality)) : undefined; const delayMs = opts.delayMs ? Number.parseInt(String(opts.delayMs), 10) : undefined; const deviceId = opts.deviceId ? String(opts.deviceId).trim() : undefined; + if (deviceId && facings.length > 1) { + throw new Error("facing=both is not allowed when --device-id is set"); + } const timeoutMs = opts.invokeTimeout ? Number.parseInt(String(opts.invokeTimeout), 10) : undefined; diff --git a/src/cli/program.nodes-media.test.ts b/src/cli/program.nodes-media.test.ts index 4b97281ce8e..d4eb426d4ed 100644 --- a/src/cli/program.nodes-media.test.ts +++ b/src/cli/program.nodes-media.test.ts @@ -284,6 +284,38 @@ describe("cli program (nodes media)", () => { ); }); + it("fails nodes camera snap when --facing both and --device-id are combined", async () => { + mockNodeGateway(); + + const program = new Command(); + program.exitOverride(); + registerNodesCli(program); + runtime.error.mockClear(); + + await expect( + program.parseAsync( + [ + "nodes", + "camera", + "snap", + "--node", + "ios-node", + "--facing", + "both", + "--device-id", + "cam-123", + ], + { from: "user" }, + ), + ).rejects.toThrow(/exit/i); + + expect( + runtime.error.mock.calls.some(([msg]) => + /facing=both is not allowed when --device-id is set/i.test(String(msg)), + ), + ).toBe(true); + }); + describe("URL-based payloads", () => { let originalFetch: typeof globalThis.fetch;