diff --git a/src/agents/tools/sessions-spawn-tool.test.ts b/src/agents/tools/sessions-spawn-tool.test.ts index d452ec23a32..bd67d8eab7d 100644 --- a/src/agents/tools/sessions-spawn-tool.test.ts +++ b/src/agents/tools/sessions-spawn-tool.test.ts @@ -446,6 +446,24 @@ describe("sessions_spawn tool", () => { expect(spawnArgs.runTimeoutSeconds).toBe(2); }); + it.each([ + [{ runTimeoutSeconds: 1.5 }, "runTimeoutSeconds must be a non-negative integer"], + [{ runTimeoutSeconds: -1 }, "runTimeoutSeconds must be a non-negative integer"], + [{ timeoutSeconds: "1sec" }, "timeoutSeconds must be a non-negative integer"], + ])("rejects invalid timeout override %o", async (params, message) => { + const tool = createSessionsSpawnTool({ + agentSessionKey: "agent:main:main", + }); + + await expect( + tool.execute("call-invalid-timeout", { + task: "do thing", + ...params, + }), + ).rejects.toThrow(message); + expect(hoisted.spawnSubagentDirectMock).not.toHaveBeenCalled(); + }); + it("passes inherited workspaceDir from tool context, not from tool args", async () => { const tool = createSessionsSpawnTool({ agentSessionKey: "agent:main:main", diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index a0fb89387d3..fbafe150a3a 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -35,6 +35,7 @@ import type { AnyAgentTool } from "./common.js"; import { jsonResult, normalizeToolModelOverride, + readNonNegativeIntegerParam, readStringParam, ToolInputError, } from "./common.js"; @@ -169,9 +170,9 @@ function createSessionsSpawnToolSchema(params: { model: Type.Optional(Type.String()), thinking: Type.Optional(Type.String()), cwd: Type.Optional(Type.String()), - runTimeoutSeconds: Type.Optional(Type.Number({ minimum: 0 })), + runTimeoutSeconds: Type.Optional(Type.Integer({ minimum: 0 })), // Back-compat: older callers used timeoutSeconds for this tool. - timeoutSeconds: Type.Optional(Type.Number({ minimum: 0 })), + timeoutSeconds: Type.Optional(Type.Integer({ minimum: 0 })), ...(params.threadAvailable ? { thread: Type.Optional( @@ -342,17 +343,10 @@ export function createSessionsSpawnTool( if (runtime === "acp" && context === "fork") { throw new Error('context="fork" is only supported for runtime="subagent".'); } - // Back-compat: older callers used timeoutSeconds for this tool. - const timeoutSecondsCandidate = - typeof params.runTimeoutSeconds === "number" - ? params.runTimeoutSeconds - : typeof params.timeoutSeconds === "number" - ? params.timeoutSeconds - : undefined; const runTimeoutSeconds = - typeof timeoutSecondsCandidate === "number" && Number.isFinite(timeoutSecondsCandidate) - ? Math.max(0, Math.floor(timeoutSecondsCandidate)) - : undefined; + readNonNegativeIntegerParam(params, "runTimeoutSeconds") ?? + // Back-compat: older callers used timeoutSeconds for this tool. + readNonNegativeIntegerParam(params, "timeoutSeconds"); const thread = params.thread === true; const attachments = Array.isArray(params.attachments) ? (params.attachments as Array<{