From 75ef73d4f75e04bd1b6c7ee575937b3cccc17fd5 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 29 May 2026 14:30:59 -0400 Subject: [PATCH] fix(talk): cap fast context timeout delay --- src/talk/fast-context-runtime.test.ts | 47 +++++++++++++++++++++++++++ src/talk/fast-context-runtime.ts | 7 +++- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/talk/fast-context-runtime.test.ts diff --git a/src/talk/fast-context-runtime.test.ts b/src/talk/fast-context-runtime.test.ts new file mode 100644 index 00000000000..812a408b2f0 --- /dev/null +++ b/src/talk/fast-context-runtime.test.ts @@ -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); + }); +}); diff --git a/src/talk/fast-context-runtime.ts b/src/talk/fast-context-runtime.ts index 4b1379be2eb..880ca9387b6 100644 --- a/src/talk/fast-context-runtime.ts +++ b/src/talk/fast-context-runtime.ts @@ -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(promise: Promise, timeoutMs: number): Promise { + const resolvedTimeoutMs = clampTimerTimeoutMs(timeoutMs) ?? 1; let timer: ReturnType | undefined; try { return await Promise.race([ promise, new Promise((_resolve, reject) => { - timer = setTimeout(() => reject(new RealtimeFastContextTimeoutError(timeoutMs)), timeoutMs); + timer = setTimeout( + () => reject(new RealtimeFastContextTimeoutError(resolvedTimeoutMs)), + resolvedTimeoutMs, + ); }), ]); } finally {