@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
package/lib/preview.ts CHANGED
@@ -4,9 +4,14 @@
4
4
  * Owns safe draft preview selection, runtime updates, and preview finalization
5
5
  */
6
6
 
7
- import { normalizeTelegramNativeMarkdown } from "./replies.ts";
8
7
  import type { TelegramAssistantOutputPreparation } from "./activity.ts";
9
8
  import { stripTelegramCommentMarkupForPreview } from "./outbound-markup.ts";
9
+ import {
10
+ isTelegramMarkdownFenceClosing,
11
+ normalizeTelegramNativeMarkdown,
12
+ parseTelegramMarkdownFenceOpening,
13
+ type TelegramMarkdownFence,
14
+ } from "./replies.ts";
10
15
  import {
11
16
  getTelegramTargetThreadParams,
12
17
  type TelegramTarget,
@@ -100,8 +105,9 @@ export interface TelegramAssistantMessagePreviewUpdateDeps<TMessage> {
100
105
  ) => void;
101
106
  }
102
107
 
103
- export type TelegramAssistantMessagePreviewHookDeps<TMessage> = TelegramAssistantMessagePreviewStartDeps<TMessage> &
104
- TelegramAssistantMessagePreviewUpdateDeps<TMessage>;
108
+ export type TelegramAssistantMessagePreviewHookDeps<TMessage> =
109
+ TelegramAssistantMessagePreviewStartDeps<TMessage> &
110
+ TelegramAssistantMessagePreviewUpdateDeps<TMessage>;
105
111
 
106
112
  export interface TelegramAssistantMessagePreviewHookEvent<TMessage> {
107
113
  message: TMessage;
@@ -206,7 +212,9 @@ export interface TelegramAssistantPreviewRuntimeDeps<
206
212
  export interface TelegramPreparedPreviewDelivery<TReplyMarkup = unknown> {
207
213
  clearPreview: TelegramPreviewController["clear"];
208
214
  setPreviewPendingText: TelegramPreviewController["setPendingText"];
209
- finalizeMarkdownPreview: ReturnType<typeof createTelegramNativeMarkdownPreviewFinalizer<TReplyMarkup>>;
215
+ finalizeMarkdownPreview: ReturnType<
216
+ typeof createTelegramNativeMarkdownPreviewFinalizer<TReplyMarkup>
217
+ >;
210
218
  }
211
219
 
212
220
  export type TelegramAssistantPreviewRuntime<
@@ -214,7 +222,9 @@ export type TelegramAssistantPreviewRuntime<
214
222
  TReplyMarkup = TelegramPreviewReplyMarkup,
215
223
  > = TelegramPreviewController &
216
224
  TelegramAssistantMessagePreviewHooks<TMessage> & {
217
- prepareDelivery: (isDeliveryActive: () => boolean) => TelegramPreparedPreviewDelivery<TReplyMarkup>;
225
+ prepareDelivery: (
226
+ isDeliveryActive: () => boolean,
227
+ ) => TelegramPreparedPreviewDelivery<TReplyMarkup>;
218
228
  finalizeMarkdown: (
219
229
  chatId: number,
220
230
  markdown: string,
@@ -223,7 +233,9 @@ export type TelegramAssistantPreviewRuntime<
223
233
  ) => Promise<boolean>;
224
234
  };
225
235
 
226
- function sealTelegramPreviewState(state: TelegramPreviewRuntimeState | undefined): void {
236
+ function sealTelegramPreviewState(
237
+ state: TelegramPreviewRuntimeState | undefined,
238
+ ): void {
227
239
  if (!state) return;
228
240
  state.sealed = true;
229
241
  state.flushRequested = false;
@@ -253,21 +265,28 @@ export function createTelegramNativeMarkdownPreviewFinalizer<
253
265
  replyToMessageId?: number,
254
266
  options?: { replyMarkup?: TReplyMarkup; target?: TelegramTarget },
255
267
  ) => Promise<boolean> {
256
- return (...args) => prepareTelegramNativeMarkdownPreviewFinalizer(deps)(...args);
268
+ return (...args) =>
269
+ prepareTelegramNativeMarkdownPreviewFinalizer(deps)(...args);
257
270
  }
258
271
 
259
272
  function prepareTelegramNativeMarkdownPreviewFinalizer<TReplyMarkup>(
260
- deps: Parameters<typeof createTelegramNativeMarkdownPreviewFinalizer<TReplyMarkup>>[0],
261
- ): ReturnType<typeof createTelegramNativeMarkdownPreviewFinalizer<TReplyMarkup>> {
273
+ deps: Parameters<
274
+ typeof createTelegramNativeMarkdownPreviewFinalizer<TReplyMarkup>
275
+ >[0],
276
+ ): ReturnType<
277
+ typeof createTelegramNativeMarkdownPreviewFinalizer<TReplyMarkup>
278
+ > {
262
279
  const state = deps.getState();
263
280
  sealTelegramPreviewState(state);
264
281
  const inFlight = state?.flushPromise ?? state?.precedingFlush;
265
282
  return async (chatId, markdown, replyToMessageId, options) => {
266
283
  if (deps.isDeliveryActive?.() === false) return false;
267
284
  await inFlight?.catch(() => {});
268
- if (deps.getState() !== state || deps.isDeliveryActive?.() === false) return false;
285
+ if (deps.getState() !== state || deps.isDeliveryActive?.() === false)
286
+ return false;
269
287
  await deps.sendMarkdownReply(chatId, replyToMessageId, markdown, options);
270
- if (deps.getState() === state && deps.isDeliveryActive?.() !== false) deps.discard?.();
288
+ if (deps.getState() === state && deps.isDeliveryActive?.() !== false)
289
+ deps.discard?.();
271
290
  return true;
272
291
  };
273
292
  }
@@ -287,18 +306,26 @@ export function createTelegramAssistantPreviewRuntime<
287
306
  };
288
307
  return {
289
308
  ...controller,
290
- finalizeMarkdown: createTelegramNativeMarkdownPreviewFinalizer(finalizerDeps),
309
+ finalizeMarkdown:
310
+ createTelegramNativeMarkdownPreviewFinalizer(finalizerDeps),
291
311
  prepareDelivery(isDeliveryActive) {
292
312
  const state = controller.getState();
293
313
  return {
294
314
  setPreviewPendingText(text) {
295
- if (controller.getState() === state && isDeliveryActive()) controller.setPendingText(text);
315
+ if (controller.getState() === state && isDeliveryActive())
316
+ controller.setPendingText(text);
296
317
  },
297
318
  clearPreview: async (chatId, options) => {
298
319
  if (controller.getState() !== state || !isDeliveryActive()) return;
299
- await controller.prepareClear(chatId, { ...options, isDeliveryActive })();
320
+ await controller.prepareClear(chatId, {
321
+ ...options,
322
+ isDeliveryActive,
323
+ })();
300
324
  },
301
- finalizeMarkdownPreview: prepareTelegramNativeMarkdownPreviewFinalizer({ ...finalizerDeps, isDeliveryActive }),
325
+ finalizeMarkdownPreview: prepareTelegramNativeMarkdownPreviewFinalizer({
326
+ ...finalizerDeps,
327
+ isDeliveryActive,
328
+ }),
302
329
  };
303
330
  },
304
331
  ...createTelegramAssistantMessagePreviewHooks({
@@ -325,7 +352,9 @@ export function createTelegramPreviewController(
325
352
  deps.maxMessageLength ?? TELEGRAM_DRAFT_PREVIEW_MAX_CHARS;
326
353
  let draftSupport = deps.initialDraftSupport ?? "unknown";
327
354
  let nextDraftId = 0;
328
- const setState = (nextState: TelegramPreviewRuntimeState | undefined): void => {
355
+ const setState = (
356
+ nextState: TelegramPreviewRuntimeState | undefined,
357
+ ): void => {
329
358
  if (state !== nextState) sealTelegramPreviewState(state);
330
359
  state = nextState;
331
360
  };
@@ -358,7 +387,10 @@ export function createTelegramPreviewController(
358
387
  createState: () => createTelegramPreviewRuntimeState(),
359
388
  resetState: () => {
360
389
  generation += 1;
361
- setState({ ...createTelegramPreviewRuntimeState(), nextDraftAt: state?.nextDraftAt });
390
+ setState({
391
+ ...createTelegramPreviewRuntimeState(),
392
+ nextDraftAt: state?.nextDraftAt,
393
+ });
362
394
  },
363
395
  invalidate: () => {
364
396
  generation += 1;
@@ -368,10 +400,18 @@ export function createTelegramPreviewController(
368
400
  preparePublication: () => {
369
401
  if (!state) return undefined;
370
402
  sealTelegramPreviewState(state);
371
- const prior = state.publicationPromise ?? state.flushPromise ?? state.precedingFlush;
403
+ const prior =
404
+ state.publicationPromise ?? state.flushPromise ?? state.precedingFlush;
372
405
  let settle!: () => void;
373
- state.publicationPromise = new Promise<void>((resolve) => { settle = resolve; });
374
- return { wait: async () => { await prior?.catch(() => {}); }, settle };
406
+ state.publicationPromise = new Promise<void>((resolve) => {
407
+ settle = resolve;
408
+ });
409
+ return {
410
+ wait: async () => {
411
+ await prior?.catch(() => {});
412
+ },
413
+ settle,
414
+ };
375
415
  },
376
416
  prepareClear: (chatId, options) => {
377
417
  const admittedState = state;
@@ -380,7 +420,9 @@ export function createTelegramPreviewController(
380
420
  if (state !== admittedState) return;
381
421
  await clearTelegramPreview(chatId, runtime, {
382
422
  ...options,
383
- isDeliveryActive: () => runtime.canSend?.() !== false && options?.isDeliveryActive?.() !== false,
423
+ isDeliveryActive: () =>
424
+ runtime.canSend?.() !== false &&
425
+ options?.isDeliveryActive?.() !== false,
384
426
  });
385
427
  };
386
428
  },
@@ -449,7 +491,8 @@ async function handleTelegramAssistantMessagePreviewStart<TMessage>(
449
491
  // Carry the previous delivery boundary; permanent text remains with its publication owner.
450
492
  next.draftId = state?.draftId;
451
493
  next.nextDraftAt = state?.nextDraftAt;
452
- next.precedingFlush = state?.publicationPromise ?? state?.flushPromise ?? state?.precedingFlush;
494
+ next.precedingFlush =
495
+ state?.publicationPromise ?? state?.flushPromise ?? state?.precedingFlush;
453
496
  deps.setState(next);
454
497
  }
455
498
 
@@ -529,7 +572,11 @@ export function shouldUseTelegramDraftPreview(_options: {
529
572
  export async function clearTelegramPreview(
530
573
  chatId: number,
531
574
  deps: TelegramPreviewRuntimeDeps,
532
- options: { awaitFlush?: boolean; target?: TelegramTarget; isDeliveryActive?: () => boolean } = {},
575
+ options: {
576
+ awaitFlush?: boolean;
577
+ target?: TelegramTarget;
578
+ isDeliveryActive?: () => boolean;
579
+ } = {},
533
580
  ): Promise<void> {
534
581
  const state = deps.getState();
535
582
  if (!state || options.isDeliveryActive?.() === false) return;
@@ -542,7 +589,11 @@ export async function clearTelegramPreview(
542
589
  }
543
590
  if (options.isDeliveryActive?.() === false) return;
544
591
  deps.setState(undefined);
545
- if (state.mode === "draft" && state.draftId !== undefined && deps.canSend?.() !== false) {
592
+ if (
593
+ state.mode === "draft" &&
594
+ state.draftId !== undefined &&
595
+ deps.canSend?.() !== false
596
+ ) {
546
597
  try {
547
598
  await deps.sendDraft(chatId, state.draftId, undefined, {
548
599
  ...getTelegramTargetThreadParams(options.target ?? { chatId }),
@@ -561,7 +612,7 @@ interface TelegramDraftInlineState {
561
612
  codeTicks: number;
562
613
  htmlComment: boolean;
563
614
  displayMath: boolean;
564
- fence?: { marker: "`" | "~"; length: number };
615
+ fence?: TelegramMarkdownFence;
565
616
  strongAsterisk: boolean;
566
617
  emphasisAsterisk: boolean;
567
618
  strongUnderscore: boolean;
@@ -724,13 +775,8 @@ function updateTelegramDraftBlockStateForLine(
724
775
  line: string,
725
776
  state: TelegramDraftInlineState,
726
777
  ): boolean {
727
- const fenceMatch = line.match(/^ {0,3}(`{3,}|~{3,})/);
728
778
  if (state.fence) {
729
- if (
730
- new RegExp(
731
- `^ {0,3}${state.fence.marker}{${state.fence.length},}\\s*$`,
732
- ).test(line)
733
- ) {
779
+ if (isTelegramMarkdownFenceClosing(line, state.fence)) {
734
780
  state.fence = undefined;
735
781
  }
736
782
  return true;
@@ -739,12 +785,9 @@ function updateTelegramDraftBlockStateForLine(
739
785
  if (line.trim() === "$$") state.displayMath = false;
740
786
  return true;
741
787
  }
742
- if (fenceMatch) {
743
- const markerText = fenceMatch[1] ?? "```";
744
- state.fence = {
745
- marker: markerText[0] as "`" | "~",
746
- length: markerText.length,
747
- };
788
+ const openingFence = parseTelegramMarkdownFenceOpening(line);
789
+ if (openingFence) {
790
+ state.fence = openingFence;
748
791
  return true;
749
792
  }
750
793
  if (line.trim() === "$$") {
@@ -852,7 +895,12 @@ async function performTelegramPreviewFlush(
852
895
  normalizeTelegramNativeMarkdown(snapshot.text),
853
896
  { ...getTelegramTargetThreadParams(options.target ?? { chatId }) },
854
897
  );
855
- if (delivered === false || deps.getState() !== state || deps.canSend?.() === false) return;
898
+ if (
899
+ delivered === false ||
900
+ deps.getState() !== state ||
901
+ deps.canSend?.() === false
902
+ )
903
+ return;
856
904
  deps.setDraftSupport("supported");
857
905
  state.mode = "draft";
858
906
  state.lastSentText = snapshot.text;
@@ -893,7 +941,8 @@ export async function flushTelegramPreview(
893
941
  if (!state.flushTimer) {
894
942
  state.flushTimer = setTimeout(() => {
895
943
  state.flushTimer = undefined;
896
- if (deps.getState() === state && !state.sealed) void flushTelegramPreview(chatId, deps, options);
944
+ if (deps.getState() === state && !state.sealed)
945
+ void flushTelegramPreview(chatId, deps, options);
897
946
  }, delay);
898
947
  state.flushTimer.unref?.();
899
948
  }
@@ -911,7 +960,11 @@ export async function flushTelegramPreview(
911
960
  });
912
961
  break;
913
962
  }
914
- } while (deps.getState() === state && !state.sealed && state.flushRequested);
963
+ } while (
964
+ deps.getState() === state &&
965
+ !state.sealed &&
966
+ state.flushRequested
967
+ );
915
968
  })();
916
969
  try {
917
970
  await state.flushPromise;
@@ -0,0 +1,164 @@
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
+
8
+ import { execFileSync } from "node:child_process";
9
+ import { createHash } from "node:crypto";
10
+ import { readFileSync } from "node:fs";
11
+ import { platform as getPlatform } from "node:os";
12
+
13
+ export function isProcessAlive(pid: number): boolean {
14
+ if (!Number.isInteger(pid) || pid <= 0) return false;
15
+ try {
16
+ process.kill(pid, 0);
17
+ return true;
18
+ } catch (error) {
19
+ // Only an absent PID proves death; permission and unexpected failures do not.
20
+ return (error as { code?: string }).code !== "ESRCH";
21
+ }
22
+ }
23
+
24
+ interface TelegramProcessBirthIdentityOptions {
25
+ platform?: NodeJS.Platform;
26
+ readProcStat?: (pid: number) => string;
27
+ readDarwinProcessStart?: (pid: number) => string;
28
+ readWindowsProcessStart?: (pid: number) => string;
29
+ }
30
+
31
+ type TelegramProcessBirthProof =
32
+ { status: "proven"; identity: string } | { status: "unverifiable" };
33
+
34
+ export type TelegramProcessLiveness = "alive" | "dead" | "unverifiable";
35
+
36
+ interface TelegramProcessLivenessOptions extends TelegramProcessBirthIdentityOptions {
37
+ isProcessAlive?: (pid: number) => boolean;
38
+ }
39
+
40
+ function readDarwinProcessStart(pid: number): string {
41
+ return execFileSync("/bin/ps", ["-o", "lstart=", "-p", String(pid)], {
42
+ encoding: "utf8",
43
+ stdio: ["ignore", "pipe", "ignore"],
44
+ }).trim();
45
+ }
46
+
47
+ /** Creation time in UTC 100 ns ticks; PowerShell ships with every supported Windows and needs no native addon. */
48
+ function readWindowsProcessStart(pid: number): string {
49
+ const root = process.env.SystemRoot ?? "C:\\Windows";
50
+ return execFileSync(
51
+ `${root}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`,
52
+ [
53
+ "-NoProfile",
54
+ "-NonInteractive",
55
+ "-Command",
56
+ `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks`,
57
+ ],
58
+ {
59
+ encoding: "utf8",
60
+ stdio: ["ignore", "pipe", "ignore"],
61
+ timeout: 10_000,
62
+ windowsHide: true,
63
+ },
64
+ ).trim();
65
+ }
66
+
67
+ /** The current process never changes birth, so its slow Windows proof is read once; other PIDs are never cached. */
68
+ let ownWindowsBirthProof: TelegramProcessBirthProof | undefined;
69
+
70
+ function getTelegramProcessBirthProof(
71
+ pid: number,
72
+ options: TelegramProcessBirthIdentityOptions = {},
73
+ ): TelegramProcessBirthProof {
74
+ if (!Number.isSafeInteger(pid) || pid <= 0) return { status: "unverifiable" };
75
+ const platform = options.platform ?? getPlatform();
76
+ if (platform === "linux") {
77
+ try {
78
+ const stat = (
79
+ options.readProcStat ??
80
+ ((targetPid) => readFileSync(`/proc/${targetPid}/stat`, "utf8"))
81
+ )(pid);
82
+ const closeParen = stat.lastIndexOf(")");
83
+ const fields = stat
84
+ .slice(closeParen + 2)
85
+ .trim()
86
+ .split(/\s+/u);
87
+ const startTicks = fields[19];
88
+ if (startTicks) {
89
+ return { status: "proven", identity: `${pid}:start:${startTicks}` };
90
+ }
91
+ } catch {
92
+ /* inaccessible process metadata */
93
+ }
94
+ } else if (platform === "darwin") {
95
+ try {
96
+ const startedAt = (
97
+ options.readDarwinProcessStart ?? readDarwinProcessStart
98
+ )(pid);
99
+ if (startedAt) {
100
+ const fingerprint = createHash("sha256")
101
+ .update(startedAt)
102
+ .digest("hex")
103
+ .slice(0, 16);
104
+ return { status: "proven", identity: `${pid}:start:${fingerprint}` };
105
+ }
106
+ } catch {
107
+ /* inaccessible process metadata */
108
+ }
109
+ } else if (platform === "win32") {
110
+ const injected = options.readWindowsProcessStart;
111
+ if (!injected && pid === process.pid && ownWindowsBirthProof)
112
+ return ownWindowsBirthProof;
113
+ let proof: TelegramProcessBirthProof = { status: "unverifiable" };
114
+ try {
115
+ const ticks = (injected ?? readWindowsProcessStart)(pid);
116
+ if (/^\d+$/u.test(ticks))
117
+ proof = { status: "proven", identity: `${pid}:start:${ticks}` };
118
+ } catch {
119
+ /* inaccessible process metadata, including another user's process */
120
+ }
121
+ if (!injected && pid === process.pid && proof.status === "proven")
122
+ ownWindowsBirthProof = proof;
123
+ return proof;
124
+ }
125
+ return { status: "unverifiable" };
126
+ }
127
+
128
+ export function getTelegramProcessBirthIdentity(
129
+ pid: number,
130
+ fallbackGeneration: number | string,
131
+ options: TelegramProcessBirthIdentityOptions = {},
132
+ ): string {
133
+ const proof = getTelegramProcessBirthProof(pid, options);
134
+ return proof.status === "proven"
135
+ ? proof.identity
136
+ : `${pid}:generation:${fallbackGeneration}`;
137
+ }
138
+
139
+ export function getTelegramProcessLiveness(
140
+ owner: { processId: number; processBirthId: string },
141
+ options: TelegramProcessLivenessOptions = {},
142
+ ): TelegramProcessLiveness {
143
+ const processAlive = options.isProcessAlive ?? isProcessAlive;
144
+ if (!processAlive(owner.processId)) return "dead";
145
+ const proof = getTelegramProcessBirthProof(owner.processId, options);
146
+ if (proof.status === "unverifiable") return "unverifiable";
147
+ return proof.identity === owner.processBirthId ? "alive" : "dead";
148
+ }
149
+
150
+ export function getTelegramProcessBirthIdentityLiveness(
151
+ processBirthId: string,
152
+ options: TelegramProcessLivenessOptions = {},
153
+ ): TelegramProcessLiveness {
154
+ const match = /^(\d+):(start|generation):(.+)$/u.exec(processBirthId);
155
+ if (!match) return "unverifiable";
156
+ const processId = Number(match[1]);
157
+ if (!Number.isSafeInteger(processId) || processId <= 0) return "unverifiable";
158
+ const processAlive = options.isProcessAlive ?? isProcessAlive;
159
+ if (!processAlive(processId)) return "dead";
160
+ if (match[2] === "generation") return "unverifiable";
161
+ const proof = getTelegramProcessBirthProof(processId, options);
162
+ if (proof.status === "unverifiable") return "unverifiable";
163
+ return proof.identity === processBirthId ? "alive" : "dead";
164
+ }
package/lib/prompts.ts CHANGED
@@ -86,8 +86,7 @@ export interface TelegramModelContextAvailabilityRuntime {
86
86
  reconcile: () => void;
87
87
  }
88
88
 
89
- export interface TelegramModelContextAvailabilityBinding
90
- extends TelegramModelContextAvailabilityRuntime {
89
+ export interface TelegramModelContextAvailabilityBinding extends TelegramModelContextAvailabilityRuntime {
91
90
  bind: (runtime: TelegramModelContextAvailabilityRuntime) => void;
92
91
  }
93
92
 
@@ -110,8 +109,7 @@ export function createTelegramModelContextAvailabilityRuntime(deps: {
110
109
  canReconcile?: () => boolean;
111
110
  memory?: TelegramModelContextAvailabilityMemory;
112
111
  }): TelegramModelContextAvailabilityRuntime {
113
- const memory =
114
- deps.memory ?? getTelegramModelContextAvailabilityMemory();
112
+ const memory = deps.memory ?? getTelegramModelContextAvailabilityMemory();
115
113
  return {
116
114
  reconcile() {
117
115
  if (deps.canReconcile && !deps.canReconcile()) return;
@@ -188,13 +186,8 @@ export function buildTelegramBridgeSystemPrompt(options: {
188
186
  : "";
189
187
  return {
190
188
  systemPrompt: Array.isArray(basePrompt)
191
- ? [
192
- ...basePrompt,
193
- options.localSystemPromptSuffix + telegramSuffix,
194
- ]
195
- : basePrompt +
196
- options.localSystemPromptSuffix +
197
- telegramSuffix,
189
+ ? [...basePrompt, options.localSystemPromptSuffix + telegramSuffix]
190
+ : basePrompt + options.localSystemPromptSuffix + telegramSuffix,
198
191
  };
199
192
  }
200
193