/** * * Why a separate module (vs adding the logic inline to * `dispatch-record-writer.ts`): * - the writer is the on-disk format owner; the eval logic is a small * pure function over a `DispatchRecord` snapshot. * - keeps the writer surface unchanged (no extra `markStartupTimeout` * method) so other consumers (heartbeat, watch, batch poller) do not * have to learn a new mutation API. * - a future `markStartupTimeout` writer helper, if added, would import * this module's STARTUP_OUTCOME / DEFAULT_STARTUP_BUDGET_MS so the * CLI help text and the eval share a single source of truth. * * Contract (see tests/unit/dispatch/startup-timeout.test.ts for the * pinned assertions): * * evaluateStartupTimeout(record, now, opts) → StartupTimeoutEvaluation * * - `outcome: 'within-budget' | 'never-started' | 'unreadable'` * - `targetStatus: DispatchRecordStatus` — the status to write when * `marked === true`. Distinct from `stale` (heartbeat seen, gone * quiet) and from `no-execution` (the pre-slice silent default). * - `marked: boolean` — whether the caller should write * `targetStatus` to the record. * - `reason: string` — short human-readable rationale, rendered in * the watcher text line and in the dispatch envelope's * `notes[]`. * * DEFAULT_STARTUP_BUDGET_MS = 60_000 * - 1 order of magnitude above the measured 4–6s cold-start figure * from .peaks/memory/2026-07-28-sub-agent-visibility-issue.md * - well below the 5-minute heartbeat stale threshold so this * fires first * - configurable via opts.budgetMs * * STARTUP_OUTCOME = { NEVER_STARTED: 'never-started', UNREADABLE: * 'unreadable' } * - exported so the CLI help text, the watch command, and the * reader's legacy fallback all reference the same strings. */ import type { DispatchRecord, DispatchRecordStatus } from './dispatch-record-writer.js'; /** * Default budget: 60s. 1 order of magnitude above the measured 4–6s * cold-start figure from .peaks/memory/2026-07-28-sub-agent-visibility- * issue.md; well below the 5-min heartbeat stale threshold so this * fires first. */ export declare const DEFAULT_STARTUP_BUDGET_MS = 60000; /** * String constants for the new terminal statuses. Distinct from the * pre-slice `stale` (heartbeat seen, then quiet) and `no-execution` * (legacy silent fallback). These are added to the writer's * `DispatchRecordStatus` union in the same slice; the constants here * are the canonical source so the CLI help / watch / writer all * reference the same strings. */ export declare const STARTUP_OUTCOME: { readonly NEVER_STARTED: "never-started"; readonly UNREADABLE: "unreadable"; }; export type StartupTimeoutOutcome = 'within-budget' | 'never-started' | 'unreadable'; export interface StartupTimeoutOptions { /** Override the default 60s budget. */ readonly budgetMs?: number; /** * Test seam: when true, the record is treated as if its body is * corrupt (status field unparseable). The function returns * `{ outcome: 'unreadable', ... }` and recommends writing the * `unreadable` status. The CLI never sets this; the read path does. */ readonly corrupt?: boolean; } export interface StartupTimeoutEvaluation { readonly outcome: StartupTimeoutOutcome; readonly marked: boolean; readonly targetStatus: DispatchRecordStatus; readonly reason: string; readonly ageMs: number; readonly budgetMs: number; } /** * Pure eval. Returns a typed triple; the caller decides whether to * write `targetStatus` to the record. */ export declare function evaluateStartupTimeout(record: DispatchRecord, now?: () => Date, options?: StartupTimeoutOptions): StartupTimeoutEvaluation;