@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.
Files changed (204) hide show
  1. package/AGENTS.md +9 -7
  2. package/BACKLOG.md +1 -18
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +7 -11
  5. package/api/commands.ts +3 -0
  6. package/dist/api/commands.d.ts +1 -1
  7. package/dist/lib/activity-verbosity.d.ts +1 -5
  8. package/dist/lib/activity-verbosity.js +15 -42
  9. package/dist/lib/activity.d.ts +9 -0
  10. package/dist/lib/activity.js +39 -10
  11. package/dist/lib/agent-messages.js +7 -3
  12. package/dist/lib/bindings.d.ts +43 -5
  13. package/dist/lib/bindings.js +163 -20
  14. package/dist/lib/bus-api.d.ts +12 -0
  15. package/dist/lib/bus-api.js +182 -79
  16. package/dist/lib/bus-follower.d.ts +111 -31
  17. package/dist/lib/bus-follower.js +1324 -359
  18. package/dist/lib/bus-leader.d.ts +41 -91
  19. package/dist/lib/bus-leader.js +969 -637
  20. package/dist/lib/bus-transport.js +16 -9
  21. package/dist/lib/bus.d.ts +215 -18
  22. package/dist/lib/bus.js +1048 -327
  23. package/dist/lib/channel-posts.d.ts +5 -0
  24. package/dist/lib/channel-posts.js +434 -261
  25. package/dist/lib/command-templates.d.ts +26 -5
  26. package/dist/lib/command-templates.js +92 -176
  27. package/dist/lib/commands.d.ts +168 -82
  28. package/dist/lib/commands.js +1409 -291
  29. package/dist/lib/config.d.ts +1 -8
  30. package/dist/lib/config.js +34 -76
  31. package/dist/lib/delivery.d.ts +2 -2
  32. package/dist/lib/delivery.js +16 -16
  33. package/dist/lib/extension.js +639 -171
  34. package/dist/lib/generative-app-worker.mjs +19 -7
  35. package/dist/lib/generative-apps.d.ts +0 -5
  36. package/dist/lib/generative-apps.js +61 -49
  37. package/dist/lib/inbound.d.ts +5 -0
  38. package/dist/lib/inbound.js +35 -59
  39. package/dist/lib/journal.d.ts +33 -3
  40. package/dist/lib/journal.js +2063 -782
  41. package/dist/lib/lifecycle.d.ts +2 -6
  42. package/dist/lib/lifecycle.js +75 -58
  43. package/dist/lib/locks.d.ts +29 -3
  44. package/dist/lib/locks.js +289 -108
  45. package/dist/lib/logging.js +50 -14
  46. package/dist/lib/media.d.ts +22 -1
  47. package/dist/lib/media.js +46 -28
  48. package/dist/lib/menu-model.d.ts +19 -5
  49. package/dist/lib/menu-model.js +53 -11
  50. package/dist/lib/menu-queue.d.ts +5 -4
  51. package/dist/lib/menu-queue.js +45 -63
  52. package/dist/lib/menu-settings.d.ts +5 -4
  53. package/dist/lib/menu-settings.js +114 -78
  54. package/dist/lib/menu-status.d.ts +1 -1
  55. package/dist/lib/menu-status.js +8 -25
  56. package/dist/lib/menu-thinking.d.ts +6 -1
  57. package/dist/lib/menu-thinking.js +14 -21
  58. package/dist/lib/menu.d.ts +8 -61
  59. package/dist/lib/menu.js +92 -164
  60. package/dist/lib/model.d.ts +1 -1
  61. package/dist/lib/model.js +2 -16
  62. package/dist/lib/outbound-attachments.d.ts +6 -27
  63. package/dist/lib/outbound-attachments.js +45 -38
  64. package/dist/lib/outbound-buttons.js +22 -16
  65. package/dist/lib/outbound-markup.d.ts +0 -2
  66. package/dist/lib/outbound-markup.js +51 -64
  67. package/dist/lib/outbound-voice.js +6 -3
  68. package/dist/lib/outbound.d.ts +6 -6
  69. package/dist/lib/outbound.js +65 -51
  70. package/dist/lib/ownership.js +6 -2
  71. package/dist/lib/paths.d.ts +5 -0
  72. package/dist/lib/paths.js +37 -21
  73. package/dist/lib/pi.d.ts +2 -0
  74. package/dist/lib/pi.js +8 -1
  75. package/dist/lib/polling.d.ts +21 -37
  76. package/dist/lib/polling.js +59 -50
  77. package/dist/lib/preview.js +39 -19
  78. package/dist/lib/process-identity.d.ts +24 -0
  79. package/dist/lib/process-identity.js +133 -0
  80. package/dist/lib/prompts.js +2 -7
  81. package/dist/lib/queue.d.ts +14 -16
  82. package/dist/lib/queue.js +252 -127
  83. package/dist/lib/recovery.d.ts +2 -83
  84. package/dist/lib/recovery.js +20 -274
  85. package/dist/lib/replies.d.ts +16 -24
  86. package/dist/lib/replies.js +99 -116
  87. package/dist/lib/routing.d.ts +120 -7
  88. package/dist/lib/routing.js +4667 -1152
  89. package/dist/lib/runtime.d.ts +1 -11
  90. package/dist/lib/sections.d.ts +4 -29
  91. package/dist/lib/sections.js +7 -20
  92. package/dist/lib/skills.js +4 -3
  93. package/dist/lib/status.js +86 -57
  94. package/dist/lib/sync.d.ts +1 -3
  95. package/dist/lib/sync.js +67 -43
  96. package/dist/lib/target.d.ts +4 -0
  97. package/dist/lib/target.js +18 -0
  98. package/dist/lib/telegram-api.d.ts +18 -13
  99. package/dist/lib/telegram-api.js +148 -53
  100. package/dist/lib/text-groups.js +4 -39
  101. package/dist/lib/thread-cleanup-manager.d.ts +6 -27
  102. package/dist/lib/thread-cleanup-manager.js +369 -186
  103. package/dist/lib/thread-display.d.ts +1 -2
  104. package/dist/lib/thread-display.js +58 -38
  105. package/dist/lib/thread-naming.d.ts +28 -8
  106. package/dist/lib/thread-naming.js +175 -66
  107. package/dist/lib/thread-reconciler.d.ts +42 -16
  108. package/dist/lib/thread-reconciler.js +90 -50
  109. package/dist/lib/threads.d.ts +82 -20
  110. package/dist/lib/threads.js +2247 -855
  111. package/dist/lib/time-injection.js +1 -2
  112. package/dist/lib/turns.js +1 -19
  113. package/dist/lib/updates.d.ts +169 -80
  114. package/dist/lib/updates.js +2418 -641
  115. package/dist/lib/voice.d.ts +7 -22
  116. package/dist/lib/voice.js +7 -22
  117. package/dist/lib/wire.js +1 -1
  118. package/dist/lib/workspace-admission.d.ts +3 -1
  119. package/dist/lib/workspace-admission.js +425 -211
  120. package/dist/lib/workspace-identity.js +8 -13
  121. package/dist/lib/workspace-retirement.d.ts +1 -1
  122. package/dist/lib/workspace-retirement.js +328 -140
  123. package/dist/lib/workspace-slots.js +8 -4
  124. package/dist/package.json +1 -1
  125. package/docs/architecture.md +31 -29
  126. package/docs/inbound.md +1 -1
  127. package/docs/multi-instance-bus.md +15 -3
  128. package/docs/outbound.md +1 -1
  129. package/docs/public-api.md +29 -1
  130. package/docs/ui-style.md +25 -8
  131. package/lib/activity-verbosity.ts +59 -82
  132. package/lib/activity.ts +146 -102
  133. package/lib/agent-messages.ts +12 -5
  134. package/lib/bindings.ts +389 -82
  135. package/lib/bus-api.ts +263 -102
  136. package/lib/bus-follower.ts +2440 -601
  137. package/lib/bus-leader.ts +2209 -1290
  138. package/lib/bus-transport.ts +46 -12
  139. package/lib/bus.ts +1673 -444
  140. package/lib/channel-posts.ts +848 -327
  141. package/lib/command-templates.ts +152 -265
  142. package/lib/commands.ts +2398 -555
  143. package/lib/config.ts +177 -170
  144. package/lib/delivery.ts +41 -29
  145. package/lib/extension.ts +1206 -433
  146. package/lib/generative-app-worker.mjs +19 -7
  147. package/lib/generative-apps.ts +324 -211
  148. package/lib/inbound.ts +95 -97
  149. package/lib/journal.ts +5311 -1763
  150. package/lib/keyboard.ts +1 -3
  151. package/lib/lifecycle.ts +128 -78
  152. package/lib/locks.ts +677 -203
  153. package/lib/logging.ts +147 -58
  154. package/lib/media.ts +82 -43
  155. package/lib/menu-model.ts +85 -18
  156. package/lib/menu-queue.ts +88 -129
  157. package/lib/menu-settings.ts +260 -155
  158. package/lib/menu-status.ts +20 -60
  159. package/lib/menu-thinking.ts +41 -56
  160. package/lib/menu.ts +112 -285
  161. package/lib/model.ts +8 -22
  162. package/lib/outbound-attachments.ts +156 -125
  163. package/lib/outbound-buttons.ts +76 -49
  164. package/lib/outbound-markup.ts +96 -94
  165. package/lib/outbound-voice.ts +33 -27
  166. package/lib/outbound.ts +128 -125
  167. package/lib/ownership.ts +19 -10
  168. package/lib/paths.ts +148 -49
  169. package/lib/pi.ts +35 -15
  170. package/lib/polling.ts +144 -147
  171. package/lib/preview.ts +93 -40
  172. package/lib/process-identity.ts +164 -0
  173. package/lib/prompts.ts +4 -11
  174. package/lib/queue.ts +516 -301
  175. package/lib/recovery.ts +50 -454
  176. package/lib/rendering.ts +1 -3
  177. package/lib/replies.ts +248 -209
  178. package/lib/routing.ts +8509 -2186
  179. package/lib/runtime.ts +1 -12
  180. package/lib/sections.ts +24 -89
  181. package/lib/setup.ts +1 -3
  182. package/lib/skills.ts +9 -4
  183. package/lib/status.ts +215 -110
  184. package/lib/sync.ts +448 -361
  185. package/lib/target.ts +22 -3
  186. package/lib/telegram-api.ts +302 -127
  187. package/lib/text-groups.ts +5 -39
  188. package/lib/thread-cleanup-manager.ts +833 -331
  189. package/lib/thread-display.ts +213 -97
  190. package/lib/thread-naming.ts +232 -87
  191. package/lib/thread-reconciler.ts +155 -72
  192. package/lib/threads.ts +5318 -1845
  193. package/lib/time-injection.ts +1 -4
  194. package/lib/turns.ts +15 -28
  195. package/lib/updates.ts +5184 -1612
  196. package/lib/voice.ts +7 -35
  197. package/lib/wire.ts +1 -1
  198. package/lib/workspace-admission.ts +996 -416
  199. package/lib/workspace-identity.ts +14 -17
  200. package/lib/workspace-retirement.ts +878 -376
  201. package/lib/workspace-slots.ts +22 -13
  202. package/package.json +1 -1
  203. package/scripts/check-downgrade.mjs +0 -472
  204. 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
- function isCurrentRecord(record) {
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
- if (!(error instanceof Error))
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
- if (isTelegramTopicTargetDeletedOrMissingError(error))
116
- return true;
117
- if (!(error instanceof Error))
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 planDisconnectedInstanceThreadCleanup(input) {
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
- actions: [
184
- {
185
- kind: "close-delete-disconnected-instance-topic",
186
- target: input.target,
187
- reason: "manual-disconnect",
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 ? ["deleteForumTopic"] : ["closeForumTopic", "deleteForumTopic"]) {
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 || ports.isCleanupTargetProtected?.(action.target, action)) {
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-disconnected-instance-topic"
375
- ? "Disconnected instance Telegram topic deleted"
376
- : action.kind === "close-delete-graceful-shutdown-topic"
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
- "close-delete-disconnected-instance-topic"
389
- ? "thread-reconciler-disconnected-instance-topic-delete"
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(isCurrentRecord)
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) => isCurrentRecord(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 = {
@@ -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
- export { chooseTelegramThreadName, createTelegramThreadName, getTelegramManualThreadDisplayNameValidationError, getTelegramTopicIdentityName, getTelegramTopicThreadNameValidationError, getTelegramTopicTitleForThreadName, isTelegramTopicThreadNameValidForSlot, type TelegramThreadNameInput, } from "./thread-naming.ts";
9
- export declare const getTelegramTopicName: (request: TelegramTopicTargetProvisionRequest, template?: string, slot?: string) => string;
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
- persist: () => Promise<void>;
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, "renameByTarget" | "list" | "listWorkspaceBindings" | "listPendingProvisions">;
560
- callApi: <TResponse>(method: string, body: Record<string, unknown>) => Promise<TResponse>;
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>;