@langchain/langgraph 1.4.21 → 1.5.0

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 (51) hide show
  1. package/dist/channels/base.cjs +113 -8
  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 +110 -10
  8. package/dist/channels/base.js.map +1 -1
  9. package/dist/channels/index.cjs +2 -0
  10. package/dist/channels/index.d.cts +2 -2
  11. package/dist/channels/index.d.ts +2 -2
  12. package/dist/channels/index.js +2 -2
  13. package/dist/constants.cjs +14 -0
  14. package/dist/constants.cjs.map +1 -1
  15. package/dist/constants.d.cts.map +1 -1
  16. package/dist/constants.d.ts.map +1 -1
  17. package/dist/constants.js +13 -1
  18. package/dist/constants.js.map +1 -1
  19. package/dist/graph/index.cjs +1 -1
  20. package/dist/graph/index.js +1 -1
  21. package/dist/graph/messages_reducer.cjs +7 -0
  22. package/dist/graph/messages_reducer.cjs.map +1 -1
  23. package/dist/graph/messages_reducer.js +7 -1
  24. package/dist/graph/messages_reducer.js.map +1 -1
  25. package/dist/index.cjs +1 -1
  26. package/dist/index.js +1 -1
  27. package/dist/pregel/algo.cjs +46 -45
  28. package/dist/pregel/algo.cjs.map +1 -1
  29. package/dist/pregel/algo.d.cts.map +1 -1
  30. package/dist/pregel/algo.d.ts.map +1 -1
  31. package/dist/pregel/algo.js +47 -47
  32. package/dist/pregel/algo.js.map +1 -1
  33. package/dist/pregel/checkpoint.cjs +70 -0
  34. package/dist/pregel/checkpoint.cjs.map +1 -0
  35. package/dist/pregel/checkpoint.js +67 -0
  36. package/dist/pregel/checkpoint.js.map +1 -0
  37. package/dist/pregel/index.cjs +87 -34
  38. package/dist/pregel/index.cjs.map +1 -1
  39. package/dist/pregel/index.d.cts.map +1 -1
  40. package/dist/pregel/index.d.ts.map +1 -1
  41. package/dist/pregel/index.js +91 -38
  42. package/dist/pregel/index.js.map +1 -1
  43. package/dist/pregel/loop.cjs +325 -109
  44. package/dist/pregel/loop.cjs.map +1 -1
  45. package/dist/pregel/loop.js +330 -115
  46. package/dist/pregel/loop.js.map +1 -1
  47. package/dist/state/prebuilt/messages.cjs +1 -1
  48. package/dist/state/prebuilt/messages.js +1 -1
  49. package/dist/web.cjs +1 -1
  50. package/dist/web.js +1 -1
  51. 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,65 @@ 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.
123
226
  */
124
- _deltaChannelsWithOverwrite = /* @__PURE__ */ new Set();
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
+ /**
237
+ * An update or fork checkpoint addressed by `checkpoint_id` that the thread
238
+ * has moved past, so it may already have children.
239
+ */
240
+ addressedCheckpointSuperseded;
125
241
  /** Whether a real checkpoint was loaded from the saver at initialization. */
126
242
  _hasPersistedParent = false;
127
243
  /**
244
+ * Whether initialization loaded the thread's latest checkpoint, not one a
245
+ * `checkpoint_id` addressed, so nothing has been built on it yet.
246
+ */
247
+ _loadedLatest = false;
248
+ /**
128
249
  * Whether this branch carries {@link DELTA_WRITES_VERSIONED}: a graph with
129
250
  * a DeltaChannel on a new thread, or a loaded checkpoint that has it. Every
130
251
  * checkpoint this loop writes inherits it.
@@ -162,6 +283,14 @@ var PregelLoop = class PregelLoop {
162
283
  resumeAtHead;
163
284
  _checkpointerChainedPromise = Promise.resolve();
164
285
  /**
286
+ * The DeltaChannel writes this loop sent to the saver since the last
287
+ * checkpoint save was scheduled. That save waits for them, and a rejection
288
+ * skips it and every later save: a DeltaChannel is rebuilt from its writes
289
+ * along the parent chain, so a checkpoint saved past a missing write or
290
+ * parent reads back short for good.
291
+ */
292
+ _pendingDeltaWrites = [];
293
+ /**
165
294
  * Track a checkpointer promise, removing it from the set on success.
166
295
  * Failed promises are kept so that Promise.all() in the finally block
167
296
  * of _streamIterator can surface the error.
@@ -175,6 +304,7 @@ var PregelLoop = class PregelLoop {
175
304
  }, (error) => {
176
305
  throw error;
177
306
  });
307
+ tracked.catch(() => {});
178
308
  this.checkpointerPromises.add(tracked);
179
309
  }
180
310
  /**
@@ -222,12 +352,14 @@ var PregelLoop = class PregelLoop {
222
352
  this.checkpointPreviousVersions = params.checkpointPreviousVersions;
223
353
  this.channels = params.channels;
224
354
  this.checkpointPendingWrites = params.checkpointPendingWrites;
355
+ 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
356
  this.step = params.step;
226
357
  this.stop = params.stop;
227
358
  this.config = params.config;
228
359
  this.checkpointConfig = params.checkpointConfig;
229
360
  this.isNested = params.isNested;
230
361
  this.resumeAtHead = params.resumeAtHead;
362
+ this.addressedCheckpointSuperseded = params.addressedCheckpointSuperseded;
231
363
  this.manager = params.manager;
232
364
  this.lifecycleEvents = params.hasGraphCallbacks ? [] : void 0;
233
365
  this.outputKeys = params.outputKeys;
@@ -247,6 +379,7 @@ var PregelLoop = class PregelLoop {
247
379
  this.control = this.config.control;
248
380
  this._exitDeltaWrites = this.durability === "exit" && this.checkpointer != null ? [] : void 0;
249
381
  this._hasPersistedParent = params.hasPersistedParent ?? false;
382
+ this._loadedLatest = params.loadedLatest ?? false;
250
383
  this._deltaWritesVersioned = params.deltaWritesVersioned ?? false;
251
384
  this._initialCheckpointConfig = params.checkpointConfig;
252
385
  this.checkpointIdSaved = params.checkpoint.id;
@@ -260,7 +393,6 @@ var PregelLoop = class PregelLoop {
260
393
  if (scratchpad.subgraphCounter > 0) config = patchConfigurable(config, { [CONFIG_KEY_CHECKPOINT_NS]: [config.configurable[CONFIG_KEY_CHECKPOINT_NS], scratchpad.subgraphCounter.toString()].join("|") });
261
394
  scratchpad.subgraphCounter += 1;
262
395
  }
263
- const requestedCheckpointId = config.configurable?.checkpoint_id;
264
396
  const isNested = CONFIG_KEY_READ in (config.configurable ?? {});
265
397
  if (!isNested && config.configurable?.checkpoint_ns !== void 0 && config.configurable?.checkpoint_ns !== "") config = patchConfigurable(config, {
266
398
  checkpoint_ns: "",
@@ -270,13 +402,18 @@ var PregelLoop = class PregelLoop {
270
402
  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] });
271
403
  const checkpointNamespace = checkpointNamespaceFromNs(config.configurable?.checkpoint_ns);
272
404
  let saved;
405
+ let loadedLatest = false;
273
406
  if (!params.checkpointer) saved = void 0;
274
407
  else if (checkpointConfig.configurable?.["checkpoint_id"]) saved = await params.checkpointer.getTuple(checkpointConfig);
275
408
  else if (config.configurable?.["__pregel_replay_state"]) {
276
409
  saved = await config.configurable[CONFIG_KEY_REPLAY_STATE].getCheckpoint(config.configurable?.["checkpoint_ns"] ?? "", params.checkpointer, checkpointConfig);
277
410
  if (config.configurable) delete config.configurable[CONFIG_KEY_RESUMING];
278
- } else saved = await params.checkpointer.getTuple(checkpointConfig);
411
+ } else {
412
+ saved = await params.checkpointer.getTuple(checkpointConfig);
413
+ loadedLatest = true;
414
+ }
279
415
  const hasPersistedParent = saved !== void 0;
416
+ const addressedConfig = checkpointConfig;
280
417
  if (!saved) saved = {
281
418
  config,
282
419
  checkpoint: emptyCheckpoint(),
@@ -304,12 +441,13 @@ var PregelLoop = class PregelLoop {
304
441
  const checkpointMap = config.configurable?.[CONFIG_KEY_CHECKPOINT_MAP];
305
442
  if (typeof currentCheckpointNamespace === "string" && currentCheckpointNamespace !== "" && typeof checkpointMap === "object" && checkpointMap !== null && currentCheckpointNamespace in checkpointMap && checkpointPendingWrites.length > 0) checkpointPendingWrites = checkpointPendingWrites.filter(([, channel]) => channel !== RESUME);
306
443
  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";
444
+ let addressedCheckpointSuperseded = false;
445
+ if (params.checkpointer && hasPersistedParent) {
446
+ const superseded = await checkpointSuperseded(params.checkpointer, addressedConfig, saved);
447
+ const isUpdateOrFork = checkpointMetadata.source === "update" || checkpointMetadata.source === "fork";
448
+ resumeAtHead = addressedConfig.configurable?.checkpoint_id !== void 0 && !superseded && !isUpdateOrFork;
449
+ addressedCheckpointSuperseded = superseded && isUpdateOrFork;
450
+ }
313
451
  const deltaWritesVersioned = Object.values(params.channelSpecs).some(isDeltaChannel) && (!hasPersistedParent || isDeltaWritesVersioned(saved.metadata));
314
452
  const channels = await channelsFromCheckpoint(params.channelSpecs, checkpoint, {
315
453
  saver: params.checkpointer,
@@ -333,6 +471,7 @@ var PregelLoop = class PregelLoop {
333
471
  channels,
334
472
  isNested,
335
473
  resumeAtHead,
474
+ addressedCheckpointSuperseded,
336
475
  manager: params.manager,
337
476
  hasGraphCallbacks: params.hasGraphCallbacks,
338
477
  skipDoneTasks,
@@ -352,11 +491,14 @@ var PregelLoop = class PregelLoop {
352
491
  debug: params.debug,
353
492
  triggerToNodes: params.triggerToNodes,
354
493
  hasPersistedParent,
494
+ loadedLatest,
355
495
  deltaWritesVersioned
356
496
  });
357
497
  }
358
498
  _checkpointerPutAfterPrevious(input) {
359
- this._checkpointerChainedPromise = this._checkpointerChainedPromise.then(() => {
499
+ const pendingDeltaWrites = this._pendingDeltaWrites;
500
+ this._pendingDeltaWrites = [];
501
+ this._checkpointerChainedPromise = this._checkpointerChainedPromise.then(() => Promise.all(pendingDeltaWrites)).then(() => {
360
502
  return this.checkpointer?.put(input.config, input.checkpoint, input.metadata, input.newVersions);
361
503
  });
362
504
  this._trackCheckpointerPromise(this._checkpointerChainedPromise);
@@ -365,10 +507,13 @@ var PregelLoop = class PregelLoop {
365
507
  * Put writes for a task, to be read by the next tick.
366
508
  * @param taskId
367
509
  * @param writes
510
+ * @param cached The writes were served from the node cache: stream them as
511
+ * cached and don't write them to the cache again.
368
512
  */
369
- putWrites(taskId, writes) {
513
+ putWrites(taskId, writes, cached = false) {
370
514
  let writesCopy = writes;
371
515
  if (writesCopy.length === 0) return;
516
+ const deltaWrites = ensureDeltaMessageIds(this.channels, writes);
372
517
  if (writesCopy.every(([key]) => key in WRITES_IDX_MAP)) writesCopy = Array.from(new Map(writesCopy.map((w) => [w[0], w])).values());
373
518
  let hasUntrackedChannels = false;
374
519
  for (const key in this.channels) if (Object.prototype.hasOwnProperty.call(this.channels, key)) {
@@ -391,17 +536,18 @@ var PregelLoop = class PregelLoop {
391
536
  c,
392
537
  v
393
538
  ]);
394
- for (const [c, v] of writesToSave) {
395
- const channel = this.channels[c];
396
- if (channel != null && isDeltaChannel(channel)) ensureMessageIds(v);
397
- }
398
539
  const config = patchConfigurable(this.checkpointConfig, {
399
540
  [CONFIG_KEY_CHECKPOINT_NS]: this.config.configurable?.checkpoint_ns ?? "",
400
541
  [CONFIG_KEY_CHECKPOINT_ID]: this.checkpoint.id
401
542
  });
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;
543
+ if (this.durability !== "exit" && this.checkpointer != null) {
544
+ const task = this.tasks[taskId];
545
+ const saved = this.checkpointer.putWrites(config, writesToSave, taskId, task != null ? taskPathStr(task.path) : "");
546
+ this._trackCheckpointerPromise(saved);
547
+ if (deltaWrites.length > 0) this._pendingDeltaWrites.push(saved);
548
+ }
549
+ if (this.tasks) this._outputWrites(taskId, writesCopy, cached);
550
+ if (cached || !writes.length || !this.cache || !this.tasks) return;
405
551
  const task = this.tasks[taskId];
406
552
  if (task == null || task.cache_key == null) return;
407
553
  if (writes[0][0] === "__error__" || writes[0][0] === "__interrupt__") return;
@@ -411,6 +557,10 @@ var PregelLoop = class PregelLoop {
411
557
  ttl: task.cache_key.ttl
412
558
  }]);
413
559
  }
560
+ /** Streams the writes reapplied to tasks that finished in a previous run. */
561
+ _outputCachedWrites(tasks) {
562
+ for (const task of tasks) if (task.writes.length > 0) this._outputWrites(task.id, task.writes, true);
563
+ }
414
564
  _outputWrites(taskId, writes, cached = false) {
415
565
  const task = this.tasks[taskId];
416
566
  if (task !== void 0) {
@@ -469,19 +619,37 @@ var PregelLoop = class PregelLoop {
469
619
  const finishTaskList = Object.values(this.tasks);
470
620
  const writes = finishTaskList.flatMap((t) => t.writes);
471
621
  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);
622
+ for (const [ch, v] of deltaChannelWrites(this.channels, writes)) if (_isOverwriteValue(v)) this._deltaChannelsForcedSnapshot.add(ch);
623
+ if (this._resumeLoadedWrites.length > 0) {
624
+ for (const ch of deltaChannelsWithPendingWrites(this.channels, this._resumeLoadedWrites.filter(([tid]) => !this._reappliedTaskIds.has(tid)))) this._deltaChannelsForcedSnapshot.add(ch);
625
+ this._resumeLoadedWrites = [];
475
626
  }
476
627
  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
- ]);
628
+ if (this._exitDeltaWrites !== void 0) {
629
+ const first = this._exitFirstStep === null;
630
+ if (first) {
631
+ this._exitFirstStep = this.step;
632
+ for (const [ch, v] of this._exitCommandWrites) this._exitDeltaWrites.push([
633
+ this.step,
634
+ NULL_TASK_ID,
635
+ "",
636
+ ch,
637
+ v
638
+ ]);
639
+ }
640
+ for (const [tid, ch, v] of this.checkpointPendingWrites) {
641
+ const channel = this.channels[ch];
642
+ if (channel == null || !isDeltaChannel(channel)) continue;
643
+ if (first && (tid === "00000000-0000-0000-0000-000000000000" || this._storedDeltaWrites.has(`${tid}\u0000${ch}`))) continue;
644
+ const task = this.tasks[tid];
645
+ this._exitDeltaWrites.push([
646
+ this.step,
647
+ tid,
648
+ task != null ? taskPathStr(task.path) : "",
649
+ ch,
650
+ v
651
+ ]);
652
+ }
485
653
  }
486
654
  this.checkpointPendingWrites = [];
487
655
  await this._putCheckpoint({ source: "loop" });
@@ -513,25 +681,24 @@ var PregelLoop = class PregelLoop {
513
681
  this.status = "done";
514
682
  return false;
515
683
  }
516
- if (this.control != null && this.control.drainRequested) {
517
- this.status = "draining";
518
- return false;
519
- }
520
684
  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
- }
685
+ this._reapplyWritesToSucceededNodes(this.tasks);
526
686
  this._resumeErrorHandlersIfApplicable();
527
687
  taskList = Object.values(this.tasks);
528
- for (const task of taskList) if (task.writes.length > 0) this._outputWrites(task.id, task.writes, true);
529
688
  }
530
- if (taskList.every((task) => task.writes.length > 0)) return this.tick({ inputKeys });
689
+ if (taskList.every((task) => task.writes.length > 0)) {
690
+ this._outputCachedWrites(taskList);
691
+ return this.tick({ inputKeys });
692
+ }
531
693
  if (shouldInterrupt(this.checkpoint, this.interruptBefore, taskList)) {
532
694
  this.status = "interrupt_before";
533
695
  throw new GraphInterrupt();
534
696
  }
697
+ if (this.control != null && this.control.drainRequested) {
698
+ this.status = "draining";
699
+ return false;
700
+ }
701
+ this._outputCachedWrites(taskList);
535
702
  if (this.stream.modes.has("tasks") || this.stream.modes.has("debug")) {
536
703
  const debugOutput = await gatherIterator(prefixGenerator(mapDebugTasks(taskList), "tasks"));
537
704
  this._emit(debugOutput);
@@ -583,7 +750,7 @@ var PregelLoop = class PregelLoop {
583
750
  this.tasks[pushed.id] = pushed;
584
751
  if (this.skipDoneTasks) this._matchWrites({ [pushed.id]: pushed });
585
752
  const tasks = await this._matchCachedWrites();
586
- for (const { task } of tasks) this._outputWrites(task.id, task.writes, true);
753
+ for (const { task } of tasks) this.putWrites(task.id, task.writes, true);
587
754
  return pushed;
588
755
  }
589
756
  /**
@@ -667,33 +834,53 @@ var PregelLoop = class PregelLoop {
667
834
  }
668
835
  async _first(inputKeys) {
669
836
  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)) {
837
+ const inputIsCommand = isCommand(this.input);
838
+ 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));
839
+ const isUpdateOrFork = this.checkpointMetadata.source === "update" || this.checkpointMetadata.source === "fork";
840
+ const forks = isTimeTraveling && (inputIsCommand || this.isResuming) && (!isUpdateOrFork || this.addressedCheckpointSuperseded);
841
+ const carried = [];
842
+ const reapplies = this.isResuming && this.skipDoneTasks;
843
+ this._resumeLoadedWrites = reapplies ? this.checkpointPendingWrites.filter(([tid]) => tid !== NULL_TASK_ID) : [];
844
+ this._reappliedTaskIds = /* @__PURE__ */ new Set();
845
+ this._deltaChannelsForcedSnapshot = reapplies ? /* @__PURE__ */ new Set() : deltaChannelsWithPendingWrites(this.channels, this.checkpointPendingWrites);
846
+ if (inputIsCommand) {
847
+ const command = this.input;
848
+ const hasResume = command.resume != null;
849
+ if (command.resume != null && typeof command.resume === "object" && Object.keys(command.resume).every(isXXH3)) {
673
850
  this.config.configurable ??= {};
674
- this.config.configurable[CONFIG_KEY_RESUME_MAP] = this.input.resume;
851
+ this.config.configurable[CONFIG_KEY_RESUME_MAP] = command.resume;
675
852
  }
676
853
  if (hasResume && this.checkpointer == null) throw new Error("Cannot use Command(resume=...) without checkpointer");
677
854
  const writes = {};
678
- for (const [tid, key, value] of mapCommand(this.input, this.checkpointPendingWrites)) {
855
+ for (const [tid, key, value] of mapCommand(command, this.checkpointPendingWrites)) {
679
856
  writes[tid] ??= [];
680
857
  writes[tid].push([key, value]);
681
858
  }
682
859
  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);
860
+ for (const [tid, ws] of Object.entries(writes)) if (forks) {
861
+ ensureDeltaMessageIds(this.channels, ws);
862
+ for (const [c, v] of ws) carried.push([
863
+ tid,
864
+ c,
865
+ v
866
+ ]);
867
+ } else {
868
+ this.putWrites(tid, ws);
869
+ if (this._exitDeltaWrites !== void 0 && tid === "00000000-0000-0000-0000-000000000000") this._exitCommandWrites.push(...deltaChannelWrites(this.channels, ws));
870
+ }
871
+ for (const ch of deltaChannelsWithPendingWrites(this.channels, carried)) this._deltaChannelsForcedSnapshot.add(ch);
684
872
  }
685
- const nullWrites = (this.checkpointPendingWrites ?? []).filter((w) => w[0] === NULL_TASK_ID).map((w) => w.slice(1));
873
+ const nullWrites = [...this.checkpointPendingWrites ?? [], ...carried].filter((w) => w[0] === NULL_TASK_ID).map((w) => w.slice(1));
686
874
  if (nullWrites.length > 0) _applyWrites(this.checkpoint, this.channels, [{
687
875
  name: INPUT,
688
876
  writes: nullWrites,
689
877
  triggers: []
690
878
  }], this.checkpointerGetNextVersion, this.triggerToNodes);
691
- const inputIsCommand = isCommand(this.input);
692
879
  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
880
  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]]);
881
+ const nullResume = (configurable?.[CONFIG_KEY_SCRATCHPAD])?.nullResume;
882
+ if (nullResume !== void 0 && !forks) this.putWrites(NULL_TASK_ID, [[RESUME, nullResume]]);
883
+ let replayBound;
697
884
  const cachedIsResuming = this.isResuming;
698
885
  if (cachedIsResuming || isCommandUpdateOrGoto) {
699
886
  const interruptSeen = { ...this.checkpoint.versions_seen[INTERRUPT] };
@@ -702,9 +889,11 @@ var PregelLoop = class PregelLoop {
702
889
  if (this.checkpoint.channel_versions[channelName] !== void 0) interruptSeen[channelName] = this.checkpoint.channel_versions[channelName];
703
890
  }
704
891
  this.checkpoint.versions_seen[INTERRUPT] = interruptSeen;
705
- if (isTimeTraveling && this.checkpointMetadata.source !== "update" && this.checkpointMetadata.source !== "fork") {
892
+ if (forks) {
893
+ if (isUpdateOrFork && this.prevCheckpointConfig) replayBound = this.prevCheckpointConfig;
706
894
  this.checkpointPendingWrites = this.checkpointPendingWrites.filter((w) => w[1] !== INTERRUPT);
707
895
  await this._putCheckpoint({ source: "fork" });
896
+ if (nullResume !== void 0) this.putWrites(NULL_TASK_ID, [[RESUME, nullResume]]);
708
897
  }
709
898
  const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, true, this.channels), "values"));
710
899
  if (cachedIsResuming) this.input = INPUT_RESUMING;
@@ -716,25 +905,24 @@ var PregelLoop = class PregelLoop {
716
905
  } else {
717
906
  const inputWrites = await gatherIterator(mapInput(inputKeys, this.input));
718
907
  if (inputWrites.length > 0) {
908
+ const deltaInput = ensureDeltaMessageIds(this.channels, inputWrites);
719
909
  const discardTasks = _prepareNextTasks(this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, this.config, true, { step: this.step });
720
910
  this.updatedChannels = _applyWrites(this.checkpoint, this.channels, Object.values(discardTasks).concat([{
721
911
  name: INPUT,
722
912
  writes: inputWrites,
723
913
  triggers: []
724
914
  }]), 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);
915
+ for (const [c, v] of deltaInput) if (_isOverwriteValue(v)) this._deltaChannelsForcedSnapshot.add(c);
730
916
  if (deltaInput.length > 0) {
731
917
  if (this._exitDeltaWrites !== void 0) for (const [c, v] of deltaInput) this._exitDeltaWrites.push([
732
918
  this.step,
733
919
  NULL_TASK_ID,
920
+ "",
734
921
  c,
735
922
  v
736
923
  ]);
737
- else if (this.checkpointer != null) this.putWrites(NULL_TASK_ID, deltaInput);
924
+ else if (this.checkpointer != null) if (this._hasPersistedParent && this._loadedLatest) this.putWrites(uuid5(INPUT, this.checkpoint.id), deltaInput);
925
+ else for (const [c] of deltaInput) this._deltaChannelsForcedSnapshot.add(c);
738
926
  }
739
927
  await this._putCheckpoint({ source: "input" });
740
928
  this.input = INPUT_DONE;
@@ -745,7 +933,8 @@ var PregelLoop = class PregelLoop {
745
933
  let replayState;
746
934
  if (isTimeTraveling) {
747
935
  let replayCheckpointId = this.checkpoint.id;
748
- if ((this.checkpointMetadata.source === "update" || this.checkpointMetadata.source === "fork") && this.prevCheckpointConfig) replayCheckpointId = this.prevCheckpointConfig.configurable?.["checkpoint_id"] ?? replayCheckpointId;
936
+ if (replayBound !== void 0) replayCheckpointId = replayBound.configurable?.["checkpoint_id"] ?? replayCheckpointId;
937
+ else if ((this.checkpointMetadata.source === "update" || this.checkpointMetadata.source === "fork") && this.prevCheckpointConfig) replayCheckpointId = this.prevCheckpointConfig.configurable?.["checkpoint_id"] ?? replayCheckpointId;
749
938
  replayState = new ReplayState(replayCheckpointId);
750
939
  }
751
940
  this.config = patchConfigurable(this.config, {
@@ -855,15 +1044,7 @@ var PregelLoop = class PregelLoop {
855
1044
  };
856
1045
  let newCounters;
857
1046
  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
- }
1047
+ newCounters = advanceDeltaCounters(this.channels, this.updatedChannels ?? /* @__PURE__ */ new Set(), this.checkpointMetadata.counters_since_delta_snapshot);
867
1048
  this.checkpointMetadata = {
868
1049
  ...inputMetadata,
869
1050
  step: this.step,
@@ -872,16 +1053,17 @@ var PregelLoop = class PregelLoop {
872
1053
  } else newCounters = { ...this.checkpointMetadata.counters_since_delta_snapshot ?? {} };
873
1054
  if (this._deltaWritesVersioned) this.checkpointMetadata[DELTA_WRITES_VERSIONED] = true;
874
1055
  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);
1056
+ if (doCheckpoint) for (const ch of this._deltaChannelsForcedSnapshot) channelsToSnapshot.add(ch);
876
1057
  this.checkpoint = createCheckpoint(this.checkpoint, doCheckpoint ? this.channels : void 0, this.step, {
877
1058
  id: exiting ? this.checkpoint.id : void 0,
878
1059
  channelsToSnapshot,
879
- updatedChannels: this.updatedChannels,
880
- getNextVersion: doCheckpoint ? (current) => this.checkpointerGetNextVersion(current) : void 0
1060
+ storedVersions: this.checkpointPreviousVersions,
1061
+ getNextVersion: doCheckpoint ? (current) => this.checkpointerGetNextVersion(current) : void 0,
1062
+ triggerToNodes: this.triggerToNodes
881
1063
  });
882
1064
  for (const k of channelsToSnapshot) {
883
1065
  newCounters[k] = [0, 0];
884
- this._deltaChannelsWithOverwrite.delete(k);
1066
+ this._deltaChannelsForcedSnapshot.delete(k);
885
1067
  }
886
1068
  const nonZero = {};
887
1069
  for (const k in newCounters) {
@@ -907,10 +1089,11 @@ var PregelLoop = class PregelLoop {
907
1089
  */
908
1090
  async _putExitDeltaWrites() {
909
1091
  if (this._exitDeltaWrites === void 0 || this._exitDeltaWrites.length === 0 || this.checkpointer == null || this._initialCheckpointConfig === void 0) return;
1092
+ if (this._hasPersistedParent && !this._loadedLatest) for (const [, , , ch] of this._exitDeltaWrites) this._deltaChannelsForcedSnapshot.add(ch);
910
1093
  const counters = this.checkpointMetadata.counters_since_delta_snapshot ?? {};
911
1094
  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));
1095
+ for (const ch of this._deltaChannelsForcedSnapshot) channelsToSnapshot.add(ch);
1096
+ const pending = this._exitDeltaWrites.filter(([, , , ch]) => !channelsToSnapshot.has(ch));
914
1097
  if (pending.length === 0) return;
915
1098
  let anchorConfig;
916
1099
  if (this._hasPersistedParent) anchorConfig = this._initialCheckpointConfig;
@@ -920,11 +1103,16 @@ var PregelLoop = class PregelLoop {
920
1103
  stubCp.ts = (/* @__PURE__ */ new Date()).toISOString();
921
1104
  const stubPutConfig = patchConfigurable(this._initialCheckpointConfig, { [CONFIG_KEY_CHECKPOINT_ID]: void 0 });
922
1105
  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
- }, {}));
1106
+ this._checkpointerPutAfterPrevious({
1107
+ config: stubPutConfig,
1108
+ checkpoint: stubCp,
1109
+ metadata: {
1110
+ source: "loop",
1111
+ step: -2,
1112
+ parents: {}
1113
+ },
1114
+ newVersions: {}
1115
+ });
928
1116
  this.checkpointConfig = anchorConfig;
929
1117
  }
930
1118
  const anchorWriteConfig = patchConfigurable(anchorConfig, {
@@ -933,23 +1121,36 @@ var PregelLoop = class PregelLoop {
933
1121
  });
934
1122
  const grouped = /* @__PURE__ */ new Map();
935
1123
  const order = [];
936
- for (const [step, tid, ch, v] of pending) {
937
- const key = `${step}\u0000${tid}`;
1124
+ for (const [step, tid, path, ch, v] of pending) {
1125
+ let synthTid;
1126
+ let synthPath;
1127
+ if (tid === "00000000-0000-0000-0000-000000000000") {
1128
+ synthTid = exitDeltaTaskId(step, tid);
1129
+ synthPath = "";
1130
+ } else if (step === this._exitFirstStep) {
1131
+ synthTid = exitDeltaTaskId(step, tid);
1132
+ synthPath = path;
1133
+ } else {
1134
+ synthTid = exitDeltaLateTaskId(step, tid);
1135
+ synthPath = `~~${taskPathStr(step)}${path}`;
1136
+ }
1137
+ const key = `${synthTid}\u0000${synthPath}`;
938
1138
  let group = grouped.get(key);
939
1139
  if (group === void 0) {
940
1140
  group = [];
941
1141
  grouped.set(key, group);
942
1142
  order.push({
943
1143
  key,
944
- step,
945
- tid
1144
+ tid: synthTid,
1145
+ path: synthPath
946
1146
  });
947
1147
  }
948
1148
  group.push([ch, v]);
949
1149
  }
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));
1150
+ for (const { key, tid, path } of order) {
1151
+ const saved = this.checkpointer.putWrites(anchorWriteConfig, grouped.get(key), tid, path);
1152
+ this._trackCheckpointerPromise(saved);
1153
+ this._pendingDeltaWrites.push(saved);
953
1154
  }
954
1155
  }
955
1156
  _flushPendingWrites() {
@@ -964,18 +1165,32 @@ var PregelLoop = class PregelLoop {
964
1165
  byTask[tid] ??= [];
965
1166
  byTask[tid].push([key, value]);
966
1167
  }
967
- for (const [tid, ws] of Object.entries(byTask)) this._trackCheckpointerPromise(this.checkpointer.putWrites(config, ws, tid));
1168
+ for (const [tid, ws] of Object.entries(byTask)) {
1169
+ const task = this.tasks[tid];
1170
+ this._trackCheckpointerPromise(this.checkpointer.putWrites(config, ws, tid, task != null ? taskPathStr(task.path) : ""));
1171
+ }
968
1172
  }
969
- _matchWrites(tasks) {
1173
+ /**
1174
+ * Hands the writes loaded with the checkpoint back to the finished tasks
1175
+ * among `tasks`. A failed or interrupted task keeps empty writes, so it
1176
+ * reruns or goes to its error handler.
1177
+ */
1178
+ _reapplyWritesToSucceededNodes(tasks) {
970
1179
  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]);
1180
+ if (k === "__error__" || k === "__error_source_node__" || k === "__interrupt__" || k === "__resume__") continue;
1181
+ const task = tasks[tid];
1182
+ if (task) {
1183
+ task.writes.push([k, v]);
1184
+ this._reappliedTaskIds.add(tid);
1185
+ }
974
1186
  }
1187
+ }
1188
+ _matchWrites(tasks) {
1189
+ this._reapplyWritesToSucceededNodes(tasks);
975
1190
  for (const task of Object.values(tasks)) if (task.writes.length > 0) this._outputWrites(task.id, task.writes, true);
976
1191
  }
977
1192
  };
978
1193
  //#endregion
979
- export { PregelLoop };
1194
+ export { PregelLoop, ensureDeltaMessageIds };
980
1195
 
981
1196
  //# sourceMappingURL=loop.js.map