fix(talk): cap fast context timeout delay

This commit is contained in:
Peter Steinberger
2026-05-29 14:30:59 -04:00
parent f440121a49
commit 75ef73d4f7
2 changed files with 53 additions and 1 deletions

View File

@@ -0,0 +1,47 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js";
const mocks = vi.hoisted(() => ({
getActiveMemorySearchManager: vi.fn(),
}));
vi.mock("../plugins/memory-runtime.js", () => ({
getActiveMemorySearchManager: mocks.getActiveMemorySearchManager,
}));
import { resolveRealtimeVoiceFastContextConsult } from "./fast-context-runtime.js";
describe("resolveRealtimeVoiceFastContextConsult", () => {
afterEach(() => {
vi.restoreAllMocks();
mocks.getActiveMemorySearchManager.mockReset();
});
it("caps oversized fast-context timeouts before scheduling Node timers", async () => {
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
mocks.getActiveMemorySearchManager.mockResolvedValue({
manager: {
search: vi.fn().mockResolvedValue([]),
},
});
await expect(
resolveRealtimeVoiceFastContextConsult({
cfg: {},
agentId: "main",
sessionKey: "voice:15550001234",
config: {
enabled: true,
timeoutMs: Number.MAX_SAFE_INTEGER,
maxResults: 3,
sources: ["memory", "sessions"],
fallbackToConsult: true,
},
args: { question: "What do you remember?" },
logger: {},
}),
).resolves.toEqual({ handled: false });
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS);
});
});

View File

@@ -1,6 +1,7 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { formatErrorMessage } from "../infra/errors.js";
import { getActiveMemorySearchManager } from "../plugins/memory-runtime.js";
import { clampTimerTimeoutMs } from "../shared/number-coercion.js";
import type { RealtimeVoiceAgentConsultResult } from "./agent-consult-runtime.js";
import { parseRealtimeVoiceAgentConsultArgs } from "./agent-consult-tool.js";
@@ -97,12 +98,16 @@ function buildMissText(query: string, labels: RealtimeVoiceFastContextLabels): s
}
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
const resolvedTimeoutMs = clampTimerTimeoutMs(timeoutMs) ?? 1;
let timer: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
promise,
new Promise<T>((_resolve, reject) => {
timer = setTimeout(() => reject(new RealtimeFastContextTimeoutError(timeoutMs)), timeoutMs);
timer = setTimeout(
() => reject(new RealtimeFastContextTimeoutError(resolvedTimeoutMs)),
resolvedTimeoutMs,
);
}),
]);
} finally {