




















@@ -156,6 +156,24 @@ describe("createNodesTool screen_record duration guardrails", () => {
156156expect(schema.properties?.limit?.maximum).toBe(20);
157157});
158158159+it("advertises node media numeric constraints in the tool schema", () => {
160+const tool = createNodesTool();
161+const schema = tool.parameters as {
162+properties?: {
163+maxWidth?: { minimum?: number; type?: string };
164+quality?: { minimum?: number; maximum?: number; type?: string };
165+delayMs?: { minimum?: number; type?: string };
166+fps?: { exclusiveMinimum?: number; type?: string };
167+screenIndex?: { minimum?: number; type?: string };
168+};
169+};
170+expect(schema.properties?.maxWidth).toMatchObject({ type: "integer", minimum: 1 });
171+expect(schema.properties?.quality).toMatchObject({ type: "number", minimum: 0, maximum: 1 });
172+expect(schema.properties?.delayMs).toMatchObject({ type: "integer", minimum: 0 });
173+expect(schema.properties?.fps).toMatchObject({ type: "number", exclusiveMinimum: 0 });
174+expect(schema.properties?.screenIndex).toMatchObject({ type: "integer", minimum: 0 });
175+});
176+159177it("clamps screen_record durationMs argument to 300000 before gateway invoke", async () => {
160178gatewayMocks.callGatewayTool.mockResolvedValue({ payload: { ok: true } });
161179const tool = createNodesTool();
@@ -341,6 +359,51 @@ describe("createNodesTool screen_record duration guardrails", () => {
341359expect(call?.[2].params?.limit).toBe(20);
342360});
343361362+it.each([
363+["camera_snap", { maxWidth: 640.5 }, "maxWidth must be a positive integer"],
364+["camera_snap", { delayMs: -1 }, "delayMs must be a non-negative integer"],
365+["camera_snap", { quality: 1.1 }, "quality must be between 0 and 1"],
366+["photos_latest", { maxWidth: "wide" }, "maxWidth must be a positive integer"],
367+["photos_latest", { quality: -0.1 }, "quality must be between 0 and 1"],
368+["screen_record", { fps: 0 }, "fps must be greater than 0"],
369+["screen_record", { screenIndex: 1.5 }, "screenIndex must be a non-negative integer"],
370+])("rejects invalid %s numeric params %s", async (action, params, message) => {
371+const tool = createNodesTool();
372+373+await expect(
374+tool.execute("call-invalid-media-number", {
375+ action,
376+node: "macbook",
377+ ...params,
378+}),
379+).rejects.toThrow(message);
380+expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled();
381+});
382+383+it("forwards validated camera_snap numeric params to gateway invoke", async () => {
384+gatewayMocks.callGatewayTool.mockResolvedValue({ payload: { ok: true } });
385+const tool = createNodesTool();
386+387+await tool.execute("call-camera-numbers", {
388+action: "camera_snap",
389+node: "macbook",
390+facing: "front",
391+maxWidth: "640",
392+quality: "0.8",
393+delayMs: "2000",
394+});
395+396+const call = gatewayMocks.callGatewayTool.mock.calls[0] as
397+| [string, unknown, { params?: { maxWidth?: unknown; quality?: unknown; delayMs?: unknown } }]
398+| undefined;
399+expect(call?.[0]).toBe("node.invoke");
400+expect(call?.[2].params).toMatchObject({
401+maxWidth: 640,
402+quality: 0.8,
403+delayMs: 2000,
404+});
405+});
406+344407it("uses operator.pairing plus operator.admin to approve exec-capable node pair requests", async () => {
345408mockNodePairApproveFlow({
346409requiredApproveScopes: ["operator.pairing", "operator.admin"],
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。