@llblab/pi-telegram 0.52.1 → 0.53.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 (204) hide show
  1. package/AGENTS.md +10 -8
  2. package/BACKLOG.md +1 -16
  3. package/CHANGELOG.md +17 -0
  4. package/README.md +8 -10
  5. package/api/commands.ts +3 -0
  6. package/dist/api/commands.d.ts +1 -1
  7. package/dist/lib/activity-verbosity.d.ts +1 -5
  8. package/dist/lib/activity-verbosity.js +15 -42
  9. package/dist/lib/activity.d.ts +9 -0
  10. package/dist/lib/activity.js +39 -10
  11. package/dist/lib/agent-messages.js +7 -3
  12. package/dist/lib/bindings.d.ts +43 -5
  13. package/dist/lib/bindings.js +163 -20
  14. package/dist/lib/bus-api.d.ts +12 -0
  15. package/dist/lib/bus-api.js +182 -79
  16. package/dist/lib/bus-follower.d.ts +111 -31
  17. package/dist/lib/bus-follower.js +1324 -359
  18. package/dist/lib/bus-leader.d.ts +41 -91
  19. package/dist/lib/bus-leader.js +969 -637
  20. package/dist/lib/bus-transport.js +16 -9
  21. package/dist/lib/bus.d.ts +215 -18
  22. package/dist/lib/bus.js +1048 -327
  23. package/dist/lib/channel-posts.d.ts +5 -0
  24. package/dist/lib/channel-posts.js +434 -261
  25. package/dist/lib/command-templates.d.ts +26 -5
  26. package/dist/lib/command-templates.js +92 -176
  27. package/dist/lib/commands.d.ts +168 -82
  28. package/dist/lib/commands.js +1409 -291
  29. package/dist/lib/config.d.ts +1 -8
  30. package/dist/lib/config.js +34 -76
  31. package/dist/lib/delivery.d.ts +2 -2
  32. package/dist/lib/delivery.js +16 -16
  33. package/dist/lib/extension.js +640 -171
  34. package/dist/lib/generative-app-worker.mjs +19 -7
  35. package/dist/lib/generative-apps.d.ts +0 -5
  36. package/dist/lib/generative-apps.js +61 -49
  37. package/dist/lib/inbound.d.ts +5 -0
  38. package/dist/lib/inbound.js +35 -59
  39. package/dist/lib/journal.d.ts +33 -3
  40. package/dist/lib/journal.js +2063 -782
  41. package/dist/lib/lifecycle.d.ts +2 -6
  42. package/dist/lib/lifecycle.js +75 -58
  43. package/dist/lib/locks.d.ts +34 -3
  44. package/dist/lib/locks.js +318 -119
  45. package/dist/lib/logging.js +50 -14
  46. package/dist/lib/media.d.ts +22 -1
  47. package/dist/lib/media.js +46 -28
  48. package/dist/lib/menu-model.d.ts +19 -5
  49. package/dist/lib/menu-model.js +53 -11
  50. package/dist/lib/menu-queue.d.ts +5 -4
  51. package/dist/lib/menu-queue.js +45 -63
  52. package/dist/lib/menu-settings.d.ts +5 -4
  53. package/dist/lib/menu-settings.js +114 -78
  54. package/dist/lib/menu-status.d.ts +1 -1
  55. package/dist/lib/menu-status.js +8 -25
  56. package/dist/lib/menu-thinking.d.ts +6 -1
  57. package/dist/lib/menu-thinking.js +14 -21
  58. package/dist/lib/menu.d.ts +8 -61
  59. package/dist/lib/menu.js +92 -164
  60. package/dist/lib/model.d.ts +1 -1
  61. package/dist/lib/model.js +2 -16
  62. package/dist/lib/outbound-attachments.d.ts +6 -27
  63. package/dist/lib/outbound-attachments.js +45 -38
  64. package/dist/lib/outbound-buttons.js +22 -16
  65. package/dist/lib/outbound-markup.d.ts +0 -2
  66. package/dist/lib/outbound-markup.js +51 -64
  67. package/dist/lib/outbound-voice.js +6 -3
  68. package/dist/lib/outbound.d.ts +6 -6
  69. package/dist/lib/outbound.js +61 -51
  70. package/dist/lib/ownership.js +6 -2
  71. package/dist/lib/paths.d.ts +5 -0
  72. package/dist/lib/paths.js +37 -21
  73. package/dist/lib/pi.d.ts +2 -0
  74. package/dist/lib/pi.js +8 -1
  75. package/dist/lib/polling.d.ts +23 -37
  76. package/dist/lib/polling.js +71 -52
  77. package/dist/lib/preview.js +39 -19
  78. package/dist/lib/process-identity.d.ts +24 -0
  79. package/dist/lib/process-identity.js +133 -0
  80. package/dist/lib/prompts.js +2 -7
  81. package/dist/lib/queue.d.ts +14 -16
  82. package/dist/lib/queue.js +249 -126
  83. package/dist/lib/recovery.d.ts +2 -83
  84. package/dist/lib/recovery.js +20 -274
  85. package/dist/lib/replies.d.ts +16 -24
  86. package/dist/lib/replies.js +99 -116
  87. package/dist/lib/routing.d.ts +120 -7
  88. package/dist/lib/routing.js +4667 -1152
  89. package/dist/lib/runtime.d.ts +1 -11
  90. package/dist/lib/sections.d.ts +4 -29
  91. package/dist/lib/sections.js +7 -20
  92. package/dist/lib/skills.js +4 -3
  93. package/dist/lib/status.js +86 -57
  94. package/dist/lib/sync.d.ts +6 -3
  95. package/dist/lib/sync.js +112 -45
  96. package/dist/lib/target.d.ts +4 -0
  97. package/dist/lib/target.js +18 -0
  98. package/dist/lib/telegram-api.d.ts +18 -13
  99. package/dist/lib/telegram-api.js +165 -53
  100. package/dist/lib/text-groups.js +4 -39
  101. package/dist/lib/thread-cleanup-manager.d.ts +6 -27
  102. package/dist/lib/thread-cleanup-manager.js +369 -186
  103. package/dist/lib/thread-display.d.ts +1 -2
  104. package/dist/lib/thread-display.js +58 -38
  105. package/dist/lib/thread-naming.d.ts +28 -8
  106. package/dist/lib/thread-naming.js +175 -66
  107. package/dist/lib/thread-reconciler.d.ts +42 -16
  108. package/dist/lib/thread-reconciler.js +90 -50
  109. package/dist/lib/threads.d.ts +82 -20
  110. package/dist/lib/threads.js +2247 -855
  111. package/dist/lib/time-injection.js +1 -2
  112. package/dist/lib/turns.js +1 -19
  113. package/dist/lib/updates.d.ts +169 -80
  114. package/dist/lib/updates.js +2418 -641
  115. package/dist/lib/voice.d.ts +7 -22
  116. package/dist/lib/voice.js +7 -22
  117. package/dist/lib/wire.js +1 -1
  118. package/dist/lib/workspace-admission.d.ts +3 -1
  119. package/dist/lib/workspace-admission.js +425 -211
  120. package/dist/lib/workspace-identity.js +8 -13
  121. package/dist/lib/workspace-retirement.d.ts +1 -1
  122. package/dist/lib/workspace-retirement.js +328 -140
  123. package/dist/lib/workspace-slots.js +8 -4
  124. package/dist/package.json +1 -1
  125. package/docs/architecture.md +36 -30
  126. package/docs/inbound.md +1 -1
  127. package/docs/multi-instance-bus.md +15 -3
  128. package/docs/outbound.md +1 -1
  129. package/docs/public-api.md +29 -1
  130. package/docs/ui-style.md +25 -8
  131. package/lib/activity-verbosity.ts +59 -82
  132. package/lib/activity.ts +146 -102
  133. package/lib/agent-messages.ts +12 -5
  134. package/lib/bindings.ts +389 -82
  135. package/lib/bus-api.ts +263 -102
  136. package/lib/bus-follower.ts +2440 -601
  137. package/lib/bus-leader.ts +2209 -1290
  138. package/lib/bus-transport.ts +46 -12
  139. package/lib/bus.ts +1673 -444
  140. package/lib/channel-posts.ts +848 -327
  141. package/lib/command-templates.ts +152 -265
  142. package/lib/commands.ts +2398 -555
  143. package/lib/config.ts +177 -170
  144. package/lib/delivery.ts +41 -29
  145. package/lib/extension.ts +1207 -433
  146. package/lib/generative-app-worker.mjs +19 -7
  147. package/lib/generative-apps.ts +324 -211
  148. package/lib/inbound.ts +95 -97
  149. package/lib/journal.ts +5311 -1763
  150. package/lib/keyboard.ts +1 -3
  151. package/lib/lifecycle.ts +128 -78
  152. package/lib/locks.ts +703 -212
  153. package/lib/logging.ts +147 -58
  154. package/lib/media.ts +82 -43
  155. package/lib/menu-model.ts +85 -18
  156. package/lib/menu-queue.ts +88 -129
  157. package/lib/menu-settings.ts +260 -155
  158. package/lib/menu-status.ts +20 -60
  159. package/lib/menu-thinking.ts +41 -56
  160. package/lib/menu.ts +112 -285
  161. package/lib/model.ts +8 -22
  162. package/lib/outbound-attachments.ts +156 -125
  163. package/lib/outbound-buttons.ts +76 -49
  164. package/lib/outbound-markup.ts +96 -94
  165. package/lib/outbound-voice.ts +33 -27
  166. package/lib/outbound.ts +125 -125
  167. package/lib/ownership.ts +19 -10
  168. package/lib/paths.ts +148 -49
  169. package/lib/pi.ts +35 -15
  170. package/lib/polling.ts +165 -149
  171. package/lib/preview.ts +93 -40
  172. package/lib/process-identity.ts +164 -0
  173. package/lib/prompts.ts +4 -11
  174. package/lib/queue.ts +511 -300
  175. package/lib/recovery.ts +50 -454
  176. package/lib/rendering.ts +1 -3
  177. package/lib/replies.ts +248 -209
  178. package/lib/routing.ts +8509 -2186
  179. package/lib/runtime.ts +1 -12
  180. package/lib/sections.ts +24 -89
  181. package/lib/setup.ts +1 -3
  182. package/lib/skills.ts +9 -4
  183. package/lib/status.ts +215 -110
  184. package/lib/sync.ts +488 -359
  185. package/lib/target.ts +22 -3
  186. package/lib/telegram-api.ts +322 -127
  187. package/lib/text-groups.ts +5 -39
  188. package/lib/thread-cleanup-manager.ts +833 -331
  189. package/lib/thread-display.ts +213 -97
  190. package/lib/thread-naming.ts +232 -87
  191. package/lib/thread-reconciler.ts +155 -72
  192. package/lib/threads.ts +5318 -1845
  193. package/lib/time-injection.ts +1 -4
  194. package/lib/turns.ts +15 -28
  195. package/lib/updates.ts +5184 -1612
  196. package/lib/voice.ts +7 -35
  197. package/lib/wire.ts +1 -1
  198. package/lib/workspace-admission.ts +996 -416
  199. package/lib/workspace-identity.ts +14 -17
  200. package/lib/workspace-retirement.ts +878 -376
  201. package/lib/workspace-slots.ts +22 -13
  202. package/package.json +1 -1
  203. package/scripts/check-downgrade.mjs +0 -472
  204. package/scripts/measure-workspace.mjs +0 -108
@@ -14,7 +14,8 @@ export const TELEGRAM_GET_UPDATES_CONFLICT_STOP_LIMIT = 10;
14
14
  const TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT = 3;
15
15
  const TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_MS = 1_000;
16
16
  const TELEGRAM_GET_UPDATES_CONFLICT_SLOW_RETRY_MS = 3_000;
17
- const TELEGRAM_POLLING_RETRY_MS = 3_000;
17
+ const TELEGRAM_POLLING_RETRY_BASE_MS = 1_000;
18
+ const TELEGRAM_POLLING_RETRY_MAX_MS = 30_000;
18
19
  const TELEGRAM_GET_UPDATES_GRACE_MS = 10_000;
19
20
  // Standard Telegram DM polling does not expose ordinary message-deletion events.
20
21
  // Business deletions belong to a separate namespace and default routing ignores them.
@@ -51,6 +52,11 @@ export class TelegramPersistentGetUpdatesConflictError extends Error {
51
52
  this.count = count;
52
53
  }
53
54
  }
55
+ /** Exponential backoff for non-conflict polling/admission failures. */
56
+ export function getTelegramPollingRetryDelayMs(consecutiveFailures) {
57
+ const exponent = Math.max(0, Math.min(consecutiveFailures - 1, 16));
58
+ return Math.min(TELEGRAM_POLLING_RETRY_BASE_MS * 2 ** exponent, TELEGRAM_POLLING_RETRY_MAX_MS);
59
+ }
54
60
  export class TelegramGetUpdatesTimeoutError extends Error {
55
61
  timeoutMs;
56
62
  constructor(timeoutMs) {
@@ -152,7 +158,10 @@ export function createTelegramDurablePollingRuntimeAssembly(deps) {
152
158
  }
153
159
  catch (error) {
154
160
  try {
155
- deps.recordRuntimeEvent?.("polling", error, { phase: "batch-preparation", updateCount: updates.length });
161
+ deps.recordRuntimeEvent?.("polling", error, {
162
+ phase: "batch-preparation",
163
+ updateCount: updates.length,
164
+ });
156
165
  }
157
166
  catch {
158
167
  // Already-published input must still reach the worker if diagnostics fail.
@@ -356,8 +365,13 @@ export function startTelegramPollingRuntime(ctx, deps) {
356
365
  deps.setPollingController(undefined);
357
366
  if (!ownsPromise && !ownsController)
358
367
  return;
359
- deps.onPollingStopped?.(controller.signal.aborted ? "requested" : persistentConflict
360
- ? "persistent-conflict" : failed ? "failed" : "completed");
368
+ deps.onPollingStopped?.(controller.signal.aborted
369
+ ? "requested"
370
+ : persistentConflict
371
+ ? "persistent-conflict"
372
+ : failed
373
+ ? "failed"
374
+ : "completed");
361
375
  // Detach the inner promise before outer teardown calls polling.stop().
362
376
  if (persistentConflict && !controller.signal.aborted) {
363
377
  try {
@@ -367,12 +381,15 @@ export function startTelegramPollingRuntime(ctx, deps) {
367
381
  else {
368
382
  deps.stopTypingLoop();
369
383
  deps.recordRuntimeEvent?.("polling", persistentConflict, {
370
- phase: "persistent-conflict", count: persistentConflict.count,
384
+ phase: "persistent-conflict",
385
+ count: persistentConflict.count,
371
386
  });
372
387
  }
373
388
  }
374
389
  catch (error) {
375
- deps.recordRuntimeEvent?.("polling", error, { phase: "conflict-stand-down" });
390
+ deps.recordRuntimeEvent?.("polling", error, {
391
+ phase: "conflict-stand-down",
392
+ });
376
393
  }
377
394
  if (deps.getPollingController() || deps.getPollingPromise())
378
395
  return;
@@ -429,7 +446,9 @@ export function createTelegramThreadCapabilityOrchestration(deps) {
429
446
  const expected = generation;
430
447
  return () => expected === generation;
431
448
  },
432
- invalidate() { generation++; },
449
+ invalidate() {
450
+ generation++;
451
+ },
433
452
  };
434
453
  const capabilityDeps = {
435
454
  lifecycle,
@@ -537,6 +556,29 @@ function hasTelegramThreadCapabilityBindings(store) {
537
556
  record.status !== "stale");
538
557
  }) ?? false);
539
558
  }
559
+ /** Falls back to classic polling, recording a failed restore durably; false once the attempt is stale. */
560
+ async function restoreTelegramClassicPolling(ctx, phase, deps, isCurrent) {
561
+ try {
562
+ await deps.startClassicPolling(ctx);
563
+ return isCurrent();
564
+ }
565
+ catch (classicError) {
566
+ if (!isCurrent())
567
+ return false;
568
+ deps.topicTargetStore.setBotState({
569
+ threadMode: "disabled",
570
+ updatedAtMs: (deps.getNowMs ?? Date.now)(),
571
+ lastReconcileAction: `${phase}-classic-restore-failed`,
572
+ });
573
+ await deps.topicTargetStore.persist();
574
+ if (!isCurrent())
575
+ return false;
576
+ deps.recordEvent("bus", classicError, {
577
+ phase: `${phase}-classic-restore`,
578
+ });
579
+ return true;
580
+ }
581
+ }
540
582
  export async function applyTelegramThreadCapability(ctx, threadModeEnabled, phase, deps, isCurrent = deps.lifecycle?.capture() ?? (() => true)) {
541
583
  if (!isCurrent())
542
584
  return;
@@ -574,26 +616,8 @@ export async function applyTelegramThreadCapability(ctx, threadModeEnabled, phas
574
616
  deps.setPollingStartedWithTelegramBus(false);
575
617
  if (hadLiveThreadTransport)
576
618
  deps.suspendLiveThreadTarget?.();
577
- try {
578
- await deps.startClassicPolling(ctx);
579
- if (!isCurrent())
580
- return;
581
- }
582
- catch (classicError) {
583
- if (!isCurrent())
584
- return;
585
- deps.topicTargetStore.setBotState({
586
- threadMode: "disabled",
587
- updatedAtMs: (deps.getNowMs ?? Date.now)(),
588
- lastReconcileAction: `${phase}-classic-restore-failed`,
589
- });
590
- await deps.topicTargetStore.persist();
591
- if (!isCurrent())
592
- return;
593
- deps.recordEvent("bus", classicError, {
594
- phase: `${phase}-classic-restore`,
595
- });
596
- }
619
+ if (!(await restoreTelegramClassicPolling(ctx, phase, deps, isCurrent)))
620
+ return;
597
621
  }
598
622
  deps.updateStatus(ctx);
599
623
  return;
@@ -635,26 +659,8 @@ export async function applyTelegramThreadCapability(ctx, threadModeEnabled, phas
635
659
  return;
636
660
  deps.setTopicModeUnavailable(true);
637
661
  }
638
- try {
639
- await deps.startClassicPolling(ctx);
640
- if (!isCurrent())
641
- return;
642
- }
643
- catch (classicError) {
644
- if (!isCurrent())
645
- return;
646
- deps.topicTargetStore.setBotState({
647
- threadMode: "disabled",
648
- updatedAtMs: (deps.getNowMs ?? Date.now)(),
649
- lastReconcileAction: `${phase}-classic-restore-failed`,
650
- });
651
- await deps.topicTargetStore.persist();
652
- if (!isCurrent())
653
- return;
654
- deps.recordEvent("bus", classicError, {
655
- phase: `${phase}-classic-restore`,
656
- });
657
- }
662
+ if (!(await restoreTelegramClassicPolling(ctx, phase, deps, isCurrent)))
663
+ return;
658
664
  deps.updateStatus(ctx);
659
665
  if (threadModeUnavailable)
660
666
  return;
@@ -753,11 +759,17 @@ export function createTelegramThreadAwarePollingPorts(deps) {
753
759
  };
754
760
  const restoreFollowerWithOwner = async (ctx, owner) => {
755
761
  if (!(await refreshFollowerState())) {
756
- deps.recordEvent("bus", "Telegram follower auto-connect skipped: Threaded Mode is not enabled.", { phase: "follower-auto-connect-skip", reason: "thread-mode-unavailable" });
762
+ deps.recordEvent("bus", "Telegram follower auto-connect skipped: Threaded Mode is not enabled.", {
763
+ phase: "follower-auto-connect-skip",
764
+ reason: "thread-mode-unavailable",
765
+ });
757
766
  return undefined;
758
767
  }
759
768
  if (!deps.hasRememberedWorkspaceBinding?.(ctx)) {
760
- deps.recordEvent("bus", "Telegram follower auto-connect skipped: no binding for this session.", { phase: "follower-auto-connect-skip", reason: "session-binding-unavailable" });
769
+ deps.recordEvent("bus", "Telegram follower auto-connect skipped: no binding for this session.", {
770
+ phase: "follower-auto-connect-skip",
771
+ reason: "session-binding-unavailable",
772
+ });
761
773
  return undefined;
762
774
  }
763
775
  return deps.restoreFollowerWithLeader?.(ctx, owner);
@@ -892,7 +904,9 @@ export function createTelegramThreadCapabilityMonitor(deps) {
892
904
  try {
893
905
  stop();
894
906
  }
895
- catch { /* Timer shutdown must not escape the callback. */ }
907
+ catch {
908
+ /* Timer shutdown must not escape the callback. */
909
+ }
896
910
  try {
897
911
  deps.recordEvent("bus", error, { phase: "capability-monitor" });
898
912
  }
@@ -1103,12 +1117,14 @@ export async function runTelegramPollLoop(deps) {
1103
1117
  if (!deps.config.botToken)
1104
1118
  return;
1105
1119
  let consecutiveGetUpdatesConflicts = 0;
1120
+ let consecutiveFailures = 0;
1106
1121
  const retryConflict = async () => {
1107
1122
  consecutiveGetUpdatesConflicts += 1;
1108
1123
  if (consecutiveGetUpdatesConflicts >= TELEGRAM_GET_UPDATES_CONFLICT_STOP_LIMIT) {
1109
1124
  throw new TelegramPersistentGetUpdatesConflictError(consecutiveGetUpdatesConflicts);
1110
1125
  }
1111
- await deps.sleep(consecutiveGetUpdatesConflicts < TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT
1126
+ await deps.sleep(consecutiveGetUpdatesConflicts <
1127
+ TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT
1112
1128
  ? TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_MS
1113
1129
  : TELEGRAM_GET_UPDATES_CONFLICT_SLOW_RETRY_MS, deps.signal);
1114
1130
  };
@@ -1171,6 +1187,7 @@ export async function runTelegramPollLoop(deps) {
1171
1187
  onPhaseChange: deps.onPhaseChange,
1172
1188
  recordRuntimeEvent: deps.recordRuntimeEvent,
1173
1189
  });
1190
+ consecutiveFailures = 0;
1174
1191
  currentUpdateId = undefined;
1175
1192
  }
1176
1193
  catch (error) {
@@ -1190,8 +1207,10 @@ export async function runTelegramPollLoop(deps) {
1190
1207
  : {}),
1191
1208
  });
1192
1209
  consecutiveGetUpdatesConflicts = 0;
1210
+ consecutiveFailures += 1;
1193
1211
  deps.onErrorStatus(getTelegramPollingErrorMessage(error));
1194
- await deps.sleep(TELEGRAM_POLLING_RETRY_MS, deps.signal);
1212
+ // Cap the pause, not retry count: outages must recover without manual reconnect.
1213
+ await deps.sleep(getTelegramPollingRetryDelayMs(consecutiveFailures), deps.signal);
1195
1214
  if (deps.signal.aborted)
1196
1215
  return;
1197
1216
  deps.onStatusReset();
@@ -3,8 +3,8 @@
3
3
  * Zones: telegram outbound, native rich markdown drafts
4
4
  * Owns safe draft preview selection, runtime updates, and preview finalization
5
5
  */
6
- import { normalizeTelegramNativeMarkdown } from "./replies.js";
7
6
  import { stripTelegramCommentMarkupForPreview } from "./outbound-markup.js";
7
+ import { isTelegramMarkdownFenceClosing, normalizeTelegramNativeMarkdown, parseTelegramMarkdownFenceOpening, } from "./replies.js";
8
8
  import { getTelegramTargetThreadParams, } from "./target.js";
9
9
  import { shouldSuppressPreviewForVoice } from "./voice.js";
10
10
  const TELEGRAM_DRAFT_ID_MAX = 2_147_483_647;
@@ -73,9 +73,15 @@ export function createTelegramAssistantPreviewRuntime(deps) {
73
73
  clearPreview: async (chatId, options) => {
74
74
  if (controller.getState() !== state || !isDeliveryActive())
75
75
  return;
76
- await controller.prepareClear(chatId, { ...options, isDeliveryActive })();
76
+ await controller.prepareClear(chatId, {
77
+ ...options,
78
+ isDeliveryActive,
79
+ })();
77
80
  },
78
- finalizeMarkdownPreview: prepareTelegramNativeMarkdownPreviewFinalizer({ ...finalizerDeps, isDeliveryActive }),
81
+ finalizeMarkdownPreview: prepareTelegramNativeMarkdownPreviewFinalizer({
82
+ ...finalizerDeps,
83
+ isDeliveryActive,
84
+ }),
79
85
  };
80
86
  },
81
87
  ...createTelegramAssistantMessagePreviewHooks({
@@ -130,7 +136,10 @@ export function createTelegramPreviewController(deps) {
130
136
  createState: () => createTelegramPreviewRuntimeState(),
131
137
  resetState: () => {
132
138
  generation += 1;
133
- setState({ ...createTelegramPreviewRuntimeState(), nextDraftAt: state?.nextDraftAt });
139
+ setState({
140
+ ...createTelegramPreviewRuntimeState(),
141
+ nextDraftAt: state?.nextDraftAt,
142
+ });
134
143
  },
135
144
  invalidate: () => {
136
145
  generation += 1;
@@ -143,8 +152,15 @@ export function createTelegramPreviewController(deps) {
143
152
  sealTelegramPreviewState(state);
144
153
  const prior = state.publicationPromise ?? state.flushPromise ?? state.precedingFlush;
145
154
  let settle;
146
- state.publicationPromise = new Promise((resolve) => { settle = resolve; });
147
- return { wait: async () => { await prior?.catch(() => { }); }, settle };
155
+ state.publicationPromise = new Promise((resolve) => {
156
+ settle = resolve;
157
+ });
158
+ return {
159
+ wait: async () => {
160
+ await prior?.catch(() => { });
161
+ },
162
+ settle,
163
+ };
148
164
  },
149
165
  prepareClear: (chatId, options) => {
150
166
  const admittedState = state;
@@ -154,7 +170,8 @@ export function createTelegramPreviewController(deps) {
154
170
  return;
155
171
  await clearTelegramPreview(chatId, runtime, {
156
172
  ...options,
157
- isDeliveryActive: () => runtime.canSend?.() !== false && options?.isDeliveryActive?.() !== false,
173
+ isDeliveryActive: () => runtime.canSend?.() !== false &&
174
+ options?.isDeliveryActive?.() !== false,
158
175
  });
159
176
  };
160
177
  },
@@ -208,7 +225,8 @@ async function handleTelegramAssistantMessagePreviewStart(message, deps) {
208
225
  // Carry the previous delivery boundary; permanent text remains with its publication owner.
209
226
  next.draftId = state?.draftId;
210
227
  next.nextDraftAt = state?.nextDraftAt;
211
- next.precedingFlush = state?.publicationPromise ?? state?.flushPromise ?? state?.precedingFlush;
228
+ next.precedingFlush =
229
+ state?.publicationPromise ?? state?.flushPromise ?? state?.precedingFlush;
212
230
  deps.setState(next);
213
231
  }
214
232
  async function handleTelegramAssistantMessagePreviewUpdate(message, deps) {
@@ -280,7 +298,9 @@ export async function clearTelegramPreview(chatId, deps, options = {}) {
280
298
  if (options.isDeliveryActive?.() === false)
281
299
  return;
282
300
  deps.setState(undefined);
283
- if (state.mode === "draft" && state.draftId !== undefined && deps.canSend?.() !== false) {
301
+ if (state.mode === "draft" &&
302
+ state.draftId !== undefined &&
303
+ deps.canSend?.() !== false) {
284
304
  try {
285
305
  await deps.sendDraft(chatId, state.draftId, undefined, {
286
306
  ...getTelegramTargetThreadParams(options.target ?? { chatId }),
@@ -423,9 +443,8 @@ function updateTelegramDraftInlineStateForLine(line, state) {
423
443
  }
424
444
  }
425
445
  function updateTelegramDraftBlockStateForLine(line, state) {
426
- const fenceMatch = line.match(/^ {0,3}(`{3,}|~{3,})/);
427
446
  if (state.fence) {
428
- if (new RegExp(`^ {0,3}${state.fence.marker}{${state.fence.length},}\\s*$`).test(line)) {
447
+ if (isTelegramMarkdownFenceClosing(line, state.fence)) {
429
448
  state.fence = undefined;
430
449
  }
431
450
  return true;
@@ -435,12 +454,9 @@ function updateTelegramDraftBlockStateForLine(line, state) {
435
454
  state.displayMath = false;
436
455
  return true;
437
456
  }
438
- if (fenceMatch) {
439
- const markerText = fenceMatch[1] ?? "```";
440
- state.fence = {
441
- marker: markerText[0],
442
- length: markerText.length,
443
- };
457
+ const openingFence = parseTelegramMarkdownFenceOpening(line);
458
+ if (openingFence) {
459
+ state.fence = openingFence;
444
460
  return true;
445
461
  }
446
462
  if (line.trim() === "$$") {
@@ -523,7 +539,9 @@ async function performTelegramPreviewFlush(chatId, state, deps, options = {}) {
523
539
  state.nextDraftAt = Date.now() + (deps.minDraftIntervalMs ?? 0);
524
540
  try {
525
541
  const delivered = await deps.sendDraft(chatId, draftId, normalizeTelegramNativeMarkdown(snapshot.text), { ...getTelegramTargetThreadParams(options.target ?? { chatId }) });
526
- if (delivered === false || deps.getState() !== state || deps.canSend?.() === false)
542
+ if (delivered === false ||
543
+ deps.getState() !== state ||
544
+ deps.canSend?.() === false)
527
545
  return;
528
546
  deps.setDraftSupport("supported");
529
547
  state.mode = "draft";
@@ -584,7 +602,9 @@ export async function flushTelegramPreview(chatId, deps, options = {}) {
584
602
  });
585
603
  break;
586
604
  }
587
- } while (deps.getState() === state && !state.sealed && state.flushRequested);
605
+ } while (deps.getState() === state &&
606
+ !state.sealed &&
607
+ state.flushRequested);
588
608
  })();
589
609
  try {
590
610
  await state.flushPromise;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Process identity and liveness proofs
3
+ * Zones: shared utils, multi-instance bus, durable admission
4
+ * Owns PID liveness probes and stable Linux/macOS/Windows process-birth identity. Only an absent PID or a
5
+ * mismatched birth proof establishes death; inaccessible metadata stays unverifiable.
6
+ */
7
+ export declare function isProcessAlive(pid: number): boolean;
8
+ interface TelegramProcessBirthIdentityOptions {
9
+ platform?: NodeJS.Platform;
10
+ readProcStat?: (pid: number) => string;
11
+ readDarwinProcessStart?: (pid: number) => string;
12
+ readWindowsProcessStart?: (pid: number) => string;
13
+ }
14
+ export type TelegramProcessLiveness = "alive" | "dead" | "unverifiable";
15
+ interface TelegramProcessLivenessOptions extends TelegramProcessBirthIdentityOptions {
16
+ isProcessAlive?: (pid: number) => boolean;
17
+ }
18
+ export declare function getTelegramProcessBirthIdentity(pid: number, fallbackGeneration: number | string, options?: TelegramProcessBirthIdentityOptions): string;
19
+ export declare function getTelegramProcessLiveness(owner: {
20
+ processId: number;
21
+ processBirthId: string;
22
+ }, options?: TelegramProcessLivenessOptions): TelegramProcessLiveness;
23
+ export declare function getTelegramProcessBirthIdentityLiveness(processBirthId: string, options?: TelegramProcessLivenessOptions): TelegramProcessLiveness;
24
+ export {};
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Process identity and liveness proofs
3
+ * Zones: shared utils, multi-instance bus, durable admission
4
+ * Owns PID liveness probes and stable Linux/macOS/Windows process-birth identity. Only an absent PID or a
5
+ * mismatched birth proof establishes death; inaccessible metadata stays unverifiable.
6
+ */
7
+ import { execFileSync } from "node:child_process";
8
+ import { createHash } from "node:crypto";
9
+ import { readFileSync } from "node:fs";
10
+ import { platform as getPlatform } from "node:os";
11
+ export function isProcessAlive(pid) {
12
+ if (!Number.isInteger(pid) || pid <= 0)
13
+ return false;
14
+ try {
15
+ process.kill(pid, 0);
16
+ return true;
17
+ }
18
+ catch (error) {
19
+ // Only an absent PID proves death; permission and unexpected failures do not.
20
+ return error.code !== "ESRCH";
21
+ }
22
+ }
23
+ function readDarwinProcessStart(pid) {
24
+ return execFileSync("/bin/ps", ["-o", "lstart=", "-p", String(pid)], {
25
+ encoding: "utf8",
26
+ stdio: ["ignore", "pipe", "ignore"],
27
+ }).trim();
28
+ }
29
+ /** Creation time in UTC 100 ns ticks; PowerShell ships with every supported Windows and needs no native addon. */
30
+ function readWindowsProcessStart(pid) {
31
+ const root = process.env.SystemRoot ?? "C:\\Windows";
32
+ return execFileSync(`${root}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`, [
33
+ "-NoProfile",
34
+ "-NonInteractive",
35
+ "-Command",
36
+ `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks`,
37
+ ], {
38
+ encoding: "utf8",
39
+ stdio: ["ignore", "pipe", "ignore"],
40
+ timeout: 10_000,
41
+ windowsHide: true,
42
+ }).trim();
43
+ }
44
+ /** The current process never changes birth, so its slow Windows proof is read once; other PIDs are never cached. */
45
+ let ownWindowsBirthProof;
46
+ function getTelegramProcessBirthProof(pid, options = {}) {
47
+ if (!Number.isSafeInteger(pid) || pid <= 0)
48
+ return { status: "unverifiable" };
49
+ const platform = options.platform ?? getPlatform();
50
+ if (platform === "linux") {
51
+ try {
52
+ const stat = (options.readProcStat ??
53
+ ((targetPid) => readFileSync(`/proc/${targetPid}/stat`, "utf8")))(pid);
54
+ const closeParen = stat.lastIndexOf(")");
55
+ const fields = stat
56
+ .slice(closeParen + 2)
57
+ .trim()
58
+ .split(/\s+/u);
59
+ const startTicks = fields[19];
60
+ if (startTicks) {
61
+ return { status: "proven", identity: `${pid}:start:${startTicks}` };
62
+ }
63
+ }
64
+ catch {
65
+ /* inaccessible process metadata */
66
+ }
67
+ }
68
+ else if (platform === "darwin") {
69
+ try {
70
+ const startedAt = (options.readDarwinProcessStart ?? readDarwinProcessStart)(pid);
71
+ if (startedAt) {
72
+ const fingerprint = createHash("sha256")
73
+ .update(startedAt)
74
+ .digest("hex")
75
+ .slice(0, 16);
76
+ return { status: "proven", identity: `${pid}:start:${fingerprint}` };
77
+ }
78
+ }
79
+ catch {
80
+ /* inaccessible process metadata */
81
+ }
82
+ }
83
+ else if (platform === "win32") {
84
+ const injected = options.readWindowsProcessStart;
85
+ if (!injected && pid === process.pid && ownWindowsBirthProof)
86
+ return ownWindowsBirthProof;
87
+ let proof = { status: "unverifiable" };
88
+ try {
89
+ const ticks = (injected ?? readWindowsProcessStart)(pid);
90
+ if (/^\d+$/u.test(ticks))
91
+ proof = { status: "proven", identity: `${pid}:start:${ticks}` };
92
+ }
93
+ catch {
94
+ /* inaccessible process metadata, including another user's process */
95
+ }
96
+ if (!injected && pid === process.pid && proof.status === "proven")
97
+ ownWindowsBirthProof = proof;
98
+ return proof;
99
+ }
100
+ return { status: "unverifiable" };
101
+ }
102
+ export function getTelegramProcessBirthIdentity(pid, fallbackGeneration, options = {}) {
103
+ const proof = getTelegramProcessBirthProof(pid, options);
104
+ return proof.status === "proven"
105
+ ? proof.identity
106
+ : `${pid}:generation:${fallbackGeneration}`;
107
+ }
108
+ export function getTelegramProcessLiveness(owner, options = {}) {
109
+ const processAlive = options.isProcessAlive ?? isProcessAlive;
110
+ if (!processAlive(owner.processId))
111
+ return "dead";
112
+ const proof = getTelegramProcessBirthProof(owner.processId, options);
113
+ if (proof.status === "unverifiable")
114
+ return "unverifiable";
115
+ return proof.identity === owner.processBirthId ? "alive" : "dead";
116
+ }
117
+ export function getTelegramProcessBirthIdentityLiveness(processBirthId, options = {}) {
118
+ const match = /^(\d+):(start|generation):(.+)$/u.exec(processBirthId);
119
+ if (!match)
120
+ return "unverifiable";
121
+ const processId = Number(match[1]);
122
+ if (!Number.isSafeInteger(processId) || processId <= 0)
123
+ return "unverifiable";
124
+ const processAlive = options.isProcessAlive ?? isProcessAlive;
125
+ if (!processAlive(processId))
126
+ return "dead";
127
+ if (match[2] === "generation")
128
+ return "unverifiable";
129
+ const proof = getTelegramProcessBirthProof(processId, options);
130
+ if (proof.status === "unverifiable")
131
+ return "unverifiable";
132
+ return proof.identity === processBirthId ? "alive" : "dead";
133
+ }
@@ -121,13 +121,8 @@ export function buildTelegramBridgeSystemPrompt(options) {
121
121
  : "";
122
122
  return {
123
123
  systemPrompt: Array.isArray(basePrompt)
124
- ? [
125
- ...basePrompt,
126
- options.localSystemPromptSuffix + telegramSuffix,
127
- ]
128
- : basePrompt +
129
- options.localSystemPromptSuffix +
130
- telegramSuffix,
124
+ ? [...basePrompt, options.localSystemPromptSuffix + telegramSuffix]
125
+ : basePrompt + options.localSystemPromptSuffix + telegramSuffix,
131
126
  };
132
127
  }
133
128
  export function createTelegramBeforeAgentStartHook(options = {}) {
@@ -188,9 +188,7 @@ export declare function createTelegramQueueAdmissionReceipt(options: {
188
188
  scope: string;
189
189
  sourceUpdateIds: readonly number[];
190
190
  }): TelegramQueueAdmissionReceipt | undefined;
191
- export declare function isTelegramQueueItemDurablyAdmitted<TContext = unknown>(item: TelegramQueueItem<TContext>, isReceiptCommitted: (receipt: TelegramQueueAdmissionReceipt) => boolean): boolean;
192
191
  export declare function getTelegramQueueLaneContract(lane: TelegramQueueLane): TelegramQueueLaneContract;
193
- export declare function getTelegramQueueItemAdmissionMode(item: Pick<TelegramQueueItem, "queueLane">): TelegramQueueAdmissionMode;
194
192
  export declare function isTelegramQueueItemAdmissionValid(item: Pick<TelegramQueueItem, "kind" | "queueLane">): boolean;
195
193
  export declare function assertTelegramQueueItemAdmissionValid(item: Pick<TelegramQueueItem, "kind" | "queueLane" | "admissionReceipts">): void;
196
194
  export declare function createTelegramQueueStore<TContext = unknown>(initialItems?: TelegramQueueItem<TContext>[]): TelegramQueueStateStore<TContext>;
@@ -202,6 +200,13 @@ export declare function createTelegramTransportStampedQueueStore<TContext>(store
202
200
  export declare function countExecutableTelegramQueueItems<TContext = unknown>(items: readonly TelegramQueueItem<TContext>[]): number;
203
201
  export declare function createTelegramQueueItemCountGetter<TContext = unknown>(store: Pick<TelegramQueueStore<TContext>, "getQueuedItems">): () => number;
204
202
  export declare function createTelegramActiveTurnStore<TTurn extends PendingTelegramTurn = PendingTelegramTurn>(): TelegramActiveTurnStore<TTurn>;
203
+ /** Current captured targets only; skipped/receipt-held items remain work, never historical census. */
204
+ export declare function observeTelegramTargetQueueWork(target: TelegramQueueTarget & {
205
+ threadId: number;
206
+ }, activeTurn: Pick<TelegramQueueItemBase, "chatId" | "target"> | undefined, items: readonly Pick<TelegramQueueItemBase, "chatId" | "target">[]): {
207
+ protected: boolean;
208
+ unknown: boolean;
209
+ };
205
210
  export declare function partitionTelegramQueueItemsForHistory<TContext = unknown>(items: TelegramQueueItem<TContext>[]): {
206
211
  historyTurns: PendingTelegramTurn[];
207
212
  remainingItems: TelegramQueueItem<TContext>[];
@@ -586,7 +591,7 @@ export interface TelegramSessionShutdownRuntimeDeps<TQueueItem> {
586
591
  clearAbort: () => void;
587
592
  stopPolling: () => Promise<void>;
588
593
  }
589
- export interface TelegramSessionLifecycleHookRuntimeDeps<TContext, TQueueItem, TModel = unknown> extends TelegramRuntimeEventRecorderPort {
594
+ export interface TelegramSessionLifecycleHookRuntimeDeps<TContext, TQueueItem, TModel = unknown> extends TelegramRuntimeEventRecorderPort, Omit<TelegramSessionShutdownRuntimeDeps<TQueueItem>, "isSessionActive" | "discardQueuedItems" | "applyState"> {
590
595
  getCurrentModel: (ctx: TContext) => TModel | undefined;
591
596
  loadConfig: () => Promise<void>;
592
597
  applySessionStartState: (state: TelegramSessionStartState<TModel>) => void;
@@ -594,20 +599,8 @@ export interface TelegramSessionLifecycleHookRuntimeDeps<TContext, TQueueItem, T
594
599
  prepareTempDir: () => Promise<unknown>;
595
600
  updateStatus: (ctx: TContext) => void;
596
601
  isSessionActive?: (ctx: TContext) => boolean;
597
- unbindDeferredDispatchContext?: () => void;
598
602
  discardQueuedItems?: (ctx: TContext) => void;
599
603
  applySessionShutdownState: (state: TelegramSessionShutdownState<TQueueItem>) => void;
600
- clearPendingMediaGroups: () => void;
601
- clearModelMenuState: () => void;
602
- getActiveTurnChatId: () => number | undefined;
603
- getActiveTurnTarget?: () => TelegramQueueTarget | undefined;
604
- clearPreview: (chatId: number, options?: {
605
- target?: TelegramQueueTarget;
606
- }) => Promise<void>;
607
- previewShutdownTimeoutMs?: number;
608
- clearActiveTurn: () => void;
609
- clearAbort: () => void;
610
- stopPolling: () => Promise<void>;
611
604
  }
612
605
  export type TelegramSessionLifecycleHookEvent = unknown;
613
606
  export declare function createTelegramSessionStateApplier<TQueueItem, TModel>(deps: TelegramSessionStateApplierDeps<TQueueItem, TModel>): TelegramSessionStateApplier<TQueueItem, TModel>;
@@ -648,6 +641,8 @@ export interface TelegramPromptEnqueueRuntimeDeps<TMessage, TContext = unknown>
648
641
  updateStatus: () => void;
649
642
  dispatchNextQueuedTelegramTurn: () => void;
650
643
  assertExecutionCurrent?: () => void;
644
+ /** Input-specific relocation must not fold or replace unrelated queued work. */
645
+ preserveQueued?: boolean;
651
646
  onQueued?: (turn: PendingTelegramTurn) => void;
652
647
  }
653
648
  export interface TelegramPromptEnqueueControllerDeps<TMessage, TContext = unknown> extends TelegramQueueStore<TContext> {
@@ -660,7 +655,10 @@ export interface TelegramPromptEnqueueControllerDeps<TMessage, TContext = unknow
660
655
  assertExecutionCurrent?: (messages: TMessage[]) => void;
661
656
  }
662
657
  export interface TelegramPromptEnqueueController<TMessage, TContext = unknown> {
663
- enqueue: (messages: TMessage[], ctx: TContext, onQueued?: (turn: PendingTelegramTurn) => void) => Promise<PendingTelegramTurn>;
658
+ enqueue: (messages: TMessage[], ctx: TContext, onQueued?: (turn: PendingTelegramTurn) => void, options?: {
659
+ assertCurrent?: () => void;
660
+ preserveQueued?: boolean;
661
+ }) => Promise<PendingTelegramTurn>;
664
662
  }
665
663
  export declare function buildTelegramSessionStartState<TModel = unknown>(currentModel: TModel | undefined): TelegramSessionStartState<TModel>;
666
664
  export declare function buildTelegramSessionShutdownState<TQueueItem>(): TelegramSessionShutdownState<TQueueItem>;