fix: validate session spawn timeout

This commit is contained in:
Peter Steinberger
2026-05-28 19:41:11 -04:00
parent f2843d3d79
commit 9a4aa438bb
2 changed files with 24 additions and 12 deletions

View File

@@ -446,6 +446,24 @@ describe("sessions_spawn tool", () => {
expect(spawnArgs.runTimeoutSeconds).toBe(2); 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 () => { it("passes inherited workspaceDir from tool context, not from tool args", async () => {
const tool = createSessionsSpawnTool({ const tool = createSessionsSpawnTool({
agentSessionKey: "agent:main:main", agentSessionKey: "agent:main:main",

View File

@@ -35,6 +35,7 @@ import type { AnyAgentTool } from "./common.js";
import { import {
jsonResult, jsonResult,
normalizeToolModelOverride, normalizeToolModelOverride,
readNonNegativeIntegerParam,
readStringParam, readStringParam,
ToolInputError, ToolInputError,
} from "./common.js"; } from "./common.js";
@@ -169,9 +170,9 @@ function createSessionsSpawnToolSchema(params: {
model: Type.Optional(Type.String()), model: Type.Optional(Type.String()),
thinking: Type.Optional(Type.String()), thinking: Type.Optional(Type.String()),
cwd: 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. // 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 ...(params.threadAvailable
? { ? {
thread: Type.Optional( thread: Type.Optional(
@@ -342,17 +343,10 @@ export function createSessionsSpawnTool(
if (runtime === "acp" && context === "fork") { if (runtime === "acp" && context === "fork") {
throw new Error('context="fork" is only supported for runtime="subagent".'); 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 = const runTimeoutSeconds =
typeof timeoutSecondsCandidate === "number" && Number.isFinite(timeoutSecondsCandidate) readNonNegativeIntegerParam(params, "runTimeoutSeconds") ??
? Math.max(0, Math.floor(timeoutSecondsCandidate)) // Back-compat: older callers used timeoutSeconds for this tool.
: undefined; readNonNegativeIntegerParam(params, "timeoutSeconds");
const thread = params.thread === true; const thread = params.thread === true;
const attachments = Array.isArray(params.attachments) const attachments = Array.isArray(params.attachments)
? (params.attachments as Array<{ ? (params.attachments as Array<{