@langchain/langgraph
Version:
1 lines • 91.3 kB
Source Map (JSON)
{"version":3,"file":"loop.cjs","names":["BaseMessage","BaseCache","isCommand","increment","createDuplexStream","CONFIG_KEY_STREAM","CONFIG_KEY_SCRATCHPAD","patchConfigurable","CONFIG_KEY_CHECKPOINT_NS","CONFIG_KEY_READ","CONFIG_KEY_CHECKPOINT_MAP","CONFIG_KEY_REPLAY_STATE","CONFIG_KEY_RESUMING","RESUME","channelsFromCheckpoint","AsyncBatchedStore","WRITES_IDX_MAP","_isSend","sanitizeUntrackedValuesInSend","isDeltaChannel","CONFIG_KEY_CHECKPOINT_ID","INTERRUPT","gatherIteratorSync","prefixGenerator","mapOutputUpdates","mapDebugTaskResults","GraphInterrupt","_applyWrites","_isOverwriteValue","gatherIterator","mapOutputValues","shouldInterrupt","_prepareNextTasks","mapDebugCheckpoint","mapDebugTasks","readChannels","isGraphInterrupt","#interruptStreamNamespace","_prepareSingleTask","PUSH","_prepareNodeErrorHandlerTask","ERROR","NULL_TASK_ID","isXXH3","CONFIG_KEY_RESUME_MAP","mapCommand","EmptyInputError","INPUT","mapInput","ReplayState","getNewChannelVersions","deltaChannelsToSnapshot","createCheckpoint","exitDeltaTaskId"],"sources":["../../src/pregel/loop.ts"],"sourcesContent":["import type { RunnableConfig } from \"@langchain/core/runnables\";\nimport type { CallbackManagerForChainRun } from \"@langchain/core/callbacks/manager\";\nimport { BaseMessage } from \"@langchain/core/messages\";\nimport { v4 as uuidv4 } from \"@langchain/core/utils/uuid\";\nimport {\n BaseCheckpointSaver,\n Checkpoint,\n CheckpointTuple,\n copyCheckpoint,\n emptyCheckpoint,\n PendingWrite,\n CheckpointPendingWrite,\n CheckpointMetadata,\n All,\n BaseStore,\n AsyncBatchedStore,\n WRITES_IDX_MAP,\n BaseCache,\n CacheFullKey,\n CacheNamespace,\n} from \"@langchain/langgraph-checkpoint\";\n\nimport {\n BaseChannel,\n createCheckpoint,\n channelsFromCheckpoint,\n deltaChannelsToSnapshot,\n exitDeltaTaskId,\n isDeltaChannel,\n} from \"../channels/base.js\";\nimport type {\n Call,\n CallTaskPath,\n Durability,\n PregelExecutableTask,\n PregelScratchpad,\n StreamMode,\n} from \"./types.js\";\nimport {\n isCommand,\n _isSend,\n _isOverwriteValue,\n CHECKPOINT_NAMESPACE_SEPARATOR,\n Command,\n CONFIG_KEY_CHECKPOINT_MAP,\n CONFIG_KEY_READ,\n CONFIG_KEY_RESUMING,\n CONFIG_KEY_STREAM,\n ERROR,\n ERROR_SOURCE_NODE,\n INPUT,\n INTERRUPT,\n NULL_TASK_ID,\n RESUME,\n TAG_HIDDEN,\n TASKS,\n PUSH,\n CONFIG_KEY_SCRATCHPAD,\n CONFIG_KEY_CHECKPOINT_NS,\n CHECKPOINT_NAMESPACE_END,\n CONFIG_KEY_CHECKPOINT_ID,\n CONFIG_KEY_RESUME_MAP,\n CONFIG_KEY_REPLAY_STATE,\n START,\n} from \"../constants.js\";\nimport { ReplayState } from \"./replay.js\";\nimport {\n _applyWrites,\n _prepareNextTasks,\n _prepareNodeErrorHandlerTask,\n _prepareSingleTask,\n increment,\n shouldInterrupt,\n sanitizeUntrackedValuesInSend,\n WritesProtocol,\n} from \"./algo.js\";\nimport {\n gatherIterator,\n gatherIteratorSync,\n prefixGenerator,\n} from \"../utils.js\";\nimport {\n mapCommand,\n mapInput,\n mapOutputUpdates,\n mapOutputValues,\n readChannels,\n} from \"./io.js\";\nimport {\n EmptyInputError,\n GraphInterrupt,\n isGraphInterrupt,\n} from \"../errors.js\";\nimport { getNewChannelVersions, patchConfigurable } from \"./utils/index.js\";\nimport {\n mapDebugTasks,\n mapDebugCheckpoint,\n mapDebugTaskResults,\n printStepTasks,\n} from \"./debug.js\";\nimport { PregelNode } from \"./read.js\";\nimport { LangGraphRunnableConfig } from \"./runnable_types.js\";\nimport type { RunControl } from \"./runtime.js\";\nimport {\n createDuplexStream,\n IterableReadableWritableStream,\n StreamChunkMeta,\n} from \"./stream.js\";\nimport { isXXH3 } from \"../hash.js\";\n\nconst INPUT_DONE = Symbol.for(\"INPUT_DONE\");\nconst INPUT_RESUMING = Symbol.for(\"INPUT_RESUMING\");\nconst DEFAULT_LOOP_LIMIT = 25;\n\n/**\n * Recursively assign a stable UUID to any {@link BaseMessage} (in a value, an\n * array, or an object's values) that is missing an `id`. Used so DeltaChannel\n * writes — replayed on every read — reconstruct identical message identities.\n */\nfunction ensureMessageIds(value: unknown): void {\n if (value == null || typeof value !== \"object\") return;\n if (BaseMessage.isInstance(value)) {\n const msg = value as BaseMessage;\n if (msg.id == null) {\n msg.id = uuidv4();\n if (msg.lc_kwargs != null) msg.lc_kwargs.id = msg.id;\n }\n return;\n }\n if (Array.isArray(value)) {\n for (const item of value) ensureMessageIds(item);\n return;\n }\n}\n\nexport type PregelLoopInitializeParams = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n input?: any | Command;\n config: RunnableConfig;\n checkpointer?: BaseCheckpointSaver;\n outputKeys: string | string[];\n streamKeys: string | string[];\n nodes: Record<string, PregelNode>;\n channelSpecs: Record<string, BaseChannel>;\n stream: IterableReadableWritableStream;\n store?: BaseStore;\n cache?: BaseCache<PendingWrite<string>[]>;\n interruptAfter: string[] | All;\n interruptBefore: string[] | All;\n durability: Durability;\n manager?: CallbackManagerForChainRun;\n debug: boolean;\n triggerToNodes: Record<string, string[]>;\n};\n\ntype PregelLoopParams = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n input?: any | Command;\n config: RunnableConfig;\n checkpointer?: BaseCheckpointSaver;\n checkpoint: Checkpoint;\n checkpointMetadata: CheckpointMetadata;\n checkpointPreviousVersions: Record<string, string | number>;\n checkpointPendingWrites: CheckpointPendingWrite[];\n checkpointConfig: RunnableConfig;\n channels: Record<string, BaseChannel>;\n step: number;\n stop: number;\n outputKeys: string | string[];\n streamKeys: string | string[];\n nodes: Record<string, PregelNode>;\n checkpointNamespace: string[];\n skipDoneTasks: boolean;\n isNested: boolean;\n resumeAtHead: boolean;\n manager?: CallbackManagerForChainRun;\n stream: IterableReadableWritableStream;\n store?: AsyncBatchedStore;\n cache?: BaseCache<PendingWrite<string>[]>;\n prevCheckpointConfig: RunnableConfig | undefined;\n interruptAfter: string[] | All;\n interruptBefore: string[] | All;\n durability: Durability;\n debug: boolean;\n triggerToNodes: Record<string, string[]>;\n hasPersistedParent?: boolean;\n};\n\n/**\n * Split a serialized checkpoint namespace into its path segments.\n *\n * Checkpoint namespaces are stored as a single string whose nested levels are\n * joined by {@link CHECKPOINT_NAMESPACE_SEPARATOR} (e.g. `\"parent|child\"`).\n * The root namespace — represented as `undefined` or the empty string — maps\n * to an empty array.\n *\n * @param ns - The serialized checkpoint namespace, or `undefined`.\n * @returns The namespace as an array of path segments (`[]` for the root).\n */\nfunction checkpointNamespaceFromNs(ns: string | undefined): string[] {\n if (ns === undefined || ns === \"\") return [];\n return ns.split(CHECKPOINT_NAMESPACE_SEPARATOR);\n}\n\n/**\n * Find the most deeply nested namespace recorded in a checkpoint map.\n *\n * The checkpoint map ({@link CONFIG_KEY_CHECKPOINT_MAP}) associates every\n * namespace seen on a thread with its checkpoint id. Because nested namespaces\n * are built by appending segments to their parent, a deeper namespace always\n * yields a longer key — so the longest non-empty key is the deepest one.\n *\n * Used by the loop's `#interruptStreamNamespace()` during subgraph\n * time-travel: interrupt events must be emitted against the active (deepest)\n * subgraph namespace rather than the root graph.\n *\n * @param map - The checkpoint map (namespace -> checkpoint id), or `undefined`.\n * @returns The deepest namespace as path segments, or `[]` when the map is\n * absent, empty, or only contains the root namespace.\n */\nfunction deepestCheckpointMapNamespace(\n map: Record<string, string> | undefined\n): string[] {\n if (!map) return [];\n let deepest = \"\";\n for (const key of Object.keys(map)) {\n if (key !== \"\" && key.length > deepest.length) {\n deepest = key;\n }\n }\n return checkpointNamespaceFromNs(deepest);\n}\n\nclass AsyncBatchedCache extends BaseCache<PendingWrite<string>[]> {\n protected cache: BaseCache<PendingWrite<string>[]>;\n\n private queue: Promise<unknown> = Promise.resolve();\n\n constructor(cache: BaseCache<unknown>) {\n super();\n this.cache = cache as BaseCache<PendingWrite<string>[]>;\n }\n\n async get(keys: CacheFullKey[]) {\n return this.enqueueOperation(\"get\", keys);\n }\n\n async set(\n pairs: {\n key: CacheFullKey;\n value: PendingWrite<string>[];\n ttl?: number;\n }[]\n ) {\n return this.enqueueOperation(\"set\", pairs);\n }\n\n async clear(namespaces: CacheNamespace[]) {\n return this.enqueueOperation(\"clear\", namespaces);\n }\n\n async stop() {\n await this.queue;\n }\n\n private enqueueOperation<Type extends \"get\" | \"set\" | \"clear\">(\n type: Type,\n ...args: Parameters<(typeof this.cache)[Type]>\n ) {\n const newPromise = this.queue.then(() => {\n // @ts-expect-error Tuple type warning\n return this.cache[type](...args) as Promise<\n ReturnType<(typeof this.cache)[Type]>\n >;\n });\n\n this.queue = newPromise.then(\n () => void 0,\n () => void 0\n );\n\n return newPromise;\n }\n}\n\nexport class PregelLoop {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected input?: any | Command;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n output: any;\n\n config: LangGraphRunnableConfig;\n\n protected checkpointer?: BaseCheckpointSaver;\n\n protected checkpointerGetNextVersion: (current: number | undefined) => number;\n\n channels: Record<string, BaseChannel>;\n\n protected checkpoint: Checkpoint;\n\n protected checkpointIdSaved: string | undefined;\n\n /**\n * Exit-mode accumulator of DeltaChannel writes across the whole run, as\n * `[step, taskId, channel, value]`. `undefined` outside \"exit\" durability.\n */\n protected _exitDeltaWrites: [number, string, string, unknown][] | undefined;\n\n /**\n * DeltaChannels that saw an Overwrite since the last checkpoint. These\n * channels are force-snapshotted at the next checkpoint so reconstruction\n * starts from the post-overwrite value and never has to replay across the\n * reset (the live `update` discards every sibling write in the overwriting\n * super-step). Cleared once the channel snapshots.\n */\n protected _deltaChannelsWithOverwrite: Set<string> = new Set();\n\n /** Whether a real checkpoint was loaded from the saver at initialization. */\n protected _hasPersistedParent = false;\n\n /** The checkpointConfig as captured at initialization (anchor for exit writes). */\n protected _initialCheckpointConfig: RunnableConfig | undefined;\n\n protected checkpointConfig: RunnableConfig;\n\n checkpointMetadata: CheckpointMetadata;\n\n protected checkpointNamespace: string[];\n\n protected checkpointPendingWrites: CheckpointPendingWrite[] = [];\n\n protected checkpointPreviousVersions: Record<string, string | number>;\n\n step: number;\n\n protected stop: number;\n\n protected durability: Durability;\n\n protected outputKeys: string | string[];\n\n protected streamKeys: string | string[];\n\n protected nodes: Record<string, PregelNode>;\n\n protected skipDoneTasks: boolean;\n\n protected prevCheckpointConfig: RunnableConfig | undefined;\n\n protected updatedChannels: Set<string> | undefined;\n\n status:\n | \"pending\"\n | \"done\"\n | \"interrupt_before\"\n | \"interrupt_after\"\n | \"out_of_steps\"\n | \"draining\" = \"pending\";\n\n /**\n * Run-scoped control surface for cooperative draining. Populated from the\n * run config. When `control.drainRequested` is true, the loop stops at the\n * next superstep boundary instead of dispatching more tasks.\n */\n control?: RunControl;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n tasks: Record<string, PregelExecutableTask<any, any>> = {};\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n stream: IterableReadableWritableStream;\n\n checkpointerPromises: Set<Promise<unknown>> = new Set();\n\n isNested: boolean;\n\n /** True when an explicit checkpoint_id targets the latest saved checkpoint. */\n protected resumeAtHead: boolean;\n\n protected _checkpointerChainedPromise: Promise<unknown> = Promise.resolve();\n\n /**\n * Track a checkpointer promise, removing it from the set on success.\n * Failed promises are kept so that Promise.all() in the finally block\n * of _streamIterator can surface the error.\n *\n * @internal\n */\n protected _trackCheckpointerPromise(promise: Promise<unknown>) {\n const tracked = promise.then(\n (value) => {\n this.checkpointerPromises.delete(tracked);\n return value;\n },\n (error) => {\n // Keep failed promises in the set so errors surface via Promise.all()\n throw error;\n }\n );\n this.checkpointerPromises.add(tracked);\n }\n\n store?: AsyncBatchedStore;\n\n cache?: AsyncBatchedCache;\n\n manager?: CallbackManagerForChainRun;\n\n interruptAfter: string[] | All;\n\n interruptBefore: string[] | All;\n\n toInterrupt: PregelExecutableTask<string, string>[] = [];\n\n debug: boolean = false;\n\n triggerToNodes: Record<string, string[]>;\n\n get isResuming() {\n let hasChannelVersions = false;\n if (START in this.checkpoint.channel_versions) {\n // For common channels, we can short-circuit the check\n hasChannelVersions = true;\n } else {\n for (const chan in this.checkpoint.channel_versions) {\n if (\n Object.prototype.hasOwnProperty.call(\n this.checkpoint.channel_versions,\n chan\n )\n ) {\n hasChannelVersions = true;\n break;\n }\n }\n }\n\n const configHasResumingFlag =\n this.config.configurable?.[CONFIG_KEY_RESUMING] !== undefined;\n const configIsResuming =\n configHasResumingFlag && this.config.configurable?.[CONFIG_KEY_RESUMING];\n\n const inputIsNullOrUndefined =\n this.input === null || this.input === undefined;\n const inputIsCommandResuming =\n isCommand(this.input) && this.input.resume != null;\n const inputIsResuming = this.input === INPUT_RESUMING;\n\n const runIdMatchesPrevious =\n !this.isNested &&\n this.config.metadata?.run_id !== undefined &&\n (this.checkpointMetadata as { run_id?: unknown })?.run_id !== undefined &&\n this.config.metadata.run_id ===\n (this.checkpointMetadata as { run_id?: unknown })?.run_id;\n\n return (\n hasChannelVersions &&\n (configIsResuming ||\n inputIsNullOrUndefined ||\n inputIsCommandResuming ||\n inputIsResuming ||\n runIdMatchesPrevious)\n );\n }\n\n get isReplaying(): boolean {\n return !this.skipDoneTasks;\n }\n\n constructor(params: PregelLoopParams) {\n this.input = params.input;\n this.checkpointer = params.checkpointer;\n // TODO: if managed values no longer needs graph we can replace with\n // managed_specs, channel_specs\n if (this.checkpointer !== undefined) {\n this.checkpointerGetNextVersion = this.checkpointer.getNextVersion.bind(\n this.checkpointer\n );\n } else {\n this.checkpointerGetNextVersion = increment;\n }\n this.checkpoint = params.checkpoint;\n this.checkpointMetadata = params.checkpointMetadata;\n this.checkpointPreviousVersions = params.checkpointPreviousVersions;\n this.channels = params.channels;\n this.checkpointPendingWrites = params.checkpointPendingWrites;\n this.step = params.step;\n this.stop = params.stop;\n this.config = params.config;\n this.checkpointConfig = params.checkpointConfig;\n this.isNested = params.isNested;\n this.resumeAtHead = params.resumeAtHead;\n this.manager = params.manager;\n this.outputKeys = params.outputKeys;\n this.streamKeys = params.streamKeys;\n this.nodes = params.nodes;\n this.skipDoneTasks = params.skipDoneTasks;\n this.store = params.store;\n this.cache = params.cache ? new AsyncBatchedCache(params.cache) : undefined;\n this.stream = params.stream;\n this.checkpointNamespace = params.checkpointNamespace;\n this.prevCheckpointConfig = params.prevCheckpointConfig;\n this.interruptAfter = params.interruptAfter;\n this.interruptBefore = params.interruptBefore;\n this.durability = params.durability;\n this.debug = params.debug;\n this.triggerToNodes = params.triggerToNodes;\n this.control = this.config.control;\n // Exit-mode delta-channel accumulator: in \"exit\" durability, per-step\n // writes are not persisted incrementally, so DeltaChannel writes would be\n // lost. Accumulate them across the run and persist (anchored to a parent\n // or stub) at exit. `undefined` outside exit mode disables capture.\n this._exitDeltaWrites =\n this.durability === \"exit\" && this.checkpointer != null ? [] : undefined;\n this._hasPersistedParent = params.hasPersistedParent ?? false;\n this._initialCheckpointConfig = params.checkpointConfig;\n this.checkpointIdSaved = params.checkpoint.id;\n }\n\n static async initialize(params: PregelLoopInitializeParams) {\n let { config, stream } = params;\n if (\n stream !== undefined &&\n config.configurable?.[CONFIG_KEY_STREAM] !== undefined\n ) {\n stream = createDuplexStream(\n stream,\n config.configurable[CONFIG_KEY_STREAM]\n );\n }\n const skipDoneTasks = config.configurable\n ? !(\"checkpoint_id\" in config.configurable)\n : true;\n\n const scratchpad = config.configurable?.[CONFIG_KEY_SCRATCHPAD] as\n | PregelScratchpad\n | undefined;\n\n if (config.configurable && scratchpad) {\n if (scratchpad.subgraphCounter > 0) {\n config = patchConfigurable(config, {\n [CONFIG_KEY_CHECKPOINT_NS]: [\n config.configurable[CONFIG_KEY_CHECKPOINT_NS],\n scratchpad.subgraphCounter.toString(),\n ].join(CHECKPOINT_NAMESPACE_SEPARATOR),\n });\n }\n\n scratchpad.subgraphCounter += 1;\n }\n\n const requestedCheckpointId = config.configurable?.checkpoint_id as\n | string\n | undefined;\n\n const isNested = CONFIG_KEY_READ in (config.configurable ?? {});\n if (\n !isNested &&\n config.configurable?.checkpoint_ns !== undefined &&\n config.configurable?.checkpoint_ns !== \"\"\n ) {\n config = patchConfigurable(config, {\n checkpoint_ns: \"\",\n checkpoint_id: undefined,\n });\n }\n let checkpointConfig = config;\n if (\n config.configurable?.checkpoint_id === undefined &&\n config.configurable?.[CONFIG_KEY_CHECKPOINT_MAP] !== undefined &&\n config.configurable?.[CONFIG_KEY_CHECKPOINT_MAP]?.[\n config.configurable?.checkpoint_ns\n ]\n ) {\n checkpointConfig = patchConfigurable(config, {\n checkpoint_id:\n config.configurable[CONFIG_KEY_CHECKPOINT_MAP][\n config.configurable?.checkpoint_ns\n ],\n });\n }\n const checkpointNamespace = checkpointNamespaceFromNs(\n config.configurable?.checkpoint_ns\n );\n\n let saved: CheckpointTuple | undefined;\n if (!params.checkpointer) {\n saved = undefined;\n } else if (checkpointConfig.configurable?.[CONFIG_KEY_CHECKPOINT_ID]) {\n saved = await params.checkpointer.getTuple(checkpointConfig);\n } else if (config.configurable?.[CONFIG_KEY_REPLAY_STATE]) {\n const replayState = config.configurable[\n CONFIG_KEY_REPLAY_STATE\n ] as ReplayState;\n saved = await replayState.getCheckpoint(\n config.configurable?.[CONFIG_KEY_CHECKPOINT_NS] ?? \"\",\n params.checkpointer,\n checkpointConfig\n );\n if (config.configurable) {\n delete config.configurable[CONFIG_KEY_RESUMING];\n }\n } else {\n saved = await params.checkpointer.getTuple(checkpointConfig);\n }\n const hasPersistedParent = saved !== undefined;\n if (!saved) {\n saved = {\n config,\n checkpoint: emptyCheckpoint(),\n metadata: { source: \"input\", step: -2, parents: {} },\n pendingWrites: [],\n };\n }\n checkpointConfig = {\n ...config,\n ...saved.config,\n configurable: {\n checkpoint_ns: \"\",\n ...config.configurable,\n ...saved.config.configurable,\n },\n };\n const prevCheckpointConfig = saved.parentConfig;\n const checkpoint = copyCheckpoint(saved.checkpoint);\n const checkpointMetadata = { ...saved.metadata } as CheckpointMetadata;\n let checkpointPendingWrites = saved.pendingWrites ?? [];\n const currentCheckpointNamespace = config.configurable?.checkpoint_ns;\n const checkpointMap = config.configurable?.[CONFIG_KEY_CHECKPOINT_MAP];\n const isDirectSubgraphTimeTravel =\n typeof currentCheckpointNamespace === \"string\" &&\n currentCheckpointNamespace !== \"\" &&\n typeof checkpointMap === \"object\" &&\n checkpointMap !== null &&\n currentCheckpointNamespace in checkpointMap;\n\n if (isDirectSubgraphTimeTravel && checkpointPendingWrites.length > 0) {\n // Direct subgraph time-travel should re-fire interrupts instead of\n // consuming stale resume values that were written during the original run.\n checkpointPendingWrites = checkpointPendingWrites.filter(\n ([, channel]) => channel !== RESUME\n );\n }\n\n let resumeAtHead = false;\n const threadId = checkpointConfig.configurable?.thread_id;\n const checkpointNs = checkpointConfig.configurable?.checkpoint_ns ?? \"\";\n if (\n params.checkpointer &&\n requestedCheckpointId &&\n typeof threadId === \"string\"\n ) {\n const latest = await params.checkpointer.getTuple({\n configurable: { thread_id: threadId, checkpoint_ns: checkpointNs },\n });\n resumeAtHead =\n latest?.config.configurable?.checkpoint_id === requestedCheckpointId &&\n checkpointMetadata.source !== \"update\" &&\n checkpointMetadata.source !== \"fork\";\n }\n\n const channels = await channelsFromCheckpoint(\n params.channelSpecs,\n checkpoint,\n {\n saver: params.checkpointer,\n config: checkpointConfig,\n }\n );\n\n const step = (checkpointMetadata.step ?? 0) + 1;\n const stop = step + (config.recursionLimit ?? DEFAULT_LOOP_LIMIT) + 1;\n const checkpointPreviousVersions = { ...checkpoint.channel_versions };\n\n const store = params.store\n ? new AsyncBatchedStore(params.store)\n : undefined;\n\n if (store) {\n // Start the store. This is a batch store, so it will run continuously\n await store.start();\n }\n return new PregelLoop({\n input: params.input,\n config,\n checkpointer: params.checkpointer,\n checkpoint,\n checkpointMetadata,\n checkpointConfig,\n prevCheckpointConfig,\n checkpointNamespace,\n channels,\n isNested,\n resumeAtHead,\n manager: params.manager,\n skipDoneTasks,\n step,\n stop,\n checkpointPreviousVersions,\n checkpointPendingWrites,\n outputKeys: params.outputKeys ?? [],\n streamKeys: params.streamKeys ?? [],\n nodes: params.nodes,\n stream,\n store,\n cache: params.cache,\n interruptAfter: params.interruptAfter,\n interruptBefore: params.interruptBefore,\n durability: params.durability,\n debug: params.debug,\n triggerToNodes: params.triggerToNodes,\n hasPersistedParent,\n });\n }\n\n protected _checkpointerPutAfterPrevious(input: {\n config: RunnableConfig;\n checkpoint: Checkpoint;\n metadata: CheckpointMetadata;\n newVersions: Record<string, string | number>;\n }) {\n this._checkpointerChainedPromise = this._checkpointerChainedPromise.then(\n () => {\n return this.checkpointer?.put(\n input.config,\n input.checkpoint,\n input.metadata,\n input.newVersions\n );\n }\n );\n this._trackCheckpointerPromise(this._checkpointerChainedPromise);\n }\n\n /**\n * Put writes for a task, to be read by the next tick.\n * @param taskId\n * @param writes\n */\n putWrites(taskId: string, writes: PendingWrite<string>[]) {\n let writesCopy = writes;\n if (writesCopy.length === 0) return;\n\n // deduplicate writes to special channels, last write wins\n if (writesCopy.every(([key]) => key in WRITES_IDX_MAP)) {\n writesCopy = Array.from(\n new Map(writesCopy.map((w) => [w[0], w])).values()\n );\n }\n\n // Check if any channels are UntrackedValue (manual loop for perf)\n let hasUntrackedChannels = false;\n for (const key in this.channels) {\n if (Object.prototype.hasOwnProperty.call(this.channels, key)) {\n const channel = this.channels[key];\n if (channel.lc_graph_name === \"UntrackedValue\") {\n hasUntrackedChannels = true;\n break;\n }\n }\n }\n\n // Sanitize writes for checkpointing: remove UntrackedValue writes and sanitize Send packets\n let writesToSave = writesCopy;\n if (hasUntrackedChannels) {\n writesToSave = writesCopy\n .filter(([c]) => {\n // Don't persist UntrackedValue channel writes\n const channel = this.channels[c];\n return !channel || channel.lc_graph_name !== \"UntrackedValue\";\n })\n .map(([c, v]) => {\n // Sanitize UntrackedValues nested within Send packets\n if (c === TASKS && _isSend(v)) {\n return [c, sanitizeUntrackedValuesInSend(v, this.channels)] as [\n string,\n unknown,\n ];\n }\n return [c, v] as [string, unknown];\n });\n }\n\n // remove existing writes for this task\n this.checkpointPendingWrites = this.checkpointPendingWrites.filter(\n (w) => w[0] !== taskId\n );\n\n // save writes\n for (const [c, v] of writesToSave) {\n this.checkpointPendingWrites.push([taskId, c, v]);\n }\n\n // Assign stable IDs to any id-less BaseMessages in DeltaChannel writes\n // before they are serialised. DeltaChannel state is reconstructed by\n // replaying these stored writes, so without stable IDs every getState()\n // replay would mint a fresh UUID and dedup/RemoveMessage would break.\n for (const [c, v] of writesToSave) {\n const channel = this.channels[c];\n if (channel != null && isDeltaChannel(channel)) {\n ensureMessageIds(v);\n }\n }\n\n const config = patchConfigurable(this.checkpointConfig, {\n [CONFIG_KEY_CHECKPOINT_NS]: this.config.configurable?.checkpoint_ns ?? \"\",\n [CONFIG_KEY_CHECKPOINT_ID]: this.checkpoint.id,\n });\n\n if (this.durability !== \"exit\" && this.checkpointer != null) {\n this._trackCheckpointerPromise(\n // Use sanitized writes for checkpointer\n this.checkpointer.putWrites(config, writesToSave, taskId)\n );\n }\n\n if (this.tasks) {\n this._outputWrites(taskId, writesCopy);\n }\n\n if (!writes.length || !this.cache || !this.tasks) {\n return;\n }\n\n // only cache tasks with a cache key\n const task = this.tasks[taskId];\n if (task == null || task.cache_key == null) {\n return;\n }\n\n // only cache successful tasks\n if (writes[0][0] === ERROR || writes[0][0] === INTERRUPT) {\n return;\n }\n\n void this.cache.set([\n {\n key: [task.cache_key.ns, task.cache_key.key],\n value: task.writes,\n ttl: task.cache_key.ttl,\n },\n ]);\n }\n\n _outputWrites(taskId: string, writes: [string, unknown][], cached = false) {\n const task = this.tasks[taskId];\n if (task !== undefined) {\n if (\n task.config !== undefined &&\n (task.config.tags ?? []).includes(TAG_HIDDEN)\n ) {\n return;\n }\n\n if (writes.length > 0) {\n if (writes[0][0] === INTERRUPT) {\n // in `algo.ts` we append a bool to the task path to indicate\n // whether or not a call was present. If so, we don't emit the\n // the interrupt as it'll be emitted by the parent.\n if (\n task.path?.[0] === PUSH &&\n task.path?.[task.path.length - 1] === true\n )\n return;\n\n const interruptWrites = writes\n .filter((w) => w[0] === INTERRUPT)\n .flatMap((w) => w[1] as string[]);\n\n this._emit([\n [\"updates\", { [INTERRUPT]: interruptWrites }],\n [\"values\", { [INTERRUPT]: interruptWrites }],\n ]);\n } else if (writes[0][0] !== ERROR) {\n this._emit(\n gatherIteratorSync(\n prefixGenerator(\n mapOutputUpdates(this.outputKeys, [[task, writes]], cached),\n \"updates\"\n )\n )\n );\n }\n }\n if (!cached) {\n this._emit(\n gatherIteratorSync(\n prefixGenerator(\n mapDebugTaskResults([[task, writes]], this.streamKeys),\n \"tasks\"\n )\n )\n );\n }\n }\n }\n\n async _matchCachedWrites() {\n if (!this.cache) return [];\n\n const matched: {\n task: PregelExecutableTask<string, string>;\n result: unknown;\n }[] = [];\n\n const serializeKey = ([ns, key]: CacheFullKey) => {\n return `ns:${ns.join(\",\")}|key:${key}`;\n };\n\n const keys: CacheFullKey[] = [];\n const keyMap: Record<string, PregelExecutableTask<string, string>> = {};\n\n for (const task of Object.values(this.tasks)) {\n if (task.cache_key != null && !task.writes.length) {\n keys.push([task.cache_key.ns, task.cache_key.key]);\n keyMap[serializeKey([task.cache_key.ns, task.cache_key.key])] = task;\n }\n }\n\n if (keys.length === 0) return [];\n const cache = await this.cache.get(keys);\n\n for (const { key, value } of cache) {\n const task = keyMap[serializeKey(key)];\n if (task != null) {\n // update the task with the cached writes\n task.writes.push(...value);\n matched.push({ task, result: value });\n }\n }\n\n return matched;\n }\n\n /**\n * Execute a single iteration of the Pregel loop.\n * Returns true if more iterations are needed.\n * @param params - The input keys to use for the tick.\n * @returns True if more iterations are needed, false otherwise.\n */\n async tick(params: { inputKeys?: string | string[] }): Promise<boolean> {\n if (this.store && !this.store.isRunning) {\n await this.store?.start();\n }\n const { inputKeys = [] } = params;\n if (this.status !== \"pending\") {\n throw new Error(\n `Cannot tick when status is no longer \"pending\". Current status: \"${this.status}\"`\n );\n }\n if (![INPUT_DONE, INPUT_RESUMING].includes(this.input)) {\n await this._first(inputKeys);\n } else if (this.toInterrupt.length > 0) {\n this.status = \"interrupt_before\";\n throw new GraphInterrupt();\n } else if (\n Object.values(this.tasks).every((task) => task.writes.length > 0)\n ) {\n const finishTaskList = Object.values(this.tasks);\n // finish superstep\n const writes = finishTaskList.flatMap((t) => t.writes);\n // All tasks have finished\n this.updatedChannels = _applyWrites(\n this.checkpoint,\n this.channels,\n finishTaskList,\n this.checkpointerGetNextVersion,\n this.triggerToNodes\n );\n // Track DeltaChannels that saw an Overwrite this super-step. They must\n // snapshot at the next checkpoint so sparse replay starts from the\n // post-overwrite value (live `update` already discarded the siblings).\n for (const [ch, v] of writes) {\n const channel = this.channels[ch];\n if (\n channel != null &&\n isDeltaChannel(channel) &&\n _isOverwriteValue(v)\n ) {\n this._deltaChannelsWithOverwrite.add(ch);\n }\n }\n // produce values output\n const valuesOutput = await gatherIterator(\n prefixGenerator(\n mapOutputValues(this.outputKeys, writes, this.channels),\n \"values\"\n )\n );\n // capture delta-channel writes for the exit-mode accumulator before\n // clearing (in \"exit\" durability they are not persisted incrementally)\n if (this._exitDeltaWrites !== undefined) {\n for (const [tid, ch, v] of this.checkpointPendingWrites) {\n const channel = this.channels[ch];\n if (channel != null && isDeltaChannel(channel)) {\n this._exitDeltaWrites.push([this.step, tid, ch, v]);\n }\n }\n }\n // clear pending writes\n this.checkpointPendingWrites = [];\n // persist the new checkpoint BEFORE emitting values, so the\n // attached `checkpoint` envelope on the values event points at\n // the fork target that captures this superstep's final state.\n await this._putCheckpoint({ source: \"loop\" });\n this._emitValuesWithCheckpointMeta(valuesOutput);\n // after execution, check if we should interrupt\n if (\n shouldInterrupt(this.checkpoint, this.interruptAfter, finishTaskList)\n ) {\n this.status = \"interrupt_after\";\n throw new GraphInterrupt();\n }\n\n // unset resuming flag\n if (this.config.configurable?.[CONFIG_KEY_RESUMING] !== undefined) {\n delete this.config.configurable?.[CONFIG_KEY_RESUMING];\n }\n } else {\n return false;\n }\n if (this.step > this.stop) {\n this.status = \"out_of_steps\";\n return false;\n }\n\n const nextTasks = _prepareNextTasks(\n this.checkpoint,\n this.checkpointPendingWrites,\n this.nodes,\n this.channels,\n this.config,\n true,\n {\n step: this.step,\n checkpointer: this.checkpointer,\n isResuming: this.isResuming,\n manager: this.manager,\n store: this.store,\n stream: this.stream,\n triggerToNodes: this.triggerToNodes,\n updatedChannels: this.updatedChannels,\n }\n );\n this.tasks = nextTasks;\n let taskList = Object.values(this.tasks);\n\n // Full-state checkpoint snapshots are expensive; skip unless a consumer\n // subscribed to \"checkpoints\" or the legacy \"debug\" wrapper mode.\n if (\n this.checkpointer &&\n (this.stream.modes.has(\"checkpoints\") || this.stream.modes.has(\"debug\"))\n ) {\n this._emit(\n await gatherIterator(\n prefixGenerator(\n mapDebugCheckpoint(\n this.checkpointConfig,\n this.channels,\n this.streamKeys,\n this.checkpointMetadata,\n taskList,\n this.checkpointPendingWrites,\n this.prevCheckpointConfig,\n this.outputKeys\n ),\n \"checkpoints\"\n )\n )\n );\n }\n\n if (taskList.length === 0) {\n this.status = \"done\";\n return false;\n }\n // Cooperative drain: the previous superstep's writes have been applied\n // and checkpointed above, and the next tasks have been prepared. If a\n // drain was requested and tasks remain, stop here (without dispatching\n // them) so the run can be resumed later from the saved checkpoint.\n if (this.control != null && this.control.drainRequested) {\n this.status = \"draining\";\n return false;\n }\n // if there are pending writes from a previous loop, apply them\n if (this.skipDoneTasks && this.checkpointPendingWrites.length > 0) {\n for (const [tid, k, v] of this.checkpointPendingWrites) {\n if (\n k === ERROR ||\n k === ERROR_SOURCE_NODE ||\n k === INTERRUPT ||\n k === RESUME\n ) {\n continue;\n }\n const task = taskList.find((t) => t.id === tid);\n if (task) {\n task.writes.push([k, v]);\n }\n }\n // On resume, re-schedule error handlers for nodes that failed in a prior\n // run (recorded via ERROR_SOURCE_NODE) before they completed handling.\n this._resumeErrorHandlersIfApplicable();\n // Re-scheduling can add handler tasks to `this.tasks`, so refresh the\n // cached task list before emitting writes and the downstream re-tick /\n // interrupt / debug checks see the newly scheduled handlers.\n taskList = Object.values(this.tasks);\n for (const task of taskList) {\n if (task.writes.length > 0) {\n this._outputWrites(task.id, task.writes, true);\n }\n }\n }\n // if all tasks have finished, re-tick\n if (taskList.every((task) => task.writes.length > 0)) {\n return this.tick({ inputKeys });\n }\n\n // Before execution, check if we should interrupt\n if (shouldInterrupt(this.checkpoint, this.interruptBefore, taskList)) {\n this.status = \"interrupt_before\";\n throw new GraphInterrupt();\n }\n\n if (this.stream.modes.has(\"tasks\") || this.stream.modes.has(\"debug\")) {\n const debugOutput = await gatherIterator(\n prefixGenerator(mapDebugTasks(taskList), \"tasks\")\n );\n this._emit(debugOutput);\n }\n\n return true;\n }\n\n async finishAndHandleError(error?: Error) {\n // persist current checkpoint and writes\n if (\n this.durability === \"exit\" &&\n // if it's a top graph\n (!this.isNested ||\n // or a nested graph with error or interrupt\n typeof error !== \"undefined\" ||\n // or a nested graph with checkpointer: true\n this.checkpointNamespace.every(\n (part) => !part.includes(CHECKPOINT_NAMESPACE_END)\n ))\n ) {\n await this._putExitDeltaWrites();\n this._putCheckpoint(this.checkpointMetadata);\n this._flushPendingWrites();\n }\n\n const suppress = this._suppressInterrupt(error);\n if (suppress || error === undefined) {\n this.output = readChannels(this.channels, this.outputKeys);\n }\n if (suppress) {\n // emit one last \"values\" event, with pending writes applied\n if (\n this.tasks !== undefined &&\n this.checkpointPendingWrites.length > 0 &&\n Object.values(this.tasks).some((task) => task.writes.length > 0)\n ) {\n this.updatedChannels = _applyWrites(\n this.checkpoint,\n this.channels,\n Object.values(this.tasks),\n this.checkpointerGetNextVersion,\n this.triggerToNodes\n );\n\n this._emitValuesWithCheckpointMeta(\n gatherIteratorSync(\n prefixGenerator(\n mapOutputValues(\n this.outputKeys,\n Object.values(this.tasks).flatMap((t) => t.writes),\n this.channels\n ),\n \"values\"\n )\n )\n );\n }\n\n // Emit INTERRUPT event (not a state snapshot — no checkpoint envelope)\n if (isGraphInterrupt(error) && !error.interrupts.length) {\n this._emit(\n [\n [\"updates\", { [INTERRUPT]: [] }],\n [\"values\", { [INTERRUPT]: [] }],\n ],\n this.#interruptStreamNamespace()\n );\n }\n }\n return suppress;\n }\n\n async acceptPush(\n task: PregelExecutableTask<string, string>,\n writeIdx: number,\n call?: Call\n ): Promise<PregelExecutableTask<string, string> | void> {\n if (\n this.interruptAfter?.length > 0 &&\n shouldInterrupt(this.checkpoint, this.interruptAfter, [task])\n ) {\n this.toInterrupt.push(task);\n return;\n }\n\n const pushed = _prepareSingleTask(\n [PUSH, task.path ?? [], writeIdx, task.id, call] as CallTaskPath,\n this.checkpoint,\n this.checkpointPendingWrites,\n this.nodes,\n this.channels,\n task.config ?? {},\n true,\n {\n step: this.step,\n checkpointer: this.checkpointer,\n manager: this.manager,\n store: this.store,\n stream: this.stream,\n }\n );\n\n if (!pushed) return;\n if (\n this.interruptBefore?.length > 0 &&\n shouldInterrupt(this.checkpoint, this.interruptBefore, [pushed])\n ) {\n this.toInterrupt.push(pushed);\n return;\n }\n\n if (this.stream.modes.has(\"tasks\") || this.stream.modes.has(\"debug\")) {\n this._emit(\n gatherIteratorSync(prefixGenerator(mapDebugTasks([pushed]), \"tasks\"))\n );\n }\n\n if (this.debug) printStepTasks(this.step, [pushed]);\n this.tasks[pushed.id] = pushed;\n if (this.skipDoneTasks) this._matchWrites({ [pushed.id]: pushed });\n\n const tasks = await this._matchCachedWrites();\n for (const { task } of tasks) {\n this._outputWrites(task.id, task.writes, true);\n }\n\n return pushed;\n }\n\n /**\n * Returns the name of the error handler node registered for `nodeName`, or\n * `undefined` if none is configured.\n */\n getErrorHandlerNode(nodeName: string): string | undefined {\n return this.nodes[nodeName]?.errorHandlerNode;\n }\n\n /**\n * Whether `nodeName` is itself an auto-generated error handler node.\n */\n isErrorHandlerNode(nodeName: string): boolean {\n return this.nodes[nodeName]?.isErrorHandler === true;\n }\n\n /**\n * Schedule a node-level error handler task for a task that failed after its\n * retry policy was exhausted. Prepares the handler task (injecting a\n * {@link NodeError}), registers it so the runner executes it within the\n * current step, and returns it (or `undefined` if no handler applies).\n *\n * The failure provenance (`ERROR` + `ERROR_SOURCE_NODE`) is checkpointed by\n * the runner via {@link PregelLoop#putWrites} so handlers observe the same\n * context after a resume.\n */\n scheduleErrorHandler(\n failedTask: PregelExecutableTask<string, string>,\n error: Error\n ): PregelExecutableTask<string, string> | undefined {\n const handlerNode = this.getErrorHandlerNode(String(failedTask.name));\n if (!handlerNode) return undefined;\n\n const handlerTask = _prepareNodeErrorHandlerTask(\n failedTask,\n handlerNode,\n error,\n this.checkpoint,\n this.checkpointPendingWrites,\n this.nodes,\n this.channels,\n failedTask.config ?? this.config,\n {\n step: this.step,\n checkpointer: this.checkpointer,\n manager: this.manager,\n store: this.store,\n stream: this.stream,\n }\n ) as PregelExecutableTask<string, string> | undefined;\n\n if (handlerTask === undefined) return undefined;\n\n this.tasks[handlerTask.id] = handlerTask;\n\n this._emit(\n gatherIteratorSync(prefixGenerator(mapDebugTasks([handlerTask]), \"tasks\"))\n );\n if (this.debug) printStepTasks(this.step, [handlerTask]);\n\n return handlerTask;\n }\n\n /**\n * On resume, re-schedule error handlers for tasks that failed in a prior run\n * but had not finished being handled. Scans pending writes for\n * `ERROR_SOURCE_NODE` markers (paired with `ERROR`), marks the originating\n * task as done (so the runner won't re-run it), and prepares a fresh handler\n * task so the runner picks it up.\n */\n protected _resumeErrorHandlersIfApplicable() {\n // Collect failed task ids with both ERROR_SOURCE_NODE and ERROR writes.\n const failed = new Map<string, Error>();\n for (const [tid, chan] of this.checkpointPendingWrites) {\n if (chan !== ERROR_SOURCE_NODE) continue;\n const errorWrite = this.checkpointPendingWrites.find(\n ([t, c]) => t === tid && c === ERROR\n );\n if (errorWrite === undefined) continue;\n const value = errorWrite[2] as { message?: string; name?: string };\n const error = new Error(value?.message ?? String(value));\n if (value?.name) error.name = value.name;\n failed.set(tid, error);\n }\n\n for (const [tid, error] of failed) {\n const task = this.tasks[tid];\n if (task === undefined) continue;\n const handlerNode = this.getErrorHandlerNode(String(task.name));\n if (!handlerNode) continue;\n // Non-empty writes => runner's `writes.length === 0` filter skips it.\n if (task.writes.length === 0) {\n task.writes.push([ERROR, { message: error.message, name: error.name }]);\n }\n this.scheduleErrorHandler(task, error);\n }\n }\n\n protected _suppressInterrupt(e?: Error): boolean {\n return isGraphInterrupt(e) && !this.isNested;\n }\n\n protected async _first(inputKeys: string | string[]) {\n /*\n * Resuming from previous checkpoint requires\n * - finding a previous checkpoint\n * - receiving null input (outer graph) or RESUMING flag (subgraph)\n */\n\n const { configurable } = this.config;\n\n // take resume value from parent\n const scratchpad = configurable?.[\n CONFIG_KEY_SCRATCHPAD\n ] as PregelScratchpad;\n\n if (scratchpad && scratchpad.nullResume !== undefined) {\n this.putWrites(NULL_TASK_ID, [[RESUME, scratchpad.nullResume]]);\n }\n\n // map command to writes\n if (isCommand(this.input)) {\n const hasResume = this.input.resume != null;\n\n if (\n this.input.resume != null &&\n typeof this.input.resume === \"object\" &&\n Object.keys(this.input.resume).every(isXXH3)\n ) {\n this.config.configurable ??= {};\n this.config.configurable[CONFIG_KEY_RESUME_MAP] = this.input.resume;\n }\n\n if (hasResume && this.checkpointer == null) {\n throw new Error(\"Cannot use Command(resume=...) without checkpointer\");\n }\n\n const writes: { [key: string]: PendingWrite[] } = {};\n\n // group writes by task id\n for (const [tid, key, value] of mapCommand(\n this.input,\n this.checkpointPendingWrites\n )) {\n writes[tid] ??= [];\n writes[tid].push([key, value]);\n }\n if (Object.keys(writes).length === 0) {\n throw new EmptyInputError(\"Received empty Command input\");\n }\n\n // save writes\n for (const [tid, ws] of Object.entries(writes)) {\n this.putWrites(tid, ws);\n }\n }\n\n // apply null writes\n const nullWrites = (this.checkpointPendingWrites ?? [])\n .filter((w) => w[0] === NULL_TASK_ID)\n .map((w) => w.slice(1)) as PendingWrite<string>[];\n if (nullWrites.length > 0) {\n _applyWrites(\n this.checkpoint,\n this.channels,\n [\n {\n name: INPUT,\n writes: nullWrites,\n triggers: [],\n },\n ],\n this.checkpointerGetNextVersion,\n this.triggerToNodes\n );\n }\n const inputIsCommand = isCommand(this.input);\n const isCommandUpdateOrGoto = inputIsCommand && nullWrites.length > 0;\n\n const isTimeTraveling =\n this.isReplaying &&\n // Time-travel to a subgraph checkpoint: the parent sets RESUMING=True\n // (it can't distinguish time-travel from resume), so we check if this\n // subgraph's own ns is in checkpoint_map.\n ((this.isNested &&\n configurable?.[CONFIG_KEY_CHECKPOINT_NS] !== undefined &&\n configurable?.[CONFIG_KEY_CHECKPOINT_NS] !== \"\" &&\n configurable?.[CONFIG_KEY_CHECKPOINT_MAP] !== undefined &&\n configurable[CONFIG_KEY_CHECKPOINT_NS] in\n configurable[CONFIG_KEY_CHECKPOINT_MAP]) ||\n !(\n (inputIsCommand && (this.input as Command).resume != null) ||\n configurable?.[CONFIG_KEY_RESUMING] === true ||\n this.resumeAtHead\n ));\n\n if (isTimeTraveling) {\n this.checkpointPendingWrites = this.checkpointPendingWrites.filter(\n (w) => w[1] !== RESUME\n );\n }\n\n const cachedIsResuming = this.isResuming;\n if (cachedIsResuming || isCommandUpdateOrGoto) {\n // One spread (O(N)) instead of O(N²) per-channel spreads. Must be a\n // new object — copyCheckpoint shallow-copies versions_seen.\n const interruptSeen: Record<string, string | number> = {\n ...this.checkpoint.versions_seen[INTERRUPT],\n };\n for (const channelName in this.channels) {\n if (!Object.prototype.hasOwnProperty.call(this.channels, channelName))\n continue;\n if (this.checkpoint.channel_versions[channelName] !== undefined) {\n interruptSeen[channelName] =\n this.checkpoint.channel_versions[channelName];\n }\n }\n this.checkpoint.versions_seen[INTERRUPT] = interruptSeen;\n\n if (\n isTimeTraveling &&\n this.checkpointMetadata.source !== \"update\" &&\n this.checkpointMetadata.source !== \"fork\"\n ) {\n this.checkpointPendingWrites = this.checkpointPendingWrites.filter(\n (w) => w[1] !== INTERRUPT\n );\n await this._putCheckpoint({ source: \"fork\" });\n }\n\n // produce values output\n const valuesOutput = await gatherIterator(\n prefixGenerator(\n mapOutputValues(this.outputKeys, true, this.channels),\n \"values\"\n )\n );\n // Preserve the original `isResuming`-first priority: when both\n // `isResuming` and `isCommandUpdateOrGoto` are true (resuming from\n // an interrupt with a Command update/goto), the resume path wins\n // and no new input checkpoint is created here.\n