@llblab/pi-telegram 0.11.1 → 0.12.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 +17 -12
- package/BACKLOG.md +0 -10
- package/CHANGELOG.md +33 -2
- package/README.md +42 -25
- package/api/inbound.ts +14 -0
- package/api/keyboard.ts +10 -0
- package/api/outbound.ts +11 -0
- package/api/sections.ts +17 -0
- package/api/updates.ts +11 -0
- package/api/voice.ts +24 -0
- package/docs/README.md +7 -5
- package/docs/architecture.md +176 -135
- package/docs/callback-namespaces.md +3 -3
- package/docs/command-templates.md +81 -24
- package/docs/{inbound-handlers.md → inbound.md} +13 -10
- package/docs/locks.md +3 -3
- package/docs/{outbound-handlers.md → outbound.md} +13 -10
- package/docs/public-api.md +266 -0
- package/docs/{extension-sections.md → sections.md} +31 -27
- package/docs/ui-style.md +165 -0
- package/docs/{external-handlers.md → updates.md} +33 -31
- package/docs/voice.md +17 -14
- package/index.ts +86 -261
- package/lib/bindings.ts +301 -0
- package/lib/command-templates.ts +163 -32
- package/lib/commands.ts +114 -1
- package/lib/config.ts +45 -4
- package/lib/{inbound-handlers.ts → inbound.ts} +5 -4
- package/lib/lifecycle.ts +122 -1
- package/lib/menu-model.ts +3 -3
- package/lib/menu-queue.ts +1 -1
- package/lib/menu-settings.ts +63 -32
- package/lib/menu-status.ts +1 -1
- package/lib/menu.ts +1 -1
- package/lib/{outbound-handlers.ts → outbound.ts} +21 -11
- package/lib/pi.ts +4 -0
- package/lib/polling.ts +4 -3
- package/lib/preview.ts +1 -1
- package/lib/routing.ts +45 -13
- package/lib/{extension-sections.ts → sections.ts} +37 -8
- package/lib/time-injection.ts +1 -1
- package/lib/updates.ts +121 -1
- package/lib/voice.ts +33 -14
- package/package.json +11 -1
- package/lib/external-handlers.ts +0 -166
package/lib/menu-settings.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import {
|
|
8
8
|
getTelegramExtensionSettingsRows,
|
|
9
9
|
type TelegramSectionRegistry,
|
|
10
|
-
} from "./
|
|
10
|
+
} from "./sections.ts";
|
|
11
11
|
import type { TelegramTimeMode } from "./config.ts";
|
|
12
12
|
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
|
13
13
|
import type { TelegramModelMenuState } from "./menu-model.ts";
|
|
@@ -115,7 +115,8 @@ export interface TelegramSettingsMenuRuntimeDeps<
|
|
|
115
115
|
|
|
116
116
|
export const SETTINGS_MENU_TITLE = "<b>⚙️ Settings:</b>";
|
|
117
117
|
export const PROACTIVE_PUSH_SETTINGS_TITLE = "<b>📌 Proactive push:</b>";
|
|
118
|
-
export const TIME_INJECTION_MODE_SETTINGS_TITLE =
|
|
118
|
+
export const TIME_INJECTION_MODE_SETTINGS_TITLE =
|
|
119
|
+
"<b>🕒 Time injection mode:</b>";
|
|
119
120
|
export const VOICE_REPLY_MODE_SETTINGS_TITLE = "<b>👄 Voice reply mode:</b>";
|
|
120
121
|
|
|
121
122
|
type TelegramVoiceReplyModeSetting = TelegramVoiceReplyMode | "hidden";
|
|
@@ -124,6 +125,10 @@ function getVoiceReplyModeLabel(mode: TelegramVoiceReplyModeSetting): string {
|
|
|
124
125
|
return mode;
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
function getTelegramSettingsStateValueLabel(value: string): string {
|
|
129
|
+
return value.length > 0 ? value[0]!.toUpperCase() + value.slice(1) : value;
|
|
130
|
+
}
|
|
131
|
+
|
|
127
132
|
function getVoiceReplyModeSetting(
|
|
128
133
|
mode: TelegramVoiceReplyMode,
|
|
129
134
|
configured: boolean,
|
|
@@ -135,17 +140,24 @@ export function buildTelegramSettingsMenuText(): string {
|
|
|
135
140
|
return SETTINGS_MENU_TITLE;
|
|
136
141
|
}
|
|
137
142
|
|
|
138
|
-
export function buildProactivePushSettingsText(
|
|
143
|
+
export function buildProactivePushSettingsText(
|
|
144
|
+
proactivePushEnabled: boolean,
|
|
145
|
+
): string {
|
|
139
146
|
return [
|
|
140
|
-
PROACTIVE_PUSH_SETTINGS_TITLE
|
|
147
|
+
`${PROACTIVE_PUSH_SETTINGS_TITLE} <code>${proactivePushEnabled ? "on" : "off"}</code>`,
|
|
141
148
|
"",
|
|
142
149
|
"Send successful local π task results to Telegram when the bridge is connected.",
|
|
143
150
|
].join("\n");
|
|
144
151
|
}
|
|
145
152
|
|
|
146
|
-
export function buildVoiceReplyModeSettingsText(
|
|
153
|
+
export function buildVoiceReplyModeSettingsText(
|
|
154
|
+
mode: TelegramVoiceReplyMode,
|
|
155
|
+
configured = true,
|
|
156
|
+
): string {
|
|
147
157
|
return [
|
|
148
|
-
VOICE_REPLY_MODE_SETTINGS_TITLE
|
|
158
|
+
`${VOICE_REPLY_MODE_SETTINGS_TITLE} <code>${getVoiceReplyModeLabel(
|
|
159
|
+
getVoiceReplyModeSetting(mode, configured),
|
|
160
|
+
)}</code>`,
|
|
149
161
|
"",
|
|
150
162
|
"Controls when pi-telegram converts assistant text replies into Telegram voice messages.",
|
|
151
163
|
"",
|
|
@@ -156,13 +168,15 @@ export function buildVoiceReplyModeSettingsText(): string {
|
|
|
156
168
|
].join("\n");
|
|
157
169
|
}
|
|
158
170
|
|
|
159
|
-
export function buildTimeInjectionModeSettingsText(
|
|
171
|
+
export function buildTimeInjectionModeSettingsText(
|
|
172
|
+
mode: TelegramTimeMode,
|
|
173
|
+
): string {
|
|
160
174
|
return [
|
|
161
|
-
TIME_INJECTION_MODE_SETTINGS_TITLE
|
|
175
|
+
`${TIME_INJECTION_MODE_SETTINGS_TITLE} <code>${mode}</code>`,
|
|
162
176
|
"",
|
|
163
177
|
"Controls whether Telegram-originated prompts include a compact wall-clock [time] line.",
|
|
164
178
|
"",
|
|
165
|
-
"<code>-</code> <code>
|
|
179
|
+
"<code>-</code> <code>hidden</code> (default): no time line is added to prompt context.",
|
|
166
180
|
"<code>-</code> <code>always</code>: add time to every Telegram turn.",
|
|
167
181
|
"<code>-</code> <code>interval</code>: add time at most once per chat interval (default: 1 hour).",
|
|
168
182
|
].join("\n");
|
|
@@ -188,21 +202,23 @@ export function buildTelegramSettingsMenuReplyMarkup(
|
|
|
188
202
|
rows.push(
|
|
189
203
|
[
|
|
190
204
|
{
|
|
191
|
-
text: `👄 Voice reply: ${
|
|
192
|
-
|
|
205
|
+
text: `👄 Voice reply: ${getTelegramSettingsStateValueLabel(
|
|
206
|
+
getVoiceReplyModeLabel(
|
|
207
|
+
getVoiceReplyModeSetting(voiceReplyMode, voiceReplyModeConfigured),
|
|
208
|
+
),
|
|
193
209
|
)}`,
|
|
194
210
|
callback_data: "settings:open:voice-reply",
|
|
195
211
|
},
|
|
196
212
|
],
|
|
197
213
|
[
|
|
198
214
|
{
|
|
199
|
-
text: `🕒 Time injection: ${timeInjectionMode}`,
|
|
215
|
+
text: `🕒 Time injection: ${getTelegramSettingsStateValueLabel(timeInjectionMode)}`,
|
|
200
216
|
callback_data: "settings:open:time-injection",
|
|
201
217
|
},
|
|
202
218
|
],
|
|
203
219
|
[
|
|
204
220
|
{
|
|
205
|
-
text: `📌 Proactive push: ${proactivePushEnabled ? "
|
|
221
|
+
text: `📌 Proactive push: ${proactivePushEnabled ? "On" : "Off"}`,
|
|
206
222
|
callback_data: "settings:open:proactive",
|
|
207
223
|
},
|
|
208
224
|
],
|
|
@@ -242,11 +258,11 @@ export function buildProactivePushSettingsReplyMarkup(
|
|
|
242
258
|
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
243
259
|
[
|
|
244
260
|
{
|
|
245
|
-
text: proactivePushEnabled ? "🟢
|
|
261
|
+
text: proactivePushEnabled ? "🟢 On" : "⚫️ On",
|
|
246
262
|
callback_data: "settings:set:proactive:on",
|
|
247
263
|
},
|
|
248
264
|
{
|
|
249
|
-
text: proactivePushEnabled ? "⚫️
|
|
265
|
+
text: proactivePushEnabled ? "⚫️ Off" : "🟡 Off",
|
|
250
266
|
callback_data: "settings:set:proactive:off",
|
|
251
267
|
},
|
|
252
268
|
],
|
|
@@ -257,7 +273,7 @@ export function buildProactivePushSettingsReplyMarkup(
|
|
|
257
273
|
export function buildTimeInjectionModeSettingsReplyMarkup(
|
|
258
274
|
mode: TelegramTimeMode,
|
|
259
275
|
): TelegramSettingsMenuReplyMarkup {
|
|
260
|
-
const modes: TelegramTimeMode[] = ["
|
|
276
|
+
const modes: TelegramTimeMode[] = ["hidden", "always", "interval"];
|
|
261
277
|
return {
|
|
262
278
|
inline_keyboard: [
|
|
263
279
|
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
@@ -314,30 +330,31 @@ export async function updateTelegramSettingsMenuMessage(
|
|
|
314
330
|
export async function updateProactivePushSettingsMessage(
|
|
315
331
|
deps: TelegramSettingsMenuCallbackDeps,
|
|
316
332
|
): Promise<void> {
|
|
333
|
+
const proactivePushEnabled = deps.isProactivePushEnabled();
|
|
317
334
|
await deps.updateSettingsMessage(
|
|
318
|
-
buildProactivePushSettingsText(),
|
|
319
|
-
buildProactivePushSettingsReplyMarkup(
|
|
335
|
+
buildProactivePushSettingsText(proactivePushEnabled),
|
|
336
|
+
buildProactivePushSettingsReplyMarkup(proactivePushEnabled),
|
|
320
337
|
);
|
|
321
338
|
}
|
|
322
339
|
|
|
323
340
|
export async function updateTimeInjectionModeSettingsMessage(
|
|
324
341
|
deps: TelegramSettingsMenuCallbackDeps,
|
|
325
342
|
): Promise<void> {
|
|
343
|
+
const mode = deps.getTimeInjectionMode();
|
|
326
344
|
await deps.updateSettingsMessage(
|
|
327
|
-
buildTimeInjectionModeSettingsText(),
|
|
328
|
-
buildTimeInjectionModeSettingsReplyMarkup(
|
|
345
|
+
buildTimeInjectionModeSettingsText(mode),
|
|
346
|
+
buildTimeInjectionModeSettingsReplyMarkup(mode),
|
|
329
347
|
);
|
|
330
348
|
}
|
|
331
349
|
|
|
332
350
|
export async function updateVoiceReplyModeSettingsMessage(
|
|
333
351
|
deps: TelegramSettingsMenuCallbackDeps,
|
|
334
352
|
): Promise<void> {
|
|
353
|
+
const mode = deps.getVoiceReplyMode();
|
|
354
|
+
const configured = deps.isVoiceReplyModeConfigured();
|
|
335
355
|
await deps.updateSettingsMessage(
|
|
336
|
-
buildVoiceReplyModeSettingsText(),
|
|
337
|
-
buildVoiceReplyModeSettingsReplyMarkup(
|
|
338
|
-
deps.getVoiceReplyMode(),
|
|
339
|
-
deps.isVoiceReplyModeConfigured(),
|
|
340
|
-
),
|
|
356
|
+
buildVoiceReplyModeSettingsText(mode, configured),
|
|
357
|
+
buildVoiceReplyModeSettingsReplyMarkup(mode, configured),
|
|
341
358
|
);
|
|
342
359
|
}
|
|
343
360
|
|
|
@@ -362,7 +379,10 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
362
379
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
363
380
|
return true;
|
|
364
381
|
}
|
|
365
|
-
if (
|
|
382
|
+
if (
|
|
383
|
+
data === "settings:open:time-injection" ||
|
|
384
|
+
data === "settings:open:time"
|
|
385
|
+
) {
|
|
366
386
|
await updateTimeInjectionModeSettingsMessage(deps);
|
|
367
387
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
368
388
|
return true;
|
|
@@ -391,10 +411,18 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
391
411
|
const mode = data.startsWith("settings:set:time-injection:")
|
|
392
412
|
? data.slice("settings:set:time-injection:".length)
|
|
393
413
|
: data.slice("settings:set:time:".length);
|
|
394
|
-
|
|
395
|
-
|
|
414
|
+
const normalizedMode = mode === "off" ? "hidden" : mode;
|
|
415
|
+
if (
|
|
416
|
+
normalizedMode === "hidden" ||
|
|
417
|
+
normalizedMode === "always" ||
|
|
418
|
+
normalizedMode === "interval"
|
|
419
|
+
) {
|
|
420
|
+
await deps.setTimeInjectionMode(normalizedMode);
|
|
396
421
|
await updateTimeInjectionModeSettingsMessage(deps);
|
|
397
|
-
await deps.answerCallbackQuery(
|
|
422
|
+
await deps.answerCallbackQuery(
|
|
423
|
+
callbackQueryId,
|
|
424
|
+
`Time injection: ${normalizedMode}`,
|
|
425
|
+
);
|
|
398
426
|
return true;
|
|
399
427
|
}
|
|
400
428
|
}
|
|
@@ -488,15 +516,18 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
488
516
|
? query.data.slice("settings:set:time-injection:".length)
|
|
489
517
|
: query.data.slice("settings:set:time:".length);
|
|
490
518
|
if (
|
|
491
|
-
(hasTimeInjectionPrefix ||
|
|
519
|
+
(hasTimeInjectionPrefix ||
|
|
520
|
+
query.data.startsWith("settings:set:time:")) &&
|
|
492
521
|
(timeMode === "off" ||
|
|
522
|
+
timeMode === "hidden" ||
|
|
493
523
|
timeMode === "always" ||
|
|
494
524
|
timeMode === "interval")
|
|
495
525
|
) {
|
|
496
|
-
|
|
526
|
+
const normalizedMode = timeMode === "off" ? "hidden" : timeMode;
|
|
527
|
+
await deps.setTimeInjectionMode(normalizedMode);
|
|
497
528
|
await deps.answerCallbackQuery(
|
|
498
529
|
query.id,
|
|
499
|
-
`Time injection: ${
|
|
530
|
+
`Time injection: ${normalizedMode}`,
|
|
500
531
|
);
|
|
501
532
|
return true;
|
|
502
533
|
}
|
package/lib/menu-status.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { formatTelegramCommandEmojiPrefix } from "./commands.ts";
|
|
|
8
8
|
import {
|
|
9
9
|
getTelegramSectionMainMenuRows,
|
|
10
10
|
type TelegramSectionRegistry,
|
|
11
|
-
} from "./
|
|
11
|
+
} from "./sections.ts";
|
|
12
12
|
import {
|
|
13
13
|
formatStatusButtonLabel,
|
|
14
14
|
type TelegramMenuMessageRuntimeDeps,
|
package/lib/menu.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
handleTelegramSectionSettingsOpen,
|
|
11
11
|
parseTelegramSectionCallback,
|
|
12
12
|
type TelegramSectionRegistry,
|
|
13
|
-
} from "./
|
|
13
|
+
} from "./sections.ts";
|
|
14
14
|
import {
|
|
15
15
|
createTelegramModelMenuStateBuilder,
|
|
16
16
|
handleTelegramModelMenuCallbackAction,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram outbound
|
|
2
|
+
* Telegram outbound surface helpers
|
|
3
3
|
* Zones: telegram outbound, assistant markup, command templates, callback routing
|
|
4
|
-
* Owns assistant-authored outbound markup extraction, configured artifact generation, callback actions, and Telegram outbound delivery
|
|
4
|
+
* Owns assistant-authored outbound markup extraction, configured artifact generation, callback actions, runtime-event bridge, and Telegram outbound delivery
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
@@ -61,6 +61,18 @@ const DEFAULT_VOICE_TIMEOUT_MS = 120_000;
|
|
|
61
61
|
* diagnostics alongside pi-telegram's own events. Events are silently dropped
|
|
62
62
|
* when pi-telegram is not loaded.
|
|
63
63
|
*/
|
|
64
|
+
export type TelegramRuntimeEventRecorder = (
|
|
65
|
+
category: string,
|
|
66
|
+
error: unknown,
|
|
67
|
+
details?: Record<string, unknown>,
|
|
68
|
+
) => void;
|
|
69
|
+
|
|
70
|
+
export function bindTelegramRuntimeEventRecorder(
|
|
71
|
+
recorder: TelegramRuntimeEventRecorder,
|
|
72
|
+
): void {
|
|
73
|
+
(globalThis as Record<string, unknown>)[VOICE_EVENT_RECORDER_KEY] = recorder;
|
|
74
|
+
}
|
|
75
|
+
|
|
64
76
|
export function recordTelegramRuntimeEvent(
|
|
65
77
|
category: string,
|
|
66
78
|
error: unknown,
|
|
@@ -70,13 +82,7 @@ export function recordTelegramRuntimeEvent(
|
|
|
70
82
|
VOICE_EVENT_RECORDER_KEY
|
|
71
83
|
];
|
|
72
84
|
if (typeof recorder === "function") {
|
|
73
|
-
(
|
|
74
|
-
recorder as (
|
|
75
|
-
category: string,
|
|
76
|
-
error: unknown,
|
|
77
|
-
details?: Record<string, unknown>,
|
|
78
|
-
) => void
|
|
79
|
-
)(category, error, details);
|
|
85
|
+
(recorder as TelegramRuntimeEventRecorder)(category, error, details);
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
|
|
@@ -1028,7 +1034,9 @@ export function createTelegramVoiceReplySender(
|
|
|
1028
1034
|
});
|
|
1029
1035
|
return;
|
|
1030
1036
|
} catch (error) {
|
|
1031
|
-
deps.recordRuntimeEvent?.("voice", error, {
|
|
1037
|
+
deps.recordRuntimeEvent?.("voice", error, {
|
|
1038
|
+
phase: "template-handler-send",
|
|
1039
|
+
});
|
|
1032
1040
|
}
|
|
1033
1041
|
}
|
|
1034
1042
|
|
|
@@ -1045,7 +1053,9 @@ export function createTelegramVoiceReplySender(
|
|
|
1045
1053
|
});
|
|
1046
1054
|
return;
|
|
1047
1055
|
} catch (error) {
|
|
1048
|
-
deps.recordRuntimeEvent?.("voice", error, {
|
|
1056
|
+
deps.recordRuntimeEvent?.("voice", error, {
|
|
1057
|
+
phase: "programmatic-handler-send",
|
|
1058
|
+
});
|
|
1049
1059
|
}
|
|
1050
1060
|
}
|
|
1051
1061
|
|
package/lib/pi.ts
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
type ExtensionAPI,
|
|
12
12
|
type ExtensionCommandContext,
|
|
13
13
|
type ExtensionContext,
|
|
14
|
+
type SessionBeforeCompactEvent,
|
|
15
|
+
type SessionCompactEvent,
|
|
14
16
|
type SessionShutdownEvent,
|
|
15
17
|
type SessionStartEvent,
|
|
16
18
|
type SlashCommandInfo,
|
|
@@ -24,6 +26,8 @@ export type {
|
|
|
24
26
|
ExtensionAPI,
|
|
25
27
|
ExtensionCommandContext,
|
|
26
28
|
ExtensionContext,
|
|
29
|
+
SessionBeforeCompactEvent,
|
|
30
|
+
SessionCompactEvent,
|
|
27
31
|
SessionShutdownEvent,
|
|
28
32
|
SessionStartEvent,
|
|
29
33
|
SlashCommandInfo,
|
package/lib/polling.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram polling domain helpers
|
|
2
|
+
* Telegram polling runtime domain helpers
|
|
3
3
|
* Zones: telegram transport, polling runtime
|
|
4
4
|
* Owns polling request builders, stop conditions, and the long-poll loop runtime for Telegram updates
|
|
5
5
|
*/
|
|
@@ -91,8 +91,9 @@ export function createTelegramPollingActivityReader(
|
|
|
91
91
|
return () => isTelegramPollingControllerActive(state);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export interface TelegramPollingRuntimeDeps<
|
|
95
|
-
|
|
94
|
+
export interface TelegramPollingRuntimeDeps<
|
|
95
|
+
TContext,
|
|
96
|
+
> extends TelegramRuntimeEventRecorderPort {
|
|
96
97
|
hasBotToken: () => boolean;
|
|
97
98
|
getPollingPromise: () => Promise<void> | undefined;
|
|
98
99
|
setPollingPromise: (promise: Promise<void> | undefined) => void;
|
package/lib/preview.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from "./rendering.ts";
|
|
23
23
|
|
|
24
24
|
import { buildTelegramReplyParameters } from "./replies.ts";
|
|
25
|
-
import { stripTelegramCommentMarkupForPreview } from "./outbound
|
|
25
|
+
import { stripTelegramCommentMarkupForPreview } from "./outbound.ts";
|
|
26
26
|
import { shouldSuppressPreviewForVoice } from "./voice.ts";
|
|
27
27
|
|
|
28
28
|
const TELEGRAM_PREVIEW_THROTTLE_MS = 750;
|
package/lib/routing.ts
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
import { readFile } from "node:fs/promises";
|
|
8
8
|
import * as Commands from "./commands.ts";
|
|
9
9
|
import type { TelegramConfigStore } from "./config.ts";
|
|
10
|
-
import type { TelegramSectionRegistry } from "./
|
|
11
|
-
import type { TelegramInboundHandlerRuntime } from "./inbound
|
|
10
|
+
import type { TelegramSectionRegistry } from "./sections.ts";
|
|
11
|
+
import type { TelegramInboundHandlerRuntime } from "./inbound.ts";
|
|
12
12
|
import * as Media from "./media.ts";
|
|
13
13
|
import * as Menu from "./menu.ts";
|
|
14
14
|
import * as Model from "./model.ts";
|
|
15
|
-
import * as OutboundHandlers from "./outbound
|
|
15
|
+
import * as OutboundHandlers from "./outbound.ts";
|
|
16
16
|
import * as PromptTemplates from "./prompt-templates.ts";
|
|
17
17
|
import * as Queue from "./queue.ts";
|
|
18
18
|
import type { TelegramBridgeRuntime } from "./runtime.ts";
|
|
@@ -138,6 +138,7 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
const TELEGRAM_OWNED_CALLBACK_PREFIXES = [
|
|
141
|
+
"compact:",
|
|
141
142
|
"menu:",
|
|
142
143
|
"model:",
|
|
143
144
|
"queue:",
|
|
@@ -262,6 +263,45 @@ export function createTelegramInboundRouteRuntime<
|
|
|
262
263
|
);
|
|
263
264
|
if (handled) return;
|
|
264
265
|
}
|
|
266
|
+
const handledByCompact =
|
|
267
|
+
await Commands.handleTelegramCompactConfirmationCallback(query, {
|
|
268
|
+
ctx,
|
|
269
|
+
answerCallbackQuery: deps.answerCallbackQuery,
|
|
270
|
+
editInteractiveMessage: deps.editInteractiveMessage ?? (async () => {}),
|
|
271
|
+
runCompact: async (compactCtx, chatId, replyToMessageId) => {
|
|
272
|
+
await Commands.handleTelegramCompactCommand({
|
|
273
|
+
isIdle: () => deps.isIdle(compactCtx),
|
|
274
|
+
hasPendingMessages: () => deps.hasPendingMessages(compactCtx),
|
|
275
|
+
hasActiveTelegramTurn: deps.activeTurnRuntime.has,
|
|
276
|
+
hasDispatchPending: deps.bridgeRuntime.lifecycle.hasDispatchPending,
|
|
277
|
+
hasQueuedTelegramItems: deps.telegramQueueStore.hasQueuedItems,
|
|
278
|
+
isCompactionInProgress:
|
|
279
|
+
deps.bridgeRuntime.lifecycle.isCompactionInProgress,
|
|
280
|
+
setCompactionInProgress:
|
|
281
|
+
deps.bridgeRuntime.lifecycle.setCompactionInProgress,
|
|
282
|
+
updateStatus: () => deps.updateStatus(compactCtx),
|
|
283
|
+
dispatchNextQueuedTelegramTurn: () =>
|
|
284
|
+
deps.dispatchNextQueuedTelegramTurn(compactCtx),
|
|
285
|
+
requestDeferredDispatchNextQueuedTelegramTurn:
|
|
286
|
+
deps.requestDeferredDispatchNextQueuedTelegramTurn
|
|
287
|
+
? (dispatch) =>
|
|
288
|
+
deps.requestDeferredDispatchNextQueuedTelegramTurn?.(() =>
|
|
289
|
+
dispatch(),
|
|
290
|
+
)
|
|
291
|
+
: undefined,
|
|
292
|
+
compact: (callbacks) => deps.compact(compactCtx, callbacks),
|
|
293
|
+
startTypingLoop: deps.startTypingLoop
|
|
294
|
+
? () => deps.startTypingLoop?.(compactCtx, chatId)
|
|
295
|
+
: undefined,
|
|
296
|
+
stopTypingLoop: deps.stopTypingLoop,
|
|
297
|
+
sendTextReply: (text) =>
|
|
298
|
+
deps.sendTextReply(chatId, replyToMessageId, text).then(() => {}),
|
|
299
|
+
suppressStartNotice: true,
|
|
300
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
if (handledByCompact) return;
|
|
265
305
|
const handledByQueue = await deps.queueMenuCallbackHandler(query, ctx);
|
|
266
306
|
if (handledByQueue) return;
|
|
267
307
|
const handledBySettings = await deps.settingsMenuCallbackHandler?.(
|
|
@@ -302,28 +342,19 @@ export function createTelegramInboundRouteRuntime<
|
|
|
302
342
|
message: TMessage,
|
|
303
343
|
ctx: TContext,
|
|
304
344
|
): Promise<void> => {
|
|
305
|
-
const enqueuePlan = Queue.planTelegramPromptEnqueue(
|
|
306
|
-
deps.telegramQueueStore.getQueuedItems(),
|
|
307
|
-
deps.bridgeRuntime.lifecycle.shouldPreserveQueuedTurnsAsHistory(),
|
|
308
|
-
);
|
|
309
345
|
deps.bridgeRuntime.lifecycle.setPreserveQueuedTurnsAsHistory(false);
|
|
310
346
|
const continueMessage = {
|
|
311
347
|
...message,
|
|
312
348
|
text: "continue",
|
|
313
349
|
caption: undefined,
|
|
314
350
|
} as TMessage;
|
|
315
|
-
const turn = await promptTurnBuilder(
|
|
316
|
-
[continueMessage],
|
|
317
|
-
enqueuePlan.historyTurns,
|
|
318
|
-
ctx,
|
|
319
|
-
);
|
|
351
|
+
const turn = await promptTurnBuilder([continueMessage], [], ctx);
|
|
320
352
|
const continueTurn = {
|
|
321
353
|
...turn,
|
|
322
354
|
queueLane: "priority" as const,
|
|
323
355
|
laneOrder: Number.MIN_SAFE_INTEGER + turn.queueOrder,
|
|
324
356
|
statusSummary: "continue",
|
|
325
357
|
};
|
|
326
|
-
deps.telegramQueueStore.setQueuedItems(enqueuePlan.remainingItems);
|
|
327
358
|
deps.queueMutationRuntime.append(continueTurn, ctx);
|
|
328
359
|
deps.dispatchNextQueuedTelegramTurn(ctx);
|
|
329
360
|
};
|
|
@@ -381,6 +412,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
381
412
|
getPromptTemplateCommands,
|
|
382
413
|
persistConfig: deps.configStore.persist,
|
|
383
414
|
sendTextReply: deps.sendTextReply,
|
|
415
|
+
sendInteractiveMessage: deps.sendInteractiveMessage,
|
|
384
416
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
385
417
|
});
|
|
386
418
|
const promptEnqueue = Queue.createTelegramPromptEnqueueController<
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram Extension Sections registry and callback routing
|
|
3
3
|
* Zones: telegram ui, extension platform, callback routing
|
|
4
|
-
* Owns section registration, token mapping, main-menu/settings row injection, and section callback dispatch
|
|
4
|
+
* Owns section registration, global registry binding, token mapping, main-menu/settings row injection, and section callback dispatch
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
|
8
8
|
|
|
9
9
|
const SECTION_REGISTRY_KEY = "__piTelegramSectionRegistry__";
|
|
10
|
+
const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
|
|
10
11
|
|
|
11
12
|
// --- Core Types ---
|
|
12
13
|
|
|
@@ -181,9 +182,7 @@ function buildTelegramSectionContext(
|
|
|
181
182
|
.then(() => {}),
|
|
182
183
|
enqueuePrompt: deps.enqueuePrompt,
|
|
183
184
|
callbackData: (action, payload) =>
|
|
184
|
-
payload
|
|
185
|
-
? `section:${token}:${action}:${payload}`
|
|
186
|
-
: `section:${token}:${action}`,
|
|
185
|
+
buildTelegramSectionCallbackData(token, action, payload),
|
|
187
186
|
deleteMessage: () =>
|
|
188
187
|
messageId !== undefined
|
|
189
188
|
? deps.deleteMessage(chatId, messageId)
|
|
@@ -231,9 +230,7 @@ function buildTelegramSectionCallbackContext(
|
|
|
231
230
|
.then(() => {}),
|
|
232
231
|
enqueuePrompt: deps.enqueuePrompt,
|
|
233
232
|
callbackData: (action, payload) =>
|
|
234
|
-
payload
|
|
235
|
-
? `section:${token}:${action}:${payload}`
|
|
236
|
-
: `section:${token}:${action}`,
|
|
233
|
+
buildTelegramSectionCallbackData(token, action, payload),
|
|
237
234
|
deleteMessage: () =>
|
|
238
235
|
messageId !== undefined
|
|
239
236
|
? deps.deleteMessage(chatId, messageId)
|
|
@@ -250,6 +247,13 @@ export function setGlobalTelegramSectionRegistry(
|
|
|
250
247
|
(globalThis as Record<string, unknown>)[SECTION_REGISTRY_KEY] = registry;
|
|
251
248
|
}
|
|
252
249
|
|
|
250
|
+
/** @internal */
|
|
251
|
+
export function createAndBindTelegramSectionRegistry(): TelegramSectionRegistry {
|
|
252
|
+
const registry = createTelegramExtensionSectionRegistry();
|
|
253
|
+
setGlobalTelegramSectionRegistry(registry);
|
|
254
|
+
return registry;
|
|
255
|
+
}
|
|
256
|
+
|
|
253
257
|
/**
|
|
254
258
|
* Register a Telegram Extension Section from any pi extension.
|
|
255
259
|
* Returns a disposer. Throws if no section registry is active.
|
|
@@ -271,8 +275,8 @@ export function registerTelegramSection(
|
|
|
271
275
|
|
|
272
276
|
/**
|
|
273
277
|
* Get current section diagnostics. Returns empty array when registry is absent.
|
|
278
|
+
* @internal
|
|
274
279
|
*/
|
|
275
|
-
/** @internal */
|
|
276
280
|
export function getTelegramSectionDiagnostics(): TelegramSectionDiagnostic[] {
|
|
277
281
|
const registry = (globalThis as Record<string, unknown>)[
|
|
278
282
|
SECTION_REGISTRY_KEY
|
|
@@ -291,6 +295,27 @@ const BACK_NAV_ROW = {
|
|
|
291
295
|
text: "⬆️ Back",
|
|
292
296
|
} as const;
|
|
293
297
|
|
|
298
|
+
function getUtf8ByteLength(value: string): number {
|
|
299
|
+
return new TextEncoder().encode(value).byteLength;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function buildTelegramSectionCallbackData(
|
|
303
|
+
token: TelegramSectionToken,
|
|
304
|
+
action: string,
|
|
305
|
+
payload?: string,
|
|
306
|
+
): string {
|
|
307
|
+
const data = payload
|
|
308
|
+
? `section:${token}:${action}:${payload}`
|
|
309
|
+
: `section:${token}:${action}`;
|
|
310
|
+
const byteLength = getUtf8ByteLength(data);
|
|
311
|
+
if (byteLength > TELEGRAM_CALLBACK_DATA_MAX_BYTES) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`Telegram section callback_data exceeds ${TELEGRAM_CALLBACK_DATA_MAX_BYTES} bytes (${byteLength}). Use a shorter action/payload or store state behind a compact key.`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return data;
|
|
317
|
+
}
|
|
318
|
+
|
|
294
319
|
function prependBackRow(
|
|
295
320
|
replyMarkup: TelegramInlineKeyboardMarkup | undefined,
|
|
296
321
|
backCallback: string,
|
|
@@ -319,6 +344,10 @@ export function createTelegramExtensionSectionRegistry(): TelegramSectionRegistr
|
|
|
319
344
|
let nextToken = 0;
|
|
320
345
|
|
|
321
346
|
function register(section: TelegramSectionRegistration): () => void {
|
|
347
|
+
const duplicate = [...sections.values()].find((s) => s.id === section.id);
|
|
348
|
+
if (duplicate) {
|
|
349
|
+
throw new Error(`Telegram section id already registered: ${section.id}`);
|
|
350
|
+
}
|
|
322
351
|
const token = String(nextToken++);
|
|
323
352
|
const registered: RegisteredTelegramSection = {
|
|
324
353
|
id: section.id,
|
package/lib/time-injection.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function createTimeInjectionRuntime(
|
|
|
52
52
|
return {
|
|
53
53
|
resolveLine: (chatId, now = new Date()) => {
|
|
54
54
|
const config = deps.getConfig();
|
|
55
|
-
if (config.injectionMode === "
|
|
55
|
+
if (config.injectionMode === "hidden") return null;
|
|
56
56
|
let line: string;
|
|
57
57
|
try {
|
|
58
58
|
line = formatTelegramTimeInjectionLine(now, config.timezone);
|