@llblab/pi-kit 0.1.13 → 0.3.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.
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +10 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-actors/AGENTS.md +5 -3
- package/node_modules/@llblab/pi-actors/BACKLOG.md +1 -1
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +15 -0
- package/node_modules/@llblab/pi-actors/README.md +5 -3
- package/node_modules/@llblab/pi-actors/dist/index.js +4 -1
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +2 -2
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +42 -19
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.d.ts +2 -1
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +7 -2
- package/node_modules/@llblab/pi-actors/dist/lib/limits.d.ts +9 -0
- package/node_modules/@llblab/pi-actors/dist/lib/limits.js +9 -0
- package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +10 -11
- package/node_modules/@llblab/pi-actors/dist/lib/observability.js +81 -56
- package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +31 -0
- package/node_modules/@llblab/pi-actors/dist/lib/pi.js +180 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +115 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +623 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +341 -13
- package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.d.ts +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.js +5 -3
- package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.d.ts +16 -0
- package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.js +143 -0
- package/node_modules/@llblab/pi-actors/dist/lib/temp.js +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.js +3 -1
- package/node_modules/@llblab/pi-actors/dist/scripts/async-runner.mjs +5 -19
- package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +2 -2
- package/node_modules/@llblab/pi-actors/dist/skills/actors/references/runs.md +1 -1
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/SKILL.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/README.md +1 -0
- package/node_modules/@llblab/pi-actors/docs/async-runs.md +8 -4
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +207 -0
- package/node_modules/@llblab/pi-actors/index.ts +4 -1
- package/node_modules/@llblab/pi-actors/lib/async-runs.ts +42 -21
- package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +8 -3
- package/node_modules/@llblab/pi-actors/lib/limits.ts +9 -0
- package/node_modules/@llblab/pi-actors/lib/observability.ts +97 -78
- package/node_modules/@llblab/pi-actors/lib/pi.ts +210 -0
- package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +800 -0
- package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +370 -18
- package/node_modules/@llblab/pi-actors/lib/runs-trace.ts +6 -4
- package/node_modules/@llblab/pi-actors/lib/session-evidence.ts +153 -0
- package/node_modules/@llblab/pi-actors/lib/temp.ts +1 -1
- package/node_modules/@llblab/pi-actors/lib/tools-inspect.ts +4 -1
- package/node_modules/@llblab/pi-actors/package.json +3 -3
- package/node_modules/@llblab/pi-actors/scripts/async-runner.mjs +5 -19
- package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +2 -2
- package/node_modules/@llblab/pi-actors/skills/actors/references/runs.md +1 -1
- package/node_modules/@llblab/pi-actors/skills/swarm/SKILL.md +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +4 -4
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +25 -0
- package/node_modules/@llblab/pi-telegram/README.md +4 -4
- package/node_modules/@llblab/pi-telegram/docs/activity.md +4 -4
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +10 -10
- package/node_modules/@llblab/pi-telegram/docs/compact-matrix-literal.md +16 -14
- package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +13 -22
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +6 -7
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +4 -5
- package/node_modules/@llblab/pi-telegram/docs/voice.md +12 -2
- package/node_modules/@llblab/pi-telegram/index.ts +0 -1
- package/node_modules/@llblab/pi-telegram/lib/activity.ts +10 -5
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +16 -17
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +16 -6
- package/node_modules/@llblab/pi-telegram/lib/config.ts +25 -25
- package/node_modules/@llblab/pi-telegram/lib/media.ts +12 -2
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +10 -73
- package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +9 -8
- package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +212 -82
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +78 -36
- package/node_modules/@llblab/pi-telegram/lib/status.ts +23 -4
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +10 -1
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +1 -1
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +61 -205
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/references/capability-adapters.md +27 -0
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/references/layout-and-state.md +35 -0
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/SKILL.md +70 -110
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/configuration.md +15 -0
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +14 -0
- package/package.json +3 -3
|
@@ -21,7 +21,6 @@ import type { TelegramVoiceReplyMode } from "./voice.ts";
|
|
|
21
21
|
export type TelegramSettingsMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
22
22
|
|
|
23
23
|
export interface TelegramSettingsStateDeps {
|
|
24
|
-
isProactivePushEnabled: () => boolean;
|
|
25
24
|
areDraftPreviewsEnabled: () => boolean;
|
|
26
25
|
getAssistantRenderingMode: () => TelegramAssistantRenderingMode;
|
|
27
26
|
getActivityVerbosity: () => TelegramActivityVerbosity;
|
|
@@ -32,7 +31,6 @@ export interface TelegramSettingsStateDeps {
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps {
|
|
35
|
-
setProactivePushEnabled: (enabled: boolean) => Promise<void>;
|
|
36
34
|
setDraftPreviewsEnabled: (enabled: boolean) => Promise<void>;
|
|
37
35
|
setAssistantRenderingMode: (
|
|
38
36
|
mode: TelegramAssistantRenderingMode,
|
|
@@ -139,7 +137,6 @@ export interface TelegramSettingsMenuRuntimeDeps<
|
|
|
139
137
|
export const SETTINGS_MENU_TITLE = "<b>⚙️ Settings:</b>";
|
|
140
138
|
export const AUTOMATIC_THREAD_CLEANUP_SETTINGS_TITLE =
|
|
141
139
|
"<b>🧹 Thread cleanup:</b>";
|
|
142
|
-
export const PROACTIVE_PUSH_SETTINGS_TITLE = "<b>📌 Proactive push:</b>";
|
|
143
140
|
export const DRAFT_PREVIEWS_SETTINGS_TITLE = "<b>📝 Draft previews:</b>";
|
|
144
141
|
export const ASSISTANT_RENDERING_SETTINGS_TITLE =
|
|
145
142
|
"<b>🧾 Assistant rendering:</b>";
|
|
@@ -181,19 +178,6 @@ export function buildAutomaticThreadCleanupSettingsText(
|
|
|
181
178
|
].join("\n");
|
|
182
179
|
}
|
|
183
180
|
|
|
184
|
-
export function buildProactivePushSettingsText(
|
|
185
|
-
proactivePushEnabled: boolean,
|
|
186
|
-
): string {
|
|
187
|
-
return [
|
|
188
|
-
`${PROACTIVE_PUSH_SETTINGS_TITLE} <code>${proactivePushEnabled ? "on" : "off"}</code>`,
|
|
189
|
-
"",
|
|
190
|
-
"Control whether public assistant output from local/autonomous work is projected to Telegram.",
|
|
191
|
-
"",
|
|
192
|
-
"<code>-</code> <code>on</code> (default): send each completed public block, including visible checkpoints and the final answer, while connected.",
|
|
193
|
-
"<code>-</code> <code>off</code>: keep local/autonomous assistant blocks in Pi; Telegram-originated replies still use their normal delivery path.",
|
|
194
|
-
].join("\n");
|
|
195
|
-
}
|
|
196
|
-
|
|
197
181
|
export function buildDraftPreviewsSettingsText(enabled: boolean): string {
|
|
198
182
|
return [
|
|
199
183
|
`${DRAFT_PREVIEWS_SETTINGS_TITLE} <code>${enabled ? "on" : "off"}</code>`,
|
|
@@ -265,7 +249,6 @@ export function buildTimeInjectionModeSettingsText(
|
|
|
265
249
|
}
|
|
266
250
|
|
|
267
251
|
export function buildTelegramSettingsMenuReplyMarkup(
|
|
268
|
-
proactivePushEnabled: boolean,
|
|
269
252
|
draftPreviewsEnabled: boolean,
|
|
270
253
|
assistantRenderingModeOrVoiceReplyMode:
|
|
271
254
|
TelegramAssistantRenderingMode | TelegramVoiceReplyMode,
|
|
@@ -325,10 +308,6 @@ export function buildTelegramSettingsMenuReplyMarkup(
|
|
|
325
308
|
text: `🔬 Activity: ${activityVerbosity}`,
|
|
326
309
|
callback_data: "settings:open:activity-verbosity",
|
|
327
310
|
},
|
|
328
|
-
{
|
|
329
|
-
text: `📌 Proactive push: ${proactivePushEnabled ? "on" : "off"}`,
|
|
330
|
-
callback_data: "settings:open:proactive",
|
|
331
|
-
},
|
|
332
311
|
{
|
|
333
312
|
text: `🕒 Time injection: ${getTelegramSettingsStateValueLabel(timeInjectionMode)}`,
|
|
334
313
|
callback_data: "settings:open:time-injection",
|
|
@@ -362,7 +341,6 @@ export async function openTelegramSettingsMenu<
|
|
|
362
341
|
state,
|
|
363
342
|
buildTelegramSettingsMenuText(),
|
|
364
343
|
buildTelegramSettingsMenuReplyMarkup(
|
|
365
|
-
deps.isProactivePushEnabled(),
|
|
366
344
|
deps.areDraftPreviewsEnabled(),
|
|
367
345
|
deps.getAssistantRenderingMode(),
|
|
368
346
|
deps.getVoiceReplyMode(),
|
|
@@ -399,26 +377,6 @@ export function buildAutomaticThreadCleanupSettingsReplyMarkup(
|
|
|
399
377
|
};
|
|
400
378
|
}
|
|
401
379
|
|
|
402
|
-
export function buildProactivePushSettingsReplyMarkup(
|
|
403
|
-
proactivePushEnabled: boolean,
|
|
404
|
-
): TelegramSettingsMenuReplyMarkup {
|
|
405
|
-
return {
|
|
406
|
-
inline_keyboard: [
|
|
407
|
-
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
408
|
-
[
|
|
409
|
-
{
|
|
410
|
-
text: proactivePushEnabled ? "🟢 On" : "⚫️ On",
|
|
411
|
-
callback_data: "settings:set:proactive:on",
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
text: proactivePushEnabled ? "⚫️ Off" : "🟡 Off",
|
|
415
|
-
callback_data: "settings:set:proactive:off",
|
|
416
|
-
},
|
|
417
|
-
],
|
|
418
|
-
],
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
380
|
export function buildDraftPreviewsSettingsReplyMarkup(
|
|
423
381
|
enabled: boolean,
|
|
424
382
|
): TelegramSettingsMenuReplyMarkup {
|
|
@@ -521,7 +479,6 @@ export async function updateTelegramSettingsMenuMessage(
|
|
|
521
479
|
await deps.updateSettingsMessage(
|
|
522
480
|
buildTelegramSettingsMenuText(),
|
|
523
481
|
buildTelegramSettingsMenuReplyMarkup(
|
|
524
|
-
deps.isProactivePushEnabled(),
|
|
525
482
|
deps.areDraftPreviewsEnabled(),
|
|
526
483
|
deps.getAssistantRenderingMode(),
|
|
527
484
|
deps.getVoiceReplyMode(),
|
|
@@ -544,16 +501,6 @@ export async function updateAutomaticThreadCleanupSettingsMessage(
|
|
|
544
501
|
);
|
|
545
502
|
}
|
|
546
503
|
|
|
547
|
-
export async function updateProactivePushSettingsMessage(
|
|
548
|
-
deps: TelegramSettingsMenuCallbackDeps,
|
|
549
|
-
): Promise<void> {
|
|
550
|
-
const proactivePushEnabled = deps.isProactivePushEnabled();
|
|
551
|
-
await deps.updateSettingsMessage(
|
|
552
|
-
buildProactivePushSettingsText(proactivePushEnabled),
|
|
553
|
-
buildProactivePushSettingsReplyMarkup(proactivePushEnabled),
|
|
554
|
-
);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
504
|
export async function updateDraftPreviewsSettingsMessage(
|
|
558
505
|
deps: TelegramSettingsMenuCallbackDeps,
|
|
559
506
|
): Promise<void> {
|
|
@@ -621,9 +568,16 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
621
568
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
622
569
|
return true;
|
|
623
570
|
}
|
|
624
|
-
if (
|
|
625
|
-
|
|
626
|
-
|
|
571
|
+
if (
|
|
572
|
+
data === "settings:open:proactive" ||
|
|
573
|
+
data === "settings:set:proactive:on" ||
|
|
574
|
+
data === "settings:set:proactive:off"
|
|
575
|
+
) {
|
|
576
|
+
await updateTelegramSettingsMenuMessage(deps, deps.sectionRegistry);
|
|
577
|
+
await deps.answerCallbackQuery(
|
|
578
|
+
callbackQueryId,
|
|
579
|
+
"Public assistant output is always delivered while Telegram is connected.",
|
|
580
|
+
);
|
|
627
581
|
return true;
|
|
628
582
|
}
|
|
629
583
|
if (
|
|
@@ -751,19 +705,6 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
751
705
|
);
|
|
752
706
|
return true;
|
|
753
707
|
}
|
|
754
|
-
if (
|
|
755
|
-
data === "settings:set:proactive:on" ||
|
|
756
|
-
data === "settings:set:proactive:off"
|
|
757
|
-
) {
|
|
758
|
-
const enabled = data.endsWith(":on");
|
|
759
|
-
await deps.setProactivePushEnabled(enabled);
|
|
760
|
-
await updateProactivePushSettingsMessage(deps);
|
|
761
|
-
await deps.answerCallbackQuery(
|
|
762
|
-
callbackQueryId,
|
|
763
|
-
`Proactive push ${enabled ? "enabled" : "disabled"}`,
|
|
764
|
-
);
|
|
765
|
-
return true;
|
|
766
|
-
}
|
|
767
708
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
768
709
|
return true;
|
|
769
710
|
}
|
|
@@ -781,7 +722,6 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
781
722
|
return openTelegramSettingsMenu(
|
|
782
723
|
{
|
|
783
724
|
getModelMenuState: () => deps.getModelMenuState(chatId, ctx),
|
|
784
|
-
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
785
725
|
areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled,
|
|
786
726
|
getAssistantRenderingMode: deps.getAssistantRenderingMode,
|
|
787
727
|
getActivityVerbosity: deps.getActivityVerbosity,
|
|
@@ -805,7 +745,6 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
805
745
|
await deps.reloadConfig?.();
|
|
806
746
|
return updateTelegramSettingsMenuMessage(
|
|
807
747
|
{
|
|
808
|
-
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
809
748
|
areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled,
|
|
810
749
|
getAssistantRenderingMode: deps.getAssistantRenderingMode,
|
|
811
750
|
getActivityVerbosity: deps.getActivityVerbosity,
|
|
@@ -849,7 +788,6 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
849
788
|
deps.storeModelMenuState(state);
|
|
850
789
|
}
|
|
851
790
|
return handleTelegramSettingsMenuCallbackAction(query.id, query.data, {
|
|
852
|
-
isProactivePushEnabled: deps.isProactivePushEnabled,
|
|
853
791
|
areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled,
|
|
854
792
|
getAssistantRenderingMode: deps.getAssistantRenderingMode,
|
|
855
793
|
getActivityVerbosity: deps.getActivityVerbosity,
|
|
@@ -857,7 +795,6 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
857
795
|
isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
|
|
858
796
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
859
797
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
860
|
-
setProactivePushEnabled: deps.setProactivePushEnabled,
|
|
861
798
|
setDraftPreviewsEnabled: deps.setDraftPreviewsEnabled,
|
|
862
799
|
setAssistantRenderingMode: deps.setAssistantRenderingMode,
|
|
863
800
|
setActivityVerbosity: deps.setActivityVerbosity,
|
|
@@ -12,7 +12,6 @@ import type {
|
|
|
12
12
|
} from "./keyboard.ts";
|
|
13
13
|
import {
|
|
14
14
|
parseTelegramActionPayloadRows,
|
|
15
|
-
parseTopLevelTelegramComment,
|
|
16
15
|
replaceTopLevelHtmlComments,
|
|
17
16
|
} from "./outbound-markup.ts";
|
|
18
17
|
import {
|
|
@@ -113,8 +112,10 @@ function parseTelegramButtonAction(
|
|
|
113
112
|
payload: Record<string, unknown>,
|
|
114
113
|
): TelegramOutboundButtonAction | undefined {
|
|
115
114
|
const value = getTelegramButtonString(payload, "value");
|
|
116
|
-
const
|
|
117
|
-
const
|
|
115
|
+
const explicitLabel = getTelegramButtonString(payload, "label");
|
|
116
|
+
const explicitPrompt = getTelegramButtonString(payload, "prompt");
|
|
117
|
+
const label = explicitLabel ?? value ?? explicitPrompt;
|
|
118
|
+
const prompt = explicitPrompt ?? value ?? explicitLabel;
|
|
118
119
|
if (!label || !prompt) return undefined;
|
|
119
120
|
const selectedStyle = payload.selected_style;
|
|
120
121
|
return {
|
|
@@ -179,10 +180,9 @@ export function planTelegramButtonReply(
|
|
|
179
180
|
): TelegramButtonReplyPlan {
|
|
180
181
|
const keyboard: TelegramOutboundButtonMarkup["inline_keyboard"] = [];
|
|
181
182
|
const stripped = replaceTopLevelHtmlComments(markdown, (comment) => {
|
|
182
|
-
const command =
|
|
183
|
-
|
|
184
|
-
);
|
|
185
|
-
if (!command) return comment.raw;
|
|
183
|
+
const command = "telegram_button";
|
|
184
|
+
const normalizedContent = comment.content.replace(/^\s+/, "").replace(/^!/, "");
|
|
185
|
+
if (!normalizedContent.startsWith(command)) return comment.raw;
|
|
186
186
|
const payloadRows = parseTelegramActionPayloadRows(comment, command);
|
|
187
187
|
if (!payloadRows) return "";
|
|
188
188
|
const actionRows = payloadRows.map((payloadRow) =>
|
|
@@ -225,8 +225,9 @@ export function createTelegramButtonPromptTurn(options: {
|
|
|
225
225
|
queueOrder: number;
|
|
226
226
|
action: TelegramOutboundButtonAction;
|
|
227
227
|
target?: TelegramQueueTarget;
|
|
228
|
+
telegramPrefix?: string;
|
|
228
229
|
}): PendingTelegramTurn {
|
|
229
|
-
const prompt =
|
|
230
|
+
const prompt = `${options.telegramPrefix ?? "[telegram]"} ${options.action.prompt}`;
|
|
230
231
|
return {
|
|
231
232
|
kind: "prompt",
|
|
232
233
|
chatId: options.chatId,
|
|
@@ -153,63 +153,153 @@ export function parseTopLevelTelegramComment(
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
function
|
|
156
|
+
function parseTolerantTelegramAttributes(
|
|
157
157
|
source: string,
|
|
158
|
+
names: readonly string[],
|
|
158
159
|
): Record<string, string> | undefined {
|
|
159
160
|
const attributes: Record<string, string> = {};
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const value = match[2].trim();
|
|
167
|
-
if (value) attributes[match[1]] = value;
|
|
168
|
-
offset = pattern.lastIndex;
|
|
161
|
+
const namePattern = names.join("|");
|
|
162
|
+
const pattern = new RegExp(
|
|
163
|
+
`\\b(${namePattern})\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s]+))`,
|
|
164
|
+
"gu",
|
|
165
|
+
);
|
|
166
|
+
for (const match of source.matchAll(pattern)) {
|
|
167
|
+
const value = (match[2] ?? match[3] ?? match[4] ?? "").trim();
|
|
168
|
+
if (value) attributes[match[1]!] = value;
|
|
169
169
|
}
|
|
170
170
|
return Object.keys(attributes).length > 0 ? attributes : undefined;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
function getTelegramActionPayloadSource(
|
|
174
|
-
comment: TelegramTopLevelHtmlComment,
|
|
175
|
-
command: string,
|
|
176
|
-
): { source: string; hasBody: boolean } | undefined {
|
|
177
|
-
const parsed = parseTopLevelTelegramComment(comment, command);
|
|
178
|
-
if (!parsed || parsed.head.trimStart().startsWith(":")) return undefined;
|
|
179
|
-
const source = [parsed.head, parsed.body]
|
|
180
|
-
.filter((part): part is string => part !== undefined)
|
|
181
|
-
.join("\n")
|
|
182
|
-
.trim();
|
|
183
|
-
return source ? { source, hasBody: parsed.body !== undefined } : undefined;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
173
|
function isTelegramActionPayload(value: unknown): value is Record<string, unknown> {
|
|
187
174
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
188
175
|
}
|
|
189
176
|
|
|
177
|
+
function removeTelegramJsonTrailingCommas(source: string): string {
|
|
178
|
+
let normalized = "";
|
|
179
|
+
let inString = false;
|
|
180
|
+
let escaped = false;
|
|
181
|
+
for (let offset = 0; offset < source.length; offset += 1) {
|
|
182
|
+
const character = source[offset]!;
|
|
183
|
+
if (inString) {
|
|
184
|
+
normalized += character;
|
|
185
|
+
if (escaped) escaped = false;
|
|
186
|
+
else if (character === "\\") escaped = true;
|
|
187
|
+
else if (character === '"') inString = false;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (character === '"') {
|
|
191
|
+
inString = true;
|
|
192
|
+
normalized += character;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (character === ",") {
|
|
196
|
+
let next = offset + 1;
|
|
197
|
+
while (/\s/u.test(source[next] ?? "")) next += 1;
|
|
198
|
+
if (source[next] === "}" || source[next] === "]") continue;
|
|
199
|
+
}
|
|
200
|
+
normalized += character;
|
|
201
|
+
}
|
|
202
|
+
return normalized;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function parseTelegramJsonObjectCandidate(
|
|
206
|
+
source: string,
|
|
207
|
+
): Record<string, unknown> | undefined {
|
|
208
|
+
const normalized = removeTelegramJsonTrailingCommas(source);
|
|
209
|
+
for (const candidate of normalized === source ? [source] : [source, normalized]) {
|
|
210
|
+
try {
|
|
211
|
+
const value: unknown = JSON.parse(candidate);
|
|
212
|
+
if (isTelegramActionPayload(value)) return value;
|
|
213
|
+
} catch {
|
|
214
|
+
// Try the bounded trailing-comma normalization before rejecting JSON.
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function looksLikeTelegramNamedJsonObject(
|
|
221
|
+
source: string,
|
|
222
|
+
offset: number,
|
|
223
|
+
): boolean {
|
|
224
|
+
return /^\{\s*"(?:[^"\\]|\\.)*"\s*:/u.test(source.slice(offset));
|
|
225
|
+
}
|
|
226
|
+
|
|
190
227
|
export function parseTelegramActionPayload(
|
|
191
228
|
comment: TelegramTopLevelHtmlComment,
|
|
192
229
|
command: string,
|
|
193
230
|
): Record<string, unknown> | undefined {
|
|
194
|
-
|
|
195
|
-
if (!
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
231
|
+
let content = comment.content.replace(/^\s+/, "").replace(/^!/, "");
|
|
232
|
+
if (!content.startsWith(command)) return undefined;
|
|
233
|
+
content = content.slice(command.length);
|
|
234
|
+
let attributeEnvelope = content;
|
|
235
|
+
for (let offset = 0; offset < content.length; offset += 1) {
|
|
236
|
+
if (content[offset] !== "{" && content[offset] !== "[") continue;
|
|
237
|
+
if (
|
|
238
|
+
content[offset] === "[" &&
|
|
239
|
+
!isPlausibleTelegramMatrixStart(content, offset)
|
|
240
|
+
) {
|
|
241
|
+
const noiseEnd = findTelegramStructuredPayloadEnd(content, offset);
|
|
242
|
+
if (noiseEnd !== undefined) {
|
|
243
|
+
attributeEnvelope = `${attributeEnvelope.slice(0, offset)}${" ".repeat(noiseEnd - offset)}${attributeEnvelope.slice(noiseEnd)}`;
|
|
244
|
+
offset = noiseEnd - 1;
|
|
245
|
+
}
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (content[offset] === "{") {
|
|
249
|
+
const parsed = parseTelegramAdaptiveActionPayloadRows(
|
|
250
|
+
content.slice(offset),
|
|
251
|
+
parseTelegramVoiceCompactActionPayload,
|
|
252
|
+
{ allowTrailing: true },
|
|
253
|
+
);
|
|
254
|
+
if (parsed) return parsed.rows[0]![0];
|
|
202
255
|
}
|
|
256
|
+
const end = findTelegramStructuredPayloadEnd(content, offset);
|
|
257
|
+
if (end === undefined) continue;
|
|
258
|
+
attributeEnvelope = `${attributeEnvelope.slice(0, offset)}${" ".repeat(end - offset)}${attributeEnvelope.slice(end)}`;
|
|
259
|
+
offset = end - 1;
|
|
203
260
|
}
|
|
204
|
-
|
|
205
|
-
|
|
261
|
+
return parseTolerantTelegramAttributes(attributeEnvelope, [
|
|
262
|
+
"text",
|
|
263
|
+
"value",
|
|
264
|
+
"lang",
|
|
265
|
+
"rate",
|
|
266
|
+
]);
|
|
206
267
|
}
|
|
207
268
|
|
|
208
269
|
const TELEGRAM_COMPACT_ACTION_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
209
270
|
|
|
271
|
+
type TelegramCompactActionPayloadParser = (
|
|
272
|
+
atoms: readonly string[],
|
|
273
|
+
) => Record<string, unknown> | undefined;
|
|
274
|
+
|
|
275
|
+
function parseTelegramButtonCompactActionPayload(
|
|
276
|
+
atoms: readonly string[],
|
|
277
|
+
): Record<string, unknown> | undefined {
|
|
278
|
+
const [label, prompt, selectedStyle] = atoms;
|
|
279
|
+
if (atoms.length === 1) return { value: label };
|
|
280
|
+
if (atoms.length === 2) return { label, prompt };
|
|
281
|
+
if (
|
|
282
|
+
selectedStyle !== "primary" &&
|
|
283
|
+
selectedStyle !== "success" &&
|
|
284
|
+
selectedStyle !== "danger"
|
|
285
|
+
) return undefined;
|
|
286
|
+
return { label, prompt, selected_style: selectedStyle };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function parseTelegramVoiceCompactActionPayload(
|
|
290
|
+
atoms: readonly string[],
|
|
291
|
+
): Record<string, unknown> | undefined {
|
|
292
|
+
const [text, lang, rate] = atoms;
|
|
293
|
+
if (atoms.length === 1) return { text };
|
|
294
|
+
if (atoms.length === 2) return { text, lang };
|
|
295
|
+
return { text, lang, rate };
|
|
296
|
+
}
|
|
297
|
+
|
|
210
298
|
function parseTelegramAdaptiveActionPayloadRows(
|
|
211
299
|
source: string,
|
|
212
|
-
|
|
300
|
+
parseCompactPayload: TelegramCompactActionPayloadParser,
|
|
301
|
+
options: { allowTrailing?: boolean } = {},
|
|
302
|
+
): { rows: Record<string, unknown>[][]; end: number } | undefined {
|
|
213
303
|
let offset = 0;
|
|
214
304
|
const isStructuralWhitespace = (character: string | undefined): boolean =>
|
|
215
305
|
character === " " ||
|
|
@@ -224,7 +314,7 @@ function parseTelegramAdaptiveActionPayloadRows(
|
|
|
224
314
|
if (source[offset] !== ",") return true;
|
|
225
315
|
offset += 1;
|
|
226
316
|
skipWhitespace();
|
|
227
|
-
return source[offset] !== ","
|
|
317
|
+
return source[offset] !== ",";
|
|
228
318
|
};
|
|
229
319
|
const normalizeAtom = (value: string): string | undefined => {
|
|
230
320
|
const normalized = value.trim();
|
|
@@ -259,15 +349,7 @@ function parseTelegramAdaptiveActionPayloadRows(
|
|
|
259
349
|
normalizeAtom(atom.join("")),
|
|
260
350
|
);
|
|
261
351
|
if (atoms.some((atom) => atom === undefined)) return undefined;
|
|
262
|
-
|
|
263
|
-
if (atoms.length === 1) return { value: label };
|
|
264
|
-
if (atoms.length === 2) return { label, prompt };
|
|
265
|
-
if (
|
|
266
|
-
selectedStyle !== "primary" &&
|
|
267
|
-
selectedStyle !== "success" &&
|
|
268
|
-
selectedStyle !== "danger"
|
|
269
|
-
) return undefined;
|
|
270
|
-
return { label, prompt, selected_style: selectedStyle };
|
|
352
|
+
return parseCompactPayload(atoms as string[]);
|
|
271
353
|
}
|
|
272
354
|
atomSources.at(-1)!.push(character);
|
|
273
355
|
offset += 1;
|
|
@@ -304,14 +386,10 @@ function parseTelegramAdaptiveActionPayloadRows(
|
|
|
304
386
|
) return undefined;
|
|
305
387
|
if (stack.length > 0) continue;
|
|
306
388
|
const candidate = source.slice(start, index + 1);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return value;
|
|
312
|
-
} catch {
|
|
313
|
-
return undefined;
|
|
314
|
-
}
|
|
389
|
+
const value = parseTelegramJsonObjectCandidate(candidate);
|
|
390
|
+
if (!value) return undefined;
|
|
391
|
+
offset = index + 1;
|
|
392
|
+
return value;
|
|
315
393
|
}
|
|
316
394
|
return undefined;
|
|
317
395
|
};
|
|
@@ -320,6 +398,7 @@ function parseTelegramAdaptiveActionPayloadRows(
|
|
|
320
398
|
const jsonCell = parseJsonObjectCell();
|
|
321
399
|
if (jsonCell) return jsonCell;
|
|
322
400
|
offset = start;
|
|
401
|
+
if (looksLikeTelegramNamedJsonObject(source, start)) return undefined;
|
|
323
402
|
return parseCompactCell();
|
|
324
403
|
};
|
|
325
404
|
const parseRow = (): Record<string, unknown>[] | undefined => {
|
|
@@ -377,42 +456,92 @@ function parseTelegramAdaptiveActionPayloadRows(
|
|
|
377
456
|
}
|
|
378
457
|
if (!rows) return undefined;
|
|
379
458
|
skipWhitespace();
|
|
380
|
-
|
|
459
|
+
if (!options.allowTrailing && offset !== source.length) return undefined;
|
|
460
|
+
return { rows, end: offset };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function findTelegramStructuredPayloadEnd(
|
|
464
|
+
source: string,
|
|
465
|
+
start: number,
|
|
466
|
+
): number | undefined {
|
|
467
|
+
const stack: string[] = [source[start]!];
|
|
468
|
+
let inString = false;
|
|
469
|
+
let escaped = false;
|
|
470
|
+
for (let offset = start + 1; offset < source.length; offset += 1) {
|
|
471
|
+
const character = source[offset]!;
|
|
472
|
+
if (inString) {
|
|
473
|
+
if (escaped) escaped = false;
|
|
474
|
+
else if (character === "\\") escaped = true;
|
|
475
|
+
else if (character === '"') inString = false;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (character === '"') {
|
|
479
|
+
inString = true;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (character === "[" || character === "{") {
|
|
483
|
+
stack.push(character);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (character !== "]" && character !== "}") continue;
|
|
487
|
+
const expected = character === "]" ? "[" : "{";
|
|
488
|
+
if (stack.at(-1) === expected) stack.pop();
|
|
489
|
+
if (stack.length === 0) return offset + 1;
|
|
490
|
+
}
|
|
491
|
+
return undefined;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function isPlausibleTelegramMatrixStart(
|
|
495
|
+
source: string,
|
|
496
|
+
start: number,
|
|
497
|
+
): boolean {
|
|
498
|
+
let offset = start + 1;
|
|
499
|
+
while (/\s/u.test(source[offset] ?? "")) offset += 1;
|
|
500
|
+
return (
|
|
501
|
+
source[offset] === "{" ||
|
|
502
|
+
source[offset] === "[" ||
|
|
503
|
+
source[offset] === "]"
|
|
504
|
+
);
|
|
381
505
|
}
|
|
382
506
|
|
|
383
507
|
export function parseTelegramActionPayloadRows(
|
|
384
508
|
comment: TelegramTopLevelHtmlComment,
|
|
385
509
|
command: string,
|
|
386
510
|
): Record<string, unknown>[][] | undefined {
|
|
387
|
-
|
|
388
|
-
if (!
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
!Array.isArray(entry) ||
|
|
402
|
-
entry.length === 0 ||
|
|
403
|
-
!entry.every(isTelegramActionPayload)
|
|
404
|
-
) {
|
|
405
|
-
return undefined;
|
|
406
|
-
}
|
|
407
|
-
rows.push(entry);
|
|
511
|
+
let content = comment.content.replace(/^\s+/, "").replace(/^!/, "");
|
|
512
|
+
if (!content.startsWith(command)) return undefined;
|
|
513
|
+
content = content.slice(command.length);
|
|
514
|
+
let attributeEnvelope = content;
|
|
515
|
+
for (let offset = 0; offset < content.length; offset += 1) {
|
|
516
|
+
if (content[offset] !== "[" && content[offset] !== "{") continue;
|
|
517
|
+
if (
|
|
518
|
+
content[offset] === "[" &&
|
|
519
|
+
!isPlausibleTelegramMatrixStart(content, offset)
|
|
520
|
+
) {
|
|
521
|
+
const noiseEnd = findTelegramStructuredPayloadEnd(content, offset);
|
|
522
|
+
if (noiseEnd !== undefined) {
|
|
523
|
+
attributeEnvelope = `${attributeEnvelope.slice(0, offset)}${" ".repeat(noiseEnd - offset)}${attributeEnvelope.slice(noiseEnd)}`;
|
|
524
|
+
offset = noiseEnd - 1;
|
|
408
525
|
}
|
|
409
|
-
|
|
410
|
-
} catch {
|
|
411
|
-
return parseTelegramAdaptiveActionPayloadRows(payload.source);
|
|
526
|
+
continue;
|
|
412
527
|
}
|
|
528
|
+
const parsed = parseTelegramAdaptiveActionPayloadRows(
|
|
529
|
+
content.slice(offset),
|
|
530
|
+
parseTelegramButtonCompactActionPayload,
|
|
531
|
+
{ allowTrailing: true },
|
|
532
|
+
);
|
|
533
|
+
if (parsed) return parsed.rows;
|
|
534
|
+
const end = findTelegramStructuredPayloadEnd(content, offset);
|
|
535
|
+
if (end === undefined) continue;
|
|
536
|
+
attributeEnvelope = `${attributeEnvelope.slice(0, offset)}${" ".repeat(end - offset)}${attributeEnvelope.slice(end)}`;
|
|
537
|
+
offset = end - 1;
|
|
413
538
|
}
|
|
414
|
-
|
|
415
|
-
|
|
539
|
+
const attributes = parseTolerantTelegramAttributes(attributeEnvelope, [
|
|
540
|
+
"label",
|
|
541
|
+
"prompt",
|
|
542
|
+
"value",
|
|
543
|
+
"selected_style",
|
|
544
|
+
]);
|
|
416
545
|
return attributes ? [[attributes]] : undefined;
|
|
417
546
|
}
|
|
418
547
|
|
|
@@ -481,9 +610,10 @@ export function planTelegramVoiceReply(
|
|
|
481
610
|
let lang: string | undefined;
|
|
482
611
|
let rate: string | undefined;
|
|
483
612
|
const stripped = replaceTopLevelHtmlComments(markdown, (comment) => {
|
|
484
|
-
const command =
|
|
485
|
-
|
|
486
|
-
|
|
613
|
+
const command = "telegram_voice";
|
|
614
|
+
const normalizedContent = comment.content.replace(/^\s+/, "").replace(/^!/, "");
|
|
615
|
+
if (!normalizedContent.startsWith(command)) return comment.raw;
|
|
616
|
+
const payload = parseTelegramActionPayload(comment, command);
|
|
487
617
|
if (!payload) return "";
|
|
488
618
|
const text =
|
|
489
619
|
getTelegramActionString(payload, "text") ??
|