@llblab/pi-kit 0.1.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 (523) hide show
  1. package/AGENTS.md +35 -0
  2. package/BACKLOG.md +3 -0
  3. package/CHANGELOG.md +9 -0
  4. package/LICENSE +21 -0
  5. package/README.md +43 -0
  6. package/node_modules/@llblab/pi-actors/AGENTS.md +150 -0
  7. package/node_modules/@llblab/pi-actors/BACKLOG.md +6 -0
  8. package/node_modules/@llblab/pi-actors/CHANGELOG.md +865 -0
  9. package/node_modules/@llblab/pi-actors/README.md +231 -0
  10. package/node_modules/@llblab/pi-actors/banner.jpg +0 -0
  11. package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/artifact-manifest.json +9 -0
  12. package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/control-endpoint.json +6 -0
  13. package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/control-record.json +9 -0
  14. package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/recipe-summary.json +8 -0
  15. package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/run-state.json +10 -0
  16. package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/trace-event.json +9 -0
  17. package/node_modules/@llblab/pi-actors/dist/index.d.ts +7 -0
  18. package/node_modules/@llblab/pi-actors/dist/index.js +17 -0
  19. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +146 -0
  20. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +800 -0
  21. package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.d.ts +20 -0
  22. package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.js +103 -0
  23. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.d.ts +85 -0
  24. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +804 -0
  25. package/node_modules/@llblab/pi-actors/dist/lib/config.d.ts +34 -0
  26. package/node_modules/@llblab/pi-actors/dist/lib/config.js +216 -0
  27. package/node_modules/@llblab/pi-actors/dist/lib/control-projection.d.ts +20 -0
  28. package/node_modules/@llblab/pi-actors/dist/lib/control-projection.js +66 -0
  29. package/node_modules/@llblab/pi-actors/dist/lib/control.d.ts +15 -0
  30. package/node_modules/@llblab/pi-actors/dist/lib/control.js +97 -0
  31. package/node_modules/@llblab/pi-actors/dist/lib/draft-consolidation-transaction.d.ts +65 -0
  32. package/node_modules/@llblab/pi-actors/dist/lib/draft-consolidation-transaction.js +610 -0
  33. package/node_modules/@llblab/pi-actors/dist/lib/draft-consolidation.d.ts +35 -0
  34. package/node_modules/@llblab/pi-actors/dist/lib/draft-consolidation.js +126 -0
  35. package/node_modules/@llblab/pi-actors/dist/lib/draft-review.d.ts +56 -0
  36. package/node_modules/@llblab/pi-actors/dist/lib/draft-review.js +254 -0
  37. package/node_modules/@llblab/pi-actors/dist/lib/draft-sleep.d.ts +65 -0
  38. package/node_modules/@llblab/pi-actors/dist/lib/draft-sleep.js +467 -0
  39. package/node_modules/@llblab/pi-actors/dist/lib/execution-output.d.ts +25 -0
  40. package/node_modules/@llblab/pi-actors/dist/lib/execution-output.js +88 -0
  41. package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.d.ts +17 -0
  42. package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.js +85 -0
  43. package/node_modules/@llblab/pi-actors/dist/lib/execution.d.ts +99 -0
  44. package/node_modules/@llblab/pi-actors/dist/lib/execution.js +725 -0
  45. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.d.ts +21 -0
  46. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +141 -0
  47. package/node_modules/@llblab/pi-actors/dist/lib/file-state.d.ts +18 -0
  48. package/node_modules/@llblab/pi-actors/dist/lib/file-state.js +310 -0
  49. package/node_modules/@llblab/pi-actors/dist/lib/identity.d.ts +9 -0
  50. package/node_modules/@llblab/pi-actors/dist/lib/identity.js +27 -0
  51. package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.d.ts +16 -0
  52. package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.js +57 -0
  53. package/node_modules/@llblab/pi-actors/dist/lib/inspector-command.d.ts +7 -0
  54. package/node_modules/@llblab/pi-actors/dist/lib/inspector-command.js +37 -0
  55. package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.d.ts +100 -0
  56. package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.js +824 -0
  57. package/node_modules/@llblab/pi-actors/dist/lib/inspector.d.ts +20 -0
  58. package/node_modules/@llblab/pi-actors/dist/lib/inspector.js +136 -0
  59. package/node_modules/@llblab/pi-actors/dist/lib/limits.d.ts +28 -0
  60. package/node_modules/@llblab/pi-actors/dist/lib/limits.js +28 -0
  61. package/node_modules/@llblab/pi-actors/dist/lib/model-context.d.ts +56 -0
  62. package/node_modules/@llblab/pi-actors/dist/lib/model-context.js +220 -0
  63. package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +186 -0
  64. package/node_modules/@llblab/pi-actors/dist/lib/observability.js +951 -0
  65. package/node_modules/@llblab/pi-actors/dist/lib/paths.d.ts +27 -0
  66. package/node_modules/@llblab/pi-actors/dist/lib/paths.js +72 -0
  67. package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +19 -0
  68. package/node_modules/@llblab/pi-actors/dist/lib/pi.js +21 -0
  69. package/node_modules/@llblab/pi-actors/dist/lib/preflight-diagnostics.d.ts +27 -0
  70. package/node_modules/@llblab/pi-actors/dist/lib/preflight-diagnostics.js +86 -0
  71. package/node_modules/@llblab/pi-actors/dist/lib/prompts.d.ts +24 -0
  72. package/node_modules/@llblab/pi-actors/dist/lib/prompts.js +49 -0
  73. package/node_modules/@llblab/pi-actors/dist/lib/recipe-control.d.ts +7 -0
  74. package/node_modules/@llblab/pi-actors/dist/lib/recipe-control.js +43 -0
  75. package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.d.ts +36 -0
  76. package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.js +197 -0
  77. package/node_modules/@llblab/pi-actors/dist/lib/recipes-discovery.d.ts +77 -0
  78. package/node_modules/@llblab/pi-actors/dist/lib/recipes-discovery.js +783 -0
  79. package/node_modules/@llblab/pi-actors/dist/lib/recipes-references.d.ts +113 -0
  80. package/node_modules/@llblab/pi-actors/dist/lib/recipes-references.js +1108 -0
  81. package/node_modules/@llblab/pi-actors/dist/lib/recipes-usage.d.ts +21 -0
  82. package/node_modules/@llblab/pi-actors/dist/lib/recipes-usage.js +494 -0
  83. package/node_modules/@llblab/pi-actors/dist/lib/registry.d.ts +67 -0
  84. package/node_modules/@llblab/pi-actors/dist/lib/registry.js +504 -0
  85. package/node_modules/@llblab/pi-actors/dist/lib/review-control.d.ts +14 -0
  86. package/node_modules/@llblab/pi-actors/dist/lib/review-control.js +110 -0
  87. package/node_modules/@llblab/pi-actors/dist/lib/review-diagnostics.d.ts +11 -0
  88. package/node_modules/@llblab/pi-actors/dist/lib/review-diagnostics.js +148 -0
  89. package/node_modules/@llblab/pi-actors/dist/lib/review-projection.d.ts +14 -0
  90. package/node_modules/@llblab/pi-actors/dist/lib/review-projection.js +166 -0
  91. package/node_modules/@llblab/pi-actors/dist/lib/run-evidence-policy.d.ts +95 -0
  92. package/node_modules/@llblab/pi-actors/dist/lib/run-evidence-policy.js +177 -0
  93. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.d.ts +18 -0
  94. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +125 -0
  95. package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.d.ts +21 -0
  96. package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.js +68 -0
  97. package/node_modules/@llblab/pi-actors/dist/lib/runs-control-delivery.d.ts +28 -0
  98. package/node_modules/@llblab/pi-actors/dist/lib/runs-control-delivery.js +150 -0
  99. package/node_modules/@llblab/pi-actors/dist/lib/runs-control.d.ts +21 -0
  100. package/node_modules/@llblab/pi-actors/dist/lib/runs-control.js +89 -0
  101. package/node_modules/@llblab/pi-actors/dist/lib/runs-controls.d.ts +39 -0
  102. package/node_modules/@llblab/pi-actors/dist/lib/runs-controls.js +283 -0
  103. package/node_modules/@llblab/pi-actors/dist/lib/runs-identity.d.ts +5 -0
  104. package/node_modules/@llblab/pi-actors/dist/lib/runs-identity.js +11 -0
  105. package/node_modules/@llblab/pi-actors/dist/lib/runs-index.d.ts +27 -0
  106. package/node_modules/@llblab/pi-actors/dist/lib/runs-index.js +142 -0
  107. package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.d.ts +7 -0
  108. package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.js +96 -0
  109. package/node_modules/@llblab/pi-actors/dist/lib/runs-parent-teardown.d.ts +51 -0
  110. package/node_modules/@llblab/pi-actors/dist/lib/runs-parent-teardown.js +172 -0
  111. package/node_modules/@llblab/pi-actors/dist/lib/runs-process.d.ts +22 -0
  112. package/node_modules/@llblab/pi-actors/dist/lib/runs-process.js +125 -0
  113. package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.d.ts +18 -0
  114. package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.js +108 -0
  115. package/node_modules/@llblab/pi-actors/dist/lib/runs-start.d.ts +12 -0
  116. package/node_modules/@llblab/pi-actors/dist/lib/runs-start.js +68 -0
  117. package/node_modules/@llblab/pi-actors/dist/lib/runs-status.d.ts +10 -0
  118. package/node_modules/@llblab/pi-actors/dist/lib/runs-status.js +68 -0
  119. package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.d.ts +48 -0
  120. package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.js +491 -0
  121. package/node_modules/@llblab/pi-actors/dist/lib/runtime-identity.d.ts +7 -0
  122. package/node_modules/@llblab/pi-actors/dist/lib/runtime-identity.js +35 -0
  123. package/node_modules/@llblab/pi-actors/dist/lib/runtime-triage.d.ts +29 -0
  124. package/node_modules/@llblab/pi-actors/dist/lib/runtime-triage.js +60 -0
  125. package/node_modules/@llblab/pi-actors/dist/lib/runtime.d.ts +69 -0
  126. package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +339 -0
  127. package/node_modules/@llblab/pi-actors/dist/lib/schema.d.ts +60 -0
  128. package/node_modules/@llblab/pi-actors/dist/lib/schema.js +428 -0
  129. package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.d.ts +45 -0
  130. package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.js +204 -0
  131. package/node_modules/@llblab/pi-actors/dist/lib/state-readers.d.ts +21 -0
  132. package/node_modules/@llblab/pi-actors/dist/lib/state-readers.js +74 -0
  133. package/node_modules/@llblab/pi-actors/dist/lib/temp.d.ts +10 -0
  134. package/node_modules/@llblab/pi-actors/dist/lib/temp.js +90 -0
  135. package/node_modules/@llblab/pi-actors/dist/lib/tool-review-lineage-transaction.d.ts +27 -0
  136. package/node_modules/@llblab/pi-actors/dist/lib/tool-review-lineage-transaction.js +597 -0
  137. package/node_modules/@llblab/pi-actors/dist/lib/tool-review-lineage.d.ts +24 -0
  138. package/node_modules/@llblab/pi-actors/dist/lib/tool-review-lineage.js +98 -0
  139. package/node_modules/@llblab/pi-actors/dist/lib/tool-review-scheduler.d.ts +80 -0
  140. package/node_modules/@llblab/pi-actors/dist/lib/tool-review-scheduler.js +494 -0
  141. package/node_modules/@llblab/pi-actors/dist/lib/tool-review-transaction.d.ts +50 -0
  142. package/node_modules/@llblab/pi-actors/dist/lib/tool-review-transaction.js +362 -0
  143. package/node_modules/@llblab/pi-actors/dist/lib/tool-review.d.ts +56 -0
  144. package/node_modules/@llblab/pi-actors/dist/lib/tool-review.js +197 -0
  145. package/node_modules/@llblab/pi-actors/dist/lib/tools-access.d.ts +19 -0
  146. package/node_modules/@llblab/pi-actors/dist/lib/tools-access.js +44 -0
  147. package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.d.ts +15 -0
  148. package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.js +532 -0
  149. package/node_modules/@llblab/pi-actors/dist/lib/tools-local.d.ts +17 -0
  150. package/node_modules/@llblab/pi-actors/dist/lib/tools-local.js +191 -0
  151. package/node_modules/@llblab/pi-actors/dist/lib/tools-message.d.ts +9 -0
  152. package/node_modules/@llblab/pi-actors/dist/lib/tools-message.js +139 -0
  153. package/node_modules/@llblab/pi-actors/dist/lib/tools-register.d.ts +18 -0
  154. package/node_modules/@llblab/pi-actors/dist/lib/tools-register.js +41 -0
  155. package/node_modules/@llblab/pi-actors/dist/lib/tools-response.d.ts +20 -0
  156. package/node_modules/@llblab/pi-actors/dist/lib/tools-response.js +243 -0
  157. package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.d.ts +15 -0
  158. package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.js +133 -0
  159. package/node_modules/@llblab/pi-actors/dist/lib/tools.d.ts +28 -0
  160. package/node_modules/@llblab/pi-actors/dist/lib/tools.js +49 -0
  161. package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.d.ts +22 -0
  162. package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.js +231 -0
  163. package/node_modules/@llblab/pi-actors/dist/pi-actors/index.js +1 -0
  164. package/node_modules/@llblab/pi-actors/dist/scripts/async-runner.mjs +555 -0
  165. package/node_modules/@llblab/pi-actors/dist/scripts/build-dist.mjs +60 -0
  166. package/node_modules/@llblab/pi-actors/dist/scripts/conformance.mjs +63 -0
  167. package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +126 -0
  168. package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/command-validate.json +13 -0
  169. package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/jsonl-tail.json +10 -0
  170. package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/recipe-validate.json +11 -0
  171. package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/resource-locker-snapshot.json +11 -0
  172. package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/resource-locker.json +25 -0
  173. package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/run-ops-snapshot.json +15 -0
  174. package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/run-state-files.json +13 -0
  175. package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/run-summary.json +9 -0
  176. package/node_modules/@llblab/pi-actors/dist/skills/actors/references/diagnostics.md +44 -0
  177. package/node_modules/@llblab/pi-actors/dist/skills/actors/references/persistent-tools.md +76 -0
  178. package/node_modules/@llblab/pi-actors/dist/skills/actors/references/recipes.md +51 -0
  179. package/node_modules/@llblab/pi-actors/dist/skills/actors/references/runs.md +41 -0
  180. package/node_modules/@llblab/pi-actors/dist/skills/actors/scripts/resource-locker.mjs +425 -0
  181. package/node_modules/@llblab/pi-actors/dist/skills/actors/scripts/run-utils.mjs +164 -0
  182. package/node_modules/@llblab/pi-actors/dist/skills/actors/scripts/validate-recipe.mjs +370 -0
  183. package/node_modules/@llblab/pi-actors/dist/skills/artifacts/SKILL.md +33 -0
  184. package/node_modules/@llblab/pi-actors/dist/skills/artifacts/recipes/bundle.json +77 -0
  185. package/node_modules/@llblab/pi-actors/dist/skills/artifacts/recipes/file-write.json +14 -0
  186. package/node_modules/@llblab/pi-actors/dist/skills/artifacts/recipes/manifest.json +14 -0
  187. package/node_modules/@llblab/pi-actors/dist/skills/artifacts/recipes/report.json +39 -0
  188. package/node_modules/@llblab/pi-actors/dist/skills/artifacts/recipes/write.json +53 -0
  189. package/node_modules/@llblab/pi-actors/dist/skills/artifacts/scripts/artifact-utils.mjs +63 -0
  190. package/node_modules/@llblab/pi-actors/dist/skills/music-player/SKILL.md +57 -0
  191. package/node_modules/@llblab/pi-actors/dist/skills/music-player/genapps/music-player.mjs +360 -0
  192. package/node_modules/@llblab/pi-actors/dist/skills/music-player/recipes/playback.json +29 -0
  193. package/node_modules/@llblab/pi-actors/dist/skills/music-player/scripts/playback-client.mjs +143 -0
  194. package/node_modules/@llblab/pi-actors/dist/skills/music-player/scripts/playback.mjs +1446 -0
  195. package/node_modules/@llblab/pi-actors/dist/skills/project-work/SKILL.md +37 -0
  196. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/changelog-head.json +11 -0
  197. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/changelog-section.json +11 -0
  198. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/docs-maintenance.json +66 -0
  199. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/git-log.json +11 -0
  200. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/git-status.json +9 -0
  201. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/markdown-index.json +14 -0
  202. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/package-summary.json +9 -0
  203. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/release-readiness.json +115 -0
  204. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/release-summary.json +74 -0
  205. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/repo-health.json +75 -0
  206. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/run-ops.json +56 -0
  207. package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/skill-summary.json +11 -0
  208. package/node_modules/@llblab/pi-actors/dist/skills/project-work/scripts/project-utils.mjs +89 -0
  209. package/node_modules/@llblab/pi-actors/dist/skills/recipe-memory/SKILL.md +36 -0
  210. package/node_modules/@llblab/pi-actors/dist/skills/recipe-memory/recipes/draft-review.json +14 -0
  211. package/node_modules/@llblab/pi-actors/dist/skills/recipe-memory/recipes/tool-review.json +14 -0
  212. package/node_modules/@llblab/pi-actors/dist/skills/swarm/SKILL.md +84 -0
  213. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/architect.json +82 -0
  214. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/checkpoint-continuation.json +51 -0
  215. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/development-tasking.json +67 -0
  216. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/lens-review.json +63 -0
  217. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/quorum-review.json +65 -0
  218. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/research-synthesis.json +81 -0
  219. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/review-readiness.json +59 -0
  220. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-artifact.json +21 -0
  221. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-checkpoint.json +21 -0
  222. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-conflict-report.json +19 -0
  223. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-contradiction-map.json +20 -0
  224. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-critic.json +22 -0
  225. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-evidence-map.json +20 -0
  226. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-followup.json +21 -0
  227. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-judge.json +22 -0
  228. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-merge.json +24 -0
  229. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-normalize.json +20 -0
  230. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-plan.json +20 -0
  231. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-preflight.json +18 -0
  232. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-prompt.json +16 -0
  233. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-prompts.json +39 -0
  234. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-quorum.json +31 -0
  235. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-review-coordinator.json +170 -0
  236. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-review.json +26 -0
  237. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-task-card.json +22 -0
  238. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-tools.json +15 -0
  239. package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-verify.json +23 -0
  240. package/node_modules/@llblab/pi-actors/dist/skills/swarm/references/development-swarm.md +189 -0
  241. package/node_modules/@llblab/pi-actors/dist/skills/swarm/references/review-swarms.md +115 -0
  242. package/node_modules/@llblab/pi-actors/docs/README.md +20 -0
  243. package/node_modules/@llblab/pi-actors/docs/actor-inspector.md +50 -0
  244. package/node_modules/@llblab/pi-actors/docs/async-runs.md +141 -0
  245. package/node_modules/@llblab/pi-actors/docs/command-templates.md +321 -0
  246. package/node_modules/@llblab/pi-actors/docs/recipe-library.md +89 -0
  247. package/node_modules/@llblab/pi-actors/docs/releasing.md +28 -0
  248. package/node_modules/@llblab/pi-actors/docs/template-recipes.md +175 -0
  249. package/node_modules/@llblab/pi-actors/docs/tool-registry.md +90 -0
  250. package/node_modules/@llblab/pi-actors/fixtures/protocol/artifact-manifest.json +9 -0
  251. package/node_modules/@llblab/pi-actors/fixtures/protocol/control-endpoint.json +6 -0
  252. package/node_modules/@llblab/pi-actors/fixtures/protocol/control-record.json +9 -0
  253. package/node_modules/@llblab/pi-actors/fixtures/protocol/recipe-summary.json +8 -0
  254. package/node_modules/@llblab/pi-actors/fixtures/protocol/run-state.json +10 -0
  255. package/node_modules/@llblab/pi-actors/fixtures/protocol/trace-event.json +9 -0
  256. package/node_modules/@llblab/pi-actors/index.ts +30 -0
  257. package/node_modules/@llblab/pi-actors/lib/async-runs.ts +1245 -0
  258. package/node_modules/@llblab/pi-actors/lib/automatic-review-runtime.ts +148 -0
  259. package/node_modules/@llblab/pi-actors/lib/command-templates.ts +1116 -0
  260. package/node_modules/@llblab/pi-actors/lib/config.ts +261 -0
  261. package/node_modules/@llblab/pi-actors/lib/control-projection.ts +105 -0
  262. package/node_modules/@llblab/pi-actors/lib/control.ts +117 -0
  263. package/node_modules/@llblab/pi-actors/lib/draft-consolidation-transaction.ts +821 -0
  264. package/node_modules/@llblab/pi-actors/lib/draft-consolidation.ts +181 -0
  265. package/node_modules/@llblab/pi-actors/lib/draft-review.ts +325 -0
  266. package/node_modules/@llblab/pi-actors/lib/draft-sleep.ts +575 -0
  267. package/node_modules/@llblab/pi-actors/lib/execution-output.ts +123 -0
  268. package/node_modules/@llblab/pi-actors/lib/execution-sessions.ts +111 -0
  269. package/node_modules/@llblab/pi-actors/lib/execution.ts +1153 -0
  270. package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +181 -0
  271. package/node_modules/@llblab/pi-actors/lib/file-state.ts +306 -0
  272. package/node_modules/@llblab/pi-actors/lib/identity.ts +29 -0
  273. package/node_modules/@llblab/pi-actors/lib/inspector-actions.ts +79 -0
  274. package/node_modules/@llblab/pi-actors/lib/inspector-command.ts +54 -0
  275. package/node_modules/@llblab/pi-actors/lib/inspector-overlay.ts +901 -0
  276. package/node_modules/@llblab/pi-actors/lib/inspector.ts +204 -0
  277. package/node_modules/@llblab/pi-actors/lib/limits.ts +29 -0
  278. package/node_modules/@llblab/pi-actors/lib/model-context.ts +359 -0
  279. package/node_modules/@llblab/pi-actors/lib/observability.ts +1337 -0
  280. package/node_modules/@llblab/pi-actors/lib/paths.ts +111 -0
  281. package/node_modules/@llblab/pi-actors/lib/pi.ts +47 -0
  282. package/node_modules/@llblab/pi-actors/lib/preflight-diagnostics.ts +132 -0
  283. package/node_modules/@llblab/pi-actors/lib/prompts.ts +69 -0
  284. package/node_modules/@llblab/pi-actors/lib/recipe-control.ts +52 -0
  285. package/node_modules/@llblab/pi-actors/lib/recipes-context.ts +268 -0
  286. package/node_modules/@llblab/pi-actors/lib/recipes-discovery.ts +1060 -0
  287. package/node_modules/@llblab/pi-actors/lib/recipes-references.ts +1598 -0
  288. package/node_modules/@llblab/pi-actors/lib/recipes-usage.ts +638 -0
  289. package/node_modules/@llblab/pi-actors/lib/registry.ts +806 -0
  290. package/node_modules/@llblab/pi-actors/lib/review-control.ts +136 -0
  291. package/node_modules/@llblab/pi-actors/lib/review-diagnostics.ts +164 -0
  292. package/node_modules/@llblab/pi-actors/lib/review-projection.ts +196 -0
  293. package/node_modules/@llblab/pi-actors/lib/run-evidence-policy.ts +242 -0
  294. package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +155 -0
  295. package/node_modules/@llblab/pi-actors/lib/runs-artifacts.ts +96 -0
  296. package/node_modules/@llblab/pi-actors/lib/runs-control-delivery.ts +209 -0
  297. package/node_modules/@llblab/pi-actors/lib/runs-control.ts +148 -0
  298. package/node_modules/@llblab/pi-actors/lib/runs-controls.ts +282 -0
  299. package/node_modules/@llblab/pi-actors/lib/runs-identity.ts +14 -0
  300. package/node_modules/@llblab/pi-actors/lib/runs-index.ts +192 -0
  301. package/node_modules/@llblab/pi-actors/lib/runs-ownership.ts +136 -0
  302. package/node_modules/@llblab/pi-actors/lib/runs-parent-teardown.ts +257 -0
  303. package/node_modules/@llblab/pi-actors/lib/runs-process.ts +174 -0
  304. package/node_modules/@llblab/pi-actors/lib/runs-retention.ts +140 -0
  305. package/node_modules/@llblab/pi-actors/lib/runs-start.ts +113 -0
  306. package/node_modules/@llblab/pi-actors/lib/runs-status.ts +101 -0
  307. package/node_modules/@llblab/pi-actors/lib/runs-trace.ts +611 -0
  308. package/node_modules/@llblab/pi-actors/lib/runtime-identity.ts +39 -0
  309. package/node_modules/@llblab/pi-actors/lib/runtime-triage.ts +106 -0
  310. package/node_modules/@llblab/pi-actors/lib/runtime.ts +430 -0
  311. package/node_modules/@llblab/pi-actors/lib/schema.ts +525 -0
  312. package/node_modules/@llblab/pi-actors/lib/session-evidence.ts +302 -0
  313. package/node_modules/@llblab/pi-actors/lib/state-readers.ts +93 -0
  314. package/node_modules/@llblab/pi-actors/lib/temp.ts +99 -0
  315. package/node_modules/@llblab/pi-actors/lib/tool-review-lineage-transaction.ts +881 -0
  316. package/node_modules/@llblab/pi-actors/lib/tool-review-lineage.ts +145 -0
  317. package/node_modules/@llblab/pi-actors/lib/tool-review-scheduler.ts +635 -0
  318. package/node_modules/@llblab/pi-actors/lib/tool-review-transaction.ts +563 -0
  319. package/node_modules/@llblab/pi-actors/lib/tool-review.ts +270 -0
  320. package/node_modules/@llblab/pi-actors/lib/tools-access.ts +68 -0
  321. package/node_modules/@llblab/pi-actors/lib/tools-inspect.ts +673 -0
  322. package/node_modules/@llblab/pi-actors/lib/tools-local.ts +303 -0
  323. package/node_modules/@llblab/pi-actors/lib/tools-message.ts +176 -0
  324. package/node_modules/@llblab/pi-actors/lib/tools-register.ts +63 -0
  325. package/node_modules/@llblab/pi-actors/lib/tools-response.ts +285 -0
  326. package/node_modules/@llblab/pi-actors/lib/tools-spawn.ts +212 -0
  327. package/node_modules/@llblab/pi-actors/lib/tools.ts +88 -0
  328. package/node_modules/@llblab/pi-actors/lib/trace-projection.ts +306 -0
  329. package/node_modules/@llblab/pi-actors/package.json +76 -0
  330. package/node_modules/@llblab/pi-actors/scripts/async-runner.mjs +555 -0
  331. package/node_modules/@llblab/pi-actors/scripts/build-dist.mjs +60 -0
  332. package/node_modules/@llblab/pi-actors/scripts/conformance.mjs +63 -0
  333. package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +126 -0
  334. package/node_modules/@llblab/pi-actors/skills/actors/recipes/command-validate.json +13 -0
  335. package/node_modules/@llblab/pi-actors/skills/actors/recipes/jsonl-tail.json +10 -0
  336. package/node_modules/@llblab/pi-actors/skills/actors/recipes/recipe-validate.json +11 -0
  337. package/node_modules/@llblab/pi-actors/skills/actors/recipes/resource-locker-snapshot.json +11 -0
  338. package/node_modules/@llblab/pi-actors/skills/actors/recipes/resource-locker.json +25 -0
  339. package/node_modules/@llblab/pi-actors/skills/actors/recipes/run-ops-snapshot.json +15 -0
  340. package/node_modules/@llblab/pi-actors/skills/actors/recipes/run-state-files.json +13 -0
  341. package/node_modules/@llblab/pi-actors/skills/actors/recipes/run-summary.json +9 -0
  342. package/node_modules/@llblab/pi-actors/skills/actors/references/diagnostics.md +44 -0
  343. package/node_modules/@llblab/pi-actors/skills/actors/references/persistent-tools.md +76 -0
  344. package/node_modules/@llblab/pi-actors/skills/actors/references/recipes.md +51 -0
  345. package/node_modules/@llblab/pi-actors/skills/actors/references/runs.md +41 -0
  346. package/node_modules/@llblab/pi-actors/skills/actors/scripts/resource-locker.mjs +425 -0
  347. package/node_modules/@llblab/pi-actors/skills/actors/scripts/run-utils.mjs +164 -0
  348. package/node_modules/@llblab/pi-actors/skills/actors/scripts/validate-recipe.mjs +370 -0
  349. package/node_modules/@llblab/pi-actors/skills/artifacts/SKILL.md +33 -0
  350. package/node_modules/@llblab/pi-actors/skills/artifacts/recipes/bundle.json +77 -0
  351. package/node_modules/@llblab/pi-actors/skills/artifacts/recipes/file-write.json +14 -0
  352. package/node_modules/@llblab/pi-actors/skills/artifacts/recipes/manifest.json +14 -0
  353. package/node_modules/@llblab/pi-actors/skills/artifacts/recipes/report.json +39 -0
  354. package/node_modules/@llblab/pi-actors/skills/artifacts/recipes/write.json +53 -0
  355. package/node_modules/@llblab/pi-actors/skills/artifacts/scripts/artifact-utils.mjs +63 -0
  356. package/node_modules/@llblab/pi-actors/skills/music-player/SKILL.md +57 -0
  357. package/node_modules/@llblab/pi-actors/skills/music-player/genapps/music-player.mjs +360 -0
  358. package/node_modules/@llblab/pi-actors/skills/music-player/recipes/playback.json +29 -0
  359. package/node_modules/@llblab/pi-actors/skills/music-player/scripts/playback-client.mjs +143 -0
  360. package/node_modules/@llblab/pi-actors/skills/music-player/scripts/playback.mjs +1446 -0
  361. package/node_modules/@llblab/pi-actors/skills/project-work/SKILL.md +37 -0
  362. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/changelog-head.json +11 -0
  363. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/changelog-section.json +11 -0
  364. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/docs-maintenance.json +66 -0
  365. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/git-log.json +11 -0
  366. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/git-status.json +9 -0
  367. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/markdown-index.json +14 -0
  368. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/package-summary.json +9 -0
  369. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/release-readiness.json +115 -0
  370. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/release-summary.json +74 -0
  371. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/repo-health.json +75 -0
  372. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/run-ops.json +56 -0
  373. package/node_modules/@llblab/pi-actors/skills/project-work/recipes/skill-summary.json +11 -0
  374. package/node_modules/@llblab/pi-actors/skills/project-work/scripts/project-utils.mjs +89 -0
  375. package/node_modules/@llblab/pi-actors/skills/recipe-memory/SKILL.md +36 -0
  376. package/node_modules/@llblab/pi-actors/skills/recipe-memory/recipes/draft-review.json +14 -0
  377. package/node_modules/@llblab/pi-actors/skills/recipe-memory/recipes/tool-review.json +14 -0
  378. package/node_modules/@llblab/pi-actors/skills/swarm/SKILL.md +84 -0
  379. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/architect.json +82 -0
  380. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/checkpoint-continuation.json +51 -0
  381. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/development-tasking.json +67 -0
  382. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/lens-review.json +63 -0
  383. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/quorum-review.json +65 -0
  384. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/research-synthesis.json +81 -0
  385. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/review-readiness.json +59 -0
  386. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-artifact.json +21 -0
  387. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-checkpoint.json +21 -0
  388. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-conflict-report.json +19 -0
  389. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-contradiction-map.json +20 -0
  390. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-critic.json +22 -0
  391. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-evidence-map.json +20 -0
  392. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-followup.json +21 -0
  393. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-judge.json +22 -0
  394. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-merge.json +24 -0
  395. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-normalize.json +20 -0
  396. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-plan.json +20 -0
  397. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-preflight.json +18 -0
  398. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-prompt.json +16 -0
  399. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-prompts.json +39 -0
  400. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-quorum.json +31 -0
  401. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-review-coordinator.json +170 -0
  402. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-review.json +26 -0
  403. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-task-card.json +22 -0
  404. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-tools.json +15 -0
  405. package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-verify.json +23 -0
  406. package/node_modules/@llblab/pi-actors/skills/swarm/references/development-swarm.md +189 -0
  407. package/node_modules/@llblab/pi-actors/skills/swarm/references/review-swarms.md +115 -0
  408. package/node_modules/@llblab/pi-codex-usage/AGENTS.md +17 -0
  409. package/node_modules/@llblab/pi-codex-usage/BACKLOG.md +8 -0
  410. package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +80 -0
  411. package/node_modules/@llblab/pi-codex-usage/LICENSE +22 -0
  412. package/node_modules/@llblab/pi-codex-usage/README.md +113 -0
  413. package/node_modules/@llblab/pi-codex-usage/banner.jpg +0 -0
  414. package/node_modules/@llblab/pi-codex-usage/index.ts +1525 -0
  415. package/node_modules/@llblab/pi-codex-usage/package.json +59 -0
  416. package/node_modules/@llblab/pi-grow-loop/AGENTS.md +56 -0
  417. package/node_modules/@llblab/pi-grow-loop/BACKLOG.md +3 -0
  418. package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +126 -0
  419. package/node_modules/@llblab/pi-grow-loop/README.md +216 -0
  420. package/node_modules/@llblab/pi-grow-loop/banner.jpg +0 -0
  421. package/node_modules/@llblab/pi-grow-loop/index.ts +219 -0
  422. package/node_modules/@llblab/pi-grow-loop/package.json +64 -0
  423. package/node_modules/@llblab/pi-grow-loop/skills/grow-loop/SKILL.md +131 -0
  424. package/node_modules/@llblab/pi-grow-loop/skills/while-true/SKILL.md +233 -0
  425. package/node_modules/@llblab/pi-telegram/AGENTS.md +157 -0
  426. package/node_modules/@llblab/pi-telegram/BACKLOG.md +15 -0
  427. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +886 -0
  428. package/node_modules/@llblab/pi-telegram/README.md +330 -0
  429. package/node_modules/@llblab/pi-telegram/api/activity.ts +15 -0
  430. package/node_modules/@llblab/pi-telegram/api/commands.ts +11 -0
  431. package/node_modules/@llblab/pi-telegram/api/delivery.ts +21 -0
  432. package/node_modules/@llblab/pi-telegram/api/inbound.ts +14 -0
  433. package/node_modules/@llblab/pi-telegram/api/keyboard.ts +11 -0
  434. package/node_modules/@llblab/pi-telegram/api/outbound.ts +11 -0
  435. package/node_modules/@llblab/pi-telegram/api/sections.ts +17 -0
  436. package/node_modules/@llblab/pi-telegram/api/status.ts +12 -0
  437. package/node_modules/@llblab/pi-telegram/api/updates.ts +16 -0
  438. package/node_modules/@llblab/pi-telegram/api/voice.ts +24 -0
  439. package/node_modules/@llblab/pi-telegram/docs/README.md +25 -0
  440. package/node_modules/@llblab/pi-telegram/docs/activity.md +304 -0
  441. package/node_modules/@llblab/pi-telegram/docs/architecture.md +418 -0
  442. package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +48 -0
  443. package/node_modules/@llblab/pi-telegram/docs/command-templates.md +365 -0
  444. package/node_modules/@llblab/pi-telegram/docs/compact-matrix-literal.md +176 -0
  445. package/node_modules/@llblab/pi-telegram/docs/delivery.md +225 -0
  446. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +310 -0
  447. package/node_modules/@llblab/pi-telegram/docs/inbound.md +136 -0
  448. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +467 -0
  449. package/node_modules/@llblab/pi-telegram/docs/outbound.md +193 -0
  450. package/node_modules/@llblab/pi-telegram/docs/public-api.md +599 -0
  451. package/node_modules/@llblab/pi-telegram/docs/sections.md +452 -0
  452. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +256 -0
  453. package/node_modules/@llblab/pi-telegram/docs/updates.md +171 -0
  454. package/node_modules/@llblab/pi-telegram/docs/voice.md +217 -0
  455. package/node_modules/@llblab/pi-telegram/index.ts +1336 -0
  456. package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +714 -0
  457. package/node_modules/@llblab/pi-telegram/lib/activity.ts +782 -0
  458. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +121 -0
  459. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +1129 -0
  460. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +379 -0
  461. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +1941 -0
  462. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +2119 -0
  463. package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +244 -0
  464. package/node_modules/@llblab/pi-telegram/lib/bus.ts +2645 -0
  465. package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +954 -0
  466. package/node_modules/@llblab/pi-telegram/lib/commands.ts +1854 -0
  467. package/node_modules/@llblab/pi-telegram/lib/config.ts +1143 -0
  468. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +891 -0
  469. package/node_modules/@llblab/pi-telegram/lib/generative-app-worker.mjs +104 -0
  470. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +958 -0
  471. package/node_modules/@llblab/pi-telegram/lib/inbound.ts +824 -0
  472. package/node_modules/@llblab/pi-telegram/lib/journal.ts +3140 -0
  473. package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +59 -0
  474. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +623 -0
  475. package/node_modules/@llblab/pi-telegram/lib/locks.ts +1422 -0
  476. package/node_modules/@llblab/pi-telegram/lib/logs.ts +393 -0
  477. package/node_modules/@llblab/pi-telegram/lib/media.ts +921 -0
  478. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +1182 -0
  479. package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +926 -0
  480. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +876 -0
  481. package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +274 -0
  482. package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +185 -0
  483. package/node_modules/@llblab/pi-telegram/lib/menu.ts +867 -0
  484. package/node_modules/@llblab/pi-telegram/lib/model.ts +672 -0
  485. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +981 -0
  486. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +331 -0
  487. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +515 -0
  488. package/node_modules/@llblab/pi-telegram/lib/outbound-voice.ts +285 -0
  489. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +1007 -0
  490. package/node_modules/@llblab/pi-telegram/lib/ownership.ts +298 -0
  491. package/node_modules/@llblab/pi-telegram/lib/paths.ts +127 -0
  492. package/node_modules/@llblab/pi-telegram/lib/pi.ts +197 -0
  493. package/node_modules/@llblab/pi-telegram/lib/polling.ts +1588 -0
  494. package/node_modules/@llblab/pi-telegram/lib/preview.ts +846 -0
  495. package/node_modules/@llblab/pi-telegram/lib/prompt-templates.ts +158 -0
  496. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +218 -0
  497. package/node_modules/@llblab/pi-telegram/lib/queue.ts +3075 -0
  498. package/node_modules/@llblab/pi-telegram/lib/recovery.ts +464 -0
  499. package/node_modules/@llblab/pi-telegram/lib/rendering.ts +1213 -0
  500. package/node_modules/@llblab/pi-telegram/lib/replies.ts +892 -0
  501. package/node_modules/@llblab/pi-telegram/lib/routing.ts +2889 -0
  502. package/node_modules/@llblab/pi-telegram/lib/runtime.ts +628 -0
  503. package/node_modules/@llblab/pi-telegram/lib/sections.ts +710 -0
  504. package/node_modules/@llblab/pi-telegram/lib/setup.ts +220 -0
  505. package/node_modules/@llblab/pi-telegram/lib/skills.ts +21 -0
  506. package/node_modules/@llblab/pi-telegram/lib/status.ts +1550 -0
  507. package/node_modules/@llblab/pi-telegram/lib/sync.ts +883 -0
  508. package/node_modules/@llblab/pi-telegram/lib/target.ts +49 -0
  509. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +1747 -0
  510. package/node_modules/@llblab/pi-telegram/lib/text-groups.ts +487 -0
  511. package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +1002 -0
  512. package/node_modules/@llblab/pi-telegram/lib/threads.ts +3183 -0
  513. package/node_modules/@llblab/pi-telegram/lib/time-injection.ts +78 -0
  514. package/node_modules/@llblab/pi-telegram/lib/turns.ts +753 -0
  515. package/node_modules/@llblab/pi-telegram/lib/updates.ts +4950 -0
  516. package/node_modules/@llblab/pi-telegram/lib/voice.ts +340 -0
  517. package/node_modules/@llblab/pi-telegram/package.json +83 -0
  518. package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
  519. package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +435 -0
  520. package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +241 -0
  521. package/node_modules/@llblab/pi-telegram/skills/generative-apps/SKILL.md +114 -0
  522. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/SKILL.md +149 -0
  523. package/package.json +67 -0
@@ -0,0 +1,2119 @@
1
+ /**
2
+ * Telegram bus leader orchestration
3
+ * Zones: multi-instance bus, leader polling/server lifecycle, follower routing
4
+ * Owns leader-only runtime orchestration: follower registration envelopes, follower API proxying,
5
+ * leader activation hot-switching, local bus server startup, and stale follower pruning.
6
+ */
7
+
8
+ import * as Sync from "./sync.ts";
9
+ import * as ThreadReconciler from "./thread-reconciler.ts";
10
+ import {
11
+ isTelegramApiCommitUnknownError,
12
+ type TelegramApiCallOptions,
13
+ } from "./telegram-api.ts";
14
+ import type { TelegramTarget } from "./target.ts";
15
+ import * as Threads from "./threads.ts";
16
+ import {
17
+ createTelegramBusLocalServer,
18
+ createUnauthorizedBusAck,
19
+ getTelegramBusEnvelopeTrafficClass,
20
+ getTelegramBusProtocolCompatibility,
21
+ hasTelegramBusCapability,
22
+ isTelegramBusEnvelopeAuthorized,
23
+ getTelegramBusFollowerSocketPath,
24
+ sendTelegramBusLocalEnvelope,
25
+ stripTelegramBusApiMetadata,
26
+ type TelegramBusEnvelope,
27
+ type TelegramBusFollowerRegistry,
28
+ type TelegramBusFollowerView,
29
+ type TelegramBusInstanceRegistration,
30
+ type TelegramBusProtocolIdentity,
31
+ type TelegramBusSocketPathSource,
32
+ TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
33
+ TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF,
34
+ } from "./bus.ts";
35
+ import { getTelegramBusTransportRetryPolicy } from "./bus-transport.ts";
36
+ import type { TelegramQueueHandoffPayload } from "./queue.ts";
37
+
38
+ export interface TelegramBusLeaderRuntime<TContext> {
39
+ startPolling: (ctx: TContext) => Promise<void>;
40
+ stopPolling: () => Promise<void>;
41
+ routeQueueHandoff: (input: {
42
+ requestId: string;
43
+ auth?: string;
44
+ recipientInstanceId: string;
45
+ recipientRegistrationGeneration: string;
46
+ donorInstanceId: string;
47
+ donorProcessId: number;
48
+ donorProcessBirthId: string;
49
+ donorSessionGeneration: number;
50
+ donorAcquisitionId: string;
51
+ donorAcquiredAtMs: number;
52
+ handoffToken: string;
53
+ payload: TelegramQueueHandoffPayload;
54
+ sentAtMs: number;
55
+ }) => Promise<TelegramBusEnvelope>;
56
+ }
57
+
58
+ export interface TelegramBusFollowerLifecycleAnnouncement {
59
+ target: TelegramTarget & { threadId: number };
60
+ text: string;
61
+ parseMode: "HTML";
62
+ }
63
+
64
+ function escapeHtml(text: string): string {
65
+ return text
66
+ .replace(/&/g, "&amp;")
67
+ .replace(/</g, "&lt;")
68
+ .replace(/>/g, "&gt;");
69
+ }
70
+
71
+ function formatTelegramBusInstanceLabel(input: {
72
+ threadName?: string;
73
+ slot?: string;
74
+ }): string {
75
+ const threadName = input.threadName?.trim();
76
+ if (threadName) return escapeHtml(threadName);
77
+ return input.slot && /^[A-Z]$/.test(input.slot) ? input.slot : "?";
78
+ }
79
+
80
+ export interface TelegramBusLeaderTargetProvisionerDeps<TContext> {
81
+ getAllowedUserId: () => number | undefined;
82
+ instanceId: string;
83
+ getCwd?: (ctx: TContext) => string | undefined;
84
+ getTelegramProfile?: () => string | undefined;
85
+ shouldForceFreshUnnamed?: () => boolean;
86
+ topicTargetStore: Threads.TelegramTopicTargetStore;
87
+ callApi: <TResponse>(
88
+ method: string,
89
+ body: Record<string, unknown>,
90
+ ) => Promise<TResponse>;
91
+ getCurrentLeaderEpoch?: () => number | string | undefined;
92
+ getThreadReconciliationMachineState?: () =>
93
+ ThreadReconciler.ThreadReconciliationMachineState | undefined;
94
+ recordThreadReconciliationPlan?: (
95
+ plan: ThreadReconciler.ThreadReconciliationPlan,
96
+ ) => void;
97
+ getSyncState: () => Sync.TelegramSyncState;
98
+ setSyncState: (state: Sync.TelegramSyncState) => void;
99
+ setLeaderTarget: (input: {
100
+ target: TelegramTarget;
101
+ slot?: string;
102
+ threadName?: string;
103
+ }) => void;
104
+ onProvisioningStart?: () => void;
105
+ onProvisioningEnd?: () => void;
106
+ recordRuntimeEvent: (
107
+ category: string,
108
+ error: unknown,
109
+ details?: Record<string, unknown>,
110
+ ) => void;
111
+ getNowMs?: () => number;
112
+ }
113
+
114
+ export interface TelegramBusFollowerTargetProvisionerDeps {
115
+ getAllowedUserId: () => number | undefined;
116
+ topicTargetStore: Threads.TelegramTopicTargetStore;
117
+ callApi: <TResponse>(
118
+ method: string,
119
+ body: Record<string, unknown>,
120
+ ) => Promise<TResponse>;
121
+ getCurrentLeaderEpoch?: () => number | string | undefined;
122
+ getSyncState: () => Sync.TelegramSyncState;
123
+ setSyncState: (state: Sync.TelegramSyncState) => void;
124
+ onProvisioningStart?: () => void;
125
+ onProvisioningEnd?: () => void;
126
+ getNowMs?: () => number;
127
+ recordRuntimeEvent: (
128
+ category: string,
129
+ error: unknown,
130
+ details?: Record<string, unknown>,
131
+ ) => void;
132
+ }
133
+
134
+ export interface TelegramBusFollowerDisconnectHandlerDeps {
135
+ topicTargetStore: Pick<
136
+ Threads.TelegramTopicTargetStore,
137
+ | "markStaleByTarget"
138
+ | "persist"
139
+ | "upsertPendingCleanup"
140
+ | "removePendingCleanup"
141
+ >;
142
+ callApi: <TResponse>(
143
+ method: string,
144
+ body: Record<string, unknown>,
145
+ ) => Promise<TResponse>;
146
+ getCurrentLeaderEpoch?: () => number | string | undefined;
147
+ getSyncState: () => Sync.TelegramSyncState;
148
+ setSyncState: (state: Sync.TelegramSyncState) => void;
149
+ getNowMs?: () => number;
150
+ recordRuntimeEvent: (
151
+ category: string,
152
+ error: unknown,
153
+ details?: Record<string, unknown>,
154
+ ) => void;
155
+ }
156
+
157
+ export interface TelegramBusLeaderApiProxyDeps {
158
+ call: (
159
+ method: string,
160
+ body: Record<string, unknown>,
161
+ options?: TelegramApiCallOptions,
162
+ ) => Promise<unknown>;
163
+ callMultipart: (
164
+ method: string,
165
+ fields: Record<string, string>,
166
+ fieldName: string,
167
+ filePath: string,
168
+ fileName: string,
169
+ options?: TelegramApiCallOptions,
170
+ ) => Promise<unknown>;
171
+ downloadFile: (fileId: string, destinationDir: string) => Promise<unknown>;
172
+ recoverStaleTargetError?: (
173
+ apiBody: unknown,
174
+ error: unknown,
175
+ ) => Promise<unknown> | unknown;
176
+ }
177
+
178
+ export interface TelegramBusLeaderRuntimeAssemblyDeps<TContext> {
179
+ runtime: Omit<
180
+ TelegramBusLeaderRuntimeDeps<TContext>,
181
+ | "callApi"
182
+ | "onFollowerDisconnected"
183
+ | "onFollowerConfirmedDead"
184
+ | "provisionFollowerTarget"
185
+ | "provisionLeaderTarget"
186
+ | "recordRuntimeEvent"
187
+ >;
188
+ getAllowedUserId: () => number | undefined;
189
+ instanceId: string;
190
+ getCwd?: (ctx: TContext) => string | undefined;
191
+ getTelegramProfile?: () => string | undefined;
192
+ shouldForceFreshUnnamed?: () => boolean;
193
+ topicTargetStore: Threads.TelegramTopicTargetStore;
194
+ callApi: TelegramBusLeaderTargetProvisionerDeps<TContext>["callApi"];
195
+ callMultipart: TelegramBusLeaderApiProxyDeps["callMultipart"];
196
+ downloadFile: TelegramBusLeaderApiProxyDeps["downloadFile"];
197
+ recoverStaleTargetError?: TelegramBusLeaderApiProxyDeps["recoverStaleTargetError"];
198
+ getCurrentLeaderEpoch?: () => number | string | undefined;
199
+ getThreadReconciliationMachineState?: TelegramBusLeaderTargetProvisionerDeps<TContext>["getThreadReconciliationMachineState"];
200
+ recordThreadReconciliationPlan?: TelegramBusLeaderTargetProvisionerDeps<TContext>["recordThreadReconciliationPlan"];
201
+ getSyncState: () => Sync.TelegramSyncState;
202
+ setSyncState: (state: Sync.TelegramSyncState) => void;
203
+ setLeaderTarget: TelegramBusLeaderTargetProvisionerDeps<TContext>["setLeaderTarget"];
204
+ onProvisioningStart?: () => void;
205
+ onProvisioningEnd?: () => void;
206
+ recordRuntimeEvent: NonNullable<
207
+ TelegramBusLeaderRuntimeDeps<TContext>["recordRuntimeEvent"]
208
+ >;
209
+ }
210
+
211
+ export function createTelegramBusLeaderRuntimeAssembly<TContext>(
212
+ deps: TelegramBusLeaderRuntimeAssemblyDeps<TContext>,
213
+ ): TelegramBusLeaderRuntime<TContext> {
214
+ const provisionerPorts = {
215
+ getAllowedUserId: deps.getAllowedUserId,
216
+ topicTargetStore: deps.topicTargetStore,
217
+ callApi: deps.callApi,
218
+ getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
219
+ getSyncState: deps.getSyncState,
220
+ setSyncState: deps.setSyncState,
221
+ onProvisioningStart: deps.onProvisioningStart,
222
+ onProvisioningEnd: deps.onProvisioningEnd,
223
+ recordRuntimeEvent: deps.recordRuntimeEvent,
224
+ };
225
+ return createTelegramBusLeaderRuntime({
226
+ ...deps.runtime,
227
+ provisionLeaderTarget: createTelegramBusLeaderTargetProvisioner({
228
+ ...provisionerPorts,
229
+ instanceId: deps.instanceId,
230
+ getCwd: deps.getCwd,
231
+ getTelegramProfile: deps.getTelegramProfile,
232
+ shouldForceFreshUnnamed: deps.shouldForceFreshUnnamed,
233
+ getThreadReconciliationMachineState:
234
+ deps.getThreadReconciliationMachineState,
235
+ recordThreadReconciliationPlan: deps.recordThreadReconciliationPlan,
236
+ setLeaderTarget: deps.setLeaderTarget,
237
+ }),
238
+ onFollowerDisconnected: createTelegramBusFollowerDisconnectHandler({
239
+ ...provisionerPorts,
240
+ }),
241
+ onFollowerConfirmedDead: createTelegramBusFollowerConfirmedDeadHandler({
242
+ ...provisionerPorts,
243
+ }),
244
+ provisionFollowerTarget: createTelegramBusFollowerTargetProvisioner({
245
+ ...provisionerPorts,
246
+ }),
247
+ getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
248
+ callApi: createTelegramBusLeaderApiProxy({
249
+ call: deps.callApi,
250
+ callMultipart: deps.callMultipart,
251
+ downloadFile: deps.downloadFile,
252
+ recoverStaleTargetError: deps.recoverStaleTargetError,
253
+ }),
254
+ recordRuntimeEvent: deps.recordRuntimeEvent,
255
+ });
256
+ }
257
+
258
+ export interface TelegramBusFollowerMessageOwnershipRecord {
259
+ follower: TelegramBusFollowerView;
260
+ chatId: number;
261
+ messageId: number;
262
+ target?: TelegramTarget;
263
+ }
264
+
265
+ export type TelegramBusFollowerMessageOwnershipRecorder = (
266
+ record: TelegramBusFollowerMessageOwnershipRecord,
267
+ ) => void;
268
+
269
+ export interface TelegramBusLeaderRuntimeDeps<TContext> {
270
+ socketPath: TelegramBusSocketPathSource;
271
+ commitEndpointPublication?: (commit: () => void) => boolean;
272
+ followerRegistry: TelegramBusFollowerRegistry;
273
+ authSecret?: string;
274
+ protocolIdentity: TelegramBusProtocolIdentity;
275
+ startPolling: (ctx: TContext) => void | Promise<void>;
276
+ stopPolling: () => void | Promise<void>;
277
+ callApi?: (method: string, args: unknown[]) => Promise<unknown> | unknown;
278
+ authorizeFollowerApiCall?: (input: {
279
+ follower: TelegramBusFollowerView;
280
+ method: string;
281
+ args: unknown[];
282
+ }) => boolean;
283
+ recordFollowerMessageOwnership?: TelegramBusFollowerMessageOwnershipRecorder;
284
+ resolveAgentTarget?: (
285
+ follower: TelegramBusFollowerView,
286
+ selector: Extract<
287
+ TelegramBusEnvelope,
288
+ { kind: "follower.resolveAgentTarget" }
289
+ >["selector"],
290
+ ) => Promise<TelegramTarget | undefined> | TelegramTarget | undefined;
291
+ routeAgentMessage?: (
292
+ follower: TelegramBusFollowerView,
293
+ message: Extract<
294
+ TelegramBusEnvelope,
295
+ { kind: "follower.routeAgentMessage" }
296
+ >["message"],
297
+ ) => Promise<void> | void;
298
+ routeQueueHandoff?: (
299
+ follower: TelegramBusFollowerView,
300
+ envelope: Extract<
301
+ TelegramBusEnvelope,
302
+ { kind: "follower.offerQueueHandoff" }
303
+ >,
304
+ ) => Promise<unknown> | unknown;
305
+ provisionFollowerTarget?: (
306
+ registration: TelegramBusInstanceRegistration,
307
+ ) => Promise<TelegramTarget | undefined> | TelegramTarget | undefined;
308
+ getCurrentLeaderEpoch?: () => number | string | undefined;
309
+ provisionLeaderTarget?: (ctx: TContext) => Promise<void> | void;
310
+ getNowMs?: () => number;
311
+ timeoutMs?: number;
312
+ followerPruneIntervalMs?: number;
313
+ followerStaleAfterMs?: number;
314
+ isFollowerProcessAlive?: (pid: number) => boolean;
315
+ shouldCleanupConfirmedDeadFollower?: () => Promise<boolean> | boolean;
316
+ onFollowerDisconnected?: (
317
+ follower: TelegramBusFollowerView,
318
+ ) => Promise<void> | void;
319
+ onFollowerConfirmedDead?: (
320
+ follower: TelegramBusFollowerView,
321
+ ) => Promise<void> | void;
322
+ recordRuntimeEvent?: (
323
+ category: string,
324
+ error: unknown,
325
+ details?: Record<string, unknown>,
326
+ ) => void;
327
+ }
328
+
329
+ export function createTelegramBusInstanceLifecycleAnnouncement(input: {
330
+ target: TelegramTarget & { threadId: number };
331
+ threadName?: string;
332
+ slot?: string;
333
+ state: "connected";
334
+ }): TelegramBusFollowerLifecycleAnnouncement {
335
+ return {
336
+ target: { ...input.target },
337
+ text: `📡 Instance <b>${formatTelegramBusInstanceLabel(input)}</b> ${input.state}.`,
338
+ parseMode: "HTML",
339
+ };
340
+ }
341
+
342
+ const TELEGRAM_BUS_SLOW_FOLLOWER_REGISTRATION_MS = 1000;
343
+
344
+ function scheduleTelegramBusLeaderBackgroundTask(
345
+ task: () => Promise<void>,
346
+ onError: (error: unknown) => void,
347
+ ): void {
348
+ const timer = setTimeout(() => {
349
+ void task().catch((error) => {
350
+ try {
351
+ onError(error);
352
+ } catch {
353
+ // Background diagnostics cannot create an unhandled timer rejection.
354
+ }
355
+ });
356
+ }, 0);
357
+ timer.unref?.();
358
+ }
359
+
360
+ function recordSlowTelegramBusFollowerRegistrationStep(
361
+ deps: Pick<TelegramBusFollowerTargetProvisionerDeps, "recordRuntimeEvent">,
362
+ input: {
363
+ phase: string;
364
+ elapsedMs: number;
365
+ instanceId: string;
366
+ target?: TelegramTarget;
367
+ reused?: boolean;
368
+ },
369
+ ): void {
370
+ if (input.elapsedMs < TELEGRAM_BUS_SLOW_FOLLOWER_REGISTRATION_MS) return;
371
+ deps.recordRuntimeEvent(
372
+ "bus",
373
+ `Telegram bus follower registration step was slow (${input.elapsedMs}ms).`,
374
+ {
375
+ phase: input.phase,
376
+ elapsedMs: input.elapsedMs,
377
+ instanceId: input.instanceId,
378
+ reused: input.reused,
379
+ chatId: input.target?.chatId,
380
+ threadId: input.target?.threadId,
381
+ },
382
+ );
383
+ }
384
+
385
+ export function createTelegramBusFollowerTargetProvisioner(
386
+ deps: TelegramBusFollowerTargetProvisionerDeps,
387
+ ): (
388
+ registration: TelegramBusInstanceRegistration,
389
+ ) => Promise<
390
+ (TelegramTarget & { slot?: string; threadName?: string }) | undefined
391
+ > {
392
+ const getNowMs = deps.getNowMs ?? Date.now;
393
+ const pendingRegistrations = new Map<
394
+ string,
395
+ Promise<
396
+ (TelegramTarget & { slot?: string; threadName?: string }) | undefined
397
+ >
398
+ >();
399
+ return async (registration) => {
400
+ const registrationStartedAtMs = Date.now();
401
+ const chatId = deps.getAllowedUserId();
402
+ if (typeof chatId !== "number") return registration.target;
403
+ await deps.topicTargetStore.load();
404
+ const provision = Threads.createTelegramTopicTargetProvisioner({
405
+ topicChatId: chatId,
406
+ store: deps.topicTargetStore,
407
+ getNowMs,
408
+ getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
409
+ callApi: deps.callApi,
410
+ // Manual follower registration should create a visible fresh topic unless
411
+ // the same profile key already has a known live/reusable binding. Do not
412
+ // silently claim old offline/failed tabs: they may be closed/deleted in
413
+ // Telegram and therefore invisible to the operator.
414
+ claimPendingTargets: false,
415
+ });
416
+ const recordsBeforeProvision = deps.topicTargetStore.list();
417
+ const followerProfileKey =
418
+ registration.profileKey ?? `manual:${registration.instanceId}`;
419
+ const requestedTarget = registration.target;
420
+ const reconnectRecord = recordsBeforeProvision.find((record) => {
421
+ const matchesRequestedTarget =
422
+ !requestedTarget ||
423
+ (record.target.chatId === requestedTarget.chatId &&
424
+ record.target.threadId === requestedTarget.threadId);
425
+ const matchesCurrentIdentity =
426
+ record.instanceId === registration.instanceId ||
427
+ record.profileKey === followerProfileKey;
428
+ const matchesSessionHandoff =
429
+ !!requestedTarget &&
430
+ !!registration.previousInstanceId &&
431
+ record.instanceId === registration.previousInstanceId &&
432
+ matchesRequestedTarget;
433
+ return (
434
+ record.owner?.kind === "manual-follower" &&
435
+ ((matchesCurrentIdentity && matchesRequestedTarget) ||
436
+ matchesSessionHandoff)
437
+ );
438
+ });
439
+ const followerOwner =
440
+ Threads.getTelegramThreadOwnerFromProfileKey(followerProfileKey);
441
+ const recoverableTarget =
442
+ !reconnectRecord &&
443
+ requestedTarget?.chatId === chatId &&
444
+ requestedTarget.threadId !== undefined &&
445
+ !recordsBeforeProvision.some(
446
+ (record) =>
447
+ record.target.chatId === requestedTarget.chatId &&
448
+ record.target.threadId === requestedTarget.threadId,
449
+ )
450
+ ? requestedTarget
451
+ : undefined;
452
+ const recoveryHint = recoverableTarget
453
+ ? deps.topicTargetStore.getFollowerRecoveryHintByTarget?.(
454
+ recoverableTarget,
455
+ )
456
+ : undefined;
457
+ const registrationKey = followerProfileKey || registration.instanceId;
458
+ const pendingRegistration = pendingRegistrations.get(registrationKey);
459
+ if (pendingRegistration) return pendingRegistration;
460
+ const provisionTarget = async () => {
461
+ deps.onProvisioningStart?.();
462
+ try {
463
+ return await provision({
464
+ instanceId: registration.instanceId,
465
+ owner:
466
+ followerOwner.kind === "manual-follower"
467
+ ? followerOwner
468
+ : {
469
+ kind: "manual-follower",
470
+ instanceId: registration.instanceId,
471
+ },
472
+ profileKey: followerProfileKey,
473
+ threadName: registration.threadName,
474
+ });
475
+ } finally {
476
+ deps.onProvisioningEnd?.();
477
+ }
478
+ };
479
+ const recoverRequestedTarget = async () => {
480
+ const nowMs = getNowMs();
481
+ const carriedThreadName = registration.threadName;
482
+ const requestedThreadName =
483
+ carriedThreadName &&
484
+ Threads.isTelegramTopicThreadNameValidForSlot(
485
+ carriedThreadName,
486
+ registration.slot,
487
+ )
488
+ ? carriedThreadName
489
+ : recoveryHint?.threadName &&
490
+ Threads.isTelegramTopicThreadNameValidForSlot(
491
+ recoveryHint.threadName,
492
+ recoveryHint.slot,
493
+ )
494
+ ? recoveryHint.threadName
495
+ : undefined;
496
+ let recoveredRecord: Threads.TelegramTopicTargetRecord = {
497
+ profileKey: followerProfileKey,
498
+ owner:
499
+ followerOwner.kind === "manual-follower"
500
+ ? followerOwner
501
+ : {
502
+ kind: "manual-follower",
503
+ instanceId: registration.instanceId,
504
+ },
505
+ target: {
506
+ chatId: recoverableTarget!.chatId,
507
+ threadId: recoverableTarget!.threadId!,
508
+ },
509
+ status: "active",
510
+ createdAtMs: registration.connectedAtMs || nowMs,
511
+ updatedAtMs: nowMs,
512
+ instanceId: registration.instanceId,
513
+ ...(requestedThreadName ? { threadName: requestedThreadName } : {}),
514
+ lastSyncObservedAtMs: nowMs,
515
+ lastReconcileAction: "follower-live-target-recovery",
516
+ };
517
+ const requestedSlot = registration.slot ?? recoveryHint?.slot;
518
+ const requestedSlotAvailable =
519
+ !!requestedSlot &&
520
+ /^[A-Z]$/.test(requestedSlot) &&
521
+ !recordsBeforeProvision.some((record) => record.slot === requestedSlot);
522
+ if (requestedSlotAvailable) {
523
+ recoveredRecord = { ...recoveredRecord, slot: requestedSlot };
524
+ }
525
+ return {
526
+ target: recoveredRecord.target,
527
+ reused: true,
528
+ record: recoveredRecord,
529
+ };
530
+ };
531
+ const runRegistration = async (): Promise<
532
+ (TelegramTarget & { slot?: string; threadName?: string }) | undefined
533
+ > => {
534
+ let result = reconnectRecord
535
+ ? {
536
+ target: reconnectRecord.target,
537
+ reused: true,
538
+ record: reconnectRecord,
539
+ }
540
+ : recoverableTarget
541
+ ? await recoverRequestedTarget()
542
+ : await provisionTarget();
543
+ const crossSessionReuse =
544
+ !!reconnectRecord &&
545
+ reconnectRecord.instanceId !== registration.instanceId;
546
+ if (reconnectRecord && !crossSessionReuse) {
547
+ const nowMs = getNowMs();
548
+ const refreshedRecord = deps.topicTargetStore.upsert({
549
+ ...reconnectRecord,
550
+ instanceId: registration.instanceId,
551
+ updatedAtMs: nowMs,
552
+ lastSyncObservedAtMs: nowMs,
553
+ lastReconcileAction: "follower-register-reuse",
554
+ });
555
+ await deps.topicTargetStore.persist();
556
+ result = {
557
+ target: refreshedRecord.target,
558
+ reused: true,
559
+ record: refreshedRecord,
560
+ };
561
+ }
562
+ const probeRequiredRecord =
563
+ reconnectRecord?.status === "probe-required";
564
+ const exactSessionHandoff =
565
+ crossSessionReuse &&
566
+ !!requestedTarget &&
567
+ registration.previousInstanceId === reconnectRecord?.instanceId &&
568
+ requestedTarget.chatId === reconnectRecord.target.chatId &&
569
+ requestedTarget.threadId === reconnectRecord.target.threadId;
570
+ const requiresVisibilityProbe =
571
+ crossSessionReuse ||
572
+ probeRequiredRecord ||
573
+ recoverableTarget !== undefined;
574
+ let connectedAnnouncement =
575
+ !result.reused || requiresVisibilityProbe
576
+ ? createTelegramBusInstanceLifecycleAnnouncement({
577
+ target: result.target,
578
+ threadName: result.record.threadName,
579
+ slot: result.record.slot,
580
+ state: "connected",
581
+ })
582
+ : undefined;
583
+ if (requiresVisibilityProbe && connectedAnnouncement) {
584
+ try {
585
+ await deps.callApi(
586
+ exactSessionHandoff ? "sendChatAction" : "sendMessage",
587
+ exactSessionHandoff
588
+ ? {
589
+ chat_id: connectedAnnouncement.target.chatId,
590
+ message_thread_id: connectedAnnouncement.target.threadId,
591
+ action: "typing",
592
+ }
593
+ : {
594
+ chat_id: connectedAnnouncement.target.chatId,
595
+ message_thread_id: connectedAnnouncement.target.threadId,
596
+ text: connectedAnnouncement.text,
597
+ parse_mode: connectedAnnouncement.parseMode,
598
+ },
599
+ );
600
+ if (recoverableTarget || probeRequiredRecord) {
601
+ const activatedRecord = deps.topicTargetStore.upsert({
602
+ ...result.record,
603
+ status: "active",
604
+ updatedAtMs: getNowMs(),
605
+ lastSyncObservedAtMs: getNowMs(),
606
+ lastReconcileAction: "follower-live-target-recovery",
607
+ });
608
+ await deps.topicTargetStore.persist();
609
+ result = {
610
+ target: activatedRecord.target,
611
+ reused: true,
612
+ record: activatedRecord,
613
+ };
614
+ } else if (crossSessionReuse && reconnectRecord) {
615
+ const nowMs = getNowMs();
616
+ const transferredRecord = deps.topicTargetStore.upsert({
617
+ ...reconnectRecord,
618
+ profileKey: followerProfileKey,
619
+ owner:
620
+ followerOwner.kind === "manual-follower"
621
+ ? followerOwner
622
+ : {
623
+ kind: "manual-follower",
624
+ instanceId: registration.instanceId,
625
+ },
626
+ instanceId: registration.instanceId,
627
+ updatedAtMs: nowMs,
628
+ lastSyncObservedAtMs: nowMs,
629
+ lastReconcileAction: "follower-session-handoff",
630
+ });
631
+ await deps.topicTargetStore.persist();
632
+ result = {
633
+ target: transferredRecord.target,
634
+ reused: true,
635
+ record: transferredRecord,
636
+ };
637
+ }
638
+ connectedAnnouncement = undefined;
639
+ } catch (error) {
640
+ if (Threads.isTelegramTopicTargetStaleError(error)) {
641
+ deps.topicTargetStore.markStaleByTarget(
642
+ result.target,
643
+ "deleted",
644
+ error instanceof Error ? error.message : String(error),
645
+ );
646
+ await deps.topicTargetStore.persist();
647
+ result = await provisionTarget();
648
+ connectedAnnouncement =
649
+ createTelegramBusInstanceLifecycleAnnouncement({
650
+ target: result.target,
651
+ threadName: result.record.threadName,
652
+ slot: result.record.slot,
653
+ state: "connected",
654
+ });
655
+ } else {
656
+ deps.recordRuntimeEvent("telegram", error, {
657
+ phase: "follower-topic-reuse-probe",
658
+ instanceId: registration.instanceId,
659
+ chatId: result.target.chatId,
660
+ threadId: result.target.threadId,
661
+ });
662
+ if (recoverableTarget) {
663
+ deps.topicTargetStore.upsert({
664
+ ...result.record,
665
+ status: "probe-required",
666
+ updatedAtMs: getNowMs(),
667
+ lastSyncError:
668
+ error instanceof Error ? error.message : String(error),
669
+ lastReconcileAction: "follower-visibility-probe-required",
670
+ });
671
+ await deps.topicTargetStore.persist();
672
+ }
673
+ throw error;
674
+ }
675
+ }
676
+ }
677
+ deps.setSyncState(
678
+ Sync.markTelegramSyncSliceFresh(
679
+ deps.getSyncState(),
680
+ "target-bindings",
681
+ {
682
+ nowMs: getNowMs(),
683
+ action: "follower-register",
684
+ },
685
+ ),
686
+ );
687
+ recordSlowTelegramBusFollowerRegistrationStep(deps, {
688
+ phase: "follower-register-critical",
689
+ elapsedMs: Date.now() - registrationStartedAtMs,
690
+ instanceId: registration.instanceId,
691
+ target: result.target,
692
+ reused: result.reused,
693
+ });
694
+ scheduleTelegramBusLeaderBackgroundTask(async () => {
695
+ const backgroundStartedAtMs = Date.now();
696
+ if (connectedAnnouncement) {
697
+ try {
698
+ await deps.callApi("sendMessage", {
699
+ chat_id: connectedAnnouncement.target.chatId,
700
+ message_thread_id: connectedAnnouncement.target.threadId,
701
+ text: connectedAnnouncement.text,
702
+ parse_mode: connectedAnnouncement.parseMode,
703
+ });
704
+ } catch (error) {
705
+ deps.recordRuntimeEvent("telegram", error, {
706
+ phase: "follower-topic-announce",
707
+ instanceId: registration.instanceId,
708
+ chatId: result.target.chatId,
709
+ threadId: result.target.threadId,
710
+ });
711
+ }
712
+ }
713
+ try {
714
+ await ThreadReconciler.applyThreadReconciliationPlan(
715
+ ThreadReconciler.planThreadReconciliation({
716
+ nowMs: getNowMs(),
717
+ currentLeaderEpoch: deps.getCurrentLeaderEpoch?.(),
718
+ records: recordsBeforeProvision,
719
+ pendingProvisions: deps.topicTargetStore.listPendingProvisions(),
720
+ replacedBindings: [
721
+ {
722
+ instanceId: registration.instanceId,
723
+ replacementTarget: result.target,
724
+ },
725
+ ],
726
+ }),
727
+ {
728
+ callApi: deps.callApi,
729
+ markStaleByTarget(target, syncStatus, lastSyncError) {
730
+ return deps.topicTargetStore.markStaleByTarget(
731
+ target,
732
+ syncStatus,
733
+ lastSyncError,
734
+ );
735
+ },
736
+ persist() {
737
+ return deps.topicTargetStore.persist();
738
+ },
739
+ removePendingProvisionById(id) {
740
+ return deps.topicTargetStore.removePendingProvision(id);
741
+ },
742
+ getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
743
+ recordRuntimeEvent: deps.recordRuntimeEvent,
744
+ },
745
+ );
746
+ await deps.topicTargetStore.persist();
747
+ } catch (error) {
748
+ deps.recordRuntimeEvent("telegram", error, {
749
+ phase: "follower-register-background-reconcile",
750
+ instanceId: registration.instanceId,
751
+ chatId: result.target.chatId,
752
+ threadId: result.target.threadId,
753
+ });
754
+ }
755
+ recordSlowTelegramBusFollowerRegistrationStep(deps, {
756
+ phase: "follower-register-background",
757
+ elapsedMs: Date.now() - backgroundStartedAtMs,
758
+ instanceId: registration.instanceId,
759
+ target: result.target,
760
+ reused: result.reused,
761
+ });
762
+ }, (error) => {
763
+ deps.recordRuntimeEvent("bus", error, {
764
+ phase: "follower-register-background-owner",
765
+ instanceId: registration.instanceId,
766
+ });
767
+ });
768
+ return {
769
+ ...result.target,
770
+ slot: result.record.slot,
771
+ threadName: result.record.threadName,
772
+ };
773
+ };
774
+ const registrationPromise = runRegistration().finally(() => {
775
+ pendingRegistrations.delete(registrationKey);
776
+ });
777
+ pendingRegistrations.set(registrationKey, registrationPromise);
778
+ return registrationPromise;
779
+ };
780
+ }
781
+
782
+ function createTelegramBusFollowerCleanupHandler(
783
+ deps: TelegramBusFollowerDisconnectHandlerDeps,
784
+ trigger: "graceful-disconnect" | "confirmed-dead",
785
+ ): (follower: TelegramBusFollowerView) => Promise<void> {
786
+ return async (follower) => {
787
+ const target = follower.target;
788
+ if (!target?.threadId) return;
789
+ const leaderEpoch = deps.getCurrentLeaderEpoch?.();
790
+ if (deps.getCurrentLeaderEpoch && leaderEpoch === undefined) {
791
+ throw new Error("Follower disconnect cleanup requires leader ownership.");
792
+ }
793
+ if (!follower.registrationGeneration) {
794
+ throw new Error(
795
+ "Follower disconnect cleanup requires an exact registration generation.",
796
+ );
797
+ }
798
+ const intent: ThreadReconciler.TelegramThreadCleanupIntent = {
799
+ id: `cleanup:${follower.instanceId}:${follower.registrationGeneration}:${target.chatId}:${target.threadId}`,
800
+ owner: "manual-follower",
801
+ instanceId: follower.instanceId,
802
+ runtimeGeneration: follower.registrationGeneration,
803
+ ...(follower.profileKey ? { profileKey: follower.profileKey } : {}),
804
+ target: { chatId: target.chatId, threadId: target.threadId },
805
+ requestedAtMs: (deps.getNowMs ?? Date.now)(),
806
+ };
807
+ deps.topicTargetStore.upsertPendingCleanup(intent);
808
+ await deps.topicTargetStore.persist();
809
+ const cleanup = await ThreadReconciler.applyThreadReconciliationPlan(
810
+ ThreadReconciler.planThreadReconciliation({
811
+ nowMs: (deps.getNowMs ?? Date.now)(),
812
+ currentLeaderEpoch: leaderEpoch,
813
+ records: [],
814
+ pendingCleanups: [intent],
815
+ }),
816
+ {
817
+ callApi: deps.callApi,
818
+ markStaleByTarget: deps.topicTargetStore.markStaleByTarget,
819
+ removeCleanupIntentById: deps.topicTargetStore.removePendingCleanup,
820
+ persist: deps.topicTargetStore.persist,
821
+ getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
822
+ recordRuntimeEvent: deps.recordRuntimeEvent,
823
+ },
824
+ );
825
+ if (cleanup.incompleteActions?.length) {
826
+ throw new Error(
827
+ "Telegram follower thread deletion was not confirmed; reconnect the leader to retry cleanup.",
828
+ );
829
+ }
830
+ if (
831
+ deps.getCurrentLeaderEpoch &&
832
+ deps.getCurrentLeaderEpoch() !== leaderEpoch
833
+ ) {
834
+ throw new Error("Follower disconnect cleanup lost leader ownership.");
835
+ }
836
+ deps.setSyncState(
837
+ Sync.markTelegramSyncSliceFresh(deps.getSyncState(), "target-bindings", {
838
+ nowMs: (deps.getNowMs ?? Date.now)(),
839
+ action:
840
+ trigger === "confirmed-dead"
841
+ ? "manual-follower-confirmed-dead"
842
+ : "manual-follower-disconnect",
843
+ }),
844
+ );
845
+ deps.recordRuntimeEvent(
846
+ "bus",
847
+ trigger === "confirmed-dead"
848
+ ? "Confirmed-dead Telegram bus follower thread cleaned up"
849
+ : "Telegram bus follower disconnected",
850
+ {
851
+ phase:
852
+ trigger === "confirmed-dead"
853
+ ? "follower-confirmed-dead-cleanup"
854
+ : "follower-disconnect",
855
+ instanceId: follower.instanceId,
856
+ chatId: target.chatId,
857
+ threadId: target.threadId,
858
+ },
859
+ );
860
+ };
861
+ }
862
+
863
+ export function createTelegramBusFollowerDisconnectHandler(
864
+ deps: TelegramBusFollowerDisconnectHandlerDeps,
865
+ ): (follower: TelegramBusFollowerView) => Promise<void> {
866
+ return createTelegramBusFollowerCleanupHandler(deps, "graceful-disconnect");
867
+ }
868
+
869
+ export function createTelegramBusFollowerConfirmedDeadHandler(
870
+ deps: TelegramBusFollowerDisconnectHandlerDeps,
871
+ ): (follower: TelegramBusFollowerView) => Promise<void> {
872
+ return createTelegramBusFollowerCleanupHandler(deps, "confirmed-dead");
873
+ }
874
+
875
+ export function createTelegramBusLeaderTargetProvisioner<TContext>(
876
+ deps: TelegramBusLeaderTargetProvisionerDeps<TContext>,
877
+ ): (ctx: TContext) => Promise<void> {
878
+ const getNowMs = deps.getNowMs ?? Date.now;
879
+ return async (ctx) => {
880
+ const leaderEpoch = deps.getCurrentLeaderEpoch?.();
881
+ if (deps.getCurrentLeaderEpoch && leaderEpoch === undefined) {
882
+ throw new Error(
883
+ "Telegram leader target provisioning requires ownership.",
884
+ );
885
+ }
886
+ await deps.topicTargetStore.load();
887
+ const profileKey = Threads.getTelegramThreadOwnerKey({
888
+ kind: "leader",
889
+ cwd: deps.getCwd?.(ctx),
890
+ instanceId: deps.instanceId,
891
+ telegramProfile: deps.getTelegramProfile?.(),
892
+ });
893
+ const reusableOwnRecord = deps.topicTargetStore.getByProfileKey(profileKey);
894
+ const pendingCleanups = deps.topicTargetStore.listPendingCleanups();
895
+ const deferredOwnCleanups =
896
+ reusableOwnRecord?.status === "active"
897
+ ? pendingCleanups.filter(
898
+ (cleanup) =>
899
+ cleanup.owner === "leader" &&
900
+ cleanup.target.chatId === reusableOwnRecord.target.chatId &&
901
+ cleanup.target.threadId === reusableOwnRecord.target.threadId,
902
+ )
903
+ : [];
904
+ const deferredOwnCleanupIds = new Set(
905
+ deferredOwnCleanups.map((cleanup) => cleanup.id),
906
+ );
907
+ const pendingCleanupPlan = ThreadReconciler.planThreadReconciliation({
908
+ nowMs: getNowMs(),
909
+ currentLeaderEpoch: leaderEpoch,
910
+ previousState: deps.getThreadReconciliationMachineState?.(),
911
+ records: deps.topicTargetStore.list(),
912
+ pendingCleanups: pendingCleanups.filter(
913
+ (cleanup) => !deferredOwnCleanupIds.has(cleanup.id),
914
+ ),
915
+ });
916
+ deps.recordThreadReconciliationPlan?.(pendingCleanupPlan);
917
+ const cleanupPorts = {
918
+ callApi: deps.callApi,
919
+ markStaleByTarget: deps.topicTargetStore.markStaleByTarget,
920
+ removeCleanupIntentById: deps.topicTargetStore.removePendingCleanup,
921
+ persist: deps.topicTargetStore.persist,
922
+ getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
923
+ recordRuntimeEvent: deps.recordRuntimeEvent,
924
+ };
925
+ await ThreadReconciler.applyThreadReconciliationPlan(
926
+ pendingCleanupPlan,
927
+ cleanupPorts,
928
+ );
929
+ deps.onProvisioningStart?.();
930
+ let ownTarget: Threads.TelegramOwnTopicProvisionResult | undefined;
931
+ try {
932
+ ownTarget = await Sync.ensureTelegramLeaderThreadBinding({
933
+ getAllowedUserId: deps.getAllowedUserId,
934
+ instanceId: deps.instanceId,
935
+ cwd: deps.getCwd?.(ctx),
936
+ telegramProfile: deps.getTelegramProfile?.(),
937
+ forceFreshUnnamed: deps.shouldForceFreshUnnamed?.(),
938
+ getNowMs,
939
+ getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
940
+ getThreadReconciliationMachineState:
941
+ deps.getThreadReconciliationMachineState,
942
+ recordThreadReconciliationPlan: deps.recordThreadReconciliationPlan,
943
+ topicTargetStore: deps.topicTargetStore,
944
+ callApi: deps.callApi,
945
+ recordEvent: deps.recordRuntimeEvent,
946
+ });
947
+ } finally {
948
+ deps.onProvisioningEnd?.();
949
+ }
950
+ if (deferredOwnCleanups.length > 0) {
951
+ const supersededCleanupPlan = ThreadReconciler.planThreadReconciliation({
952
+ nowMs: getNowMs(),
953
+ currentLeaderEpoch: leaderEpoch,
954
+ previousState: deps.getThreadReconciliationMachineState?.(),
955
+ records: deps.topicTargetStore.list(),
956
+ pendingCleanups: deferredOwnCleanups,
957
+ });
958
+ deps.recordThreadReconciliationPlan?.(supersededCleanupPlan);
959
+ await ThreadReconciler.applyThreadReconciliationPlan(
960
+ supersededCleanupPlan,
961
+ cleanupPorts,
962
+ );
963
+ }
964
+ if (
965
+ deps.getCurrentLeaderEpoch &&
966
+ deps.getCurrentLeaderEpoch() !== leaderEpoch
967
+ ) {
968
+ throw new Error("Telegram leader target provisioning lost ownership.");
969
+ }
970
+ if (!ownTarget) return;
971
+ deps.setLeaderTarget({
972
+ target: ownTarget.target,
973
+ slot: ownTarget.slot,
974
+ threadName: ownTarget.threadName,
975
+ });
976
+ const nowMs = getNowMs();
977
+ let syncState = deps.getSyncState();
978
+ syncState = Sync.markTelegramSyncSliceFresh(syncState, "target-bindings", {
979
+ nowMs,
980
+ action: "leader-startup",
981
+ });
982
+ syncState = Sync.markTelegramSyncSliceFresh(syncState, "reservations", {
983
+ nowMs,
984
+ action: "leader-startup",
985
+ });
986
+ syncState = Sync.markTelegramSyncSliceFresh(syncState, "topic-capability", {
987
+ nowMs,
988
+ action: "leader-startup",
989
+ });
990
+ deps.setSyncState(syncState);
991
+ if (ownTarget.reused) return;
992
+ const connectedAnnouncement =
993
+ createTelegramBusInstanceLifecycleAnnouncement({
994
+ target: ownTarget.target,
995
+ threadName: ownTarget.threadName,
996
+ slot: ownTarget.slot,
997
+ state: "connected",
998
+ });
999
+ try {
1000
+ await deps.callApi("sendMessage", {
1001
+ chat_id: connectedAnnouncement.target.chatId,
1002
+ message_thread_id: connectedAnnouncement.target.threadId,
1003
+ text: connectedAnnouncement.text,
1004
+ parse_mode: connectedAnnouncement.parseMode,
1005
+ });
1006
+ } catch (error) {
1007
+ deps.recordRuntimeEvent("telegram", error, {
1008
+ phase: "leader-topic-announce",
1009
+ instanceId: deps.instanceId,
1010
+ chatId: ownTarget.target.chatId,
1011
+ threadId: ownTarget.target.threadId,
1012
+ slot: ownTarget.slot,
1013
+ });
1014
+ }
1015
+ };
1016
+ }
1017
+
1018
+ export function createTelegramBusLeaderApiProxy(
1019
+ deps: TelegramBusLeaderApiProxyDeps,
1020
+ ): (method: string, args: unknown[]) => Promise<unknown> {
1021
+ return async (method, args) => {
1022
+ if (method === "call") {
1023
+ const body = stripTelegramBusApiMetadata(
1024
+ args[1] as Record<string, unknown>,
1025
+ );
1026
+ try {
1027
+ return await deps.call(
1028
+ args[0] as string,
1029
+ body,
1030
+ args[2] as TelegramApiCallOptions | undefined,
1031
+ );
1032
+ } catch (error) {
1033
+ await deps.recoverStaleTargetError?.(body, error);
1034
+ throw error;
1035
+ }
1036
+ }
1037
+ if (method === "callMultipart") {
1038
+ const fields = args[1] as Record<string, string>;
1039
+ try {
1040
+ return await deps.callMultipart(
1041
+ args[0] as string,
1042
+ fields,
1043
+ args[2] as string,
1044
+ args[3] as string,
1045
+ args[4] as string,
1046
+ args[5] as TelegramApiCallOptions | undefined,
1047
+ );
1048
+ } catch (error) {
1049
+ await deps.recoverStaleTargetError?.(fields, error);
1050
+ throw error;
1051
+ }
1052
+ }
1053
+ if (method === "downloadFile") {
1054
+ return deps.downloadFile(args[0] as string, args[1] as string);
1055
+ }
1056
+ throw new Error(`Unsupported Telegram bus API method: ${method}`);
1057
+ };
1058
+ }
1059
+
1060
+ type TelegramBusFollowerMutationRunner = <T>(
1061
+ follower: { instanceId: string; profileKey?: string },
1062
+ operation: () => Promise<T>,
1063
+ ) => Promise<T>;
1064
+
1065
+ function createTelegramBusFollowerMutationRunner(): TelegramBusFollowerMutationRunner {
1066
+ const tails = new Map<string, Promise<void>>();
1067
+ return async (follower, operation) => {
1068
+ const key = follower.profileKey
1069
+ ? `profile:${follower.profileKey}`
1070
+ : `instance:${follower.instanceId}`;
1071
+ const previous = tails.get(key);
1072
+ let release!: () => void;
1073
+ const current = new Promise<void>((resolve) => {
1074
+ release = resolve;
1075
+ });
1076
+ tails.set(key, current);
1077
+ if (previous) await previous;
1078
+ try {
1079
+ return await operation();
1080
+ } finally {
1081
+ release();
1082
+ if (tails.get(key) === current) tails.delete(key);
1083
+ }
1084
+ };
1085
+ }
1086
+
1087
+ export function createTelegramBusLeaderEnvelopeHandler(deps: {
1088
+ followerRegistry: TelegramBusFollowerRegistry;
1089
+ authSecret?: string;
1090
+ protocolIdentity: TelegramBusProtocolIdentity;
1091
+ getNowMs?: () => number;
1092
+ timeoutMs?: number;
1093
+ callApi?: (method: string, args: unknown[]) => Promise<unknown> | unknown;
1094
+ authorizeFollowerApiCall?: (input: {
1095
+ follower: TelegramBusFollowerView;
1096
+ method: string;
1097
+ args: unknown[];
1098
+ }) => boolean;
1099
+ recordFollowerMessageOwnership?: TelegramBusFollowerMessageOwnershipRecorder;
1100
+ resolveAgentTarget?: (
1101
+ follower: TelegramBusFollowerView,
1102
+ selector: Extract<
1103
+ TelegramBusEnvelope,
1104
+ { kind: "follower.resolveAgentTarget" }
1105
+ >["selector"],
1106
+ ) => Promise<TelegramTarget | undefined> | TelegramTarget | undefined;
1107
+ routeAgentMessage?: (
1108
+ follower: TelegramBusFollowerView,
1109
+ message: Extract<
1110
+ TelegramBusEnvelope,
1111
+ { kind: "follower.routeAgentMessage" }
1112
+ >["message"],
1113
+ ) => Promise<void> | void;
1114
+ routeQueueHandoff?: (
1115
+ follower: TelegramBusFollowerView,
1116
+ envelope: Extract<
1117
+ TelegramBusEnvelope,
1118
+ { kind: "follower.offerQueueHandoff" }
1119
+ >,
1120
+ ) => Promise<unknown> | unknown;
1121
+ provisionFollowerTarget?: (
1122
+ registration: TelegramBusInstanceRegistration,
1123
+ ) =>
1124
+ | Promise<
1125
+ | (TelegramTarget & { slot?: string; threadName?: string })
1126
+ | undefined
1127
+ >
1128
+ | (TelegramTarget & { slot?: string; threadName?: string })
1129
+ | undefined;
1130
+ onFollowerDisconnected?: (
1131
+ follower: TelegramBusFollowerView,
1132
+ ) => Promise<void> | void;
1133
+ getCurrentLeaderEpoch?: () => number | string | undefined;
1134
+ runFollowerMutation?: TelegramBusFollowerMutationRunner;
1135
+ }): (
1136
+ envelope: TelegramBusEnvelope,
1137
+ ) => Promise<TelegramBusEnvelope> | TelegramBusEnvelope {
1138
+ const getNowMs = deps.getNowMs ?? Date.now;
1139
+ const runFollowerMutation =
1140
+ deps.runFollowerMutation ?? createTelegramBusFollowerMutationRunner();
1141
+ const handleAgentRequest = async (
1142
+ envelope: Extract<
1143
+ TelegramBusEnvelope,
1144
+ {
1145
+ kind:
1146
+ | "follower.resolveAgentTarget"
1147
+ | "follower.routeAgentMessage";
1148
+ }
1149
+ >,
1150
+ ): Promise<TelegramBusEnvelope> => {
1151
+ const follower = deps.followerRegistry.get(envelope.instanceId);
1152
+ if (!follower) {
1153
+ return {
1154
+ kind: "bus.ack",
1155
+ requestId: envelope.requestId,
1156
+ ok: false,
1157
+ message: "Unknown Telegram bus follower instance.",
1158
+ };
1159
+ }
1160
+ if (
1161
+ !follower.registrationGeneration ||
1162
+ envelope.registrationGeneration !== follower.registrationGeneration
1163
+ ) {
1164
+ return {
1165
+ kind: "bus.ack",
1166
+ requestId: envelope.requestId,
1167
+ ok: false,
1168
+ message: "Stale Telegram bus follower registration generation.",
1169
+ };
1170
+ }
1171
+ deps.followerRegistry.heartbeat(envelope.instanceId, getNowMs());
1172
+ if (envelope.kind === "follower.resolveAgentTarget") {
1173
+ const target = await deps.resolveAgentTarget?.(
1174
+ follower,
1175
+ envelope.selector,
1176
+ );
1177
+ return target
1178
+ ? {
1179
+ kind: "bus.ack",
1180
+ requestId: envelope.requestId,
1181
+ ok: true,
1182
+ result: target,
1183
+ }
1184
+ : {
1185
+ kind: "bus.ack",
1186
+ requestId: envelope.requestId,
1187
+ ok: false,
1188
+ message: "Telegram agent target is unavailable or ambiguous.",
1189
+ };
1190
+ }
1191
+ if (!deps.routeAgentMessage) {
1192
+ return {
1193
+ kind: "bus.ack",
1194
+ requestId: envelope.requestId,
1195
+ ok: false,
1196
+ message: "Telegram agent message routing is unavailable.",
1197
+ };
1198
+ }
1199
+ await deps.routeAgentMessage(follower, envelope.message);
1200
+ return { kind: "bus.ack", requestId: envelope.requestId, ok: true };
1201
+ };
1202
+ const routeQueueHandoff = async (
1203
+ envelope: Extract<
1204
+ TelegramBusEnvelope,
1205
+ { kind: "follower.offerQueueHandoff" }
1206
+ >,
1207
+ ): Promise<TelegramBusEnvelope> => {
1208
+ const donor = deps.followerRegistry.get(envelope.instanceId);
1209
+ if (!donor) {
1210
+ return {
1211
+ kind: "bus.ack",
1212
+ requestId: envelope.requestId,
1213
+ ok: false,
1214
+ message: "Unknown Telegram bus follower instance.",
1215
+ };
1216
+ }
1217
+ if (
1218
+ !donor.registrationGeneration ||
1219
+ envelope.registrationGeneration !== donor.registrationGeneration
1220
+ ) {
1221
+ return {
1222
+ kind: "bus.ack",
1223
+ requestId: envelope.requestId,
1224
+ ok: false,
1225
+ message: "Stale Telegram bus follower registration generation.",
1226
+ };
1227
+ }
1228
+ const recipient = deps.followerRegistry.get(envelope.recipientInstanceId);
1229
+ if (
1230
+ !hasTelegramBusCapability(
1231
+ deps.protocolIdentity,
1232
+ TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF,
1233
+ ) ||
1234
+ !hasTelegramBusCapability(
1235
+ donor.protocol,
1236
+ TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF,
1237
+ ) ||
1238
+ !hasTelegramBusCapability(
1239
+ recipient?.protocol,
1240
+ TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF,
1241
+ )
1242
+ ) {
1243
+ return {
1244
+ kind: "bus.ack",
1245
+ requestId: envelope.requestId,
1246
+ ok: false,
1247
+ message: "Telegram queue handoff capability was not negotiated.",
1248
+ };
1249
+ }
1250
+ if (
1251
+ !recipient?.registrationGeneration ||
1252
+ envelope.recipientRegistrationGeneration !==
1253
+ recipient.registrationGeneration
1254
+ ) {
1255
+ return {
1256
+ kind: "bus.ack",
1257
+ requestId: envelope.requestId,
1258
+ ok: false,
1259
+ message: "Stale Telegram queue handoff recipient registration generation.",
1260
+ };
1261
+ }
1262
+ if (donor.instanceId === recipient.instanceId) {
1263
+ return {
1264
+ kind: "bus.ack",
1265
+ requestId: envelope.requestId,
1266
+ ok: false,
1267
+ message: "Telegram queue handoff recipient must be another runtime.",
1268
+ };
1269
+ }
1270
+ if (deps.routeQueueHandoff) {
1271
+ const result = await deps.routeQueueHandoff(donor, envelope);
1272
+ return {
1273
+ kind: "bus.ack",
1274
+ requestId: envelope.requestId,
1275
+ ok: true,
1276
+ ...(result !== undefined ? { result } : {}),
1277
+ };
1278
+ }
1279
+ const recipientSocketPath =
1280
+ recipient.busSocketPath ??
1281
+ getTelegramBusFollowerSocketPath(recipient.instanceId);
1282
+ const response = await sendTelegramBusLocalEnvelope({
1283
+ socketPath: recipientSocketPath,
1284
+ timeoutMs: deps.timeoutMs,
1285
+ retry: getTelegramBusTransportRetryPolicy({
1286
+ endpoint: recipientSocketPath,
1287
+ operation: "operation",
1288
+ }),
1289
+ envelope: {
1290
+ kind: "leader.offerQueueHandoff",
1291
+ requestId: envelope.requestId,
1292
+ auth: envelope.auth,
1293
+ recipientInstanceId: recipient.instanceId,
1294
+ recipientRegistrationGeneration: recipient.registrationGeneration,
1295
+ donorInstanceId: donor.instanceId,
1296
+ donorProcessId: envelope.donorProcessId,
1297
+ donorProcessBirthId: envelope.donorProcessBirthId,
1298
+ donorSessionGeneration: envelope.donorSessionGeneration,
1299
+ donorAcquisitionId: envelope.donorAcquisitionId,
1300
+ donorAcquiredAtMs: envelope.donorAcquiredAtMs,
1301
+ handoffToken: envelope.handoffToken,
1302
+ payload: envelope.payload,
1303
+ sentAtMs: envelope.sentAtMs,
1304
+ },
1305
+ });
1306
+ if (response?.kind === "bus.ack" && response.ok) {
1307
+ deps.followerRegistry.heartbeat(donor.instanceId, getNowMs());
1308
+ deps.followerRegistry.heartbeat(recipient.instanceId, getNowMs());
1309
+ return {
1310
+ kind: "bus.ack",
1311
+ requestId: envelope.requestId,
1312
+ ok: true,
1313
+ ...(response.result !== undefined ? { result: response.result } : {}),
1314
+ };
1315
+ }
1316
+ return {
1317
+ kind: "bus.ack",
1318
+ requestId: envelope.requestId,
1319
+ ok: false,
1320
+ message:
1321
+ response?.kind === "bus.ack"
1322
+ ? response.message
1323
+ : "Telegram queue handoff recipient did not acknowledge staging.",
1324
+ };
1325
+ };
1326
+ const forwardToFollower = async (
1327
+ envelope: Extract<
1328
+ TelegramBusEnvelope,
1329
+ {
1330
+ kind:
1331
+ | "leader.forwardCallback"
1332
+ | "leader.forwardReaction"
1333
+ | "leader.forwardMessage"
1334
+ | "leader.forwardEditedMessage";
1335
+ }
1336
+ >,
1337
+ ): Promise<TelegramBusEnvelope> => {
1338
+ const follower = deps.followerRegistry.get(envelope.recipientInstanceId);
1339
+ if (!follower) {
1340
+ return {
1341
+ kind: "bus.ack",
1342
+ requestId: envelope.requestId,
1343
+ ok: false,
1344
+ message: "Unknown Telegram bus follower instance.",
1345
+ };
1346
+ }
1347
+ if (
1348
+ !follower.registrationGeneration ||
1349
+ envelope.recipientRegistrationGeneration !==
1350
+ follower.registrationGeneration
1351
+ ) {
1352
+ return {
1353
+ kind: "bus.ack",
1354
+ requestId: envelope.requestId,
1355
+ ok: false,
1356
+ message: "Stale Telegram bus follower registration generation.",
1357
+ };
1358
+ }
1359
+ const followerSocketPath =
1360
+ follower.busSocketPath ??
1361
+ getTelegramBusFollowerSocketPath(envelope.recipientInstanceId);
1362
+ deps.followerRegistry.heartbeat(follower.instanceId, getNowMs());
1363
+ try {
1364
+ const response = await sendTelegramBusLocalEnvelope({
1365
+ socketPath: followerSocketPath,
1366
+ envelope,
1367
+ timeoutMs: deps.timeoutMs,
1368
+ retry: getTelegramBusTransportRetryPolicy({
1369
+ endpoint: followerSocketPath,
1370
+ operation: "operation",
1371
+ }),
1372
+ });
1373
+ if (response?.kind === "bus.ack" && response.ok) {
1374
+ deps.followerRegistry.heartbeat(follower.instanceId, getNowMs());
1375
+ return {
1376
+ kind: "bus.ack",
1377
+ requestId: envelope.requestId,
1378
+ ok: true,
1379
+ ...(response.result !== undefined ? { result: response.result } : {}),
1380
+ };
1381
+ }
1382
+ const message =
1383
+ response?.kind === "bus.ack" ? response.message : undefined;
1384
+ return {
1385
+ kind: "bus.ack",
1386
+ requestId: envelope.requestId,
1387
+ ok: false,
1388
+ message: message ?? "Telegram bus follower rejected forwarded update.",
1389
+ };
1390
+ } catch (error) {
1391
+ return {
1392
+ kind: "bus.ack",
1393
+ requestId: envelope.requestId,
1394
+ ok: false,
1395
+ message:
1396
+ error instanceof Error
1397
+ ? error.message
1398
+ : "Telegram bus follower forwarding failed.",
1399
+ };
1400
+ }
1401
+ };
1402
+ return async (envelope) => {
1403
+ const trafficClass = getTelegramBusEnvelopeTrafficClass(envelope);
1404
+ if (trafficClass === "response") {
1405
+ return {
1406
+ kind: "bus.ack",
1407
+ requestId: envelope.requestId,
1408
+ ok: false,
1409
+ message: "Telegram bus response envelope cannot be used as a request.",
1410
+ };
1411
+ }
1412
+ if (!isTelegramBusEnvelopeAuthorized(envelope, deps.authSecret)) {
1413
+ return createUnauthorizedBusAck(envelope.requestId);
1414
+ }
1415
+ switch (envelope.kind) {
1416
+ case "follower.register": {
1417
+ const compatibility = getTelegramBusProtocolCompatibility({
1418
+ local: deps.protocolIdentity,
1419
+ remote: envelope.registration.protocol,
1420
+ });
1421
+ if (!compatibility.compatible) {
1422
+ return {
1423
+ kind: "bus.ack" as const,
1424
+ requestId: envelope.requestId,
1425
+ ok: false,
1426
+ protocol: deps.protocolIdentity,
1427
+ error: { code: "incompatible-protocol" as const },
1428
+ message: `Incompatible Telegram bus protocol: ${compatibility.reason}.`,
1429
+ };
1430
+ }
1431
+ return runFollowerMutation(envelope.registration, async () => {
1432
+ try {
1433
+ if (!envelope.registration.registrationGeneration) {
1434
+ throw new Error(
1435
+ "Telegram follower registration requires an exact generation.",
1436
+ );
1437
+ }
1438
+ const leaderEpoch = deps.getCurrentLeaderEpoch?.();
1439
+ if (deps.getCurrentLeaderEpoch && leaderEpoch === undefined) {
1440
+ throw new Error(
1441
+ "Telegram follower registration requires leader ownership.",
1442
+ );
1443
+ }
1444
+ const target = await deps.provisionFollowerTarget?.(
1445
+ envelope.registration,
1446
+ );
1447
+ if (
1448
+ deps.getCurrentLeaderEpoch &&
1449
+ deps.getCurrentLeaderEpoch() !== leaderEpoch
1450
+ ) {
1451
+ throw new Error(
1452
+ "Telegram follower registration lost leader ownership.",
1453
+ );
1454
+ }
1455
+ const registeredTarget = target ?? envelope.registration.target;
1456
+ deps.followerRegistry.register({
1457
+ ...envelope.registration,
1458
+ connectedAtMs: getNowMs(),
1459
+ target: registeredTarget,
1460
+ ...((target?.slot ?? envelope.registration.slot)
1461
+ ? { slot: target?.slot ?? envelope.registration.slot }
1462
+ : {}),
1463
+ ...((target?.threadName ?? envelope.registration.threadName)
1464
+ ? {
1465
+ threadName:
1466
+ target?.threadName ?? envelope.registration.threadName,
1467
+ }
1468
+ : {}),
1469
+ });
1470
+ return {
1471
+ kind: "bus.ack" as const,
1472
+ requestId: envelope.requestId,
1473
+ ok: true,
1474
+ protocol: deps.protocolIdentity,
1475
+ ...(registeredTarget ? { result: registeredTarget } : {}),
1476
+ };
1477
+ } catch (error) {
1478
+ return {
1479
+ kind: "bus.ack" as const,
1480
+ requestId: envelope.requestId,
1481
+ ok: false,
1482
+ protocol: deps.protocolIdentity,
1483
+ message:
1484
+ error instanceof Error
1485
+ ? error.message
1486
+ : "Telegram bus follower target provisioning failed.",
1487
+ };
1488
+ }
1489
+ },
1490
+ );
1491
+ }
1492
+ case "follower.offerQueueHandoff":
1493
+ return routeQueueHandoff(envelope);
1494
+ case "follower.disconnect": {
1495
+ const registeredFollower = deps.followerRegistry.get(envelope.instanceId);
1496
+ return runFollowerMutation(
1497
+ registeredFollower ?? { instanceId: envelope.instanceId },
1498
+ async () => {
1499
+ const follower = deps.followerRegistry.get(envelope.instanceId);
1500
+ if (!follower) {
1501
+ return {
1502
+ kind: "bus.ack" as const,
1503
+ requestId: envelope.requestId,
1504
+ ok: false,
1505
+ message: "Unknown Telegram bus follower instance.",
1506
+ };
1507
+ }
1508
+ if (
1509
+ !follower.registrationGeneration ||
1510
+ !envelope.registrationGeneration ||
1511
+ envelope.registrationGeneration !== follower.registrationGeneration
1512
+ ) {
1513
+ return {
1514
+ kind: "bus.ack" as const,
1515
+ requestId: envelope.requestId,
1516
+ ok: false,
1517
+ message: "Stale Telegram bus follower registration generation.",
1518
+ };
1519
+ }
1520
+ await deps.onFollowerDisconnected?.(follower);
1521
+ const current = deps.followerRegistry.get(follower.instanceId);
1522
+ if (
1523
+ current?.registrationGeneration !== follower.registrationGeneration
1524
+ ) {
1525
+ return {
1526
+ kind: "bus.ack" as const,
1527
+ requestId: envelope.requestId,
1528
+ ok: false,
1529
+ message: "Stale Telegram bus follower registration generation.",
1530
+ };
1531
+ }
1532
+ deps.followerRegistry.remove(follower.instanceId);
1533
+ return {
1534
+ kind: "bus.ack" as const,
1535
+ requestId: envelope.requestId,
1536
+ ok: true,
1537
+ };
1538
+ },
1539
+ );
1540
+ }
1541
+ case "follower.heartbeat": {
1542
+ const current = deps.followerRegistry.get(envelope.instanceId);
1543
+ if (!current) {
1544
+ return {
1545
+ kind: "bus.ack",
1546
+ requestId: envelope.requestId,
1547
+ ok: false,
1548
+ message: "Unknown Telegram bus follower instance.",
1549
+ };
1550
+ }
1551
+ if (
1552
+ !current.registrationGeneration ||
1553
+ envelope.registrationGeneration !== current.registrationGeneration
1554
+ ) {
1555
+ return {
1556
+ kind: "bus.ack",
1557
+ requestId: envelope.requestId,
1558
+ ok: false,
1559
+ message: "Stale Telegram bus follower registration generation.",
1560
+ };
1561
+ }
1562
+ const follower = deps.followerRegistry.heartbeat(
1563
+ envelope.instanceId,
1564
+ getNowMs(),
1565
+ );
1566
+ return follower
1567
+ ? {
1568
+ kind: "bus.ack",
1569
+ requestId: envelope.requestId,
1570
+ ok: true,
1571
+ result: {
1572
+ eligibleElectionSlots: deps.followerRegistry
1573
+ .list()
1574
+ .filter(
1575
+ (candidate) =>
1576
+ !deps.protocolIdentity.capabilities.includes(
1577
+ TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
1578
+ ) ||
1579
+ candidate.protocol?.capabilities.includes(
1580
+ TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
1581
+ ),
1582
+ )
1583
+ .map((candidate) => candidate.slot)
1584
+ .filter((slot): slot is string =>
1585
+ typeof slot === "string" && /^[A-Z]$/.test(slot),
1586
+ )
1587
+ .sort(),
1588
+ },
1589
+ }
1590
+ : {
1591
+ kind: "bus.ack",
1592
+ requestId: envelope.requestId,
1593
+ ok: false,
1594
+ message: "Unknown Telegram bus follower instance.",
1595
+ };
1596
+ }
1597
+ case "follower.resolveAgentTarget":
1598
+ case "follower.routeAgentMessage":
1599
+ return handleAgentRequest(envelope);
1600
+ case "leader.forwardCallback":
1601
+ case "leader.forwardReaction":
1602
+ case "leader.forwardMessage":
1603
+ case "leader.forwardEditedMessage":
1604
+ return forwardToFollower(envelope);
1605
+ case "follower.callApi":
1606
+ return handleFollowerApiCall(envelope, { ...deps, getNowMs });
1607
+ default:
1608
+ return {
1609
+ kind: "bus.ack",
1610
+ requestId: envelope.requestId,
1611
+ ok: false,
1612
+ message: "Telegram bus envelope is not handled by this leader.",
1613
+ };
1614
+ }
1615
+ };
1616
+ }
1617
+
1618
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
1619
+ return value && typeof value === "object" && !Array.isArray(value)
1620
+ ? (value as Record<string, unknown>)
1621
+ : undefined;
1622
+ }
1623
+
1624
+ function asInteger(value: unknown): number | undefined {
1625
+ if (typeof value === "number" && Number.isInteger(value)) return value;
1626
+ if (typeof value !== "string" || value.trim() === "") return undefined;
1627
+ const parsed = Number(value);
1628
+ return Number.isInteger(parsed) ? parsed : undefined;
1629
+ }
1630
+
1631
+ function getFollowerApiMethodAndBody(
1632
+ envelope: Extract<TelegramBusEnvelope, { kind: "follower.callApi" }>,
1633
+ ): {
1634
+ apiMethod: string;
1635
+ body?: Record<string, unknown>;
1636
+ } {
1637
+ if (envelope.method === "call" || envelope.method === "callMultipart") {
1638
+ return {
1639
+ apiMethod: typeof envelope.args[0] === "string" ? envelope.args[0] : "",
1640
+ body: asRecord(envelope.args[1]),
1641
+ };
1642
+ }
1643
+ return { apiMethod: envelope.method, body: asRecord(envelope.args[0]) };
1644
+ }
1645
+
1646
+ function getSentMessageIds(result: unknown): number[] {
1647
+ const values = Array.isArray(result) ? result : [result];
1648
+ return values
1649
+ .map((value) => asInteger(asRecord(value)?.message_id))
1650
+ .filter((messageId): messageId is number => messageId !== undefined);
1651
+ }
1652
+
1653
+ function recordFollowerApiMessageOwnership(input: {
1654
+ envelope: Extract<TelegramBusEnvelope, { kind: "follower.callApi" }>;
1655
+ follower: TelegramBusFollowerView;
1656
+ result: unknown;
1657
+ record?: TelegramBusFollowerMessageOwnershipRecorder;
1658
+ }): void {
1659
+ if (!input.record) return;
1660
+ const { apiMethod, body } = getFollowerApiMethodAndBody(input.envelope);
1661
+ if (
1662
+ apiMethod !== "sendMessage" &&
1663
+ apiMethod !== "sendRichMessage" &&
1664
+ apiMethod !== "sendPhoto" &&
1665
+ apiMethod !== "sendDocument" &&
1666
+ apiMethod !== "sendVoice" &&
1667
+ apiMethod !== "sendMediaGroup"
1668
+ ) {
1669
+ return;
1670
+ }
1671
+ const chatId = asInteger(body?.chat_id) ?? input.follower.target?.chatId;
1672
+ if (chatId === undefined) return;
1673
+ const threadId =
1674
+ asInteger(body?.message_thread_id) ?? input.follower.target?.threadId;
1675
+ const target = threadId !== undefined ? { chatId, threadId } : { chatId };
1676
+ for (const messageId of getSentMessageIds(input.result)) {
1677
+ input.record({
1678
+ follower: input.follower,
1679
+ chatId,
1680
+ messageId,
1681
+ target,
1682
+ });
1683
+ }
1684
+ }
1685
+
1686
+ async function handleFollowerApiCall(
1687
+ envelope: Extract<TelegramBusEnvelope, { kind: "follower.callApi" }>,
1688
+ deps: {
1689
+ followerRegistry: TelegramBusFollowerRegistry;
1690
+ getNowMs: () => number;
1691
+ callApi?: (method: string, args: unknown[]) => Promise<unknown> | unknown;
1692
+ authorizeFollowerApiCall?: (input: {
1693
+ follower: TelegramBusFollowerView;
1694
+ method: string;
1695
+ args: unknown[];
1696
+ }) => boolean;
1697
+ recordFollowerMessageOwnership?: TelegramBusFollowerMessageOwnershipRecorder;
1698
+ },
1699
+ ): Promise<TelegramBusEnvelope> {
1700
+ const follower = deps.followerRegistry.get(envelope.instanceId);
1701
+ if (!follower) {
1702
+ return {
1703
+ kind: "bus.ack",
1704
+ requestId: envelope.requestId,
1705
+ ok: false,
1706
+ message: "Unknown Telegram bus follower instance.",
1707
+ };
1708
+ }
1709
+ if (
1710
+ !follower.registrationGeneration ||
1711
+ envelope.registrationGeneration !== follower.registrationGeneration
1712
+ ) {
1713
+ return {
1714
+ kind: "bus.ack",
1715
+ requestId: envelope.requestId,
1716
+ ok: false,
1717
+ message: "Stale Telegram bus follower registration generation.",
1718
+ };
1719
+ }
1720
+ deps.followerRegistry.heartbeat(envelope.instanceId, deps.getNowMs());
1721
+ if (
1722
+ deps.authorizeFollowerApiCall &&
1723
+ !deps.authorizeFollowerApiCall({
1724
+ follower,
1725
+ method: envelope.method,
1726
+ args: envelope.args,
1727
+ })
1728
+ ) {
1729
+ return {
1730
+ kind: "bus.ack",
1731
+ requestId: envelope.requestId,
1732
+ ok: false,
1733
+ message: "Telegram bus API call is not allowed for this follower.",
1734
+ };
1735
+ }
1736
+ if (!deps.callApi) {
1737
+ return {
1738
+ kind: "bus.ack",
1739
+ requestId: envelope.requestId,
1740
+ ok: false,
1741
+ message: "Telegram bus leader does not expose API calling.",
1742
+ };
1743
+ }
1744
+ try {
1745
+ const result = await deps.callApi(envelope.method, envelope.args);
1746
+ recordFollowerApiMessageOwnership({
1747
+ envelope,
1748
+ follower,
1749
+ result,
1750
+ record: deps.recordFollowerMessageOwnership,
1751
+ });
1752
+ return {
1753
+ kind: "bus.ack",
1754
+ requestId: envelope.requestId,
1755
+ ok: true,
1756
+ result,
1757
+ };
1758
+ } catch (error) {
1759
+ return {
1760
+ kind: "bus.ack",
1761
+ requestId: envelope.requestId,
1762
+ ok: false,
1763
+ message:
1764
+ error instanceof Error
1765
+ ? error.message
1766
+ : "Telegram bus API call failed.",
1767
+ ...(isTelegramApiCommitUnknownError(error)
1768
+ ? {
1769
+ error: {
1770
+ code: "commit-unknown" as const,
1771
+ method: error.method,
1772
+ },
1773
+ }
1774
+ : {}),
1775
+ };
1776
+ }
1777
+ }
1778
+
1779
+ export interface TelegramBusLeaderActivationSchedulerDeps<TContext> {
1780
+ isBusEnabled: () => boolean;
1781
+ ownsPolling: (ctx: TContext) => boolean;
1782
+ isBusPollingStarted: () => boolean;
1783
+ setBusPollingStarted: (started: boolean) => void;
1784
+ stopClassicPolling: () => Promise<void>;
1785
+ startClassicPolling: (ctx: TContext) => void | Promise<void>;
1786
+ startBusLeaderPolling: (ctx: TContext) => Promise<void>;
1787
+ updateStatus: (ctx: TContext) => void;
1788
+ recordRuntimeEvent?: (
1789
+ category: string,
1790
+ error: unknown,
1791
+ details?: Record<string, unknown>,
1792
+ ) => void;
1793
+ }
1794
+
1795
+ export function createTelegramBusLeaderActivationScheduler<TContext>(
1796
+ deps: TelegramBusLeaderActivationSchedulerDeps<TContext>,
1797
+ ): (ctx: TContext) => void {
1798
+ let pending = false;
1799
+ return (ctx) => {
1800
+ if (deps.isBusPollingStarted() || pending) return;
1801
+ if (!deps.isBusEnabled()) return;
1802
+ if (!deps.ownsPolling(ctx)) return;
1803
+ pending = true;
1804
+ const timer = setTimeout(() => {
1805
+ void (async () => {
1806
+ try {
1807
+ if (deps.isBusPollingStarted()) return;
1808
+ if (!deps.isBusEnabled()) return;
1809
+ if (!deps.ownsPolling(ctx)) return;
1810
+ await deps.stopClassicPolling();
1811
+ try {
1812
+ await deps.startBusLeaderPolling(ctx);
1813
+ deps.setBusPollingStarted(true);
1814
+ deps.updateStatus(ctx);
1815
+ deps.recordRuntimeEvent?.(
1816
+ "bus",
1817
+ "Telegram bus leader mode activated",
1818
+ { phase: "leader-hot-switch" },
1819
+ );
1820
+ } catch (error) {
1821
+ deps.recordRuntimeEvent?.("bus", error, {
1822
+ phase: "leader-hot-switch",
1823
+ });
1824
+ deps.setBusPollingStarted(false);
1825
+ await deps.startClassicPolling(ctx);
1826
+ }
1827
+ } finally {
1828
+ pending = false;
1829
+ }
1830
+ })();
1831
+ }, 0);
1832
+ timer.unref?.();
1833
+ };
1834
+ }
1835
+
1836
+ export function createTelegramBusLeaderRuntime<TContext>(
1837
+ deps: TelegramBusLeaderRuntimeDeps<TContext>,
1838
+ ): TelegramBusLeaderRuntime<TContext> {
1839
+ const getNowMs = deps.getNowMs ?? Date.now;
1840
+ const followerPruneIntervalMs = deps.followerPruneIntervalMs ?? 1000;
1841
+ const followerStaleAfterMs = deps.followerStaleAfterMs ?? 5000;
1842
+ const runFollowerMutation = createTelegramBusFollowerMutationRunner();
1843
+ let pruneInterval: ReturnType<typeof setInterval> | undefined;
1844
+ let pruneGeneration = 0;
1845
+ let prunePromise: Promise<void> | undefined;
1846
+ const stopPruning = () => {
1847
+ pruneGeneration += 1;
1848
+ if (pruneInterval) clearInterval(pruneInterval);
1849
+ pruneInterval = undefined;
1850
+ prunePromise = undefined;
1851
+ };
1852
+ const recordPruneEvent = (
1853
+ error: unknown,
1854
+ details: Record<string, unknown>,
1855
+ ): void => {
1856
+ try {
1857
+ deps.recordRuntimeEvent?.("bus", error, details);
1858
+ } catch {
1859
+ // Prune diagnostics cannot replace lifecycle-owned reconciliation.
1860
+ }
1861
+ };
1862
+ const pruneFollowers = async (expectedGeneration: number) => {
1863
+ const isCurrent = (): boolean => pruneGeneration === expectedGeneration;
1864
+ try {
1865
+ await localServer.ensureEndpoint();
1866
+ } catch (error) {
1867
+ recordPruneEvent(error, {
1868
+ phase: "leader-endpoint-recovery",
1869
+ });
1870
+ }
1871
+ if (!isCurrent()) return;
1872
+ const removed = deps.followerRegistry.pruneStale(
1873
+ getNowMs(),
1874
+ followerStaleAfterMs,
1875
+ );
1876
+ for (const follower of removed) {
1877
+ let processConfirmedDead = false;
1878
+ if (follower.pid !== undefined && deps.isFollowerProcessAlive) {
1879
+ try {
1880
+ processConfirmedDead = !deps.isFollowerProcessAlive(follower.pid);
1881
+ } catch (error) {
1882
+ recordPruneEvent(error, {
1883
+ phase: "follower-process-liveness",
1884
+ instanceId: follower.instanceId,
1885
+ pid: follower.pid,
1886
+ });
1887
+ }
1888
+ }
1889
+ if (!processConfirmedDead) {
1890
+ recordPruneEvent(
1891
+ "Telegram bus follower heartbeat stale; preserving thread binding",
1892
+ {
1893
+ phase: "follower-pruned",
1894
+ instanceId: follower.instanceId,
1895
+ processLiveness:
1896
+ follower.pid === undefined || !deps.isFollowerProcessAlive
1897
+ ? "unknown"
1898
+ : "alive-or-unknown",
1899
+ },
1900
+ );
1901
+ continue;
1902
+ }
1903
+ let cleanupEnabled = false;
1904
+ try {
1905
+ cleanupEnabled =
1906
+ (await deps.shouldCleanupConfirmedDeadFollower?.()) ?? false;
1907
+ } catch (error) {
1908
+ recordPruneEvent(error, {
1909
+ phase: "follower-confirmed-dead-cleanup-policy",
1910
+ instanceId: follower.instanceId,
1911
+ pid: follower.pid,
1912
+ });
1913
+ }
1914
+ if (!isCurrent()) return;
1915
+ if (!cleanupEnabled || !deps.onFollowerConfirmedDead) {
1916
+ recordPruneEvent(
1917
+ "Telegram bus follower process confirmed dead; preserving thread binding",
1918
+ {
1919
+ phase: "follower-confirmed-dead-preserved",
1920
+ instanceId: follower.instanceId,
1921
+ pid: follower.pid,
1922
+ cleanupEnabled,
1923
+ },
1924
+ );
1925
+ continue;
1926
+ }
1927
+ try {
1928
+ await runFollowerMutation(follower, async () => {
1929
+ if (!isCurrent()) return;
1930
+ const replacement = deps.followerRegistry.list().find((candidate) =>
1931
+ follower.profileKey
1932
+ ? candidate.profileKey === follower.profileKey
1933
+ : candidate.instanceId === follower.instanceId,
1934
+ );
1935
+ if (replacement) {
1936
+ recordPruneEvent(
1937
+ "Telegram bus follower replaced before confirmed-dead cleanup; preserving thread binding",
1938
+ {
1939
+ phase: "follower-confirmed-dead-replaced",
1940
+ instanceId: follower.instanceId,
1941
+ replacementInstanceId: replacement.instanceId,
1942
+ },
1943
+ );
1944
+ return;
1945
+ }
1946
+ await deps.onFollowerConfirmedDead!(follower);
1947
+ });
1948
+ } catch (error) {
1949
+ recordPruneEvent(error, {
1950
+ phase: "follower-confirmed-dead-cleanup",
1951
+ instanceId: follower.instanceId,
1952
+ pid: follower.pid,
1953
+ chatId: follower.target?.chatId,
1954
+ threadId: follower.target?.threadId,
1955
+ });
1956
+ }
1957
+ }
1958
+ };
1959
+ const requestPrune = (): Promise<void> => {
1960
+ if (prunePromise) return prunePromise;
1961
+ const expectedGeneration = pruneGeneration;
1962
+ let tracked: Promise<void>;
1963
+ tracked = pruneFollowers(expectedGeneration)
1964
+ .catch((error) => {
1965
+ if (pruneGeneration === expectedGeneration) {
1966
+ recordPruneEvent(error, { phase: "follower-prune-owner" });
1967
+ }
1968
+ })
1969
+ .finally(() => {
1970
+ if (prunePromise === tracked) prunePromise = undefined;
1971
+ });
1972
+ prunePromise = tracked;
1973
+ return tracked;
1974
+ };
1975
+ const startPruning = () => {
1976
+ stopPruning();
1977
+ pruneInterval = setInterval(() => {
1978
+ void requestPrune();
1979
+ }, followerPruneIntervalMs);
1980
+ pruneInterval.unref?.();
1981
+ };
1982
+ const handleEnvelope = createTelegramBusLeaderEnvelopeHandler({
1983
+ followerRegistry: deps.followerRegistry,
1984
+ authSecret: deps.authSecret,
1985
+ protocolIdentity: deps.protocolIdentity,
1986
+ getNowMs,
1987
+ callApi: deps.callApi,
1988
+ authorizeFollowerApiCall: deps.authorizeFollowerApiCall,
1989
+ recordFollowerMessageOwnership: deps.recordFollowerMessageOwnership,
1990
+ resolveAgentTarget: deps.resolveAgentTarget,
1991
+ routeAgentMessage: deps.routeAgentMessage,
1992
+ routeQueueHandoff: deps.routeQueueHandoff,
1993
+ provisionFollowerTarget: deps.provisionFollowerTarget,
1994
+ onFollowerDisconnected: deps.onFollowerDisconnected,
1995
+ getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
1996
+ runFollowerMutation,
1997
+ });
1998
+ const routeQueueHandoffEnvelope = async (
1999
+ input: Parameters<TelegramBusLeaderRuntime<TContext>["routeQueueHandoff"]>[0],
2000
+ ): Promise<TelegramBusEnvelope> => {
2001
+ const recipient = deps.followerRegistry.get(input.recipientInstanceId);
2002
+ if (
2003
+ !hasTelegramBusCapability(
2004
+ deps.protocolIdentity,
2005
+ TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF,
2006
+ ) ||
2007
+ !hasTelegramBusCapability(
2008
+ recipient?.protocol,
2009
+ TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF,
2010
+ )
2011
+ ) {
2012
+ return {
2013
+ kind: "bus.ack",
2014
+ requestId: input.requestId,
2015
+ ok: false,
2016
+ message: "Telegram queue handoff capability was not negotiated.",
2017
+ };
2018
+ }
2019
+ if (
2020
+ !recipient?.registrationGeneration ||
2021
+ input.recipientRegistrationGeneration !==
2022
+ recipient.registrationGeneration
2023
+ ) {
2024
+ return {
2025
+ kind: "bus.ack",
2026
+ requestId: input.requestId,
2027
+ ok: false,
2028
+ message: "Stale Telegram queue handoff recipient registration generation.",
2029
+ };
2030
+ }
2031
+ const recipientSocketPath =
2032
+ recipient.busSocketPath ??
2033
+ getTelegramBusFollowerSocketPath(recipient.instanceId);
2034
+ const response = await sendTelegramBusLocalEnvelope({
2035
+ socketPath: recipientSocketPath,
2036
+ timeoutMs: deps.timeoutMs,
2037
+ retry: getTelegramBusTransportRetryPolicy({
2038
+ endpoint: recipientSocketPath,
2039
+ operation: "operation",
2040
+ }),
2041
+ envelope: {
2042
+ kind: "leader.offerQueueHandoff",
2043
+ requestId: input.requestId,
2044
+ auth: input.auth,
2045
+ recipientInstanceId: recipient.instanceId,
2046
+ recipientRegistrationGeneration: recipient.registrationGeneration,
2047
+ donorInstanceId: input.donorInstanceId,
2048
+ donorProcessId: input.donorProcessId,
2049
+ donorProcessBirthId: input.donorProcessBirthId,
2050
+ donorSessionGeneration: input.donorSessionGeneration,
2051
+ donorAcquisitionId: input.donorAcquisitionId,
2052
+ donorAcquiredAtMs: input.donorAcquiredAtMs,
2053
+ handoffToken: input.handoffToken,
2054
+ payload: input.payload,
2055
+ sentAtMs: input.sentAtMs,
2056
+ },
2057
+ });
2058
+ if (response?.kind === "bus.ack" && response.ok) {
2059
+ deps.followerRegistry.heartbeat(recipient.instanceId, getNowMs());
2060
+ return {
2061
+ kind: "bus.ack",
2062
+ requestId: input.requestId,
2063
+ ok: true,
2064
+ ...(response.result !== undefined ? { result: response.result } : {}),
2065
+ };
2066
+ }
2067
+ return {
2068
+ kind: "bus.ack",
2069
+ requestId: input.requestId,
2070
+ ok: false,
2071
+ message:
2072
+ response?.kind === "bus.ack"
2073
+ ? response.message
2074
+ : "Telegram queue handoff recipient did not acknowledge staging.",
2075
+ };
2076
+ };
2077
+ const localServer = createTelegramBusLocalServer({
2078
+ socketPath: deps.socketPath,
2079
+ commitEndpointPublication: deps.commitEndpointPublication,
2080
+ recordTransportEvent(phase, details) {
2081
+ deps.recordRuntimeEvent?.("bus", `Telegram bus ${phase}`, {
2082
+ phase: `leader-${phase}`,
2083
+ ...details,
2084
+ });
2085
+ },
2086
+ handleEnvelope,
2087
+ });
2088
+ return {
2089
+ routeQueueHandoff: (envelope) =>
2090
+ routeQueueHandoffEnvelope(envelope),
2091
+ startPolling: async (ctx) => {
2092
+ // Replay durable cleanup before publishing the follower endpoint so a
2093
+ // replacement registration cannot reclaim a target while it is deleted.
2094
+ await deps.provisionLeaderTarget?.(ctx);
2095
+ await localServer.start();
2096
+ startPruning();
2097
+ try {
2098
+ await deps.startPolling(ctx);
2099
+ } catch (error) {
2100
+ stopPruning();
2101
+ await localServer.stop();
2102
+ throw error;
2103
+ }
2104
+ },
2105
+ stopPolling: async () => {
2106
+ stopPruning();
2107
+ try {
2108
+ await deps.stopPolling();
2109
+ } finally {
2110
+ await localServer
2111
+ .stop()
2112
+ .catch((error) =>
2113
+ deps.recordRuntimeEvent?.("bus", error, { phase: "stop" }),
2114
+ );
2115
+ deps.followerRegistry.clear();
2116
+ }
2117
+ },
2118
+ };
2119
+ }