@langchain/langgraph 1.4.21 → 1.5.1

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.
Files changed (87) hide show
  1. package/dist/channels/base.cjs +123 -11
  2. package/dist/channels/base.cjs.map +1 -1
  3. package/dist/channels/base.d.cts +26 -4
  4. package/dist/channels/base.d.cts.map +1 -1
  5. package/dist/channels/base.d.ts +26 -4
  6. package/dist/channels/base.d.ts.map +1 -1
  7. package/dist/channels/base.js +120 -13
  8. package/dist/channels/base.js.map +1 -1
  9. package/dist/channels/binop.cjs +2 -1
  10. package/dist/channels/binop.cjs.map +1 -1
  11. package/dist/channels/binop.d.cts.map +1 -1
  12. package/dist/channels/binop.d.ts.map +1 -1
  13. package/dist/channels/binop.js +2 -1
  14. package/dist/channels/binop.js.map +1 -1
  15. package/dist/channels/index.cjs +2 -0
  16. package/dist/channels/index.d.cts +2 -2
  17. package/dist/channels/index.d.ts +2 -2
  18. package/dist/channels/index.js +2 -2
  19. package/dist/constants.cjs +14 -0
  20. package/dist/constants.cjs.map +1 -1
  21. package/dist/constants.d.cts.map +1 -1
  22. package/dist/constants.d.ts.map +1 -1
  23. package/dist/constants.js +13 -1
  24. package/dist/constants.js.map +1 -1
  25. package/dist/errors.cjs +22 -0
  26. package/dist/errors.cjs.map +1 -1
  27. package/dist/errors.d.cts +10 -1
  28. package/dist/errors.d.cts.map +1 -1
  29. package/dist/errors.d.ts +10 -1
  30. package/dist/errors.d.ts.map +1 -1
  31. package/dist/errors.js +21 -1
  32. package/dist/errors.js.map +1 -1
  33. package/dist/graph/index.cjs +1 -1
  34. package/dist/graph/index.js +1 -1
  35. package/dist/graph/messages_reducer.cjs +30 -6
  36. package/dist/graph/messages_reducer.cjs.map +1 -1
  37. package/dist/graph/messages_reducer.d.cts +4 -2
  38. package/dist/graph/messages_reducer.d.cts.map +1 -1
  39. package/dist/graph/messages_reducer.d.ts +4 -2
  40. package/dist/graph/messages_reducer.d.ts.map +1 -1
  41. package/dist/graph/messages_reducer.js +31 -8
  42. package/dist/graph/messages_reducer.js.map +1 -1
  43. package/dist/index.cjs +3 -1
  44. package/dist/index.d.cts +2 -2
  45. package/dist/index.d.ts +2 -2
  46. package/dist/index.js +3 -3
  47. package/dist/interrupt.cjs +9 -1
  48. package/dist/interrupt.cjs.map +1 -1
  49. package/dist/interrupt.d.cts.map +1 -1
  50. package/dist/interrupt.d.ts.map +1 -1
  51. package/dist/interrupt.js +10 -2
  52. package/dist/interrupt.js.map +1 -1
  53. package/dist/pregel/algo.cjs +46 -45
  54. package/dist/pregel/algo.cjs.map +1 -1
  55. package/dist/pregel/algo.d.cts.map +1 -1
  56. package/dist/pregel/algo.d.ts.map +1 -1
  57. package/dist/pregel/algo.js +47 -47
  58. package/dist/pregel/algo.js.map +1 -1
  59. package/dist/pregel/checkpoint.cjs +70 -0
  60. package/dist/pregel/checkpoint.cjs.map +1 -0
  61. package/dist/pregel/checkpoint.js +67 -0
  62. package/dist/pregel/checkpoint.js.map +1 -0
  63. package/dist/pregel/debug.cjs +1 -0
  64. package/dist/pregel/debug.cjs.map +1 -1
  65. package/dist/pregel/debug.js +1 -0
  66. package/dist/pregel/debug.js.map +1 -1
  67. package/dist/pregel/index.cjs +87 -34
  68. package/dist/pregel/index.cjs.map +1 -1
  69. package/dist/pregel/index.d.cts.map +1 -1
  70. package/dist/pregel/index.d.ts.map +1 -1
  71. package/dist/pregel/index.js +91 -38
  72. package/dist/pregel/index.js.map +1 -1
  73. package/dist/pregel/loop.cjs +336 -113
  74. package/dist/pregel/loop.cjs.map +1 -1
  75. package/dist/pregel/loop.js +341 -119
  76. package/dist/pregel/loop.js.map +1 -1
  77. package/dist/pregel/runner.cjs +1 -1
  78. package/dist/pregel/runner.cjs.map +1 -1
  79. package/dist/pregel/runner.js +2 -2
  80. package/dist/pregel/runner.js.map +1 -1
  81. package/dist/state/prebuilt/messages.cjs +1 -1
  82. package/dist/state/prebuilt/messages.js +1 -1
  83. package/dist/web.cjs +3 -1
  84. package/dist/web.d.cts +2 -2
  85. package/dist/web.d.ts +2 -2
  86. package/dist/web.js +3 -3
  87. package/package.json +5 -5
@@ -1,40 +1,116 @@
1
- import { CONFIG_KEY_CHECKPOINT_ID, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_CHECKPOINT_NS, CONFIG_KEY_READ, CONFIG_KEY_REPLAY_STATE, CONFIG_KEY_RESUME_MAP, CONFIG_KEY_RESUMING, CONFIG_KEY_SCRATCHPAD, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, NULL_TASK_ID, PUSH, RESUME, _isOverwriteValue, _isSend, isCommand } from "../constants.js";
1
+ import { CONFIG_KEY_CHECKPOINT_ID, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_CHECKPOINT_NS, CONFIG_KEY_READ, CONFIG_KEY_REPLAY_STATE, CONFIG_KEY_RESUME_MAP, CONFIG_KEY_RESUMING, CONFIG_KEY_SCRATCHPAD, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, NULL_TASK_ID, Overwrite, PUSH, RESUME, _getOverwriteValue, _isOverwriteValue, _isSend, isCommand } from "../constants.js";
2
2
  import { EmptyInputError, GraphInterrupt, isGraphInterrupt } from "../errors.js";
3
- import { DELTA_WRITES_VERSIONED, channelsFromCheckpoint, createCheckpoint, deltaChannelsToSnapshot, exitDeltaTaskId, isDeltaChannel, isDeltaWritesVersioned } from "../channels/base.js";
3
+ import { DELTA_WRITES_VERSIONED, channelsFromCheckpoint, createCheckpoint, deltaChannelWrites, deltaChannelsToSnapshot, deltaChannelsWithPendingWrites, exitDeltaLateTaskId, exitDeltaTaskId, isDeltaChannel, isDeltaWritesVersioned } from "../channels/base.js";
4
4
  import { gatherIterator, gatherIteratorSync, prefixGenerator } from "../utils.js";
5
+ import { advanceDeltaCounters, checkpointSuperseded } from "./checkpoint.js";
5
6
  import { isXXH3 } from "../hash.js";
6
7
  import { mapCommand, mapInput, mapOutputUpdates, mapOutputValues, readChannels } from "./io.js";
7
8
  import { getNewChannelVersions, patchConfigurable } from "./utils/index.js";
8
- import { _applyWrites, _prepareNextTasks, _prepareNodeErrorHandlerTask, _prepareSingleTask, increment, sanitizeUntrackedValuesInSend, shouldInterrupt } from "./algo.js";
9
+ import { _applyWrites, _prepareNextTasks, _prepareNodeErrorHandlerTask, _prepareSingleTask, increment, sanitizeUntrackedValuesInSend, shouldInterrupt, taskPathStr } from "./algo.js";
9
10
  import { mapDebugCheckpoint, mapDebugTaskResults, mapDebugTasks, printStepTasks } from "./debug.js";
11
+ import { isMessagesDeltaReducer } from "../graph/messages_reducer.js";
10
12
  import { ReplayState } from "./replay.js";
11
13
  import { createDuplexStream } from "./stream.js";
12
- import { AsyncBatchedStore, BaseCache, WRITES_IDX_MAP, copyCheckpoint, emptyCheckpoint } from "@langchain/langgraph-checkpoint";
14
+ import { AsyncBatchedStore, BaseCache, WRITES_IDX_MAP, copyCheckpoint, emptyCheckpoint, uuid5 } from "@langchain/langgraph-checkpoint";
13
15
  import { v4 } from "@langchain/core/utils/uuid";
14
- import { BaseMessage } from "@langchain/core/messages";
16
+ import { BaseMessage, coerceMessageLikeToMessage } from "@langchain/core/messages";
15
17
  //#region src/pregel/loop.ts
16
18
  const INPUT_DONE = Symbol.for("INPUT_DONE");
17
19
  const INPUT_RESUMING = Symbol.for("INPUT_RESUMING");
18
20
  const DEFAULT_LOOP_LIMIT = 25;
19
21
  /**
20
- * Recursively assign a stable UUID to any {@link BaseMessage} (in a value, an
21
- * array, or an object's values) that is missing an `id`. Used so DeltaChannel
22
- * writes — replayed on every read — reconstruct identical message identities.
22
+ * Give the messages written to DeltaChannels stable ids, and return the writes
23
+ * in `writes` that go to a DeltaChannel.
24
+ *
25
+ * A DeltaChannel replays its saved writes on every read, and its reducer may
26
+ * not assign missing ids (`messagesDeltaReducer` doesn't), so a message saved
27
+ * without an id comes back without one, or with a new one on every read, and
28
+ * nothing can target it by id, for example `RemoveMessage`.
29
+ *
30
+ * In a channel whose reducer is {@link messagesDeltaReducer}, every
31
+ * message-like it accepts (a message, a plain object, a string, a
32
+ * `[role, content]` pair; alone, in an array or in an `Overwrite`) gets an id.
33
+ * A plain message object stays plain, with the id on a copy, except in an
34
+ * `Overwrite`, whose value the channel keeps as it is, so there it becomes a
35
+ * message. Other DeltaChannels may hold other data, so there only message
36
+ * objects get ids. Anything that can't become a message is left as it is, so
37
+ * it fails where it always has, when the reducer reads it.
38
+ *
39
+ * A write that changes is replaced in `writes` by a new `[channel, value]`
40
+ * pair: arrays and pairs the caller passed in are never changed, only the ids
41
+ * of message objects.
23
42
  */
24
- function ensureMessageIds(value) {
25
- if (value == null || typeof value !== "object") return;
43
+ function ensureDeltaMessageIds(channels, writes) {
44
+ const deltaWrites = [];
45
+ for (let i = 0; i < writes.length; i += 1) {
46
+ const [channelName, value] = writes[i];
47
+ const channel = channels[String(channelName)];
48
+ if (!isDeltaChannel(channel)) continue;
49
+ if (isMessagesDeltaReducer(channel.reducer)) {
50
+ const newValue = withMessageIds(value);
51
+ if (newValue !== value) writes[i] = [channelName, newValue];
52
+ } else stampMessageIds(value);
53
+ deltaWrites.push(writes[i]);
54
+ }
55
+ return deltaWrites;
56
+ }
57
+ function withMessageIds(value) {
58
+ const [isOverwrite, overwriteValue] = _getOverwriteValue(value);
59
+ if (!isOverwrite) return Array.isArray(value) ? mapItems(value, writtenMessageWithId) : writtenMessageWithId(value);
60
+ if (!Array.isArray(overwriteValue)) return value;
61
+ const items = mapItems(overwriteValue, messageWithId);
62
+ return items === overwriteValue ? value : new Overwrite(items);
63
+ }
64
+ function mapItems(items, fn) {
65
+ const mapped = items.map(fn);
66
+ return mapped.every((item, i) => item === items[i]) ? items : mapped;
67
+ }
68
+ function writtenMessageWithId(item) {
69
+ if (!isPlainObject(item)) return messageWithId(item);
70
+ try {
71
+ if (coerceMessageLikeToMessage(item).id) return item;
72
+ } catch {
73
+ return item;
74
+ }
75
+ return {
76
+ ...item,
77
+ id: v4()
78
+ };
79
+ }
80
+ function messageWithId(item) {
81
+ if (BaseMessage.isInstance(item)) {
82
+ stampMessageIds(item);
83
+ return item;
84
+ }
85
+ let message;
86
+ try {
87
+ message = coerceMessageLikeToMessage(item);
88
+ } catch {
89
+ return item;
90
+ }
91
+ if (!message.id) setMessageId(message);
92
+ return message;
93
+ }
94
+ function isPlainObject(item) {
95
+ if (typeof item !== "object" || item === null) return false;
96
+ const proto = Object.getPrototypeOf(item);
97
+ return (proto === Object.prototype || proto === null) && !("lc" in item);
98
+ }
99
+ function stampMessageIds(value) {
26
100
  if (BaseMessage.isInstance(value)) {
27
- const msg = value;
28
- if (msg.id == null) {
29
- msg.id = v4();
30
- if (msg.lc_kwargs != null) msg.lc_kwargs.id = msg.id;
31
- }
101
+ if (value.id == null) setMessageId(value);
32
102
  return;
33
103
  }
34
104
  if (Array.isArray(value)) {
35
- for (const item of value) ensureMessageIds(item);
105
+ for (const item of value) stampMessageIds(item);
36
106
  return;
37
107
  }
108
+ const [isOverwrite, overwriteValue] = _getOverwriteValue(value);
109
+ if (isOverwrite) stampMessageIds(overwriteValue);
110
+ }
111
+ function setMessageId(message) {
112
+ message.id = v4();
113
+ if (message.lc_kwargs != null) message.lc_kwargs.id = message.id;
38
114
  }
39
115
  /**
40
116
  * Split a serialized checkpoint namespace into its path segments.
@@ -111,20 +187,67 @@ var PregelLoop = class PregelLoop {
111
187
  checkpointIdSaved;
112
188
  /**
113
189
  * Exit-mode accumulator of DeltaChannel writes across the whole run, as
114
- * `[step, taskId, channel, value]`. `undefined` outside "exit" durability.
190
+ * `[step, taskId, taskPath, channel, value]` — see `_putExitDeltaWrites`
191
+ * for how they are ordered. `undefined` outside "exit" durability.
115
192
  */
116
193
  _exitDeltaWrites;
117
194
  /**
118
- * DeltaChannels that saw an Overwrite since the last checkpoint. These
119
- * channels are force-snapshotted at the next checkpoint so reconstruction
120
- * starts from the post-overwrite value and never has to replay across the
121
- * reset (the live `update` discards every sibling write in the overwriting
122
- * super-step). Cleared once the channel snapshots.
195
+ * The `(taskId, channel)` pairs whose delta writes are stored on the loaded
196
+ * checkpoint, which a resume addressed by `checkpoint_id` can rerun.
197
+ * Captured in the constructor, before this run's Command processing adds
198
+ * writes (mirrors Python's `_first`). Used by the exit-mode accumulator to
199
+ * skip the loaded writes on the first superstep — they are already stored
200
+ * on the checkpoint.
201
+ */
202
+ _storedDeltaWrites = /* @__PURE__ */ new Set();
203
+ /**
204
+ * This run's `Command` delta writes (`NULL_TASK_ID`), kept apart from the
205
+ * `NULL_TASK_ID` writes loaded with the checkpoint, which the first
206
+ * superstep skips. Written by the input handler; replayed into the
207
+ * exit-mode accumulator at the first superstep.
208
+ */
209
+ _exitCommandWrites = [];
210
+ /**
211
+ * The step of the first superstep this run completed (the loaded
212
+ * checkpoint's own), or `null` before it ticks. The exit-mode accumulator
213
+ * keeps that superstep's real task paths so its writes interleave with the
214
+ * loaded ones as live execution did; later supersteps get `~~`-prefixed
215
+ * synthetic paths (see `_putExitDeltaWrites`).
216
+ */
217
+ _exitFirstStep = null;
218
+ /**
219
+ * DeltaChannels that must snapshot at the next checkpoint, whatever their
220
+ * cadence counters say:
221
+ * - an Overwrite arrived since the last checkpoint, so sparse replay has to
222
+ * start from the post-overwrite value;
223
+ * - the checkpoint this run starts from has pending writes to them; see
224
+ * {@link deltaChannelsWithPendingWrites}.
225
+ * Cleared once the channel snapshots.
226
+ */
227
+ _deltaChannelsForcedSnapshot = /* @__PURE__ */ new Set();
228
+ /**
229
+ * Set by `_first` for a resume: the writes it loaded with the checkpoint,
230
+ * which the end of the superstep checks against the ones handed back to
231
+ * their tasks.
232
+ */
233
+ _resumeLoadedWrites = [];
234
+ /** Tasks a resume handed loaded writes back to. */
235
+ _reappliedTaskIds = /* @__PURE__ */ new Set();
236
+ /** Tasks that wrote during this run, the only ones exit mode saves at exit. */
237
+ _writtenTaskIds = /* @__PURE__ */ new Set();
238
+ /**
239
+ * A checkpoint addressed by `checkpoint_id` that the thread has moved past,
240
+ * so it may already have children.
123
241
  */
124
- _deltaChannelsWithOverwrite = /* @__PURE__ */ new Set();
242
+ addressedCheckpointSuperseded;
125
243
  /** Whether a real checkpoint was loaded from the saver at initialization. */
126
244
  _hasPersistedParent = false;
127
245
  /**
246
+ * Whether initialization loaded the thread's latest checkpoint, not one a
247
+ * `checkpoint_id` addressed, so nothing has been built on it yet.
248
+ */
249
+ _loadedLatest = false;
250
+ /**
128
251
  * Whether this branch carries {@link DELTA_WRITES_VERSIONED}: a graph with
129
252
  * a DeltaChannel on a new thread, or a loaded checkpoint that has it. Every
130
253
  * checkpoint this loop writes inherits it.
@@ -143,7 +266,9 @@ var PregelLoop = class PregelLoop {
143
266
  outputKeys;
144
267
  streamKeys;
145
268
  nodes;
146
- skipDoneTasks;
269
+ /** Whether the run points this loop at a checkpoint. */
270
+ addressed;
271
+ skipDoneTasks = true;
147
272
  prevCheckpointConfig;
148
273
  updatedChannels;
149
274
  status = "pending";
@@ -162,6 +287,14 @@ var PregelLoop = class PregelLoop {
162
287
  resumeAtHead;
163
288
  _checkpointerChainedPromise = Promise.resolve();
164
289
  /**
290
+ * The DeltaChannel writes this loop sent to the saver since the last
291
+ * checkpoint save was scheduled. That save waits for them, and a rejection
292
+ * skips it and every later save: a DeltaChannel is rebuilt from its writes
293
+ * along the parent chain, so a checkpoint saved past a missing write or
294
+ * parent reads back short for good.
295
+ */
296
+ _pendingDeltaWrites = [];
297
+ /**
165
298
  * Track a checkpointer promise, removing it from the set on success.
166
299
  * Failed promises are kept so that Promise.all() in the finally block
167
300
  * of _streamIterator can surface the error.
@@ -175,6 +308,7 @@ var PregelLoop = class PregelLoop {
175
308
  }, (error) => {
176
309
  throw error;
177
310
  });
311
+ tracked.catch(() => {});
178
312
  this.checkpointerPromises.add(tracked);
179
313
  }
180
314
  /**
@@ -222,18 +356,20 @@ var PregelLoop = class PregelLoop {
222
356
  this.checkpointPreviousVersions = params.checkpointPreviousVersions;
223
357
  this.channels = params.channels;
224
358
  this.checkpointPendingWrites = params.checkpointPendingWrites;
359
+ for (const [tid, ch] of this.checkpointPendingWrites) if (tid !== "00000000-0000-0000-0000-000000000000" && isDeltaChannel(this.channels[ch])) this._storedDeltaWrites.add(`${tid}\u0000${ch}`);
225
360
  this.step = params.step;
226
361
  this.stop = params.stop;
227
362
  this.config = params.config;
228
363
  this.checkpointConfig = params.checkpointConfig;
229
364
  this.isNested = params.isNested;
230
365
  this.resumeAtHead = params.resumeAtHead;
366
+ this.addressedCheckpointSuperseded = params.addressedCheckpointSuperseded;
231
367
  this.manager = params.manager;
232
368
  this.lifecycleEvents = params.hasGraphCallbacks ? [] : void 0;
233
369
  this.outputKeys = params.outputKeys;
234
370
  this.streamKeys = params.streamKeys;
235
371
  this.nodes = params.nodes;
236
- this.skipDoneTasks = params.skipDoneTasks;
372
+ this.addressed = params.addressed;
237
373
  this.store = params.store;
238
374
  this.cache = params.cache ? new AsyncBatchedCache(params.cache) : void 0;
239
375
  this.stream = params.stream;
@@ -247,6 +383,7 @@ var PregelLoop = class PregelLoop {
247
383
  this.control = this.config.control;
248
384
  this._exitDeltaWrites = this.durability === "exit" && this.checkpointer != null ? [] : void 0;
249
385
  this._hasPersistedParent = params.hasPersistedParent ?? false;
386
+ this._loadedLatest = params.loadedLatest ?? false;
250
387
  this._deltaWritesVersioned = params.deltaWritesVersioned ?? false;
251
388
  this._initialCheckpointConfig = params.checkpointConfig;
252
389
  this.checkpointIdSaved = params.checkpoint.id;
@@ -254,13 +391,11 @@ var PregelLoop = class PregelLoop {
254
391
  static async initialize(params) {
255
392
  let { config, stream } = params;
256
393
  if (stream !== void 0 && config.configurable?.["__pregel_stream"] !== void 0) stream = createDuplexStream(stream, config.configurable[CONFIG_KEY_STREAM]);
257
- const skipDoneTasks = config.configurable ? !("checkpoint_id" in config.configurable) : true;
258
394
  const scratchpad = config.configurable?.[CONFIG_KEY_SCRATCHPAD];
259
395
  if (config.configurable && scratchpad) {
260
396
  if (scratchpad.subgraphCounter > 0) config = patchConfigurable(config, { [CONFIG_KEY_CHECKPOINT_NS]: [config.configurable[CONFIG_KEY_CHECKPOINT_NS], scratchpad.subgraphCounter.toString()].join("|") });
261
397
  scratchpad.subgraphCounter += 1;
262
398
  }
263
- const requestedCheckpointId = config.configurable?.checkpoint_id;
264
399
  const isNested = CONFIG_KEY_READ in (config.configurable ?? {});
265
400
  if (!isNested && config.configurable?.checkpoint_ns !== void 0 && config.configurable?.checkpoint_ns !== "") config = patchConfigurable(config, {
266
401
  checkpoint_ns: "",
@@ -268,15 +403,21 @@ var PregelLoop = class PregelLoop {
268
403
  });
269
404
  let checkpointConfig = config;
270
405
  if (config.configurable?.checkpoint_id === void 0 && config.configurable?.["checkpoint_map"] !== void 0 && config.configurable?.["checkpoint_map"]?.[config.configurable?.checkpoint_ns]) checkpointConfig = patchConfigurable(config, { checkpoint_id: config.configurable[CONFIG_KEY_CHECKPOINT_MAP][config.configurable?.checkpoint_ns] });
406
+ const addressed = Boolean(checkpointConfig.configurable?.["checkpoint_id"] || config.configurable?.["__pregel_replay_state"]);
271
407
  const checkpointNamespace = checkpointNamespaceFromNs(config.configurable?.checkpoint_ns);
272
408
  let saved;
409
+ let loadedLatest = false;
273
410
  if (!params.checkpointer) saved = void 0;
274
411
  else if (checkpointConfig.configurable?.["checkpoint_id"]) saved = await params.checkpointer.getTuple(checkpointConfig);
275
412
  else if (config.configurable?.["__pregel_replay_state"]) {
276
413
  saved = await config.configurable[CONFIG_KEY_REPLAY_STATE].getCheckpoint(config.configurable?.["checkpoint_ns"] ?? "", params.checkpointer, checkpointConfig);
277
414
  if (config.configurable) delete config.configurable[CONFIG_KEY_RESUMING];
278
- } else saved = await params.checkpointer.getTuple(checkpointConfig);
415
+ } else {
416
+ saved = await params.checkpointer.getTuple(checkpointConfig);
417
+ loadedLatest = true;
418
+ }
279
419
  const hasPersistedParent = saved !== void 0;
420
+ const addressedConfig = checkpointConfig;
280
421
  if (!saved) saved = {
281
422
  config,
282
423
  checkpoint: emptyCheckpoint(),
@@ -304,12 +445,13 @@ var PregelLoop = class PregelLoop {
304
445
  const checkpointMap = config.configurable?.[CONFIG_KEY_CHECKPOINT_MAP];
305
446
  if (typeof currentCheckpointNamespace === "string" && currentCheckpointNamespace !== "" && typeof checkpointMap === "object" && checkpointMap !== null && currentCheckpointNamespace in checkpointMap && checkpointPendingWrites.length > 0) checkpointPendingWrites = checkpointPendingWrites.filter(([, channel]) => channel !== RESUME);
306
447
  let resumeAtHead = false;
307
- const threadId = checkpointConfig.configurable?.thread_id;
308
- const checkpointNs = checkpointConfig.configurable?.checkpoint_ns ?? "";
309
- if (params.checkpointer && requestedCheckpointId && typeof threadId === "string") resumeAtHead = (await params.checkpointer.getTuple({ configurable: {
310
- thread_id: threadId,
311
- checkpoint_ns: checkpointNs
312
- } }))?.config.configurable?.checkpoint_id === requestedCheckpointId && checkpointMetadata.source !== "update" && checkpointMetadata.source !== "fork";
448
+ let addressedCheckpointSuperseded = false;
449
+ if (params.checkpointer && hasPersistedParent) {
450
+ const superseded = await checkpointSuperseded(params.checkpointer, addressedConfig, saved);
451
+ const isUpdateOrFork = checkpointMetadata.source === "update" || checkpointMetadata.source === "fork";
452
+ resumeAtHead = addressedConfig.configurable?.checkpoint_id !== void 0 && !superseded && !isUpdateOrFork;
453
+ addressedCheckpointSuperseded = superseded;
454
+ }
313
455
  const deltaWritesVersioned = Object.values(params.channelSpecs).some(isDeltaChannel) && (!hasPersistedParent || isDeltaWritesVersioned(saved.metadata));
314
456
  const channels = await channelsFromCheckpoint(params.channelSpecs, checkpoint, {
315
457
  saver: params.checkpointer,
@@ -333,9 +475,10 @@ var PregelLoop = class PregelLoop {
333
475
  channels,
334
476
  isNested,
335
477
  resumeAtHead,
478
+ addressedCheckpointSuperseded,
336
479
  manager: params.manager,
337
480
  hasGraphCallbacks: params.hasGraphCallbacks,
338
- skipDoneTasks,
481
+ addressed,
339
482
  step,
340
483
  stop,
341
484
  checkpointPreviousVersions,
@@ -352,11 +495,14 @@ var PregelLoop = class PregelLoop {
352
495
  debug: params.debug,
353
496
  triggerToNodes: params.triggerToNodes,
354
497
  hasPersistedParent,
498
+ loadedLatest,
355
499
  deltaWritesVersioned
356
500
  });
357
501
  }
358
502
  _checkpointerPutAfterPrevious(input) {
359
- this._checkpointerChainedPromise = this._checkpointerChainedPromise.then(() => {
503
+ const pendingDeltaWrites = this._pendingDeltaWrites;
504
+ this._pendingDeltaWrites = [];
505
+ this._checkpointerChainedPromise = this._checkpointerChainedPromise.then(() => Promise.all(pendingDeltaWrites)).then(() => {
360
506
  return this.checkpointer?.put(input.config, input.checkpoint, input.metadata, input.newVersions);
361
507
  });
362
508
  this._trackCheckpointerPromise(this._checkpointerChainedPromise);
@@ -365,10 +511,14 @@ var PregelLoop = class PregelLoop {
365
511
  * Put writes for a task, to be read by the next tick.
366
512
  * @param taskId
367
513
  * @param writes
514
+ * @param cached The writes were served from the node cache: stream them as
515
+ * cached and don't write them to the cache again.
368
516
  */
369
- putWrites(taskId, writes) {
517
+ putWrites(taskId, writes, cached = false) {
370
518
  let writesCopy = writes;
371
519
  if (writesCopy.length === 0) return;
520
+ this._writtenTaskIds.add(taskId);
521
+ const deltaWrites = ensureDeltaMessageIds(this.channels, writes);
372
522
  if (writesCopy.every(([key]) => key in WRITES_IDX_MAP)) writesCopy = Array.from(new Map(writesCopy.map((w) => [w[0], w])).values());
373
523
  let hasUntrackedChannels = false;
374
524
  for (const key in this.channels) if (Object.prototype.hasOwnProperty.call(this.channels, key)) {
@@ -391,17 +541,18 @@ var PregelLoop = class PregelLoop {
391
541
  c,
392
542
  v
393
543
  ]);
394
- for (const [c, v] of writesToSave) {
395
- const channel = this.channels[c];
396
- if (channel != null && isDeltaChannel(channel)) ensureMessageIds(v);
397
- }
398
544
  const config = patchConfigurable(this.checkpointConfig, {
399
545
  [CONFIG_KEY_CHECKPOINT_NS]: this.config.configurable?.checkpoint_ns ?? "",
400
546
  [CONFIG_KEY_CHECKPOINT_ID]: this.checkpoint.id
401
547
  });
402
- if (this.durability !== "exit" && this.checkpointer != null) this._trackCheckpointerPromise(this.checkpointer.putWrites(config, writesToSave, taskId));
403
- if (this.tasks) this._outputWrites(taskId, writesCopy);
404
- if (!writes.length || !this.cache || !this.tasks) return;
548
+ if (this.durability !== "exit" && this.checkpointer != null) {
549
+ const task = this.tasks[taskId];
550
+ const saved = this.checkpointer.putWrites(config, writesToSave, taskId, task != null ? taskPathStr(task.path) : "");
551
+ this._trackCheckpointerPromise(saved);
552
+ if (deltaWrites.length > 0) this._pendingDeltaWrites.push(saved);
553
+ }
554
+ if (this.tasks) this._outputWrites(taskId, writesCopy, cached);
555
+ if (cached || !writes.length || !this.cache || !this.tasks) return;
405
556
  const task = this.tasks[taskId];
406
557
  if (task == null || task.cache_key == null) return;
407
558
  if (writes[0][0] === "__error__" || writes[0][0] === "__interrupt__") return;
@@ -411,6 +562,10 @@ var PregelLoop = class PregelLoop {
411
562
  ttl: task.cache_key.ttl
412
563
  }]);
413
564
  }
565
+ /** Streams the writes reapplied to tasks that finished in a previous run. */
566
+ _outputCachedWrites(tasks) {
567
+ for (const task of tasks) if (task.writes.length > 0) this._outputWrites(task.id, task.writes, true);
568
+ }
414
569
  _outputWrites(taskId, writes, cached = false) {
415
570
  const task = this.tasks[taskId];
416
571
  if (task !== void 0) {
@@ -469,19 +624,37 @@ var PregelLoop = class PregelLoop {
469
624
  const finishTaskList = Object.values(this.tasks);
470
625
  const writes = finishTaskList.flatMap((t) => t.writes);
471
626
  this.updatedChannels = _applyWrites(this.checkpoint, this.channels, finishTaskList, this.checkpointerGetNextVersion, this.triggerToNodes);
472
- for (const [ch, v] of writes) {
473
- const channel = this.channels[ch];
474
- if (channel != null && isDeltaChannel(channel) && _isOverwriteValue(v)) this._deltaChannelsWithOverwrite.add(ch);
627
+ for (const [ch, v] of deltaChannelWrites(this.channels, writes)) if (_isOverwriteValue(v)) this._deltaChannelsForcedSnapshot.add(ch);
628
+ if (this._resumeLoadedWrites.length > 0) {
629
+ for (const ch of deltaChannelsWithPendingWrites(this.channels, this._resumeLoadedWrites.filter(([tid]) => !this._reappliedTaskIds.has(tid)))) this._deltaChannelsForcedSnapshot.add(ch);
630
+ this._resumeLoadedWrites = [];
475
631
  }
476
632
  const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, writes, this.channels), "values"));
477
- if (this._exitDeltaWrites !== void 0) for (const [tid, ch, v] of this.checkpointPendingWrites) {
478
- const channel = this.channels[ch];
479
- if (channel != null && isDeltaChannel(channel)) this._exitDeltaWrites.push([
480
- this.step,
481
- tid,
482
- ch,
483
- v
484
- ]);
633
+ if (this._exitDeltaWrites !== void 0) {
634
+ const first = this._exitFirstStep === null;
635
+ if (first) {
636
+ this._exitFirstStep = this.step;
637
+ for (const [ch, v] of this._exitCommandWrites) this._exitDeltaWrites.push([
638
+ this.step,
639
+ NULL_TASK_ID,
640
+ "",
641
+ ch,
642
+ v
643
+ ]);
644
+ }
645
+ for (const [tid, ch, v] of this.checkpointPendingWrites) {
646
+ const channel = this.channels[ch];
647
+ if (channel == null || !isDeltaChannel(channel)) continue;
648
+ if (first && (tid === "00000000-0000-0000-0000-000000000000" || this._storedDeltaWrites.has(`${tid}\u0000${ch}`))) continue;
649
+ const task = this.tasks[tid];
650
+ this._exitDeltaWrites.push([
651
+ this.step,
652
+ tid,
653
+ task != null ? taskPathStr(task.path) : "",
654
+ ch,
655
+ v
656
+ ]);
657
+ }
485
658
  }
486
659
  this.checkpointPendingWrites = [];
487
660
  await this._putCheckpoint({ source: "loop" });
@@ -513,25 +686,24 @@ var PregelLoop = class PregelLoop {
513
686
  this.status = "done";
514
687
  return false;
515
688
  }
516
- if (this.control != null && this.control.drainRequested) {
517
- this.status = "draining";
518
- return false;
519
- }
520
689
  if (this.skipDoneTasks && this.checkpointPendingWrites.length > 0) {
521
- for (const [tid, k, v] of this.checkpointPendingWrites) {
522
- if (k === "__error__" || k === "__error_source_node__" || k === "__interrupt__" || k === "__resume__") continue;
523
- const task = taskList.find((t) => t.id === tid);
524
- if (task) task.writes.push([k, v]);
525
- }
690
+ this._reapplyWritesToSucceededNodes(this.tasks);
526
691
  this._resumeErrorHandlersIfApplicable();
527
692
  taskList = Object.values(this.tasks);
528
- for (const task of taskList) if (task.writes.length > 0) this._outputWrites(task.id, task.writes, true);
529
693
  }
530
- if (taskList.every((task) => task.writes.length > 0)) return this.tick({ inputKeys });
694
+ if (taskList.every((task) => task.writes.length > 0)) {
695
+ this._outputCachedWrites(taskList);
696
+ return this.tick({ inputKeys });
697
+ }
531
698
  if (shouldInterrupt(this.checkpoint, this.interruptBefore, taskList)) {
532
699
  this.status = "interrupt_before";
533
700
  throw new GraphInterrupt();
534
701
  }
702
+ if (this.control != null && this.control.drainRequested) {
703
+ this.status = "draining";
704
+ return false;
705
+ }
706
+ this._outputCachedWrites(taskList);
535
707
  if (this.stream.modes.has("tasks") || this.stream.modes.has("debug")) {
536
708
  const debugOutput = await gatherIterator(prefixGenerator(mapDebugTasks(taskList), "tasks"));
537
709
  this._emit(debugOutput);
@@ -583,7 +755,7 @@ var PregelLoop = class PregelLoop {
583
755
  this.tasks[pushed.id] = pushed;
584
756
  if (this.skipDoneTasks) this._matchWrites({ [pushed.id]: pushed });
585
757
  const tasks = await this._matchCachedWrites();
586
- for (const { task } of tasks) this._outputWrites(task.id, task.writes, true);
758
+ for (const { task } of tasks) this.putWrites(task.id, task.writes, true);
587
759
  return pushed;
588
760
  }
589
761
  /**
@@ -667,33 +839,54 @@ var PregelLoop = class PregelLoop {
667
839
  }
668
840
  async _first(inputKeys) {
669
841
  const { configurable } = this.config;
670
- if (isCommand(this.input)) {
671
- const hasResume = this.input.resume != null;
672
- if (this.input.resume != null && typeof this.input.resume === "object" && Object.keys(this.input.resume).every(isXXH3)) {
842
+ const inputIsCommand = isCommand(this.input);
843
+ const isTimeTraveling = this.addressed && (this.isNested && configurable?.["checkpoint_ns"] !== void 0 && configurable?.["checkpoint_ns"] !== "" && configurable?.["checkpoint_map"] !== void 0 && configurable["checkpoint_ns"] in configurable["checkpoint_map"] || !(inputIsCommand && this.input.resume != null || configurable?.["__pregel_resuming"] === true || this.resumeAtHead));
844
+ this.skipDoneTasks = !(isTimeTraveling || this.addressedCheckpointSuperseded);
845
+ const isUpdateOrFork = this.checkpointMetadata.source === "update" || this.checkpointMetadata.source === "fork";
846
+ const forks = isTimeTraveling && (inputIsCommand || this.isResuming) && (!isUpdateOrFork || this.addressedCheckpointSuperseded);
847
+ const carried = [];
848
+ const reapplies = this.isResuming && this.skipDoneTasks;
849
+ this._resumeLoadedWrites = reapplies ? this.checkpointPendingWrites.filter(([tid]) => tid !== NULL_TASK_ID) : [];
850
+ this._reappliedTaskIds = /* @__PURE__ */ new Set();
851
+ this._deltaChannelsForcedSnapshot = reapplies ? /* @__PURE__ */ new Set() : deltaChannelsWithPendingWrites(this.channels, this.checkpointPendingWrites);
852
+ if (inputIsCommand) {
853
+ const command = this.input;
854
+ const hasResume = command.resume != null;
855
+ if (command.resume != null && typeof command.resume === "object" && Object.keys(command.resume).every(isXXH3)) {
673
856
  this.config.configurable ??= {};
674
- this.config.configurable[CONFIG_KEY_RESUME_MAP] = this.input.resume;
857
+ this.config.configurable[CONFIG_KEY_RESUME_MAP] = command.resume;
675
858
  }
676
859
  if (hasResume && this.checkpointer == null) throw new Error("Cannot use Command(resume=...) without checkpointer");
677
860
  const writes = {};
678
- for (const [tid, key, value] of mapCommand(this.input, this.checkpointPendingWrites)) {
861
+ for (const [tid, key, value] of mapCommand(command, this.checkpointPendingWrites)) {
679
862
  writes[tid] ??= [];
680
863
  writes[tid].push([key, value]);
681
864
  }
682
865
  if (Object.keys(writes).length === 0) throw new EmptyInputError("Received empty Command input");
683
- for (const [tid, ws] of Object.entries(writes)) this.putWrites(tid, ws);
866
+ for (const [tid, ws] of Object.entries(writes)) if (forks) {
867
+ ensureDeltaMessageIds(this.channels, ws);
868
+ for (const [c, v] of ws) carried.push([
869
+ tid,
870
+ c,
871
+ v
872
+ ]);
873
+ } else {
874
+ this.putWrites(tid, ws);
875
+ if (this._exitDeltaWrites !== void 0 && tid === "00000000-0000-0000-0000-000000000000") this._exitCommandWrites.push(...deltaChannelWrites(this.channels, ws));
876
+ }
877
+ for (const ch of deltaChannelsWithPendingWrites(this.channels, carried)) this._deltaChannelsForcedSnapshot.add(ch);
684
878
  }
685
- const nullWrites = (this.checkpointPendingWrites ?? []).filter((w) => w[0] === NULL_TASK_ID).map((w) => w.slice(1));
879
+ const nullWrites = [...this.checkpointPendingWrites ?? [], ...carried].filter((w) => w[0] === NULL_TASK_ID).map((w) => w.slice(1));
686
880
  if (nullWrites.length > 0) _applyWrites(this.checkpoint, this.channels, [{
687
881
  name: INPUT,
688
882
  writes: nullWrites,
689
883
  triggers: []
690
884
  }], this.checkpointerGetNextVersion, this.triggerToNodes);
691
- const inputIsCommand = isCommand(this.input);
692
885
  const isCommandUpdateOrGoto = inputIsCommand && nullWrites.length > 0;
693
- const isTimeTraveling = this.isReplaying && (this.isNested && configurable?.["checkpoint_ns"] !== void 0 && configurable?.["checkpoint_ns"] !== "" && configurable?.["checkpoint_map"] !== void 0 && configurable["checkpoint_ns"] in configurable["checkpoint_map"] || !(inputIsCommand && this.input.resume != null || configurable?.["__pregel_resuming"] === true || this.resumeAtHead));
694
886
  if (isTimeTraveling) this.checkpointPendingWrites = this.checkpointPendingWrites.filter((w) => w[1] !== RESUME);
695
- const scratchpad = configurable?.[CONFIG_KEY_SCRATCHPAD];
696
- if (scratchpad && scratchpad.nullResume !== void 0) this.putWrites(NULL_TASK_ID, [[RESUME, scratchpad.nullResume]]);
887
+ const nullResume = (configurable?.[CONFIG_KEY_SCRATCHPAD])?.nullResume;
888
+ if (nullResume !== void 0 && !forks) this.putWrites(NULL_TASK_ID, [[RESUME, nullResume]]);
889
+ let replayBound;
697
890
  const cachedIsResuming = this.isResuming;
698
891
  if (cachedIsResuming || isCommandUpdateOrGoto) {
699
892
  const interruptSeen = { ...this.checkpoint.versions_seen[INTERRUPT] };
@@ -702,9 +895,11 @@ var PregelLoop = class PregelLoop {
702
895
  if (this.checkpoint.channel_versions[channelName] !== void 0) interruptSeen[channelName] = this.checkpoint.channel_versions[channelName];
703
896
  }
704
897
  this.checkpoint.versions_seen[INTERRUPT] = interruptSeen;
705
- if (isTimeTraveling && this.checkpointMetadata.source !== "update" && this.checkpointMetadata.source !== "fork") {
898
+ if (forks) {
899
+ if (isUpdateOrFork && this.prevCheckpointConfig) replayBound = this.prevCheckpointConfig;
706
900
  this.checkpointPendingWrites = this.checkpointPendingWrites.filter((w) => w[1] !== INTERRUPT);
707
901
  await this._putCheckpoint({ source: "fork" });
902
+ if (nullResume !== void 0) this.putWrites(NULL_TASK_ID, [[RESUME, nullResume]]);
708
903
  }
709
904
  const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, true, this.channels), "values"));
710
905
  if (cachedIsResuming) this.input = INPUT_RESUMING;
@@ -716,25 +911,24 @@ var PregelLoop = class PregelLoop {
716
911
  } else {
717
912
  const inputWrites = await gatherIterator(mapInput(inputKeys, this.input));
718
913
  if (inputWrites.length > 0) {
914
+ const deltaInput = ensureDeltaMessageIds(this.channels, inputWrites);
719
915
  const discardTasks = _prepareNextTasks(this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, this.config, true, { step: this.step });
720
916
  this.updatedChannels = _applyWrites(this.checkpoint, this.channels, Object.values(discardTasks).concat([{
721
917
  name: INPUT,
722
918
  writes: inputWrites,
723
919
  triggers: []
724
920
  }]), this.checkpointerGetNextVersion, this.triggerToNodes);
725
- const deltaInput = inputWrites.filter(([c]) => {
726
- const channel = this.channels[c];
727
- return channel != null && isDeltaChannel(channel);
728
- });
729
- for (const [c, v] of deltaInput) if (_isOverwriteValue(v)) this._deltaChannelsWithOverwrite.add(c);
921
+ for (const [c, v] of deltaInput) if (_isOverwriteValue(v)) this._deltaChannelsForcedSnapshot.add(c);
730
922
  if (deltaInput.length > 0) {
731
923
  if (this._exitDeltaWrites !== void 0) for (const [c, v] of deltaInput) this._exitDeltaWrites.push([
732
924
  this.step,
733
925
  NULL_TASK_ID,
926
+ "",
734
927
  c,
735
928
  v
736
929
  ]);
737
- else if (this.checkpointer != null) this.putWrites(NULL_TASK_ID, deltaInput);
930
+ else if (this.checkpointer != null) if (this._hasPersistedParent && this._loadedLatest) this.putWrites(uuid5(INPUT, this.checkpoint.id), deltaInput);
931
+ else for (const [c] of deltaInput) this._deltaChannelsForcedSnapshot.add(c);
738
932
  }
739
933
  await this._putCheckpoint({ source: "input" });
740
934
  this.input = INPUT_DONE;
@@ -745,7 +939,8 @@ var PregelLoop = class PregelLoop {
745
939
  let replayState;
746
940
  if (isTimeTraveling) {
747
941
  let replayCheckpointId = this.checkpoint.id;
748
- if ((this.checkpointMetadata.source === "update" || this.checkpointMetadata.source === "fork") && this.prevCheckpointConfig) replayCheckpointId = this.prevCheckpointConfig.configurable?.["checkpoint_id"] ?? replayCheckpointId;
942
+ if (replayBound !== void 0) replayCheckpointId = replayBound.configurable?.["checkpoint_id"] ?? replayCheckpointId;
943
+ else if ((this.checkpointMetadata.source === "update" || this.checkpointMetadata.source === "fork") && this.prevCheckpointConfig) replayCheckpointId = this.prevCheckpointConfig.configurable?.["checkpoint_id"] ?? replayCheckpointId;
749
944
  replayState = new ReplayState(replayCheckpointId);
750
945
  }
751
946
  this.config = patchConfigurable(this.config, {
@@ -855,15 +1050,7 @@ var PregelLoop = class PregelLoop {
855
1050
  };
856
1051
  let newCounters;
857
1052
  if (!exiting) {
858
- const prevCounters = this.checkpointMetadata.counters_since_delta_snapshot ?? {};
859
- newCounters = {};
860
- const updated = this.updatedChannels ?? /* @__PURE__ */ new Set();
861
- for (const chName in this.channels) {
862
- if (!Object.prototype.hasOwnProperty.call(this.channels, chName)) continue;
863
- if (!isDeltaChannel(this.channels[chName])) continue;
864
- const [u, s] = prevCounters[chName] ?? [0, 0];
865
- newCounters[chName] = [updated.has(chName) ? u + 1 : u, s + 1];
866
- }
1053
+ newCounters = advanceDeltaCounters(this.channels, this.updatedChannels ?? /* @__PURE__ */ new Set(), this.checkpointMetadata.counters_since_delta_snapshot);
867
1054
  this.checkpointMetadata = {
868
1055
  ...inputMetadata,
869
1056
  step: this.step,
@@ -872,16 +1059,17 @@ var PregelLoop = class PregelLoop {
872
1059
  } else newCounters = { ...this.checkpointMetadata.counters_since_delta_snapshot ?? {} };
873
1060
  if (this._deltaWritesVersioned) this.checkpointMetadata[DELTA_WRITES_VERSIONED] = true;
874
1061
  const channelsToSnapshot = doCheckpoint ? deltaChannelsToSnapshot(this.channels, newCounters, this._deltaWritesVersioned ? this.checkpoint.channel_versions : void 0) : /* @__PURE__ */ new Set();
875
- if (doCheckpoint) for (const ch of this._deltaChannelsWithOverwrite) channelsToSnapshot.add(ch);
1062
+ if (doCheckpoint) for (const ch of this._deltaChannelsForcedSnapshot) channelsToSnapshot.add(ch);
876
1063
  this.checkpoint = createCheckpoint(this.checkpoint, doCheckpoint ? this.channels : void 0, this.step, {
877
1064
  id: exiting ? this.checkpoint.id : void 0,
878
1065
  channelsToSnapshot,
879
- updatedChannels: this.updatedChannels,
880
- getNextVersion: doCheckpoint ? (current) => this.checkpointerGetNextVersion(current) : void 0
1066
+ storedVersions: this.checkpointPreviousVersions,
1067
+ getNextVersion: doCheckpoint ? (current) => this.checkpointerGetNextVersion(current) : void 0,
1068
+ triggerToNodes: this.triggerToNodes
881
1069
  });
882
1070
  for (const k of channelsToSnapshot) {
883
1071
  newCounters[k] = [0, 0];
884
- this._deltaChannelsWithOverwrite.delete(k);
1072
+ this._deltaChannelsForcedSnapshot.delete(k);
885
1073
  }
886
1074
  const nonZero = {};
887
1075
  for (const k in newCounters) {
@@ -907,10 +1095,11 @@ var PregelLoop = class PregelLoop {
907
1095
  */
908
1096
  async _putExitDeltaWrites() {
909
1097
  if (this._exitDeltaWrites === void 0 || this._exitDeltaWrites.length === 0 || this.checkpointer == null || this._initialCheckpointConfig === void 0) return;
1098
+ if (this._hasPersistedParent && !this._loadedLatest) for (const [, , , ch] of this._exitDeltaWrites) this._deltaChannelsForcedSnapshot.add(ch);
910
1099
  const counters = this.checkpointMetadata.counters_since_delta_snapshot ?? {};
911
1100
  const channelsToSnapshot = deltaChannelsToSnapshot(this.channels, counters, this._deltaWritesVersioned ? this.checkpoint.channel_versions : void 0);
912
- for (const ch of this._deltaChannelsWithOverwrite) channelsToSnapshot.add(ch);
913
- const pending = this._exitDeltaWrites.filter(([, , ch]) => !channelsToSnapshot.has(ch));
1101
+ for (const ch of this._deltaChannelsForcedSnapshot) channelsToSnapshot.add(ch);
1102
+ const pending = this._exitDeltaWrites.filter(([, , , ch]) => !channelsToSnapshot.has(ch));
914
1103
  if (pending.length === 0) return;
915
1104
  let anchorConfig;
916
1105
  if (this._hasPersistedParent) anchorConfig = this._initialCheckpointConfig;
@@ -920,11 +1109,16 @@ var PregelLoop = class PregelLoop {
920
1109
  stubCp.ts = (/* @__PURE__ */ new Date()).toISOString();
921
1110
  const stubPutConfig = patchConfigurable(this._initialCheckpointConfig, { [CONFIG_KEY_CHECKPOINT_ID]: void 0 });
922
1111
  anchorConfig = patchConfigurable(this._initialCheckpointConfig, { [CONFIG_KEY_CHECKPOINT_ID]: stubCp.id });
923
- this._trackCheckpointerPromise(this.checkpointer.put(stubPutConfig, stubCp, {
924
- source: "loop",
925
- step: -2,
926
- parents: {}
927
- }, {}));
1112
+ this._checkpointerPutAfterPrevious({
1113
+ config: stubPutConfig,
1114
+ checkpoint: stubCp,
1115
+ metadata: {
1116
+ source: "loop",
1117
+ step: -2,
1118
+ parents: {}
1119
+ },
1120
+ newVersions: {}
1121
+ });
928
1122
  this.checkpointConfig = anchorConfig;
929
1123
  }
930
1124
  const anchorWriteConfig = patchConfigurable(anchorConfig, {
@@ -933,23 +1127,36 @@ var PregelLoop = class PregelLoop {
933
1127
  });
934
1128
  const grouped = /* @__PURE__ */ new Map();
935
1129
  const order = [];
936
- for (const [step, tid, ch, v] of pending) {
937
- const key = `${step}\u0000${tid}`;
1130
+ for (const [step, tid, path, ch, v] of pending) {
1131
+ let synthTid;
1132
+ let synthPath;
1133
+ if (tid === "00000000-0000-0000-0000-000000000000") {
1134
+ synthTid = exitDeltaTaskId(step, tid);
1135
+ synthPath = "";
1136
+ } else if (step === this._exitFirstStep) {
1137
+ synthTid = exitDeltaTaskId(step, tid);
1138
+ synthPath = path;
1139
+ } else {
1140
+ synthTid = exitDeltaLateTaskId(step, tid);
1141
+ synthPath = `~~${taskPathStr(step)}${path}`;
1142
+ }
1143
+ const key = `${synthTid}\u0000${synthPath}`;
938
1144
  let group = grouped.get(key);
939
1145
  if (group === void 0) {
940
1146
  group = [];
941
1147
  grouped.set(key, group);
942
1148
  order.push({
943
1149
  key,
944
- step,
945
- tid
1150
+ tid: synthTid,
1151
+ path: synthPath
946
1152
  });
947
1153
  }
948
1154
  group.push([ch, v]);
949
1155
  }
950
- for (const { key, step, tid } of order) {
951
- const synthTid = exitDeltaTaskId(step, tid);
952
- this._trackCheckpointerPromise(this.checkpointer.putWrites(anchorWriteConfig, grouped.get(key), synthTid));
1156
+ for (const { key, tid, path } of order) {
1157
+ const saved = this.checkpointer.putWrites(anchorWriteConfig, grouped.get(key), tid, path);
1158
+ this._trackCheckpointerPromise(saved);
1159
+ this._pendingDeltaWrites.push(saved);
953
1160
  }
954
1161
  }
955
1162
  _flushPendingWrites() {
@@ -961,21 +1168,36 @@ var PregelLoop = class PregelLoop {
961
1168
  });
962
1169
  const byTask = {};
963
1170
  for (const [tid, key, value] of this.checkpointPendingWrites) {
1171
+ if (!this._writtenTaskIds.has(tid)) continue;
964
1172
  byTask[tid] ??= [];
965
1173
  byTask[tid].push([key, value]);
966
1174
  }
967
- for (const [tid, ws] of Object.entries(byTask)) this._trackCheckpointerPromise(this.checkpointer.putWrites(config, ws, tid));
1175
+ for (const [tid, ws] of Object.entries(byTask)) {
1176
+ const task = this.tasks[tid];
1177
+ this._trackCheckpointerPromise(this.checkpointer.putWrites(config, ws, tid, task != null ? taskPathStr(task.path) : ""));
1178
+ }
968
1179
  }
969
- _matchWrites(tasks) {
1180
+ /**
1181
+ * Hands the writes loaded with the checkpoint back to the finished tasks
1182
+ * among `tasks`. A failed or interrupted task keeps empty writes, so it
1183
+ * reruns or goes to its error handler.
1184
+ */
1185
+ _reapplyWritesToSucceededNodes(tasks) {
970
1186
  for (const [tid, k, v] of this.checkpointPendingWrites) {
971
- if (k === "__error__" || k === "__interrupt__" || k === "__resume__") continue;
972
- const task = Object.values(tasks).find((t) => t.id === tid);
973
- if (task) task.writes.push([k, v]);
1187
+ if (k === "__error__" || k === "__error_source_node__" || k === "__interrupt__" || k === "__resume__") continue;
1188
+ const task = tasks[tid];
1189
+ if (task) {
1190
+ task.writes.push([k, v]);
1191
+ this._reappliedTaskIds.add(tid);
1192
+ }
974
1193
  }
1194
+ }
1195
+ _matchWrites(tasks) {
1196
+ this._reapplyWritesToSucceededNodes(tasks);
975
1197
  for (const task of Object.values(tasks)) if (task.writes.length > 0) this._outputWrites(task.id, task.writes, true);
976
1198
  }
977
1199
  };
978
1200
  //#endregion
979
- export { PregelLoop };
1201
+ export { PregelLoop, ensureDeltaMessageIds };
980
1202
 
981
1203
  //# sourceMappingURL=loop.js.map