@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,409 @@
1
+ /**
2
+ * Telegram model control domain helpers
3
+ * Zones: pi agent model control, telegram controls, queue continuation
4
+ * Owns model identity, thinking levels, scoped resolution, current-model state, and in-flight model switching
5
+ */
6
+ import { TELEGRAM_PREFIX } from "./turns.js";
7
+ export const THINKING_LEVELS = [
8
+ "off",
9
+ "minimal",
10
+ "low",
11
+ "medium",
12
+ "high",
13
+ "xhigh",
14
+ "max",
15
+ ];
16
+ export function createCurrentModelStore(getContextModel) {
17
+ let currentModel;
18
+ return {
19
+ get: (ctx) => currentModel ?? getContextModel(ctx),
20
+ getStored: () => currentModel,
21
+ set: (model) => {
22
+ currentModel = model;
23
+ },
24
+ };
25
+ }
26
+ export function createCurrentModelUpdateRuntime(deps) {
27
+ const setAndUpdate = (model, ctx) => {
28
+ deps.setCurrentModel(model);
29
+ deps.updateStatus(ctx);
30
+ };
31
+ return {
32
+ setCurrentModel: setAndUpdate,
33
+ onModelSelect: (event, ctx) => {
34
+ setAndUpdate(event.model, ctx);
35
+ },
36
+ };
37
+ }
38
+ export function createCurrentModelRuntime(deps) {
39
+ const store = createCurrentModelStore(deps.getContextModel);
40
+ return {
41
+ ...store,
42
+ ...createCurrentModelUpdateRuntime({
43
+ setCurrentModel: store.set,
44
+ updateStatus: deps.updateStatus,
45
+ }),
46
+ };
47
+ }
48
+ export function modelsMatch(a, b) {
49
+ return !!a && !!b && a.provider === b.provider && a.id === b.id;
50
+ }
51
+ export function getCanonicalModelId(model) {
52
+ return `${model.provider}/${model.id}`;
53
+ }
54
+ export function isThinkingLevel(value) {
55
+ return THINKING_LEVELS.includes(value);
56
+ }
57
+ export function parseTelegramScopedModelPatternList(value) {
58
+ return value
59
+ .split(",")
60
+ .map((pattern) => pattern.trim())
61
+ .filter(Boolean);
62
+ }
63
+ export function parseTelegramCliScopedModelPatterns(args) {
64
+ for (let i = 0; i < args.length; i++) {
65
+ const arg = args[i];
66
+ if (arg === "--models") {
67
+ const patterns = parseTelegramScopedModelPatternList(args[i + 1] ?? "");
68
+ return patterns.length > 0 ? patterns : undefined;
69
+ }
70
+ if (arg.startsWith("--models=")) {
71
+ const patterns = parseTelegramScopedModelPatternList(arg.slice("--models=".length));
72
+ return patterns.length > 0 ? patterns : undefined;
73
+ }
74
+ }
75
+ return undefined;
76
+ }
77
+ function escapeRegex(text) {
78
+ return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
79
+ }
80
+ function globMatches(text, pattern) {
81
+ let regex = "^";
82
+ for (let i = 0; i < pattern.length; i++) {
83
+ const char = pattern[i];
84
+ if (char === "*") {
85
+ regex += ".*";
86
+ continue;
87
+ }
88
+ if (char === "?") {
89
+ regex += ".";
90
+ continue;
91
+ }
92
+ if (char === "[") {
93
+ const end = pattern.indexOf("]", i + 1);
94
+ if (end !== -1) {
95
+ const content = pattern.slice(i + 1, end);
96
+ regex += content.startsWith("!")
97
+ ? `[^${content.slice(1)}]`
98
+ : `[${content}]`;
99
+ i = end;
100
+ continue;
101
+ }
102
+ }
103
+ regex += escapeRegex(char);
104
+ }
105
+ regex += "$";
106
+ return new RegExp(regex, "i").test(text);
107
+ }
108
+ function isAliasModelId(id) {
109
+ if (id.endsWith("-latest"))
110
+ return true;
111
+ return !/-\d{8}$/.test(id);
112
+ }
113
+ function findUniqueModelMatch(availableModels, matches) {
114
+ let model;
115
+ for (const candidate of availableModels) {
116
+ if (!matches(candidate))
117
+ continue;
118
+ if (model)
119
+ return { ambiguous: true };
120
+ model = candidate;
121
+ }
122
+ return { model, ambiguous: false };
123
+ }
124
+ function findExactModelReferenceMatch(modelReference, availableModels) {
125
+ const trimmedReference = modelReference.trim();
126
+ if (!trimmedReference)
127
+ return undefined;
128
+ const normalizedReference = trimmedReference.toLowerCase();
129
+ const canonicalMatch = findUniqueModelMatch(availableModels, (model) => getCanonicalModelId(model).toLowerCase() === normalizedReference);
130
+ if (canonicalMatch.model || canonicalMatch.ambiguous) {
131
+ return canonicalMatch.model;
132
+ }
133
+ const slashIndex = trimmedReference.indexOf("/");
134
+ if (slashIndex !== -1) {
135
+ const provider = trimmedReference.substring(0, slashIndex).trim();
136
+ const modelId = trimmedReference.substring(slashIndex + 1).trim();
137
+ if (provider && modelId) {
138
+ const normalizedProvider = provider.toLowerCase();
139
+ const normalizedModelId = modelId.toLowerCase();
140
+ const providerMatch = findUniqueModelMatch(availableModels, (model) => model.provider.toLowerCase() === normalizedProvider &&
141
+ model.id.toLowerCase() === normalizedModelId);
142
+ if (providerMatch.model || providerMatch.ambiguous) {
143
+ return providerMatch.model;
144
+ }
145
+ }
146
+ }
147
+ return findUniqueModelMatch(availableModels, (model) => model.id.toLowerCase() === normalizedReference).model;
148
+ }
149
+ function tryMatchScopedModel(modelPattern, availableModels) {
150
+ const exactMatch = findExactModelReferenceMatch(modelPattern, availableModels);
151
+ if (exactMatch)
152
+ return exactMatch;
153
+ const normalizedPattern = modelPattern.toLowerCase();
154
+ let bestAlias;
155
+ let bestDatedVersion;
156
+ for (const model of availableModels) {
157
+ if (!model.id.toLowerCase().includes(normalizedPattern) &&
158
+ !model.name?.toLowerCase().includes(normalizedPattern)) {
159
+ continue;
160
+ }
161
+ if (isAliasModelId(model.id)) {
162
+ if (!bestAlias || model.id.localeCompare(bestAlias.id) > 0) {
163
+ bestAlias = model;
164
+ }
165
+ }
166
+ else if (!bestDatedVersion ||
167
+ model.id.localeCompare(bestDatedVersion.id) > 0) {
168
+ bestDatedVersion = model;
169
+ }
170
+ }
171
+ return bestAlias ?? bestDatedVersion;
172
+ }
173
+ function parseScopedModelPattern(pattern, availableModels) {
174
+ const exactMatch = tryMatchScopedModel(pattern, availableModels);
175
+ if (exactMatch) {
176
+ return { model: exactMatch, thinkingLevel: undefined };
177
+ }
178
+ const lastColonIndex = pattern.lastIndexOf(":");
179
+ if (lastColonIndex === -1) {
180
+ return { model: undefined, thinkingLevel: undefined };
181
+ }
182
+ const prefix = pattern.substring(0, lastColonIndex);
183
+ const suffix = pattern.substring(lastColonIndex + 1);
184
+ if (isThinkingLevel(suffix)) {
185
+ const parsedPrefix = parseScopedModelPattern(prefix, availableModels);
186
+ if (parsedPrefix.model) {
187
+ return { model: parsedPrefix.model, thinkingLevel: suffix };
188
+ }
189
+ return parsedPrefix;
190
+ }
191
+ return parseScopedModelPattern(prefix, availableModels);
192
+ }
193
+ export function resolveScopedModelPatterns(patterns, availableModels) {
194
+ const resolved = [];
195
+ const seen = new Set();
196
+ for (const pattern of patterns) {
197
+ if (pattern.includes("*") ||
198
+ pattern.includes("?") ||
199
+ pattern.includes("[")) {
200
+ const colonIndex = pattern.lastIndexOf(":");
201
+ let globPattern = pattern;
202
+ let thinkingLevel;
203
+ if (colonIndex !== -1) {
204
+ const suffix = pattern.substring(colonIndex + 1);
205
+ if (isThinkingLevel(suffix)) {
206
+ thinkingLevel = suffix;
207
+ globPattern = pattern.substring(0, colonIndex);
208
+ }
209
+ }
210
+ const matches = availableModels.filter((model) => globMatches(getCanonicalModelId(model), globPattern) ||
211
+ globMatches(model.id, globPattern));
212
+ for (const model of matches) {
213
+ const key = getCanonicalModelId(model);
214
+ if (seen.has(key))
215
+ continue;
216
+ seen.add(key);
217
+ resolved.push({ model, thinkingLevel });
218
+ }
219
+ continue;
220
+ }
221
+ const matched = parseScopedModelPattern(pattern, availableModels);
222
+ if (!matched.model)
223
+ continue;
224
+ const key = getCanonicalModelId(matched.model);
225
+ if (seen.has(key))
226
+ continue;
227
+ seen.add(key);
228
+ resolved.push({
229
+ model: matched.model,
230
+ thinkingLevel: matched.thinkingLevel,
231
+ });
232
+ }
233
+ return resolved;
234
+ }
235
+ export function sortScopedModels(models, currentModel) {
236
+ const sorted = [...models];
237
+ sorted.sort((a, b) => {
238
+ const aIsCurrent = modelsMatch(a.model, currentModel);
239
+ const bIsCurrent = modelsMatch(b.model, currentModel);
240
+ if (aIsCurrent && !bIsCurrent)
241
+ return -1;
242
+ if (!aIsCurrent && bIsCurrent)
243
+ return 1;
244
+ const providerCompare = a.model.provider.localeCompare(b.model.provider);
245
+ if (providerCompare !== 0)
246
+ return providerCompare;
247
+ return a.model.id.localeCompare(b.model.id);
248
+ });
249
+ return sorted;
250
+ }
251
+ export function createPendingModelSwitchStore() {
252
+ let selection;
253
+ return {
254
+ get: () => selection,
255
+ set: (nextSelection) => {
256
+ selection = nextSelection;
257
+ },
258
+ clear: () => {
259
+ selection = undefined;
260
+ },
261
+ has: () => selection !== undefined,
262
+ };
263
+ }
264
+ export function canRestartTelegramTurnForModelSwitch(state) {
265
+ return !state.isIdle && state.hasActiveTelegramTurn && state.hasAbortHandler;
266
+ }
267
+ export function shouldTriggerPendingTelegramModelSwitchAbort(state) {
268
+ return (state.hasPendingModelSwitch &&
269
+ state.hasActiveTelegramTurn &&
270
+ state.hasAbortHandler &&
271
+ state.activeToolExecutions === 0);
272
+ }
273
+ export function restartTelegramModelSwitchContinuation(state) {
274
+ if (!state.activeTurn || !state.abort)
275
+ return false;
276
+ state.queueContinuation(state.activeTurn, state.selection);
277
+ state.abort();
278
+ return true;
279
+ }
280
+ function truncateTelegramModelSwitchStatusSummary(text, maxWords = 4, maxLength = 32) {
281
+ const normalized = text.replace(/\s+/g, " ").trim();
282
+ if (!normalized)
283
+ return "";
284
+ const words = normalized.split(" ");
285
+ let summary = words.slice(0, maxWords).join(" ");
286
+ if (summary.length === 0)
287
+ summary = normalized;
288
+ if (summary.length > maxLength) {
289
+ summary = summary.slice(0, maxLength).trimEnd();
290
+ }
291
+ return summary.length < normalized.length || words.length > maxWords
292
+ ? `${summary}…`
293
+ : summary;
294
+ }
295
+ export function buildTelegramModelSwitchContinuationText(telegramPrefix, model, thinkingLevel) {
296
+ const modelLabel = `${model.provider}/${model.id}`;
297
+ const thinkingSuffix = thinkingLevel
298
+ ? ` Keep the selected thinking level (${thinkingLevel}) if it still applies.`
299
+ : "";
300
+ return `${telegramPrefix} Continue the interrupted previous Telegram request using the newly selected model (${modelLabel}). Resume from the last unfinished step instead of restarting from scratch unless necessary.${thinkingSuffix}`;
301
+ }
302
+ export function buildTelegramModelSwitchContinuationTurn(options) {
303
+ const modelLabel = `${options.selection.model.provider}/${options.selection.model.id}`;
304
+ const statusLabel = truncateTelegramModelSwitchStatusSummary(`continue on ${options.selection.model.id}`);
305
+ return {
306
+ kind: "prompt",
307
+ chatId: options.turn.chatId,
308
+ ...(options.turn.target ? { target: { ...options.turn.target } } : {}),
309
+ replyToMessageId: options.turn.replyToMessageId,
310
+ sourceMessageIds: [],
311
+ queueOrder: options.queueOrder,
312
+ queueLane: "control",
313
+ laneOrder: options.laneOrder,
314
+ queuedAttachments: [],
315
+ content: [
316
+ {
317
+ type: "text",
318
+ text: buildTelegramModelSwitchContinuationText(options.telegramPrefix ?? TELEGRAM_PREFIX, options.selection.model, options.selection.thinkingLevel),
319
+ },
320
+ ],
321
+ historyText: `Continue interrupted Telegram request on ${modelLabel}`,
322
+ statusSummary: `↻ ${statusLabel || "continue"}`,
323
+ };
324
+ }
325
+ export function createTelegramModelSwitchContinuationTurnBuilder(deps) {
326
+ return (options) => buildTelegramModelSwitchContinuationTurn({
327
+ ...options,
328
+ telegramPrefix: deps.telegramPrefix,
329
+ queueOrder: deps.allocateItemOrder(),
330
+ laneOrder: deps.allocateControlOrder(),
331
+ });
332
+ }
333
+ export function createTelegramModelSwitchContinuationQueue(deps) {
334
+ return (turn, selection, ctx) => {
335
+ deps.appendQueuedItem(deps.createContinuationTurn({ turn, selection }), ctx);
336
+ };
337
+ }
338
+ export function createTelegramModelSwitchContinuationQueueRuntime(deps) {
339
+ return createTelegramModelSwitchContinuationQueue({
340
+ createContinuationTurn: createTelegramModelSwitchContinuationTurnBuilder({
341
+ telegramPrefix: deps.telegramPrefix,
342
+ allocateItemOrder: deps.allocateItemOrder,
343
+ allocateControlOrder: deps.allocateControlOrder,
344
+ }),
345
+ appendQueuedItem: deps.appendQueuedItem,
346
+ });
347
+ }
348
+ export function createTelegramModelSwitchControllerRuntime(deps) {
349
+ return createTelegramModelSwitchController({
350
+ isIdle: deps.isIdle,
351
+ getPendingModelSwitch: deps.getPendingModelSwitch,
352
+ setPendingModelSwitch: deps.setPendingModelSwitch,
353
+ getActiveTurn: deps.getActiveTurn,
354
+ getAbortHandler: deps.getAbortHandler,
355
+ hasAbortHandler: deps.hasAbortHandler,
356
+ getActiveToolExecutions: deps.getActiveToolExecutions,
357
+ queueContinuation: createTelegramModelSwitchContinuationQueueRuntime({
358
+ telegramPrefix: deps.telegramPrefix,
359
+ allocateItemOrder: deps.allocateItemOrder,
360
+ allocateControlOrder: deps.allocateControlOrder,
361
+ appendQueuedItem: deps.appendQueuedItem,
362
+ }),
363
+ updateStatus: deps.updateStatus,
364
+ });
365
+ }
366
+ export function createTelegramModelSwitchController(deps) {
367
+ return {
368
+ canOfferInFlightSwitch: (ctx) => canRestartTelegramTurnForModelSwitch({
369
+ isIdle: deps.isIdle(ctx),
370
+ hasActiveTelegramTurn: !!deps.getActiveTurn(),
371
+ hasAbortHandler: deps.hasAbortHandler(),
372
+ }),
373
+ stagePendingSwitch: (selection, ctx) => {
374
+ deps.setPendingModelSwitch(selection);
375
+ deps.updateStatus(ctx);
376
+ },
377
+ clearPendingSwitch: () => {
378
+ deps.setPendingModelSwitch(undefined);
379
+ },
380
+ queueContinuation: deps.queueContinuation,
381
+ triggerPendingAbort: (ctx) => {
382
+ if (!shouldTriggerPendingTelegramModelSwitchAbort({
383
+ hasPendingModelSwitch: !!deps.getPendingModelSwitch(),
384
+ hasActiveTelegramTurn: !!deps.getActiveTurn(),
385
+ hasAbortHandler: deps.hasAbortHandler(),
386
+ activeToolExecutions: deps.getActiveToolExecutions(),
387
+ })) {
388
+ return false;
389
+ }
390
+ const selection = deps.getPendingModelSwitch();
391
+ const turn = deps.getActiveTurn();
392
+ const abort = deps.getAbortHandler();
393
+ if (!selection || !turn || !abort)
394
+ return false;
395
+ deps.setPendingModelSwitch(undefined);
396
+ deps.queueContinuation(turn, selection, ctx);
397
+ abort();
398
+ return true;
399
+ },
400
+ restartInterruptedTurn: (selection, ctx) => restartTelegramModelSwitchContinuation({
401
+ activeTurn: deps.getActiveTurn(),
402
+ abort: deps.getAbortHandler(),
403
+ selection,
404
+ queueContinuation: (turn, nextSelection) => {
405
+ deps.queueContinuation(turn, nextSelection, ctx);
406
+ },
407
+ }),
408
+ };
409
+ }
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Telegram outbound attachment helpers
3
+ * Zones: telegram outbound, pi agent tool, filesystem
4
+ * Owns telegram_attach registration, outbound attachment queueing, and delivery so Telegram file output stays in one domain module
5
+ */
6
+ import type { TelegramBusAgentMessage, TelegramBusAgentTargetSelector } from "./bus.ts";
7
+ import type { ExtensionAPI } from "./pi.ts";
8
+ import { type TelegramTarget } from "./target.ts";
9
+ export declare const TELEGRAM_OUTBOUND_ATTACHMENT_DEFAULT_MAX_BYTES: number;
10
+ export declare function getTelegramOutboundAttachmentByteLimitFromEnv(env: NodeJS.ProcessEnv, names: string[], defaultValue?: number): number;
11
+ export declare const TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES: number;
12
+ export interface TelegramOutboundAttachmentToolResult {
13
+ content: Array<{
14
+ type: "text";
15
+ text: string;
16
+ }>;
17
+ details: {
18
+ paths: string[];
19
+ };
20
+ }
21
+ export interface TelegramOutboundAttachmentRuntimeEventRecorderPort {
22
+ recordRuntimeEvent?: (category: string, error: unknown, details?: Record<string, unknown>) => void;
23
+ }
24
+ export interface TelegramOutboundAttachmentToolRegistrationDeps extends TelegramOutboundAttachmentRuntimeEventRecorderPort {
25
+ maxAttachmentsPerTurn?: number;
26
+ maxAttachmentSizeBytes?: number;
27
+ getActiveTurn: () => TelegramOutboundAttachmentQueueTargetView | undefined;
28
+ getDefaultChatId?: () => number | undefined;
29
+ getDefaultTarget?: () => TelegramTarget | undefined;
30
+ canSendDirect?: () => boolean;
31
+ sendMultipart?: TelegramQueuedOutboundAttachmentDeliveryDeps["sendMultipart"];
32
+ statPath?: (path: string) => Promise<{
33
+ isFile(): boolean;
34
+ size?: number;
35
+ }>;
36
+ }
37
+ export interface TelegramOutboundMessagePlan {
38
+ markdown: string;
39
+ replyMarkup?: unknown;
40
+ }
41
+ export interface TelegramOutboundMessageToolRegistrationDeps extends TelegramOutboundAttachmentRuntimeEventRecorderPort {
42
+ getDefaultChatId: () => number | undefined;
43
+ getDefaultTarget?: () => TelegramTarget | undefined;
44
+ getActiveTurn?: () => {
45
+ chatId: number;
46
+ target?: TelegramTarget;
47
+ } | undefined;
48
+ resolveAgentTarget?: (selector: TelegramBusAgentTargetSelector) => Promise<TelegramTarget & {
49
+ threadId: number;
50
+ }>;
51
+ routeAgentMessage?: (message: TelegramBusAgentMessage) => Promise<void>;
52
+ canSendDirect: () => boolean;
53
+ planMessage: (markdown: string) => TelegramOutboundMessagePlan;
54
+ sendMarkdownMessage: (chatId: number, markdown: string, options?: {
55
+ replyMarkup?: unknown;
56
+ target?: TelegramTarget;
57
+ }) => Promise<number | undefined>;
58
+ sendChannelMarkdownMessage?: (channel: number | string, markdown: string, options: {
59
+ operationId: string;
60
+ replyMarkup?: unknown;
61
+ }) => Promise<number | undefined>;
62
+ sendChannelMediaMessage?: (channel: number | string, mediaPath: string, markdown: string, options: {
63
+ operationId: string;
64
+ replyMarkup?: unknown;
65
+ }) => Promise<number | undefined>;
66
+ }
67
+ export interface TelegramQueuedOutboundAttachmentView {
68
+ path: string;
69
+ fileName: string;
70
+ }
71
+ export interface TelegramOutboundAttachmentQueueTargetView {
72
+ queuedAttachments: TelegramQueuedOutboundAttachmentView[];
73
+ guestQueryId?: string;
74
+ }
75
+ export interface TelegramQueuedOutboundAttachmentTurnView extends TelegramOutboundAttachmentQueueTargetView {
76
+ chatId: number;
77
+ replyToMessageId: number;
78
+ target?: TelegramTarget;
79
+ }
80
+ export interface TelegramRichOutboundAttachmentPlan {
81
+ method: "sendRichMessage";
82
+ fields: Record<string, string>;
83
+ fileField: "rich_media_upload";
84
+ filePath: string;
85
+ fileName: string;
86
+ }
87
+ export interface TelegramRichOutboundAttachmentSenderDeps extends TelegramOutboundAttachmentRuntimeEventRecorderPort {
88
+ sendMultipart: TelegramQueuedOutboundAttachmentDeliveryDeps["sendMultipart"];
89
+ getRenderingMode: () => "rich" | "html";
90
+ recordOwnership?: (input: {
91
+ chatId: number;
92
+ messageId: number;
93
+ target?: TelegramTarget;
94
+ }) => void;
95
+ }
96
+ export declare function planTelegramRichOutboundAttachment(options: {
97
+ turn: TelegramQueuedOutboundAttachmentTurnView;
98
+ markdown: string;
99
+ renderingMode: "rich" | "html";
100
+ replyMarkup?: unknown;
101
+ }): TelegramRichOutboundAttachmentPlan | undefined;
102
+ export declare function createTelegramRichOutboundAttachmentSender(deps: TelegramRichOutboundAttachmentSenderDeps): (turn: TelegramQueuedOutboundAttachmentTurnView, markdown: string, options?: {
103
+ replyMarkup?: unknown;
104
+ isDeliveryActive?: () => boolean;
105
+ }) => Promise<boolean>;
106
+ export type TelegramGuestCachedAttachmentResult = {
107
+ type: "document";
108
+ id: string;
109
+ title: string;
110
+ document_file_id: string;
111
+ caption?: string;
112
+ } | {
113
+ type: "photo";
114
+ id: string;
115
+ photo_file_id: string;
116
+ caption?: string;
117
+ } | {
118
+ type: "audio";
119
+ id: string;
120
+ audio_file_id: string;
121
+ caption?: string;
122
+ } | {
123
+ type: "voice";
124
+ id: string;
125
+ voice_file_id: string;
126
+ title: string;
127
+ caption?: string;
128
+ };
129
+ export declare function registerTelegramOutboundAttachmentTool(pi: ExtensionAPI, deps: TelegramOutboundAttachmentToolRegistrationDeps): void;
130
+ export declare function registerTelegramOutboundMessageTool(pi: ExtensionAPI, deps: TelegramOutboundMessageToolRegistrationDeps): void;
131
+ export interface TelegramQueuedOutboundAttachmentDeliveryDeps {
132
+ sendMultipart: (method: string, fields: Record<string, string>, fileField: string, filePath: string, fileName: string) => Promise<unknown>;
133
+ sendTextReply: (chatId: number, replyToMessageId: number, text: string, options?: {
134
+ target?: TelegramTarget;
135
+ }) => Promise<unknown>;
136
+ recordRuntimeEvent?: (category: string, error: unknown, details?: Record<string, unknown>) => void;
137
+ statPath?: (path: string) => Promise<{
138
+ size: number;
139
+ }>;
140
+ maxAttachmentSizeBytes?: number;
141
+ isDeliveryActive?: () => boolean;
142
+ }
143
+ export declare function queueTelegramOutboundAttachments(options: {
144
+ activeTurn: TelegramOutboundAttachmentQueueTargetView | undefined;
145
+ paths: string[];
146
+ chatId?: number;
147
+ threadId?: number;
148
+ caption?: string;
149
+ maxAttachmentsPerTurn: number;
150
+ maxAttachmentSizeBytes?: number;
151
+ sendMultipart?: TelegramQueuedOutboundAttachmentDeliveryDeps["sendMultipart"];
152
+ getDefaultChatId?: () => number | undefined;
153
+ getDefaultTarget?: () => TelegramTarget | undefined;
154
+ canSendDirect?: () => boolean;
155
+ statPath?: (path: string) => Promise<{
156
+ isFile(): boolean;
157
+ size?: number;
158
+ }>;
159
+ }): Promise<TelegramOutboundAttachmentToolResult>;
160
+ export declare function deliverTelegramGuestCachedAttachment(options: {
161
+ guestQueryId: string;
162
+ stagingChatId: number;
163
+ stagingTarget?: TelegramTarget;
164
+ attachment: TelegramQueuedOutboundAttachmentView;
165
+ caption?: string;
166
+ sendMultipart: TelegramQueuedOutboundAttachmentDeliveryDeps["sendMultipart"];
167
+ answerGuestQuery: (guestQueryId: string, result: TelegramGuestCachedAttachmentResult) => Promise<void>;
168
+ answerGuestText?: (guestQueryId: string, text: string) => Promise<void>;
169
+ fallbackText?: string;
170
+ deleteMessage: (chatId: number, messageId: number) => Promise<void>;
171
+ recordRuntimeEvent?: TelegramOutboundAttachmentRuntimeEventRecorderPort["recordRuntimeEvent"];
172
+ }): Promise<void>;
173
+ export declare function sendTelegramOutboundMessage(options: {
174
+ text: string;
175
+ media?: string;
176
+ operationId?: string;
177
+ channel?: boolean;
178
+ chatId?: number | string;
179
+ threadId?: number;
180
+ agentThread?: string | number;
181
+ target?: TelegramTarget;
182
+ getDefaultChatId?: () => number | undefined;
183
+ getDefaultTarget?: () => TelegramTarget | undefined;
184
+ getActiveTurn?: () => {
185
+ chatId: number;
186
+ target?: TelegramTarget;
187
+ } | undefined;
188
+ resolveAgentTarget?: (selector: TelegramBusAgentTargetSelector) => Promise<TelegramTarget & {
189
+ threadId: number;
190
+ }>;
191
+ routeAgentMessage?: (message: TelegramBusAgentMessage) => Promise<void>;
192
+ canSendDirect: () => boolean;
193
+ planMessage: (markdown: string) => TelegramOutboundMessagePlan;
194
+ sendMarkdownMessage: (chatId: number, markdown: string, options?: {
195
+ replyMarkup?: unknown;
196
+ target?: TelegramTarget;
197
+ }) => Promise<number | undefined>;
198
+ sendChannelMarkdownMessage?: (channel: number | string, markdown: string, options: {
199
+ operationId: string;
200
+ replyMarkup?: unknown;
201
+ }) => Promise<number | undefined>;
202
+ sendChannelMediaMessage?: (channel: number | string, mediaPath: string, markdown: string, options: {
203
+ operationId: string;
204
+ replyMarkup?: unknown;
205
+ }) => Promise<number | undefined>;
206
+ }): Promise<{
207
+ content: Array<{
208
+ type: "text";
209
+ text: string;
210
+ }>;
211
+ details: {
212
+ chatId: number | string;
213
+ messageId?: number;
214
+ };
215
+ }>;
216
+ export declare function sendTelegramOutboundFiles(options: {
217
+ paths: string[];
218
+ chatId?: number;
219
+ threadId?: number;
220
+ target?: TelegramTarget;
221
+ caption?: string;
222
+ maxAttachmentsPerTurn: number;
223
+ maxAttachmentSizeBytes?: number;
224
+ sendMultipart: TelegramQueuedOutboundAttachmentDeliveryDeps["sendMultipart"];
225
+ getDefaultChatId?: () => number | undefined;
226
+ getDefaultTarget?: () => TelegramTarget | undefined;
227
+ canSendDirect?: () => boolean;
228
+ statPath?: (path: string) => Promise<{
229
+ isFile(): boolean;
230
+ size?: number;
231
+ }>;
232
+ }): Promise<TelegramOutboundAttachmentToolResult & {
233
+ details: {
234
+ paths: string[];
235
+ chatId: number;
236
+ };
237
+ }>;
238
+ export declare function createTelegramQueuedOutboundAttachmentSender(deps: TelegramQueuedOutboundAttachmentDeliveryDeps): (turn: TelegramQueuedOutboundAttachmentTurnView, options?: {
239
+ isDeliveryActive?: () => boolean;
240
+ }) => Promise<void>;
241
+ export declare function sendQueuedTelegramOutboundAttachments(turn: TelegramQueuedOutboundAttachmentTurnView, deps: TelegramQueuedOutboundAttachmentDeliveryDeps): Promise<void>;