fix(agents): default non-finite run wait timeouts

This commit is contained in:
Peter Steinberger
2026-05-28 23:05:26 -04:00
parent 01d9963e4e
commit f2dfb67f2c
2 changed files with 41 additions and 2 deletions

View File

@@ -252,6 +252,22 @@ describe("waitForAgentRun", () => {
});
});
it("defaults non-finite wait timeouts before sending agent.wait", async () => {
callGatewayMock.mockResolvedValue({ status: "ok" });
const result = await waitForAgentRun({ runId: "run-nan", timeoutMs: Number.NaN });
expect(result).toEqual({ status: "ok" });
expect(callGatewayMock).toHaveBeenCalledWith({
method: "agent.wait",
params: {
runId: "run-nan",
timeoutMs: 1,
},
timeoutMs: 2_001,
});
});
it("preserves timing metadata from agent.wait", async () => {
callGatewayMock.mockResolvedValue({
status: "ok",
@@ -457,4 +473,22 @@ describe("waitForAgentRunsToDrain", () => {
expectAgentWaitRequest(requireRequestAt(requests, 0), "run-1", 1_000);
expectAgentWaitRequest(requireRequestAt(requests, 1), "run-2", 1_000);
});
it("defaults non-finite drain timeouts before computing the deadline", async () => {
callGatewayMock.mockResolvedValue({ status: "ok" });
let activeRunIds = ["run-1"];
const result = await waitForAgentRunsToDrain({
timeoutMs: Number.NaN,
getPendingRunIds: () => {
const current = activeRunIds;
activeRunIds = [];
return current;
},
});
expect(result.timedOut).toBe(false);
expect(Number.isFinite(result.deadlineAtMs)).toBe(true);
expectAgentWaitRequest(requireRequestAt(gatewayWaitRequests(), 0), "run-1", 1);
});
});

View File

@@ -1,6 +1,7 @@
import { callGateway } from "../gateway/call.js";
import { formatErrorMessage } from "../infra/errors.js";
import { normalizeBlockedLivenessWaitStatus } from "../shared/agent-liveness.js";
import { parseFiniteNumber } from "../shared/number-coercion.js";
import { AGENT_RUN_ABORTED_ERROR, isAbortedAgentStopReason } from "./run-termination.js";
import {
normalizeAgentRunTimeoutPhase,
@@ -19,6 +20,10 @@ let runWaitDeps: {
callGateway: GatewayCaller;
} = defaultRunWaitDeps;
function resolveRunWaitTimeoutMs(value: number | undefined): number {
return Math.max(1, Math.floor(parseFiniteNumber(value) ?? 1));
}
export type AssistantReplySnapshot = {
text?: string;
fingerprint?: string;
@@ -176,7 +181,7 @@ export async function waitForAgentRun(params: {
timeoutMs: number;
callGateway?: GatewayCaller;
}): Promise<AgentWaitResult> {
const timeoutMs = Math.max(1, Math.floor(params.timeoutMs));
const timeoutMs = resolveRunWaitTimeoutMs(params.timeoutMs);
try {
const wait = await (params.callGateway ?? runWaitDeps.callGateway)({
method: "agent.wait",
@@ -246,7 +251,7 @@ export async function waitForAgentRunsToDrain(params: {
callGateway?: GatewayCaller;
}): Promise<AgentRunsDrainResult> {
const deadlineAtMs =
params.deadlineAtMs ?? Date.now() + Math.max(1, Math.floor(params.timeoutMs ?? 0));
params.deadlineAtMs ?? Date.now() + resolveRunWaitTimeoutMs(params.timeoutMs);
// Runs may finish and spawn more runs, so refresh until no pending IDs remain.
let pendingRunIds = new Set<string>(