@llblab/pi-telegram 0.52.2 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/AGENTS.md +9 -7
  2. package/BACKLOG.md +1 -18
  3. package/CHANGELOG.md +11 -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 +61 -51
  70. package/dist/lib/ownership.js +6 -2
  71. package/dist/lib/paths.d.ts +5 -0
  72. package/dist/lib/paths.js +37 -21
  73. package/dist/lib/pi.d.ts +2 -0
  74. package/dist/lib/pi.js +8 -1
  75. package/dist/lib/polling.d.ts +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 +249 -126
  83. package/dist/lib/recovery.d.ts +2 -83
  84. package/dist/lib/recovery.js +20 -274
  85. package/dist/lib/replies.d.ts +16 -24
  86. package/dist/lib/replies.js +99 -116
  87. package/dist/lib/routing.d.ts +120 -7
  88. package/dist/lib/routing.js +4667 -1152
  89. package/dist/lib/runtime.d.ts +1 -11
  90. package/dist/lib/sections.d.ts +4 -29
  91. package/dist/lib/sections.js +7 -20
  92. package/dist/lib/skills.js +4 -3
  93. package/dist/lib/status.js +86 -57
  94. package/dist/lib/sync.d.ts +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 +125 -125
  167. package/lib/ownership.ts +19 -10
  168. package/lib/paths.ts +148 -49
  169. package/lib/pi.ts +35 -15
  170. package/lib/polling.ts +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 +511 -300
  175. package/lib/recovery.ts +50 -454
  176. package/lib/rendering.ts +1 -3
  177. package/lib/replies.ts +248 -209
  178. package/lib/routing.ts +8509 -2186
  179. package/lib/runtime.ts +1 -12
  180. package/lib/sections.ts +24 -89
  181. package/lib/setup.ts +1 -3
  182. package/lib/skills.ts +9 -4
  183. package/lib/status.ts +215 -110
  184. package/lib/sync.ts +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
@@ -142,18 +142,8 @@ export interface TelegramPromptDispatchLifecycleDeps<TContext> extends TelegramR
142
142
  }) => boolean | void;
143
143
  updateStatus: (ctx: TContext, error?: string) => void;
144
144
  }
145
- export interface TelegramPromptDispatchRuntimeDeps<TContext> extends TelegramRuntimeEventRecorderPort {
145
+ export interface TelegramPromptDispatchRuntimeDeps<TContext> extends TelegramTypingLoopStarterDeps<TContext> {
146
146
  lifecycle: TelegramPromptDispatchLifecycleDeps<TContext>["lifecycle"];
147
- typing: TelegramRuntimeTypingPort;
148
- getDefaultChatId: () => number | undefined;
149
- sendTypingAction: (chatId: number, options?: {
150
- message_thread_id?: number;
151
- }) => Promise<unknown>;
152
- updateStatus: (ctx: TContext, error?: string) => void;
153
- isContextActive?: (ctx: TContext) => boolean;
154
- isTransportAvailable?: () => boolean;
155
- getTransportAuthority?: () => string | number | undefined;
156
- intervalMs?: number;
157
147
  }
158
148
  export interface TelegramPromptDispatchRuntime<TContext> {
159
149
  startTypingLoop: (ctx: TContext, chatId?: number, options?: {
@@ -50,20 +50,9 @@ export interface TelegramSectionContext {
50
50
  /** Delete the message that triggered this callback (dialog cleanup) */
51
51
  deleteMessage(): Promise<void>;
52
52
  }
53
- export interface TelegramSectionCallbackContext {
54
- sectionId: string;
55
- chatId: number;
56
- messageId?: number;
53
+ export interface TelegramSectionCallbackContext extends TelegramSectionContext {
57
54
  action: string;
58
55
  payload: string;
59
- answerCallback(text?: string): Promise<void>;
60
- edit(view: TelegramSectionView): Promise<void>;
61
- open(view: TelegramSectionView): Promise<void>;
62
- openRich(message: TelegramInputRichMessage): Promise<void>;
63
- enqueuePrompt(prompt: string): Promise<void>;
64
- callbackData(action: string, payload?: string): string;
65
- /** Delete the message that triggered this callback (dialog cleanup) */
66
- deleteMessage(): Promise<void>;
67
56
  }
68
57
  /** @internal */
69
58
  export interface RegisteredTelegramSection {
@@ -144,20 +133,6 @@ export declare function parseTelegramSectionCallback(data: string): {
144
133
  action: string;
145
134
  payload: string;
146
135
  } | undefined;
147
- /** @internal */
148
- export interface TelegramSectionCallbackHandlerDeps {
149
- answerCallbackQuery: (id: string, text?: string) => Promise<void>;
150
- target?: TelegramSectionTarget;
151
- editInteractiveMessage: (chatId: number, messageId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramInlineKeyboardMarkup) => Promise<void>;
152
- sendInteractiveMessage: (chatId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramInlineKeyboardMarkup, options?: {
153
- target?: TelegramSectionTarget;
154
- }) => Promise<number | undefined>;
155
- sendRichMessage: (chatId: number, message: TelegramInputRichMessage, options?: {
156
- target?: TelegramSectionTarget;
157
- }) => Promise<number | undefined>;
158
- enqueuePrompt: (prompt: string) => Promise<void>;
159
- deleteMessage: (chatId: number, messageId: number) => Promise<void>;
160
- }
161
- export declare function handleTelegramSectionOpen(registry: TelegramSectionRegistry, token: TelegramSectionToken, chatId: number, messageId: number, callbackQueryId: string, deps: TelegramSectionCallbackHandlerDeps): Promise<boolean>;
162
- export declare function handleTelegramSectionCallback(registry: TelegramSectionRegistry, token: TelegramSectionToken, action: string, payload: string, chatId: number, messageId: number, callbackQueryId: string, deps: TelegramSectionCallbackHandlerDeps): Promise<boolean>;
163
- export declare function handleTelegramSectionSettingsOpen(registry: TelegramSectionRegistry, token: TelegramSectionToken, chatId: number, messageId: number, callbackQueryId: string, deps: TelegramSectionCallbackHandlerDeps): Promise<boolean>;
136
+ export declare function handleTelegramSectionOpen(registry: TelegramSectionRegistry, token: TelegramSectionToken, chatId: number, messageId: number, callbackQueryId: string, deps: TelegramSectionRuntimeDeps): Promise<boolean>;
137
+ export declare function handleTelegramSectionCallback(registry: TelegramSectionRegistry, token: TelegramSectionToken, action: string, payload: string, chatId: number, messageId: number, callbackQueryId: string, deps: TelegramSectionRuntimeDeps): Promise<boolean>;
138
+ export declare function handleTelegramSectionSettingsOpen(registry: TelegramSectionRegistry, token: TelegramSectionToken, chatId: number, messageId: number, callbackQueryId: string, deps: TelegramSectionRuntimeDeps): Promise<boolean>;
@@ -19,7 +19,9 @@ function buildTelegramSectionContext(sectionId, token, chatId, messageId, callba
19
19
  open: (view) => deps
20
20
  .sendInteractiveMessage(chatId, view.text, view.parseMode ?? "html", view.replyMarkup ?? { inline_keyboard: [] }, deps.target ? { target: deps.target } : undefined)
21
21
  .then(() => { }),
22
- openRich: (message) => deps.sendRichMessage(chatId, message, deps.target ? { target: deps.target } : undefined).then(() => { }),
22
+ openRich: (message) => deps
23
+ .sendRichMessage(chatId, message, deps.target ? { target: deps.target } : undefined)
24
+ .then(() => { }),
23
25
  enqueuePrompt: deps.enqueuePrompt,
24
26
  callbackData: (action, payload) => buildTelegramSectionCallbackData(token, action, payload),
25
27
  deleteMessage: () => messageId !== undefined
@@ -29,24 +31,9 @@ function buildTelegramSectionContext(sectionId, token, chatId, messageId, callba
29
31
  }
30
32
  function buildTelegramSectionCallbackContext(sectionId, token, chatId, messageId, action, payload, callbackQueryId, deps, backCallback = "menu:back", backLabel = "⬆️ Back") {
31
33
  return {
32
- sectionId,
33
- chatId,
34
- messageId,
34
+ ...buildTelegramSectionContext(sectionId, token, chatId, messageId, callbackQueryId, deps, backCallback, backLabel),
35
35
  action,
36
36
  payload,
37
- answerCallback: (text) => deps.answerCallbackQuery(callbackQueryId, text),
38
- edit: (view) => messageId !== undefined
39
- ? deps.editInteractiveMessage(chatId, messageId, view.text, view.parseMode ?? "html", prependBackRow(view.replyMarkup, backCallback, backLabel))
40
- : Promise.resolve(),
41
- open: (view) => deps
42
- .sendInteractiveMessage(chatId, view.text, view.parseMode ?? "html", view.replyMarkup ?? { inline_keyboard: [] }, deps.target ? { target: deps.target } : undefined)
43
- .then(() => { }),
44
- openRich: (message) => deps.sendRichMessage(chatId, message, deps.target ? { target: deps.target } : undefined).then(() => { }),
45
- enqueuePrompt: deps.enqueuePrompt,
46
- callbackData: (action, payload) => buildTelegramSectionCallbackData(token, action, payload),
47
- deleteMessage: () => messageId !== undefined
48
- ? deps.deleteMessage(chatId, messageId)
49
- : Promise.resolve(),
50
37
  };
51
38
  }
52
39
  // --- GlobalThis Bridge ---
@@ -242,7 +229,7 @@ export function parseTelegramSectionCallback(data) {
242
229
  export async function handleTelegramSectionOpen(registry, token, chatId, messageId, callbackQueryId, deps) {
243
230
  const section = registry.getByToken(token);
244
231
  if (!section) {
245
- await deps.answerCallbackQuery(callbackQueryId, "This section is no longer available.");
232
+ await deps.answerCallbackQuery(callbackQueryId, "This section is no longer available");
246
233
  return true;
247
234
  }
248
235
  try {
@@ -267,7 +254,7 @@ export async function handleTelegramSectionOpen(registry, token, chatId, message
267
254
  export async function handleTelegramSectionCallback(registry, token, action, payload, chatId, messageId, callbackQueryId, deps) {
268
255
  const section = registry.getByToken(token);
269
256
  if (!section) {
270
- await deps.answerCallbackQuery(callbackQueryId, "This section is no longer available.");
257
+ await deps.answerCallbackQuery(callbackQueryId, "This section is no longer available");
271
258
  return true;
272
259
  }
273
260
  // Try main handleCallback first, then settings handleCallback as fallback.
@@ -303,7 +290,7 @@ export async function handleTelegramSectionCallback(registry, token, action, pay
303
290
  export async function handleTelegramSectionSettingsOpen(registry, token, chatId, messageId, callbackQueryId, deps) {
304
291
  const section = registry.getByToken(token);
305
292
  if (!section || !section.registration.settings) {
306
- await deps.answerCallbackQuery(callbackQueryId, "This section is no longer available.");
293
+ await deps.answerCallbackQuery(callbackQueryId, "This section is no longer available");
307
294
  return true;
308
295
  }
309
296
  try {
@@ -13,7 +13,8 @@ export function getTelegramExtensionPackageRoot(modulePath) {
13
13
  while (true) {
14
14
  if (existsSync(join(current, "package.json"))) {
15
15
  const parent = dirname(current);
16
- if (basename(current) === "dist" && existsSync(join(parent, "package.json"))) {
16
+ if (basename(current) === "dist" &&
17
+ existsSync(join(parent, "package.json"))) {
17
18
  return parent;
18
19
  }
19
20
  return current;
@@ -28,8 +29,8 @@ export function isRawTelegramExtensionCheckout(modulePath, options = {}) {
28
29
  const packageRoot = resolve(getTelegramExtensionPackageRoot(modulePath));
29
30
  const agentDir = resolve(options.agentDir ?? resolveAgentDir());
30
31
  const cwd = resolve(options.cwd ?? process.cwd());
31
- return dirname(packageRoot) === join(agentDir, "extensions") ||
32
- dirname(packageRoot) === join(cwd, ".pi", "extensions");
32
+ return (dirname(packageRoot) === join(agentDir, "extensions") ||
33
+ dirname(packageRoot) === join(cwd, ".pi", "extensions"));
33
34
  }
34
35
  export const TELEGRAM_SKILLS_PATH = join(getTelegramExtensionPackageRoot(TELEGRAM_SKILLS_MODULE_PATH), "skills");
35
36
  export function registerTelegramSkillDiscovery(pi, modulePath = TELEGRAM_SKILLS_MODULE_PATH, options = {}) {
@@ -7,21 +7,31 @@
7
7
  import { isDeepStrictEqual } from "node:util";
8
8
  /** UI copy is allowlisted; raw exception text belongs only in redacted diagnostics. */
9
9
  export function formatTelegramConnectionFailure(error) {
10
- const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
11
- const status = error && typeof error === "object" && "status" in error ? error.status : undefined;
12
- const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
10
+ const message = error instanceof Error
11
+ ? error.message
12
+ : typeof error === "string"
13
+ ? error
14
+ : "";
15
+ const status = error && typeof error === "object" && "status" in error
16
+ ? error.status
17
+ : undefined;
18
+ const code = error && typeof error === "object" && "code" in error
19
+ ? error.code
20
+ : undefined;
13
21
  if (status === 401)
14
22
  return "Telegram token rejected. Run /telegram-setup.";
15
23
  if (status === 403)
16
24
  return "Telegram access denied. Check /telegram-status --debug.";
17
- if ((typeof code === "string" && ["ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "ECONNRESET"].includes(code)) ||
25
+ if ((typeof code === "string" &&
26
+ ["ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "ECONNRESET"].includes(code)) ||
18
27
  /network unavailable|fetch failed/i.test(message)) {
19
28
  return "Telegram network unavailable. Retry /telegram-connect.";
20
29
  }
21
30
  if (/Workspace slots?.*(?:unavailable|exhausted)|no free.*slot/i.test(message)) {
22
31
  return "No Telegram slot available. Check /telegram-status --debug.";
23
32
  }
24
- if (code === "incompatible-protocol" || /protocol.*(?:incompatible|mismatch)|incompatible.*protocol/i.test(message)) {
33
+ if (code === "incompatible-protocol" ||
34
+ /protocol.*(?:incompatible|mismatch)|incompatible.*protocol/i.test(message)) {
25
35
  return "Telegram instances are incompatible. Update them together.";
26
36
  }
27
37
  if (/unsupported.*(?:journal|version)|(?:journal|version).*unsupported/i.test(message)) {
@@ -61,17 +71,11 @@ function truncateTelegramRuntimeEventText(text, maxLength) {
61
71
  return text;
62
72
  return `${text.slice(0, maxLength).trimEnd()}… [truncated ${text.length - maxLength} chars]`;
63
73
  }
64
- function redactTelegramRuntimeMessage(message, botToken) {
65
- const redacted = botToken
66
- ? message.split(botToken).join("<redacted-token>")
67
- : message;
68
- return truncateTelegramRuntimeEventText(redacted, MAX_TELEGRAM_RUNTIME_EVENT_MESSAGE_LENGTH);
69
- }
70
- function redactTelegramRuntimeDetail(message, botToken) {
74
+ function redactTelegramRuntimeText(text, botToken, maxLength) {
71
75
  const redacted = botToken
72
- ? message.split(botToken).join("<redacted-token>")
73
- : message;
74
- return truncateTelegramRuntimeEventText(redacted, MAX_TELEGRAM_RUNTIME_EVENT_DETAIL_LENGTH);
76
+ ? text.split(botToken).join("<redacted-token>")
77
+ : text;
78
+ return truncateTelegramRuntimeEventText(redacted, maxLength);
75
79
  }
76
80
  function normalizeTelegramRuntimeEventDetails(details, botToken) {
77
81
  if (!details)
@@ -81,7 +85,7 @@ function normalizeTelegramRuntimeEventDetails(details, botToken) {
81
85
  if (value === undefined)
82
86
  continue;
83
87
  if (typeof value === "string") {
84
- normalized[key] = redactTelegramRuntimeDetail(value, botToken);
88
+ normalized[key] = redactTelegramRuntimeText(value, botToken, MAX_TELEGRAM_RUNTIME_EVENT_DETAIL_LENGTH);
85
89
  continue;
86
90
  }
87
91
  if (typeof value === "number" || typeof value === "boolean") {
@@ -92,7 +96,7 @@ function normalizeTelegramRuntimeEventDetails(details, botToken) {
92
96
  normalized[key] = null;
93
97
  continue;
94
98
  }
95
- normalized[key] = redactTelegramRuntimeDetail(String(value), botToken);
99
+ normalized[key] = redactTelegramRuntimeText(String(value), botToken, MAX_TELEGRAM_RUNTIME_EVENT_DETAIL_LENGTH);
96
100
  }
97
101
  return Object.keys(normalized).length > 0 ? normalized : undefined;
98
102
  }
@@ -108,7 +112,7 @@ export function recordStructuredTelegramRuntimeEvent(events, input, options) {
108
112
  events.push({
109
113
  at: options.now ?? Date.now(),
110
114
  category: input.category,
111
- message: redactTelegramRuntimeMessage(getTelegramRuntimeEventMessage(input), options.botToken),
115
+ message: redactTelegramRuntimeText(getTelegramRuntimeEventMessage(input), options.botToken, MAX_TELEGRAM_RUNTIME_EVENT_MESSAGE_LENGTH),
112
116
  ...(details ? { details } : {}),
113
117
  });
114
118
  while (events.length > options.maxEvents) {
@@ -248,7 +252,9 @@ export function createTelegramBridgeStatusRuntime(deps) {
248
252
  const config = deps.getConfig();
249
253
  const queuedItems = deps.getQueuedItems();
250
254
  const hasPendingDispatch = deps.hasDispatchPending();
251
- const waitingItems = hasPendingDispatch ? queuedItems.slice(1) : queuedItems;
255
+ const waitingItems = hasPendingDispatch
256
+ ? queuedItems.slice(1)
257
+ : queuedItems;
252
258
  const queuedItemCount = deps.getQueuedItemCount?.(waitingItems) ?? waitingItems.length;
253
259
  const hasActiveTurn = deps.hasActiveTurn();
254
260
  const hasPendingModelSwitch = deps.hasPendingModelSwitch();
@@ -305,9 +311,7 @@ export function createTelegramBridgeStatusRuntime(deps) {
305
311
  instanceThreadName: deps.getInstanceThreadName?.(),
306
312
  lockState: deps.getRuntimeLockState?.(),
307
313
  pollingActive: deps.isPollingActive(),
308
- ...(deps.getPollingState
309
- ? { polling: deps.getPollingState() }
310
- : {}),
314
+ ...(deps.getPollingState ? { polling: deps.getPollingState() } : {}),
311
315
  ...(deps.getInboundWorkerState
312
316
  ? { inboundWorker: deps.getInboundWorkerState() }
313
317
  : {}),
@@ -377,9 +381,21 @@ function isRuntimeProjection(value) {
377
381
  if (!value || typeof value !== "object" || Array.isArray(value))
378
382
  return false;
379
383
  const record = value;
380
- return record.version === 1 && record.source === "snapshot" && Number.isSafeInteger(record.writtenAtMs) && record.writtenAtMs >= 0 &&
381
- Object.keys(record).every(key => ["version", "source", "writtenAtMs", "runtime", "liveRoster", "diagnostics"].includes(key)) &&
382
- ["runtime", "liveRoster", "diagnostics"].every(key => record[key] !== null && typeof record[key] === "object" && !Array.isArray(record[key]));
384
+ return (record.version === 1 &&
385
+ record.source === "snapshot" &&
386
+ Number.isSafeInteger(record.writtenAtMs) &&
387
+ record.writtenAtMs >= 0 &&
388
+ Object.keys(record).every((key) => [
389
+ "version",
390
+ "source",
391
+ "writtenAtMs",
392
+ "runtime",
393
+ "liveRoster",
394
+ "diagnostics",
395
+ ].includes(key)) &&
396
+ ["runtime", "liveRoster", "diagnostics"].every((key) => record[key] !== null &&
397
+ typeof record[key] === "object" &&
398
+ !Array.isArray(record[key])));
383
399
  }
384
400
  /** Observational runtime storage only; Workspace, admission, transport, logs and recovery remain with their owners. */
385
401
  export function createTelegramRuntimeProjectionStore(options) {
@@ -392,7 +408,11 @@ export function createTelegramRuntimeProjectionStore(options) {
392
408
  read() {
393
409
  try {
394
410
  const path = getPath(), profile = getProfile(), value = read({ path, profile });
395
- return getPath() === path && getProfile() === profile && isRuntimeProjection(value) ? value : undefined;
411
+ return getPath() === path &&
412
+ getProfile() === profile &&
413
+ isRuntimeProjection(value)
414
+ ? value
415
+ : undefined;
396
416
  }
397
417
  catch {
398
418
  return undefined;
@@ -403,8 +423,13 @@ export function createTelegramRuntimeProjectionStore(options) {
403
423
  if (!authority)
404
424
  return false;
405
425
  // Freeze wire content at submission, but do not duplicate the event log in persistent runtime state.
406
- const semantic = JSON.parse(JSON.stringify({ version: 1, source: "snapshot", runtime: snapshot.runtime,
407
- liveRoster: snapshot.liveRoster, diagnostics: snapshot.diagnostics }));
426
+ const semantic = JSON.parse(JSON.stringify({
427
+ version: 1,
428
+ source: "snapshot",
429
+ runtime: snapshot.runtime,
430
+ liveRoster: snapshot.liveRoster,
431
+ diagnostics: snapshot.diagnostics,
432
+ }));
408
433
  if (!isRuntimeProjection({ ...semantic, writtenAtMs: 0 }))
409
434
  throw new Error("Telegram runtime projection is malformed.");
410
435
  delete semantic.diagnostics.recentRuntimeEvents;
@@ -412,7 +437,7 @@ export function createTelegramRuntimeProjectionStore(options) {
412
437
  const publication = publicationQueue.then(() => {
413
438
  if (!isCurrent())
414
439
  return false;
415
- return publish({ path, profile }, current => {
440
+ return (publish({ path, profile }, (current) => {
416
441
  if (isRuntimeProjection(current)) {
417
442
  const { writtenAtMs: _writtenAtMs, ...existing } = current;
418
443
  if (isDeepStrictEqual(existing, semantic))
@@ -422,7 +447,7 @@ export function createTelegramRuntimeProjectionStore(options) {
422
447
  if (!Number.isSafeInteger(writtenAtMs) || writtenAtMs < 0)
423
448
  throw new Error("Telegram runtime projection timestamp is invalid.");
424
449
  return { value: { ...semantic, writtenAtMs }, changed: true };
425
- }, isCurrent) === true;
450
+ }, isCurrent) === true);
426
451
  });
427
452
  publicationQueue = publication.then(() => undefined, () => undefined);
428
453
  return publication;
@@ -432,7 +457,8 @@ export function createTelegramRuntimeProjectionStore(options) {
432
457
  const TELEGRAM_DIAGNOSTICS_SNAPSHOT_COALESCE_MS = 100;
433
458
  export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps) {
434
459
  const setTimer = deps.setTimer ?? setTimeout;
435
- const clearTimer = deps.clearTimer ?? ((handle) => clearTimeout(handle));
460
+ const clearTimer = deps.clearTimer ??
461
+ ((handle) => clearTimeout(handle));
436
462
  let generation = 0;
437
463
  let enabled = true;
438
464
  let timer;
@@ -503,7 +529,9 @@ export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps) {
503
529
  return;
504
530
  let tracked;
505
531
  tracked = Promise.resolve()
506
- .then(() => publicationAuthority() ? deps.persistSnapshot(publicationAuthority) : undefined)
532
+ .then(() => publicationAuthority()
533
+ ? deps.persistSnapshot(publicationAuthority)
534
+ : undefined)
507
535
  .catch(recordError)
508
536
  .finally(() => {
509
537
  if (persistPromise !== tracked)
@@ -553,13 +581,7 @@ export function getTelegramStatusBarProcessingStatus(state) {
553
581
  return undefined;
554
582
  }
555
583
  function getTelegramStatusBarLabel(state) {
556
- const threadName = state.instanceThreadName?.trim();
557
- if (!threadName)
558
- return "telegram";
559
- const genericLabels = new Set(["telegram", "leader", "follower"]);
560
- if (genericLabels.has(threadName.toLowerCase()))
561
- return "telegram";
562
- return threadName;
584
+ return state.instanceThreadName?.trim() || "telegram";
563
585
  }
564
586
  export function buildTelegramStatusBarText(theme, state) {
565
587
  const label = theme.fg("accent", getTelegramStatusBarLabel(state));
@@ -568,14 +590,15 @@ export function buildTelegramStatusBarText(theme, state) {
568
590
  : "";
569
591
  if (!state.hasBotToken)
570
592
  return `${label} ${theme.fg("muted", "not configured")}${queued}`;
571
- if (state.pollingStopReason === "persistent-conflict" && state.busRole !== "follower")
593
+ if (state.pollingStopReason === "persistent-conflict" &&
594
+ state.busRole !== "follower")
572
595
  return `${label} ${theme.fg("error", "error")}`;
573
596
  if (!state.paired)
574
597
  return `${label} ${theme.fg("warning", "awaiting pairing")}${queued}`;
575
598
  if (state.busLifecyclePhase === "electing")
576
599
  return `${label} ${theme.fg("warning", "electing")}${queued}`;
577
600
  if (!state.pollingActive && state.busRole !== "follower")
578
- return `${theme.fg("accent", "telegram")} ${theme.fg("dim", "disconnected")}${queued}`;
601
+ return `${label} ${theme.fg("dim", "disconnected")}${queued}`;
579
602
  if (state.error) {
580
603
  return `${label} ${theme.fg("error", "error")}${queued}`;
581
604
  }
@@ -820,9 +843,7 @@ function buildTelegramPollingDiagnosticLines(polling) {
820
843
  return [
821
844
  `- phase: ${polling.phase}`,
822
845
  ...(polling.phaseStartedAtMs !== undefined
823
- ? [
824
- `- phase started: ${new Date(polling.phaseStartedAtMs).toISOString()}`,
825
- ]
846
+ ? [`- phase started: ${new Date(polling.phaseStartedAtMs).toISOString()}`]
826
847
  : []),
827
848
  ...(polling.currentUpdateId !== undefined
828
849
  ? [`- current update id: ${polling.currentUpdateId}`]
@@ -889,7 +910,9 @@ function buildTelegramInboundWorkerDiagnosticLines(worker) {
889
910
  ? [`- blocked reason: ${worker.blockedReason}`]
890
911
  : []),
891
912
  ...(worker.blockedInputCustody
892
- ? [`- blocked input custody: update=${worker.blockedInputCustody.updateId}, kind=${worker.blockedInputCustody.kind}`]
913
+ ? [
914
+ `- blocked input custody: update=${worker.blockedInputCustody.updateId}, kind=${worker.blockedInputCustody.kind}`,
915
+ ]
893
916
  : []),
894
917
  ...(worker.lastCompletedUpdateId !== undefined
895
918
  ? [
@@ -903,10 +926,18 @@ function buildTelegramInboundWorkerDiagnosticLines(worker) {
903
926
  : []),
904
927
  ];
905
928
  }
929
+ function countTelegramQueueLanes(items) {
930
+ const counts = {
931
+ control: 0,
932
+ priority: 0,
933
+ default: 0,
934
+ };
935
+ for (const item of items)
936
+ counts[item.queueLane]++;
937
+ return counts;
938
+ }
906
939
  function buildTelegramBridgeCompactStatusLines(state) {
907
- const controlQueueCount = state.queuedItems.filter((item) => item.queueLane === "control").length;
908
- const priorityQueueCount = state.queuedItems.filter((item) => item.queueLane === "priority").length;
909
- const defaultQueueCount = state.queuedItems.filter((item) => item.queueLane === "default").length;
940
+ const { control: controlQueueCount, priority: priorityQueueCount, default: defaultQueueCount, } = countTelegramQueueLanes(state.queuedItems);
910
941
  const queueLine = `- queued turns: ${state.queuedItems.length}${state.queuedItems.length > 0
911
942
  ? ` (control=${controlQueueCount}, priority=${priorityQueueCount}, default=${defaultQueueCount})`
912
943
  : ""}`;
@@ -966,9 +997,7 @@ export function buildTelegramBridgeStatusLines(state, options = {}) {
966
997
  return buildTelegramBridgeCompactStatusLines(state);
967
998
  }
968
999
  function buildTelegramBridgeDiagnosticStatusLines(state) {
969
- const controlQueueCount = state.queuedItems.filter((item) => item.queueLane === "control").length;
970
- const priorityQueueCount = state.queuedItems.filter((item) => item.queueLane === "priority").length;
971
- const defaultQueueCount = state.queuedItems.filter((item) => item.queueLane === "default").length;
1000
+ const { control: controlQueueCount, priority: priorityQueueCount, default: defaultQueueCount, } = countTelegramQueueLanes(state.queuedItems);
972
1001
  return [
973
1002
  "connection:",
974
1003
  `- bot: ${formatTelegramBridgeBotStatus(state)}`,
@@ -1024,12 +1053,6 @@ function buildTelegramBridgeDiagnosticStatusLines(state) {
1024
1053
  ...buildTelegramRuntimeEventLines(state.recentRuntimeEvents),
1025
1054
  ];
1026
1055
  }
1027
- function escapeHtml(text) {
1028
- return text
1029
- .replace(/&/g, "&amp;")
1030
- .replace(/</g, "&lt;")
1031
- .replace(/>/g, "&gt;");
1032
- }
1033
1056
  function formatTokens(count) {
1034
1057
  if (count < 1000)
1035
1058
  return count.toString();
@@ -1150,3 +1173,9 @@ function buildStatusHtml(ctx, activeModel, bridgeStatus) {
1150
1173
  }
1151
1174
  return lines.join("\n");
1152
1175
  }
1176
+ function escapeHtml(text) {
1177
+ return text
1178
+ .replace(/&/g, "&amp;")
1179
+ .replace(/</g, "&lt;")
1180
+ .replace(/>/g, "&gt;");
1181
+ }
@@ -5,8 +5,8 @@
5
5
  */
6
6
  import { type TelegramTarget } from "./target.ts";
7
7
  import * as ThreadReconciler from "./thread-reconciler.ts";
8
- import { type TelegramWorkspaceAdmissionLedger } from "./workspace-admission.ts";
9
8
  import { type TelegramOwnTopicProvisionResult, type TelegramTopicTargetStore, type TelegramWorkspaceDisplayBinding, type TelegramWorkspaceThreadBinding } from "./threads.ts";
9
+ import { type TelegramWorkspaceAdmissionLedger } from "./workspace-admission.ts";
10
10
  export interface TelegramTopicLifecycleSyncUpdate<TMessage = unknown> {
11
11
  kind: "created" | "closed" | "reopened";
12
12
  target: TelegramTarget & {
@@ -144,7 +144,6 @@ export declare function recoverStaleTelegramTopicApiError<TSyncState extends Tel
144
144
  export declare function ensureTelegramLeaderThreadBinding(deps: TelegramLeaderThreadSyncDeps): Promise<TelegramOwnTopicProvisionResult | undefined>;
145
145
  export declare const TELEGRAM_SYNC_SLICES: readonly ["bot-identity", "bot-capabilities", "pairing", "allowed-user", "topic-capability", "topic-state", "target-bindings", "reservations", "transport-health"];
146
146
  export type TelegramSyncSlice = (typeof TELEGRAM_SYNC_SLICES)[number];
147
- export type TelegramSyncTrigger = "startup" | "reload" | "topic-lifecycle" | "stale-api-error" | "setup-change" | "pairing-change" | "follower-register" | "follower-prune" | "status-request" | "leader-health-tick" | "ordinary-message" | "ordinary-send";
148
147
  export interface TelegramSyncSliceState {
149
148
  status: "fresh" | "suspect" | "unknown";
150
149
  updatedAtMs?: number;
@@ -174,7 +173,6 @@ export interface TelegramProvisioningActivityRuntime {
174
173
  end(): void;
175
174
  }
176
175
  export declare function createTelegramProvisioningActivityRuntime(): TelegramProvisioningActivityRuntime;
177
- export declare function shouldReconcileTelegramSync(trigger: TelegramSyncTrigger): boolean;
178
176
  export declare function markTelegramSyncSliceSuspect(state: TelegramSyncState, slice: TelegramSyncSlice, input: {
179
177
  reason: string;
180
178
  nowMs: number;