@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
@@ -3,13 +3,13 @@
3
3
  * Zones: Telegram bot reality mirror, demand-driven reconciliation, status diagnostics
4
4
  * Owns pure contracts for deciding when local Telegram mirror state should be refreshed without querying Telegram on every action
5
5
  */
6
- import { getTelegramApiErrorRequestTarget, isTelegramStaleTargetHttpError } from "./telegram-api.js";
7
6
  import { getTelegramTargetKey } from "./target.js";
7
+ import { getTelegramApiErrorRequestTarget, isTelegramStaleTargetHttpError, } from "./telegram-api.js";
8
8
  import * as ThreadReconciler from "./thread-reconciler.js";
9
- import { TelegramWorkspaceSlotUnavailableError } from "./workspace-slots.js";
10
- import { normalizeTelegramWorkspacePath } from "./workspace-identity.js";
9
+ import { commitTelegramWorkspaceProvisionBinding, createTelegramCleanupTargetProtection, getTelegramTargetFromApiBody, getTelegramThreadOwnerKey, isSameTelegramProcessInstance, isTelegramTopicTargetStaleError, provisionOwnBusTopic, } from "./threads.js";
11
10
  import { createTelegramWorkspaceAdmissionOperationId, runWithTelegramWorkspaceAdmissionsAsync, } from "./workspace-admission.js";
12
- import { createTelegramCleanupTargetProtection, commitTelegramWorkspaceProvisionBinding, getTelegramTargetFromApiBody, getTelegramThreadOwnerKey, isSameTelegramProcessInstance, isTelegramTopicTargetStaleError, provisionOwnBusTopic, } from "./threads.js";
11
+ import { normalizeTelegramWorkspacePath } from "./workspace-identity.js";
12
+ import { TelegramWorkspaceSlotUnavailableError } from "./workspace-slots.js";
13
13
  function markTelegramConfigSyncChange(state, action, options) {
14
14
  const nowMs = options?.nowMs ?? Date.now();
15
15
  let nextState = markTelegramSyncSliceFresh(state, "pairing", {
@@ -32,10 +32,13 @@ export function createTelegramPreservedLeaderQuitHandler(deps) {
32
32
  const profile = deps.getProfileName();
33
33
  if (epoch === undefined || !isSessionCurrent())
34
34
  return undefined;
35
- const isCurrent = () => isSessionCurrent() && deps.getCurrentLeaderEpoch() === epoch &&
36
- deps.getProfileName() === profile && deps.isPollingSuspended();
35
+ const isCurrent = () => isSessionCurrent() &&
36
+ deps.getCurrentLeaderEpoch() === epoch &&
37
+ deps.getProfileName() === profile &&
38
+ deps.isPollingSuspended();
37
39
  return async () => {
38
- if (!isCurrent() || await deps.resolveAutomaticThreadCleanupEnabled() !== false)
40
+ if (!isCurrent() ||
41
+ (await deps.resolveAutomaticThreadCleanupEnabled()) !== false)
39
42
  return;
40
43
  if (!isCurrent())
41
44
  return;
@@ -49,13 +52,20 @@ export function createTelegramPreservedLeaderQuitHandler(deps) {
49
52
  await deps.topicTargetStore.load();
50
53
  if (!isCurrent())
51
54
  return;
52
- const records = deps.topicTargetStore.list().filter((record) => record.instanceId === deps.instanceId && (record.status === "active" || record.status === "starting"));
55
+ const records = deps.topicTargetStore
56
+ .list()
57
+ .filter((record) => record.instanceId === deps.instanceId &&
58
+ (record.status === "active" || record.status === "starting"));
53
59
  if (records.length !== 1)
54
60
  return;
55
61
  const record = records[0];
56
- if (deps.topicTargetStore.listPendingCleanups().some((intent) => intent.target.chatId === record.target.chatId && intent.target.threadId === record.target.threadId))
62
+ if (deps.topicTargetStore
63
+ .listPendingCleanups()
64
+ .some((intent) => intent.target.chatId === record.target.chatId &&
65
+ intent.target.threadId === record.target.threadId))
57
66
  return;
58
- if (!await deps.topicTargetStore.detachTargetOwner(record, isCurrent) && isCurrent()) {
67
+ if (!(await deps.topicTargetStore.detachTargetOwner(record, isCurrent)) &&
68
+ isCurrent()) {
59
69
  throw new Error("Telegram preserved leader detachment was not committed.");
60
70
  }
61
71
  });
@@ -74,11 +84,47 @@ function createTelegramSessionRestartThreadCleanupHandler(deps) {
74
84
  }
75
85
  export function createTelegramThreadDisconnectAssembly(deps) {
76
86
  return {
77
- disconnect: createTelegramManualThreadDisconnectHandler({
78
- ...deps,
79
- workspaceOperationKind: "workspace.disconnect-thread",
80
- stopPolling: deps.stopPolling,
81
- }),
87
+ async disconnect() {
88
+ const captured = deps.captureStopPolling?.();
89
+ const stop = captured?.stop ?? deps.stopPolling;
90
+ let stopAttempted = false;
91
+ const disconnect = createTelegramManualThreadDisconnectHandler({
92
+ ...deps,
93
+ workspaceOperationKind: "workspace.disconnect-thread",
94
+ isDisconnectCurrent: captured?.isCurrent ?? deps.isDisconnectCurrent,
95
+ stopPolling() {
96
+ stopAttempted = true;
97
+ return stop();
98
+ },
99
+ });
100
+ try {
101
+ return await disconnect();
102
+ }
103
+ catch (error) {
104
+ // Never retry a stop whose teardown/release outcome may already have committed.
105
+ if (stopAttempted) {
106
+ deps.recordRuntimeEvent("connection", error, {
107
+ phase: "disconnect-stop",
108
+ });
109
+ throw error;
110
+ }
111
+ deps.recordRuntimeEvent("connection", error, {
112
+ phase: "disconnect-cleanup",
113
+ });
114
+ let stopped;
115
+ try {
116
+ stopped = await stop();
117
+ }
118
+ catch (stopError) {
119
+ deps.recordRuntimeEvent("connection", stopError, {
120
+ phase: "disconnect-stop",
121
+ });
122
+ throw stopError;
123
+ }
124
+ const separator = /[.!?]$/u.test(stopped) ? " " : ". ";
125
+ return `${stopped}${separator}Thread cleanup was not confirmed. Check /telegram-status --debug.`;
126
+ }
127
+ },
82
128
  cleanupForSessionRestart: createTelegramSessionRestartThreadCleanupHandler({
83
129
  ...deps,
84
130
  suspendPolling: deps.suspendPolling,
@@ -86,7 +132,12 @@ export function createTelegramThreadDisconnectAssembly(deps) {
86
132
  };
87
133
  }
88
134
  export function createTelegramManualThreadDisconnectHandler(deps) {
135
+ const assertCurrent = () => {
136
+ if (deps.isDisconnectCurrent?.() === false)
137
+ throw new Error("Telegram disconnect was superseded by a new connection.");
138
+ };
89
139
  const operation = async () => {
140
+ assertCurrent();
90
141
  const currentRecord = deps.getCurrentThreadRecord();
91
142
  let cleanupPending = false;
92
143
  if (currentRecord?.target.threadId) {
@@ -98,6 +149,7 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
98
149
  if (isManualFollower && !ownsLeader) {
99
150
  if (deps.disconnectFollowerThread) {
100
151
  const disconnected = await deps.disconnectFollowerThread();
152
+ assertCurrent();
101
153
  if (!disconnected) {
102
154
  throw new Error("Telegram follower thread deletion requires a live leader registration.");
103
155
  }
@@ -117,11 +169,15 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
117
169
  target,
118
170
  requestedAtMs: (deps.getNowMs ?? Date.now)(),
119
171
  };
120
- const departingRecord = deps.topicTargetStore.list().find((record) => record.instanceId === currentRecord.instanceId &&
121
- record.target.chatId === target.chatId && record.target.threadId === target.threadId);
172
+ const departingRecord = deps.topicTargetStore
173
+ .list()
174
+ .find((record) => record.instanceId === currentRecord.instanceId &&
175
+ record.target.chatId === target.chatId &&
176
+ record.target.threadId === target.threadId);
122
177
  const isCleanupTargetProtected = createTelegramCleanupTargetProtection(deps.topicTargetStore, departingRecord);
123
178
  deps.topicTargetStore.upsertPendingCleanup(intent);
124
179
  await deps.topicTargetStore.persist();
180
+ assertCurrent();
125
181
  const cleanupPlan = ThreadReconciler.planThreadReconciliation({
126
182
  nowMs: (deps.getNowMs ?? Date.now)(),
127
183
  currentLeaderEpoch: leaderEpoch,
@@ -131,20 +187,25 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
131
187
  const cleanup = await ThreadReconciler.applyThreadReconciliationPlan(cleanupPlan, {
132
188
  isCleanupTargetProtected,
133
189
  callApi(method, body) {
190
+ assertCurrent();
134
191
  return deps.callApi(method, body);
135
192
  },
136
193
  markStaleByTarget(targetToMark, syncStatus, lastSyncError) {
194
+ assertCurrent();
137
195
  return deps.topicTargetStore.markStaleByTarget(targetToMark, syncStatus, lastSyncError);
138
196
  },
139
197
  removeCleanupIntentById(id) {
198
+ assertCurrent();
140
199
  return deps.topicTargetStore.removePendingCleanup(id);
141
200
  },
142
201
  persist() {
202
+ assertCurrent();
143
203
  return deps.topicTargetStore.persist();
144
204
  },
145
205
  getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
146
206
  recordRuntimeEvent: deps.recordRuntimeEvent,
147
207
  });
208
+ assertCurrent();
148
209
  if (cleanupPlan.actions.some((action) => isCleanupTargetProtected(action.target, action))) {
149
210
  return "Thread disconnect superseded by a new binding.";
150
211
  }
@@ -160,6 +221,7 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
160
221
  action: "manual-disconnect",
161
222
  }));
162
223
  }
224
+ assertCurrent();
163
225
  const stopped = await deps.stopPolling();
164
226
  return cleanupPending
165
227
  ? `${stopped} Telegram thread cleanup remains pending for the next leader.`
@@ -269,23 +331,32 @@ export function captureTelegramStaleTargetRequestRecovery(body, deps) {
269
331
  if (!target || epoch === undefined)
270
332
  return undefined;
271
333
  const key = getTelegramTargetKey(target);
272
- const record = deps.topicTargetStore.list().find((candidate) => getTelegramTargetKey(candidate.target) === key);
334
+ const record = deps.topicTargetStore
335
+ .list()
336
+ .find((candidate) => getTelegramTargetKey(candidate.target) === key);
273
337
  if (!record)
274
338
  return undefined;
275
339
  const generation = deps.getSessionGeneration();
276
340
  const profile = deps.getProfileName();
277
341
  const isAuthorityCurrent = () => deps.getCurrentLeaderEpoch() === epoch &&
278
- deps.getSessionGeneration() === generation && deps.getProfileName() === profile;
342
+ deps.getSessionGeneration() === generation &&
343
+ deps.getProfileName() === profile;
279
344
  const isCurrent = () => {
280
- const current = deps.topicTargetStore.list().find((candidate) => getTelegramTargetKey(candidate.target) === key);
281
- return isAuthorityCurrent() &&
282
- current?.instanceId === record.instanceId && current?.profileKey === record.profileKey &&
283
- current?.updatedAtMs === record.updatedAtMs && current?.createdAtMs === record.createdAtMs;
345
+ const current = deps.topicTargetStore
346
+ .list()
347
+ .find((candidate) => getTelegramTargetKey(candidate.target) === key);
348
+ return (isAuthorityCurrent() &&
349
+ current?.instanceId === record.instanceId &&
350
+ current?.profileKey === record.profileKey &&
351
+ current?.updatedAtMs === record.updatedAtMs &&
352
+ current?.createdAtMs === record.createdAtMs);
284
353
  };
285
354
  return async (error) => {
286
355
  const requestTarget = getTelegramApiErrorRequestTarget(error);
287
- if (!isTelegramStaleTargetHttpError(error) || !requestTarget ||
288
- getTelegramTargetKey(requestTarget) !== key || !isCurrent())
356
+ if (!isTelegramStaleTargetHttpError(error) ||
357
+ !requestTarget ||
358
+ getTelegramTargetKey(requestTarget) !== key ||
359
+ !isCurrent())
289
360
  return;
290
361
  if (await recoverStaleTelegramTopicApiError({ chat_id: target.chatId, message_thread_id: target.threadId }, error, { ...deps, isCurrent, isAuthorityCurrent })) {
291
362
  deps.onRecovered();
@@ -311,7 +382,7 @@ export async function recoverStaleTelegramTopicApiError(apiBody, error, deps) {
311
382
  const recover = async () => {
312
383
  if (deps.isCurrent) {
313
384
  if (!deps.topicTargetStore.invalidateTarget ||
314
- !await deps.topicTargetStore.invalidateTarget(target, deps.isCurrent, String(error)))
385
+ !(await deps.topicTargetStore.invalidateTarget(target, deps.isCurrent, String(error))))
315
386
  return false;
316
387
  if (deps.isAuthorityCurrent?.() === false)
317
388
  return false;
@@ -393,7 +464,12 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
393
464
  const legacyLeaderRecord = deps.topicTargetStore.getByProfileKey(leaderProfileKey);
394
465
  let capacityUnavailable = false;
395
466
  const workspaceIdentity = normalizedLeaderCwd
396
- ? deps.topicTargetStore.claimWorkspaceIdentity(normalizedLeaderCwd, deps.instanceId, legacyLeaderRecord?.instanceId, { sessionId: deps.sessionId, onCapacityUnavailable() { capacityUnavailable = true; } })
467
+ ? deps.topicTargetStore.claimWorkspaceIdentity(normalizedLeaderCwd, deps.instanceId, legacyLeaderRecord?.instanceId, {
468
+ sessionId: deps.sessionId,
469
+ onCapacityUnavailable() {
470
+ capacityUnavailable = true;
471
+ },
472
+ })
397
473
  : undefined;
398
474
  if (deps.cwd && !workspaceIdentity) {
399
475
  if (capacityUnavailable)
@@ -428,7 +504,9 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
428
504
  target: { ...committed.target },
429
505
  slot: committed.slot ?? result.slot,
430
506
  ...(committed.threadName ? { threadName: committed.threadName } : {}),
431
- ...(committed.displayTitle ? { displayTitle: committed.displayTitle } : {}),
507
+ ...(committed.displayTitle
508
+ ? { displayTitle: committed.displayTitle }
509
+ : {}),
432
510
  };
433
511
  };
434
512
  try {
@@ -469,7 +547,9 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
469
547
  ...(legacyLeaderRecord.threadName
470
548
  ? { threadName: legacyLeaderRecord.threadName }
471
549
  : {}),
472
- ...(legacyLeaderRecord.slot ? { slot: legacyLeaderRecord.slot } : {}),
550
+ ...(legacyLeaderRecord.slot
551
+ ? { slot: legacyLeaderRecord.slot }
552
+ : {}),
473
553
  updatedAtMs: legacyLeaderRecord.updatedAtMs,
474
554
  }
475
555
  : undefined;
@@ -557,7 +637,9 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
557
637
  });
558
638
  // Legacy callers without session identity may reuse the active instance target.
559
639
  // Session-aware callers must resolve their exact Workspace binding instead.
560
- if (deps.sessionId === undefined && !deps.forceFreshUnnamed && priorTargets.length > 0) {
640
+ if (deps.sessionId === undefined &&
641
+ !deps.forceFreshUnnamed &&
642
+ priorTargets.length > 0) {
561
643
  const record = priorTargets[0];
562
644
  deps.recordEvent("telegram", "Leader thread preserved after session lifecycle change", {
563
645
  phase: "leader-thread-reused",
@@ -704,21 +786,6 @@ export function createTelegramProvisioningActivityRuntime() {
704
786
  },
705
787
  };
706
788
  }
707
- const RECONCILE_TRIGGERS = new Set([
708
- "startup",
709
- "reload",
710
- "topic-lifecycle",
711
- "stale-api-error",
712
- "setup-change",
713
- "pairing-change",
714
- "follower-register",
715
- "follower-prune",
716
- "status-request",
717
- "leader-health-tick",
718
- ]);
719
- export function shouldReconcileTelegramSync(trigger) {
720
- return RECONCILE_TRIGGERS.has(trigger);
721
- }
722
789
  export function markTelegramSyncSliceSuspect(state, slice, input) {
723
790
  return {
724
791
  ...state,
@@ -18,3 +18,7 @@ export declare function areTelegramTargetsEqual(left: TelegramTarget, right: Tel
18
18
  export declare function getTelegramTargetThreadParams(target: TelegramTarget): {
19
19
  message_thread_id?: number;
20
20
  };
21
+ /** Pure wire parser for `{ chatId, threadId? }`; unknown fields are dropped and malformed shapes refuse. */
22
+ export declare function parseTelegramTarget(value: unknown): TelegramTarget | undefined;
23
+ /** Pure Bot API integer id parser accepting integer numbers or non-blank integer strings. */
24
+ export declare function parseTelegramIntegerId(value: unknown): number | undefined;
@@ -25,3 +25,21 @@ export function getTelegramTargetThreadParams(target) {
25
25
  ? { message_thread_id: target.threadId }
26
26
  : {};
27
27
  }
28
+ /** Pure wire parser for `{ chatId, threadId? }`; unknown fields are dropped and malformed shapes refuse. */
29
+ export function parseTelegramTarget(value) {
30
+ if (typeof value !== "object" || value === null || Array.isArray(value))
31
+ return undefined;
32
+ const { chatId, threadId } = value;
33
+ if (typeof chatId !== "number")
34
+ return undefined;
35
+ return typeof threadId === "number" ? { chatId, threadId } : { chatId };
36
+ }
37
+ /** Pure Bot API integer id parser accepting integer numbers or non-blank integer strings. */
38
+ export function parseTelegramIntegerId(value) {
39
+ if (typeof value === "number" && Number.isInteger(value))
40
+ return value;
41
+ if (typeof value !== "string" || value.trim() === "")
42
+ return undefined;
43
+ const parsed = Number(value);
44
+ return Number.isInteger(parsed) ? parsed : undefined;
45
+ }
@@ -7,7 +7,8 @@
7
7
  */
8
8
  import type { TelegramAttachmentSource } from "./media.ts";
9
9
  export declare const TELEGRAM_FILE_MAX_BYTES: number;
10
- export declare function getTelegramInboundFileByteLimitFromEnv(env: NodeJS.ProcessEnv, names: string[], defaultValue?: number): number;
10
+ /** First positive safe-integer byte limit among ordered env names, else the caller's default. */
11
+ export declare function getTelegramByteLimitFromEnv(env: NodeJS.ProcessEnv, names: readonly string[], defaultValue: number): number;
11
12
  export type TelegramNetworkFamilyPolicy = "auto" | "ipv4" | "ipv6" | "ipv4-fallback";
12
13
  type TelegramNetworkFamily = 4 | 6;
13
14
  export interface TelegramUser {
@@ -288,6 +289,8 @@ export interface TelegramApiRetryWait {
288
289
  retryAfterSeconds?: number;
289
290
  }
290
291
  export interface TelegramApiCallOptions {
292
+ /** Local per-call effect authority; independent of source completion and transport ownership. */
293
+ assertAuthority?: () => void;
291
294
  signal?: AbortSignal;
292
295
  maxAttempts?: number;
293
296
  retryRateLimit?: boolean;
@@ -297,6 +300,12 @@ export interface TelegramApiCallOptions {
297
300
  /** Observability hook fired before a 429 retry wait, never for 5xx waits. */
298
301
  onRetryWait?: (wait: TelegramApiRetryWait) => void;
299
302
  }
303
+ export declare class TelegramApiAuthorityError extends Error {
304
+ readonly requestIssued: boolean;
305
+ constructor(requestIssued: boolean);
306
+ }
307
+ /** Recheck caller authority, classifying refusal by whether a Bot API request was already issued. */
308
+ export declare function assertTelegramApiCallAuthority(assertAuthority: (() => void) | undefined, requestIssued: boolean): void;
300
309
  export interface TelegramFileDownloadOptions {
301
310
  signal?: AbortSignal;
302
311
  maxFileSizeBytes?: number;
@@ -338,14 +347,14 @@ export interface TelegramEditGuestInlineMessageContent {
338
347
  richMessage?: TelegramInputRichMessage;
339
348
  parseMode?: "HTML";
340
349
  }
341
- export interface TelegramAnswerCallbackQueryOptions {
350
+ export interface TelegramAnswerCallbackQueryOptions extends Pick<TelegramApiCallOptions, "assertAuthority"> {
342
351
  recordRuntimeEvent?: (kind: "api", error: unknown, details?: Record<string, unknown>) => void;
343
352
  }
344
353
  export interface TelegramApiClient {
345
354
  call: <TResponse>(method: string, body: Record<string, unknown>, options?: TelegramApiCallOptions) => Promise<TResponse>;
346
355
  callMultipart: <TResponse>(method: string, fields: Record<string, string>, fileField: string, filePath: string, fileName: string, options?: TelegramApiCallOptions) => Promise<TResponse>;
347
356
  downloadFile: (fileId: string, suggestedName: string, tempDir: string, options?: TelegramFileDownloadOptions) => Promise<string>;
348
- answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
357
+ answerCallbackQuery: (callbackQueryId: string, text?: string, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
349
358
  answerGuestQuery?: (guestQueryId: string, text?: string, options?: TelegramAnswerGuestQueryOptions) => Promise<void>;
350
359
  }
351
360
  export interface TelegramApiTargetActivityRuntime {
@@ -422,16 +431,14 @@ export interface TelegramBridgeApiRuntimeDeps {
422
431
  chatActionMinIntervalMs?: number;
423
432
  chatActionMaxGates?: number;
424
433
  }
425
- export interface TelegramBridgeApiRuntime {
426
- call: <TResponse>(method: string, body: Record<string, unknown>, options?: TelegramApiCallOptions) => Promise<TResponse>;
427
- callMultipart: <TResponse>(method: string, fields: Record<string, string>, fileField: string, filePath: string, fileName: string, options?: TelegramApiCallOptions) => Promise<TResponse>;
434
+ export interface TelegramBridgeApiRuntime extends Pick<TelegramApiClient, "call" | "callMultipart"> {
428
435
  downloadFile: (fileId: string, suggestedName: string, source?: TelegramAttachmentSource) => Promise<string>;
429
436
  deleteWebhook: (signal?: AbortSignal) => Promise<boolean>;
430
437
  getUpdates: (body: Record<string, unknown>, signal?: AbortSignal) => Promise<TelegramUpdate[]>;
431
438
  setMyCommands: (commands: readonly {
432
439
  command: string;
433
440
  description: string;
434
- }[]) => Promise<boolean>;
441
+ }[], options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<boolean>;
435
442
  sendChatAction: (chatId: number, action: string, options?: {
436
443
  message_thread_id?: number;
437
444
  }) => Promise<boolean>;
@@ -446,12 +453,12 @@ export interface TelegramBridgeApiRuntime {
446
453
  entities?: unknown[];
447
454
  message_thread_id?: number;
448
455
  }) => Promise<boolean>;
449
- sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
450
- sendRichMessage: (body: TelegramSendRichMessageBody) => Promise<TelegramSentMessage>;
456
+ sendMessage: (body: TelegramSendMessageBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<TelegramSentMessage>;
457
+ sendRichMessage: (body: TelegramSendRichMessageBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<TelegramSentMessage>;
451
458
  sendRichMessageDraft: (body: TelegramSendRichMessageDraftBody) => Promise<boolean>;
452
- editMessageText: (body: TelegramEditMessageTextBody) => Promise<"edited" | "unchanged">;
459
+ editMessageText: (body: TelegramEditMessageTextBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<"edited" | "unchanged">;
453
460
  editMessageReplyMarkup: (chatId: number, messageId: number, replyMarkup: unknown) => Promise<void>;
454
- answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
461
+ answerCallbackQuery: (callbackQueryId: string, text?: string, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
455
462
  answerGuestQuery: (guestQueryId: string, text?: string, options?: TelegramAnswerGuestQueryOptions) => Promise<void>;
456
463
  /**
457
464
  * Temporary Guest Mode ACK experiment: answers the guest query and returns