// TASK-consult-answer-clock-20261001: the answer clock is a five-minute hang // guard, the general / no-birth-minute loop runs on it from the start, and the // domain budget does not move with it. import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { AGENT_TIMEOUT_MS, CONSULTATION_ANSWER_TIMEOUT_MS, CONSULTATION_DOMAIN_WALL_CLOCK_MS, MAX_CONSULTATION_DOMAINS, createConsultationRunClock, } from "../src/mastra/consultation-tools.ts"; const route = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8"); const tools = readFileSync(new URL("../src/mastra/consultation-tools.ts", import.meta.url), "utf8"); test("the answer clock is five minutes and the tool phase stays 110s", () => { assert.equal(CONSULTATION_ANSWER_TIMEOUT_MS, 300_000); assert.equal(AGENT_TIMEOUT_MS, 110_000); const maxDuration = Number(route.match(/export const maxDuration = (\d+);/)?.[1]); assert.ok(AGENT_TIMEOUT_MS + CONSULTATION_ANSWER_TIMEOUT_MS + 60_000 <= maxDuration * 1000, "setup and settlement still fit"); }); test("the domain budget is unchanged by the answer clock", () => { // Same numbers as before the change: 110s tool phase minus a 45s reserve, // 31s per domain, so two domains run. assert.equal(CONSULTATION_DOMAIN_WALL_CLOCK_MS, 65_000); assert.equal(MAX_CONSULTATION_DOMAINS, 2); const formula = tools.match(/export const CONSULTATION_DOMAIN_WALL_CLOCK_MS = [^;]+;/)?.[0] ?? ""; assert.match(formula, /AGENT_TIMEOUT_MS - CONSULTATION_ANSWER_RESERVE_MS/); assert.doesNotMatch(formula, /CONSULTATION_ANSWER_TIMEOUT_MS/); }); test("a loop with no calculation runs on the answer clock from the start", async () => { const clock = createConsultationRunClock({ toolPhaseMs: 30, answerMs: 200, answerFromStart: true }); await new Promise((resolve) => setTimeout(resolve, 60)); assert.equal(clock.toolSignal.aborted, true, "the tool phase's timer still fires"); assert.equal(clock.loopSignal.aborted, false, "but it does not cut a loop that is already writing the answer"); await new Promise((resolve) => setTimeout(resolve, 220)); assert.equal(clock.loopSignal.aborted, true, "the answer clock still bounds it"); assert.equal(clock.answerSignal(), clock.answerSignal(), "retries and continuations share the same clock"); }); test("the consult route starts the general / no-birth-minute loop on the answer clock", () => { assert.match( route, /const runClock = createConsultationRunClock\(\{[\s\S]*?answerFromStart: usesPublicDailyGeneralAgent\(consultationMode, generalDailyContext\),\s*\}\);/, ); // That branch still streams on the shared loop signal and keeps its traces. const general = route.match(/if \(usesPublicDailyGeneralAgent\(consultationMode, generalDailyContext\)\) \{[\s\S]*?\n {4}\}\n/)?.[0] ?? ""; assert.match(general, /await streamWithOverflowRetry\(agent\)/); assert.match(general, /onError: \(error\) => settleRun\(\s*cancel,/); });