@llblab/pi-kit 0.27.5 → 0.28.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 (194) hide show
  1. package/BACKLOG.md +3 -1
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +4 -4
  4. package/node_modules/@llblab/pi-state-flow/AGENTS.md +1 -1
  5. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +6 -2
  6. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +4 -1
  7. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +4 -3
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +26 -13
  9. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  10. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -1
  11. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +2 -2
  12. package/node_modules/@llblab/pi-state-flow/docs/usage.md +2 -1
  13. package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +4 -3
  14. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +25 -12
  15. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  16. package/node_modules/@llblab/pi-telegram/AGENTS.md +10 -8
  17. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -9
  18. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
  19. package/node_modules/@llblab/pi-telegram/LICENSE +21 -0
  20. package/node_modules/@llblab/pi-telegram/README.md +15 -5
  21. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
  22. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +5 -7
  23. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +5 -1
  24. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +6 -1
  25. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +4 -2
  26. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +54 -56
  27. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +206 -139
  28. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -2
  29. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +181 -25
  30. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.d.ts +4 -1
  31. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +28 -4
  32. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +52 -56
  33. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +78 -246
  34. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +1 -3
  35. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +23 -26
  36. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +0 -5
  37. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +5 -5
  38. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +0 -23
  39. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +15 -15
  40. package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +0 -17
  41. package/node_modules/@llblab/pi-telegram/dist/lib/config.js +12 -14
  42. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +0 -4
  43. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +2 -5
  44. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +255 -61
  45. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -19
  46. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +0 -2
  47. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +2 -2
  48. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +177 -16
  49. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +1115 -249
  50. package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.d.ts +0 -2
  51. package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.js +2 -2
  52. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +1 -1
  53. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +100 -12
  54. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +380 -14
  55. package/node_modules/@llblab/pi-telegram/dist/lib/logging.d.ts +30 -12
  56. package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +129 -72
  57. package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +28 -5
  58. package/node_modules/@llblab/pi-telegram/dist/lib/media.js +26 -8
  59. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +0 -11
  60. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +8 -8
  61. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +0 -18
  62. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +18 -18
  63. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +4 -4
  64. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +12 -7
  65. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +1 -1
  66. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +1 -1
  67. package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +1 -0
  68. package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -3
  69. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +0 -1
  70. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +1 -1
  71. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +1 -8
  72. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +1 -6
  73. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +4 -21
  74. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +0 -8
  75. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +5 -5
  76. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +50 -7
  77. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +155 -17
  78. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +1 -0
  79. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +2 -1
  80. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +0 -5
  81. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -5
  82. package/node_modules/@llblab/pi-telegram/dist/lib/preview.d.ts +0 -3
  83. package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +3 -3
  84. package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.d.ts +0 -1
  85. package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.js +1 -1
  86. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +3 -15
  87. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +69 -8
  88. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +29 -9
  89. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +104 -33
  90. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +0 -4
  91. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +1 -1
  92. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +73 -8
  93. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +2206 -307
  94. package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +0 -2
  95. package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +1 -1
  96. package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +44 -9
  97. package/node_modules/@llblab/pi-telegram/dist/lib/status.js +149 -27
  98. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +0 -5
  99. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +5 -4
  100. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +11 -3
  101. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +34 -23
  102. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +1 -0
  103. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +18 -21
  104. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +32 -5
  105. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +191 -7
  106. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +2 -0
  107. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +1 -1
  108. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +291 -59
  109. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +1647 -369
  110. package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +0 -1
  111. package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -1
  112. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +184 -28
  113. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +1032 -114
  114. package/node_modules/@llblab/pi-telegram/dist/lib/wire.d.ts +12 -0
  115. package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +21 -0
  116. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +25 -17
  117. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +95 -20
  118. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.d.ts +20 -0
  119. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +103 -0
  120. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +43 -8
  121. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +148 -52
  122. package/node_modules/@llblab/pi-telegram/dist/package.json +6 -6
  123. package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/SKILL.md +3 -3
  124. package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
  125. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
  126. package/node_modules/@llblab/pi-telegram/docs/activity.md +1 -1
  127. package/node_modules/@llblab/pi-telegram/docs/architecture.md +152 -34
  128. package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
  129. package/node_modules/@llblab/pi-telegram/docs/delivery.md +1 -1
  130. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +2 -2
  131. package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
  132. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +125 -19
  133. package/node_modules/@llblab/pi-telegram/docs/public-api.md +3 -1
  134. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +22 -8
  135. package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
  136. package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +5 -9
  137. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +6 -0
  138. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +5 -2
  139. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +243 -224
  140. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +178 -31
  141. package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +30 -8
  142. package/node_modules/@llblab/pi-telegram/lib/bus.ts +110 -334
  143. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +29 -29
  144. package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +5 -5
  145. package/node_modules/@llblab/pi-telegram/lib/commands.ts +15 -15
  146. package/node_modules/@llblab/pi-telegram/lib/config.ts +12 -17
  147. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +2 -8
  148. package/node_modules/@llblab/pi-telegram/lib/extension.ts +257 -72
  149. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +0 -22
  150. package/node_modules/@llblab/pi-telegram/lib/inbound.ts +2 -2
  151. package/node_modules/@llblab/pi-telegram/lib/journal.ts +1182 -326
  152. package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +2 -2
  153. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +1 -1
  154. package/node_modules/@llblab/pi-telegram/lib/locks.ts +402 -23
  155. package/node_modules/@llblab/pi-telegram/lib/logging.ts +151 -101
  156. package/node_modules/@llblab/pi-telegram/lib/media.ts +51 -9
  157. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +8 -8
  158. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +18 -18
  159. package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +11 -0
  160. package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +2 -2
  161. package/node_modules/@llblab/pi-telegram/lib/menu.ts +5 -1
  162. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +1 -1
  163. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +1 -10
  164. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +5 -24
  165. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +5 -5
  166. package/node_modules/@llblab/pi-telegram/lib/paths.ts +177 -17
  167. package/node_modules/@llblab/pi-telegram/lib/pi.ts +5 -2
  168. package/node_modules/@llblab/pi-telegram/lib/polling.ts +5 -5
  169. package/node_modules/@llblab/pi-telegram/lib/preview.ts +3 -3
  170. package/node_modules/@llblab/pi-telegram/lib/prompt-templates.ts +1 -1
  171. package/node_modules/@llblab/pi-telegram/lib/queue.ts +67 -9
  172. package/node_modules/@llblab/pi-telegram/lib/recovery.ts +104 -44
  173. package/node_modules/@llblab/pi-telegram/lib/replies.ts +1 -1
  174. package/node_modules/@llblab/pi-telegram/lib/routing.ts +1955 -375
  175. package/node_modules/@llblab/pi-telegram/lib/sections.ts +1 -1
  176. package/node_modules/@llblab/pi-telegram/lib/status.ts +156 -36
  177. package/node_modules/@llblab/pi-telegram/lib/sync.ts +4 -4
  178. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +48 -33
  179. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +24 -21
  180. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +267 -9
  181. package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +3 -1
  182. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1697 -488
  183. package/node_modules/@llblab/pi-telegram/lib/turns.ts +1 -1
  184. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1072 -151
  185. package/node_modules/@llblab/pi-telegram/lib/wire.ts +28 -0
  186. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +97 -47
  187. package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +147 -0
  188. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +188 -89
  189. package/node_modules/@llblab/pi-telegram/package.json +6 -6
  190. package/node_modules/@llblab/pi-telegram/scripts/audit-exports.mjs +100 -0
  191. package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +80 -43
  192. package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +3 -3
  193. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +3 -3
  194. package/package.json +3 -3
@@ -143,19 +143,19 @@ export interface TelegramSettingsMenuRuntimeDeps<
143
143
  ) => Promise<void>;
144
144
  }
145
145
 
146
- export const SETTINGS_MENU_TITLE = "<b>⚙️ Settings:</b>";
147
- export const AUTOMATIC_THREAD_CLEANUP_SETTINGS_TITLE =
146
+ const SETTINGS_MENU_TITLE = "<b>⚙️ Settings:</b>";
147
+ const AUTOMATIC_THREAD_CLEANUP_SETTINGS_TITLE =
148
148
  "<b>🧹 Thread cleanup:</b>";
149
- export const INACTIVE_THREAD_REVIEW_TITLE = "<b>🔎 Inactive tabs review:</b>";
150
- export const DRAFT_PREVIEWS_SETTINGS_TITLE = "<b>📝 Draft previews:</b>";
151
- export const ASSISTANT_RENDERING_SETTINGS_TITLE =
149
+ const INACTIVE_THREAD_REVIEW_TITLE = "<b>🔎 Inactive tabs review:</b>";
150
+ const DRAFT_PREVIEWS_SETTINGS_TITLE = "<b>📝 Draft previews:</b>";
151
+ const ASSISTANT_RENDERING_SETTINGS_TITLE =
152
152
  "<b>🧾 Assistant rendering:</b>";
153
- export const ACTIVITY_VERBOSITY_SETTINGS_TITLE =
153
+ const ACTIVITY_VERBOSITY_SETTINGS_TITLE =
154
154
  "<b>🔬 Activity:</b>";
155
- export const TIME_INJECTION_MODE_SETTINGS_TITLE =
155
+ const TIME_INJECTION_MODE_SETTINGS_TITLE =
156
156
  "<b>🕒 Time injection mode:</b>";
157
- export const VOICE_REPLY_MODE_SETTINGS_TITLE = "<b>👄 Voice reply mode:</b>";
158
- export const THREAD_DISPLAY_SETTINGS_TITLE = "<b>🧵 Thread display:</b>";
157
+ const VOICE_REPLY_MODE_SETTINGS_TITLE = "<b>👄 Voice reply mode:</b>";
158
+ const THREAD_DISPLAY_SETTINGS_TITLE = "<b>🧵 Thread display:</b>";
159
159
 
160
160
  function getVoiceReplyModeLabel(mode: TelegramVoiceReplyMode): string {
161
161
  return mode;
@@ -209,7 +209,7 @@ export function buildAutomaticThreadCleanupSettingsText(
209
209
  ].join("\n");
210
210
  }
211
211
 
212
- export function buildInactiveThreadReviewText(count: number): string {
212
+ function buildInactiveThreadReviewText(count: number): string {
213
213
  return [INACTIVE_THREAD_REVIEW_TITLE, "",
214
214
  `${count} proven inactive tab${count === 1 ? "" : "s"}.`,
215
215
  "No tabs were deleted.",
@@ -393,7 +393,7 @@ export function buildTelegramSettingsMenuReplyMarkup(
393
393
  return { inline_keyboard: rows };
394
394
  }
395
395
 
396
- export async function openTelegramSettingsMenu<
396
+ async function openTelegramSettingsMenu<
397
397
  TModel extends MenuModel = MenuModel,
398
398
  >(
399
399
  deps: TelegramSettingsMenuOpenDeps<TModel>,
@@ -560,7 +560,7 @@ export function buildVoiceReplyModeSettingsReplyMarkup(
560
560
  };
561
561
  }
562
562
 
563
- export async function updateTelegramSettingsMenuMessage(
563
+ async function updateTelegramSettingsMenuMessage(
564
564
  deps: TelegramSettingsMenuMessageUpdateDeps,
565
565
  sectionRegistry?: TelegramSectionRegistry,
566
566
  ): Promise<void> {
@@ -581,7 +581,7 @@ export async function updateTelegramSettingsMenuMessage(
581
581
  );
582
582
  }
583
583
 
584
- export async function updateAutomaticThreadCleanupSettingsMessage(
584
+ async function updateAutomaticThreadCleanupSettingsMessage(
585
585
  deps: TelegramSettingsMenuCallbackDeps,
586
586
  ): Promise<void> {
587
587
  const enabled = deps.isAutomaticThreadCleanupEnabled();
@@ -591,7 +591,7 @@ export async function updateAutomaticThreadCleanupSettingsMessage(
591
591
  );
592
592
  }
593
593
 
594
- export async function updateDraftPreviewsSettingsMessage(
594
+ async function updateDraftPreviewsSettingsMessage(
595
595
  deps: TelegramSettingsMenuCallbackDeps,
596
596
  ): Promise<void> {
597
597
  const enabled = deps.areDraftPreviewsEnabled();
@@ -601,7 +601,7 @@ export async function updateDraftPreviewsSettingsMessage(
601
601
  );
602
602
  }
603
603
 
604
- export async function updateAssistantRenderingSettingsMessage(
604
+ async function updateAssistantRenderingSettingsMessage(
605
605
  deps: TelegramSettingsMenuCallbackDeps,
606
606
  ): Promise<void> {
607
607
  const mode = deps.getAssistantRenderingMode();
@@ -611,7 +611,7 @@ export async function updateAssistantRenderingSettingsMessage(
611
611
  );
612
612
  }
613
613
 
614
- export async function updateActivityVerbositySettingsMessage(
614
+ async function updateActivityVerbositySettingsMessage(
615
615
  deps: TelegramSettingsMenuCallbackDeps,
616
616
  ): Promise<void> {
617
617
  const verbosity = deps.getActivityVerbosity();
@@ -621,7 +621,7 @@ export async function updateActivityVerbositySettingsMessage(
621
621
  );
622
622
  }
623
623
 
624
- export async function updateTimeInjectionModeSettingsMessage(
624
+ async function updateTimeInjectionModeSettingsMessage(
625
625
  deps: TelegramSettingsMenuCallbackDeps,
626
626
  ): Promise<void> {
627
627
  const mode = deps.getTimeInjectionMode();
@@ -631,7 +631,7 @@ export async function updateTimeInjectionModeSettingsMessage(
631
631
  );
632
632
  }
633
633
 
634
- export async function updateVoiceReplyModeSettingsMessage(
634
+ async function updateVoiceReplyModeSettingsMessage(
635
635
  deps: TelegramSettingsMenuCallbackDeps,
636
636
  ): Promise<void> {
637
637
  const mode = deps.getVoiceReplyMode();
@@ -160,6 +160,7 @@ export function buildStatusReplyMarkup(
160
160
  queueItemCount = 0,
161
161
  sectionRegistry?: TelegramSectionRegistry,
162
162
  isVoiceReplyActive?: boolean,
163
+ pendingCancellationCount = 0,
163
164
  ): TelegramReplyMarkup {
164
165
  const rows: Array<Array<{ text: string; callback_data: string }>> = [];
165
166
  rows.push([
@@ -188,6 +189,10 @@ export function buildStatusReplyMarkup(
188
189
  callback_data: "menu:queue",
189
190
  },
190
191
  ]);
192
+ if (pendingCancellationCount > 0) rows.push([{
193
+ text: `❌ Pending cancellations: ${pendingCancellationCount}`,
194
+ callback_data: "reroutecancel:review:open",
195
+ }]);
191
196
  if (sectionRegistry) {
192
197
  const sectionRows = getTelegramSectionMainMenuRows(sectionRegistry);
193
198
  for (const row of sectionRows) {
@@ -210,6 +215,7 @@ export function buildTelegramStatusMenuRenderPayload(
210
215
  queueItemCount = 0,
211
216
  sectionRegistry?: TelegramSectionRegistry,
212
217
  isVoiceReplyActive?: boolean,
218
+ pendingCancellationCount = 0,
213
219
  ): TelegramMenuRenderPayload {
214
220
  return {
215
221
  nextMode: "status",
@@ -221,6 +227,7 @@ export function buildTelegramStatusMenuRenderPayload(
221
227
  queueItemCount,
222
228
  sectionRegistry,
223
229
  isVoiceReplyActive,
230
+ pendingCancellationCount,
224
231
  ),
225
232
  };
226
233
  }
@@ -234,6 +241,7 @@ export async function updateTelegramStatusMessage(
234
241
  queueItemCount = 0,
235
242
  sectionRegistry?: TelegramSectionRegistry,
236
243
  isVoiceReplyActive?: boolean,
244
+ pendingCancellationCount = 0,
237
245
  ): Promise<void> {
238
246
  await editTelegramMenuMessage(
239
247
  state,
@@ -244,6 +252,7 @@ export async function updateTelegramStatusMessage(
244
252
  queueItemCount,
245
253
  sectionRegistry,
246
254
  isVoiceReplyActive,
255
+ pendingCancellationCount,
247
256
  ),
248
257
  deps,
249
258
  );
@@ -258,6 +267,7 @@ export function sendTelegramStatusMessage(
258
267
  queueItemCount = 0,
259
268
  sectionRegistry?: TelegramSectionRegistry,
260
269
  isVoiceReplyActive?: boolean,
270
+ pendingCancellationCount = 0,
261
271
  ): Promise<number | undefined> {
262
272
  return sendTelegramMenuMessage(
263
273
  state,
@@ -268,6 +278,7 @@ export function sendTelegramStatusMessage(
268
278
  queueItemCount,
269
279
  sectionRegistry,
270
280
  isVoiceReplyActive,
281
+ pendingCancellationCount,
271
282
  ),
272
283
  deps,
273
284
  );
@@ -20,7 +20,7 @@ import {
20
20
  export interface TelegramThinkingMenuCallbackDeps {
21
21
  setThinkingLevel: (level: ThinkingLevel) => void;
22
22
  getCurrentThinkingLevel: () => ThinkingLevel;
23
- updateStatusMessage: () => Promise<void>;
23
+ updateThinkingMenuMessage: () => Promise<void>;
24
24
  answerCallbackQuery: (
25
25
  callbackQueryId: string,
26
26
  text?: string,
@@ -112,7 +112,7 @@ export async function handleTelegramThinkingMenuCallbackAction(
112
112
  return true;
113
113
  }
114
114
  deps.setThinkingLevel(action.level);
115
- await deps.updateStatusMessage();
115
+ await deps.updateThinkingMenuMessage();
116
116
  await deps.answerCallbackQuery(
117
117
  callbackQueryId,
118
118
  `Thinking: ${deps.getCurrentThinkingLevel()}`,
@@ -255,6 +255,7 @@ export interface TelegramMenuActionRuntimeDeps<
255
255
  getActiveModel: (ctx: TContext) => TModel | undefined;
256
256
  getThinkingLevel: () => ThinkingLevel;
257
257
  getQueueItemCount?: () => number;
258
+ getPendingCancellationCount?: () => number;
258
259
  buildStatusHtml: (ctx: TContext) => string;
259
260
  storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
260
261
  isIdle: (ctx: TContext) => boolean;
@@ -702,7 +703,7 @@ export async function handleTelegramMenuCallbackRuntime<
702
703
  deps.updateStatus(ctx);
703
704
  },
704
705
  getCurrentThinkingLevel: deps.getThinkingLevel,
705
- updateStatusMessage: () => deps.updateStatusMessage(state, ctx),
706
+ updateThinkingMenuMessage: () => deps.updateThinkingMenuMessage(state, ctx),
706
707
  answerCallbackQuery: deps.answerCallbackQuery,
707
708
  isVoiceReplyActive: deps.isVoiceReplyActive,
708
709
  },
@@ -782,6 +783,7 @@ export function createTelegramMenuActionRuntimeWithStateBuilder<
782
783
  getActiveModel: deps.getActiveModel,
783
784
  getThinkingLevel: deps.getThinkingLevel,
784
785
  getQueueItemCount: deps.getQueueItemCount,
786
+ getPendingCancellationCount: deps.getPendingCancellationCount,
785
787
  buildStatusHtml: deps.buildStatusHtml,
786
788
  storeModelMenuState: deps.storeModelMenuState,
787
789
  isIdle: deps.isIdle,
@@ -819,6 +821,7 @@ export function createTelegramMenuActionRuntime<
819
821
  deps.getQueueItemCount?.() ?? 0,
820
822
  deps.sectionRegistry,
821
823
  deps.isVoiceReplyActive?.(),
824
+ deps.getPendingCancellationCount?.() ?? 0,
822
825
  ),
823
826
  sendStatusMessage: (chatId, replyToMessageId, ctx, threadId) =>
824
827
  openTelegramStatusMenu({
@@ -852,6 +855,7 @@ export function createTelegramMenuActionRuntime<
852
855
  queueItemCount,
853
856
  deps.sectionRegistry,
854
857
  deps.isVoiceReplyActive?.(),
858
+ deps.getPendingCancellationCount?.() ?? 0,
855
859
  ),
856
860
  storeModelMenuState: deps.storeModelMenuState,
857
861
  }),
@@ -51,7 +51,7 @@ export function getTelegramOutboundAttachmentByteLimitFromEnv(
51
51
  return defaultValue;
52
52
  }
53
53
 
54
- export const TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES =
54
+ const TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES =
55
55
  getTelegramOutboundAttachmentByteLimitFromEnv(process.env, [
56
56
  "PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES",
57
57
  "TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES",
@@ -11,6 +11,7 @@ import type {
11
11
  TelegramInlineKeyboardMarkup,
12
12
  } from "./keyboard.ts";
13
13
  import {
14
+ getTelegramActionString as getTelegramButtonString,
14
15
  parseTelegramActionPayloadRows,
15
16
  parseTelegramButtonPayloadRows,
16
17
  replaceTelegramButtonFences,
@@ -101,16 +102,6 @@ function normalizeMarkdownAfterButtonExtraction(markdown: string): string {
101
102
  return markdown.replace(/\n{3,}/g, "\n\n").trim();
102
103
  }
103
104
 
104
- function getTelegramButtonString(
105
- payload: Record<string, unknown>,
106
- key: string,
107
- ): string | undefined {
108
- const value = payload[key];
109
- if (typeof value !== "string") return undefined;
110
- const trimmed = value.trim();
111
- return trimmed || undefined;
112
- }
113
-
114
105
  function parseTelegramButtonAction(
115
106
  payload: Record<string, unknown>,
116
107
  ): TelegramOutboundButtonAction | undefined {
@@ -4,6 +4,8 @@
4
4
  * Owns top-level assistant action comment extraction, attribute parsing, and markup stripping shared by voice and outbound delivery
5
5
  */
6
6
 
7
+ import { isWireRecord as isTelegramActionPayload } from "./wire.ts";
8
+
7
9
  export interface TelegramTopLevelHtmlComment {
8
10
  raw: string;
9
11
  content: string;
@@ -150,7 +152,7 @@ export function replaceTopLevelHtmlComments(
150
152
  return result + markdown.slice(offset);
151
153
  }
152
154
 
153
- export function findTopLevelOpenOrPartialHtmlCommentIndex(
155
+ function findTopLevelOpenOrPartialHtmlCommentIndex(
154
156
  markdown: string,
155
157
  ): number {
156
158
  const { openCommentStart } = collectTopLevelHtmlComments(markdown);
@@ -180,23 +182,6 @@ export function findTopLevelOpenOrPartialHtmlCommentIndex(
180
182
  return -1;
181
183
  }
182
184
 
183
- export function parseTopLevelTelegramComment(
184
- comment: TelegramTopLevelHtmlComment,
185
- command: string,
186
- ): { head: string; body?: string } | undefined {
187
- let normalizedContent = comment.content.replace(/^\s+/, "");
188
- normalizedContent = normalizedContent.replace(/^!/, "");
189
- const [rawHead = "", ...bodyLines] = normalizedContent.split(/\r?\n/);
190
- let head = rawHead.trimStart();
191
- if (!head.startsWith(command)) return undefined;
192
- const nextChar = head[command.length];
193
- if (nextChar !== undefined && !/\s|:/.test(nextChar)) return undefined;
194
- return {
195
- head: head.slice(command.length),
196
- ...(bodyLines.length > 0 ? { body: bodyLines.join("\n") } : {}),
197
- };
198
- }
199
-
200
185
  function parseTolerantTelegramAttributes(
201
186
  source: string,
202
187
  names: readonly string[],
@@ -214,10 +199,6 @@ function parseTolerantTelegramAttributes(
214
199
  return Object.keys(attributes).length > 0 ? attributes : undefined;
215
200
  }
216
201
 
217
- function isTelegramActionPayload(value: unknown): value is Record<string, unknown> {
218
- return value !== null && typeof value === "object" && !Array.isArray(value);
219
- }
220
-
221
202
  function removeTelegramJsonTrailingCommas(source: string): string {
222
203
  let normalized = "";
223
204
  let inString = false;
@@ -268,7 +249,7 @@ function looksLikeTelegramNamedJsonObject(
268
249
  return /^\{\s*"(?:[^"\\]|\\.)*"\s*:/u.test(source.slice(offset));
269
250
  }
270
251
 
271
- export function parseTelegramActionPayload(
252
+ function parseTelegramActionPayload(
272
253
  comment: TelegramTopLevelHtmlComment,
273
254
  command: string,
274
255
  ): Record<string, unknown> | undefined {
@@ -711,7 +692,7 @@ export interface TelegramVoiceReplyPlan {
711
692
  rate?: string;
712
693
  }
713
694
 
714
- function getTelegramActionString(
695
+ export function getTelegramActionString(
715
696
  payload: Record<string, unknown>,
716
697
  key: string,
717
698
  ): string | undefined {
@@ -9,7 +9,7 @@ import { mkdir } from "node:fs/promises";
9
9
  import { join } from "node:path";
10
10
 
11
11
  import type { TelegramAssistantSegmentEvent } from "./activity.ts";
12
- import { resolveTelegramTempDir } from "./paths.ts";
12
+ import { resolveTelegramAttachmentsDir } from "./paths.ts";
13
13
  import * as Replies from "./replies.ts";
14
14
  import type { TelegramPreparedPreviewDelivery } from "./preview.ts";
15
15
  import { isTelegramApiCommitUnknownError } from "./telegram-api.ts";
@@ -202,7 +202,7 @@ export function registerTelegramOutboundHandler(
202
202
  };
203
203
  }
204
204
 
205
- export function getTelegramOutboundProgrammaticHandlers(
205
+ function getTelegramOutboundProgrammaticHandlers(
206
206
  kind: string,
207
207
  ): TelegramOutboundProgrammaticHandler[] {
208
208
  const registry = getOrCreateOutboundHandlerRegistry();
@@ -451,7 +451,7 @@ function getVoiceReplyTemplateValues(
451
451
  }
452
452
 
453
453
  function getDefaultTelegramVoiceTempDir(): string {
454
- return resolveTelegramTempDir();
454
+ return resolveTelegramAttachmentsDir();
455
455
  }
456
456
 
457
457
  async function generateTelegramVoiceReplyFileWithHandler(
@@ -598,7 +598,7 @@ async function transformTelegramOutboundTextWithHandler(
598
598
  return result.stdout.trim() || text;
599
599
  }
600
600
 
601
- export async function transformTelegramOutboundText(
601
+ async function transformTelegramOutboundText(
602
602
  text: string,
603
603
  options: {
604
604
  handlers?: TelegramOutboundHandlerConfig[];
@@ -658,7 +658,7 @@ async function transformTelegramOutboundReplyMarkup<TReplyMarkup>(
658
658
  return { ...replyMarkup, inline_keyboard: translatedRows } as TReplyMarkup;
659
659
  }
660
660
 
661
- export async function transformTelegramOutboundTextReply<
661
+ async function transformTelegramOutboundTextReply<
662
662
  TReplyMarkup = unknown,
663
663
  >(
664
664
  text: string,
@@ -3,13 +3,15 @@
3
3
  * Zones: telemetry paths, filesystem, runtime identity
4
4
  * Owns agent-dir detection and extension-local path derivation
5
5
  *
6
- * This domain is pure/path-only: it resolves directories and file paths
7
- * from environment and runtime identity. It does not read config, manage
8
- * state, or import broader Telegram domains.
6
+ * This domain is path-only: it resolves directories and file paths from
7
+ * environment and runtime identity. Its only filesystem read canonicalizes the
8
+ * existing agent-dir prefix; it does not read config, manage state, or import
9
+ * broader Telegram domains.
9
10
  */
10
11
  import { createHash } from "node:crypto";
12
+ import { realpathSync } from "node:fs";
11
13
  import { homedir } from "node:os";
12
- import { isAbsolute, join, resolve } from "node:path";
14
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
13
15
 
14
16
  export const TELEGRAM_DEFAULT_PROFILE_NAME = "default";
15
17
 
@@ -62,12 +64,56 @@ export function resolveTelegramConfigPath(): string {
62
64
  return join(resolveAgentDir(), "telegram.json");
63
65
  }
64
66
 
65
- /** Telegram bridge temporary directory (<agentDir>/tmp/telegram). */
67
+ /**
68
+ * Resolve symlinks in the longest existing prefix of `path` and keep the missing suffix. Strict journal reads require
69
+ * canonical anchors, so a symlinked agent directory (for example macOS `/var` → `/private/var`) must not leak in.
70
+ * Relative input is returned unchanged so callers' exact-absolute-path guards still reject it. Windows keeps its
71
+ * spelling: strict journal reads are POSIX-only there, and resolving a drive-less root would add a drive letter.
72
+ */
73
+ function canonicalizeExistingPrefix(path: string): string {
74
+ if (!isAbsolute(path) || process.platform === "win32") return path;
75
+ const absolute = resolve(path);
76
+ const missing: string[] = [];
77
+ for (let current = absolute; ; current = dirname(current)) {
78
+ try { return join(realpathSync(current), ...missing.reverse()); }
79
+ catch (error) {
80
+ if ((error as { code?: unknown }).code !== "ENOENT" || dirname(current) === current) return absolute;
81
+ missing.push(basename(current));
82
+ }
83
+ }
84
+ }
85
+
86
+ /** Telegram bridge temporary directory (<agentDir>/tmp/pi-telegram); releases before 0.52.0 used `tmp/telegram`. */
66
87
  export function resolveTelegramTempDir(agentDir = resolveAgentDir()): string {
67
- return join(agentDir, "tmp", "telegram");
88
+ return join(canonicalizeExistingPrefix(agentDir), "tmp", "pi-telegram");
89
+ }
90
+
91
+ /** Consolidated-root service artifacts (transaction guards, staging and IPC), never session custody. */
92
+ export function resolveTelegramRuntimeDir(agentDir = resolveAgentDir()): string {
93
+ return join(resolveTelegramTempDir(agentDir), "runtime");
68
94
  }
69
95
 
70
- /** Telegram transport ownership store (<agentDir>/tmp/telegram/owners.json). */
96
+ /** Rotated shared runtime event segments, outside the persistent root-file census. */
97
+ export function resolveTelegramRuntimeLogsDir(agentDir = resolveAgentDir()): string {
98
+ return join(resolveTelegramTempDir(agentDir), "logs");
99
+ }
100
+
101
+ /** Consolidated runtime state for every logical profile (`tmp/pi-telegram/state.json`). */
102
+ export function resolveTelegramStatePath(agentDir = resolveAgentDir()): string {
103
+ return join(resolveTelegramTempDir(agentDir), "state.json");
104
+ }
105
+
106
+ /** Flat attachment scratch directory (<agentDir>/tmp/pi-telegram/attachments). */
107
+ export function resolveTelegramAttachmentsDir(agentDir = resolveAgentDir()): string {
108
+ return join(resolveTelegramTempDir(agentDir), "attachments");
109
+ }
110
+
111
+ /** Read-only pre-0.52.0 ownership file, consulted only to refuse a second poller beside a live older leader. */
112
+ export function resolveLegacyTelegramOwnersPath(agentDir = resolveAgentDir()): string {
113
+ return join(agentDir, "tmp", "telegram", "owners.json");
114
+ }
115
+
116
+ /** Telegram transport ownership store (<agentDir>/tmp/pi-telegram/owners.json). */
71
117
  export function resolveTelegramOwnersPath(): string {
72
118
  return join(resolveTelegramTempDir(), "owners.json");
73
119
  }
@@ -89,19 +135,17 @@ export function resolveTelegramProfileTempFilePath(
89
135
  );
90
136
  }
91
137
 
92
- export function getTelegramDiagnosticsDisplayPaths(profileName?: string): {
138
+ export function getTelegramDiagnosticsDisplayPaths(_profileName?: string): {
93
139
  state: string;
94
140
  logs: string;
95
141
  } {
96
- const suffix = getTelegramProfilePathSuffix(profileName);
97
- const profileSlug = suffix.slice(1);
98
142
  return {
99
- state: `~/.pi/agent/tmp/telegram/state${suffix}.json`,
100
- logs: `~/.pi/agent/tmp/telegram/logs${profileSlug ? `.${profileSlug}` : ""}.jsonl`,
143
+ state: "~/.pi/agent/tmp/pi-telegram/state.json",
144
+ logs: "~/.pi/agent/tmp/pi-telegram/logs.jsonl",
101
145
  };
102
146
  }
103
147
 
104
- /** Durable Workspace admission ledger (<agentDir>/tmp/telegram/workspace-admission[.<profile>].json). */
148
+ /** Durable Workspace admission ledger (<agentDir>/tmp/pi-telegram/workspace-admission[.<profile>].json). */
105
149
  export function resolveTelegramWorkspaceAdmissionPath(
106
150
  agentDir = resolveAgentDir(),
107
151
  profileName?: string,
@@ -121,6 +165,31 @@ export function resolveTelegramWorkspaceAdmissionPathForProfile(
121
165
  return resolveTelegramWorkspaceAdmissionPath(resolveAgentDir(), profileName);
122
166
  }
123
167
 
168
+ /** Consolidated non-session service journal (`journals/<kind>.<profile sha256>.json`) and its runtime namespace. */
169
+ export function resolveTelegramServiceJournalStorage(
170
+ kind: "thread-cleanup" | "channel-posts",
171
+ agentDir = resolveAgentDir(),
172
+ profileName = TELEGRAM_DEFAULT_PROFILE_NAME,
173
+ ): { path: string; runtimeDir: string } {
174
+ const profileHash = createHash("sha256").update(profileName).digest("hex");
175
+ return {
176
+ path: join(resolveTelegramTempDir(agentDir), "journals", `${kind}.${profileHash}.json`),
177
+ runtimeDir: resolveTelegramRuntimeDir(agentDir),
178
+ };
179
+ }
180
+
181
+ /** Bind one non-session journal to its exact colocated-root service namespace, never a format fallback. */
182
+ export function getTelegramJournalPublicationPaths(path: string, runtimeDir?: string): {
183
+ transactionPath: string;
184
+ temporaryBasePath: string;
185
+ } {
186
+ if (runtimeDir === undefined) return { transactionPath: `${path}.transaction`, temporaryBasePath: path };
187
+ requireTelegramStoragePathReference(path, resolve(path));
188
+ requireTelegramStoragePathReference(runtimeDir, join(dirname(dirname(path)), "runtime"));
189
+ if (basename(dirname(path)) !== "journals") throw new Error("Telegram service journal must use its journals namespace.");
190
+ return { transactionPath: join(runtimeDir, `${basename(path)}.transaction`), temporaryBasePath: join(runtimeDir, basename(path)) };
191
+ }
192
+
124
193
  /** Durable inactive Thread cleanup work-set journal. */
125
194
  export function resolveTelegramThreadCleanupWorkPath(
126
195
  agentDir = resolveAgentDir(),
@@ -137,7 +206,7 @@ export function resolveTelegramChannelPostJournalPath(
137
206
  return resolveTelegramProfileTempFilePath("channel-posts", "json", agentDir, profileName);
138
207
  }
139
208
 
140
- /** Durable inbound update journal (<agentDir>/tmp/telegram/inbox[.<profile>].json). */
209
+ /** Durable inbound update journal (<agentDir>/tmp/pi-telegram/inbox[.<profile>].json). */
141
210
  export function resolveTelegramUpdateJournalPath(
142
211
  agentDir = resolveAgentDir(),
143
212
  profileName?: string,
@@ -178,7 +247,98 @@ export function resolveTelegramFollowerJournalPath(
178
247
  );
179
248
  }
180
249
 
181
- /** Runtime event log (<agentDir>/tmp/telegram/logs.jsonl). */
182
- export function resolveTelegramRuntimeLogPath(): string {
183
- return resolveTelegramProfileTempFilePath("logs", "jsonl");
250
+ const TELEGRAM_SESSION_DIRECTORY_MAX_LENGTH = 200;
251
+ const WINDOWS_RESERVED_NAME = /^(?:con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\..*)?$/u;
252
+
253
+ /**
254
+ * Directory name for a session id. Lowercase letters, digits, `_`, `-` and inner `.` stay verbatim, so every real
255
+ * (UUID-shaped) id is unchanged; any other UTF-8 byte is percent-encoded. Uppercase is encoded so case-insensitive
256
+ * filesystems never merge two sessions; a leading or trailing dot and Windows reserved names are encoded so the name
257
+ * can never be `.`/`..`, hidden, or a device. The encoding is reversible; an empty or over-long result is refused.
258
+ */
259
+ export function encodeTelegramSessionDirectoryName(sessionId: string): string | undefined {
260
+ if (typeof sessionId !== "string" || sessionId.length === 0) return undefined;
261
+ const bytes = Buffer.from(sessionId, "utf8");
262
+ if (bytes.toString("utf8") !== sessionId) return undefined;
263
+ let name = "";
264
+ bytes.forEach((byte, index) => {
265
+ const char = String.fromCharCode(byte);
266
+ const safe = /[a-z0-9_-]/u.test(char) || (char === "." && index > 0 && index < bytes.length - 1);
267
+ name += safe ? char : `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
268
+ });
269
+ if (WINDOWS_RESERVED_NAME.test(name)) name = `%${name.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}${name.slice(1)}`;
270
+ return name.length <= TELEGRAM_SESSION_DIRECTORY_MAX_LENGTH ? name : undefined;
271
+ }
272
+
273
+ /** Inverse of `encodeTelegramSessionDirectoryName`; undefined unless the name is exactly canonical. */
274
+ export function decodeTelegramSessionDirectoryName(name: string): string | undefined {
275
+ if (!/^[a-z0-9_.%A-F-]+$/u.test(name)) return undefined;
276
+ const bytes: number[] = [];
277
+ for (let index = 0; index < name.length; index++) {
278
+ if (name[index] === "%") {
279
+ const hex = name.slice(index + 1, index + 3);
280
+ if (!/^[0-9A-F]{2}$/u.test(hex)) return undefined;
281
+ bytes.push(Number.parseInt(hex, 16));
282
+ index += 2;
283
+ } else bytes.push(name.charCodeAt(index));
284
+ }
285
+ const decoded = Buffer.from(bytes).toString("utf8");
286
+ return encodeTelegramSessionDirectoryName(decoded) === name ? decoded : undefined;
287
+ }
288
+
289
+ /** Root of every session folder (<agentDir>/tmp/pi-telegram/sessions). */
290
+ export function resolveTelegramSessionsDir(agentDir = resolveAgentDir()): string {
291
+ return join(resolveTelegramTempDir(agentDir), "sessions");
292
+ }
293
+
294
+ /** One session's folder (<agentDir>/tmp/pi-telegram/sessions/<session id>). */
295
+ export function resolveTelegramSessionDir(sessionId: string, agentDir = resolveAgentDir()): string {
296
+ const name = encodeTelegramSessionDirectoryName(sessionId);
297
+ if (!name) throw new Error("Telegram session directory requires a usable session id.");
298
+ return join(resolveTelegramSessionsDir(agentDir), name);
299
+ }
300
+
301
+ /** Hash naming one recipient incarnation inside its session folder; unchanged from the former follower inbox. */
302
+ export function getTelegramRecipientJournalHash(recipientBindingKey: string): string {
303
+ if (!recipientBindingKey) throw new Error("Telegram session journal binding key is required.");
304
+ return createHash("sha256").update(recipientBindingKey).digest("hex").slice(0, 16);
305
+ }
306
+
307
+ /** Durable per-session journal (`sessions/<session id>/journal.<hash>[.<profile>].json`); the name never reveals a role. */
308
+ export function resolveTelegramSessionJournalPath(
309
+ sessionId: string,
310
+ recipientBindingKey: string,
311
+ agentDir = resolveAgentDir(),
312
+ profileName?: string,
313
+ ): string {
314
+ return join(
315
+ resolveTelegramSessionDir(sessionId, agentDir),
316
+ `journal.${getTelegramRecipientJournalHash(recipientBindingKey)}${getTelegramProfilePathSuffix(profileName)}.json`,
317
+ );
318
+ }
319
+
320
+ /** Leader polling journal hosted by the session that first led (`sessions/<id>/inbox[.<profile>].json`); owners.json names it. */
321
+ export function resolveTelegramSessionPollingJournalPath(
322
+ sessionId: string,
323
+ agentDir = resolveAgentDir(),
324
+ profileName?: string,
325
+ ): string {
326
+ return join(resolveTelegramSessionDir(sessionId, agentDir), `inbox${getTelegramProfilePathSuffix(profileName)}.json`);
327
+ }
328
+
329
+ /** Accept only a canonical `sessions/<id>/inbox[.<profile>].json` beneath this agent's runtime. */
330
+ export function isTelegramSessionPollingJournalPath(path: string, agentDir = resolveAgentDir()): boolean {
331
+ const parts = relative(resolveTelegramSessionsDir(agentDir), path).split(sep);
332
+ return isAbsolute(path) && parts.length === 2 && decodeTelegramSessionDirectoryName(parts[0]!) !== undefined &&
333
+ /^inbox(?:\.[a-zA-Z0-9._-]+)?\.json$/u.test(parts[1]!);
334
+ }
335
+
336
+ /** Runtime event log (<agentDir>/tmp/pi-telegram/logs.jsonl). */
337
+ export function resolveTelegramRuntimeLogPath(agentDir = resolveAgentDir()): string {
338
+ return resolveTelegramProfileTempFilePath("logs", "jsonl", agentDir);
339
+ }
340
+
341
+ /** Bounded previous shared log segment below `logs/`. */
342
+ export function resolveTelegramPreviousSharedRuntimeLogPath(agentDir = resolveAgentDir()): string {
343
+ return join(resolveTelegramRuntimeLogsDir(agentDir), "logs._prev.jsonl");
184
344
  }
@@ -263,8 +263,11 @@ export function getExtensionContextCwd(ctx: ExtensionContext): string {
263
263
  return ctx.cwd;
264
264
  }
265
265
 
266
- export function getExtensionContextSessionId(ctx: ExtensionContext): string {
267
- return ctx.sessionManager.getSessionId();
266
+ export function getExtensionContextSessionId(ctx: ExtensionContext): string;
267
+ export function getExtensionContextSessionId(ctx: ExtensionContext | undefined): string | undefined;
268
+ export function getExtensionContextSessionId(ctx: ExtensionContext | undefined): string | undefined {
269
+ // Minimal hosts and stale contexts may omit a session manager; absence is not an invented ID.
270
+ return ctx?.sessionManager?.getSessionId?.();
268
271
  }
269
272
 
270
273
  export function isExtensionContextIdle(ctx: ExtensionContext): boolean {