@llblab/pi-telegram 0.46.0 → 0.48.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 +4 -2
  2. package/BACKLOG.md +0 -6
  3. package/CHANGELOG.md +14 -0
  4. package/README.md +5 -2
  5. package/dist/api/activity.d.ts +6 -0
  6. package/dist/api/activity.js +6 -0
  7. package/dist/api/commands.d.ts +6 -0
  8. package/dist/api/commands.js +6 -0
  9. package/dist/api/delivery.d.ts +6 -0
  10. package/dist/api/delivery.js +6 -0
  11. package/dist/api/inbound.d.ts +6 -0
  12. package/dist/api/inbound.js +6 -0
  13. package/dist/api/keyboard.d.ts +6 -0
  14. package/dist/api/keyboard.js +6 -0
  15. package/dist/api/outbound.d.ts +6 -0
  16. package/dist/api/outbound.js +6 -0
  17. package/dist/api/sections.d.ts +7 -0
  18. package/dist/api/sections.js +6 -0
  19. package/dist/api/status.d.ts +6 -0
  20. package/dist/api/status.js +6 -0
  21. package/dist/api/updates.d.ts +6 -0
  22. package/dist/api/updates.js +6 -0
  23. package/dist/api/voice.d.ts +6 -0
  24. package/dist/api/voice.js +6 -0
  25. package/dist/index.d.ts +6 -0
  26. package/dist/index.js +6 -0
  27. package/dist/lib/activity-verbosity.d.ts +52 -0
  28. package/dist/lib/activity-verbosity.js +596 -0
  29. package/dist/lib/activity.d.ts +220 -0
  30. package/dist/lib/activity.js +574 -0
  31. package/dist/lib/agent-messages.d.ts +28 -0
  32. package/dist/lib/agent-messages.js +86 -0
  33. package/dist/lib/bindings.d.ts +250 -0
  34. package/dist/lib/bindings.js +936 -0
  35. package/dist/lib/bus-api.d.ts +18 -0
  36. package/dist/lib/bus-api.js +254 -0
  37. package/dist/lib/bus-follower.d.ts +464 -0
  38. package/dist/lib/bus-follower.js +1686 -0
  39. package/dist/lib/bus-leader.d.ts +292 -0
  40. package/dist/lib/bus-leader.js +2242 -0
  41. package/dist/lib/bus-transport.d.ts +64 -0
  42. package/dist/lib/bus-transport.js +140 -0
  43. package/dist/lib/bus.d.ts +518 -0
  44. package/dist/lib/bus.js +2055 -0
  45. package/dist/lib/channel-posts.d.ts +170 -0
  46. package/dist/lib/channel-posts.js +611 -0
  47. package/dist/lib/command-templates.d.ts +70 -0
  48. package/dist/lib/command-templates.js +744 -0
  49. package/dist/lib/commands.d.ts +541 -0
  50. package/dist/lib/commands.js +1155 -0
  51. package/dist/lib/config.d.ts +252 -0
  52. package/dist/lib/config.js +889 -0
  53. package/dist/lib/delivery.d.ts +163 -0
  54. package/dist/lib/delivery.js +542 -0
  55. package/dist/lib/extension.d.ts +7 -0
  56. package/dist/lib/extension.js +1604 -0
  57. package/dist/lib/generative-app-worker.mjs +104 -0
  58. package/dist/lib/generative-apps.d.ts +212 -0
  59. package/dist/lib/generative-apps.js +1006 -0
  60. package/dist/lib/inbound.d.ts +91 -0
  61. package/dist/lib/inbound.js +502 -0
  62. package/dist/lib/journal.d.ts +665 -0
  63. package/dist/lib/journal.js +3701 -0
  64. package/dist/lib/keyboard.d.ts +23 -0
  65. package/dist/lib/keyboard.js +37 -0
  66. package/dist/lib/lifecycle.d.ts +157 -0
  67. package/dist/lib/lifecycle.js +395 -0
  68. package/dist/lib/locks.d.ts +164 -0
  69. package/dist/lib/locks.js +1208 -0
  70. package/dist/lib/logging.d.ts +50 -0
  71. package/dist/lib/logging.js +274 -0
  72. package/dist/lib/media.d.ts +181 -0
  73. package/dist/lib/media.js +602 -0
  74. package/dist/lib/menu-model.d.ts +217 -0
  75. package/dist/lib/menu-model.js +663 -0
  76. package/dist/lib/menu-queue.d.ts +37 -0
  77. package/dist/lib/menu-queue.js +408 -0
  78. package/dist/lib/menu-settings.d.ts +115 -0
  79. package/dist/lib/menu-settings.js +595 -0
  80. package/dist/lib/menu-status.d.ts +32 -0
  81. package/dist/lib/menu-status.js +112 -0
  82. package/dist/lib/menu-thinking.d.ts +29 -0
  83. package/dist/lib/menu-thinking.js +82 -0
  84. package/dist/lib/menu.d.ts +171 -0
  85. package/dist/lib/menu.js +323 -0
  86. package/dist/lib/model.d.ts +127 -0
  87. package/dist/lib/model.js +409 -0
  88. package/dist/lib/outbound-attachments.d.ts +241 -0
  89. package/dist/lib/outbound-attachments.js +639 -0
  90. package/dist/lib/outbound-buttons.d.ts +69 -0
  91. package/dist/lib/outbound-buttons.js +248 -0
  92. package/dist/lib/outbound-markup.d.ts +42 -0
  93. package/dist/lib/outbound-markup.js +678 -0
  94. package/dist/lib/outbound-voice.d.ts +55 -0
  95. package/dist/lib/outbound-voice.js +152 -0
  96. package/dist/lib/outbound.d.ts +185 -0
  97. package/dist/lib/outbound.js +516 -0
  98. package/dist/lib/ownership.d.ts +77 -0
  99. package/dist/lib/ownership.js +174 -0
  100. package/dist/lib/paths.d.ts +40 -0
  101. package/dist/lib/paths.js +94 -0
  102. package/dist/lib/pi.d.ts +72 -0
  103. package/dist/lib/pi.js +121 -0
  104. package/dist/lib/polling.d.ts +351 -0
  105. package/dist/lib/polling.js +1196 -0
  106. package/dist/lib/preview.d.ts +192 -0
  107. package/dist/lib/preview.js +614 -0
  108. package/dist/lib/prompt-templates.d.ts +24 -0
  109. package/dist/lib/prompt-templates.js +118 -0
  110. package/dist/lib/prompts.d.ts +57 -0
  111. package/dist/lib/prompts.js +167 -0
  112. package/dist/lib/queue.d.ts +766 -0
  113. package/dist/lib/queue.js +1965 -0
  114. package/dist/lib/recovery.d.ts +86 -0
  115. package/dist/lib/recovery.js +285 -0
  116. package/dist/lib/rendering.d.ts +20 -0
  117. package/dist/lib/rendering.js +983 -0
  118. package/dist/lib/replies.d.ts +214 -0
  119. package/dist/lib/replies.js +737 -0
  120. package/dist/lib/routing.d.ts +207 -0
  121. package/dist/lib/routing.js +2282 -0
  122. package/dist/lib/runtime.d.ts +172 -0
  123. package/dist/lib/runtime.js +402 -0
  124. package/dist/lib/sections.d.ts +165 -0
  125. package/dist/lib/sections.js +327 -0
  126. package/dist/lib/setup.d.ts +82 -0
  127. package/dist/lib/setup.js +150 -0
  128. package/dist/lib/skills.d.ts +8 -0
  129. package/dist/lib/skills.js +12 -0
  130. package/dist/lib/status.d.ts +442 -0
  131. package/dist/lib/status.js +1008 -0
  132. package/dist/lib/sync.d.ts +179 -0
  133. package/dist/lib/sync.js +782 -0
  134. package/dist/lib/target.d.ts +20 -0
  135. package/dist/lib/target.js +27 -0
  136. package/dist/lib/telegram-api.d.ts +541 -0
  137. package/dist/lib/telegram-api.js +1159 -0
  138. package/dist/lib/text-groups.d.ts +89 -0
  139. package/dist/lib/text-groups.js +317 -0
  140. package/dist/lib/thread-cleanup-manager.d.ts +288 -0
  141. package/dist/lib/thread-cleanup-manager.js +560 -0
  142. package/dist/lib/thread-display.d.ts +46 -0
  143. package/dist/lib/thread-display.js +257 -0
  144. package/dist/lib/thread-naming.d.ts +46 -0
  145. package/dist/lib/thread-naming.js +78 -0
  146. package/dist/lib/thread-reconciler.d.ts +239 -0
  147. package/dist/lib/thread-reconciler.js +644 -0
  148. package/dist/lib/threads.d.ts +621 -0
  149. package/dist/lib/threads.js +3679 -0
  150. package/dist/lib/time-injection.d.ts +15 -0
  151. package/dist/lib/time-injection.js +56 -0
  152. package/dist/lib/turns.d.ts +109 -0
  153. package/dist/lib/turns.js +500 -0
  154. package/dist/lib/updates.d.ts +1290 -0
  155. package/dist/lib/updates.js +3634 -0
  156. package/dist/lib/voice.d.ts +117 -0
  157. package/dist/lib/voice.js +174 -0
  158. package/dist/lib/workspace-admission.d.ts +264 -0
  159. package/dist/lib/workspace-admission.js +1136 -0
  160. package/dist/lib/workspace-retirement.d.ts +219 -0
  161. package/dist/lib/workspace-retirement.js +587 -0
  162. package/dist/lib/workspace-slots.d.ts +30 -0
  163. package/dist/lib/workspace-slots.js +54 -0
  164. package/dist/package.json +126 -0
  165. package/dist/pi-telegram/index.js +1 -0
  166. package/dist/skills/generated-control-surface/SKILL.md +107 -0
  167. package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
  168. package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
  169. package/dist/skills/generative-apps/SKILL.md +115 -0
  170. package/dist/skills/show-me/SKILL.md +166 -0
  171. package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
  172. package/dist/skills/telegram-bridge/SKILL.md +129 -0
  173. package/dist/skills/telegram-bridge/references/configuration.md +15 -0
  174. package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
  175. package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
  176. package/docs/README.md +2 -0
  177. package/docs/architecture.md +1 -1
  178. package/docs/compact-matrix-literal.md +10 -3
  179. package/docs/generative-apps.md +15 -13
  180. package/docs/multi-instance-bus.md +6 -4
  181. package/docs/outbound.md +1 -1
  182. package/docs/public-api.md +3 -3
  183. package/lib/bindings.ts +89 -6
  184. package/lib/bus-follower.ts +7 -3
  185. package/lib/bus-leader.ts +44 -19
  186. package/lib/bus.ts +4 -1
  187. package/lib/commands.ts +2 -2
  188. package/lib/config.ts +14 -7
  189. package/lib/delivery.ts +38 -6
  190. package/lib/extension.ts +7 -0
  191. package/lib/generative-apps.ts +379 -13
  192. package/lib/menu-settings.ts +20 -6
  193. package/lib/outbound-markup.ts +16 -7
  194. package/lib/status.ts +15 -12
  195. package/lib/telegram-api.ts +12 -1
  196. package/lib/thread-display.ts +96 -18
  197. package/package.json +56 -13
  198. package/scripts/build-dist.mjs +44 -0
  199. package/scripts/measure-bus.mjs +8 -1
  200. package/scripts/measure-workspace.mjs +8 -1
  201. package/skills/generative-apps/SKILL.md +1 -1
  202. package/skills/show-me/SKILL.md +1 -1
  203. package/skills/show-me/references/telegram-surfaces.md +1 -1
  204. package/skills/telegram-bridge/SKILL.md +1 -1
@@ -0,0 +1,1008 @@
1
+ /**
2
+ * Telegram status rendering helpers
3
+ * Zones: telegram ui, pi agent diagnostics, tui
4
+ * Builds usage, cost, and context summaries for the interactive Telegram status view
5
+ */
6
+ const TELEGRAM_STATUS_DEFAULT_PROFILE_NAME = "default";
7
+ const TELEGRAM_STATUS_LINE_PROVIDER_REGISTRY_KEY = "__piTelegramStatusLineProviders__";
8
+ const MAX_RECENT_TELEGRAM_RUNTIME_EVENTS = 10;
9
+ const MAX_TELEGRAM_RUNTIME_EVENT_MESSAGE_LENGTH = 1000;
10
+ const MAX_TELEGRAM_RUNTIME_EVENT_DETAIL_LENGTH = 1000;
11
+ /**
12
+ * Project a config store into the status view without moving token-reference
13
+ * resolution into this structural leaf domain.
14
+ */
15
+ export function createTelegramBridgeStatusConfigGetter(source) {
16
+ return () => {
17
+ const config = source.get();
18
+ return {
19
+ ...config,
20
+ ...(source.hasBotToken ? { botHasToken: source.hasBotToken() } : {}),
21
+ ...(source.getBotTokenDiagnostic
22
+ ? { botTokenDiagnostic: source.getBotTokenDiagnostic() }
23
+ : {}),
24
+ };
25
+ };
26
+ }
27
+ function truncateTelegramRuntimeEventText(text, maxLength) {
28
+ if (text.length <= maxLength)
29
+ return text;
30
+ return `${text.slice(0, maxLength).trimEnd()}… [truncated ${text.length - maxLength} chars]`;
31
+ }
32
+ export function redactTelegramRuntimeMessage(message, botToken) {
33
+ const redacted = botToken
34
+ ? message.split(botToken).join("<redacted-token>")
35
+ : message;
36
+ return truncateTelegramRuntimeEventText(redacted, MAX_TELEGRAM_RUNTIME_EVENT_MESSAGE_LENGTH);
37
+ }
38
+ function redactTelegramRuntimeDetail(message, botToken) {
39
+ const redacted = botToken
40
+ ? message.split(botToken).join("<redacted-token>")
41
+ : message;
42
+ return truncateTelegramRuntimeEventText(redacted, MAX_TELEGRAM_RUNTIME_EVENT_DETAIL_LENGTH);
43
+ }
44
+ function normalizeTelegramRuntimeEventDetails(details, botToken) {
45
+ if (!details)
46
+ return undefined;
47
+ const normalized = {};
48
+ for (const [key, value] of Object.entries(details)) {
49
+ if (value === undefined)
50
+ continue;
51
+ if (typeof value === "string") {
52
+ normalized[key] = redactTelegramRuntimeDetail(value, botToken);
53
+ continue;
54
+ }
55
+ if (typeof value === "number" || typeof value === "boolean") {
56
+ normalized[key] = value;
57
+ continue;
58
+ }
59
+ if (value === null) {
60
+ normalized[key] = null;
61
+ continue;
62
+ }
63
+ normalized[key] = redactTelegramRuntimeDetail(String(value), botToken);
64
+ }
65
+ return Object.keys(normalized).length > 0 ? normalized : undefined;
66
+ }
67
+ function getTelegramRuntimeEventMessage(input) {
68
+ if (input.message !== undefined)
69
+ return input.message;
70
+ if (input.error instanceof Error)
71
+ return input.error.message;
72
+ return String(input.error);
73
+ }
74
+ export function recordStructuredTelegramRuntimeEvent(events, input, options) {
75
+ const details = normalizeTelegramRuntimeEventDetails(input.details, options.botToken);
76
+ events.push({
77
+ at: options.now ?? Date.now(),
78
+ category: input.category,
79
+ message: redactTelegramRuntimeMessage(getTelegramRuntimeEventMessage(input), options.botToken),
80
+ ...(details ? { details } : {}),
81
+ });
82
+ while (events.length > options.maxEvents) {
83
+ events.shift();
84
+ }
85
+ }
86
+ function getOrCreateTelegramStatusLineProviderRegistry() {
87
+ const existing = globalThis[TELEGRAM_STATUS_LINE_PROVIDER_REGISTRY_KEY];
88
+ if (existing instanceof Map)
89
+ return existing;
90
+ const registry = new Map();
91
+ globalThis[TELEGRAM_STATUS_LINE_PROVIDER_REGISTRY_KEY] = registry;
92
+ return registry;
93
+ }
94
+ /**
95
+ * Register a compact extension-provided line for the Telegram status menu.
96
+ *
97
+ * Providers are synchronous and should return undefined when their line is not
98
+ * relevant for the active model. Errors are isolated so optional extension
99
+ * status cannot break the core Telegram menu.
100
+ */
101
+ export function registerTelegramStatusLineProvider(provider, options) {
102
+ const registry = getOrCreateTelegramStatusLineProviderRegistry();
103
+ registry.set(options.id, provider);
104
+ return () => {
105
+ if (registry.get(options.id) === provider)
106
+ registry.delete(options.id);
107
+ };
108
+ }
109
+ export function getTelegramStatusLineProviderResults(ctx) {
110
+ const results = [];
111
+ const registry = getOrCreateTelegramStatusLineProviderRegistry();
112
+ for (const provider of registry.values()) {
113
+ try {
114
+ const result = provider(ctx);
115
+ if (!result?.label || !result.value)
116
+ continue;
117
+ results.push(result);
118
+ }
119
+ catch {
120
+ continue;
121
+ }
122
+ }
123
+ return results;
124
+ }
125
+ export function clearTelegramStatusLineProviders() {
126
+ getOrCreateTelegramStatusLineProviderRegistry().clear();
127
+ }
128
+ export function createTelegramRuntimeEventRecorder(options) {
129
+ const events = [];
130
+ return {
131
+ record: (category, error, details) => {
132
+ recordStructuredTelegramRuntimeEvent(events, { category, error, details }, {
133
+ botToken: options.getBotToken(),
134
+ maxEvents: options.maxEvents ?? MAX_RECENT_TELEGRAM_RUNTIME_EVENTS,
135
+ now: options.now?.(),
136
+ });
137
+ },
138
+ getEvents: () => events,
139
+ clear: () => {
140
+ events.length = 0;
141
+ },
142
+ };
143
+ }
144
+ function formatTelegramRuntimeEventCategory(event) {
145
+ const method = event.details?.method;
146
+ return typeof method === "string"
147
+ ? `${event.category}:${method}`
148
+ : event.category;
149
+ }
150
+ function formatTelegramRuntimeEventDetails(event) {
151
+ if (!event.details)
152
+ return "";
153
+ const details = Object.entries(event.details)
154
+ .filter(([key]) => key !== "method")
155
+ .map(([key, value]) => `${key}=${JSON.stringify(value)}`);
156
+ return details.length > 0 ? ` (${details.join(", ")})` : "";
157
+ }
158
+ function formatTelegramRuntimeEventSummary(event) {
159
+ return `${formatTelegramRuntimeEventCategory(event)}: ${event.message}${formatTelegramRuntimeEventDetails(event)}`;
160
+ }
161
+ function formatTelegramRuntimeEvent(event) {
162
+ return `${new Date(event.at).toISOString()} ${formatTelegramRuntimeEventSummary(event)}`;
163
+ }
164
+ function buildTelegramRuntimeEventSummary(events) {
165
+ const counts = new Map();
166
+ for (const event of events) {
167
+ const category = formatTelegramRuntimeEventCategory(event);
168
+ counts.set(category, (counts.get(category) ?? 0) + 1);
169
+ }
170
+ return Array.from(counts.entries())
171
+ .sort(([left], [right]) => left.localeCompare(right))
172
+ .map(([category, count]) => `${category}=${count}`)
173
+ .join(", ");
174
+ }
175
+ export function buildTelegramRuntimeEventLines(events) {
176
+ if (events.length === 0)
177
+ return ["recent runtime events: none"];
178
+ return [
179
+ "recent runtime events:",
180
+ `- summary: ${buildTelegramRuntimeEventSummary(events)}`,
181
+ ...events
182
+ .slice()
183
+ .reverse()
184
+ .map((event) => `- ${formatTelegramRuntimeEvent(event)}`),
185
+ ];
186
+ }
187
+ export function createTelegramStatusHtmlBuilder(deps) {
188
+ return (ctx) => buildStatusHtml({ ...ctx, isCompactionInProgress: deps.isCompactionInProgress }, deps.getActiveModel(ctx), deps.getBridgeStatusLineState?.());
189
+ }
190
+ function resolveStatusBarTheme(ctx) {
191
+ try {
192
+ const theme = ctx.ui?.theme;
193
+ return typeof theme?.fg === "function" ? theme : undefined;
194
+ }
195
+ catch {
196
+ return undefined;
197
+ }
198
+ }
199
+ export function createTelegramStatusRuntime(deps) {
200
+ const statusKey = deps.statusKey ?? "telegram";
201
+ return {
202
+ updateStatus: (ctx, error) => {
203
+ const theme = resolveStatusBarTheme(ctx);
204
+ if (!theme)
205
+ return;
206
+ ctx.ui.setStatus(statusKey, buildTelegramStatusBarText(theme, deps.getStatusBarState(ctx, error)));
207
+ },
208
+ getStatusLines: (options) => buildTelegramBridgeStatusLines(deps.getBridgeStatusLineState(), options),
209
+ getStatusState: deps.getBridgeStatusLineState,
210
+ };
211
+ }
212
+ export function createTelegramBridgeStatusRuntime(deps) {
213
+ return createTelegramStatusRuntime({
214
+ statusKey: deps.statusKey,
215
+ getStatusBarState: (_ctx, error) => {
216
+ const config = deps.getConfig();
217
+ const queuedItems = deps.getQueuedItems();
218
+ const queuedItemCount = deps.getQueuedItemCount?.(queuedItems) ?? queuedItems.length;
219
+ const hasActiveTurn = deps.hasActiveTurn();
220
+ const hasPendingDispatch = deps.hasDispatchPending();
221
+ const hasPendingModelSwitch = deps.hasPendingModelSwitch();
222
+ const activeToolExecutions = deps.getActiveToolExecutions();
223
+ const compactionInProgress = deps.isCompactionInProgress();
224
+ const activeWorkCount = hasActiveTurn ||
225
+ hasPendingModelSwitch ||
226
+ activeToolExecutions > 0 ||
227
+ compactionInProgress ||
228
+ (hasPendingDispatch && queuedItemCount === 0)
229
+ ? 1
230
+ : 0;
231
+ const localBus = deps.getLocalBus?.();
232
+ return {
233
+ hasBotToken: config.botHasToken ?? Boolean(config.botToken),
234
+ pollingActive: deps.isPollingActive(),
235
+ paired: !!config.allowedUserId,
236
+ busRole: deps.getBusRole?.(),
237
+ followerRegistered: localBus?.followerRegistered,
238
+ busLifecyclePhase: deps.getBusLifecyclePhase?.(),
239
+ instanceSlot: deps.getInstanceSlot?.(),
240
+ instanceThreadName: deps.getInstanceThreadName?.(),
241
+ compactionInProgress,
242
+ processing: hasActiveTurn ||
243
+ hasPendingDispatch ||
244
+ hasPendingModelSwitch ||
245
+ activeToolExecutions > 0 ||
246
+ queuedItemCount > 0,
247
+ processingStatus: getTelegramStatusBarProcessingStatus({
248
+ hasActiveTurn,
249
+ hasPendingDispatch,
250
+ hasPendingModelSwitch,
251
+ activeToolExecutions,
252
+ queuedItems: queuedItemCount,
253
+ }),
254
+ queuedStatus: activeWorkCount + queuedItemCount > 0
255
+ ? ` +${activeWorkCount + queuedItemCount}`
256
+ : "",
257
+ pollingStopReason: deps.getPollingState?.().stopReason,
258
+ error,
259
+ };
260
+ },
261
+ getBridgeStatusLineState: () => {
262
+ const config = deps.getConfig();
263
+ const botThreadMode = deps.getBotThreadMode?.();
264
+ const activeProfileName = deps.getActiveProfileName
265
+ ? (deps.getActiveProfileName() ?? TELEGRAM_STATUS_DEFAULT_PROFILE_NAME)
266
+ : undefined;
267
+ return {
268
+ hasBotToken: config.botHasToken ?? Boolean(config.botToken),
269
+ botUsername: config.botUsername,
270
+ botTokenDiagnostic: config.botTokenDiagnostic,
271
+ activeProfileName,
272
+ diagnosticPaths: deps.getDiagnosticPaths?.(activeProfileName),
273
+ allowedUserId: config.allowedUserId,
274
+ botThreadMode: botThreadMode?.threadMode,
275
+ botThreadModeUpdatedAtMs: botThreadMode?.updatedAtMs,
276
+ botThreadModeAction: botThreadMode?.lastReconcileAction,
277
+ busRole: deps.getBusRole?.(),
278
+ busProtocol: deps.getBusProtocol?.(),
279
+ busLifecyclePhase: deps.getBusLifecyclePhase?.(),
280
+ instanceSlot: deps.getInstanceSlot?.(),
281
+ instanceThreadName: deps.getInstanceThreadName?.(),
282
+ lockState: deps.getRuntimeLockState?.(),
283
+ pollingActive: deps.isPollingActive(),
284
+ ...(deps.getPollingState
285
+ ? { polling: deps.getPollingState() }
286
+ : {}),
287
+ ...(deps.getInboundWorkerState
288
+ ? { inboundWorker: deps.getInboundWorkerState() }
289
+ : {}),
290
+ lastUpdateId: deps.getAcceptedThroughUpdateId?.(),
291
+ activeSourceMessageIds: deps.getActiveSourceMessageIds(),
292
+ pendingDispatch: deps.hasDispatchPending(),
293
+ compactionInProgress: deps.isCompactionInProgress(),
294
+ activeToolExecutions: deps.getActiveToolExecutions(),
295
+ pendingModelSwitch: deps.hasPendingModelSwitch(),
296
+ queuedItems: deps.getQueuedItems(),
297
+ busFollowers: deps.getBusFollowers?.(),
298
+ localBus: deps.getLocalBus?.(),
299
+ topicTargets: deps.getTopicTargets?.(),
300
+ threadReservations: deps.getThreadReservations?.(),
301
+ topicSyncObservations: deps.getTopicSyncObservations?.(),
302
+ syncState: deps.getSyncState?.(),
303
+ threadReconciliation: deps.getThreadReconciliationState?.(),
304
+ busNowMs: deps.getNowMs?.(),
305
+ recentRuntimeEvents: deps.getRecentRuntimeEvents(),
306
+ };
307
+ },
308
+ });
309
+ }
310
+ export function createTelegramRuntimeLogScope(input) {
311
+ return {
312
+ instanceId: input.instanceId,
313
+ role: input.state.busRole ?? "classic-or-disconnected",
314
+ slot: input.state.instanceSlot,
315
+ threadName: input.state.instanceThreadName,
316
+ lockState: input.state.lockState,
317
+ };
318
+ }
319
+ export function createTelegramStatusSnapshot(state) {
320
+ return {
321
+ runtime: {
322
+ busRole: state.busRole,
323
+ ...(state.busLifecyclePhase
324
+ ? { busLifecyclePhase: state.busLifecyclePhase }
325
+ : {}),
326
+ botThreadMode: state.botThreadMode,
327
+ botThreadModeUpdatedAtMs: state.botThreadModeUpdatedAtMs,
328
+ botThreadModeAction: state.botThreadModeAction,
329
+ instanceSlot: state.instanceSlot,
330
+ instanceThreadName: state.instanceThreadName,
331
+ pollingActive: state.pollingActive,
332
+ ...(state.polling ? { polling: state.polling } : {}),
333
+ lockState: state.lockState,
334
+ },
335
+ liveRoster: {
336
+ busFollowers: state.busFollowers ?? [],
337
+ ...(state.localBus ? { localBus: state.localBus } : {}),
338
+ topicTargets: state.topicTargets ?? [],
339
+ reservations: state.threadReservations ?? [],
340
+ syncObservations: state.topicSyncObservations ?? [],
341
+ },
342
+ diagnostics: {
343
+ pendingDispatch: state.pendingDispatch,
344
+ compactionInProgress: state.compactionInProgress,
345
+ activeToolExecutions: state.activeToolExecutions,
346
+ pendingModelSwitch: state.pendingModelSwitch,
347
+ syncState: state.syncState,
348
+ threadReconciliation: state.threadReconciliation,
349
+ recentRuntimeEvents: state.recentRuntimeEvents,
350
+ },
351
+ };
352
+ }
353
+ const TELEGRAM_DIAGNOSTICS_SNAPSHOT_COALESCE_MS = 100;
354
+ export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps) {
355
+ const setTimer = deps.setTimer ?? setTimeout;
356
+ let timer;
357
+ let persistPromise;
358
+ let pending = false;
359
+ const recordError = (error) => {
360
+ try {
361
+ deps.recordError(error);
362
+ }
363
+ catch {
364
+ // Snapshot diagnostics cannot create an unhandled scheduler rejection.
365
+ }
366
+ };
367
+ const request = () => {
368
+ if (timer || persistPromise) {
369
+ pending = true;
370
+ return;
371
+ }
372
+ timer = setTimer(() => {
373
+ timer = undefined;
374
+ let tracked;
375
+ tracked = Promise.resolve()
376
+ .then(deps.persistSnapshot)
377
+ .catch(recordError)
378
+ .finally(() => {
379
+ if (persistPromise !== tracked)
380
+ return;
381
+ persistPromise = undefined;
382
+ if (pending) {
383
+ pending = false;
384
+ request();
385
+ }
386
+ });
387
+ persistPromise = tracked;
388
+ }, TELEGRAM_DIAGNOSTICS_SNAPSHOT_COALESCE_MS);
389
+ if (typeof timer !== "number")
390
+ timer?.unref?.();
391
+ };
392
+ return request;
393
+ }
394
+ export function getTelegramStatusBarProcessingStatus(state) {
395
+ if (state.hasPendingModelSwitch)
396
+ return "model";
397
+ if (state.hasActiveTurn || state.activeToolExecutions > 0)
398
+ return "active";
399
+ if (state.hasPendingDispatch)
400
+ return "dispatching";
401
+ if (state.queuedItems > 0)
402
+ return "queued";
403
+ return undefined;
404
+ }
405
+ function getTelegramStatusBarLabel(state) {
406
+ const threadName = state.instanceThreadName?.trim();
407
+ if (!threadName)
408
+ return "telegram";
409
+ const genericLabels = new Set(["telegram", "leader", "follower"]);
410
+ if (genericLabels.has(threadName.toLowerCase()))
411
+ return "telegram";
412
+ return threadName;
413
+ }
414
+ export function buildTelegramStatusBarText(theme, state) {
415
+ const label = theme.fg("accent", getTelegramStatusBarLabel(state));
416
+ const queued = state.queuedStatus
417
+ ? theme.fg("success", state.queuedStatus)
418
+ : "";
419
+ if (!state.hasBotToken)
420
+ return `${label} ${theme.fg("muted", "not configured")}${queued}`;
421
+ if (state.pollingStopReason === "persistent-conflict" && state.busRole !== "follower")
422
+ return `${label} ${theme.fg("error", "error")}`;
423
+ if (!state.paired)
424
+ return `${label} ${theme.fg("warning", "awaiting pairing")}${queued}`;
425
+ if (state.busLifecyclePhase === "electing")
426
+ return `${label} ${theme.fg("warning", "electing")}${queued}`;
427
+ if (!state.pollingActive && state.busRole !== "follower")
428
+ return `${theme.fg("accent", "telegram")} ${theme.fg("dim", "disconnected")}${queued}`;
429
+ if (state.error) {
430
+ return `${label} ${theme.fg("error", "error")}${queued}`;
431
+ }
432
+ if (state.busRole === "follower" && state.followerRegistered === false) {
433
+ return `${label} ${theme.fg("warning", "reconnecting")}${queued}`;
434
+ }
435
+ if (state.busRole === "follower")
436
+ return `${label} ${theme.fg("success", "follower")}${queued}`;
437
+ if (state.busRole === "leader")
438
+ return `${label} ${theme.fg("success", "leader")}${queued}`;
439
+ return `${label} ${theme.fg("success", "connected")}${queued}`;
440
+ }
441
+ function formatTelegramBridgeBotStatus(state) {
442
+ if (state.botUsername)
443
+ return `@${state.botUsername}`;
444
+ if (state.hasBotToken)
445
+ return "unknown";
446
+ return state.botTokenDiagnostic ?? "not configured";
447
+ }
448
+ function formatTelegramStatusTarget(target) {
449
+ if (!target)
450
+ return "";
451
+ return target.threadId === undefined
452
+ ? ` target ${target.chatId}`
453
+ : ` target ${target.chatId}:${target.threadId}`;
454
+ }
455
+ function formatTelegramThreadStatusLabel(input) {
456
+ const threadName = input.threadName?.trim();
457
+ if (threadName)
458
+ return threadName;
459
+ return input.slot ? `[${input.slot}]` : "";
460
+ }
461
+ function formatTelegramBusProtocolIdentity(protocol) {
462
+ if (!protocol)
463
+ return "";
464
+ const capabilities = protocol.capabilities.length > 0
465
+ ? ` capabilities=${protocol.capabilities.join(",")}`
466
+ : " capabilities=none";
467
+ return ` protocol=v${protocol.protocolVersion} build=${protocol.runtimeBuild}${capabilities}`;
468
+ }
469
+ function buildTelegramBusFollowerLines(state) {
470
+ const followers = state.busFollowers ?? [];
471
+ if (followers.length === 0)
472
+ return [];
473
+ const nowMs = state.busNowMs ?? Date.now();
474
+ return [
475
+ "",
476
+ "bus:",
477
+ `- followers: ${followers.length}`,
478
+ ...followers.map((follower) => {
479
+ const ageSeconds = Math.max(0, Math.round((nowMs - follower.lastHeartbeatMs) / 1000));
480
+ const label = formatTelegramThreadStatusLabel(follower);
481
+ const labelSuffix = label ? ` ${label}` : "";
482
+ const statusLabel = follower.status ? ` (${follower.status})` : "";
483
+ const cwd = follower.cwd ? ` ${follower.cwd}` : "";
484
+ const target = formatTelegramStatusTarget(follower.target);
485
+ const protocol = formatTelegramBusProtocolIdentity(follower.protocol);
486
+ return `- ${follower.instanceId}:${labelSuffix} heartbeat ${ageSeconds}s ago${statusLabel}${target}${cwd}${protocol}`;
487
+ }),
488
+ ];
489
+ }
490
+ function buildTelegramLocalBusLines(state, options = {}) {
491
+ const localBus = state.localBus;
492
+ if (!localBus)
493
+ return [];
494
+ const target = formatTelegramStatusTarget(localBus.followerTarget);
495
+ const label = formatTelegramThreadStatusLabel({
496
+ slot: localBus.followerSlot,
497
+ threadName: localBus.followerThreadName,
498
+ });
499
+ const protocol = formatTelegramBusProtocolIdentity(localBus.leaderProtocol);
500
+ const followerLine = `- follower registered: ${localBus.followerRegistered ? "yes" : "no"}${label ? ` ${label}` : ""}${target}${protocol}`;
501
+ const lines = ["", "local bus:", followerLine];
502
+ if (options.verbose) {
503
+ if (localBus.leaderSocketPath) {
504
+ const transport = localBus.leaderTransport
505
+ ? ` [${localBus.leaderTransport}]`
506
+ : "";
507
+ lines.push(`- leader endpoint${transport}: ${localBus.leaderSocketPath}`);
508
+ }
509
+ if (localBus.followerSocketPath) {
510
+ const transport = localBus.followerTransport
511
+ ? ` [${localBus.followerTransport}]`
512
+ : "";
513
+ lines.push(`- follower endpoint${transport}: ${localBus.followerSocketPath}`);
514
+ }
515
+ }
516
+ return lines;
517
+ }
518
+ function buildTelegramSyncSliceLines(state) {
519
+ const syncState = state.syncState;
520
+ if (!syncState || Object.keys(syncState).length === 0)
521
+ return [];
522
+ return [
523
+ "sync:",
524
+ ...Object.entries(syncState).map(([slice, value]) => {
525
+ const status = value?.status ?? "unknown";
526
+ const action = value?.lastReconcileAction
527
+ ? ` reconcile=${value.lastReconcileAction}`
528
+ : "";
529
+ const reason = value?.reason ? ` reason=${value.reason}` : "";
530
+ return `- ${slice}: ${status}${action}${reason}`;
531
+ }),
532
+ ];
533
+ }
534
+ function buildTelegramThreadReconciliationLines(state) {
535
+ const reconciliation = state.threadReconciliation;
536
+ if (!reconciliation)
537
+ return [];
538
+ const epoch = reconciliation.leaderEpoch !== undefined
539
+ ? ` epoch=${reconciliation.leaderEpoch}`
540
+ : "";
541
+ return [
542
+ "reconciliation:",
543
+ `- phase: ${reconciliation.phase} event=${reconciliation.event}${epoch}`,
544
+ `- counts: pending=${reconciliation.pendingProvisionCount}, sync=${reconciliation.syncActionCount}, cleanup=${reconciliation.cleanupActionCount}`,
545
+ ];
546
+ }
547
+ function buildTelegramTopicTargetDiagnosticLines(state) {
548
+ const activeTargets = (state.topicTargets ?? []).filter((record) => !!record.instanceId &&
549
+ (record.status === "active" || record.status === "starting"));
550
+ const reservations = state.threadReservations ?? [];
551
+ const observations = state.topicSyncObservations ?? [];
552
+ if (activeTargets.length === 0 &&
553
+ reservations.length === 0 &&
554
+ observations.length === 0)
555
+ return [];
556
+ const byInstance = new Map();
557
+ for (const record of activeTargets) {
558
+ const key = record.instanceId;
559
+ if (!key)
560
+ continue;
561
+ const records = byInstance.get(key) ?? [];
562
+ records.push(record);
563
+ byInstance.set(key, records);
564
+ }
565
+ const duplicateLines = Array.from(byInstance.entries())
566
+ .filter(([, records]) => records.length > 1)
567
+ .map(([instanceId, records]) => {
568
+ const targets = records
569
+ .map((record) => {
570
+ const label = formatTelegramThreadStatusLabel(record);
571
+ return `${label}${formatTelegramStatusTarget(record.target) || " unknown"}`.trim();
572
+ })
573
+ .join(", ");
574
+ return `- duplicate ${instanceId}: ${records.length} active threads ${targets}`;
575
+ });
576
+ const twinLines = activeTargets.map((record) => {
577
+ const label = formatTelegramThreadStatusLabel(record);
578
+ const target = formatTelegramStatusTarget(record.target) || " unknown";
579
+ const sync = record.syncStatus ? ` sync=${record.syncStatus}` : "";
580
+ const observed = record.lastSyncObservedAtMs
581
+ ? ` observed=${new Date(record.lastSyncObservedAtMs).toISOString()}`
582
+ : "";
583
+ const probe = record.lastSyncProbeAtMs
584
+ ? ` probed=${new Date(record.lastSyncProbeAtMs).toISOString()}`
585
+ : "";
586
+ const error = record.lastSyncError
587
+ ? ` syncError=${record.lastSyncError}`
588
+ : "";
589
+ const action = record.lastReconcileAction
590
+ ? ` reconcile=${record.lastReconcileAction}`
591
+ : "";
592
+ return `- ${label}${target}${sync}${observed}${probe}${error}${action}`.trim();
593
+ });
594
+ const reservationLines = reservations.map((reservation) => {
595
+ const slot = reservation.slot ? `[${reservation.slot}]` : "";
596
+ const target = formatTelegramStatusTarget(reservation.target) || " unknown";
597
+ const reason = reservation.reason ? ` reason=${reservation.reason}` : "";
598
+ const instance = reservation.instanceId
599
+ ? ` instance=${reservation.instanceId}`
600
+ : "";
601
+ const action = reservation.lastReconcileAction
602
+ ? ` reconcile=${reservation.lastReconcileAction}`
603
+ : "";
604
+ return `- reservation ${slot}${target}${reason}${instance}${action}`.trim();
605
+ });
606
+ const observationLines = observations.map((observation) => {
607
+ const slot = observation.slot ? `[${observation.slot}]` : "";
608
+ const target = formatTelegramStatusTarget(observation.target) || " unknown";
609
+ const observed = ` observed=${new Date(observation.observedAtMs).toISOString()}`;
610
+ const instance = observation.instanceId
611
+ ? ` instance=${observation.instanceId}`
612
+ : "";
613
+ const error = observation.lastSyncError
614
+ ? ` syncError=${observation.lastSyncError}`
615
+ : "";
616
+ const action = observation.lastReconcileAction
617
+ ? ` reconcile=${observation.lastReconcileAction}`
618
+ : "";
619
+ return `- sync ${slot}${target} sync=${observation.syncStatus}${observed}${instance}${error}${action}`.trim();
620
+ });
621
+ return [
622
+ "topics:",
623
+ `- active bindings: instances=${byInstance.size}, targets=${activeTargets.length}`,
624
+ ...duplicateLines,
625
+ ...twinLines,
626
+ ...reservationLines,
627
+ ...observationLines,
628
+ ];
629
+ }
630
+ function buildTelegramBridgeCompactThreadLines(state) {
631
+ const activeTargets = (state.topicTargets ?? []).filter((record) => !!record.instanceId &&
632
+ (record.status === "active" || record.status === "starting"));
633
+ const activeLabels = activeTargets
634
+ .map(formatTelegramThreadStatusLabel)
635
+ .filter((label) => label.length > 0);
636
+ const followers = state.busFollowers ?? [];
637
+ const reservations = state.threadReservations ?? [];
638
+ const observations = state.topicSyncObservations ?? [];
639
+ if (activeLabels.length === 0 &&
640
+ followers.length === 0 &&
641
+ reservations.length === 0 &&
642
+ observations.length === 0) {
643
+ return [];
644
+ }
645
+ const lines = ["threads:"];
646
+ if (activeLabels.length > 0)
647
+ lines.push(`- active: ${activeLabels.join(", ")}`);
648
+ if (followers.length > 0)
649
+ lines.push(`- followers: ${followers.length}`);
650
+ if (reservations.length > 0)
651
+ lines.push(`- reserved: ${reservations.length}`);
652
+ const syncIssueCount = observations.filter((observation) => observation.syncStatus !== "open").length;
653
+ if (syncIssueCount > 0)
654
+ lines.push(`- sync issues: ${syncIssueCount}`);
655
+ return lines;
656
+ }
657
+ function formatTelegramPollingLifecycle(state) {
658
+ const lifecycle = state.pollingActive ? "running" : "stopped";
659
+ if (!state.polling)
660
+ return lifecycle;
661
+ if (state.pollingActive)
662
+ return `${lifecycle} (${state.polling.phase})`;
663
+ return state.polling.stopReason
664
+ ? `${lifecycle} (${state.polling.stopReason})`
665
+ : lifecycle;
666
+ }
667
+ function buildTelegramPollingDiagnosticLines(polling) {
668
+ if (!polling)
669
+ return [];
670
+ return [
671
+ `- phase: ${polling.phase}`,
672
+ ...(polling.phaseStartedAtMs !== undefined
673
+ ? [
674
+ `- phase started: ${new Date(polling.phaseStartedAtMs).toISOString()}`,
675
+ ]
676
+ : []),
677
+ ...(polling.currentUpdateId !== undefined
678
+ ? [`- current update id: ${polling.currentUpdateId}`]
679
+ : []),
680
+ ...(polling.lastSuccessfulResponseAtMs !== undefined
681
+ ? [
682
+ `- last successful response: ${new Date(polling.lastSuccessfulResponseAtMs).toISOString()} (updates=${polling.lastSuccessfulResponseUpdateCount ?? "unknown"})`,
683
+ ]
684
+ : []),
685
+ ...(polling.startedAtMs !== undefined
686
+ ? [`- started: ${new Date(polling.startedAtMs).toISOString()}`]
687
+ : []),
688
+ ...(polling.stoppedAtMs !== undefined
689
+ ? [`- stopped: ${new Date(polling.stoppedAtMs).toISOString()}`]
690
+ : []),
691
+ ...(polling.stopReason ? [`- stop reason: ${polling.stopReason}`] : []),
692
+ ];
693
+ }
694
+ function formatTelegramInboundWorkerState(worker) {
695
+ if (!worker)
696
+ return "not started";
697
+ const depth = worker.journalEntryCount;
698
+ return `${worker.phase} (depth=${depth}, queued=${worker.queuedClaimCount}, foreign=${worker.foreignQueuedCount}, deferred=${worker.deferredClaimCount}, retry=${worker.retryWaitCount}, failed=${worker.failedCount})`;
699
+ }
700
+ function buildTelegramInboundWorkerDiagnosticLines(worker) {
701
+ if (!worker)
702
+ return ["- state: not started"];
703
+ return [
704
+ `- state: ${worker.phase}`,
705
+ `- generation: ${worker.generation}`,
706
+ ...(worker.phaseStartedAtMs !== undefined
707
+ ? [`- phase started: ${new Date(worker.phaseStartedAtMs).toISOString()}`]
708
+ : []),
709
+ `- journal: entries=${worker.journalEntryCount}, bytes=${worker.journalSerializedBytes}`,
710
+ `- claims: queued=${worker.queuedClaimCount}, foreign-queued=${worker.foreignQueuedCount}, deferred=${worker.deferredClaimCount}, unsettled=${worker.unsettledExecutionCount}`,
711
+ ...(worker.foreignQueuedOwner
712
+ ? [
713
+ `- queued semantic owner: instance=${worker.foreignQueuedOwner.instanceId}, pid=${worker.foreignQueuedOwner.processId}, birth=${worker.foreignQueuedOwner.processBirthId}, session=${worker.foreignQueuedOwner.sessionGeneration}, acquisition=${worker.foreignQueuedOwner.acquisitionId}, liveness=${worker.foreignQueuedOwnerLiveness ?? "unknown"}`,
714
+ ]
715
+ : []),
716
+ `- failures: retry-wait=${worker.retryWaitCount}, terminal=${worker.failedCount}`,
717
+ ...(worker.currentUpdateId !== undefined
718
+ ? [`- current update id: ${worker.currentUpdateId}`]
719
+ : []),
720
+ ...(worker.nextRetryUpdateId !== undefined
721
+ ? [
722
+ `- next retry: update=${worker.nextRetryUpdateId}, attempt=${worker.nextRetryAttemptCount ?? "unknown"}, class=${worker.nextRetryFailureClass ?? "unknown"}${worker.nextRetryAtMs !== undefined ? ` at ${new Date(worker.nextRetryAtMs).toISOString()}` : ""}`,
723
+ ]
724
+ : []),
725
+ ...(worker.failedUpdateId !== undefined
726
+ ? [
727
+ `- terminal update: id=${worker.failedUpdateId}, failure=${worker.failedFailureId ?? "unknown"}, attempts=${worker.failedAttemptCount ?? "unknown"}, class=${worker.failedClass ?? "unknown"}${worker.terminalFailureAtMs !== undefined ? ` at ${new Date(worker.terminalFailureAtMs).toISOString()}` : ""}`,
728
+ ...(worker.failedSummary
729
+ ? [`- terminal summary: ${worker.failedSummary}`]
730
+ : []),
731
+ ]
732
+ : []),
733
+ ...(worker.oldestAdmittedAtMs !== undefined
734
+ ? [
735
+ `- oldest admitted: ${new Date(worker.oldestAdmittedAtMs).toISOString()}`,
736
+ ]
737
+ : []),
738
+ ...(worker.blockedReason
739
+ ? [`- blocked reason: ${worker.blockedReason}`]
740
+ : []),
741
+ ...(worker.blockedInputCustody
742
+ ? [`- blocked input custody: update=${worker.blockedInputCustody.updateId}, kind=${worker.blockedInputCustody.kind}`]
743
+ : []),
744
+ ...(worker.lastCompletedUpdateId !== undefined
745
+ ? [
746
+ `- last completed: ${worker.lastCompletedUpdateId}${worker.lastCompletedAtMs !== undefined ? ` at ${new Date(worker.lastCompletedAtMs).toISOString()}` : ""}`,
747
+ ]
748
+ : []),
749
+ ...(worker.lastFailurePhase
750
+ ? [
751
+ `- last failure: ${worker.lastFailurePhase}${worker.lastFailureAtMs !== undefined ? ` at ${new Date(worker.lastFailureAtMs).toISOString()}` : ""}`,
752
+ ]
753
+ : []),
754
+ ];
755
+ }
756
+ function buildTelegramBridgeCompactStatusLines(state) {
757
+ const controlQueueCount = state.queuedItems.filter((item) => item.queueLane === "control").length;
758
+ const priorityQueueCount = state.queuedItems.filter((item) => item.queueLane === "priority").length;
759
+ const defaultQueueCount = state.queuedItems.filter((item) => item.queueLane === "default").length;
760
+ const queueLine = `- queued turns: ${state.queuedItems.length}${state.queuedItems.length > 0
761
+ ? ` (control=${controlQueueCount}, priority=${priorityQueueCount}, default=${defaultQueueCount})`
762
+ : ""}`;
763
+ const executionState = state.pendingDispatch
764
+ ? "pending dispatch"
765
+ : state.activeSourceMessageIds?.length
766
+ ? "active"
767
+ : "idle";
768
+ const diagnosticsProfileName = state.activeProfileName === TELEGRAM_STATUS_DEFAULT_PROFILE_NAME
769
+ ? undefined
770
+ : state.activeProfileName;
771
+ const profileSuffix = diagnosticsProfileName
772
+ ? `.${diagnosticsProfileName.replace(/[^a-zA-Z0-9._-]+/g, "_")}`
773
+ : "";
774
+ const diagnosticsPaths = state.diagnosticPaths ?? {
775
+ state: `~/.pi/agent/tmp/telegram/state${profileSuffix}.json`,
776
+ logs: `~/.pi/agent/tmp/telegram/logs${profileSuffix}.jsonl`,
777
+ };
778
+ return [
779
+ "connection:",
780
+ `- bot: ${formatTelegramBridgeBotStatus(state)}`,
781
+ ...(state.activeProfileName
782
+ ? [`- profile: ${state.activeProfileName}`]
783
+ : []),
784
+ `- user: ${state.allowedUserId ?? "not paired"}`,
785
+ ...(state.botThreadMode ? [`- thread mode: ${state.botThreadMode}`] : []),
786
+ ...(state.busRole ? [`- role: ${state.busRole}`] : []),
787
+ ...(state.busLifecyclePhase
788
+ ? [`- lifecycle: ${state.busLifecyclePhase}`]
789
+ : []),
790
+ ...(state.instanceThreadName || state.instanceSlot
791
+ ? [`- instance: ${state.instanceThreadName ?? state.instanceSlot}`]
792
+ : []),
793
+ ...(state.lockState ? [`- owner: ${state.lockState}`] : []),
794
+ "",
795
+ "health:",
796
+ `- polling: ${formatTelegramPollingLifecycle(state)}`,
797
+ ...(state.inboundWorker
798
+ ? [
799
+ `- inbound worker: ${formatTelegramInboundWorkerState(state.inboundWorker)}`,
800
+ ]
801
+ : []),
802
+ `- state: ${executionState}`,
803
+ queueLine,
804
+ ...(state.activeToolExecutions > 0
805
+ ? [`- active tools: ${state.activeToolExecutions}`]
806
+ : []),
807
+ ...(state.pendingModelSwitch ? ["- pending model switch: yes"] : []),
808
+ ...buildTelegramBridgeCompactThreadLines(state),
809
+ ...buildTelegramBusFollowerLines(state),
810
+ ...buildTelegramLocalBusLines(state),
811
+ ...buildTelegramThreadReconciliationLines(state),
812
+ "",
813
+ "diagnostics:",
814
+ `- state: ${diagnosticsPaths.state}`,
815
+ `- logs: ${diagnosticsPaths.logs}`,
816
+ "- full dump: /telegram-status --debug",
817
+ ];
818
+ }
819
+ export function buildTelegramBridgeStatusLines(state, options = {}) {
820
+ if (options.verbose)
821
+ return buildTelegramBridgeDiagnosticStatusLines(state);
822
+ return buildTelegramBridgeCompactStatusLines(state);
823
+ }
824
+ export function buildTelegramBridgeDiagnosticStatusLines(state) {
825
+ const controlQueueCount = state.queuedItems.filter((item) => item.queueLane === "control").length;
826
+ const priorityQueueCount = state.queuedItems.filter((item) => item.queueLane === "priority").length;
827
+ const defaultQueueCount = state.queuedItems.filter((item) => item.queueLane === "default").length;
828
+ return [
829
+ "connection:",
830
+ `- bot: ${formatTelegramBridgeBotStatus(state)}`,
831
+ ...(state.activeProfileName
832
+ ? [`- profile: ${state.activeProfileName}`]
833
+ : []),
834
+ `- allowed user: ${state.allowedUserId ?? "not paired"}`,
835
+ ...(state.botThreadMode
836
+ ? [
837
+ `- thread mode: ${state.botThreadMode}${state.botThreadModeAction ? ` reconcile=${state.botThreadModeAction}` : ""}`,
838
+ ]
839
+ : []),
840
+ ...(state.busRole ? [`- bus role: ${state.busRole}`] : []),
841
+ ...(state.busProtocol
842
+ ? [`- bus${formatTelegramBusProtocolIdentity(state.busProtocol)}`]
843
+ : []),
844
+ ...(state.busLifecyclePhase
845
+ ? [`- bus lifecycle: ${state.busLifecyclePhase}`]
846
+ : []),
847
+ ...(state.instanceThreadName || state.instanceSlot
848
+ ? [`- instance: ${state.instanceThreadName ?? state.instanceSlot}`]
849
+ : []),
850
+ ...(state.lockState ? [`- owner: ${state.lockState}`] : []),
851
+ "",
852
+ "polling:",
853
+ `- state: ${formatTelegramPollingLifecycle(state)}`,
854
+ ...buildTelegramPollingDiagnosticLines(state.polling),
855
+ `- last update id: ${state.lastUpdateId ?? "none"}`,
856
+ "",
857
+ ...(state.inboundWorker
858
+ ? [
859
+ "inbound worker:",
860
+ ...buildTelegramInboundWorkerDiagnosticLines(state.inboundWorker),
861
+ "",
862
+ ]
863
+ : []),
864
+ "execution:",
865
+ `- active turn: ${state.activeSourceMessageIds?.join(",") || "no"}`,
866
+ `- pending dispatch: ${state.pendingDispatch ? "yes" : "no"}`,
867
+ `- compaction: ${state.compactionInProgress ? "running" : "idle"}`,
868
+ `- active tools: ${state.activeToolExecutions}`,
869
+ `- pending model switch: ${state.pendingModelSwitch ? "yes" : "no"}`,
870
+ "",
871
+ "queue:",
872
+ `- queued turns: ${state.queuedItems.length}`,
873
+ `- lanes: control=${controlQueueCount}, priority=${priorityQueueCount}, default=${defaultQueueCount}`,
874
+ ...buildTelegramBusFollowerLines(state),
875
+ ...buildTelegramLocalBusLines(state, { verbose: true }),
876
+ ...buildTelegramTopicTargetDiagnosticLines(state),
877
+ ...buildTelegramThreadReconciliationLines(state),
878
+ ...buildTelegramSyncSliceLines(state),
879
+ "",
880
+ ...buildTelegramRuntimeEventLines(state.recentRuntimeEvents),
881
+ ];
882
+ }
883
+ function escapeHtml(text) {
884
+ return text
885
+ .replace(/&/g, "&amp;")
886
+ .replace(/</g, "&lt;")
887
+ .replace(/>/g, "&gt;");
888
+ }
889
+ function formatTokens(count) {
890
+ if (count < 1000)
891
+ return count.toString();
892
+ if (count < 10000)
893
+ return `${(count / 1000).toFixed(1)}k`;
894
+ if (count < 1000000)
895
+ return `${Math.round(count / 1000)}k`;
896
+ if (count < 10000000)
897
+ return `${(count / 1000000).toFixed(1)}M`;
898
+ return `${Math.round(count / 1000000)}M`;
899
+ }
900
+ function collectUsageStats(ctx) {
901
+ const stats = {
902
+ totalInput: 0,
903
+ totalOutput: 0,
904
+ totalCacheRead: 0,
905
+ totalCacheWrite: 0,
906
+ totalCost: 0,
907
+ };
908
+ for (const entry of ctx.sessionManager.getEntries()) {
909
+ const usage = entry.message?.usage;
910
+ if (entry.type !== "message" ||
911
+ entry.message?.role !== "assistant" ||
912
+ !usage) {
913
+ continue;
914
+ }
915
+ stats.totalInput += usage.input;
916
+ stats.totalOutput += usage.output;
917
+ stats.totalCacheRead += usage.cacheRead;
918
+ stats.totalCacheWrite += usage.cacheWrite;
919
+ stats.totalCost += usage.cost.total;
920
+ const promptTokens = usage.input + usage.cacheRead + usage.cacheWrite;
921
+ stats.latestCacheHitRate =
922
+ promptTokens > 0 ? (usage.cacheRead / promptTokens) * 100 : undefined;
923
+ }
924
+ return stats;
925
+ }
926
+ function formatStatusRowLabel(label) {
927
+ if (!label)
928
+ return label;
929
+ return `${label[0]?.toUpperCase() ?? ""}${label.slice(1)}`;
930
+ }
931
+ function buildStatusRow(label, value) {
932
+ return `<b>${escapeHtml(formatStatusRowLabel(label))}:</b> <code>${escapeHtml(value)}</code>`;
933
+ }
934
+ function buildUsageSummary(stats) {
935
+ const tokenParts = [];
936
+ if (stats.totalInput)
937
+ tokenParts.push(`↑${formatTokens(stats.totalInput)}`);
938
+ if (stats.totalOutput)
939
+ tokenParts.push(`↓${formatTokens(stats.totalOutput)}`);
940
+ return tokenParts.length > 0 ? tokenParts.join(" ") : undefined;
941
+ }
942
+ function buildCacheSummary(stats) {
943
+ const cacheParts = [];
944
+ if (stats.totalCacheRead)
945
+ cacheParts.push(`R${formatTokens(stats.totalCacheRead)}`);
946
+ if (stats.totalCacheWrite)
947
+ cacheParts.push(`W${formatTokens(stats.totalCacheWrite)}`);
948
+ if ((stats.totalCacheRead > 0 || stats.totalCacheWrite > 0) &&
949
+ stats.latestCacheHitRate !== undefined) {
950
+ cacheParts.push(`CH${stats.latestCacheHitRate.toFixed(1)}%`);
951
+ }
952
+ return cacheParts.length > 0 ? cacheParts.join(" ") : undefined;
953
+ }
954
+ function buildCostSummary(stats, usesSubscription) {
955
+ if (!stats.totalCost && !usesSubscription)
956
+ return undefined;
957
+ return `$${stats.totalCost.toFixed(3)}${usesSubscription ? " (sub)" : ""}`;
958
+ }
959
+ function buildContextSummary(ctx, activeModel) {
960
+ const usage = ctx.getContextUsage();
961
+ if (!usage)
962
+ return "unknown";
963
+ const contextWindow = usage.contextWindow ?? activeModel?.contextWindow ?? 0;
964
+ const percent = usage.percent !== null ? `${usage.percent.toFixed(1)}%` : "?";
965
+ return `${percent}/${formatTokens(contextWindow)}`;
966
+ }
967
+ function buildStatusSummary(ctx) {
968
+ if (ctx.isCompactionInProgress?.())
969
+ return "compacting";
970
+ if (ctx.hasPendingMessages?.())
971
+ return "pending";
972
+ if (ctx.isIdle?.() === false)
973
+ return "active";
974
+ if (ctx.isIdle?.() === true)
975
+ return "idle";
976
+ return "unknown";
977
+ }
978
+ function buildTelegramStatusRoleSuffix(state) {
979
+ if (state?.botThreadMode !== "enabled" || !state.busRole)
980
+ return "";
981
+ return ` @${state.busRole}`;
982
+ }
983
+ export function buildStatusHtml(ctx, activeModel, bridgeStatus) {
984
+ const stats = collectUsageStats(ctx);
985
+ const usesSubscription = activeModel
986
+ ? ctx.modelRegistry.isUsingOAuth(activeModel)
987
+ : false;
988
+ const lines = [
989
+ buildStatusRow("Status", `${buildStatusSummary(ctx)}${buildTelegramStatusRoleSuffix(bridgeStatus)}`),
990
+ ];
991
+ const usageSummary = buildUsageSummary(stats);
992
+ const cacheSummary = buildCacheSummary(stats);
993
+ const costSummary = buildCostSummary(stats, usesSubscription);
994
+ if (usageSummary) {
995
+ lines.push(buildStatusRow("Tokens", usageSummary));
996
+ }
997
+ if (cacheSummary) {
998
+ lines.push(buildStatusRow("Cache", cacheSummary));
999
+ }
1000
+ if (costSummary) {
1001
+ lines.push(buildStatusRow("Cost", costSummary));
1002
+ }
1003
+ lines.push(buildStatusRow("Context", buildContextSummary(ctx, activeModel)));
1004
+ for (const row of getTelegramStatusLineProviderResults({ activeModel })) {
1005
+ lines.push(buildStatusRow(row.label, row.value));
1006
+ }
1007
+ return lines.join("\n");
1008
+ }