@llblab/pi-kit 0.29.1 → 0.30.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.
Files changed (222) hide show
  1. package/BACKLOG.md +3 -1
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +4 -4
  4. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +12 -0
  5. package/node_modules/@llblab/pi-state-flow/README.md +2 -2
  6. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +2 -2
  7. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +17 -8
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +7 -3
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +2 -1
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +3 -2
  11. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  12. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -3
  13. package/node_modules/@llblab/pi-state-flow/docs/usage.md +3 -2
  14. package/node_modules/@llblab/pi-state-flow/lib/context.ts +17 -8
  15. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +6 -3
  16. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +4 -2
  17. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  18. package/node_modules/@llblab/pi-telegram/AGENTS.md +13 -10
  19. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -1
  20. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +23 -0
  21. package/node_modules/@llblab/pi-telegram/README.md +10 -11
  22. package/node_modules/@llblab/pi-telegram/api/commands.ts +3 -0
  23. package/node_modules/@llblab/pi-telegram/dist/api/commands.d.ts +1 -1
  24. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
  25. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +15 -42
  26. package/node_modules/@llblab/pi-telegram/dist/lib/activity.d.ts +9 -0
  27. package/node_modules/@llblab/pi-telegram/dist/lib/activity.js +39 -10
  28. package/node_modules/@llblab/pi-telegram/dist/lib/agent-messages.js +7 -3
  29. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +43 -5
  30. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +164 -20
  31. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.d.ts +12 -0
  32. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +181 -77
  33. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +111 -31
  34. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +1324 -359
  35. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +41 -91
  36. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +969 -637
  37. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +16 -9
  38. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +215 -18
  39. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +1048 -327
  40. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +5 -0
  41. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +434 -261
  42. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +26 -5
  43. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +92 -176
  44. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +168 -82
  45. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +1409 -291
  46. package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +1 -8
  47. package/node_modules/@llblab/pi-telegram/dist/lib/config.js +34 -76
  48. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +2 -2
  49. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +16 -16
  50. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +642 -170
  51. package/node_modules/@llblab/pi-telegram/dist/lib/generative-app-worker.mjs +19 -7
  52. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -5
  53. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.js +61 -49
  54. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +5 -0
  55. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +35 -59
  56. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +40 -4
  57. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +2102 -760
  58. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -6
  59. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +75 -58
  60. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +34 -3
  61. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +318 -119
  62. package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +50 -14
  63. package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +22 -1
  64. package/node_modules/@llblab/pi-telegram/dist/lib/media.js +46 -28
  65. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +19 -5
  66. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +53 -11
  67. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +5 -4
  68. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +45 -63
  69. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +5 -4
  70. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +114 -78
  71. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +1 -1
  72. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +8 -25
  73. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +6 -1
  74. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +14 -21
  75. package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +8 -61
  76. package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +92 -164
  77. package/node_modules/@llblab/pi-telegram/dist/lib/model.d.ts +1 -1
  78. package/node_modules/@llblab/pi-telegram/dist/lib/model.js +2 -16
  79. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +6 -27
  80. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +45 -38
  81. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +22 -16
  82. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +0 -2
  83. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +51 -64
  84. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-voice.js +6 -3
  85. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +6 -6
  86. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +61 -51
  87. package/node_modules/@llblab/pi-telegram/dist/lib/ownership.js +6 -2
  88. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +5 -0
  89. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +37 -21
  90. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -0
  91. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +8 -1
  92. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +23 -37
  93. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +71 -52
  94. package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +39 -19
  95. package/node_modules/@llblab/pi-telegram/dist/lib/process-identity.d.ts +24 -0
  96. package/node_modules/@llblab/pi-telegram/dist/lib/process-identity.js +133 -0
  97. package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +2 -7
  98. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +16 -16
  99. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +261 -116
  100. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +2 -83
  101. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +20 -274
  102. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +16 -24
  103. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +99 -116
  104. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +121 -7
  105. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +4750 -1161
  106. package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +1 -11
  107. package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +4 -29
  108. package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +7 -20
  109. package/node_modules/@llblab/pi-telegram/dist/lib/skills.js +4 -3
  110. package/node_modules/@llblab/pi-telegram/dist/lib/status.js +86 -57
  111. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +6 -3
  112. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +112 -45
  113. package/node_modules/@llblab/pi-telegram/dist/lib/target.d.ts +4 -0
  114. package/node_modules/@llblab/pi-telegram/dist/lib/target.js +18 -0
  115. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +18 -11
  116. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +165 -49
  117. package/node_modules/@llblab/pi-telegram/dist/lib/text-groups.js +4 -39
  118. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +6 -27
  119. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +369 -186
  120. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +1 -2
  121. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +58 -38
  122. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +28 -8
  123. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +175 -66
  124. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +42 -16
  125. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +90 -50
  126. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +88 -24
  127. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +2276 -842
  128. package/node_modules/@llblab/pi-telegram/dist/lib/time-injection.js +1 -2
  129. package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -19
  130. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +178 -82
  131. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +2438 -645
  132. package/node_modules/@llblab/pi-telegram/dist/lib/voice.d.ts +7 -22
  133. package/node_modules/@llblab/pi-telegram/dist/lib/voice.js +7 -22
  134. package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +1 -1
  135. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +3 -1
  136. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +425 -211
  137. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +8 -13
  138. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +1 -1
  139. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +328 -140
  140. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +8 -4
  141. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  142. package/node_modules/@llblab/pi-telegram/docs/architecture.md +43 -35
  143. package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
  144. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +15 -3
  145. package/node_modules/@llblab/pi-telegram/docs/outbound.md +1 -1
  146. package/node_modules/@llblab/pi-telegram/docs/public-api.md +29 -1
  147. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +25 -8
  148. package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +59 -82
  149. package/node_modules/@llblab/pi-telegram/lib/activity.ts +146 -102
  150. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +12 -5
  151. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +390 -82
  152. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +262 -99
  153. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +2440 -601
  154. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +2209 -1290
  155. package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +46 -12
  156. package/node_modules/@llblab/pi-telegram/lib/bus.ts +1673 -444
  157. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +848 -327
  158. package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +152 -265
  159. package/node_modules/@llblab/pi-telegram/lib/commands.ts +2398 -555
  160. package/node_modules/@llblab/pi-telegram/lib/config.ts +177 -170
  161. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +41 -29
  162. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1209 -431
  163. package/node_modules/@llblab/pi-telegram/lib/generative-app-worker.mjs +19 -7
  164. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +324 -211
  165. package/node_modules/@llblab/pi-telegram/lib/inbound.ts +95 -97
  166. package/node_modules/@llblab/pi-telegram/lib/journal.ts +5329 -1712
  167. package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +1 -3
  168. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +128 -78
  169. package/node_modules/@llblab/pi-telegram/lib/locks.ts +703 -212
  170. package/node_modules/@llblab/pi-telegram/lib/logging.ts +147 -58
  171. package/node_modules/@llblab/pi-telegram/lib/media.ts +82 -43
  172. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +85 -18
  173. package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +88 -129
  174. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +260 -155
  175. package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +20 -60
  176. package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +41 -56
  177. package/node_modules/@llblab/pi-telegram/lib/menu.ts +112 -285
  178. package/node_modules/@llblab/pi-telegram/lib/model.ts +8 -22
  179. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +156 -125
  180. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +76 -49
  181. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +96 -94
  182. package/node_modules/@llblab/pi-telegram/lib/outbound-voice.ts +33 -27
  183. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +125 -125
  184. package/node_modules/@llblab/pi-telegram/lib/ownership.ts +19 -10
  185. package/node_modules/@llblab/pi-telegram/lib/paths.ts +148 -49
  186. package/node_modules/@llblab/pi-telegram/lib/pi.ts +35 -15
  187. package/node_modules/@llblab/pi-telegram/lib/polling.ts +165 -149
  188. package/node_modules/@llblab/pi-telegram/lib/preview.ts +93 -40
  189. package/node_modules/@llblab/pi-telegram/lib/process-identity.ts +164 -0
  190. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +4 -11
  191. package/node_modules/@llblab/pi-telegram/lib/queue.ts +523 -289
  192. package/node_modules/@llblab/pi-telegram/lib/recovery.ts +50 -454
  193. package/node_modules/@llblab/pi-telegram/lib/rendering.ts +1 -3
  194. package/node_modules/@llblab/pi-telegram/lib/replies.ts +248 -209
  195. package/node_modules/@llblab/pi-telegram/lib/routing.ts +8527 -2149
  196. package/node_modules/@llblab/pi-telegram/lib/runtime.ts +1 -12
  197. package/node_modules/@llblab/pi-telegram/lib/sections.ts +24 -89
  198. package/node_modules/@llblab/pi-telegram/lib/setup.ts +1 -3
  199. package/node_modules/@llblab/pi-telegram/lib/skills.ts +9 -4
  200. package/node_modules/@llblab/pi-telegram/lib/status.ts +215 -110
  201. package/node_modules/@llblab/pi-telegram/lib/sync.ts +488 -359
  202. package/node_modules/@llblab/pi-telegram/lib/target.ts +22 -3
  203. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +322 -122
  204. package/node_modules/@llblab/pi-telegram/lib/text-groups.ts +5 -39
  205. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +833 -331
  206. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +213 -97
  207. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +232 -87
  208. package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +155 -72
  209. package/node_modules/@llblab/pi-telegram/lib/threads.ts +5329 -1817
  210. package/node_modules/@llblab/pi-telegram/lib/time-injection.ts +1 -4
  211. package/node_modules/@llblab/pi-telegram/lib/turns.ts +15 -28
  212. package/node_modules/@llblab/pi-telegram/lib/updates.ts +5194 -1600
  213. package/node_modules/@llblab/pi-telegram/lib/voice.ts +7 -35
  214. package/node_modules/@llblab/pi-telegram/lib/wire.ts +1 -1
  215. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +996 -416
  216. package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +14 -17
  217. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +878 -376
  218. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +22 -13
  219. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  220. package/package.json +3 -3
  221. package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +0 -472
  222. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +0 -108
@@ -132,14 +132,6 @@ export type ThreadReconciliationAction =
132
132
  messageId?: number;
133
133
  leaderEpoch?: number | string;
134
134
  }
135
- | {
136
- kind: "close-delete-disconnected-instance-topic";
137
- target: TelegramTarget & { threadId: number };
138
- reason: "manual-disconnect";
139
- instanceId?: string;
140
- messageId?: number;
141
- leaderEpoch?: number | string;
142
- }
143
135
  | {
144
136
  kind: "close-delete-graceful-shutdown-topic";
145
137
  target: TelegramTarget & { threadId: number };
@@ -204,7 +196,10 @@ export interface ThreadReconciliationApplyResult {
204
196
  export interface ThreadReconciliationApplyPorts {
205
197
  /** Private-chat temporary tabs support deletion, not forum closing. */
206
198
  skipCloseBeforeDelete?: boolean;
207
- isCleanupTargetProtected?: (target: ThreadTarget, action: ThreadReconciliationAction) => boolean;
199
+ isCleanupTargetProtected?: (
200
+ target: ThreadTarget,
201
+ action: ThreadReconciliationAction,
202
+ ) => boolean;
208
203
  callApi?: <TResponse>(
209
204
  method: string,
210
205
  body: Record<string, unknown>,
@@ -249,7 +244,10 @@ function targetKey(target: ThreadTarget): string {
249
244
  return `${target.chatId}:${target.threadId}`;
250
245
  }
251
246
 
252
- function isCurrentRecord(record: ThreadReconciliationRecord): boolean {
247
+ /** A Thread record still claims its slot/target while active, starting or pending. */
248
+ export function isCurrentThreadRecord(record: {
249
+ readonly status: string;
250
+ }): boolean {
253
251
  return (
254
252
  record.status === "active" ||
255
253
  record.status === "starting" ||
@@ -323,7 +321,6 @@ function isCleanupAction(action: ThreadReconciliationAction): boolean {
323
321
  action.kind === "close-stale-replaced-topic" ||
324
322
  action.kind === "close-delete-replaced-follower-topic" ||
325
323
  action.kind === "close-delete-previous-leader-topic" ||
326
- action.kind === "close-delete-disconnected-instance-topic" ||
327
324
  action.kind === "close-delete-graceful-shutdown-topic" ||
328
325
  action.kind === "cancel-superseded-graceful-shutdown-cleanup" ||
329
326
  action.kind === "close-delete-expired-pending-provision-topic"
@@ -331,7 +328,9 @@ function isCleanupAction(action: ThreadReconciliationAction): boolean {
331
328
  }
332
329
 
333
330
  function isSyncAction(action: ThreadReconciliationAction): boolean {
334
- return action.kind === "mark-topic-active" || action.kind === "mark-topic-stale";
331
+ return (
332
+ action.kind === "mark-topic-active" || action.kind === "mark-topic-stale"
333
+ );
335
334
  }
336
335
 
337
336
  function createThreadReconciliationMachineState(
@@ -392,27 +391,40 @@ function getActionLeaderEpoch(
392
391
  return "leaderEpoch" in action ? action.leaderEpoch : undefined;
393
392
  }
394
393
 
394
+ /** Pure Bot API vocabulary for a deleted/missing Thread; callers own their HTTP-status policy. */
395
+ export function isTelegramTopicDeletedErrorMessage(message: string): boolean {
396
+ const text = message.toLowerCase();
397
+ return [
398
+ "topic_id_invalid",
399
+ "message thread not found",
400
+ "thread not found",
401
+ "topic not found",
402
+ "topic deleted",
403
+ ].some((fragment) => text.includes(fragment));
404
+ }
405
+
406
+ /** Pure Bot API vocabulary for a closed Thread; closure is not deletion evidence. */
407
+ export function isTelegramTopicClosedErrorMessage(message: string): boolean {
408
+ const text = message.toLowerCase();
409
+ return [
410
+ "topic closed",
411
+ "thread closed",
412
+ "forum topic closed",
413
+ "message thread closed",
414
+ ].some((fragment) => text.includes(fragment));
415
+ }
416
+
395
417
  function isTelegramTopicTargetDeletedOrMissingError(error: unknown): boolean {
396
- if (!(error instanceof Error)) return false;
397
- const message = error.message.toLowerCase();
398
418
  return (
399
- message.includes("topic_id_invalid") ||
400
- message.includes("message thread not found") ||
401
- message.includes("thread not found") ||
402
- message.includes("topic not found") ||
403
- message.includes("topic deleted")
419
+ error instanceof Error && isTelegramTopicDeletedErrorMessage(error.message)
404
420
  );
405
421
  }
406
422
 
407
423
  function isTelegramTopicTargetGoneError(error: unknown): boolean {
408
- if (isTelegramTopicTargetDeletedOrMissingError(error)) return true;
409
- if (!(error instanceof Error)) return false;
410
- const message = error.message.toLowerCase();
411
424
  return (
412
- message.includes("topic closed") ||
413
- message.includes("thread closed") ||
414
- message.includes("forum topic closed") ||
415
- message.includes("message thread closed")
425
+ error instanceof Error &&
426
+ (isTelegramTopicDeletedErrorMessage(error.message) ||
427
+ isTelegramTopicClosedErrorMessage(error.message))
416
428
  );
417
429
  }
418
430
 
@@ -501,26 +513,96 @@ export function createThreadReconciliationRuntime(deps: {
501
513
  };
502
514
  }
503
515
 
504
- export function planDisconnectedInstanceThreadCleanup(input: {
505
- target: TelegramTarget & { threadId: number };
506
- instanceId?: string;
507
- leaderEpoch?: number | string;
508
- }): ThreadReconciliationPlan {
516
+ /** Momentary preparation evidence only: not an action, lease, issued effect or reusable deletion grant. */
517
+ export interface TelegramLiveRebindCleanupPreparation {
518
+ status: "prepared";
519
+ operationId: string;
520
+ target: ThreadTarget;
521
+ recipientTarget: ThreadTarget;
522
+ leaderEpoch: string;
523
+ }
524
+
525
+ export function prepareLiveRebindThreadCleanup(input: {
526
+ operationId: string;
527
+ oldTarget: ThreadTarget;
528
+ recipientTarget: ThreadTarget;
529
+ leaderEpoch: string;
530
+ targetProtected: boolean;
531
+ /** Narrow body-free current-work projection; no cached idle or historical disposition proof. */
532
+ work:
533
+ | Readonly<{
534
+ sessionBusy: boolean;
535
+ targetWork: boolean;
536
+ deliveryPending: boolean;
537
+ unknown: boolean;
538
+ }>
539
+ | undefined;
540
+ }): TelegramLiveRebindCleanupPreparation | undefined {
541
+ const { operationId, oldTarget, recipientTarget, leaderEpoch, work } = input;
542
+ const validTarget = (target: ThreadTarget) =>
543
+ Number.isSafeInteger(target?.chatId) &&
544
+ target.chatId !== 0 &&
545
+ Number.isSafeInteger(target.threadId) &&
546
+ target.threadId > 0;
547
+ if (
548
+ !operationId?.trim() ||
549
+ !leaderEpoch?.trim() ||
550
+ !validTarget(oldTarget) ||
551
+ !validTarget(recipientTarget) ||
552
+ oldTarget.chatId !== recipientTarget.chatId ||
553
+ oldTarget.threadId === recipientTarget.threadId ||
554
+ input.targetProtected !== false ||
555
+ work?.sessionBusy !== false ||
556
+ work.targetWork !== false ||
557
+ work.deliveryPending !== false ||
558
+ work.unknown !== false
559
+ )
560
+ return undefined;
509
561
  return {
510
- actions: [
511
- {
512
- kind: "close-delete-disconnected-instance-topic",
513
- target: input.target,
514
- reason: "manual-disconnect",
515
- instanceId: input.instanceId,
516
- ...(input.leaderEpoch !== undefined
517
- ? { leaderEpoch: input.leaderEpoch }
518
- : {}),
519
- },
520
- ],
562
+ status: "prepared",
563
+ operationId,
564
+ target: { ...oldTarget },
565
+ recipientTarget: { ...recipientTarget },
566
+ leaderEpoch,
521
567
  };
522
568
  }
523
569
 
570
+ export type TelegramLiveRebindCleanupOutcome =
571
+ "confirmed" | "failed" | "unknown";
572
+
573
+ /**
574
+ * One unretried deletion for an already issued live-rebind cleanup grant. Success or confirmed absence is `confirmed`;
575
+ * protection or authority loss before the request and a definite request rejection are `failed`; every other outcome is
576
+ * `unknown`. The caller owns the durable issue marker and terminal record; this never repeats or rolls back.
577
+ */
578
+ export async function issueLiveRebindThreadCleanup(
579
+ prepared: TelegramLiveRebindCleanupPreparation,
580
+ ports: {
581
+ /** Throws when the issuer's current recipient/work/ownership authority no longer holds. */
582
+ assertCurrent(): void;
583
+ deleteTopic(target: ThreadTarget): Promise<unknown>;
584
+ classifyFailure(error: unknown): "absent" | "rejected" | "unknown";
585
+ },
586
+ ): Promise<TelegramLiveRebindCleanupOutcome> {
587
+ const target = { ...prepared.target };
588
+ try {
589
+ ports.assertCurrent();
590
+ } catch {
591
+ return "failed";
592
+ }
593
+ try {
594
+ await ports.deleteTopic(target);
595
+ } catch (error) {
596
+ const failure = ports.classifyFailure(error);
597
+ return failure === "absent"
598
+ ? "confirmed"
599
+ : failure === "rejected"
600
+ ? "failed"
601
+ : "unknown";
602
+ }
603
+ return "confirmed";
604
+ }
605
+
524
606
  export async function applyThreadReconciliationPlan(
525
607
  plan: ThreadReconciliationPlan,
526
608
  ports: ThreadReconciliationApplyPorts,
@@ -614,7 +696,6 @@ export async function applyThreadReconciliationPlan(
614
696
  action.kind === "close-delete-reserved-topic" ||
615
697
  action.kind === "close-delete-replaced-follower-topic" ||
616
698
  action.kind === "close-delete-previous-leader-topic" ||
617
- action.kind === "close-delete-disconnected-instance-topic" ||
618
699
  action.kind === "close-delete-graceful-shutdown-topic" ||
619
700
  action.kind === "close-delete-expired-pending-provision-topic"
620
701
  ) {
@@ -638,7 +719,9 @@ export async function applyThreadReconciliationPlan(
638
719
  }
639
720
  let deleteConfirmed = false;
640
721
  let superseded = false;
641
- for (const method of ports.skipCloseBeforeDelete ? ["deleteForumTopic"] : ["closeForumTopic", "deleteForumTopic"]) {
722
+ for (const method of ports.skipCloseBeforeDelete
723
+ ? ["deleteForumTopic"]
724
+ : ["closeForumTopic", "deleteForumTopic"]) {
642
725
  if (ports.isCleanupTargetProtected?.(action.target, action)) {
643
726
  superseded = true;
644
727
  break;
@@ -670,13 +753,20 @@ export async function applyThreadReconciliationPlan(
670
753
  incompleteActions.push(action);
671
754
  continue;
672
755
  }
673
- if (superseded || ports.isCleanupTargetProtected?.(action.target, action)) {
674
- ports.recordRuntimeEvent?.("telegram", "Cancelled cleanup of a protected Telegram target", {
675
- phase: "thread-reconciler-cleanup-target-reused",
676
- action: action.kind,
677
- chatId: action.target.chatId,
678
- threadId: action.target.threadId,
679
- });
756
+ if (
757
+ superseded ||
758
+ ports.isCleanupTargetProtected?.(action.target, action)
759
+ ) {
760
+ ports.recordRuntimeEvent?.(
761
+ "telegram",
762
+ "Cancelled cleanup of a protected Telegram target",
763
+ {
764
+ phase: "thread-reconciler-cleanup-target-reused",
765
+ action: action.kind,
766
+ chatId: action.target.chatId,
767
+ threadId: action.target.threadId,
768
+ },
769
+ );
680
770
  continue;
681
771
  }
682
772
  if (!deleteConfirmed) {
@@ -699,7 +789,6 @@ export async function applyThreadReconciliationPlan(
699
789
  if (
700
790
  action.kind !== "close-delete-previous-leader-topic" &&
701
791
  action.kind !== "close-delete-replaced-follower-topic" &&
702
- action.kind !== "close-delete-disconnected-instance-topic" &&
703
792
  action.kind !== "close-delete-expired-pending-provision-topic"
704
793
  ) {
705
794
  const changed =
@@ -714,14 +803,12 @@ export async function applyThreadReconciliationPlan(
714
803
  : action.kind === "close-delete-reserved-topic"
715
804
  ? "Reserved Telegram topic deleted"
716
805
  : action.kind === "close-delete-replaced-follower-topic"
717
- ? "Replaced follower Telegram topic deleted"
718
- : action.kind === "close-delete-previous-leader-topic"
719
- ? "Previous leader Telegram topic deleted"
720
- : action.kind === "close-delete-disconnected-instance-topic"
721
- ? "Disconnected instance Telegram topic deleted"
722
- : action.kind === "close-delete-graceful-shutdown-topic"
723
- ? "Graceful shutdown Telegram topic deleted"
724
- : "Expired pending provision Telegram topic deleted",
806
+ ? "Replaced follower Telegram topic deleted"
807
+ : action.kind === "close-delete-previous-leader-topic"
808
+ ? "Previous leader Telegram topic deleted"
809
+ : action.kind === "close-delete-graceful-shutdown-topic"
810
+ ? "Graceful shutdown Telegram topic deleted"
811
+ : "Expired pending provision Telegram topic deleted",
725
812
  {
726
813
  phase:
727
814
  action.kind === "close-delete-unbound-topic"
@@ -729,16 +816,12 @@ export async function applyThreadReconciliationPlan(
729
816
  : action.kind === "close-delete-reserved-topic"
730
817
  ? "thread-reconciler-reserved-topic-delete"
731
818
  : action.kind === "close-delete-replaced-follower-topic"
732
- ? "thread-reconciler-replaced-follower-topic-delete"
733
- : action.kind === "close-delete-previous-leader-topic"
734
- ? "thread-reconciler-previous-leader-topic-delete"
735
- : action.kind ===
736
- "close-delete-disconnected-instance-topic"
737
- ? "thread-reconciler-disconnected-instance-topic-delete"
738
- : action.kind ===
739
- "close-delete-graceful-shutdown-topic"
740
- ? "thread-reconciler-graceful-shutdown-topic-delete"
741
- : "thread-reconciler-expired-pending-provision-topic-delete",
819
+ ? "thread-reconciler-replaced-follower-topic-delete"
820
+ : action.kind === "close-delete-previous-leader-topic"
821
+ ? "thread-reconciler-previous-leader-topic-delete"
822
+ : action.kind === "close-delete-graceful-shutdown-topic"
823
+ ? "thread-reconciler-graceful-shutdown-topic-delete"
824
+ : "thread-reconciler-expired-pending-provision-topic-delete",
742
825
  chatId: action.target.chatId,
743
826
  threadId: action.target.threadId,
744
827
  ...("messageId" in action ? { messageId: action.messageId } : {}),
@@ -793,7 +876,7 @@ export function planThreadReconciliation(
793
876
  );
794
877
  const currentTargets = new Set(
795
878
  input.records
796
- .filter(isCurrentRecord)
879
+ .filter(isCurrentThreadRecord)
797
880
  .map((record) => targetKey(record.target)),
798
881
  );
799
882
  const reservedTargets = new Set(
@@ -826,7 +909,7 @@ export function planThreadReconciliation(
826
909
  for (const cleanup of input.pendingCleanups ?? []) {
827
910
  const superseded = input.records.some(
828
911
  (record) =>
829
- isCurrentRecord(record) &&
912
+ isCurrentThreadRecord(record) &&
830
913
  targetKey(record.target) === targetKey(cleanup.target) &&
831
914
  record.instanceId !== cleanup.instanceId,
832
915
  );