/**
 * RunStepRecorder — slider-ready ordered list of RunSteps, BUILT
 * INCREMENTALLY during traversal. Real-time recorder, not a walker.
 *
 * Pattern: extends `SequenceStore<RunStep>` (shared storage shelf)
 *          and implements `CombinedRecorder` (FlowRecorder hooks).
 *          Subscribes to the agentfootprint typed-event dispatcher
 *          for actor-arrow events. Each event handler decides whether
 *          to emit a step; state lives on the instance and persists
 *          across the run.
 * Role:    The single source of truth for "what slider positions
 *          exist in this run, and what transitions does each light
 *          up." Lens consumers attach the recorder once and read
 *          `getSteps()` — no per-render re-derivation.
 *
 * Why this matters: walking a saved event log on every live update violates
 * footprintjs's core principle ("collect during traversal, never
 * post-process"), yielding O(N²) total work for a streaming run. The recorder pattern
 * is O(N) — one handler call per event — and matches BoundaryRecorder /
 * FlowchartRecorder / KeyedStore idioms throughout the library.
 *
 * Saved-event consumers feed a complete recording to `ingestDomainEvents`
 * once, then read `getSteps(drillPath?)`. Live consumers attach the recorder
 * directly via `runner.attach(rec)` instead of replaying on every update.
 */
import type { CombinedRecorder, FlowDecisionEvent, FlowForkEvent, FlowLoopEvent, FlowSubflowEvent, TraversalContext } from 'footprintjs';
interface FlowRunEvent {
    readonly payload?: unknown;
    readonly traversalContext?: TraversalContext;
}
import type { AgentfootprintEvent } from '../../events/registry.js';
import type { EventDispatcher, Unsubscribe } from '../../events/dispatcher.js';
import type { DomainEvent } from './BoundaryRecorder.js';
/**
 * One slider position. The smallest scrubable unit of the run.
 *
 * `transitions` is 1+ — fan-out / merge steps light up multiple
 * transitions at once; sequential / decide / react steps light up
 * exactly one. Renderers iterate `transitions` to highlight edges;
 * details panels read `anchor.runtimeStageId`.
 */
export interface RunStep {
    /** 0-based slider index (matches array position in `getSteps()`). */
    readonly seq: number;
    readonly kind: RunStepKind;
    readonly transitions: readonly RunStepTransition[];
    /**
     * Per-step key — required by `SequenceStore<T>` for time-travel
     * utilities (`getEntriesForStep`, `getEntryRanges`). Mirrors
     * `anchor.runtimeStageId`; both fields point at the same value.
     * Top-level placement satisfies the recorder's storage contract.
     */
    readonly runtimeStageId: string;
    /** Anchor for commentary highlight + details pane lookup. */
    readonly anchor: {
        readonly runtimeStageId: string;
        readonly subflowPath: readonly string[];
    };
    /** Human label — short, kind-specific. */
    readonly label: string;
    /** Wall-clock ms at which this step occurred. */
    readonly tsMs: number;
    /** Kind-specific decoration. Discriminate on `kind`. */
    readonly meta?: RunStepMeta;
}
export type RunStepKind = 'sequential' | 'fork' | 'merge' | 'decide' | 'iteration' | 'iteration-exit' | 'react';
export interface RunStepTransition {
    readonly from: string;
    readonly to: string;
    readonly via: 'next' | 'fork-branch' | 'decision-branch' | 'loop-iteration' | 'actor-arrow';
    readonly label?: string;
}
export type RunStepMeta = {
    readonly kind: 'decide';
    readonly chosen: string;
    readonly rationale?: string;
} | {
    readonly kind: 'iteration';
    readonly index: number;
    readonly target: string;
} | {
    readonly kind: 'iteration-exit';
    readonly index: number;
    readonly reason?: string;
} | {
    readonly kind: 'fork';
    readonly parentSubflowId: string;
} | {
    readonly kind: 'merge';
    readonly mergedCount: number;
} | {
    readonly kind: 'react';
    readonly actorArrow: 'user→llm' | 'tool→llm' | 'llm→tool' | 'llm→user';
};
export interface RunStepRecorderOptions {
    readonly id?: string;
}
/** Factory — matches the `boundaryRecorder()` / `topologyRecorder()` style. */
export declare function runStepRecorder(options?: RunStepRecorderOptions): RunStepRecorder;
/**
 * Real-time slider-step recorder. Emits a `RunStep` whenever an event
 * marks a meaningful slider transition. State persists on the instance
 * so successive events update bookkeeping in O(1).
 *
 * Attach via `runner.attach(rec)` for FlowRecorder events; call
 * `rec.subscribe(runner.dispatcher)` for actor-arrow events. The
 * `getSteps(drillPath?)` method returns the already-built list (no
 * walking) with optional drill-scope filtering.
 */
export declare class RunStepRecorder implements CombinedRecorder {
    readonly id: string;
    /** Composition: storage shelf for the slider-step sequence. */
    private readonly store;
    /** Run-boundary observer — fires this.clear() when traversalContext.runId
     *  changes between events. THIS IS THE FIX for the Parallel multi-run
     *  aliasing bug — without it `forkKey = ${parent}@${rid}` collides
     *  because rid resets to `seed#0` on each run. */
    private readonly runIdGuard;
    /** Stack of currently-open boundaries. The recorder owns this
     *  directly because it's a simple stack and frames are recorder-
     *  shaped. */
    private boundaryStack;
    /** Fork-emission coalescing + branch-exit tally. */
    private readonly forks;
    /** Tracks the most-recent leaf exit per depth → "forwards" handoff. */
    private readonly siblings;
    /** Buffers a "this MIGHT be the answer" leaf until onRunEnd. */
    private readonly answerBuffer;
    /** Run-root inference state machine (leaf vs composition). */
    private readonly rootInferrer;
    /** llm.start / llm.end actor-arrow classifier. */
    private readonly actorArrows;
    /** Has the first `asks` step fired? */
    private asksEmitted;
    constructor(options?: RunStepRecorderOptions);
    /**
     * Emit a RunStep, auto-mirroring `anchor.runtimeStageId` to the
     * top-level `runtimeStageId` field that the keyed index uses. Single
     * source of truth (the anchor) — never inconsistent with the storage
     * key.
     */
    private push;
    /** Internal seq-numbering helper — mirrors the store size so each
     *  RunStep gets a unique 0-based index in emit order. */
    private get entryCount();
    clear(): void;
    /** Internal — wipe all per-run state WITHOUT resetting the runIdGuard
     *  itself. Called by `clear()` (which then resets the guard) AND by
     *  the runIdGuard's onNewRun callback (where the guard is mid-update
     *  and must NOT be reset, only the recorder's data should be).
     *
     *  Note: each sub-tracker owns its OWN clear; the orchestrator just
     *  fans out. Adding new state to a sub-tracker requires no edit here. */
    private resetForNewRun;
    private observeRunId;
    onRunStart(event: FlowRunEvent): void;
    onRunEnd(event: FlowRunEvent): void;
    onSubflowEntry(event: FlowSubflowEvent): void;
    onSubflowExit(event: FlowSubflowEvent): void;
    onFork(event: FlowForkEvent): void;
    onDecision(event: FlowDecisionEvent): void;
    onLoop(event: FlowLoopEvent): void;
    /**
     * Subscribe to the runner's typed-event dispatcher and emit a
     * `react` RunStep on every `llm.start` / `llm.end`. The recorder
     * classifies `actorArrow` locally (mirrors BoundaryRecorder's
     * pattern) so consumers don't have to depend on BoundaryRecorder's
     * own subscription order.
     */
    subscribe(dispatcher: EventDispatcher): Unsubscribe;
    /** Internal — also called by `ingestDomainEvent` for saved-event replay.
     *
     *  NOTE: deliberately does NOT call observeRunId(event.meta.runId).
     *  The agentfootprint dispatcher's runId is a DIFFERENT generator
     *  than footprintjs's traversalContext.runId — mixing them would
     *  toggle lastRunId on every event and trigger a false reset.
     *  Run-boundary detection happens reliably on the FlowRecorder side
     *  (onRunStart fires FIRST in any new run before any typed event). */
    protected ingestTypedEvent(event: AgentfootprintEvent): void;
    /**
     * Replay a complete saved `BoundaryRecorder` event list in recorded order.
     * Fork branches sharing a parent and timestamp are ingested together, so
     * one parallel fork keeps all its transitions and its matching merge.
     *
     * Use a fresh recorder (or call `clear()`) for each independent recording;
     * do not split a fork's branch events across batches. Afterwards,
     * `getSteps(drillPath?)` reads the built projection without replaying it.
     * Live consumers should attach this recorder instead of calling this
     * method on every update.
     *
     * @example
     * const rec = runStepRecorder();
     * rec.ingestDomainEvents(savedEvents);
     * const steps = rec.getSteps();
     */
    ingestDomainEvents(events: readonly DomainEvent[]): void;
    /**
     * Feed a single recorded `DomainEvent` (from BoundaryRecorder) into
     * this recorder as if it had fired live. For a saved event list, use
     * `ingestDomainEvents` so a parallel fork's branches stay together.
     *
     * Live consumers should use `runner.attach(rec)` +
     * `rec.subscribe(dispatcher)` instead — the recorder's hooks fire
     * naturally during traversal.
     */
    ingestDomainEvent(e: DomainEvent): void;
    /**
     * Read-only query — returns the already-built step list filtered to
     * `drillPath` scope. O(1) per call when scope is empty; O(N) filter
     * otherwise. Composition-vs-leaf root filter is applied so the
     * slider semantics match the user's mental model:
     *
     *   - **Leaf root** (single Agent / LLMCall): show `react` steps only.
     *   - **Composition root** (Sequence / Parallel / Conditional / Loop):
     *     show composition steps; hide intra-leaf `react` steps.
     *
     * Drill-down filters by `anchor.subflowPath` prefix and re-applies
     * the leaf-vs-composition rule for the drilled scope.
     */
    getSteps(drillPath?: readonly string[]): readonly RunStep[];
    /** Flush any deferred answer-candidate from the buffer. Called by
     *  `onRunEnd` so a single `answers` step appears for runs that end
     *  on a leaf exit (no further leaf entries followed). */
    private flushCandidateAnswer;
}
export {};
