@llblab/pi-kit 0.19.1 → 0.20.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/CHANGELOG.md +12 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +4 -4
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-state-flow/README.md +3 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +45 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +19 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +52 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +0 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +36 -15
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +5 -5
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +2 -1
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +2 -2
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +48 -5
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +9 -9
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +65 -11
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +1 -2
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +35 -15
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +9 -7
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +13 -32
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +11 -0
- package/node_modules/@llblab/pi-telegram/README.md +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +3 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +93 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +9 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +231 -71
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +4 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +34 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +63 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +3 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +13 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +25 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +32 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +10 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +22 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +1 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +9 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +41 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +9 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +23 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +7 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +68 -37
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +6 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +134 -61
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +9 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +49 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +77 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +384 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.d.ts +3 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +21 -14
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +31 -10
- package/node_modules/@llblab/pi-telegram/docs/updates.md +2 -0
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +5 -7
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +69 -12
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +236 -94
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +39 -9
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +69 -2
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +14 -0
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +23 -4
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +32 -13
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -1
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +2 -3
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +45 -1
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +30 -2
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +11 -3
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +73 -36
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +145 -78
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +67 -4
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +465 -7
- package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +7 -0
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
|
@@ -11,8 +11,9 @@ import { getTelegramApiErrorRequestTarget, isTelegramApiCommitUnknownError, } fr
|
|
|
11
11
|
import * as Threads from "./threads.js";
|
|
12
12
|
import { createTelegramBusLocalServer, createUnauthorizedBusAck, getTelegramBusEnvelopeTrafficClass, getTelegramBusProtocolCompatibility, hasTelegramBusCapability, isTelegramBusEnvelopeAuthorized, getTelegramBusFollowerSocketPath, sendTelegramBusLocalEnvelope, stripTelegramBusApiMetadata, TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION, TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF, TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, } from "./bus.js";
|
|
13
13
|
import { getTelegramBusTransportRetryPolicy } from "./bus-transport.js";
|
|
14
|
-
import { createTelegramWorkspaceOperationRuntime, } from "./workspace-retirement.js";
|
|
14
|
+
import { createTelegramWorkspaceOperationRuntime, createTelegramWorkspaceSlotRotation, } from "./workspace-retirement.js";
|
|
15
15
|
import { createTelegramWorkspaceAdmissionOperationId, runWithTelegramWorkspaceAdmissionsAsync, } from "./workspace-admission.js";
|
|
16
|
+
import { TELEGRAM_WORKSPACE_SLOTS, TelegramWorkspaceSlotUnavailableError } from "./workspace-slots.js";
|
|
16
17
|
export const TELEGRAM_BUS_FOLLOWER_STALE_AFTER_MS = 15_000;
|
|
17
18
|
function escapeHtml(text) {
|
|
18
19
|
return text
|
|
@@ -56,6 +57,13 @@ export function createTelegramBusLeaderRuntimeAssembly(deps) {
|
|
|
56
57
|
});
|
|
57
58
|
},
|
|
58
59
|
}).run;
|
|
60
|
+
const runWithWorkspaceCapacity = deps.workspaceRotation && deps.captureWorkspaceExternalProtection &&
|
|
61
|
+
deps.getCurrentLeaderEpoch ? createTelegramWorkspaceSlotRotation({
|
|
62
|
+
...deps.workspaceRotation, store: deps.topicTargetStore,
|
|
63
|
+
getLeaderEpoch: deps.getCurrentLeaderEpoch,
|
|
64
|
+
getExternalProtection: deps.captureWorkspaceExternalProtection,
|
|
65
|
+
recordEvent(message, details) { deps.recordRuntimeEvent("bus", message, details); },
|
|
66
|
+
}) : undefined;
|
|
59
67
|
const captureLiveBindingKeys = (bindings) => resolveTelegramLiveWorkspaceBindingKeys(bindings, deps.topicTargetStore.getActiveByInstanceId(deps.instanceId)?.target, deps.runtime.followerRegistry.list());
|
|
60
68
|
const provisionerPorts = {
|
|
61
69
|
getAllowedUserId: deps.getAllowedUserId,
|
|
@@ -114,13 +122,40 @@ export function createTelegramBusLeaderRuntimeAssembly(deps) {
|
|
|
114
122
|
operationKind: "workspace.reconcile-display",
|
|
115
123
|
scopes: [{ kind: "profile" }],
|
|
116
124
|
}, reconcileThreadDisplayOperation);
|
|
125
|
+
let startupDisplayTimer;
|
|
126
|
+
let startupDisplayGeneration = 0;
|
|
127
|
+
let startupDisplaySettled = true;
|
|
128
|
+
const cancelStartupDisplayStabilization = () => {
|
|
129
|
+
startupDisplayGeneration += 1;
|
|
130
|
+
startupDisplaySettled = true;
|
|
131
|
+
if (startupDisplayTimer)
|
|
132
|
+
clearTimeout(startupDisplayTimer);
|
|
133
|
+
startupDisplayTimer = undefined;
|
|
134
|
+
};
|
|
117
135
|
const scheduleDisplay = () => {
|
|
118
|
-
if (!display)
|
|
136
|
+
if (!display || !startupDisplaySettled)
|
|
119
137
|
return;
|
|
120
138
|
void reconcileThreadDisplay().catch((error) => {
|
|
121
139
|
deps.recordRuntimeEvent("bus", error, { phase: "thread-display-reconcile" });
|
|
122
140
|
});
|
|
123
141
|
};
|
|
142
|
+
const beginStartupDisplayStabilization = () => {
|
|
143
|
+
cancelStartupDisplayStabilization();
|
|
144
|
+
startupDisplaySettled = false;
|
|
145
|
+
};
|
|
146
|
+
const armStartupDisplayReconciliation = () => {
|
|
147
|
+
const generation = startupDisplayGeneration;
|
|
148
|
+
const delayMs = deps.runtime.followerStaleAfterMs ??
|
|
149
|
+
TELEGRAM_BUS_FOLLOWER_STALE_AFTER_MS;
|
|
150
|
+
startupDisplayTimer = setTimeout(() => {
|
|
151
|
+
if (startupDisplayGeneration !== generation)
|
|
152
|
+
return;
|
|
153
|
+
startupDisplayTimer = undefined;
|
|
154
|
+
startupDisplaySettled = true;
|
|
155
|
+
scheduleDisplay();
|
|
156
|
+
}, delayMs);
|
|
157
|
+
startupDisplayTimer.unref?.();
|
|
158
|
+
};
|
|
124
159
|
let modeTail = Promise.resolve();
|
|
125
160
|
const applyThreadDisplayMode = (mode, isCurrent) => {
|
|
126
161
|
const epoch = deps.getCurrentLeaderEpoch?.();
|
|
@@ -299,7 +334,7 @@ export function createTelegramBusLeaderRuntimeAssembly(deps) {
|
|
|
299
334
|
onFollowerRegistered: scheduleDisplay,
|
|
300
335
|
provisionLeaderTarget: (ctx) => {
|
|
301
336
|
const chatId = deps.getAllowedUserId();
|
|
302
|
-
|
|
337
|
+
const provision = () => runWorkspaceOperation({
|
|
303
338
|
operationId: `leader-provision:${deps.instanceId}`,
|
|
304
339
|
operationKind: "workspace.provision-leader",
|
|
305
340
|
scopes: [
|
|
@@ -308,6 +343,7 @@ export function createTelegramBusLeaderRuntimeAssembly(deps) {
|
|
|
308
343
|
: { kind: "profile" },
|
|
309
344
|
],
|
|
310
345
|
}, () => provisionLeaderTarget(ctx));
|
|
346
|
+
return runWithWorkspaceCapacity ? runWithWorkspaceCapacity(provision) : provision();
|
|
311
347
|
},
|
|
312
348
|
getFollowerDisplayTitle(follower) {
|
|
313
349
|
const binding = deps.topicTargetStore.listWorkspaceBindings().find((binding) => binding.target.chatId === follower.target?.chatId &&
|
|
@@ -399,13 +435,50 @@ export function createTelegramBusLeaderRuntimeAssembly(deps) {
|
|
|
399
435
|
}, () => cleanupConfirmedDeadFollower(follower));
|
|
400
436
|
scheduleDisplay();
|
|
401
437
|
},
|
|
438
|
+
async onFollowerConfirmedDeadPreserved(follower, isDetached, operationId) {
|
|
439
|
+
const epoch = deps.getCurrentLeaderEpoch?.();
|
|
440
|
+
const profile = deps.getTelegramProfile?.();
|
|
441
|
+
if (epoch === undefined || !follower.registrationGeneration ||
|
|
442
|
+
typeof follower.target?.threadId !== "number")
|
|
443
|
+
return false;
|
|
444
|
+
const isCurrent = () => isDetached() && deps.getCurrentLeaderEpoch?.() === epoch &&
|
|
445
|
+
deps.getTelegramProfile?.() === profile;
|
|
446
|
+
if (!isCurrent())
|
|
447
|
+
return false;
|
|
448
|
+
const settled = await runWorkspaceOperation({
|
|
449
|
+
operationId,
|
|
450
|
+
operationKind: "workspace.preserve-dead-follower",
|
|
451
|
+
scopes: [{ kind: "profile" }],
|
|
452
|
+
}, async () => {
|
|
453
|
+
if (!isCurrent())
|
|
454
|
+
return false;
|
|
455
|
+
await deps.topicTargetStore.load();
|
|
456
|
+
if (!isCurrent())
|
|
457
|
+
return false;
|
|
458
|
+
const record = deps.topicTargetStore.getActiveByInstanceId(follower.instanceId);
|
|
459
|
+
if (!record || (follower.profileKey && record.profileKey !== follower.profileKey) ||
|
|
460
|
+
record.target.chatId !== follower.target.chatId ||
|
|
461
|
+
record.target.threadId !== follower.target.threadId)
|
|
462
|
+
return true;
|
|
463
|
+
const committed = await deps.topicTargetStore.detachTargetOwner(record, isCurrent);
|
|
464
|
+
if (!committed && isCurrent()) {
|
|
465
|
+
throw new Error("Telegram preserved follower detachment was not committed.");
|
|
466
|
+
}
|
|
467
|
+
return committed;
|
|
468
|
+
});
|
|
469
|
+
if (settled && isCurrent())
|
|
470
|
+
scheduleDisplay();
|
|
471
|
+
return settled;
|
|
472
|
+
},
|
|
402
473
|
provisionFollowerTarget: (registration, options) => runWorkspaceOperation({
|
|
403
474
|
operationId: `follower-provision:${registration.instanceId}:${registration.registrationGeneration}`,
|
|
404
475
|
operationKind: "workspace.provision-follower",
|
|
405
476
|
scopes: [{ kind: "profile" }],
|
|
406
477
|
}, () => provisionFollowerTarget(registration, options)),
|
|
407
478
|
getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
|
|
479
|
+
getTelegramProfile: deps.getTelegramProfile,
|
|
408
480
|
runWorkspaceAdmission,
|
|
481
|
+
runWithWorkspaceCapacity,
|
|
409
482
|
callApi: createTelegramBusLeaderApiProxy({
|
|
410
483
|
call: deps.callApi,
|
|
411
484
|
callMultipart: deps.callMultipart,
|
|
@@ -423,9 +496,21 @@ export function createTelegramBusLeaderRuntimeAssembly(deps) {
|
|
|
423
496
|
resetThreadNameAdmitted,
|
|
424
497
|
captureWorkspaceExternalProtection: deps.captureWorkspaceExternalProtection,
|
|
425
498
|
async startPolling(ctx) {
|
|
426
|
-
await runtime.startPolling(ctx);
|
|
427
499
|
if (display)
|
|
428
|
-
|
|
500
|
+
beginStartupDisplayStabilization();
|
|
501
|
+
try {
|
|
502
|
+
await runtime.startPolling(ctx);
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
cancelStartupDisplayStabilization();
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
if (display)
|
|
509
|
+
armStartupDisplayReconciliation();
|
|
510
|
+
},
|
|
511
|
+
async stopPolling() {
|
|
512
|
+
cancelStartupDisplayStabilization();
|
|
513
|
+
await runtime.stopPolling();
|
|
429
514
|
},
|
|
430
515
|
};
|
|
431
516
|
if (!display)
|
|
@@ -495,7 +580,7 @@ export function createTelegramBusFollowerTargetProvisioner(deps) {
|
|
|
495
580
|
if (options?.existingWorkspaceBindingOnly)
|
|
496
581
|
return undefined;
|
|
497
582
|
if (capacityUnavailable) {
|
|
498
|
-
throw new
|
|
583
|
+
throw new TelegramWorkspaceSlotUnavailableError();
|
|
499
584
|
}
|
|
500
585
|
throw new Error("Telegram Workspace identity is already claimed.");
|
|
501
586
|
}
|
|
@@ -591,7 +676,7 @@ export function createTelegramBusFollowerTargetProvisioner(deps) {
|
|
|
591
676
|
const requestedSlot = workspaceIdentity?.slot ?? workspaceBinding?.slot ?? registration.slot ?? recoveryHint?.slot;
|
|
592
677
|
const recoveredSlot = deps.topicTargetStore.allocateSlot(followerProfileKey, requestedSlot, workspaceIdentity?.bindingKey);
|
|
593
678
|
if (!recoveredSlot) {
|
|
594
|
-
throw new
|
|
679
|
+
throw new TelegramWorkspaceSlotUnavailableError();
|
|
595
680
|
}
|
|
596
681
|
const recoveredRecord = {
|
|
597
682
|
profileKey: followerProfileKey,
|
|
@@ -945,9 +1030,7 @@ function createTelegramBusFollowerCleanupHandler(deps, trigger) {
|
|
|
945
1030
|
isCleanupTargetProtected,
|
|
946
1031
|
callApi: deps.callApi,
|
|
947
1032
|
markStaleByTarget(target, syncStatus, lastSyncError) {
|
|
948
|
-
|
|
949
|
-
const inactive = deps.topicTargetStore.markWorkspaceBindingInactiveByTarget(target, (deps.getNowMs ?? Date.now)());
|
|
950
|
-
return stale || inactive;
|
|
1033
|
+
return deps.topicTargetStore.markStaleByTarget(target, syncStatus, lastSyncError);
|
|
951
1034
|
},
|
|
952
1035
|
removeCleanupIntentById: deps.topicTargetStore.removePendingCleanup,
|
|
953
1036
|
persist: deps.topicTargetStore.persist,
|
|
@@ -1440,11 +1523,15 @@ export function createTelegramBusLeaderEnvelopeHandler(deps) {
|
|
|
1440
1523
|
: "missing-capability")}.`,
|
|
1441
1524
|
};
|
|
1442
1525
|
}
|
|
1526
|
+
if (!envelope.registration.registrationGeneration) {
|
|
1527
|
+
return {
|
|
1528
|
+
kind: "bus.ack", requestId: envelope.requestId, ok: false,
|
|
1529
|
+
protocol: deps.protocolIdentity,
|
|
1530
|
+
message: "Telegram follower registration requires an exact generation.",
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1443
1533
|
const registrationOperation = () => runFollowerMutation(envelope.registration, async () => {
|
|
1444
1534
|
try {
|
|
1445
|
-
if (!envelope.registration.registrationGeneration) {
|
|
1446
|
-
throw new Error("Telegram follower registration requires an exact generation.");
|
|
1447
|
-
}
|
|
1448
1535
|
const leaderEpoch = deps.getCurrentLeaderEpoch?.();
|
|
1449
1536
|
if (deps.getCurrentLeaderEpoch && leaderEpoch === undefined) {
|
|
1450
1537
|
throw new Error("Telegram follower registration requires leader ownership.");
|
|
@@ -1502,6 +1589,9 @@ export function createTelegramBusLeaderEnvelopeHandler(deps) {
|
|
|
1502
1589
|
};
|
|
1503
1590
|
}
|
|
1504
1591
|
catch (error) {
|
|
1592
|
+
if (!restoringWorkspace && deps.runWithWorkspaceCapacity &&
|
|
1593
|
+
error instanceof TelegramWorkspaceSlotUnavailableError)
|
|
1594
|
+
throw error;
|
|
1505
1595
|
return {
|
|
1506
1596
|
kind: "bus.ack",
|
|
1507
1597
|
requestId: envelope.requestId,
|
|
@@ -1513,14 +1603,14 @@ export function createTelegramBusLeaderEnvelopeHandler(deps) {
|
|
|
1513
1603
|
};
|
|
1514
1604
|
}
|
|
1515
1605
|
});
|
|
1516
|
-
|
|
1517
|
-
|
|
1606
|
+
const register = () => deps.runWorkspaceAdmission ? deps.runWorkspaceAdmission({
|
|
1607
|
+
operationId: `follower-registration:${envelope.requestId}`,
|
|
1608
|
+
operationKind: "workspace.register-follower",
|
|
1609
|
+
scopes: [{ kind: "profile" }],
|
|
1610
|
+
}, registrationOperation) : registrationOperation();
|
|
1518
1611
|
try {
|
|
1519
|
-
return await deps.
|
|
1520
|
-
|
|
1521
|
-
operationKind: "workspace.register-follower",
|
|
1522
|
-
scopes: [{ kind: "profile" }],
|
|
1523
|
-
}, registrationOperation);
|
|
1612
|
+
return await (!restoringWorkspace && deps.runWithWorkspaceCapacity
|
|
1613
|
+
? deps.runWithWorkspaceCapacity(register) : register());
|
|
1524
1614
|
}
|
|
1525
1615
|
catch (error) {
|
|
1526
1616
|
return {
|
|
@@ -1981,8 +2071,19 @@ export function createTelegramBusLeaderRuntime(deps) {
|
|
|
1981
2071
|
let pruneInterval;
|
|
1982
2072
|
let pruneGeneration = 0;
|
|
1983
2073
|
let prunePromise;
|
|
2074
|
+
const pendingPreservations = new Map();
|
|
2075
|
+
const forgetPreservation = (observation) => {
|
|
2076
|
+
if (!observation)
|
|
2077
|
+
return;
|
|
2078
|
+
if (pendingPreservations.get(observation.follower.instanceId) === observation) {
|
|
2079
|
+
pendingPreservations.delete(observation.follower.instanceId);
|
|
2080
|
+
}
|
|
2081
|
+
observation.registration.release();
|
|
2082
|
+
};
|
|
1984
2083
|
const stopPruning = () => {
|
|
1985
2084
|
pruneGeneration += 1;
|
|
2085
|
+
for (const observation of pendingPreservations.values())
|
|
2086
|
+
forgetPreservation(observation);
|
|
1986
2087
|
if (pruneInterval)
|
|
1987
2088
|
clearInterval(pruneInterval);
|
|
1988
2089
|
pruneInterval = undefined;
|
|
@@ -1997,92 +2098,146 @@ export function createTelegramBusLeaderRuntime(deps) {
|
|
|
1997
2098
|
}
|
|
1998
2099
|
};
|
|
1999
2100
|
const pruneFollowers = async (expectedGeneration) => {
|
|
2000
|
-
const
|
|
2101
|
+
const epoch = deps.getCurrentLeaderEpoch?.();
|
|
2102
|
+
const profile = deps.getTelegramProfile?.();
|
|
2103
|
+
const isCurrent = () => pruneGeneration === expectedGeneration &&
|
|
2104
|
+
deps.getTelegramProfile?.() === profile &&
|
|
2105
|
+
(!deps.getCurrentLeaderEpoch || (epoch !== undefined && deps.getCurrentLeaderEpoch() === epoch));
|
|
2001
2106
|
try {
|
|
2002
2107
|
await localServer.ensureEndpoint();
|
|
2003
2108
|
}
|
|
2004
2109
|
catch (error) {
|
|
2005
|
-
recordPruneEvent(error, {
|
|
2006
|
-
|
|
2007
|
-
|
|
2110
|
+
recordPruneEvent(error, { phase: "leader-endpoint-recovery" });
|
|
2111
|
+
}
|
|
2112
|
+
if (pruneGeneration !== expectedGeneration)
|
|
2113
|
+
return;
|
|
2114
|
+
for (const observation of pendingPreservations.values()) {
|
|
2115
|
+
if (!observation.isCurrent())
|
|
2116
|
+
forgetPreservation(observation);
|
|
2008
2117
|
}
|
|
2009
2118
|
if (!isCurrent())
|
|
2010
2119
|
return;
|
|
2120
|
+
const attempts = [...pendingPreservations.values()].map((observation) => ({
|
|
2121
|
+
follower: observation.follower, observation, fresh: false,
|
|
2122
|
+
}));
|
|
2011
2123
|
const removed = deps.followerRegistry.pruneStale(getNowMs(), followerStaleAfterMs);
|
|
2012
2124
|
for (const follower of removed) {
|
|
2125
|
+
forgetPreservation(pendingPreservations.get(follower.instanceId));
|
|
2126
|
+
let observation;
|
|
2127
|
+
if (deps.onFollowerConfirmedDeadPreserved && epoch !== undefined &&
|
|
2128
|
+
follower.registrationGeneration && Number.isSafeInteger(follower.pid) && follower.pid > 0 &&
|
|
2129
|
+
Number.isSafeInteger(follower.target?.threadId) && follower.target.threadId > 0) {
|
|
2130
|
+
if (pendingPreservations.size < TELEGRAM_WORKSPACE_SLOTS.length) {
|
|
2131
|
+
const registration = deps.followerRegistry.observeUnregistered(follower);
|
|
2132
|
+
let current = true;
|
|
2133
|
+
observation = {
|
|
2134
|
+
follower, registration,
|
|
2135
|
+
// Reuse exact admission authority after a lost acquisition acknowledgement.
|
|
2136
|
+
operationId: createTelegramWorkspaceAdmissionOperationId(),
|
|
2137
|
+
isCurrent() {
|
|
2138
|
+
current = current && isCurrent() && registration.isCurrent();
|
|
2139
|
+
return current;
|
|
2140
|
+
},
|
|
2141
|
+
};
|
|
2142
|
+
pendingPreservations.set(follower.instanceId, observation);
|
|
2143
|
+
}
|
|
2144
|
+
else {
|
|
2145
|
+
recordPruneEvent("Telegram follower preservation observation capacity exhausted; retaining binding", {
|
|
2146
|
+
phase: "follower-preservation-capacity", instanceId: follower.instanceId,
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
attempts.push({ follower, observation, fresh: true });
|
|
2151
|
+
}
|
|
2152
|
+
for (const { follower, observation, fresh } of attempts) {
|
|
2153
|
+
if (!isCurrent())
|
|
2154
|
+
return;
|
|
2155
|
+
if (observation && !observation.isCurrent()) {
|
|
2156
|
+
forgetPreservation(observation);
|
|
2157
|
+
continue;
|
|
2158
|
+
}
|
|
2013
2159
|
let processConfirmedDead = false;
|
|
2014
|
-
if (follower.pid !== undefined &&
|
|
2160
|
+
if (follower.pid !== undefined && Number.isSafeInteger(follower.pid) &&
|
|
2161
|
+
follower.pid > 0 && deps.isFollowerProcessAlive) {
|
|
2015
2162
|
try {
|
|
2016
|
-
processConfirmedDead =
|
|
2163
|
+
processConfirmedDead = deps.isFollowerProcessAlive(follower.pid) === false;
|
|
2017
2164
|
}
|
|
2018
2165
|
catch (error) {
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
});
|
|
2166
|
+
if (fresh)
|
|
2167
|
+
recordPruneEvent(error, {
|
|
2168
|
+
phase: "follower-process-liveness", instanceId: follower.instanceId, pid: follower.pid,
|
|
2169
|
+
});
|
|
2024
2170
|
}
|
|
2025
2171
|
}
|
|
2026
2172
|
if (!processConfirmedDead) {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
: "alive-or-unknown",
|
|
2033
|
-
});
|
|
2173
|
+
if (fresh)
|
|
2174
|
+
recordPruneEvent("Telegram bus follower heartbeat stale; preserving thread binding", {
|
|
2175
|
+
phase: "follower-pruned", instanceId: follower.instanceId,
|
|
2176
|
+
processLiveness: follower.pid === undefined || !deps.isFollowerProcessAlive ? "unknown" : "alive-or-unknown",
|
|
2177
|
+
});
|
|
2034
2178
|
continue;
|
|
2035
2179
|
}
|
|
2036
|
-
let cleanupEnabled
|
|
2180
|
+
let cleanupEnabled;
|
|
2037
2181
|
try {
|
|
2038
|
-
cleanupEnabled =
|
|
2039
|
-
(await deps.shouldCleanupConfirmedDeadFollower?.()) ?? false;
|
|
2182
|
+
cleanupEnabled = await deps.shouldCleanupConfirmedDeadFollower?.();
|
|
2040
2183
|
}
|
|
2041
2184
|
catch (error) {
|
|
2042
2185
|
recordPruneEvent(error, {
|
|
2043
|
-
phase: "follower-confirmed-dead-cleanup-policy",
|
|
2044
|
-
instanceId: follower.instanceId,
|
|
2045
|
-
pid: follower.pid,
|
|
2186
|
+
phase: "follower-confirmed-dead-cleanup-policy", instanceId: follower.instanceId, pid: follower.pid,
|
|
2046
2187
|
});
|
|
2047
2188
|
}
|
|
2048
|
-
if (!isCurrent())
|
|
2189
|
+
if (!isCurrent()) {
|
|
2190
|
+
forgetPreservation(observation);
|
|
2049
2191
|
return;
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2192
|
+
}
|
|
2193
|
+
const recordPreserved = () => recordPruneEvent("Telegram bus follower process confirmed dead; preserving thread binding", { phase: "follower-confirmed-dead-preserved", instanceId: follower.instanceId,
|
|
2194
|
+
pid: follower.pid, cleanupEnabled });
|
|
2195
|
+
// Deferred observations authorize only non-destructive publication, never deletion retries.
|
|
2196
|
+
if (cleanupEnabled === true && !fresh) {
|
|
2197
|
+
forgetPreservation(observation);
|
|
2198
|
+
recordPreserved();
|
|
2057
2199
|
continue;
|
|
2058
2200
|
}
|
|
2201
|
+
const onConfirmedDead = cleanupEnabled === true ? deps.onFollowerConfirmedDead :
|
|
2202
|
+
cleanupEnabled === false && observation ? deps.onFollowerConfirmedDeadPreserved : undefined;
|
|
2203
|
+
if (!onConfirmedDead) {
|
|
2204
|
+
if (cleanupEnabled === true)
|
|
2205
|
+
forgetPreservation(observation);
|
|
2206
|
+
if (fresh)
|
|
2207
|
+
recordPreserved();
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
const findReplacement = () => deps.followerRegistry.list().find((candidate) => candidate.instanceId === follower.instanceId ||
|
|
2211
|
+
(!!follower.profileKey && candidate.profileKey === follower.profileKey) ||
|
|
2212
|
+
(!!follower.target && candidate.target?.chatId === follower.target.chatId &&
|
|
2213
|
+
candidate.target?.threadId === follower.target.threadId));
|
|
2214
|
+
let settled = false;
|
|
2059
2215
|
try {
|
|
2060
2216
|
await runFollowerMutation(follower, async () => {
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
? candidate.profileKey === follower.profileKey
|
|
2065
|
-
: candidate.instanceId === follower.instanceId);
|
|
2066
|
-
if (replacement) {
|
|
2067
|
-
recordPruneEvent("Telegram bus follower replaced before confirmed-dead cleanup; preserving thread binding", {
|
|
2068
|
-
phase: "follower-confirmed-dead-replaced",
|
|
2069
|
-
instanceId: follower.instanceId,
|
|
2070
|
-
replacementInstanceId: replacement.instanceId,
|
|
2071
|
-
});
|
|
2217
|
+
const isDetached = () => isCurrent() && (!observation || observation.isCurrent()) &&
|
|
2218
|
+
!findReplacement() && deps.isFollowerProcessAlive?.(follower.pid) === false;
|
|
2219
|
+
if (!isDetached())
|
|
2072
2220
|
return;
|
|
2073
|
-
|
|
2074
|
-
|
|
2221
|
+
if (cleanupEnabled === true)
|
|
2222
|
+
await deps.onFollowerConfirmedDead(follower);
|
|
2223
|
+
else
|
|
2224
|
+
settled = await deps.onFollowerConfirmedDeadPreserved(follower, isDetached, observation.operationId);
|
|
2075
2225
|
});
|
|
2226
|
+
if (!cleanupEnabled)
|
|
2227
|
+
recordPreserved();
|
|
2076
2228
|
}
|
|
2077
2229
|
catch (error) {
|
|
2078
2230
|
recordPruneEvent(error, {
|
|
2079
|
-
phase: "follower-confirmed-dead-cleanup",
|
|
2080
|
-
instanceId: follower.instanceId,
|
|
2081
|
-
|
|
2082
|
-
chatId: follower.target?.chatId,
|
|
2083
|
-
threadId: follower.target?.threadId,
|
|
2231
|
+
phase: cleanupEnabled ? "follower-confirmed-dead-cleanup" : "follower-confirmed-dead-preserve",
|
|
2232
|
+
instanceId: follower.instanceId, pid: follower.pid,
|
|
2233
|
+
chatId: follower.target?.chatId, threadId: follower.target?.threadId,
|
|
2084
2234
|
});
|
|
2085
2235
|
}
|
|
2236
|
+
finally {
|
|
2237
|
+
if (settled || cleanupEnabled === true || (observation && !observation.isCurrent())) {
|
|
2238
|
+
forgetPreservation(observation);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2086
2241
|
}
|
|
2087
2242
|
};
|
|
2088
2243
|
const requestPrune = () => {
|
|
@@ -2121,7 +2276,11 @@ export function createTelegramBusLeaderRuntime(deps) {
|
|
|
2121
2276
|
resolveAgentTarget: deps.resolveAgentTarget,
|
|
2122
2277
|
routeAgentMessage: deps.routeAgentMessage,
|
|
2123
2278
|
routeQueueHandoff: deps.routeQueueHandoff,
|
|
2124
|
-
provisionFollowerTarget: deps.provisionFollowerTarget,
|
|
2279
|
+
provisionFollowerTarget: deps.provisionFollowerTarget ? (registration, options) => {
|
|
2280
|
+
// Same-instance ownership can change in the store before registry publication.
|
|
2281
|
+
forgetPreservation(pendingPreservations.get(registration.instanceId));
|
|
2282
|
+
return deps.provisionFollowerTarget(registration, options);
|
|
2283
|
+
} : undefined,
|
|
2125
2284
|
onFollowerDisconnected: deps.onFollowerDisconnected,
|
|
2126
2285
|
renameFollowerThread: deps.renameFollowerThread,
|
|
2127
2286
|
resetFollowerThreadName: deps.resetFollowerThreadName,
|
|
@@ -2132,6 +2291,7 @@ export function createTelegramBusLeaderRuntime(deps) {
|
|
|
2132
2291
|
getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
|
|
2133
2292
|
runFollowerMutation,
|
|
2134
2293
|
runWorkspaceAdmission: deps.runWorkspaceAdmission,
|
|
2294
|
+
runWithWorkspaceCapacity: deps.runWithWorkspaceCapacity,
|
|
2135
2295
|
});
|
|
2136
2296
|
const routeQueueHandoffEnvelope = async (input) => {
|
|
2137
2297
|
const recipient = deps.followerRegistry.get(input.recipientInstanceId);
|
|
@@ -512,6 +512,10 @@ export interface TelegramBusFollowerRegistry {
|
|
|
512
512
|
list: () => TelegramBusFollowerView[];
|
|
513
513
|
remove: (instanceId: string) => boolean;
|
|
514
514
|
clear: () => void;
|
|
515
|
+
observeUnregistered: (follower: TelegramBusFollowerView) => {
|
|
516
|
+
isCurrent: () => boolean;
|
|
517
|
+
release: () => void;
|
|
518
|
+
};
|
|
515
519
|
pruneStale: (nowMs: number, staleAfterMs: number) => TelegramBusFollowerView[];
|
|
516
520
|
}
|
|
517
521
|
export declare function createTelegramBusForwardOwnershipValidator(registry: Pick<TelegramBusFollowerRegistry, "get">): (ownership: TelegramBusForwardOwnership) => boolean;
|
|
@@ -1317,8 +1317,15 @@ export function createTelegramBusForwardOwnershipValidator(registry) {
|
|
|
1317
1317
|
} : undefined);
|
|
1318
1318
|
};
|
|
1319
1319
|
}
|
|
1320
|
+
function hasTelegramBusFollowerIdentityOverlap(first, second) {
|
|
1321
|
+
return first.instanceId === second.instanceId ||
|
|
1322
|
+
(first.profileKey !== undefined && first.profileKey === second.profileKey) ||
|
|
1323
|
+
(first.target !== undefined && first.target.chatId === second.target?.chatId &&
|
|
1324
|
+
first.target.threadId === second.target.threadId);
|
|
1325
|
+
}
|
|
1320
1326
|
export function createTelegramBusFollowerRegistry() {
|
|
1321
1327
|
const followers = new Map();
|
|
1328
|
+
const observations = new Set();
|
|
1322
1329
|
const clone = (follower) => ({
|
|
1323
1330
|
...follower,
|
|
1324
1331
|
target: follower.target ? { ...follower.target } : undefined,
|
|
@@ -1337,12 +1344,7 @@ export function createTelegramBusFollowerRegistry() {
|
|
|
1337
1344
|
for (const [instanceId, follower] of followers.entries()) {
|
|
1338
1345
|
if (instanceId === registration.instanceId)
|
|
1339
1346
|
continue;
|
|
1340
|
-
|
|
1341
|
-
registration.profileKey === follower.profileKey;
|
|
1342
|
-
const sameTarget = registration.target !== undefined &&
|
|
1343
|
-
follower.target?.chatId === registration.target.chatId &&
|
|
1344
|
-
follower.target.threadId === registration.target.threadId;
|
|
1345
|
-
if (sameProfile || sameTarget)
|
|
1347
|
+
if (hasTelegramBusFollowerIdentityOverlap(registration, follower))
|
|
1346
1348
|
followers.delete(instanceId);
|
|
1347
1349
|
}
|
|
1348
1350
|
const next = {
|
|
@@ -1359,6 +1361,12 @@ export function createTelegramBusFollowerRegistry() {
|
|
|
1359
1361
|
lastHeartbeatMs: existing?.lastHeartbeatMs ?? registration.connectedAtMs,
|
|
1360
1362
|
};
|
|
1361
1363
|
followers.set(registration.instanceId, next);
|
|
1364
|
+
for (const observation of observations) {
|
|
1365
|
+
if (!hasTelegramBusFollowerIdentityOverlap(next, observation.follower))
|
|
1366
|
+
continue;
|
|
1367
|
+
observation.current = false;
|
|
1368
|
+
observations.delete(observation);
|
|
1369
|
+
}
|
|
1362
1370
|
return clone(next);
|
|
1363
1371
|
},
|
|
1364
1372
|
heartbeat: (instanceId, nowMs) => {
|
|
@@ -1384,7 +1392,26 @@ export function createTelegramBusFollowerRegistry() {
|
|
|
1384
1392
|
},
|
|
1385
1393
|
list: () => [...followers.values()].map(clone),
|
|
1386
1394
|
remove: (instanceId) => followers.delete(instanceId),
|
|
1387
|
-
clear: () =>
|
|
1395
|
+
clear: () => {
|
|
1396
|
+
followers.clear();
|
|
1397
|
+
for (const observation of observations)
|
|
1398
|
+
observation.current = false;
|
|
1399
|
+
observations.clear();
|
|
1400
|
+
},
|
|
1401
|
+
observeUnregistered: (follower) => {
|
|
1402
|
+
// This watches replacement, not process death or delivery authority.
|
|
1403
|
+
const observation = { follower: clone(follower), current: ![...followers.values()]
|
|
1404
|
+
.some((current) => hasTelegramBusFollowerIdentityOverlap(current, follower)) };
|
|
1405
|
+
if (observation.current)
|
|
1406
|
+
observations.add(observation);
|
|
1407
|
+
return {
|
|
1408
|
+
isCurrent: () => observation.current,
|
|
1409
|
+
release() {
|
|
1410
|
+
observation.current = false;
|
|
1411
|
+
observations.delete(observation);
|
|
1412
|
+
},
|
|
1413
|
+
};
|
|
1414
|
+
},
|
|
1388
1415
|
pruneStale: (nowMs, staleAfterMs) => {
|
|
1389
1416
|
const removed = [];
|
|
1390
1417
|
for (const [instanceId, follower] of followers.entries()) {
|
|
@@ -79,7 +79,7 @@ export default function (pi) {
|
|
|
79
79
|
const workspaceAdmissionRuntime = WorkspaceAdmission.createTelegramWorkspaceAdmissionRuntimeBinding({
|
|
80
80
|
getProfileName: configStore.getActiveProfileName,
|
|
81
81
|
getBotToken: configStore.getBotToken,
|
|
82
|
-
getPath: Paths.
|
|
82
|
+
getPath: Paths.resolveTelegramWorkspaceAdmissionPathForProfile,
|
|
83
83
|
owner: {
|
|
84
84
|
processId: telegramProcessId,
|
|
85
85
|
processBirthId: telegramQueueProcessBirthId,
|
|
@@ -146,7 +146,7 @@ export default function (pi) {
|
|
|
146
146
|
},
|
|
147
147
|
getWorkspaceAdmission: workspaceAdmissionRuntime.resolve,
|
|
148
148
|
},
|
|
149
|
-
getLeaderJournalPath: Paths.
|
|
149
|
+
getLeaderJournalPath: Paths.resolveTelegramUpdateJournalPathForProfile,
|
|
150
150
|
getFollowerJournalPath(bindingKey, profileName) {
|
|
151
151
|
return Paths.resolveTelegramFollowerJournalPath(bindingKey, undefined, profileName);
|
|
152
152
|
},
|
|
@@ -295,6 +295,45 @@ export default function (pi) {
|
|
|
295
295
|
: "clear";
|
|
296
296
|
},
|
|
297
297
|
});
|
|
298
|
+
const reclaimTelegramWorkspaceDeadOwnerQueue = WorkspaceRetirement.createTelegramWorkspaceDeadOwnerQueueReclaimer({
|
|
299
|
+
getExternalProtection: captureWorkspaceExternalProtection,
|
|
300
|
+
getActiveTurnTarget: activeTurnRuntime.getTarget,
|
|
301
|
+
getQueuedItems: telegramQueueStore.getQueuedItems,
|
|
302
|
+
resolveLeaderJournal: resolveTelegramUpdateJournalBinding,
|
|
303
|
+
createFollowerJournalResolver: telegramJournalBindingRuntime.createRecipientResolver,
|
|
304
|
+
discoverFollowerJournals() {
|
|
305
|
+
return Journal.discoverTelegramFollowerJournalPaths({
|
|
306
|
+
directory: Paths.resolveTelegramTempDir(),
|
|
307
|
+
profileName: configStore.getActiveProfileName(),
|
|
308
|
+
});
|
|
309
|
+
},
|
|
310
|
+
createJournalPathResolver: telegramJournalBindingRuntime.createPathResolver,
|
|
311
|
+
withJournalReference(binding, operation) {
|
|
312
|
+
if (!binding.recoveryKey)
|
|
313
|
+
throw new Error("Telegram workspace journal reference identity is unavailable.");
|
|
314
|
+
return telegramJournalReferenceRegistry.withReference({
|
|
315
|
+
referenceClass: "workspace-retirement",
|
|
316
|
+
recoveryKey: binding.recoveryKey,
|
|
317
|
+
}, operation);
|
|
318
|
+
},
|
|
319
|
+
getRecoveryOwner() {
|
|
320
|
+
return {
|
|
321
|
+
instanceId: telegramInstanceId,
|
|
322
|
+
processId: telegramProcessId,
|
|
323
|
+
processBirthId: telegramQueueProcessBirthId,
|
|
324
|
+
sessionGeneration: telegramSessionContextStore.getGeneration(),
|
|
325
|
+
};
|
|
326
|
+
},
|
|
327
|
+
getQueueOwnerLiveness: Bus.getTelegramProcessLiveness,
|
|
328
|
+
onMutationError(error) {
|
|
329
|
+
recordRuntimeEvent("Telegram dead-owner queue reclamation was refused.", {
|
|
330
|
+
error: error instanceof Error ? error.message : String(error),
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
isBindingCurrent(binding) {
|
|
334
|
+
return WorkspaceRetirement.isCurrentTelegramWorkspaceBinding(threadStore, binding);
|
|
335
|
+
},
|
|
336
|
+
});
|
|
298
337
|
const inactiveThreadCleanupReviewRuntime = ThreadCleanupManager.createTelegramInactiveThreadCleanupReviewRuntime({
|
|
299
338
|
getProfileName() { return configStore.getActiveProfileName() ?? "default"; },
|
|
300
339
|
listBindings: threadStore.listWorkspaceBindings,
|
|
@@ -1169,6 +1208,18 @@ export default function (pi) {
|
|
|
1169
1208
|
getWorkspaceAdmission: workspaceAdmissionRuntime.resolve,
|
|
1170
1209
|
runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
|
|
1171
1210
|
captureWorkspaceExternalProtection,
|
|
1211
|
+
workspaceRotation: {
|
|
1212
|
+
getAdmission: workspaceAdmissionRuntime.resolve,
|
|
1213
|
+
runExclusive: telegramWorkspaceOperationRuntime.runExclusive,
|
|
1214
|
+
deleteThread: directTelegramApiRuntime.deleteWorkspaceThread,
|
|
1215
|
+
reclaimDeadOwnerQueuedWork(binding, isCurrent) {
|
|
1216
|
+
return telegramWorkspaceOperationRuntime.run({
|
|
1217
|
+
operationId: WorkspaceAdmission.createTelegramWorkspaceAdmissionOperationId(),
|
|
1218
|
+
operationKind: "workspace.reclaim-dead-owner-queue",
|
|
1219
|
+
scopes: [{ kind: "target", target: binding.target }],
|
|
1220
|
+
}, reclaimTelegramWorkspaceDeadOwnerQueue.bind(undefined, binding, isCurrent));
|
|
1221
|
+
},
|
|
1222
|
+
},
|
|
1172
1223
|
callApi(method, body, options) {
|
|
1173
1224
|
return directTelegramApiRuntime.call(method, body, options);
|
|
1174
1225
|
},
|
|
@@ -1282,6 +1333,15 @@ export default function (pi) {
|
|
|
1282
1333
|
recordRuntimeEvent,
|
|
1283
1334
|
runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
|
|
1284
1335
|
});
|
|
1336
|
+
const prepareThreadPreservationOnQuit = Sync.createTelegramPreservedLeaderQuitHandler({
|
|
1337
|
+
instanceId: telegramInstanceId,
|
|
1338
|
+
topicTargetStore: threadStore,
|
|
1339
|
+
getCurrentLeaderEpoch,
|
|
1340
|
+
getProfileName: configStore.getActiveProfileName,
|
|
1341
|
+
isPollingSuspended: lockedPollingRuntime.isSuspended,
|
|
1342
|
+
resolveAutomaticThreadCleanupEnabled: configControls.resolveAutomaticThreadCleanupEnabled,
|
|
1343
|
+
runWorkspaceOperation: telegramWorkspaceOperationRuntime.run,
|
|
1344
|
+
});
|
|
1285
1345
|
const telegramBridgeSessionLifecycleDeps = Lifecycle.createTelegramBridgeSessionLifecycleDeps({
|
|
1286
1346
|
contextStore: telegramSessionContextStore,
|
|
1287
1347
|
queue: {
|
|
@@ -1335,6 +1395,7 @@ export default function (pi) {
|
|
|
1335
1395
|
capabilityMonitor: telegramThreadCapabilityMonitor,
|
|
1336
1396
|
queueWatchdog: queueDispatchWatchdogRuntime,
|
|
1337
1397
|
guestPlaceholder: { stopAll: guestPlaceholderRuntime.stopAll },
|
|
1398
|
+
prepareThreadPreservationOnQuit,
|
|
1338
1399
|
},
|
|
1339
1400
|
});
|
|
1340
1401
|
const sessionLifecycleRuntime = Lifecycle.createTelegramBridgeSessionLifecycleAssembly(telegramBridgeSessionLifecycleDeps);
|