@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/lib/preview.ts CHANGED
@@ -9,7 +9,7 @@ import type {
9
9
  TelegramReplyParameters,
10
10
  TelegramSendMessageBody,
11
11
  TelegramSentMessage,
12
- } from "./api.ts";
12
+ } from "./telegram-api.ts";
13
13
  import {
14
14
  buildTelegramPreviewSnapshot,
15
15
  MAX_MESSAGE_LENGTH,
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 { TelegramReplyParameters, TelegramSentMessage } from "./api.ts";
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)}`,
@@ -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 = 10;
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 <= previous.message_id + TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP &&
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 = options?.id ?? `voice-synthesis-provider-${registry.size}`;
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 = options?.id ?? `voice-transcription-provider-${registry.size}`;
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 Handler Re-Exports ---
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
- normalizeMarkdownAfterVoiceExtraction,
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.12.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.0.0"
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"