@langchain/langgraph-sdk 1.9.7 → 1.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"controller.js","names":["#options","#messagesKey","#subagents","#subgraphs","#messageMetadata","#resolvedInterrupts","#selfCreatedThreadIds","#rootEventListeners","#rootBus","#threadListeners","#currentThreadId","#createInitialSnapshot","#rootMessages","#lifecycleLoading","#disposed","#submitter","#ensureThread","#startDeferredRootPump","#abandonDeferredRootPump","#rootPumpReady","#awaitNextTerminal","#thread","#hydratedActiveInterruptIds","#submitGeneration","#markLocalRunStart","#notifyCreated","#notifyCompleted","#markLocalRunEnd","#hydrationPromise","#createHydrationPromise","#resolveHydration","#rejectHydration","#resetHydrationPromise","#teardownThread","#applyValues","#localRunDepth","#activeRunId","#runLifecycleListener","#resolveInterruptForResume","#cancelPendingDispose","#pendingDisposeTimer","#rootPumpDeferred","#startRootPump","#notifyThreadListeners","#threadEventUnsubscribe","#rootSubscription","#rootPump","#rootToolAssembler","#onWildcardEvent","#usesEventStreamTransport","#onRootEvent","#recordRootInterrupt"],"sources":["../../src/stream/controller.ts"],"sourcesContent":["/**\n * Framework-agnostic controller for the experimental v2 stream.\n *\n * Responsibilities:\n * - Owns at most one {@link ThreadStream} at a time (swapped on\n * `hydrate(newThreadId)` or `dispose`).\n * - Exposes three always-on observable surfaces via {@link StreamStore}:\n * - `rootStore` : root values/messages/toolCalls/interrupts/…\n * - `subagentStore` : discovery map of subagents (no content)\n * - `subgraphStore` : discovery map of subgraphs (no content)\n * - Owns a {@link ChannelRegistry} that framework selector hooks\n * (`useMessages`, `useToolCalls`, `useExtension`, `useChannel`)\n * use to lazily open per-namespace subscriptions.\n * - Imperative run surface: `submit`, `stop`, `respond`, `joinStream`.\n *\n * A single multi-channel subscription (`values`, `lifecycle`, `input`,\n * `messages`, `tools`) powers every always-on projection and both\n * discovery runners. Selector hooks add their own (deduped)\n * subscriptions on top — so even a UI with many subagents only opens\n * one extra subscription per `(channels, namespace)` actually\n * rendered on screen.\n */\nimport { AIMessage, type BaseMessage } from \"@langchain/core/messages\";\nimport type {\n Channel,\n Event,\n LifecycleEvent,\n MessagesEvent,\n ToolsEvent,\n ValuesEvent,\n} from \"@langchain/protocol\";\nimport type { Interrupt } from \"../schema.js\";\nimport type { ThreadStream } from \"../client/stream/index.js\";\nimport type { SubscriptionHandle } from \"../client/stream/index.js\";\nimport { ToolCallAssembler } from \"../client/stream/handles/tools.js\";\nimport { ensureMessageInstances } from \"../ui/messages.js\";\nimport type { Message } from \"../types.messages.js\";\nimport { StreamStore } from \"./store.js\";\nimport { ChannelRegistry } from \"./channel-registry.js\";\nimport {\n SubagentDiscovery,\n type SubagentMap,\n SubgraphDiscovery,\n type SubgraphMap,\n type SubgraphByNodeMap,\n} from \"./discovery/index.js\";\nimport {\n isInternalWorkNamespace,\n isLegacySubagentNamespace,\n isRootNamespace,\n} from \"./namespace.js\";\nimport {\n MessageMetadataTracker,\n type CheckpointEnvelope,\n type MessageMetadata,\n type MessageMetadataMap,\n} from \"./message-metadata-tracker.js\";\nimport { LifecycleLoadingTracker } from \"./lifecycle-loading-tracker.js\";\nimport { RootMessageProjection } from \"./root-message-projection.js\";\nimport {\n EMPTY_QUEUE,\n SubmitCoordinator,\n type SubmissionQueueEntry,\n type SubmissionQueueSnapshot,\n} from \"./submit-coordinator.js\";\nimport {\n reconcileToolCallsFromMessages,\n upsertToolCall,\n} from \"./tool-calls.js\";\nimport {\n buildResumeRunInput,\n resolveInterruptTargetForHeadlessResume,\n} from \"../headless-tools.js\";\nimport type {\n RootEventBus,\n RootSnapshot,\n RunExecutionReason,\n StreamControllerOptions,\n StreamSubmitOptions,\n} from \"./types.js\";\n\nfunction isAbortLikeError(error: unknown): boolean {\n if (error == null || typeof error !== \"object\") return false;\n const maybeError = error as { name?: unknown; message?: unknown };\n return (\n maybeError.name === \"AbortError\" ||\n (typeof maybeError.message === \"string\" &&\n maybeError.message.includes(\"aborted\"))\n );\n}\n\nfunction lifecycleReason(event: string | undefined): RunExecutionReason | null {\n if (event === \"completed\") return \"success\";\n if (event === \"failed\") return \"error\";\n if (event === \"interrupted\") return \"interrupt\";\n return null;\n}\n\nconst ROOT_NAMESPACE: readonly string[] = [];\n\n/**\n * Channel set covered by the always-on root subscription. Exported so\n * projections (and transports) can reason about what the root pump\n * already delivers before opening additional server subscriptions.\n */\nexport const ROOT_PUMP_CHANNELS: readonly Channel[] = [\n \"values\",\n \"checkpoints\",\n \"lifecycle\",\n \"input\",\n \"messages\",\n \"tools\",\n];\n\ninterface ResolvedInterrupt {\n interruptId: string;\n namespace: string[];\n}\n\nexport type {\n MessageMetadata,\n MessageMetadataMap,\n SubmissionQueueEntry,\n SubmissionQueueSnapshot,\n};\n\n/**\n * Coordinates one thread's protocol-v2 stream and exposes stable\n * observable projections for framework bindings.\n *\n * The controller owns the root subscription, lazily binds scoped\n * projections through {@link ChannelRegistry}, and normalizes protocol\n * events into class-message, tool-call, discovery, interrupt, and queue\n * stores.\n *\n * @typeParam StateType - Shape of the graph state exposed on `values`.\n * @typeParam InterruptType - Shape of protocol interrupt payloads.\n * @typeParam ConfigurableType - Shape of `config.configurable` accepted by submit.\n */\nexport class StreamController<\n StateType extends object = Record<string, unknown>,\n InterruptType = unknown,\n ConfigurableType extends object = Record<string, unknown>,\n> {\n readonly rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n readonly subagentStore: StreamStore<SubagentMap>;\n readonly subgraphStore: StreamStore<SubgraphMap>;\n readonly subgraphByNodeStore: StreamStore<SubgraphByNodeMap>;\n readonly messageMetadataStore: StreamStore<MessageMetadataMap>;\n readonly queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n readonly registry: ChannelRegistry;\n\n readonly #options: StreamControllerOptions<StateType>;\n readonly #messagesKey: string;\n readonly #subagents = new SubagentDiscovery();\n readonly #subgraphs = new SubgraphDiscovery();\n readonly #messageMetadata = new MessageMetadataTracker();\n\n #thread: ThreadStream | undefined;\n #currentThreadId: string | null;\n #rootSubscription: SubscriptionHandle<Event> | undefined;\n #rootPump: Promise<void> | undefined;\n #rootPumpReady: Promise<void> | undefined;\n /**\n * `true` while a self-created thread has its root pump deferred until\n * the first `submitRun` / `respondInput` commits the thread row\n * server-side. See `#ensureThread` and `#startDeferredRootPump`.\n */\n #rootPumpDeferred = false;\n #threadEventUnsubscribe: (() => void) | undefined;\n #disposed = false;\n #pendingDisposeTimer: ReturnType<typeof setTimeout> | null = null;\n readonly #resolvedInterrupts = new Set<string>();\n /**\n * Set of interrupt IDs the server reports as currently *active* on\n * the thread (from `state.tasks[].interrupts`). Populated during\n * {@link hydrate} from `client.threads.getState()` and used as a\n * strict allowlist when processing replayed `input.requested`\n * events from the persistent SSE subscription. Without this guard,\n * SSE replay re-adds historically-requested interrupts that have\n * since been resolved (no `input.responded` event exists in the\n * protocol, so the SDK has no other way to tell replay from live\n * for an idle thread). `null` outside the hydrate-window so\n * genuinely new live interrupts on an active run aren't filtered;\n * cleared at the start of `submit()` for the same reason.\n */\n #hydratedActiveInterruptIds: Set<string> | null = null;\n /**\n * Monotonic counter bumped at the start of each `submit()` and used\n * by {@link hydrate} to skip its post-fetch allowlist write when a\n * submit started while the state fetch was in flight. Without this\n * guard, a submit-then-hydrate race could re-install a stale\n * allowlist that filters out genuinely-new live interrupts emitted\n * by the just-started run.\n */\n #submitGeneration = 0;\n /**\n * Thread ids we minted client-side on first `submit()`. Keeping them\n * here lets `hydrate()` skip the `threads.getState()` round-trip —\n * we know there is nothing checkpointed server-side yet (and the\n * request would 404 and surface a spurious error to the UI).\n */\n readonly #selfCreatedThreadIds = new Set<string>();\n readonly #rootEventListeners = new Set<(event: Event) => void>();\n readonly #rootBus: RootEventBus;\n #activeRunId: string | undefined;\n #localRunDepth = 0;\n\n /**\n * Single-shot hydration promise. Exposed via `hydrationPromise`\n * so Suspense wrappers can throw it until the first hydrate\n * completes (resolve) or fails (reject). Reset whenever a new\n * hydrate cycle begins so `<Suspense>` boundaries re-suspend on\n * thread switch.\n */\n #hydrationPromise: Promise<void>;\n #resolveHydration!: () => void;\n #rejectHydration!: (error: unknown) => void;\n\n /**\n * Tool assembler lives for the lifetime of a thread; reset on\n * rebind so a fresh thread starts with a clean slate.\n */\n #rootToolAssembler = new ToolCallAssembler();\n #rootMessages!: RootMessageProjection<StateType, InterruptType>;\n #lifecycleLoading!: LifecycleLoadingTracker<\n RootSnapshot<StateType, InterruptType>\n >;\n #submitter!: SubmitCoordinator<StateType, InterruptType, ConfigurableType>;\n\n readonly #threadListeners = new Set<\n (thread: ThreadStream | undefined) => void\n >();\n\n /**\n * Create a controller around a LangGraph client and optional initial thread.\n *\n * @param options - Runtime configuration, client, thread id, and initial state.\n */\n constructor(options: StreamControllerOptions<StateType>) {\n this.#options = options;\n this.#messagesKey = options.messagesKey ?? \"messages\";\n this.#currentThreadId = options.threadId ?? null;\n this.#rootBus = {\n channels: ROOT_PUMP_CHANNELS,\n subscribe: (listener) => {\n this.#rootEventListeners.add(listener);\n return () => {\n this.#rootEventListeners.delete(listener);\n };\n },\n };\n this.registry = new ChannelRegistry(this.#rootBus);\n this.subagentStore = this.#subagents.store;\n this.subgraphStore = this.#subgraphs.store;\n this.subgraphByNodeStore = this.#subgraphs.byNodeStore;\n this.rootStore = new StreamStore<RootSnapshot<StateType, InterruptType>>(\n this.#createInitialSnapshot()\n );\n this.#rootMessages = new RootMessageProjection({\n messagesKey: this.#messagesKey,\n store: this.rootStore,\n });\n this.#lifecycleLoading = new LifecycleLoadingTracker({\n store: this.rootStore,\n isDisposed: () => this.#disposed,\n });\n this.messageMetadataStore = this.#messageMetadata.store;\n this.queueStore = new StreamStore<SubmissionQueueSnapshot<StateType>>(\n EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n this.#submitter = new SubmitCoordinator({\n options: this.#options,\n rootStore: this.rootStore,\n queueStore: this.queueStore,\n getDisposed: () => this.#disposed,\n getCurrentThreadId: () => this.#currentThreadId,\n setCurrentThreadId: (threadId) => {\n this.#currentThreadId = threadId;\n },\n rememberSelfCreatedThreadId: (threadId) => {\n this.#selfCreatedThreadIds.add(threadId);\n },\n hydrate: (threadId) => this.hydrate(threadId),\n ensureThread: (threadId, deferRootPump) =>\n this.#ensureThread(threadId, deferRootPump),\n startDeferredRootPump: () => this.#startDeferredRootPump(),\n abandonDeferredRootPump: () => this.#abandonDeferredRootPump(),\n forgetSelfCreatedThreadId: (threadId) => {\n this.#selfCreatedThreadIds.delete(threadId);\n },\n waitForRootPumpReady: () => this.#rootPumpReady,\n awaitNextTerminal: (signal) => this.#awaitNextTerminal(signal),\n buildResumeRunInput: (resume) => {\n const thread = this.#thread;\n if (thread == null) return null;\n return buildResumeRunInput(\n resume,\n thread.interrupts,\n this.#resolvedInterrupts\n );\n },\n markInterruptResolved: (interruptId) => {\n this.#resolvedInterrupts.add(interruptId);\n },\n onSubmitStart: () => {\n // Clear the hydrate-window allowlist so genuinely-new live\n // interrupts on the just-started run aren't filtered. Bump\n // the generation so any in-flight hydrate skips its\n // allowlist write on return (see #hydratedActiveInterruptIds).\n this.#hydratedActiveInterruptIds = null;\n this.#submitGeneration += 1;\n },\n onRunStart: () => this.#markLocalRunStart(),\n onRunCreated: (runId) => this.#notifyCreated(runId),\n onRunCompleted: (reason, runId) => this.#notifyCompleted(reason, runId),\n onRunEnd: () => this.#markLocalRunEnd(),\n });\n this.#hydrationPromise = this.#createHydrationPromise();\n /**\n * Attach a default no-op catch so orphaned hydrationPromise\n * rejections (e.g. controllers spawned during Suspense retries\n * whose promise never gets subscribed to because the suspense\n * cache already holds an earlier one) don't surface as unhandled\n * rejections. Callers that attach their own handlers via .then()\n * still receive the rejection on their derived promise.\n */\n this.#hydrationPromise.catch(() => undefined);\n /**\n * Kick off the initial hydrate eagerly when a caller-supplied\n * thread id is present. Suspense consumers throw\n * `hydrationPromise` on the very first render, which unmounts the\n * subtree before any `useEffect` can run — so if we waited for an\n * effect to drive the hydrate we'd deadlock. Firing here makes\n * the promise settle independently of the component lifecycle.\n */\n if (this.#currentThreadId != null) {\n void this.hydrate(this.#currentThreadId);\n } else {\n this.#resolveHydration();\n }\n }\n\n /**\n * Promise that settles the first time {@link hydrate} finishes on\n * the current thread. Resolves on a clean hydration, rejects when\n * the thread-state fetch errors. A fresh promise is installed on\n * every thread swap so `<Suspense>` wrappers re-suspend on\n * `switchThread`.\n */\n get hydrationPromise(): Promise<void> {\n return this.#hydrationPromise;\n }\n\n /**\n * Create the deferred promise backing the current hydration cycle.\n */\n #createHydrationPromise(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.#resolveHydration = resolve;\n this.#rejectHydration = reject;\n });\n }\n\n /**\n * Replace the current hydration promise before a thread swap.\n */\n #resetHydrationPromise(): void {\n /**\n * Swallow rejection on the orphaned promise so Node doesn't\n * flag it as unhandled; Suspense callers that still hold a\n * reference see the rejection they subscribed to.\n */\n this.#hydrationPromise.catch(() => undefined);\n this.#hydrationPromise = this.#createHydrationPromise();\n }\n\n // ---------- public imperatives ----------\n\n /**\n * Fetch the checkpointed thread state and seed the root snapshot.\n * Re-calling with a different `threadId` swaps the underlying\n * {@link ThreadStream}, rewires the registry to the new thread, and\n * resets assemblers.\n *\n * @param threadId - Optional replacement thread id; `null` clears the active thread.\n */\n async hydrate(threadId?: string | null): Promise<void> {\n if (this.#disposed) return;\n const target = threadId === undefined ? this.#currentThreadId : threadId;\n const changed = target !== this.#currentThreadId;\n this.#currentThreadId = target ?? null;\n this.rootStore.setState((s) => ({ ...s, threadId: this.#currentThreadId }));\n\n if (changed) {\n /**\n * Swap to a new thread: re-arm the hydration promise so any\n * Suspense boundary remounted against the new id suspends again.\n */\n this.#resetHydrationPromise();\n await this.#teardownThread();\n /**\n * Reset UI-facing snapshot so stale messages/values/tool-calls\n * from the previous thread don't bleed into the new one. The\n * new thread's state (if any) is then populated below via\n * `#applyValues`.\n */\n this.rootStore.setState(() => ({\n ...this.#createInitialSnapshot(),\n threadId: this.#currentThreadId,\n }));\n /**\n * Drop queued submissions — they were targeted at the previous\n * thread so dispatching them against the new thread would be\n * surprising. Mirrors the legacy `StreamOrchestrator` behaviour.\n */\n this.queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n }\n\n if (this.#currentThreadId == null) {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n this.#resolveHydration();\n return;\n }\n\n /**\n * Self-generated thread ids have nothing to fetch server-side yet\n * — the thread is created lazily by the first `run.start`. Calling\n * `threads.getState()` here would return a 404 and surface a\n * spurious error to the UI.\n */\n if (this.#selfCreatedThreadIds.has(this.#currentThreadId)) {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n this.#resolveHydration();\n return;\n }\n\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: true }));\n let hydrationError: unknown;\n let threadExists = false;\n try {\n const state = await this.#options.client.threads.getState<StateType>(\n this.#currentThreadId\n );\n threadExists = state != null;\n if (state?.values != null) {\n /**\n * `threads.getState()` returns the legacy `ThreadState` shape\n * where `parent_checkpoint` is an object (`{ thread_id,\n * checkpoint_id, checkpoint_ns }`). Synthesize the v2\n * `Checkpoint` envelope (matching the `checkpoints` channel\n * payload) so hydrated messages also get their\n * `parentCheckpointId` populated for fork / edit flows.\n */\n const checkpointId = state.checkpoint?.checkpoint_id;\n const parentCheckpointId =\n state.parent_checkpoint?.checkpoint_id ?? undefined;\n const syntheticCheckpoint =\n typeof checkpointId === \"string\"\n ? {\n id: checkpointId,\n ...(parentCheckpointId != null\n ? { parent_id: parentCheckpointId }\n : {}),\n }\n : undefined;\n this.#applyValues(state.values as unknown, syntheticCheckpoint);\n }\n /**\n * Sync the visible interrupt list to the server's authoritative\n * `state.tasks[].interrupts`. Without this, SSE replay of past\n * `input.requested` events would re-add resolved interrupts to\n * the UI on every page navigation back to the thread.\n *\n * Only runs when `state.tasks` is an array — runtimes that don't\n * surface tasks in `threads.getState()` are left untouched (an\n * unconditional overwrite would wipe any in-flight interrupt\n * state the wildcard watcher may already have recorded).\n */\n if (Array.isArray(state?.tasks)) {\n const generationAtFetch = this.#submitGeneration;\n const activeInterrupts: Interrupt<InterruptType>[] = [];\n const activeIds = new Set<string>();\n for (const task of state.tasks) {\n if (!Array.isArray(task?.interrupts)) continue;\n for (const interrupt of task.interrupts) {\n const typed = interrupt as\n | { id?: string; value?: unknown }\n | null\n | undefined;\n const id = typed?.id;\n if (typeof id !== \"string\" || activeIds.has(id)) continue;\n activeIds.add(id);\n activeInterrupts.push({\n id,\n value: typed?.value as InterruptType,\n });\n }\n }\n this.rootStore.setState((s) => ({\n ...s,\n interrupts: activeInterrupts,\n interrupt: activeInterrupts[0],\n }));\n // Only seed the allowlist when no submit started while the\n // state fetch was in flight. If one did, the cleared\n // (null) allowlist must stay null so the new run's live\n // interrupts are not filtered.\n if (this.#submitGeneration === generationAtFetch) {\n this.#hydratedActiveInterruptIds = activeIds;\n }\n }\n } catch (error) {\n /**\n * A 404 on hydrate means the thread does not exist server-side\n * yet (most commonly because the caller supplied a brand-new,\n * externally-minted thread id and is about to create it via the\n * first `submit()`). Treat it as \"empty state\" rather than a\n * fatal hydration error so Suspense boundaries resolve cleanly\n * and no spurious error renders in the UI.\n */\n const status = (error as { status?: number } | null)?.status;\n if (status !== 404) {\n hydrationError = error;\n this.rootStore.setState((s) => ({ ...s, error }));\n }\n } finally {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n if (hydrationError != null) {\n this.#rejectHydration(hydrationError);\n } else {\n this.#resolveHydration();\n }\n }\n\n /**\n * P0 fix: open the shared subscription on mount so in-flight\n * server-side runs are observed even when no local `submit()` is\n * active. The transport replays the run from `seq=0` on a rotating\n * subscribe, so late-joining is free once the subscription exists.\n * `isLoading` transitions are driven by the persistent root\n * lifecycle listener registered in `#startRootPump`.\n */\n const thread = this.#ensureThread(this.#currentThreadId);\n\n /**\n * Start the wildcard lifecycle watcher up-front for existing\n * threads. The root content pump runs at `depth: 1`, which covers\n * root-namespace and one-deep events but not arbitrarily-nested\n * subagent / subgraph lifecycle — the dedicated watcher handles\n * those.\n *\n * For self-created (new) threads we skip — the watcher would 404\n * against a not-yet-existent thread. `submitRun` / `respondInput`\n * call `startLifecycleWatcher` on first submission to cover that\n * case.\n */\n if (threadExists) {\n thread.startLifecycleWatcher();\n }\n }\n\n /**\n * Submit input or a resume command to the active thread.\n *\n * @param input - Input payload for a new run; `null`/`undefined` submits no input.\n * @param options - Per-run config, metadata, multitask behavior, and callbacks.\n */\n async submit(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): Promise<void> {\n await this.#submitter.submit(input, options);\n }\n\n /**\n * Abort the currently tracked run and mark the controller idle.\n */\n async stop(): Promise<void> {\n await this.#submitter.stop();\n }\n\n #markLocalRunStart(): void {\n this.#localRunDepth += 1;\n }\n\n #markLocalRunEnd(): void {\n this.#localRunDepth = Math.max(0, this.#localRunDepth - 1);\n }\n\n #notifyCreated(runId: string): void {\n this.#activeRunId = runId;\n try {\n this.#options.onCreated?.({ runId });\n } catch {\n /* caller-supplied callback errors must not crash the stream */\n }\n }\n\n #notifyCompleted(\n reason: RunExecutionReason,\n runId = this.#activeRunId\n ): void {\n if (runId != null && runId === this.#activeRunId) {\n this.#activeRunId = undefined;\n }\n setTimeout(() => {\n if (this.#disposed) return;\n try {\n this.#options.onCompleted?.(\n runId == null ? { reason } : { runId, reason }\n );\n } catch {\n /* caller-supplied callback errors must not crash the stream */\n }\n }, 0);\n }\n\n readonly #runLifecycleListener = (event: Event): void => {\n if (this.#localRunDepth > 0) return;\n if (event.method !== \"lifecycle\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n if (!this.rootStore.getSnapshot().isLoading) return;\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n };\n const reason = lifecycleReason(lifecycle?.event);\n if (reason == null) return;\n this.#notifyCompleted(reason);\n };\n\n /**\n * Cancel a queued submission by id. Returns `true` when the entry\n * was found and removed, `false` otherwise.\n *\n * Today this only removes the entry from the client-side mirror —\n * once the server exposes queue cancel (roadmap A0.3) the\n * controller will additionally issue a cancel call against the\n * active transport.\n *\n * @param id - Client-side queue entry id to remove.\n */\n async cancelQueued(id: string): Promise<boolean> {\n return this.#submitter.cancelQueued(id);\n }\n\n /**\n * Drop every queued submission. Server-side cancel arrives with A0.3.\n */\n async clearQueue(): Promise<void> {\n await this.#submitter.clearQueue();\n }\n\n /**\n * Respond to a pending protocol interrupt.\n *\n * @param response - Payload to send back to the interrupted namespace.\n * @param target - Optional explicit interrupt id and namespace; defaults to the latest unresolved interrupt.\n */\n async respond(\n response: unknown,\n target?: { interruptId: string; namespace?: string[] }\n ): Promise<void> {\n if (this.#disposed || this.#thread == null) {\n throw new Error(\"No active thread to respond to.\");\n }\n const resolved =\n target != null\n ? {\n interruptId: target.interruptId,\n namespace: target.namespace ?? [...ROOT_NAMESPACE],\n }\n : this.#resolveInterruptForResume();\n if (resolved == null) {\n throw new Error(\"No pending interrupt to respond to.\");\n }\n try {\n await this.#thread.respondInput({\n namespace: resolved.namespace,\n interrupt_id: resolved.interruptId,\n response,\n });\n this.#resolvedInterrupts.add(resolved.interruptId);\n } catch (error) {\n if (this.#disposed && isAbortLikeError(error)) {\n return;\n }\n throw error;\n }\n }\n\n /**\n * Dispose the active thread, subscriptions, registry entries, and listeners.\n */\n async dispose(): Promise<void> {\n if (this.#disposed) return;\n this.#cancelPendingDispose();\n this.#disposed = true;\n this.#submitter.abortActiveRun();\n await this.#teardownThread();\n await this.registry.dispose();\n this.#threadListeners.clear();\n }\n\n /**\n * StrictMode-safe lifecycle hook for framework bindings.\n *\n * React 18+ `StrictMode` intentionally mounts → unmounts → remounts\n * components in dev to surface effect-cleanup bugs. A naive\n * `useEffect(() => () => controller.dispose())` would permanently\n * tear the controller down on that first synthetic unmount, leaving\n * every subsequent `submit()` a silent no-op.\n *\n * Call {@link activate} from the bind site's effect and return the\n * result as the effect's cleanup. The controller uses deferred\n * disposal: a `release()` only schedules a dispose on the next\n * microtask, which is cancelled if another `activate()` arrives\n * before it fires (the normal StrictMode remount path).\n */\n activate(): () => void {\n this.#cancelPendingDispose();\n return () => {\n if (this.#disposed) return;\n this.#pendingDisposeTimer = setTimeout(() => {\n this.#pendingDisposeTimer = null;\n void this.dispose().catch(() => undefined);\n }, 0);\n };\n }\n\n /**\n * Cancel a deferred dispose scheduled by {@link activate}.\n */\n #cancelPendingDispose(): void {\n if (this.#pendingDisposeTimer != null) {\n clearTimeout(this.#pendingDisposeTimer);\n this.#pendingDisposeTimer = null;\n }\n }\n\n // ---------- thread access ----------\n\n /**\n * Returns the bound {@link ThreadStream}, if one exists. Prefer\n * {@link StreamController.rootStore} and selector projections for\n * UI work; use this for low-level protocol access.\n */\n getThread(): ThreadStream | undefined {\n return this.#thread;\n }\n\n /**\n * Listen for `ThreadStream` lifecycle (swap on thread-id change,\n * detach on dispose). The listener fires immediately with the\n * current thread (may be `undefined`).\n *\n * @param listener - Callback invoked immediately and on every thread swap.\n */\n subscribeThread(\n listener: (thread: ThreadStream | undefined) => void\n ): () => void {\n this.#threadListeners.add(listener);\n listener(this.#thread);\n return () => {\n this.#threadListeners.delete(listener);\n };\n }\n\n // ---------- internals ----------\n\n /**\n * Build the initial root snapshot from configured initial values.\n */\n #createInitialSnapshot(): RootSnapshot<StateType, InterruptType> {\n const values = (this.#options.initialValues ??\n ({} as StateType)) as StateType;\n const messages = extractAndCoerceMessages(\n values as unknown as Record<string, unknown>,\n this.#messagesKey\n );\n /**\n * Seed `isThreadLoading: true` synchronously when a caller-supplied\n * threadId is on the controller at construction/swap time. Without\n * this Suspense wrappers would render their fallback for a tick\n * because `isThreadLoading` flips false → true → false once the\n * deferred `hydrate()` starts, and the synchronous render observes\n * the initial `false`.\n */\n const willHydrate =\n this.#currentThreadId != null &&\n !this.#selfCreatedThreadIds.has(this.#currentThreadId);\n return {\n values,\n messages,\n toolCalls: [],\n interrupts: [],\n interrupt: undefined,\n isLoading: false,\n isThreadLoading: willHydrate,\n error: undefined,\n threadId: this.#currentThreadId,\n };\n }\n\n /**\n * Return the active thread stream, creating and binding one when needed.\n *\n * @param threadId - Thread id used when constructing the stream.\n * @param deferRootPump - When `true`, build the ThreadStream and bind\n * the registry but skip starting the persistent root SSE pump. Used\n * for client-self-created thread ids whose server-side thread row\n * doesn't exist yet — opening the pump's `subscription.subscribe`\n * against a not-yet-existent thread produces a `404: Thread not\n * found` protocol error that strands terminal lifecycle events and\n * leaves the UI showing nothing until the user reloads. The pump is\n * started later via {@link #startDeferredRootPump} after `submitRun`\n * / `respondInput` commits the thread server-side.\n *\n * Note: PR 2381's `#runStartReady` gate covers the analogous race\n * for the in-flight `run.start` send, but only when that send is\n * already pending. `#ensureThread` runs *before* `submitRun` is\n * called (and thus before the gate is armed), so on transports\n * that subscribe synchronously (WebSocket) the deferred path is\n * still required.\n */\n #ensureThread(threadId: string, deferRootPump = false): ThreadStream {\n if (this.#thread != null) return this.#thread;\n this.#thread = this.#options.client.threads.stream(threadId, {\n assistantId: this.#options.assistantId,\n transport: this.#options.transport,\n fetch: this.#options.fetch,\n webSocketFactory: this.#options.webSocketFactory,\n });\n this.registry.bind(this.#thread);\n if (deferRootPump) {\n // Resolve `#rootPumpReady` immediately so `submit()`'s `await\n // this.#rootPumpReady` doesn't block — the dispatch path only\n // needs the ThreadStream wired up to call `submitRun`, not the\n // persistent subscription.\n this.#rootPumpReady = Promise.resolve();\n this.#rootPumpDeferred = true;\n } else {\n this.#startRootPump(this.#thread);\n }\n this.#notifyThreadListeners();\n return this.#thread;\n }\n\n /**\n * Start the previously-deferred root SSE pump after the first\n * `submitRun` / `respondInput` has committed the thread server-side.\n *\n * No-op when the pump was started eagerly in {@link #ensureThread}\n * (i.e. for hydrated existing threads, or for any thread whose pump\n * has already been brought up).\n */\n #startDeferredRootPump(): void {\n if (!this.#rootPumpDeferred) return;\n if (this.#thread == null) return;\n this.#rootPumpDeferred = false;\n this.#startRootPump(this.#thread);\n }\n\n /**\n * Abandon a deferred root pump that never started because its\n * triggering dispatch (`submitRun` / `respondInput`) failed.\n *\n * Without this, the controller would be wedged in a state where:\n * - `#thread` is wired but no content pump is open\n * - `#rootPumpDeferred` stays `true`\n * - `selfCreatedThreadIds` still holds the id\n *\n * A retry submit on the same controller would see\n * `wasSelfCreated=false` (because `currentThreadId` is no longer\n * null), `#ensureThread(id, false)` would early-return because\n * `#thread != null`, and the pump would never start. The thread\n * would have an id committed to the URL but no live subscription.\n *\n * Tearing down `#thread` so the next submit re-runs `#ensureThread`\n * from scratch is the simplest recovery — the failed dispatch\n * means there was nothing to subscribe to anyway.\n */\n #abandonDeferredRootPump(): void {\n if (!this.#rootPumpDeferred) return;\n this.#rootPumpDeferred = false;\n void this.#teardownThread();\n }\n\n /**\n * Close the current thread stream and reset per-thread assembly state.\n */\n async #teardownThread(): Promise<void> {\n const thread = this.#thread;\n this.#thread = undefined;\n this.registry.bind(undefined);\n this.#threadEventUnsubscribe?.();\n this.#threadEventUnsubscribe = undefined;\n /**\n * Persistent lifecycle driver is scoped to the current thread\n * stream. Remove it so a swap to a new thread starts with a clean\n * listener set (a new one is installed in `#startRootPump`).\n */\n this.#rootEventListeners.delete(this.#lifecycleLoading.listener);\n this.#rootEventListeners.delete(this.#runLifecycleListener);\n try {\n await this.#rootSubscription?.unsubscribe();\n } catch {\n /* already closed */\n }\n this.#rootSubscription = undefined;\n this.#rootPumpReady = undefined;\n // Reset so a swap to a new thread doesn't carry over a stale\n // deferred flag — `#ensureThread` will set it again if the new\n // thread is self-created.\n this.#rootPumpDeferred = false;\n try {\n await this.#rootPump;\n } catch {\n /* ignore */\n }\n this.#rootPump = undefined;\n\n // Reset per-thread assembly state.\n this.#rootMessages.reset();\n this.#rootToolAssembler = new ToolCallAssembler();\n this.#lifecycleLoading.reset();\n this.#subagents.reset();\n this.#subgraphs.reset();\n this.#activeRunId = undefined;\n this.#localRunDepth = 0;\n this.#messageMetadata.reset();\n // Drop the hydrate-window allowlist — the next thread's hydrate\n // will repopulate it from that thread's `state.tasks[].interrupts`.\n this.#hydratedActiveInterruptIds = null;\n this.queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n\n if (thread != null) {\n try {\n await thread.close();\n } catch {\n /* already closed */\n }\n this.#notifyThreadListeners();\n }\n }\n\n /**\n * Determine whether the configured transport uses the resumable event-stream path.\n */\n #usesEventStreamTransport(): boolean {\n const transport = this.#options.transport;\n if (transport === \"websocket\") return false;\n if (transport == null || transport === \"sse\") return true;\n return typeof transport.openEventStream === \"function\";\n }\n\n /**\n * Start the always-on root subscription pump for the provided thread.\n *\n * @param thread - Thread stream to subscribe to and fan out from.\n */\n #startRootPump(thread: ThreadStream): void {\n if (this.#rootPump != null) return;\n let resolveReady: (() => void) | undefined;\n this.#rootPumpReady = new Promise<void>((resolve) => {\n resolveReady = resolve;\n });\n\n /**\n * Wildcard discovery + interrupt tracking is delivered via the\n * thread's dedicated lifecycle watcher (see `ThreadStream.onEvent`).\n * This callback fires once per globally-unique event across both\n * the content pump AND the watcher, so we can drive discovery\n * runners and nested HITL capture without widening the content\n * pump's narrow filter.\n */\n this.#threadEventUnsubscribe = thread.onEvent((event) =>\n this.#onWildcardEvent(event)\n );\n\n /**\n * Persistent isLoading driver. Drives `isLoading` from\n * root-namespace lifecycle events so that in-flight runs observed\n * via `hydrate()` (not initiated by a local `submit()`) still flip\n * the UI to loading. `running` → true; terminals → false. The\n * optimistic `isLoading = true` inside `submit()` stays because\n * that fires before any subscription event arrives.\n */\n this.#rootEventListeners.add(this.#lifecycleLoading.listener);\n this.#rootEventListeners.add(this.#runLifecycleListener);\n\n this.#rootPump = (async () => {\n try {\n /**\n * Root content pump: depth 1 is required because the controller\n * classifies tool events at namespace length ≤ 1 as root-level\n * (see `#onWildcardEvent`'s `isRootLevelTool` check). The deep-\n * agent `task` dispatcher fires `tools.tool-started` at\n * `[\"tools:<id>\"]` (length 1), so a depth-0 filter would drop\n * those events server-side before they reached `root.toolCalls`.\n *\n * Deeper content (subagent message tokens, values snapshots) is\n * pulled in on demand by per-namespace selector projections\n * (e.g. `useMessages(sub)`).\n */\n const subscriptionPromise = thread.subscribe({\n channels: [...ROOT_PUMP_CHANNELS] as Channel[],\n namespaces: [[] as string[]],\n depth: 1,\n });\n if (this.#usesEventStreamTransport()) {\n /**\n * SSE streams can legitimately withhold response headers until\n * the first event is available. Waiting for `subscribe()` here\n * would deadlock new-thread submits: the run is not dispatched\n * until the root pump is \"ready\", but the pump does not become\n * ready until the run emits. `thread.subscribe()` has already\n * registered the local subscription and scheduled the stream\n * rotation by this point; waiting one microtask lets the fetch\n * get kicked off without requiring headers to arrive.\n */\n queueMicrotask(() => {\n resolveReady?.();\n resolveReady = undefined;\n });\n }\n const subscription = await subscriptionPromise;\n resolveReady?.();\n resolveReady = undefined;\n this.#rootSubscription = subscription;\n /**\n * The SSE transport pauses the underlying subscription when\n * a terminal root lifecycle event arrives (so `for await`\n * loops observing a single run exit cleanly) and re-opens\n * the next run's server stream on `#prepareForNextRun`,\n * resuming the subscription handle. The root pump needs to\n * survive that hand-off: we re-enter the inner `for await`\n * for every resumed iteration until the subscription is\n * permanently closed or the controller is disposed.\n */\n while (!this.#disposed) {\n for await (const event of subscription) {\n if (this.#disposed) {\n break;\n }\n /**\n * Resilience: isolate per-event dispatch from the pump loop.\n *\n * `#onRootEvent` runs synchronously and, transitively,\n * invokes every root-bus listener (selector projections that\n * opted into the shared stream) plus every `rootStore`\n * subscriber. Some of those subscribers live in a React\n * render tree — `useStream` drives\n * `useSyncExternalStore`, so a misbehaving component can\n * surface a render-phase error (\"Maximum update depth\n * exceeded\", \"The result of getSnapshot should be cached\",\n * etc.) that propagates out here.\n *\n * Without this guard, a single throw bubbles through the\n * `for await` loop and terminates the root pump permanently.\n * That is catastrophic: no more root events get processed —\n * the terminal `lifecycle: completed` never lands, so\n * `#awaitNextTerminal` never resolves, `isLoading` stays\n * `true`, composers stay disabled, and the final assistant\n * turn never commits to `stream.messages`. The UI looks\n * hung even though the server is still emitting events\n * (and `ThreadStream.onEvent` keeps firing).\n *\n * We therefore swallow the error and keep pumping. The\n * the pump's correctness guarantees do not depend on any\n * consumer behaving well.\n */\n try {\n this.#onRootEvent(event);\n } catch {\n /**\n * Best-effort — a consumer-facing store subscriber should not\n * terminate the root pump. Store mutations happen before\n * listeners are notified, so continuing with later events keeps\n * the controller's authoritative state moving forward.\n */\n }\n }\n if (this.#disposed) break;\n if (!subscription.isPaused) {\n break;\n }\n await subscription.waitForResume();\n }\n } catch {\n resolveReady?.();\n resolveReady = undefined;\n /* thread closed or errored */\n }\n })();\n }\n\n /**\n * Handle an event delivered via {@link ThreadStream.onEvent}.\n *\n * `onEvent` fires once per globally-unique event across the content\n * pump and the wildcard lifecycle watcher, so this is the single\n * entry point for wildcard discovery / interrupt tracking. It does\n * NOT fan events out to the root bus (that's driven by the content\n * pump iterator so root-bus short-circuits stay depth-1 scoped) and\n * it does NOT process root content — messages/tools/values at root\n * are handled by `#onRootEvent` off the content pump.\n *\n * @param event - Raw protocol event observed by the thread-wide listener.\n */\n #onWildcardEvent(event: Event): void {\n try {\n this.#subagents.push(event);\n } catch {\n /**\n * Discovery store subscribers are user/UI code. If one throws, still\n * let the wildcard watcher update subgraphs, loading, and interrupts.\n */\n }\n this.#subgraphs.push(event);\n this.#lifecycleLoading.handle(event);\n\n /**\n * Nested `input.requested` events (HITL inside a subagent /\n * subgraph) are not observable via the narrow content pump. The\n * `ThreadStream` itself already records them into\n * `thread.interrupts`, which `#latestUnresolvedInterrupt()`\n * consults — so HITL respond() works for any depth. Root-level\n * interrupts are also mirrored into `rootStore.interrupts` here so\n * UI state does not depend on the narrower content pump being the\n * first consumer to see the event.\n */\n this.#recordRootInterrupt(event);\n }\n\n /**\n * Process one root-pump event and update all root projections.\n *\n * @param event - Event yielded by the root subscription.\n */\n #onRootEvent(event: Event): void {\n try {\n this.#subagents.push(event);\n } catch {\n /**\n * Discovery store subscribers are user/UI code. If one throws, still\n * process the root event below so orchestrator messages and terminal\n * state continue to advance.\n */\n }\n\n /**\n * Fan root-pump events out to every root-bus listener (selector\n * projections that opted into the shared stream,\n * `#awaitTerminal`, etc.). The root bus mirrors the content\n * pump's narrow scope (depth 1 at root) so projections that\n * short-circuit via the bus stay bounded.\n */\n if (this.#rootEventListeners.size > 0) {\n for (const listener of this.#rootEventListeners) {\n try {\n listener(event);\n } catch {\n /**\n * Best-effort — a bad listener should not wedge other\n * projections or the root pump itself.\n */\n }\n }\n }\n\n /**\n * `messages` and `tools` events are emitted under a node's\n * namespace — for a typical StateGraph the LLM's token deltas\n * land on `[\"model:<uuid>\"]`, tool executions on\n * `[\"tools:<uuid>\"]`, etc. The orchestrator's own turns (root\n * agent, or an orchestrator-scoped subgraph like `model:*` /\n * `model_request:*`) belong in `root.messages` and\n * `root.toolCalls`.\n *\n * Subagent / tool-internal branches do NOT:\n * - `task:*` segment — legacy subagent convention.\n * - `tools:*` segment — every tool execution is wrapped in a\n * `tools` subgraph. For simple tools its only content is\n * the eventual tool result (also echoed verbatim by\n * `values.messages` so we don't lose anything). For the\n * deep-agent `task` tool its content IS the spawned\n * subagent's full message + tool stream, which is surfaced\n * separately via `useMessages(stream, subagent)` /\n * `useToolCalls(stream, subagent)`.\n *\n * We therefore drop `messages` events from any namespace that\n * contains a `task:*` or `tools:*` segment; the authoritative\n * tool-result text lands in `root.messages` via the root\n * `values.messages` snapshot merge in `#applyValues`.\n */\n const isInternalNamespace = isInternalWorkNamespace(event.params.namespace);\n const hasLegacySubagentNamespace = isLegacySubagentNamespace(\n event.params.namespace\n );\n\n if (event.method === \"messages\") {\n if (!isInternalNamespace) {\n this.#rootMessages.handleMessage(event as MessagesEvent);\n }\n return;\n }\n\n if (event.method === \"tools\") {\n /**\n * Root-level tool events (both for simple orchestrator tools\n * and the deep-agent `task` dispatcher) fire at a\n * single-segment `[\"tools:<id>\"]` namespace. Anything deeper\n * (e.g. `[tools:<outer>, tools:<inner>]`) is a subagent's own\n * tool call and belongs to that subagent's `useToolCalls`\n * view, not the orchestrator's `root.toolCalls`.\n */\n const isRootLevelTool =\n event.params.namespace.length <= 1 && !hasLegacySubagentNamespace;\n if (isRootLevelTool) {\n /**\n * Record the `namespace → tool_call_id` association so that\n * the ensuing `message-start` (role: \"tool\") at the same\n * namespace can recover the `tool_call_id` (the `messages`\n * channel's start event doesn't carry it directly).\n */\n const toolData = event.params.data as {\n event?: string;\n tool_call_id?: string;\n };\n if (\n toolData.event === \"tool-started\" &&\n typeof toolData.tool_call_id === \"string\"\n ) {\n this.#rootMessages.recordToolCallNamespace(\n event.params.namespace,\n toolData.tool_call_id\n );\n }\n const tc = this.#rootToolAssembler.consume(event as ToolsEvent);\n if (tc != null) {\n this.rootStore.setState((s) => ({\n ...s,\n toolCalls: upsertToolCall(s.toolCalls, tc),\n }));\n }\n }\n return;\n }\n\n /**\n * The `checkpoints` channel carries the lightweight envelope\n * (`id`, `parent_id`, `step`, `source`) emitted immediately\n * before its companion `values` event on the same superstep.\n * Buffer the envelope per-namespace so the ensuing `values`\n * event at the same namespace can pair with it in `#applyValues`.\n * The buffer is read-and-cleared on consumption so a subsequent\n * `values` event without a new checkpoint doesn't reuse stale\n * metadata.\n */\n if (event.method === \"checkpoints\") {\n const data = event.params.data as {\n id?: unknown;\n parent_id?: unknown;\n } | null;\n this.#messageMetadata.bufferCheckpoint(event.params.namespace, data);\n return;\n }\n\n // Channels below are only meaningful at the root namespace.\n const isRoot = isRootNamespace(event.params.namespace);\n if (!isRoot) return;\n\n if (event.method === \"values\") {\n const valuesEvent = event as ValuesEvent;\n const bufferedCheckpoint = this.#messageMetadata.consumeCheckpoint(\n event.params.namespace\n );\n this.#applyValues(valuesEvent.params.data, bufferedCheckpoint);\n return;\n }\n\n if (event.method === \"input.requested\") {\n this.#recordRootInterrupt(event);\n return;\n }\n\n if (event.method === \"lifecycle\") {\n /**\n * Root lifecycle transitions are observed elsewhere\n * (#awaitTerminal) to unblock `submit`.\n */\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n };\n void lifecycle;\n }\n }\n\n /**\n * Merge a `values` payload into root values and root messages.\n *\n * @param raw - Raw `values` channel payload.\n * @param checkpoint - Optional checkpoint envelope paired with the values event.\n */\n #applyValues(raw: unknown, checkpoint?: CheckpointEnvelope): void {\n if (raw == null || typeof raw !== \"object\" || Array.isArray(raw)) {\n return;\n }\n const state = raw as Record<string, unknown>;\n /**\n * Surface parent_checkpoint per-message when the values event\n * carries the lightweight checkpoint envelope (populated by\n * `@langchain/langgraph-core`'s `_emitValuesWithCheckpointMeta` and\n * forwarded through `convertToProtocolEvent`). Consumers surface\n * this as `useMessageMetadata(stream, msg.id).parentCheckpointId`\n * for fork / edit flows.\n */\n const parentCheckpointId = checkpoint?.parent_id;\n if (parentCheckpointId != null && Array.isArray(state[this.#messagesKey])) {\n this.#messageMetadata.recordMessages(\n state[this.#messagesKey] as Array<{ id?: string }>,\n { parentCheckpointId }\n );\n }\n const maybeMessages = state[this.#messagesKey];\n let nextValues: StateType;\n let nextMessages: BaseMessage[];\n if (Array.isArray(maybeMessages)) {\n const coerced = ensureMessageInstances(\n maybeMessages as (Message | BaseMessage)[]\n );\n nextValues = {\n ...(state as StateType),\n [this.#messagesKey]: coerced,\n } as StateType;\n nextMessages = coerced;\n } else {\n nextValues = state as StateType;\n nextMessages = [];\n }\n this.#rootMessages.applyValues(nextValues, nextMessages);\n if (nextMessages.length > 0) {\n this.rootStore.setState((s) => {\n const toolCalls = reconcileToolCallsFromMessages(\n s.toolCalls,\n nextMessages\n );\n if (toolCalls === s.toolCalls) return s;\n return { ...s, toolCalls };\n });\n }\n }\n\n /**\n * Mirror root protocol interrupts into the root snapshot.\n *\n * This can be called from both the wildcard lifecycle/input watcher and the\n * root content pump. Store-level dedup keeps the user-facing list stable.\n */\n #recordRootInterrupt(event: Event): void {\n if (event.method !== \"input.requested\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n const data = event.params.data as {\n interrupt_id?: string;\n payload?: unknown;\n };\n const interruptId = data?.interrupt_id;\n if (\n typeof interruptId !== \"string\" ||\n this.#resolvedInterrupts.has(interruptId)\n ) {\n return;\n }\n // Strict allowlist when populated by the most-recent hydrate: SSE\n // replay of `input.requested` carries no signal distinguishing\n // historical (already-resolved) interrupts from live ones, so we\n // accept only ids the server reported as currently active in\n // `state.tasks[].interrupts`. `null` (outside the hydrate window\n // / after a submit clears it) disables filtering entirely so new\n // live interrupts on an active run pass through.\n if (\n this.#hydratedActiveInterruptIds != null &&\n !this.#hydratedActiveInterruptIds.has(interruptId)\n ) {\n return;\n }\n const interrupt: Interrupt<InterruptType> = {\n id: interruptId,\n value: data.payload as InterruptType,\n };\n this.rootStore.setState((s) => {\n if (s.interrupts.some((entry) => entry.id === interruptId)) return s;\n const interrupts = [...s.interrupts, interrupt];\n return { ...s, interrupts, interrupt: interrupts[0] };\n });\n }\n\n /**\n * Resolve on the next root-namespace terminal lifecycle event\n * (`completed` / `failed` / `interrupted`) or on abort.\n *\n * Attaches to the controller's root event bus instead of opening\n * a second server subscription. Callers should register the\n * returned promise **before** dispatching the command that\n * triggers the run (`thread.run.start` / `thread.input.respond`)\n * — the root pump fans events out synchronously on arrival, so a\n * late registration would miss the terminal for fast runs.\n *\n * @param signal - Abort signal for the local submit lifecycle.\n */\n #awaitNextTerminal(signal: AbortSignal): Promise<{\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n }> {\n return new Promise((resolve) => {\n let settled = false;\n function finish(result: {\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n }) {\n if (settled) return;\n settled = true;\n unsubscribeRoot?.();\n unsubscribeThread?.();\n signal.removeEventListener(\"abort\", finishAborted);\n resolve(result);\n }\n const finishAborted = () => finish({ event: \"aborted\" });\n const onEvent = (event: Event) => {\n if (settled) return;\n if (event.method !== \"lifecycle\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n error?: string;\n };\n if (lifecycle?.event === \"completed\") {\n setTimeout(() => finish({ event: \"completed\" }), 0);\n } else if (lifecycle?.event === \"failed\") {\n setTimeout(\n () => finish({ event: \"failed\", error: lifecycle.error }),\n 0\n );\n } else if (lifecycle?.event === \"interrupted\") {\n setTimeout(() => finish({ event: \"interrupted\" }), 0);\n }\n };\n const unsubscribeRoot = this.#rootBus.subscribe(onEvent);\n const unsubscribeThread = this.#thread?.onEvent(onEvent);\n if (signal.aborted) {\n finishAborted();\n } else {\n signal.addEventListener(\"abort\", finishAborted, { once: true });\n }\n });\n }\n\n /**\n * Resolve which protocol interrupt a resume command should target.\n * Headless-tool resumes are keyed by tool-call id; without matching\n * on that id, parallel tool handlers would respond to the wrong\n * interrupt (always the newest).\n */\n #resolveInterruptForResume(resume?: unknown): ResolvedInterrupt | null {\n const thread = this.#thread;\n if (thread == null) return null;\n return resolveInterruptTargetForHeadlessResume(\n resume,\n thread.interrupts,\n this.#resolvedInterrupts\n );\n }\n\n /**\n * Notify listeners that the underlying thread stream changed.\n */\n #notifyThreadListeners(): void {\n for (const listener of this.#threadListeners) listener(this.#thread);\n }\n}\n\n// ---------- helpers ----------\n\n/**\n * Extract and coerce the configured messages key from a values object.\n *\n * @param values - State values object to read from.\n * @param messagesKey - Key that contains the message array.\n */\nfunction extractAndCoerceMessages(\n values: Record<string, unknown>,\n messagesKey: string\n): BaseMessage[] {\n const raw = values[messagesKey];\n if (!Array.isArray(raw)) return [];\n return ensureMessageInstances(\n raw as (Message | BaseMessage)[]\n ) as BaseMessage[];\n}\n\n// Unused import guard — `AIMessage` is only referenced by type tests.\nvoid AIMessage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"controller.js","names":["#options","#messagesKey","#subagents","#subgraphs","#messageMetadata","#resolvedInterrupts","#selfCreatedThreadIds","#rootEventListeners","#rootBus","#threadListeners","#currentThreadId","#createInitialSnapshot","#rootMessages","#lifecycleLoading","#disposed","#submitter","#ensureThread","#startDeferredRootPump","#abandonDeferredRootPump","#rootPumpReady","#awaitNextTerminal","#thread","#hydratedActiveInterruptIds","#submitGeneration","#markLocalRunStart","#notifyCreated","#notifyCompleted","#markLocalRunEnd","#hydrationPromise","#createHydrationPromise","#resolveHydration","#rejectHydration","#resetHydrationPromise","#teardownThread","#applyValues","#activeRunId","#localRunDepth","#runLifecycleListener","#resolveInterruptForResume","#cancelPendingDispose","#pendingDisposeTimer","#rootPumpDeferred","#startRootPump","#notifyThreadListeners","#threadEventUnsubscribe","#rootSubscription","#rootPump","#rootToolAssembler","#onWildcardEvent","#usesEventStreamTransport","#onRootEvent","#recordRootInterrupt"],"sources":["../../src/stream/controller.ts"],"sourcesContent":["/**\n * Framework-agnostic controller for the experimental v2 stream.\n *\n * Responsibilities:\n * - Owns at most one {@link ThreadStream} at a time (swapped on\n * `hydrate(newThreadId)` or `dispose`).\n * - Exposes three always-on observable surfaces via {@link StreamStore}:\n * - `rootStore` : root values/messages/toolCalls/interrupts/…\n * - `subagentStore` : discovery map of subagents (no content)\n * - `subgraphStore` : discovery map of subgraphs (no content)\n * - Owns a {@link ChannelRegistry} that framework selector hooks\n * (`useMessages`, `useToolCalls`, `useExtension`, `useChannel`)\n * use to lazily open per-namespace subscriptions.\n * - Imperative run surface: `submit`, `stop`, `respond`, `joinStream`.\n *\n * A single multi-channel subscription (`values`, `lifecycle`, `input`,\n * `messages`, `tools`) powers every always-on projection and both\n * discovery runners. Selector hooks add their own (deduped)\n * subscriptions on top — so even a UI with many subagents only opens\n * one extra subscription per `(channels, namespace)` actually\n * rendered on screen.\n */\nimport { AIMessage, type BaseMessage } from \"@langchain/core/messages\";\nimport type {\n Channel,\n Event,\n LifecycleEvent,\n MessagesEvent,\n ToolsEvent,\n ValuesEvent,\n} from \"@langchain/protocol\";\nimport type { Interrupt } from \"../schema.js\";\nimport type { ThreadStream } from \"../client/stream/index.js\";\nimport type { SubscriptionHandle } from \"../client/stream/index.js\";\nimport { ToolCallAssembler } from \"../client/stream/handles/tools.js\";\nimport { ensureMessageInstances } from \"../ui/messages.js\";\nimport type { Message } from \"../types.messages.js\";\nimport { StreamStore } from \"./store.js\";\nimport { ChannelRegistry } from \"./channel-registry.js\";\nimport {\n SubagentDiscovery,\n type SubagentMap,\n SubgraphDiscovery,\n type SubgraphMap,\n type SubgraphByNodeMap,\n} from \"./discovery/index.js\";\nimport {\n isInternalWorkNamespace,\n isLegacySubagentNamespace,\n isRootNamespace,\n} from \"./namespace.js\";\nimport {\n MessageMetadataTracker,\n type CheckpointEnvelope,\n type MessageMetadata,\n type MessageMetadataMap,\n} from \"./message-metadata-tracker.js\";\nimport { LifecycleLoadingTracker } from \"./lifecycle-loading-tracker.js\";\nimport { RootMessageProjection } from \"./root-message-projection.js\";\nimport {\n EMPTY_QUEUE,\n SubmitCoordinator,\n type SubmissionQueueEntry,\n type SubmissionQueueSnapshot,\n} from \"./submit-coordinator.js\";\nimport {\n reconcileToolCallsFromMessages,\n upsertToolCall,\n} from \"./tool-calls.js\";\nimport {\n buildResumeRunInput,\n resolveInterruptTargetForHeadlessResume,\n} from \"../headless-tools.js\";\nimport type {\n RootEventBus,\n RootSnapshot,\n RunExecutionReason,\n StreamControllerOptions,\n StreamStopOptions,\n StreamSubmitOptions,\n} from \"./types.js\";\n\nfunction isAbortLikeError(error: unknown): boolean {\n if (error == null || typeof error !== \"object\") return false;\n const maybeError = error as { name?: unknown; message?: unknown };\n return (\n maybeError.name === \"AbortError\" ||\n (typeof maybeError.message === \"string\" &&\n maybeError.message.includes(\"aborted\"))\n );\n}\n\nfunction lifecycleReason(event: string | undefined): RunExecutionReason | null {\n if (event === \"completed\") return \"success\";\n if (event === \"failed\") return \"error\";\n if (event === \"interrupted\") return \"interrupt\";\n return null;\n}\n\nconst ROOT_NAMESPACE: readonly string[] = [];\n\n/**\n * Channel set covered by the always-on root subscription. Exported so\n * projections (and transports) can reason about what the root pump\n * already delivers before opening additional server subscriptions.\n */\nexport const ROOT_PUMP_CHANNELS: readonly Channel[] = [\n \"values\",\n \"checkpoints\",\n \"lifecycle\",\n \"input\",\n \"messages\",\n \"tools\",\n];\n\ninterface ResolvedInterrupt {\n interruptId: string;\n namespace: string[];\n}\n\nexport type {\n MessageMetadata,\n MessageMetadataMap,\n SubmissionQueueEntry,\n SubmissionQueueSnapshot,\n};\n\n/**\n * Coordinates one thread's protocol-v2 stream and exposes stable\n * observable projections for framework bindings.\n *\n * The controller owns the root subscription, lazily binds scoped\n * projections through {@link ChannelRegistry}, and normalizes protocol\n * events into class-message, tool-call, discovery, interrupt, and queue\n * stores.\n *\n * @typeParam StateType - Shape of the graph state exposed on `values`.\n * @typeParam InterruptType - Shape of protocol interrupt payloads.\n * @typeParam ConfigurableType - Shape of `config.configurable` accepted by submit.\n */\nexport class StreamController<\n StateType extends object = Record<string, unknown>,\n InterruptType = unknown,\n ConfigurableType extends object = Record<string, unknown>,\n> {\n readonly rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n readonly subagentStore: StreamStore<SubagentMap>;\n readonly subgraphStore: StreamStore<SubgraphMap>;\n readonly subgraphByNodeStore: StreamStore<SubgraphByNodeMap>;\n readonly messageMetadataStore: StreamStore<MessageMetadataMap>;\n readonly queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n readonly registry: ChannelRegistry;\n\n readonly #options: StreamControllerOptions<StateType>;\n readonly #messagesKey: string;\n readonly #subagents = new SubagentDiscovery();\n readonly #subgraphs = new SubgraphDiscovery();\n readonly #messageMetadata = new MessageMetadataTracker();\n\n #thread: ThreadStream | undefined;\n #currentThreadId: string | null;\n #rootSubscription: SubscriptionHandle<Event> | undefined;\n #rootPump: Promise<void> | undefined;\n #rootPumpReady: Promise<void> | undefined;\n /**\n * `true` while a self-created thread has its root pump deferred until\n * the first `submitRun` / `respondInput` commits the thread row\n * server-side. See `#ensureThread` and `#startDeferredRootPump`.\n */\n #rootPumpDeferred = false;\n #threadEventUnsubscribe: (() => void) | undefined;\n #disposed = false;\n #pendingDisposeTimer: ReturnType<typeof setTimeout> | null = null;\n readonly #resolvedInterrupts = new Set<string>();\n /**\n * Set of interrupt IDs the server reports as currently *active* on\n * the thread (from `state.tasks[].interrupts`). Populated during\n * {@link hydrate} from `client.threads.getState()` and used as a\n * strict allowlist when processing replayed `input.requested`\n * events from the persistent SSE subscription. Without this guard,\n * SSE replay re-adds historically-requested interrupts that have\n * since been resolved (no `input.responded` event exists in the\n * protocol, so the SDK has no other way to tell replay from live\n * for an idle thread). `null` outside the hydrate-window so\n * genuinely new live interrupts on an active run aren't filtered;\n * cleared at the start of `submit()` for the same reason.\n */\n #hydratedActiveInterruptIds: Set<string> | null = null;\n /**\n * Monotonic counter bumped at the start of each `submit()` and used\n * by {@link hydrate} to skip its post-fetch allowlist write when a\n * submit started while the state fetch was in flight. Without this\n * guard, a submit-then-hydrate race could re-install a stale\n * allowlist that filters out genuinely-new live interrupts emitted\n * by the just-started run.\n */\n #submitGeneration = 0;\n /**\n * Thread ids we minted client-side on first `submit()`. Keeping them\n * here lets `hydrate()` skip the `threads.getState()` round-trip —\n * we know there is nothing checkpointed server-side yet (and the\n * request would 404 and surface a spurious error to the UI).\n */\n readonly #selfCreatedThreadIds = new Set<string>();\n readonly #rootEventListeners = new Set<(event: Event) => void>();\n readonly #rootBus: RootEventBus;\n #activeRunId: string | undefined;\n #localRunDepth = 0;\n\n /**\n * Single-shot hydration promise. Exposed via `hydrationPromise`\n * so Suspense wrappers can throw it until the first hydrate\n * completes (resolve) or fails (reject). Reset whenever a new\n * hydrate cycle begins so `<Suspense>` boundaries re-suspend on\n * thread switch.\n */\n #hydrationPromise: Promise<void>;\n #resolveHydration!: () => void;\n #rejectHydration!: (error: unknown) => void;\n\n /**\n * Tool assembler lives for the lifetime of a thread; reset on\n * rebind so a fresh thread starts with a clean slate.\n */\n #rootToolAssembler = new ToolCallAssembler();\n #rootMessages!: RootMessageProjection<StateType, InterruptType>;\n #lifecycleLoading!: LifecycleLoadingTracker<\n RootSnapshot<StateType, InterruptType>\n >;\n #submitter!: SubmitCoordinator<StateType, InterruptType, ConfigurableType>;\n\n readonly #threadListeners = new Set<\n (thread: ThreadStream | undefined) => void\n >();\n\n /**\n * Create a controller around a LangGraph client and optional initial thread.\n *\n * @param options - Runtime configuration, client, thread id, and initial state.\n */\n constructor(options: StreamControllerOptions<StateType>) {\n this.#options = options;\n this.#messagesKey = options.messagesKey ?? \"messages\";\n this.#currentThreadId = options.threadId ?? null;\n this.#rootBus = {\n channels: ROOT_PUMP_CHANNELS,\n subscribe: (listener) => {\n this.#rootEventListeners.add(listener);\n return () => {\n this.#rootEventListeners.delete(listener);\n };\n },\n };\n this.registry = new ChannelRegistry(this.#rootBus);\n this.subagentStore = this.#subagents.store;\n this.subgraphStore = this.#subgraphs.store;\n this.subgraphByNodeStore = this.#subgraphs.byNodeStore;\n this.rootStore = new StreamStore<RootSnapshot<StateType, InterruptType>>(\n this.#createInitialSnapshot()\n );\n this.#rootMessages = new RootMessageProjection({\n messagesKey: this.#messagesKey,\n store: this.rootStore,\n });\n this.#lifecycleLoading = new LifecycleLoadingTracker({\n store: this.rootStore,\n isDisposed: () => this.#disposed,\n });\n this.messageMetadataStore = this.#messageMetadata.store;\n this.queueStore = new StreamStore<SubmissionQueueSnapshot<StateType>>(\n EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n this.#submitter = new SubmitCoordinator({\n options: this.#options,\n rootStore: this.rootStore,\n queueStore: this.queueStore,\n getDisposed: () => this.#disposed,\n getCurrentThreadId: () => this.#currentThreadId,\n setCurrentThreadId: (threadId) => {\n this.#currentThreadId = threadId;\n },\n rememberSelfCreatedThreadId: (threadId) => {\n this.#selfCreatedThreadIds.add(threadId);\n },\n hydrate: (threadId) => this.hydrate(threadId),\n ensureThread: (threadId, deferRootPump) =>\n this.#ensureThread(threadId, deferRootPump),\n startDeferredRootPump: () => this.#startDeferredRootPump(),\n abandonDeferredRootPump: () => this.#abandonDeferredRootPump(),\n forgetSelfCreatedThreadId: (threadId) => {\n this.#selfCreatedThreadIds.delete(threadId);\n },\n waitForRootPumpReady: () => this.#rootPumpReady,\n awaitNextTerminal: (signal) => this.#awaitNextTerminal(signal),\n buildResumeRunInput: (resume) => {\n const thread = this.#thread;\n if (thread == null) return null;\n return buildResumeRunInput(\n resume,\n thread.interrupts,\n this.#resolvedInterrupts\n );\n },\n markInterruptResolved: (interruptId) => {\n this.#resolvedInterrupts.add(interruptId);\n },\n onSubmitStart: () => {\n // Clear the hydrate-window allowlist so genuinely-new live\n // interrupts on the just-started run aren't filtered. Bump\n // the generation so any in-flight hydrate skips its\n // allowlist write on return (see #hydratedActiveInterruptIds).\n this.#hydratedActiveInterruptIds = null;\n this.#submitGeneration += 1;\n },\n onRunStart: () => this.#markLocalRunStart(),\n onRunCreated: (runId) => this.#notifyCreated(runId),\n onRunCompleted: (reason, runId) => this.#notifyCompleted(reason, runId),\n onRunEnd: () => this.#markLocalRunEnd(),\n });\n this.#hydrationPromise = this.#createHydrationPromise();\n /**\n * Attach a default no-op catch so orphaned hydrationPromise\n * rejections (e.g. controllers spawned during Suspense retries\n * whose promise never gets subscribed to because the suspense\n * cache already holds an earlier one) don't surface as unhandled\n * rejections. Callers that attach their own handlers via .then()\n * still receive the rejection on their derived promise.\n */\n this.#hydrationPromise.catch(() => undefined);\n /**\n * Kick off the initial hydrate eagerly when a caller-supplied\n * thread id is present. Suspense consumers throw\n * `hydrationPromise` on the very first render, which unmounts the\n * subtree before any `useEffect` can run — so if we waited for an\n * effect to drive the hydrate we'd deadlock. Firing here makes\n * the promise settle independently of the component lifecycle.\n */\n if (this.#currentThreadId != null) {\n void this.hydrate(this.#currentThreadId);\n } else {\n this.#resolveHydration();\n }\n }\n\n /**\n * Promise that settles the first time {@link hydrate} finishes on\n * the current thread. Resolves on a clean hydration, rejects when\n * the thread-state fetch errors. A fresh promise is installed on\n * every thread swap so `<Suspense>` wrappers re-suspend on\n * `switchThread`.\n */\n get hydrationPromise(): Promise<void> {\n return this.#hydrationPromise;\n }\n\n /**\n * Create the deferred promise backing the current hydration cycle.\n */\n #createHydrationPromise(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.#resolveHydration = resolve;\n this.#rejectHydration = reject;\n });\n }\n\n /**\n * Replace the current hydration promise before a thread swap.\n */\n #resetHydrationPromise(): void {\n /**\n * Swallow rejection on the orphaned promise so Node doesn't\n * flag it as unhandled; Suspense callers that still hold a\n * reference see the rejection they subscribed to.\n */\n this.#hydrationPromise.catch(() => undefined);\n this.#hydrationPromise = this.#createHydrationPromise();\n }\n\n // ---------- public imperatives ----------\n\n /**\n * Fetch the checkpointed thread state and seed the root snapshot.\n * Re-calling with a different `threadId` swaps the underlying\n * {@link ThreadStream}, rewires the registry to the new thread, and\n * resets assemblers.\n *\n * @param threadId - Optional replacement thread id; `null` clears the active thread.\n */\n async hydrate(threadId?: string | null): Promise<void> {\n if (this.#disposed) return;\n const target = threadId === undefined ? this.#currentThreadId : threadId;\n const changed = target !== this.#currentThreadId;\n this.#currentThreadId = target ?? null;\n this.rootStore.setState((s) => ({ ...s, threadId: this.#currentThreadId }));\n\n if (changed) {\n /**\n * Swap to a new thread: re-arm the hydration promise so any\n * Suspense boundary remounted against the new id suspends again.\n */\n this.#resetHydrationPromise();\n await this.#teardownThread();\n /**\n * Reset UI-facing snapshot so stale messages/values/tool-calls\n * from the previous thread don't bleed into the new one. The\n * new thread's state (if any) is then populated below via\n * `#applyValues`.\n */\n this.rootStore.setState(() => ({\n ...this.#createInitialSnapshot(),\n threadId: this.#currentThreadId,\n }));\n /**\n * Drop queued submissions — they were targeted at the previous\n * thread so dispatching them against the new thread would be\n * surprising. Mirrors the legacy `StreamOrchestrator` behaviour.\n */\n this.queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n }\n\n if (this.#currentThreadId == null) {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n this.#resolveHydration();\n return;\n }\n\n /**\n * Self-generated thread ids have nothing to fetch server-side yet\n * — the thread is created lazily by the first `run.start`. Calling\n * `threads.getState()` here would return a 404 and surface a\n * spurious error to the UI.\n */\n if (this.#selfCreatedThreadIds.has(this.#currentThreadId)) {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n this.#resolveHydration();\n return;\n }\n\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: true }));\n let hydrationError: unknown;\n let threadExists = false;\n try {\n const state = await this.#options.client.threads.getState<StateType>(\n this.#currentThreadId\n );\n threadExists = state != null;\n if (state?.values != null) {\n /**\n * `threads.getState()` returns the legacy `ThreadState` shape\n * where `parent_checkpoint` is an object (`{ thread_id,\n * checkpoint_id, checkpoint_ns }`). Synthesize the v2\n * `Checkpoint` envelope (matching the `checkpoints` channel\n * payload) so hydrated messages also get their\n * `parentCheckpointId` populated for fork / edit flows.\n */\n const checkpointId = state.checkpoint?.checkpoint_id;\n const parentCheckpointId =\n state.parent_checkpoint?.checkpoint_id ?? undefined;\n const syntheticCheckpoint =\n typeof checkpointId === \"string\"\n ? {\n id: checkpointId,\n ...(parentCheckpointId != null\n ? { parent_id: parentCheckpointId }\n : {}),\n }\n : undefined;\n this.#applyValues(state.values as unknown, syntheticCheckpoint);\n }\n /**\n * Sync the visible interrupt list to the server's authoritative\n * `state.tasks[].interrupts`. Without this, SSE replay of past\n * `input.requested` events would re-add resolved interrupts to\n * the UI on every page navigation back to the thread.\n *\n * Only runs when `state.tasks` is an array — runtimes that don't\n * surface tasks in `threads.getState()` are left untouched (an\n * unconditional overwrite would wipe any in-flight interrupt\n * state the wildcard watcher may already have recorded).\n */\n if (Array.isArray(state?.tasks)) {\n const generationAtFetch = this.#submitGeneration;\n const activeInterrupts: Interrupt<InterruptType>[] = [];\n const activeIds = new Set<string>();\n for (const task of state.tasks) {\n if (!Array.isArray(task?.interrupts)) continue;\n for (const interrupt of task.interrupts) {\n const typed = interrupt as\n | { id?: string; value?: unknown }\n | null\n | undefined;\n const id = typed?.id;\n if (typeof id !== \"string\" || activeIds.has(id)) continue;\n activeIds.add(id);\n activeInterrupts.push({\n id,\n value: typed?.value as InterruptType,\n });\n }\n }\n this.rootStore.setState((s) => ({\n ...s,\n interrupts: activeInterrupts,\n interrupt: activeInterrupts[0],\n }));\n // Only seed the allowlist when no submit started while the\n // state fetch was in flight. If one did, the cleared\n // (null) allowlist must stay null so the new run's live\n // interrupts are not filtered.\n if (this.#submitGeneration === generationAtFetch) {\n this.#hydratedActiveInterruptIds = activeIds;\n }\n }\n } catch (error) {\n /**\n * A 404 on hydrate means the thread does not exist server-side\n * yet (most commonly because the caller supplied a brand-new,\n * externally-minted thread id and is about to create it via the\n * first `submit()`). Treat it as \"empty state\" rather than a\n * fatal hydration error so Suspense boundaries resolve cleanly\n * and no spurious error renders in the UI.\n */\n const status = (error as { status?: number } | null)?.status;\n if (status !== 404) {\n hydrationError = error;\n this.rootStore.setState((s) => ({ ...s, error }));\n }\n } finally {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n if (hydrationError != null) {\n this.#rejectHydration(hydrationError);\n } else {\n this.#resolveHydration();\n }\n }\n\n /**\n * P0 fix: open the shared subscription on mount so in-flight\n * server-side runs are observed even when no local `submit()` is\n * active. The transport replays the run from `seq=0` on a rotating\n * subscribe, so late-joining is free once the subscription exists.\n * `isLoading` transitions are driven by the persistent root\n * lifecycle listener registered in `#startRootPump`.\n */\n const thread = this.#ensureThread(this.#currentThreadId);\n\n /**\n * Start the wildcard lifecycle watcher up-front for existing\n * threads. The root content pump runs at `depth: 1`, which covers\n * root-namespace and one-deep events but not arbitrarily-nested\n * subagent / subgraph lifecycle — the dedicated watcher handles\n * those.\n *\n * For self-created (new) threads we skip — the watcher would 404\n * against a not-yet-existent thread. `submitRun` / `respondInput`\n * call `startLifecycleWatcher` on first submission to cover that\n * case.\n */\n if (threadExists) {\n thread.startLifecycleWatcher();\n }\n }\n\n /**\n * Submit input or a resume command to the active thread.\n *\n * @param input - Input payload for a new run; `null`/`undefined` submits no input.\n * @param options - Per-run config, metadata, multitask behavior, and callbacks.\n */\n async submit(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): Promise<void> {\n await this.#submitter.submit(input, options);\n }\n\n /**\n * Disconnect the client from the active run and mark the controller\n * idle. By default also cancels the run server-side; pass\n * `{ cancel: false }` or call {@link disconnect} to keep the agent\n * running (join/rejoin).\n */\n async stop(options?: StreamStopOptions): Promise<void> {\n const shouldCancel = options?.cancel ?? true;\n if (shouldCancel) {\n const threadId = this.#currentThreadId;\n const runId = this.#activeRunId;\n if (threadId != null && runId != null) {\n try {\n await this.#options.client.runs.cancel(threadId, runId);\n } catch {\n /* server cancel failures must not block client disconnect */\n }\n }\n }\n await this.#submitter.stop();\n }\n\n /**\n * Disconnect the client without cancelling the run server-side.\n * Alias for `stop({ cancel: false })`.\n */\n async disconnect(): Promise<void> {\n return this.stop({ cancel: false });\n }\n\n #markLocalRunStart(): void {\n this.#localRunDepth += 1;\n }\n\n #markLocalRunEnd(): void {\n this.#localRunDepth = Math.max(0, this.#localRunDepth - 1);\n }\n\n #notifyCreated(runId: string): void {\n this.#activeRunId = runId;\n try {\n this.#options.onCreated?.({ runId });\n } catch {\n /* caller-supplied callback errors must not crash the stream */\n }\n }\n\n #notifyCompleted(\n reason: RunExecutionReason,\n runId = this.#activeRunId\n ): void {\n if (runId != null && runId === this.#activeRunId) {\n this.#activeRunId = undefined;\n }\n setTimeout(() => {\n if (this.#disposed) return;\n try {\n this.#options.onCompleted?.(\n runId == null ? { reason } : { runId, reason }\n );\n } catch {\n /* caller-supplied callback errors must not crash the stream */\n }\n }, 0);\n }\n\n readonly #runLifecycleListener = (event: Event): void => {\n if (this.#localRunDepth > 0) return;\n if (event.method !== \"lifecycle\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n if (!this.rootStore.getSnapshot().isLoading) return;\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n };\n const reason = lifecycleReason(lifecycle?.event);\n if (reason == null) return;\n this.#notifyCompleted(reason);\n };\n\n /**\n * Cancel a queued submission by id. Returns `true` when the entry\n * was found and removed, `false` otherwise.\n *\n * Today this only removes the entry from the client-side mirror —\n * once the server exposes queue cancel (roadmap A0.3) the\n * controller will additionally issue a cancel call against the\n * active transport.\n *\n * @param id - Client-side queue entry id to remove.\n */\n async cancelQueued(id: string): Promise<boolean> {\n return this.#submitter.cancelQueued(id);\n }\n\n /**\n * Drop every queued submission. Server-side cancel arrives with A0.3.\n */\n async clearQueue(): Promise<void> {\n await this.#submitter.clearQueue();\n }\n\n /**\n * Respond to a pending protocol interrupt.\n *\n * @param response - Payload to send back to the interrupted namespace.\n * @param target - Optional explicit interrupt id and namespace; defaults to the latest unresolved interrupt.\n */\n async respond(\n response: unknown,\n target?: { interruptId: string; namespace?: string[] }\n ): Promise<void> {\n if (this.#disposed || this.#thread == null) {\n throw new Error(\"No active thread to respond to.\");\n }\n const resolved =\n target != null\n ? {\n interruptId: target.interruptId,\n namespace: target.namespace ?? [...ROOT_NAMESPACE],\n }\n : this.#resolveInterruptForResume();\n if (resolved == null) {\n throw new Error(\"No pending interrupt to respond to.\");\n }\n try {\n await this.#thread.respondInput({\n namespace: resolved.namespace,\n interrupt_id: resolved.interruptId,\n response,\n });\n this.#resolvedInterrupts.add(resolved.interruptId);\n } catch (error) {\n if (this.#disposed && isAbortLikeError(error)) {\n return;\n }\n throw error;\n }\n }\n\n /**\n * Dispose the active thread, subscriptions, registry entries, and listeners.\n */\n async dispose(): Promise<void> {\n if (this.#disposed) return;\n this.#cancelPendingDispose();\n this.#disposed = true;\n this.#submitter.abortActiveRun();\n await this.#teardownThread();\n await this.registry.dispose();\n this.#threadListeners.clear();\n }\n\n /**\n * StrictMode-safe lifecycle hook for framework bindings.\n *\n * React 18+ `StrictMode` intentionally mounts → unmounts → remounts\n * components in dev to surface effect-cleanup bugs. A naive\n * `useEffect(() => () => controller.dispose())` would permanently\n * tear the controller down on that first synthetic unmount, leaving\n * every subsequent `submit()` a silent no-op.\n *\n * Call {@link activate} from the bind site's effect and return the\n * result as the effect's cleanup. The controller uses deferred\n * disposal: a `release()` only schedules a dispose on the next\n * microtask, which is cancelled if another `activate()` arrives\n * before it fires (the normal StrictMode remount path).\n */\n activate(): () => void {\n this.#cancelPendingDispose();\n return () => {\n if (this.#disposed) return;\n this.#pendingDisposeTimer = setTimeout(() => {\n this.#pendingDisposeTimer = null;\n void this.dispose().catch(() => undefined);\n }, 0);\n };\n }\n\n /**\n * Cancel a deferred dispose scheduled by {@link activate}.\n */\n #cancelPendingDispose(): void {\n if (this.#pendingDisposeTimer != null) {\n clearTimeout(this.#pendingDisposeTimer);\n this.#pendingDisposeTimer = null;\n }\n }\n\n // ---------- thread access ----------\n\n /**\n * Returns the bound {@link ThreadStream}, if one exists. Prefer\n * {@link StreamController.rootStore} and selector projections for\n * UI work; use this for low-level protocol access.\n */\n getThread(): ThreadStream | undefined {\n return this.#thread;\n }\n\n /**\n * Listen for `ThreadStream` lifecycle (swap on thread-id change,\n * detach on dispose). The listener fires immediately with the\n * current thread (may be `undefined`).\n *\n * @param listener - Callback invoked immediately and on every thread swap.\n */\n subscribeThread(\n listener: (thread: ThreadStream | undefined) => void\n ): () => void {\n this.#threadListeners.add(listener);\n listener(this.#thread);\n return () => {\n this.#threadListeners.delete(listener);\n };\n }\n\n // ---------- internals ----------\n\n /**\n * Build the initial root snapshot from configured initial values.\n */\n #createInitialSnapshot(): RootSnapshot<StateType, InterruptType> {\n const values = (this.#options.initialValues ??\n ({} as StateType)) as StateType;\n const messages = extractAndCoerceMessages(\n values as unknown as Record<string, unknown>,\n this.#messagesKey\n );\n /**\n * Seed `isThreadLoading: true` synchronously when a caller-supplied\n * threadId is on the controller at construction/swap time. Without\n * this Suspense wrappers would render their fallback for a tick\n * because `isThreadLoading` flips false → true → false once the\n * deferred `hydrate()` starts, and the synchronous render observes\n * the initial `false`.\n */\n const willHydrate =\n this.#currentThreadId != null &&\n !this.#selfCreatedThreadIds.has(this.#currentThreadId);\n return {\n values,\n messages,\n toolCalls: [],\n interrupts: [],\n interrupt: undefined,\n isLoading: false,\n isThreadLoading: willHydrate,\n error: undefined,\n threadId: this.#currentThreadId,\n };\n }\n\n /**\n * Return the active thread stream, creating and binding one when needed.\n *\n * @param threadId - Thread id used when constructing the stream.\n * @param deferRootPump - When `true`, build the ThreadStream and bind\n * the registry but skip starting the persistent root SSE pump. Used\n * for client-self-created thread ids whose server-side thread row\n * doesn't exist yet — opening the pump's `subscription.subscribe`\n * against a not-yet-existent thread produces a `404: Thread not\n * found` protocol error that strands terminal lifecycle events and\n * leaves the UI showing nothing until the user reloads. The pump is\n * started later via {@link #startDeferredRootPump} after `submitRun`\n * / `respondInput` commits the thread server-side.\n *\n * Note: PR 2381's `#runStartReady` gate covers the analogous race\n * for the in-flight `run.start` send, but only when that send is\n * already pending. `#ensureThread` runs *before* `submitRun` is\n * called (and thus before the gate is armed), so on transports\n * that subscribe synchronously (WebSocket) the deferred path is\n * still required.\n */\n #ensureThread(threadId: string, deferRootPump = false): ThreadStream {\n if (this.#thread != null) return this.#thread;\n this.#thread = this.#options.client.threads.stream(threadId, {\n assistantId: this.#options.assistantId,\n transport: this.#options.transport,\n fetch: this.#options.fetch,\n webSocketFactory: this.#options.webSocketFactory,\n });\n this.registry.bind(this.#thread);\n if (deferRootPump) {\n // Resolve `#rootPumpReady` immediately so `submit()`'s `await\n // this.#rootPumpReady` doesn't block — the dispatch path only\n // needs the ThreadStream wired up to call `submitRun`, not the\n // persistent subscription.\n this.#rootPumpReady = Promise.resolve();\n this.#rootPumpDeferred = true;\n } else {\n this.#startRootPump(this.#thread);\n }\n this.#notifyThreadListeners();\n return this.#thread;\n }\n\n /**\n * Start the previously-deferred root SSE pump after the first\n * `submitRun` / `respondInput` has committed the thread server-side.\n *\n * No-op when the pump was started eagerly in {@link #ensureThread}\n * (i.e. for hydrated existing threads, or for any thread whose pump\n * has already been brought up).\n */\n #startDeferredRootPump(): void {\n if (!this.#rootPumpDeferred) return;\n if (this.#thread == null) return;\n this.#rootPumpDeferred = false;\n this.#startRootPump(this.#thread);\n }\n\n /**\n * Abandon a deferred root pump that never started because its\n * triggering dispatch (`submitRun` / `respondInput`) failed.\n *\n * Without this, the controller would be wedged in a state where:\n * - `#thread` is wired but no content pump is open\n * - `#rootPumpDeferred` stays `true`\n * - `selfCreatedThreadIds` still holds the id\n *\n * A retry submit on the same controller would see\n * `wasSelfCreated=false` (because `currentThreadId` is no longer\n * null), `#ensureThread(id, false)` would early-return because\n * `#thread != null`, and the pump would never start. The thread\n * would have an id committed to the URL but no live subscription.\n *\n * Tearing down `#thread` so the next submit re-runs `#ensureThread`\n * from scratch is the simplest recovery — the failed dispatch\n * means there was nothing to subscribe to anyway.\n */\n #abandonDeferredRootPump(): void {\n if (!this.#rootPumpDeferred) return;\n this.#rootPumpDeferred = false;\n void this.#teardownThread();\n }\n\n /**\n * Close the current thread stream and reset per-thread assembly state.\n */\n async #teardownThread(): Promise<void> {\n const thread = this.#thread;\n this.#thread = undefined;\n this.registry.bind(undefined);\n this.#threadEventUnsubscribe?.();\n this.#threadEventUnsubscribe = undefined;\n /**\n * Persistent lifecycle driver is scoped to the current thread\n * stream. Remove it so a swap to a new thread starts with a clean\n * listener set (a new one is installed in `#startRootPump`).\n */\n this.#rootEventListeners.delete(this.#lifecycleLoading.listener);\n this.#rootEventListeners.delete(this.#runLifecycleListener);\n try {\n await this.#rootSubscription?.unsubscribe();\n } catch {\n /* already closed */\n }\n this.#rootSubscription = undefined;\n this.#rootPumpReady = undefined;\n // Reset so a swap to a new thread doesn't carry over a stale\n // deferred flag — `#ensureThread` will set it again if the new\n // thread is self-created.\n this.#rootPumpDeferred = false;\n try {\n await this.#rootPump;\n } catch {\n /* ignore */\n }\n this.#rootPump = undefined;\n\n // Reset per-thread assembly state.\n this.#rootMessages.reset();\n this.#rootToolAssembler = new ToolCallAssembler();\n this.#lifecycleLoading.reset();\n this.#subagents.reset();\n this.#subgraphs.reset();\n this.#activeRunId = undefined;\n this.#localRunDepth = 0;\n this.#messageMetadata.reset();\n // Drop the hydrate-window allowlist — the next thread's hydrate\n // will repopulate it from that thread's `state.tasks[].interrupts`.\n this.#hydratedActiveInterruptIds = null;\n this.queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n\n if (thread != null) {\n try {\n await thread.close();\n } catch {\n /* already closed */\n }\n this.#notifyThreadListeners();\n }\n }\n\n /**\n * Determine whether the configured transport uses the resumable event-stream path.\n */\n #usesEventStreamTransport(): boolean {\n const transport = this.#options.transport;\n if (transport === \"websocket\") return false;\n if (transport == null || transport === \"sse\") return true;\n return typeof transport.openEventStream === \"function\";\n }\n\n /**\n * Start the always-on root subscription pump for the provided thread.\n *\n * @param thread - Thread stream to subscribe to and fan out from.\n */\n #startRootPump(thread: ThreadStream): void {\n if (this.#rootPump != null) return;\n let resolveReady: (() => void) | undefined;\n this.#rootPumpReady = new Promise<void>((resolve) => {\n resolveReady = resolve;\n });\n\n /**\n * Wildcard discovery + interrupt tracking is delivered via the\n * thread's dedicated lifecycle watcher (see `ThreadStream.onEvent`).\n * This callback fires once per globally-unique event across both\n * the content pump AND the watcher, so we can drive discovery\n * runners and nested HITL capture without widening the content\n * pump's narrow filter.\n */\n this.#threadEventUnsubscribe = thread.onEvent((event) =>\n this.#onWildcardEvent(event)\n );\n\n /**\n * Persistent isLoading driver. Drives `isLoading` from\n * root-namespace lifecycle events so that in-flight runs observed\n * via `hydrate()` (not initiated by a local `submit()`) still flip\n * the UI to loading. `running` → true; terminals → false. The\n * optimistic `isLoading = true` inside `submit()` stays because\n * that fires before any subscription event arrives.\n */\n this.#rootEventListeners.add(this.#lifecycleLoading.listener);\n this.#rootEventListeners.add(this.#runLifecycleListener);\n\n this.#rootPump = (async () => {\n try {\n /**\n * Root content pump: depth 1 is required because the controller\n * classifies tool events at namespace length ≤ 1 as root-level\n * (see `#onWildcardEvent`'s `isRootLevelTool` check). The deep-\n * agent `task` dispatcher fires `tools.tool-started` at\n * `[\"tools:<id>\"]` (length 1), so a depth-0 filter would drop\n * those events server-side before they reached `root.toolCalls`.\n *\n * Deeper content (subagent message tokens, values snapshots) is\n * pulled in on demand by per-namespace selector projections\n * (e.g. `useMessages(sub)`).\n */\n const subscriptionPromise = thread.subscribe({\n channels: [...ROOT_PUMP_CHANNELS] as Channel[],\n namespaces: [[] as string[]],\n depth: 1,\n });\n if (this.#usesEventStreamTransport()) {\n /**\n * SSE streams can legitimately withhold response headers until\n * the first event is available. Waiting for `subscribe()` here\n * would deadlock new-thread submits: the run is not dispatched\n * until the root pump is \"ready\", but the pump does not become\n * ready until the run emits. `thread.subscribe()` has already\n * registered the local subscription and scheduled the stream\n * rotation by this point; waiting one microtask lets the fetch\n * get kicked off without requiring headers to arrive.\n */\n queueMicrotask(() => {\n resolveReady?.();\n resolveReady = undefined;\n });\n }\n const subscription = await subscriptionPromise;\n resolveReady?.();\n resolveReady = undefined;\n this.#rootSubscription = subscription;\n /**\n * The SSE transport pauses the underlying subscription when\n * a terminal root lifecycle event arrives (so `for await`\n * loops observing a single run exit cleanly) and re-opens\n * the next run's server stream on `#prepareForNextRun`,\n * resuming the subscription handle. The root pump needs to\n * survive that hand-off: we re-enter the inner `for await`\n * for every resumed iteration until the subscription is\n * permanently closed or the controller is disposed.\n */\n while (!this.#disposed) {\n for await (const event of subscription) {\n if (this.#disposed) {\n break;\n }\n /**\n * Resilience: isolate per-event dispatch from the pump loop.\n *\n * `#onRootEvent` runs synchronously and, transitively,\n * invokes every root-bus listener (selector projections that\n * opted into the shared stream) plus every `rootStore`\n * subscriber. Some of those subscribers live in a React\n * render tree — `useStream` drives\n * `useSyncExternalStore`, so a misbehaving component can\n * surface a render-phase error (\"Maximum update depth\n * exceeded\", \"The result of getSnapshot should be cached\",\n * etc.) that propagates out here.\n *\n * Without this guard, a single throw bubbles through the\n * `for await` loop and terminates the root pump permanently.\n * That is catastrophic: no more root events get processed —\n * the terminal `lifecycle: completed` never lands, so\n * `#awaitNextTerminal` never resolves, `isLoading` stays\n * `true`, composers stay disabled, and the final assistant\n * turn never commits to `stream.messages`. The UI looks\n * hung even though the server is still emitting events\n * (and `ThreadStream.onEvent` keeps firing).\n *\n * We therefore swallow the error and keep pumping. The\n * the pump's correctness guarantees do not depend on any\n * consumer behaving well.\n */\n try {\n this.#onRootEvent(event);\n } catch {\n /**\n * Best-effort — a consumer-facing store subscriber should not\n * terminate the root pump. Store mutations happen before\n * listeners are notified, so continuing with later events keeps\n * the controller's authoritative state moving forward.\n */\n }\n }\n if (this.#disposed) break;\n if (!subscription.isPaused) {\n break;\n }\n await subscription.waitForResume();\n }\n } catch {\n resolveReady?.();\n resolveReady = undefined;\n /* thread closed or errored */\n }\n })();\n }\n\n /**\n * Handle an event delivered via {@link ThreadStream.onEvent}.\n *\n * `onEvent` fires once per globally-unique event across the content\n * pump and the wildcard lifecycle watcher, so this is the single\n * entry point for wildcard discovery / interrupt tracking. It does\n * NOT fan events out to the root bus (that's driven by the content\n * pump iterator so root-bus short-circuits stay depth-1 scoped) and\n * it does NOT process root content — messages/tools/values at root\n * are handled by `#onRootEvent` off the content pump.\n *\n * @param event - Raw protocol event observed by the thread-wide listener.\n */\n #onWildcardEvent(event: Event): void {\n try {\n this.#subagents.push(event);\n } catch {\n /**\n * Discovery store subscribers are user/UI code. If one throws, still\n * let the wildcard watcher update subgraphs, loading, and interrupts.\n */\n }\n this.#subgraphs.push(event);\n this.#lifecycleLoading.handle(event);\n\n /**\n * Nested `input.requested` events (HITL inside a subagent /\n * subgraph) are not observable via the narrow content pump. The\n * `ThreadStream` itself already records them into\n * `thread.interrupts`, which `#latestUnresolvedInterrupt()`\n * consults — so HITL respond() works for any depth. Root-level\n * interrupts are also mirrored into `rootStore.interrupts` here so\n * UI state does not depend on the narrower content pump being the\n * first consumer to see the event.\n */\n this.#recordRootInterrupt(event);\n }\n\n /**\n * Process one root-pump event and update all root projections.\n *\n * @param event - Event yielded by the root subscription.\n */\n #onRootEvent(event: Event): void {\n try {\n this.#subagents.push(event);\n } catch {\n /**\n * Discovery store subscribers are user/UI code. If one throws, still\n * process the root event below so orchestrator messages and terminal\n * state continue to advance.\n */\n }\n\n /**\n * Fan root-pump events out to every root-bus listener (selector\n * projections that opted into the shared stream,\n * `#awaitTerminal`, etc.). The root bus mirrors the content\n * pump's narrow scope (depth 1 at root) so projections that\n * short-circuit via the bus stay bounded.\n */\n if (this.#rootEventListeners.size > 0) {\n for (const listener of this.#rootEventListeners) {\n try {\n listener(event);\n } catch {\n /**\n * Best-effort — a bad listener should not wedge other\n * projections or the root pump itself.\n */\n }\n }\n }\n\n /**\n * `messages` and `tools` events are emitted under a node's\n * namespace — for a typical StateGraph the LLM's token deltas\n * land on `[\"model:<uuid>\"]`, tool executions on\n * `[\"tools:<uuid>\"]`, etc. The orchestrator's own turns (root\n * agent, or an orchestrator-scoped subgraph like `model:*` /\n * `model_request:*`) belong in `root.messages` and\n * `root.toolCalls`.\n *\n * Subagent / tool-internal branches do NOT:\n * - `task:*` segment — legacy subagent convention.\n * - `tools:*` segment — every tool execution is wrapped in a\n * `tools` subgraph. For simple tools its only content is\n * the eventual tool result (also echoed verbatim by\n * `values.messages` so we don't lose anything). For the\n * deep-agent `task` tool its content IS the spawned\n * subagent's full message + tool stream, which is surfaced\n * separately via `useMessages(stream, subagent)` /\n * `useToolCalls(stream, subagent)`.\n *\n * We therefore drop `messages` events from any namespace that\n * contains a `task:*` or `tools:*` segment; the authoritative\n * tool-result text lands in `root.messages` via the root\n * `values.messages` snapshot merge in `#applyValues`.\n */\n const isInternalNamespace = isInternalWorkNamespace(event.params.namespace);\n const hasLegacySubagentNamespace = isLegacySubagentNamespace(\n event.params.namespace\n );\n\n if (event.method === \"messages\") {\n if (!isInternalNamespace) {\n this.#rootMessages.handleMessage(event as MessagesEvent);\n }\n return;\n }\n\n if (event.method === \"tools\") {\n /**\n * Root-level tool events (both for simple orchestrator tools\n * and the deep-agent `task` dispatcher) fire at a\n * single-segment `[\"tools:<id>\"]` namespace. Anything deeper\n * (e.g. `[tools:<outer>, tools:<inner>]`) is a subagent's own\n * tool call and belongs to that subagent's `useToolCalls`\n * view, not the orchestrator's `root.toolCalls`.\n */\n const isRootLevelTool =\n event.params.namespace.length <= 1 && !hasLegacySubagentNamespace;\n if (isRootLevelTool) {\n /**\n * Record the `namespace → tool_call_id` association so that\n * the ensuing `message-start` (role: \"tool\") at the same\n * namespace can recover the `tool_call_id` (the `messages`\n * channel's start event doesn't carry it directly).\n */\n const toolData = event.params.data as {\n event?: string;\n tool_call_id?: string;\n };\n if (\n toolData.event === \"tool-started\" &&\n typeof toolData.tool_call_id === \"string\"\n ) {\n this.#rootMessages.recordToolCallNamespace(\n event.params.namespace,\n toolData.tool_call_id\n );\n }\n const tc = this.#rootToolAssembler.consume(event as ToolsEvent);\n if (tc != null) {\n this.rootStore.setState((s) => ({\n ...s,\n toolCalls: upsertToolCall(s.toolCalls, tc),\n }));\n }\n }\n return;\n }\n\n /**\n * The `checkpoints` channel carries the lightweight envelope\n * (`id`, `parent_id`, `step`, `source`) emitted immediately\n * before its companion `values` event on the same superstep.\n * Buffer the envelope per-namespace so the ensuing `values`\n * event at the same namespace can pair with it in `#applyValues`.\n * The buffer is read-and-cleared on consumption so a subsequent\n * `values` event without a new checkpoint doesn't reuse stale\n * metadata.\n */\n if (event.method === \"checkpoints\") {\n const data = event.params.data as {\n id?: unknown;\n parent_id?: unknown;\n } | null;\n this.#messageMetadata.bufferCheckpoint(event.params.namespace, data);\n return;\n }\n\n // Channels below are only meaningful at the root namespace.\n const isRoot = isRootNamespace(event.params.namespace);\n if (!isRoot) return;\n\n if (event.method === \"values\") {\n const valuesEvent = event as ValuesEvent;\n const bufferedCheckpoint = this.#messageMetadata.consumeCheckpoint(\n event.params.namespace\n );\n this.#applyValues(valuesEvent.params.data, bufferedCheckpoint);\n return;\n }\n\n if (event.method === \"input.requested\") {\n this.#recordRootInterrupt(event);\n return;\n }\n\n if (event.method === \"lifecycle\") {\n /**\n * Root lifecycle transitions are observed elsewhere\n * (#awaitTerminal) to unblock `submit`.\n */\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n };\n void lifecycle;\n }\n }\n\n /**\n * Merge a `values` payload into root values and root messages.\n *\n * @param raw - Raw `values` channel payload.\n * @param checkpoint - Optional checkpoint envelope paired with the values event.\n */\n #applyValues(raw: unknown, checkpoint?: CheckpointEnvelope): void {\n if (raw == null || typeof raw !== \"object\" || Array.isArray(raw)) {\n return;\n }\n const state = raw as Record<string, unknown>;\n /**\n * Surface parent_checkpoint per-message when the values event\n * carries the lightweight checkpoint envelope (populated by\n * `@langchain/langgraph-core`'s `_emitValuesWithCheckpointMeta` and\n * forwarded through `convertToProtocolEvent`). Consumers surface\n * this as `useMessageMetadata(stream, msg.id).parentCheckpointId`\n * for fork / edit flows.\n */\n const parentCheckpointId = checkpoint?.parent_id;\n if (parentCheckpointId != null && Array.isArray(state[this.#messagesKey])) {\n this.#messageMetadata.recordMessages(\n state[this.#messagesKey] as Array<{ id?: string }>,\n { parentCheckpointId }\n );\n }\n const maybeMessages = state[this.#messagesKey];\n let nextValues: StateType;\n let nextMessages: BaseMessage[];\n if (Array.isArray(maybeMessages)) {\n const coerced = ensureMessageInstances(\n maybeMessages as (Message | BaseMessage)[]\n );\n nextValues = {\n ...(state as StateType),\n [this.#messagesKey]: coerced,\n } as StateType;\n nextMessages = coerced;\n } else {\n nextValues = state as StateType;\n nextMessages = [];\n }\n this.#rootMessages.applyValues(nextValues, nextMessages);\n if (nextMessages.length > 0) {\n this.rootStore.setState((s) => {\n const toolCalls = reconcileToolCallsFromMessages(\n s.toolCalls,\n nextMessages\n );\n if (toolCalls === s.toolCalls) return s;\n return { ...s, toolCalls };\n });\n }\n }\n\n /**\n * Mirror root protocol interrupts into the root snapshot.\n *\n * This can be called from both the wildcard lifecycle/input watcher and the\n * root content pump. Store-level dedup keeps the user-facing list stable.\n */\n #recordRootInterrupt(event: Event): void {\n if (event.method !== \"input.requested\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n const data = event.params.data as {\n interrupt_id?: string;\n payload?: unknown;\n };\n const interruptId = data?.interrupt_id;\n if (\n typeof interruptId !== \"string\" ||\n this.#resolvedInterrupts.has(interruptId)\n ) {\n return;\n }\n // Strict allowlist when populated by the most-recent hydrate: SSE\n // replay of `input.requested` carries no signal distinguishing\n // historical (already-resolved) interrupts from live ones, so we\n // accept only ids the server reported as currently active in\n // `state.tasks[].interrupts`. `null` (outside the hydrate window\n // / after a submit clears it) disables filtering entirely so new\n // live interrupts on an active run pass through.\n if (\n this.#hydratedActiveInterruptIds != null &&\n !this.#hydratedActiveInterruptIds.has(interruptId)\n ) {\n return;\n }\n const interrupt: Interrupt<InterruptType> = {\n id: interruptId,\n value: data.payload as InterruptType,\n };\n this.rootStore.setState((s) => {\n if (s.interrupts.some((entry) => entry.id === interruptId)) return s;\n const interrupts = [...s.interrupts, interrupt];\n return { ...s, interrupts, interrupt: interrupts[0] };\n });\n }\n\n /**\n * Resolve on the next root-namespace terminal lifecycle event\n * (`completed` / `failed` / `interrupted`) or on abort.\n *\n * Attaches to the controller's root event bus instead of opening\n * a second server subscription. Callers should register the\n * returned promise **before** dispatching the command that\n * triggers the run (`thread.run.start` / `thread.input.respond`)\n * — the root pump fans events out synchronously on arrival, so a\n * late registration would miss the terminal for fast runs.\n *\n * @param signal - Abort signal for the local submit lifecycle.\n */\n #awaitNextTerminal(signal: AbortSignal): Promise<{\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n }> {\n return new Promise((resolve) => {\n let settled = false;\n function finish(result: {\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n }) {\n if (settled) return;\n settled = true;\n unsubscribeRoot?.();\n unsubscribeThread?.();\n signal.removeEventListener(\"abort\", finishAborted);\n resolve(result);\n }\n const finishAborted = () => finish({ event: \"aborted\" });\n const onEvent = (event: Event) => {\n if (settled) return;\n if (event.method !== \"lifecycle\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n error?: string;\n };\n if (lifecycle?.event === \"completed\") {\n setTimeout(() => finish({ event: \"completed\" }), 0);\n } else if (lifecycle?.event === \"failed\") {\n setTimeout(\n () => finish({ event: \"failed\", error: lifecycle.error }),\n 0\n );\n } else if (lifecycle?.event === \"interrupted\") {\n setTimeout(() => finish({ event: \"interrupted\" }), 0);\n }\n };\n const unsubscribeRoot = this.#rootBus.subscribe(onEvent);\n const unsubscribeThread = this.#thread?.onEvent(onEvent);\n if (signal.aborted) {\n finishAborted();\n } else {\n signal.addEventListener(\"abort\", finishAborted, { once: true });\n }\n });\n }\n\n /**\n * Resolve which protocol interrupt a resume command should target.\n * Headless-tool resumes are keyed by tool-call id; without matching\n * on that id, parallel tool handlers would respond to the wrong\n * interrupt (always the newest).\n */\n #resolveInterruptForResume(resume?: unknown): ResolvedInterrupt | null {\n const thread = this.#thread;\n if (thread == null) return null;\n return resolveInterruptTargetForHeadlessResume(\n resume,\n thread.interrupts,\n this.#resolvedInterrupts\n );\n }\n\n /**\n * Notify listeners that the underlying thread stream changed.\n */\n #notifyThreadListeners(): void {\n for (const listener of this.#threadListeners) listener(this.#thread);\n }\n}\n\n// ---------- helpers ----------\n\n/**\n * Extract and coerce the configured messages key from a values object.\n *\n * @param values - State values object to read from.\n * @param messagesKey - Key that contains the message array.\n */\nfunction extractAndCoerceMessages(\n values: Record<string, unknown>,\n messagesKey: string\n): BaseMessage[] {\n const raw = values[messagesKey];\n if (!Array.isArray(raw)) return [];\n return ensureMessageInstances(\n raw as (Message | BaseMessage)[]\n ) as BaseMessage[];\n}\n\n// Unused import guard — `AIMessage` is only referenced by type tests.\nvoid AIMessage;\n"],"mappings":"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"}
@@ -6,7 +6,7 @@ import { AgentTypeConfigLike, CompiledSubAgentLike, DeepAgentTypeConfigLike, Def
6
6
  import { InferBag, InferNodeNames } from "../ui/stream/index.cjs";
7
7
  import { AssembledToolCallFromTool, InferStateType, InferSubagentStates, InferToolCalls, WidenUpdateMessages } from "./types-inference.cjs";
8
8
  import { StoreListener, StreamStore } from "./store.cjs";
9
- import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot, RunCompletedInfo, RunExecutionInfo, RunExecutionReason, StreamControllerOptions, StreamSubmitOptions, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, Target, UseStreamCommonOptions, UseStreamOptions } from "./types.cjs";
9
+ import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot, RunCompletedInfo, RunExecutionInfo, RunExecutionReason, StreamControllerOptions, StreamStopOptions, StreamSubmitOptions, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, Target, UseStreamCommonOptions, UseStreamOptions } from "./types.cjs";
10
10
  import { ChannelRegistry } from "./channel-registry.cjs";
11
11
  import { SubagentDiscovery, SubagentMap } from "./discovery/subagents.cjs";
12
12
  import { SubgraphByNodeMap, SubgraphDiscovery, SubgraphMap } from "./discovery/subgraphs.cjs";
@@ -22,4 +22,4 @@ import { MediaProjectionOptions, audioProjection, filesProjection, imagesProject
22
22
  import { AssembledToMessageInput, ExtendedMessageRole, assembledMessageToBaseMessage, assembledToBaseMessage } from "./assembled-to-message.cjs";
23
23
  import { NAMESPACE_SEPARATOR } from "./constants.cjs";
24
24
  import { Channel, Event } from "@langchain/protocol";
25
- export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AssembledToolCallFromTool, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type InferToolOutput, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type RunCompletedInfo, type RunExecutionInfo, type RunExecutionReason, type StoreListener, StreamController, type StreamControllerOptions, StreamStore, type StreamSubmitOptions, type SubAgentLike, SubagentDiscovery, type SubagentDiscoverySnapshot, type SubagentMap, type SubagentStateMap, type SubagentToolCall, type SubgraphByNodeMap, SubgraphDiscovery, type SubgraphDiscoverySnapshot, type SubgraphMap, type SubmissionQueueEntry, type SubmissionQueueSnapshot, type Target, type ToolCallFromTool, type ToolCallStatus, type TransportAdapter, type UseStreamCommonOptions, type UseStreamOptions, type VideoMedia, type WidenUpdateMessages, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, parseToolOutput, parseToolPayload, toolCallsProjection, valuesProjection, videoProjection };
25
+ export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AssembledToolCallFromTool, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type InferToolOutput, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type RunCompletedInfo, type RunExecutionInfo, type RunExecutionReason, type StoreListener, StreamController, type StreamControllerOptions, type StreamStopOptions, StreamStore, type StreamSubmitOptions, type SubAgentLike, SubagentDiscovery, type SubagentDiscoverySnapshot, type SubagentMap, type SubagentStateMap, type SubagentToolCall, type SubgraphByNodeMap, SubgraphDiscovery, type SubgraphDiscoverySnapshot, type SubgraphMap, type SubmissionQueueEntry, type SubmissionQueueSnapshot, type Target, type ToolCallFromTool, type ToolCallStatus, type TransportAdapter, type UseStreamCommonOptions, type UseStreamOptions, type VideoMedia, type WidenUpdateMessages, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, parseToolOutput, parseToolPayload, toolCallsProjection, valuesProjection, videoProjection };
@@ -6,7 +6,7 @@ import { AgentTypeConfigLike, CompiledSubAgentLike, DeepAgentTypeConfigLike, Def
6
6
  import { InferBag, InferNodeNames } from "../ui/stream/index.js";
7
7
  import { AssembledToolCallFromTool, InferStateType, InferSubagentStates, InferToolCalls, WidenUpdateMessages } from "./types-inference.js";
8
8
  import { StoreListener, StreamStore } from "./store.js";
9
- import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot, RunCompletedInfo, RunExecutionInfo, RunExecutionReason, StreamControllerOptions, StreamSubmitOptions, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, Target, UseStreamCommonOptions, UseStreamOptions } from "./types.js";
9
+ import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot, RunCompletedInfo, RunExecutionInfo, RunExecutionReason, StreamControllerOptions, StreamStopOptions, StreamSubmitOptions, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, Target, UseStreamCommonOptions, UseStreamOptions } from "./types.js";
10
10
  import { ChannelRegistry } from "./channel-registry.js";
11
11
  import { SubagentDiscovery, SubagentMap } from "./discovery/subagents.js";
12
12
  import { SubgraphByNodeMap, SubgraphDiscovery, SubgraphMap } from "./discovery/subgraphs.js";
@@ -22,4 +22,4 @@ import { MediaProjectionOptions, audioProjection, filesProjection, imagesProject
22
22
  import { AssembledToMessageInput, ExtendedMessageRole, assembledMessageToBaseMessage, assembledToBaseMessage } from "./assembled-to-message.js";
23
23
  import { NAMESPACE_SEPARATOR } from "./constants.js";
24
24
  import { Channel, Event } from "@langchain/protocol";
25
- export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AssembledToolCallFromTool, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type InferToolOutput, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type RunCompletedInfo, type RunExecutionInfo, type RunExecutionReason, type StoreListener, StreamController, type StreamControllerOptions, StreamStore, type StreamSubmitOptions, type SubAgentLike, SubagentDiscovery, type SubagentDiscoverySnapshot, type SubagentMap, type SubagentStateMap, type SubagentToolCall, type SubgraphByNodeMap, SubgraphDiscovery, type SubgraphDiscoverySnapshot, type SubgraphMap, type SubmissionQueueEntry, type SubmissionQueueSnapshot, type Target, type ToolCallFromTool, type ToolCallStatus, type TransportAdapter, type UseStreamCommonOptions, type UseStreamOptions, type VideoMedia, type WidenUpdateMessages, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, parseToolOutput, parseToolPayload, toolCallsProjection, valuesProjection, videoProjection };
25
+ export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AssembledToolCallFromTool, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type InferToolOutput, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type RunCompletedInfo, type RunExecutionInfo, type RunExecutionReason, type StoreListener, StreamController, type StreamControllerOptions, type StreamStopOptions, StreamStore, type StreamSubmitOptions, type SubAgentLike, SubagentDiscovery, type SubagentDiscoverySnapshot, type SubagentMap, type SubagentStateMap, type SubagentToolCall, type SubgraphByNodeMap, SubgraphDiscovery, type SubgraphDiscoverySnapshot, type SubgraphMap, type SubmissionQueueEntry, type SubmissionQueueSnapshot, type Target, type ToolCallFromTool, type ToolCallStatus, type TransportAdapter, type UseStreamCommonOptions, type UseStreamOptions, type VideoMedia, type WidenUpdateMessages, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, parseToolOutput, parseToolPayload, toolCallsProjection, valuesProjection, videoProjection };
@@ -175,7 +175,6 @@ var SubmitCoordinator = class {
175
175
  * - `"enqueue"` — defers via {@link #enqueueSubmission};
176
176
  * the call returns without dispatching.
177
177
  * - `"interrupt"` — falls through to the default path
178
- * (server-side cancellation lands with roadmap A0.3).
179
178
  *
180
179
  * Errors are routed through both the per-submit `onError` callback
181
180
  * and `rootStore.error`. Aborts (controller dispose / rollback) are
@@ -350,11 +349,8 @@ var SubmitCoordinator = class {
350
349
  /**
351
350
  * Abort the current run (if any) and force `isLoading=false`.
352
351
  *
353
- * Does NOT issue a server-side cancel that lands with roadmap
354
- * A0.3. Today this is a client-side stop only: subsequent events
355
- * for the aborted run are ignored by the controller's pump because
356
- * the abort signal is the same one `#awaitNextTerminal` is wired
357
- * to.
352
+ * Client-side only server-side cancel is handled by
353
+ * {@link StreamController.stop} before this is invoked.
358
354
  */
359
355
  async stop() {
360
356
  this.abortActiveRun();
@@ -1 +1 @@
1
- {"version":3,"file":"submit-coordinator.cjs","names":["#options","#rootStore","#queueStore","#getDisposed","#getCurrentThreadId","#setCurrentThreadId","#rememberSelfCreatedThreadId","#forgetSelfCreatedThreadId","#hydrate","#ensureThread","#startDeferredRootPump","#abandonDeferredRootPump","#waitForRootPumpReady","#awaitNextTerminal","#buildResumeRunInput","#markInterruptResolved","#onSubmitStart","#onRunStart","#onRunCreated","#onRunCompleted","#onRunEnd","#runAbort","#enqueueSubmission","#drainQueue"],"sources":["../../src/stream/submit-coordinator.ts"],"sourcesContent":["/**\n * Owns the run-submission lifecycle for a single\n * {@link StreamController}.\n *\n * # What this module is\n *\n * The {@link SubmitCoordinator} is the piece of the controller that\n * dispatches runs (`submit()`), enforces multitask strategies, queues\n * deferred submissions, races dispatch against terminal lifecycle\n * events, and surfaces errors back through the per-submit `onError`\n * callback and the root snapshot.\n *\n * Conceptually a submit looks like:\n *\n * 1. Optionally rebind to a different thread (`options.threadId`).\n * 2. Mint a thread id if one isn't bound yet.\n * 3. Wait for the controller's root pump to be ready (so the\n * transport is subscribed before the run is dispatched —\n * otherwise we could miss replayed events).\n * 4. Apply the {@link StreamSubmitOptions.multitaskStrategy} to\n * decide whether to abort, enqueue, reject, or proceed.\n * 5. Race the dispatch promise (`thread.submitRun()` or\n * `thread.submitRun()` for resumes) against the next root\n * terminal lifecycle event.\n * 6. Settle the resulting state (loading flag, error slot) and\n * drain the next queued submission, if any.\n *\n * # Why it lives in its own class\n *\n * The submit lifecycle is the most state-heavy part of the\n * controller — six promises, an abort controller, a queue, a\n * terminal-vs-command race, and bidirectional callback wiring with\n * the controller. Splitting it out keeps `controller.ts` focused on\n * subscription / projection wiring while letting the submit logic\n * evolve independently.\n *\n * # Why we race \"command\" against \"terminal\"\n *\n * For fast runs, the server's terminal lifecycle event can arrive\n * *before* the dispatch HTTP response has resolved. Racing the two\n * lets us detect terminal early and not block waiting for a now-stale\n * dispatch response. The dispatch response is still consumed (via\n * `.then(notifyCreated).catch(reportError)`) so `onCreated` still\n * fires and dispatch errors still surface through `onError`.\n *\n * # Queue semantics (`multitaskStrategy: \"enqueue\"`)\n *\n * When a run is already in flight, an `\"enqueue\"` submit is recorded\n * into {@link queueStore} and the call returns immediately. After the\n * active run terminates, `#drainQueue` schedules the head of the\n * queue as a fresh submit on the next macrotask. Each drained\n * submission has its own `multitaskStrategy` cleared so it doesn't\n * recursively re-enqueue.\n *\n * @see StreamController - The owner; injects every collaborator dep.\n */\nimport { v7 as uuidv7 } from \"uuid\";\nimport type { ThreadStream } from \"../client/stream/index.js\";\nimport { StreamStore } from \"./store.js\";\nimport type {\n RootSnapshot,\n RunExecutionReason,\n StreamControllerOptions,\n StreamSubmitOptions,\n} from \"./types.js\";\n\n/**\n * Result of awaiting the next root terminal lifecycle event. Mirrors\n * the three terminal lifecycle states the protocol surfaces, plus a\n * synthetic `\"aborted\"` for client-side cancellation.\n */\ntype TerminalResult = {\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n};\n\nfunction terminalReason(event: TerminalResult[\"event\"]): RunExecutionReason {\n if (event === \"completed\") return \"success\";\n if (event === \"failed\") return \"error\";\n if (event === \"interrupted\") return \"interrupt\";\n return \"stopped\";\n}\n\n/**\n * Queued submission entry mirrored from the server-side run queue.\n *\n * Surfaces the deferred submission to UI consumers via\n * {@link StreamController.queueStore}.\n */\nexport interface SubmissionQueueEntry<\n StateType extends object = Record<string, unknown>,\n> {\n /** Stable id minted on enqueue (uuidv7 — sortable by creation time). */\n readonly id: string;\n /** Original submit input, narrowed to the partial state shape. */\n readonly values: Partial<StateType> | null | undefined;\n /** Original submit options, minus the strategy slot which is reset on drain. */\n readonly options?: StreamSubmitOptions<StateType>;\n /** Wall-clock timestamp at enqueue. */\n readonly createdAt: Date;\n}\n\n/**\n * Read-only snapshot of the queue. The queue store hands this out\n * directly; consumers must not mutate the array.\n */\nexport type SubmissionQueueSnapshot<\n StateType extends object = Record<string, unknown>,\n> = ReadonlyArray<SubmissionQueueEntry<StateType>>;\n\n/**\n * Frozen empty queue value used as the initial / cleared snapshot.\n *\n * Reusing one frozen reference keeps store identity stable across\n * empty resets, so React's `useSyncExternalStore` doesn't think the\n * queue changed when it actually didn't.\n */\nexport const EMPTY_QUEUE: SubmissionQueueSnapshot<never> = Object.freeze([]);\n\n/**\n * Coordinates one controller's run-submission lifecycle.\n *\n * The constructor takes a bag of callbacks rather than a reference to\n * the parent {@link StreamController} on purpose:\n *\n * - It keeps the dependency surface explicit and testable — every\n * piece of controller state the submit lifecycle touches is one\n * of these closures.\n * - It avoids a cyclic dependency between controller and coordinator.\n * - Tests can construct one with stub callbacks and assert behavior\n * without mocking the entire controller.\n *\n * @typeParam StateType - Root state shape.\n * @typeParam InterruptType - Root interrupt payload shape.\n * @typeParam ConfigurableType - `config.configurable` shape accepted\n * by submit (usually `Record<string, unknown>`).\n */\nexport class SubmitCoordinator<\n StateType extends object = Record<string, unknown>,\n InterruptType = unknown,\n ConfigurableType extends object = Record<string, unknown>,\n> {\n /** Controller-level options forwarded into `submitRun` / callbacks. */\n readonly #options: StreamControllerOptions<StateType>;\n /** Root snapshot store; written for `isLoading`, `error`, `interrupts`. */\n readonly #rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n /** Pending submissions awaiting the active run to terminate. */\n readonly #queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n /** Probes the controller's `disposed` flag from deferred work. */\n readonly #getDisposed: () => boolean;\n /** Reads the controller's currently-bound thread id. */\n readonly #getCurrentThreadId: () => string | null;\n /** Updates the controller's thread id (used when minting a new id). */\n readonly #setCurrentThreadId: (threadId: string | null) => void;\n /** Records a thread id we created client-side so hydrate can skip a 404 round-trip. */\n readonly #rememberSelfCreatedThreadId: (threadId: string) => void;\n /** Drops a thread id from the self-created set once it's committed server-side. */\n readonly #forgetSelfCreatedThreadId: (threadId: string) => void;\n /** Triggers a hydrate on the controller (used by `options.threadId` rebinds). */\n readonly #hydrate: (threadId?: string | null) => Promise<void>;\n /** Lazily creates / returns the active {@link ThreadStream}. */\n readonly #ensureThread: (\n threadId: string,\n deferRootPump?: boolean\n ) => ThreadStream;\n /** Starts the previously-deferred root pump after a self-created thread commits. */\n readonly #startDeferredRootPump: () => void;\n /** Abandons a deferred root pump after a self-created dispatch fails. */\n readonly #abandonDeferredRootPump: () => void;\n /** Resolves once the controller's root subscription pump is up. */\n readonly #waitForRootPumpReady: () => Promise<void> | undefined;\n /** Resolves on the next root terminal lifecycle (or on abort). */\n readonly #awaitNextTerminal: (signal: AbortSignal) => Promise<TerminalResult>;\n /** Builds interrupt-id keyed `run.start` input from `command.resume`. */\n readonly #buildResumeRunInput: (\n resume: unknown\n ) => Record<string, unknown> | null;\n /** Marks an interrupt id as resolved so it isn't re-targeted. */\n readonly #markInterruptResolved: (interruptId: string) => void;\n /** Called once at the start of every {@link submit} invocation. */\n readonly #onSubmitStart: () => void;\n /** Marks that a local run dispatch is now active. */\n readonly #onRunStart: () => void;\n /** Records a server-accepted local run id and fires `onCreated`. */\n readonly #onRunCreated: (runId: string) => void;\n /** Fires `onCompleted` for the local run lifecycle. */\n readonly #onRunCompleted: (\n reason: RunExecutionReason,\n runId?: string\n ) => void;\n /** Marks the local run dispatch lifecycle as settled. */\n readonly #onRunEnd: () => void;\n\n /**\n * Active submission's abort controller. `undefined` between submits.\n *\n * Used both for `multitaskStrategy: \"rollback\"` (abort the previous\n * controller's signal) and `stop()` (abort the current one without\n * starting a new one).\n */\n #runAbort: AbortController | undefined;\n\n constructor(params: {\n options: StreamControllerOptions<StateType>;\n rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n getDisposed: () => boolean;\n getCurrentThreadId: () => string | null;\n setCurrentThreadId: (threadId: string | null) => void;\n rememberSelfCreatedThreadId: (threadId: string) => void;\n forgetSelfCreatedThreadId: (threadId: string) => void;\n hydrate: (threadId?: string | null) => Promise<void>;\n ensureThread: (threadId: string, deferRootPump?: boolean) => ThreadStream;\n startDeferredRootPump: () => void;\n abandonDeferredRootPump: () => void;\n waitForRootPumpReady: () => Promise<void> | undefined;\n awaitNextTerminal: (signal: AbortSignal) => Promise<TerminalResult>;\n buildResumeRunInput: (resume: unknown) => Record<string, unknown> | null;\n markInterruptResolved: (interruptId: string) => void;\n onSubmitStart?: () => void;\n onRunStart?: () => void;\n onRunCreated?: (runId: string) => void;\n onRunCompleted?: (reason: RunExecutionReason, runId?: string) => void;\n onRunEnd?: () => void;\n }) {\n this.#options = params.options;\n this.#rootStore = params.rootStore;\n this.#queueStore = params.queueStore;\n this.#getDisposed = params.getDisposed;\n this.#getCurrentThreadId = params.getCurrentThreadId;\n this.#setCurrentThreadId = params.setCurrentThreadId;\n this.#rememberSelfCreatedThreadId = params.rememberSelfCreatedThreadId;\n this.#forgetSelfCreatedThreadId = params.forgetSelfCreatedThreadId;\n this.#hydrate = params.hydrate;\n this.#ensureThread = params.ensureThread;\n this.#startDeferredRootPump = params.startDeferredRootPump;\n this.#abandonDeferredRootPump = params.abandonDeferredRootPump;\n this.#waitForRootPumpReady = params.waitForRootPumpReady;\n this.#awaitNextTerminal = params.awaitNextTerminal;\n this.#buildResumeRunInput = params.buildResumeRunInput;\n this.#markInterruptResolved = params.markInterruptResolved;\n this.#onSubmitStart = params.onSubmitStart ?? (() => undefined);\n this.#onRunStart = params.onRunStart ?? (() => undefined);\n this.#onRunCreated = params.onRunCreated ?? (() => undefined);\n this.#onRunCompleted = params.onRunCompleted ?? (() => undefined);\n this.#onRunEnd = params.onRunEnd ?? (() => undefined);\n }\n\n /**\n * Submit input or a resume command to the active thread.\n *\n * Honours {@link StreamSubmitOptions.multitaskStrategy}:\n *\n * - `\"rollback\"` (default) — aborts any in-flight run and\n * dispatches immediately.\n * - `\"reject\"` — throws synchronously when a run is\n * already in flight.\n * - `\"enqueue\"` — defers via {@link #enqueueSubmission};\n * the call returns without dispatching.\n * - `\"interrupt\"` — falls through to the default path\n * (server-side cancellation lands with roadmap A0.3).\n *\n * Errors are routed through both the per-submit `onError` callback\n * and `rootStore.error`. Aborts (controller dispose / rollback) are\n * silently dropped.\n *\n * @param input - Input payload, or `null`/`undefined` for no input\n * (typical for resume commands).\n * @param options - Per-submit options (config, metadata, callbacks,\n * strategy, etc).\n */\n async submit(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): Promise<void> {\n if (this.#getDisposed()) return;\n this.#onSubmitStart();\n\n // Per-submit thread override: rebind first so the rest of the\n // submit operates against the new thread.\n const overrideThreadId = options?.threadId;\n if (\n overrideThreadId !== undefined &&\n overrideThreadId !== this.#getCurrentThreadId()\n ) {\n await this.#hydrate(overrideThreadId);\n }\n\n // Self-created thread id path: mint client-side so the controller\n // (and Suspense boundaries) get a stable id even before the run\n // is dispatched.\n const wasSelfCreated = this.#getCurrentThreadId() == null;\n if (wasSelfCreated) {\n const threadId = uuidv7();\n this.#setCurrentThreadId(threadId);\n this.#rememberSelfCreatedThreadId(threadId);\n this.#options.onThreadId?.(threadId);\n this.#rootStore.setState((s) => ({\n ...s,\n threadId,\n }));\n }\n\n const currentThreadId = this.#getCurrentThreadId();\n if (currentThreadId == null) return;\n // For client-self-created threads we defer the persistent root SSE\n // pump until after `submitRun` / `respondInput` commits the thread\n // server-side. Opening the pump's `subscription.subscribe` against\n // a not-yet-existent thread row produces a `404: Thread not found`\n // protocol error that strands lifecycle / messages events for the\n // first run. The deferred path starts the pump after dispatch\n // returns (see `#startDeferredRootPump` calls below).\n const thread = this.#ensureThread(currentThreadId, wasSelfCreated);\n const activeThreadId = currentThreadId;\n // Wait for the root subscription to be live; otherwise the\n // dispatch could resolve before we're listening for events and\n // we'd miss the terminal that ends the run.\n await this.#waitForRootPumpReady();\n\n const strategy = options?.multitaskStrategy ?? \"rollback\";\n // `wasSelfCreated` short-circuit: when this submit just minted a\n // brand-new thread id (the user clicked \"New Thread\"), the\n // strategy check shouldn't see a run on the *previous* thread as\n // a reason to enqueue. The previous run is on a thread the user\n // navigated away from; abandoning its client-side abort tracking\n // is correct (the server-side run continues independently).\n // Without this, `enqueue` would trap the new submission and\n // `submitRun` never fires for the new thread — leaving a freshly-\n // minted thread id committed to the URL but never to the server.\n const hasActiveRun =\n !wasSelfCreated &&\n this.#runAbort != null &&\n !this.#runAbort.signal.aborted;\n if (hasActiveRun && strategy === \"reject\") {\n throw new Error(\n \"submit() rejected: a run is already in flight and multitaskStrategy is 'reject'.\"\n );\n }\n if (hasActiveRun && strategy === \"enqueue\") {\n this.#enqueueSubmission(input, options);\n return;\n }\n\n // Rollback: abort the previous run before starting a new one.\n this.#runAbort?.abort();\n const abort = new AbortController();\n this.#runAbort = abort;\n\n const resumeCommand = options?.command?.resume;\n const isResume = resumeCommand !== undefined;\n\n // Optimistically clear interrupts/error and flip loading. The\n // root pump's lifecycle listener will re-flip these as the run\n // terminates.\n this.#rootStore.setState((s) => ({\n ...s,\n interrupts: [],\n interrupt: undefined,\n error: undefined,\n isLoading: true,\n }));\n\n const boundConfig = bindThreadConfig(options?.config, currentThreadId);\n // Subscribe to the next terminal *before* dispatching so a fast\n // run's terminal can't race us.\n const terminalPromise = this.#awaitNextTerminal(abort.signal);\n this.#onRunStart();\n\n let terminalSettled = false;\n let createdRunId: string | undefined;\n let pendingCompletionReason: RunExecutionReason | undefined;\n let completionNotified = false;\n const notifyCompletion = (reason: RunExecutionReason): void => {\n if (completionNotified) return;\n if (!isResume && createdRunId == null) {\n pendingCompletionReason = reason;\n return;\n }\n completionNotified = true;\n this.#onRunCompleted(reason, createdRunId);\n };\n const reportError = (error: unknown): void => {\n if (abort.signal.aborted) return;\n this.#rootStore.setState((s) => ({ ...s, error }));\n try {\n options?.onError?.(error);\n } catch {\n /* caller-supplied callback errors must not crash the submit */\n }\n };\n\n try {\n let terminal: TerminalResult | undefined;\n\n if (isResume) {\n const resumeInput = this.#buildResumeRunInput(resumeCommand);\n if (resumeInput == null) {\n throw new Error(\n \"submit({ command: { resume } }) called but no pending protocol interrupt is available.\"\n );\n }\n const commandPromise = thread.submitRun({\n input: resumeInput,\n config: boundConfig,\n metadata: (options?.metadata ?? undefined) as Record<string, unknown>,\n });\n // Defer the pump start until the dispatch HTTP response\n // lands — see the analogous block in the non-resume path\n // below for the rationale (thread row not committed\n // synchronously). For a resume the thread exists already\n // (it must, since `buildResumeRunInput()` was non-null),\n // so `#startDeferredRootPump` is typically a no-op here, but\n // we keep the same shape to avoid a future regression.\n //\n // Asymmetry with the non-resume path: we don't call\n // `#forgetSelfCreatedThreadId` because a resume implies the\n // thread already committed, so the self-created marker was\n // already cleared on the original submit. Same reason we\n // don't call `#abandonDeferredRootPump` on failure here.\n void commandPromise.then(\n () => this.#startDeferredRootPump(),\n () => {}\n );\n // Mark resolved synchronously: even if the response races and\n // the command settles after the terminal, we don't want to\n // re-target these interrupts on the next submit.\n for (const interruptId of Object.keys(resumeInput)) {\n this.#markInterruptResolved(interruptId);\n }\n const first = await Promise.race([\n terminalPromise.then((value) => ({\n type: \"terminal\" as const,\n value,\n })),\n commandPromise.then(\n () => ({ type: \"command\" as const }),\n (error) => ({ type: \"error\" as const, error })\n ),\n ]);\n if (first.type === \"error\") throw first.error;\n if (first.type === \"terminal\") {\n terminal = first.value;\n terminalSettled = true;\n // Stale command response — surface as error only if it\n // arrives with a real failure (not just our own abort).\n void commandPromise.catch((error) => {\n if (!terminalSettled) reportError(error);\n });\n }\n } else {\n const commandPromise = thread.submitRun({\n input: input ?? null,\n config: boundConfig,\n metadata: (options?.metadata ?? undefined) as Record<string, unknown>,\n forkFrom: options?.forkFrom,\n multitaskStrategy:\n options?.multitaskStrategy === \"enqueue\"\n ? \"enqueue\"\n : options?.multitaskStrategy,\n });\n // Start the deferred root pump *after* the dispatch HTTP\n // response lands — that's when the thread row exists server-\n // side. Doing it synchronously here would race the response\n // and the pump's `subscription.subscribe` would 404. Same\n // reason we drop the self-created flag only after dispatch:\n // future hydrates need the thread to exist before they fetch\n // state.\n //\n // Fire-and-forget: we don't want to gate Promise.race on this,\n // and `commandPromise.catch` is already handled below. A\n // dispatch failure means there's no thread to pump anyway.\n void commandPromise.then(\n () => {\n this.#startDeferredRootPump();\n this.#forgetSelfCreatedThreadId(activeThreadId);\n },\n () => {\n // Dispatch failed. Without abandoning, `#rootPumpDeferred`\n // stays armed and `selfCreatedThreadIds` still holds this\n // id — a retry submit would see `wasSelfCreated=false`\n // (currentThreadId is no longer null), `#ensureThread`\n // would early-return because `#thread != null`, and the\n // root pump would never start. Tear down so the next\n // submit re-runs `#ensureThread` from scratch.\n if (wasSelfCreated) {\n this.#abandonDeferredRootPump();\n this.#forgetSelfCreatedThreadId(activeThreadId);\n }\n }\n );\n const notifyCreated = (result: { run_id?: unknown }) => {\n if (typeof result.run_id !== \"string\") return;\n createdRunId = result.run_id;\n this.#onRunCreated(createdRunId);\n if (pendingCompletionReason != null) {\n notifyCompletion(pendingCompletionReason);\n }\n };\n const first = await Promise.race([\n terminalPromise.then((value) => ({\n type: \"terminal\" as const,\n value,\n })),\n commandPromise.then(\n (result) => ({ type: \"command\" as const, result }),\n (error) => ({ type: \"error\" as const, error })\n ),\n ]);\n if (first.type === \"error\") throw first.error;\n if (first.type === \"command\") {\n notifyCreated(first.result);\n } else {\n // Terminal landed first (very fast runs). Wait for the\n // dispatch response in the background so onCreated fires\n // and dispatch errors still surface.\n terminal = first.value;\n terminalSettled = true;\n void commandPromise.then(notifyCreated).catch((error) => {\n if (!terminalSettled) reportError(error);\n });\n }\n }\n\n terminal ??= await terminalPromise;\n terminalSettled = true;\n if (terminal.event === \"failed\" && !abort.signal.aborted) {\n const runError = new Error(\n terminal.error ?? \"Run failed with no error message\"\n );\n this.#rootStore.setState((s) => ({ ...s, error: runError }));\n try {\n options?.onError?.(runError);\n } catch {\n /* caller-supplied callback errors must not crash the submit */\n }\n }\n notifyCompletion(terminalReason(terminal.event));\n } catch (error) {\n reportError(error);\n } finally {\n // Always settle loading and clear our slot of the abort\n // controller. Schedule queue drain on the next macrotask so any\n // late state updates from this run finish flushing first.\n this.#rootStore.setState((s) => ({ ...s, isLoading: false }));\n if (this.#runAbort === abort) this.#runAbort = undefined;\n this.#onRunEnd();\n setTimeout(() => this.#drainQueue(), 0);\n }\n }\n\n /**\n * Abort the current run (if any) and force `isLoading=false`.\n *\n * Does NOT issue a server-side cancel — that lands with roadmap\n * A0.3. Today this is a client-side stop only: subsequent events\n * for the aborted run are ignored by the controller's pump because\n * the abort signal is the same one `#awaitNextTerminal` is wired\n * to.\n */\n async stop(): Promise<void> {\n this.abortActiveRun();\n this.#rootStore.setState((s) => ({ ...s, isLoading: false }));\n }\n\n /**\n * Abort the current run without forcing the loading flag down.\n *\n * Used by {@link StreamController.dispose}: disposal already tears\n * down the root store, so flipping `isLoading` here is unnecessary\n * and would race the dispose path.\n */\n abortActiveRun(): void {\n this.#runAbort?.abort();\n this.#runAbort = undefined;\n }\n\n /**\n * Cancel a queued submission by id.\n *\n * @param id - Client-side queue entry id to remove.\n * @returns `true` when the entry was found and dropped, `false` otherwise.\n */\n async cancelQueued(id: string): Promise<boolean> {\n const current = this.#queueStore.getSnapshot();\n const next = current.filter((entry) => entry.id !== id);\n if (next.length === current.length) return false;\n this.#queueStore.setState(() => next);\n return true;\n }\n\n /**\n * Drop every queued submission. Server-side cancel arrives with A0.3.\n */\n async clearQueue(): Promise<void> {\n this.#queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n }\n\n /**\n * Append a submission to the queue without dispatching.\n *\n * The drained submission is later run via {@link #drainQueue} after\n * the active run terminates.\n */\n #enqueueSubmission(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): void {\n const entry: SubmissionQueueEntry<StateType> = {\n id: uuidv7(),\n values: (input ?? undefined) as Partial<StateType> | null | undefined,\n options: options as StreamSubmitOptions<StateType> | undefined,\n createdAt: new Date(),\n };\n this.#queueStore.setState((current) => [...current, entry]);\n }\n\n /**\n * Drain the head of the queue if no run is active.\n *\n * Called from the `finally` block of `submit()` on the next\n * macrotask (so the just-finished run's state flushes first).\n * Strips the strategy off the dequeued options to prevent infinite\n * re-enqueueing.\n */\n #drainQueue(): void {\n if (this.#getDisposed()) return;\n if (this.#runAbort != null && !this.#runAbort.signal.aborted) return;\n const current = this.#queueStore.getSnapshot();\n if (current.length === 0) return;\n const [next, ...rest] = current;\n this.#queueStore.setState(() => rest);\n const nextOptions: StreamSubmitOptions<StateType, ConfigurableType> = {\n ...((next.options ?? {}) as StreamSubmitOptions<\n StateType,\n ConfigurableType\n >),\n multitaskStrategy: undefined,\n };\n void this.submit(next.values, nextOptions).catch(() => {\n /* submit() already routes errors through the per-submit onError\n * hook and the root store; swallow here so a failing drain does\n * not surface as an unhandled rejection. */\n });\n }\n}\n\n/**\n * Merge `thread_id` into a user-supplied `config.configurable` blob.\n *\n * The platform expects `config.configurable.thread_id` on every run\n * dispatch; we set it last so user-supplied values can't accidentally\n * override the active thread id (which would route the run to a\n * different thread).\n */\nfunction bindThreadConfig(\n config: unknown,\n threadId: string\n): Record<string, unknown> {\n const base =\n config != null && typeof config === \"object\"\n ? (config as Record<string, unknown>)\n : {};\n const configurable =\n base.configurable != null && typeof base.configurable === \"object\"\n ? (base.configurable as Record<string, unknown>)\n : {};\n return {\n ...base,\n configurable: {\n ...configurable,\n thread_id: threadId,\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"submit-coordinator.cjs","names":["#options","#rootStore","#queueStore","#getDisposed","#getCurrentThreadId","#setCurrentThreadId","#rememberSelfCreatedThreadId","#forgetSelfCreatedThreadId","#hydrate","#ensureThread","#startDeferredRootPump","#abandonDeferredRootPump","#waitForRootPumpReady","#awaitNextTerminal","#buildResumeRunInput","#markInterruptResolved","#onSubmitStart","#onRunStart","#onRunCreated","#onRunCompleted","#onRunEnd","#runAbort","#enqueueSubmission","#drainQueue"],"sources":["../../src/stream/submit-coordinator.ts"],"sourcesContent":["/**\n * Owns the run-submission lifecycle for a single\n * {@link StreamController}.\n *\n * # What this module is\n *\n * The {@link SubmitCoordinator} is the piece of the controller that\n * dispatches runs (`submit()`), enforces multitask strategies, queues\n * deferred submissions, races dispatch against terminal lifecycle\n * events, and surfaces errors back through the per-submit `onError`\n * callback and the root snapshot.\n *\n * Conceptually a submit looks like:\n *\n * 1. Optionally rebind to a different thread (`options.threadId`).\n * 2. Mint a thread id if one isn't bound yet.\n * 3. Wait for the controller's root pump to be ready (so the\n * transport is subscribed before the run is dispatched —\n * otherwise we could miss replayed events).\n * 4. Apply the {@link StreamSubmitOptions.multitaskStrategy} to\n * decide whether to abort, enqueue, reject, or proceed.\n * 5. Race the dispatch promise (`thread.submitRun()` or\n * `thread.submitRun()` for resumes) against the next root\n * terminal lifecycle event.\n * 6. Settle the resulting state (loading flag, error slot) and\n * drain the next queued submission, if any.\n *\n * # Why it lives in its own class\n *\n * The submit lifecycle is the most state-heavy part of the\n * controller — six promises, an abort controller, a queue, a\n * terminal-vs-command race, and bidirectional callback wiring with\n * the controller. Splitting it out keeps `controller.ts` focused on\n * subscription / projection wiring while letting the submit logic\n * evolve independently.\n *\n * # Why we race \"command\" against \"terminal\"\n *\n * For fast runs, the server's terminal lifecycle event can arrive\n * *before* the dispatch HTTP response has resolved. Racing the two\n * lets us detect terminal early and not block waiting for a now-stale\n * dispatch response. The dispatch response is still consumed (via\n * `.then(notifyCreated).catch(reportError)`) so `onCreated` still\n * fires and dispatch errors still surface through `onError`.\n *\n * # Queue semantics (`multitaskStrategy: \"enqueue\"`)\n *\n * When a run is already in flight, an `\"enqueue\"` submit is recorded\n * into {@link queueStore} and the call returns immediately. After the\n * active run terminates, `#drainQueue` schedules the head of the\n * queue as a fresh submit on the next macrotask. Each drained\n * submission has its own `multitaskStrategy` cleared so it doesn't\n * recursively re-enqueue.\n *\n * @see StreamController - The owner; injects every collaborator dep.\n */\nimport { v7 as uuidv7 } from \"uuid\";\nimport type { ThreadStream } from \"../client/stream/index.js\";\nimport { StreamStore } from \"./store.js\";\nimport type {\n RootSnapshot,\n RunExecutionReason,\n StreamControllerOptions,\n StreamSubmitOptions,\n} from \"./types.js\";\n\n/**\n * Result of awaiting the next root terminal lifecycle event. Mirrors\n * the three terminal lifecycle states the protocol surfaces, plus a\n * synthetic `\"aborted\"` for client-side cancellation.\n */\ntype TerminalResult = {\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n};\n\nfunction terminalReason(event: TerminalResult[\"event\"]): RunExecutionReason {\n if (event === \"completed\") return \"success\";\n if (event === \"failed\") return \"error\";\n if (event === \"interrupted\") return \"interrupt\";\n return \"stopped\";\n}\n\n/**\n * Queued submission entry mirrored from the server-side run queue.\n *\n * Surfaces the deferred submission to UI consumers via\n * {@link StreamController.queueStore}.\n */\nexport interface SubmissionQueueEntry<\n StateType extends object = Record<string, unknown>,\n> {\n /** Stable id minted on enqueue (uuidv7 — sortable by creation time). */\n readonly id: string;\n /** Original submit input, narrowed to the partial state shape. */\n readonly values: Partial<StateType> | null | undefined;\n /** Original submit options, minus the strategy slot which is reset on drain. */\n readonly options?: StreamSubmitOptions<StateType>;\n /** Wall-clock timestamp at enqueue. */\n readonly createdAt: Date;\n}\n\n/**\n * Read-only snapshot of the queue. The queue store hands this out\n * directly; consumers must not mutate the array.\n */\nexport type SubmissionQueueSnapshot<\n StateType extends object = Record<string, unknown>,\n> = ReadonlyArray<SubmissionQueueEntry<StateType>>;\n\n/**\n * Frozen empty queue value used as the initial / cleared snapshot.\n *\n * Reusing one frozen reference keeps store identity stable across\n * empty resets, so React's `useSyncExternalStore` doesn't think the\n * queue changed when it actually didn't.\n */\nexport const EMPTY_QUEUE: SubmissionQueueSnapshot<never> = Object.freeze([]);\n\n/**\n * Coordinates one controller's run-submission lifecycle.\n *\n * The constructor takes a bag of callbacks rather than a reference to\n * the parent {@link StreamController} on purpose:\n *\n * - It keeps the dependency surface explicit and testable — every\n * piece of controller state the submit lifecycle touches is one\n * of these closures.\n * - It avoids a cyclic dependency between controller and coordinator.\n * - Tests can construct one with stub callbacks and assert behavior\n * without mocking the entire controller.\n *\n * @typeParam StateType - Root state shape.\n * @typeParam InterruptType - Root interrupt payload shape.\n * @typeParam ConfigurableType - `config.configurable` shape accepted\n * by submit (usually `Record<string, unknown>`).\n */\nexport class SubmitCoordinator<\n StateType extends object = Record<string, unknown>,\n InterruptType = unknown,\n ConfigurableType extends object = Record<string, unknown>,\n> {\n /** Controller-level options forwarded into `submitRun` / callbacks. */\n readonly #options: StreamControllerOptions<StateType>;\n /** Root snapshot store; written for `isLoading`, `error`, `interrupts`. */\n readonly #rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n /** Pending submissions awaiting the active run to terminate. */\n readonly #queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n /** Probes the controller's `disposed` flag from deferred work. */\n readonly #getDisposed: () => boolean;\n /** Reads the controller's currently-bound thread id. */\n readonly #getCurrentThreadId: () => string | null;\n /** Updates the controller's thread id (used when minting a new id). */\n readonly #setCurrentThreadId: (threadId: string | null) => void;\n /** Records a thread id we created client-side so hydrate can skip a 404 round-trip. */\n readonly #rememberSelfCreatedThreadId: (threadId: string) => void;\n /** Drops a thread id from the self-created set once it's committed server-side. */\n readonly #forgetSelfCreatedThreadId: (threadId: string) => void;\n /** Triggers a hydrate on the controller (used by `options.threadId` rebinds). */\n readonly #hydrate: (threadId?: string | null) => Promise<void>;\n /** Lazily creates / returns the active {@link ThreadStream}. */\n readonly #ensureThread: (\n threadId: string,\n deferRootPump?: boolean\n ) => ThreadStream;\n /** Starts the previously-deferred root pump after a self-created thread commits. */\n readonly #startDeferredRootPump: () => void;\n /** Abandons a deferred root pump after a self-created dispatch fails. */\n readonly #abandonDeferredRootPump: () => void;\n /** Resolves once the controller's root subscription pump is up. */\n readonly #waitForRootPumpReady: () => Promise<void> | undefined;\n /** Resolves on the next root terminal lifecycle (or on abort). */\n readonly #awaitNextTerminal: (signal: AbortSignal) => Promise<TerminalResult>;\n /** Builds interrupt-id keyed `run.start` input from `command.resume`. */\n readonly #buildResumeRunInput: (\n resume: unknown\n ) => Record<string, unknown> | null;\n /** Marks an interrupt id as resolved so it isn't re-targeted. */\n readonly #markInterruptResolved: (interruptId: string) => void;\n /** Called once at the start of every {@link submit} invocation. */\n readonly #onSubmitStart: () => void;\n /** Marks that a local run dispatch is now active. */\n readonly #onRunStart: () => void;\n /** Records a server-accepted local run id and fires `onCreated`. */\n readonly #onRunCreated: (runId: string) => void;\n /** Fires `onCompleted` for the local run lifecycle. */\n readonly #onRunCompleted: (\n reason: RunExecutionReason,\n runId?: string\n ) => void;\n /** Marks the local run dispatch lifecycle as settled. */\n readonly #onRunEnd: () => void;\n\n /**\n * Active submission's abort controller. `undefined` between submits.\n *\n * Used both for `multitaskStrategy: \"rollback\"` (abort the previous\n * controller's signal) and `stop()` (abort the current one without\n * starting a new one).\n */\n #runAbort: AbortController | undefined;\n\n constructor(params: {\n options: StreamControllerOptions<StateType>;\n rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n getDisposed: () => boolean;\n getCurrentThreadId: () => string | null;\n setCurrentThreadId: (threadId: string | null) => void;\n rememberSelfCreatedThreadId: (threadId: string) => void;\n forgetSelfCreatedThreadId: (threadId: string) => void;\n hydrate: (threadId?: string | null) => Promise<void>;\n ensureThread: (threadId: string, deferRootPump?: boolean) => ThreadStream;\n startDeferredRootPump: () => void;\n abandonDeferredRootPump: () => void;\n waitForRootPumpReady: () => Promise<void> | undefined;\n awaitNextTerminal: (signal: AbortSignal) => Promise<TerminalResult>;\n buildResumeRunInput: (resume: unknown) => Record<string, unknown> | null;\n markInterruptResolved: (interruptId: string) => void;\n onSubmitStart?: () => void;\n onRunStart?: () => void;\n onRunCreated?: (runId: string) => void;\n onRunCompleted?: (reason: RunExecutionReason, runId?: string) => void;\n onRunEnd?: () => void;\n }) {\n this.#options = params.options;\n this.#rootStore = params.rootStore;\n this.#queueStore = params.queueStore;\n this.#getDisposed = params.getDisposed;\n this.#getCurrentThreadId = params.getCurrentThreadId;\n this.#setCurrentThreadId = params.setCurrentThreadId;\n this.#rememberSelfCreatedThreadId = params.rememberSelfCreatedThreadId;\n this.#forgetSelfCreatedThreadId = params.forgetSelfCreatedThreadId;\n this.#hydrate = params.hydrate;\n this.#ensureThread = params.ensureThread;\n this.#startDeferredRootPump = params.startDeferredRootPump;\n this.#abandonDeferredRootPump = params.abandonDeferredRootPump;\n this.#waitForRootPumpReady = params.waitForRootPumpReady;\n this.#awaitNextTerminal = params.awaitNextTerminal;\n this.#buildResumeRunInput = params.buildResumeRunInput;\n this.#markInterruptResolved = params.markInterruptResolved;\n this.#onSubmitStart = params.onSubmitStart ?? (() => undefined);\n this.#onRunStart = params.onRunStart ?? (() => undefined);\n this.#onRunCreated = params.onRunCreated ?? (() => undefined);\n this.#onRunCompleted = params.onRunCompleted ?? (() => undefined);\n this.#onRunEnd = params.onRunEnd ?? (() => undefined);\n }\n\n /**\n * Submit input or a resume command to the active thread.\n *\n * Honours {@link StreamSubmitOptions.multitaskStrategy}:\n *\n * - `\"rollback\"` (default) — aborts any in-flight run and\n * dispatches immediately.\n * - `\"reject\"` — throws synchronously when a run is\n * already in flight.\n * - `\"enqueue\"` — defers via {@link #enqueueSubmission};\n * the call returns without dispatching.\n * - `\"interrupt\"` — falls through to the default path\n *\n * Errors are routed through both the per-submit `onError` callback\n * and `rootStore.error`. Aborts (controller dispose / rollback) are\n * silently dropped.\n *\n * @param input - Input payload, or `null`/`undefined` for no input\n * (typical for resume commands).\n * @param options - Per-submit options (config, metadata, callbacks,\n * strategy, etc).\n */\n async submit(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): Promise<void> {\n if (this.#getDisposed()) return;\n this.#onSubmitStart();\n\n // Per-submit thread override: rebind first so the rest of the\n // submit operates against the new thread.\n const overrideThreadId = options?.threadId;\n if (\n overrideThreadId !== undefined &&\n overrideThreadId !== this.#getCurrentThreadId()\n ) {\n await this.#hydrate(overrideThreadId);\n }\n\n // Self-created thread id path: mint client-side so the controller\n // (and Suspense boundaries) get a stable id even before the run\n // is dispatched.\n const wasSelfCreated = this.#getCurrentThreadId() == null;\n if (wasSelfCreated) {\n const threadId = uuidv7();\n this.#setCurrentThreadId(threadId);\n this.#rememberSelfCreatedThreadId(threadId);\n this.#options.onThreadId?.(threadId);\n this.#rootStore.setState((s) => ({\n ...s,\n threadId,\n }));\n }\n\n const currentThreadId = this.#getCurrentThreadId();\n if (currentThreadId == null) return;\n // For client-self-created threads we defer the persistent root SSE\n // pump until after `submitRun` / `respondInput` commits the thread\n // server-side. Opening the pump's `subscription.subscribe` against\n // a not-yet-existent thread row produces a `404: Thread not found`\n // protocol error that strands lifecycle / messages events for the\n // first run. The deferred path starts the pump after dispatch\n // returns (see `#startDeferredRootPump` calls below).\n const thread = this.#ensureThread(currentThreadId, wasSelfCreated);\n const activeThreadId = currentThreadId;\n // Wait for the root subscription to be live; otherwise the\n // dispatch could resolve before we're listening for events and\n // we'd miss the terminal that ends the run.\n await this.#waitForRootPumpReady();\n\n const strategy = options?.multitaskStrategy ?? \"rollback\";\n // `wasSelfCreated` short-circuit: when this submit just minted a\n // brand-new thread id (the user clicked \"New Thread\"), the\n // strategy check shouldn't see a run on the *previous* thread as\n // a reason to enqueue. The previous run is on a thread the user\n // navigated away from; abandoning its client-side abort tracking\n // is correct (the server-side run continues independently).\n // Without this, `enqueue` would trap the new submission and\n // `submitRun` never fires for the new thread — leaving a freshly-\n // minted thread id committed to the URL but never to the server.\n const hasActiveRun =\n !wasSelfCreated &&\n this.#runAbort != null &&\n !this.#runAbort.signal.aborted;\n if (hasActiveRun && strategy === \"reject\") {\n throw new Error(\n \"submit() rejected: a run is already in flight and multitaskStrategy is 'reject'.\"\n );\n }\n if (hasActiveRun && strategy === \"enqueue\") {\n this.#enqueueSubmission(input, options);\n return;\n }\n\n // Rollback: abort the previous run before starting a new one.\n this.#runAbort?.abort();\n const abort = new AbortController();\n this.#runAbort = abort;\n\n const resumeCommand = options?.command?.resume;\n const isResume = resumeCommand !== undefined;\n\n // Optimistically clear interrupts/error and flip loading. The\n // root pump's lifecycle listener will re-flip these as the run\n // terminates.\n this.#rootStore.setState((s) => ({\n ...s,\n interrupts: [],\n interrupt: undefined,\n error: undefined,\n isLoading: true,\n }));\n\n const boundConfig = bindThreadConfig(options?.config, currentThreadId);\n // Subscribe to the next terminal *before* dispatching so a fast\n // run's terminal can't race us.\n const terminalPromise = this.#awaitNextTerminal(abort.signal);\n this.#onRunStart();\n\n let terminalSettled = false;\n let createdRunId: string | undefined;\n let pendingCompletionReason: RunExecutionReason | undefined;\n let completionNotified = false;\n const notifyCompletion = (reason: RunExecutionReason): void => {\n if (completionNotified) return;\n if (!isResume && createdRunId == null) {\n pendingCompletionReason = reason;\n return;\n }\n completionNotified = true;\n this.#onRunCompleted(reason, createdRunId);\n };\n const reportError = (error: unknown): void => {\n if (abort.signal.aborted) return;\n this.#rootStore.setState((s) => ({ ...s, error }));\n try {\n options?.onError?.(error);\n } catch {\n /* caller-supplied callback errors must not crash the submit */\n }\n };\n\n try {\n let terminal: TerminalResult | undefined;\n\n if (isResume) {\n const resumeInput = this.#buildResumeRunInput(resumeCommand);\n if (resumeInput == null) {\n throw new Error(\n \"submit({ command: { resume } }) called but no pending protocol interrupt is available.\"\n );\n }\n const commandPromise = thread.submitRun({\n input: resumeInput,\n config: boundConfig,\n metadata: (options?.metadata ?? undefined) as Record<string, unknown>,\n });\n // Defer the pump start until the dispatch HTTP response\n // lands — see the analogous block in the non-resume path\n // below for the rationale (thread row not committed\n // synchronously). For a resume the thread exists already\n // (it must, since `buildResumeRunInput()` was non-null),\n // so `#startDeferredRootPump` is typically a no-op here, but\n // we keep the same shape to avoid a future regression.\n //\n // Asymmetry with the non-resume path: we don't call\n // `#forgetSelfCreatedThreadId` because a resume implies the\n // thread already committed, so the self-created marker was\n // already cleared on the original submit. Same reason we\n // don't call `#abandonDeferredRootPump` on failure here.\n void commandPromise.then(\n () => this.#startDeferredRootPump(),\n () => {}\n );\n // Mark resolved synchronously: even if the response races and\n // the command settles after the terminal, we don't want to\n // re-target these interrupts on the next submit.\n for (const interruptId of Object.keys(resumeInput)) {\n this.#markInterruptResolved(interruptId);\n }\n const first = await Promise.race([\n terminalPromise.then((value) => ({\n type: \"terminal\" as const,\n value,\n })),\n commandPromise.then(\n () => ({ type: \"command\" as const }),\n (error) => ({ type: \"error\" as const, error })\n ),\n ]);\n if (first.type === \"error\") throw first.error;\n if (first.type === \"terminal\") {\n terminal = first.value;\n terminalSettled = true;\n // Stale command response — surface as error only if it\n // arrives with a real failure (not just our own abort).\n void commandPromise.catch((error) => {\n if (!terminalSettled) reportError(error);\n });\n }\n } else {\n const commandPromise = thread.submitRun({\n input: input ?? null,\n config: boundConfig,\n metadata: (options?.metadata ?? undefined) as Record<string, unknown>,\n forkFrom: options?.forkFrom,\n multitaskStrategy:\n options?.multitaskStrategy === \"enqueue\"\n ? \"enqueue\"\n : options?.multitaskStrategy,\n });\n // Start the deferred root pump *after* the dispatch HTTP\n // response lands — that's when the thread row exists server-\n // side. Doing it synchronously here would race the response\n // and the pump's `subscription.subscribe` would 404. Same\n // reason we drop the self-created flag only after dispatch:\n // future hydrates need the thread to exist before they fetch\n // state.\n //\n // Fire-and-forget: we don't want to gate Promise.race on this,\n // and `commandPromise.catch` is already handled below. A\n // dispatch failure means there's no thread to pump anyway.\n void commandPromise.then(\n () => {\n this.#startDeferredRootPump();\n this.#forgetSelfCreatedThreadId(activeThreadId);\n },\n () => {\n // Dispatch failed. Without abandoning, `#rootPumpDeferred`\n // stays armed and `selfCreatedThreadIds` still holds this\n // id — a retry submit would see `wasSelfCreated=false`\n // (currentThreadId is no longer null), `#ensureThread`\n // would early-return because `#thread != null`, and the\n // root pump would never start. Tear down so the next\n // submit re-runs `#ensureThread` from scratch.\n if (wasSelfCreated) {\n this.#abandonDeferredRootPump();\n this.#forgetSelfCreatedThreadId(activeThreadId);\n }\n }\n );\n const notifyCreated = (result: { run_id?: unknown }) => {\n if (typeof result.run_id !== \"string\") return;\n createdRunId = result.run_id;\n this.#onRunCreated(createdRunId);\n if (pendingCompletionReason != null) {\n notifyCompletion(pendingCompletionReason);\n }\n };\n const first = await Promise.race([\n terminalPromise.then((value) => ({\n type: \"terminal\" as const,\n value,\n })),\n commandPromise.then(\n (result) => ({ type: \"command\" as const, result }),\n (error) => ({ type: \"error\" as const, error })\n ),\n ]);\n if (first.type === \"error\") throw first.error;\n if (first.type === \"command\") {\n notifyCreated(first.result);\n } else {\n // Terminal landed first (very fast runs). Wait for the\n // dispatch response in the background so onCreated fires\n // and dispatch errors still surface.\n terminal = first.value;\n terminalSettled = true;\n void commandPromise.then(notifyCreated).catch((error) => {\n if (!terminalSettled) reportError(error);\n });\n }\n }\n\n terminal ??= await terminalPromise;\n terminalSettled = true;\n if (terminal.event === \"failed\" && !abort.signal.aborted) {\n const runError = new Error(\n terminal.error ?? \"Run failed with no error message\"\n );\n this.#rootStore.setState((s) => ({ ...s, error: runError }));\n try {\n options?.onError?.(runError);\n } catch {\n /* caller-supplied callback errors must not crash the submit */\n }\n }\n notifyCompletion(terminalReason(terminal.event));\n } catch (error) {\n reportError(error);\n } finally {\n // Always settle loading and clear our slot of the abort\n // controller. Schedule queue drain on the next macrotask so any\n // late state updates from this run finish flushing first.\n this.#rootStore.setState((s) => ({ ...s, isLoading: false }));\n if (this.#runAbort === abort) this.#runAbort = undefined;\n this.#onRunEnd();\n setTimeout(() => this.#drainQueue(), 0);\n }\n }\n\n /**\n * Abort the current run (if any) and force `isLoading=false`.\n *\n * Client-side only — server-side cancel is handled by\n * {@link StreamController.stop} before this is invoked.\n */\n async stop(): Promise<void> {\n this.abortActiveRun();\n this.#rootStore.setState((s) => ({ ...s, isLoading: false }));\n }\n\n /**\n * Abort the current run without forcing the loading flag down.\n *\n * Used by {@link StreamController.dispose}: disposal already tears\n * down the root store, so flipping `isLoading` here is unnecessary\n * and would race the dispose path.\n */\n abortActiveRun(): void {\n this.#runAbort?.abort();\n this.#runAbort = undefined;\n }\n\n /**\n * Cancel a queued submission by id.\n *\n * @param id - Client-side queue entry id to remove.\n * @returns `true` when the entry was found and dropped, `false` otherwise.\n */\n async cancelQueued(id: string): Promise<boolean> {\n const current = this.#queueStore.getSnapshot();\n const next = current.filter((entry) => entry.id !== id);\n if (next.length === current.length) return false;\n this.#queueStore.setState(() => next);\n return true;\n }\n\n /**\n * Drop every queued submission. Server-side cancel arrives with A0.3.\n */\n async clearQueue(): Promise<void> {\n this.#queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n }\n\n /**\n * Append a submission to the queue without dispatching.\n *\n * The drained submission is later run via {@link #drainQueue} after\n * the active run terminates.\n */\n #enqueueSubmission(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): void {\n const entry: SubmissionQueueEntry<StateType> = {\n id: uuidv7(),\n values: (input ?? undefined) as Partial<StateType> | null | undefined,\n options: options as StreamSubmitOptions<StateType> | undefined,\n createdAt: new Date(),\n };\n this.#queueStore.setState((current) => [...current, entry]);\n }\n\n /**\n * Drain the head of the queue if no run is active.\n *\n * Called from the `finally` block of `submit()` on the next\n * macrotask (so the just-finished run's state flushes first).\n * Strips the strategy off the dequeued options to prevent infinite\n * re-enqueueing.\n */\n #drainQueue(): void {\n if (this.#getDisposed()) return;\n if (this.#runAbort != null && !this.#runAbort.signal.aborted) return;\n const current = this.#queueStore.getSnapshot();\n if (current.length === 0) return;\n const [next, ...rest] = current;\n this.#queueStore.setState(() => rest);\n const nextOptions: StreamSubmitOptions<StateType, ConfigurableType> = {\n ...((next.options ?? {}) as StreamSubmitOptions<\n StateType,\n ConfigurableType\n >),\n multitaskStrategy: undefined,\n };\n void this.submit(next.values, nextOptions).catch(() => {\n /* submit() already routes errors through the per-submit onError\n * hook and the root store; swallow here so a failing drain does\n * not surface as an unhandled rejection. */\n });\n }\n}\n\n/**\n * Merge `thread_id` into a user-supplied `config.configurable` blob.\n *\n * The platform expects `config.configurable.thread_id` on every run\n * dispatch; we set it last so user-supplied values can't accidentally\n * override the active thread id (which would route the run to a\n * different thread).\n */\nfunction bindThreadConfig(\n config: unknown,\n threadId: string\n): Record<string, unknown> {\n const base =\n config != null && typeof config === \"object\"\n ? (config as Record<string, unknown>)\n : {};\n const configurable =\n base.configurable != null && typeof base.configurable === \"object\"\n ? (base.configurable as Record<string, unknown>)\n : {};\n return {\n ...base,\n configurable: {\n ...configurable,\n thread_id: threadId,\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,SAAS,eAAe,OAAoD;AAC1E,KAAI,UAAU,YAAa,QAAO;AAClC,KAAI,UAAU,SAAU,QAAO;AAC/B,KAAI,UAAU,cAAe,QAAO;AACpC,QAAO;;;;;;;;;AAqCT,MAAa,cAA8C,OAAO,OAAO,EAAE,CAAC;;;;;;;;;;;;;;;;;;;AAoB5E,IAAa,oBAAb,MAIE;;CAEA;;CAEA;;CAEA;;CAEA;;CAEA;;CAEA;;CAEA;;CAEA;;CAEA;;CAEA;;CAKA;;CAEA;;CAEA;;CAEA;;CAEA;;CAIA;;CAEA;;CAEA;;CAEA;;CAEA;;CAKA;;;;;;;;CASA;CAEA,YAAY,QAsBT;AACD,QAAA,UAAgB,OAAO;AACvB,QAAA,YAAkB,OAAO;AACzB,QAAA,aAAmB,OAAO;AAC1B,QAAA,cAAoB,OAAO;AAC3B,QAAA,qBAA2B,OAAO;AAClC,QAAA,qBAA2B,OAAO;AAClC,QAAA,8BAAoC,OAAO;AAC3C,QAAA,4BAAkC,OAAO;AACzC,QAAA,UAAgB,OAAO;AACvB,QAAA,eAAqB,OAAO;AAC5B,QAAA,wBAA8B,OAAO;AACrC,QAAA,0BAAgC,OAAO;AACvC,QAAA,uBAA6B,OAAO;AACpC,QAAA,oBAA0B,OAAO;AACjC,QAAA,sBAA4B,OAAO;AACnC,QAAA,wBAA8B,OAAO;AACrC,QAAA,gBAAsB,OAAO,wBAAwB,KAAA;AACrD,QAAA,aAAmB,OAAO,qBAAqB,KAAA;AAC/C,QAAA,eAAqB,OAAO,uBAAuB,KAAA;AACnD,QAAA,iBAAuB,OAAO,yBAAyB,KAAA;AACvD,QAAA,WAAiB,OAAO,mBAAmB,KAAA;;;;;;;;;;;;;;;;;;;;;;;;CAyB7C,MAAM,OACJ,OACA,SACe;AACf,MAAI,MAAA,aAAmB,CAAE;AACzB,QAAA,eAAqB;EAIrB,MAAM,mBAAmB,SAAS;AAClC,MACE,qBAAqB,KAAA,KACrB,qBAAqB,MAAA,oBAA0B,CAE/C,OAAM,MAAA,QAAc,iBAAiB;EAMvC,MAAM,iBAAiB,MAAA,oBAA0B,IAAI;AACrD,MAAI,gBAAgB;GAClB,MAAM,YAAA,GAAA,KAAA,KAAmB;AACzB,SAAA,mBAAyB,SAAS;AAClC,SAAA,4BAAkC,SAAS;AAC3C,SAAA,QAAc,aAAa,SAAS;AACpC,SAAA,UAAgB,UAAU,OAAO;IAC/B,GAAG;IACH;IACD,EAAE;;EAGL,MAAM,kBAAkB,MAAA,oBAA0B;AAClD,MAAI,mBAAmB,KAAM;EAQ7B,MAAM,SAAS,MAAA,aAAmB,iBAAiB,eAAe;EAClE,MAAM,iBAAiB;AAIvB,QAAM,MAAA,sBAA4B;EAElC,MAAM,WAAW,SAAS,qBAAqB;EAU/C,MAAM,eACJ,CAAC,kBACD,MAAA,YAAkB,QAClB,CAAC,MAAA,SAAe,OAAO;AACzB,MAAI,gBAAgB,aAAa,SAC/B,OAAM,IAAI,MACR,mFACD;AAEH,MAAI,gBAAgB,aAAa,WAAW;AAC1C,SAAA,kBAAwB,OAAO,QAAQ;AACvC;;AAIF,QAAA,UAAgB,OAAO;EACvB,MAAM,QAAQ,IAAI,iBAAiB;AACnC,QAAA,WAAiB;EAEjB,MAAM,gBAAgB,SAAS,SAAS;EACxC,MAAM,WAAW,kBAAkB,KAAA;AAKnC,QAAA,UAAgB,UAAU,OAAO;GAC/B,GAAG;GACH,YAAY,EAAE;GACd,WAAW,KAAA;GACX,OAAO,KAAA;GACP,WAAW;GACZ,EAAE;EAEH,MAAM,cAAc,iBAAiB,SAAS,QAAQ,gBAAgB;EAGtE,MAAM,kBAAkB,MAAA,kBAAwB,MAAM,OAAO;AAC7D,QAAA,YAAkB;EAElB,IAAI,kBAAkB;EACtB,IAAI;EACJ,IAAI;EACJ,IAAI,qBAAqB;EACzB,MAAM,oBAAoB,WAAqC;AAC7D,OAAI,mBAAoB;AACxB,OAAI,CAAC,YAAY,gBAAgB,MAAM;AACrC,8BAA0B;AAC1B;;AAEF,wBAAqB;AACrB,SAAA,eAAqB,QAAQ,aAAa;;EAE5C,MAAM,eAAe,UAAyB;AAC5C,OAAI,MAAM,OAAO,QAAS;AAC1B,SAAA,UAAgB,UAAU,OAAO;IAAE,GAAG;IAAG;IAAO,EAAE;AAClD,OAAI;AACF,aAAS,UAAU,MAAM;WACnB;;AAKV,MAAI;GACF,IAAI;AAEJ,OAAI,UAAU;IACZ,MAAM,cAAc,MAAA,oBAA0B,cAAc;AAC5D,QAAI,eAAe,KACjB,OAAM,IAAI,MACR,yFACD;IAEH,MAAM,iBAAiB,OAAO,UAAU;KACtC,OAAO;KACP,QAAQ;KACR,UAAW,SAAS,YAAY,KAAA;KACjC,CAAC;AAcG,mBAAe,WACZ,MAAA,uBAA6B,QAC7B,GACP;AAID,SAAK,MAAM,eAAe,OAAO,KAAK,YAAY,CAChD,OAAA,sBAA4B,YAAY;IAE1C,MAAM,QAAQ,MAAM,QAAQ,KAAK,CAC/B,gBAAgB,MAAM,WAAW;KAC/B,MAAM;KACN;KACD,EAAE,EACH,eAAe,YACN,EAAE,MAAM,WAAoB,IAClC,WAAW;KAAE,MAAM;KAAkB;KAAO,EAC9C,CACF,CAAC;AACF,QAAI,MAAM,SAAS,QAAS,OAAM,MAAM;AACxC,QAAI,MAAM,SAAS,YAAY;AAC7B,gBAAW,MAAM;AACjB,uBAAkB;AAGb,oBAAe,OAAO,UAAU;AACnC,UAAI,CAAC,gBAAiB,aAAY,MAAM;OACxC;;UAEC;IACL,MAAM,iBAAiB,OAAO,UAAU;KACtC,OAAO,SAAS;KAChB,QAAQ;KACR,UAAW,SAAS,YAAY,KAAA;KAChC,UAAU,SAAS;KACnB,mBACE,SAAS,sBAAsB,YAC3B,YACA,SAAS;KAChB,CAAC;AAYG,mBAAe,WACZ;AACJ,WAAA,uBAA6B;AAC7B,WAAA,0BAAgC,eAAe;aAE3C;AAQJ,SAAI,gBAAgB;AAClB,YAAA,yBAA+B;AAC/B,YAAA,0BAAgC,eAAe;;MAGpD;IACD,MAAM,iBAAiB,WAAiC;AACtD,SAAI,OAAO,OAAO,WAAW,SAAU;AACvC,oBAAe,OAAO;AACtB,WAAA,aAAmB,aAAa;AAChC,SAAI,2BAA2B,KAC7B,kBAAiB,wBAAwB;;IAG7C,MAAM,QAAQ,MAAM,QAAQ,KAAK,CAC/B,gBAAgB,MAAM,WAAW;KAC/B,MAAM;KACN;KACD,EAAE,EACH,eAAe,MACZ,YAAY;KAAE,MAAM;KAAoB;KAAQ,IAChD,WAAW;KAAE,MAAM;KAAkB;KAAO,EAC9C,CACF,CAAC;AACF,QAAI,MAAM,SAAS,QAAS,OAAM,MAAM;AACxC,QAAI,MAAM,SAAS,UACjB,eAAc,MAAM,OAAO;SACtB;AAIL,gBAAW,MAAM;AACjB,uBAAkB;AACb,oBAAe,KAAK,cAAc,CAAC,OAAO,UAAU;AACvD,UAAI,CAAC,gBAAiB,aAAY,MAAM;OACxC;;;AAIN,gBAAa,MAAM;AACnB,qBAAkB;AAClB,OAAI,SAAS,UAAU,YAAY,CAAC,MAAM,OAAO,SAAS;IACxD,MAAM,WAAW,IAAI,MACnB,SAAS,SAAS,mCACnB;AACD,UAAA,UAAgB,UAAU,OAAO;KAAE,GAAG;KAAG,OAAO;KAAU,EAAE;AAC5D,QAAI;AACF,cAAS,UAAU,SAAS;YACtB;;AAIV,oBAAiB,eAAe,SAAS,MAAM,CAAC;WACzC,OAAO;AACd,eAAY,MAAM;YACV;AAIR,SAAA,UAAgB,UAAU,OAAO;IAAE,GAAG;IAAG,WAAW;IAAO,EAAE;AAC7D,OAAI,MAAA,aAAmB,MAAO,OAAA,WAAiB,KAAA;AAC/C,SAAA,UAAgB;AAChB,oBAAiB,MAAA,YAAkB,EAAE,EAAE;;;;;;;;;CAU3C,MAAM,OAAsB;AAC1B,OAAK,gBAAgB;AACrB,QAAA,UAAgB,UAAU,OAAO;GAAE,GAAG;GAAG,WAAW;GAAO,EAAE;;;;;;;;;CAU/D,iBAAuB;AACrB,QAAA,UAAgB,OAAO;AACvB,QAAA,WAAiB,KAAA;;;;;;;;CASnB,MAAM,aAAa,IAA8B;EAC/C,MAAM,UAAU,MAAA,WAAiB,aAAa;EAC9C,MAAM,OAAO,QAAQ,QAAQ,UAAU,MAAM,OAAO,GAAG;AACvD,MAAI,KAAK,WAAW,QAAQ,OAAQ,QAAO;AAC3C,QAAA,WAAiB,eAAe,KAAK;AACrC,SAAO;;;;;CAMT,MAAM,aAA4B;AAChC,QAAA,WAAiB,eACT,YACP;;;;;;;;CASH,mBACE,OACA,SACM;EACN,MAAM,QAAyC;GAC7C,KAAA,GAAA,KAAA,KAAY;GACZ,QAAS,SAAS,KAAA;GACT;GACT,2BAAW,IAAI,MAAM;GACtB;AACD,QAAA,WAAiB,UAAU,YAAY,CAAC,GAAG,SAAS,MAAM,CAAC;;;;;;;;;;CAW7D,cAAoB;AAClB,MAAI,MAAA,aAAmB,CAAE;AACzB,MAAI,MAAA,YAAkB,QAAQ,CAAC,MAAA,SAAe,OAAO,QAAS;EAC9D,MAAM,UAAU,MAAA,WAAiB,aAAa;AAC9C,MAAI,QAAQ,WAAW,EAAG;EAC1B,MAAM,CAAC,MAAM,GAAG,QAAQ;AACxB,QAAA,WAAiB,eAAe,KAAK;EACrC,MAAM,cAAgE;GACpE,GAAK,KAAK,WAAW,EAAE;GAIvB,mBAAmB,KAAA;GACpB;AACI,OAAK,OAAO,KAAK,QAAQ,YAAY,CAAC,YAAY,GAIrD;;;;;;;;;;;AAYN,SAAS,iBACP,QACA,UACyB;CACzB,MAAM,OACJ,UAAU,QAAQ,OAAO,WAAW,WAC/B,SACD,EAAE;CACR,MAAM,eACJ,KAAK,gBAAgB,QAAQ,OAAO,KAAK,iBAAiB,WACrD,KAAK,eACN,EAAE;AACR,QAAO;EACL,GAAG;EACH,cAAc;GACZ,GAAG;GACH,WAAW;GACZ;EACF"}