@llblab/pi-kit 0.27.6 → 0.28.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/BACKLOG.md +2 -5
- package/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-telegram/AGENTS.md +10 -8
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -9
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
- package/node_modules/@llblab/pi-telegram/LICENSE +21 -0
- package/node_modules/@llblab/pi-telegram/README.md +15 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +5 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +6 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +4 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +54 -56
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +206 -139
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +181 -25
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.d.ts +4 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +28 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +52 -56
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +78 -246
- package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +1 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +23 -26
- package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +0 -23
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +15 -15
- package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +0 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/config.js +12 -14
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +0 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +2 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +255 -61
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -19
- package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +177 -16
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +1115 -249
- package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +100 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +380 -14
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.d.ts +30 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +129 -72
- package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +28 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/media.js +26 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +0 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +8 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +0 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +18 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +4 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +12 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +1 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +1 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +4 -21
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +0 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +50 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +155 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.d.ts +0 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +3 -15
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +69 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +29 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +104 -33
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +0 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +73 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +2206 -307
- package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +44 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/status.js +149 -27
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +5 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +11 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +34 -23
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +18 -21
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +32 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +191 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +291 -59
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +1647 -369
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +184 -28
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +1032 -114
- package/node_modules/@llblab/pi-telegram/dist/lib/wire.d.ts +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +21 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +25 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +95 -20
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.d.ts +20 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +103 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +43 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +148 -52
- package/node_modules/@llblab/pi-telegram/dist/package.json +6 -6
- package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/SKILL.md +3 -3
- package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/activity.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +152 -34
- package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/delivery.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +125 -19
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +3 -1
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +22 -8
- package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +5 -9
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +6 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +243 -224
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +178 -31
- package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +30 -8
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +110 -334
- package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +29 -29
- package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +15 -15
- package/node_modules/@llblab/pi-telegram/lib/config.ts +12 -17
- package/node_modules/@llblab/pi-telegram/lib/delivery.ts +2 -8
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +257 -72
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +0 -22
- package/node_modules/@llblab/pi-telegram/lib/inbound.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +1182 -326
- package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +402 -23
- package/node_modules/@llblab/pi-telegram/lib/logging.ts +151 -101
- package/node_modules/@llblab/pi-telegram/lib/media.ts +51 -9
- package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +8 -8
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +18 -18
- package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +11 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/menu.ts +5 -1
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +1 -10
- package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +5 -24
- package/node_modules/@llblab/pi-telegram/lib/outbound.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +177 -17
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +3 -3
- package/node_modules/@llblab/pi-telegram/lib/prompt-templates.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +67 -9
- package/node_modules/@llblab/pi-telegram/lib/recovery.ts +104 -44
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +1955 -375
- package/node_modules/@llblab/pi-telegram/lib/sections.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/status.ts +156 -36
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +4 -4
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +48 -33
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +24 -21
- package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +267 -9
- package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +3 -1
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +1697 -488
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +1072 -151
- package/node_modules/@llblab/pi-telegram/lib/wire.ts +28 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +97 -47
- package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +147 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +188 -89
- package/node_modules/@llblab/pi-telegram/package.json +6 -6
- package/node_modules/@llblab/pi-telegram/scripts/audit-exports.mjs +100 -0
- package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +80 -43
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +3 -3
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +3 -3
- package/package.json +2 -2
|
@@ -121,8 +121,6 @@ export interface TelegramSectionRuntimeDeps {
|
|
|
121
121
|
deleteMessage: (chatId: number, messageId: number) => Promise<void>;
|
|
122
122
|
}
|
|
123
123
|
/** @internal */
|
|
124
|
-
export declare function setGlobalTelegramSectionRegistry(registry: TelegramSectionRegistry): void;
|
|
125
|
-
/** @internal */
|
|
126
124
|
export declare function createAndBindTelegramSectionRegistry(): TelegramSectionRegistry;
|
|
127
125
|
/**
|
|
128
126
|
* Register a Telegram Extension Section from any pi extension.
|
|
@@ -51,7 +51,7 @@ function buildTelegramSectionCallbackContext(sectionId, token, chatId, messageId
|
|
|
51
51
|
}
|
|
52
52
|
// --- GlobalThis Bridge ---
|
|
53
53
|
/** @internal */
|
|
54
|
-
|
|
54
|
+
function setGlobalTelegramSectionRegistry(registry) {
|
|
55
55
|
globalThis[SECTION_REGISTRY_KEY] = registry;
|
|
56
56
|
}
|
|
57
57
|
/** @internal */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram status rendering helpers
|
|
3
3
|
* Zones: telegram ui, pi agent diagnostics, tui
|
|
4
|
-
* Owns status summaries, redacted
|
|
4
|
+
* Owns status summaries, redacted diagnostics, projection lifecycle and compact connection-failure copy
|
|
5
|
+
* Excludes canonical state, transport admission and filesystem policy
|
|
5
6
|
*/
|
|
6
7
|
/** UI copy is allowlisted; raw exception text belongs only in redacted diagnostics. */
|
|
7
8
|
export declare function formatTelegramConnectionFailure(error: unknown): string;
|
|
@@ -378,7 +379,6 @@ export interface TelegramStatusRuntime<TContext extends TelegramStatusRuntimeCon
|
|
|
378
379
|
getStatusLines: (options?: TelegramBridgeStatusLineOptions) => string[];
|
|
379
380
|
getStatusState: () => TelegramBridgeStatusLineState;
|
|
380
381
|
}
|
|
381
|
-
export declare function redactTelegramRuntimeMessage(message: string, botToken: string | undefined): string;
|
|
382
382
|
export declare function recordStructuredTelegramRuntimeEvent(events: TelegramRuntimeEvent[], input: TelegramRuntimeEventInput, options: {
|
|
383
383
|
botToken?: string;
|
|
384
384
|
maxEvents: number;
|
|
@@ -394,7 +394,6 @@ export declare function recordStructuredTelegramRuntimeEvent(events: TelegramRun
|
|
|
394
394
|
export declare function registerTelegramStatusLineProvider(provider: TelegramStatusLineProvider, options: {
|
|
395
395
|
id: string;
|
|
396
396
|
}): () => void;
|
|
397
|
-
export declare function getTelegramStatusLineProviderResults(ctx: TelegramStatusLineProviderContext): TelegramStatusLineProviderResult[];
|
|
398
397
|
export declare function clearTelegramStatusLineProviders(): void;
|
|
399
398
|
export declare function createTelegramRuntimeEventRecorder(options: TelegramRuntimeEventRecorderOptions): TelegramRuntimeEventRecorder;
|
|
400
399
|
export declare function buildTelegramRuntimeEventLines(events: TelegramRuntimeEvent[]): string[];
|
|
@@ -418,18 +417,56 @@ export declare function createTelegramRuntimeLogScope(input: {
|
|
|
418
417
|
state: TelegramBridgeStatusLineState;
|
|
419
418
|
instanceId: string;
|
|
420
419
|
}): TelegramRuntimeLogScope;
|
|
421
|
-
export
|
|
420
|
+
export interface TelegramStatusSnapshot {
|
|
422
421
|
runtime: Record<string, unknown>;
|
|
423
422
|
liveRoster: Record<string, unknown>;
|
|
424
423
|
diagnostics: Record<string, unknown>;
|
|
424
|
+
}
|
|
425
|
+
export interface TelegramStoredRuntimeProjection extends TelegramStatusSnapshot {
|
|
426
|
+
version: 1;
|
|
427
|
+
source: "snapshot";
|
|
428
|
+
writtenAtMs: number;
|
|
429
|
+
}
|
|
430
|
+
export declare function createTelegramStatusSnapshot(state: TelegramBridgeStatusLineState): TelegramStatusSnapshot;
|
|
431
|
+
export interface TelegramRuntimeProjectionScope {
|
|
432
|
+
path: string;
|
|
433
|
+
profile: string;
|
|
434
|
+
}
|
|
435
|
+
export interface TelegramRuntimeProjectionStorage {
|
|
436
|
+
read: (scope: TelegramRuntimeProjectionScope) => unknown;
|
|
437
|
+
/** The adapter must enforce exact owner plus the supplied physical/logical scope in one transaction. */
|
|
438
|
+
publish: (scope: TelegramRuntimeProjectionScope, mutate: (current: unknown) => {
|
|
439
|
+
value: unknown;
|
|
440
|
+
changed: boolean;
|
|
441
|
+
}, isCurrent: () => boolean) => boolean;
|
|
442
|
+
}
|
|
443
|
+
export interface TelegramRuntimeProjectionStoreOptions {
|
|
444
|
+
getPath: () => string;
|
|
445
|
+
getProfile: () => string | undefined;
|
|
446
|
+
/** Capture exact owner/context/session authority at submission, never renew it after the queued await. */
|
|
447
|
+
captureAuthority: () => (() => boolean) | undefined;
|
|
448
|
+
storage: TelegramRuntimeProjectionStorage;
|
|
449
|
+
getNowMs?: () => number;
|
|
450
|
+
}
|
|
451
|
+
/** Observational runtime storage only; Workspace, admission, transport, logs and recovery remain with their owners. */
|
|
452
|
+
export declare function createTelegramRuntimeProjectionStore(options: TelegramRuntimeProjectionStoreOptions): {
|
|
453
|
+
read: () => TelegramStoredRuntimeProjection | undefined;
|
|
454
|
+
persist: (snapshot: TelegramStatusSnapshot) => Promise<boolean>;
|
|
425
455
|
};
|
|
426
456
|
export declare function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps: {
|
|
427
|
-
persistSnapshot: () => Promise<void>;
|
|
457
|
+
persistSnapshot: (isCurrent: () => boolean) => Promise<void>;
|
|
428
458
|
recordError: (error: unknown) => void;
|
|
459
|
+
captureScope?: () => (() => boolean) | undefined;
|
|
429
460
|
setTimer?: (callback: () => void, ms: number) => {
|
|
430
461
|
unref?: () => void;
|
|
431
|
-
};
|
|
432
|
-
|
|
462
|
+
} | number;
|
|
463
|
+
clearTimer?: (timer: {
|
|
464
|
+
unref?: () => void;
|
|
465
|
+
} | number) => void;
|
|
466
|
+
}): (() => void) & {
|
|
467
|
+
resume(): void;
|
|
468
|
+
suspend(): Promise<void>;
|
|
469
|
+
};
|
|
433
470
|
export declare function getTelegramStatusBarProcessingStatus(state: {
|
|
434
471
|
hasActiveTurn: boolean;
|
|
435
472
|
hasPendingDispatch: boolean;
|
|
@@ -439,6 +476,4 @@ export declare function getTelegramStatusBarProcessingStatus(state: {
|
|
|
439
476
|
}): string | undefined;
|
|
440
477
|
export declare function buildTelegramStatusBarText(theme: TelegramStatusBarTheme, state: TelegramStatusBarState): string;
|
|
441
478
|
export declare function buildTelegramBridgeStatusLines(state: TelegramBridgeStatusLineState, options?: TelegramBridgeStatusLineOptions): string[];
|
|
442
|
-
export declare function buildTelegramBridgeDiagnosticStatusLines(state: TelegramBridgeStatusLineState): string[];
|
|
443
|
-
export declare function buildStatusHtml(ctx: TelegramStatusContext, activeModel: TelegramStatusActiveModel | undefined, bridgeStatus?: TelegramBridgeStatusLineState): string;
|
|
444
479
|
export {};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram status rendering helpers
|
|
3
3
|
* Zones: telegram ui, pi agent diagnostics, tui
|
|
4
|
-
* Owns status summaries, redacted
|
|
4
|
+
* Owns status summaries, redacted diagnostics, projection lifecycle and compact connection-failure copy
|
|
5
|
+
* Excludes canonical state, transport admission and filesystem policy
|
|
5
6
|
*/
|
|
7
|
+
import { isDeepStrictEqual } from "node:util";
|
|
6
8
|
/** UI copy is allowlisted; raw exception text belongs only in redacted diagnostics. */
|
|
7
9
|
export function formatTelegramConnectionFailure(error) {
|
|
8
10
|
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
@@ -59,7 +61,7 @@ function truncateTelegramRuntimeEventText(text, maxLength) {
|
|
|
59
61
|
return text;
|
|
60
62
|
return `${text.slice(0, maxLength).trimEnd()}… [truncated ${text.length - maxLength} chars]`;
|
|
61
63
|
}
|
|
62
|
-
|
|
64
|
+
function redactTelegramRuntimeMessage(message, botToken) {
|
|
63
65
|
const redacted = botToken
|
|
64
66
|
? message.split(botToken).join("<redacted-token>")
|
|
65
67
|
: message;
|
|
@@ -136,7 +138,7 @@ export function registerTelegramStatusLineProvider(provider, options) {
|
|
|
136
138
|
registry.delete(options.id);
|
|
137
139
|
};
|
|
138
140
|
}
|
|
139
|
-
|
|
141
|
+
function getTelegramStatusLineProviderResults(ctx) {
|
|
140
142
|
const results = [];
|
|
141
143
|
const registry = getOrCreateTelegramStatusLineProviderRegistry();
|
|
142
144
|
for (const provider of registry.values()) {
|
|
@@ -359,7 +361,6 @@ export function createTelegramStatusSnapshot(state) {
|
|
|
359
361
|
...(state.localBus ? { localBus: state.localBus } : {}),
|
|
360
362
|
topicTargets: state.topicTargets ?? [],
|
|
361
363
|
reservations: state.threadReservations ?? [],
|
|
362
|
-
syncObservations: state.topicSyncObservations ?? [],
|
|
363
364
|
},
|
|
364
365
|
diagnostics: {
|
|
365
366
|
pendingDispatch: state.pendingDispatch,
|
|
@@ -372,12 +373,71 @@ export function createTelegramStatusSnapshot(state) {
|
|
|
372
373
|
},
|
|
373
374
|
};
|
|
374
375
|
}
|
|
376
|
+
function isRuntimeProjection(value) {
|
|
377
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
378
|
+
return false;
|
|
379
|
+
const record = value;
|
|
380
|
+
return record.version === 1 && record.source === "snapshot" && Number.isSafeInteger(record.writtenAtMs) && record.writtenAtMs >= 0 &&
|
|
381
|
+
Object.keys(record).every(key => ["version", "source", "writtenAtMs", "runtime", "liveRoster", "diagnostics"].includes(key)) &&
|
|
382
|
+
["runtime", "liveRoster", "diagnostics"].every(key => record[key] !== null && typeof record[key] === "object" && !Array.isArray(record[key]));
|
|
383
|
+
}
|
|
384
|
+
/** Observational runtime storage only; Workspace, admission, transport, logs and recovery remain with their owners. */
|
|
385
|
+
export function createTelegramRuntimeProjectionStore(options) {
|
|
386
|
+
let publicationQueue = Promise.resolve();
|
|
387
|
+
const { getPath, getProfile: readProfile, captureAuthority } = options;
|
|
388
|
+
const { read, publish } = options.storage;
|
|
389
|
+
const getNowMs = options.getNowMs ?? Date.now;
|
|
390
|
+
const getProfile = () => readProfile() || TELEGRAM_STATUS_DEFAULT_PROFILE_NAME;
|
|
391
|
+
return {
|
|
392
|
+
read() {
|
|
393
|
+
try {
|
|
394
|
+
const path = getPath(), profile = getProfile(), value = read({ path, profile });
|
|
395
|
+
return getPath() === path && getProfile() === profile && isRuntimeProjection(value) ? value : undefined;
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
return undefined;
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
async persist(snapshot) {
|
|
402
|
+
const path = getPath(), profile = getProfile(), authority = captureAuthority();
|
|
403
|
+
if (!authority)
|
|
404
|
+
return false;
|
|
405
|
+
// Freeze wire content at submission, but do not duplicate the event log in persistent runtime state.
|
|
406
|
+
const semantic = JSON.parse(JSON.stringify({ version: 1, source: "snapshot", runtime: snapshot.runtime,
|
|
407
|
+
liveRoster: snapshot.liveRoster, diagnostics: snapshot.diagnostics }));
|
|
408
|
+
if (!isRuntimeProjection({ ...semantic, writtenAtMs: 0 }))
|
|
409
|
+
throw new Error("Telegram runtime projection is malformed.");
|
|
410
|
+
delete semantic.diagnostics.recentRuntimeEvents;
|
|
411
|
+
const isCurrent = () => getPath() === path && getProfile() === profile && authority() === true;
|
|
412
|
+
const publication = publicationQueue.then(() => {
|
|
413
|
+
if (!isCurrent())
|
|
414
|
+
return false;
|
|
415
|
+
return publish({ path, profile }, current => {
|
|
416
|
+
if (isRuntimeProjection(current)) {
|
|
417
|
+
const { writtenAtMs: _writtenAtMs, ...existing } = current;
|
|
418
|
+
if (isDeepStrictEqual(existing, semantic))
|
|
419
|
+
return { value: current, changed: false };
|
|
420
|
+
}
|
|
421
|
+
const writtenAtMs = getNowMs();
|
|
422
|
+
if (!Number.isSafeInteger(writtenAtMs) || writtenAtMs < 0)
|
|
423
|
+
throw new Error("Telegram runtime projection timestamp is invalid.");
|
|
424
|
+
return { value: { ...semantic, writtenAtMs }, changed: true };
|
|
425
|
+
}, isCurrent) === true;
|
|
426
|
+
});
|
|
427
|
+
publicationQueue = publication.then(() => undefined, () => undefined);
|
|
428
|
+
return publication;
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
}
|
|
375
432
|
const TELEGRAM_DIAGNOSTICS_SNAPSHOT_COALESCE_MS = 100;
|
|
376
433
|
export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps) {
|
|
377
434
|
const setTimer = deps.setTimer ?? setTimeout;
|
|
435
|
+
const clearTimer = deps.clearTimer ?? ((handle) => clearTimeout(handle));
|
|
436
|
+
let generation = 0;
|
|
437
|
+
let enabled = true;
|
|
378
438
|
let timer;
|
|
379
439
|
let persistPromise;
|
|
380
|
-
let pending
|
|
440
|
+
let pending;
|
|
381
441
|
const recordError = (error) => {
|
|
382
442
|
try {
|
|
383
443
|
deps.recordError(error);
|
|
@@ -386,32 +446,100 @@ export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps) {
|
|
|
386
446
|
// Snapshot diagnostics cannot create an unhandled scheduler rejection.
|
|
387
447
|
}
|
|
388
448
|
};
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
449
|
+
const cancelTimer = () => {
|
|
450
|
+
const previous = timer;
|
|
451
|
+
timer = undefined;
|
|
452
|
+
if (previous) {
|
|
453
|
+
try {
|
|
454
|
+
clearTimer(previous.handle);
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
recordError(error);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
const capture = () => {
|
|
462
|
+
if (!enabled)
|
|
463
|
+
return undefined;
|
|
464
|
+
const expectedGeneration = generation;
|
|
465
|
+
let scope;
|
|
466
|
+
try {
|
|
467
|
+
scope = deps.captureScope ? deps.captureScope() : () => true;
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
recordError(error);
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
if (!scope)
|
|
474
|
+
return undefined;
|
|
475
|
+
return () => {
|
|
476
|
+
if (!enabled || generation !== expectedGeneration)
|
|
477
|
+
return false;
|
|
478
|
+
try {
|
|
479
|
+
return scope();
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
recordError(error);
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
};
|
|
487
|
+
const arm = () => {
|
|
488
|
+
if (timer || persistPromise || !pending)
|
|
489
|
+
return;
|
|
490
|
+
const authority = pending;
|
|
491
|
+
if (!authority()) {
|
|
492
|
+
pending = undefined;
|
|
392
493
|
return;
|
|
393
494
|
}
|
|
394
|
-
|
|
495
|
+
const token = {};
|
|
496
|
+
const handle = setTimer(() => {
|
|
497
|
+
if (timer?.token !== token)
|
|
498
|
+
return;
|
|
395
499
|
timer = undefined;
|
|
500
|
+
const publicationAuthority = pending;
|
|
501
|
+
pending = undefined;
|
|
502
|
+
if (!authority() || !publicationAuthority?.())
|
|
503
|
+
return;
|
|
396
504
|
let tracked;
|
|
397
505
|
tracked = Promise.resolve()
|
|
398
|
-
.then(deps.persistSnapshot)
|
|
506
|
+
.then(() => publicationAuthority() ? deps.persistSnapshot(publicationAuthority) : undefined)
|
|
399
507
|
.catch(recordError)
|
|
400
508
|
.finally(() => {
|
|
401
509
|
if (persistPromise !== tracked)
|
|
402
510
|
return;
|
|
403
511
|
persistPromise = undefined;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
request();
|
|
407
|
-
}
|
|
512
|
+
// Only an explicitly captured new request can arm a successor publication.
|
|
513
|
+
arm();
|
|
408
514
|
});
|
|
409
515
|
persistPromise = tracked;
|
|
410
516
|
}, TELEGRAM_DIAGNOSTICS_SNAPSHOT_COALESCE_MS);
|
|
411
|
-
|
|
412
|
-
|
|
517
|
+
timer = { token, handle };
|
|
518
|
+
if (typeof handle !== "number")
|
|
519
|
+
handle.unref?.();
|
|
520
|
+
};
|
|
521
|
+
const request = () => {
|
|
522
|
+
const authority = capture();
|
|
523
|
+
if (!authority)
|
|
524
|
+
return;
|
|
525
|
+
pending = authority;
|
|
526
|
+
arm();
|
|
413
527
|
};
|
|
414
|
-
return request
|
|
528
|
+
return Object.assign(request, {
|
|
529
|
+
resume() {
|
|
530
|
+
generation += 1;
|
|
531
|
+
cancelTimer();
|
|
532
|
+
pending = undefined;
|
|
533
|
+
enabled = true;
|
|
534
|
+
},
|
|
535
|
+
suspend() {
|
|
536
|
+
enabled = false;
|
|
537
|
+
generation += 1;
|
|
538
|
+
cancelTimer();
|
|
539
|
+
pending = undefined;
|
|
540
|
+
return persistPromise ?? Promise.resolve();
|
|
541
|
+
},
|
|
542
|
+
});
|
|
415
543
|
}
|
|
416
544
|
export function getTelegramStatusBarProcessingStatus(state) {
|
|
417
545
|
if (state.hasPendingModelSwitch)
|
|
@@ -787,15 +915,9 @@ function buildTelegramBridgeCompactStatusLines(state) {
|
|
|
787
915
|
: state.activeSourceMessageIds?.length
|
|
788
916
|
? "active"
|
|
789
917
|
: "idle";
|
|
790
|
-
const diagnosticsProfileName = state.activeProfileName === TELEGRAM_STATUS_DEFAULT_PROFILE_NAME
|
|
791
|
-
? undefined
|
|
792
|
-
: state.activeProfileName;
|
|
793
|
-
const profileSuffix = diagnosticsProfileName
|
|
794
|
-
? `.${diagnosticsProfileName.replace(/[^a-zA-Z0-9._-]+/g, "_")}`
|
|
795
|
-
: "";
|
|
796
918
|
const diagnosticsPaths = state.diagnosticPaths ?? {
|
|
797
|
-
state:
|
|
798
|
-
logs:
|
|
919
|
+
state: "~/.pi/agent/tmp/pi-telegram/state.json",
|
|
920
|
+
logs: "~/.pi/agent/tmp/pi-telegram/logs.jsonl",
|
|
799
921
|
};
|
|
800
922
|
return [
|
|
801
923
|
"connection:",
|
|
@@ -843,7 +965,7 @@ export function buildTelegramBridgeStatusLines(state, options = {}) {
|
|
|
843
965
|
return buildTelegramBridgeDiagnosticStatusLines(state);
|
|
844
966
|
return buildTelegramBridgeCompactStatusLines(state);
|
|
845
967
|
}
|
|
846
|
-
|
|
968
|
+
function buildTelegramBridgeDiagnosticStatusLines(state) {
|
|
847
969
|
const controlQueueCount = state.queuedItems.filter((item) => item.queueLane === "control").length;
|
|
848
970
|
const priorityQueueCount = state.queuedItems.filter((item) => item.queueLane === "priority").length;
|
|
849
971
|
const defaultQueueCount = state.queuedItems.filter((item) => item.queueLane === "default").length;
|
|
@@ -1002,7 +1124,7 @@ function buildTelegramStatusRoleSuffix(state) {
|
|
|
1002
1124
|
return "";
|
|
1003
1125
|
return ` @${state.busRole}`;
|
|
1004
1126
|
}
|
|
1005
|
-
|
|
1127
|
+
function buildStatusHtml(ctx, activeModel, bridgeStatus) {
|
|
1006
1128
|
const stats = collectUsageStats(ctx);
|
|
1007
1129
|
const usesSubscription = activeModel
|
|
1008
1130
|
? ctx.modelRegistry.isUsingOAuth(activeModel)
|
|
@@ -94,9 +94,6 @@ export interface TelegramManualThreadDisconnectDeps<TSyncState> {
|
|
|
94
94
|
workspaceOperationKind?: string;
|
|
95
95
|
getNowMs?: () => number;
|
|
96
96
|
}
|
|
97
|
-
export declare function markTelegramConfigSyncChange<TSyncState extends TelegramSyncState>(state: TSyncState, action: string, options?: {
|
|
98
|
-
nowMs?: number;
|
|
99
|
-
}): TSyncState;
|
|
100
97
|
export declare function createTelegramPreservedLeaderQuitHandler(deps: {
|
|
101
98
|
instanceId: string;
|
|
102
99
|
topicTargetStore: Pick<TelegramTopicTargetStore, "load" | "list" | "listPendingCleanups" | "detachTargetOwner">;
|
|
@@ -109,7 +106,6 @@ export declare function createTelegramPreservedLeaderQuitHandler(deps: {
|
|
|
109
106
|
export interface TelegramSessionRestartThreadCleanupDeps<TSyncState extends TelegramSyncState> extends Omit<TelegramManualThreadDisconnectDeps<TSyncState>, "stopPolling"> {
|
|
110
107
|
suspendPolling: () => Promise<void>;
|
|
111
108
|
}
|
|
112
|
-
export declare function createTelegramSessionRestartThreadCleanupHandler<TSyncState extends TelegramSyncState>(deps: TelegramSessionRestartThreadCleanupDeps<TSyncState>): () => Promise<string>;
|
|
113
109
|
export interface TelegramThreadDisconnectAssembly {
|
|
114
110
|
disconnect: () => Promise<string>;
|
|
115
111
|
cleanupForSessionRestart: () => Promise<string>;
|
|
@@ -141,7 +137,6 @@ export declare function createTelegramStaleTopicApiErrorRecoveryRuntime<TSyncSta
|
|
|
141
137
|
export declare function settleStaleTelegramTopicExecutionFailure<TSyncState extends TelegramSyncState>(error: unknown, deps: TelegramStaleTopicApiErrorRecoveryDeps<TSyncState>): Promise<boolean>;
|
|
142
138
|
export declare function recoverStaleTelegramTopicApiError<TSyncState extends TelegramSyncState>(apiBody: unknown, error: unknown, deps: TelegramStaleTopicApiErrorRecoveryDeps<TSyncState>): Promise<boolean>;
|
|
143
139
|
export declare function ensureTelegramLeaderThreadBinding(deps: TelegramLeaderThreadSyncDeps): Promise<TelegramOwnTopicProvisionResult | undefined>;
|
|
144
|
-
export declare const TELEGRAM_SYNC_SLICE_TARGET_BINDINGS = "target-bindings";
|
|
145
140
|
export declare const TELEGRAM_SYNC_SLICES: readonly ["bot-identity", "bot-capabilities", "pairing", "allowed-user", "topic-capability", "topic-state", "target-bindings", "reservations", "transport-health"];
|
|
146
141
|
export type TelegramSyncSlice = (typeof TELEGRAM_SYNC_SLICES)[number];
|
|
147
142
|
export type TelegramSyncTrigger = "startup" | "reload" | "topic-lifecycle" | "stale-api-error" | "setup-change" | "pairing-change" | "follower-register" | "follower-prune" | "status-request" | "leader-health-tick" | "ordinary-message" | "ordinary-send";
|
|
@@ -7,9 +7,10 @@ import { getTelegramApiErrorRequestTarget, isTelegramStaleTargetHttpError } from
|
|
|
7
7
|
import { getTelegramTargetKey } from "./target.js";
|
|
8
8
|
import * as ThreadReconciler from "./thread-reconciler.js";
|
|
9
9
|
import { TelegramWorkspaceSlotUnavailableError } from "./workspace-slots.js";
|
|
10
|
+
import { normalizeTelegramWorkspacePath } from "./workspace-identity.js";
|
|
10
11
|
import { createTelegramWorkspaceAdmissionOperationId, runWithTelegramWorkspaceAdmissionsAsync, } from "./workspace-admission.js";
|
|
11
|
-
import { createTelegramCleanupTargetProtection, commitTelegramWorkspaceProvisionBinding, getTelegramTargetFromApiBody, getTelegramThreadOwnerKey, isSameTelegramProcessInstance, isTelegramTopicTargetStaleError,
|
|
12
|
-
|
|
12
|
+
import { createTelegramCleanupTargetProtection, commitTelegramWorkspaceProvisionBinding, getTelegramTargetFromApiBody, getTelegramThreadOwnerKey, isSameTelegramProcessInstance, isTelegramTopicTargetStaleError, provisionOwnBusTopic, } from "./threads.js";
|
|
13
|
+
function markTelegramConfigSyncChange(state, action, options) {
|
|
13
14
|
const nowMs = options?.nowMs ?? Date.now();
|
|
14
15
|
let nextState = markTelegramSyncSliceFresh(state, "pairing", {
|
|
15
16
|
nowMs,
|
|
@@ -61,7 +62,7 @@ export function createTelegramPreservedLeaderQuitHandler(deps) {
|
|
|
61
62
|
};
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
+
function createTelegramSessionRestartThreadCleanupHandler(deps) {
|
|
65
66
|
return createTelegramManualThreadDisconnectHandler({
|
|
66
67
|
...deps,
|
|
67
68
|
workspaceOperationKind: "workspace.cleanup-session-restart",
|
|
@@ -655,7 +656,7 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
|
|
|
655
656
|
deps.topicTargetStore.releaseWorkspaceClaim(deps.instanceId);
|
|
656
657
|
}
|
|
657
658
|
}
|
|
658
|
-
|
|
659
|
+
const TELEGRAM_SYNC_SLICE_TARGET_BINDINGS = "target-bindings";
|
|
659
660
|
export const TELEGRAM_SYNC_SLICES = [
|
|
660
661
|
"bot-identity",
|
|
661
662
|
"bot-capabilities",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Wraps bot API calls, file uploads/downloads (including voice messages),
|
|
6
6
|
* multipart sending, runtime transport binding, and Telegram temp-file lifecycle.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
import type { TelegramAttachmentSource } from "./media.ts";
|
|
9
9
|
export declare const TELEGRAM_FILE_MAX_BYTES: number;
|
|
10
10
|
export declare function getTelegramInboundFileByteLimitFromEnv(env: NodeJS.ProcessEnv, names: string[], defaultValue?: number): number;
|
|
11
11
|
export type TelegramNetworkFamilyPolicy = "auto" | "ipv4" | "ipv6" | "ipv4-fallback";
|
|
@@ -415,6 +415,8 @@ export interface TelegramBridgeApiRuntimeDeps {
|
|
|
415
415
|
tempDir: string;
|
|
416
416
|
maxFileSizeBytes: number;
|
|
417
417
|
tempFileMaxAgeMs: number;
|
|
418
|
+
/** Bot `@username` (or numeric id) naming private-chat attachments. */
|
|
419
|
+
getBotScope?: () => string | undefined;
|
|
418
420
|
recordRuntimeEvent: (kind: "api" | "multipart" | "download", error: unknown, details?: Record<string, unknown>) => void;
|
|
419
421
|
now?: () => number;
|
|
420
422
|
chatActionMinIntervalMs?: number;
|
|
@@ -423,7 +425,7 @@ export interface TelegramBridgeApiRuntimeDeps {
|
|
|
423
425
|
export interface TelegramBridgeApiRuntime {
|
|
424
426
|
call: <TResponse>(method: string, body: Record<string, unknown>, options?: TelegramApiCallOptions) => Promise<TResponse>;
|
|
425
427
|
callMultipart: <TResponse>(method: string, fields: Record<string, string>, fileField: string, filePath: string, fileName: string, options?: TelegramApiCallOptions) => Promise<TResponse>;
|
|
426
|
-
downloadFile: (fileId: string, suggestedName: string) => Promise<string>;
|
|
428
|
+
downloadFile: (fileId: string, suggestedName: string, source?: TelegramAttachmentSource) => Promise<string>;
|
|
427
429
|
deleteWebhook: (signal?: AbortSignal) => Promise<boolean>;
|
|
428
430
|
getUpdates: (body: Record<string, unknown>, signal?: AbortSignal) => Promise<TelegramUpdate[]>;
|
|
429
431
|
setMyCommands: (commands: readonly {
|
|
@@ -462,6 +464,12 @@ export interface TelegramBridgeApiRuntime {
|
|
|
462
464
|
deleteMessage: (chatId: number, messageId: number) => Promise<void>;
|
|
463
465
|
prepareTempDir: () => Promise<number>;
|
|
464
466
|
}
|
|
467
|
+
/** Names follow `<kind>-<scope>-<messageId>[-<index>][-<name>|.<ext>]`. */
|
|
468
|
+
/**
|
|
469
|
+
* `scope` is the bot `@username` (id fallback) in a private chat and the public chat `@username` (id fallback) elsewhere.
|
|
470
|
+
* A negative chat id loses its sign, so the name never contains two adjacent dashes.
|
|
471
|
+
*/
|
|
472
|
+
export declare function createTelegramAttachmentFileName(source: TelegramAttachmentSource, generatedName: string, botScope?: string): string;
|
|
465
473
|
export declare class TelegramApiCommitUnknownError extends Error {
|
|
466
474
|
readonly kind: "commit-unknown";
|
|
467
475
|
readonly method: string;
|
|
@@ -507,7 +515,6 @@ export declare function fetchTelegramBotIdentity(botToken: string, fetchImpl?: t
|
|
|
507
515
|
export declare function callTelegramMultipart<TResponse>(botToken: string | undefined, method: string, fields: Record<string, string>, fileField: string, filePath: string, fileName: string, options?: TelegramApiCallOptions): Promise<TResponse>;
|
|
508
516
|
export declare function downloadTelegramFile(botToken: string | undefined, fileId: string, suggestedName: string, tempDir: string, options?: TelegramFileDownloadOptions): Promise<string>;
|
|
509
517
|
export declare function answerTelegramCallbackQuery(botToken: string | undefined, callbackQueryId: string, text?: string, options?: TelegramAnswerCallbackQueryOptions): Promise<void>;
|
|
510
|
-
export declare function deleteTelegramMessage(botToken: string | undefined, chatId: number, messageId: number): Promise<void>;
|
|
511
518
|
export declare function createTelegramChatActionSender<TAction extends string>(sendChatAction: (chatId: number, action: TAction, options?: {
|
|
512
519
|
message_thread_id?: number;
|
|
513
520
|
}) => Promise<unknown>, action: TAction): (chatId: number, options?: {
|
|
@@ -530,6 +537,7 @@ export type TelegramWorkspaceThreadDeletionTransport = (authorize: () => {
|
|
|
530
537
|
}) => Promise<void>;
|
|
531
538
|
export declare function createDefaultTelegramBridgeApiRuntime(deps: {
|
|
532
539
|
getBotToken: () => string | undefined;
|
|
540
|
+
getBotScope?: () => string | undefined;
|
|
533
541
|
recordRuntimeEvent: TelegramBridgeApiRuntimeDeps["recordRuntimeEvent"];
|
|
534
542
|
captureRequestErrorHandler?: TelegramBridgeApiRuntimeDeps["captureRequestErrorHandler"];
|
|
535
543
|
targetActivity?: TelegramApiTargetActivityRuntime;
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { createWriteStream, openAsBlob } from "node:fs";
|
|
10
|
-
import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
10
|
+
import { mkdir, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
11
11
|
import { request as requestHttps } from "node:https";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
-
import {
|
|
13
|
+
import { resolveTelegramAttachmentsDir } from "./paths.js";
|
|
14
14
|
import { Readable, Transform } from "node:stream";
|
|
15
15
|
import { pipeline } from "node:stream/promises";
|
|
16
|
-
|
|
16
|
+
const TELEGRAM_API_BASE = "https://api.telegram.org";
|
|
17
17
|
export const TELEGRAM_FILE_MAX_BYTES = 50 * 1024 * 1024;
|
|
18
18
|
export function getTelegramInboundFileByteLimitFromEnv(env, names, defaultValue = TELEGRAM_FILE_MAX_BYTES) {
|
|
19
19
|
for (const name of names) {
|
|
@@ -27,11 +27,10 @@ export function getTelegramInboundFileByteLimitFromEnv(env, names, defaultValue
|
|
|
27
27
|
return defaultValue;
|
|
28
28
|
}
|
|
29
29
|
function getTelegramApiTempDir() {
|
|
30
|
-
return
|
|
30
|
+
return resolveTelegramAttachmentsDir();
|
|
31
31
|
}
|
|
32
32
|
const TELEGRAM_TEMP_FILE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
33
33
|
const activeTelegramApiWorkspaceAdmissionOperationIds = new Set();
|
|
34
|
-
const TELEGRAM_TEMP_SCRATCH_FILE_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-/u;
|
|
35
34
|
const TELEGRAM_INBOUND_FILE_MAX_BYTES = getTelegramInboundFileByteLimitFromEnv(process.env, ["PI_TELEGRAM_INBOUND_FILE_MAX_BYTES", "TELEGRAM_MAX_FILE_SIZE_BYTES"], TELEGRAM_FILE_MAX_BYTES);
|
|
36
35
|
const TELEGRAM_NETWORK_FAMILY_ENV = "PI_TELEGRAM_NETWORK_FAMILY";
|
|
37
36
|
const TELEGRAM_NETWORK_FAMILY_VALUES = new Set([
|
|
@@ -208,6 +207,23 @@ export function createTelegramApiWorkspaceAdmissionClient(client, admission, opt
|
|
|
208
207
|
function sanitizeFileName(name) {
|
|
209
208
|
return name.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
210
209
|
}
|
|
210
|
+
/** Names follow `<kind>-<scope>-<messageId>[-<index>][-<name>|.<ext>]`. */
|
|
211
|
+
/**
|
|
212
|
+
* `scope` is the bot `@username` (id fallback) in a private chat and the public chat `@username` (id fallback) elsewhere.
|
|
213
|
+
* A negative chat id loses its sign, so the name never contains two adjacent dashes.
|
|
214
|
+
*/
|
|
215
|
+
export function createTelegramAttachmentFileName(source, generatedName, botScope) {
|
|
216
|
+
const chat = source.chat;
|
|
217
|
+
const clean = (value) => sanitizeFileName(value.replace(/^@/u, "")).replace(/^[-.]+/u, "");
|
|
218
|
+
const scope = source.scope ? clean(source.scope) : chat?.type === "private" || !chat
|
|
219
|
+
? botScope ? clean(botScope) : chat ? String(Math.abs(chat.id)) : ""
|
|
220
|
+
: chat.username ? clean(chat.username) : String(Math.abs(chat.id));
|
|
221
|
+
const dot = generatedName.lastIndexOf(".");
|
|
222
|
+
const extension = dot > 0 ? sanitizeFileName(generatedName.slice(dot)) : "";
|
|
223
|
+
const tail = source.userFileName ? `-${clean(source.userFileName)}` : extension;
|
|
224
|
+
return [sanitizeFileName(source.kind), scope, String(source.messageId)].filter(Boolean).join("-") +
|
|
225
|
+
(source.index !== undefined ? `-${source.index}` : "") + tail;
|
|
226
|
+
}
|
|
211
227
|
class TelegramApiMalformedSuccessError extends Error {
|
|
212
228
|
constructor(method, detail) {
|
|
213
229
|
super(`Telegram API ${method} ${detail}`);
|
|
@@ -672,9 +688,9 @@ export async function cleanupTelegramTempFiles(tempDir, maxAgeMs, now = Date.now
|
|
|
672
688
|
return 0;
|
|
673
689
|
}
|
|
674
690
|
for (const entry of entries) {
|
|
675
|
-
|
|
691
|
+
// The directory holds only attachment scratch files, so age alone decides.
|
|
692
|
+
if (!entry.isFile())
|
|
676
693
|
continue;
|
|
677
|
-
}
|
|
678
694
|
const path = join(tempDir, entry.name);
|
|
679
695
|
try {
|
|
680
696
|
const stats = await stat(path);
|
|
@@ -760,7 +776,9 @@ export async function downloadTelegramFile(botToken, fileId, suggestedName, temp
|
|
|
760
776
|
const file = await callTelegram(configuredBotToken, "getFile", { file_id: fileId }, { signal: options?.signal });
|
|
761
777
|
assertTelegramFileSizeWithinLimit(file.file_size, options?.maxFileSizeBytes);
|
|
762
778
|
await mkdir(tempDir, { recursive: true, mode: 0o700 });
|
|
763
|
-
|
|
779
|
+
// Names carry scope and message id, so the same message maps to the same path; publish by rename.
|
|
780
|
+
const targetPath = join(tempDir, sanitizeFileName(suggestedName));
|
|
781
|
+
const partPath = `${targetPath}.${randomUUID()}.part`;
|
|
764
782
|
const response = await callTelegramTransportRequest((family) => telegramFetch(`${TELEGRAM_API_BASE}/file/bot${configuredBotToken}/${file.file_path}`, { signal: options?.signal }, family));
|
|
765
783
|
if (!response.ok) {
|
|
766
784
|
throw new Error(`Failed to download Telegram file: ${response.status}`);
|
|
@@ -768,10 +786,11 @@ export async function downloadTelegramFile(botToken, fileId, suggestedName, temp
|
|
|
768
786
|
const contentLength = response.headers?.get("content-length");
|
|
769
787
|
assertTelegramFileSizeWithinLimit(contentLength ? Number.parseInt(contentLength, 10) : undefined, options?.maxFileSizeBytes);
|
|
770
788
|
try {
|
|
771
|
-
await writeTelegramDownloadResponse(response,
|
|
789
|
+
await writeTelegramDownloadResponse(response, partPath, options?.maxFileSizeBytes);
|
|
790
|
+
await rename(partPath, targetPath);
|
|
772
791
|
}
|
|
773
792
|
catch (error) {
|
|
774
|
-
await removeTelegramPartialDownload(
|
|
793
|
+
await removeTelegramPartialDownload(partPath);
|
|
775
794
|
throw error;
|
|
776
795
|
}
|
|
777
796
|
return targetPath;
|
|
@@ -788,17 +807,6 @@ export async function answerTelegramCallbackQuery(botToken, callbackQueryId, tex
|
|
|
788
807
|
}));
|
|
789
808
|
}
|
|
790
809
|
}
|
|
791
|
-
export async function deleteTelegramMessage(botToken, chatId, messageId) {
|
|
792
|
-
try {
|
|
793
|
-
await callTelegram(botToken, "deleteMessage", {
|
|
794
|
-
chat_id: chatId,
|
|
795
|
-
message_id: messageId,
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
catch {
|
|
799
|
-
// ignore
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
810
|
export function createTelegramChatActionSender(sendChatAction, action) {
|
|
803
811
|
return (chatId, options) => sendChatAction(chatId, action, options);
|
|
804
812
|
}
|
|
@@ -871,6 +879,7 @@ export function createDefaultTelegramBridgeApiRuntime(deps) {
|
|
|
871
879
|
tempDir: getTelegramApiTempDir(),
|
|
872
880
|
maxFileSizeBytes: TELEGRAM_INBOUND_FILE_MAX_BYTES,
|
|
873
881
|
tempFileMaxAgeMs: TELEGRAM_TEMP_FILE_MAX_AGE_MS,
|
|
882
|
+
...(deps.getBotScope ? { getBotScope: deps.getBotScope } : {}),
|
|
874
883
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
875
884
|
captureRequestErrorHandler: deps.captureRequestErrorHandler,
|
|
876
885
|
});
|
|
@@ -1029,9 +1038,11 @@ export function createTelegramBridgeApiRuntime(deps) {
|
|
|
1029
1038
|
* Downloads a file from the Telegram servers into the local temp directory.
|
|
1030
1039
|
* Used for inbound voice messages, photos, documents, etc.
|
|
1031
1040
|
*/
|
|
1032
|
-
downloadFile: async (fileId, suggestedName) => {
|
|
1041
|
+
downloadFile: async (fileId, suggestedName, source) => {
|
|
1033
1042
|
try {
|
|
1034
|
-
return await deps.client.downloadFile(fileId,
|
|
1043
|
+
return await deps.client.downloadFile(fileId, source
|
|
1044
|
+
? createTelegramAttachmentFileName(source, suggestedName, deps.getBotScope?.())
|
|
1045
|
+
: suggestedName, deps.tempDir, {
|
|
1035
1046
|
maxFileSizeBytes: deps.maxFileSizeBytes,
|
|
1036
1047
|
});
|
|
1037
1048
|
}
|