fix(nodes): cap screen_record duration to 5 minutes (land #31106 by @BlueBirdBack)

Landed-from: #31106
Contributor: @BlueBirdBack
Co-authored-by: BlueBirdBack <126304167+BlueBirdBack@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-03-02 01:53:07 +00:00
parent 757e09fe43
commit e70fc5eb62
3 changed files with 94 additions and 3 deletions

View File

@@ -0,0 +1,88 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const gatewayMocks = vi.hoisted(() => ({
callGatewayTool: vi.fn(),
readGatewayCallOptions: vi.fn(() => ({})),
}));
const nodeUtilsMocks = vi.hoisted(() => ({
resolveNodeId: vi.fn(async () => "node-1"),
listNodes: vi.fn(async () => []),
resolveNodeIdFromList: vi.fn(() => "node-1"),
}));
const screenMocks = vi.hoisted(() => ({
parseScreenRecordPayload: vi.fn(() => ({
base64: "ZmFrZQ==",
format: "mp4",
durationMs: 300_000,
fps: 10,
screenIndex: 0,
hasAudio: true,
})),
screenRecordTempPath: vi.fn(() => "/tmp/screen-record.mp4"),
writeScreenRecordToFile: vi.fn(async () => ({ path: "/tmp/screen-record.mp4" })),
}));
vi.mock("./gateway.js", () => ({
callGatewayTool: (...args: unknown[]) => gatewayMocks.callGatewayTool(...args),
readGatewayCallOptions: (...args: unknown[]) => gatewayMocks.readGatewayCallOptions(...args),
}));
vi.mock("./nodes-utils.js", () => ({
resolveNodeId: (...args: unknown[]) => nodeUtilsMocks.resolveNodeId(...args),
listNodes: (...args: unknown[]) => nodeUtilsMocks.listNodes(...args),
resolveNodeIdFromList: (...args: unknown[]) => nodeUtilsMocks.resolveNodeIdFromList(...args),
}));
vi.mock("../../cli/nodes-screen.js", () => ({
parseScreenRecordPayload: (...args: unknown[]) => screenMocks.parseScreenRecordPayload(...args),
screenRecordTempPath: (...args: unknown[]) => screenMocks.screenRecordTempPath(...args),
writeScreenRecordToFile: (...args: unknown[]) => screenMocks.writeScreenRecordToFile(...args),
}));
import { createNodesTool } from "./nodes-tool.js";
describe("createNodesTool screen_record duration guardrails", () => {
beforeEach(() => {
gatewayMocks.callGatewayTool.mockReset();
gatewayMocks.readGatewayCallOptions.mockReset();
gatewayMocks.readGatewayCallOptions.mockReturnValue({});
nodeUtilsMocks.resolveNodeId.mockClear();
screenMocks.parseScreenRecordPayload.mockClear();
screenMocks.writeScreenRecordToFile.mockClear();
});
it("caps durationMs schema at 300000", () => {
const tool = createNodesTool();
const schema = tool.parameters as {
properties?: {
durationMs?: {
maximum?: number;
};
};
};
expect(schema.properties?.durationMs?.maximum).toBe(300_000);
});
it("clamps screen_record durationMs argument to 300000 before gateway invoke", async () => {
gatewayMocks.callGatewayTool.mockResolvedValue({ payload: { ok: true } });
const tool = createNodesTool();
await tool.execute("call-1", {
action: "screen_record",
node: "macbook",
durationMs: 900_000,
});
expect(gatewayMocks.callGatewayTool).toHaveBeenCalledWith(
"node.invoke",
{},
expect.objectContaining({
params: expect.objectContaining({
durationMs: 300_000,
}),
}),
);
});
});