Honor local agent timeout defaults

This commit is contained in:
Codex committed 2026-07-07 16:05:20 -04:00
1 parent e53c61e712
commit 75b7ca02ef
3 files changed
+26 -10

No files matched your search

+8 -7
View File
@@ -821,13 +821,14 @@ export class TempestCommand extends EventEmitter<CommandEvents> {
* GENEROUS per-dispatch wall-clock backstop (ms). If a single task dispatch stays * 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 * 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 * 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 * operator promise wedges. Deliberately large (default 5 min for API models,
* legitimately-slow-but-working LLM task (e.g. opus planning ~165s); it only fires * 30 min for local-agent backends) so it does not kill legitimately slow local
* on truly-hung dispatches. Override via T3MP3ST_TASK_TIMEOUT_MS. * 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 * Resolve the dispatch timeout from the environment, falling back to the
* default. Guards against a non-numeric / non-positive override. * provider-specific default. Guards against a non-numeric / non-positive override.
*/ */
private static resolveTaskTimeoutMs(provider?: LLMProvider): number { private static resolveTaskTimeoutMs(provider?: LLMProvider): number {
const DEFAULT_TASK_TIMEOUT_MS = 300000; // 5 minutes — generous backstop, not a deadline const DEFAULT_TASK_TIMEOUT_MS = 300000; // 5 minutes — generous backstop, not a deadline
@@ -1035,8 +1036,8 @@ export class TempestCommand extends EventEmitter<CommandEvents> {
* never advances, and completeMission() is never called: the mission HANGS. * 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 * 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 * exceeded the GENEROUS wall-clock backstop (taskTimeoutMs), or that exhibit
* that exhibit the exact wedge symptom (owning operator is `executing`/`tasked` * the exact wedge symptom (owning operator is `executing`/`tasked`
* but its currentTask is null — i.e. it has silently dropped the task). For each * 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 * 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 * bookkeeping, and (3) reset the owning operator back to idle so it can take new
+2 -1
View File
@@ -1070,7 +1070,8 @@ class LocalAgentAdapter implements LLMProviderAdapter {
async chat(messages: LLMMessage[], options?: ChatOptions): Promise<LLMResponse> { async chat(messages: LLMMessage[], options?: ChatOptions): Promise<LLMResponse> {
const agentId = this.config.model || 'codex'; const agentId = this.config.model || 'codex';
const prompt = this.formatPrompt(messages, options); 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 // 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. // ReAct loop EXECUTES them instead of treating this planning turn as the (abstaining) final answer.
const toolCalls = options?.tools?.length ? parseTextToolCalls(content) : undefined; const toolCalls = options?.tools?.length ? parseTextToolCalls(content) : undefined;
+16 -2
View File
@@ -6478,6 +6478,18 @@ function providerNeedsApiKey(provider: string): boolean {
return !['codex', 'mock', 'local', 'local-agent'].includes(provider); 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 // 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 // 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 // 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', model: model || 'codex',
maxTokens: 8192, maxTokens: 8192,
temperature: 0.4, 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); const baseConfig = config.getLLMConfig(selectedProvider as any, model);
@@ -6515,7 +6529,7 @@ function resolveGeneralLLMConfig(provider: string, model: string | undefined, ap
apiKey: effectiveKey, apiKey: effectiveKey,
maxTokens: 8192, maxTokens: 8192,
temperature: 0.4, 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
}; };
} }