@trigger.dev/sdk 0.0.0-prerelease-20260909121655 → 0.0.0-prerelease-20260911094758

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.
@@ -1615,15 +1615,11 @@ async function installChatInputRouter(chatId, options) {
1615
1615
  checkpoint.appliedThrough = Math.max(checkpoint.appliedThrough ?? replayWindowEnd, replayWindowEnd);
1616
1616
  }
1617
1617
  }
1618
- // A boot that replayed `.in` itself has already answered everything up to
1619
- // `recoveredThrough`, so the floor has to cover it before the tail opens.
1620
- if (options?.recoveredThrough !== undefined) {
1621
- const recovered = options.recoveredThrough;
1622
- checkpoint.resumeFrom = Math.max(checkpoint.resumeFrom ?? recovered, recovered);
1623
- checkpoint.appliedThrough = Math.max(checkpoint.appliedThrough ?? checkpoint.resumeFrom, checkpoint.resumeFrom);
1624
- }
1625
1618
  const router = entry.router;
1626
1619
  router.restore(checkpoint);
1620
+ if (options?.recoveredSeqNums && options.recoveredSeqNums.length > 0) {
1621
+ router.markRecovered(options.recoveredSeqNums);
1622
+ }
1627
1623
  const floor = router.resumeFrom();
1628
1624
  if (floor !== undefined) {
1629
1625
  v3_1.sessionStreams.setLastSeqNum(chatId, "in", floor);
@@ -4074,6 +4070,13 @@ function chatAgent(options) {
4074
4070
  // default, `inFlightUsers`). The turn-loop checks this queue ahead of
4075
4071
  // `messagesInput.waitWithIdleTimeout` so recovered turns fire first.
4076
4072
  const bootInjectedQueue = [];
4073
+ const recoveredSeqByPayload = new WeakMap();
4074
+ const dispatchBootInjected = () => bootInjectedQueue.shift();
4075
+ const settleRecoveredTurn = (wirePayload) => {
4076
+ const settledSeq = recoveredSeqByPayload.get(wirePayload);
4077
+ if (settledSeq !== undefined)
4078
+ chatInputRouter().settleRecovered(settledSeq);
4079
+ };
4077
4080
  const couldHavePriorState = payload.continuation === true || ctx.attempt.number > 1;
4078
4081
  // `.in` resume cursor, computed at most once per boot. The boot
4079
4082
  // block below resolves it (snapshot field or records scan) and the
@@ -4214,16 +4217,10 @@ function chatAgent(options) {
4214
4217
  }
4215
4218
  // ── session.in router ──────────────────────────────────────────
4216
4219
  //
4217
- // Reads the turn boundary and subscribes in one call. `bootInCursor` is
4218
- // only a fallback: the boot block above may already have resolved a
4219
- // cursor from the snapshot, which is used when the boundary itself
4220
- // carries none. Everything the boot replayed off `.in` is dispatched from
4221
- // `bootInjectedQueue` below, so it goes into the floor here — folded in
4222
- // after the subscription opens, the live tail re-delivers it as a turn.
4223
- const lastRecoveredInSeq = replayedInTail.length > 0 ? replayedInTail[replayedInTail.length - 1].seqNum : undefined;
4220
+ const recoveredSeqNums = replayedInTail.map((r) => r.seqNum);
4224
4221
  await installChatInputRouter(payload.chatId, {
4225
4222
  fallbackResumeFrom: bootInCursorResolved ? bootInCursor : undefined,
4226
- recoveredThrough: lastRecoveredInSeq,
4223
+ recoveredSeqNums,
4227
4224
  resuming: Boolean(payload.continuation) || ctx.attempt.number > 1,
4228
4225
  });
4229
4226
  // ── Recovery boot + chain reconstruction ────────────────────────
@@ -4306,7 +4303,7 @@ function chatAgent(options) {
4306
4303
  // branches: at n=1 the orphan partial is dropped and the interrupted
4307
4304
  // user is re-dispatched as a fresh turn instead.
4308
4305
  let seedChain;
4309
- let recoveredTurns;
4306
+ let recoveredEntries;
4310
4307
  if (hookChain !== undefined) {
4311
4308
  seedChain = hookChain;
4312
4309
  }
@@ -4317,13 +4314,26 @@ function chatAgent(options) {
4317
4314
  seedChain = settledMessages;
4318
4315
  }
4319
4316
  if (hookRecoveredTurns !== undefined) {
4320
- recoveredTurns = hookRecoveredTurns;
4317
+ const seqNumsByRecoveredId = new Map();
4318
+ for (const entry of replayedInTail) {
4319
+ const existing = seqNumsByRecoveredId.get(entry.message.id);
4320
+ if (existing)
4321
+ existing.push(entry.seqNum);
4322
+ else
4323
+ seqNumsByRecoveredId.set(entry.message.id, [entry.seqNum]);
4324
+ }
4325
+ recoveredEntries = hookRecoveredTurns.map((message) => ({
4326
+ message,
4327
+ seqNum: seqNumsByRecoveredId.get(message.id)?.shift(),
4328
+ }));
4321
4329
  }
4322
4330
  else if (partialAssistant !== undefined && inFlightUsers.length > 1) {
4323
- recoveredTurns = inFlightUsers.slice(1);
4331
+ recoveredEntries = replayedInTail
4332
+ .slice(1)
4333
+ .map((r) => ({ message: r.message, seqNum: r.seqNum }));
4324
4334
  }
4325
4335
  else {
4326
- recoveredTurns = inFlightUsers;
4336
+ recoveredEntries = replayedInTail.map((r) => ({ message: r.message, seqNum: r.seqNum }));
4327
4337
  }
4328
4338
  // `beforeBoot` errors bubble — the customer opted into blocking
4329
4339
  // persistence and a failure there should fail the run rather than
@@ -4353,13 +4363,14 @@ function chatAgent(options) {
4353
4363
  for (const entry of replayedInTail) {
4354
4364
  metadataById.set(entry.message.id, entry.metadata);
4355
4365
  }
4356
- for (const msg of recoveredTurns) {
4366
+ const dispatchedRecoveredSeqs = new Set();
4367
+ for (const { message: msg, seqNum } of recoveredEntries) {
4357
4368
  if (wireMessageId && msg.id === wireMessageId)
4358
4369
  continue;
4359
4370
  const recoveredMetadata = metadataById.has(msg.id)
4360
4371
  ? metadataById.get(msg.id)
4361
4372
  : payload.metadata;
4362
- bootInjectedQueue.push({
4373
+ const injectedPayload = {
4363
4374
  chatId: payload.chatId,
4364
4375
  sessionId: payload.sessionId,
4365
4376
  metadata: recoveredMetadata,
@@ -4368,7 +4379,17 @@ function chatAgent(options) {
4368
4379
  messageId: msg.id,
4369
4380
  continuation: payload.continuation,
4370
4381
  previousRunId: payload.previousRunId,
4371
- });
4382
+ };
4383
+ bootInjectedQueue.push(injectedPayload);
4384
+ if (seqNum !== undefined) {
4385
+ recoveredSeqByPayload.set(injectedPayload, seqNum);
4386
+ dispatchedRecoveredSeqs.add(seqNum);
4387
+ }
4388
+ }
4389
+ for (const entry of replayedInTail) {
4390
+ if (!dispatchedRecoveredSeqs.has(entry.seqNum)) {
4391
+ chatInputRouter().settleRecovered(entry.seqNum);
4392
+ }
4372
4393
  }
4373
4394
  accumulatedUIMessages = seedChain;
4374
4395
  // ── Head-start bootstrap ─────────────────────────────────────
@@ -4528,7 +4549,7 @@ function chatAgent(options) {
4528
4549
  */
4529
4550
  let dispatchedRecoveredFirstTurn = false;
4530
4551
  if (preloaded && bootInjectedQueue.length > 0) {
4531
- currentWirePayload = bootInjectedQueue.shift();
4552
+ currentWirePayload = dispatchBootInjected();
4532
4553
  dispatchedRecoveredFirstTurn = true;
4533
4554
  }
4534
4555
  // Handle preloaded runs — fire onPreload, then wait for the first real message
@@ -4736,7 +4757,7 @@ function chatAgent(options) {
4736
4757
  // waiting on the live session.in. Subsequent recovered turns
4737
4758
  // get drained by the end-of-turn picker below.
4738
4759
  if (bootInjectedQueue.length > 0) {
4739
- currentWirePayload = bootInjectedQueue.shift();
4760
+ currentWirePayload = dispatchBootInjected();
4740
4761
  }
4741
4762
  else {
4742
4763
  const effectiveIdleTimeout = idleTimeoutInSeconds ?? payload.idleTimeoutInSeconds;
@@ -5300,6 +5321,7 @@ function chatAgent(options) {
5300
5321
  chatId: currentWirePayload.chatId,
5301
5322
  messageId: currentWirePayload.messageId,
5302
5323
  });
5324
+ settleRecoveredTurn(currentWirePayload);
5303
5325
  await writeTurnCompleteChunk(currentWirePayload.chatId);
5304
5326
  // Not a turn — don't consume an iteration.
5305
5327
  turn--;
@@ -6015,6 +6037,7 @@ function chatAgent(options) {
6015
6037
  }
6016
6038
  locals_js_1.locals.set(chatResponsePartsKey, []);
6017
6039
  }
6040
+ settleRecoveredTurn(currentWirePayload);
6018
6041
  // Write turn-complete control chunk — closes the frontend stream.
6019
6042
  const turnCompleteResult = await writeTurnCompleteChunk(currentWirePayload.chatId, turnAccessToken);
6020
6043
  // Fire onTurnComplete — stream is closed, use for persistence.
@@ -6126,7 +6149,7 @@ function chatAgent(options) {
6126
6149
  // produced these from in-flight user messages on session.in
6127
6150
  // that the dead predecessor never acknowledged.
6128
6151
  if (bootInjectedQueue.length > 0) {
6129
- currentWirePayload = bootInjectedQueue.shift();
6152
+ currentWirePayload = dispatchBootInjected();
6130
6153
  return "continue";
6131
6154
  }
6132
6155
  // chat.requestUpgrade() was called — exit the loop; the handover
@@ -6446,7 +6469,7 @@ function chatAgent(options) {
6446
6469
  // recovered turn shouldn't strand the rest of the boot queue
6447
6470
  // until an unrelated live message arrives.
6448
6471
  if (bootInjectedQueue.length > 0) {
6449
- currentWirePayload = bootInjectedQueue.shift();
6472
+ currentWirePayload = dispatchBootInjected();
6450
6473
  continue;
6451
6474
  }
6452
6475
  // Wait for the next message — same as after a successful turn