larkin 0.5.16 → 0.5.18

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.
@@ -308,6 +308,16 @@ function createRuntimeHost(options) {
308
308
  maxAttempts: options.retryPolicy?.maxAttempts ?? 6,
309
309
  stableWindowMs: options.retryPolicy?.stableWindowMs ?? 30_000,
310
310
  };
311
+ const turnEndRetryPolicy = {
312
+ baseDelayMs: options.turnEndRetryPolicy?.baseDelayMs ?? 1_000,
313
+ maxDelayMs: options.turnEndRetryPolicy?.maxDelayMs ?? 30_000,
314
+ maxAttempts: options.turnEndRetryPolicy?.maxAttempts ?? 3,
315
+ };
316
+ const turnEndRetryScheduler = options.turnEndRetryScheduler ?? {
317
+ now: Date.now,
318
+ setTimeout,
319
+ clearTimeout,
320
+ };
311
321
  const emit = (event) => {
312
322
  if (event.type === "delivery")
313
323
  telemetry?.delivery(event.agentId, event.messageId, event.status);
@@ -316,6 +326,7 @@ function createRuntimeHost(options) {
316
326
  };
317
327
  const runtimeEnv = (config, generation) => {
318
328
  const base = {
329
+ LARKIN_RUNTIME: "1",
319
330
  LARKIN_AGENT_ID: config.agentId,
320
331
  ...(generation ? {
321
332
  LARKIN_RUNTIME_OBSERVATION_GENERATION: generation,
@@ -361,6 +372,8 @@ function createRuntimeHost(options) {
361
372
  record.updatedAt = now();
362
373
  agent.records.set(record.deliveryId, record);
363
374
  agent.byMessage.set(record.messageId, record.deliveryId);
375
+ if (status === "consumed" || status === "error")
376
+ clearPendingRetryTimerWhenIdle(agent);
364
377
  emitConsumed(agent, persist(agent));
365
378
  return agent.records.get(record.deliveryId) ?? record;
366
379
  };
@@ -466,8 +479,42 @@ function createRuntimeHost(options) {
466
479
  emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: next.deliveryId, messageId: next.messageId, status: "consumed" });
467
480
  }
468
481
  }
482
+ clearPendingRetryTimerWhenIdle(agent);
469
483
  };
470
484
  const TURN_END_RETRY_REASON = "runtime turn ended before Inbox consumption was observed";
485
+ const turnEndRetryDelayMs = (attempt) => Math.min(turnEndRetryPolicy.maxDelayMs, turnEndRetryPolicy.baseDelayMs * 2 ** (attempt - 1));
486
+ const isRetryDue = (record) => !record.retryNotBefore || Number.isNaN(Date.parse(record.retryNotBefore))
487
+ || Date.parse(record.retryNotBefore) <= turnEndRetryScheduler.now();
488
+ const clearDeliveryRetryTimer = (agent) => {
489
+ if (!agent.deliveryRetryTimer)
490
+ return;
491
+ turnEndRetryScheduler.clearTimeout(agent.deliveryRetryTimer);
492
+ agent.deliveryRetryTimer = null;
493
+ };
494
+ function clearPendingRetryTimerWhenIdle(agent) {
495
+ if (!agent.deliveryRetryTimer)
496
+ return;
497
+ if ([...agent.records.values()].some((record) => record.status === "pending" && record.retryNotBefore))
498
+ return;
499
+ clearDeliveryRetryTimer(agent);
500
+ }
501
+ const schedulePendingRetry = (agent) => {
502
+ if (agent.stopped || agent.deliveryRetryTimer)
503
+ return;
504
+ const next = [...agent.records.values()]
505
+ .filter((record) => record.status === "pending" && record.retryNotBefore)
506
+ .map((record) => Date.parse(record.retryNotBefore))
507
+ .filter((at) => Number.isFinite(at))
508
+ .sort((left, right) => left - right)[0];
509
+ if (next === undefined)
510
+ return;
511
+ const delay = Math.max(0, next - turnEndRetryScheduler.now());
512
+ agent.deliveryRetryTimer = turnEndRetryScheduler.setTimeout(() => {
513
+ agent.deliveryRetryTimer = null;
514
+ void retryPending(agent);
515
+ }, delay);
516
+ agent.deliveryRetryTimer.unref?.();
517
+ };
471
518
  const INBOX_UPDATE_PROMOTE_REASON = "accepted inbox_update promoted after Agent became idle";
472
519
  const isInboxUpdateKind = (kind) => kind === "inbox_update" || kind === "inbox-update";
473
520
  const agentIsIdleForInboxScan = (agent) => !agent.stopped && !agent.busy && !agent.submitting && !agent.turnInProgress && !agent.starting && Boolean(agent.session);
@@ -495,13 +542,32 @@ function createRuntimeHost(options) {
495
542
  for (const record of agent.records.values()) {
496
543
  if (record.status !== "accepted")
497
544
  continue;
498
- emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: record.deliveryId,
499
- messageId: record.messageId, status: "deferred", reason: TURN_END_RETRY_REASON });
500
- }
545
+ const attempts = record.turnEndRetryAttempts ?? 0;
546
+ if (attempts >= turnEndRetryPolicy.maxAttempts) {
547
+ record.reason = `turn-end redelivery exhausted after ${turnEndRetryPolicy.maxAttempts} attempts`;
548
+ record.retryable = false;
549
+ const terminal = setRecord(agent, record, "error");
550
+ emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: terminal.deliveryId,
551
+ messageId: terminal.messageId, status: "error", reason: terminal.reason });
552
+ continue;
553
+ }
554
+ const attempt = attempts + 1;
555
+ const delay = turnEndRetryDelayMs(attempt);
556
+ record.turnEndRetryAttempts = attempt;
557
+ record.retryNotBefore = new Date(turnEndRetryScheduler.now() + delay).toISOString();
558
+ record.reason = TURN_END_RETRY_REASON;
559
+ record.retryable = true;
560
+ record.input = { ...record.input, attempt: (record.input.attempt ?? 0) + 1 };
561
+ const deferred = setRecord(agent, record, "pending");
562
+ emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: deferred.deliveryId,
563
+ messageId: deferred.messageId, status: "deferred", reason: TURN_END_RETRY_REASON });
564
+ }
565
+ schedulePendingRetry(agent);
501
566
  return;
502
567
  }
503
568
  const consumed = [];
504
569
  const deferred = [];
570
+ const exhausted = [];
505
571
  try {
506
572
  store.withInboxTransaction(() => {
507
573
  const inboxIds = new Set(store.readNdjson("inbox").flatMap((row) => typeof row?.message_id === "string" ? [row.message_id] : []));
@@ -522,8 +588,31 @@ function createRuntimeHost(options) {
522
588
  if (current.status !== "accepted" || candidate.status !== "accepted" || !inboxIds.has(candidate.messageId)) {
523
589
  return candidate;
524
590
  }
525
- const next = { ...candidate, status: "pending", updatedAt: now(),
526
- reason: TURN_END_RETRY_REASON, retryable: true };
591
+ const attempts = candidate.turnEndRetryAttempts ?? 0;
592
+ if (attempts >= turnEndRetryPolicy.maxAttempts) {
593
+ const terminal = { ...candidate, status: "error", updatedAt: now(),
594
+ reason: `turn-end redelivery exhausted after ${turnEndRetryPolicy.maxAttempts} attempts`,
595
+ retryable: false };
596
+ changed = true;
597
+ pendingUpdates.push(terminal);
598
+ return terminal;
599
+ }
600
+ const attempt = attempts + 1;
601
+ const delay = turnEndRetryDelayMs(attempt);
602
+ const staleInput = candidate.input && typeof candidate.input === "object" ? candidate.input : null;
603
+ const next = {
604
+ ...candidate,
605
+ status: "pending",
606
+ updatedAt: now(),
607
+ reason: TURN_END_RETRY_REASON,
608
+ retryable: true,
609
+ turnEndRetryAttempts: attempt,
610
+ retryNotBefore: new Date(turnEndRetryScheduler.now() + delay).toISOString(),
611
+ input: {
612
+ ...(staleInput ?? { inputId: candidate.deliveryId, deliveryId: candidate.deliveryId, kind: "wake", text: "" }),
613
+ attempt: (Number.isSafeInteger(staleInput?.attempt) && Number(staleInput?.attempt) >= 0 ? Number(staleInput.attempt) : 0) + 1,
614
+ },
615
+ };
527
616
  changed = true;
528
617
  pendingUpdates.push(next);
529
618
  return next;
@@ -532,7 +621,10 @@ function createRuntimeHost(options) {
532
621
  store.writeJson("runtimeDeliveries", { ...disk, records });
533
622
  for (const next of pendingUpdates) {
534
623
  agent.records.set(next.deliveryId, next);
535
- deferred.push(next);
624
+ if (next.status === "error")
625
+ exhausted.push(next);
626
+ else
627
+ deferred.push(next);
536
628
  }
537
629
  }
538
630
  });
@@ -545,6 +637,11 @@ function createRuntimeHost(options) {
545
637
  for (const record of deferred)
546
638
  emit({ type: "delivery", agentId: agent.config.agentId,
547
639
  deliveryId: record.deliveryId, messageId: record.messageId, status: "deferred", reason: TURN_END_RETRY_REASON });
640
+ for (const record of exhausted)
641
+ emit({ type: "delivery", agentId: agent.config.agentId,
642
+ deliveryId: record.deliveryId, messageId: record.messageId, status: "error", reason: record.reason });
643
+ if (deferred.length)
644
+ schedulePendingRetry(agent);
548
645
  };
549
646
  // Production callers persist the canonical Inbox before calling deliver(). If a
550
647
  // concurrent drain wins between that append and ledger creation, close only the
@@ -743,9 +840,11 @@ function createRuntimeHost(options) {
743
840
  if (idleGate)
744
841
  await idleGate;
745
842
  }
746
- const record = [...agent.records.values()].find((candidate) => candidate.status === "pending" || candidate.status === "submitting");
843
+ const record = [...agent.records.values()].find((candidate) => (candidate.status === "pending" && isRetryDue(candidate)) || candidate.status === "submitting");
747
844
  if (record)
748
845
  await submit(agent, record, false);
846
+ else
847
+ schedulePendingRetry(agent);
749
848
  })();
750
849
  agent.retryPendingInFlight = run;
751
850
  try {
@@ -1519,6 +1618,8 @@ function createRuntimeHost(options) {
1519
1618
  clearInterval(previous.poller);
1520
1619
  if (previous.retryTimer)
1521
1620
  clearTimeout(previous.retryTimer);
1621
+ if (previous.deliveryRetryTimer)
1622
+ turnEndRetryScheduler.clearTimeout(previous.deliveryRetryTimer);
1522
1623
  if (previous.stabilityTimer)
1523
1624
  clearTimeout(previous.stabilityTimer);
1524
1625
  const previousFailure = currentAuthFailure(previous);
@@ -1530,7 +1631,7 @@ function createRuntimeHost(options) {
1530
1631
  }, previousFailure));
1531
1632
  const candidate = {
1532
1633
  ...previous, config, adapter, session, launchId: node_crypto_1.default.randomUUID(), busy: false, submitting: false,
1533
- starting: null, retryAfterSubmit: false, generation: 0, poller: null, retryTimer: null,
1634
+ starting: null, retryAfterSubmit: false, generation: 0, poller: null, retryTimer: null, deliveryRetryTimer: null,
1534
1635
  recreateAttempts: 0, stabilityTimer: null, recreateReason: null, stopped: false,
1535
1636
  disabledReason: null, configurationRecovery: null,
1536
1637
  authFailureActive: scoped && previous.authFailureActive,
@@ -1623,6 +1724,7 @@ function createRuntimeHost(options) {
1623
1724
  if (pendingAfterCreate > 0) {
1624
1725
  throw new RuntimeSessionResetError("inbox_backlog", `Agent ${agentId} received Inbox backlog during reset`, pendingAfterCreate);
1625
1726
  }
1727
+ clearDeliveryRetryTimer(agent);
1626
1728
  agent.generation += 1;
1627
1729
  agent.launchId = node_crypto_1.default.randomUUID();
1628
1730
  agent.config.sessionId = null;
@@ -1893,7 +1995,7 @@ function createRuntimeHost(options) {
1893
1995
  retryPendingInFlight: null,
1894
1996
  records: new Map(persisted.records.map((record) => [record.deliveryId, record])),
1895
1997
  byMessage: new Map(persisted.records.map((record) => [record.messageId, record.deliveryId])),
1896
- generation: 0, poller: null, retryTimer: null, recreateAttempts: 0,
1998
+ generation: 0, poller: null, retryTimer: null, deliveryRetryTimer: null, recreateAttempts: 0,
1897
1999
  stabilityTimer: null, recreateReason: null, stopped: false, disabledReason: null,
1898
2000
  configurationRecovery: null, stateStore,
1899
2001
  compactionBreaker: config.runtime === "pi" && stateStore
@@ -2065,6 +2167,7 @@ function createRuntimeHost(options) {
2065
2167
  clearInterval(agent.poller);
2066
2168
  if (agent.retryTimer)
2067
2169
  clearTimeout(agent.retryTimer);
2170
+ clearDeliveryRetryTimer(agent);
2068
2171
  if (agent.stabilityTimer)
2069
2172
  clearTimeout(agent.stabilityTimer);
2070
2173
  if (agent.busy)
@@ -299,6 +299,16 @@ export function createRuntimeHost(options) {
299
299
  maxAttempts: options.retryPolicy?.maxAttempts ?? 6,
300
300
  stableWindowMs: options.retryPolicy?.stableWindowMs ?? 30_000,
301
301
  };
302
+ const turnEndRetryPolicy = {
303
+ baseDelayMs: options.turnEndRetryPolicy?.baseDelayMs ?? 1_000,
304
+ maxDelayMs: options.turnEndRetryPolicy?.maxDelayMs ?? 30_000,
305
+ maxAttempts: options.turnEndRetryPolicy?.maxAttempts ?? 3,
306
+ };
307
+ const turnEndRetryScheduler = options.turnEndRetryScheduler ?? {
308
+ now: Date.now,
309
+ setTimeout,
310
+ clearTimeout,
311
+ };
302
312
  const emit = (event) => {
303
313
  if (event.type === "delivery")
304
314
  telemetry?.delivery(event.agentId, event.messageId, event.status);
@@ -307,6 +317,7 @@ export function createRuntimeHost(options) {
307
317
  };
308
318
  const runtimeEnv = (config, generation) => {
309
319
  const base = {
320
+ LARKIN_RUNTIME: "1",
310
321
  LARKIN_AGENT_ID: config.agentId,
311
322
  ...(generation ? {
312
323
  LARKIN_RUNTIME_OBSERVATION_GENERATION: generation,
@@ -352,6 +363,8 @@ export function createRuntimeHost(options) {
352
363
  record.updatedAt = now();
353
364
  agent.records.set(record.deliveryId, record);
354
365
  agent.byMessage.set(record.messageId, record.deliveryId);
366
+ if (status === "consumed" || status === "error")
367
+ clearPendingRetryTimerWhenIdle(agent);
355
368
  emitConsumed(agent, persist(agent));
356
369
  return agent.records.get(record.deliveryId) ?? record;
357
370
  };
@@ -457,8 +470,42 @@ export function createRuntimeHost(options) {
457
470
  emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: next.deliveryId, messageId: next.messageId, status: "consumed" });
458
471
  }
459
472
  }
473
+ clearPendingRetryTimerWhenIdle(agent);
460
474
  };
461
475
  const TURN_END_RETRY_REASON = "runtime turn ended before Inbox consumption was observed";
476
+ const turnEndRetryDelayMs = (attempt) => Math.min(turnEndRetryPolicy.maxDelayMs, turnEndRetryPolicy.baseDelayMs * 2 ** (attempt - 1));
477
+ const isRetryDue = (record) => !record.retryNotBefore || Number.isNaN(Date.parse(record.retryNotBefore))
478
+ || Date.parse(record.retryNotBefore) <= turnEndRetryScheduler.now();
479
+ const clearDeliveryRetryTimer = (agent) => {
480
+ if (!agent.deliveryRetryTimer)
481
+ return;
482
+ turnEndRetryScheduler.clearTimeout(agent.deliveryRetryTimer);
483
+ agent.deliveryRetryTimer = null;
484
+ };
485
+ function clearPendingRetryTimerWhenIdle(agent) {
486
+ if (!agent.deliveryRetryTimer)
487
+ return;
488
+ if ([...agent.records.values()].some((record) => record.status === "pending" && record.retryNotBefore))
489
+ return;
490
+ clearDeliveryRetryTimer(agent);
491
+ }
492
+ const schedulePendingRetry = (agent) => {
493
+ if (agent.stopped || agent.deliveryRetryTimer)
494
+ return;
495
+ const next = [...agent.records.values()]
496
+ .filter((record) => record.status === "pending" && record.retryNotBefore)
497
+ .map((record) => Date.parse(record.retryNotBefore))
498
+ .filter((at) => Number.isFinite(at))
499
+ .sort((left, right) => left - right)[0];
500
+ if (next === undefined)
501
+ return;
502
+ const delay = Math.max(0, next - turnEndRetryScheduler.now());
503
+ agent.deliveryRetryTimer = turnEndRetryScheduler.setTimeout(() => {
504
+ agent.deliveryRetryTimer = null;
505
+ void retryPending(agent);
506
+ }, delay);
507
+ agent.deliveryRetryTimer.unref?.();
508
+ };
462
509
  const INBOX_UPDATE_PROMOTE_REASON = "accepted inbox_update promoted after Agent became idle";
463
510
  const isInboxUpdateKind = (kind) => kind === "inbox_update" || kind === "inbox-update";
464
511
  const agentIsIdleForInboxScan = (agent) => !agent.stopped && !agent.busy && !agent.submitting && !agent.turnInProgress && !agent.starting && Boolean(agent.session);
@@ -486,13 +533,32 @@ export function createRuntimeHost(options) {
486
533
  for (const record of agent.records.values()) {
487
534
  if (record.status !== "accepted")
488
535
  continue;
489
- emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: record.deliveryId,
490
- messageId: record.messageId, status: "deferred", reason: TURN_END_RETRY_REASON });
491
- }
536
+ const attempts = record.turnEndRetryAttempts ?? 0;
537
+ if (attempts >= turnEndRetryPolicy.maxAttempts) {
538
+ record.reason = `turn-end redelivery exhausted after ${turnEndRetryPolicy.maxAttempts} attempts`;
539
+ record.retryable = false;
540
+ const terminal = setRecord(agent, record, "error");
541
+ emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: terminal.deliveryId,
542
+ messageId: terminal.messageId, status: "error", reason: terminal.reason });
543
+ continue;
544
+ }
545
+ const attempt = attempts + 1;
546
+ const delay = turnEndRetryDelayMs(attempt);
547
+ record.turnEndRetryAttempts = attempt;
548
+ record.retryNotBefore = new Date(turnEndRetryScheduler.now() + delay).toISOString();
549
+ record.reason = TURN_END_RETRY_REASON;
550
+ record.retryable = true;
551
+ record.input = { ...record.input, attempt: (record.input.attempt ?? 0) + 1 };
552
+ const deferred = setRecord(agent, record, "pending");
553
+ emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: deferred.deliveryId,
554
+ messageId: deferred.messageId, status: "deferred", reason: TURN_END_RETRY_REASON });
555
+ }
556
+ schedulePendingRetry(agent);
492
557
  return;
493
558
  }
494
559
  const consumed = [];
495
560
  const deferred = [];
561
+ const exhausted = [];
496
562
  try {
497
563
  store.withInboxTransaction(() => {
498
564
  const inboxIds = new Set(store.readNdjson("inbox").flatMap((row) => typeof row?.message_id === "string" ? [row.message_id] : []));
@@ -513,8 +579,31 @@ export function createRuntimeHost(options) {
513
579
  if (current.status !== "accepted" || candidate.status !== "accepted" || !inboxIds.has(candidate.messageId)) {
514
580
  return candidate;
515
581
  }
516
- const next = { ...candidate, status: "pending", updatedAt: now(),
517
- reason: TURN_END_RETRY_REASON, retryable: true };
582
+ const attempts = candidate.turnEndRetryAttempts ?? 0;
583
+ if (attempts >= turnEndRetryPolicy.maxAttempts) {
584
+ const terminal = { ...candidate, status: "error", updatedAt: now(),
585
+ reason: `turn-end redelivery exhausted after ${turnEndRetryPolicy.maxAttempts} attempts`,
586
+ retryable: false };
587
+ changed = true;
588
+ pendingUpdates.push(terminal);
589
+ return terminal;
590
+ }
591
+ const attempt = attempts + 1;
592
+ const delay = turnEndRetryDelayMs(attempt);
593
+ const staleInput = candidate.input && typeof candidate.input === "object" ? candidate.input : null;
594
+ const next = {
595
+ ...candidate,
596
+ status: "pending",
597
+ updatedAt: now(),
598
+ reason: TURN_END_RETRY_REASON,
599
+ retryable: true,
600
+ turnEndRetryAttempts: attempt,
601
+ retryNotBefore: new Date(turnEndRetryScheduler.now() + delay).toISOString(),
602
+ input: {
603
+ ...(staleInput ?? { inputId: candidate.deliveryId, deliveryId: candidate.deliveryId, kind: "wake", text: "" }),
604
+ attempt: (Number.isSafeInteger(staleInput?.attempt) && Number(staleInput?.attempt) >= 0 ? Number(staleInput.attempt) : 0) + 1,
605
+ },
606
+ };
518
607
  changed = true;
519
608
  pendingUpdates.push(next);
520
609
  return next;
@@ -523,7 +612,10 @@ export function createRuntimeHost(options) {
523
612
  store.writeJson("runtimeDeliveries", { ...disk, records });
524
613
  for (const next of pendingUpdates) {
525
614
  agent.records.set(next.deliveryId, next);
526
- deferred.push(next);
615
+ if (next.status === "error")
616
+ exhausted.push(next);
617
+ else
618
+ deferred.push(next);
527
619
  }
528
620
  }
529
621
  });
@@ -536,6 +628,11 @@ export function createRuntimeHost(options) {
536
628
  for (const record of deferred)
537
629
  emit({ type: "delivery", agentId: agent.config.agentId,
538
630
  deliveryId: record.deliveryId, messageId: record.messageId, status: "deferred", reason: TURN_END_RETRY_REASON });
631
+ for (const record of exhausted)
632
+ emit({ type: "delivery", agentId: agent.config.agentId,
633
+ deliveryId: record.deliveryId, messageId: record.messageId, status: "error", reason: record.reason });
634
+ if (deferred.length)
635
+ schedulePendingRetry(agent);
539
636
  };
540
637
  // Production callers persist the canonical Inbox before calling deliver(). If a
541
638
  // concurrent drain wins between that append and ledger creation, close only the
@@ -734,9 +831,11 @@ export function createRuntimeHost(options) {
734
831
  if (idleGate)
735
832
  await idleGate;
736
833
  }
737
- const record = [...agent.records.values()].find((candidate) => candidate.status === "pending" || candidate.status === "submitting");
834
+ const record = [...agent.records.values()].find((candidate) => (candidate.status === "pending" && isRetryDue(candidate)) || candidate.status === "submitting");
738
835
  if (record)
739
836
  await submit(agent, record, false);
837
+ else
838
+ schedulePendingRetry(agent);
740
839
  })();
741
840
  agent.retryPendingInFlight = run;
742
841
  try {
@@ -1510,6 +1609,8 @@ export function createRuntimeHost(options) {
1510
1609
  clearInterval(previous.poller);
1511
1610
  if (previous.retryTimer)
1512
1611
  clearTimeout(previous.retryTimer);
1612
+ if (previous.deliveryRetryTimer)
1613
+ turnEndRetryScheduler.clearTimeout(previous.deliveryRetryTimer);
1513
1614
  if (previous.stabilityTimer)
1514
1615
  clearTimeout(previous.stabilityTimer);
1515
1616
  const previousFailure = currentAuthFailure(previous);
@@ -1521,7 +1622,7 @@ export function createRuntimeHost(options) {
1521
1622
  }, previousFailure));
1522
1623
  const candidate = {
1523
1624
  ...previous, config, adapter, session, launchId: crypto.randomUUID(), busy: false, submitting: false,
1524
- starting: null, retryAfterSubmit: false, generation: 0, poller: null, retryTimer: null,
1625
+ starting: null, retryAfterSubmit: false, generation: 0, poller: null, retryTimer: null, deliveryRetryTimer: null,
1525
1626
  recreateAttempts: 0, stabilityTimer: null, recreateReason: null, stopped: false,
1526
1627
  disabledReason: null, configurationRecovery: null,
1527
1628
  authFailureActive: scoped && previous.authFailureActive,
@@ -1614,6 +1715,7 @@ export function createRuntimeHost(options) {
1614
1715
  if (pendingAfterCreate > 0) {
1615
1716
  throw new RuntimeSessionResetError("inbox_backlog", `Agent ${agentId} received Inbox backlog during reset`, pendingAfterCreate);
1616
1717
  }
1718
+ clearDeliveryRetryTimer(agent);
1617
1719
  agent.generation += 1;
1618
1720
  agent.launchId = crypto.randomUUID();
1619
1721
  agent.config.sessionId = null;
@@ -1884,7 +1986,7 @@ export function createRuntimeHost(options) {
1884
1986
  retryPendingInFlight: null,
1885
1987
  records: new Map(persisted.records.map((record) => [record.deliveryId, record])),
1886
1988
  byMessage: new Map(persisted.records.map((record) => [record.messageId, record.deliveryId])),
1887
- generation: 0, poller: null, retryTimer: null, recreateAttempts: 0,
1989
+ generation: 0, poller: null, retryTimer: null, deliveryRetryTimer: null, recreateAttempts: 0,
1888
1990
  stabilityTimer: null, recreateReason: null, stopped: false, disabledReason: null,
1889
1991
  configurationRecovery: null, stateStore,
1890
1992
  compactionBreaker: config.runtime === "pi" && stateStore
@@ -2056,6 +2158,7 @@ export function createRuntimeHost(options) {
2056
2158
  clearInterval(agent.poller);
2057
2159
  if (agent.retryTimer)
2058
2160
  clearTimeout(agent.retryTimer);
2161
+ clearDeliveryRetryTimer(agent);
2059
2162
  if (agent.stabilityTimer)
2060
2163
  clearTimeout(agent.stabilityTimer);
2061
2164
  if (agent.busy)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkin",
3
- "version": "0.5.16",
3
+ "version": "0.5.18",
4
4
  "private": false,
5
5
  "larkinPackageRole": "npm-published",
6
6
  "files": [