refactor(eval): changed timeout from inactivity to wall-clock budget

- Only bridge heartbeats (`agent()`/`llm()`) now re-arm the watchdog; compute, stdout, `log()`/`phase()`, and ordinary tool calls count against the budget.
- Emitted an immediate heartbeat at bridge call start to avoid early abort near budget edge.
- Removed `idle` flag and "of inactivity" suffix from timeout annotation strings.
- Updated docs, prompts, and comments to reflect the new wall-clock semantics.
This commit is contained in:
can1357
2026-06-01 17:17:00 +02:00
parent c7923e77eb
commit dbd9489010
11 changed files with 200 additions and 66 deletions
+7 -17
View File
@@ -232,20 +232,18 @@ async function waitForPromiseWithCancellation<T>(
// Result formatting
// ---------------------------------------------------------------------------
function formatTimeoutAnnotation(timeoutMs?: number, idle = false): string | undefined {
const suffix = idle ? " of inactivity" : "";
function formatTimeoutAnnotation(timeoutMs?: number): string | undefined {
if (timeoutMs === undefined) return "Command timed out";
const secs = Math.max(1, Math.round(timeoutMs / 1000));
return `Command timed out after ${secs} seconds${suffix}`;
return `Command timed out after ${secs} seconds`;
}
function formatKernelTimeoutAnnotation(timeoutMs: number | undefined, kernelKilled: boolean, idle = false): string {
function formatKernelTimeoutAnnotation(timeoutMs: number | undefined, kernelKilled: boolean): string {
const secs = timeoutMs === undefined ? undefined : Math.max(1, Math.round(timeoutMs / 1000));
const suffix = idle ? " of inactivity" : "";
if (kernelKilled) {
return `eval cell timed out${suffix} and the kernel was unresponsive to interrupt; the kernel has been killed and will be recreated on the next call.`;
return "eval cell timed out and the kernel was unresponsive to interrupt; the kernel has been killed and will be recreated on the next call.";
}
const duration = secs === undefined ? "the configured timeout" : `${secs}s${suffix}`;
const duration = secs === undefined ? "the configured timeout" : `${secs}s`;
return `eval cell timed out after ${duration}; kernel interrupted but remains running. Reset the kernel via { reset: true } if state appears corrupted.`;
}
@@ -489,10 +487,6 @@ async function executeWithKernel(
const displayOutputs: KernelDisplayOutput[] = [];
const deadlineMs = getExecutionDeadlineMs(options);
let executionTimeoutMs: number | undefined;
// Idle mode: the caller (eval tool) drives cancellation via an idle-aware
// signal and passes no wall-clock deadline, so annotate timeouts with the
// configured inactivity budget rather than a remaining-deadline figure.
const idleMode = deadlineMs === undefined && options?.idleTimeoutMs !== undefined;
// Collect every display output and, for status events, stream them live so
// long-running bridge helpers (e.g. `agent()`) surface progress mid-cell.
@@ -530,11 +524,7 @@ async function executeWithKernel(
if (result.cancelled) {
const annotation = result.timedOut
? formatKernelTimeoutAnnotation(
executionTimeoutMs ?? options?.idleTimeoutMs,
result.kernelKilled ?? false,
idleMode,
)
? formatKernelTimeoutAnnotation(executionTimeoutMs ?? options?.idleTimeoutMs, result.kernelKilled ?? false)
: undefined;
return {
exitCode: undefined,
@@ -572,7 +562,7 @@ async function executeWithKernel(
displayOutputs,
stdinRequested: false,
...(await sink.dump(
timedOut ? formatTimeoutAnnotation(executionTimeoutMs ?? options?.idleTimeoutMs, idleMode) : undefined,
timedOut ? formatTimeoutAnnotation(executionTimeoutMs ?? options?.idleTimeoutMs) : undefined,
)),
};
}