@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.
Files changed (204) hide show
  1. package/BACKLOG.md +5 -1
  2. package/CHANGELOG.md +12 -0
  3. package/README.md +6 -6
  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 +17 -3
  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 +11 -3
  183. package/lib/bus-leader.ts +57 -20
  184. package/lib/bus.ts +12 -2
  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 +26 -1
  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/pi.ts +4 -0
  193. package/lib/sync.ts +3 -0
  194. package/lib/telegram-api.ts +12 -1
  195. package/lib/thread-cleanup-manager.ts +36 -6
  196. package/lib/thread-display.ts +96 -18
  197. package/lib/threads.ts +150 -30
  198. package/package.json +56 -13
  199. package/scripts/build-dist.mjs +44 -0
  200. package/scripts/measure-bus.mjs +8 -1
  201. package/scripts/measure-workspace.mjs +8 -1
  202. package/skills/generative-apps/SKILL.md +1 -1
  203. package/skills/show-me/SKILL.md +1 -1
  204. package/skills/telegram-bridge/SKILL.md +1 -1
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Telegram menu and inline-keyboard rendering helpers
3
+ * Zones: telegram ui, controls, status menu
4
+ * Owns app-menu/status state, inline UI text, and callback composition while model/thinking/queue menu details live in dedicated domains
5
+ */
6
+ import { createTelegramModelMenuStateBuilder, handleTelegramModelMenuCallbackAction, openTelegramModelMenu, sendTelegramModelMenuMessage, updateTelegramModelMenuMessage, } from "./menu-model.js";
7
+ import { handleTelegramStatusMenuCallbackAction, openTelegramStatusMenu, sendTelegramStatusMessage, updateTelegramStatusMessage, } from "./menu-status.js";
8
+ import { handleTelegramThinkingMenuCallbackAction, openTelegramThinkingMenu, updateTelegramThinkingMenuMessage, } from "./menu-thinking.js";
9
+ import { handleTelegramSectionCallback, handleTelegramSectionOpen, handleTelegramSectionSettingsOpen, parseTelegramSectionCallback, } from "./sections.js";
10
+ export { applyTelegramModelPageSelection, applyTelegramModelScopeSelection, buildModelMenuReplyMarkup, buildModelPageMenuReplyMarkup, buildTelegramModelCallbackPlan, buildTelegramModelMenuRenderPayload, buildTelegramModelMenuState, buildTelegramModelMenuStateRuntime, buildTelegramModelPageMenuRenderPayload, createTelegramModelMenuRuntime, createTelegramModelMenuStateBuilder, formatScopedModelButtonText, getModelMenuItems, getStoredTelegramModelMenuState, getTelegramModelMenuPage, getTelegramModelSelection, handleTelegramModelMenuCallbackAction, MODEL_MENU_TITLE, MODEL_PAGE_MENU_TITLE, openTelegramModelMenu, pruneStoredTelegramModelMenus, resolveCachedTelegramModelMenuInputs, sendTelegramModelMenuMessage, storeTelegramModelMenuState, TELEGRAM_MODEL_PAGE_SIZE, updateTelegramModelMenuMessage, } from "./menu-model.js";
11
+ export { buildStatusReplyMarkup, buildTelegramStatusMenuRenderPayload, handleTelegramStatusMenuCallbackAction, openTelegramStatusMenu, sendTelegramStatusMessage, updateTelegramStatusMessage, } from "./menu-status.js";
12
+ export { buildTelegramThinkingMenuRenderPayload, buildThinkingMenuReplyMarkup, buildThinkingMenuText, handleTelegramThinkingMenuCallbackAction, openTelegramThinkingMenu, updateTelegramThinkingMenuMessage, } from "./menu-thinking.js";
13
+ export function parseTelegramMenuCallbackAction(data) {
14
+ if (data === "menu:model" || data === "status:model") {
15
+ return { kind: "status", action: "model" };
16
+ }
17
+ if (data === "menu:thinking" || data === "status:thinking") {
18
+ return { kind: "status", action: "thinking" };
19
+ }
20
+ if (data === "menu:queue" || data === "status:queue") {
21
+ return { kind: "status", action: "queue" };
22
+ }
23
+ if (data === "menu:settings" || data === "status:settings") {
24
+ return { kind: "status", action: "settings" };
25
+ }
26
+ if (data?.startsWith("thinking:set:")) {
27
+ return {
28
+ kind: "thinking:set",
29
+ level: data.slice("thinking:set:".length),
30
+ };
31
+ }
32
+ if (data?.startsWith("model:")) {
33
+ const [, action, value] = data.split(":");
34
+ if (action === "noop" ||
35
+ action === "scope" ||
36
+ action === "page" ||
37
+ action === "pages" ||
38
+ action === "open" ||
39
+ action === "pick" ||
40
+ action === "pick-selected" ||
41
+ action === "scope-enable" ||
42
+ action === "scope-disable" ||
43
+ action === "scope-toggle") {
44
+ return { kind: "model", action, value };
45
+ }
46
+ }
47
+ return { kind: "ignore" };
48
+ }
49
+ export async function handleTelegramMenuCallbackEntry(callbackQueryId, data, state, deps) {
50
+ if (!data) {
51
+ await deps.answerCallbackQuery(callbackQueryId);
52
+ return;
53
+ }
54
+ if (!state) {
55
+ await deps.answerCallbackQuery(callbackQueryId, "Interactive message expired.");
56
+ return;
57
+ }
58
+ const handled = (await deps.handleStatusAction()) ||
59
+ (await deps.handleThinkingAction()) ||
60
+ (await deps.handleModelAction());
61
+ if (!handled) {
62
+ await deps.answerCallbackQuery(callbackQueryId);
63
+ }
64
+ }
65
+ export async function handleStoredTelegramMenuCallback(query, deps) {
66
+ const state = deps.getStoredModelMenuState(query.message?.message_id, query.message?.chat?.id);
67
+ await handleTelegramMenuCallbackEntry(query.id, query.data, state, {
68
+ handleStatusAction: async () => {
69
+ if (!state)
70
+ return false;
71
+ return deps.handleStatusAction(state);
72
+ },
73
+ handleThinkingAction: async () => {
74
+ if (!state)
75
+ return false;
76
+ return deps.handleThinkingAction(state);
77
+ },
78
+ handleModelAction: async () => {
79
+ if (!state)
80
+ return false;
81
+ return deps.handleModelAction(state);
82
+ },
83
+ answerCallbackQuery: deps.answerCallbackQuery,
84
+ });
85
+ }
86
+ export function createTelegramMenuCallbackHandler(deps) {
87
+ return (query, ctx) => handleTelegramMenuCallbackRuntime(query, ctx, deps);
88
+ }
89
+ export function createTelegramMenuCallbackHandlerForContext(deps) {
90
+ return createTelegramMenuCallbackHandler({
91
+ getStoredModelMenuState: deps.getStoredModelMenuState,
92
+ getActiveModel: deps.getActiveModel,
93
+ getThinkingLevel: deps.getThinkingLevel,
94
+ setThinkingLevel: deps.setThinkingLevel,
95
+ updateStatus: deps.updateStatus,
96
+ updateModelMenuMessage: deps.updateModelMenuMessage,
97
+ updateThinkingMenuMessage: deps.updateThinkingMenuMessage,
98
+ updateStatusMessage: deps.updateStatusMessage,
99
+ updateSettingsMenuMessage: deps.updateSettingsMenuMessage,
100
+ answerCallbackQuery: deps.answerCallbackQuery,
101
+ isIdle: deps.isIdle,
102
+ hasActiveTelegramTurn: deps.hasActiveTelegramTurn,
103
+ hasAbortHandler: deps.hasAbortHandler,
104
+ hasActiveToolExecutions: () => deps.getActiveToolExecutions() > 0,
105
+ persistScopedModelPatterns: deps.persistScopedModelPatterns,
106
+ setModel: deps.setModel,
107
+ setCurrentModel: deps.setCurrentModel,
108
+ stagePendingModelSwitch: deps.stagePendingModelSwitch,
109
+ restartInterruptedTelegramTurn: deps.restartInterruptedTelegramTurn,
110
+ sectionRegistry: deps.sectionRegistry,
111
+ editInteractiveMessage: deps.editInteractiveMessage,
112
+ sendInteractiveMessage: deps.sendInteractiveMessage,
113
+ sendSectionRichMessage: deps.sendSectionRichMessage,
114
+ enqueueSectionPrompt: deps.enqueueSectionPrompt,
115
+ deleteMessage: deps.deleteMessage,
116
+ });
117
+ }
118
+ export async function handleTelegramMenuCallbackRuntime(query, ctx, deps) {
119
+ if (query.data === "menu:back") {
120
+ const state = deps.getStoredModelMenuState(query.message?.message_id, query.message?.chat?.id);
121
+ if (!state) {
122
+ await deps.answerCallbackQuery(query.id, "Interactive message expired.");
123
+ return;
124
+ }
125
+ await deps.updateStatusMessage(state, ctx);
126
+ await deps.answerCallbackQuery(query.id);
127
+ return;
128
+ }
129
+ // Section callbacks: dispatch before built-in menu handling
130
+ if (deps.sectionRegistry && query.data?.startsWith("section:")) {
131
+ const parsed = parseTelegramSectionCallback(query.data);
132
+ if (parsed) {
133
+ const message = query.message;
134
+ const chatId = message?.chat?.id;
135
+ const messageId = message?.message_id;
136
+ const target = typeof chatId === "number"
137
+ ? typeof message?.message_thread_id === "number"
138
+ ? { chatId, threadId: message.message_thread_id }
139
+ : { chatId }
140
+ : undefined;
141
+ if (typeof chatId === "number" && typeof messageId === "number") {
142
+ const { token, action, payload } = parsed;
143
+ if (action === "open") {
144
+ const state = deps.getStoredModelMenuState(messageId, chatId);
145
+ if (!state) {
146
+ await deps.answerCallbackQuery(query.id, "Interactive message expired.");
147
+ return;
148
+ }
149
+ const handled = await handleTelegramSectionOpen(deps.sectionRegistry, token, chatId, messageId, query.id, {
150
+ answerCallbackQuery: deps.answerCallbackQuery,
151
+ target,
152
+ editInteractiveMessage: deps.editInteractiveMessage ?? (async () => { }),
153
+ sendInteractiveMessage: deps.sendInteractiveMessage ?? (async () => undefined),
154
+ sendRichMessage: deps.sendSectionRichMessage ?? (async () => {
155
+ throw new Error("Rich Message delivery is unavailable");
156
+ }),
157
+ enqueuePrompt: deps.enqueueSectionPrompt
158
+ ? (prompt) => deps.enqueueSectionPrompt(prompt, ctx, target, query)
159
+ : async () => { },
160
+ deleteMessage: deps.deleteMessage ?? (async () => { }),
161
+ });
162
+ if (handled)
163
+ return;
164
+ }
165
+ else if (action === "settings") {
166
+ if (typeof chatId === "number" && typeof messageId === "number") {
167
+ const handled = await handleTelegramSectionSettingsOpen(deps.sectionRegistry, token, chatId, messageId, query.id, {
168
+ answerCallbackQuery: deps.answerCallbackQuery,
169
+ target,
170
+ editInteractiveMessage: deps.editInteractiveMessage ?? (async () => { }),
171
+ sendInteractiveMessage: deps.sendInteractiveMessage ?? (async () => undefined),
172
+ sendRichMessage: deps.sendSectionRichMessage ?? (async () => {
173
+ throw new Error("Rich Message delivery is unavailable");
174
+ }),
175
+ enqueuePrompt: deps.enqueueSectionPrompt
176
+ ? (prompt) => deps.enqueueSectionPrompt(prompt, ctx, target, query)
177
+ : async () => { },
178
+ deleteMessage: deps.deleteMessage ?? (async () => { }),
179
+ });
180
+ if (handled)
181
+ return;
182
+ }
183
+ }
184
+ else {
185
+ const handled = await handleTelegramSectionCallback(deps.sectionRegistry, token, action, payload, chatId, messageId, query.id, {
186
+ answerCallbackQuery: deps.answerCallbackQuery,
187
+ target,
188
+ editInteractiveMessage: deps.editInteractiveMessage ?? (async () => { }),
189
+ sendInteractiveMessage: deps.sendInteractiveMessage ?? (async () => undefined),
190
+ sendRichMessage: deps.sendSectionRichMessage ?? (async () => {
191
+ throw new Error("Rich Message delivery is unavailable");
192
+ }),
193
+ enqueuePrompt: deps.enqueueSectionPrompt
194
+ ? (prompt) => deps.enqueueSectionPrompt(prompt, ctx, target, query)
195
+ : async () => { },
196
+ deleteMessage: deps.deleteMessage ?? (async () => { }),
197
+ });
198
+ if (handled)
199
+ return;
200
+ }
201
+ }
202
+ }
203
+ }
204
+ await handleStoredTelegramMenuCallback(query, {
205
+ getStoredModelMenuState: deps.getStoredModelMenuState,
206
+ handleStatusAction: async (state) => handleTelegramStatusMenuCallbackAction(query.id, query.data, deps.getActiveModel(ctx), {
207
+ updateModelMenuMessage: () => deps.updateModelMenuMessage(state, ctx),
208
+ updateThinkingMenuMessage: () => deps.updateThinkingMenuMessage(state, ctx),
209
+ updateSettingsMenuMessage: () => deps.updateSettingsMenuMessage?.(state, ctx) ?? Promise.resolve(),
210
+ answerCallbackQuery: deps.answerCallbackQuery,
211
+ isVoiceReplyActive: deps.isVoiceReplyActive,
212
+ }),
213
+ handleThinkingAction: async (state) => handleTelegramThinkingMenuCallbackAction(query.id, query.data, deps.getActiveModel(ctx), {
214
+ setThinkingLevel: (level) => {
215
+ deps.setThinkingLevel(level);
216
+ deps.updateStatus(ctx);
217
+ },
218
+ getCurrentThinkingLevel: deps.getThinkingLevel,
219
+ updateStatusMessage: () => deps.updateStatusMessage(state, ctx),
220
+ answerCallbackQuery: deps.answerCallbackQuery,
221
+ isVoiceReplyActive: deps.isVoiceReplyActive,
222
+ }),
223
+ handleModelAction: async (state) => {
224
+ try {
225
+ return await handleTelegramModelMenuCallbackAction(query.id, {
226
+ data: query.data,
227
+ state,
228
+ activeModel: deps.getActiveModel(ctx),
229
+ currentThinkingLevel: deps.getThinkingLevel(),
230
+ isIdle: deps.isIdle(ctx),
231
+ canRestartBusyRun: deps.hasActiveTelegramTurn() && deps.hasAbortHandler(),
232
+ hasActiveToolExecutions: deps.hasActiveToolExecutions(),
233
+ }, {
234
+ updateModelMenuMessage: () => deps.updateModelMenuMessage(state, ctx),
235
+ updateStatusMessage: () => deps.updateStatusMessage(state, ctx),
236
+ answerCallbackQuery: deps.answerCallbackQuery,
237
+ persistScopedModelPatterns: deps.persistScopedModelPatterns
238
+ ? (patterns) => deps.persistScopedModelPatterns(patterns, ctx)
239
+ : undefined,
240
+ setModel: deps.setModel,
241
+ setCurrentModel: (model) => deps.setCurrentModel(model, ctx),
242
+ setThinkingLevel: (level) => {
243
+ deps.setThinkingLevel(level);
244
+ deps.updateStatus(ctx);
245
+ },
246
+ stagePendingModelSwitch: (selection) => {
247
+ deps.stagePendingModelSwitch(selection, ctx);
248
+ },
249
+ restartInterruptedTelegramTurn: (selection) => deps.restartInterruptedTelegramTurn(selection, ctx),
250
+ });
251
+ }
252
+ catch (error) {
253
+ const message = error instanceof Error ? error.message : String(error);
254
+ await deps.answerCallbackQuery(query.id, message);
255
+ return true;
256
+ }
257
+ },
258
+ answerCallbackQuery: deps.answerCallbackQuery,
259
+ });
260
+ }
261
+ export function createTelegramMenuActionRuntimeWithStateBuilder(deps) {
262
+ return createTelegramMenuActionRuntime({
263
+ getModelMenuState: createTelegramModelMenuStateBuilder({
264
+ runtime: deps.runtime,
265
+ createSettingsManager: deps.createSettingsManager,
266
+ getActiveModel: deps.getActiveModel,
267
+ }),
268
+ getActiveModel: deps.getActiveModel,
269
+ getThinkingLevel: deps.getThinkingLevel,
270
+ getQueueItemCount: deps.getQueueItemCount,
271
+ buildStatusHtml: deps.buildStatusHtml,
272
+ storeModelMenuState: deps.storeModelMenuState,
273
+ isIdle: deps.isIdle,
274
+ canOfferInFlightModelSwitch: deps.canOfferInFlightModelSwitch,
275
+ sendTextReply: deps.sendTextReply,
276
+ editInteractiveMessage: deps.editInteractiveMessage,
277
+ sendInteractiveMessage: deps.sendInteractiveMessage,
278
+ sectionRegistry: deps.sectionRegistry,
279
+ });
280
+ }
281
+ export function createTelegramMenuActionRuntime(deps) {
282
+ return {
283
+ updateModelMenuMessage: (state, ctx) => updateTelegramModelMenuMessage(state, deps.getActiveModel(ctx), deps),
284
+ updateThinkingMenuMessage: (state, ctx) => updateTelegramThinkingMenuMessage(state, deps.getActiveModel(ctx), deps.getThinkingLevel(), deps),
285
+ updateStatusMessage: (state, ctx) => updateTelegramStatusMessage(state, deps.buildStatusHtml(ctx), deps.getActiveModel(ctx), deps.getThinkingLevel(), deps, deps.getQueueItemCount?.() ?? 0, deps.sectionRegistry, deps.isVoiceReplyActive?.()),
286
+ sendStatusMessage: (chatId, replyToMessageId, ctx, threadId) => openTelegramStatusMenu({
287
+ isIdle: () => deps.isIdle(ctx),
288
+ sendBusyMessage: async () => {
289
+ await deps.sendTextReply(chatId, replyToMessageId, "<b>⏳ Cannot open status while Pi is busy. Send /abort, /next, or /stop.</b>", { target: { chatId, threadId }, parseMode: "HTML" });
290
+ },
291
+ getModelMenuState: () => deps.getModelMenuState(chatId, ctx, threadId),
292
+ buildStatusHtml: () => deps.buildStatusHtml(ctx),
293
+ getActiveModel: () => deps.getActiveModel(ctx),
294
+ getThinkingLevel: deps.getThinkingLevel,
295
+ getQueueItemCount: deps.getQueueItemCount,
296
+ sendStatusMenu: (state, statusHtml, activeModel, thinkingLevel, queueItemCount) => sendTelegramStatusMessage(state, statusHtml, activeModel, thinkingLevel, deps, queueItemCount, deps.sectionRegistry, deps.isVoiceReplyActive?.()),
297
+ storeModelMenuState: deps.storeModelMenuState,
298
+ }),
299
+ openModelMenu: (chatId, replyToMessageId, ctx, threadId) => openTelegramModelMenu({
300
+ isIdle: () => deps.isIdle(ctx),
301
+ canOfferInFlightModelSwitch: () => deps.canOfferInFlightModelSwitch(ctx),
302
+ sendBusyMessage: async () => {
303
+ await deps.sendTextReply(chatId, replyToMessageId, "<b>⏳ Cannot switch model while Pi is busy. Send /abort, /next, or /stop.</b>", { target: { chatId, threadId }, parseMode: "HTML" });
304
+ },
305
+ sendNoModelsMessage: async () => {
306
+ await deps.sendTextReply(chatId, replyToMessageId, "<b>🚫 No available models with configured auth.</b>", { target: { chatId, threadId }, parseMode: "HTML" });
307
+ },
308
+ getModelMenuState: () => deps.getModelMenuState(chatId, ctx, threadId),
309
+ getActiveModel: () => deps.getActiveModel(ctx),
310
+ sendModelMenu: (state, activeModel) => sendTelegramModelMenuMessage(state, activeModel, deps),
311
+ storeModelMenuState: deps.storeModelMenuState,
312
+ }),
313
+ openThinkingMenu: (chatId, _replyToMessageId, ctx) => openTelegramThinkingMenu({
314
+ getModelMenuState: () => deps.getModelMenuState(chatId, ctx),
315
+ getActiveModel: () => deps.getActiveModel(ctx),
316
+ getThinkingLevel: deps.getThinkingLevel,
317
+ storeModelMenuState: deps.storeModelMenuState,
318
+ editInteractiveMessage: deps.editInteractiveMessage,
319
+ sendInteractiveMessage: deps.sendInteractiveMessage,
320
+ isVoiceReplyActive: deps.isVoiceReplyActive,
321
+ }),
322
+ };
323
+ }
@@ -0,0 +1,127 @@
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 type { PendingTelegramTurn } from "./queue.ts";
7
+ export interface MenuModel {
8
+ provider: string;
9
+ id: string;
10
+ name?: string;
11
+ reasoning?: boolean;
12
+ }
13
+ export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
14
+ export interface ScopedTelegramModel<TModel extends MenuModel = MenuModel> {
15
+ model: TModel;
16
+ thinkingLevel?: ThinkingLevel;
17
+ }
18
+ export declare const THINKING_LEVELS: readonly ThinkingLevel[];
19
+ export interface CurrentModelStore<TContext, TModel extends MenuModel = MenuModel> {
20
+ get: (ctx: TContext) => TModel | undefined;
21
+ getStored: () => TModel | undefined;
22
+ set: (model: TModel | undefined) => void;
23
+ }
24
+ export interface CurrentModelUpdateRuntime<TContext, TModel extends MenuModel = MenuModel> {
25
+ setCurrentModel: (model: TModel | undefined, ctx: TContext) => void;
26
+ onModelSelect: (event: {
27
+ model: TModel | undefined;
28
+ }, ctx: TContext) => void;
29
+ }
30
+ export type CurrentModelRuntime<TContext, TModel extends MenuModel = MenuModel> = CurrentModelStore<TContext, TModel> & CurrentModelUpdateRuntime<TContext, TModel>;
31
+ export declare function createCurrentModelStore<TContext, TModel extends MenuModel = MenuModel>(getContextModel: (ctx: TContext) => TModel | undefined): CurrentModelStore<TContext, TModel>;
32
+ export declare function createCurrentModelUpdateRuntime<TContext, TModel extends MenuModel = MenuModel>(deps: {
33
+ setCurrentModel: (model: TModel | undefined) => void;
34
+ updateStatus: (ctx: TContext) => void;
35
+ }): CurrentModelUpdateRuntime<TContext, TModel>;
36
+ export declare function createCurrentModelRuntime<TContext, TModel extends MenuModel = MenuModel>(deps: {
37
+ getContextModel: (ctx: TContext) => TModel | undefined;
38
+ updateStatus: (ctx: TContext) => void;
39
+ }): CurrentModelRuntime<TContext, TModel>;
40
+ export declare function modelsMatch(a: Pick<MenuModel, "provider" | "id"> | undefined, b: Pick<MenuModel, "provider" | "id"> | undefined): boolean;
41
+ export declare function getCanonicalModelId(model: Pick<MenuModel, "provider" | "id">): string;
42
+ export declare function isThinkingLevel(value: string): value is ThinkingLevel;
43
+ export declare function parseTelegramScopedModelPatternList(value: string): string[];
44
+ export declare function parseTelegramCliScopedModelPatterns(args: string[]): string[] | undefined;
45
+ export declare function resolveScopedModelPatterns<TModel extends MenuModel = MenuModel>(patterns: string[], availableModels: TModel[]): ScopedTelegramModel<TModel>[];
46
+ export declare function sortScopedModels<TModel extends MenuModel = MenuModel>(models: ScopedTelegramModel<TModel>[], currentModel: TModel | undefined): ScopedTelegramModel<TModel>[];
47
+ export interface PendingModelSwitchStore<TSelection> {
48
+ get: () => TSelection | undefined;
49
+ set: (selection: TSelection | undefined) => void;
50
+ clear: () => void;
51
+ has: () => boolean;
52
+ }
53
+ export interface TelegramInFlightModelSwitchState {
54
+ isIdle: boolean;
55
+ hasActiveTelegramTurn: boolean;
56
+ hasAbortHandler: boolean;
57
+ }
58
+ export declare function createPendingModelSwitchStore<TSelection>(): PendingModelSwitchStore<TSelection>;
59
+ export declare function canRestartTelegramTurnForModelSwitch(state: TelegramInFlightModelSwitchState): boolean;
60
+ export declare function shouldTriggerPendingTelegramModelSwitchAbort(state: {
61
+ hasPendingModelSwitch: boolean;
62
+ hasActiveTelegramTurn: boolean;
63
+ hasAbortHandler: boolean;
64
+ activeToolExecutions: number;
65
+ }): boolean;
66
+ export declare function restartTelegramModelSwitchContinuation<TTurn, TSelection>(state: {
67
+ activeTurn: TTurn | undefined;
68
+ abort: (() => void) | undefined;
69
+ selection: TSelection;
70
+ queueContinuation: (turn: TTurn, selection: TSelection) => void;
71
+ }): boolean;
72
+ export declare function buildTelegramModelSwitchContinuationText<TModel extends MenuModel>(telegramPrefix: string, model: TModel, thinkingLevel?: ScopedTelegramModel<TModel>["thinkingLevel"]): string;
73
+ export declare function buildTelegramModelSwitchContinuationTurn<TModel extends MenuModel>(options: {
74
+ turn: Pick<PendingTelegramTurn, "chatId" | "replyToMessageId" | "target">;
75
+ selection: ScopedTelegramModel<TModel>;
76
+ telegramPrefix?: string;
77
+ queueOrder: number;
78
+ laneOrder: number;
79
+ }): PendingTelegramTurn;
80
+ export declare function createTelegramModelSwitchContinuationTurnBuilder<TModel extends MenuModel>(deps: {
81
+ telegramPrefix?: string;
82
+ allocateItemOrder: () => number;
83
+ allocateControlOrder: () => number;
84
+ }): (options: {
85
+ turn: Pick<PendingTelegramTurn, "chatId" | "replyToMessageId" | "target">;
86
+ selection: ScopedTelegramModel<TModel>;
87
+ }) => PendingTelegramTurn;
88
+ export declare function createTelegramModelSwitchContinuationQueue<TContext, TSelection extends ScopedTelegramModel>(deps: {
89
+ createContinuationTurn: (options: {
90
+ turn: Pick<PendingTelegramTurn, "chatId" | "replyToMessageId" | "target">;
91
+ selection: TSelection;
92
+ }) => PendingTelegramTurn;
93
+ appendQueuedItem: (item: PendingTelegramTurn, ctx: TContext) => void;
94
+ }): (turn: PendingTelegramTurn, selection: TSelection, ctx: TContext) => void;
95
+ export declare function createTelegramModelSwitchContinuationQueueRuntime<TContext, TSelection extends ScopedTelegramModel>(deps: {
96
+ telegramPrefix?: string;
97
+ allocateItemOrder: () => number;
98
+ allocateControlOrder: () => number;
99
+ appendQueuedItem: (item: PendingTelegramTurn, ctx: TContext) => void;
100
+ }): (turn: PendingTelegramTurn, selection: TSelection, ctx: TContext) => void;
101
+ export interface TelegramModelSwitchControllerDeps<TContext, TSelection> {
102
+ isIdle: (ctx: TContext) => boolean;
103
+ getPendingModelSwitch: () => TSelection | undefined;
104
+ setPendingModelSwitch: (selection: TSelection | undefined) => void;
105
+ getActiveTurn: () => PendingTelegramTurn | undefined;
106
+ getAbortHandler: () => (() => void) | undefined;
107
+ hasAbortHandler: () => boolean;
108
+ getActiveToolExecutions: () => number;
109
+ queueContinuation: (turn: PendingTelegramTurn, selection: TSelection, ctx: TContext) => void;
110
+ updateStatus: (ctx: TContext) => void;
111
+ }
112
+ export interface TelegramModelSwitchController<TContext, TSelection> {
113
+ canOfferInFlightSwitch: (ctx: TContext) => boolean;
114
+ stagePendingSwitch: (selection: TSelection, ctx: TContext) => void;
115
+ clearPendingSwitch: () => void;
116
+ queueContinuation: (turn: PendingTelegramTurn, selection: TSelection, ctx: TContext) => void;
117
+ triggerPendingAbort: (ctx: TContext) => boolean;
118
+ restartInterruptedTurn: (selection: TSelection, ctx: TContext) => boolean;
119
+ }
120
+ export interface TelegramModelSwitchControllerRuntimeDeps<TContext, TSelection extends ScopedTelegramModel> extends Omit<TelegramModelSwitchControllerDeps<TContext, TSelection>, "queueContinuation"> {
121
+ telegramPrefix?: string;
122
+ allocateItemOrder: () => number;
123
+ allocateControlOrder: () => number;
124
+ appendQueuedItem: (item: PendingTelegramTurn, ctx: TContext) => void;
125
+ }
126
+ export declare function createTelegramModelSwitchControllerRuntime<TContext, TSelection extends ScopedTelegramModel>(deps: TelegramModelSwitchControllerRuntimeDeps<TContext, TSelection>): TelegramModelSwitchController<TContext, TSelection>;
127
+ export declare function createTelegramModelSwitchController<TContext, TSelection>(deps: TelegramModelSwitchControllerDeps<TContext, TSelection>): TelegramModelSwitchController<TContext, TSelection>;