@llblab/pi-telegram 0.37.2 → 0.39.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 +3 -3
- package/CHANGELOG.md +15 -0
- package/README.md +3 -3
- package/api/voice.ts +0 -1
- package/docs/architecture.md +4 -4
- package/docs/multi-instance-bus.md +1 -1
- package/docs/outbound.md +2 -3
- package/docs/public-api.md +4 -13
- package/docs/ui-style.md +21 -8
- package/docs/voice.md +9 -37
- package/index.ts +2 -0
- package/lib/bindings.ts +49 -13
- package/lib/commands.ts +103 -26
- package/lib/config.ts +10 -7
- package/lib/journal.ts +2 -9
- package/lib/lifecycle.ts +20 -18
- package/lib/menu-queue.ts +15 -4
- package/lib/menu-settings.ts +15 -11
- package/lib/menu.ts +10 -7
- package/lib/outbound-voice.ts +3 -18
- package/lib/prompts.ts +2 -2
- package/lib/queue.ts +49 -12
- package/lib/status.ts +4 -2
- package/lib/updates.ts +3 -3
- package/lib/voice.ts +7 -31
- package/package.json +1 -1
- package/skills/telegram-bridge/SKILL.md +1 -1
package/lib/commands.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
TELEGRAM_DEFAULT_PROFILE_NAME,
|
|
10
10
|
} from "./config.ts";
|
|
11
11
|
import type { ExtensionAPI, ExtensionCommandContext } from "./pi.ts";
|
|
12
|
+
import { escapeHtml } from "./rendering.ts";
|
|
12
13
|
import type { TelegramBridgeStatusLineOptions } from "./status.ts";
|
|
13
14
|
import {
|
|
14
15
|
createTelegramControlItemBuilder,
|
|
@@ -185,9 +186,24 @@ export function formatTelegramCommandEmojiPrefix(
|
|
|
185
186
|
return `${getTelegramCommandEmoji(command)} `;
|
|
186
187
|
}
|
|
187
188
|
|
|
189
|
+
export function formatTelegramInformationHeading(
|
|
190
|
+
emoji: string,
|
|
191
|
+
text: string,
|
|
192
|
+
): string {
|
|
193
|
+
return `<b>${escapeHtml(emoji)} ${escapeHtml(text)}</b>`;
|
|
194
|
+
}
|
|
195
|
+
|
|
188
196
|
export const TELEGRAM_COMPACTION_STARTED_TEXT =
|
|
189
|
-
|
|
190
|
-
|
|
197
|
+
formatTelegramInformationHeading(
|
|
198
|
+
getTelegramCommandEmoji("compact"),
|
|
199
|
+
"Compaction started.",
|
|
200
|
+
);
|
|
201
|
+
export const TELEGRAM_COMPACTION_COMPLETED_TEXT =
|
|
202
|
+
formatTelegramInformationHeading("✅", "Compaction completed.");
|
|
203
|
+
export const TELEGRAM_COMPACTION_STARTED_MARKDOWN =
|
|
204
|
+
`**${formatTelegramCommandEmojiPrefix("compact")}Compaction started.**`;
|
|
205
|
+
export const TELEGRAM_COMPACTION_COMPLETED_MARKDOWN =
|
|
206
|
+
"**✅ Compaction completed.**";
|
|
191
207
|
|
|
192
208
|
function formatTelegramBotCommandDescription(
|
|
193
209
|
command: TelegramCommandEmojiName,
|
|
@@ -585,7 +601,10 @@ export interface TelegramStopCommandDeps {
|
|
|
585
601
|
setFoldQueuedPromptsIntoHistory: (fold: boolean) => void;
|
|
586
602
|
abortCurrentTurn: () => void;
|
|
587
603
|
updateStatus: () => void;
|
|
588
|
-
sendTextReply: (
|
|
604
|
+
sendTextReply: (
|
|
605
|
+
text: string,
|
|
606
|
+
options?: { parseMode?: "HTML" },
|
|
607
|
+
) => Promise<void>;
|
|
589
608
|
}
|
|
590
609
|
|
|
591
610
|
export interface TelegramRuntimeEventRecorderPort {
|
|
@@ -619,7 +638,10 @@ export interface TelegramCompactCommandDeps extends TelegramRuntimeEventRecorder
|
|
|
619
638
|
onComplete: () => void;
|
|
620
639
|
onError: (error: unknown) => void;
|
|
621
640
|
}) => void;
|
|
622
|
-
sendTextReply: (
|
|
641
|
+
sendTextReply: (
|
|
642
|
+
text: string,
|
|
643
|
+
options?: { parseMode?: "HTML" },
|
|
644
|
+
) => Promise<void>;
|
|
623
645
|
suppressStartNotice?: boolean;
|
|
624
646
|
}
|
|
625
647
|
|
|
@@ -921,8 +943,11 @@ export function createTelegramCommandTargetRuntime<
|
|
|
921
943
|
await deps.sendTextReply(
|
|
922
944
|
target.chatId,
|
|
923
945
|
target.replyToMessageId,
|
|
924
|
-
|
|
925
|
-
|
|
946
|
+
formatTelegramInformationHeading(
|
|
947
|
+
"🚫",
|
|
948
|
+
"Settings menu is unavailable.",
|
|
949
|
+
),
|
|
950
|
+
{ target, parseMode: "HTML" },
|
|
926
951
|
);
|
|
927
952
|
return;
|
|
928
953
|
}
|
|
@@ -1165,7 +1190,10 @@ export async function handleTelegramStopCommand(
|
|
|
1165
1190
|
? ` Cleared ${formatTelegramQueuedTurnCount(clearedCount)}.`
|
|
1166
1191
|
: "";
|
|
1167
1192
|
if (clearedCount > 0) deps.updateStatus();
|
|
1168
|
-
await deps.sendTextReply(
|
|
1193
|
+
await deps.sendTextReply(
|
|
1194
|
+
formatTelegramInformationHeading("💤", `No active turn.${clearedSuffix}`),
|
|
1195
|
+
{ parseMode: "HTML" },
|
|
1196
|
+
);
|
|
1169
1197
|
return;
|
|
1170
1198
|
}
|
|
1171
1199
|
deps.abortCurrentTurn();
|
|
@@ -1174,7 +1202,13 @@ export async function handleTelegramStopCommand(
|
|
|
1174
1202
|
clearedCount > 0
|
|
1175
1203
|
? ` Cleared ${formatTelegramQueuedTurnCount(clearedCount)}.`
|
|
1176
1204
|
: "";
|
|
1177
|
-
await deps.sendTextReply(
|
|
1205
|
+
await deps.sendTextReply(
|
|
1206
|
+
formatTelegramInformationHeading(
|
|
1207
|
+
"⏹️",
|
|
1208
|
+
`Aborted current turn.${clearedSuffix}`,
|
|
1209
|
+
),
|
|
1210
|
+
{ parseMode: "HTML" },
|
|
1211
|
+
);
|
|
1178
1212
|
}
|
|
1179
1213
|
|
|
1180
1214
|
export async function handleTelegramAbortCommand(deps: {
|
|
@@ -1184,17 +1218,26 @@ export async function handleTelegramAbortCommand(deps: {
|
|
|
1184
1218
|
abortCurrentTurn: () => void;
|
|
1185
1219
|
setFoldQueuedPromptsIntoHistory: (fold: boolean) => void;
|
|
1186
1220
|
updateStatus: () => void;
|
|
1187
|
-
sendTextReply: (
|
|
1221
|
+
sendTextReply: (
|
|
1222
|
+
text: string,
|
|
1223
|
+
options?: { parseMode?: "HTML" },
|
|
1224
|
+
) => Promise<void>;
|
|
1188
1225
|
}): Promise<void> {
|
|
1189
1226
|
deps.clearPendingModelSwitch();
|
|
1190
1227
|
if (!deps.hasAbortHandler()) {
|
|
1191
|
-
await deps.sendTextReply(
|
|
1228
|
+
await deps.sendTextReply(
|
|
1229
|
+
formatTelegramInformationHeading("💤", "No active turn."),
|
|
1230
|
+
{ parseMode: "HTML" },
|
|
1231
|
+
);
|
|
1192
1232
|
return;
|
|
1193
1233
|
}
|
|
1194
1234
|
deps.setFoldQueuedPromptsIntoHistory(deps.hasActiveTelegramTurn());
|
|
1195
1235
|
deps.abortCurrentTurn();
|
|
1196
1236
|
deps.updateStatus();
|
|
1197
|
-
await deps.sendTextReply(
|
|
1237
|
+
await deps.sendTextReply(
|
|
1238
|
+
formatTelegramInformationHeading("⏹️", "Aborted current turn."),
|
|
1239
|
+
{ parseMode: "HTML" },
|
|
1240
|
+
);
|
|
1198
1241
|
}
|
|
1199
1242
|
|
|
1200
1243
|
export async function handleTelegramNextCommand(deps: {
|
|
@@ -1213,7 +1256,10 @@ export async function handleTelegramNextCommand(deps: {
|
|
|
1213
1256
|
}): Promise<void> {
|
|
1214
1257
|
deps.clearPendingModelSwitch();
|
|
1215
1258
|
if (!deps.hasQueuedItems()) {
|
|
1216
|
-
await deps.sendTextReply(
|
|
1259
|
+
await deps.sendTextReply(
|
|
1260
|
+
formatTelegramInformationHeading("⌛", "Queue is empty."),
|
|
1261
|
+
{ parseMode: "HTML" },
|
|
1262
|
+
);
|
|
1217
1263
|
return;
|
|
1218
1264
|
}
|
|
1219
1265
|
if (!deps.isIdle() && deps.hasAbortHandler()) {
|
|
@@ -1221,17 +1267,30 @@ export async function handleTelegramNextCommand(deps: {
|
|
|
1221
1267
|
deps.abortCurrentTurn();
|
|
1222
1268
|
deps.updateStatus();
|
|
1223
1269
|
await deps.sendTextReply(
|
|
1224
|
-
|
|
1270
|
+
formatTelegramInformationHeading(
|
|
1271
|
+
"⏩",
|
|
1272
|
+
"Aborted! Dispatching next queued turn.",
|
|
1273
|
+
),
|
|
1274
|
+
{ parseMode: "HTML" },
|
|
1225
1275
|
);
|
|
1226
1276
|
return;
|
|
1227
1277
|
}
|
|
1228
1278
|
if (!deps.isIdle()) {
|
|
1229
|
-
await deps.sendTextReply(
|
|
1279
|
+
await deps.sendTextReply(
|
|
1280
|
+
formatTelegramInformationHeading(
|
|
1281
|
+
"⏳",
|
|
1282
|
+
"Pi is busy. Send /abort or /stop first.",
|
|
1283
|
+
),
|
|
1284
|
+
{ parseMode: "HTML" },
|
|
1285
|
+
);
|
|
1230
1286
|
return;
|
|
1231
1287
|
}
|
|
1232
1288
|
deps.dispatchNextQueuedTurn();
|
|
1233
1289
|
deps.updateStatus();
|
|
1234
|
-
await deps.sendTextReply(
|
|
1290
|
+
await deps.sendTextReply(
|
|
1291
|
+
formatTelegramInformationHeading("▶️", "Dispatching next queued turn."),
|
|
1292
|
+
{ parseMode: "HTML" },
|
|
1293
|
+
);
|
|
1235
1294
|
}
|
|
1236
1295
|
|
|
1237
1296
|
export async function handleTelegramContinueCommand<TMessage, TContext>(
|
|
@@ -1301,15 +1360,15 @@ export async function handleTelegramCompactConfirmationCallback<TContext>(
|
|
|
1301
1360
|
const chatId = callbackMessage?.chat?.id;
|
|
1302
1361
|
const messageId = callbackMessage?.message_id;
|
|
1303
1362
|
if (typeof chatId !== "number" || typeof messageId !== "number") {
|
|
1304
|
-
await deps.answerCallbackQuery(query.id, "Interactive message expired.");
|
|
1363
|
+
await deps.answerCallbackQuery(query.id, "⌛ Interactive message expired.");
|
|
1305
1364
|
return true;
|
|
1306
1365
|
}
|
|
1307
1366
|
if (query.data === "compact:cancel") {
|
|
1308
1367
|
await deps.editInteractiveMessage(
|
|
1309
1368
|
chatId,
|
|
1310
1369
|
messageId,
|
|
1311
|
-
"Compaction cancelled
|
|
1312
|
-
"
|
|
1370
|
+
"<b>🚫 Compaction cancelled.</b>",
|
|
1371
|
+
"html",
|
|
1313
1372
|
{ inline_keyboard: [] },
|
|
1314
1373
|
);
|
|
1315
1374
|
await deps.answerCallbackQuery(query.id);
|
|
@@ -1319,7 +1378,7 @@ export async function handleTelegramCompactConfirmationCallback<TContext>(
|
|
|
1319
1378
|
chatId,
|
|
1320
1379
|
messageId,
|
|
1321
1380
|
TELEGRAM_COMPACTION_STARTED_TEXT,
|
|
1322
|
-
"
|
|
1381
|
+
"html",
|
|
1323
1382
|
{ inline_keyboard: [] },
|
|
1324
1383
|
);
|
|
1325
1384
|
await deps.answerCallbackQuery(query.id);
|
|
@@ -1345,7 +1404,11 @@ export async function handleTelegramCompactCommand(
|
|
|
1345
1404
|
deps.isCompactionInProgress()
|
|
1346
1405
|
) {
|
|
1347
1406
|
await deps.sendTextReply(
|
|
1348
|
-
|
|
1407
|
+
formatTelegramInformationHeading(
|
|
1408
|
+
"⏳",
|
|
1409
|
+
"Cannot compact while Pi or the Telegram queue is busy. Wait for queued turns to finish or send /abort first.",
|
|
1410
|
+
),
|
|
1411
|
+
{ parseMode: "HTML" },
|
|
1349
1412
|
);
|
|
1350
1413
|
return;
|
|
1351
1414
|
}
|
|
@@ -1359,7 +1422,9 @@ export async function handleTelegramCompactCommand(
|
|
|
1359
1422
|
deps.setCompactionInProgress(false);
|
|
1360
1423
|
deps.updateStatus();
|
|
1361
1424
|
dispatchNextQueuedTelegramTurnAfterCompact(deps);
|
|
1362
|
-
void deps.sendTextReply(TELEGRAM_COMPACTION_COMPLETED_TEXT
|
|
1425
|
+
void deps.sendTextReply(TELEGRAM_COMPACTION_COMPLETED_TEXT, {
|
|
1426
|
+
parseMode: "HTML",
|
|
1427
|
+
});
|
|
1363
1428
|
},
|
|
1364
1429
|
onError: (error) => {
|
|
1365
1430
|
deps.stopTypingLoop?.();
|
|
@@ -1368,7 +1433,13 @@ export async function handleTelegramCompactCommand(
|
|
|
1368
1433
|
dispatchNextQueuedTelegramTurnAfterCompact(deps);
|
|
1369
1434
|
deps.recordRuntimeEvent?.("compact", error);
|
|
1370
1435
|
const errorMessage = getTelegramCommandErrorMessage(error);
|
|
1371
|
-
void deps.sendTextReply(
|
|
1436
|
+
void deps.sendTextReply(
|
|
1437
|
+
formatTelegramInformationHeading(
|
|
1438
|
+
"❌",
|
|
1439
|
+
`Compaction failed: ${errorMessage}`,
|
|
1440
|
+
),
|
|
1441
|
+
{ parseMode: "HTML" },
|
|
1442
|
+
);
|
|
1372
1443
|
},
|
|
1373
1444
|
});
|
|
1374
1445
|
} catch (error) {
|
|
@@ -1377,13 +1448,19 @@ export async function handleTelegramCompactCommand(
|
|
|
1377
1448
|
deps.updateStatus();
|
|
1378
1449
|
deps.recordRuntimeEvent?.("compact", error);
|
|
1379
1450
|
const errorMessage = getTelegramCommandErrorMessage(error);
|
|
1380
|
-
await deps.sendTextReply(
|
|
1451
|
+
await deps.sendTextReply(
|
|
1452
|
+
formatTelegramInformationHeading(
|
|
1453
|
+
"❌",
|
|
1454
|
+
`Compaction failed: ${errorMessage}`,
|
|
1455
|
+
),
|
|
1456
|
+
{ parseMode: "HTML" },
|
|
1457
|
+
);
|
|
1381
1458
|
return;
|
|
1382
1459
|
}
|
|
1383
1460
|
if (!deps.suppressStartNotice) {
|
|
1384
|
-
await deps.sendTextReply(
|
|
1385
|
-
|
|
1386
|
-
);
|
|
1461
|
+
await deps.sendTextReply(TELEGRAM_COMPACTION_STARTED_TEXT, {
|
|
1462
|
+
parseMode: "HTML",
|
|
1463
|
+
});
|
|
1387
1464
|
}
|
|
1388
1465
|
}
|
|
1389
1466
|
|
package/lib/config.ts
CHANGED
|
@@ -118,9 +118,8 @@ export interface TelegramConfig {
|
|
|
118
118
|
/** @deprecated use assistant.rendering */
|
|
119
119
|
assistantRendering?: TelegramAssistantRenderingMode;
|
|
120
120
|
voice?: {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
sendTranscript?: boolean;
|
|
121
|
+
/** `hidden` is a read-only compatibility alias for the former manual mode. */
|
|
122
|
+
replyMode?: "manual" | "hidden" | "mirror" | "always";
|
|
124
123
|
};
|
|
125
124
|
time?: TelegramTimeConfig;
|
|
126
125
|
threads?: {
|
|
@@ -845,10 +844,10 @@ export function createTelegramActivityVerbositySetter(
|
|
|
845
844
|
|
|
846
845
|
export function createTelegramVoiceReplyModeGetter(
|
|
847
846
|
configStore: Pick<TelegramConfigStore, "get">,
|
|
848
|
-
): () => "
|
|
847
|
+
): () => "manual" | "mirror" | "always" {
|
|
849
848
|
return () => {
|
|
850
849
|
const mode = configStore.get().voice?.replyMode;
|
|
851
|
-
return mode === "mirror" || mode === "always" ? mode : "
|
|
850
|
+
return mode === "mirror" || mode === "always" ? mode : "manual";
|
|
852
851
|
};
|
|
853
852
|
}
|
|
854
853
|
|
|
@@ -863,11 +862,15 @@ export function createTelegramVoiceReplyModeConfiguredChecker(
|
|
|
863
862
|
|
|
864
863
|
export function createTelegramVoiceReplyModeSetter(
|
|
865
864
|
configStore: TelegramMutableConfigStore,
|
|
866
|
-
): (replyMode: "hidden" | "mirror" | "always" | undefined) => Promise<void> {
|
|
865
|
+
): (replyMode: "manual" | "hidden" | "mirror" | "always" | undefined) => Promise<void> {
|
|
867
866
|
return async (replyMode) => {
|
|
868
867
|
await loadLatestTelegramConfig(configStore);
|
|
869
868
|
const current = configStore.get();
|
|
870
|
-
if (
|
|
869
|
+
if (
|
|
870
|
+
replyMode === undefined ||
|
|
871
|
+
replyMode === "manual" ||
|
|
872
|
+
replyMode === "hidden"
|
|
873
|
+
) {
|
|
871
874
|
const { replyMode: _replyMode, ...remainingVoice } = current.voice ?? {};
|
|
872
875
|
const next = { ...current };
|
|
873
876
|
if (Object.keys(remainingVoice).length > 0) next.voice = remainingVoice;
|
package/lib/journal.ts
CHANGED
|
@@ -3381,15 +3381,8 @@ export function createTelegramUpdateJournalStore(
|
|
|
3381
3381
|
const recoveredUpdateIds = [...requestedIds].sort((a, b) => a - b);
|
|
3382
3382
|
const published = publishMutation(
|
|
3383
3383
|
current,
|
|
3384
|
-
current.file.entries.
|
|
3385
|
-
requestedIds.has(entry.updateId)
|
|
3386
|
-
? {
|
|
3387
|
-
updateId: entry.updateId,
|
|
3388
|
-
update: entry.update,
|
|
3389
|
-
admittedAtMs: entry.admittedAtMs,
|
|
3390
|
-
state: "pending" as const,
|
|
3391
|
-
}
|
|
3392
|
-
: entry,
|
|
3384
|
+
current.file.entries.filter(
|
|
3385
|
+
(entry) => !requestedIds.has(entry.updateId),
|
|
3393
3386
|
),
|
|
3394
3387
|
true,
|
|
3395
3388
|
);
|
package/lib/lifecycle.ts
CHANGED
|
@@ -345,25 +345,27 @@ export function createTelegramBridgeSessionLifecycleAssembly<
|
|
|
345
345
|
stopPolling: suspendForReplacement,
|
|
346
346
|
clearPendingMediaGroups: deps.services.suspendGroupedInput,
|
|
347
347
|
});
|
|
348
|
-
const servicesLifecycle =
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
},
|
|
358
|
-
async onSessionShutdown() {
|
|
359
|
-
await deps.services.delivery.onSessionShutdown();
|
|
360
|
-
deps.services.queueWatchdog.stop();
|
|
361
|
-
deps.services.capabilityMonitor.stop();
|
|
362
|
-
await deps.services.inboundWorker.onSessionShutdown();
|
|
363
|
-
},
|
|
348
|
+
const servicesLifecycle: TelegramSessionLifecycleHooks = {
|
|
349
|
+
async onSessionStart(event, ctx) {
|
|
350
|
+
await queueLifecycle.onSessionStart(event, ctx);
|
|
351
|
+
if (!isSessionActive(ctx)) return;
|
|
352
|
+
deps.services.resumeGroupedInput(ctx);
|
|
353
|
+
await deps.services.delivery.onSessionStart();
|
|
354
|
+
await deps.services.polling.onSessionStart(event, ctx);
|
|
355
|
+
deps.services.capabilityMonitor.start(ctx);
|
|
356
|
+
deps.services.queueWatchdog.start(ctx);
|
|
364
357
|
},
|
|
365
|
-
|
|
366
|
-
|
|
358
|
+
async onSessionShutdown(event, ctx) {
|
|
359
|
+
if (!isSessionActive(ctx)) return;
|
|
360
|
+
await deps.services.delivery.onSessionShutdown();
|
|
361
|
+
if (!isSessionActive(ctx)) return;
|
|
362
|
+
deps.services.queueWatchdog.stop();
|
|
363
|
+
deps.services.capabilityMonitor.stop();
|
|
364
|
+
await queueLifecycle.onSessionShutdown(event, ctx);
|
|
365
|
+
if (!isSessionActive(ctx)) return;
|
|
366
|
+
await deps.services.inboundWorker.onSessionShutdown();
|
|
367
|
+
},
|
|
368
|
+
};
|
|
367
369
|
const followerLifecycle = appendTelegramLifecycleHooks(
|
|
368
370
|
servicesLifecycle,
|
|
369
371
|
{
|
package/lib/menu-queue.ts
CHANGED
|
@@ -73,6 +73,10 @@ function toTelegramQueueMenuItems<Context>(
|
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
function formatSkippedTelegramQueuePosition(position: number): string {
|
|
77
|
+
return Array.from(String(position), (char) => `${char}\u0335`).join("");
|
|
78
|
+
}
|
|
79
|
+
|
|
76
80
|
function buildTelegramQueueMenuReplyMarkup(
|
|
77
81
|
items: readonly TelegramQueueMenuItem[],
|
|
78
82
|
emptyRefreshIndex = 0,
|
|
@@ -86,7 +90,7 @@ function buildTelegramQueueMenuReplyMarkup(
|
|
|
86
90
|
: "queue:refresh";
|
|
87
91
|
const refreshRow = [{ text: "🌀 Refresh", callback_data: refreshData }];
|
|
88
92
|
if (items.length === 0) return { inline_keyboard: [backRow, refreshRow] };
|
|
89
|
-
const rows = items.map((item
|
|
93
|
+
const rows = items.map((item) => {
|
|
90
94
|
const prefix = item.reactionSuppressionEmoji
|
|
91
95
|
? `${item.reactionSuppressionEmoji} `
|
|
92
96
|
: item.isPriority
|
|
@@ -94,7 +98,11 @@ function buildTelegramQueueMenuReplyMarkup(
|
|
|
94
98
|
: item.hasAttachments
|
|
95
99
|
? "📎 "
|
|
96
100
|
: "";
|
|
97
|
-
const
|
|
101
|
+
const position = item.reactionSuppressionEmoji
|
|
102
|
+
? formatSkippedTelegramQueuePosition(item.queuePosition)
|
|
103
|
+
: String(item.queuePosition);
|
|
104
|
+
const ordinalSeparator = item.reactionSuppressionEmoji ? "\u200A" : "";
|
|
105
|
+
const label = `${position}${ordinalSeparator}. ${prefix}${item.statusSummary}`;
|
|
98
106
|
return [
|
|
99
107
|
{
|
|
100
108
|
text: label,
|
|
@@ -162,7 +170,10 @@ function getTelegramQueueMenuItemText(item: TelegramQueueMenuItem): string {
|
|
|
162
170
|
: item.isPriority
|
|
163
171
|
? ` ${item.priorityEmoji ?? "⚡"}`
|
|
164
172
|
: "";
|
|
165
|
-
const
|
|
173
|
+
const position = item.reactionSuppressionEmoji
|
|
174
|
+
? `<s>${item.queuePosition}</s>.`
|
|
175
|
+
: `<b>${item.queuePosition}.</b>`;
|
|
176
|
+
const heading = `${position}${badge}`;
|
|
166
177
|
const preview = `<pre>${escapeTelegramQueueMenuHtmlPreview(item.promptText)}</pre>`;
|
|
167
178
|
return `${heading}\n${preview}`;
|
|
168
179
|
}
|
|
@@ -182,7 +193,7 @@ function buildTelegramQueueItemSubmenuReplyMarkup(
|
|
|
182
193
|
callback_data: `queue:prio-set:${chatId}:${replyToMessageId}:priority`,
|
|
183
194
|
},
|
|
184
195
|
{
|
|
185
|
-
text: isPriority ? "⚫️ Normal" : "
|
|
196
|
+
text: isPriority ? "⚫️ Normal" : "🔵 Normal",
|
|
186
197
|
callback_data: `queue:prio-set:${chatId}:${replyToMessageId}:normal`,
|
|
187
198
|
},
|
|
188
199
|
],
|
package/lib/menu-settings.ts
CHANGED
|
@@ -149,9 +149,7 @@ export const TIME_INJECTION_MODE_SETTINGS_TITLE =
|
|
|
149
149
|
"<b>🕒 Time injection mode:</b>";
|
|
150
150
|
export const VOICE_REPLY_MODE_SETTINGS_TITLE = "<b>👄 Voice reply mode:</b>";
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
function getVoiceReplyModeLabel(mode: TelegramVoiceReplyModeSetting): string {
|
|
152
|
+
function getVoiceReplyModeLabel(mode: TelegramVoiceReplyMode): string {
|
|
155
153
|
return mode;
|
|
156
154
|
}
|
|
157
155
|
|
|
@@ -162,8 +160,8 @@ function getTelegramSettingsStateValueLabel(value: string): string {
|
|
|
162
160
|
function getVoiceReplyModeSetting(
|
|
163
161
|
mode: TelegramVoiceReplyMode,
|
|
164
162
|
configured: boolean,
|
|
165
|
-
):
|
|
166
|
-
return configured ? mode : "
|
|
163
|
+
): TelegramVoiceReplyMode {
|
|
164
|
+
return configured ? mode : "manual";
|
|
167
165
|
}
|
|
168
166
|
|
|
169
167
|
export function buildTelegramSettingsMenuText(): string {
|
|
@@ -246,8 +244,8 @@ export function buildVoiceReplyModeSettingsText(
|
|
|
246
244
|
"",
|
|
247
245
|
"Controls when pi-telegram converts assistant text replies into Telegram voice messages.",
|
|
248
246
|
"",
|
|
249
|
-
"<code>-</code> <code>
|
|
250
|
-
"<code>-</code> <code>mirror</code>: voice input activates automatic voice delivery; text input follows '
|
|
247
|
+
"<code>-</code> <code>manual</code> (default): add no automatic voice context; explicit 'telegram_voice' actions still work.",
|
|
248
|
+
"<code>-</code> <code>mirror</code>: voice input activates automatic voice delivery; text input follows 'manual' behavior.",
|
|
251
249
|
"<code>-</code> <code>always</code>: activate automatic voice delivery for every reply.",
|
|
252
250
|
].join("\n");
|
|
253
251
|
}
|
|
@@ -502,7 +500,7 @@ export function buildVoiceReplyModeSettingsReplyMarkup(
|
|
|
502
500
|
configured = true,
|
|
503
501
|
): TelegramSettingsMenuReplyMarkup {
|
|
504
502
|
const activeMode = getVoiceReplyModeSetting(mode, configured);
|
|
505
|
-
const modes:
|
|
503
|
+
const modes: TelegramVoiceReplyMode[] = ["manual", "mirror", "always"];
|
|
506
504
|
return {
|
|
507
505
|
inline_keyboard: [
|
|
508
506
|
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
@@ -661,12 +659,18 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
661
659
|
}
|
|
662
660
|
if (data.startsWith("settings:set:voice-reply:")) {
|
|
663
661
|
const mode = data.slice("settings:set:voice-reply:".length);
|
|
664
|
-
if (
|
|
665
|
-
|
|
662
|
+
if (
|
|
663
|
+
mode === "manual" ||
|
|
664
|
+
mode === "hidden" ||
|
|
665
|
+
mode === "mirror" ||
|
|
666
|
+
mode === "always"
|
|
667
|
+
) {
|
|
668
|
+
const normalizedMode = mode === "hidden" ? "manual" : mode;
|
|
669
|
+
await deps.setVoiceReplyMode(normalizedMode);
|
|
666
670
|
await updateVoiceReplyModeSettingsMessage(deps);
|
|
667
671
|
await deps.answerCallbackQuery(
|
|
668
672
|
callbackQueryId,
|
|
669
|
-
`Voice reply mode: ${
|
|
673
|
+
`Voice reply mode: ${normalizedMode}`,
|
|
670
674
|
);
|
|
671
675
|
return true;
|
|
672
676
|
}
|
package/lib/menu.ts
CHANGED
|
@@ -255,7 +255,10 @@ export interface TelegramMenuActionRuntimeDeps<
|
|
|
255
255
|
chatId: number,
|
|
256
256
|
replyToMessageId: number,
|
|
257
257
|
text: string,
|
|
258
|
-
options?: {
|
|
258
|
+
options?: {
|
|
259
|
+
target?: { chatId: number; threadId?: number };
|
|
260
|
+
parseMode?: "HTML";
|
|
261
|
+
},
|
|
259
262
|
) => Promise<unknown>;
|
|
260
263
|
sectionRegistry?: TelegramSectionRegistry;
|
|
261
264
|
isVoiceReplyActive?: () => boolean;
|
|
@@ -798,8 +801,8 @@ export function createTelegramMenuActionRuntime<
|
|
|
798
801
|
await deps.sendTextReply(
|
|
799
802
|
chatId,
|
|
800
803
|
replyToMessageId,
|
|
801
|
-
"Cannot open status while Pi is busy. Send /abort, /next, or /stop
|
|
802
|
-
{ target: { chatId, threadId } },
|
|
804
|
+
"<b>⏳ Cannot open status while Pi is busy. Send /abort, /next, or /stop.</b>",
|
|
805
|
+
{ target: { chatId, threadId }, parseMode: "HTML" },
|
|
803
806
|
);
|
|
804
807
|
},
|
|
805
808
|
getModelMenuState: () => deps.getModelMenuState(chatId, ctx, threadId),
|
|
@@ -835,16 +838,16 @@ export function createTelegramMenuActionRuntime<
|
|
|
835
838
|
await deps.sendTextReply(
|
|
836
839
|
chatId,
|
|
837
840
|
replyToMessageId,
|
|
838
|
-
"Cannot switch model while Pi is busy. Send /abort, /next, or /stop
|
|
839
|
-
{ target: { chatId, threadId } },
|
|
841
|
+
"<b>⏳ Cannot switch model while Pi is busy. Send /abort, /next, or /stop.</b>",
|
|
842
|
+
{ target: { chatId, threadId }, parseMode: "HTML" },
|
|
840
843
|
);
|
|
841
844
|
},
|
|
842
845
|
sendNoModelsMessage: async () => {
|
|
843
846
|
await deps.sendTextReply(
|
|
844
847
|
chatId,
|
|
845
848
|
replyToMessageId,
|
|
846
|
-
"No available models with configured auth
|
|
847
|
-
{ target: { chatId, threadId } },
|
|
849
|
+
"<b>🚫 No available models with configured auth.</b>",
|
|
850
|
+
{ target: { chatId, threadId }, parseMode: "HTML" },
|
|
848
851
|
);
|
|
849
852
|
},
|
|
850
853
|
getModelMenuState: () => deps.getModelMenuState(chatId, ctx, threadId),
|
package/lib/outbound-voice.ts
CHANGED
|
@@ -100,17 +100,6 @@ async function ensureTelegramVoiceFileFormat(
|
|
|
100
100
|
);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function extractVoiceResult(result: any): {
|
|
104
|
-
filePath: string;
|
|
105
|
-
transcriptText?: string;
|
|
106
|
-
} {
|
|
107
|
-
if (typeof result === "string") return { filePath: result };
|
|
108
|
-
return {
|
|
109
|
-
filePath: result.audioPath,
|
|
110
|
-
transcriptText: result.transcriptText,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
103
|
async function sendVoiceChatAction(
|
|
115
104
|
deps: TelegramVoiceReplySenderDeps,
|
|
116
105
|
chatId: number,
|
|
@@ -132,7 +121,6 @@ export function createTelegramVoiceReplySender<THandler = unknown>(
|
|
|
132
121
|
options?: {
|
|
133
122
|
replyToPrompt?: boolean;
|
|
134
123
|
replyMarkup?: unknown;
|
|
135
|
-
transcriptText?: string;
|
|
136
124
|
},
|
|
137
125
|
): Promise<void> => {
|
|
138
126
|
const voiceFilePath = await ensureTelegramVoiceFileFormat(filePath);
|
|
@@ -148,7 +136,6 @@ export function createTelegramVoiceReplySender<THandler = unknown>(
|
|
|
148
136
|
"sendVoice",
|
|
149
137
|
{
|
|
150
138
|
chat_id: String(turn.chatId),
|
|
151
|
-
...(options?.transcriptText ? { caption: options.transcriptText } : {}),
|
|
152
139
|
...(replyParameters ? { reply_parameters: replyParameters } : {}),
|
|
153
140
|
...(turn.target
|
|
154
141
|
? Object.fromEntries(
|
|
@@ -257,13 +244,11 @@ export function createTelegramVoiceReplySender<THandler = unknown>(
|
|
|
257
244
|
continue;
|
|
258
245
|
}
|
|
259
246
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
await uploadVoiceFile(turn, filePath, {
|
|
247
|
+
voiceFilePath = providerResult;
|
|
248
|
+
originalFilePath = providerResult;
|
|
249
|
+
await uploadVoiceFile(turn, providerResult, {
|
|
264
250
|
replyToPrompt: options?.replyToPrompt,
|
|
265
251
|
replyMarkup: options?.replyMarkup,
|
|
266
|
-
transcriptText,
|
|
267
252
|
});
|
|
268
253
|
return;
|
|
269
254
|
} catch (error) {
|
package/lib/prompts.ts
CHANGED
|
@@ -14,11 +14,11 @@ export const TELEGRAM_DISCONNECTED_CONTEXT_MESSAGE =
|
|
|
14
14
|
|
|
15
15
|
const LOCAL_SYSTEM_PROMPT_SUFFIX = `
|
|
16
16
|
|
|
17
|
-
${TELEGRAM_CONNECTED_CONTEXT_MESSAGE}
|
|
17
|
+
${TELEGRAM_CONNECTED_CONTEXT_MESSAGE} For Telegram work, consult bundled Skills in routing order: \`telegram-bridge\` for the transport and turn protocol, \`generated-control-surface\` when contextual controls materially shorten feedback, then \`generative-apps\` when the interaction warrants a reusable deterministic app. Load a Skill only if its instructions are not already present in the current context. Do not use Telegram-specific features from unrelated local/TUI prompts.`;
|
|
18
18
|
|
|
19
19
|
const TELEGRAM_TURN_SYSTEM_PROMPT_SUFFIX = `
|
|
20
20
|
|
|
21
|
-
Telegram turn note:
|
|
21
|
+
Telegram turn note: Follow the applicable bundled Telegram Skills in routing order; load only missing instructions.`;
|
|
22
22
|
|
|
23
23
|
export const TELEGRAM_ATTACH_PROMPT_SNIPPET =
|
|
24
24
|
"Queue files for the active Telegram reply; outside Telegram turns, send files directly to Telegram.";
|