@llblab/pi-telegram 0.45.11 → 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.
- package/BACKLOG.md +5 -1
- package/CHANGELOG.md +12 -0
- package/README.md +6 -6
- package/dist/api/activity.d.ts +6 -0
- package/dist/api/activity.js +6 -0
- package/dist/api/commands.d.ts +6 -0
- package/dist/api/commands.js +6 -0
- package/dist/api/delivery.d.ts +6 -0
- package/dist/api/delivery.js +6 -0
- package/dist/api/inbound.d.ts +6 -0
- package/dist/api/inbound.js +6 -0
- package/dist/api/keyboard.d.ts +6 -0
- package/dist/api/keyboard.js +6 -0
- package/dist/api/outbound.d.ts +6 -0
- package/dist/api/outbound.js +6 -0
- package/dist/api/sections.d.ts +7 -0
- package/dist/api/sections.js +6 -0
- package/dist/api/status.d.ts +6 -0
- package/dist/api/status.js +6 -0
- package/dist/api/updates.d.ts +6 -0
- package/dist/api/updates.js +6 -0
- package/dist/api/voice.d.ts +6 -0
- package/dist/api/voice.js +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/lib/activity-verbosity.d.ts +52 -0
- package/dist/lib/activity-verbosity.js +596 -0
- package/dist/lib/activity.d.ts +220 -0
- package/dist/lib/activity.js +574 -0
- package/dist/lib/agent-messages.d.ts +28 -0
- package/dist/lib/agent-messages.js +86 -0
- package/dist/lib/bindings.d.ts +250 -0
- package/dist/lib/bindings.js +936 -0
- package/dist/lib/bus-api.d.ts +18 -0
- package/dist/lib/bus-api.js +254 -0
- package/dist/lib/bus-follower.d.ts +464 -0
- package/dist/lib/bus-follower.js +1686 -0
- package/dist/lib/bus-leader.d.ts +292 -0
- package/dist/lib/bus-leader.js +2242 -0
- package/dist/lib/bus-transport.d.ts +64 -0
- package/dist/lib/bus-transport.js +140 -0
- package/dist/lib/bus.d.ts +518 -0
- package/dist/lib/bus.js +2055 -0
- package/dist/lib/channel-posts.d.ts +170 -0
- package/dist/lib/channel-posts.js +611 -0
- package/dist/lib/command-templates.d.ts +70 -0
- package/dist/lib/command-templates.js +744 -0
- package/dist/lib/commands.d.ts +541 -0
- package/dist/lib/commands.js +1155 -0
- package/dist/lib/config.d.ts +252 -0
- package/dist/lib/config.js +889 -0
- package/dist/lib/delivery.d.ts +163 -0
- package/dist/lib/delivery.js +542 -0
- package/dist/lib/extension.d.ts +7 -0
- package/dist/lib/extension.js +1608 -0
- package/dist/lib/generative-app-worker.mjs +104 -0
- package/dist/lib/generative-apps.d.ts +212 -0
- package/dist/lib/generative-apps.js +1006 -0
- package/dist/lib/inbound.d.ts +91 -0
- package/dist/lib/inbound.js +502 -0
- package/dist/lib/journal.d.ts +665 -0
- package/dist/lib/journal.js +3701 -0
- package/dist/lib/keyboard.d.ts +23 -0
- package/dist/lib/keyboard.js +37 -0
- package/dist/lib/lifecycle.d.ts +157 -0
- package/dist/lib/lifecycle.js +395 -0
- package/dist/lib/locks.d.ts +164 -0
- package/dist/lib/locks.js +1208 -0
- package/dist/lib/logging.d.ts +50 -0
- package/dist/lib/logging.js +274 -0
- package/dist/lib/media.d.ts +181 -0
- package/dist/lib/media.js +602 -0
- package/dist/lib/menu-model.d.ts +217 -0
- package/dist/lib/menu-model.js +663 -0
- package/dist/lib/menu-queue.d.ts +37 -0
- package/dist/lib/menu-queue.js +408 -0
- package/dist/lib/menu-settings.d.ts +116 -0
- package/dist/lib/menu-settings.js +595 -0
- package/dist/lib/menu-status.d.ts +32 -0
- package/dist/lib/menu-status.js +112 -0
- package/dist/lib/menu-thinking.d.ts +29 -0
- package/dist/lib/menu-thinking.js +82 -0
- package/dist/lib/menu.d.ts +171 -0
- package/dist/lib/menu.js +323 -0
- package/dist/lib/model.d.ts +127 -0
- package/dist/lib/model.js +409 -0
- package/dist/lib/outbound-attachments.d.ts +241 -0
- package/dist/lib/outbound-attachments.js +639 -0
- package/dist/lib/outbound-buttons.d.ts +69 -0
- package/dist/lib/outbound-buttons.js +248 -0
- package/dist/lib/outbound-markup.d.ts +42 -0
- package/dist/lib/outbound-markup.js +678 -0
- package/dist/lib/outbound-voice.d.ts +55 -0
- package/dist/lib/outbound-voice.js +152 -0
- package/dist/lib/outbound.d.ts +185 -0
- package/dist/lib/outbound.js +516 -0
- package/dist/lib/ownership.d.ts +77 -0
- package/dist/lib/ownership.js +174 -0
- package/dist/lib/paths.d.ts +40 -0
- package/dist/lib/paths.js +94 -0
- package/dist/lib/pi.d.ts +72 -0
- package/dist/lib/pi.js +121 -0
- package/dist/lib/polling.d.ts +351 -0
- package/dist/lib/polling.js +1196 -0
- package/dist/lib/preview.d.ts +192 -0
- package/dist/lib/preview.js +614 -0
- package/dist/lib/prompt-templates.d.ts +24 -0
- package/dist/lib/prompt-templates.js +118 -0
- package/dist/lib/prompts.d.ts +57 -0
- package/dist/lib/prompts.js +167 -0
- package/dist/lib/queue.d.ts +766 -0
- package/dist/lib/queue.js +1965 -0
- package/dist/lib/recovery.d.ts +86 -0
- package/dist/lib/recovery.js +285 -0
- package/dist/lib/rendering.d.ts +20 -0
- package/dist/lib/rendering.js +983 -0
- package/dist/lib/replies.d.ts +214 -0
- package/dist/lib/replies.js +737 -0
- package/dist/lib/routing.d.ts +207 -0
- package/dist/lib/routing.js +2282 -0
- package/dist/lib/runtime.d.ts +172 -0
- package/dist/lib/runtime.js +402 -0
- package/dist/lib/sections.d.ts +165 -0
- package/dist/lib/sections.js +327 -0
- package/dist/lib/setup.d.ts +82 -0
- package/dist/lib/setup.js +150 -0
- package/dist/lib/skills.d.ts +8 -0
- package/dist/lib/skills.js +12 -0
- package/dist/lib/status.d.ts +442 -0
- package/dist/lib/status.js +1006 -0
- package/dist/lib/sync.d.ts +179 -0
- package/dist/lib/sync.js +782 -0
- package/dist/lib/target.d.ts +20 -0
- package/dist/lib/target.js +27 -0
- package/dist/lib/telegram-api.d.ts +541 -0
- package/dist/lib/telegram-api.js +1159 -0
- package/dist/lib/text-groups.d.ts +89 -0
- package/dist/lib/text-groups.js +317 -0
- package/dist/lib/thread-cleanup-manager.d.ts +288 -0
- package/dist/lib/thread-cleanup-manager.js +560 -0
- package/dist/lib/thread-display.d.ts +46 -0
- package/dist/lib/thread-display.js +257 -0
- package/dist/lib/thread-naming.d.ts +46 -0
- package/dist/lib/thread-naming.js +78 -0
- package/dist/lib/thread-reconciler.d.ts +239 -0
- package/dist/lib/thread-reconciler.js +644 -0
- package/dist/lib/threads.d.ts +621 -0
- package/dist/lib/threads.js +3679 -0
- package/dist/lib/time-injection.d.ts +15 -0
- package/dist/lib/time-injection.js +56 -0
- package/dist/lib/turns.d.ts +109 -0
- package/dist/lib/turns.js +500 -0
- package/dist/lib/updates.d.ts +1290 -0
- package/dist/lib/updates.js +3634 -0
- package/dist/lib/voice.d.ts +117 -0
- package/dist/lib/voice.js +174 -0
- package/dist/lib/workspace-admission.d.ts +264 -0
- package/dist/lib/workspace-admission.js +1136 -0
- package/dist/lib/workspace-retirement.d.ts +219 -0
- package/dist/lib/workspace-retirement.js +587 -0
- package/dist/lib/workspace-slots.d.ts +30 -0
- package/dist/lib/workspace-slots.js +54 -0
- package/dist/package.json +126 -0
- package/dist/pi-telegram/index.js +1 -0
- package/dist/skills/generated-control-surface/SKILL.md +107 -0
- package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
- package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
- package/dist/skills/generative-apps/SKILL.md +115 -0
- package/dist/skills/show-me/SKILL.md +166 -0
- package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
- package/dist/skills/telegram-bridge/SKILL.md +129 -0
- package/dist/skills/telegram-bridge/references/configuration.md +15 -0
- package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
- package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
- package/docs/architecture.md +1 -1
- package/docs/compact-matrix-literal.md +10 -3
- package/docs/generative-apps.md +15 -13
- package/docs/multi-instance-bus.md +17 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +2 -2
- package/lib/bindings.ts +89 -6
- package/lib/bus-follower.ts +11 -3
- package/lib/bus-leader.ts +57 -20
- package/lib/bus.ts +12 -2
- package/lib/commands.ts +2 -2
- package/lib/config.ts +14 -7
- package/lib/delivery.ts +38 -6
- package/lib/extension.ts +26 -1
- package/lib/generative-apps.ts +379 -13
- package/lib/menu-settings.ts +21 -5
- package/lib/outbound-markup.ts +16 -7
- package/lib/pi.ts +4 -0
- package/lib/sync.ts +3 -0
- package/lib/telegram-api.ts +12 -1
- package/lib/thread-cleanup-manager.ts +36 -6
- package/lib/thread-display.ts +96 -18
- package/lib/threads.ts +150 -30
- package/package.json +56 -13
- package/scripts/build-dist.mjs +44 -0
- package/scripts/measure-bus.mjs +8 -1
- package/scripts/measure-workspace.mjs +8 -1
- package/skills/generative-apps/SKILL.md +1 -1
- package/skills/show-me/SKILL.md +1 -1
- 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, "&")
|
|
884
|
+
.replace(/</g, "<")
|
|
885
|
+
.replace(/>/g, ">");
|
|
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
|
+
}
|