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