@llblab/pi-telegram 0.52.2 → 0.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -7
- package/BACKLOG.md +1 -18
- package/CHANGELOG.md +15 -0
- package/README.md +7 -11
- package/api/commands.ts +3 -0
- package/dist/api/commands.d.ts +1 -1
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +15 -42
- package/dist/lib/activity.d.ts +9 -0
- package/dist/lib/activity.js +39 -10
- package/dist/lib/agent-messages.js +7 -3
- package/dist/lib/bindings.d.ts +43 -5
- package/dist/lib/bindings.js +163 -20
- package/dist/lib/bus-api.d.ts +12 -0
- package/dist/lib/bus-api.js +182 -79
- package/dist/lib/bus-follower.d.ts +111 -31
- package/dist/lib/bus-follower.js +1324 -359
- package/dist/lib/bus-leader.d.ts +41 -91
- package/dist/lib/bus-leader.js +969 -637
- package/dist/lib/bus-transport.js +16 -9
- package/dist/lib/bus.d.ts +215 -18
- package/dist/lib/bus.js +1048 -327
- package/dist/lib/channel-posts.d.ts +5 -0
- package/dist/lib/channel-posts.js +434 -261
- package/dist/lib/command-templates.d.ts +26 -5
- package/dist/lib/command-templates.js +92 -176
- package/dist/lib/commands.d.ts +168 -82
- package/dist/lib/commands.js +1409 -291
- package/dist/lib/config.d.ts +1 -8
- package/dist/lib/config.js +34 -76
- package/dist/lib/delivery.d.ts +2 -2
- package/dist/lib/delivery.js +16 -16
- package/dist/lib/extension.js +639 -171
- package/dist/lib/generative-app-worker.mjs +19 -7
- package/dist/lib/generative-apps.d.ts +0 -5
- package/dist/lib/generative-apps.js +61 -49
- package/dist/lib/inbound.d.ts +5 -0
- package/dist/lib/inbound.js +35 -59
- package/dist/lib/journal.d.ts +33 -3
- package/dist/lib/journal.js +2063 -782
- package/dist/lib/lifecycle.d.ts +2 -6
- package/dist/lib/lifecycle.js +75 -58
- package/dist/lib/locks.d.ts +29 -3
- package/dist/lib/locks.js +289 -108
- package/dist/lib/logging.js +50 -14
- package/dist/lib/media.d.ts +22 -1
- package/dist/lib/media.js +46 -28
- package/dist/lib/menu-model.d.ts +19 -5
- package/dist/lib/menu-model.js +53 -11
- package/dist/lib/menu-queue.d.ts +5 -4
- package/dist/lib/menu-queue.js +45 -63
- package/dist/lib/menu-settings.d.ts +5 -4
- package/dist/lib/menu-settings.js +114 -78
- package/dist/lib/menu-status.d.ts +1 -1
- package/dist/lib/menu-status.js +8 -25
- package/dist/lib/menu-thinking.d.ts +6 -1
- package/dist/lib/menu-thinking.js +14 -21
- package/dist/lib/menu.d.ts +8 -61
- package/dist/lib/menu.js +92 -164
- package/dist/lib/model.d.ts +1 -1
- package/dist/lib/model.js +2 -16
- package/dist/lib/outbound-attachments.d.ts +6 -27
- package/dist/lib/outbound-attachments.js +45 -38
- package/dist/lib/outbound-buttons.js +22 -16
- package/dist/lib/outbound-markup.d.ts +0 -2
- package/dist/lib/outbound-markup.js +51 -64
- package/dist/lib/outbound-voice.js +6 -3
- package/dist/lib/outbound.d.ts +6 -6
- package/dist/lib/outbound.js +65 -51
- package/dist/lib/ownership.js +6 -2
- package/dist/lib/paths.d.ts +5 -0
- package/dist/lib/paths.js +37 -21
- package/dist/lib/pi.d.ts +2 -0
- package/dist/lib/pi.js +8 -1
- package/dist/lib/polling.d.ts +21 -37
- package/dist/lib/polling.js +59 -50
- package/dist/lib/preview.js +39 -19
- package/dist/lib/process-identity.d.ts +24 -0
- package/dist/lib/process-identity.js +133 -0
- package/dist/lib/prompts.js +2 -7
- package/dist/lib/queue.d.ts +14 -16
- package/dist/lib/queue.js +252 -127
- package/dist/lib/recovery.d.ts +2 -83
- package/dist/lib/recovery.js +20 -274
- package/dist/lib/replies.d.ts +16 -24
- package/dist/lib/replies.js +99 -116
- package/dist/lib/routing.d.ts +120 -7
- package/dist/lib/routing.js +4667 -1152
- package/dist/lib/runtime.d.ts +1 -11
- package/dist/lib/sections.d.ts +4 -29
- package/dist/lib/sections.js +7 -20
- package/dist/lib/skills.js +4 -3
- package/dist/lib/status.js +86 -57
- package/dist/lib/sync.d.ts +1 -3
- package/dist/lib/sync.js +67 -43
- package/dist/lib/target.d.ts +4 -0
- package/dist/lib/target.js +18 -0
- package/dist/lib/telegram-api.d.ts +18 -13
- package/dist/lib/telegram-api.js +148 -53
- package/dist/lib/text-groups.js +4 -39
- package/dist/lib/thread-cleanup-manager.d.ts +6 -27
- package/dist/lib/thread-cleanup-manager.js +369 -186
- package/dist/lib/thread-display.d.ts +1 -2
- package/dist/lib/thread-display.js +58 -38
- package/dist/lib/thread-naming.d.ts +28 -8
- package/dist/lib/thread-naming.js +175 -66
- package/dist/lib/thread-reconciler.d.ts +42 -16
- package/dist/lib/thread-reconciler.js +90 -50
- package/dist/lib/threads.d.ts +82 -20
- package/dist/lib/threads.js +2247 -855
- package/dist/lib/time-injection.js +1 -2
- package/dist/lib/turns.js +1 -19
- package/dist/lib/updates.d.ts +169 -80
- package/dist/lib/updates.js +2418 -641
- package/dist/lib/voice.d.ts +7 -22
- package/dist/lib/voice.js +7 -22
- package/dist/lib/wire.js +1 -1
- package/dist/lib/workspace-admission.d.ts +3 -1
- package/dist/lib/workspace-admission.js +425 -211
- package/dist/lib/workspace-identity.js +8 -13
- package/dist/lib/workspace-retirement.d.ts +1 -1
- package/dist/lib/workspace-retirement.js +328 -140
- package/dist/lib/workspace-slots.js +8 -4
- package/dist/package.json +1 -1
- package/docs/architecture.md +31 -29
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +15 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +29 -1
- package/docs/ui-style.md +25 -8
- package/lib/activity-verbosity.ts +59 -82
- package/lib/activity.ts +146 -102
- package/lib/agent-messages.ts +12 -5
- package/lib/bindings.ts +389 -82
- package/lib/bus-api.ts +263 -102
- package/lib/bus-follower.ts +2440 -601
- package/lib/bus-leader.ts +2209 -1290
- package/lib/bus-transport.ts +46 -12
- package/lib/bus.ts +1673 -444
- package/lib/channel-posts.ts +848 -327
- package/lib/command-templates.ts +152 -265
- package/lib/commands.ts +2398 -555
- package/lib/config.ts +177 -170
- package/lib/delivery.ts +41 -29
- package/lib/extension.ts +1206 -433
- package/lib/generative-app-worker.mjs +19 -7
- package/lib/generative-apps.ts +324 -211
- package/lib/inbound.ts +95 -97
- package/lib/journal.ts +5311 -1763
- package/lib/keyboard.ts +1 -3
- package/lib/lifecycle.ts +128 -78
- package/lib/locks.ts +677 -203
- package/lib/logging.ts +147 -58
- package/lib/media.ts +82 -43
- package/lib/menu-model.ts +85 -18
- package/lib/menu-queue.ts +88 -129
- package/lib/menu-settings.ts +260 -155
- package/lib/menu-status.ts +20 -60
- package/lib/menu-thinking.ts +41 -56
- package/lib/menu.ts +112 -285
- package/lib/model.ts +8 -22
- package/lib/outbound-attachments.ts +156 -125
- package/lib/outbound-buttons.ts +76 -49
- package/lib/outbound-markup.ts +96 -94
- package/lib/outbound-voice.ts +33 -27
- package/lib/outbound.ts +128 -125
- package/lib/ownership.ts +19 -10
- package/lib/paths.ts +148 -49
- package/lib/pi.ts +35 -15
- package/lib/polling.ts +144 -147
- package/lib/preview.ts +93 -40
- package/lib/process-identity.ts +164 -0
- package/lib/prompts.ts +4 -11
- package/lib/queue.ts +516 -301
- package/lib/recovery.ts +50 -454
- package/lib/rendering.ts +1 -3
- package/lib/replies.ts +248 -209
- package/lib/routing.ts +8509 -2186
- package/lib/runtime.ts +1 -12
- package/lib/sections.ts +24 -89
- package/lib/setup.ts +1 -3
- package/lib/skills.ts +9 -4
- package/lib/status.ts +215 -110
- package/lib/sync.ts +448 -361
- package/lib/target.ts +22 -3
- package/lib/telegram-api.ts +302 -127
- package/lib/text-groups.ts +5 -39
- package/lib/thread-cleanup-manager.ts +833 -331
- package/lib/thread-display.ts +213 -97
- package/lib/thread-naming.ts +232 -87
- package/lib/thread-reconciler.ts +155 -72
- package/lib/threads.ts +5318 -1845
- package/lib/time-injection.ts +1 -4
- package/lib/turns.ts +15 -28
- package/lib/updates.ts +5184 -1612
- package/lib/voice.ts +7 -35
- package/lib/wire.ts +1 -1
- package/lib/workspace-admission.ts +996 -416
- package/lib/workspace-identity.ts +14 -17
- package/lib/workspace-retirement.ts +878 -376
- package/lib/workspace-slots.ts +22 -13
- package/package.json +1 -1
- package/scripts/check-downgrade.mjs +0 -472
- package/scripts/measure-workspace.mjs +0 -108
|
@@ -8,7 +8,8 @@ const DEFAULT_FRESH_CREATION_GRACE_MS = 30_000;
|
|
|
8
8
|
function targetKey(target) {
|
|
9
9
|
return `${target.chatId}:${target.threadId}`;
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
/** A Thread record still claims its slot/target while active, starting or pending. */
|
|
12
|
+
export function isCurrentThreadRecord(record) {
|
|
12
13
|
return (record.status === "active" ||
|
|
13
14
|
record.status === "starting" ||
|
|
14
15
|
record.status === "pending");
|
|
@@ -50,13 +51,12 @@ function isCleanupAction(action) {
|
|
|
50
51
|
action.kind === "close-stale-replaced-topic" ||
|
|
51
52
|
action.kind === "close-delete-replaced-follower-topic" ||
|
|
52
53
|
action.kind === "close-delete-previous-leader-topic" ||
|
|
53
|
-
action.kind === "close-delete-disconnected-instance-topic" ||
|
|
54
54
|
action.kind === "close-delete-graceful-shutdown-topic" ||
|
|
55
55
|
action.kind === "cancel-superseded-graceful-shutdown-cleanup" ||
|
|
56
56
|
action.kind === "close-delete-expired-pending-provision-topic");
|
|
57
57
|
}
|
|
58
58
|
function isSyncAction(action) {
|
|
59
|
-
return action.kind === "mark-topic-active" || action.kind === "mark-topic-stale";
|
|
59
|
+
return (action.kind === "mark-topic-active" || action.kind === "mark-topic-stale");
|
|
60
60
|
}
|
|
61
61
|
function createThreadReconciliationMachineState(input, actions) {
|
|
62
62
|
const pendingProvisionCount = (input.pendingProvisions ?? []).filter((provision) => isPendingProvisionAlive(provision, input.nowMs, input.currentLeaderEpoch)).length;
|
|
@@ -101,26 +101,34 @@ function createThreadReconciliationTransition(previous, current) {
|
|
|
101
101
|
function getActionLeaderEpoch(action) {
|
|
102
102
|
return "leaderEpoch" in action ? action.leaderEpoch : undefined;
|
|
103
103
|
}
|
|
104
|
+
/** Pure Bot API vocabulary for a deleted/missing Thread; callers own their HTTP-status policy. */
|
|
105
|
+
export function isTelegramTopicDeletedErrorMessage(message) {
|
|
106
|
+
const text = message.toLowerCase();
|
|
107
|
+
return [
|
|
108
|
+
"topic_id_invalid",
|
|
109
|
+
"message thread not found",
|
|
110
|
+
"thread not found",
|
|
111
|
+
"topic not found",
|
|
112
|
+
"topic deleted",
|
|
113
|
+
].some((fragment) => text.includes(fragment));
|
|
114
|
+
}
|
|
115
|
+
/** Pure Bot API vocabulary for a closed Thread; closure is not deletion evidence. */
|
|
116
|
+
export function isTelegramTopicClosedErrorMessage(message) {
|
|
117
|
+
const text = message.toLowerCase();
|
|
118
|
+
return [
|
|
119
|
+
"topic closed",
|
|
120
|
+
"thread closed",
|
|
121
|
+
"forum topic closed",
|
|
122
|
+
"message thread closed",
|
|
123
|
+
].some((fragment) => text.includes(fragment));
|
|
124
|
+
}
|
|
104
125
|
function isTelegramTopicTargetDeletedOrMissingError(error) {
|
|
105
|
-
|
|
106
|
-
return false;
|
|
107
|
-
const message = error.message.toLowerCase();
|
|
108
|
-
return (message.includes("topic_id_invalid") ||
|
|
109
|
-
message.includes("message thread not found") ||
|
|
110
|
-
message.includes("thread not found") ||
|
|
111
|
-
message.includes("topic not found") ||
|
|
112
|
-
message.includes("topic deleted"));
|
|
126
|
+
return (error instanceof Error && isTelegramTopicDeletedErrorMessage(error.message));
|
|
113
127
|
}
|
|
114
128
|
function isTelegramTopicTargetGoneError(error) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return false;
|
|
119
|
-
const message = error.message.toLowerCase();
|
|
120
|
-
return (message.includes("topic closed") ||
|
|
121
|
-
message.includes("thread closed") ||
|
|
122
|
-
message.includes("forum topic closed") ||
|
|
123
|
-
message.includes("message thread closed"));
|
|
129
|
+
return (error instanceof Error &&
|
|
130
|
+
(isTelegramTopicDeletedErrorMessage(error.message) ||
|
|
131
|
+
isTelegramTopicClosedErrorMessage(error.message)));
|
|
124
132
|
}
|
|
125
133
|
function shouldSkipForStaleLeaderEpoch(action, ports) {
|
|
126
134
|
const actionLeaderEpoch = getActionLeaderEpoch(action);
|
|
@@ -178,21 +186,58 @@ export function createThreadReconciliationRuntime(deps) {
|
|
|
178
186
|
},
|
|
179
187
|
};
|
|
180
188
|
}
|
|
181
|
-
export function
|
|
189
|
+
export function prepareLiveRebindThreadCleanup(input) {
|
|
190
|
+
const { operationId, oldTarget, recipientTarget, leaderEpoch, work } = input;
|
|
191
|
+
const validTarget = (target) => Number.isSafeInteger(target?.chatId) &&
|
|
192
|
+
target.chatId !== 0 &&
|
|
193
|
+
Number.isSafeInteger(target.threadId) &&
|
|
194
|
+
target.threadId > 0;
|
|
195
|
+
if (!operationId?.trim() ||
|
|
196
|
+
!leaderEpoch?.trim() ||
|
|
197
|
+
!validTarget(oldTarget) ||
|
|
198
|
+
!validTarget(recipientTarget) ||
|
|
199
|
+
oldTarget.chatId !== recipientTarget.chatId ||
|
|
200
|
+
oldTarget.threadId === recipientTarget.threadId ||
|
|
201
|
+
input.targetProtected !== false ||
|
|
202
|
+
work?.sessionBusy !== false ||
|
|
203
|
+
work.targetWork !== false ||
|
|
204
|
+
work.deliveryPending !== false ||
|
|
205
|
+
work.unknown !== false)
|
|
206
|
+
return undefined;
|
|
182
207
|
return {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
instanceId: input.instanceId,
|
|
189
|
-
...(input.leaderEpoch !== undefined
|
|
190
|
-
? { leaderEpoch: input.leaderEpoch }
|
|
191
|
-
: {}),
|
|
192
|
-
},
|
|
193
|
-
],
|
|
208
|
+
status: "prepared",
|
|
209
|
+
operationId,
|
|
210
|
+
target: { ...oldTarget },
|
|
211
|
+
recipientTarget: { ...recipientTarget },
|
|
212
|
+
leaderEpoch,
|
|
194
213
|
};
|
|
195
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* One unretried deletion for an already issued live-rebind cleanup grant. Success or confirmed absence is `confirmed`;
|
|
217
|
+
* protection or authority loss before the request and a definite request rejection are `failed`; every other outcome is
|
|
218
|
+
* `unknown`. The caller owns the durable issue marker and terminal record; this never repeats or rolls back.
|
|
219
|
+
*/
|
|
220
|
+
export async function issueLiveRebindThreadCleanup(prepared, ports) {
|
|
221
|
+
const target = { ...prepared.target };
|
|
222
|
+
try {
|
|
223
|
+
ports.assertCurrent();
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return "failed";
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
await ports.deleteTopic(target);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
const failure = ports.classifyFailure(error);
|
|
233
|
+
return failure === "absent"
|
|
234
|
+
? "confirmed"
|
|
235
|
+
: failure === "rejected"
|
|
236
|
+
? "failed"
|
|
237
|
+
: "unknown";
|
|
238
|
+
}
|
|
239
|
+
return "confirmed";
|
|
240
|
+
}
|
|
196
241
|
export async function applyThreadReconciliationPlan(plan, ports) {
|
|
197
242
|
let shouldPersist = false;
|
|
198
243
|
const persistFences = [];
|
|
@@ -276,7 +321,6 @@ export async function applyThreadReconciliationPlan(plan, ports) {
|
|
|
276
321
|
action.kind === "close-delete-reserved-topic" ||
|
|
277
322
|
action.kind === "close-delete-replaced-follower-topic" ||
|
|
278
323
|
action.kind === "close-delete-previous-leader-topic" ||
|
|
279
|
-
action.kind === "close-delete-disconnected-instance-topic" ||
|
|
280
324
|
action.kind === "close-delete-graceful-shutdown-topic" ||
|
|
281
325
|
action.kind === "close-delete-expired-pending-provision-topic") {
|
|
282
326
|
if (shouldSkipForStaleLeaderEpoch(action, ports)) {
|
|
@@ -295,7 +339,9 @@ export async function applyThreadReconciliationPlan(plan, ports) {
|
|
|
295
339
|
}
|
|
296
340
|
let deleteConfirmed = false;
|
|
297
341
|
let superseded = false;
|
|
298
|
-
for (const method of ports.skipCloseBeforeDelete
|
|
342
|
+
for (const method of ports.skipCloseBeforeDelete
|
|
343
|
+
? ["deleteForumTopic"]
|
|
344
|
+
: ["closeForumTopic", "deleteForumTopic"]) {
|
|
299
345
|
if (ports.isCleanupTargetProtected?.(action.target, action)) {
|
|
300
346
|
superseded = true;
|
|
301
347
|
break;
|
|
@@ -331,7 +377,8 @@ export async function applyThreadReconciliationPlan(plan, ports) {
|
|
|
331
377
|
incompleteActions.push(action);
|
|
332
378
|
continue;
|
|
333
379
|
}
|
|
334
|
-
if (superseded ||
|
|
380
|
+
if (superseded ||
|
|
381
|
+
ports.isCleanupTargetProtected?.(action.target, action)) {
|
|
335
382
|
ports.recordRuntimeEvent?.("telegram", "Cancelled cleanup of a protected Telegram target", {
|
|
336
383
|
phase: "thread-reconciler-cleanup-target-reused",
|
|
337
384
|
action: action.kind,
|
|
@@ -356,7 +403,6 @@ export async function applyThreadReconciliationPlan(plan, ports) {
|
|
|
356
403
|
continue;
|
|
357
404
|
if (action.kind !== "close-delete-previous-leader-topic" &&
|
|
358
405
|
action.kind !== "close-delete-replaced-follower-topic" &&
|
|
359
|
-
action.kind !== "close-delete-disconnected-instance-topic" &&
|
|
360
406
|
action.kind !== "close-delete-expired-pending-provision-topic") {
|
|
361
407
|
const changed = ports.markStaleByTarget?.(action.target, "deleted") ?? false;
|
|
362
408
|
if (changed)
|
|
@@ -371,11 +417,9 @@ export async function applyThreadReconciliationPlan(plan, ports) {
|
|
|
371
417
|
? "Replaced follower Telegram topic deleted"
|
|
372
418
|
: action.kind === "close-delete-previous-leader-topic"
|
|
373
419
|
? "Previous leader Telegram topic deleted"
|
|
374
|
-
: action.kind === "close-delete-
|
|
375
|
-
? "
|
|
376
|
-
:
|
|
377
|
-
? "Graceful shutdown Telegram topic deleted"
|
|
378
|
-
: "Expired pending provision Telegram topic deleted", {
|
|
420
|
+
: action.kind === "close-delete-graceful-shutdown-topic"
|
|
421
|
+
? "Graceful shutdown Telegram topic deleted"
|
|
422
|
+
: "Expired pending provision Telegram topic deleted", {
|
|
379
423
|
phase: action.kind === "close-delete-unbound-topic"
|
|
380
424
|
? "thread-reconciler-unbound-topic-delete"
|
|
381
425
|
: action.kind === "close-delete-reserved-topic"
|
|
@@ -384,13 +428,9 @@ export async function applyThreadReconciliationPlan(plan, ports) {
|
|
|
384
428
|
? "thread-reconciler-replaced-follower-topic-delete"
|
|
385
429
|
: action.kind === "close-delete-previous-leader-topic"
|
|
386
430
|
? "thread-reconciler-previous-leader-topic-delete"
|
|
387
|
-
: action.kind ===
|
|
388
|
-
"
|
|
389
|
-
|
|
390
|
-
: action.kind ===
|
|
391
|
-
"close-delete-graceful-shutdown-topic"
|
|
392
|
-
? "thread-reconciler-graceful-shutdown-topic-delete"
|
|
393
|
-
: "thread-reconciler-expired-pending-provision-topic-delete",
|
|
431
|
+
: action.kind === "close-delete-graceful-shutdown-topic"
|
|
432
|
+
? "thread-reconciler-graceful-shutdown-topic-delete"
|
|
433
|
+
: "thread-reconciler-expired-pending-provision-topic-delete",
|
|
394
434
|
chatId: action.target.chatId,
|
|
395
435
|
threadId: action.target.threadId,
|
|
396
436
|
...("messageId" in action ? { messageId: action.messageId } : {}),
|
|
@@ -436,7 +476,7 @@ export function planThreadReconciliation(input) {
|
|
|
436
476
|
const graceMs = input.freshCreationGraceMs ?? DEFAULT_FRESH_CREATION_GRACE_MS;
|
|
437
477
|
const knownTargets = new Set(input.records.map((record) => targetKey(record.target)));
|
|
438
478
|
const currentTargets = new Set(input.records
|
|
439
|
-
.filter(
|
|
479
|
+
.filter(isCurrentThreadRecord)
|
|
440
480
|
.map((record) => targetKey(record.target)));
|
|
441
481
|
const reservedTargets = new Set((input.reservations ?? [])
|
|
442
482
|
.filter((reservation) => isReservationAlive(reservation, input.nowMs))
|
|
@@ -449,7 +489,7 @@ export function planThreadReconciliation(input) {
|
|
|
449
489
|
.map((observation) => targetKey(observation.target)));
|
|
450
490
|
const actions = [];
|
|
451
491
|
for (const cleanup of input.pendingCleanups ?? []) {
|
|
452
|
-
const superseded = input.records.some((record) =>
|
|
492
|
+
const superseded = input.records.some((record) => isCurrentThreadRecord(record) &&
|
|
453
493
|
targetKey(record.target) === targetKey(cleanup.target) &&
|
|
454
494
|
record.instanceId !== cleanup.instanceId);
|
|
455
495
|
const common = {
|
package/dist/lib/threads.d.ts
CHANGED
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
* Zones: multi-instance bus, Telegram UI threads, durable Workspace state
|
|
4
4
|
* Owns live mappings, Workspace bindings and guarded Restore transitions; routing and transport effects stay outside.
|
|
5
5
|
*/
|
|
6
|
-
import { type TelegramApiCallOptions } from "./telegram-api.ts";
|
|
7
6
|
import { type TelegramTarget } from "./target.ts";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { type TelegramWorkspaceBindingIdentity } from "./workspace-identity.ts";
|
|
11
|
-
export { createTelegramWorkspaceBindingIdentity, createTelegramWorkspaceDirectoryKey, normalizeTelegramSessionId, normalizeTelegramWorkspacePath, type TelegramWorkspaceBindingIdentity, } from "./workspace-identity.ts";
|
|
12
|
-
import { type TelegramLockRuntime, type TelegramLockContext, type TelegramRuntimeStateMutation, type TelegramOwnedStatePublicationOptions, type TelegramOwnedStatePublicationResult } from "./locks.ts";
|
|
7
|
+
import { type TelegramApiCallOptions } from "./telegram-api.ts";
|
|
8
|
+
import { type TelegramLockContext, type TelegramLockRuntime, type TelegramOwnedStatePublicationOptions, type TelegramOwnedStatePublicationResult, type TelegramRuntimeStateMutation } from "./locks.ts";
|
|
13
9
|
import * as ThreadReconciler from "./thread-reconciler.ts";
|
|
10
|
+
import { type TelegramWorkspaceBindingIdentity } from "./workspace-identity.ts";
|
|
14
11
|
import { type TelegramWorkspaceSlotOccupancy } from "./workspace-slots.ts";
|
|
12
|
+
export { createTelegramWorkspaceBindingIdentity, createTelegramWorkspaceDirectoryKey, normalizeTelegramSessionId, normalizeTelegramWorkspacePath, type TelegramWorkspaceBindingIdentity, } from "./workspace-identity.ts";
|
|
15
13
|
export type TelegramTopicTargetStatus = "active" | "offline" | "stale" | "pending" | "starting" | "probe-required" | "failed";
|
|
16
14
|
export type TelegramTopicSyncStatus = "open" | "closed" | "deleted" | "unknown";
|
|
17
15
|
export type TelegramThreadOwner = {
|
|
@@ -247,6 +245,20 @@ export interface TelegramWorkspaceRestoreIntent {
|
|
|
247
245
|
cleanup?: "issued" | "completed" | "not-issued";
|
|
248
246
|
};
|
|
249
247
|
}
|
|
248
|
+
/** Live same-session metadata, never a legacy scoped ACK or permission to dispatch/delete. */
|
|
249
|
+
export type TelegramWorkspaceLiveRebindIntent = Pick<TelegramWorkspaceRestoreIntent, "request" | "operatorUserId" | "executor" | "revision" | "createdAtMs" | "updatedAtMs"> & {
|
|
250
|
+
kind: "live-rebind";
|
|
251
|
+
recipient: TelegramWorkspaceRestoreRecipient;
|
|
252
|
+
} & ({
|
|
253
|
+
phase: "rebound";
|
|
254
|
+
cleanup?: never;
|
|
255
|
+
} | {
|
|
256
|
+
phase: "released";
|
|
257
|
+
cleanup?: "issued";
|
|
258
|
+
} | {
|
|
259
|
+
phase: "finished";
|
|
260
|
+
cleanup: "confirmed" | "failed" | "unknown" | "not-issued";
|
|
261
|
+
});
|
|
250
262
|
/** Immutable source membership, not readiness, cancellation, or proof of complete Thread coverage. */
|
|
251
263
|
export interface TelegramTemporaryThreadInput {
|
|
252
264
|
journalBindingKey: string;
|
|
@@ -304,11 +316,19 @@ interface TelegramWorkspaceRestoreSnapshot {
|
|
|
304
316
|
tokenSha256: string;
|
|
305
317
|
revision: number;
|
|
306
318
|
operations: TelegramWorkspaceRestoreIntent[];
|
|
319
|
+
liveRebindings?: TelegramWorkspaceLiveRebindIntent[];
|
|
307
320
|
temporaryThreads?: TelegramTemporaryThreadEntry[];
|
|
308
321
|
}
|
|
309
322
|
/** Scope-bound Workspace operations; raw snapshot mutation is private to the store. */
|
|
310
323
|
export interface TelegramWorkspaceRestore {
|
|
311
324
|
list(): TelegramWorkspaceRestoreIntent[];
|
|
325
|
+
listLiveRebindings(): TelegramWorkspaceLiveRebindIntent[];
|
|
326
|
+
/** Caller has saved and held the selected input; this publishes the binding, not dispatch readiness. */
|
|
327
|
+
commitLiveRebind(request: TelegramWorkspaceRestoreRequest, recipient: TelegramWorkspaceRestoreRecipient, authority: TelegramWorkspaceRestoreAuthority): Promise<TelegramWorkspaceLiveRebindIntent | undefined>;
|
|
328
|
+
/** Caller proves local apply for release or current live clearance for cleanup; unknown issuance never repeats. */
|
|
329
|
+
advanceLiveRebind(expected: TelegramWorkspaceLiveRebindIntent, step: "release" | "issue-cleanup" | Extract<TelegramWorkspaceLiveRebindIntent, {
|
|
330
|
+
phase: "finished";
|
|
331
|
+
}>["cleanup"], authority: TelegramWorkspaceRestoreAuthority): TelegramWorkspaceLiveRebindIntent | undefined;
|
|
312
332
|
commit(request: TelegramWorkspaceRestoreRequest, authority: TelegramWorkspaceRestoreAuthority): Promise<TelegramWorkspaceRestoreIntent | undefined>;
|
|
313
333
|
adopt(expected: TelegramWorkspaceRestoreIntent, authority: TelegramWorkspaceRestoreAuthority): TelegramWorkspaceRestoreIntent | undefined;
|
|
314
334
|
issueRecipient(expected: TelegramWorkspaceRestoreIntent, recipient: TelegramWorkspaceRestoreRecipient, authority: TelegramWorkspaceRestoreAuthority): {
|
|
@@ -376,6 +396,7 @@ export interface TelegramWorkspaceRestore {
|
|
|
376
396
|
forgetPreviousWorld(authority: TelegramWorkspaceRestoreAuthority, preserveTemporaryTokens?: readonly string[]): {
|
|
377
397
|
operations: TelegramWorkspaceRestoreIntent[];
|
|
378
398
|
temporaryThreads: TelegramTemporaryThreadEntry[];
|
|
399
|
+
liveRebindings?: TelegramWorkspaceLiveRebindIntent[];
|
|
379
400
|
} | undefined;
|
|
380
401
|
}
|
|
381
402
|
export interface TelegramWorkspaceRestoreOptions {
|
|
@@ -426,7 +447,8 @@ export interface TelegramTopicTargetStore {
|
|
|
426
447
|
load: () => Promise<void>;
|
|
427
448
|
/** Discard process-local projections and reload owner-published state. */
|
|
428
449
|
refresh?: () => Promise<void>;
|
|
429
|
-
|
|
450
|
+
/** Optional caller fence supplements, never replaces, captured publication authority. */
|
|
451
|
+
persist: (isCurrent?: () => boolean) => Promise<void>;
|
|
430
452
|
invalidateTarget: (target: TelegramTarget, isCurrent: () => boolean, lastSyncError: string) => Promise<boolean>;
|
|
431
453
|
/** Caller proves owner detachment; this does not assert Telegram Thread absence. */
|
|
432
454
|
detachTargetOwner: (expected: TelegramTopicTargetRecord, isCurrent: () => boolean) => Promise<boolean>;
|
|
@@ -441,7 +463,18 @@ export interface TelegramTopicTargetStore {
|
|
|
441
463
|
/** Runs synchronous live publication under the same evidence transaction; caller retains authentication/admission. */
|
|
442
464
|
commitWorkspaceRestoreRegistration: (candidate: Parameters<TelegramTopicTargetStore["assertWorkspaceRestoreRegistration"]>[0], publish: () => void) => void;
|
|
443
465
|
/** Read-only canonical observation; the callback must finish synchronously under the snapshot transaction. */
|
|
444
|
-
withWorkspaceRestoreSnapshot: (expected: TelegramWorkspaceRestoreIntent, observe: (snapshot: Readonly<Pick<TelegramTopicTargetFile, "threads" | "workspaceBindings">>) => undefined) => void;
|
|
466
|
+
withWorkspaceRestoreSnapshot: (expected: TelegramWorkspaceRestoreIntent | TelegramWorkspaceLiveRebindIntent, observe: (snapshot: Readonly<Pick<TelegramTopicTargetFile, "threads" | "workspaceBindings">>) => undefined) => void;
|
|
467
|
+
/** Exact synchronous live-operation snapshot, including a current publication frame; no mutation, lock or authority grant. */
|
|
468
|
+
withWorkspaceLiveRebindSnapshot: (expected: TelegramWorkspaceLiveRebindIntent, observe: (snapshot: Readonly<Pick<TelegramTopicTargetFile, "threads" | "workspaceBindings">>) => undefined) => void;
|
|
469
|
+
/** Current live-origin ownership veto; effect callers still require Workspace admission. False is not work/recipient/deletion authority. */
|
|
470
|
+
isWorkspaceLiveRebindCleanupTargetProtected: (expected: TelegramWorkspaceLiveRebindIntent) => boolean;
|
|
471
|
+
/** Read-only captured identity with an optional exact manual-name result fence; no transport/session/admission grant or lock. */
|
|
472
|
+
captureWorkspaceThreadRenameObservation: (binding: TelegramWorkspaceThreadBinding, owner: TelegramTopicTargetRecord) => (expectedManualThreadName?: string) => boolean;
|
|
473
|
+
/** Captured read-only identity and exact absent-manual-name/automatic-title result fences; no authority grant or lock. */
|
|
474
|
+
captureWorkspaceThreadResetObservation: (binding: TelegramWorkspaceThreadBinding, owner: TelegramTopicTargetRecord) => {
|
|
475
|
+
isCurrent: () => boolean;
|
|
476
|
+
isResultCurrent: (automaticTitle: string) => boolean;
|
|
477
|
+
};
|
|
445
478
|
/** Fresh strict disk read of acknowledged temporary-tab targets; throws rather than guessing absence. */
|
|
446
479
|
listTemporaryThreadTargets: () => TelegramTarget[];
|
|
447
480
|
list: () => TelegramTopicTargetRecord[];
|
|
@@ -526,14 +559,19 @@ export interface TelegramTopicTargetStore {
|
|
|
526
559
|
renameByTarget: (target: TelegramTarget, threadName: string, options?: {
|
|
527
560
|
updateDisplayTitle: boolean;
|
|
528
561
|
}) => TelegramTopicTargetRecord | undefined;
|
|
562
|
+
/** Publish a manual-name candidate without first retaining it in the dirty local projection. */
|
|
563
|
+
renameByTargetAndPersist: (target: TelegramTarget, threadName: string, options: {
|
|
564
|
+
updateDisplayTitle: boolean;
|
|
565
|
+
}, isCurrent: () => boolean) => Promise<TelegramTopicTargetRecord | undefined>;
|
|
529
566
|
clearManualNameByTarget: (target: TelegramTarget, automaticTitle: string) => TelegramTopicTargetRecord | undefined;
|
|
567
|
+
/** Stage exact manual-name removal and automatic display metadata in the existing publication candidate. */
|
|
568
|
+
clearManualNameByTargetAndPersist: (target: TelegramTarget, automaticTitle: string, isCurrent: () => boolean) => Promise<TelegramTopicTargetRecord | undefined>;
|
|
530
569
|
allocateSlot: (profileKey: string, preferredSlot?: string, workspaceBindingKey?: string, options?: {
|
|
531
570
|
excludeCurrentRecord?: boolean;
|
|
532
571
|
}) => string | undefined;
|
|
533
572
|
/** Claim the first reusable inactive thread for an instance, linking it to instanceId. */
|
|
534
573
|
claimReusableTarget: (instanceId: string, threadName?: string) => TelegramTopicTargetRecord | undefined;
|
|
535
574
|
}
|
|
536
|
-
export declare function reconcileTelegramFreshAllocationCursor(store: Pick<TelegramTopicTargetStore, "getBotState" | "list" | "setBotState">, nowMs?: number): boolean;
|
|
537
575
|
export declare function createTelegramCleanupTargetProtection(store: Pick<TelegramTopicTargetStore, "list"> & Partial<Pick<TelegramTopicTargetStore, "listReservations" | "listPendingProvisions" | "listPendingCleanups" | "listTemporaryThreadTargets">>, departingRecord?: TelegramTopicTargetRecord): NonNullable<ThreadReconciler.ThreadReconciliationApplyPorts["isCleanupTargetProtected"]>;
|
|
538
576
|
export interface TelegramTopicTargetStoreOptions {
|
|
539
577
|
path: string | (() => string);
|
|
@@ -556,8 +594,10 @@ export interface TelegramTopicTargetProvisionerDeps {
|
|
|
556
594
|
claimPendingTargets?: boolean;
|
|
557
595
|
}
|
|
558
596
|
export interface TelegramTopicTargetRenamerDeps {
|
|
559
|
-
store: Pick<TelegramTopicTargetStore, "
|
|
560
|
-
|
|
597
|
+
store: Pick<TelegramTopicTargetStore, "list" | "listWorkspaceBindings" | "listPendingProvisions"> & {
|
|
598
|
+
renameByTarget: (...args: Parameters<TelegramTopicTargetStore["renameByTarget"]>) => ReturnType<TelegramTopicTargetStore["renameByTarget"]> | Promise<ReturnType<TelegramTopicTargetStore["renameByTarget"]>>;
|
|
599
|
+
};
|
|
600
|
+
callApi: <TResponse>(method: string, body: Record<string, unknown>, options?: TelegramApiCallOptions) => Promise<TResponse>;
|
|
561
601
|
assertAuthority?: () => void;
|
|
562
602
|
shouldRenameDisplayedTitle?: () => boolean;
|
|
563
603
|
topicNameTemplate?: string;
|
|
@@ -597,8 +637,6 @@ export interface TelegramTopicTargetProvisionResult {
|
|
|
597
637
|
record: TelegramTopicTargetRecord;
|
|
598
638
|
displayTitle?: string;
|
|
599
639
|
}
|
|
600
|
-
export declare function getTelegramStatePath(agentDir?: string, profileName?: string): string;
|
|
601
|
-
export declare function getTelegramTopicTargetsPath(agentDir?: string, profileName?: string): string;
|
|
602
640
|
export declare const TELEGRAM_LEADER_SESSION_HANDOFF_TTL_MS = 30000;
|
|
603
641
|
export interface TelegramLeaderSessionHandoff {
|
|
604
642
|
pid: number;
|
|
@@ -705,6 +743,37 @@ export declare function resolveTelegramInstanceThreadIdentity(options: {
|
|
|
705
743
|
leader?: TelegramInstanceThreadIdentityCandidate;
|
|
706
744
|
record?: TelegramTopicTargetRecord;
|
|
707
745
|
}): TelegramInstanceThreadIdentityCandidate;
|
|
746
|
+
export interface TelegramWorkspaceThreadRenameAuthority {
|
|
747
|
+
context: unknown;
|
|
748
|
+
sessionId?: string;
|
|
749
|
+
sessionGeneration: number;
|
|
750
|
+
cwd?: string;
|
|
751
|
+
profileName?: string;
|
|
752
|
+
botToken?: string;
|
|
753
|
+
operatorUserId?: number;
|
|
754
|
+
leaderEpoch?: string | number;
|
|
755
|
+
ownsDirectDelivery: boolean;
|
|
756
|
+
followerRegistered: boolean;
|
|
757
|
+
localTarget?: TelegramTarget;
|
|
758
|
+
localSlot?: string;
|
|
759
|
+
}
|
|
760
|
+
interface TelegramWorkspaceThreadNameRecipientDeps {
|
|
761
|
+
store: TelegramTopicTargetStore;
|
|
762
|
+
instanceId: string;
|
|
763
|
+
target: TelegramTarget;
|
|
764
|
+
assertAuthority: () => void;
|
|
765
|
+
getAuthority: () => TelegramWorkspaceThreadRenameAuthority;
|
|
766
|
+
}
|
|
767
|
+
/** Prepared recipient identity/result fences; callers retain Workspace admission and transport effects. */
|
|
768
|
+
export declare function createTelegramWorkspaceThreadRenameRecipient(deps: TelegramWorkspaceThreadNameRecipientDeps): {
|
|
769
|
+
assertAuthority: () => void;
|
|
770
|
+
assertResult: (result: TelegramTopicTargetRecord) => void;
|
|
771
|
+
};
|
|
772
|
+
/** Exact automatic-title/absent-name result fences, separate from recipient identity and issued effects. */
|
|
773
|
+
export declare function createTelegramWorkspaceThreadResetRecipient(deps: TelegramWorkspaceThreadNameRecipientDeps): {
|
|
774
|
+
assertAuthority: () => void;
|
|
775
|
+
assertResult: (automaticTitle: string) => void;
|
|
776
|
+
};
|
|
708
777
|
export interface TelegramLeaderThreadStateRuntime {
|
|
709
778
|
getTarget(): TelegramTarget | undefined;
|
|
710
779
|
getIdentity(): TelegramInstanceThreadIdentityCandidate | undefined;
|
|
@@ -735,13 +804,6 @@ export declare function findCurrentTelegramInstanceThreadRecord(options: {
|
|
|
735
804
|
instanceId: string;
|
|
736
805
|
preferredTarget?: TelegramTarget;
|
|
737
806
|
}): TelegramTopicTargetRecord | undefined;
|
|
738
|
-
export declare function resolveTelegramInstanceThreadTarget(options: {
|
|
739
|
-
followerTarget?: TelegramTarget;
|
|
740
|
-
leaderTarget?: TelegramTarget;
|
|
741
|
-
currentRecord?: TelegramTopicTargetRecord;
|
|
742
|
-
}): (TelegramTarget & {
|
|
743
|
-
threadId: number;
|
|
744
|
-
}) | undefined;
|
|
745
807
|
export interface TelegramThreadStatusProjectionRuntime {
|
|
746
808
|
getBusRole(): "leader" | "follower" | undefined;
|
|
747
809
|
getBusFollowers(): ReturnType<typeof listTelegramThreadStatusFollowers>;
|