@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
@@ -4,9 +4,10 @@
4
4
  * Owns queue item contracts, lane admission, pure queue mutations, and dispatch planning
5
5
  */
6
6
  import { createHash } from "node:crypto";
7
+ import { isPiStaleContextError } from "./pi.js";
8
+ import { isTelegramApiCommitUnknownError } from "./telegram-api.js";
7
9
  import { isVoiceTurn } from "./voice.js";
8
10
  import { isWireRecord as isQueuePayloadRecord } from "./wire.js";
9
- import { isTelegramApiCommitUnknownError } from "./telegram-api.js";
10
11
  export const TELEGRAM_QUEUE_LANE_CONTRACTS = [
11
12
  // Control lane intentionally accepts both direct controls and resume prompts.
12
13
  // Model-switch continuations need prompt semantics but must run before queued user work.
@@ -33,64 +34,139 @@ export const TELEGRAM_QUEUE_LANE_CONTRACTS = [
33
34
  export const TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES = 8 * 1024 * 1024;
34
35
  const TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS = 256;
35
36
  function parseQueuePayloadReceipt(value, kind) {
36
- if (!isQueuePayloadRecord(value) || value.queueKind !== kind ||
37
- typeof value.receiptId !== "string" || !value.receiptId ||
38
- (value.journalBindingKey !== undefined && (typeof value.journalBindingKey !== "string" || !value.journalBindingKey.trim())) ||
39
- !Array.isArray(value.sourceUpdateIds) || value.sourceUpdateIds.length === 0 ||
40
- value.sourceUpdateIds.some((id, index) => !Number.isSafeInteger(id) || id < 0 ||
37
+ if (!isQueuePayloadRecord(value) ||
38
+ value.queueKind !== kind ||
39
+ typeof value.receiptId !== "string" ||
40
+ !value.receiptId ||
41
+ (value.journalBindingKey !== undefined &&
42
+ (typeof value.journalBindingKey !== "string" ||
43
+ !value.journalBindingKey.trim())) ||
44
+ !Array.isArray(value.sourceUpdateIds) ||
45
+ value.sourceUpdateIds.length === 0 ||
46
+ value.sourceUpdateIds.some((id, index) => !Number.isSafeInteger(id) ||
47
+ id < 0 ||
41
48
  (index > 0 && id <= value.sourceUpdateIds[index - 1])))
42
49
  return undefined;
43
- return { queueKind: kind, receiptId: value.receiptId, sourceUpdateIds: value.sourceUpdateIds,
44
- ...(typeof value.journalBindingKey === "string" ? { journalBindingKey: value.journalBindingKey } : {}) };
50
+ return {
51
+ queueKind: kind,
52
+ receiptId: value.receiptId,
53
+ sourceUpdateIds: value.sourceUpdateIds,
54
+ ...(typeof value.journalBindingKey === "string"
55
+ ? { journalBindingKey: value.journalBindingKey }
56
+ : {}),
57
+ };
45
58
  }
46
59
  /** Data decoder shared by authenticated IPC and cold storage; decoding grants no admission. */
47
60
  export function parseTelegramQueueHandoffPayload(value) {
48
- if (!isQueuePayloadRecord(value) || (value.kind !== "prompt" && value.kind !== "control"))
61
+ if (!isQueuePayloadRecord(value) ||
62
+ (value.kind !== "prompt" && value.kind !== "control"))
49
63
  return undefined;
50
- const target = isQueuePayloadRecord(value.target) && typeof value.target.chatId === "number"
51
- ? { chatId: value.target.chatId, ...(typeof value.target.threadId === "number" ? { threadId: value.target.threadId } : {}) } : undefined;
64
+ const target = isQueuePayloadRecord(value.target) &&
65
+ typeof value.target.chatId === "number"
66
+ ? {
67
+ chatId: value.target.chatId,
68
+ ...(typeof value.target.threadId === "number"
69
+ ? { threadId: value.target.threadId }
70
+ : {}),
71
+ }
72
+ : undefined;
52
73
  const transportStamp = isQueuePayloadRecord(value.transportStamp) &&
53
- typeof value.transportStamp.profile === "string" && typeof value.transportStamp.generation === "string"
54
- ? { profile: value.transportStamp.profile, generation: value.transportStamp.generation } : undefined;
55
- if (!Number.isSafeInteger(value.chatId) || (value.target !== undefined && !target) ||
56
- (value.transportStamp !== undefined && !transportStamp) || !Number.isSafeInteger(value.replyToMessageId) ||
57
- (value.guestQueryId !== undefined && typeof value.guestQueryId !== "string") ||
58
- (value.guestInlineMessageId !== undefined && typeof value.guestInlineMessageId !== "string") ||
59
- !Number.isSafeInteger(value.queueOrder) || (value.queueLane !== "control" && value.queueLane !== "priority" && value.queueLane !== "default") ||
60
- !Number.isSafeInteger(value.laneOrder) || typeof value.statusSummary !== "string" || !Array.isArray(value.admissionReceipts))
74
+ typeof value.transportStamp.profile === "string" &&
75
+ typeof value.transportStamp.generation === "string"
76
+ ? {
77
+ profile: value.transportStamp.profile,
78
+ generation: value.transportStamp.generation,
79
+ }
80
+ : undefined;
81
+ if (!Number.isSafeInteger(value.chatId) ||
82
+ (value.target !== undefined && !target) ||
83
+ (value.transportStamp !== undefined && !transportStamp) ||
84
+ !Number.isSafeInteger(value.replyToMessageId) ||
85
+ (value.guestQueryId !== undefined &&
86
+ typeof value.guestQueryId !== "string") ||
87
+ (value.guestInlineMessageId !== undefined &&
88
+ typeof value.guestInlineMessageId !== "string") ||
89
+ !Number.isSafeInteger(value.queueOrder) ||
90
+ (value.queueLane !== "control" &&
91
+ value.queueLane !== "priority" &&
92
+ value.queueLane !== "default") ||
93
+ !Number.isSafeInteger(value.laneOrder) ||
94
+ typeof value.statusSummary !== "string" ||
95
+ !Array.isArray(value.admissionReceipts))
61
96
  return undefined;
62
- const admissionReceipts = value.admissionReceipts.map(receipt => parseQueuePayloadReceipt(receipt, value.kind));
63
- if (admissionReceipts.length === 0 || admissionReceipts.length > TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS ||
64
- admissionReceipts.some(receipt => !receipt))
97
+ const admissionReceipts = value.admissionReceipts.map((receipt) => parseQueuePayloadReceipt(receipt, value.kind));
98
+ if (admissionReceipts.length === 0 ||
99
+ admissionReceipts.length > TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS ||
100
+ admissionReceipts.some((receipt) => !receipt))
65
101
  return undefined;
66
- const base = { chatId: value.chatId, ...(target ? { target } : {}), ...(transportStamp ? { transportStamp } : {}),
102
+ const base = {
103
+ chatId: value.chatId,
104
+ ...(target ? { target } : {}),
105
+ ...(transportStamp ? { transportStamp } : {}),
67
106
  replyToMessageId: value.replyToMessageId,
68
- ...(typeof value.guestQueryId === "string" ? { guestQueryId: value.guestQueryId } : {}),
69
- ...(typeof value.guestInlineMessageId === "string" ? { guestInlineMessageId: value.guestInlineMessageId } : {}),
70
- queueOrder: value.queueOrder, queueLane: value.queueLane,
71
- laneOrder: value.laneOrder, statusSummary: value.statusSummary,
72
- admissionReceipts: admissionReceipts };
107
+ ...(typeof value.guestQueryId === "string"
108
+ ? { guestQueryId: value.guestQueryId }
109
+ : {}),
110
+ ...(typeof value.guestInlineMessageId === "string"
111
+ ? { guestInlineMessageId: value.guestInlineMessageId }
112
+ : {}),
113
+ queueOrder: value.queueOrder,
114
+ queueLane: value.queueLane,
115
+ laneOrder: value.laneOrder,
116
+ statusSummary: value.statusSummary,
117
+ admissionReceipts: admissionReceipts,
118
+ };
73
119
  if (value.kind === "control") {
74
- return value.queueLane === "control" && (value.controlType === "status" || value.controlType === "model")
75
- ? { kind: "control", controlType: value.controlType, ...base } : undefined;
120
+ return value.queueLane === "control" &&
121
+ (value.controlType === "status" || value.controlType === "model")
122
+ ? { kind: "control", controlType: value.controlType, ...base }
123
+ : undefined;
76
124
  }
77
- if (value.queueLane === "control" || !Array.isArray(value.sourceMessageIds) || value.sourceMessageIds.some(id => !Number.isSafeInteger(id)) ||
78
- !Array.isArray(value.queuedAttachments) || value.queuedAttachments.some(attachment => !isQueuePayloadRecord(attachment) ||
79
- typeof attachment.path !== "string" || typeof attachment.fileName !== "string") ||
80
- !Array.isArray(value.content) || value.content.some(content => !isQueuePayloadRecord(content) ||
81
- (content.type === "text" ? typeof content.text !== "string" : content.type === "image"
82
- ? typeof content.data !== "string" || typeof content.mimeType !== "string" : true)) ||
83
- typeof value.historyText !== "string" || (value.priorityEmoji !== undefined && typeof value.priorityEmoji !== "string") ||
84
- (value.reactionSuppressionEmoji !== undefined && typeof value.reactionSuppressionEmoji !== "string") ||
85
- (value.voiceReplyPreferred !== undefined && typeof value.voiceReplyPreferred !== "boolean") ||
86
- (value.voiceReplyRequired !== undefined && typeof value.voiceReplyRequired !== "boolean"))
125
+ if (value.queueLane === "control" ||
126
+ !Array.isArray(value.sourceMessageIds) ||
127
+ value.sourceMessageIds.some((id) => !Number.isSafeInteger(id)) ||
128
+ !Array.isArray(value.queuedAttachments) ||
129
+ value.queuedAttachments.some((attachment) => !isQueuePayloadRecord(attachment) ||
130
+ typeof attachment.path !== "string" ||
131
+ typeof attachment.fileName !== "string") ||
132
+ !Array.isArray(value.content) ||
133
+ value.content.some((content) => !isQueuePayloadRecord(content) ||
134
+ (content.type === "text"
135
+ ? typeof content.text !== "string"
136
+ : content.type === "image"
137
+ ? typeof content.data !== "string" ||
138
+ typeof content.mimeType !== "string"
139
+ : true)) ||
140
+ typeof value.historyText !== "string" ||
141
+ (value.priorityEmoji !== undefined &&
142
+ typeof value.priorityEmoji !== "string") ||
143
+ (value.reactionSuppressionEmoji !== undefined &&
144
+ typeof value.reactionSuppressionEmoji !== "string") ||
145
+ (value.voiceReplyPreferred !== undefined &&
146
+ typeof value.voiceReplyPreferred !== "boolean") ||
147
+ (value.voiceReplyRequired !== undefined &&
148
+ typeof value.voiceReplyRequired !== "boolean"))
87
149
  return undefined;
88
- return { kind: "prompt", ...base, sourceMessageIds: value.sourceMessageIds,
89
- queuedAttachments: value.queuedAttachments, content: value.content, historyText: value.historyText,
90
- ...(typeof value.priorityEmoji === "string" ? { priorityEmoji: value.priorityEmoji } : {}),
91
- ...(typeof value.reactionSuppressionEmoji === "string" ? { reactionSuppressionEmoji: value.reactionSuppressionEmoji } : {}),
92
- ...(typeof value.voiceReplyPreferred === "boolean" ? { voiceReplyPreferred: value.voiceReplyPreferred } : {}),
93
- ...(typeof value.voiceReplyRequired === "boolean" ? { voiceReplyRequired: value.voiceReplyRequired } : {}) };
150
+ return {
151
+ kind: "prompt",
152
+ ...base,
153
+ sourceMessageIds: value.sourceMessageIds,
154
+ queuedAttachments: value.queuedAttachments,
155
+ content: value.content,
156
+ historyText: value.historyText,
157
+ ...(typeof value.priorityEmoji === "string"
158
+ ? { priorityEmoji: value.priorityEmoji }
159
+ : {}),
160
+ ...(typeof value.reactionSuppressionEmoji === "string"
161
+ ? { reactionSuppressionEmoji: value.reactionSuppressionEmoji }
162
+ : {}),
163
+ ...(typeof value.voiceReplyPreferred === "boolean"
164
+ ? { voiceReplyPreferred: value.voiceReplyPreferred }
165
+ : {}),
166
+ ...(typeof value.voiceReplyRequired === "boolean"
167
+ ? { voiceReplyRequired: value.voiceReplyRequired }
168
+ : {}),
169
+ };
94
170
  }
95
171
  export function createTelegramQueueAdmissionReceipt(options) {
96
172
  if (options.sourceUpdateIds.length === 0)
@@ -120,19 +196,12 @@ export function createTelegramQueueAdmissionReceipt(options) {
120
196
  journalBindingKey: scope,
121
197
  };
122
198
  }
123
- export function isTelegramQueueItemDurablyAdmitted(item, isReceiptCommitted) {
124
- assertTelegramQueueItemAdmissionValid(item);
125
- return (item.admissionReceipts ?? []).every(isReceiptCommitted);
126
- }
127
199
  export function getTelegramQueueLaneContract(lane) {
128
200
  const contract = TELEGRAM_QUEUE_LANE_CONTRACTS.find((entry) => entry.lane === lane);
129
201
  if (!contract)
130
202
  throw new Error(`Unknown Telegram queue lane: ${lane}`);
131
203
  return contract;
132
204
  }
133
- export function getTelegramQueueItemAdmissionMode(item) {
134
- return getTelegramQueueLaneContract(item.queueLane).admissionMode;
135
- }
136
205
  export function isTelegramQueueItemAdmissionValid(item) {
137
206
  return getTelegramQueueLaneContract(item.queueLane).allowedKinds.includes(item.kind);
138
207
  }
@@ -248,6 +317,33 @@ export function createTelegramActiveTurnStore() {
248
317
  getSourceMessageIds: () => activeTurn?.sourceMessageIds,
249
318
  };
250
319
  }
320
+ /** Current captured targets only; skipped/receipt-held items remain work, never historical census. */
321
+ export function observeTelegramTargetQueueWork(target, activeTurn, items) {
322
+ let protectedWork = false, unknown = !Number.isSafeInteger(target.chatId) ||
323
+ !Number.isSafeInteger(target.threadId) ||
324
+ target.threadId <= 0;
325
+ const observe = (item) => {
326
+ if (item.chatId !== target.chatId && item.target?.chatId !== target.chatId)
327
+ return;
328
+ const captured = item.target;
329
+ if (!captured ||
330
+ captured.chatId !== item.chatId ||
331
+ !Number.isSafeInteger(captured.threadId) ||
332
+ !captured.threadId ||
333
+ captured.threadId < 0) {
334
+ unknown = true;
335
+ return;
336
+ }
337
+ if (captured.chatId === target.chatId &&
338
+ captured.threadId === target.threadId)
339
+ protectedWork = true;
340
+ };
341
+ if (activeTurn)
342
+ observe(activeTurn);
343
+ for (const item of items)
344
+ observe(item);
345
+ return { protected: protectedWork, unknown };
346
+ }
251
347
  // --- Queue Mutations ---
252
348
  export function partitionTelegramQueueItemsForHistory(items) {
253
349
  const historyTurns = [];
@@ -275,10 +371,7 @@ export function areTelegramQueueAdmissionReceiptsEqual(left, right) {
275
371
  left.sourceUpdateIds.every((updateId, index) => updateId === right.sourceUpdateIds[index]));
276
372
  }
277
373
  function getTelegramQueueAdmissionReceiptKey(receipt) {
278
- return JSON.stringify([
279
- receipt.receiptId,
280
- receipt.journalBindingKey ?? null,
281
- ]);
374
+ return JSON.stringify([receipt.receiptId, receipt.journalBindingKey ?? null]);
282
375
  }
283
376
  function isDuplicateTelegramQueueAdmission(items, item) {
284
377
  const incomingReceipts = item.admissionReceipts ?? [];
@@ -326,7 +419,8 @@ export function createTelegramQueueHandoff(input) {
326
419
  handoffToken: input.handoffToken,
327
420
  payload: createTelegramQueueHandoffPayload(input.item),
328
421
  };
329
- if (Buffer.byteLength(JSON.stringify(handoff)) > TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES) {
422
+ if (Buffer.byteLength(JSON.stringify(handoff)) >
423
+ TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES) {
330
424
  throw new Error("Telegram queue handoff payload exceeds its byte limit.");
331
425
  }
332
426
  return handoff;
@@ -339,27 +433,7 @@ export function createTelegramQueueHandoffPayload(item) {
339
433
  if (item.admissionReceipts.length > TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS) {
340
434
  throw new Error("Telegram queue handoff has too many admission receipts.");
341
435
  }
342
- if (item.kind === "control") {
343
- return structuredClone({
344
- kind: item.kind,
345
- controlType: item.controlType,
346
- chatId: item.chatId,
347
- ...(item.target ? { target: item.target } : {}),
348
- ...(item.transportStamp ? { transportStamp: item.transportStamp } : {}),
349
- replyToMessageId: item.replyToMessageId,
350
- ...(item.guestQueryId ? { guestQueryId: item.guestQueryId } : {}),
351
- ...(item.guestInlineMessageId
352
- ? { guestInlineMessageId: item.guestInlineMessageId }
353
- : {}),
354
- queueOrder: item.queueOrder,
355
- queueLane: item.queueLane,
356
- laneOrder: item.laneOrder,
357
- statusSummary: item.statusSummary,
358
- admissionReceipts: item.admissionReceipts,
359
- });
360
- }
361
- return structuredClone({
362
- kind: item.kind,
436
+ const base = {
363
437
  chatId: item.chatId,
364
438
  ...(item.target ? { target: item.target } : {}),
365
439
  ...(item.transportStamp ? { transportStamp: item.transportStamp } : {}),
@@ -373,6 +447,17 @@ export function createTelegramQueueHandoffPayload(item) {
373
447
  laneOrder: item.laneOrder,
374
448
  statusSummary: item.statusSummary,
375
449
  admissionReceipts: item.admissionReceipts,
450
+ };
451
+ if (item.kind === "control") {
452
+ return structuredClone({
453
+ kind: item.kind,
454
+ controlType: item.controlType,
455
+ ...base,
456
+ });
457
+ }
458
+ return structuredClone({
459
+ kind: item.kind,
460
+ ...base,
376
461
  sourceMessageIds: item.sourceMessageIds,
377
462
  queuedAttachments: item.queuedAttachments,
378
463
  content: item.content,
@@ -566,9 +651,9 @@ export function removeTelegramQueueItemsByMessageIds(items, messageIds, scope) {
566
651
  }
567
652
  function removeTelegramQueuedGuestPromptByOrder(items, queueOrder) {
568
653
  const index = items.findIndex((item) => {
569
- return isPendingTelegramTurn(item) &&
654
+ return (isPendingTelegramTurn(item) &&
570
655
  item.guestQueryId !== undefined &&
571
- item.queueOrder === queueOrder;
656
+ item.queueOrder === queueOrder);
572
657
  });
573
658
  if (index < 0)
574
659
  return { items, removedItems: [], removedCount: 0 };
@@ -583,6 +668,7 @@ export function applyTelegramQueuePromptReactionDisposition(items, messageId, di
583
668
  let nextItems = items;
584
669
  for (const [index, item] of items.entries()) {
585
670
  if (!isPendingTelegramTurn(item) ||
671
+ item.queueLane === "control" ||
586
672
  !isTelegramQueueItemInMessageScope(item, scope) ||
587
673
  !item.sourceMessageIds.includes(messageId)) {
588
674
  continue;
@@ -594,16 +680,14 @@ export function applyTelegramQueuePromptReactionDisposition(items, messageId, di
594
680
  : disposition.kind === "priority" ||
595
681
  disposition.kind === "priority-suppressed";
596
682
  const queueLane = isPriority ? "priority" : "default";
597
- const laneOrder = item.queueLane === queueLane
598
- ? item.laneOrder
599
- : destinationLaneOrder;
683
+ const laneOrder = item.queueLane === queueLane ? item.laneOrder : destinationLaneOrder;
600
684
  if (laneOrder === undefined) {
601
685
  throw new Error("Telegram destination lane order is unavailable.");
602
686
  }
603
687
  const priorityEmoji = disposition.kind === "reaction-transition"
604
688
  ? disposition.priorityEmoji === undefined
605
689
  ? item.priorityEmoji
606
- : disposition.priorityEmoji ?? undefined
690
+ : (disposition.priorityEmoji ?? undefined)
607
691
  : disposition.kind === "priority"
608
692
  ? disposition.emoji
609
693
  : disposition.kind === "priority-suppressed"
@@ -612,7 +696,7 @@ export function applyTelegramQueuePromptReactionDisposition(items, messageId, di
612
696
  const reactionSuppressionEmoji = disposition.kind === "reaction-transition"
613
697
  ? disposition.suppressionEmoji === undefined
614
698
  ? item.reactionSuppressionEmoji
615
- : disposition.suppressionEmoji ?? undefined
699
+ : (disposition.suppressionEmoji ?? undefined)
616
700
  : disposition.kind === "suppressed"
617
701
  ? disposition.emoji
618
702
  : disposition.kind === "priority-suppressed"
@@ -944,12 +1028,17 @@ export function createTelegramAgentEndHook(deps) {
944
1028
  const assistant = deps.isAssistantAlreadyPublished?.(extractedAssistant)
945
1029
  ? { stopReason: extractedAssistant.stopReason }
946
1030
  : extractedAssistant;
947
- const hasPublication = !!assistant.text || assistant.stopReason === "error" || !!turn?.queuedAttachments.length;
948
- const reservation = turn && !turn.guestQueryId && hasPublication ? deps.reserveActiveTurnDelivery?.() : undefined;
1031
+ const hasPublication = !!assistant.text ||
1032
+ assistant.stopReason === "error" ||
1033
+ !!turn?.queuedAttachments.length;
1034
+ const reservation = turn && !turn.guestQueryId && hasPublication
1035
+ ? deps.reserveActiveTurnDelivery?.()
1036
+ : undefined;
949
1037
  const scheduleDelivery = reservation?.schedule ?? deps.scheduleActiveTurnDelivery;
950
1038
  try {
951
1039
  await deps.loadConfig?.();
952
- if (deps.isSessionActive?.(ctx) === false || deps.getActiveTurn() !== turn)
1040
+ if (deps.isSessionActive?.(ctx) === false ||
1041
+ deps.getActiveTurn() !== turn)
953
1042
  return;
954
1043
  await handleTelegramAgentEndRuntime({
955
1044
  turn,
@@ -1026,20 +1115,25 @@ export async function handleTelegramAgentEndRuntime(deps) {
1026
1115
  const replyMarkup = outboundReply?.replyMarkup;
1027
1116
  const isDeliveryActive = () => deps.isSessionActive?.() !== false &&
1028
1117
  (!turn || deps.isTurnTransportActive?.(turn) !== false);
1029
- const preview = turn && !turn.guestQueryId ? deps.preparePreviewDelivery?.(isDeliveryActive) : undefined;
1118
+ const preview = turn && !turn.guestQueryId
1119
+ ? deps.preparePreviewDelivery?.(isDeliveryActive)
1120
+ : undefined;
1030
1121
  const setPreviewPendingText = preview?.setPreviewPendingText ?? deps.setPreviewPendingText;
1031
1122
  const finalizeMarkdownPreview = preview?.finalizeMarkdownPreview ?? deps.finalizeMarkdownPreview;
1032
1123
  const clearPreview = turn
1033
- ? preview ? () => preview.clearPreview(turn.chatId, { target: turn.target })
1034
- : deps.preparePreviewClear?.(turn.chatId, { target: turn.target, isDeliveryActive })
1035
- ?? (() => deps.clearPreview(turn.chatId, { target: turn.target }))
1124
+ ? preview
1125
+ ? () => preview.clearPreview(turn.chatId, { target: turn.target })
1126
+ : (deps.preparePreviewClear?.(turn.chatId, {
1127
+ target: turn.target,
1128
+ isDeliveryActive,
1129
+ }) ?? (() => deps.clearPreview(turn.chatId, { target: turn.target })))
1036
1130
  : undefined;
1037
1131
  const updateStatusIgnoringStaleContext = () => {
1038
1132
  try {
1039
1133
  deps.updateStatus();
1040
1134
  }
1041
1135
  catch (error) {
1042
- if (!isTelegramStaleContextError(error))
1136
+ if (!isPiStaleContextError(error))
1043
1137
  throw error;
1044
1138
  }
1045
1139
  };
@@ -1349,7 +1443,11 @@ export async function handleTelegramAgentEndRuntime(deps) {
1349
1443
  deps.dispatchNextQueuedTelegramTurn();
1350
1444
  };
1351
1445
  if (deps.scheduleActiveTurnDelivery &&
1352
- (endPlan.kind === "text" || endPlan.kind === "attachments-only" || endPlan.shouldSendAbortMessage || endPlan.shouldSendErrorMessage || endPlan.shouldClearPreview)) {
1446
+ (endPlan.kind === "text" ||
1447
+ endPlan.kind === "attachments-only" ||
1448
+ endPlan.shouldSendAbortMessage ||
1449
+ endPlan.shouldSendErrorMessage ||
1450
+ endPlan.shouldClearPreview)) {
1353
1451
  deps.scheduleActiveTurnDelivery(deliverActiveTurn);
1354
1452
  return;
1355
1453
  }
@@ -1372,11 +1470,6 @@ export function createTelegramSessionStateApplier(deps) {
1372
1470
  },
1373
1471
  };
1374
1472
  }
1375
- function isTelegramStaleContextError(error) {
1376
- return (error instanceof Error &&
1377
- (error.message.includes("stale after session") ||
1378
- error.message.includes("stale ctx")));
1379
- }
1380
1473
  export function buildTelegramSessionStartState(currentModel) {
1381
1474
  return {
1382
1475
  currentTelegramModel: currentModel,
@@ -1413,7 +1506,7 @@ export async function startTelegramSessionRuntime(deps) {
1413
1506
  deps.bindDeferredDispatchContext?.(deps.ctx);
1414
1507
  }
1415
1508
  catch (error) {
1416
- if (!isTelegramStaleContextError(error))
1509
+ if (!isPiStaleContextError(error))
1417
1510
  throw error;
1418
1511
  }
1419
1512
  deps.updateStatus();
@@ -1545,12 +1638,24 @@ function updateTelegramQueueStatusRuntime(deps) {
1545
1638
  deps.updateStatus(deps.ctx);
1546
1639
  }
1547
1640
  catch (error) {
1548
- if (!isTelegramStaleContextError(error))
1641
+ if (!isPiStaleContextError(error))
1549
1642
  throw error;
1550
1643
  }
1551
1644
  }
1552
1645
  function commitReorderedTelegramQueueItemsRuntime(items, deps) {
1553
- deps.setQueuedItems([...items].sort(compareTelegramQueueItems));
1646
+ const pendingHead = deps.hasPendingDispatch?.()
1647
+ ? deps.getQueuedItems()[0]
1648
+ : undefined;
1649
+ const ordered = [...items].sort(compareTelegramQueueItems);
1650
+ if (pendingHead) {
1651
+ const index = ordered.findIndex((item) => item.queueOrder === pendingHead.queueOrder &&
1652
+ item.kind === pendingHead.kind &&
1653
+ item.chatId === pendingHead.chatId &&
1654
+ item.replyToMessageId === pendingHead.replyToMessageId);
1655
+ if (index > 0)
1656
+ ordered.unshift(ordered.splice(index, 1)[0]);
1657
+ }
1658
+ deps.setQueuedItems(ordered);
1554
1659
  updateTelegramQueueStatusRuntime(deps);
1555
1660
  }
1556
1661
  function appendTelegramQueueItemRuntime(item, deps) {
@@ -1602,8 +1707,34 @@ function removeTelegramQueuedGuestPromptByOrderRuntime(queueOrder, deps) {
1602
1707
  }
1603
1708
  export function applyTelegramQueuePromptReactionDispositionRuntime(messageId, disposition, deps, scope) {
1604
1709
  const queuedItems = deps.getQueuedItems();
1710
+ const suppression = disposition.kind === "reaction-transition"
1711
+ ? disposition.suppressionEmoji
1712
+ : disposition.kind === "suppressed"
1713
+ ? disposition.emoji
1714
+ : disposition.kind === "priority-suppressed"
1715
+ ? disposition.suppressionEmoji
1716
+ : undefined;
1717
+ // Source-addressed control-lane prompts are explicit continuations. Synthetic model-switch turns have no source IDs.
1718
+ // A Pi-owned dispatched head cannot be removed: agent_start must consume that exact item, not its successor.
1719
+ const cancelled = suppression && deps.hasPendingDispatch
1720
+ ? queuedItems.filter((item, index) => isPendingTelegramTurn(item) &&
1721
+ item.queueLane === "control" &&
1722
+ isTelegramQueueItemInMessageScope(item, scope) &&
1723
+ item.sourceMessageIds.includes(messageId) &&
1724
+ !(index === 0 && deps.hasPendingDispatch()))
1725
+ : [];
1726
+ if (cancelled.length) {
1727
+ if (cancelled.some((item) => (item.admissionReceipts?.length ?? 0) > 0) &&
1728
+ !deps.onItemsDiscarded)
1729
+ return false;
1730
+ deps.onItemsDiscarded?.(cancelled, deps.ctx);
1731
+ deps.setQueuedItems(queuedItems.filter((item) => !cancelled.includes(item)));
1732
+ updateTelegramQueueStatusRuntime(deps);
1733
+ return true;
1734
+ }
1605
1735
  const changesLane = queuedItems.some((item) => {
1606
1736
  if (!isPendingTelegramTurn(item) ||
1737
+ item.queueLane === "control" ||
1607
1738
  !isTelegramQueueItemInMessageScope(item, scope) ||
1608
1739
  !item.sourceMessageIds.includes(messageId)) {
1609
1740
  return false;
@@ -1633,21 +1764,26 @@ export function applyTelegramQueuePromptReactionDispositionRuntime(messageId, di
1633
1764
  }
1634
1765
  export async function enqueueTelegramPromptTurnRuntime(messages, deps) {
1635
1766
  deps.assertExecutionCurrent?.();
1636
- const historyOrders = new Set(planTelegramPromptEnqueue(deps.getQueuedItems(), deps.getFoldQueuedPromptsIntoHistory()).historyTurns.map((turn) => turn.queueOrder));
1637
- deps.setFoldQueuedPromptsIntoHistory(false);
1767
+ const historyOrders = new Set(deps.preserveQueued
1768
+ ? []
1769
+ : planTelegramPromptEnqueue(deps.getQueuedItems(), deps.getFoldQueuedPromptsIntoHistory()).historyTurns.map((turn) => turn.queueOrder));
1770
+ if (!deps.preserveQueued)
1771
+ deps.setFoldQueuedPromptsIntoHistory(false);
1638
1772
  const buildTurn = await deps.prepareTurn(messages);
1639
1773
  deps.assertExecutionCurrent?.();
1640
1774
  // Preserve the Pi-owned head until agent_start, plus later arrivals and current edits/reactions.
1641
1775
  const pendingDispatch = deps.hasPendingDispatch();
1642
1776
  const historyTurns = [];
1643
1777
  const remainingItems = deps.getQueuedItems().filter((item, index) => {
1644
- if ((pendingDispatch && index === 0) || !isPendingTelegramTurn(item) ||
1778
+ if ((pendingDispatch && index === 0) ||
1779
+ !isPendingTelegramTurn(item) ||
1645
1780
  !historyOrders.has(item.queueOrder))
1646
1781
  return true;
1647
1782
  historyTurns.push(item);
1648
1783
  return false;
1649
1784
  });
1650
1785
  const turn = buildTurn(historyTurns);
1786
+ deps.assertExecutionCurrent?.();
1651
1787
  deps.setQueuedItems(appendTelegramQueueItem(remainingItems, turn));
1652
1788
  deps.onQueued?.(turn);
1653
1789
  deps.updateStatus();
@@ -1656,12 +1792,16 @@ export async function enqueueTelegramPromptTurnRuntime(messages, deps) {
1656
1792
  }
1657
1793
  export function createTelegramPromptEnqueueController(deps) {
1658
1794
  return {
1659
- enqueue: (messages, ctx, onQueued) => enqueueTelegramPromptTurnRuntime(messages, {
1795
+ enqueue: (messages, ctx, onQueued, options) => enqueueTelegramPromptTurnRuntime(messages, {
1660
1796
  ...deps,
1661
1797
  prepareTurn: (nextMessages) => deps.prepareTurn(nextMessages, ctx),
1662
1798
  updateStatus: () => deps.updateStatus(ctx),
1663
1799
  dispatchNextQueuedTelegramTurn: () => deps.dispatchNextQueuedTelegramTurn(ctx),
1664
- assertExecutionCurrent: () => deps.assertExecutionCurrent?.(messages),
1800
+ assertExecutionCurrent: () => {
1801
+ deps.assertExecutionCurrent?.(messages);
1802
+ options?.assertCurrent?.();
1803
+ },
1804
+ preserveQueued: options?.preserveQueued,
1665
1805
  onQueued,
1666
1806
  }),
1667
1807
  };
@@ -1963,8 +2103,7 @@ export function createTelegramQueueDispatchController(deps) {
1963
2103
  nextDispatchAnnouncementRequested = false;
1964
2104
  }
1965
2105
  }
1966
- if (nextItem &&
1967
- deps.hasPendingInboundQueueMutationForItem?.(nextItem)) {
2106
+ if (nextItem && deps.hasPendingInboundQueueMutationForItem?.(nextItem)) {
1968
2107
  deps.updateStatus(ctx);
1969
2108
  return;
1970
2109
  }
@@ -1984,9 +2123,10 @@ export function createTelegramQueueDispatchController(deps) {
1984
2123
  const queueDrifted = currentItems.length !== dispatchBasisItems.length ||
1985
2124
  currentItems.some((item, index) => item !== dispatchBasisItems[index]);
1986
2125
  const dispatchEligibilityDrifted = !deps.canDispatch(ctx) ||
1987
- deps.hasPendingInboundQueueMutationForItem?.(dispatchPlan.item) === true ||
1988
- (deps.isQueueItemAdmissionReady?.(dispatchPlan.item) === false) ||
1989
- (deps.isQueueItemTransportActive?.(dispatchPlan.item) === false);
2126
+ deps.hasPendingInboundQueueMutationForItem?.(dispatchPlan.item) ===
2127
+ true ||
2128
+ deps.isQueueItemAdmissionReady?.(dispatchPlan.item) === false ||
2129
+ deps.isQueueItemTransportActive?.(dispatchPlan.item) === false;
1990
2130
  if (queueDrifted || dispatchEligibilityDrifted) {
1991
2131
  const selectedItemRetained = currentItems.includes(dispatchPlan.item);
1992
2132
  const selectedTransportActive = deps.isQueueItemTransportActive?.(dispatchPlan.item) !== false;
@@ -1996,7 +2136,9 @@ export function createTelegramQueueDispatchController(deps) {
1996
2136
  ? dispatchPlan.item
1997
2137
  : undefined;
1998
2138
  deps.updateStatus(ctx);
1999
- if (nextDispatchAnnouncementRequested && queueDrifted && !dispatchEligibilityDrifted) {
2139
+ if (nextDispatchAnnouncementRequested &&
2140
+ queueDrifted &&
2141
+ !dispatchEligibilityDrifted) {
2000
2142
  controller.dispatchNext(ctx);
2001
2143
  }
2002
2144
  return false;
@@ -2065,11 +2207,14 @@ export function createTelegramQueueDispatchController(deps) {
2065
2207
  const announcementGeneration = nextDispatchAnnouncementGeneration;
2066
2208
  const dispatchGeneration = deps.getDispatchGeneration?.();
2067
2209
  deps.updateStatus(ctx);
2068
- void deps.sendTextReply(dispatchPlan.item.chatId, dispatchPlan.item.replyToMessageId, "<b>⏩ Dispatching next queued turn.</b>", { target: dispatchPlan.item.target, parseMode: "HTML" }).catch((error) => {
2210
+ void deps
2211
+ .sendTextReply(dispatchPlan.item.chatId, dispatchPlan.item.replyToMessageId, "<b>⏩ Dispatching next queued turn.</b>", { target: dispatchPlan.item.target, parseMode: "HTML" })
2212
+ .catch((error) => {
2069
2213
  deps.recordRuntimeEvent?.("dispatch", error, {
2070
2214
  phase: "next-announcement",
2071
2215
  });
2072
- }).finally(() => {
2216
+ })
2217
+ .finally(() => {
2073
2218
  try {
2074
2219
  deps.reconcileNextDispatchAnnouncementReplyOwnership?.(dispatchPlan.item);
2075
2220
  }