@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,165 @@
1
+ /**
2
+ * Telegram Extension Sections registry and callback routing
3
+ * Zones: telegram ui, extension platform, callback routing
4
+ * Owns section registration, global registry binding, token mapping, main-menu/settings row injection, and section callback dispatch
5
+ */
6
+ import { type TelegramInlineKeyboardMarkup } from "./keyboard.ts";
7
+ import type { TelegramInputRichMessage } from "./telegram-api.ts";
8
+ /** @internal */
9
+ export type TelegramSectionId = string;
10
+ /** @internal */
11
+ export type TelegramSectionToken = string;
12
+ /** @internal */
13
+ export type TelegramSectionCallbackResult = "handled" | "pass";
14
+ export interface TelegramSectionView {
15
+ text: string;
16
+ /**
17
+ * Source format for extension-provided section content.
18
+ * Defaults to "html" for explicit Telegram UI markup; use "markdown"
19
+ * when a section naturally owns Markdown content, or "plain" for text.
20
+ */
21
+ parseMode?: "markdown" | "html" | "plain";
22
+ replyMarkup?: TelegramInlineKeyboardMarkup;
23
+ }
24
+ export interface TelegramSectionSettingsRegistration {
25
+ label: string;
26
+ order?: number;
27
+ getLabel?: () => string;
28
+ open: (ctx: TelegramSectionContext) => TelegramSectionView | Promise<TelegramSectionView>;
29
+ handleCallback?: (ctx: TelegramSectionCallbackContext) => TelegramSectionCallbackResult | Promise<TelegramSectionCallbackResult>;
30
+ }
31
+ export interface TelegramSectionRegistration {
32
+ id: TelegramSectionId;
33
+ label: string;
34
+ order?: number;
35
+ getLabel?: () => string;
36
+ render: (ctx: TelegramSectionContext) => TelegramSectionView | Promise<TelegramSectionView>;
37
+ handleCallback?: (ctx: TelegramSectionCallbackContext) => TelegramSectionCallbackResult | Promise<TelegramSectionCallbackResult>;
38
+ settings?: TelegramSectionSettingsRegistration;
39
+ }
40
+ export interface TelegramSectionContext {
41
+ sectionId: string;
42
+ chatId: number;
43
+ messageId?: number;
44
+ answerCallback(text?: string): Promise<void>;
45
+ edit(view: TelegramSectionView): Promise<void>;
46
+ open(view: TelegramSectionView): Promise<void>;
47
+ openRich(message: TelegramInputRichMessage): Promise<void>;
48
+ enqueuePrompt(prompt: string): Promise<void>;
49
+ callbackData(action: string, payload?: string): string;
50
+ /** Delete the message that triggered this callback (dialog cleanup) */
51
+ deleteMessage(): Promise<void>;
52
+ }
53
+ export interface TelegramSectionCallbackContext {
54
+ sectionId: string;
55
+ chatId: number;
56
+ messageId?: number;
57
+ action: string;
58
+ payload: string;
59
+ answerCallback(text?: string): Promise<void>;
60
+ edit(view: TelegramSectionView): Promise<void>;
61
+ open(view: TelegramSectionView): Promise<void>;
62
+ openRich(message: TelegramInputRichMessage): Promise<void>;
63
+ enqueuePrompt(prompt: string): Promise<void>;
64
+ callbackData(action: string, payload?: string): string;
65
+ /** Delete the message that triggered this callback (dialog cleanup) */
66
+ deleteMessage(): Promise<void>;
67
+ }
68
+ /** @internal */
69
+ export interface RegisteredTelegramSection {
70
+ id: TelegramSectionId;
71
+ token: TelegramSectionToken;
72
+ label: string;
73
+ order: number;
74
+ registration: TelegramSectionRegistration;
75
+ }
76
+ /** @internal */
77
+ export interface TelegramSectionDiagnostic {
78
+ id: TelegramSectionId;
79
+ token: TelegramSectionToken;
80
+ label: string;
81
+ status: "active" | "error";
82
+ lastError?: string;
83
+ }
84
+ /** @internal */
85
+ export interface TelegramSectionRegistry {
86
+ register(section: TelegramSectionRegistration): () => void;
87
+ getSections(): RegisteredTelegramSection[];
88
+ getByToken(token: TelegramSectionToken): RegisteredTelegramSection | undefined;
89
+ getDiagnostics(): TelegramSectionDiagnostic[];
90
+ recordError(token: TelegramSectionToken, message: string, source?: string): void;
91
+ clearError(token: TelegramSectionToken, source?: string): void;
92
+ clear(): void;
93
+ }
94
+ /** @internal */
95
+ export interface TelegramSectionMainMenuRow {
96
+ text: string;
97
+ callback_data: string;
98
+ }
99
+ /** @internal */
100
+ export interface TelegramSectionSettingsRow {
101
+ label: string;
102
+ callback_data: string;
103
+ }
104
+ /** @internal */
105
+ export interface TelegramSectionTarget {
106
+ chatId: number;
107
+ threadId?: number;
108
+ }
109
+ /** @internal */
110
+ export interface TelegramSectionRuntimeDeps {
111
+ answerCallbackQuery: (id: string, text?: string) => Promise<void>;
112
+ target?: TelegramSectionTarget;
113
+ editInteractiveMessage: (chatId: number, messageId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramInlineKeyboardMarkup) => Promise<void>;
114
+ sendInteractiveMessage: (chatId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramInlineKeyboardMarkup, options?: {
115
+ target?: TelegramSectionTarget;
116
+ }) => Promise<number | undefined>;
117
+ sendRichMessage: (chatId: number, message: TelegramInputRichMessage, options?: {
118
+ target?: TelegramSectionTarget;
119
+ }) => Promise<number | undefined>;
120
+ enqueuePrompt: (prompt: string) => Promise<void>;
121
+ deleteMessage: (chatId: number, messageId: number) => Promise<void>;
122
+ }
123
+ /** @internal */
124
+ export declare function setGlobalTelegramSectionRegistry(registry: TelegramSectionRegistry): void;
125
+ /** @internal */
126
+ export declare function createAndBindTelegramSectionRegistry(): TelegramSectionRegistry;
127
+ /**
128
+ * Register a Telegram Extension Section from any pi extension.
129
+ * Returns a disposer. Throws if no section registry is active.
130
+ */
131
+ export declare function registerTelegramSection(section: TelegramSectionRegistration): () => void;
132
+ /**
133
+ * Get current section diagnostics. Returns empty array when registry is absent.
134
+ * @internal
135
+ */
136
+ export declare function getTelegramSectionDiagnostics(): TelegramSectionDiagnostic[];
137
+ /** @internal */
138
+ export declare function createTelegramExtensionSectionRegistry(): TelegramSectionRegistry;
139
+ /** @internal */
140
+ export declare function getTelegramExtensionSettingsRows(registry: TelegramSectionRegistry): TelegramSectionSettingsRow[];
141
+ /** @internal */
142
+ export declare function getTelegramSectionMainMenuRows(registry: TelegramSectionRegistry): TelegramSectionMainMenuRow[];
143
+ /** @internal */
144
+ export declare function parseTelegramSectionCallback(data: string): {
145
+ token: string;
146
+ action: string;
147
+ payload: string;
148
+ } | undefined;
149
+ /** @internal */
150
+ export interface TelegramSectionCallbackHandlerDeps {
151
+ answerCallbackQuery: (id: string, text?: string) => Promise<void>;
152
+ target?: TelegramSectionTarget;
153
+ editInteractiveMessage: (chatId: number, messageId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramInlineKeyboardMarkup) => Promise<void>;
154
+ sendInteractiveMessage: (chatId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramInlineKeyboardMarkup, options?: {
155
+ target?: TelegramSectionTarget;
156
+ }) => Promise<number | undefined>;
157
+ sendRichMessage: (chatId: number, message: TelegramInputRichMessage, options?: {
158
+ target?: TelegramSectionTarget;
159
+ }) => Promise<number | undefined>;
160
+ enqueuePrompt: (prompt: string) => Promise<void>;
161
+ deleteMessage: (chatId: number, messageId: number) => Promise<void>;
162
+ }
163
+ export declare function handleTelegramSectionOpen(registry: TelegramSectionRegistry, token: TelegramSectionToken, chatId: number, messageId: number, callbackQueryId: string, deps: TelegramSectionCallbackHandlerDeps): Promise<boolean>;
164
+ export declare function handleTelegramSectionCallback(registry: TelegramSectionRegistry, token: TelegramSectionToken, action: string, payload: string, chatId: number, messageId: number, callbackQueryId: string, deps: TelegramSectionCallbackHandlerDeps): Promise<boolean>;
165
+ export declare function handleTelegramSectionSettingsOpen(registry: TelegramSectionRegistry, token: TelegramSectionToken, chatId: number, messageId: number, callbackQueryId: string, deps: TelegramSectionCallbackHandlerDeps): Promise<boolean>;
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Telegram Extension Sections registry and callback routing
3
+ * Zones: telegram ui, extension platform, callback routing
4
+ * Owns section registration, global registry binding, token mapping, main-menu/settings row injection, and section callback dispatch
5
+ */
6
+ import { assertTelegramCallbackData, } from "./keyboard.js";
7
+ const SECTION_REGISTRY_KEY = "__piTelegramSectionRegistry__";
8
+ function buildTelegramSectionContext(sectionId, token, chatId, messageId, callbackQueryId, deps, backCallback = "menu:back", backLabel = "⬆️ Main menu") {
9
+ return {
10
+ sectionId,
11
+ chatId,
12
+ messageId,
13
+ answerCallback: (text) => callbackQueryId
14
+ ? deps.answerCallbackQuery(callbackQueryId, text)
15
+ : Promise.resolve(),
16
+ edit: (view) => messageId !== undefined
17
+ ? deps.editInteractiveMessage(chatId, messageId, view.text, view.parseMode ?? "html", prependBackRow(view.replyMarkup, backCallback, backLabel))
18
+ : Promise.resolve(),
19
+ open: (view) => deps
20
+ .sendInteractiveMessage(chatId, view.text, view.parseMode ?? "html", view.replyMarkup ?? { inline_keyboard: [] }, deps.target ? { target: deps.target } : undefined)
21
+ .then(() => { }),
22
+ openRich: (message) => deps.sendRichMessage(chatId, message, deps.target ? { target: deps.target } : undefined).then(() => { }),
23
+ enqueuePrompt: deps.enqueuePrompt,
24
+ callbackData: (action, payload) => buildTelegramSectionCallbackData(token, action, payload),
25
+ deleteMessage: () => messageId !== undefined
26
+ ? deps.deleteMessage(chatId, messageId)
27
+ : Promise.resolve(),
28
+ };
29
+ }
30
+ function buildTelegramSectionCallbackContext(sectionId, token, chatId, messageId, action, payload, callbackQueryId, deps, backCallback = "menu:back", backLabel = "⬆️ Back") {
31
+ return {
32
+ sectionId,
33
+ chatId,
34
+ messageId,
35
+ action,
36
+ payload,
37
+ answerCallback: (text) => deps.answerCallbackQuery(callbackQueryId, text),
38
+ edit: (view) => messageId !== undefined
39
+ ? deps.editInteractiveMessage(chatId, messageId, view.text, view.parseMode ?? "html", prependBackRow(view.replyMarkup, backCallback, backLabel))
40
+ : Promise.resolve(),
41
+ open: (view) => deps
42
+ .sendInteractiveMessage(chatId, view.text, view.parseMode ?? "html", view.replyMarkup ?? { inline_keyboard: [] }, deps.target ? { target: deps.target } : undefined)
43
+ .then(() => { }),
44
+ openRich: (message) => deps.sendRichMessage(chatId, message, deps.target ? { target: deps.target } : undefined).then(() => { }),
45
+ enqueuePrompt: deps.enqueuePrompt,
46
+ callbackData: (action, payload) => buildTelegramSectionCallbackData(token, action, payload),
47
+ deleteMessage: () => messageId !== undefined
48
+ ? deps.deleteMessage(chatId, messageId)
49
+ : Promise.resolve(),
50
+ };
51
+ }
52
+ // --- GlobalThis Bridge ---
53
+ /** @internal */
54
+ export function setGlobalTelegramSectionRegistry(registry) {
55
+ globalThis[SECTION_REGISTRY_KEY] = registry;
56
+ }
57
+ /** @internal */
58
+ export function createAndBindTelegramSectionRegistry() {
59
+ const registry = createTelegramExtensionSectionRegistry();
60
+ setGlobalTelegramSectionRegistry(registry);
61
+ return registry;
62
+ }
63
+ /**
64
+ * Register a Telegram Extension Section from any pi extension.
65
+ * Returns a disposer. Throws if no section registry is active.
66
+ */
67
+ export function registerTelegramSection(section) {
68
+ const registry = globalThis[SECTION_REGISTRY_KEY];
69
+ if (!registry) {
70
+ throw new Error("Telegram section registry not available. Is pi-telegram loaded and initialized?");
71
+ }
72
+ return registry.register(section);
73
+ }
74
+ /**
75
+ * Get current section diagnostics. Returns empty array when registry is absent.
76
+ * @internal
77
+ */
78
+ export function getTelegramSectionDiagnostics() {
79
+ const registry = globalThis[SECTION_REGISTRY_KEY];
80
+ return registry ? registry.getDiagnostics() : [];
81
+ }
82
+ // --- Registry ---
83
+ const BACK_NAV_ROW = {
84
+ text: "⬆️ Back",
85
+ };
86
+ function sectionErrorMessage(error) {
87
+ return error instanceof Error ? error.message : String(error);
88
+ }
89
+ function buildTelegramSectionCallbackData(token, action, payload) {
90
+ const data = payload
91
+ ? `section:${token}:${action}:${payload}`
92
+ : `section:${token}:${action}`;
93
+ return assertTelegramCallbackData(data, "Telegram section callback_data");
94
+ }
95
+ function prependBackRow(replyMarkup, backCallback, backLabel) {
96
+ const backRow = {
97
+ text: backLabel ?? BACK_NAV_ROW.text,
98
+ callback_data: backCallback,
99
+ };
100
+ if (!replyMarkup || replyMarkup.inline_keyboard.length === 0) {
101
+ return { inline_keyboard: [[backRow]] };
102
+ }
103
+ const firstRow = replyMarkup.inline_keyboard[0];
104
+ const firstIsBack = firstRow.length === 1 && firstRow[0].callback_data === backCallback;
105
+ if (firstIsBack)
106
+ return replyMarkup;
107
+ return {
108
+ inline_keyboard: [[backRow], ...replyMarkup.inline_keyboard],
109
+ };
110
+ }
111
+ /** @internal */
112
+ export function createTelegramExtensionSectionRegistry() {
113
+ const sections = new Map();
114
+ const errors = new Map();
115
+ let nextToken = 0;
116
+ const register = (section) => {
117
+ const duplicate = [...sections.values()].find((s) => s.id === section.id);
118
+ if (duplicate) {
119
+ throw new Error(`Telegram section id already registered: ${section.id}`);
120
+ }
121
+ const token = String(nextToken++);
122
+ const registered = {
123
+ id: section.id,
124
+ token,
125
+ label: section.label,
126
+ order: section.order ?? 0,
127
+ registration: section,
128
+ };
129
+ sections.set(token, registered);
130
+ return () => {
131
+ sections.delete(token);
132
+ errors.delete(token);
133
+ };
134
+ };
135
+ const getSections = () => {
136
+ return [...sections.values()].sort((a, b) => {
137
+ if (a.order !== b.order)
138
+ return a.order - b.order;
139
+ return a.id.localeCompare(b.id);
140
+ });
141
+ };
142
+ const getByToken = (token) => {
143
+ return sections.get(token);
144
+ };
145
+ const getDiagnostics = () => {
146
+ return getSections().map((s) => ({
147
+ id: s.id,
148
+ token: s.token,
149
+ label: s.label,
150
+ status: errors.has(s.token) ? "error" : "active",
151
+ lastError: errors.get(s.token)?.message,
152
+ }));
153
+ };
154
+ const recordError = (token, message, source = "runtime") => {
155
+ if (sections.has(token))
156
+ errors.set(token, { message, source });
157
+ };
158
+ const clearError = (token, source) => {
159
+ const current = errors.get(token);
160
+ if (!source || !current || current.source === source)
161
+ errors.delete(token);
162
+ };
163
+ const clear = () => {
164
+ sections.clear();
165
+ errors.clear();
166
+ nextToken = 0;
167
+ };
168
+ return {
169
+ register,
170
+ getSections,
171
+ getByToken,
172
+ getDiagnostics,
173
+ recordError,
174
+ clearError,
175
+ clear,
176
+ };
177
+ }
178
+ /** @internal */
179
+ export function getTelegramExtensionSettingsRows(registry) {
180
+ const rows = [];
181
+ for (const section of registry
182
+ .getSections()
183
+ .filter((s) => s.registration.settings)
184
+ .sort((a, b) => {
185
+ const orderA = a.registration.settings.order ?? 0;
186
+ const orderB = b.registration.settings.order ?? 0;
187
+ if (orderA !== orderB)
188
+ return orderA - orderB;
189
+ return a.id.localeCompare(b.id);
190
+ })) {
191
+ try {
192
+ rows.push({
193
+ label: section.registration.settings.getLabel?.() ??
194
+ section.registration.settings.label,
195
+ callback_data: `section:${section.token}:settings:open`,
196
+ });
197
+ registry.clearError(section.token, "settings_label");
198
+ }
199
+ catch (error) {
200
+ registry.recordError(section.token, sectionErrorMessage(error), "settings_label");
201
+ }
202
+ }
203
+ return rows;
204
+ }
205
+ /** @internal */
206
+ export function getTelegramSectionMainMenuRows(registry) {
207
+ const rows = [];
208
+ for (const section of registry.getSections()) {
209
+ try {
210
+ rows.push({
211
+ text: section.registration.getLabel?.() ?? section.label,
212
+ callback_data: `section:${section.token}:open`,
213
+ });
214
+ registry.clearError(section.token, "main_label");
215
+ }
216
+ catch (error) {
217
+ registry.recordError(section.token, sectionErrorMessage(error), "main_label");
218
+ }
219
+ }
220
+ return rows;
221
+ }
222
+ /** @internal */
223
+ export function parseTelegramSectionCallback(data) {
224
+ if (!data.startsWith("section:"))
225
+ return undefined;
226
+ const rest = data.slice("section:".length);
227
+ const firstColon = rest.indexOf(":");
228
+ if (firstColon === -1)
229
+ return undefined;
230
+ const token = rest.slice(0, firstColon);
231
+ const afterToken = rest.slice(firstColon + 1);
232
+ const secondColon = afterToken.indexOf(":");
233
+ if (secondColon === -1) {
234
+ return { token, action: afterToken, payload: "" };
235
+ }
236
+ return {
237
+ token,
238
+ action: afterToken.slice(0, secondColon),
239
+ payload: afterToken.slice(secondColon + 1),
240
+ };
241
+ }
242
+ export async function handleTelegramSectionOpen(registry, token, chatId, messageId, callbackQueryId, deps) {
243
+ const section = registry.getByToken(token);
244
+ if (!section) {
245
+ await deps.answerCallbackQuery(callbackQueryId, "This section is no longer available.");
246
+ return true;
247
+ }
248
+ try {
249
+ const sectionCtx = buildTelegramSectionContext(section.id, token, chatId, messageId, callbackQueryId, deps);
250
+ const view = await section.registration.render(sectionCtx);
251
+ const viewWithBack = {
252
+ ...view,
253
+ replyMarkup: prependBackRow(view.replyMarkup, "menu:back", "⬆️ Main menu"),
254
+ };
255
+ await deps.editInteractiveMessage(chatId, messageId, viewWithBack.text, viewWithBack.parseMode ?? "html", viewWithBack.replyMarkup);
256
+ await deps.answerCallbackQuery(callbackQueryId);
257
+ registry.clearError(token, "render");
258
+ return true;
259
+ }
260
+ catch (error) {
261
+ const message = sectionErrorMessage(error);
262
+ registry.recordError(token, message, "render");
263
+ await deps.answerCallbackQuery(callbackQueryId, `Section error: ${message}`);
264
+ return true;
265
+ }
266
+ }
267
+ export async function handleTelegramSectionCallback(registry, token, action, payload, chatId, messageId, callbackQueryId, deps) {
268
+ const section = registry.getByToken(token);
269
+ if (!section) {
270
+ await deps.answerCallbackQuery(callbackQueryId, "This section is no longer available.");
271
+ return true;
272
+ }
273
+ // Try main handleCallback first, then settings handleCallback as fallback.
274
+ const mainHandler = section.registration.handleCallback;
275
+ const settingsHandler = section.registration.settings?.handleCallback;
276
+ const handler = mainHandler ?? settingsHandler;
277
+ if (!handler) {
278
+ await deps.answerCallbackQuery(callbackQueryId);
279
+ return true;
280
+ }
281
+ try {
282
+ const ctx = buildTelegramSectionCallbackContext(section.id, token, chatId, messageId, action, payload, callbackQueryId, deps, mainHandler ? `section:${token}:open` : "settings:list");
283
+ let result = await handler(ctx);
284
+ // Fallback: if main handler passed and settings handler exists, try settings
285
+ // with the correct navigation context (back → settings list).
286
+ if (result === "pass" && mainHandler && settingsHandler) {
287
+ const settingsCtx = buildTelegramSectionCallbackContext(section.id, token, chatId, messageId, action, payload, callbackQueryId, deps, "settings:list");
288
+ result = await settingsHandler(settingsCtx);
289
+ }
290
+ if (result === "pass") {
291
+ await deps.answerCallbackQuery(callbackQueryId);
292
+ }
293
+ registry.clearError(token, "callback");
294
+ return true;
295
+ }
296
+ catch (error) {
297
+ const message = sectionErrorMessage(error);
298
+ registry.recordError(token, message, "callback");
299
+ await deps.answerCallbackQuery(callbackQueryId, `Section error: ${message}`);
300
+ return true;
301
+ }
302
+ }
303
+ export async function handleTelegramSectionSettingsOpen(registry, token, chatId, messageId, callbackQueryId, deps) {
304
+ const section = registry.getByToken(token);
305
+ if (!section || !section.registration.settings) {
306
+ await deps.answerCallbackQuery(callbackQueryId, "This section is no longer available.");
307
+ return true;
308
+ }
309
+ try {
310
+ const sectionCtx = buildTelegramSectionContext(section.id, token, chatId, messageId, callbackQueryId, deps, "settings:list", "⬆️ Back");
311
+ const view = await section.registration.settings.open(sectionCtx);
312
+ const viewWithBack = {
313
+ ...view,
314
+ replyMarkup: prependBackRow(view.replyMarkup, "settings:list", "⬆️ Back"),
315
+ };
316
+ await deps.editInteractiveMessage(chatId, messageId, viewWithBack.text, viewWithBack.parseMode ?? "html", viewWithBack.replyMarkup);
317
+ await deps.answerCallbackQuery(callbackQueryId);
318
+ registry.clearError(token, "settings_open");
319
+ return true;
320
+ }
321
+ catch (error) {
322
+ const message = sectionErrorMessage(error);
323
+ registry.recordError(token, message, "settings_open");
324
+ await deps.answerCallbackQuery(callbackQueryId, `Section error: ${message}`);
325
+ return true;
326
+ }
327
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Telegram setup prompt helpers
3
+ * Zones: pi agent command ui, telegram config
4
+ * Computes token-prefill defaults and prompt mode selection for /telegram-setup
5
+ */
6
+ export interface TelegramSetupConfig {
7
+ botToken?: string;
8
+ botId?: number;
9
+ botUsername?: string;
10
+ allowedUserId?: number;
11
+ }
12
+ export interface TelegramBotTokenPromptSpec {
13
+ method: "input" | "editor";
14
+ value: string;
15
+ }
16
+ export interface TelegramSetupUser {
17
+ id: number;
18
+ username?: string;
19
+ }
20
+ export interface TelegramPollingStartResult {
21
+ ok: boolean;
22
+ message?: string;
23
+ }
24
+ export type TelegramSetupCompletion = {
25
+ status: "success";
26
+ config: TelegramSetupConfig;
27
+ } | {
28
+ status: "cancelled" | "unavailable" | "busy" | "validation-failed";
29
+ } | {
30
+ status: "polling-failed";
31
+ config: TelegramSetupConfig;
32
+ };
33
+ export interface TelegramSetupDeps {
34
+ hasUI: boolean;
35
+ env: NodeJS.ProcessEnv;
36
+ config: TelegramSetupConfig;
37
+ promptInput: (label: string, value: string) => Promise<string | undefined>;
38
+ promptEditor: (label: string, value: string) => Promise<string | undefined>;
39
+ getMe: (botToken: string) => Promise<{
40
+ ok: boolean;
41
+ result?: TelegramSetupUser;
42
+ description?: string;
43
+ }>;
44
+ /** Resolve a submitted literal token or `$NAME`/`${NAME}` reference. */
45
+ resolveBotToken?: (value: string) => string | undefined;
46
+ /** Redacted diagnostic for an unresolved or malformed token reference. */
47
+ describeBotToken?: (value: string) => string | undefined;
48
+ persistConfig: (config: TelegramSetupConfig) => Promise<void>;
49
+ notify: (message: string, level: "info" | "error") => void;
50
+ startPolling: () => unknown | Promise<unknown>;
51
+ updateStatus: () => void;
52
+ }
53
+ export interface TelegramSetupPromptContext {
54
+ hasUI: boolean;
55
+ ui: {
56
+ input: (label: string, value: string) => Promise<string | undefined>;
57
+ editor: (label: string, value: string) => Promise<string | undefined>;
58
+ notify: (message: string, level: "info" | "error") => void;
59
+ };
60
+ }
61
+ export interface TelegramSetupGuard {
62
+ start: () => boolean;
63
+ finish: () => void;
64
+ }
65
+ export interface TelegramSetupPromptRuntimeDeps<TContext extends TelegramSetupPromptContext> {
66
+ env?: NodeJS.ProcessEnv;
67
+ getConfig: () => TelegramSetupConfig;
68
+ setConfig: (config: TelegramSetupConfig) => void;
69
+ setupGuard: TelegramSetupGuard;
70
+ getMe: TelegramSetupDeps["getMe"];
71
+ resolveBotToken?: TelegramSetupDeps["resolveBotToken"];
72
+ describeBotToken?: TelegramSetupDeps["describeBotToken"];
73
+ persistConfig: (config: TelegramSetupConfig) => Promise<void>;
74
+ startPolling: (ctx: TContext) => unknown | Promise<unknown>;
75
+ updateStatus: (ctx: TContext) => void;
76
+ recordRuntimeEvent?: (category: string, error: unknown, details?: Record<string, unknown>) => void;
77
+ }
78
+ export declare const TELEGRAM_BOT_TOKEN_INPUT_PLACEHOLDER = "123456:ABCDEF...";
79
+ export declare function getTelegramBotTokenInputDefault(env?: NodeJS.ProcessEnv, configToken?: string): string;
80
+ export declare function getTelegramBotTokenPromptSpec(env?: NodeJS.ProcessEnv, configToken?: string): TelegramBotTokenPromptSpec;
81
+ export declare function runTelegramSetup(deps: TelegramSetupDeps): Promise<TelegramSetupCompletion>;
82
+ export declare function createTelegramSetupPromptRuntime<TContext extends TelegramSetupPromptContext>(deps: TelegramSetupPromptRuntimeDeps<TContext>): (ctx: TContext) => Promise<TelegramSetupCompletion>;