@llblab/pi-kit 0.19.2 → 0.20.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 (66) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/node_modules/@llblab/pi-telegram/AGENTS.md +9 -7
  4. package/node_modules/@llblab/pi-telegram/BACKLOG.md +13 -32
  5. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +11 -0
  6. package/node_modules/@llblab/pi-telegram/README.md +4 -3
  7. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +2 -2
  8. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +3 -5
  9. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +1 -1
  10. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +93 -9
  11. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +9 -3
  12. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +231 -71
  13. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +4 -0
  14. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +34 -7
  15. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +63 -2
  16. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +3 -0
  17. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +13 -0
  18. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -0
  19. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +25 -4
  20. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +1 -0
  21. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +32 -12
  22. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +10 -0
  23. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +22 -1
  24. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +2 -2
  25. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +1 -1
  26. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +1 -2
  27. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +9 -0
  28. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +41 -1
  29. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +9 -1
  30. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +23 -3
  31. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +7 -2
  32. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +2 -0
  33. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +68 -37
  34. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +6 -4
  35. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +134 -61
  36. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +9 -0
  37. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +49 -3
  38. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +77 -6
  39. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +384 -8
  40. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.d.ts +3 -0
  41. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +6 -0
  42. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  43. package/node_modules/@llblab/pi-telegram/docs/architecture.md +21 -14
  44. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +31 -10
  45. package/node_modules/@llblab/pi-telegram/docs/updates.md +2 -0
  46. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +5 -7
  47. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +69 -12
  48. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +236 -94
  49. package/node_modules/@llblab/pi-telegram/lib/bus.ts +39 -9
  50. package/node_modules/@llblab/pi-telegram/lib/extension.ts +69 -2
  51. package/node_modules/@llblab/pi-telegram/lib/journal.ts +14 -0
  52. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +23 -4
  53. package/node_modules/@llblab/pi-telegram/lib/locks.ts +32 -13
  54. package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -1
  55. package/node_modules/@llblab/pi-telegram/lib/polling.ts +2 -2
  56. package/node_modules/@llblab/pi-telegram/lib/queue.ts +2 -3
  57. package/node_modules/@llblab/pi-telegram/lib/sync.ts +45 -1
  58. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +30 -2
  59. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +11 -3
  60. package/node_modules/@llblab/pi-telegram/lib/threads.ts +73 -36
  61. package/node_modules/@llblab/pi-telegram/lib/updates.ts +145 -78
  62. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +67 -4
  63. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +465 -7
  64. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +7 -0
  65. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  66. package/package.json +2 -2
@@ -126,7 +126,7 @@ export interface TelegramBusFollowerRegistrationRuntime<TContext> {
126
126
  restoreWorkspace?: boolean;
127
127
  },
128
128
  ) => Promise<boolean>;
129
- setContext: (ctx: TContext) => void;
129
+ setContext: (ctx: TContext) => void | Promise<void>;
130
130
  disconnectFromLeader?: () => Promise<boolean>;
131
131
  renameThread?: (
132
132
  target: TelegramTarget & { threadId: number },
@@ -623,6 +623,22 @@ export function createTelegramBusFollowerRuntimeAssembly<
623
623
  >(
624
624
  ports: TelegramBusFollowerRuntimeAssemblyPorts<TContext>,
625
625
  ): TelegramBusFollowerRuntimeAssembly<TContext> {
626
+ // Binding startup needs registration identity before it can grant inbound authority.
627
+ let readyContext: { generation: string; ctx: TContext; sessionGeneration: number | undefined } | undefined;
628
+ const isReadyContext = (ctx?: TContext): boolean => Boolean(readyContext &&
629
+ (ctx === undefined || readyContext.ctx === ctx) &&
630
+ ports.registrationState.getGeneration() === readyContext.generation &&
631
+ ports.registration.getSessionGeneration?.() === readyContext.sessionGeneration &&
632
+ ports.registration.isContextActive?.(readyContext.ctx) !== false);
633
+ const prepareContext = async (ctx: TContext): Promise<void> => {
634
+ const generation = ports.registrationState.getGeneration();
635
+ const sessionGeneration = ports.registration.getSessionGeneration?.();
636
+ readyContext = undefined;
637
+ await ports.registration.onRegistered?.(ctx);
638
+ if (generation && ports.registrationState.getGeneration() === generation &&
639
+ ports.registration.getSessionGeneration?.() === sessionGeneration &&
640
+ ports.registration.isContextActive?.(ctx) !== false) readyContext = { generation, ctx, sessionGeneration };
641
+ };
626
642
  const sharedRuntimeDeps = {
627
643
  instanceId: ports.instanceId,
628
644
  registrationState: ports.registrationState,
@@ -632,7 +648,9 @@ export function createTelegramBusFollowerRuntimeAssembly<
632
648
  ...ports.receiver,
633
649
  instanceId: ports.instanceId,
634
650
  recordRuntimeEvent: ports.recordRuntimeEvent,
635
- getRegistrationGeneration: ports.registrationState.getGeneration,
651
+ getRegistrationGeneration() {
652
+ return isReadyContext() ? readyContext?.generation : undefined;
653
+ },
636
654
  handleReplaceTarget: createTelegramBusFollowerTargetReplacementHandler({
637
655
  ...ports.targetReplacement,
638
656
  ...sharedRuntimeDeps,
@@ -650,8 +668,21 @@ export function createTelegramBusFollowerRuntimeAssembly<
650
668
  ...sharedRuntimeDeps,
651
669
  startReceiving: receiver.start,
652
670
  stopReceiving: receiver.stop,
671
+ onRegistered: prepareContext,
653
672
  onHeartbeatFailure: recovery,
654
673
  });
674
+ const baseRegistration = registration;
675
+ registration = {
676
+ ...baseRegistration,
677
+ async setContext(ctx) {
678
+ const sessionGeneration = ports.registration.getSessionGeneration?.();
679
+ if (ports.registration.isContextActive?.(ctx) === false) return;
680
+ await baseRegistration.setContext(ctx);
681
+ if (ports.registration.getSessionGeneration?.() !== sessionGeneration ||
682
+ ports.registration.isContextActive?.(ctx) === false) return;
683
+ if (!isReadyContext(ctx)) await prepareContext(ctx);
684
+ },
685
+ };
655
686
  return { receiver, registration };
656
687
  }
657
688
 
@@ -1194,7 +1225,13 @@ export function createTelegramBusFollowerSessionRefreshHook<TContext>(
1194
1225
  }
1195
1226
  if (!deps.registrationState.isRegistered()) return;
1196
1227
  if (deps.isSessionActive && !deps.isSessionActive(ctx)) return;
1197
- deps.registrationRuntime.setContext(ctx);
1228
+ try {
1229
+ await deps.registrationRuntime.setContext(ctx);
1230
+ } catch (error) {
1231
+ deps.recordRuntimeEvent("bus", error, { phase: "follower-session-refresh" });
1232
+ return;
1233
+ }
1234
+ if (deps.isSessionActive && !deps.isSessionActive(ctx)) return;
1198
1235
  deps.updateStatus(ctx);
1199
1236
  deps.recordRuntimeEvent(
1200
1237
  "bus",
@@ -1596,6 +1633,7 @@ export function createTelegramBusFollowerRegistrationRuntime<
1596
1633
  let activeLeaderSocketPath: string | undefined;
1597
1634
  let activeAuthSecret: string | undefined;
1598
1635
  let activeRegistrationGeneration: string | undefined;
1636
+ let registrationAttempt: { ctx: TContext; sessionGeneration: number | undefined } | undefined;
1599
1637
  let activeContext: TContext | undefined;
1600
1638
  let lastKnownTarget: TelegramTarget | undefined;
1601
1639
  let lastKnownSlot: string | undefined;
@@ -1606,6 +1644,7 @@ export function createTelegramBusFollowerRegistrationRuntime<
1606
1644
  heartbeatInterval = undefined;
1607
1645
  };
1608
1646
  const stop = () => {
1647
+ registrationAttempt = undefined;
1609
1648
  stopHeartbeat();
1610
1649
  activeAuthSecret = undefined;
1611
1650
  activeRegistrationGeneration = undefined;
@@ -1729,6 +1768,24 @@ export function createTelegramBusFollowerRegistrationRuntime<
1729
1768
  };
1730
1769
  return {
1731
1770
  registerWithLeader: async (ctx, leader, options) => {
1771
+ if (deps.isContextActive?.(ctx) === false) return false;
1772
+ const sessionGeneration = deps.getSessionGeneration?.();
1773
+ const attempt = { ctx, sessionGeneration };
1774
+ registrationAttempt = attempt;
1775
+ const isCurrentRequest = () => registrationAttempt === attempt &&
1776
+ deps.getSessionGeneration?.() === sessionGeneration && deps.isContextActive?.(ctx) !== false;
1777
+ const abandonOwnedRequest = async (): Promise<void> => {
1778
+ if (registrationAttempt !== attempt) return;
1779
+ registrationAttempt = undefined;
1780
+ stopHeartbeat();
1781
+ activeLeaderSocketPath = undefined;
1782
+ activeAuthSecret = undefined;
1783
+ activeRegistrationGeneration = undefined;
1784
+ activeContext = undefined;
1785
+ deps.registrationState?.setRegistered(false);
1786
+ deps.setActiveAuthSecret?.(undefined);
1787
+ await deps.stopReceiving?.();
1788
+ };
1732
1789
  const pendingHandoff = options
1733
1790
  ? undefined
1734
1791
  : getTelegramFollowerSessionHandoff();
@@ -1750,6 +1807,7 @@ export function createTelegramBusFollowerRegistrationRuntime<
1750
1807
  deps.getLeaderSocketPath?.() ??
1751
1808
  getTelegramBusSocketPath();
1752
1809
  await deps.startReceiving?.();
1810
+ if (!isCurrentRequest()) { await abandonOwnedRequest(); return false; }
1753
1811
  activeAuthSecret = deps.getLeaderAuthSecret
1754
1812
  ? deps.getLeaderAuthSecret(leader)
1755
1813
  : leader?.busSecret;
@@ -1781,7 +1839,7 @@ export function createTelegramBusFollowerRegistrationRuntime<
1781
1839
  sessionId: deps.getSessionId?.(ctx),
1782
1840
  pid: getPid(),
1783
1841
  processBirthId: deps.getProcessBirthId?.(),
1784
- sessionGeneration: deps.getSessionGeneration?.(),
1842
+ sessionGeneration,
1785
1843
  target:
1786
1844
  registrationOptions?.target ??
1787
1845
  deps.registrationState?.getTarget() ??
@@ -1815,6 +1873,7 @@ export function createTelegramBusFollowerRegistrationRuntime<
1815
1873
  },
1816
1874
  });
1817
1875
  } catch (error) {
1876
+ if (!isCurrentRequest()) { await abandonOwnedRequest(); return false; }
1818
1877
  stopHeartbeat();
1819
1878
  activeLeaderSocketPath = undefined;
1820
1879
  activeAuthSecret = undefined;
@@ -1823,14 +1882,7 @@ export function createTelegramBusFollowerRegistrationRuntime<
1823
1882
  await deps.stopReceiving?.();
1824
1883
  throw error;
1825
1884
  }
1826
- if (deps.isContextActive && !deps.isContextActive(ctx)) {
1827
- stopHeartbeat();
1828
- activeLeaderSocketPath = undefined;
1829
- activeAuthSecret = undefined;
1830
- deps.setActiveAuthSecret?.(undefined);
1831
- await deps.stopReceiving?.();
1832
- return false;
1833
- }
1885
+ if (!isCurrentRequest()) { await abandonOwnedRequest(); return false; }
1834
1886
  const compatibility = getTelegramBusProtocolCompatibility({
1835
1887
  local: deps.protocolIdentity,
1836
1888
  remote: response?.kind === "bus.ack" ? response.protocol : undefined,
@@ -1880,6 +1932,7 @@ export function createTelegramBusFollowerRegistrationRuntime<
1880
1932
  try {
1881
1933
  await deps.onRegistered?.(ctx);
1882
1934
  } catch (error) {
1935
+ if (!isCurrentRequest()) { await abandonOwnedRequest(); return false; }
1883
1936
  stopHeartbeat();
1884
1937
  activeLeaderSocketPath = undefined;
1885
1938
  activeAuthSecret = undefined;
@@ -1889,7 +1942,9 @@ export function createTelegramBusFollowerRegistrationRuntime<
1889
1942
  await deps.stopReceiving?.();
1890
1943
  throw error;
1891
1944
  }
1945
+ if (!isCurrentRequest()) { await abandonOwnedRequest(); return false; }
1892
1946
  await requestHeartbeat();
1947
+ if (!isCurrentRequest()) { await abandonOwnedRequest(); return false; }
1893
1948
  startHeartbeat(leaderSocketPath);
1894
1949
  if (
1895
1950
  pendingHandoffOptions &&
@@ -1909,6 +1964,8 @@ export function createTelegramBusFollowerRegistrationRuntime<
1909
1964
  return false;
1910
1965
  },
1911
1966
  setContext(ctx) {
1967
+ if (registrationAttempt && (registrationAttempt.ctx !== ctx ||
1968
+ registrationAttempt.sessionGeneration !== deps.getSessionGeneration?.())) registrationAttempt = undefined;
1912
1969
  activeContext = ctx;
1913
1970
  },
1914
1971
  async setThreadDisplayMode(mode) {