import type { RunEvent, RunEventType, RunTelemetry } from "@claudexor/schema"; export { RETAINED_OUTPUT_PATH, retainedOutput, writeRetainedOutput } from "./retained-output.js"; export type TerminalRunEventType = Extract; export interface PreparedTerminalEvent { type: TerminalRunEventType; payload: Record; /** Prepared in memory by the receipt owner; a SQL terminal sink writes it. */ telemetry?: RunTelemetry | null; /** * Publish the prepared receipt's canonical commit marker after the durable * journal accepts the terminal, but before file-tail/live observers can see * the per-run terminal event. */ commit?: () => void; /** * Undo preparation/commit side effects when no durable journal authority was * established. The callback must be idempotent. */ rollback?: () => void; } /** A synchronous replacement for terminal persistence and file finalization. * A throw means no authority committed; pending means it committed and repair * remains owed. The event log never repeats the sink's file writes. */ export type TerminalPersistenceHook = (event: RunEvent, telemetry: RunTelemetry | null) => { state: "materialized"; } | { state: "pending"; error: unknown; }; /** * Append-only JSONL event log for a single run. Terminal output and human * summaries are projections; this file is the canonical event stream. * * Every emitted event carries a monotonic per-run `seq` stamped here — the * durable cursor for SSE resume (Last-Event-ID) and snapshot fencing * (detail.lastSeq). The counter initializes from the existing file tail so a * re-opened log (or an out-of-band appender like the control-api audit * writer) continues the sequence instead of restarting it. */ export declare class EventLog { private readonly path; private readonly runId; private readonly taskId; /** * Durable in-process authority. Non-terminal events reach it after their * per-run append; terminal events reach it first so crash recovery can * reconstruct the canonical receipt and per-run tail from the journal. */ private readonly onPersist?; /** * Thread this run is a turn of, when any. Stamped on every event so the * global event multiplex can route live progress to a chat surface without * a reverse job lookup. */ private readonly threadId?; /** Best-effort live observer, invoked only after terminal finalization. */ private readonly onPublish?; private nextSeq; private deferTerminalEvents; private deferredTerminal; private terminalCommittedFlag; private terminalCommitInProgress; private terminalWriterPoisoned; private terminalPersistence?; private prepareOutput?; private beforeTerminal?; constructor(path: string, runId: string, taskId: string, /** * Durable in-process authority. Non-terminal events reach it after their * per-run append; terminal events reach it first so crash recovery can * reconstruct the canonical receipt and per-run tail from the journal. */ onPersist?: ((event: RunEvent) => void) | undefined, /** * Thread this run is a turn of, when any. Stamped on every event so the * global event multiplex can route live progress to a chat surface without * a reverse job lookup. */ threadId?: string | undefined, /** Best-effort live observer, invoked only after terminal finalization. */ onPublish?: ((event: RunEvent) => void) | undefined); /** * Unregister this log as the live writer for its path. Out-of-band * appenders fall back to file-tail stamping afterwards, which is safe once * the owning run is terminal (nobody else holds an in-memory counter). */ dispose(): void; /** Hold the one terminal event while a Delegate parent drains children. * Non-terminal events (notably aggregate budget.cash) continue to append and * therefore remain ordered before the eventual final event. */ deferTerminal(): void; clearDeferredTerminal(): void; /** The terminal held by `deferTerminal()` and not yet flushed, if any (a copy). */ deferredTerminalPending(): { type: RunEventType; payload: Record; } | null; flushDeferredTerminal(): RunEvent | null; /** * Register the engine's once-only terminal preparation hook. It builds the * canonical receipt in memory before the durable authority accepts the * terminal; commit runs afterward and before file-tail/live observers. */ setBeforeTerminal(hook: (type: TerminalRunEventType, payload: Record) => PreparedTerminalEvent | void, prepareOutput?: (type: TerminalRunEventType, payload: Record) => void): void; /** Whether this writer has durably committed its exactly-once terminal. */ terminalCommitted(): boolean; setTerminalPersistence(hook: TerminalPersistenceHook): void; /** Highest seq this writer has appended: its in-memory counter, no file read. */ lastSeq(): number; /** Append a typed run event. Validates against the schema before writing. */ emit(type: RunEventType, payload?: Record, publish?: boolean): RunEvent; private poisonAfterDurableCommit; /** @internal Release a poisoned live owner once journal recovery repairs its tail. */ releaseRecoveredTerminalFence(): boolean; private rollbackTerminal; /** Read and parse all events (skipping malformed lines, which are surfaced separately). */ readAll(): { events: RunEvent[]; malformed: number; }; } /** Shared reader for live terminal preparation and selected interrupted-run recovery. */ export declare function readRunEvents(path: string): { events: RunEvent[]; malformed: number; }; /** * Append an out-of-band event (e.g. a control-api audit record) into a run's * canonical log WITHOUT corrupting the seq space: routes through the live * EventLog when the run is still active (same counter, same onEmit sink), and * falls back to file-tail stamping for terminal runs. */ export declare function appendRunEvent(path: string, runId: string, taskId: string, type: RunEventType, payload?: Record): RunEvent; /** * The run's highest event `seq` for snapshot fencing. A live writer answers from * its in-memory counter (the one seq owner; its file holds exactly the events up * to that seq); any other log answers from its file through `lastSeqInFile`. */ export declare function lastRunEventSeq(path: string): number; /** * Highest `seq` already present in an events.jsonl file (0 for missing/empty). * Legacy lines without seq count by position so a continued log never reuses * a line number an SSE replayer may have already served as a fallback id. * * Seqs only grow within a log (its writer is the one owner and continues from * the tail when reopened), so the last line carries the highest one. When the * last 64 KiB hold that whole line and it carries a numeric seq, the tail * answers; a legacy or torn last line (valued by its position), a last line * longer than the window, or a read failure takes the full scan. */ export declare function lastSeqInFile(path: string): number; //# sourceMappingURL=index.d.ts.map