@llblab/pi-telegram 0.52.2 → 0.53.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 +9 -7
- package/BACKLOG.md +1 -18
- package/CHANGELOG.md +11 -0
- package/README.md +7 -11
- package/api/commands.ts +3 -0
- package/dist/api/commands.d.ts +1 -1
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +15 -42
- package/dist/lib/activity.d.ts +9 -0
- package/dist/lib/activity.js +39 -10
- package/dist/lib/agent-messages.js +7 -3
- package/dist/lib/bindings.d.ts +43 -5
- package/dist/lib/bindings.js +163 -20
- package/dist/lib/bus-api.d.ts +12 -0
- package/dist/lib/bus-api.js +182 -79
- package/dist/lib/bus-follower.d.ts +111 -31
- package/dist/lib/bus-follower.js +1324 -359
- package/dist/lib/bus-leader.d.ts +41 -91
- package/dist/lib/bus-leader.js +969 -637
- package/dist/lib/bus-transport.js +16 -9
- package/dist/lib/bus.d.ts +215 -18
- package/dist/lib/bus.js +1048 -327
- package/dist/lib/channel-posts.d.ts +5 -0
- package/dist/lib/channel-posts.js +434 -261
- package/dist/lib/command-templates.d.ts +26 -5
- package/dist/lib/command-templates.js +92 -176
- package/dist/lib/commands.d.ts +168 -82
- package/dist/lib/commands.js +1409 -291
- package/dist/lib/config.d.ts +1 -8
- package/dist/lib/config.js +34 -76
- package/dist/lib/delivery.d.ts +2 -2
- package/dist/lib/delivery.js +16 -16
- package/dist/lib/extension.js +639 -171
- package/dist/lib/generative-app-worker.mjs +19 -7
- package/dist/lib/generative-apps.d.ts +0 -5
- package/dist/lib/generative-apps.js +61 -49
- package/dist/lib/inbound.d.ts +5 -0
- package/dist/lib/inbound.js +35 -59
- package/dist/lib/journal.d.ts +33 -3
- package/dist/lib/journal.js +2063 -782
- package/dist/lib/lifecycle.d.ts +2 -6
- package/dist/lib/lifecycle.js +75 -58
- package/dist/lib/locks.d.ts +29 -3
- package/dist/lib/locks.js +289 -108
- package/dist/lib/logging.js +50 -14
- package/dist/lib/media.d.ts +22 -1
- package/dist/lib/media.js +46 -28
- package/dist/lib/menu-model.d.ts +19 -5
- package/dist/lib/menu-model.js +53 -11
- package/dist/lib/menu-queue.d.ts +5 -4
- package/dist/lib/menu-queue.js +45 -63
- package/dist/lib/menu-settings.d.ts +5 -4
- package/dist/lib/menu-settings.js +114 -78
- package/dist/lib/menu-status.d.ts +1 -1
- package/dist/lib/menu-status.js +8 -25
- package/dist/lib/menu-thinking.d.ts +6 -1
- package/dist/lib/menu-thinking.js +14 -21
- package/dist/lib/menu.d.ts +8 -61
- package/dist/lib/menu.js +92 -164
- package/dist/lib/model.d.ts +1 -1
- package/dist/lib/model.js +2 -16
- package/dist/lib/outbound-attachments.d.ts +6 -27
- package/dist/lib/outbound-attachments.js +45 -38
- package/dist/lib/outbound-buttons.js +22 -16
- package/dist/lib/outbound-markup.d.ts +0 -2
- package/dist/lib/outbound-markup.js +51 -64
- package/dist/lib/outbound-voice.js +6 -3
- package/dist/lib/outbound.d.ts +6 -6
- package/dist/lib/outbound.js +61 -51
- package/dist/lib/ownership.js +6 -2
- package/dist/lib/paths.d.ts +5 -0
- package/dist/lib/paths.js +37 -21
- package/dist/lib/pi.d.ts +2 -0
- package/dist/lib/pi.js +8 -1
- package/dist/lib/polling.d.ts +21 -37
- package/dist/lib/polling.js +59 -50
- package/dist/lib/preview.js +39 -19
- package/dist/lib/process-identity.d.ts +24 -0
- package/dist/lib/process-identity.js +133 -0
- package/dist/lib/prompts.js +2 -7
- package/dist/lib/queue.d.ts +14 -16
- package/dist/lib/queue.js +249 -126
- package/dist/lib/recovery.d.ts +2 -83
- package/dist/lib/recovery.js +20 -274
- package/dist/lib/replies.d.ts +16 -24
- package/dist/lib/replies.js +99 -116
- package/dist/lib/routing.d.ts +120 -7
- package/dist/lib/routing.js +4667 -1152
- package/dist/lib/runtime.d.ts +1 -11
- package/dist/lib/sections.d.ts +4 -29
- package/dist/lib/sections.js +7 -20
- package/dist/lib/skills.js +4 -3
- package/dist/lib/status.js +86 -57
- package/dist/lib/sync.d.ts +1 -3
- package/dist/lib/sync.js +67 -43
- package/dist/lib/target.d.ts +4 -0
- package/dist/lib/target.js +18 -0
- package/dist/lib/telegram-api.d.ts +18 -13
- package/dist/lib/telegram-api.js +148 -53
- package/dist/lib/text-groups.js +4 -39
- package/dist/lib/thread-cleanup-manager.d.ts +6 -27
- package/dist/lib/thread-cleanup-manager.js +369 -186
- package/dist/lib/thread-display.d.ts +1 -2
- package/dist/lib/thread-display.js +58 -38
- package/dist/lib/thread-naming.d.ts +28 -8
- package/dist/lib/thread-naming.js +175 -66
- package/dist/lib/thread-reconciler.d.ts +42 -16
- package/dist/lib/thread-reconciler.js +90 -50
- package/dist/lib/threads.d.ts +82 -20
- package/dist/lib/threads.js +2247 -855
- package/dist/lib/time-injection.js +1 -2
- package/dist/lib/turns.js +1 -19
- package/dist/lib/updates.d.ts +169 -80
- package/dist/lib/updates.js +2418 -641
- package/dist/lib/voice.d.ts +7 -22
- package/dist/lib/voice.js +7 -22
- package/dist/lib/wire.js +1 -1
- package/dist/lib/workspace-admission.d.ts +3 -1
- package/dist/lib/workspace-admission.js +425 -211
- package/dist/lib/workspace-identity.js +8 -13
- package/dist/lib/workspace-retirement.d.ts +1 -1
- package/dist/lib/workspace-retirement.js +328 -140
- package/dist/lib/workspace-slots.js +8 -4
- package/dist/package.json +1 -1
- package/docs/architecture.md +31 -29
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +15 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +29 -1
- package/docs/ui-style.md +25 -8
- package/lib/activity-verbosity.ts +59 -82
- package/lib/activity.ts +146 -102
- package/lib/agent-messages.ts +12 -5
- package/lib/bindings.ts +389 -82
- package/lib/bus-api.ts +263 -102
- package/lib/bus-follower.ts +2440 -601
- package/lib/bus-leader.ts +2209 -1290
- package/lib/bus-transport.ts +46 -12
- package/lib/bus.ts +1673 -444
- package/lib/channel-posts.ts +848 -327
- package/lib/command-templates.ts +152 -265
- package/lib/commands.ts +2398 -555
- package/lib/config.ts +177 -170
- package/lib/delivery.ts +41 -29
- package/lib/extension.ts +1206 -433
- package/lib/generative-app-worker.mjs +19 -7
- package/lib/generative-apps.ts +324 -211
- package/lib/inbound.ts +95 -97
- package/lib/journal.ts +5311 -1763
- package/lib/keyboard.ts +1 -3
- package/lib/lifecycle.ts +128 -78
- package/lib/locks.ts +677 -203
- package/lib/logging.ts +147 -58
- package/lib/media.ts +82 -43
- package/lib/menu-model.ts +85 -18
- package/lib/menu-queue.ts +88 -129
- package/lib/menu-settings.ts +260 -155
- package/lib/menu-status.ts +20 -60
- package/lib/menu-thinking.ts +41 -56
- package/lib/menu.ts +112 -285
- package/lib/model.ts +8 -22
- package/lib/outbound-attachments.ts +156 -125
- package/lib/outbound-buttons.ts +76 -49
- package/lib/outbound-markup.ts +96 -94
- package/lib/outbound-voice.ts +33 -27
- package/lib/outbound.ts +125 -125
- package/lib/ownership.ts +19 -10
- package/lib/paths.ts +148 -49
- package/lib/pi.ts +35 -15
- package/lib/polling.ts +144 -147
- package/lib/preview.ts +93 -40
- package/lib/process-identity.ts +164 -0
- package/lib/prompts.ts +4 -11
- package/lib/queue.ts +511 -300
- package/lib/recovery.ts +50 -454
- package/lib/rendering.ts +1 -3
- package/lib/replies.ts +248 -209
- package/lib/routing.ts +8509 -2186
- package/lib/runtime.ts +1 -12
- package/lib/sections.ts +24 -89
- package/lib/setup.ts +1 -3
- package/lib/skills.ts +9 -4
- package/lib/status.ts +215 -110
- package/lib/sync.ts +448 -361
- package/lib/target.ts +22 -3
- package/lib/telegram-api.ts +302 -127
- package/lib/text-groups.ts +5 -39
- package/lib/thread-cleanup-manager.ts +833 -331
- package/lib/thread-display.ts +213 -97
- package/lib/thread-naming.ts +232 -87
- package/lib/thread-reconciler.ts +155 -72
- package/lib/threads.ts +5318 -1845
- package/lib/time-injection.ts +1 -4
- package/lib/turns.ts +15 -28
- package/lib/updates.ts +5184 -1612
- package/lib/voice.ts +7 -35
- package/lib/wire.ts +1 -1
- package/lib/workspace-admission.ts +996 -416
- package/lib/workspace-identity.ts +14 -17
- package/lib/workspace-retirement.ts +878 -376
- package/lib/workspace-slots.ts +22 -13
- package/package.json +1 -1
- package/scripts/check-downgrade.mjs +0 -472
- package/scripts/measure-workspace.mjs +0 -108
package/lib/config.ts
CHANGED
|
@@ -15,20 +15,20 @@ import {
|
|
|
15
15
|
unlinkSync,
|
|
16
16
|
writeFileSync,
|
|
17
17
|
} from "node:fs";
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
import type { CommandTemplateObjectConfig } from "./command-templates.ts";
|
|
20
|
+
import type { TelegramInboundHandlerConfig } from "./inbound.ts";
|
|
21
|
+
import { withTelegramFileTransaction } from "./locks.ts";
|
|
19
22
|
import {
|
|
20
23
|
resolveAgentDir,
|
|
21
24
|
resolveTelegramConfigPath,
|
|
22
25
|
TELEGRAM_DEFAULT_PROFILE_NAME,
|
|
23
26
|
} from "./paths.ts";
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
import type { CommandTemplateObjectConfig } from "./command-templates.ts";
|
|
27
|
+
import { isPiStaleContextError } from "./pi.ts";
|
|
27
28
|
import { isWireRecord as isPlainConfigRecord } from "./wire.ts";
|
|
28
|
-
import type { TelegramInboundHandlerConfig } from "./inbound.ts";
|
|
29
|
-
import { withTelegramFileTransaction } from "./locks.ts";
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
export { TELEGRAM_DEFAULT_PROFILE_NAME } from "./paths.ts";
|
|
31
|
+
|
|
32
32
|
const CONFIG_REPLACE_RETRY_ATTEMPTS = 5;
|
|
33
33
|
const CONFIG_REPLACE_RETRY_DELAY_MS = 25;
|
|
34
34
|
|
|
@@ -136,21 +136,22 @@ export interface ResolvedTelegramTimeConfig {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
export type TelegramThreadDisplayMode =
|
|
139
|
-
| "
|
|
140
|
-
| "names"
|
|
141
|
-
| "directories"
|
|
142
|
-
| "directory-snake"
|
|
143
|
-
| "directory-title";
|
|
139
|
+
"letters" | "names" | "directories" | "directory-snake" | "directory-title";
|
|
144
140
|
|
|
145
141
|
const TELEGRAM_THREAD_DISPLAY_MODES: readonly TelegramThreadDisplayMode[] = [
|
|
146
|
-
"letters",
|
|
142
|
+
"letters",
|
|
143
|
+
"names",
|
|
144
|
+
"directory-snake",
|
|
145
|
+
"directory-title",
|
|
147
146
|
];
|
|
148
147
|
|
|
149
148
|
export function resolveTelegramThreadDisplayMode(
|
|
150
149
|
config: Pick<TelegramConfig, "threadDisplayMode">,
|
|
151
150
|
): TelegramThreadDisplayMode {
|
|
152
|
-
return TELEGRAM_THREAD_DISPLAY_MODES.includes(
|
|
153
|
-
|
|
151
|
+
return TELEGRAM_THREAD_DISPLAY_MODES.includes(
|
|
152
|
+
config.threadDisplayMode as TelegramThreadDisplayMode,
|
|
153
|
+
)
|
|
154
|
+
? (config.threadDisplayMode as TelegramThreadDisplayMode)
|
|
154
155
|
: "letters";
|
|
155
156
|
}
|
|
156
157
|
|
|
@@ -164,21 +165,27 @@ export async function setTelegramThreadDisplayMode(
|
|
|
164
165
|
}
|
|
165
166
|
const profile = store.getActiveProfileName();
|
|
166
167
|
const current = () => isCurrent() && store.getActiveProfileName() === profile;
|
|
167
|
-
if (!current())
|
|
168
|
+
if (!current())
|
|
169
|
+
throw new Error("Telegram Thread display setting lost authority.");
|
|
168
170
|
await store.load();
|
|
169
171
|
if (!current() || !store.hasBotToken()) {
|
|
170
|
-
throw new Error(
|
|
172
|
+
throw new Error(
|
|
173
|
+
"Telegram Thread display setting lost its configured profile.",
|
|
174
|
+
);
|
|
171
175
|
}
|
|
172
|
-
await store.persist(
|
|
173
|
-
|
|
176
|
+
await store.persist(
|
|
177
|
+
{ ...store.get(), threadDisplayMode: mode },
|
|
178
|
+
{ isCurrent: current },
|
|
179
|
+
);
|
|
180
|
+
if (!current())
|
|
181
|
+
throw new Error(
|
|
182
|
+
"Telegram Thread display setting changed during persistence.",
|
|
183
|
+
);
|
|
174
184
|
}
|
|
175
185
|
|
|
176
186
|
export type TelegramAssistantRenderingMode = "rich" | "html";
|
|
177
187
|
export type TelegramActivityVerbosity =
|
|
178
|
-
| "
|
|
179
|
-
| "thinking"
|
|
180
|
-
| "tools"
|
|
181
|
-
| "verbose";
|
|
188
|
+
"quiet" | "thinking" | "tools" | "verbose";
|
|
182
189
|
|
|
183
190
|
export interface TelegramConfig {
|
|
184
191
|
/** @deprecated persisted identity belongs in profiles.default; retained for effective/legacy views */
|
|
@@ -304,7 +311,10 @@ export interface TelegramConfigStore {
|
|
|
304
311
|
) => { admitted: false } | { admitted: true; value: T };
|
|
305
312
|
load: () => Promise<void>;
|
|
306
313
|
didLastLoadRecoverInvalidConfig: () => boolean;
|
|
307
|
-
persist: (
|
|
314
|
+
persist: (
|
|
315
|
+
config?: TelegramConfig,
|
|
316
|
+
options?: { isCurrent?: () => boolean },
|
|
317
|
+
) => Promise<void>;
|
|
308
318
|
}
|
|
309
319
|
|
|
310
320
|
export function createTelegramConfigBotIdGetter(
|
|
@@ -338,29 +348,6 @@ export interface TelegramInvalidConfigRecovery {
|
|
|
338
348
|
error: unknown;
|
|
339
349
|
}
|
|
340
350
|
|
|
341
|
-
export interface TelegramConfigRuntime {
|
|
342
|
-
updateVoiceConfig: (voice: NonNullable<TelegramConfig["voice"]>) => void;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export function setGlobalTelegramConfigRuntime(
|
|
346
|
-
runtime: TelegramConfigRuntime | undefined,
|
|
347
|
-
): void {
|
|
348
|
-
const globals = globalThis as Record<string, unknown>;
|
|
349
|
-
if (runtime) globals[CONFIG_RUNTIME_KEY] = runtime;
|
|
350
|
-
else delete globals[CONFIG_RUNTIME_KEY];
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
export function updateTelegramVoiceConfig(
|
|
354
|
-
voice: NonNullable<TelegramConfig["voice"]>,
|
|
355
|
-
): boolean {
|
|
356
|
-
const runtime = (globalThis as Record<string, unknown>)[
|
|
357
|
-
CONFIG_RUNTIME_KEY
|
|
358
|
-
] as TelegramConfigRuntime | undefined;
|
|
359
|
-
if (!runtime || typeof runtime.updateVoiceConfig !== "function") return false;
|
|
360
|
-
runtime.updateVoiceConfig(voice);
|
|
361
|
-
return true;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
351
|
type TelegramMutableConfigStore = Pick<
|
|
365
352
|
TelegramConfigStore,
|
|
366
353
|
"get" | "set" | "persist"
|
|
@@ -387,22 +374,6 @@ async function loadLatestTelegramConfig(
|
|
|
387
374
|
}
|
|
388
375
|
}
|
|
389
376
|
|
|
390
|
-
export function bindGlobalTelegramConfigRuntime(
|
|
391
|
-
configStore: TelegramMutableConfigStore,
|
|
392
|
-
): void {
|
|
393
|
-
setGlobalTelegramConfigRuntime({
|
|
394
|
-
updateVoiceConfig(voice) {
|
|
395
|
-
const current = configStore.get();
|
|
396
|
-
const next = {
|
|
397
|
-
...current,
|
|
398
|
-
voice: { ...(current.voice ?? {}), ...voice },
|
|
399
|
-
};
|
|
400
|
-
configStore.set(next);
|
|
401
|
-
void configStore.persist(next);
|
|
402
|
-
},
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
|
|
406
377
|
function getInvalidTelegramConfigRecoveryPath(configPath: string): string {
|
|
407
378
|
return `${configPath}.invalid-${process.pid}-${Date.now()}`;
|
|
408
379
|
}
|
|
@@ -448,22 +419,6 @@ export async function readTelegramConfig(
|
|
|
448
419
|
}
|
|
449
420
|
}
|
|
450
421
|
|
|
451
|
-
export async function writeTelegramConfig(
|
|
452
|
-
agentDir: string,
|
|
453
|
-
configPath: string,
|
|
454
|
-
config: TelegramConfig,
|
|
455
|
-
): Promise<void> {
|
|
456
|
-
await mkdir(agentDir, { recursive: true });
|
|
457
|
-
const tempConfigPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
458
|
-
await writeFile(tempConfigPath, JSON.stringify(config, null, "\t") + "\n", {
|
|
459
|
-
encoding: "utf8",
|
|
460
|
-
mode: 0o600,
|
|
461
|
-
});
|
|
462
|
-
await chmod(tempConfigPath, 0o600);
|
|
463
|
-
await rename(tempConfigPath, configPath);
|
|
464
|
-
await chmod(configPath, 0o600);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
422
|
function cloneTelegramConfig<T>(value: T): T {
|
|
468
423
|
return structuredClone(value);
|
|
469
424
|
}
|
|
@@ -544,10 +499,16 @@ export function getTelegramProfileFields(
|
|
|
544
499
|
): TelegramBotProfile | undefined {
|
|
545
500
|
const token = config.botToken?.trim();
|
|
546
501
|
if (!token) return undefined;
|
|
502
|
+
return { botToken: token, ...pickTelegramRootProfileFields(config) };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** Root-level profile fields other than the token, including the retired legacy cursor. */
|
|
506
|
+
function pickTelegramRootProfileFields(
|
|
507
|
+
config: TelegramConfig,
|
|
508
|
+
): Omit<TelegramBotProfile, "botToken"> & TelegramLegacyCursorCarrier {
|
|
547
509
|
const legacyCursor = (config as TelegramConfig & TelegramLegacyCursorCarrier)
|
|
548
510
|
.lastUpdateId;
|
|
549
511
|
return {
|
|
550
|
-
botToken: token,
|
|
551
512
|
...(config.botUsername !== undefined
|
|
552
513
|
? { botUsername: config.botUsername }
|
|
553
514
|
: {}),
|
|
@@ -620,23 +581,7 @@ export function normalizeTelegramDefaultProfileConfig(config: TelegramConfig): {
|
|
|
620
581
|
const legacyProfile: Partial<TelegramBotProfile> &
|
|
621
582
|
TelegramLegacyCursorCarrier = {
|
|
622
583
|
...(legacyToken ? { botToken: legacyToken } : {}),
|
|
623
|
-
...(config
|
|
624
|
-
? { botUsername: config.botUsername }
|
|
625
|
-
: {}),
|
|
626
|
-
...(config.botId !== undefined ? { botId: config.botId } : {}),
|
|
627
|
-
...(config.allowedUserId !== undefined
|
|
628
|
-
? { allowedUserId: config.allowedUserId }
|
|
629
|
-
: {}),
|
|
630
|
-
...(config.threadDisplayMode !== undefined
|
|
631
|
-
? { threadDisplayMode: config.threadDisplayMode }
|
|
632
|
-
: {}),
|
|
633
|
-
...((config as TelegramConfig & TelegramLegacyCursorCarrier)
|
|
634
|
-
.lastUpdateId !== undefined
|
|
635
|
-
? {
|
|
636
|
-
lastUpdateId: (config as TelegramConfig & TelegramLegacyCursorCarrier)
|
|
637
|
-
.lastUpdateId,
|
|
638
|
-
}
|
|
639
|
-
: {}),
|
|
584
|
+
...pickTelegramRootProfileFields(config),
|
|
640
585
|
};
|
|
641
586
|
if (!canonicalProfile && !legacyToken) {
|
|
642
587
|
throw new Error("Legacy Telegram default profile has no bot token");
|
|
@@ -724,21 +669,31 @@ export function createTelegramConfigStore(
|
|
|
724
669
|
);
|
|
725
670
|
mutationVersion += 1;
|
|
726
671
|
};
|
|
727
|
-
const adoptPersistedConfig = (
|
|
672
|
+
const adoptPersistedConfig = (
|
|
673
|
+
merged: TelegramConfig,
|
|
674
|
+
preserveLocalChanges: boolean,
|
|
675
|
+
) => {
|
|
728
676
|
// Local edits are relative to the latest observation, not a queued write's older request baseline.
|
|
729
677
|
const nextConfig = preserveLocalChanges
|
|
730
|
-
? mergeTelegramConfigDelta(
|
|
731
|
-
|
|
678
|
+
? (mergeTelegramConfigDelta(
|
|
679
|
+
persistedConfig as Record<string, unknown>,
|
|
680
|
+
config as Record<string, unknown>,
|
|
681
|
+
merged as Record<string, unknown>,
|
|
682
|
+
) as TelegramConfig)
|
|
732
683
|
: cloneTelegramConfig(merged);
|
|
733
684
|
persistedConfig = cloneTelegramConfig(merged);
|
|
734
685
|
config = nextConfig;
|
|
735
686
|
};
|
|
736
687
|
const withPersistedPairingProfile = <T>(
|
|
737
|
-
profileName: string,
|
|
688
|
+
profileName: string,
|
|
689
|
+
tokenSha256: string,
|
|
738
690
|
observe: (latest: TelegramConfig, profile: TelegramBotProfile) => T,
|
|
739
691
|
): T => {
|
|
740
|
-
if (
|
|
741
|
-
|
|
692
|
+
if (
|
|
693
|
+
(profileName !== TELEGRAM_DEFAULT_PROFILE_NAME &&
|
|
694
|
+
!isValidTelegramProfileName(profileName)) ||
|
|
695
|
+
!/^[a-f0-9]{64}$/u.test(tokenSha256)
|
|
696
|
+
) {
|
|
742
697
|
throw new Error("Invalid Telegram pairing admission identity.");
|
|
743
698
|
}
|
|
744
699
|
return withTelegramFileTransaction(`${configPath}.transaction`, () => {
|
|
@@ -748,11 +703,18 @@ export function createTelegramConfigStore(
|
|
|
748
703
|
typeof profile?.botToken === "string"
|
|
749
704
|
? resolveTelegramBotToken(profile.botToken, env)
|
|
750
705
|
: undefined;
|
|
751
|
-
if (
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
706
|
+
if (
|
|
707
|
+
!profile ||
|
|
708
|
+
!resolvedToken ||
|
|
709
|
+
createHash("sha256").update(resolvedToken).digest("hex") !==
|
|
710
|
+
tokenSha256 ||
|
|
711
|
+
(profile.allowedUserId !== undefined &&
|
|
712
|
+
(!Number.isSafeInteger(profile.allowedUserId) ||
|
|
713
|
+
profile.allowedUserId <= 0))
|
|
714
|
+
) {
|
|
715
|
+
throw new Error(
|
|
716
|
+
"Telegram pairing admission authority is unavailable or changed.",
|
|
717
|
+
);
|
|
756
718
|
}
|
|
757
719
|
return observe(latest, profile);
|
|
758
720
|
});
|
|
@@ -788,17 +750,20 @@ export function createTelegramConfigStore(
|
|
|
788
750
|
return true;
|
|
789
751
|
},
|
|
790
752
|
getActiveProfileName: () => activeProfileName,
|
|
791
|
-
getBotToken: () =>
|
|
753
|
+
getBotToken: () =>
|
|
754
|
+
resolveTelegramBotToken(getEffectiveConfig().botToken, env),
|
|
792
755
|
getBotTokenDiagnostic: () =>
|
|
793
756
|
getTelegramBotTokenDiagnostic(getEffectiveConfig().botToken, env),
|
|
794
|
-
hasBotToken: () =>
|
|
757
|
+
hasBotToken: () =>
|
|
758
|
+
!!resolveTelegramBotToken(getEffectiveConfig().botToken, env),
|
|
795
759
|
getAllowedUserId: () => getEffectiveConfig().allowedUserId,
|
|
796
760
|
getLegacyPollingCursor: () =>
|
|
797
761
|
(getEffectiveConfig() as TelegramConfig & TelegramLegacyCursorCarrier)
|
|
798
762
|
.lastUpdateId,
|
|
799
763
|
removeLegacyPollingCursor: () => {
|
|
800
764
|
const next = {
|
|
801
|
-
...(getEffectiveConfig() as TelegramConfig &
|
|
765
|
+
...(getEffectiveConfig() as TelegramConfig &
|
|
766
|
+
TelegramLegacyCursorCarrier),
|
|
802
767
|
};
|
|
803
768
|
delete next.lastUpdateId;
|
|
804
769
|
setEffectiveConfig(next);
|
|
@@ -817,26 +782,47 @@ export function createTelegramConfigStore(
|
|
|
817
782
|
withSourceSerialization: (operation) =>
|
|
818
783
|
withTelegramFileTransaction(`${configPath}.transaction`, operation),
|
|
819
784
|
withPairingAdmission: (profileName, tokenSha256, publish) =>
|
|
820
|
-
withPersistedPairingProfile(
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
785
|
+
withPersistedPairingProfile(
|
|
786
|
+
profileName,
|
|
787
|
+
tokenSha256,
|
|
788
|
+
(_latest, profile) => publish(profile.allowedUserId === undefined),
|
|
789
|
+
),
|
|
790
|
+
withPairedUserAdmission: (
|
|
791
|
+
profileName,
|
|
792
|
+
tokenSha256,
|
|
793
|
+
userId,
|
|
794
|
+
publish,
|
|
795
|
+
assertExecutionCurrent,
|
|
796
|
+
) => {
|
|
797
|
+
if (!Number.isSafeInteger(userId) || userId <= 0)
|
|
798
|
+
return { admitted: false };
|
|
824
799
|
assertExecutionCurrent?.();
|
|
825
|
-
return withPersistedPairingProfile(
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
800
|
+
return withPersistedPairingProfile(
|
|
801
|
+
profileName,
|
|
802
|
+
tokenSha256,
|
|
803
|
+
(latest, profile) => {
|
|
804
|
+
if (profile.allowedUserId !== userId) return { admitted: false };
|
|
805
|
+
assertExecutionCurrent?.();
|
|
806
|
+
const current = getEffectiveConfig();
|
|
807
|
+
const previousOwner =
|
|
808
|
+
persistedConfig.profiles?.[profileName]?.allowedUserId;
|
|
809
|
+
if (
|
|
810
|
+
(activeProfileName ?? TELEGRAM_DEFAULT_PROFILE_NAME) !==
|
|
811
|
+
profileName ||
|
|
831
812
|
current.botToken !== profile.botToken ||
|
|
832
|
-
(current.allowedUserId !== undefined &&
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
813
|
+
(current.allowedUserId !== undefined &&
|
|
814
|
+
current.allowedUserId !== userId) ||
|
|
815
|
+
(current.allowedUserId === undefined && previousOwner !== undefined)
|
|
816
|
+
) {
|
|
817
|
+
throw new Error(
|
|
818
|
+
"Telegram paired admission lost local profile authority.",
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
// Observation is not a local edit; queued persistence still adopts its own fresh disk result.
|
|
822
|
+
adoptPersistedConfig(latest, true);
|
|
823
|
+
return { admitted: true, value: publish() };
|
|
824
|
+
},
|
|
825
|
+
);
|
|
840
826
|
},
|
|
841
827
|
persistAllowedUserId: (userId, assertExecutionCurrent, commitIfOwned) => {
|
|
842
828
|
const profileName = activeProfileName;
|
|
@@ -845,40 +831,63 @@ export function createTelegramConfigStore(
|
|
|
845
831
|
const previousOwner = getEffectiveConfig().allowedUserId;
|
|
846
832
|
const assertCurrent = () => {
|
|
847
833
|
assertExecutionCurrent?.();
|
|
848
|
-
if (
|
|
849
|
-
|
|
850
|
-
|
|
834
|
+
if (
|
|
835
|
+
activeProfileName !== profileName ||
|
|
836
|
+
getEffectiveConfig().botToken !== botToken ||
|
|
837
|
+
getEffectiveConfig().allowedUserId !== previousOwner
|
|
838
|
+
) {
|
|
839
|
+
throw new Error(
|
|
840
|
+
"Telegram pairing lost its originating profile authority.",
|
|
841
|
+
);
|
|
851
842
|
}
|
|
852
843
|
};
|
|
853
844
|
const pairing = persistQueue.then(() => {
|
|
854
845
|
assertCurrent();
|
|
855
|
-
if (!Number.isSafeInteger(userId) || userId <= 0)
|
|
846
|
+
if (!Number.isSafeInteger(userId) || userId <= 0)
|
|
847
|
+
throw new Error("Invalid Telegram pairing user ID.");
|
|
856
848
|
let merged: TelegramConfig | undefined;
|
|
857
849
|
const publish = () => {
|
|
858
|
-
merged = withTelegramFileTransaction(
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
850
|
+
merged = withTelegramFileTransaction(
|
|
851
|
+
`${configPath}.transaction`,
|
|
852
|
+
() => {
|
|
853
|
+
const latest = readTelegramConfigForTransaction(configPath);
|
|
854
|
+
const profile = latest.profiles?.[profileKey];
|
|
855
|
+
if (!botToken || profile?.botToken !== botToken) {
|
|
856
|
+
throw new Error(
|
|
857
|
+
"Telegram pairing profile is unavailable or changed.",
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
assertCurrent();
|
|
861
|
+
if (profile.allowedUserId !== undefined) return latest;
|
|
862
|
+
const next = {
|
|
863
|
+
...latest,
|
|
864
|
+
profiles: {
|
|
865
|
+
...latest.profiles,
|
|
866
|
+
[profileKey]: { ...profile, allowedUserId: userId },
|
|
867
|
+
},
|
|
868
|
+
};
|
|
869
|
+
writeTelegramConfigInTransaction(agentDir, configPath, next);
|
|
870
|
+
return next;
|
|
871
|
+
},
|
|
872
|
+
);
|
|
871
873
|
};
|
|
872
874
|
if (commitIfOwned) {
|
|
873
|
-
if (!commitIfOwned(publish))
|
|
875
|
+
if (!commitIfOwned(publish))
|
|
876
|
+
throw new Error(
|
|
877
|
+
"Telegram pairing lost transport ownership before publication.",
|
|
878
|
+
);
|
|
874
879
|
} else {
|
|
875
880
|
publish();
|
|
876
881
|
}
|
|
877
|
-
if (!merged)
|
|
882
|
+
if (!merged)
|
|
883
|
+
throw new Error("Telegram pairing publication did not execute.");
|
|
878
884
|
adoptPersistedConfig(merged, true);
|
|
879
885
|
return merged.profiles?.[profileKey]?.allowedUserId === userId;
|
|
880
886
|
});
|
|
881
|
-
persistQueue = pairing.then(
|
|
887
|
+
persistQueue = pairing.then(
|
|
888
|
+
() => undefined,
|
|
889
|
+
() => undefined,
|
|
890
|
+
);
|
|
882
891
|
return pairing;
|
|
883
892
|
},
|
|
884
893
|
load: async () => {
|
|
@@ -936,7 +945,9 @@ export function createTelegramConfigStore(
|
|
|
936
945
|
`${configPath}.transaction`,
|
|
937
946
|
() => {
|
|
938
947
|
if (options?.isCurrent && !options.isCurrent()) {
|
|
939
|
-
throw new Error(
|
|
948
|
+
throw new Error(
|
|
949
|
+
"Telegram config update lost its originating authority.",
|
|
950
|
+
);
|
|
940
951
|
}
|
|
941
952
|
const latestConfig = readTelegramConfigForTransaction(configPath);
|
|
942
953
|
const merged = mergeTelegramConfigDelta(
|
|
@@ -950,7 +961,10 @@ export function createTelegramConfigStore(
|
|
|
950
961
|
return merged;
|
|
951
962
|
},
|
|
952
963
|
);
|
|
953
|
-
adoptPersistedConfig(
|
|
964
|
+
adoptPersistedConfig(
|
|
965
|
+
mergedConfig,
|
|
966
|
+
mutationVersion !== capturedMutationVersion,
|
|
967
|
+
);
|
|
954
968
|
});
|
|
955
969
|
persistQueue = persist.catch(() => undefined);
|
|
956
970
|
return persist;
|
|
@@ -1051,10 +1065,8 @@ function createTelegramActivityVerbositySetter(
|
|
|
1051
1065
|
return async (verbosity) => {
|
|
1052
1066
|
await loadLatestTelegramConfig(configStore);
|
|
1053
1067
|
const current = configStore.get();
|
|
1054
|
-
const {
|
|
1055
|
-
|
|
1056
|
-
...assistant
|
|
1057
|
-
} = current.assistant ?? {};
|
|
1068
|
+
const { activityVerbosity: _legacyActivityVerbosity, ...assistant } =
|
|
1069
|
+
current.assistant ?? {};
|
|
1058
1070
|
const config = {
|
|
1059
1071
|
...current,
|
|
1060
1072
|
assistant: {
|
|
@@ -1087,7 +1099,9 @@ export function createTelegramVoiceReplyModeConfiguredChecker(
|
|
|
1087
1099
|
|
|
1088
1100
|
export function createTelegramVoiceReplyModeSetter(
|
|
1089
1101
|
configStore: TelegramMutableConfigStore,
|
|
1090
|
-
): (
|
|
1102
|
+
): (
|
|
1103
|
+
replyMode: "manual" | "hidden" | "mirror" | "always" | undefined,
|
|
1104
|
+
) => Promise<void> {
|
|
1091
1105
|
return async (replyMode) => {
|
|
1092
1106
|
await loadLatestTelegramConfig(configStore);
|
|
1093
1107
|
const current = configStore.get();
|
|
@@ -1250,12 +1264,10 @@ export function createTelegramConfigControls(
|
|
|
1250
1264
|
createTelegramAssistantRenderingModeGetter(configStore),
|
|
1251
1265
|
setAssistantRenderingMode:
|
|
1252
1266
|
createTelegramAssistantRenderingModeSetter(configStore),
|
|
1253
|
-
getActivityVerbosity:
|
|
1254
|
-
createTelegramActivityVerbosityGetter(configStore),
|
|
1267
|
+
getActivityVerbosity: createTelegramActivityVerbosityGetter(configStore),
|
|
1255
1268
|
refreshActivityVerbosity:
|
|
1256
1269
|
createTelegramActivityVerbosityRefresher(configStore),
|
|
1257
|
-
setActivityVerbosity:
|
|
1258
|
-
createTelegramActivityVerbositySetter(configStore),
|
|
1270
|
+
setActivityVerbosity: createTelegramActivityVerbositySetter(configStore),
|
|
1259
1271
|
getVoiceReplyMode: createTelegramVoiceReplyModeGetter(configStore),
|
|
1260
1272
|
isVoiceReplyModeConfigured:
|
|
1261
1273
|
createTelegramVoiceReplyModeConfiguredChecker(configStore),
|
|
@@ -1310,14 +1322,6 @@ export function getTelegramAuthorizationState(
|
|
|
1310
1322
|
return { kind: "deny" };
|
|
1311
1323
|
}
|
|
1312
1324
|
|
|
1313
|
-
function isTelegramStaleContextError(error: unknown): boolean {
|
|
1314
|
-
return (
|
|
1315
|
-
error instanceof Error &&
|
|
1316
|
-
(error.message.includes("stale after session") ||
|
|
1317
|
-
error.message.includes("stale ctx"))
|
|
1318
|
-
);
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
1325
|
export async function pairTelegramUserIfNeeded<TContext>(
|
|
1322
1326
|
userId: number,
|
|
1323
1327
|
deps: TelegramUserPairingDeps<TContext>,
|
|
@@ -1328,13 +1332,16 @@ export async function pairTelegramUserIfNeeded<TContext>(
|
|
|
1328
1332
|
);
|
|
1329
1333
|
if (authorization.kind !== "pair") return authorization.kind === "allow";
|
|
1330
1334
|
deps.assertExecutionCurrent?.();
|
|
1331
|
-
const allowed = await deps.persistAllowedUserId(
|
|
1335
|
+
const allowed = await deps.persistAllowedUserId(
|
|
1336
|
+
authorization.userId,
|
|
1337
|
+
deps.assertExecutionCurrent,
|
|
1338
|
+
);
|
|
1332
1339
|
deps.assertExecutionCurrent?.();
|
|
1333
1340
|
if (!allowed) return false;
|
|
1334
1341
|
try {
|
|
1335
1342
|
deps.updateStatus(deps.ctx);
|
|
1336
1343
|
} catch (error) {
|
|
1337
|
-
if (!
|
|
1344
|
+
if (!isPiStaleContextError(error)) throw error;
|
|
1338
1345
|
}
|
|
1339
1346
|
return true;
|
|
1340
1347
|
}
|