diff --git a/src/index.ts b/src/index.ts index e365562..e975767 100755 --- a/src/index.ts +++ b/src/index.ts @@ -821,13 +821,14 @@ export class TempestCommand extends EventEmitter { * GENEROUS per-dispatch wall-clock backstop (ms). If a single task dispatch stays * in-flight longer than this, the tick loop force-resolves it as a timeout so * pendingOrActive can reach 0 and the mission can advance/complete even when an - * operator promise wedges. Deliberately large (default 5 min) so it NEVER kills a - * legitimately-slow-but-working LLM task (e.g. opus planning ~165s); it only fires - * on truly-hung dispatches. Override via T3MP3ST_TASK_TIMEOUT_MS. + * operator promise wedges. Deliberately large (default 5 min for API models, + * 30 min for local-agent backends) so it does not kill legitimately slow local + * CLI work; it only fires on truly-hung dispatches. Override via + * T3MP3ST_TASK_TIMEOUT_MS. */ /** - * Resolve the dispatch timeout from the environment, falling back to the 5-minute - * default. Guards against a non-numeric / non-positive override. + * Resolve the dispatch timeout from the environment, falling back to the + * provider-specific default. Guards against a non-numeric / non-positive override. */ private static resolveTaskTimeoutMs(provider?: LLMProvider): number { const DEFAULT_TASK_TIMEOUT_MS = 300000; // 5 minutes — generous backstop, not a deadline @@ -1035,8 +1036,8 @@ export class TempestCommand extends EventEmitter { * never advances, and completeMission() is never called: the mission HANGS. * * On every tick we scan the in-flight dispatches and force-resolve any that have - * exceeded the GENEROUS wall-clock backstop (taskTimeoutMs, default 5 min), or - * that exhibit the exact wedge symptom (owning operator is `executing`/`tasked` + * exceeded the GENEROUS wall-clock backstop (taskTimeoutMs), or that exhibit + * the exact wedge symptom (owning operator is `executing`/`tasked` * but its currentTask is null — i.e. it has silently dropped the task). For each * we: (1) mark the task failed/timed-out in the queue, (2) clear its dispatch * bookkeeping, and (3) reset the owning operator back to idle so it can take new diff --git a/src/llm/index.ts b/src/llm/index.ts index d796e73..0b426f3 100755 --- a/src/llm/index.ts +++ b/src/llm/index.ts @@ -1070,7 +1070,8 @@ class LocalAgentAdapter implements LLMProviderAdapter { async chat(messages: LLMMessage[], options?: ChatOptions): Promise { const agentId = this.config.model || 'codex'; const prompt = this.formatPrompt(messages, options); - const content = (await localAgentChat(agentId, prompt, { timeoutMs: this.config.timeout || 240000 })).trim(); + const timeoutMs = typeof this.config.timeout === 'number' && this.config.timeout > 0 ? this.config.timeout : undefined; + const content = (await localAgentChat(agentId, prompt, { timeoutMs })).trim(); // Tool-calling over text: if the Arsenal was offered, parse the agent's tool requests so the // ReAct loop EXECUTES them instead of treating this planning turn as the (abstaining) final answer. const toolCalls = options?.tools?.length ? parseTextToolCalls(content) : undefined; diff --git a/src/server.ts b/src/server.ts index c6ce004..01b5fb2 100755 --- a/src/server.ts +++ b/src/server.ts @@ -6478,6 +6478,18 @@ function providerNeedsApiKey(provider: string): boolean { return !['codex', 'mock', 'local', 'local-agent'].includes(provider); } +function readPositiveTimeoutEnv(name: string): number | undefined { + const raw = process.env[name]; + if (raw == null || raw.trim() === '') return undefined; + const parsed = Number(raw); + return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined; +} + +function readGeneralTimeoutEnv(): number | undefined { + return readPositiveTimeoutEnv('TEMPEST_GENERAL_TIMEOUT_MS') + ?? readPositiveTimeoutEnv('T3MP3ST_GENERAL_TIMEOUT_MS'); +} + // SECURITY NOTE: `apiKey` is accepted from the request BODY here (and in the // mission/general routes that call this). Sending secrets in the body is not // ideal — an Authorization header is preferred — but the same-origin UI posts @@ -6501,7 +6513,9 @@ function resolveGeneralLLMConfig(provider: string, model: string | undefined, ap model: model || 'codex', maxTokens: 8192, temperature: 0.4, - timeout: Number(process.env.TEMPEST_GENERAL_TIMEOUT_MS) || 300000, + timeout: readGeneralTimeoutEnv() + ?? readPositiveTimeoutEnv('T3MP3ST_LOCAL_AGENT_TIMEOUT_MS') + ?? 600000, }; } const baseConfig = config.getLLMConfig(selectedProvider as any, model); @@ -6515,7 +6529,7 @@ function resolveGeneralLLMConfig(provider: string, model: string | undefined, ap apiKey: effectiveKey, maxTokens: 8192, temperature: 0.4, - timeout: Number(process.env.TEMPEST_GENERAL_TIMEOUT_MS) || 300000, // General planning needs room (was a hardcoded 60s); override via env + timeout: readGeneralTimeoutEnv() ?? 300000, // General planning needs room (was a hardcoded 60s); override via env }; }