@llblab/pi-kit 0.27.5 → 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 +3 -1
- package/CHANGELOG.md +9 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +6 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +4 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +26 -13
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -1
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +2 -1
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +25 -12
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- 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 +3 -3
|
@@ -6,21 +6,23 @@
|
|
|
6
6
|
* It must not spawn Pi processes or create hidden Telegram-originated instances.
|
|
7
7
|
*/
|
|
8
8
|
import { basename } from "node:path";
|
|
9
|
-
import
|
|
9
|
+
import { isDeepStrictEqual } from "node:util";
|
|
10
10
|
import * as Threads from "./threads.js";
|
|
11
|
+
import * as WorkspaceIdentity from "./workspace-identity.js";
|
|
12
|
+
import { isWireRecord as isRecord } from "./wire.js";
|
|
11
13
|
import { parseTelegramUpdateJournalQueueOwner } from "./journal.js";
|
|
12
14
|
import { TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS, } from "./locks.js";
|
|
13
15
|
import { isTelegramApiMethodRetrySafe, TelegramApiCommitUnknownError, TelegramApiStaleTargetError, } from "./telegram-api.js";
|
|
14
|
-
import { createTelegramBusFollowerDeliveryIdentity,
|
|
16
|
+
import { createTelegramBusFollowerDeliveryIdentity, createTelegramBusForeignOwnedUpdateForwarder, createTelegramBusLocalServer, createTelegramBusRequestIdFactory, createUnauthorizedBusAck, getTelegramBusProtocolCompatibility, getTelegramBusSocketPath, hasTelegramBusCapability, isTelegramBusEnvelopeAuthorized, resolveTelegramBusSocketPath, sendTelegramBusLocalEnvelope, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE, } from "./bus.js";
|
|
15
17
|
import { getTelegramBusTransportRetryPolicy, TELEGRAM_BUS_REGISTRATION_RETRY, } from "./bus-transport.js";
|
|
16
18
|
import { createTelegramWorkspaceAdmissionOperationId, runWithTelegramWorkspaceAdmissionsAsync, } from "./workspace-admission.js";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const TELEGRAM_BUS_FOLLOWER_PROMOTION_GRACE_MS = 2_500;
|
|
20
|
+
const TELEGRAM_FOLLOWER_SESSION_HANDOFF_TTL_MS = 30_000;
|
|
21
|
+
const TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS = 30_000;
|
|
22
|
+
const TELEGRAM_BUS_FOLLOWER_REGISTRATION_WAIT_MS = 30_000;
|
|
21
23
|
export const TELEGRAM_BUS_FOLLOWER_HEARTBEAT_TIMEOUT_MS = TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_ATTEMPTS = TELEGRAM_BUS_REGISTRATION_RETRY.attempts;
|
|
25
|
+
const TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_DELAY_MS = TELEGRAM_BUS_REGISTRATION_RETRY.delayMs;
|
|
24
26
|
const TELEGRAM_FOLLOWER_SESSION_HANDOFF_KEY = "__piTelegramFollowerSessionHandoff";
|
|
25
27
|
export function getTelegramFollowerSessionHandoff() {
|
|
26
28
|
const value = globalThis[TELEGRAM_FOLLOWER_SESSION_HANDOFF_KEY];
|
|
@@ -44,7 +46,7 @@ export function setTelegramFollowerSessionHandoff(handoff) {
|
|
|
44
46
|
else
|
|
45
47
|
store[TELEGRAM_FOLLOWER_SESSION_HANDOFF_KEY] = handoff;
|
|
46
48
|
}
|
|
47
|
-
|
|
49
|
+
function isTelegramFollowerSessionHandoffFresh(handoff, options = {}) {
|
|
48
50
|
if (!handoff)
|
|
49
51
|
return false;
|
|
50
52
|
const pid = options.pid ?? process.pid;
|
|
@@ -147,16 +149,21 @@ export function createTelegramBusFollowerRuntimeAssembly(ports) {
|
|
|
147
149
|
(ctx === undefined || readyContext.ctx === ctx) &&
|
|
148
150
|
ports.registrationState.getGeneration() === readyContext.generation &&
|
|
149
151
|
ports.registration.getSessionGeneration?.() === readyContext.sessionGeneration &&
|
|
152
|
+
ports.registration.getSessionId?.(readyContext.ctx) === readyContext.sessionId &&
|
|
150
153
|
ports.registration.isContextActive?.(readyContext.ctx) !== false);
|
|
151
154
|
const prepareContext = async (ctx) => {
|
|
152
155
|
const generation = ports.registrationState.getGeneration();
|
|
153
156
|
const sessionGeneration = ports.registration.getSessionGeneration?.();
|
|
157
|
+
const sessionId = ports.registration.getSessionId?.(ctx);
|
|
154
158
|
readyContext = undefined;
|
|
159
|
+
if (!generation || (sessionId !== undefined && ports.registrationState.getSessionId(sessionId) !== sessionId))
|
|
160
|
+
throw new Error("Telegram follower session change requires acknowledged leader registration.");
|
|
155
161
|
await ports.registration.onRegistered?.(ctx);
|
|
156
162
|
if (generation && ports.registrationState.getGeneration() === generation &&
|
|
157
163
|
ports.registration.getSessionGeneration?.() === sessionGeneration &&
|
|
164
|
+
ports.registration.getSessionId?.(ctx) === sessionId &&
|
|
158
165
|
ports.registration.isContextActive?.(ctx) !== false)
|
|
159
|
-
readyContext = { generation, ctx, sessionGeneration };
|
|
166
|
+
readyContext = { generation, ctx, sessionGeneration, sessionId };
|
|
160
167
|
};
|
|
161
168
|
const sharedRuntimeDeps = {
|
|
162
169
|
instanceId: ports.instanceId,
|
|
@@ -170,10 +177,6 @@ export function createTelegramBusFollowerRuntimeAssembly(ports) {
|
|
|
170
177
|
getRegistrationGeneration() {
|
|
171
178
|
return isReadyContext() ? readyContext?.generation : undefined;
|
|
172
179
|
},
|
|
173
|
-
handleReplaceTarget: createTelegramBusFollowerTargetReplacementHandler({
|
|
174
|
-
...ports.targetReplacement,
|
|
175
|
-
...sharedRuntimeDeps,
|
|
176
|
-
}),
|
|
177
180
|
});
|
|
178
181
|
let registration;
|
|
179
182
|
const recovery = createTelegramBusFollowerHeartbeatRecoveryHandler({
|
|
@@ -205,83 +208,123 @@ export function createTelegramBusFollowerRuntimeAssembly(ports) {
|
|
|
205
208
|
await prepareContext(ctx);
|
|
206
209
|
},
|
|
207
210
|
};
|
|
208
|
-
return { receiver, registration };
|
|
211
|
+
return { receiver, registration, getReadySessionId: () => isReadyContext() ? readyContext?.sessionId : undefined };
|
|
209
212
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
213
|
+
/** Binds Restore to real Pi lifetime and the current authenticated transport owner, not request fields. */
|
|
214
|
+
export function createTelegramBusFollowerRestoreContextGetter(deps) {
|
|
215
|
+
return (ctx) => {
|
|
216
|
+
if (!deps.isContextCurrent(ctx))
|
|
217
|
+
return undefined;
|
|
218
|
+
const leader = deps.getLeaderState();
|
|
219
|
+
const secret = deps.getAuthenticatedSecret();
|
|
220
|
+
const leaderProtocol = deps.getLeaderProtocol();
|
|
221
|
+
if (!leaderProtocol || !hasTelegramBusCapability(leaderProtocol, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE))
|
|
222
|
+
return undefined;
|
|
223
|
+
if (leader.kind !== "active-elsewhere" || !secret || leader.lock.busSecret !== secret ||
|
|
224
|
+
!leader.lock.instanceId || leader.lock.leaderEpoch === undefined)
|
|
225
|
+
return undefined;
|
|
226
|
+
const profileBindingKey = deps.getProfileBindingKey();
|
|
227
|
+
const operatorUserId = deps.getOperatorUserId();
|
|
228
|
+
const sessionId = deps.getSessionId(ctx);
|
|
229
|
+
const cwd = deps.getCwd(ctx);
|
|
230
|
+
const generation = deps.getGeneration();
|
|
231
|
+
if (!profileBindingKey || !sessionId || !cwd || typeof operatorUserId !== "number" ||
|
|
232
|
+
!Number.isSafeInteger(operatorUserId) || operatorUserId <= 0 ||
|
|
233
|
+
!Number.isSafeInteger(generation) || generation < 0 || !deps.isContextCurrent(ctx))
|
|
234
|
+
return undefined;
|
|
235
|
+
return { executor: { instanceId: leader.lock.instanceId, leaderEpoch: String(leader.lock.leaderEpoch) },
|
|
236
|
+
profileBindingKey, operatorUserId, sessionId, cwd, generation, leaderProtocol: structuredClone(leaderProtocol) };
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/** Prepared receiver: its caller authenticates the envelope; no canonical writes, dispatch or cleanup. */
|
|
240
|
+
export function createTelegramBusFollowerWorkspaceRestoreHandler(deps) {
|
|
241
|
+
return async (input, ctx) => {
|
|
242
|
+
const captured = deps.getContextAuthority(ctx);
|
|
243
|
+
if (!captured)
|
|
244
|
+
throw new Error("Stale Telegram follower Restore context.");
|
|
245
|
+
const authority = structuredClone(captured);
|
|
246
|
+
const request = structuredClone(input);
|
|
247
|
+
if (!authority.profileBindingKey || !authority.sessionId || !authority.cwd ||
|
|
248
|
+
!authority.executor?.instanceId || !authority.executor.leaderEpoch ||
|
|
249
|
+
!(typeof authority.generation === "string" ? authority.generation.length > 0 :
|
|
250
|
+
Number.isSafeInteger(authority.generation) && authority.generation >= 0) ||
|
|
251
|
+
!Number.isSafeInteger(authority.operatorUserId) || authority.operatorUserId <= 0 ||
|
|
252
|
+
!request.operationId || !request.registrationGeneration || !["apply", "inspect"].includes(request.mode))
|
|
253
|
+
throw new Error("Invalid Telegram follower Restore authority.");
|
|
254
|
+
const assertCurrent = () => {
|
|
255
|
+
if (!isDeepStrictEqual(deps.getContextAuthority(ctx), authority) ||
|
|
256
|
+
deps.registrationState.getGeneration() !== request.registrationGeneration)
|
|
257
|
+
throw new Error("Stale Telegram follower Restore authority.");
|
|
226
258
|
};
|
|
227
259
|
assertCurrent();
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
260
|
+
if (!deps.getWorkspaceAdmission)
|
|
261
|
+
throw new Error("Telegram Workspace admission authority is unavailable.");
|
|
262
|
+
const result = await runTelegramBusFollowerWorkspaceMutation(deps, "workspace.restore-follower-target", async () => {
|
|
263
|
+
assertCurrent();
|
|
264
|
+
const intent = deps.readRestoreIntent(request.operationId, authority.profileBindingKey);
|
|
265
|
+
assertCurrent();
|
|
266
|
+
if (!intent || intent.request.operationId !== request.operationId ||
|
|
267
|
+
(intent.phase !== "recipient-issued" && intent.phase !== "ready") ||
|
|
268
|
+
!isDeepStrictEqual(intent.executor, authority.executor) ||
|
|
269
|
+
intent.operatorUserId !== authority.operatorUserId || !intent.recipient ||
|
|
270
|
+
(request.mode === "apply" && (intent.recipient.kind !== "follower" || intent.recipient.instanceId !== deps.instanceId ||
|
|
271
|
+
intent.recipient.generation !== request.registrationGeneration)) ||
|
|
272
|
+
intent.recipient.sessionId !== authority.sessionId || intent.request.binding.sessionId !== authority.sessionId ||
|
|
273
|
+
intent.request.binding.cwd !== WorkspaceIdentity.normalizeTelegramWorkspacePath(authority.cwd))
|
|
274
|
+
throw new Error("Telegram follower Restore intent does not match this recipient.");
|
|
275
|
+
const expected = structuredClone(intent);
|
|
276
|
+
await deps.topicTargetStore.load();
|
|
277
|
+
assertCurrent();
|
|
278
|
+
const observe = (canonical) => {
|
|
279
|
+
if (!isDeepStrictEqual(deps.readRestoreIntent(request.operationId, authority.profileBindingKey), expected))
|
|
280
|
+
throw new Error("Telegram follower Restore intent changed.");
|
|
281
|
+
assertCurrent();
|
|
282
|
+
const { binding, target } = expected.request;
|
|
283
|
+
const bindings = (canonical.workspaceBindings ?? []).filter(value => value.bindingKey === binding.bindingKey);
|
|
284
|
+
const snapshot = canonical.threads;
|
|
285
|
+
const records = snapshot.filter(value => value.instanceId === deps.instanceId);
|
|
286
|
+
const owners = snapshot.filter(value => value.status === "active" &&
|
|
287
|
+
(value.slot === binding.slot || isDeepStrictEqual(value.target, target)));
|
|
288
|
+
if (bindings.length !== 1 || records.length !== 1 || owners.length !== 1 || bindings[0]?.cwd !== binding.cwd ||
|
|
289
|
+
bindings[0]?.sessionId !== binding.sessionId || bindings[0]?.slot !== binding.slot || bindings[0]?.inactiveSinceMs !== undefined ||
|
|
290
|
+
!isDeepStrictEqual(bindings[0]?.target, target) || records[0]?.status !== "active" ||
|
|
291
|
+
records[0]?.owner?.kind !== "manual-follower" ||
|
|
292
|
+
records[0]?.slot !== binding.slot || !isDeepStrictEqual(records[0]?.target, target) ||
|
|
293
|
+
deps.registrationState.getSlot() !== binding.slot)
|
|
294
|
+
throw new Error("Telegram follower Restore canonical binding is not committed.");
|
|
295
|
+
const localTarget = deps.registrationState.getTarget();
|
|
296
|
+
const alreadyReady = isDeepStrictEqual(localTarget, target);
|
|
297
|
+
if (!alreadyReady && !isDeepStrictEqual(localTarget, binding.target))
|
|
298
|
+
throw new Error("Telegram follower Restore local target changed.");
|
|
299
|
+
assertCurrent();
|
|
300
|
+
if (!alreadyReady && request.mode === "apply") {
|
|
301
|
+
if (expected.phase !== "recipient-issued")
|
|
302
|
+
throw new Error("Telegram follower Restore readiness regressed.");
|
|
303
|
+
deps.registrationState.setRegistered(true, target, { slot: binding.slot,
|
|
304
|
+
threadName: records[0]?.threadName, generation: request.registrationGeneration,
|
|
305
|
+
leaderProtocol: deps.registrationState.getLeaderProtocol() });
|
|
306
|
+
}
|
|
307
|
+
assertCurrent();
|
|
308
|
+
const observedTarget = deps.registrationState.getTarget();
|
|
309
|
+
if (!observedTarget || (!isDeepStrictEqual(observedTarget, target) && !isDeepStrictEqual(observedTarget, binding.target)))
|
|
310
|
+
throw new Error("Telegram follower Restore local target changed.");
|
|
311
|
+
const ready = isDeepStrictEqual(observedTarget, target);
|
|
312
|
+
assertCurrent();
|
|
313
|
+
return { operationId: request.operationId, recipient: { kind: "follower", instanceId: deps.instanceId,
|
|
314
|
+
sessionId: authority.sessionId, generation: request.registrationGeneration },
|
|
315
|
+
target: { chatId: observedTarget.chatId, threadId: observedTarget.threadId }, slot: binding.slot, ready };
|
|
316
|
+
};
|
|
317
|
+
let result;
|
|
318
|
+
deps.topicTargetStore.withWorkspaceRestoreSnapshot(expected, snapshot => { result = observe(snapshot); });
|
|
319
|
+
if (!result)
|
|
320
|
+
throw new Error("Telegram follower Restore observation was not completed.");
|
|
321
|
+
return result;
|
|
283
322
|
});
|
|
284
|
-
|
|
323
|
+
assertCurrent();
|
|
324
|
+
if (!isDeepStrictEqual(deps.registrationState.getTarget(), result.target) || deps.registrationState.getSlot() !== result.slot)
|
|
325
|
+
throw new Error("Telegram follower Restore local registration changed after admission release.");
|
|
326
|
+
return result;
|
|
327
|
+
};
|
|
285
328
|
}
|
|
286
329
|
export function createTelegramBusFollowerClientRuntime(deps) {
|
|
287
330
|
const createRequestId = createTelegramBusRequestIdFactory(deps.instanceId);
|
|
@@ -319,10 +362,6 @@ export function createTelegramBusFollowerClientRuntime(deps) {
|
|
|
319
362
|
getAuthSecret: deps.getApiAuthSecret,
|
|
320
363
|
getRegistrationGeneration: deps.getRegistrationGeneration,
|
|
321
364
|
}),
|
|
322
|
-
targetController: createTelegramBusFollowerTargetController({
|
|
323
|
-
...sharedClientDeps,
|
|
324
|
-
getAuthSecret: deps.getForwardingAuthSecret,
|
|
325
|
-
}),
|
|
326
365
|
};
|
|
327
366
|
}
|
|
328
367
|
export function createTelegramBusFollowerQueueHandoffClient(deps) {
|
|
@@ -377,7 +416,7 @@ export function createTelegramBusFollowerQueueHandoffClient(deps) {
|
|
|
377
416
|
: "Telegram queue handoff did not return an acknowledgement.");
|
|
378
417
|
};
|
|
379
418
|
}
|
|
380
|
-
|
|
419
|
+
function createTelegramBusAgentMessageClient(deps) {
|
|
381
420
|
const getNowMs = deps.getNowMs ?? Date.now;
|
|
382
421
|
const timeoutMs = deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS;
|
|
383
422
|
const request = async (envelope, requestTimeoutMs = timeoutMs) => {
|
|
@@ -652,6 +691,7 @@ export function createTelegramBusFollowerRegistrationState(options = {}) {
|
|
|
652
691
|
let threadName;
|
|
653
692
|
let displayTitle;
|
|
654
693
|
let generation;
|
|
694
|
+
let sessionId;
|
|
655
695
|
let leaderProtocol;
|
|
656
696
|
let eligibleElectionSlots = [];
|
|
657
697
|
let recoveryEpoch = 0;
|
|
@@ -677,6 +717,7 @@ export function createTelegramBusFollowerRegistrationState(options = {}) {
|
|
|
677
717
|
return true;
|
|
678
718
|
},
|
|
679
719
|
getGeneration: () => generation,
|
|
720
|
+
getSessionId: currentSessionId => registered && generation && currentSessionId === sessionId ? sessionId : undefined,
|
|
680
721
|
beginRecovery: () => {
|
|
681
722
|
if (activeRecoveryEpoch !== undefined)
|
|
682
723
|
return activeRecoveryEpoch;
|
|
@@ -720,6 +761,7 @@ export function createTelegramBusFollowerRegistrationState(options = {}) {
|
|
|
720
761
|
generation === metadata?.generation &&
|
|
721
762
|
target?.chatId === nextTarget?.chatId && target?.threadId === nextTarget?.threadId
|
|
722
763
|
? displayTitle : undefined;
|
|
764
|
+
const retainedSessionId = next && registered && generation === metadata?.generation ? sessionId : undefined;
|
|
723
765
|
const availabilityChanged = registered !== next;
|
|
724
766
|
registered = next;
|
|
725
767
|
target = next ? (nextTarget ? { ...nextTarget } : undefined) : undefined;
|
|
@@ -729,6 +771,7 @@ export function createTelegramBusFollowerRegistrationState(options = {}) {
|
|
|
729
771
|
metadata.displayTitle?.trim() && metadata.displayTitle.length <= 128
|
|
730
772
|
? metadata.displayTitle : retainedDisplayTitle;
|
|
731
773
|
generation = next ? metadata?.generation : undefined;
|
|
774
|
+
sessionId = next ? metadata?.sessionId ?? retainedSessionId : undefined;
|
|
732
775
|
leaderProtocol =
|
|
733
776
|
next && metadata?.leaderProtocol
|
|
734
777
|
? {
|
|
@@ -1086,10 +1129,12 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
|
|
|
1086
1129
|
if (deps.isContextActive?.(ctx) === false)
|
|
1087
1130
|
return false;
|
|
1088
1131
|
const sessionGeneration = deps.getSessionGeneration?.();
|
|
1089
|
-
const
|
|
1132
|
+
const sessionId = deps.getSessionId?.(ctx);
|
|
1133
|
+
const attempt = { ctx, sessionGeneration, sessionId };
|
|
1090
1134
|
registrationAttempt = attempt;
|
|
1091
1135
|
const isCurrentRequest = () => registrationAttempt === attempt &&
|
|
1092
|
-
deps.getSessionGeneration?.() === sessionGeneration && deps.
|
|
1136
|
+
deps.getSessionGeneration?.() === sessionGeneration && deps.getSessionId?.(ctx) === sessionId &&
|
|
1137
|
+
deps.isContextActive?.(ctx) !== false;
|
|
1093
1138
|
const abandonOwnedRequest = async () => {
|
|
1094
1139
|
if (registrationAttempt !== attempt)
|
|
1095
1140
|
return;
|
|
@@ -1147,7 +1192,7 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
|
|
|
1147
1192
|
? { slot: deps.registrationState?.getSlot() ?? lastKnownSlot }
|
|
1148
1193
|
: {}),
|
|
1149
1194
|
cwd: ctx.cwd,
|
|
1150
|
-
sessionId
|
|
1195
|
+
sessionId,
|
|
1151
1196
|
pid: getPid(),
|
|
1152
1197
|
processBirthId: deps.getProcessBirthId?.(),
|
|
1153
1198
|
sessionGeneration,
|
|
@@ -1203,7 +1248,9 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
|
|
|
1203
1248
|
local: deps.protocolIdentity,
|
|
1204
1249
|
remote: response?.kind === "bus.ack" ? response.protocol : undefined,
|
|
1205
1250
|
});
|
|
1206
|
-
|
|
1251
|
+
// An identity-less error ACK is a rejection/transport failure, not evidence of an incompatible leader.
|
|
1252
|
+
const unidentifiedRejection = response?.kind === "bus.ack" && !response.ok && response.protocol === undefined;
|
|
1253
|
+
if (!compatibility.compatible && !unidentifiedRejection) {
|
|
1207
1254
|
stopHeartbeat();
|
|
1208
1255
|
activeLeaderSocketPath = undefined;
|
|
1209
1256
|
activeAuthSecret = undefined;
|
|
@@ -1235,6 +1282,7 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
|
|
|
1235
1282
|
deps.registrationState?.setRegistered(true, registrationResult.target, {
|
|
1236
1283
|
...registrationResult,
|
|
1237
1284
|
generation: registrationGeneration,
|
|
1285
|
+
sessionId,
|
|
1238
1286
|
...(response.protocol
|
|
1239
1287
|
? { leaderProtocol: response.protocol }
|
|
1240
1288
|
: {}),
|
|
@@ -1289,7 +1337,8 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
|
|
|
1289
1337
|
},
|
|
1290
1338
|
setContext(ctx) {
|
|
1291
1339
|
if (registrationAttempt && (registrationAttempt.ctx !== ctx ||
|
|
1292
|
-
registrationAttempt.sessionGeneration !== deps.getSessionGeneration?.()
|
|
1340
|
+
registrationAttempt.sessionGeneration !== deps.getSessionGeneration?.() ||
|
|
1341
|
+
registrationAttempt.sessionId !== deps.getSessionId?.(ctx)))
|
|
1293
1342
|
registrationAttempt = undefined;
|
|
1294
1343
|
activeContext = ctx;
|
|
1295
1344
|
},
|
|
@@ -1557,6 +1606,17 @@ export function prepareTelegramBusFollowerJournaledUpdateForExecution(update, pr
|
|
|
1557
1606
|
return prepared;
|
|
1558
1607
|
}
|
|
1559
1608
|
export function createTelegramBusFollowerDurableAdmissionRuntime(deps) {
|
|
1609
|
+
// The journal forgets a delivery once its worker completes; this window absorbs a leader retry after a lost ACK.
|
|
1610
|
+
const recentDeliveries = new Map();
|
|
1611
|
+
const getNowMs = deps.getNowMs ?? Date.now;
|
|
1612
|
+
const limit = deps.recentDeliveryLimit ?? { maxAgeMs: 24 * 60 * 60 * 1000, maxEntries: 4096 };
|
|
1613
|
+
const pruneRecentDeliveries = (nowMs) => {
|
|
1614
|
+
for (const [deliveryId, admittedAtMs] of recentDeliveries) {
|
|
1615
|
+
if (recentDeliveries.size <= limit.maxEntries && nowMs - admittedAtMs < limit.maxAgeMs)
|
|
1616
|
+
break;
|
|
1617
|
+
recentDeliveries.delete(deliveryId);
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1560
1620
|
return {
|
|
1561
1621
|
async admit(envelope, ctx) {
|
|
1562
1622
|
const delivery = envelope.delivery;
|
|
@@ -1598,12 +1658,18 @@ export function createTelegramBusFollowerDurableAdmissionRuntime(deps) {
|
|
|
1598
1658
|
? { message: carrier }
|
|
1599
1659
|
: { edited_message: carrier }),
|
|
1600
1660
|
};
|
|
1601
|
-
|
|
1602
|
-
deps.signalWorker(ctx);
|
|
1603
|
-
return {
|
|
1661
|
+
const result = {
|
|
1604
1662
|
deliveryId: delivery.deliveryId,
|
|
1605
1663
|
sourceUpdateId: delivery.sourceUpdateId,
|
|
1606
1664
|
};
|
|
1665
|
+
pruneRecentDeliveries(getNowMs());
|
|
1666
|
+
if (recentDeliveries.has(delivery.deliveryId))
|
|
1667
|
+
return result;
|
|
1668
|
+
deps.journal.appendBatch([update]);
|
|
1669
|
+
recentDeliveries.set(delivery.deliveryId, getNowMs());
|
|
1670
|
+
pruneRecentDeliveries(getNowMs());
|
|
1671
|
+
deps.signalWorker(ctx);
|
|
1672
|
+
return result;
|
|
1607
1673
|
},
|
|
1608
1674
|
};
|
|
1609
1675
|
}
|
|
@@ -1680,7 +1746,7 @@ export function createTelegramBusForwardedUpdateReceiverRuntime(deps) {
|
|
|
1680
1746
|
envelope.kind !== "leader.forwardEditedMessage" &&
|
|
1681
1747
|
envelope.kind !== "leader.wakeInputCustody" &&
|
|
1682
1748
|
envelope.kind !== "leader.offerInputCustodyHandoff" &&
|
|
1683
|
-
envelope.kind !== "leader.
|
|
1749
|
+
envelope.kind !== "leader.workspaceRestore" &&
|
|
1684
1750
|
envelope.kind !== "leader.offerQueueHandoff") ||
|
|
1685
1751
|
envelope.recipientInstanceId !== deps.instanceId) {
|
|
1686
1752
|
return {
|
|
@@ -1700,14 +1766,33 @@ export function createTelegramBusForwardedUpdateReceiverRuntime(deps) {
|
|
|
1700
1766
|
message: "Stale Telegram bus follower registration generation.",
|
|
1701
1767
|
};
|
|
1702
1768
|
}
|
|
1769
|
+
if (envelope.kind === "leader.workspaceRestore") {
|
|
1770
|
+
const ctx = deps.getContext();
|
|
1771
|
+
if (!authSecret || !ctx || !deps.isWorkspaceRestoreEnabled?.() || !deps.handleWorkspaceRestore)
|
|
1772
|
+
return {
|
|
1773
|
+
kind: "bus.ack", requestId: envelope.requestId, ok: false, message: "Workspace Restore capability is unavailable.",
|
|
1774
|
+
};
|
|
1775
|
+
try {
|
|
1776
|
+
const result = await deps.handleWorkspaceRestore({ operationId: envelope.operationId,
|
|
1777
|
+
registrationGeneration, mode: envelope.mode }, ctx);
|
|
1778
|
+
if (deps.getAuthSecret?.() !== authSecret || deps.getRegistrationGeneration() !== registrationGeneration ||
|
|
1779
|
+
deps.getContext() !== ctx || !deps.isWorkspaceRestoreEnabled())
|
|
1780
|
+
throw new Error("Workspace Restore receiver authority changed.");
|
|
1781
|
+
return { kind: "bus.ack", requestId: envelope.requestId, ok: true, result };
|
|
1782
|
+
}
|
|
1783
|
+
catch (error) {
|
|
1784
|
+
deps.recordRuntimeEvent?.("bus", error, { phase: "follower-workspace-restore" });
|
|
1785
|
+
return { kind: "bus.ack", requestId: envelope.requestId, ok: false,
|
|
1786
|
+
message: error instanceof Error ? error.message : "Workspace Restore failed." };
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1703
1789
|
if (envelope.kind === "leader.offerInputCustodyHandoff" &&
|
|
1704
1790
|
envelope.recipientBindingKey !== deps.getRecipientBindingKey())
|
|
1705
1791
|
return {
|
|
1706
1792
|
kind: "bus.ack", requestId: envelope.requestId, ok: false,
|
|
1707
1793
|
message: "Mismatched Telegram follower handoff recipient identity.",
|
|
1708
1794
|
};
|
|
1709
|
-
if (envelope.kind !== "leader.
|
|
1710
|
-
envelope.kind !== "leader.offerQueueHandoff" &&
|
|
1795
|
+
if (envelope.kind !== "leader.offerQueueHandoff" &&
|
|
1711
1796
|
envelope.kind !== "leader.offerInputCustodyHandoff" &&
|
|
1712
1797
|
(!envelope.delivery ||
|
|
1713
1798
|
envelope.delivery.recipientBindingKey !==
|
|
@@ -1739,8 +1824,7 @@ export function createTelegramBusForwardedUpdateReceiverRuntime(deps) {
|
|
|
1739
1824
|
const result = await deps.handleInputCustodyHandoff(envelope, ctx);
|
|
1740
1825
|
return { kind: "bus.ack", requestId: envelope.requestId, ok: true, result };
|
|
1741
1826
|
}
|
|
1742
|
-
if (envelope.kind !== "leader.
|
|
1743
|
-
envelope.kind !== "leader.offerQueueHandoff") {
|
|
1827
|
+
if (envelope.kind !== "leader.offerQueueHandoff") {
|
|
1744
1828
|
const sourceReferenceEnabled = envelope.kind === "leader.wakeInputCustody" ||
|
|
1745
1829
|
(deps.isSourceReferenceAdmissionEnabled?.() ?? false);
|
|
1746
1830
|
if (sourceReferenceEnabled &&
|
|
@@ -1758,39 +1842,25 @@ export function createTelegramBusForwardedUpdateReceiverRuntime(deps) {
|
|
|
1758
1842
|
result: receipt,
|
|
1759
1843
|
};
|
|
1760
1844
|
}
|
|
1761
|
-
if (
|
|
1762
|
-
|
|
1763
|
-
throw new Error("Telegram bus receiver cannot accept queue handoff payloads.");
|
|
1764
|
-
}
|
|
1765
|
-
const result = await deps.handleQueueHandoff(envelope, ctx);
|
|
1766
|
-
const receipt = envelope.payload.admissionReceipts[0];
|
|
1767
|
-
if (envelope.payload.admissionReceipts.length !== 1 ||
|
|
1768
|
-
!receipt ||
|
|
1769
|
-
result.status !== "staged" ||
|
|
1770
|
-
result.receiptId !== receipt.receiptId ||
|
|
1771
|
-
result.sourceUpdateIds.length !== receipt.sourceUpdateIds.length ||
|
|
1772
|
-
result.sourceUpdateIds.some((updateId, index) => updateId !== receipt.sourceUpdateIds[index])) {
|
|
1773
|
-
throw new Error("Telegram queue handoff staging returned a mismatched receipt.");
|
|
1774
|
-
}
|
|
1775
|
-
return {
|
|
1776
|
-
kind: "bus.ack",
|
|
1777
|
-
requestId: envelope.requestId,
|
|
1778
|
-
ok: true,
|
|
1779
|
-
result,
|
|
1780
|
-
};
|
|
1845
|
+
if (!deps.handleQueueHandoff) {
|
|
1846
|
+
throw new Error("Telegram bus receiver cannot accept queue handoff payloads.");
|
|
1781
1847
|
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
}, ctx);
|
|
1848
|
+
const result = await deps.handleQueueHandoff(envelope, ctx);
|
|
1849
|
+
const receipt = envelope.payload.admissionReceipts[0];
|
|
1850
|
+
if (envelope.payload.admissionReceipts.length !== 1 ||
|
|
1851
|
+
!receipt ||
|
|
1852
|
+
result.status !== "staged" ||
|
|
1853
|
+
result.receiptId !== receipt.receiptId ||
|
|
1854
|
+
result.sourceUpdateIds.length !== receipt.sourceUpdateIds.length ||
|
|
1855
|
+
result.sourceUpdateIds.some((updateId, index) => updateId !== receipt.sourceUpdateIds[index])) {
|
|
1856
|
+
throw new Error("Telegram queue handoff staging returned a mismatched receipt.");
|
|
1792
1857
|
}
|
|
1793
|
-
return {
|
|
1858
|
+
return {
|
|
1859
|
+
kind: "bus.ack",
|
|
1860
|
+
requestId: envelope.requestId,
|
|
1861
|
+
ok: true,
|
|
1862
|
+
result,
|
|
1863
|
+
};
|
|
1794
1864
|
}
|
|
1795
1865
|
catch (error) {
|
|
1796
1866
|
deps.recordRuntimeEvent?.("bus", error, { phase: "follower-forward" });
|
|
@@ -1832,6 +1902,3 @@ function parseTarget(value) {
|
|
|
1832
1902
|
target.threadId = value.threadId;
|
|
1833
1903
|
return target;
|
|
1834
1904
|
}
|
|
1835
|
-
function isRecord(value) {
|
|
1836
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1837
|
-
}
|
|
@@ -9,6 +9,7 @@ import type { TelegramThreadDisplayMode } from "./config.ts";
|
|
|
9
9
|
import * as ThreadReconciler from "./thread-reconciler.ts";
|
|
10
10
|
import { type TelegramApiCallOptions } from "./telegram-api.ts";
|
|
11
11
|
import type { TelegramTarget } from "./target.ts";
|
|
12
|
+
import type { TelegramAttachmentSource } from "./media.ts";
|
|
12
13
|
import * as Threads from "./threads.ts";
|
|
13
14
|
import { type TelegramBusEnvelope, type TelegramBusFollowerRegistry, type TelegramBusFollowerView, type TelegramBusInstanceRegistration, type TelegramBusProtocolIdentity, type TelegramBusSocketPathSource } from "./bus.ts";
|
|
14
15
|
import type { TelegramQueueHandoffPayload } from "./queue.ts";
|
|
@@ -101,14 +102,14 @@ export interface TelegramBusFollowerDisconnectHandlerDeps {
|
|
|
101
102
|
export interface TelegramBusLeaderApiProxyDeps {
|
|
102
103
|
call: (method: string, body: Record<string, unknown>, options?: TelegramApiCallOptions) => Promise<unknown>;
|
|
103
104
|
callMultipart: (method: string, fields: Record<string, string>, fieldName: string, filePath: string, fileName: string, options?: TelegramApiCallOptions) => Promise<unknown>;
|
|
104
|
-
downloadFile: (fileId: string,
|
|
105
|
+
downloadFile: (fileId: string, suggestedName: string, source?: TelegramAttachmentSource) => Promise<unknown>;
|
|
105
106
|
recoverStaleTargetError?: (apiBody: unknown, error: unknown) => Promise<unknown> | unknown;
|
|
106
107
|
}
|
|
107
108
|
export interface TelegramBusLeaderRuntimeAssemblyDeps<TContext> {
|
|
108
109
|
getThreadDisplayMode?: () => TelegramThreadDisplayMode;
|
|
109
110
|
persistThreadDisplayMode?: (mode: TelegramThreadDisplayMode, isCurrent: () => boolean) => Promise<void>;
|
|
110
111
|
onThreadDisplayChanged?: () => void;
|
|
111
|
-
runtime: Omit<TelegramBusLeaderRuntimeDeps<TContext>, "callApi" | "onFollowerDisconnected" | "onFollowerConfirmedDead" | "onFollowerConfirmedDeadPreserved" | "getTelegramProfile" | "provisionFollowerTarget" | "provisionLeaderTarget" | "recordRuntimeEvent">;
|
|
112
|
+
runtime: Omit<TelegramBusLeaderRuntimeDeps<TContext>, "callApi" | "onFollowerDisconnected" | "onFollowerConfirmedDead" | "onFollowerConfirmedDeadPreserved" | "getTelegramProfile" | "getAllowedUserId" | "provisionFollowerTarget" | "commitFollowerRegistration" | "provisionLeaderTarget" | "recordRuntimeEvent">;
|
|
112
113
|
getAllowedUserId: () => number | undefined;
|
|
113
114
|
instanceId: string;
|
|
114
115
|
getCwd?: (ctx: TContext) => string | undefined;
|
|
@@ -168,6 +169,11 @@ export interface TelegramBusFollowerMessageOwnershipRecord {
|
|
|
168
169
|
target?: TelegramTarget;
|
|
169
170
|
}
|
|
170
171
|
export type TelegramBusFollowerMessageOwnershipRecorder = (record: TelegramBusFollowerMessageOwnershipRecord) => void;
|
|
172
|
+
export type TelegramBusFollowerRegistrationCommitter = (input: {
|
|
173
|
+
registration: TelegramBusInstanceRegistration;
|
|
174
|
+
target?: TelegramTarget;
|
|
175
|
+
slot?: string;
|
|
176
|
+
}, publish: () => void) => Promise<void> | void;
|
|
171
177
|
export interface TelegramBusLeaderRuntimeDeps<TContext> {
|
|
172
178
|
socketPath: TelegramBusSocketPathSource;
|
|
173
179
|
commitEndpointPublication?: (commit: () => void) => boolean;
|
|
@@ -195,6 +201,7 @@ export interface TelegramBusLeaderRuntimeDeps<TContext> {
|
|
|
195
201
|
provisionFollowerTarget?: (registration: TelegramBusInstanceRegistration, options?: {
|
|
196
202
|
existingWorkspaceBindingOnly?: boolean;
|
|
197
203
|
}) => Promise<TelegramTarget | undefined> | TelegramTarget | undefined;
|
|
204
|
+
commitFollowerRegistration?: TelegramBusFollowerRegistrationCommitter;
|
|
198
205
|
renameFollowerThread?: (follower: TelegramBusFollowerView, threadName: string) => Promise<{
|
|
199
206
|
threadName: string;
|
|
200
207
|
}> | {
|
|
@@ -209,16 +216,22 @@ export interface TelegramBusLeaderRuntimeDeps<TContext> {
|
|
|
209
216
|
settleFollowerSessionReplacement?: TelegramBusFollowerSessionReplacementOperation;
|
|
210
217
|
getFollowerDisplayTitle?: (follower: TelegramBusFollowerView) => string | undefined;
|
|
211
218
|
onFollowerRegistered?: () => void;
|
|
219
|
+
/** Observation only: reacquire admission and inspect work, never infer completion.
|
|
220
|
+
* Listener settlement ends its currentness fence; return asynchronous work to retain that fence. */
|
|
221
|
+
onWorkspaceRestoreRecipientObserved?: (follower: TelegramBusFollowerView, isCurrent: () => boolean) => Promise<void> | void;
|
|
212
222
|
applyThreadDisplayMode?: (mode: TelegramThreadDisplayMode, isCurrent: () => boolean) => Promise<void>;
|
|
213
223
|
getThreadDisplayMode?: () => TelegramThreadDisplayMode;
|
|
214
224
|
getCurrentLeaderEpoch?: () => number | string | undefined;
|
|
215
225
|
getTelegramProfile?: () => string | undefined;
|
|
226
|
+
getAllowedUserId?: () => number | undefined;
|
|
216
227
|
provisionLeaderTarget?: (ctx: TContext) => Promise<void> | void;
|
|
217
228
|
runWorkspaceAdmission?: TelegramBusWorkspaceAdmissionRunner;
|
|
218
229
|
runWithWorkspaceCapacity?: TelegramWorkspaceCapacityRunner;
|
|
219
230
|
getNowMs?: () => number;
|
|
220
231
|
timeoutMs?: number;
|
|
221
232
|
followerPruneIntervalMs?: number;
|
|
233
|
+
/** Leader housekeeping after each current prune pass; throttling is the callee's concern. */
|
|
234
|
+
afterFollowerPrune?: () => void;
|
|
222
235
|
followerStaleAfterMs?: number;
|
|
223
236
|
isFollowerProcessAlive?: (pid: number) => boolean;
|
|
224
237
|
shouldCleanupConfirmedDeadFollower?: () => Promise<boolean> | boolean;
|
|
@@ -281,6 +294,7 @@ export declare function createTelegramBusLeaderEnvelopeHandler(deps: {
|
|
|
281
294
|
slot?: string;
|
|
282
295
|
threadName?: string;
|
|
283
296
|
}) | undefined;
|
|
297
|
+
commitFollowerRegistration?: TelegramBusFollowerRegistrationCommitter;
|
|
284
298
|
onFollowerDisconnected?: (follower: TelegramBusFollowerView) => Promise<void> | void;
|
|
285
299
|
renameFollowerThread?: (follower: TelegramBusFollowerView, threadName: string) => Promise<{
|
|
286
300
|
threadName: string;
|
|
@@ -296,9 +310,14 @@ export declare function createTelegramBusLeaderEnvelopeHandler(deps: {
|
|
|
296
310
|
settleFollowerSessionReplacement?: TelegramBusFollowerSessionReplacementOperation;
|
|
297
311
|
getFollowerDisplayTitle?: (follower: TelegramBusFollowerView) => string | undefined;
|
|
298
312
|
onFollowerRegistered?: () => void;
|
|
313
|
+
onWorkspaceRestoreRecipientObserved?: TelegramBusLeaderRuntimeDeps<unknown>["onWorkspaceRestoreRecipientObserved"];
|
|
314
|
+
getWorkspaceRestoreObservationGeneration?: () => number | undefined;
|
|
315
|
+
recordRuntimeEvent?: TelegramBusLeaderRuntimeDeps<unknown>["recordRuntimeEvent"];
|
|
299
316
|
applyThreadDisplayMode?: (mode: TelegramThreadDisplayMode, isCurrent: () => boolean) => Promise<void>;
|
|
300
317
|
getThreadDisplayMode?: () => TelegramThreadDisplayMode;
|
|
301
318
|
getCurrentLeaderEpoch?: () => number | string | undefined;
|
|
319
|
+
getTelegramProfile?: () => string | undefined;
|
|
320
|
+
getAllowedUserId?: () => number | undefined;
|
|
302
321
|
runFollowerMutation?: TelegramBusFollowerMutationRunner;
|
|
303
322
|
runWorkspaceAdmission?: TelegramBusWorkspaceAdmissionRunner;
|
|
304
323
|
runWithWorkspaceCapacity?: TelegramWorkspaceCapacityRunner;
|