/** * Event subsystem — typed events, dispatcher, event bus, and session tape. * * Mirrors `_harness/_events.py`, `_dispatcher.py`, `_event_bus.py`, * `_session/_tape.py`, `_session/_tape_backend.py`. * * In the Python harness these live in separate files; here they share one * module since they form a single observer subsystem. * * The tape stamps every recorded event with a monotonic `seq` so * downstream consumers (reactors, streams, replays) can resume from a * cursor instead of rebuilding history from scratch. `since(n)` walks * the buffered prefix synchronously; `tail(fromSeq)` is an async * iterator that follows live writes. Backends (InMemory, JSONL, Null, * Chain) are pluggable via the `TapeBackend` interface. */ export type HarnessEventKind = "session_start" | "session_end" | "turn_start" | "turn_end" | "text" | "tool_call_start" | "tool_call_end" | "tool_call_error" | "model_call_start" | "model_call_end" | "compression" | "approval_request" | "approval_response" | "interrupt" | "error" | "step_started" | "step_completed" | "iteration_started" | "iteration_completed" | "branch_started" | "branch_completed" | "subagent_started" | "subagent_completed" | "attempt_failed" | "signal_changed"; export interface HarnessEventBase { kind: HarnessEventKind; timestamp: number; /** * Monotonic per-session sequence number. Assigned by `SessionTape` * at record time; undefined until the event is written to a tape. * Consumers should prefer `EventRecord` when they need `seq` to be * present. */ seq?: number; } export interface TextEvent extends HarnessEventBase { kind: "text"; text: string; } export interface ToolEvent extends HarnessEventBase { kind: "tool_call_start" | "tool_call_end" | "tool_call_error"; toolName: string; args?: Record; result?: unknown; durationMs?: number; error?: string; } export interface ModelEvent extends HarnessEventBase { kind: "model_call_start" | "model_call_end"; model?: string; inputTokens?: number; outputTokens?: number; } export interface StepLifecycleEvent extends HarnessEventBase { kind: "step_started" | "step_completed"; agentName: string; agentType?: string; parentName?: string; ok?: boolean; error?: string; } export interface IterationLifecycleEvent extends HarnessEventBase { kind: "iteration_started" | "iteration_completed"; loopName: string; iteration: number; ok?: boolean; } export interface BranchLifecycleEvent extends HarnessEventBase { kind: "branch_started" | "branch_completed"; fanoutName: string; branchName: string; ok?: boolean; error?: string; } export interface SubagentLifecycleEvent extends HarnessEventBase { kind: "subagent_started" | "subagent_completed"; role: string; prompt?: string; output?: string; isError?: boolean; error?: string; } export interface AttemptFailedEvent extends HarnessEventBase { kind: "attempt_failed"; agentName: string; attempt: number; error: string; } export interface SignalChangedEvent extends HarnessEventBase { kind: "signal_changed"; name: string; version: number; value: unknown; previous: unknown; } export interface GenericEvent extends HarnessEventBase { kind: Exclude; data?: Record; } export type HarnessEvent = TextEvent | ToolEvent | ModelEvent | StepLifecycleEvent | IterationLifecycleEvent | BranchLifecycleEvent | SubagentLifecycleEvent | AttemptFailedEvent | SignalChangedEvent | GenericEvent; /** An event that has been recorded and therefore carries a `seq`. */ export type EventRecord = HarnessEvent & { seq: number; }; export type EventSubscriber = (event: HarnessEvent) => void; /** * Lightweight pub/sub keyed on event kind. The dispatcher is the * canonical translation layer from runtime events into HarnessEvents. */ export declare class EventDispatcher { private readonly listeners; on(kind: HarnessEventKind | "*", fn: EventSubscriber): this; emit(event: HarnessEvent): void; /** Subscribe a subscriber to ALL events. */ subscribe(fn: EventSubscriber): this; clear(): void; } export interface EventBusOptions { maxBuffer?: number; } /** * Session-scoped typed event backbone. Wraps an EventDispatcher and * optionally retains the last N events. Other harness modules * (renderer, tape, hooks) subscribe to the bus instead of building * their own observation layers. */ export declare class EventBus extends EventDispatcher { readonly maxBuffer: number; readonly history: HarnessEvent[]; constructor(opts?: EventBusOptions); emit(event: HarnessEvent): void; /** Create a SessionTape pre-subscribed to this bus. */ tape(opts?: SessionTapeOptions): SessionTape; /** Build a before-tool callback that emits `tool_call_start`. */ beforeToolHook(): (toolName: string, args: Record) => void; /** Build an after-tool callback that emits `tool_call_end`. */ afterToolHook(): (toolName: string, result: unknown) => void; } /** * Pluggable persistence target for `SessionTape`. Implementations can * mirror the tape to disk, a remote queue, or nothing at all. * * The tape always keeps its own in-memory deque; the backend is * write-through. Reads (`since`, `tail`) are served from the tape's * buffer, not the backend, so durability doesn't pay a read tax. */ export interface TapeBackend { /** Persist a single recorded event. Must not throw for routine writes. */ append(event: EventRecord): void; /** Flush any buffered writes. No-op for backends that write eagerly. */ flush?(): void; /** Close the backend (e.g. release file handles). */ close?(): void; } /** Default backend — writes to nowhere. */ export declare class NullBackend implements TapeBackend { append(_event: EventRecord): void; } /** * In-memory backend. Useful for tests that want to assert what the * tape would have persisted without touching disk. */ export declare class InMemoryBackend implements TapeBackend { readonly entries: EventRecord[]; append(event: EventRecord): void; clear(): void; } export interface JsonlBackendOptions { /** Path to the JSONL file. Parent directories are created as needed. */ path: string; /** Truncate the file on construction. Default: false (append-only). */ truncate?: boolean; } /** Append-only JSONL file backend. One event per line. */ export declare class JsonlBackend implements TapeBackend { readonly path: string; constructor(opts: JsonlBackendOptions); append(event: EventRecord): void; } /** Fan an event out to multiple backends. First wins on exceptions. */ export declare class ChainBackend implements TapeBackend { readonly backends: TapeBackend[]; constructor(backends: TapeBackend[]); append(event: EventRecord): void; flush(): void; close(): void; } export interface SessionTapeOptions { maxEvents?: number; backend?: TapeBackend; } /** * Records a sequence of HarnessEvents and can serialize them to JSONL * for later replay or audit. Every recorded event is stamped with a * monotonic `seq`; consumers can resume from a cursor via `since(seq)` * (synchronous prefix drain) or `tail(fromSeq)` (async live follow). */ export declare class SessionTape { readonly maxEvents: number; readonly events: EventRecord[]; readonly backend: TapeBackend; private _nextSeq; private readonly _waiters; constructor(opts?: SessionTapeOptions); /** Sequence number that would be assigned to the *next* event. */ get head(): number; /** * Append an event. Returns the recorded version with `seq` populated. * The tape mutates the input object with the assigned `seq` so * downstream subscribers can read it from their copy too. */ record(event: HarnessEvent): EventRecord; /** Iterate over recorded events whose seq is ≥ `fromSeq`. */ since(fromSeq?: number): Iterable; /** * Follow the tape asynchronously starting at `fromSeq`. Yields events * as they are recorded. The iterator is open-ended — callers must * break out themselves (e.g. via an `AbortSignal`) when done. */ tail(fromSeq?: number, opts?: { signal?: AbortSignal; }): AsyncIterable; /** Filter events by kind. */ filter(kind: HarnessEventKind): EventRecord[]; /** Save events as JSONL. */ save(path: string): void; /** * Load events from a JSONL file (replaces any in-memory events). * Missing `seq` fields are backfilled from the line index so * pre-seq tapes still load cleanly. */ load(path: string): void; /** Replay every recorded event through a subscriber. */ replay(subscriber: EventSubscriber): void; clear(): void; /** Count of events grouped by kind. Handy for assertions. */ summary(): Record; get size(): number; } //# sourceMappingURL=events.d.ts.map