const DEFAULT_PROJECT_UNDERSTANDING_TIMEOUT_MS = 8 * 60 * 1000; const DEFAULT_MASTER_REPLY_TIMEOUT_MS = 15 * 60 * 1000; const DEFAULT_THREAD_EXECUTION_TIMEOUT_MS = 30 * 60 * 1000; function normalizeTimeoutMs(value) { const parsed = Number(value); if (!Number.isFinite(parsed) || parsed <= 0) { return null; } return Math.round(parsed); } export function resolveMasterAgentTaskTimeoutMs(config = {}, task = {}) { if (task?.projectUnderstandingTargetProjectId) { return ( normalizeTimeoutMs(config.projectUnderstandingTaskTimeoutMs) ?? DEFAULT_PROJECT_UNDERSTANDING_TIMEOUT_MS ); } if ( task?.taskType === "dispatch_execution" || (task?.taskType === "conversation_reply" && task?.projectId !== "master-agent") ) { return ( normalizeTimeoutMs(config.threadExecutionTaskTimeoutMs) ?? DEFAULT_THREAD_EXECUTION_TIMEOUT_MS ); } return normalizeTimeoutMs(config.masterAgentReplyTimeoutMs) ?? DEFAULT_MASTER_REPLY_TIMEOUT_MS; } export async function runWithTaskTimeout({ timeoutMs, label, onTimeout }, operation) { const normalizedTimeoutMs = normalizeTimeoutMs(timeoutMs); if (!normalizedTimeoutMs) { return await operation(); } return await new Promise((resolve, reject) => { let settled = false; const finish = (callback, value) => { if (settled) { return; } settled = true; clearTimeout(timer); callback(value); }; const timer = setTimeout(async () => { try { await onTimeout?.(); } catch { // Timeout cleanup is best-effort. The caller still gets a timeout error. } finish( reject, new Error(`${label || "master_agent_task"} exceeded timeout after ${normalizedTimeoutMs}ms`), ); }, normalizedTimeoutMs); Promise.resolve() .then(operation) .then((value) => finish(resolve, value)) .catch((error) => finish(reject, error)); }); }