@llblab/pi-kit 0.29.2 → 0.30.1

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 (208) hide show
  1. package/BACKLOG.md +2 -1
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +2 -2
  4. package/node_modules/@llblab/pi-telegram/AGENTS.md +9 -7
  5. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -18
  6. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +15 -0
  7. package/node_modules/@llblab/pi-telegram/README.md +7 -11
  8. package/node_modules/@llblab/pi-telegram/api/commands.ts +3 -0
  9. package/node_modules/@llblab/pi-telegram/dist/api/commands.d.ts +1 -1
  10. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
  11. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +15 -42
  12. package/node_modules/@llblab/pi-telegram/dist/lib/activity.d.ts +9 -0
  13. package/node_modules/@llblab/pi-telegram/dist/lib/activity.js +39 -10
  14. package/node_modules/@llblab/pi-telegram/dist/lib/agent-messages.js +7 -3
  15. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +43 -5
  16. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +163 -20
  17. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.d.ts +12 -0
  18. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +182 -79
  19. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +111 -31
  20. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +1324 -359
  21. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +41 -91
  22. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +969 -637
  23. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +16 -9
  24. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +215 -18
  25. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +1048 -327
  26. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +5 -0
  27. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +434 -261
  28. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +26 -5
  29. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +92 -176
  30. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +168 -82
  31. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +1409 -291
  32. package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +1 -8
  33. package/node_modules/@llblab/pi-telegram/dist/lib/config.js +34 -76
  34. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +2 -2
  35. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +16 -16
  36. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +639 -171
  37. package/node_modules/@llblab/pi-telegram/dist/lib/generative-app-worker.mjs +19 -7
  38. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -5
  39. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.js +61 -49
  40. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +5 -0
  41. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +35 -59
  42. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +33 -3
  43. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +2063 -782
  44. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -6
  45. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +75 -58
  46. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +29 -3
  47. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +289 -108
  48. package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +50 -14
  49. package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +22 -1
  50. package/node_modules/@llblab/pi-telegram/dist/lib/media.js +46 -28
  51. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +19 -5
  52. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +53 -11
  53. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +5 -4
  54. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +45 -63
  55. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +5 -4
  56. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +114 -78
  57. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +1 -1
  58. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +8 -25
  59. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +6 -1
  60. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +14 -21
  61. package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +8 -61
  62. package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +92 -164
  63. package/node_modules/@llblab/pi-telegram/dist/lib/model.d.ts +1 -1
  64. package/node_modules/@llblab/pi-telegram/dist/lib/model.js +2 -16
  65. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +6 -27
  66. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +45 -38
  67. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +22 -16
  68. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +0 -2
  69. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +51 -64
  70. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-voice.js +6 -3
  71. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +6 -6
  72. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +65 -51
  73. package/node_modules/@llblab/pi-telegram/dist/lib/ownership.js +6 -2
  74. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +5 -0
  75. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +37 -21
  76. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -0
  77. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +8 -1
  78. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +21 -37
  79. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +59 -50
  80. package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +39 -19
  81. package/node_modules/@llblab/pi-telegram/dist/lib/process-identity.d.ts +24 -0
  82. package/node_modules/@llblab/pi-telegram/dist/lib/process-identity.js +133 -0
  83. package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +2 -7
  84. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +14 -16
  85. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +252 -127
  86. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +2 -83
  87. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +20 -274
  88. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +16 -24
  89. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +99 -116
  90. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +120 -7
  91. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +4667 -1152
  92. package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +1 -11
  93. package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +4 -29
  94. package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +7 -20
  95. package/node_modules/@llblab/pi-telegram/dist/lib/skills.js +4 -3
  96. package/node_modules/@llblab/pi-telegram/dist/lib/status.js +86 -57
  97. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +1 -3
  98. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +67 -43
  99. package/node_modules/@llblab/pi-telegram/dist/lib/target.d.ts +4 -0
  100. package/node_modules/@llblab/pi-telegram/dist/lib/target.js +18 -0
  101. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +18 -13
  102. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +148 -53
  103. package/node_modules/@llblab/pi-telegram/dist/lib/text-groups.js +4 -39
  104. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +6 -27
  105. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +369 -186
  106. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +1 -2
  107. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +58 -38
  108. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +28 -8
  109. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +175 -66
  110. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +42 -16
  111. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +90 -50
  112. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +82 -20
  113. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +2247 -855
  114. package/node_modules/@llblab/pi-telegram/dist/lib/time-injection.js +1 -2
  115. package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -19
  116. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +169 -80
  117. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +2418 -641
  118. package/node_modules/@llblab/pi-telegram/dist/lib/voice.d.ts +7 -22
  119. package/node_modules/@llblab/pi-telegram/dist/lib/voice.js +7 -22
  120. package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +1 -1
  121. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +3 -1
  122. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +425 -211
  123. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +8 -13
  124. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +1 -1
  125. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +328 -140
  126. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +8 -4
  127. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  128. package/node_modules/@llblab/pi-telegram/docs/architecture.md +31 -29
  129. package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
  130. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +15 -3
  131. package/node_modules/@llblab/pi-telegram/docs/outbound.md +1 -1
  132. package/node_modules/@llblab/pi-telegram/docs/public-api.md +29 -1
  133. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +25 -8
  134. package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +59 -82
  135. package/node_modules/@llblab/pi-telegram/lib/activity.ts +146 -102
  136. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +12 -5
  137. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +389 -82
  138. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +263 -102
  139. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +2440 -601
  140. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +2209 -1290
  141. package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +46 -12
  142. package/node_modules/@llblab/pi-telegram/lib/bus.ts +1673 -444
  143. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +848 -327
  144. package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +152 -265
  145. package/node_modules/@llblab/pi-telegram/lib/commands.ts +2398 -555
  146. package/node_modules/@llblab/pi-telegram/lib/config.ts +177 -170
  147. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +41 -29
  148. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1206 -433
  149. package/node_modules/@llblab/pi-telegram/lib/generative-app-worker.mjs +19 -7
  150. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +324 -211
  151. package/node_modules/@llblab/pi-telegram/lib/inbound.ts +95 -97
  152. package/node_modules/@llblab/pi-telegram/lib/journal.ts +5311 -1763
  153. package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +1 -3
  154. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +128 -78
  155. package/node_modules/@llblab/pi-telegram/lib/locks.ts +677 -203
  156. package/node_modules/@llblab/pi-telegram/lib/logging.ts +147 -58
  157. package/node_modules/@llblab/pi-telegram/lib/media.ts +82 -43
  158. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +85 -18
  159. package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +88 -129
  160. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +260 -155
  161. package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +20 -60
  162. package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +41 -56
  163. package/node_modules/@llblab/pi-telegram/lib/menu.ts +112 -285
  164. package/node_modules/@llblab/pi-telegram/lib/model.ts +8 -22
  165. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +156 -125
  166. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +76 -49
  167. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +96 -94
  168. package/node_modules/@llblab/pi-telegram/lib/outbound-voice.ts +33 -27
  169. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +128 -125
  170. package/node_modules/@llblab/pi-telegram/lib/ownership.ts +19 -10
  171. package/node_modules/@llblab/pi-telegram/lib/paths.ts +148 -49
  172. package/node_modules/@llblab/pi-telegram/lib/pi.ts +35 -15
  173. package/node_modules/@llblab/pi-telegram/lib/polling.ts +144 -147
  174. package/node_modules/@llblab/pi-telegram/lib/preview.ts +93 -40
  175. package/node_modules/@llblab/pi-telegram/lib/process-identity.ts +164 -0
  176. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +4 -11
  177. package/node_modules/@llblab/pi-telegram/lib/queue.ts +516 -301
  178. package/node_modules/@llblab/pi-telegram/lib/recovery.ts +50 -454
  179. package/node_modules/@llblab/pi-telegram/lib/rendering.ts +1 -3
  180. package/node_modules/@llblab/pi-telegram/lib/replies.ts +248 -209
  181. package/node_modules/@llblab/pi-telegram/lib/routing.ts +8509 -2186
  182. package/node_modules/@llblab/pi-telegram/lib/runtime.ts +1 -12
  183. package/node_modules/@llblab/pi-telegram/lib/sections.ts +24 -89
  184. package/node_modules/@llblab/pi-telegram/lib/setup.ts +1 -3
  185. package/node_modules/@llblab/pi-telegram/lib/skills.ts +9 -4
  186. package/node_modules/@llblab/pi-telegram/lib/status.ts +215 -110
  187. package/node_modules/@llblab/pi-telegram/lib/sync.ts +448 -361
  188. package/node_modules/@llblab/pi-telegram/lib/target.ts +22 -3
  189. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +302 -127
  190. package/node_modules/@llblab/pi-telegram/lib/text-groups.ts +5 -39
  191. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +833 -331
  192. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +213 -97
  193. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +232 -87
  194. package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +155 -72
  195. package/node_modules/@llblab/pi-telegram/lib/threads.ts +5318 -1845
  196. package/node_modules/@llblab/pi-telegram/lib/time-injection.ts +1 -4
  197. package/node_modules/@llblab/pi-telegram/lib/turns.ts +15 -28
  198. package/node_modules/@llblab/pi-telegram/lib/updates.ts +5184 -1612
  199. package/node_modules/@llblab/pi-telegram/lib/voice.ts +7 -35
  200. package/node_modules/@llblab/pi-telegram/lib/wire.ts +1 -1
  201. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +996 -416
  202. package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +14 -17
  203. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +878 -376
  204. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +22 -13
  205. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  206. package/package.json +2 -2
  207. package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +0 -472
  208. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +0 -108
@@ -74,7 +74,9 @@ export function getLatestTelegramUpdateId(
74
74
  export class TelegramPersistentGetUpdatesConflictError extends Error {
75
75
  readonly count: number;
76
76
  constructor(count: number) {
77
- super(`Telegram polling stopped after ${count} consecutive getUpdates conflicts.`);
77
+ super(
78
+ `Telegram polling stopped after ${count} consecutive getUpdates conflicts.`,
79
+ );
78
80
  this.name = "TelegramPersistentGetUpdatesConflictError";
79
81
  this.count = count;
80
82
  }
@@ -134,22 +136,13 @@ export interface TelegramPollingStartState {
134
136
  }
135
137
 
136
138
  export type TelegramPollingWorkPhase =
137
- | "long-poll"
138
- | "persisting-journal"
139
- | "persisting-offset"
140
- | "retrying";
139
+ "long-poll" | "persisting-journal" | "persisting-offset" | "retrying";
141
140
 
142
141
  export type TelegramPollingPhase =
143
- | "stopped"
144
- | "starting"
145
- | TelegramPollingWorkPhase;
142
+ "stopped" | "starting" | TelegramPollingWorkPhase;
146
143
 
147
144
  export type TelegramPollingStopReason =
148
- | "not-started"
149
- | "requested"
150
- | "completed"
151
- | "failed"
152
- | "persistent-conflict";
145
+ "not-started" | "requested" | "completed" | "failed" | "persistent-conflict";
153
146
 
154
147
  export interface TelegramPollingStateSnapshot {
155
148
  phase: TelegramPollingPhase;
@@ -162,8 +155,7 @@ export interface TelegramPollingStateSnapshot {
162
155
  stopReason?: TelegramPollingStopReason;
163
156
  }
164
157
 
165
- export interface TelegramPollingControllerState
166
- extends TelegramPollingStateSnapshot {
158
+ export interface TelegramPollingControllerState extends TelegramPollingStateSnapshot {
167
159
  pollingPromise?: Promise<void>;
168
160
  pollingController?: AbortController;
169
161
  }
@@ -185,8 +177,7 @@ function getTelegramPollingStateSnapshot(
185
177
  startedAtMs: state.startedAtMs,
186
178
  stoppedAtMs: state.stoppedAtMs,
187
179
  lastSuccessfulResponseAtMs: state.lastSuccessfulResponseAtMs,
188
- lastSuccessfulResponseUpdateCount:
189
- state.lastSuccessfulResponseUpdateCount,
180
+ lastSuccessfulResponseUpdateCount: state.lastSuccessfulResponseUpdateCount,
190
181
  stopReason: state.stopReason,
191
182
  };
192
183
  }
@@ -263,7 +254,8 @@ export function createTelegramPollingAdmissionRuntime<TContext>(deps: {
263
254
  async start(ctx) {
264
255
  if (!(deps.canStart?.(ctx) ?? true)) return;
265
256
  const expectedGeneration = ++generation;
266
- const isCurrent = () => expectedGeneration === generation && (deps.canStart?.(ctx) ?? true);
257
+ const isCurrent = () =>
258
+ expectedGeneration === generation && (deps.canStart?.(ctx) ?? true);
267
259
  if (!isCurrent()) return;
268
260
  await deps.prepareStart?.();
269
261
  if (!isCurrent()) return;
@@ -333,7 +325,10 @@ export function createTelegramDurablePollingRuntimeAssembly<
333
325
  if (batch.length > 0) deps.prepareUpdateBatch(batch);
334
326
  } catch (error) {
335
327
  try {
336
- deps.recordRuntimeEvent?.("polling", error, { phase: "batch-preparation", updateCount: updates.length });
328
+ deps.recordRuntimeEvent?.("polling", error, {
329
+ phase: "batch-preparation",
330
+ updateCount: updates.length,
331
+ });
337
332
  } catch {
338
333
  // Already-published input must still reach the worker if diagnostics fail.
339
334
  }
@@ -592,8 +587,13 @@ export function startTelegramPollingRuntime<TContext>(
592
587
  if (ownsController) deps.setPollingController(undefined);
593
588
  if (!ownsPromise && !ownsController) return;
594
589
  deps.onPollingStopped?.(
595
- controller.signal.aborted ? "requested" : persistentConflict
596
- ? "persistent-conflict" : failed ? "failed" : "completed",
590
+ controller.signal.aborted
591
+ ? "requested"
592
+ : persistentConflict
593
+ ? "persistent-conflict"
594
+ : failed
595
+ ? "failed"
596
+ : "completed",
597
597
  );
598
598
  // Detach the inner promise before outer teardown calls polling.stop().
599
599
  if (persistentConflict && !controller.signal.aborted) {
@@ -603,11 +603,14 @@ export function startTelegramPollingRuntime<TContext>(
603
603
  } else {
604
604
  deps.stopTypingLoop();
605
605
  deps.recordRuntimeEvent?.("polling", persistentConflict, {
606
- phase: "persistent-conflict", count: persistentConflict.count,
606
+ phase: "persistent-conflict",
607
+ count: persistentConflict.count,
607
608
  });
608
609
  }
609
610
  } catch (error) {
610
- deps.recordRuntimeEvent?.("polling", error, { phase: "conflict-stand-down" });
611
+ deps.recordRuntimeEvent?.("polling", error, {
612
+ phase: "conflict-stand-down",
613
+ });
611
614
  }
612
615
  if (deps.getPollingController() || deps.getPollingPromise()) return;
613
616
  }
@@ -731,7 +734,9 @@ export interface TelegramThreadTargetObservationBinding<TContext> {
731
734
  set(handler: TelegramThreadTargetObservationHandler<TContext>): void;
732
735
  }
733
736
 
734
- export function createTelegramThreadTargetObservationBinding<TContext>(): TelegramThreadTargetObservationBinding<TContext> {
737
+ export function createTelegramThreadTargetObservationBinding<
738
+ TContext,
739
+ >(): TelegramThreadTargetObservationBinding<TContext> {
735
740
  let handler: TelegramThreadTargetObservationHandler<TContext> | undefined;
736
741
  return {
737
742
  async handle(ctx) {
@@ -760,16 +765,8 @@ export interface TelegramThreadAwarePollingPorts<TContext, TOwner> {
760
765
  stopFollowerRegistration: () => void;
761
766
  }
762
767
 
763
- export interface TelegramThreadAwarePollingDeps<
764
- TContext,
765
- TOwner,
766
- > extends TelegramStartupThreadCapabilityProbeDeps {
767
- lifecycle?: TelegramThreadCapabilityLifecycle;
768
- isBusRuntimeEnabled: () => boolean;
769
- isTopicModeUnavailableError: (error: unknown) => boolean;
770
- getPollingStartedWithTelegramBus: () => boolean;
771
- setPollingStartedWithTelegramBus: (started: boolean) => void;
772
- setForceFreshLeaderThreadOnNextStart: (forceFresh: boolean) => void;
768
+ /** Classic/leader polling and follower registration controls that thread-aware polling switches between. */
769
+ export interface TelegramThreadAwarePollingControls<TContext, TOwner> {
773
770
  startClassicPolling: (ctx: TContext) => MaybePromise<void>;
774
771
  stopClassicPolling: () => Promise<void>;
775
772
  startBusLeaderPolling: (ctx: TContext) => Promise<void>;
@@ -786,35 +783,29 @@ export interface TelegramThreadAwarePollingDeps<
786
783
  ) => Promise<boolean | undefined>;
787
784
  hasRememberedWorkspaceBinding?: (ctx: TContext) => boolean;
788
785
  stopFollowerRegistration: () => void;
786
+ isBusRuntimeEnabled: () => boolean;
787
+ isTopicModeUnavailableError: (error: unknown) => boolean;
789
788
  }
790
789
 
791
- export interface TelegramThreadCapabilityOrchestrationDeps<
792
- TContext,
793
- TOwner,
794
- > extends TelegramThreadCapabilityReaderDeps {
790
+ export interface TelegramThreadAwarePollingDeps<TContext, TOwner>
791
+ extends
792
+ TelegramStartupThreadCapabilityProbeDeps,
793
+ TelegramThreadAwarePollingControls<TContext, TOwner> {
794
+ lifecycle?: TelegramThreadCapabilityLifecycle;
795
+ getPollingStartedWithTelegramBus: () => boolean;
796
+ setPollingStartedWithTelegramBus: (started: boolean) => void;
797
+ setForceFreshLeaderThreadOnNextStart: (forceFresh: boolean) => void;
798
+ }
799
+
800
+ export interface TelegramThreadCapabilityOrchestrationDeps<TContext, TOwner>
801
+ extends
802
+ TelegramThreadCapabilityReaderDeps,
803
+ TelegramThreadAwarePollingControls<TContext, TOwner> {
795
804
  state: TelegramThreadCapabilityStateRuntime;
796
805
  topicTargetStore: TelegramThreadCapabilityStore;
797
- isBusRuntimeEnabled: () => boolean;
798
806
  ownsLock: (ctx: TContext) => boolean;
799
807
  isFollowerRegistered?: () => boolean;
800
- startClassicPolling: (ctx: TContext) => MaybePromise<void>;
801
- stopClassicPolling: () => Promise<void>;
802
- startBusLeaderPolling: (ctx: TContext) => Promise<void>;
803
- stopBusLeaderPolling: () => Promise<void>;
804
- startLeaderHealth: () => void;
805
- stopLeaderHealth: () => void;
806
- registerFollowerWithLeader: (
807
- ctx: TContext,
808
- owner: TOwner,
809
- ) => Promise<boolean | undefined>;
810
- restoreFollowerWithLeader?: (
811
- ctx: TContext,
812
- owner: TOwner,
813
- ) => Promise<boolean | undefined>;
814
- hasRememberedWorkspaceBinding?: (ctx: TContext) => boolean;
815
808
  suspendLiveThreadTarget?: () => void;
816
- stopFollowerRegistration: () => void;
817
- isTopicModeUnavailableError: (error: unknown) => boolean;
818
809
  updateStatus: (ctx: TContext) => void;
819
810
  recordEvent: (
820
811
  category: string,
@@ -864,7 +855,9 @@ export function createTelegramThreadCapabilityOrchestration<TContext, TOwner>(
864
855
  const expected = generation;
865
856
  return () => expected === generation;
866
857
  },
867
- invalidate() { generation++; },
858
+ invalidate() {
859
+ generation++;
860
+ },
868
861
  };
869
862
  const capabilityDeps: TelegramThreadCapabilityRuntimeDeps<TContext> = {
870
863
  lifecycle,
@@ -987,6 +980,32 @@ function hasTelegramThreadCapabilityBindings(
987
980
  );
988
981
  }
989
982
 
983
+ /** Falls back to classic polling, recording a failed restore durably; false once the attempt is stale. */
984
+ async function restoreTelegramClassicPolling<TContext>(
985
+ ctx: TContext,
986
+ phase: string,
987
+ deps: TelegramThreadCapabilityRuntimeDeps<TContext>,
988
+ isCurrent: () => boolean,
989
+ ): Promise<boolean> {
990
+ try {
991
+ await deps.startClassicPolling(ctx);
992
+ return isCurrent();
993
+ } catch (classicError) {
994
+ if (!isCurrent()) return false;
995
+ deps.topicTargetStore.setBotState({
996
+ threadMode: "disabled",
997
+ updatedAtMs: (deps.getNowMs ?? Date.now)(),
998
+ lastReconcileAction: `${phase}-classic-restore-failed`,
999
+ });
1000
+ await deps.topicTargetStore.persist();
1001
+ if (!isCurrent()) return false;
1002
+ deps.recordEvent("bus", classicError, {
1003
+ phase: `${phase}-classic-restore`,
1004
+ });
1005
+ return true;
1006
+ }
1007
+ }
1008
+
990
1009
  export async function applyTelegramThreadCapability<TContext>(
991
1010
  ctx: TContext,
992
1011
  threadModeEnabled: boolean,
@@ -1029,22 +1048,8 @@ export async function applyTelegramThreadCapability<TContext>(
1029
1048
  if (!isCurrent()) return;
1030
1049
  deps.setPollingStartedWithTelegramBus(false);
1031
1050
  if (hadLiveThreadTransport) deps.suspendLiveThreadTarget?.();
1032
- try {
1033
- await deps.startClassicPolling(ctx);
1034
- if (!isCurrent()) return;
1035
- } catch (classicError) {
1036
- if (!isCurrent()) return;
1037
- deps.topicTargetStore.setBotState({
1038
- threadMode: "disabled",
1039
- updatedAtMs: (deps.getNowMs ?? Date.now)(),
1040
- lastReconcileAction: `${phase}-classic-restore-failed`,
1041
- });
1042
- await deps.topicTargetStore.persist();
1043
- if (!isCurrent()) return;
1044
- deps.recordEvent("bus", classicError, {
1045
- phase: `${phase}-classic-restore`,
1046
- });
1047
- }
1051
+ if (!(await restoreTelegramClassicPolling(ctx, phase, deps, isCurrent)))
1052
+ return;
1048
1053
  }
1049
1054
  deps.updateStatus(ctx);
1050
1055
  return;
@@ -1081,22 +1086,8 @@ export async function applyTelegramThreadCapability<TContext>(
1081
1086
  if (!isCurrent()) return;
1082
1087
  deps.setTopicModeUnavailable(true);
1083
1088
  }
1084
- try {
1085
- await deps.startClassicPolling(ctx);
1086
- if (!isCurrent()) return;
1087
- } catch (classicError) {
1088
- if (!isCurrent()) return;
1089
- deps.topicTargetStore.setBotState({
1090
- threadMode: "disabled",
1091
- updatedAtMs: (deps.getNowMs ?? Date.now)(),
1092
- lastReconcileAction: `${phase}-classic-restore-failed`,
1093
- });
1094
- await deps.topicTargetStore.persist();
1095
- if (!isCurrent()) return;
1096
- deps.recordEvent("bus", classicError, {
1097
- phase: `${phase}-classic-restore`,
1098
- });
1099
- }
1089
+ if (!(await restoreTelegramClassicPolling(ctx, phase, deps, isCurrent)))
1090
+ return;
1100
1091
  deps.updateStatus(ctx);
1101
1092
  if (threadModeUnavailable) return;
1102
1093
  throw error;
@@ -1116,12 +1107,16 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
1116
1107
  const expectedGeneration = ++generation;
1117
1108
  deps.lifecycle?.invalidate();
1118
1109
  const isLifecycleCurrent = deps.lifecycle?.capture() ?? (() => true);
1119
- const isCurrent = () => expectedGeneration === generation && isLifecycleCurrent();
1110
+ const isCurrent = () =>
1111
+ expectedGeneration === generation && isLifecycleCurrent();
1120
1112
  await deps.topicTargetStore.load();
1121
1113
  if (!isCurrent()) return;
1122
1114
  let startupThreadCapability: boolean | undefined;
1123
1115
  try {
1124
- startupThreadCapability = await probeTelegramStartupThreadCapability(deps, isCurrent);
1116
+ startupThreadCapability = await probeTelegramStartupThreadCapability(
1117
+ deps,
1118
+ isCurrent,
1119
+ );
1125
1120
  } catch (error) {
1126
1121
  if (!isCurrent()) return;
1127
1122
  deps.recordEvent("bus", error, { phase: "startup-thread-mode-probe" });
@@ -1168,7 +1163,8 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
1168
1163
  deps.stopLeaderHealth();
1169
1164
  if (deps.getPollingStartedWithTelegramBus()) {
1170
1165
  await deps.stopBusLeaderPolling();
1171
- if (expectedGeneration === generation) deps.setPollingStartedWithTelegramBus(false);
1166
+ if (expectedGeneration === generation)
1167
+ deps.setPollingStartedWithTelegramBus(false);
1172
1168
  return;
1173
1169
  }
1174
1170
  await deps.stopClassicPolling();
@@ -1193,13 +1189,25 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
1193
1189
  owner: TOwner,
1194
1190
  ): Promise<boolean | undefined> => {
1195
1191
  if (!(await refreshFollowerState())) {
1196
- deps.recordEvent("bus", "Telegram follower auto-connect skipped: Threaded Mode is not enabled.",
1197
- { phase: "follower-auto-connect-skip", reason: "thread-mode-unavailable" });
1192
+ deps.recordEvent(
1193
+ "bus",
1194
+ "Telegram follower auto-connect skipped: Threaded Mode is not enabled.",
1195
+ {
1196
+ phase: "follower-auto-connect-skip",
1197
+ reason: "thread-mode-unavailable",
1198
+ },
1199
+ );
1198
1200
  return undefined;
1199
1201
  }
1200
1202
  if (!deps.hasRememberedWorkspaceBinding?.(ctx)) {
1201
- deps.recordEvent("bus", "Telegram follower auto-connect skipped: no binding for this session.",
1202
- { phase: "follower-auto-connect-skip", reason: "session-binding-unavailable" });
1203
+ deps.recordEvent(
1204
+ "bus",
1205
+ "Telegram follower auto-connect skipped: no binding for this session.",
1206
+ {
1207
+ phase: "follower-auto-connect-skip",
1208
+ reason: "session-binding-unavailable",
1209
+ },
1210
+ );
1203
1211
  return undefined;
1204
1212
  }
1205
1213
  return deps.restoreFollowerWithLeader?.(ctx, owner);
@@ -1270,7 +1278,8 @@ export function createTelegramThreadCapabilityMonitor<TContext>(
1270
1278
  }
1271
1279
  const expectedGeneration = generation;
1272
1280
  const isLifecycleCurrent = deps.lifecycle?.capture() ?? (() => true);
1273
- const isCurrent = (): boolean => generation === expectedGeneration && isLifecycleCurrent();
1281
+ const isCurrent = (): boolean =>
1282
+ generation === expectedGeneration && isLifecycleCurrent();
1274
1283
  let tracked: Promise<void>;
1275
1284
  tracked = readTelegramThreadCapability(deps)
1276
1285
  .then(async (threadModeEnabled) => {
@@ -1363,7 +1372,11 @@ export function createTelegramThreadCapabilityMonitor<TContext>(
1363
1372
  try {
1364
1373
  check(ctx);
1365
1374
  } catch (error) {
1366
- try { stop(); } catch { /* Timer shutdown must not escape the callback. */ }
1375
+ try {
1376
+ stop();
1377
+ } catch {
1378
+ /* Timer shutdown must not escape the callback. */
1379
+ }
1367
1380
  try {
1368
1381
  deps.recordEvent("bus", error, { phase: "capability-monitor" });
1369
1382
  } catch {
@@ -1487,13 +1500,10 @@ export async function admitTelegramPollingUpdateBatch<
1487
1500
  return { updateCount: deps.updates.length, latestUpdateId };
1488
1501
  }
1489
1502
 
1490
- export interface TelegramPollLoopDeps<
1503
+ /** Bot API, journal and progress ports shared by one poll loop and its restartable runner. */
1504
+ export interface TelegramPollLoopTransportPorts<
1491
1505
  TUpdate extends TelegramUpdate,
1492
- TContext = unknown,
1493
1506
  > extends TelegramRuntimeEventRecorderPort {
1494
- ctx: TContext;
1495
- signal: AbortSignal;
1496
- config: TelegramPollingConfig;
1497
1507
  deleteWebhook: (signal: AbortSignal) => Promise<unknown>;
1498
1508
  getUpdates: (
1499
1509
  body: Record<string, unknown>,
@@ -1508,9 +1518,6 @@ export interface TelegramPollLoopDeps<
1508
1518
  getAcceptedThroughUpdateId?: () => number | undefined;
1509
1519
  getJournalEntryCount: () => number;
1510
1520
  signalUpdateWorker: () => void;
1511
- onErrorStatus: (message: string) => void;
1512
- onStatusReset: () => void;
1513
- sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
1514
1521
  onPhaseChange?: (
1515
1522
  phase: TelegramPollingWorkPhase,
1516
1523
  currentUpdateId?: number,
@@ -1518,32 +1525,25 @@ export interface TelegramPollLoopDeps<
1518
1525
  onSuccessfulResponse?: (updateCount: number) => void;
1519
1526
  }
1520
1527
 
1528
+ export interface TelegramPollLoopDeps<
1529
+ TUpdate extends TelegramUpdate,
1530
+ TContext = unknown,
1531
+ > extends TelegramPollLoopTransportPorts<TUpdate> {
1532
+ ctx: TContext;
1533
+ signal: AbortSignal;
1534
+ config: TelegramPollingConfig;
1535
+ onErrorStatus: (message: string) => void;
1536
+ onStatusReset: () => void;
1537
+ sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
1538
+ }
1539
+
1521
1540
  export interface TelegramPollLoopRunnerDeps<
1522
1541
  TUpdate extends TelegramUpdate,
1523
1542
  TContext = unknown,
1524
- > extends TelegramRuntimeEventRecorderPort {
1543
+ > extends TelegramPollLoopTransportPorts<TUpdate> {
1525
1544
  getConfig: () => TelegramPollingConfig;
1526
- deleteWebhook: (signal: AbortSignal) => Promise<unknown>;
1527
- getUpdates: (
1528
- body: Record<string, unknown>,
1529
- signal: AbortSignal,
1530
- ) => Promise<TUpdate[]>;
1531
- getUpdatesRequestBudgetMs?: (body: Record<string, unknown>) => number;
1532
- persistConfig: (config: TelegramPollingConfig) => Promise<void>;
1533
- appendUpdateBatch: (
1534
- updates: readonly TUpdate[],
1535
- acceptedThroughUpdateId?: number,
1536
- ) => MaybePromise<unknown>;
1537
- getAcceptedThroughUpdateId?: () => number | undefined;
1538
- getJournalEntryCount: () => number;
1539
- signalUpdateWorker: () => void;
1540
1545
  updateStatus: (ctx: TContext, message?: string) => void;
1541
1546
  sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
1542
- onPhaseChange?: (
1543
- phase: TelegramPollingWorkPhase,
1544
- currentUpdateId?: number,
1545
- ) => void;
1546
- onSuccessfulResponse?: (updateCount: number) => void;
1547
1547
  }
1548
1548
 
1549
1549
  export function sleepTelegramPollingRetry(
@@ -1641,10 +1641,7 @@ function reportTelegramPollingPhase(
1641
1641
  function reportTelegramPollingResponse<
1642
1642
  TUpdate extends TelegramUpdate,
1643
1643
  TContext,
1644
- >(
1645
- deps: TelegramPollLoopDeps<TUpdate, TContext>,
1646
- updateCount: number,
1647
- ): void {
1644
+ >(deps: TelegramPollLoopDeps<TUpdate, TContext>, updateCount: number): void {
1648
1645
  try {
1649
1646
  deps.onSuccessfulResponse?.(updateCount);
1650
1647
  } catch (error) {
@@ -1724,11 +1721,16 @@ export async function runTelegramPollLoop<
1724
1721
  let consecutiveFailures = 0;
1725
1722
  const retryConflict = async () => {
1726
1723
  consecutiveGetUpdatesConflicts += 1;
1727
- if (consecutiveGetUpdatesConflicts >= TELEGRAM_GET_UPDATES_CONFLICT_STOP_LIMIT) {
1728
- throw new TelegramPersistentGetUpdatesConflictError(consecutiveGetUpdatesConflicts);
1724
+ if (
1725
+ consecutiveGetUpdatesConflicts >= TELEGRAM_GET_UPDATES_CONFLICT_STOP_LIMIT
1726
+ ) {
1727
+ throw new TelegramPersistentGetUpdatesConflictError(
1728
+ consecutiveGetUpdatesConflicts,
1729
+ );
1729
1730
  }
1730
1731
  await deps.sleep(
1731
- consecutiveGetUpdatesConflicts < TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT
1732
+ consecutiveGetUpdatesConflicts <
1733
+ TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT
1732
1734
  ? TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_MS
1733
1735
  : TELEGRAM_GET_UPDATES_CONFLICT_SLOW_RETRY_MS,
1734
1736
  deps.signal,
@@ -1747,9 +1749,7 @@ export async function runTelegramPollLoop<
1747
1749
  "Telegram polling cursor is missing while the durable update journal is non-empty.",
1748
1750
  );
1749
1751
  }
1750
- if (
1751
- deps.getAcceptedThroughUpdateId?.() === undefined
1752
- ) {
1752
+ if (deps.getAcceptedThroughUpdateId?.() === undefined) {
1753
1753
  try {
1754
1754
  const request = buildTelegramInitialSyncRequest();
1755
1755
  reportTelegramPollingPhase(deps, "long-poll");
@@ -1757,11 +1757,7 @@ export async function runTelegramPollLoop<
1757
1757
  reportTelegramPollingResponse(deps, updates.length);
1758
1758
  const lastUpdateId = getLatestTelegramUpdateId(updates);
1759
1759
  if (lastUpdateId !== undefined) {
1760
- reportTelegramPollingPhase(
1761
- deps,
1762
- "persisting-offset",
1763
- lastUpdateId,
1764
- );
1760
+ reportTelegramPollingPhase(deps, "persisting-offset", lastUpdateId);
1765
1761
  await deps.appendUpdateBatch([], lastUpdateId);
1766
1762
  deps.recordRuntimeEvent?.(
1767
1763
  "polling",
@@ -1774,12 +1770,13 @@ export async function runTelegramPollLoop<
1774
1770
  reportTelegramPollingPhase(deps, "retrying");
1775
1771
  if (isTelegramGetUpdatesConflictError(error)) {
1776
1772
  await retryConflict();
1777
- } else deps.recordRuntimeEvent?.("polling", error, {
1778
- phase: "initial-sync",
1779
- ...(error instanceof TelegramGetUpdatesTimeoutError
1780
- ? { timeoutMs: error.timeoutMs }
1781
- : {}),
1782
- });
1773
+ } else
1774
+ deps.recordRuntimeEvent?.("polling", error, {
1775
+ phase: "initial-sync",
1776
+ ...(error instanceof TelegramGetUpdatesTimeoutError
1777
+ ? { timeoutMs: error.timeoutMs }
1778
+ : {}),
1779
+ });
1783
1780
  }
1784
1781
  }
1785
1782
  let currentUpdateId: number | undefined;