@rivetkit/engine-runner 2.0.27 → 2.0.28

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rivetkit/engine-runner",
3
- "version": "2.0.27",
3
+ "version": "2.0.28",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "import": {
@@ -16,7 +16,7 @@
16
16
  "uuid": "^12.0.0",
17
17
  "pino": "^9.9.5",
18
18
  "ws": "^8.18.3",
19
- "@rivetkit/engine-runner-protocol": "2.0.27"
19
+ "@rivetkit/engine-runner-protocol": "2.0.28"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^22.18.1",
package/src/actor.ts CHANGED
@@ -27,6 +27,10 @@ export class RunnerActor {
27
27
  }> = [];
28
28
  actorStartPromise: ReturnType<typeof promiseWithResolvers<void>>;
29
29
 
30
+ lastCommandIdx: bigint = -1n;
31
+ nextEventIdx: bigint = 0n;
32
+ eventHistory: protocol.EventWrapper[] = [];
33
+
30
34
  /**
31
35
  * If restoreHibernatingRequests has been called. This is used to assert
32
36
  * that the caller is implemented correctly.
@@ -81,8 +85,8 @@ export class RunnerActor {
81
85
  gatewayId,
82
86
  requestId,
83
87
  request: {
84
- resolve: () => {},
85
- reject: () => {},
88
+ resolve: () => { },
89
+ reject: () => { },
86
90
  actorId: this.actorId,
87
91
  gatewayId: gatewayId,
88
92
  requestId: requestId,
@@ -118,8 +122,8 @@ export class RunnerActor {
118
122
  gatewayId,
119
123
  requestId,
120
124
  request: {
121
- resolve: () => {},
122
- reject: () => {},
125
+ resolve: () => { },
126
+ reject: () => { },
123
127
  actorId: this.actorId,
124
128
  gatewayId: gatewayId,
125
129
  requestId: requestId,
@@ -193,4 +197,14 @@ export class RunnerActor {
193
197
  this.webSockets.splice(index, 1);
194
198
  }
195
199
  }
200
+
201
+ handleAckEvents(lastEventIdx: bigint) {
202
+ this.eventHistory = this.eventHistory.filter(
203
+ (event) => event.checkpoint.index > lastEventIdx,
204
+ );
205
+ }
206
+
207
+ recordEvent(eventWrapper: protocol.EventWrapper) {
208
+ this.eventHistory.push(eventWrapper);
209
+ }
196
210
  }
package/src/mod.ts CHANGED
@@ -8,6 +8,7 @@ import { type HibernatingWebSocketMetadata, Tunnel } from "./tunnel";
8
8
  import {
9
9
  calculateBackoff,
10
10
  parseWebSocketCloseReason,
11
+ stringifyError,
11
12
  unreachable,
12
13
  } from "./utils";
13
14
  import { importWebSocket } from "./websocket.js";
@@ -17,13 +18,18 @@ export { RunnerActor, type ActorConfig };
17
18
  export { idToStr } from "./utils";
18
19
 
19
20
  const KV_EXPIRE: number = 30_000;
20
- const PROTOCOL_VERSION: number = 3;
21
- const RUNNER_PING_INTERVAL = 3_000;
21
+ const PROTOCOL_VERSION: number = 5;
22
22
 
23
23
  /** Warn once the backlog significantly exceeds the server's ack batch size. */
24
24
  const EVENT_BACKLOG_WARN_THRESHOLD = 10_000;
25
25
  const SIGNAL_HANDLERS: (() => void | Promise<void>)[] = [];
26
26
 
27
+ export class RunnerShutdownError extends Error {
28
+ constructor() {
29
+ super("Runner shut down");
30
+ }
31
+ }
32
+
27
33
  export interface RunnerConfig {
28
34
  logger?: Logger;
29
35
  version: number;
@@ -197,9 +203,6 @@ export class Runner {
197
203
  // WebSocket
198
204
  __pegboardWebSocket?: WebSocket;
199
205
  runnerId?: string;
200
- #lastCommandIdx: number = -1;
201
- #pingLoop?: NodeJS.Timeout;
202
- #nextEventIdx: bigint = 0n;
203
206
  #started: boolean = false;
204
207
  #shutdown: boolean = false;
205
208
  #shuttingDown: boolean = false;
@@ -211,7 +214,6 @@ export class Runner {
211
214
  #runnerLostTimeout?: NodeJS.Timeout;
212
215
 
213
216
  // Event storage for resending
214
- #eventHistory: protocol.EventWrapper[] = [];
215
217
  #eventBacklogWarned: boolean = false;
216
218
 
217
219
  // Command acknowledgment
@@ -255,7 +257,14 @@ export class Runner {
255
257
 
256
258
  // Start cleaning up old unsent KV requests every 15 seconds
257
259
  this.#kvCleanupInterval = setInterval(() => {
258
- this.#cleanupOldKvRequests();
260
+ try {
261
+ this.#cleanupOldKvRequests();
262
+ } catch (err) {
263
+ this.log?.error({
264
+ msg: "error cleaning up kv requests",
265
+ error: stringifyError(err),
266
+ });
267
+ }
259
268
  }, 15000); // Run every 15 seconds
260
269
  }
261
270
 
@@ -282,6 +291,11 @@ export class Runner {
282
291
  }
283
292
 
284
293
  async forceStopActor(actorId: string, generation?: number) {
294
+ this.log?.debug({
295
+ msg: "force stopping actor",
296
+ actorId,
297
+ });
298
+
285
299
  const actor = this.getActor(actorId, generation);
286
300
  if (!actor) return;
287
301
 
@@ -299,22 +313,38 @@ export class Runner {
299
313
  // Close requests after onActorStop so you can send messages over the tunnel
300
314
  this.#tunnel?.closeActiveRequests(actor);
301
315
 
316
+ this.#sendActorStateUpdate(actorId, actor.generation, "stopped");
317
+
302
318
  // Remove actor after stopping in order to ensure that we can still
303
- // call actions on the runner. Do this before sending stopped update in
304
- // order to ensure we don't have duplicate actors.
319
+ // call actions on the runner
305
320
  this.#removeActor(actorId, generation);
306
-
307
- this.#sendActorStateUpdate(actorId, actor.generation, "stopped");
308
321
  }
309
322
 
310
- #stopAllActors() {
323
+ #handleLost() {
311
324
  this.log?.info({
312
- msg: "stopping all actors due to runner lost threshold exceeded",
325
+ msg: "stopping all actors due to runner lost threshold",
313
326
  });
314
327
 
328
+ // Remove all remaining kv requests
329
+ for (const [_, request] of this.#kvRequests.entries()) {
330
+ request.reject(new RunnerShutdownError());
331
+ }
332
+
333
+ this.#kvRequests.clear();
334
+
335
+ this.#stopAllActors();
336
+ }
337
+
338
+ #stopAllActors() {
315
339
  const actorIds = Array.from(this.#actors.keys());
316
340
  for (const actorId of actorIds) {
317
- this.forceStopActor(actorId);
341
+ this.forceStopActor(actorId).catch((err) => {
342
+ this.log?.error({
343
+ msg: "error stopping actor",
344
+ actorId,
345
+ error: stringifyError(err),
346
+ });
347
+ });
318
348
  }
319
349
  }
320
350
 
@@ -477,12 +507,6 @@ export class Runner {
477
507
  this.#runnerLostTimeout = undefined;
478
508
  }
479
509
 
480
- // Clear ping loop
481
- if (this.#pingLoop) {
482
- clearInterval(this.#pingLoop);
483
- this.#pingLoop = undefined;
484
- }
485
-
486
510
  // Clear ack interval
487
511
  if (this.#ackInterval) {
488
512
  clearInterval(this.#ackInterval);
@@ -738,10 +762,6 @@ export class Runner {
738
762
  name: this.#config.runnerName,
739
763
  version: this.#config.version,
740
764
  totalSlots: this.#config.totalSlots,
741
- lastCommandIdx:
742
- this.#lastCommandIdx >= 0
743
- ? BigInt(this.#lastCommandIdx)
744
- : null,
745
765
  prepopulateActorNames: new Map(
746
766
  Object.entries(this.#config.prepopulateActorNames).map(
747
767
  ([name, data]) => [
@@ -758,33 +778,22 @@ export class Runner {
758
778
  val: init,
759
779
  });
760
780
 
761
- // Start ping interval
762
- const pingLoop = setInterval(() => {
763
- if (ws.readyState === 1) {
764
- this.__sendToServer({
765
- tag: "ToServerPing",
766
- val: {
767
- ts: BigInt(Date.now()),
768
- },
769
- });
770
- } else {
771
- clearInterval(pingLoop);
772
- this.log?.info({
773
- msg: "WebSocket not open, stopping ping loop",
774
- });
775
- }
776
- }, RUNNER_PING_INTERVAL);
777
- this.#pingLoop = pingLoop;
778
-
779
781
  // Start command acknowledgment interval (5 minutes)
780
782
  const ackInterval = 5 * 60 * 1000; // 5 minutes in milliseconds
781
783
  const ackLoop = setInterval(() => {
782
- if (ws.readyState === 1) {
783
- this.#sendCommandAcknowledgment();
784
- } else {
785
- clearInterval(ackLoop);
786
- this.log?.info({
787
- msg: "WebSocket not open, stopping ack loop",
784
+ try {
785
+ if (ws.readyState === 1) {
786
+ this.#sendCommandAcknowledgment();
787
+ } else {
788
+ clearInterval(ackLoop);
789
+ this.log?.info({
790
+ msg: "WebSocket not open, stopping ack loop",
791
+ });
792
+ }
793
+ } catch (err) {
794
+ this.log?.error({
795
+ msg: "error in command acknowledgment loop",
796
+ error: stringifyError(err),
788
797
  });
789
798
  }
790
799
  }, ackInterval);
@@ -815,8 +824,8 @@ export class Runner {
815
824
  if (this.runnerId !== init.runnerId) {
816
825
  this.runnerId = init.runnerId;
817
826
 
818
- // Clear history if runner id changed
819
- this.#eventHistory.length = 0;
827
+ // Clear actors if runner id changed
828
+ this.#stopAllActors();
820
829
  }
821
830
 
822
831
  // Store the runner lost threshold from metadata
@@ -826,13 +835,12 @@ export class Runner {
826
835
 
827
836
  this.log?.info({
828
837
  msg: "received init",
829
- lastEventIdx: init.lastEventIdx,
830
838
  runnerLostThreshold: this.#runnerLostThreshold,
831
839
  });
832
840
 
833
841
  // Resend pending events
834
842
  this.#processUnsentKvRequests();
835
- this.#resendUnacknowledgedEvents(init.lastEventIdx);
843
+ this.#resendUnacknowledgedEvents();
836
844
  this.#tunnel?.resendBufferedEvents();
837
845
 
838
846
  this.#config.onConnected();
@@ -845,10 +853,19 @@ export class Runner {
845
853
  const kvResponse = message.val;
846
854
  this.#handleKvResponse(kvResponse);
847
855
  } else if (message.tag === "ToClientTunnelMessage") {
848
- this.#tunnel?.handleTunnelMessage(message.val);
849
- } else if (message.tag === "ToClientClose") {
850
- this.#tunnel?.shutdown();
851
- ws.close(1000, "manual closure");
856
+ this.#tunnel?.handleTunnelMessage(message.val).catch((err) => {
857
+ this.log?.error({
858
+ msg: "error handling tunnel message",
859
+ error: stringifyError(err),
860
+ });
861
+ });
862
+ } else if (message.tag === "ToClientPing") {
863
+ this.__sendToServer({
864
+ tag: "ToServerPong",
865
+ val: {
866
+ ts: message.val.ts,
867
+ },
868
+ });
852
869
  } else {
853
870
  unreachable(message);
854
871
  }
@@ -871,7 +888,14 @@ export class Runner {
871
888
  seconds: this.#runnerLostThreshold / 1000,
872
889
  });
873
890
  this.#runnerLostTimeout = setTimeout(() => {
874
- this.#stopAllActors();
891
+ try {
892
+ this.#handleLost();
893
+ } catch (err) {
894
+ this.log?.error({
895
+ msg: "error handling runner lost",
896
+ error: stringifyError(err),
897
+ });
898
+ }
875
899
  }, this.#runnerLostThreshold);
876
900
  }
877
901
 
@@ -909,12 +933,6 @@ export class Runner {
909
933
  this.#config.onDisconnected(ev.code, ev.reason);
910
934
  }
911
935
 
912
- // Clear ping loop on close
913
- if (this.#pingLoop) {
914
- clearInterval(this.#pingLoop);
915
- this.#pingLoop = undefined;
916
- }
917
-
918
936
  // Clear ack interval on close
919
937
  if (this.#ackInterval) {
920
938
  clearInterval(this.#ackInterval);
@@ -933,7 +951,14 @@ export class Runner {
933
951
  seconds: this.#runnerLostThreshold / 1000,
934
952
  });
935
953
  this.#runnerLostTimeout = setTimeout(() => {
936
- this.#stopAllActors();
954
+ try {
955
+ this.#handleLost();
956
+ } catch (err) {
957
+ this.log?.error({
958
+ msg: "error handling runner lost",
959
+ error: stringifyError(err),
960
+ });
961
+ }
937
962
  }, this.#runnerLostThreshold);
938
963
  }
939
964
 
@@ -952,52 +977,88 @@ export class Runner {
952
977
  for (const commandWrapper of commands) {
953
978
  if (commandWrapper.inner.tag === "CommandStartActor") {
954
979
  // Spawn background promise
955
- this.#handleCommandStartActor(commandWrapper);
980
+ this.#handleCommandStartActor(commandWrapper).catch((err) => {
981
+ this.log?.error({
982
+ msg: "error handling start actor command",
983
+ actorId: commandWrapper.checkpoint.actorId,
984
+ error: stringifyError(err),
985
+ });
986
+ });
987
+
988
+ // NOTE: We don't do this for CommandStopActor because the actor will be removed by that call
989
+ // so we cant update the checkpoint
990
+ const actor = this.getActor(
991
+ commandWrapper.checkpoint.actorId,
992
+ commandWrapper.checkpoint.generation,
993
+ );
994
+ if (actor) actor.lastCommandIdx = commandWrapper.checkpoint.index;
956
995
  } else if (commandWrapper.inner.tag === "CommandStopActor") {
957
996
  // Spawn background promise
958
- this.#handleCommandStopActor(commandWrapper);
997
+ this.#handleCommandStopActor(commandWrapper).catch((err) => {
998
+ this.log?.error({
999
+ msg: "error handling stop actor command",
1000
+ actorId: commandWrapper.checkpoint.actorId,
1001
+ error: stringifyError(err),
1002
+ });
1003
+ });
959
1004
  } else {
960
1005
  unreachable(commandWrapper.inner);
961
1006
  }
962
-
963
- this.#lastCommandIdx = Number(commandWrapper.index);
964
1007
  }
965
1008
  }
966
1009
 
967
1010
  #handleAckEvents(ack: protocol.ToClientAckEvents) {
968
- const lastAckedIdx = ack.lastEventIdx;
1011
+ let originalTotalEvents = Array.from(this.#actors).reduce(
1012
+ (s, [_, actor]) => s + actor.eventHistory.length,
1013
+ 0,
1014
+ );
1015
+
1016
+ for (const [_, actor] of this.#actors) {
1017
+ let checkpoint = ack.lastEventCheckpoints.find(
1018
+ (x) => x.actorId == actor.actorId,
1019
+ );
969
1020
 
970
- const originalLength = this.#eventHistory.length;
971
- this.#eventHistory = this.#eventHistory.filter(
972
- (event) => event.index > lastAckedIdx,
1021
+ if (checkpoint) actor.handleAckEvents(checkpoint.index);
1022
+ }
1023
+
1024
+ const totalEvents = Array.from(this.#actors).reduce(
1025
+ (s, [_, actor]) => s + actor.eventHistory.length,
1026
+ 0,
973
1027
  );
1028
+ const prunedCount = originalTotalEvents - totalEvents;
974
1029
 
975
- const prunedCount = originalLength - this.#eventHistory.length;
976
1030
  if (prunedCount > 0) {
977
1031
  this.log?.info({
978
1032
  msg: "pruned acknowledged events",
979
- lastAckedIdx: lastAckedIdx.toString(),
980
1033
  prunedCount,
981
1034
  });
982
1035
  }
983
1036
 
984
- if (this.#eventHistory.length <= EVENT_BACKLOG_WARN_THRESHOLD) {
1037
+ if (totalEvents <= EVENT_BACKLOG_WARN_THRESHOLD) {
985
1038
  this.#eventBacklogWarned = false;
986
1039
  }
987
1040
  }
988
1041
 
989
1042
  /** Track events to send to the server in case we need to resend it on disconnect. */
990
1043
  #recordEvent(eventWrapper: protocol.EventWrapper) {
991
- this.#eventHistory.push(eventWrapper);
1044
+ const actor = this.getActor(eventWrapper.checkpoint.actorId);
1045
+ if (!actor) return;
1046
+
1047
+ actor.recordEvent(eventWrapper);
1048
+
1049
+ let totalEvents = Array.from(this.#actors).reduce(
1050
+ (s, [_, actor]) => s + actor.eventHistory.length,
1051
+ 0,
1052
+ );
992
1053
 
993
1054
  if (
994
- this.#eventHistory.length > EVENT_BACKLOG_WARN_THRESHOLD &&
1055
+ totalEvents > EVENT_BACKLOG_WARN_THRESHOLD &&
995
1056
  !this.#eventBacklogWarned
996
1057
  ) {
997
1058
  this.#eventBacklogWarned = true;
998
1059
  this.log?.warn({
999
1060
  msg: "unacknowledged event backlog exceeds threshold",
1000
- backlogSize: this.#eventHistory.length,
1061
+ backlogSize: totalEvents,
1001
1062
  threshold: EVENT_BACKLOG_WARN_THRESHOLD,
1002
1063
  });
1003
1064
  }
@@ -1013,8 +1074,8 @@ export class Runner {
1013
1074
  const startCommand = commandWrapper.inner
1014
1075
  .val as protocol.CommandStartActor;
1015
1076
 
1016
- const actorId = startCommand.actorId;
1017
- const generation = startCommand.generation;
1077
+ const actorId = commandWrapper.checkpoint.actorId;
1078
+ const generation = commandWrapper.checkpoint.generation;
1018
1079
  const config = startCommand.config;
1019
1080
 
1020
1081
  const actorConfig: ActorConfig = {
@@ -1094,8 +1155,8 @@ export class Runner {
1094
1155
  const stopCommand = commandWrapper.inner
1095
1156
  .val as protocol.CommandStopActor;
1096
1157
 
1097
- const actorId = stopCommand.actorId;
1098
- const generation = stopCommand.generation;
1158
+ const actorId = commandWrapper.checkpoint.actorId;
1159
+ const generation = commandWrapper.checkpoint.generation;
1099
1160
 
1100
1161
  await this.forceStopActor(actorId, generation);
1101
1162
  }
@@ -1105,6 +1166,9 @@ export class Runner {
1105
1166
  generation: number,
1106
1167
  intentType: "sleep" | "stop",
1107
1168
  ) {
1169
+ const actor = this.getActor(actorId, generation);
1170
+ if (!actor) return;
1171
+
1108
1172
  let actorIntent: protocol.ActorIntent;
1109
1173
 
1110
1174
  if (intentType === "sleep") {
@@ -1119,14 +1183,15 @@ export class Runner {
1119
1183
  }
1120
1184
 
1121
1185
  const intentEvent: protocol.EventActorIntent = {
1122
- actorId,
1123
- generation,
1124
1186
  intent: actorIntent,
1125
1187
  };
1126
1188
 
1127
- const eventIndex = this.#nextEventIdx++;
1128
1189
  const eventWrapper: protocol.EventWrapper = {
1129
- index: eventIndex,
1190
+ checkpoint: {
1191
+ actorId,
1192
+ generation,
1193
+ index: actor.nextEventIdx++,
1194
+ },
1130
1195
  inner: {
1131
1196
  tag: "EventActorIntent",
1132
1197
  val: intentEvent,
@@ -1146,6 +1211,9 @@ export class Runner {
1146
1211
  generation: number,
1147
1212
  stateType: "running" | "stopped",
1148
1213
  ) {
1214
+ const actor = this.getActor(actorId, generation);
1215
+ if (!actor) return;
1216
+
1149
1217
  let actorState: protocol.ActorState;
1150
1218
 
1151
1219
  if (stateType === "running") {
@@ -1163,14 +1231,15 @@ export class Runner {
1163
1231
  }
1164
1232
 
1165
1233
  const stateUpdateEvent: protocol.EventActorStateUpdate = {
1166
- actorId,
1167
- generation,
1168
1234
  state: actorState,
1169
1235
  };
1170
1236
 
1171
- const eventIndex = this.#nextEventIdx++;
1172
1237
  const eventWrapper: protocol.EventWrapper = {
1173
- index: eventIndex,
1238
+ checkpoint: {
1239
+ actorId,
1240
+ generation,
1241
+ index: actor.nextEventIdx++,
1242
+ },
1174
1243
  inner: {
1175
1244
  tag: "EventActorStateUpdate",
1176
1245
  val: stateUpdateEvent,
@@ -1186,9 +1255,19 @@ export class Runner {
1186
1255
  }
1187
1256
 
1188
1257
  #sendCommandAcknowledgment() {
1189
- if (this.#lastCommandIdx < 0) {
1190
- // No commands received yet, nothing to acknowledge
1191
- return;
1258
+ const lastCommandCheckpoints = [];
1259
+
1260
+ for (const [_, actor] of this.#actors) {
1261
+ if (actor.lastCommandIdx < 0) {
1262
+ // No commands received yet, nothing to acknowledge
1263
+ continue;
1264
+ }
1265
+
1266
+ lastCommandCheckpoints.push({
1267
+ actorId: actor.actorId,
1268
+ generation: actor.generation,
1269
+ index: actor.lastCommandIdx,
1270
+ });
1192
1271
  }
1193
1272
 
1194
1273
  //this.#log?.log("Sending command acknowledgment", this.#lastCommandIdx);
@@ -1196,7 +1275,7 @@ export class Runner {
1196
1275
  this.__sendToServer({
1197
1276
  tag: "ToServerAckCommands",
1198
1277
  val: {
1199
- lastCommandIdx: BigInt(this.#lastCommandIdx),
1278
+ lastCommandCheckpoints,
1200
1279
  },
1201
1280
  });
1202
1281
  }
@@ -1495,14 +1574,15 @@ export class Runner {
1495
1574
  if (!actor) return;
1496
1575
 
1497
1576
  const alarmEvent: protocol.EventActorSetAlarm = {
1498
- actorId,
1499
- generation: actor.generation,
1500
1577
  alarmTs: alarmTs !== null ? BigInt(alarmTs) : null,
1501
1578
  };
1502
1579
 
1503
- const eventIndex = this.#nextEventIdx++;
1504
1580
  const eventWrapper: protocol.EventWrapper = {
1505
- index: eventIndex,
1581
+ checkpoint: {
1582
+ actorId,
1583
+ generation: actor.generation,
1584
+ index: actor.nextEventIdx++,
1585
+ },
1506
1586
  inner: {
1507
1587
  tag: "EventActorSetAlarm",
1508
1588
  val: alarmEvent,
@@ -1669,6 +1749,7 @@ export class Runner {
1669
1749
  tag: "ToServerlessServerInit",
1670
1750
  val: {
1671
1751
  runnerId: this.runnerId,
1752
+ runnerProtocolVersion: PROTOCOL_VERSION,
1672
1753
  },
1673
1754
  });
1674
1755
 
@@ -1699,27 +1780,34 @@ export class Runner {
1699
1780
  msg: `Scheduling reconnect attempt ${this.#reconnectAttempt + 1} in ${delay}ms`,
1700
1781
  });
1701
1782
 
1702
- this.#reconnectTimeout = setTimeout(async () => {
1783
+ this.#reconnectTimeout = setTimeout(() => {
1703
1784
  if (!this.#shutdown) {
1704
1785
  this.#reconnectAttempt++;
1705
1786
  this.log?.debug({
1706
1787
  msg: `Attempting to reconnect (attempt ${this.#reconnectAttempt})...`,
1707
1788
  });
1708
- await this.#openPegboardWebSocket();
1789
+ this.#openPegboardWebSocket().catch((err) => {
1790
+ this.log?.error({
1791
+ msg: "error during websocket reconnection",
1792
+ error: stringifyError(err),
1793
+ });
1794
+ });
1709
1795
  }
1710
1796
  }, delay);
1711
1797
  }
1712
1798
 
1713
- #resendUnacknowledgedEvents(lastEventIdx: bigint) {
1714
- const eventsToResend = this.#eventHistory.filter(
1715
- (event) => event.index > lastEventIdx,
1716
- );
1799
+ #resendUnacknowledgedEvents() {
1800
+ const eventsToResend = [];
1801
+
1802
+ for (const [_, actor] of this.#actors) {
1803
+ eventsToResend.push(...actor.eventHistory);
1804
+ }
1717
1805
 
1718
1806
  if (eventsToResend.length === 0) return;
1719
1807
 
1720
1808
  this.log?.info({
1721
1809
  msg: "resending unacknowledged events",
1722
- fromIndex: lastEventIdx + 1n,
1810
+ count: eventsToResend.length,
1723
1811
  });
1724
1812
 
1725
1813
  // Resend events in batches