@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,678 @@
1
+ /**
2
+ * Telegram outbound markup parsing helpers
3
+ * Zones: telegram outbound, assistant markup
4
+ * Owns top-level assistant action comment extraction, attribute parsing, and markup stripping shared by voice and outbound delivery
5
+ */
6
+ function getMarkdownLineEnd(markdown, offset) {
7
+ const newlineIndex = markdown.indexOf("\n", offset);
8
+ return newlineIndex === -1 ? markdown.length : newlineIndex + 1;
9
+ }
10
+ function getMarkdownLineText(markdown, offset, end) {
11
+ return markdown.slice(offset, end).replace(/\r?\n$/, "");
12
+ }
13
+ function getTopLevelOpeningFence(line) {
14
+ const match = line.match(/^(?: {0,3})(`{3,}|~{3,})/);
15
+ const sequence = match?.[1];
16
+ if (!sequence)
17
+ return undefined;
18
+ return {
19
+ marker: sequence[0],
20
+ length: sequence.length,
21
+ };
22
+ }
23
+ function isTopLevelClosingFence(line, fence) {
24
+ const match = line.match(/^(?: {0,3})(`{3,}|~{3,})([ \t]*)$/);
25
+ const sequence = match?.[1];
26
+ return (!!sequence &&
27
+ sequence[0] === fence.marker &&
28
+ sequence.length >= fence.length);
29
+ }
30
+ export function collectTopLevelHtmlComments(markdown) {
31
+ const comments = [];
32
+ let offset = 0;
33
+ let fence;
34
+ while (offset < markdown.length) {
35
+ const lineEnd = getMarkdownLineEnd(markdown, offset);
36
+ const line = getMarkdownLineText(markdown, offset, lineEnd);
37
+ if (fence) {
38
+ if (isTopLevelClosingFence(line, fence))
39
+ fence = undefined;
40
+ offset = lineEnd;
41
+ continue;
42
+ }
43
+ const nextFence = getTopLevelOpeningFence(line);
44
+ if (nextFence) {
45
+ fence = nextFence;
46
+ offset = lineEnd;
47
+ continue;
48
+ }
49
+ if (line.startsWith("<!--")) {
50
+ const closeIndex = markdown.indexOf("-->", offset + 4);
51
+ if (closeIndex === -1)
52
+ return { comments, openCommentStart: offset };
53
+ const end = closeIndex + 3;
54
+ const raw = markdown.slice(offset, end);
55
+ const content = raw.slice(4, -3);
56
+ comments.push({ raw, content, start: offset, end });
57
+ offset = getMarkdownLineEnd(markdown, end);
58
+ continue;
59
+ }
60
+ offset = lineEnd;
61
+ }
62
+ return { comments };
63
+ }
64
+ export function replaceTelegramButtonFences(markdown, replace) {
65
+ let result = "";
66
+ let copied = 0;
67
+ let offset = 0;
68
+ let fence;
69
+ let actionStart;
70
+ let contentStart = 0;
71
+ while (offset < markdown.length) {
72
+ const end = getMarkdownLineEnd(markdown, offset);
73
+ const line = getMarkdownLineText(markdown, offset, end);
74
+ if (fence) {
75
+ if (isTopLevelClosingFence(line, fence)) {
76
+ if (actionStart !== undefined) {
77
+ result += markdown.slice(copied, actionStart);
78
+ result += replace(markdown.slice(contentStart, offset), true) + "\n";
79
+ copied = end;
80
+ actionStart = undefined;
81
+ }
82
+ fence = undefined;
83
+ }
84
+ }
85
+ else if (line.includes("<!--")) {
86
+ const close = markdown.indexOf("-->", offset + line.indexOf("<!--") + 4);
87
+ if (close < 0)
88
+ break;
89
+ offset = getMarkdownLineEnd(markdown, close + 3);
90
+ continue;
91
+ }
92
+ else {
93
+ fence = getTopLevelOpeningFence(line);
94
+ if (fence && /^```telegram_button[ \t]*$/.test(line)) {
95
+ actionStart = offset;
96
+ contentStart = end;
97
+ }
98
+ }
99
+ offset = end;
100
+ }
101
+ if (actionStart !== undefined) {
102
+ result += markdown.slice(copied, actionStart);
103
+ return result + replace(markdown.slice(contentStart), false);
104
+ }
105
+ return result + markdown.slice(copied);
106
+ }
107
+ export function replaceTopLevelHtmlComments(markdown, replacer) {
108
+ const { comments } = collectTopLevelHtmlComments(markdown);
109
+ if (comments.length === 0)
110
+ return markdown;
111
+ let result = "";
112
+ let offset = 0;
113
+ for (const comment of comments) {
114
+ result += markdown.slice(offset, comment.start);
115
+ result += replacer(comment);
116
+ offset = comment.end;
117
+ }
118
+ return result + markdown.slice(offset);
119
+ }
120
+ export function findTopLevelOpenOrPartialHtmlCommentIndex(markdown) {
121
+ const { openCommentStart } = collectTopLevelHtmlComments(markdown);
122
+ if (openCommentStart !== undefined)
123
+ return openCommentStart;
124
+ let offset = 0;
125
+ let fence;
126
+ while (offset < markdown.length) {
127
+ const lineEnd = getMarkdownLineEnd(markdown, offset);
128
+ const line = getMarkdownLineText(markdown, offset, lineEnd);
129
+ const isLastLine = lineEnd >= markdown.length;
130
+ if (fence) {
131
+ if (isTopLevelClosingFence(line, fence))
132
+ fence = undefined;
133
+ offset = lineEnd;
134
+ continue;
135
+ }
136
+ const nextFence = getTopLevelOpeningFence(line);
137
+ if (nextFence) {
138
+ fence = nextFence;
139
+ offset = lineEnd;
140
+ continue;
141
+ }
142
+ if (isLastLine && (line === "<" || line === "<!" || line === "<!-")) {
143
+ return offset;
144
+ }
145
+ offset = lineEnd;
146
+ }
147
+ return -1;
148
+ }
149
+ export function parseTopLevelTelegramComment(comment, command) {
150
+ let normalizedContent = comment.content.replace(/^\s+/, "");
151
+ normalizedContent = normalizedContent.replace(/^!/, "");
152
+ const [rawHead = "", ...bodyLines] = normalizedContent.split(/\r?\n/);
153
+ let head = rawHead.trimStart();
154
+ if (!head.startsWith(command))
155
+ return undefined;
156
+ const nextChar = head[command.length];
157
+ if (nextChar !== undefined && !/\s|:/.test(nextChar))
158
+ return undefined;
159
+ return {
160
+ head: head.slice(command.length),
161
+ ...(bodyLines.length > 0 ? { body: bodyLines.join("\n") } : {}),
162
+ };
163
+ }
164
+ function parseTolerantTelegramAttributes(source, names) {
165
+ const attributes = {};
166
+ const namePattern = names.join("|");
167
+ const pattern = new RegExp(`\\b(${namePattern})\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s]+))`, "gu");
168
+ for (const match of source.matchAll(pattern)) {
169
+ const value = (match[2] ?? match[3] ?? match[4] ?? "").trim();
170
+ if (value)
171
+ attributes[match[1]] = value;
172
+ }
173
+ return Object.keys(attributes).length > 0 ? attributes : undefined;
174
+ }
175
+ function isTelegramActionPayload(value) {
176
+ return value !== null && typeof value === "object" && !Array.isArray(value);
177
+ }
178
+ function removeTelegramJsonTrailingCommas(source) {
179
+ let normalized = "";
180
+ let inString = false;
181
+ let escaped = false;
182
+ for (let offset = 0; offset < source.length; offset += 1) {
183
+ const character = source[offset];
184
+ if (inString) {
185
+ normalized += character;
186
+ if (escaped)
187
+ escaped = false;
188
+ else if (character === "\\")
189
+ escaped = true;
190
+ else if (character === '"')
191
+ inString = false;
192
+ continue;
193
+ }
194
+ if (character === '"') {
195
+ inString = true;
196
+ normalized += character;
197
+ continue;
198
+ }
199
+ if (character === ",") {
200
+ let next = offset + 1;
201
+ while (/\s/u.test(source[next] ?? ""))
202
+ next += 1;
203
+ if (source[next] === "}" || source[next] === "]")
204
+ continue;
205
+ }
206
+ normalized += character;
207
+ }
208
+ return normalized;
209
+ }
210
+ function parseTelegramJsonObjectCandidate(source) {
211
+ const normalized = removeTelegramJsonTrailingCommas(source);
212
+ for (const candidate of normalized === source ? [source] : [source, normalized]) {
213
+ try {
214
+ const value = JSON.parse(candidate);
215
+ if (isTelegramActionPayload(value))
216
+ return value;
217
+ }
218
+ catch {
219
+ // Try the bounded trailing-comma normalization before rejecting JSON.
220
+ }
221
+ }
222
+ return undefined;
223
+ }
224
+ function looksLikeTelegramNamedJsonObject(source, offset) {
225
+ return /^\{\s*"(?:[^"\\]|\\.)*"\s*:/u.test(source.slice(offset));
226
+ }
227
+ export function parseTelegramActionPayload(comment, command) {
228
+ let content = comment.content.replace(/^\s+/, "").replace(/^!/, "");
229
+ if (!content.startsWith(command))
230
+ return undefined;
231
+ content = content.slice(command.length);
232
+ let attributeEnvelope = content;
233
+ for (let offset = 0; offset < content.length; offset += 1) {
234
+ if (content[offset] !== "{" && content[offset] !== "[")
235
+ continue;
236
+ if (content[offset] === "[" &&
237
+ !isPlausibleTelegramMatrixStart(content, offset)) {
238
+ const noiseEnd = findTelegramStructuredPayloadEnd(content, offset);
239
+ if (noiseEnd !== undefined) {
240
+ attributeEnvelope = `${attributeEnvelope.slice(0, offset)}${" ".repeat(noiseEnd - offset)}${attributeEnvelope.slice(noiseEnd)}`;
241
+ offset = noiseEnd - 1;
242
+ }
243
+ continue;
244
+ }
245
+ if (content[offset] === "{") {
246
+ const parsed = parseTelegramAdaptiveActionPayloadRows(content.slice(offset), parseTelegramVoiceCompactActionPayload, { allowTrailing: true });
247
+ if (parsed)
248
+ return parsed.rows[0][0];
249
+ }
250
+ const end = findTelegramStructuredPayloadEnd(content, offset);
251
+ if (end === undefined)
252
+ continue;
253
+ attributeEnvelope = `${attributeEnvelope.slice(0, offset)}${" ".repeat(end - offset)}${attributeEnvelope.slice(end)}`;
254
+ offset = end - 1;
255
+ }
256
+ return parseTolerantTelegramAttributes(attributeEnvelope, [
257
+ "text",
258
+ "value",
259
+ "lang",
260
+ "rate",
261
+ ]);
262
+ }
263
+ const TELEGRAM_COMPACT_ACTION_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
264
+ function parseTelegramButtonCompactActionPayload(atoms) {
265
+ const [label, prompt, selectedStyle, disabled] = atoms;
266
+ if (atoms.length === 1)
267
+ return label ? { value: label } : undefined;
268
+ const isDisabled = disabled === "1" || disabled === "true";
269
+ if (!prompt && !(atoms.length === 4 && isDisabled))
270
+ return undefined;
271
+ const action = label ? { label, prompt } : { prompt };
272
+ if (atoms.length === 2)
273
+ return action;
274
+ if (selectedStyle !== "primary" &&
275
+ selectedStyle !== "success" &&
276
+ selectedStyle !== "danger" &&
277
+ !(atoms.length === 4 && selectedStyle === ""))
278
+ return undefined;
279
+ if (atoms.length === 4 && !isDisabled && disabled !== "0" && disabled !== "false") {
280
+ return undefined;
281
+ }
282
+ return {
283
+ ...action,
284
+ ...(selectedStyle ? { selected_style: selectedStyle } : {}),
285
+ ...(atoms.length === 4 ? { disabled: isDisabled } : {}),
286
+ };
287
+ }
288
+ function parseTelegramVoiceCompactActionPayload(atoms) {
289
+ const [text, lang, rate] = atoms;
290
+ if (!text)
291
+ return undefined;
292
+ if (atoms.length === 1)
293
+ return { text };
294
+ if (!lang)
295
+ return undefined;
296
+ if (atoms.length === 2)
297
+ return { text, lang };
298
+ if (!rate)
299
+ return undefined;
300
+ return { text, lang, rate };
301
+ }
302
+ function parseTelegramAdaptiveActionPayloadRows(source, parseCompactPayload, options = {}) {
303
+ let offset = 0;
304
+ const isStructuralWhitespace = (character) => character === " " ||
305
+ character === "\t" ||
306
+ character === "\r" ||
307
+ character === "\n";
308
+ const skipWhitespace = () => {
309
+ while (isStructuralWhitespace(source[offset]))
310
+ offset += 1;
311
+ };
312
+ const consumeOptionalSeparator = () => {
313
+ skipWhitespace();
314
+ if (source[offset] !== ",")
315
+ return true;
316
+ offset += 1;
317
+ skipWhitespace();
318
+ return source[offset] !== ",";
319
+ };
320
+ const normalizeAtom = (value) => {
321
+ const normalized = value.trim();
322
+ return !TELEGRAM_COMPACT_ACTION_CONTROL_PATTERN.test(normalized)
323
+ ? normalized
324
+ : undefined;
325
+ };
326
+ const parseCompactCell = () => {
327
+ if (source[offset] !== "{")
328
+ return undefined;
329
+ offset += 1;
330
+ const atomSources = [[]];
331
+ while (offset < source.length) {
332
+ const character = source[offset];
333
+ if (character === "\\") {
334
+ const escaped = source[offset + 1];
335
+ if (escaped !== "|" && escaped !== "}" && escaped !== "\\") {
336
+ return undefined;
337
+ }
338
+ atomSources.at(-1).push(escaped);
339
+ offset += 2;
340
+ continue;
341
+ }
342
+ if (character === "|") {
343
+ if (atomSources.length >= (options.maxCompactAtoms ?? 3))
344
+ return undefined;
345
+ atomSources.push([]);
346
+ offset += 1;
347
+ continue;
348
+ }
349
+ if (character === "}") {
350
+ offset += 1;
351
+ const atoms = atomSources.map((atom) => normalizeAtom(atom.join("")));
352
+ if (atoms.some((atom) => atom === undefined))
353
+ return undefined;
354
+ return parseCompactPayload(atoms);
355
+ }
356
+ atomSources.at(-1).push(character);
357
+ offset += 1;
358
+ }
359
+ return undefined;
360
+ };
361
+ const parseJsonObjectCell = () => {
362
+ if (source[offset] !== "{")
363
+ return undefined;
364
+ const start = offset;
365
+ const stack = [];
366
+ let inString = false;
367
+ let escaped = false;
368
+ for (let index = start; index < source.length; index += 1) {
369
+ const character = source[index];
370
+ if (inString) {
371
+ if (escaped)
372
+ escaped = false;
373
+ else if (character === "\\")
374
+ escaped = true;
375
+ else if (character === '"')
376
+ inString = false;
377
+ continue;
378
+ }
379
+ if (character === '"') {
380
+ inString = true;
381
+ continue;
382
+ }
383
+ if (character === "{" || character === "[") {
384
+ stack.push(character);
385
+ continue;
386
+ }
387
+ if (character !== "}" && character !== "]")
388
+ continue;
389
+ const opening = stack.pop();
390
+ if ((character === "}" && opening !== "{") ||
391
+ (character === "]" && opening !== "["))
392
+ return undefined;
393
+ if (stack.length > 0)
394
+ continue;
395
+ const candidate = source.slice(start, index + 1);
396
+ const value = parseTelegramJsonObjectCandidate(candidate);
397
+ if (!value)
398
+ return undefined;
399
+ offset = index + 1;
400
+ return value;
401
+ }
402
+ return undefined;
403
+ };
404
+ const parseCell = () => {
405
+ const start = offset;
406
+ const jsonCell = parseJsonObjectCell();
407
+ if (jsonCell)
408
+ return jsonCell;
409
+ offset = start;
410
+ if (looksLikeTelegramNamedJsonObject(source, start))
411
+ return undefined;
412
+ return parseCompactCell();
413
+ };
414
+ const parseRow = () => {
415
+ if (source[offset] !== "[")
416
+ return undefined;
417
+ offset += 1;
418
+ const row = [];
419
+ while (offset < source.length) {
420
+ skipWhitespace();
421
+ if (source[offset] === "]") {
422
+ offset += 1;
423
+ return row.length > 0 ? row : undefined;
424
+ }
425
+ if (source[offset] !== "{")
426
+ return undefined;
427
+ const cell = parseCell();
428
+ if (!cell)
429
+ return undefined;
430
+ row.push(cell);
431
+ if (!consumeOptionalSeparator())
432
+ return undefined;
433
+ }
434
+ return undefined;
435
+ };
436
+ const parseMatrix = () => {
437
+ if (source[offset] !== "[")
438
+ return undefined;
439
+ offset += 1;
440
+ const rows = [];
441
+ while (offset < source.length) {
442
+ skipWhitespace();
443
+ const character = source[offset];
444
+ if (character === "]") {
445
+ offset += 1;
446
+ return rows.length > 0 ? rows : undefined;
447
+ }
448
+ if (character === "{") {
449
+ const cell = parseCell();
450
+ if (!cell)
451
+ return undefined;
452
+ rows.push([cell]);
453
+ }
454
+ else if (character === "[") {
455
+ const row = parseRow();
456
+ if (!row)
457
+ return undefined;
458
+ rows.push(row);
459
+ }
460
+ else {
461
+ return undefined;
462
+ }
463
+ if (!consumeOptionalSeparator())
464
+ return undefined;
465
+ }
466
+ return undefined;
467
+ };
468
+ const parseVerticalSequence = () => {
469
+ const rows = [];
470
+ while (offset < source.length) {
471
+ skipWhitespace();
472
+ if (source[offset] !== "{")
473
+ break;
474
+ const cell = parseCell();
475
+ if (!cell)
476
+ return undefined;
477
+ rows.push([cell]);
478
+ if (!consumeOptionalSeparator())
479
+ return undefined;
480
+ }
481
+ return rows.length > 0 ? rows : undefined;
482
+ };
483
+ skipWhitespace();
484
+ const rows = source[offset] === "{"
485
+ ? parseVerticalSequence()
486
+ : parseMatrix();
487
+ if (!rows)
488
+ return undefined;
489
+ skipWhitespace();
490
+ if (!options.allowTrailing && offset !== source.length)
491
+ return undefined;
492
+ return { rows, end: offset };
493
+ }
494
+ function findTelegramStructuredPayloadEnd(source, start) {
495
+ const stack = [source[start]];
496
+ let inString = false;
497
+ let escaped = false;
498
+ for (let offset = start + 1; offset < source.length; offset += 1) {
499
+ const character = source[offset];
500
+ if (inString) {
501
+ if (escaped)
502
+ escaped = false;
503
+ else if (character === "\\")
504
+ escaped = true;
505
+ else if (character === '"')
506
+ inString = false;
507
+ continue;
508
+ }
509
+ if (character === '"') {
510
+ inString = true;
511
+ continue;
512
+ }
513
+ if (character === "[" || character === "{") {
514
+ stack.push(character);
515
+ continue;
516
+ }
517
+ if (character !== "]" && character !== "}")
518
+ continue;
519
+ const expected = character === "]" ? "[" : "{";
520
+ if (stack.at(-1) === expected)
521
+ stack.pop();
522
+ if (stack.length === 0)
523
+ return offset + 1;
524
+ }
525
+ return undefined;
526
+ }
527
+ function isPlausibleTelegramMatrixStart(source, start) {
528
+ let offset = start + 1;
529
+ while (/\s/u.test(source[offset] ?? ""))
530
+ offset += 1;
531
+ return (source[offset] === "{" ||
532
+ source[offset] === "[" ||
533
+ source[offset] === "]");
534
+ }
535
+ export function parseTelegramButtonPayloadRows(source) {
536
+ return parseTelegramAdaptiveActionPayloadRows(source.trim(), parseTelegramButtonCompactActionPayload, { maxCompactAtoms: 4 })?.rows;
537
+ }
538
+ export function parseTelegramActionPayloadRows(comment, command) {
539
+ let content = comment.content.replace(/^\s+/, "").replace(/^!/, "");
540
+ if (!content.startsWith(command))
541
+ return undefined;
542
+ content = content.slice(command.length);
543
+ let attributeEnvelope = content;
544
+ for (let offset = 0; offset < content.length; offset += 1) {
545
+ if (content[offset] !== "[" && content[offset] !== "{")
546
+ continue;
547
+ if (content[offset] === "[" &&
548
+ !isPlausibleTelegramMatrixStart(content, offset)) {
549
+ const noiseEnd = findTelegramStructuredPayloadEnd(content, offset);
550
+ if (noiseEnd !== undefined) {
551
+ attributeEnvelope = `${attributeEnvelope.slice(0, offset)}${" ".repeat(noiseEnd - offset)}${attributeEnvelope.slice(noiseEnd)}`;
552
+ offset = noiseEnd - 1;
553
+ }
554
+ continue;
555
+ }
556
+ const parsed = parseTelegramAdaptiveActionPayloadRows(content.slice(offset), parseTelegramButtonCompactActionPayload, { allowTrailing: true, maxCompactAtoms: 4 });
557
+ if (parsed)
558
+ return parsed.rows;
559
+ const end = findTelegramStructuredPayloadEnd(content, offset);
560
+ if (end === undefined)
561
+ continue;
562
+ attributeEnvelope = `${attributeEnvelope.slice(0, offset)}${" ".repeat(end - offset)}${attributeEnvelope.slice(end)}`;
563
+ offset = end - 1;
564
+ }
565
+ const attributes = parseTolerantTelegramAttributes(attributeEnvelope, [
566
+ "label",
567
+ "prompt",
568
+ "value",
569
+ "selected_style",
570
+ "disabled",
571
+ ]);
572
+ if (!attributes)
573
+ return undefined;
574
+ return [[{
575
+ ...attributes,
576
+ ...(attributes.disabled === "true" || attributes.disabled === "false"
577
+ ? { disabled: attributes.disabled === "true" }
578
+ : {}),
579
+ }]];
580
+ }
581
+ export function normalizeMarkdownAfterVoiceExtraction(markdown) {
582
+ return markdown.replace(/\n{3,}/g, "\n\n").trim();
583
+ }
584
+ function isTelegramCommentOnlyLinePrefix(value) {
585
+ return /^[ \t]*(?:(?:>[ \t]*)+)?(?:(?:[-+*]|\d+[.)])[ \t]+)?$/u.test(value);
586
+ }
587
+ function stripTelegramHtmlCommentBlocks(markdown) {
588
+ let result = "";
589
+ let offset = 0;
590
+ while (offset < markdown.length) {
591
+ const start = markdown.indexOf("<!--", offset);
592
+ if (start === -1)
593
+ return result + markdown.slice(offset);
594
+ const close = markdown.indexOf("-->", start + 4);
595
+ const lineStart = markdown.lastIndexOf("\n", start - 1) + 1;
596
+ const afterComment = close === -1 ? markdown.length : close + 3;
597
+ const newlineAfterComment = markdown.indexOf("\n", afterComment);
598
+ const lineEnd = newlineAfterComment === -1 ? markdown.length : newlineAfterComment;
599
+ const commentOwnsLine = isTelegramCommentOnlyLinePrefix(markdown.slice(lineStart, start)) &&
600
+ markdown.slice(afterComment, lineEnd).trim().length === 0;
601
+ result += markdown.slice(offset, commentOwnsLine ? lineStart : start);
602
+ if (close === -1)
603
+ return result;
604
+ offset = commentOwnsLine
605
+ ? newlineAfterComment === -1
606
+ ? markdown.length
607
+ : newlineAfterComment + 1
608
+ : afterComment;
609
+ }
610
+ return result;
611
+ }
612
+ export function stripTelegramCommentMarkupForPreview(markdown) {
613
+ const withoutClosedBlocks = stripTelegramHtmlCommentBlocks(replaceTelegramButtonFences(markdown, () => ""));
614
+ const openBlockIndex = findTopLevelOpenOrPartialHtmlCommentIndex(withoutClosedBlocks);
615
+ const previewMarkdown = openBlockIndex >= 0
616
+ ? withoutClosedBlocks.slice(0, openBlockIndex)
617
+ : withoutClosedBlocks;
618
+ return normalizeMarkdownAfterVoiceExtraction(previewMarkdown);
619
+ }
620
+ export function stripTelegramCommentMarkupForDelivery(markdown) {
621
+ const withoutClosedBlocks = stripTelegramHtmlCommentBlocks(markdown);
622
+ const openBlockIndex = findTopLevelOpenOrPartialHtmlCommentIndex(withoutClosedBlocks);
623
+ const deliveryMarkdown = openBlockIndex >= 0
624
+ ? withoutClosedBlocks.slice(0, openBlockIndex)
625
+ : withoutClosedBlocks;
626
+ return normalizeMarkdownAfterVoiceExtraction(deliveryMarkdown);
627
+ }
628
+ export function stripTelegramVoiceMarkupForPreview(markdown) {
629
+ return stripTelegramCommentMarkupForPreview(markdown);
630
+ }
631
+ function getTelegramActionString(payload, key) {
632
+ const value = payload[key];
633
+ if (typeof value !== "string")
634
+ return undefined;
635
+ const trimmed = value.trim();
636
+ return trimmed || undefined;
637
+ }
638
+ export function planTelegramVoiceReply(markdown) {
639
+ const voiceReplies = [];
640
+ let lang;
641
+ let rate;
642
+ const stripped = replaceTopLevelHtmlComments(markdown, (comment) => {
643
+ const command = "telegram_voice";
644
+ const normalizedContent = comment.content.replace(/^\s+/, "").replace(/^!/, "");
645
+ if (!normalizedContent.startsWith(command))
646
+ return comment.raw;
647
+ const payload = parseTelegramActionPayload(comment, command);
648
+ if (!payload)
649
+ return "";
650
+ const text = getTelegramActionString(payload, "text") ??
651
+ getTelegramActionString(payload, "value");
652
+ const itemLang = getTelegramActionString(payload, "lang");
653
+ const itemRate = getTelegramActionString(payload, "rate");
654
+ if (text) {
655
+ voiceReplies.push({
656
+ text,
657
+ ...(itemLang ? { lang: itemLang } : {}),
658
+ ...(itemRate ? { rate: itemRate } : {}),
659
+ });
660
+ }
661
+ if (itemLang)
662
+ lang = itemLang;
663
+ if (itemRate)
664
+ rate = itemRate;
665
+ return "";
666
+ });
667
+ const voiceText = voiceReplies
668
+ .map((reply) => reply.text)
669
+ .join("\n\n")
670
+ .trim();
671
+ return {
672
+ markdown: stripTelegramCommentMarkupForDelivery(stripped),
673
+ ...(voiceText ? { voiceText } : {}),
674
+ ...(voiceReplies.length > 0 ? { voiceReplies } : {}),
675
+ ...(lang ? { lang } : {}),
676
+ ...(rate ? { rate } : {}),
677
+ };
678
+ }