@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
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* leader activation hot-switching, local bus server startup, and stale follower pruning.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { isDeepStrictEqual } from "node:util";
|
|
8
9
|
import * as Sync from "./sync.ts";
|
|
9
10
|
import {
|
|
10
11
|
createTelegramThreadDisplayReconciler,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
resolveTelegramLiveWorkspaceBindingKeys,
|
|
13
14
|
} from "./thread-display.ts";
|
|
14
15
|
import type { TelegramThreadDisplayMode } from "./config.ts";
|
|
16
|
+
import * as ThreadNaming from "./thread-naming.ts";
|
|
15
17
|
import * as ThreadReconciler from "./thread-reconciler.ts";
|
|
16
18
|
import {
|
|
17
19
|
getTelegramApiErrorRequestTarget,
|
|
@@ -19,7 +21,9 @@ import {
|
|
|
19
21
|
type TelegramApiCallOptions,
|
|
20
22
|
} from "./telegram-api.ts";
|
|
21
23
|
import type { TelegramTarget } from "./target.ts";
|
|
24
|
+
import type { TelegramAttachmentSource } from "./media.ts";
|
|
22
25
|
import * as Threads from "./threads.ts";
|
|
26
|
+
import * as WorkspaceIdentity from "./workspace-identity.ts";
|
|
23
27
|
import {
|
|
24
28
|
createTelegramBusLocalServer,
|
|
25
29
|
createUnauthorizedBusAck,
|
|
@@ -39,6 +43,7 @@ import {
|
|
|
39
43
|
TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
|
|
40
44
|
TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF,
|
|
41
45
|
TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT,
|
|
46
|
+
TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE,
|
|
42
47
|
TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME,
|
|
43
48
|
TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE,
|
|
44
49
|
TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT,
|
|
@@ -218,7 +223,7 @@ export interface TelegramBusLeaderApiProxyDeps {
|
|
|
218
223
|
fileName: string,
|
|
219
224
|
options?: TelegramApiCallOptions,
|
|
220
225
|
) => Promise<unknown>;
|
|
221
|
-
downloadFile: (fileId: string,
|
|
226
|
+
downloadFile: (fileId: string, suggestedName: string, source?: TelegramAttachmentSource) => Promise<unknown>;
|
|
222
227
|
recoverStaleTargetError?: (
|
|
223
228
|
apiBody: unknown,
|
|
224
229
|
error: unknown,
|
|
@@ -236,7 +241,9 @@ export interface TelegramBusLeaderRuntimeAssemblyDeps<TContext> {
|
|
|
236
241
|
| "onFollowerConfirmedDead"
|
|
237
242
|
| "onFollowerConfirmedDeadPreserved"
|
|
238
243
|
| "getTelegramProfile"
|
|
244
|
+
| "getAllowedUserId"
|
|
239
245
|
| "provisionFollowerTarget"
|
|
246
|
+
| "commitFollowerRegistration"
|
|
240
247
|
| "provisionLeaderTarget"
|
|
241
248
|
| "recordRuntimeEvent"
|
|
242
249
|
>;
|
|
@@ -310,10 +317,10 @@ export function createTelegramBusFollowerSessionReplacementAuthority(deps: {
|
|
|
310
317
|
intent: Threads.TelegramSessionReplacementIntent,
|
|
311
318
|
): { cwd: string; sessionId: string } => {
|
|
312
319
|
const cwd = follower.cwd
|
|
313
|
-
?
|
|
320
|
+
? WorkspaceIdentity.normalizeTelegramWorkspacePath(follower.cwd)
|
|
314
321
|
: undefined;
|
|
315
322
|
const sessionId = follower.sessionId
|
|
316
|
-
?
|
|
323
|
+
? WorkspaceIdentity.normalizeTelegramSessionId(follower.sessionId)
|
|
317
324
|
: undefined;
|
|
318
325
|
const nowMs = getNowMs();
|
|
319
326
|
if (
|
|
@@ -645,7 +652,7 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
|
|
|
645
652
|
});
|
|
646
653
|
if (!renamed) {
|
|
647
654
|
throw new Error(
|
|
648
|
-
|
|
655
|
+
ThreadNaming.getTelegramTopicThreadNameValidationError(
|
|
649
656
|
threadName,
|
|
650
657
|
record.slot,
|
|
651
658
|
) ?? "Telegram Workspace Thread name is already reserved.",
|
|
@@ -827,7 +834,7 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
|
|
|
827
834
|
}
|
|
828
835
|
if (!renamed) {
|
|
829
836
|
throw new Error(
|
|
830
|
-
|
|
837
|
+
ThreadNaming.getTelegramTopicThreadNameValidationError(
|
|
831
838
|
threadName,
|
|
832
839
|
follower.slot,
|
|
833
840
|
) ?? "Telegram Workspace Thread name is already reserved.",
|
|
@@ -921,8 +928,23 @@ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
|
|
|
921
928
|
},
|
|
922
929
|
() => provisionFollowerTarget(registration, options),
|
|
923
930
|
),
|
|
931
|
+
commitFollowerRegistration: (input, publish) => runWorkspaceOperation({
|
|
932
|
+
operationId: createTelegramWorkspaceAdmissionOperationId(),
|
|
933
|
+
operationKind: "workspace.publish-follower-registration", scopes: [{ kind: "profile" }],
|
|
934
|
+
}, async () => {
|
|
935
|
+
if (!input.target) { publish(); return; }
|
|
936
|
+
await deps.topicTargetStore.load();
|
|
937
|
+
const binding = input.registration.cwd && input.registration.sessionId
|
|
938
|
+
? deps.topicTargetStore.getWorkspaceBindingByTarget(input.target, input.registration.sessionId) : undefined;
|
|
939
|
+
const bindingKey = binding && input.registration.cwd && binding.cwd === WorkspaceIdentity.normalizeTelegramWorkspacePath(input.registration.cwd)
|
|
940
|
+
? binding.bindingKey : undefined;
|
|
941
|
+
if (input.target.threadId !== undefined && input.registration.cwd && input.registration.sessionId && !bindingKey)
|
|
942
|
+
throw new Error("Telegram follower Workspace binding changed before registration publication.");
|
|
943
|
+
deps.topicTargetStore.commitWorkspaceRestoreRegistration({ target: input.target, slot: input.slot, bindingKey }, publish);
|
|
944
|
+
}),
|
|
924
945
|
getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
|
|
925
946
|
getTelegramProfile: deps.getTelegramProfile,
|
|
947
|
+
getAllowedUserId: deps.getAllowedUserId,
|
|
926
948
|
runWorkspaceAdmission,
|
|
927
949
|
runWithWorkspaceCapacity,
|
|
928
950
|
callApi: createTelegramBusLeaderApiProxy({
|
|
@@ -975,6 +997,12 @@ export type TelegramBusFollowerMessageOwnershipRecorder = (
|
|
|
975
997
|
record: TelegramBusFollowerMessageOwnershipRecord,
|
|
976
998
|
) => void;
|
|
977
999
|
|
|
1000
|
+
export type TelegramBusFollowerRegistrationCommitter = (input: {
|
|
1001
|
+
registration: TelegramBusInstanceRegistration;
|
|
1002
|
+
target?: TelegramTarget;
|
|
1003
|
+
slot?: string;
|
|
1004
|
+
}, publish: () => void) => Promise<void> | void;
|
|
1005
|
+
|
|
978
1006
|
export interface TelegramBusLeaderRuntimeDeps<TContext> {
|
|
979
1007
|
socketPath: TelegramBusSocketPathSource;
|
|
980
1008
|
commitEndpointPublication?: (commit: () => void) => boolean;
|
|
@@ -1015,6 +1043,7 @@ export interface TelegramBusLeaderRuntimeDeps<TContext> {
|
|
|
1015
1043
|
registration: TelegramBusInstanceRegistration,
|
|
1016
1044
|
options?: { existingWorkspaceBindingOnly?: boolean },
|
|
1017
1045
|
) => Promise<TelegramTarget | undefined> | TelegramTarget | undefined;
|
|
1046
|
+
commitFollowerRegistration?: TelegramBusFollowerRegistrationCommitter;
|
|
1018
1047
|
renameFollowerThread?: (
|
|
1019
1048
|
follower: TelegramBusFollowerView,
|
|
1020
1049
|
threadName: string,
|
|
@@ -1026,16 +1055,22 @@ export interface TelegramBusLeaderRuntimeDeps<TContext> {
|
|
|
1026
1055
|
settleFollowerSessionReplacement?: TelegramBusFollowerSessionReplacementOperation;
|
|
1027
1056
|
getFollowerDisplayTitle?: (follower: TelegramBusFollowerView) => string | undefined;
|
|
1028
1057
|
onFollowerRegistered?: () => void;
|
|
1058
|
+
/** Observation only: reacquire admission and inspect work, never infer completion.
|
|
1059
|
+
* Listener settlement ends its currentness fence; return asynchronous work to retain that fence. */
|
|
1060
|
+
onWorkspaceRestoreRecipientObserved?: (follower: TelegramBusFollowerView, isCurrent: () => boolean) => Promise<void> | void;
|
|
1029
1061
|
applyThreadDisplayMode?: (mode: TelegramThreadDisplayMode, isCurrent: () => boolean) => Promise<void>;
|
|
1030
1062
|
getThreadDisplayMode?: () => TelegramThreadDisplayMode;
|
|
1031
1063
|
getCurrentLeaderEpoch?: () => number | string | undefined;
|
|
1032
1064
|
getTelegramProfile?: () => string | undefined;
|
|
1065
|
+
getAllowedUserId?: () => number | undefined;
|
|
1033
1066
|
provisionLeaderTarget?: (ctx: TContext) => Promise<void> | void;
|
|
1034
1067
|
runWorkspaceAdmission?: TelegramBusWorkspaceAdmissionRunner;
|
|
1035
1068
|
runWithWorkspaceCapacity?: TelegramWorkspaceCapacityRunner;
|
|
1036
1069
|
getNowMs?: () => number;
|
|
1037
1070
|
timeoutMs?: number;
|
|
1038
1071
|
followerPruneIntervalMs?: number;
|
|
1072
|
+
/** Leader housekeeping after each current prune pass; throttling is the callee's concern. */
|
|
1073
|
+
afterFollowerPrune?: () => void;
|
|
1039
1074
|
followerStaleAfterMs?: number;
|
|
1040
1075
|
isFollowerProcessAlive?: (pid: number) => boolean;
|
|
1041
1076
|
shouldCleanupConfirmedDeadFollower?: () => Promise<boolean> | boolean;
|
|
@@ -1264,13 +1299,13 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1264
1299
|
workspaceBinding?.threadName ?? registration.threadName;
|
|
1265
1300
|
const requestedThreadName =
|
|
1266
1301
|
carriedThreadName &&
|
|
1267
|
-
|
|
1302
|
+
ThreadNaming.isTelegramTopicThreadNameValidForSlot(
|
|
1268
1303
|
carriedThreadName,
|
|
1269
1304
|
registration.slot,
|
|
1270
1305
|
)
|
|
1271
1306
|
? carriedThreadName
|
|
1272
1307
|
: recoveryHint?.threadName &&
|
|
1273
|
-
|
|
1308
|
+
ThreadNaming.isTelegramTopicThreadNameValidForSlot(
|
|
1274
1309
|
recoveryHint.threadName,
|
|
1275
1310
|
recoveryHint.slot,
|
|
1276
1311
|
)
|
|
@@ -1317,6 +1352,10 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1317
1352
|
const runRegistration = async (): Promise<
|
|
1318
1353
|
(TelegramTarget & { slot?: string; threadName?: string }) | undefined
|
|
1319
1354
|
> => {
|
|
1355
|
+
if (requestedTarget) deps.topicTargetStore.assertWorkspaceRestoreRegistration({
|
|
1356
|
+
target: requestedTarget, bindingKey: workspaceIdentity?.bindingKey,
|
|
1357
|
+
slot: workspaceIdentity?.slot ?? workspaceBinding?.slot ?? registration.slot,
|
|
1358
|
+
});
|
|
1320
1359
|
const recoveryTarget = reconnectRecord?.target ?? recoverableTarget;
|
|
1321
1360
|
if (recoveryTarget) {
|
|
1322
1361
|
Threads.assertTelegramPendingTopicRecoveryAllowed(deps.topicTargetStore, recoveryTarget);
|
|
@@ -1358,6 +1397,10 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1358
1397
|
};
|
|
1359
1398
|
};
|
|
1360
1399
|
alignResultWithWorkspaceSlot();
|
|
1400
|
+
const assertRestoreRegistration = (): void => deps.topicTargetStore.assertWorkspaceRestoreRegistration({
|
|
1401
|
+
target: result.target, slot: result.record.slot, bindingKey: workspaceIdentity?.bindingKey,
|
|
1402
|
+
});
|
|
1403
|
+
assertRestoreRegistration();
|
|
1361
1404
|
const crossSessionReuse =
|
|
1362
1405
|
!!reconnectRecord &&
|
|
1363
1406
|
reconnectRecord.instanceId !== registration.instanceId;
|
|
@@ -1403,6 +1446,7 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1403
1446
|
: undefined;
|
|
1404
1447
|
if (requiresVisibilityProbe && connectedAnnouncement) {
|
|
1405
1448
|
try {
|
|
1449
|
+
assertRestoreRegistration();
|
|
1406
1450
|
await deps.callApi(
|
|
1407
1451
|
exactSessionHandoff ? "sendChatAction" : "sendMessage",
|
|
1408
1452
|
exactSessionHandoff
|
|
@@ -1418,6 +1462,7 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1418
1462
|
parse_mode: connectedAnnouncement.parseMode,
|
|
1419
1463
|
},
|
|
1420
1464
|
);
|
|
1465
|
+
assertRestoreRegistration();
|
|
1421
1466
|
if (recoverableTarget || probeRequiredRecord) {
|
|
1422
1467
|
const activatedRecord = deps.topicTargetStore.upsert({
|
|
1423
1468
|
...result.record,
|
|
@@ -1471,6 +1516,8 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1471
1516
|
}
|
|
1472
1517
|
connectedAnnouncement = undefined;
|
|
1473
1518
|
} catch (error) {
|
|
1519
|
+
// Restore protection failures are not evidence that a Telegram topic is missing.
|
|
1520
|
+
assertRestoreRegistration();
|
|
1474
1521
|
if (Threads.isTelegramTopicTargetStaleError(error)) {
|
|
1475
1522
|
deps.topicTargetStore.markStaleByTarget(
|
|
1476
1523
|
result.target,
|
|
@@ -1523,7 +1570,10 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1523
1570
|
? { threadName: result.record.threadName }
|
|
1524
1571
|
: {}),
|
|
1525
1572
|
slot: workspaceIdentity.slot,
|
|
1526
|
-
|
|
1573
|
+
...(workspaceIdentity.sessionId
|
|
1574
|
+
? { journalSources: [{ sessionId: workspaceIdentity.sessionId, recipientBindingKey: followerProfileKey }],
|
|
1575
|
+
journalBindingKeys: [] }
|
|
1576
|
+
: { journalBindingKeys: [followerProfileKey] }),
|
|
1527
1577
|
journalBindingsComplete: true,
|
|
1528
1578
|
updatedAtMs: getNowMs(),
|
|
1529
1579
|
},
|
|
@@ -1537,6 +1587,7 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1537
1587
|
deps.topicTargetStore.markWorkspaceBindingActiveByTarget(result.target);
|
|
1538
1588
|
await deps.topicTargetStore.persist();
|
|
1539
1589
|
}
|
|
1590
|
+
assertRestoreRegistration();
|
|
1540
1591
|
deps.setSyncState(
|
|
1541
1592
|
Sync.markTelegramSyncSliceFresh(
|
|
1542
1593
|
deps.getSyncState(),
|
|
@@ -1773,7 +1824,7 @@ export function createTelegramBusLeaderTargetProvisioner<TContext>(
|
|
|
1773
1824
|
await deps.topicTargetStore.load();
|
|
1774
1825
|
const cwd = deps.getCwd?.(ctx);
|
|
1775
1826
|
const normalizedCwd = cwd
|
|
1776
|
-
?
|
|
1827
|
+
? WorkspaceIdentity.normalizeTelegramWorkspacePath(cwd)
|
|
1777
1828
|
: undefined;
|
|
1778
1829
|
const profileKey = Threads.getTelegramThreadOwnerKey({
|
|
1779
1830
|
kind: "leader",
|
|
@@ -1925,6 +1976,25 @@ export function createTelegramBusLeaderTargetProvisioner<TContext>(
|
|
|
1925
1976
|
};
|
|
1926
1977
|
}
|
|
1927
1978
|
|
|
1979
|
+
/** Strict copy of a follower-supplied attachment source; anything malformed falls back to the plain generated name. */
|
|
1980
|
+
function parseTelegramBusAttachmentSource(value: unknown): TelegramAttachmentSource | undefined {
|
|
1981
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
1982
|
+
const raw = value as Record<string, unknown>, chat = raw.chat as Record<string, unknown> | undefined;
|
|
1983
|
+
const text = (field: unknown) => typeof field === "string" && field.length > 0 && field.length <= 256;
|
|
1984
|
+
if (!text(raw.kind) || !Number.isSafeInteger(raw.messageId) ||
|
|
1985
|
+
(raw.index !== undefined && !Number.isSafeInteger(raw.index)) ||
|
|
1986
|
+
(raw.userFileName !== undefined && !text(raw.userFileName)) || (raw.scope !== undefined && !text(raw.scope)) ||
|
|
1987
|
+
(chat !== undefined && (!chat || typeof chat !== "object" || Array.isArray(chat) || !Number.isSafeInteger(chat.id) ||
|
|
1988
|
+
(chat.type !== undefined && !text(chat.type)) || (chat.username !== undefined && !text(chat.username))))) return undefined;
|
|
1989
|
+
return { kind: raw.kind as string, messageId: raw.messageId as number,
|
|
1990
|
+
...(raw.index !== undefined ? { index: raw.index as number } : {}),
|
|
1991
|
+
...(raw.userFileName !== undefined ? { userFileName: raw.userFileName as string } : {}),
|
|
1992
|
+
...(raw.scope !== undefined ? { scope: raw.scope as string } : {}),
|
|
1993
|
+
...(chat ? { chat: { id: chat.id as number,
|
|
1994
|
+
...(chat.type !== undefined ? { type: chat.type as string } : {}),
|
|
1995
|
+
...(chat.username !== undefined ? { username: chat.username as string } : {}) } } : {}) };
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1928
1998
|
export function createTelegramBusLeaderApiProxy(
|
|
1929
1999
|
deps: TelegramBusLeaderApiProxyDeps,
|
|
1930
2000
|
): (method: string, args: unknown[]) => Promise<unknown> {
|
|
@@ -1961,7 +2031,9 @@ export function createTelegramBusLeaderApiProxy(
|
|
|
1961
2031
|
}
|
|
1962
2032
|
}
|
|
1963
2033
|
if (method === "downloadFile") {
|
|
1964
|
-
|
|
2034
|
+
// Follower IPC is a trust boundary: only an exact attachment-source shape reaches the file-name builder.
|
|
2035
|
+
const source = parseTelegramBusAttachmentSource(args[2]);
|
|
2036
|
+
return deps.downloadFile(args[0] as string, args[1] as string, ...(source ? [source] : []));
|
|
1965
2037
|
}
|
|
1966
2038
|
throw new Error(`Unsupported Telegram bus API method: ${method}`);
|
|
1967
2039
|
};
|
|
@@ -2038,6 +2110,7 @@ export function createTelegramBusLeaderEnvelopeHandler(deps: {
|
|
|
2038
2110
|
>
|
|
2039
2111
|
| (TelegramTarget & { slot?: string; threadName?: string })
|
|
2040
2112
|
| undefined;
|
|
2113
|
+
commitFollowerRegistration?: TelegramBusFollowerRegistrationCommitter;
|
|
2041
2114
|
onFollowerDisconnected?: (
|
|
2042
2115
|
follower: TelegramBusFollowerView,
|
|
2043
2116
|
) => Promise<void> | void;
|
|
@@ -2052,9 +2125,14 @@ export function createTelegramBusLeaderEnvelopeHandler(deps: {
|
|
|
2052
2125
|
settleFollowerSessionReplacement?: TelegramBusFollowerSessionReplacementOperation;
|
|
2053
2126
|
getFollowerDisplayTitle?: (follower: TelegramBusFollowerView) => string | undefined;
|
|
2054
2127
|
onFollowerRegistered?: () => void;
|
|
2128
|
+
onWorkspaceRestoreRecipientObserved?: TelegramBusLeaderRuntimeDeps<unknown>["onWorkspaceRestoreRecipientObserved"];
|
|
2129
|
+
getWorkspaceRestoreObservationGeneration?: () => number | undefined;
|
|
2130
|
+
recordRuntimeEvent?: TelegramBusLeaderRuntimeDeps<unknown>["recordRuntimeEvent"];
|
|
2055
2131
|
applyThreadDisplayMode?: (mode: TelegramThreadDisplayMode, isCurrent: () => boolean) => Promise<void>;
|
|
2056
2132
|
getThreadDisplayMode?: () => TelegramThreadDisplayMode;
|
|
2057
2133
|
getCurrentLeaderEpoch?: () => number | string | undefined;
|
|
2134
|
+
getTelegramProfile?: () => string | undefined;
|
|
2135
|
+
getAllowedUserId?: () => number | undefined;
|
|
2058
2136
|
runFollowerMutation?: TelegramBusFollowerMutationRunner;
|
|
2059
2137
|
runWorkspaceAdmission?: TelegramBusWorkspaceAdmissionRunner;
|
|
2060
2138
|
runWithWorkspaceCapacity?: TelegramWorkspaceCapacityRunner;
|
|
@@ -2064,6 +2142,47 @@ export function createTelegramBusLeaderEnvelopeHandler(deps: {
|
|
|
2064
2142
|
const getNowMs = deps.getNowMs ?? Date.now;
|
|
2065
2143
|
const runFollowerMutation =
|
|
2066
2144
|
deps.runFollowerMutation ?? createTelegramBusFollowerMutationRunner();
|
|
2145
|
+
const recipientObservations = new Map<string, {
|
|
2146
|
+
epoch: number | string; operator: number; profile: string | undefined; runtimeGeneration: number | undefined;
|
|
2147
|
+
follower: Omit<TelegramBusFollowerView, "lastHeartbeatMs" | "connectedAtMs" | "threadName">;
|
|
2148
|
+
}>();
|
|
2149
|
+
const observeRestoreRecipient = (follower: TelegramBusFollowerView): void => {
|
|
2150
|
+
if (!deps.authSecret || !deps.onWorkspaceRestoreRecipientObserved || !deps.getTelegramProfile ||
|
|
2151
|
+
!hasTelegramBusCapability(deps.protocolIdentity, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE) ||
|
|
2152
|
+
!hasTelegramBusCapability(follower.protocol, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE)) return;
|
|
2153
|
+
const instanceId = follower.instanceId;
|
|
2154
|
+
const report = (error: unknown): void => {
|
|
2155
|
+
try { deps.recordRuntimeEvent?.("bus", error, { phase: "workspace-restore-recipient-observation", instanceId }); }
|
|
2156
|
+
catch { /* Optional observation diagnostics must not break transport liveness. */ }
|
|
2157
|
+
};
|
|
2158
|
+
try {
|
|
2159
|
+
const epoch = deps.getCurrentLeaderEpoch?.(), operator = deps.getAllowedUserId?.();
|
|
2160
|
+
const runtimeGeneration = deps.getWorkspaceRestoreObservationGeneration?.();
|
|
2161
|
+
if (epoch === undefined || operator === undefined ||
|
|
2162
|
+
(deps.getWorkspaceRestoreObservationGeneration && runtimeGeneration === undefined)) return;
|
|
2163
|
+
const snapshot = structuredClone(follower);
|
|
2164
|
+
const observed = { epoch, operator, profile: deps.getTelegramProfile(), runtimeGeneration,
|
|
2165
|
+
follower: { ...snapshot, lastHeartbeatMs: undefined, connectedAtMs: undefined, threadName: undefined } };
|
|
2166
|
+
if (isDeepStrictEqual(recipientObservations.get(instanceId), observed)) return;
|
|
2167
|
+
recipientObservations.set(instanceId, observed);
|
|
2168
|
+
const isCurrent = (): boolean => {
|
|
2169
|
+
const live = deps.followerRegistry.get(instanceId);
|
|
2170
|
+
return recipientObservations.get(instanceId) === observed &&
|
|
2171
|
+
deps.getWorkspaceRestoreObservationGeneration?.() === observed.runtimeGeneration &&
|
|
2172
|
+
hasTelegramBusCapability(deps.protocolIdentity, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE) &&
|
|
2173
|
+
deps.getCurrentLeaderEpoch?.() === observed.epoch && deps.getAllowedUserId?.() === observed.operator &&
|
|
2174
|
+
deps.getTelegramProfile?.() === observed.profile && !!live &&
|
|
2175
|
+
hasTelegramBusCapability(live.protocol, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE) &&
|
|
2176
|
+
isDeepStrictEqual({ ...live, lastHeartbeatMs: undefined, connectedAtMs: undefined, threadName: undefined }, observed.follower);
|
|
2177
|
+
};
|
|
2178
|
+
// A heartbeat is a hint, not completion proof. Never await the listener before acknowledging it.
|
|
2179
|
+
void Promise.resolve().then(() => {
|
|
2180
|
+
if (isCurrent()) return deps.onWorkspaceRestoreRecipientObserved?.(structuredClone(snapshot), isCurrent);
|
|
2181
|
+
}).catch(report).finally(() => {
|
|
2182
|
+
if (recipientObservations.get(instanceId) === observed) recipientObservations.delete(instanceId);
|
|
2183
|
+
});
|
|
2184
|
+
} catch (error) { report(error); }
|
|
2185
|
+
};
|
|
2067
2186
|
const handleAgentRequest = async (
|
|
2068
2187
|
envelope: Extract<
|
|
2069
2188
|
TelegramBusEnvelope,
|
|
@@ -2393,11 +2512,19 @@ export function createTelegramBusLeaderEnvelopeHandler(deps: {
|
|
|
2393
2512
|
runFollowerMutation(envelope.registration, async () => {
|
|
2394
2513
|
try {
|
|
2395
2514
|
const leaderEpoch = deps.getCurrentLeaderEpoch?.();
|
|
2515
|
+
const telegramProfile = deps.getTelegramProfile?.();
|
|
2516
|
+
const operatorUserId = deps.getAllowedUserId?.();
|
|
2517
|
+
const registrationCurrent = (): boolean =>
|
|
2518
|
+
(!deps.getCurrentLeaderEpoch || deps.getCurrentLeaderEpoch() === leaderEpoch) &&
|
|
2519
|
+
(!deps.getTelegramProfile || deps.getTelegramProfile() === telegramProfile) &&
|
|
2520
|
+
(!deps.getAllowedUserId || deps.getAllowedUserId() === operatorUserId);
|
|
2396
2521
|
if (deps.getCurrentLeaderEpoch && leaderEpoch === undefined) {
|
|
2397
2522
|
throw new Error(
|
|
2398
2523
|
"Telegram follower registration requires leader ownership.",
|
|
2399
2524
|
);
|
|
2400
2525
|
}
|
|
2526
|
+
if (hasTelegramBusCapability(deps.protocolIdentity, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE) && !deps.commitFollowerRegistration)
|
|
2527
|
+
throw new Error("Workspace Restore registration publication protection is unavailable.");
|
|
2401
2528
|
const target = await deps.provisionFollowerTarget?.(
|
|
2402
2529
|
envelope.registration,
|
|
2403
2530
|
{ existingWorkspaceBindingOnly: restoringWorkspace },
|
|
@@ -2412,14 +2539,7 @@ export function createTelegramBusLeaderEnvelopeHandler(deps: {
|
|
|
2412
2539
|
message: "No remembered Telegram Workspace Thread is available.",
|
|
2413
2540
|
};
|
|
2414
2541
|
}
|
|
2415
|
-
if (
|
|
2416
|
-
deps.getCurrentLeaderEpoch &&
|
|
2417
|
-
deps.getCurrentLeaderEpoch() !== leaderEpoch
|
|
2418
|
-
) {
|
|
2419
|
-
throw new Error(
|
|
2420
|
-
"Telegram follower registration lost leader ownership.",
|
|
2421
|
-
);
|
|
2422
|
-
}
|
|
2542
|
+
if (!registrationCurrent()) throw new Error("Telegram follower registration lost leader ownership.");
|
|
2423
2543
|
if (!displayCompatible()) {
|
|
2424
2544
|
throw new Error("Thread display mode changed; update or restart this follower before connecting.");
|
|
2425
2545
|
}
|
|
@@ -2430,20 +2550,37 @@ export function createTelegramBusLeaderEnvelopeHandler(deps: {
|
|
|
2430
2550
|
(!registeredSlot || !/^[A-Z]$/u.test(registeredSlot))) {
|
|
2431
2551
|
throw new Error("Telegram Thread slot authority is unavailable.");
|
|
2432
2552
|
}
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
:
|
|
2444
|
-
|
|
2553
|
+
let publicationOpen = true;
|
|
2554
|
+
let published: TelegramBusFollowerView | undefined;
|
|
2555
|
+
const publish = (): void => {
|
|
2556
|
+
if (!publicationOpen) throw new Error("Telegram follower registration publication is no longer available.");
|
|
2557
|
+
publicationOpen = false;
|
|
2558
|
+
if (!registrationCurrent()) throw new Error("Telegram follower registration lost leader ownership.");
|
|
2559
|
+
if (!displayCompatible()) throw new Error("Thread display mode changed; update or restart this follower before connecting.");
|
|
2560
|
+
published = deps.followerRegistry.register({
|
|
2561
|
+
...envelope.registration,
|
|
2562
|
+
connectedAtMs: getNowMs(),
|
|
2563
|
+
target: registeredTarget,
|
|
2564
|
+
...(registeredSlot ? { slot: registeredSlot } : {}),
|
|
2565
|
+
...((target?.threadName ?? envelope.registration.threadName)
|
|
2566
|
+
? { threadName: target?.threadName ?? envelope.registration.threadName } : {}),
|
|
2567
|
+
});
|
|
2568
|
+
};
|
|
2569
|
+
try {
|
|
2570
|
+
if (deps.commitFollowerRegistration) await deps.commitFollowerRegistration({
|
|
2571
|
+
registration: envelope.registration, target: registeredTarget, slot: registeredSlot,
|
|
2572
|
+
}, publish);
|
|
2573
|
+
else publish();
|
|
2574
|
+
} finally { publicationOpen = false; }
|
|
2575
|
+
if (!published) throw new Error("Telegram follower registration publication was not confirmed.");
|
|
2576
|
+
if (!registrationCurrent()) throw new Error("Telegram follower registration lost leader ownership.");
|
|
2577
|
+
if (!displayCompatible()) throw new Error("Thread display mode changed; update or restart this follower before connecting.");
|
|
2445
2578
|
const follower = deps.followerRegistry.get(envelope.registration.instanceId);
|
|
2446
|
-
|
|
2579
|
+
if (!follower) throw new Error("Telegram follower registration changed before acknowledgement.");
|
|
2580
|
+
const { lastHeartbeatMs: _publishedHeartbeat, connectedAtMs: _publishedAt, threadName: _publishedName, ...publishedIdentity } = published;
|
|
2581
|
+
const { lastHeartbeatMs: _heartbeat, connectedAtMs: _connectedAt, threadName: _threadName, ...identity } = follower;
|
|
2582
|
+
if (!isDeepStrictEqual(publishedIdentity, identity)) throw new Error("Telegram follower registration changed before acknowledgement.");
|
|
2583
|
+
const displayTitle = deps.getFollowerDisplayTitle?.(follower);
|
|
2447
2584
|
deps.onFollowerRegistered?.();
|
|
2448
2585
|
return {
|
|
2449
2586
|
kind: "bus.ack" as const,
|
|
@@ -2814,6 +2951,7 @@ export function createTelegramBusLeaderEnvelopeHandler(deps: {
|
|
|
2814
2951
|
envelope.instanceId,
|
|
2815
2952
|
getNowMs(),
|
|
2816
2953
|
);
|
|
2954
|
+
if (follower) observeRestoreRecipient(follower);
|
|
2817
2955
|
const displayTitle = follower ? deps.getFollowerDisplayTitle?.(follower) : undefined;
|
|
2818
2956
|
return follower
|
|
2819
2957
|
? {
|
|
@@ -3276,6 +3414,9 @@ export function createTelegramBusLeaderRuntime<TContext>(
|
|
|
3276
3414
|
const expectedGeneration = pruneGeneration;
|
|
3277
3415
|
let tracked: Promise<void>;
|
|
3278
3416
|
tracked = pruneFollowers(expectedGeneration)
|
|
3417
|
+
.then(() => {
|
|
3418
|
+
if (pruneGeneration === expectedGeneration) deps.afterFollowerPrune?.();
|
|
3419
|
+
})
|
|
3279
3420
|
.catch((error) => {
|
|
3280
3421
|
if (pruneGeneration === expectedGeneration) {
|
|
3281
3422
|
recordPruneEvent(error, { phase: "follower-prune-owner" });
|
|
@@ -3310,6 +3451,7 @@ export function createTelegramBusLeaderRuntime<TContext>(
|
|
|
3310
3451
|
forgetPreservation(pendingPreservations.get(registration.instanceId));
|
|
3311
3452
|
return deps.provisionFollowerTarget!(registration, options);
|
|
3312
3453
|
} : undefined,
|
|
3454
|
+
commitFollowerRegistration: deps.commitFollowerRegistration,
|
|
3313
3455
|
onFollowerDisconnected: deps.onFollowerDisconnected,
|
|
3314
3456
|
renameFollowerThread: deps.renameFollowerThread,
|
|
3315
3457
|
resetFollowerThreadName: deps.resetFollowerThreadName,
|
|
@@ -3317,9 +3459,14 @@ export function createTelegramBusLeaderRuntime<TContext>(
|
|
|
3317
3459
|
settleFollowerSessionReplacement: deps.settleFollowerSessionReplacement,
|
|
3318
3460
|
getFollowerDisplayTitle: deps.getFollowerDisplayTitle,
|
|
3319
3461
|
onFollowerRegistered: deps.onFollowerRegistered,
|
|
3462
|
+
onWorkspaceRestoreRecipientObserved: deps.onWorkspaceRestoreRecipientObserved,
|
|
3463
|
+
getWorkspaceRestoreObservationGeneration: () => pruneInterval ? pruneGeneration : undefined,
|
|
3464
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
3320
3465
|
applyThreadDisplayMode: deps.applyThreadDisplayMode,
|
|
3321
3466
|
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
3322
3467
|
getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
|
|
3468
|
+
getTelegramProfile: deps.getTelegramProfile,
|
|
3469
|
+
getAllowedUserId: deps.getAllowedUserId,
|
|
3323
3470
|
runFollowerMutation,
|
|
3324
3471
|
runWorkspaceAdmission: deps.runWorkspaceAdmission,
|
|
3325
3472
|
runWithWorkspaceCapacity: deps.runWithWorkspaceCapacity,
|
|
@@ -6,7 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { createConnection } from "node:net";
|
|
9
|
-
import { join, resolve } from "node:path";
|
|
9
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
10
|
+
import { resolveTelegramTempDir, resolveTelegramRuntimeDir } from "./paths.ts";
|
|
11
|
+
|
|
12
|
+
export type TelegramBusEndpointLayout = "consolidated";
|
|
13
|
+
export const TELEGRAM_BUS_MAX_DIRECT_UNIX_ENDPOINT_BYTES = 80;
|
|
14
|
+
|
|
15
|
+
function requireConsolidatedUnixEndpoint(path: string): string {
|
|
16
|
+
if (!isAbsolute(path) || resolve(path) !== path) throw new Error("Consolidated Telegram IPC requires an exact absolute runtime path.");
|
|
17
|
+
// Over-budget logical paths keep their identity; socket resolution applies the bounded private external fallback.
|
|
18
|
+
return path;
|
|
19
|
+
}
|
|
10
20
|
|
|
11
21
|
export type TelegramBusTransportKind = "pipe" | "socket";
|
|
12
22
|
|
|
@@ -41,7 +51,7 @@ export const TELEGRAM_BUS_REGISTRATION_RETRY: TelegramBusTransportRetryPolicy =
|
|
|
41
51
|
delayMs: 150,
|
|
42
52
|
};
|
|
43
53
|
|
|
44
|
-
|
|
54
|
+
const TELEGRAM_BUS_OPERATION_RETRY: TelegramBusTransportRetryPolicy = {
|
|
45
55
|
attempts: 3,
|
|
46
56
|
delayMs: 100,
|
|
47
57
|
};
|
|
@@ -103,7 +113,15 @@ export function getTelegramBusLeaderEndpoint(input: {
|
|
|
103
113
|
agentDir: string;
|
|
104
114
|
platform: NodeJS.Platform | string;
|
|
105
115
|
profileName?: string;
|
|
116
|
+
layout?: TelegramBusEndpointLayout;
|
|
106
117
|
}): string {
|
|
118
|
+
if (input.layout !== undefined && input.layout !== "consolidated") throw new Error("Telegram IPC layout is invalid.");
|
|
119
|
+
if (input.layout === "consolidated") {
|
|
120
|
+
const scope = createHash("sha256").update(JSON.stringify([input.profileName ?? "default"])).digest("hex").slice(0, 16);
|
|
121
|
+
return input.platform === "win32"
|
|
122
|
+
? getTelegramBusPipePath({ agentDir: input.agentDir, scope: `bus-${scope}` })
|
|
123
|
+
: requireConsolidatedUnixEndpoint(join(resolveTelegramRuntimeDir(input.agentDir), `bus.${scope}.sock`));
|
|
124
|
+
}
|
|
107
125
|
const profileScope = input.profileName
|
|
108
126
|
? normalizeTelegramBusEndpointScope(input.profileName)
|
|
109
127
|
: undefined;
|
|
@@ -113,9 +131,7 @@ export function getTelegramBusLeaderEndpoint(input: {
|
|
|
113
131
|
scope: profileScope ? `bus-${profileScope}` : "bus",
|
|
114
132
|
})
|
|
115
133
|
: join(
|
|
116
|
-
input.agentDir,
|
|
117
|
-
"tmp",
|
|
118
|
-
"telegram",
|
|
134
|
+
resolveTelegramTempDir(input.agentDir),
|
|
119
135
|
profileScope ? `bus.${profileScope}.sock` : "bus.sock",
|
|
120
136
|
);
|
|
121
137
|
}
|
|
@@ -125,7 +141,15 @@ export function getTelegramBusFollowerEndpoint(input: {
|
|
|
125
141
|
platform: NodeJS.Platform | string;
|
|
126
142
|
instanceId: string;
|
|
127
143
|
profileName?: string;
|
|
144
|
+
layout?: TelegramBusEndpointLayout;
|
|
128
145
|
}): string {
|
|
146
|
+
if (input.layout !== undefined && input.layout !== "consolidated") throw new Error("Telegram IPC layout is invalid.");
|
|
147
|
+
if (input.layout === "consolidated") {
|
|
148
|
+
const scope = createHash("sha256").update(JSON.stringify([input.profileName ?? "default", input.instanceId])).digest("hex").slice(0, 16);
|
|
149
|
+
return input.platform === "win32"
|
|
150
|
+
? getTelegramBusPipePath({ agentDir: input.agentDir, scope: `follower-${scope}` })
|
|
151
|
+
: requireConsolidatedUnixEndpoint(join(resolveTelegramRuntimeDir(input.agentDir), `f.${scope}.sock`));
|
|
152
|
+
}
|
|
129
153
|
const instanceScope = normalizeTelegramBusEndpointScope(input.instanceId);
|
|
130
154
|
const profileScope = input.profileName
|
|
131
155
|
? normalizeTelegramBusEndpointScope(input.profileName)
|
|
@@ -138,9 +162,7 @@ export function getTelegramBusFollowerEndpoint(input: {
|
|
|
138
162
|
: `follower-${instanceScope}`,
|
|
139
163
|
})
|
|
140
164
|
: join(
|
|
141
|
-
input.agentDir,
|
|
142
|
-
"tmp",
|
|
143
|
-
"telegram",
|
|
165
|
+
resolveTelegramTempDir(input.agentDir),
|
|
144
166
|
"followers",
|
|
145
167
|
...(profileScope ? [profileScope] : []),
|
|
146
168
|
`${instanceScope}.sock`,
|