/** * Graph Execution Engine v2 — Public Engine API (barrel) * * Version: 2.0 * Date: 2026-07-24 * * The single public entry point for the graph execution engine. Consumers * construct an {@link EngineRuntime} with {@link createEngine}, then drive it * through the lifecycle: * * provision() — initialize {@link EngineState} (register nodes, bootstrap roots). * run() — transition `idle → executing` and dispatch the ready roots. * status() — read a snapshot of the {@link EngineState}. * * Beyond the core lifecycle, the full control surface is implemented: * `recover()` resumes an interrupted graph from its persisted state, * `cancel()` / `cancelNodes()` tear down running graphs, `approveNode()` / * `rejectNode()` / `partialApprove()` drive the `needs_approval` gate, * `retryNode()` re-opens a node for re-dispatch, and `dispose()` releases a * replaced runtime (cancelling its debounced persistence timer). * * Design: the engine is a role-agnostic primitive. This barrel only wires the * runtime; it carries no dispatch or role logic itself. The dispatch surface * is an injected seam (see {@link CreateEngineOptions.dispatch}) so callers * and tests can avoid real sub-agent dispatch. External integration (wiring * this runtime into the platform entry points and re-exporting it from the * package root) lives in `src/graph/tools/graph-tools.ts`. * * Design reference: `.rolebox/design/engine-state-machine.md`. */ import type { DispatchManager } from "../../dispatch/core/manager.ts"; import type { GraphDeclaration } from "../../types.graph-v2.ts"; import type { EngineState, NodeLivenessState } from "../../types.engine-v2.ts"; import { type NodeDispatchPort, type NodeLivenessFeed, type GraphBudgetPort, type EdgeConditionResolver, type NodeCompletionEvent, type GraphTerminalEvent } from "./engine-advance.ts"; import { type DispatchParentContext } from "./dispatch-bridge.ts"; import { type ApproveReport, type PruneReport, type RejectReport } from "./approval-handler.ts"; import type { GraphEventRecorder } from "./graph-events.ts"; import type { RetryNodeOptions, RetryReport } from "./node-retry.ts"; import { type NodeStallEvent } from "./engine-recovery.ts"; import { type CancelScopeOptions, type CancelScopeReport } from "./cancellation.ts"; /** * A bound engine instance for a single graph execution. * * One `EngineRuntime` owns one {@link EngineState} and one signal-driven * {@link AdvanceEngine}. It is constructed via {@link createEngine}. */ export interface EngineRuntime { /** * Initialize the {@link EngineState}: register every declared node and * bootstrap the topology (root nodes become `ready` and enter the frontier). * * Idempotent — calling more than once is a no-op. * * @returns a snapshot of the state after provisioning. */ provision(): EngineState; /** * Transition the engine from `idle` to `executing` and dispatch the ready * root nodes. Provisioning is applied first if it has not run yet. * * Requires a dispatch seam (see {@link CreateEngineOptions.dispatch} or * {@link CreateEngineOptions.manager}); without one, this rejects with a * clear error. */ run(): Promise; /** * Resume an interrupted graph instance (Phase 3 — crash recovery). * * Loads the persisted {@link EngineState} (engine-state-machine.md §5.1) and * reconciles every `running` node against the dispatch system * (`failure-resilience.md §5.2`): vanished → `timeout`, finished-during-the- * window → re-emit its signal, still-live → re-subscribe `onTaskTerminated`. * Rebuilds the frontier and drains the deferred completions. A no-op when no * persisted state exists (first run) or no persistence is configured. * * Rejects when the persisted state file exists but cannot be read (any * non-ENOENT read failure): an unreadable state file is an explicit error, * never a silent clean start (review 05-F6/L22). */ recover(): Promise; /** * Adopt a prior engine run's per-node progress into this (freshly built) * runtime. Used by the imperative `graph_*` toolset, which rebuilds a fresh * engine from the declaration after every construction step and on every * `graph_run` — without adoption, a rebuild would reset completed nodes to * `ready`/`pending` and a subsequent `run()` would re-dispatch them. * * Provisions first if needed, copies each prior node's execution state onto * the matching node (skipping nodes with no progress or a changed agent), * corrects the frontier, and reconciles adopted `running` nodes against the * dispatch system (vanished → timeout, finished-during-window → re-emit, * live → re-subscribe). Never re-dispatches an already-progressed node. */ adoptPrior(prior: EngineState, opts?: AdoptPriorOptions): Promise; /** * Return a snapshot of the current {@link EngineState}. * * Isolation contract (Y24) — what is FRESH in the returned object: * * - the `nodes` map and every mutable leaf of each node: its * `signalsObserved` record, its `upstreamResults` map INCLUDING every * {@link EdgePayload} in it (`artifacts` array and `budgetConsumed` * object), `tokensConsumed`, `artifacts` / `evidence` arrays, the declared * `budget` spec, the `result` ref, and the `liveness` carrier; * - `frontier`, `budget`, `pendingCompletions`, `terminalNotified`; * - the `signalLedger` map and each entry's `signals` / `history` containers; * - the `loopGroups` map, each group record, and each round entry (including * its `nodeIds` array); * - the `checkpoints` / `checkpointHistory` containers (the * {@link CheckpointRecord} values inside are immutable snapshots and are * shared); * - the entire `graphDeclaration` (a structural clone). * * Deliberately SHARED, and therefore NOT safe to mutate — the contents of a * signal payload value (inside `signalsObserved` or a ledger entry's * `history[].payload`). Treat the snapshot as read-only below that level: a * signal payload is arbitrary JSON produced by a worker, and deep-cloning * every payload on every call would be unbounded work. The previous JSDoc * promised unconditional isolation and invited mutation; this contract is the * accurate one. * * Caveat (monitor M7): the snapshot is taken synchronously without acquiring * the advancing lock, so while a critical section is in flight * (`state.advancingLock === true`) it may reflect the middle of that * section's mutations rather than a quiescent state. */ status(): EngineState; /** * Cancel an in-progress graph execution (Phase 3 — teardown). * * Transitions every active node (`running` / `ready` / `pending`) to * `cancelled`, cancels in-flight dispatch tasks via the dispatch seam, and * advances the engine lifecycle to `complete`. `blocked` (needs_approval) * nodes await the human and are left untouched. * * @returns a {@link CancelScopeReport} describing the teardown (contract C7): * every node id as `target`, the ids actually retired to * `cancelled → done` in `cancelled`, the ids left untouched * (`completed` / `blocked` / terminal) in `skipped`, and the dispatch task * ids handed to the cancel seam in `cancelCalls`. The report is the * authoritative "who was cancelled" answer — consumers no longer have to * re-derive it by filtering `errorReason` text (Y29), a filter that both * over-counted previously scoped cancels and silently broke if the reason * wording changed. */ cancel(): Promise; /** * Approve a blocked `needs_approval` node: `blocked → completed`, record an * `answer` signal, and activate the node's downstream `answer` edges (forward * data flow). Resolves the node's approval gate and lets the graph continue. * * @param nodeId The `needs_approval` node currently `blocked`. * @param payload Optional approval output (defaults to the node's recorded * `need_approval` summary, else an accept marker). Must be a * JSON value: a `bigint`, function, symbol, circular * reference, or an object whose property read throws is * rejected with a `TypeError` BEFORE the node changes state * (R6). * @returns an {@link ApproveReport} (contract C6) — `applied: false` for an * idempotent no-op (the node was not `blocked`), so the caller no * longer has to diff two `status()` snapshots to learn whether its * decision took effect. * @throws when `nodeId` is not a node of this graph, and when `payload` is * not a JSON value; neither case applies the approval, so the node's * state is unchanged. */ approveNode(nodeId: string, payload?: unknown): Promise; /** * Reject a blocked `needs_approval` node: `blocked → ready` (re-enter with * the rejection feedback merged into the re-execution prompt), or * `blocked → escalate` when the node has no loop group to re-open. * * @param nodeId The `needs_approval` node currently `blocked`. * @param reason Optional human-supplied rejection reason. * @returns a {@link RejectReport} (contract C6): the lane the rejection took * (`escalate` / `revise`), or `already_resolved` with the node's * status at the time of the no-op replay. * @throws when `nodeId` is not a node of this graph; the rejection is not * applied in that case. */ rejectNode(nodeId: string, reason?: string): Promise; /** * Partially approve a blocked `needs_approval` node: accept the `approved` * upstream branches and cancel the `rejected` branches' transitive * dependents that cannot survive on the approved sources alone. Rejected * upstreams re-enter `ready` with feedback; the approval node re-waits for * their re-execution (or re-renders immediately when its join is already * satisfied by the approved sources). See orchestration-patterns.md §1.5. * * @param nodeId The `needs_approval` node currently `blocked`. * @param approved Upstream node ids the human accepted. * @param rejected Upstream node ids the human rejected (re-executed). * @param reason Optional rejection feedback for the re-executed branches. * @returns a {@link PruneReport} (contract C6) naming the dependents this * verdict cancelled and the ones that survive on their remaining * approved upstreams (empty when the gate was not `blocked`, i.e. * the verdict was a no-op). * @throws when `nodeId` is not a node of this graph; the verdict is not * applied in that case. */ partialApprove(nodeId: string, approved: string[], rejected: string[], reason?: string): Promise; /** * Retry a terminal graph's node (`tool-merge-map.md` §2.2 * `graph_run(node_id, retry=true, modify_prompt=...)`). * * Re-opens the target node (and its downstream subgraph) into a clean * `pending` state so re-dispatch is fresh, optionally prepending * `modifyPrompt` to the target's prompt, then re-marks the target `ready` * and dispatches it. A terminal graph phase (`complete`) is re-opened to * `executing`. See `node-retry.ts`. * * @param nodeId A node in any terminal state (`completed` / `escalate` / * `timeout` / `cancelled` / `done`) — or a pending/ready node — * to re-run. * @param opts Optional `modifyPrompt` prepended to the node's prompt. * @returns a {@link RetryReport} with the reset set and the number of nodes * (re-)dispatched this call. */ retryNode(nodeId: string, opts?: RetryNodeOptions): Promise; /** * Cancel one or more named node ids (optionally cascading to their transitive * downstream dependents) via the scoped/cascade cancellation primitive * (`cancellation.ts`). * * Scoped — this is NOT the whole-graph {@link cancel}: only the requested * targets (and, under `cascade`, everything downstream of them) are retired, * and the engine phase is untouched. A loop-group target expands to its full * member set. Reuses only the existing lifecycle machinery: cancellable * nodes (`pending | ready | running`) advance `→ cancelled → done` with their * dispatch tasks torn down fire-and-forget; `completed` / `blocked` / * terminal nodes are reported as skipped and left untouched. * * @param nodeIds Node ids to cancel (loop targets expand to their members). * @param options `{ cascade?: boolean }` — cascade to transitive downstream * dependents over the declaration's edges when true. * @returns a {@link CancelScopeReport} of retired vs. skipped node ids. */ cancelNodes(nodeIds: string[], options?: CancelScopeOptions): CancelScopeReport; /** * Relay a session-level failure observation from the platform liveness feed * into the engine (node-anomaly-detection subtask 4 — immediate-failure fast * path). The runtime-level wiring point for the feed's session observations, * placed beside the staleness-timeout handler {@link onStaleNodeTimeout} so * both failure intakes funnel the affected node through the SAME downstream * processing: the escalate ledger signal, escalate propagation (retry * re-entry / cascade cancel), completion seam, and termination re-check — * an abnormal node never blocks graph advancement. * * - `gone` (session.deleted) is authoritative: the `running` node escalates * immediately. * - `error` (session.error) is re-checked against the dispatch system via * `isDispatchTaskLive`: a task that is still live (running / pending / * awaiting_approval) keeps the node running — transient-error protection — * and only a `session` heartbeat is recorded; a task that is genuinely not * live escalates like `gone`. * * A strict no-op for a non-running node, an unattached session (no * `dispatchSessionId`), an engine without a feed, or an unknown node id. The * returned promise resolves when the observation is processed and never * rejects (escalate advances are contained inside the advance engine). */ handleFeedSessionEvent(nodeId: string, kind: "error" | "gone", reason?: string): Promise; /** * Record a session-activity heartbeat for a running node (subtask 6 — the * runtime-level public wrapper beside {@link handleFeedSessionEvent}, so the * platform liveness feed can heartbeat a node through the PUBLIC engine * surface instead of reaching into the advance engine). Thin delegation to * the advance engine's identical intake (engine-advance.ts * `recordLivenessHeartbeat`), strictly guarded there: a heartbeat only lands * on a node that is BOTH `running` AND actually dispatched by this engine * (`dispatchTaskId` set — matching the launch that produced the session). * A strict no-op otherwise: a completed / escalated / blocked / pending node * is never revived into activity, and an unknown node id never throws. * * @param nodeId The running node to heartbeat. * @param source The observation channel that produced the heartbeat * (`"tool"` / `"message"` / `"session"` / `"dispatch"` / * `"feed"` — see {@link NodeLivenessState.heartbeatSource}). */ recordLivenessHeartbeat(nodeId: string, source: NodeLivenessState["heartbeatSource"]): void; /** * Reverse-lookup the node owning a live dispatch session (subtask 6 — the * runtime-level public wrapper beside {@link handleFeedSessionEvent}). Thin * delegation to the advance engine's identical `sessionId → nodeId` reverse * index (engine-advance.ts `getNodeIdForSession`): backs the platform * liveness feed's session-to-node resolution for the observations relayed * through this runtime. Returns `undefined` for an unknown session or a * session whose node has detached (terminal transition). Only meaningful * when a {@link NodeLivenessFeed} is wired — the index is otherwise empty. */ getNodeIdForSession(sessionId: string): string | undefined; /** * Dispose the engine runtime (monitor M4). Unregisters every * task-terminated listener this engine registered with the dispatch seam * (`removeTaskTerminatedListener`) so a disposed runtime never receives (or * leaks) stale dispatch→signal callbacks, stops the opt-in staleness * watcher (monitor M3) so a disposed runtime never keeps ticking, and — * when persistence is configured — disposes the {@link EnginePersistence} * store (review 05-F1/M14): a replaced runtime's pending debounced write is * CANCELLED and dropped (never flushed), so stale state can never overwrite * the successor runtime's newer state on the shared state file. Idempotent * — a second dispose is a no-op. */ dispose(): void; } /** Options for {@link EngineRuntime.adoptPrior}. */ export interface AdoptPriorOptions { /** * When true, replay adopted completed nodes' `answer` forward flow into * downstream targets that have not yet received their upstream result (e.g. * a node appended to the declaration after its upstream completed). Replay * may dispatch freshly-ready downstream nodes, so it should only be enabled * on an execution path (`graph_run`), never during pure construction. * Default: false. */ replayAnswers?: boolean; } /** Options for {@link createEngine}. */ export interface CreateEngineOptions { /** * Dispatch seam — the only way `run()` actually launches graph nodes. * * Inject a fake here for tests, or pass a real one backed by a * {@link DispatchManager}. When omitted (and no `manager` is supplied), the * engine is still constructible and `status()`/`provision()` work, but * `run()` rejects with a clear error. */ dispatch?: NodeDispatchPort; /** * A {@link DispatchManager} used to build the *default* dispatch and budget * seams (`DispatchBridge` / `BudgetBridge`) when `dispatch`/`budget` are not * supplied explicitly. This is the production path. */ manager?: DispatchManager; /** Budget seam (defaults to a `BudgetBridge` over `manager` when available). */ budget?: GraphBudgetPort; /** Parent context for node dispatches (defaults to a graph-scoped one). */ parentContext?: DispatchParentContext; /** * Optional `on_condition` edge resolver. When omitted, the engine injects * the default resolver (Phase 2) supporting `signal_observed()` and * `artifact_exists()`; unsupported conditions evaluate false. */ conditionResolver?: EdgeConditionResolver; /** Graph-id override (defaults to a generated unique id). */ graphId?: string; /** * Optional workspace directory for engine-state persistence * (`.rolebox/state/engine-{graphId}.json`). When provided, the advance engine * performs a write-through save after every critical transition * (implementation-roadmap Q2 Option A). When omitted, the engine runs * in-memory with no on-disk persistence. */ stateDir?: string; /** * Optional stale-lock sweep interval (ms). When provided (> 0), * `recover()` starts a periodic {@link EngineLockSweeper} at this interval * so a lock left `true` past `ADVANCING_LOCK_TIMEOUT_MS` is released. * Defaults to off — tests drive the sweeper via manual ticks instead of an * unbounded `setInterval` (see failure-resilience.md §5.6). */ sweeperIntervalMs?: number; /** * Optional staleness deadline for `running` nodes (monitor M3). When provided * (> 0), the runtime instantiates a {@link NodeStalenessWatcher} (ticking at * {@link sweeperIntervalMs} when set, else the watcher's default interval) * and a `running` node that exceeds its staleness deadline is marked * `timeout`, surfaced through the completion seam + durable event log * (`notifyNodeTimeout`), and its failure propagated via an `escalate` ledger * signal (parity with the recovery orphan path — a timed-out upstream must * not silently stall a join). Started alongside the lock sweeper in * `recover()` and on `run()`, stopped by `cancel()` / `dispose()`. Defaults * to off — engine behavior is unchanged without it. */ nodeStaleTimeoutMs?: number; /** * Optional soft-stall warn threshold (ms) for the heartbeat-based liveness * monitor (node-anomaly-detection subtask 6). When `nodeStaleTimeoutMs` is * configured (the monitor is instantiated beside the stale-node watcher), a * heartbeat-fed `running` node that goes idle for `nodeStallWarnMs` is * classified `stalling` and the {@link onNodeStall} seam fires once per stall * episode. Defaults to `min(60_000, nodeStaleTimeoutMs / 2)` (the monitor's * own default). Absent → engine behavior unchanged. */ nodeStallWarnMs?: number; /** * Optional hard-stall grace (ms) past `nodeStallWarnMs` for the liveness * monitor (node-anomaly-detection subtask 6). A `stalling` node that stays * idle for `nodeStallWarnMs + nodeStallGraceMs` (capped by the per-node * effective staleness deadline) is marked `timeout` and funnels through the * SAME {@link onStaleNodeTimeout} handler as the wall-clock watcher (escalate * ledger signal + completion seam). Defaults to 30_000 (the monitor's own * default). Absent → engine behavior unchanged. */ nodeStallGraceMs?: number; /** * Optional node-completion notification seam (subtask 1). Wired into the * advance engine's identical seam; the engine fires it exactly once per * terminating / notable transition — `answer → completed`, `revise_needed → * completed` (reviewer finished), `escalate`, `blocked → completed` on * approval-resume, and the recovery-side `timeout`. Defaults to a no-op, so * engine behavior is unchanged without it. Notification logic (a notifier) * never lives here — this is a role-agnostic DI seam like * {@link CreateEngineOptions.dispatch} (see {@link NodeCompletionEvent}). * * Contract (C5 / Y2): a notifier may be synchronous or return a promise; the * engine isolates the return value and never awaits it * (`void Promise.resolve(ret).catch(handler)` in engine-advance.ts), so both * implementations satisfy the seam without a cast. */ onNodeCompletion?: (event: NodeCompletionEvent) => void | Promise; /** * Optional write-side durable graph event log (graph monitoring). When * present, the engine records node dispatch and node terminal transitions * into the recorder alongside the `onNodeCompletion` notifier, and the * recorder's phase-change / budget-event sinks (registered on construction) * capture the engine lifecycle transitions in `engine-state.ts`. Absent → * no event logging, engine behavior unchanged. */ graphEvents?: GraphEventRecorder; /** * Optional graph-terminal notification seam. Wired into the advance engine's * identical seam; the engine fires it exactly once per terminal transition * (GRAPH COMPLETE / GRAPH BLOCKED). Defaults to a no-op, so engine behavior * is unchanged without it. Notification logic never lives here — this is a * role-agnostic DI seam like {@link CreateEngineOptions.onNodeCompletion} * (see {@link GraphTerminalEvent}). * * Contract (C5 / Y2): synchronous or promise-returning — the engine isolates * the return value and never awaits it (engine-termination.ts * `fireGraphTerminal`). */ onGraphTerminal?: (event: GraphTerminalEvent) => void | Promise; /** * Optional node-stall notification seam (node-anomaly-detection subtask 5). * Wired into the opt-in {@link NodeLivenessMonitor} instantiated alongside * the stale-node watcher when `nodeStaleTimeoutMs` is configured; the monitor * fires it once per soft-stall episode (`stalling`) for a heartbeat-fed * `running` node. Defaults to a no-op, so engine behavior is unchanged * without it. Notification logic (a notifier) never lives here — this is a * role-agnostic DI seam like {@link CreateEngineOptions.onNodeCompletion} * (see {@link NodeStallEvent}). Callback exceptions are swallowed (logged) * so a monitor tick never breaks. * * Contract (C5 / Y2): synchronous or promise-returning; the runtime contains * a synchronous throw and never awaits the returned promise. */ onNodeStall?: (event: NodeStallEvent) => void | Promise; /** * Optional node-liveness feed seam (node-anomaly-detection subtask 2). Wired * into the advance engine's identical seam: when present, the engine records * an initial `dispatch` heartbeat on every successfully launched node, * maintains a `sessionId → nodeId` reverse index of running nodes, and * registers / unregisters each node's session with the feed (`attach` on * launch, `detach` on terminal transition). Absent → engine behavior * unchanged. Session-level failure observations (subtask 4) are relayed into * the engine through {@link EngineRuntime.handleFeedSessionEvent} — the * immediate-failure fast path beside the staleness timeout handler. */ livenessFeed?: NodeLivenessFeed; } /** * Construct an {@link EngineRuntime} bound to the given graph declaration. * * @param graphDeclaration The parsed v2 graph (nodes + edges + optional budget/loops). * @param options Seams — most importantly an injectable dispatch port. */ export declare function createEngine(graphDeclaration: GraphDeclaration, options?: CreateEngineOptions): EngineRuntime; /** The top-level engine state container — re-exported for consumer typing. */ export type { EngineState } from "../../types.engine-v2.ts"; /** * Bounded-cycle orchestration primitives (Phase 2). {@link executeLoopStep} is * the coalesced convergence decision for a loop-group member's terminating * signal — it applies the failure-resilience.md §4.3 soft early-exits and * coordinates the Phase 2 primitives (traversal counting, revise re-dispatch, * escalation cascade, upstream cancellation). The fingerprint / tracker * helpers are exported for direct, testable use. * * @internal Consumed by `engine-advance.ts` and the graph test suite only. */ export { executeLoopStep, recordConvergenceOutput, resetConvergenceTracker, fingerprintPayload, extractUnresolved, } from "./loop-group-executor.ts"; /** @internal Loop-report shapes (see the group above). */ export type { LoopOutcome, LoopStepReport, LoopEscalatePayload, } from "./loop-group-executor.ts"; /** @internal Cancellation seam shape, structurally satisfied by the dispatch port. */ export type { CancelDispatchPort } from "./cascade-canceller.ts"; /** * Scoped / cascade cancellation primitive. {@link CancelScopeReport} is public * (returned by {@link EngineRuntime.cancelNodes} and {@link EngineRuntime.cancel}); * the primitive itself, its options and its notification hook are * engine-internal. */ export { cancelNodes, type CancelScopeOptions, type CancelScopeReport, type CancelNodeNotifier, } from "./cancellation.ts"; /** @internal Approval-context assembly, consumed by `engine-advance.ts`. */ export { buildApprovalPayload, type ApprovalPayload, type ApprovalUpstreamResult, } from "./approval-payload.ts"; /** * Approval-gate state primitives and the R6 payload normalization helpers. * * @internal Consumed by `engine-advance.ts` and the graph test suite. The * public result shapes are re-exported separately below. */ export { approveBlockedNode, rejectBlockedNode, pruneDownstreamSubgraph, reenterRejectedUpstreams, resetRejectedUpstreams, mergeRejectionFeedback, approveReport, normalizeApprovalPayload, approvalResultText, type ApprovalJsonValue, } from "./approval-handler.ts"; /** * Approval-gate result shapes (contract C6) — public: the `EngineRuntime` * approve / reject / partial-approve entry points return them, and the tool * layer consumes them instead of diffing `status()` snapshots. {@link ReentryReport} * was previously defined but never re-exported (B17). */ export type { ApproveReport, RejectReport, PruneReport, ReentryReport, } from "./approval-handler.ts"; /** * Node retry control path — public. {@link RetryReport} is what * {@link EngineRuntime.retryNode} resolves. */ export { retryNode } from "./node-retry.ts"; export type { RetryNodeOptions, RetryReport } from "./node-retry.ts"; /** @internal Retry reset primitive — one `retryNode` step, exported for tests. */ export { resetNodeForRetry } from "./node-retry.ts"; /** @internal Reset-scope report used inside `retryNode`'s implementation. */ export type { RetryResetReport } from "./node-retry.ts"; export type { NodeDispatchPort } from "./engine-advance.ts"; export type { NodeLivenessFeed } from "./engine-advance.ts"; export type { NodeCompletionEvent, } from "./engine-advance.ts"; export type { GraphTerminalEvent, } from "./engine-advance.ts"; export type { NodeStallEvent, } from "./engine-recovery.ts"; export { GraphEventRecorder, readGraphEventLog, type GraphEventRecord, type GraphEventType, } from "./graph-events.ts"; /** @internal Event-log path / hash helpers — no consumer outside the engine. */ export { graphEventsHash, graphEventsPath } from "./graph-events.ts"; export { type PhaseEventSink, type BudgetEventSink, } from "../../types.engine-v2.ts"; export { loadEngineStateFromJson } from "./engine-persistence.ts"; export { graphParentContext } from "./dispatch-bridge.ts"; export type { DispatchParentContext } from "./dispatch-bridge.ts"; export { createGraphNotifier, createGraphStallNotifier, createGraphTerminalNotifier, buildPropagatedBlockedText, } from "./graph-notify.ts"; export type { GraphCompletionHandler, GraphStallHandler, GraphTerminalHandler, GraphNotifyHandler, } from "./graph-notify.ts"; //# sourceMappingURL=index.d.ts.map