@llblab/pi-telegram 0.51.5 → 0.52.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 +10 -8
- package/BACKLOG.md +1 -8
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +16 -6
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +5 -7
- package/dist/lib/bindings.d.ts +9 -2
- package/dist/lib/bindings.js +22 -13
- package/dist/lib/bus-api.js +4 -2
- package/dist/lib/bus-follower.d.ts +55 -57
- package/dist/lib/bus-follower.js +213 -140
- package/dist/lib/bus-leader.d.ts +21 -2
- package/dist/lib/bus-leader.js +181 -25
- package/dist/lib/bus-transport.d.ts +4 -1
- package/dist/lib/bus-transport.js +28 -4
- package/dist/lib/bus.d.ts +52 -56
- package/dist/lib/bus.js +78 -246
- package/dist/lib/channel-posts.d.ts +1 -3
- package/dist/lib/channel-posts.js +23 -26
- package/dist/lib/command-templates.d.ts +0 -5
- package/dist/lib/command-templates.js +5 -5
- package/dist/lib/commands.d.ts +12 -26
- package/dist/lib/commands.js +152 -98
- package/dist/lib/config.d.ts +0 -17
- package/dist/lib/config.js +12 -14
- package/dist/lib/delivery.d.ts +0 -4
- package/dist/lib/delivery.js +2 -5
- package/dist/lib/extension.js +281 -61
- package/dist/lib/generative-apps.d.ts +0 -19
- package/dist/lib/inbound.d.ts +0 -2
- package/dist/lib/inbound.js +2 -2
- package/dist/lib/journal.d.ts +177 -16
- package/dist/lib/journal.js +1115 -249
- package/dist/lib/keyboard.d.ts +0 -2
- package/dist/lib/keyboard.js +2 -2
- package/dist/lib/lifecycle.d.ts +57 -2
- package/dist/lib/lifecycle.js +110 -5
- package/dist/lib/locks.d.ts +100 -12
- package/dist/lib/locks.js +380 -14
- package/dist/lib/logging.d.ts +30 -12
- package/dist/lib/logging.js +129 -72
- package/dist/lib/media.d.ts +28 -5
- package/dist/lib/media.js +26 -8
- package/dist/lib/menu-model.d.ts +0 -11
- package/dist/lib/menu-model.js +8 -8
- package/dist/lib/menu-settings.d.ts +0 -18
- package/dist/lib/menu-settings.js +18 -18
- package/dist/lib/menu-status.d.ts +4 -4
- package/dist/lib/menu-status.js +12 -7
- package/dist/lib/menu-thinking.d.ts +1 -1
- package/dist/lib/menu-thinking.js +1 -1
- package/dist/lib/menu.d.ts +1 -0
- package/dist/lib/menu.js +4 -3
- package/dist/lib/model.js +2 -4
- package/dist/lib/outbound-attachments.d.ts +0 -1
- package/dist/lib/outbound-attachments.js +1 -1
- package/dist/lib/outbound-buttons.js +1 -8
- package/dist/lib/outbound-markup.d.ts +1 -6
- package/dist/lib/outbound-markup.js +4 -21
- package/dist/lib/outbound.d.ts +0 -8
- package/dist/lib/outbound.js +5 -5
- package/dist/lib/paths.d.ts +50 -7
- package/dist/lib/paths.js +155 -17
- package/dist/lib/pi.d.ts +1 -0
- package/dist/lib/pi.js +2 -1
- package/dist/lib/polling.d.ts +0 -5
- package/dist/lib/polling.js +5 -5
- package/dist/lib/preview.d.ts +0 -3
- package/dist/lib/preview.js +3 -3
- package/dist/lib/prompt-templates.d.ts +0 -1
- package/dist/lib/prompt-templates.js +1 -1
- package/dist/lib/queue.d.ts +3 -15
- package/dist/lib/queue.js +69 -8
- package/dist/lib/recovery.d.ts +29 -9
- package/dist/lib/recovery.js +104 -33
- package/dist/lib/replies.d.ts +0 -4
- package/dist/lib/replies.js +1 -1
- package/dist/lib/routing.d.ts +73 -8
- package/dist/lib/routing.js +2206 -307
- package/dist/lib/sections.d.ts +0 -2
- package/dist/lib/sections.js +1 -1
- package/dist/lib/status.d.ts +46 -9
- package/dist/lib/status.js +179 -27
- package/dist/lib/sync.d.ts +0 -5
- package/dist/lib/sync.js +9 -8
- package/dist/lib/telegram-api.d.ts +11 -3
- package/dist/lib/telegram-api.js +34 -23
- package/dist/lib/thread-cleanup-manager.d.ts +1 -0
- package/dist/lib/thread-cleanup-manager.js +18 -21
- package/dist/lib/thread-naming.d.ts +32 -5
- package/dist/lib/thread-naming.js +191 -7
- package/dist/lib/thread-reconciler.d.ts +2 -0
- package/dist/lib/thread-reconciler.js +1 -1
- package/dist/lib/threads.d.ts +292 -59
- package/dist/lib/threads.js +1664 -373
- package/dist/lib/turns.d.ts +0 -1
- package/dist/lib/turns.js +1 -1
- package/dist/lib/updates.d.ts +184 -28
- package/dist/lib/updates.js +1032 -114
- package/dist/lib/wire.d.ts +12 -0
- package/dist/lib/wire.js +21 -0
- package/dist/lib/workspace-admission.d.ts +25 -17
- package/dist/lib/workspace-admission.js +95 -20
- package/dist/lib/workspace-identity.d.ts +20 -0
- package/dist/lib/workspace-identity.js +103 -0
- package/dist/lib/workspace-retirement.d.ts +45 -10
- package/dist/lib/workspace-retirement.js +196 -64
- package/dist/package.json +6 -6
- package/dist/skills/generated-control-surface/SKILL.md +3 -3
- package/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
- package/docs/README.md +1 -1
- package/docs/activity.md +1 -1
- package/docs/architecture.md +158 -39
- package/docs/callback-namespaces.md +1 -1
- package/docs/delivery.md +1 -1
- package/docs/generative-apps.md +2 -2
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +127 -19
- package/docs/public-api.md +5 -3
- package/docs/ui-style.md +26 -8
- package/docs/updates.md +1 -1
- package/lib/activity-verbosity.ts +5 -9
- package/lib/bindings.ts +23 -10
- package/lib/bus-api.ts +5 -2
- package/lib/bus-follower.ts +251 -226
- package/lib/bus-leader.ts +178 -31
- package/lib/bus-transport.ts +30 -8
- package/lib/bus.ts +110 -334
- package/lib/channel-posts.ts +29 -29
- package/lib/command-templates.ts +5 -5
- package/lib/commands.ts +150 -112
- package/lib/config.ts +12 -17
- package/lib/delivery.ts +2 -8
- package/lib/extension.ts +282 -72
- package/lib/generative-apps.ts +0 -22
- package/lib/inbound.ts +2 -2
- package/lib/journal.ts +1182 -326
- package/lib/keyboard.ts +2 -2
- package/lib/lifecycle.ts +141 -5
- package/lib/locks.ts +402 -23
- package/lib/logging.ts +151 -101
- package/lib/media.ts +51 -9
- package/lib/menu-model.ts +8 -8
- package/lib/menu-settings.ts +18 -18
- package/lib/menu-status.ts +11 -0
- package/lib/menu-thinking.ts +2 -2
- package/lib/menu.ts +5 -1
- package/lib/model.ts +2 -4
- package/lib/outbound-attachments.ts +1 -1
- package/lib/outbound-buttons.ts +1 -10
- package/lib/outbound-markup.ts +5 -24
- package/lib/outbound.ts +5 -5
- package/lib/paths.ts +177 -17
- package/lib/pi.ts +5 -2
- package/lib/polling.ts +5 -5
- package/lib/preview.ts +3 -3
- package/lib/prompt-templates.ts +1 -1
- package/lib/queue.ts +67 -9
- package/lib/recovery.ts +104 -44
- package/lib/replies.ts +1 -1
- package/lib/routing.ts +1955 -375
- package/lib/sections.ts +1 -1
- package/lib/status.ts +185 -36
- package/lib/sync.ts +8 -8
- package/lib/telegram-api.ts +48 -33
- package/lib/thread-cleanup-manager.ts +24 -21
- package/lib/thread-naming.ts +267 -9
- package/lib/thread-reconciler.ts +3 -1
- package/lib/threads.ts +1718 -491
- package/lib/turns.ts +1 -1
- package/lib/updates.ts +1072 -151
- package/lib/wire.ts +28 -0
- package/lib/workspace-admission.ts +97 -47
- package/lib/workspace-identity.ts +147 -0
- package/lib/workspace-retirement.ts +234 -102
- package/package.json +6 -6
- package/scripts/audit-exports.mjs +100 -0
- package/scripts/check-downgrade.mjs +80 -43
- package/skills/generated-control-surface/SKILL.md +3 -3
- package/skills/telegram-bridge/references/diagnosis.md +3 -3
package/dist/lib/sections.d.ts
CHANGED
|
@@ -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.
|
package/dist/lib/sections.js
CHANGED
|
@@ -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 */
|
package/dist/lib/status.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram status rendering helpers
|
|
3
3
|
* Zones: telegram ui, pi agent diagnostics, tui
|
|
4
|
-
*
|
|
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
|
+
/** UI copy is allowlisted; raw exception text belongs only in redacted diagnostics. */
|
|
8
|
+
export declare function formatTelegramConnectionFailure(error: unknown): string;
|
|
6
9
|
export type TelegramStatusQueueLane = "control" | "priority" | "default";
|
|
7
10
|
export interface TelegramUsageStats {
|
|
8
11
|
totalInput: number;
|
|
@@ -376,7 +379,6 @@ export interface TelegramStatusRuntime<TContext extends TelegramStatusRuntimeCon
|
|
|
376
379
|
getStatusLines: (options?: TelegramBridgeStatusLineOptions) => string[];
|
|
377
380
|
getStatusState: () => TelegramBridgeStatusLineState;
|
|
378
381
|
}
|
|
379
|
-
export declare function redactTelegramRuntimeMessage(message: string, botToken: string | undefined): string;
|
|
380
382
|
export declare function recordStructuredTelegramRuntimeEvent(events: TelegramRuntimeEvent[], input: TelegramRuntimeEventInput, options: {
|
|
381
383
|
botToken?: string;
|
|
382
384
|
maxEvents: number;
|
|
@@ -392,7 +394,6 @@ export declare function recordStructuredTelegramRuntimeEvent(events: TelegramRun
|
|
|
392
394
|
export declare function registerTelegramStatusLineProvider(provider: TelegramStatusLineProvider, options: {
|
|
393
395
|
id: string;
|
|
394
396
|
}): () => void;
|
|
395
|
-
export declare function getTelegramStatusLineProviderResults(ctx: TelegramStatusLineProviderContext): TelegramStatusLineProviderResult[];
|
|
396
397
|
export declare function clearTelegramStatusLineProviders(): void;
|
|
397
398
|
export declare function createTelegramRuntimeEventRecorder(options: TelegramRuntimeEventRecorderOptions): TelegramRuntimeEventRecorder;
|
|
398
399
|
export declare function buildTelegramRuntimeEventLines(events: TelegramRuntimeEvent[]): string[];
|
|
@@ -416,18 +417,56 @@ export declare function createTelegramRuntimeLogScope(input: {
|
|
|
416
417
|
state: TelegramBridgeStatusLineState;
|
|
417
418
|
instanceId: string;
|
|
418
419
|
}): TelegramRuntimeLogScope;
|
|
419
|
-
export
|
|
420
|
+
export interface TelegramStatusSnapshot {
|
|
420
421
|
runtime: Record<string, unknown>;
|
|
421
422
|
liveRoster: Record<string, unknown>;
|
|
422
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>;
|
|
423
455
|
};
|
|
424
456
|
export declare function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps: {
|
|
425
|
-
persistSnapshot: () => Promise<void>;
|
|
457
|
+
persistSnapshot: (isCurrent: () => boolean) => Promise<void>;
|
|
426
458
|
recordError: (error: unknown) => void;
|
|
459
|
+
captureScope?: () => (() => boolean) | undefined;
|
|
427
460
|
setTimer?: (callback: () => void, ms: number) => {
|
|
428
461
|
unref?: () => void;
|
|
429
|
-
};
|
|
430
|
-
|
|
462
|
+
} | number;
|
|
463
|
+
clearTimer?: (timer: {
|
|
464
|
+
unref?: () => void;
|
|
465
|
+
} | number) => void;
|
|
466
|
+
}): (() => void) & {
|
|
467
|
+
resume(): void;
|
|
468
|
+
suspend(): Promise<void>;
|
|
469
|
+
};
|
|
431
470
|
export declare function getTelegramStatusBarProcessingStatus(state: {
|
|
432
471
|
hasActiveTurn: boolean;
|
|
433
472
|
hasPendingDispatch: boolean;
|
|
@@ -437,6 +476,4 @@ export declare function getTelegramStatusBarProcessingStatus(state: {
|
|
|
437
476
|
}): string | undefined;
|
|
438
477
|
export declare function buildTelegramStatusBarText(theme: TelegramStatusBarTheme, state: TelegramStatusBarState): string;
|
|
439
478
|
export declare function buildTelegramBridgeStatusLines(state: TelegramBridgeStatusLineState, options?: TelegramBridgeStatusLineOptions): string[];
|
|
440
|
-
export declare function buildTelegramBridgeDiagnosticStatusLines(state: TelegramBridgeStatusLineState): string[];
|
|
441
|
-
export declare function buildStatusHtml(ctx: TelegramStatusContext, activeModel: TelegramStatusActiveModel | undefined, bridgeStatus?: TelegramBridgeStatusLineState): string;
|
|
442
479
|
export {};
|
package/dist/lib/status.js
CHANGED
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram status rendering helpers
|
|
3
3
|
* Zones: telegram ui, pi agent diagnostics, tui
|
|
4
|
-
*
|
|
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";
|
|
8
|
+
/** UI copy is allowlisted; raw exception text belongs only in redacted diagnostics. */
|
|
9
|
+
export function formatTelegramConnectionFailure(error) {
|
|
10
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
11
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : undefined;
|
|
12
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
13
|
+
if (status === 401)
|
|
14
|
+
return "Telegram token rejected. Run /telegram-setup.";
|
|
15
|
+
if (status === 403)
|
|
16
|
+
return "Telegram access denied. Check /telegram-status --debug.";
|
|
17
|
+
if ((typeof code === "string" && ["ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "ECONNRESET"].includes(code)) ||
|
|
18
|
+
/network unavailable|fetch failed/i.test(message)) {
|
|
19
|
+
return "Telegram network unavailable. Retry /telegram-connect.";
|
|
20
|
+
}
|
|
21
|
+
if (/Workspace slots?.*(?:unavailable|exhausted)|no free.*slot/i.test(message)) {
|
|
22
|
+
return "No Telegram slot available. Check /telegram-status --debug.";
|
|
23
|
+
}
|
|
24
|
+
if (code === "incompatible-protocol" || /protocol.*(?:incompatible|mismatch)|incompatible.*protocol/i.test(message)) {
|
|
25
|
+
return "Telegram instances are incompatible. Update them together.";
|
|
26
|
+
}
|
|
27
|
+
if (/unsupported.*(?:journal|version)|(?:journal|version).*unsupported/i.test(message)) {
|
|
28
|
+
return "Telegram state version unsupported. Use a compatible runtime.";
|
|
29
|
+
}
|
|
30
|
+
if (/follower registration failed|active in another Pi instance/i.test(message)) {
|
|
31
|
+
return "Telegram leader is active but unavailable. Check /telegram-status --debug.";
|
|
32
|
+
}
|
|
33
|
+
if (/unfinished Thread creation does not match/i.test(message)) {
|
|
34
|
+
return "Telegram Thread creation is unresolved. Check /telegram-status --debug.";
|
|
35
|
+
}
|
|
36
|
+
return "Telegram connection failed. Check /telegram-status --debug.";
|
|
37
|
+
}
|
|
6
38
|
const TELEGRAM_STATUS_DEFAULT_PROFILE_NAME = "default";
|
|
7
39
|
const TELEGRAM_STATUS_LINE_PROVIDER_REGISTRY_KEY = "__piTelegramStatusLineProviders__";
|
|
8
40
|
const MAX_RECENT_TELEGRAM_RUNTIME_EVENTS = 10;
|
|
@@ -29,7 +61,7 @@ function truncateTelegramRuntimeEventText(text, maxLength) {
|
|
|
29
61
|
return text;
|
|
30
62
|
return `${text.slice(0, maxLength).trimEnd()}… [truncated ${text.length - maxLength} chars]`;
|
|
31
63
|
}
|
|
32
|
-
|
|
64
|
+
function redactTelegramRuntimeMessage(message, botToken) {
|
|
33
65
|
const redacted = botToken
|
|
34
66
|
? message.split(botToken).join("<redacted-token>")
|
|
35
67
|
: message;
|
|
@@ -106,7 +138,7 @@ export function registerTelegramStatusLineProvider(provider, options) {
|
|
|
106
138
|
registry.delete(options.id);
|
|
107
139
|
};
|
|
108
140
|
}
|
|
109
|
-
|
|
141
|
+
function getTelegramStatusLineProviderResults(ctx) {
|
|
110
142
|
const results = [];
|
|
111
143
|
const registry = getOrCreateTelegramStatusLineProviderRegistry();
|
|
112
144
|
for (const provider of registry.values()) {
|
|
@@ -329,7 +361,6 @@ export function createTelegramStatusSnapshot(state) {
|
|
|
329
361
|
...(state.localBus ? { localBus: state.localBus } : {}),
|
|
330
362
|
topicTargets: state.topicTargets ?? [],
|
|
331
363
|
reservations: state.threadReservations ?? [],
|
|
332
|
-
syncObservations: state.topicSyncObservations ?? [],
|
|
333
364
|
},
|
|
334
365
|
diagnostics: {
|
|
335
366
|
pendingDispatch: state.pendingDispatch,
|
|
@@ -342,12 +373,71 @@ export function createTelegramStatusSnapshot(state) {
|
|
|
342
373
|
},
|
|
343
374
|
};
|
|
344
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
|
+
}
|
|
345
432
|
const TELEGRAM_DIAGNOSTICS_SNAPSHOT_COALESCE_MS = 100;
|
|
346
433
|
export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps) {
|
|
347
434
|
const setTimer = deps.setTimer ?? setTimeout;
|
|
435
|
+
const clearTimer = deps.clearTimer ?? ((handle) => clearTimeout(handle));
|
|
436
|
+
let generation = 0;
|
|
437
|
+
let enabled = true;
|
|
348
438
|
let timer;
|
|
349
439
|
let persistPromise;
|
|
350
|
-
let pending
|
|
440
|
+
let pending;
|
|
351
441
|
const recordError = (error) => {
|
|
352
442
|
try {
|
|
353
443
|
deps.recordError(error);
|
|
@@ -356,32 +446,100 @@ export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps) {
|
|
|
356
446
|
// Snapshot diagnostics cannot create an unhandled scheduler rejection.
|
|
357
447
|
}
|
|
358
448
|
};
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
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;
|
|
362
493
|
return;
|
|
363
494
|
}
|
|
364
|
-
|
|
495
|
+
const token = {};
|
|
496
|
+
const handle = setTimer(() => {
|
|
497
|
+
if (timer?.token !== token)
|
|
498
|
+
return;
|
|
365
499
|
timer = undefined;
|
|
500
|
+
const publicationAuthority = pending;
|
|
501
|
+
pending = undefined;
|
|
502
|
+
if (!authority() || !publicationAuthority?.())
|
|
503
|
+
return;
|
|
366
504
|
let tracked;
|
|
367
505
|
tracked = Promise.resolve()
|
|
368
|
-
.then(deps.persistSnapshot)
|
|
506
|
+
.then(() => publicationAuthority() ? deps.persistSnapshot(publicationAuthority) : undefined)
|
|
369
507
|
.catch(recordError)
|
|
370
508
|
.finally(() => {
|
|
371
509
|
if (persistPromise !== tracked)
|
|
372
510
|
return;
|
|
373
511
|
persistPromise = undefined;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
request();
|
|
377
|
-
}
|
|
512
|
+
// Only an explicitly captured new request can arm a successor publication.
|
|
513
|
+
arm();
|
|
378
514
|
});
|
|
379
515
|
persistPromise = tracked;
|
|
380
516
|
}, TELEGRAM_DIAGNOSTICS_SNAPSHOT_COALESCE_MS);
|
|
381
|
-
|
|
382
|
-
|
|
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();
|
|
383
527
|
};
|
|
384
|
-
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
|
+
});
|
|
385
543
|
}
|
|
386
544
|
export function getTelegramStatusBarProcessingStatus(state) {
|
|
387
545
|
if (state.hasPendingModelSwitch)
|
|
@@ -757,15 +915,9 @@ function buildTelegramBridgeCompactStatusLines(state) {
|
|
|
757
915
|
: state.activeSourceMessageIds?.length
|
|
758
916
|
? "active"
|
|
759
917
|
: "idle";
|
|
760
|
-
const diagnosticsProfileName = state.activeProfileName === TELEGRAM_STATUS_DEFAULT_PROFILE_NAME
|
|
761
|
-
? undefined
|
|
762
|
-
: state.activeProfileName;
|
|
763
|
-
const profileSuffix = diagnosticsProfileName
|
|
764
|
-
? `.${diagnosticsProfileName.replace(/[^a-zA-Z0-9._-]+/g, "_")}`
|
|
765
|
-
: "";
|
|
766
918
|
const diagnosticsPaths = state.diagnosticPaths ?? {
|
|
767
|
-
state:
|
|
768
|
-
logs:
|
|
919
|
+
state: "~/.pi/agent/tmp/pi-telegram/state.json",
|
|
920
|
+
logs: "~/.pi/agent/tmp/pi-telegram/logs.jsonl",
|
|
769
921
|
};
|
|
770
922
|
return [
|
|
771
923
|
"connection:",
|
|
@@ -813,7 +965,7 @@ export function buildTelegramBridgeStatusLines(state, options = {}) {
|
|
|
813
965
|
return buildTelegramBridgeDiagnosticStatusLines(state);
|
|
814
966
|
return buildTelegramBridgeCompactStatusLines(state);
|
|
815
967
|
}
|
|
816
|
-
|
|
968
|
+
function buildTelegramBridgeDiagnosticStatusLines(state) {
|
|
817
969
|
const controlQueueCount = state.queuedItems.filter((item) => item.queueLane === "control").length;
|
|
818
970
|
const priorityQueueCount = state.queuedItems.filter((item) => item.queueLane === "priority").length;
|
|
819
971
|
const defaultQueueCount = state.queuedItems.filter((item) => item.queueLane === "default").length;
|
|
@@ -972,7 +1124,7 @@ function buildTelegramStatusRoleSuffix(state) {
|
|
|
972
1124
|
return "";
|
|
973
1125
|
return ` @${state.busRole}`;
|
|
974
1126
|
}
|
|
975
|
-
|
|
1127
|
+
function buildStatusHtml(ctx, activeModel, bridgeStatus) {
|
|
976
1128
|
const stats = collectUsageStats(ctx);
|
|
977
1129
|
const usesSubscription = activeModel
|
|
978
1130
|
? ctx.modelRegistry.isUsingOAuth(activeModel)
|
package/dist/lib/sync.d.ts
CHANGED
|
@@ -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";
|
package/dist/lib/sync.js
CHANGED
|
@@ -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",
|
|
@@ -459,6 +460,7 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
|
|
|
459
460
|
? deps.topicTargetStore.getWorkspaceBinding(workspaceIdentity.cwd, workspaceIdentity.instanceSlot, workspaceIdentity.sessionId)
|
|
460
461
|
: undefined;
|
|
461
462
|
const legacyWorkspaceBinding = workspaceIdentity?.instanceSlot === "a" &&
|
|
463
|
+
deps.sessionId === undefined &&
|
|
462
464
|
!persistedWorkspaceBinding &&
|
|
463
465
|
typeof legacyLeaderRecord?.target.threadId === "number"
|
|
464
466
|
? {
|
|
@@ -553,10 +555,9 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
|
|
|
553
555
|
return (record.instanceId === deps.instanceId &&
|
|
554
556
|
(record.status === "active" || record.status === "starting"));
|
|
555
557
|
});
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
|
|
559
|
-
if (!deps.forceFreshUnnamed && priorTargets.length > 0) {
|
|
558
|
+
// Legacy callers without session identity may reuse the active instance target.
|
|
559
|
+
// Session-aware callers must resolve their exact Workspace binding instead.
|
|
560
|
+
if (deps.sessionId === undefined && !deps.forceFreshUnnamed && priorTargets.length > 0) {
|
|
560
561
|
const record = priorTargets[0];
|
|
561
562
|
deps.recordEvent("telegram", "Leader thread preserved after session lifecycle change", {
|
|
562
563
|
phase: "leader-thread-reused",
|
|
@@ -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;
|