@llblab/pi-telegram 0.46.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/AGENTS.md +4 -2
  2. package/BACKLOG.md +0 -6
  3. package/CHANGELOG.md +14 -0
  4. package/README.md +5 -2
  5. package/dist/api/activity.d.ts +6 -0
  6. package/dist/api/activity.js +6 -0
  7. package/dist/api/commands.d.ts +6 -0
  8. package/dist/api/commands.js +6 -0
  9. package/dist/api/delivery.d.ts +6 -0
  10. package/dist/api/delivery.js +6 -0
  11. package/dist/api/inbound.d.ts +6 -0
  12. package/dist/api/inbound.js +6 -0
  13. package/dist/api/keyboard.d.ts +6 -0
  14. package/dist/api/keyboard.js +6 -0
  15. package/dist/api/outbound.d.ts +6 -0
  16. package/dist/api/outbound.js +6 -0
  17. package/dist/api/sections.d.ts +7 -0
  18. package/dist/api/sections.js +6 -0
  19. package/dist/api/status.d.ts +6 -0
  20. package/dist/api/status.js +6 -0
  21. package/dist/api/updates.d.ts +6 -0
  22. package/dist/api/updates.js +6 -0
  23. package/dist/api/voice.d.ts +6 -0
  24. package/dist/api/voice.js +6 -0
  25. package/dist/index.d.ts +6 -0
  26. package/dist/index.js +6 -0
  27. package/dist/lib/activity-verbosity.d.ts +52 -0
  28. package/dist/lib/activity-verbosity.js +596 -0
  29. package/dist/lib/activity.d.ts +220 -0
  30. package/dist/lib/activity.js +574 -0
  31. package/dist/lib/agent-messages.d.ts +28 -0
  32. package/dist/lib/agent-messages.js +86 -0
  33. package/dist/lib/bindings.d.ts +250 -0
  34. package/dist/lib/bindings.js +936 -0
  35. package/dist/lib/bus-api.d.ts +18 -0
  36. package/dist/lib/bus-api.js +254 -0
  37. package/dist/lib/bus-follower.d.ts +464 -0
  38. package/dist/lib/bus-follower.js +1686 -0
  39. package/dist/lib/bus-leader.d.ts +292 -0
  40. package/dist/lib/bus-leader.js +2242 -0
  41. package/dist/lib/bus-transport.d.ts +64 -0
  42. package/dist/lib/bus-transport.js +140 -0
  43. package/dist/lib/bus.d.ts +518 -0
  44. package/dist/lib/bus.js +2055 -0
  45. package/dist/lib/channel-posts.d.ts +170 -0
  46. package/dist/lib/channel-posts.js +611 -0
  47. package/dist/lib/command-templates.d.ts +70 -0
  48. package/dist/lib/command-templates.js +744 -0
  49. package/dist/lib/commands.d.ts +541 -0
  50. package/dist/lib/commands.js +1155 -0
  51. package/dist/lib/config.d.ts +252 -0
  52. package/dist/lib/config.js +889 -0
  53. package/dist/lib/delivery.d.ts +163 -0
  54. package/dist/lib/delivery.js +542 -0
  55. package/dist/lib/extension.d.ts +7 -0
  56. package/dist/lib/extension.js +1604 -0
  57. package/dist/lib/generative-app-worker.mjs +104 -0
  58. package/dist/lib/generative-apps.d.ts +212 -0
  59. package/dist/lib/generative-apps.js +1006 -0
  60. package/dist/lib/inbound.d.ts +91 -0
  61. package/dist/lib/inbound.js +502 -0
  62. package/dist/lib/journal.d.ts +665 -0
  63. package/dist/lib/journal.js +3701 -0
  64. package/dist/lib/keyboard.d.ts +23 -0
  65. package/dist/lib/keyboard.js +37 -0
  66. package/dist/lib/lifecycle.d.ts +157 -0
  67. package/dist/lib/lifecycle.js +395 -0
  68. package/dist/lib/locks.d.ts +164 -0
  69. package/dist/lib/locks.js +1208 -0
  70. package/dist/lib/logging.d.ts +50 -0
  71. package/dist/lib/logging.js +274 -0
  72. package/dist/lib/media.d.ts +181 -0
  73. package/dist/lib/media.js +602 -0
  74. package/dist/lib/menu-model.d.ts +217 -0
  75. package/dist/lib/menu-model.js +663 -0
  76. package/dist/lib/menu-queue.d.ts +37 -0
  77. package/dist/lib/menu-queue.js +408 -0
  78. package/dist/lib/menu-settings.d.ts +115 -0
  79. package/dist/lib/menu-settings.js +595 -0
  80. package/dist/lib/menu-status.d.ts +32 -0
  81. package/dist/lib/menu-status.js +112 -0
  82. package/dist/lib/menu-thinking.d.ts +29 -0
  83. package/dist/lib/menu-thinking.js +82 -0
  84. package/dist/lib/menu.d.ts +171 -0
  85. package/dist/lib/menu.js +323 -0
  86. package/dist/lib/model.d.ts +127 -0
  87. package/dist/lib/model.js +409 -0
  88. package/dist/lib/outbound-attachments.d.ts +241 -0
  89. package/dist/lib/outbound-attachments.js +639 -0
  90. package/dist/lib/outbound-buttons.d.ts +69 -0
  91. package/dist/lib/outbound-buttons.js +248 -0
  92. package/dist/lib/outbound-markup.d.ts +42 -0
  93. package/dist/lib/outbound-markup.js +678 -0
  94. package/dist/lib/outbound-voice.d.ts +55 -0
  95. package/dist/lib/outbound-voice.js +152 -0
  96. package/dist/lib/outbound.d.ts +185 -0
  97. package/dist/lib/outbound.js +516 -0
  98. package/dist/lib/ownership.d.ts +77 -0
  99. package/dist/lib/ownership.js +174 -0
  100. package/dist/lib/paths.d.ts +40 -0
  101. package/dist/lib/paths.js +94 -0
  102. package/dist/lib/pi.d.ts +72 -0
  103. package/dist/lib/pi.js +121 -0
  104. package/dist/lib/polling.d.ts +351 -0
  105. package/dist/lib/polling.js +1196 -0
  106. package/dist/lib/preview.d.ts +192 -0
  107. package/dist/lib/preview.js +614 -0
  108. package/dist/lib/prompt-templates.d.ts +24 -0
  109. package/dist/lib/prompt-templates.js +118 -0
  110. package/dist/lib/prompts.d.ts +57 -0
  111. package/dist/lib/prompts.js +167 -0
  112. package/dist/lib/queue.d.ts +766 -0
  113. package/dist/lib/queue.js +1965 -0
  114. package/dist/lib/recovery.d.ts +86 -0
  115. package/dist/lib/recovery.js +285 -0
  116. package/dist/lib/rendering.d.ts +20 -0
  117. package/dist/lib/rendering.js +983 -0
  118. package/dist/lib/replies.d.ts +214 -0
  119. package/dist/lib/replies.js +737 -0
  120. package/dist/lib/routing.d.ts +207 -0
  121. package/dist/lib/routing.js +2282 -0
  122. package/dist/lib/runtime.d.ts +172 -0
  123. package/dist/lib/runtime.js +402 -0
  124. package/dist/lib/sections.d.ts +165 -0
  125. package/dist/lib/sections.js +327 -0
  126. package/dist/lib/setup.d.ts +82 -0
  127. package/dist/lib/setup.js +150 -0
  128. package/dist/lib/skills.d.ts +8 -0
  129. package/dist/lib/skills.js +12 -0
  130. package/dist/lib/status.d.ts +442 -0
  131. package/dist/lib/status.js +1008 -0
  132. package/dist/lib/sync.d.ts +179 -0
  133. package/dist/lib/sync.js +782 -0
  134. package/dist/lib/target.d.ts +20 -0
  135. package/dist/lib/target.js +27 -0
  136. package/dist/lib/telegram-api.d.ts +541 -0
  137. package/dist/lib/telegram-api.js +1159 -0
  138. package/dist/lib/text-groups.d.ts +89 -0
  139. package/dist/lib/text-groups.js +317 -0
  140. package/dist/lib/thread-cleanup-manager.d.ts +288 -0
  141. package/dist/lib/thread-cleanup-manager.js +560 -0
  142. package/dist/lib/thread-display.d.ts +46 -0
  143. package/dist/lib/thread-display.js +257 -0
  144. package/dist/lib/thread-naming.d.ts +46 -0
  145. package/dist/lib/thread-naming.js +78 -0
  146. package/dist/lib/thread-reconciler.d.ts +239 -0
  147. package/dist/lib/thread-reconciler.js +644 -0
  148. package/dist/lib/threads.d.ts +621 -0
  149. package/dist/lib/threads.js +3679 -0
  150. package/dist/lib/time-injection.d.ts +15 -0
  151. package/dist/lib/time-injection.js +56 -0
  152. package/dist/lib/turns.d.ts +109 -0
  153. package/dist/lib/turns.js +500 -0
  154. package/dist/lib/updates.d.ts +1290 -0
  155. package/dist/lib/updates.js +3634 -0
  156. package/dist/lib/voice.d.ts +117 -0
  157. package/dist/lib/voice.js +174 -0
  158. package/dist/lib/workspace-admission.d.ts +264 -0
  159. package/dist/lib/workspace-admission.js +1136 -0
  160. package/dist/lib/workspace-retirement.d.ts +219 -0
  161. package/dist/lib/workspace-retirement.js +587 -0
  162. package/dist/lib/workspace-slots.d.ts +30 -0
  163. package/dist/lib/workspace-slots.js +54 -0
  164. package/dist/package.json +126 -0
  165. package/dist/pi-telegram/index.js +1 -0
  166. package/dist/skills/generated-control-surface/SKILL.md +107 -0
  167. package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
  168. package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
  169. package/dist/skills/generative-apps/SKILL.md +115 -0
  170. package/dist/skills/show-me/SKILL.md +166 -0
  171. package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
  172. package/dist/skills/telegram-bridge/SKILL.md +129 -0
  173. package/dist/skills/telegram-bridge/references/configuration.md +15 -0
  174. package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
  175. package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
  176. package/docs/README.md +2 -0
  177. package/docs/architecture.md +1 -1
  178. package/docs/compact-matrix-literal.md +10 -3
  179. package/docs/generative-apps.md +15 -13
  180. package/docs/multi-instance-bus.md +6 -4
  181. package/docs/outbound.md +1 -1
  182. package/docs/public-api.md +3 -3
  183. package/lib/bindings.ts +89 -6
  184. package/lib/bus-follower.ts +7 -3
  185. package/lib/bus-leader.ts +44 -19
  186. package/lib/bus.ts +4 -1
  187. package/lib/commands.ts +2 -2
  188. package/lib/config.ts +14 -7
  189. package/lib/delivery.ts +38 -6
  190. package/lib/extension.ts +7 -0
  191. package/lib/generative-apps.ts +379 -13
  192. package/lib/menu-settings.ts +20 -6
  193. package/lib/outbound-markup.ts +16 -7
  194. package/lib/status.ts +15 -12
  195. package/lib/telegram-api.ts +12 -1
  196. package/lib/thread-display.ts +96 -18
  197. package/package.json +56 -13
  198. package/scripts/build-dist.mjs +44 -0
  199. package/scripts/measure-bus.mjs +8 -1
  200. package/scripts/measure-workspace.mjs +8 -1
  201. package/skills/generative-apps/SKILL.md +1 -1
  202. package/skills/show-me/SKILL.md +1 -1
  203. package/skills/show-me/references/telegram-surfaces.md +1 -1
  204. package/skills/telegram-bridge/SKILL.md +1 -1
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Telegram diagnostics logs
3
+ * Zones: telegram diagnostics, filesystem, session observability
4
+ * Owns bounded JSONL runtime evidence files, previous-log preservation, and profile-aware log paths without becoming routing state
5
+ */
6
+ import * as Status from "./status.ts";
7
+ export type TelegramLogPathInput = string | (() => string);
8
+ export interface TelegramRuntimeJsonlEvent {
9
+ at: number;
10
+ category: string;
11
+ message: string;
12
+ details?: Record<string, unknown>;
13
+ }
14
+ export interface TelegramRuntimeJsonlLogOptions {
15
+ path?: TelegramLogPathInput;
16
+ previousPath?: TelegramLogPathInput;
17
+ maxBytes?: number;
18
+ getNowMs?: () => number;
19
+ canReset?: () => boolean;
20
+ commitReset?: (commit: () => void) => boolean;
21
+ }
22
+ export interface TelegramRuntimeJsonlLog {
23
+ getPath: () => string;
24
+ reset: (reason: string, scope?: Record<string, unknown>) => void;
25
+ resetIfScopeChanged: (scopeKey: string, reason: string, scope?: Record<string, unknown>) => void;
26
+ record: (event: TelegramRuntimeJsonlEvent) => void;
27
+ }
28
+ export declare function getTelegramRuntimeLogPath(agentDir?: string, profileName?: string): string;
29
+ export declare function getTelegramPreviousRuntimeLogPath(agentDir?: string, profileName?: string): string;
30
+ export declare function createTelegramRuntimeJsonlLog(options?: TelegramRuntimeJsonlLogOptions): TelegramRuntimeJsonlLog;
31
+ export interface TelegramRuntimeDiagnosticsRuntime<TContext> {
32
+ events: Status.TelegramRuntimeEventRecorder;
33
+ recordRuntimeEvent(category: string, error: unknown, details?: Record<string, unknown>): void;
34
+ bindStorage(ports: {
35
+ getBotToken(): string | undefined;
36
+ getProfileName(): string | undefined;
37
+ canReset(): boolean;
38
+ commitReset(commit: () => void): boolean;
39
+ }): void;
40
+ bindStatus(ports: {
41
+ instanceId: string;
42
+ updateStatus(ctx: TContext, error?: string): void;
43
+ getStatusState(): Status.TelegramBridgeStatusLineState;
44
+ persistSnapshot(snapshot: ReturnType<typeof Status.createTelegramStatusSnapshot>): Promise<void>;
45
+ }): void;
46
+ updateStatus(ctx: TContext, error?: string): void;
47
+ getStatusLines(options?: Status.TelegramBridgeStatusLineOptions): string[];
48
+ scheduleSnapshotPersist(): void;
49
+ }
50
+ export declare function createTelegramRuntimeDiagnosticsRuntime<TContext>(): TelegramRuntimeDiagnosticsRuntime<TContext>;
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Telegram diagnostics logs
3
+ * Zones: telegram diagnostics, filesystem, session observability
4
+ * Owns bounded JSONL runtime evidence files, previous-log preservation, and profile-aware log paths without becoming routing state
5
+ */
6
+ import { copyFileSync, existsSync, mkdirSync, statSync, writeFileSync, appendFileSync, } from "node:fs";
7
+ import { dirname } from "node:path";
8
+ import { resolveAgentDir, resolveTelegramProfileTempFilePath, } from "./paths.js";
9
+ import { withTelegramFileTransaction } from "./locks.js";
10
+ import * as Status from "./status.js";
11
+ const DEFAULT_MAX_LOG_BYTES = 5 * 1024 * 1024;
12
+ export function getTelegramRuntimeLogPath(agentDir = resolveAgentDir(), profileName) {
13
+ return resolveTelegramProfileTempFilePath("logs", "jsonl", agentDir, profileName);
14
+ }
15
+ export function getTelegramPreviousRuntimeLogPath(agentDir = resolveAgentDir(), profileName) {
16
+ return resolveTelegramProfileTempFilePath("logs", "_prev.jsonl", agentDir, profileName);
17
+ }
18
+ function safeJsonLine(value) {
19
+ return JSON.stringify(value, (_key, item) => {
20
+ if (item instanceof Error)
21
+ return item.message;
22
+ if (typeof item === "bigint")
23
+ return item.toString();
24
+ if (typeof item === "function" || typeof item === "symbol")
25
+ return undefined;
26
+ return item;
27
+ });
28
+ }
29
+ export function createTelegramRuntimeJsonlLog(options = {}) {
30
+ const resolvePath = () => typeof options.path === "function"
31
+ ? options.path()
32
+ : (options.path ?? getTelegramRuntimeLogPath());
33
+ const resolvePreviousPath = () => {
34
+ if (typeof options.previousPath === "function")
35
+ return options.previousPath();
36
+ if (options.previousPath)
37
+ return options.previousPath;
38
+ return resolvePath().replace(/\.jsonl$/u, "._prev.jsonl");
39
+ };
40
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_LOG_BYTES;
41
+ const getNowMs = options.getNowMs ?? Date.now;
42
+ const scopeKeys = new Map();
43
+ let pending = Promise.resolve();
44
+ let appendScheduled = false;
45
+ let queuedAppends = [];
46
+ const ensureParent = (path) => {
47
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
48
+ };
49
+ const preserveCurrentLog = (path, previousPath) => {
50
+ if (!existsSync(path))
51
+ return;
52
+ mkdirSync(dirname(previousPath), { recursive: true, mode: 0o700 });
53
+ copyFileSync(path, previousPath);
54
+ };
55
+ const writeResetLocked = (path, previousPath, reason, scope) => {
56
+ ensureParent(path);
57
+ preserveCurrentLog(path, previousPath);
58
+ writeFileSync(path, safeJsonLine({
59
+ at: getNowMs(),
60
+ kind: "reset",
61
+ reason,
62
+ scope,
63
+ previousPath,
64
+ }) + "\n", { mode: 0o600 });
65
+ };
66
+ const writeReset = (reason, scope) => {
67
+ if (options.canReset && !options.canReset())
68
+ return false;
69
+ const path = resolvePath();
70
+ let didReset = false;
71
+ withTelegramFileTransaction(`${path}.transaction`, () => {
72
+ const commit = () => {
73
+ writeResetLocked(path, resolvePreviousPath(), reason, scope);
74
+ didReset = true;
75
+ };
76
+ if (options.commitReset) {
77
+ options.commitReset(commit);
78
+ }
79
+ else {
80
+ commit();
81
+ }
82
+ });
83
+ return didReset;
84
+ };
85
+ const appendLine = (line) => {
86
+ queuedAppends.push({
87
+ path: resolvePath(),
88
+ previousPath: resolvePreviousPath(),
89
+ line,
90
+ });
91
+ if (appendScheduled)
92
+ return;
93
+ appendScheduled = true;
94
+ pending = pending
95
+ .then(() => {
96
+ appendScheduled = false;
97
+ const batch = queuedAppends;
98
+ queuedAppends = [];
99
+ const groups = new Map();
100
+ for (const entry of batch) {
101
+ const key = `${entry.path}\u0000${entry.previousPath}`;
102
+ const group = groups.get(key);
103
+ if (group)
104
+ group.lines.push(entry.line);
105
+ else
106
+ groups.set(key, { ...entry, lines: [entry.line] });
107
+ }
108
+ for (const group of groups.values()) {
109
+ try {
110
+ ensureParent(group.path);
111
+ withTelegramFileTransaction(`${group.path}.transaction`, () => {
112
+ let currentSize = existsSync(group.path)
113
+ ? statSync(group.path).size
114
+ : 0;
115
+ let chunk = "";
116
+ let chunkBytes = 0;
117
+ const flushChunk = () => {
118
+ if (!chunk)
119
+ return;
120
+ appendFileSync(group.path, chunk, { mode: 0o600 });
121
+ currentSize += chunkBytes;
122
+ chunk = "";
123
+ chunkBytes = 0;
124
+ };
125
+ const rotate = () => {
126
+ if (options.canReset && !options.canReset())
127
+ return false;
128
+ let rotated = false;
129
+ const commit = () => {
130
+ writeResetLocked(group.path, group.previousPath, "max-bytes", { maxBytes });
131
+ rotated = true;
132
+ };
133
+ if (options.commitReset)
134
+ options.commitReset(commit);
135
+ else
136
+ commit();
137
+ if (rotated)
138
+ currentSize = statSync(group.path).size;
139
+ return rotated;
140
+ };
141
+ for (const line of group.lines) {
142
+ const lineBytes = Buffer.byteLength(line);
143
+ if (currentSize + chunkBytes > 0 &&
144
+ currentSize + chunkBytes + lineBytes > maxBytes) {
145
+ flushChunk();
146
+ rotate();
147
+ }
148
+ chunk += line;
149
+ chunkBytes += lineBytes;
150
+ }
151
+ flushChunk();
152
+ });
153
+ }
154
+ catch {
155
+ // Diagnostics failures for one profile must not drop other groups.
156
+ }
157
+ }
158
+ })
159
+ .catch(() => undefined);
160
+ };
161
+ return {
162
+ getPath: resolvePath,
163
+ reset(reason, scope) {
164
+ const path = resolvePath();
165
+ try {
166
+ if (writeReset(reason, scope)) {
167
+ scopeKeys.set(path, scope ? safeJsonLine(scope) : undefined);
168
+ }
169
+ }
170
+ catch {
171
+ // Diagnostics must never break Telegram runtime behavior.
172
+ }
173
+ },
174
+ resetIfScopeChanged(nextScopeKey, reason, scope) {
175
+ const path = resolvePath();
176
+ if (scopeKeys.get(path) === nextScopeKey)
177
+ return;
178
+ try {
179
+ if (writeReset(reason, scope))
180
+ scopeKeys.set(path, nextScopeKey);
181
+ }
182
+ catch {
183
+ // Diagnostics must never break Telegram runtime behavior.
184
+ }
185
+ },
186
+ record(event) {
187
+ try {
188
+ appendLine(safeJsonLine({ kind: "event", ...event }) + "\n");
189
+ }
190
+ catch {
191
+ // Diagnostics must never break Telegram runtime behavior.
192
+ }
193
+ },
194
+ };
195
+ }
196
+ export function createTelegramRuntimeDiagnosticsRuntime() {
197
+ let getBotToken = () => undefined;
198
+ let getProfileName = () => undefined;
199
+ let canReset = () => false;
200
+ let commitReset = (_commit) => false;
201
+ let statusPorts;
202
+ let requestSnapshotPersist = () => { };
203
+ const events = Status.createTelegramRuntimeEventRecorder({
204
+ getBotToken: () => getBotToken(),
205
+ });
206
+ const jsonl = createTelegramRuntimeJsonlLog({
207
+ path: () => getTelegramRuntimeLogPath(undefined, getProfileName()),
208
+ previousPath: () => getTelegramPreviousRuntimeLogPath(undefined, getProfileName()),
209
+ canReset: () => canReset(),
210
+ commitReset: (commit) => commitReset(commit),
211
+ });
212
+ const recordRuntimeEvent = function (category, error, details) {
213
+ events.record(category, error, details);
214
+ const latestEvent = events.getEvents().at(-1);
215
+ if (latestEvent)
216
+ jsonl.record(latestEvent);
217
+ requestSnapshotPersist();
218
+ };
219
+ const persistCurrentSnapshot = async () => {
220
+ if (!statusPorts)
221
+ return;
222
+ await statusPorts.persistSnapshot(Status.createTelegramStatusSnapshot(statusPorts.getStatusState()));
223
+ };
224
+ const updateRuntimeLogScope = function (reason) {
225
+ if (!statusPorts)
226
+ return;
227
+ const scope = Status.createTelegramRuntimeLogScope({
228
+ state: statusPorts.getStatusState(),
229
+ instanceId: statusPorts.instanceId,
230
+ });
231
+ jsonl.resetIfScopeChanged(JSON.stringify(scope), reason, scope);
232
+ };
233
+ return {
234
+ events,
235
+ recordRuntimeEvent,
236
+ bindStorage(ports) {
237
+ getBotToken = ports.getBotToken;
238
+ getProfileName = ports.getProfileName;
239
+ canReset = ports.canReset;
240
+ commitReset = ports.commitReset;
241
+ },
242
+ bindStatus(ports) {
243
+ statusPorts = ports;
244
+ requestSnapshotPersist =
245
+ Status.createTelegramRuntimeDiagnosticsSnapshotScheduler({
246
+ persistSnapshot: persistCurrentSnapshot,
247
+ recordError(error) {
248
+ events.record("telegram", error, {
249
+ phase: "runtime-diagnostics-snapshot-persist",
250
+ });
251
+ },
252
+ });
253
+ },
254
+ updateStatus(ctx, error) {
255
+ if (!statusPorts)
256
+ return;
257
+ statusPorts.updateStatus(ctx, error);
258
+ updateRuntimeLogScope("status-scope-change");
259
+ },
260
+ getStatusLines(options) {
261
+ if (!statusPorts)
262
+ return [];
263
+ void persistCurrentSnapshot().catch((error) => {
264
+ recordRuntimeEvent("telegram", error, {
265
+ phase: "status-snapshot-persist",
266
+ });
267
+ });
268
+ return Status.buildTelegramBridgeStatusLines(statusPorts.getStatusState(), options);
269
+ },
270
+ scheduleSnapshotPersist() {
271
+ requestSnapshotPersist();
272
+ },
273
+ };
274
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Telegram media and text extraction helpers
3
+ * Zones: telegram inbound, media groups, filesystem paths
4
+ * Normalizes inbound Telegram messages into reusable file, text, id, history, and media-group metadata
5
+ */
6
+ export interface TelegramPhotoSize {
7
+ file_id: string;
8
+ file_size?: number;
9
+ }
10
+ export interface TelegramDocument {
11
+ file_id: string;
12
+ file_name?: string;
13
+ mime_type?: string;
14
+ }
15
+ export type TelegramVideo = TelegramDocument;
16
+ export type TelegramAudio = TelegramDocument;
17
+ export type TelegramAnimation = TelegramDocument;
18
+ export interface TelegramVoice {
19
+ file_id: string;
20
+ mime_type?: string;
21
+ }
22
+ export interface TelegramRichMessage {
23
+ blocks?: unknown[];
24
+ }
25
+ export interface TelegramMessageUser {
26
+ id?: number;
27
+ is_bot?: boolean;
28
+ first_name?: string;
29
+ last_name?: string;
30
+ username?: string;
31
+ }
32
+ export interface TelegramMessageForwardOrigin {
33
+ type?: string;
34
+ sender_user?: TelegramMessageUser;
35
+ sender_user_name?: string;
36
+ sender_chat?: {
37
+ title?: string;
38
+ username?: string;
39
+ id?: number;
40
+ };
41
+ chat?: {
42
+ title?: string;
43
+ username?: string;
44
+ id?: number;
45
+ };
46
+ author_signature?: string;
47
+ }
48
+ export interface TelegramReplyToMessage {
49
+ message_id?: number;
50
+ from?: TelegramMessageUser;
51
+ text?: string;
52
+ caption?: string;
53
+ rich_message?: TelegramRichMessage;
54
+ photo?: TelegramPhotoSize[];
55
+ document?: TelegramDocument;
56
+ video?: TelegramVideo;
57
+ audio?: TelegramAudio;
58
+ voice?: TelegramVoice;
59
+ animation?: TelegramAnimation;
60
+ sticker?: TelegramSticker;
61
+ }
62
+ export interface TelegramSticker {
63
+ file_id: string;
64
+ }
65
+ export interface TelegramMediaMessage {
66
+ message_id: number;
67
+ from?: TelegramMessageUser;
68
+ forward_origin?: TelegramMessageForwardOrigin;
69
+ forward_from?: TelegramMessageUser;
70
+ forward_sender_name?: string;
71
+ text?: string;
72
+ caption?: string;
73
+ rich_message?: TelegramRichMessage;
74
+ reply_to_message?: TelegramReplyToMessage;
75
+ media_group_id?: string;
76
+ photo?: TelegramPhotoSize[];
77
+ document?: TelegramDocument;
78
+ video?: TelegramVideo;
79
+ audio?: TelegramAudio;
80
+ voice?: TelegramVoice;
81
+ animation?: TelegramAnimation;
82
+ sticker?: TelegramSticker;
83
+ }
84
+ export interface TelegramMediaGroupMessage {
85
+ message_id: number;
86
+ chat: {
87
+ id: number;
88
+ };
89
+ message_thread_id?: number;
90
+ media_group_id?: string;
91
+ }
92
+ export interface TelegramMediaGroupState<TMessage, TContext = unknown> {
93
+ messages: TMessage[];
94
+ context?: TContext;
95
+ flushTimer?: ReturnType<typeof setTimeout>;
96
+ dispatching?: boolean;
97
+ dispatchPromise?: Promise<void>;
98
+ dispatchNow?: () => Promise<void>;
99
+ suspended?: boolean;
100
+ reschedule?: () => void;
101
+ }
102
+ export interface TelegramMediaGroupController<TMessage extends TelegramMediaGroupMessage, TContext = unknown> {
103
+ queueMessage: (options: {
104
+ message: TMessage;
105
+ context?: TContext;
106
+ dispatchMessages: (messages: TMessage[], ctx?: TContext) => unknown | Promise<unknown>;
107
+ }) => boolean;
108
+ removeMessages: (messageIds: number[]) => number;
109
+ flushMessage: (messageId: number) => Promise<boolean>;
110
+ suspend: () => void;
111
+ resume: (context: TContext) => void;
112
+ clear: () => void;
113
+ }
114
+ export interface TelegramMediaGroupDispatchRuntimeDeps<TMessage extends TelegramMediaGroupMessage, TContext> {
115
+ mediaGroups: TelegramMediaGroupController<TMessage, TContext>;
116
+ dispatchMessages: (messages: TMessage[], ctx: TContext) => Promise<void>;
117
+ onDeferredMessage?: (message: TMessage) => void;
118
+ }
119
+ export interface TelegramMediaGroupDispatchRuntime<TMessage extends TelegramMediaGroupMessage, TContext> {
120
+ handleMessage: (message: TMessage, ctx: TContext) => Promise<void>;
121
+ }
122
+ export interface TelegramMediaGroupControllerOptions {
123
+ debounceMs?: number;
124
+ setTimer?: (callback: () => void, ms: number) => ReturnType<typeof setTimeout>;
125
+ clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
126
+ }
127
+ export type TelegramAttachmentKind = "photo" | "document" | "video" | "audio" | "voice" | "animation" | "sticker";
128
+ export interface TelegramFileInfo {
129
+ file_id: string;
130
+ fileName: string;
131
+ mimeType?: string;
132
+ kind: TelegramAttachmentKind;
133
+ isImage: boolean;
134
+ }
135
+ export interface DownloadedTelegramFile {
136
+ path: string;
137
+ fileName?: string;
138
+ isImage?: boolean;
139
+ mimeType?: string;
140
+ kind?: TelegramAttachmentKind;
141
+ }
142
+ export interface DownloadedTelegramMessageFile {
143
+ path: string;
144
+ fileName: string;
145
+ isImage: boolean;
146
+ mimeType?: string;
147
+ kind?: TelegramAttachmentKind;
148
+ }
149
+ export interface DownloadTelegramMessageFilesDeps {
150
+ downloadFile: (fileId: string, fileName: string) => Promise<string>;
151
+ }
152
+ export declare function guessExtensionFromMime(mimeType: string | undefined, fallback: string): string;
153
+ export declare function guessMediaType(path: string): string | undefined;
154
+ export declare function extractTelegramMessageText(message: TelegramMediaMessage): string;
155
+ export declare function extractTelegramForwardContextText(message: TelegramMediaMessage, allowedUserId?: number): string;
156
+ export declare function extractTelegramReplyContextText(message: TelegramMediaMessage): string;
157
+ export declare function buildTelegramReplyContextBlock(message: TelegramMediaMessage, replyFiles?: Pick<DownloadedTelegramFile, "path">[], replyOutputs?: readonly string[]): string;
158
+ export declare function appendTelegramReplyContext(text: string, replyContext: string): string;
159
+ export declare function extractTelegramMessagePromptText(message: TelegramMediaMessage): string;
160
+ export declare function extractTelegramMessagesText(messages: TelegramMediaMessage[]): string;
161
+ export declare function extractTelegramMessagesPromptText(messages: TelegramMediaMessage[]): string;
162
+ export declare function extractFirstTelegramMessageText(messages: TelegramMediaMessage[]): string;
163
+ export declare function hasTelegramMessagePromptContent(message: TelegramMediaMessage): boolean;
164
+ export declare function hasTelegramMessagesPromptContent(messages: TelegramMediaMessage[]): boolean;
165
+ export declare function collectTelegramMessageIds(messages: TelegramMediaMessage[]): number[];
166
+ export declare function getTelegramMediaGroupKey(message: TelegramMediaGroupMessage): string | undefined;
167
+ export declare function removePendingTelegramMediaGroupMessages<TMessage extends TelegramMediaGroupMessage>(groups: Map<string, TelegramMediaGroupState<TMessage, unknown>>, messageIds: number[], clearTimer: (timer: ReturnType<typeof setTimeout>) => void): number;
168
+ export declare function queueTelegramMediaGroupMessage<TMessage extends TelegramMediaGroupMessage, TContext = unknown>(options: {
169
+ message: TMessage;
170
+ context?: TContext;
171
+ groups: Map<string, TelegramMediaGroupState<TMessage, TContext>>;
172
+ debounceMs: number;
173
+ setTimer: (callback: () => void, ms: number) => ReturnType<typeof setTimeout>;
174
+ clearTimer: (timer: ReturnType<typeof setTimeout>) => void;
175
+ dispatchMessages: (messages: TMessage[], ctx?: TContext) => unknown | Promise<unknown>;
176
+ }): boolean;
177
+ export declare function createTelegramMediaGroupController<TMessage extends TelegramMediaGroupMessage, TContext = unknown>(options?: TelegramMediaGroupControllerOptions): TelegramMediaGroupController<TMessage, TContext>;
178
+ export declare function createTelegramMediaGroupDispatchRuntime<TMessage extends TelegramMediaGroupMessage, TContext>(deps: TelegramMediaGroupDispatchRuntimeDeps<TMessage, TContext>): TelegramMediaGroupDispatchRuntime<TMessage, TContext>;
179
+ export declare function formatTelegramHistoryText(rawText: string, files: DownloadedTelegramFile[], handlerOutputs?: string[]): string;
180
+ export declare function downloadTelegramMessageFiles(messages: TelegramMediaMessage[], deps: DownloadTelegramMessageFilesDeps): Promise<DownloadedTelegramMessageFile[]>;
181
+ export declare function collectTelegramFileInfos(messages: TelegramMediaMessage[]): TelegramFileInfo[];