@llblab/pi-telegram 0.52.2 → 0.53.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 (204) hide show
  1. package/AGENTS.md +9 -7
  2. package/BACKLOG.md +1 -18
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +7 -11
  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 +639 -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 +29 -3
  44. package/dist/lib/locks.js +289 -108
  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 +65 -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 +21 -37
  76. package/dist/lib/polling.js +59 -50
  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 +252 -127
  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 +1 -3
  95. package/dist/lib/sync.js +67 -43
  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 +148 -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 +31 -29
  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 +1206 -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 +677 -203
  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 +128 -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 +144 -147
  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 +516 -301
  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 +448 -361
  185. package/lib/target.ts +22 -3
  186. package/lib/telegram-api.ts +302 -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
@@ -158,7 +158,10 @@ export function createTelegramDurablePollingRuntimeAssembly(deps) {
158
158
  }
159
159
  catch (error) {
160
160
  try {
161
- deps.recordRuntimeEvent?.("polling", error, { phase: "batch-preparation", updateCount: updates.length });
161
+ deps.recordRuntimeEvent?.("polling", error, {
162
+ phase: "batch-preparation",
163
+ updateCount: updates.length,
164
+ });
162
165
  }
163
166
  catch {
164
167
  // Already-published input must still reach the worker if diagnostics fail.
@@ -362,8 +365,13 @@ export function startTelegramPollingRuntime(ctx, deps) {
362
365
  deps.setPollingController(undefined);
363
366
  if (!ownsPromise && !ownsController)
364
367
  return;
365
- deps.onPollingStopped?.(controller.signal.aborted ? "requested" : persistentConflict
366
- ? "persistent-conflict" : failed ? "failed" : "completed");
368
+ deps.onPollingStopped?.(controller.signal.aborted
369
+ ? "requested"
370
+ : persistentConflict
371
+ ? "persistent-conflict"
372
+ : failed
373
+ ? "failed"
374
+ : "completed");
367
375
  // Detach the inner promise before outer teardown calls polling.stop().
368
376
  if (persistentConflict && !controller.signal.aborted) {
369
377
  try {
@@ -373,12 +381,15 @@ export function startTelegramPollingRuntime(ctx, deps) {
373
381
  else {
374
382
  deps.stopTypingLoop();
375
383
  deps.recordRuntimeEvent?.("polling", persistentConflict, {
376
- phase: "persistent-conflict", count: persistentConflict.count,
384
+ phase: "persistent-conflict",
385
+ count: persistentConflict.count,
377
386
  });
378
387
  }
379
388
  }
380
389
  catch (error) {
381
- deps.recordRuntimeEvent?.("polling", error, { phase: "conflict-stand-down" });
390
+ deps.recordRuntimeEvent?.("polling", error, {
391
+ phase: "conflict-stand-down",
392
+ });
382
393
  }
383
394
  if (deps.getPollingController() || deps.getPollingPromise())
384
395
  return;
@@ -435,7 +446,9 @@ export function createTelegramThreadCapabilityOrchestration(deps) {
435
446
  const expected = generation;
436
447
  return () => expected === generation;
437
448
  },
438
- invalidate() { generation++; },
449
+ invalidate() {
450
+ generation++;
451
+ },
439
452
  };
440
453
  const capabilityDeps = {
441
454
  lifecycle,
@@ -543,6 +556,29 @@ function hasTelegramThreadCapabilityBindings(store) {
543
556
  record.status !== "stale");
544
557
  }) ?? false);
545
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
+ }
546
582
  export async function applyTelegramThreadCapability(ctx, threadModeEnabled, phase, deps, isCurrent = deps.lifecycle?.capture() ?? (() => true)) {
547
583
  if (!isCurrent())
548
584
  return;
@@ -580,26 +616,8 @@ export async function applyTelegramThreadCapability(ctx, threadModeEnabled, phas
580
616
  deps.setPollingStartedWithTelegramBus(false);
581
617
  if (hadLiveThreadTransport)
582
618
  deps.suspendLiveThreadTarget?.();
583
- try {
584
- await deps.startClassicPolling(ctx);
585
- if (!isCurrent())
586
- return;
587
- }
588
- catch (classicError) {
589
- if (!isCurrent())
590
- return;
591
- deps.topicTargetStore.setBotState({
592
- threadMode: "disabled",
593
- updatedAtMs: (deps.getNowMs ?? Date.now)(),
594
- lastReconcileAction: `${phase}-classic-restore-failed`,
595
- });
596
- await deps.topicTargetStore.persist();
597
- if (!isCurrent())
598
- return;
599
- deps.recordEvent("bus", classicError, {
600
- phase: `${phase}-classic-restore`,
601
- });
602
- }
619
+ if (!(await restoreTelegramClassicPolling(ctx, phase, deps, isCurrent)))
620
+ return;
603
621
  }
604
622
  deps.updateStatus(ctx);
605
623
  return;
@@ -641,26 +659,8 @@ export async function applyTelegramThreadCapability(ctx, threadModeEnabled, phas
641
659
  return;
642
660
  deps.setTopicModeUnavailable(true);
643
661
  }
644
- try {
645
- await deps.startClassicPolling(ctx);
646
- if (!isCurrent())
647
- return;
648
- }
649
- catch (classicError) {
650
- if (!isCurrent())
651
- return;
652
- deps.topicTargetStore.setBotState({
653
- threadMode: "disabled",
654
- updatedAtMs: (deps.getNowMs ?? Date.now)(),
655
- lastReconcileAction: `${phase}-classic-restore-failed`,
656
- });
657
- await deps.topicTargetStore.persist();
658
- if (!isCurrent())
659
- return;
660
- deps.recordEvent("bus", classicError, {
661
- phase: `${phase}-classic-restore`,
662
- });
663
- }
662
+ if (!(await restoreTelegramClassicPolling(ctx, phase, deps, isCurrent)))
663
+ return;
664
664
  deps.updateStatus(ctx);
665
665
  if (threadModeUnavailable)
666
666
  return;
@@ -759,11 +759,17 @@ export function createTelegramThreadAwarePollingPorts(deps) {
759
759
  };
760
760
  const restoreFollowerWithOwner = async (ctx, owner) => {
761
761
  if (!(await refreshFollowerState())) {
762
- 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
+ });
763
766
  return undefined;
764
767
  }
765
768
  if (!deps.hasRememberedWorkspaceBinding?.(ctx)) {
766
- 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
+ });
767
773
  return undefined;
768
774
  }
769
775
  return deps.restoreFollowerWithLeader?.(ctx, owner);
@@ -898,7 +904,9 @@ export function createTelegramThreadCapabilityMonitor(deps) {
898
904
  try {
899
905
  stop();
900
906
  }
901
- catch { /* Timer shutdown must not escape the callback. */ }
907
+ catch {
908
+ /* Timer shutdown must not escape the callback. */
909
+ }
902
910
  try {
903
911
  deps.recordEvent("bus", error, { phase: "capability-monitor" });
904
912
  }
@@ -1115,7 +1123,8 @@ export async function runTelegramPollLoop(deps) {
1115
1123
  if (consecutiveGetUpdatesConflicts >= TELEGRAM_GET_UPDATES_CONFLICT_STOP_LIMIT) {
1116
1124
  throw new TelegramPersistentGetUpdatesConflictError(consecutiveGetUpdatesConflicts);
1117
1125
  }
1118
- await deps.sleep(consecutiveGetUpdatesConflicts < TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT
1126
+ await deps.sleep(consecutiveGetUpdatesConflicts <
1127
+ TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT
1119
1128
  ? TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_MS
1120
1129
  : TELEGRAM_GET_UPDATES_CONFLICT_SLOW_RETRY_MS, deps.signal);
1121
1130
  };
@@ -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>;