@llblab/pi-telegram 0.12.0 → 0.13.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 +6 -5
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +25 -3
- package/README.md +3 -33
- package/docs/README.md +1 -1
- package/docs/architecture.md +2 -1
- package/docs/command-templates.md +18 -16
- package/docs/inbound.md +2 -2
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +158 -4
- package/docs/sections.md +5 -5
- package/docs/voice.md +13 -8
- package/index.ts +4 -4
- package/lib/bindings.ts +2 -4
- package/lib/command-templates.ts +249 -60
- package/lib/config.ts +1 -2
- package/lib/inbound.ts +26 -17
- package/lib/locks.ts +4 -1
- package/lib/outbound-buttons.ts +226 -0
- package/lib/outbound-markup.ts +357 -0
- package/lib/outbound-voice.ts +263 -0
- package/lib/outbound.ts +87 -852
- package/lib/preview.ts +1 -1
- package/lib/queue.ts +3 -0
- package/lib/replies.ts +4 -1
- package/lib/status.ts +13 -0
- package/lib/{api.ts → telegram-api.ts} +4 -4
- package/lib/text-groups.ts +3 -2
- package/lib/voice.ts +35 -8
- package/package.json +12 -12
package/lib/preview.ts
CHANGED
package/lib/queue.ts
CHANGED
|
@@ -44,6 +44,9 @@ export interface TelegramQueueLaneContract {
|
|
|
44
44
|
|
|
45
45
|
export const TELEGRAM_QUEUE_LANE_CONTRACTS: readonly TelegramQueueLaneContract[] =
|
|
46
46
|
[
|
|
47
|
+
// Control lane intentionally accepts both direct controls and resume prompts.
|
|
48
|
+
// Model-switch continuations need prompt semantics but must run before queued user work.
|
|
49
|
+
// Do not admit ordinary user prompts here without an explicit control-flow reason.
|
|
47
50
|
{
|
|
48
51
|
lane: "control",
|
|
49
52
|
admissionMode: "control-queue",
|
package/lib/replies.ts
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
* Owns rendered-message delivery, reply transport wiring, and plain or markdown final replies
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
TelegramReplyParameters,
|
|
9
|
+
TelegramSentMessage,
|
|
10
|
+
} from "./telegram-api.ts";
|
|
8
11
|
import {
|
|
9
12
|
renderTelegramMessage,
|
|
10
13
|
type TelegramRenderedChunk,
|
package/lib/status.ts
CHANGED
|
@@ -289,12 +289,25 @@ function formatTelegramRuntimeEvent(event: TelegramRuntimeEvent): string {
|
|
|
289
289
|
return `${new Date(event.at).toISOString()} ${formatTelegramRuntimeEventSummary(event)}`;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
function buildTelegramRuntimeEventSummary(events: TelegramRuntimeEvent[]): string {
|
|
293
|
+
const counts = new Map<string, number>();
|
|
294
|
+
for (const event of events) {
|
|
295
|
+
const category = formatTelegramRuntimeEventCategory(event);
|
|
296
|
+
counts.set(category, (counts.get(category) ?? 0) + 1);
|
|
297
|
+
}
|
|
298
|
+
return Array.from(counts.entries())
|
|
299
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
300
|
+
.map(([category, count]) => `${category}=${count}`)
|
|
301
|
+
.join(", ");
|
|
302
|
+
}
|
|
303
|
+
|
|
292
304
|
export function buildTelegramRuntimeEventLines(
|
|
293
305
|
events: TelegramRuntimeEvent[],
|
|
294
306
|
): string[] {
|
|
295
307
|
if (events.length === 0) return ["recent runtime events: none"];
|
|
296
308
|
return [
|
|
297
309
|
"recent runtime events:",
|
|
310
|
+
`- summary: ${buildTelegramRuntimeEventSummary(events)}`,
|
|
298
311
|
...events
|
|
299
312
|
.slice()
|
|
300
313
|
.reverse()
|
|
@@ -416,13 +416,13 @@ async function writeTelegramDownloadResponse(
|
|
|
416
416
|
if (!response.body) {
|
|
417
417
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
418
418
|
assertTelegramFileSizeWithinLimit(buffer.byteLength, maxFileSizeBytes);
|
|
419
|
-
await writeFile(targetPath, buffer);
|
|
419
|
+
await writeFile(targetPath, buffer, { mode: 0o600 });
|
|
420
420
|
return;
|
|
421
421
|
}
|
|
422
422
|
await pipeline(
|
|
423
423
|
Readable.from(response.body, { objectMode: false }),
|
|
424
424
|
createTelegramDownloadLimitTransform(maxFileSizeBytes),
|
|
425
|
-
createWriteStream(targetPath),
|
|
425
|
+
createWriteStream(targetPath, { mode: 0o600 }),
|
|
426
426
|
);
|
|
427
427
|
}
|
|
428
428
|
|
|
@@ -536,7 +536,7 @@ export async function prepareTelegramTempDir(
|
|
|
536
536
|
tempDir: string,
|
|
537
537
|
maxAgeMs: number,
|
|
538
538
|
): Promise<number> {
|
|
539
|
-
await mkdir(tempDir, { recursive: true });
|
|
539
|
+
await mkdir(tempDir, { recursive: true, mode: 0o700 });
|
|
540
540
|
return cleanupTelegramTempFiles(tempDir, maxAgeMs);
|
|
541
541
|
}
|
|
542
542
|
|
|
@@ -630,7 +630,7 @@ export async function downloadTelegramFile(
|
|
|
630
630
|
{ signal: options?.signal },
|
|
631
631
|
);
|
|
632
632
|
assertTelegramFileSizeWithinLimit(file.file_size, options?.maxFileSizeBytes);
|
|
633
|
-
await mkdir(tempDir, { recursive: true });
|
|
633
|
+
await mkdir(tempDir, { recursive: true, mode: 0o700 });
|
|
634
634
|
const targetPath = join(
|
|
635
635
|
tempDir,
|
|
636
636
|
`${randomUUID()}-${sanitizeFileName(suggestedName)}`,
|
package/lib/text-groups.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const TELEGRAM_TEXT_GROUP_DEBOUNCE_MS = 1000;
|
|
8
8
|
const TELEGRAM_TEXT_GROUP_MIN_SPLIT_LENGTH = 3600;
|
|
9
|
-
const TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP =
|
|
9
|
+
const TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP = 12;
|
|
10
10
|
|
|
11
11
|
export interface TelegramTextGroupMessage {
|
|
12
12
|
message_id: number;
|
|
@@ -92,7 +92,8 @@ function canAppendTelegramTextGroupMessage<
|
|
|
92
92
|
return (
|
|
93
93
|
!!previous &&
|
|
94
94
|
message.message_id > previous.message_id &&
|
|
95
|
-
message.message_id <=
|
|
95
|
+
message.message_id <=
|
|
96
|
+
previous.message_id + TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP &&
|
|
96
97
|
text.length > 0 &&
|
|
97
98
|
!isTelegramTextGroupCommand(text)
|
|
98
99
|
);
|
package/lib/voice.ts
CHANGED
|
@@ -23,6 +23,21 @@ const VOICE_SYNTHESIS_PROVIDER_REGISTRY_KEY =
|
|
|
23
23
|
const VOICE_TRANSCRIPTION_PROVIDER_REGISTRY_KEY =
|
|
24
24
|
"__piTelegramVoiceTranscriptionProviders__";
|
|
25
25
|
|
|
26
|
+
let nextGeneratedVoiceSynthesisProviderId = 0;
|
|
27
|
+
let nextGeneratedVoiceTranscriptionProviderId = 0;
|
|
28
|
+
|
|
29
|
+
function getNextAvailableProviderId<T>(
|
|
30
|
+
registry: Map<string, T>,
|
|
31
|
+
prefix: string,
|
|
32
|
+
nextId: () => number,
|
|
33
|
+
): string {
|
|
34
|
+
let id: string;
|
|
35
|
+
do {
|
|
36
|
+
id = `${prefix}-${nextId()}`;
|
|
37
|
+
} while (registry.has(id));
|
|
38
|
+
return id;
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
export type TelegramVoiceReplyMode = "mirror" | "always" | "manual";
|
|
27
42
|
|
|
28
43
|
export type TelegramVoiceSynthesisProviderResult =
|
|
@@ -106,7 +121,13 @@ export function registerTelegramVoiceSynthesisProvider(
|
|
|
106
121
|
options?: { id?: string },
|
|
107
122
|
): () => void {
|
|
108
123
|
const registry = getOrCreateVoiceSynthesisProviderRegistry();
|
|
109
|
-
const id =
|
|
124
|
+
const id =
|
|
125
|
+
options?.id ??
|
|
126
|
+
getNextAvailableProviderId(
|
|
127
|
+
registry,
|
|
128
|
+
"voice-synthesis-provider",
|
|
129
|
+
() => nextGeneratedVoiceSynthesisProviderId++,
|
|
130
|
+
);
|
|
110
131
|
const normalized =
|
|
111
132
|
typeof provider === "function"
|
|
112
133
|
? (Object.assign(
|
|
@@ -123,7 +144,7 @@ export function registerTelegramVoiceSynthesisProvider(
|
|
|
123
144
|
: provider;
|
|
124
145
|
registry.set(id, normalized);
|
|
125
146
|
return () => {
|
|
126
|
-
registry.delete(id);
|
|
147
|
+
if (registry.get(id) === normalized) registry.delete(id);
|
|
127
148
|
};
|
|
128
149
|
}
|
|
129
150
|
|
|
@@ -168,10 +189,16 @@ export function registerTelegramVoiceTranscriptionProvider(
|
|
|
168
189
|
options?: { id?: string },
|
|
169
190
|
): () => void {
|
|
170
191
|
const registry = getOrCreateVoiceTranscriptionProviderRegistry();
|
|
171
|
-
const id =
|
|
192
|
+
const id =
|
|
193
|
+
options?.id ??
|
|
194
|
+
getNextAvailableProviderId(
|
|
195
|
+
registry,
|
|
196
|
+
"voice-transcription-provider",
|
|
197
|
+
() => nextGeneratedVoiceTranscriptionProviderId++,
|
|
198
|
+
);
|
|
172
199
|
registry.set(id, provider);
|
|
173
200
|
return () => {
|
|
174
|
-
registry.delete(id);
|
|
201
|
+
if (registry.get(id) === provider) registry.delete(id);
|
|
175
202
|
};
|
|
176
203
|
}
|
|
177
204
|
|
|
@@ -303,12 +330,12 @@ export function shouldSuppressPreviewForVoice(
|
|
|
303
330
|
return !!(turn?.voiceReplyPreferred || turn?.voiceReplyRequired);
|
|
304
331
|
}
|
|
305
332
|
|
|
306
|
-
// --- Outbound
|
|
333
|
+
// --- Outbound Markup Re-Exports ---
|
|
307
334
|
|
|
308
335
|
export {
|
|
336
|
+
normalizeMarkdownAfterVoiceExtraction,
|
|
309
337
|
planTelegramVoiceReply,
|
|
310
|
-
stripTelegramCommentMarkupForPreview,
|
|
311
338
|
stripTelegramCommentMarkupForDelivery,
|
|
339
|
+
stripTelegramCommentMarkupForPreview,
|
|
312
340
|
stripTelegramVoiceMarkupForPreview,
|
|
313
|
-
|
|
314
|
-
} from "./outbound.ts";
|
|
341
|
+
} from "./outbound-markup.ts";
|
package/package.json
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-telegram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"description": "Telegram runtime adapter for π",
|
|
9
|
-
"type": "module",
|
|
10
|
-
"exports": {
|
|
11
|
-
".": "./index.ts",
|
|
12
|
-
"./inbound": "./api/inbound.ts",
|
|
13
|
-
"./outbound": "./api/outbound.ts",
|
|
14
|
-
"./updates": "./api/updates.ts",
|
|
15
|
-
"./sections": "./api/sections.ts",
|
|
16
|
-
"./voice": "./api/voice.ts",
|
|
17
|
-
"./keyboard": "./api/keyboard.ts"
|
|
18
|
-
},
|
|
19
9
|
"keywords": [
|
|
20
10
|
"pi-package",
|
|
21
11
|
"pi",
|
|
@@ -23,6 +13,7 @@
|
|
|
23
13
|
"bot",
|
|
24
14
|
"extension"
|
|
25
15
|
],
|
|
16
|
+
"type": "module",
|
|
26
17
|
"license": "MIT",
|
|
27
18
|
"repository": {
|
|
28
19
|
"type": "git",
|
|
@@ -33,7 +24,7 @@
|
|
|
33
24
|
"url": "https://github.com/llblab/pi-telegram/issues"
|
|
34
25
|
},
|
|
35
26
|
"engines": {
|
|
36
|
-
"node": ">=22.
|
|
27
|
+
"node": ">=22.19.0"
|
|
37
28
|
},
|
|
38
29
|
"scripts": {
|
|
39
30
|
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
|
@@ -53,6 +44,15 @@
|
|
|
53
44
|
"docs/",
|
|
54
45
|
"screenshot.png"
|
|
55
46
|
],
|
|
47
|
+
"exports": {
|
|
48
|
+
".": "./index.ts",
|
|
49
|
+
"./inbound": "./api/inbound.ts",
|
|
50
|
+
"./outbound": "./api/outbound.ts",
|
|
51
|
+
"./updates": "./api/updates.ts",
|
|
52
|
+
"./sections": "./api/sections.ts",
|
|
53
|
+
"./voice": "./api/voice.ts",
|
|
54
|
+
"./keyboard": "./api/keyboard.ts"
|
|
55
|
+
},
|
|
56
56
|
"pi": {
|
|
57
57
|
"extensions": [
|
|
58
58
|
"./index.ts"
|