@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,1941 @@
1
+ /**
2
+ * Telegram bus follower runtime
3
+ * Zones: multi-instance bus, follower lifecycle, manual registration
4
+ * Owns this Pi instance's follower-side bus behavior: manual registration,
5
+ * heartbeat, forwarded-update receiving, and follower-routed API calls.
6
+ * It must not spawn Pi processes or create hidden Telegram-originated instances.
7
+ */
8
+
9
+ import { basename } from "node:path";
10
+
11
+ import * as Sync from "./sync.ts";
12
+ import * as Threads from "./threads.ts";
13
+ import { parseTelegramUpdateJournalQueueOwner } from "./journal.ts";
14
+ import type { TelegramLockEntry, TelegramLockState } from "./locks.ts";
15
+ import type {
16
+ TelegramQueueHandoffPayload,
17
+ TelegramQueueHandoffStageResult,
18
+ } from "./queue.ts";
19
+ import type { TelegramTarget } from "./target.ts";
20
+ import {
21
+ isTelegramApiMethodRetrySafe,
22
+ TelegramApiCommitUnknownError,
23
+ } from "./telegram-api.ts";
24
+ import {
25
+ createTelegramBusFollowerDeliveryIdentity,
26
+ createTelegramBusFollowerTargetController,
27
+ createTelegramBusForeignOwnedUpdateForwarder,
28
+ createTelegramBusLocalServer,
29
+ createTelegramBusRequestIdFactory,
30
+ createUnauthorizedBusAck,
31
+ getTelegramBusProtocolCompatibility,
32
+ getTelegramBusSocketPath,
33
+ isTelegramBusEnvelopeAuthorized,
34
+ resolveTelegramBusSocketPath,
35
+ sendTelegramBusLocalEnvelope,
36
+ type TelegramBusAgentMessage,
37
+ type TelegramBusAgentTargetSelector,
38
+ type TelegramBusEnvelope,
39
+ type TelegramBusProtocolIdentity,
40
+ type TelegramBusSocketPathSource,
41
+ } from "./bus.ts";
42
+ import {
43
+ getTelegramBusTransportRetryPolicy,
44
+ TELEGRAM_BUS_REGISTRATION_RETRY,
45
+ } from "./bus-transport.ts";
46
+
47
+ export const TELEGRAM_BUS_FOLLOWER_PROMOTION_GRACE_MS = 2_500;
48
+ export const TELEGRAM_FOLLOWER_SESSION_HANDOFF_TTL_MS = 30_000;
49
+ export const TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS = 30_000;
50
+ export const TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_ATTEMPTS =
51
+ TELEGRAM_BUS_REGISTRATION_RETRY.attempts;
52
+ export const TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_DELAY_MS =
53
+ TELEGRAM_BUS_REGISTRATION_RETRY.delayMs;
54
+
55
+ const TELEGRAM_FOLLOWER_SESSION_HANDOFF_KEY =
56
+ "__piTelegramFollowerSessionHandoff";
57
+
58
+ export interface TelegramFollowerSessionHandoff {
59
+ pid: number;
60
+ instanceId: string;
61
+ createdAtMs: number;
62
+ target: TelegramTarget;
63
+ slot?: string;
64
+ threadName?: string;
65
+ }
66
+
67
+ export function getTelegramFollowerSessionHandoff():
68
+ TelegramFollowerSessionHandoff | undefined {
69
+ const value = (globalThis as Record<string, unknown>)[
70
+ TELEGRAM_FOLLOWER_SESSION_HANDOFF_KEY
71
+ ];
72
+ if (!value || typeof value !== "object") return undefined;
73
+ const handoff = value as Partial<TelegramFollowerSessionHandoff>;
74
+ if (
75
+ typeof handoff.pid !== "number" ||
76
+ typeof handoff.instanceId !== "string" ||
77
+ typeof handoff.createdAtMs !== "number" ||
78
+ !handoff.target ||
79
+ typeof handoff.target !== "object" ||
80
+ typeof handoff.target.chatId !== "number"
81
+ ) {
82
+ return undefined;
83
+ }
84
+ return handoff as TelegramFollowerSessionHandoff;
85
+ }
86
+
87
+ export function setTelegramFollowerSessionHandoff(
88
+ handoff: TelegramFollowerSessionHandoff | undefined,
89
+ ): void {
90
+ const store = globalThis as Record<string, unknown>;
91
+ if (!handoff) delete store[TELEGRAM_FOLLOWER_SESSION_HANDOFF_KEY];
92
+ else store[TELEGRAM_FOLLOWER_SESSION_HANDOFF_KEY] = handoff;
93
+ }
94
+
95
+ export function isTelegramFollowerSessionHandoffFresh(
96
+ handoff: TelegramFollowerSessionHandoff | undefined,
97
+ options: { pid?: number; nowMs?: number; ttlMs?: number } = {},
98
+ ): handoff is TelegramFollowerSessionHandoff {
99
+ if (!handoff) return false;
100
+ const pid = options.pid ?? process.pid;
101
+ const nowMs = options.nowMs ?? Date.now();
102
+ const ttlMs = options.ttlMs ?? TELEGRAM_FOLLOWER_SESSION_HANDOFF_TTL_MS;
103
+ return handoff.pid === pid && nowMs - handoff.createdAtMs <= ttlMs;
104
+ }
105
+
106
+ export interface TelegramBusFollowerRegistrationRuntime<TContext> {
107
+ registerWithLeader: (
108
+ ctx: TContext,
109
+ leader: { busSocketPath?: string; busSecret?: string },
110
+ options?: { target?: TelegramTarget; previousInstanceId?: string },
111
+ ) => Promise<boolean>;
112
+ setContext: (ctx: TContext) => void;
113
+ disconnectFromLeader?: () => Promise<boolean>;
114
+ stop: () => void;
115
+ }
116
+
117
+ export interface TelegramBusFollowerSessionReplacementSuspenderDeps {
118
+ registrationState: Pick<
119
+ TelegramBusFollowerRegistrationState,
120
+ "isRegistered" | "getTarget" | "getSlot" | "getThreadName"
121
+ >;
122
+ instanceId: string;
123
+ suspendPolling: () => Promise<void>;
124
+ isLeader?: () => boolean;
125
+ getLeaderBinding?: () => TelegramBusFollowerPromotedBinding | undefined;
126
+ getActiveContext?: () => { cwd?: string } | undefined;
127
+ getActiveProfileName?: () => string | undefined;
128
+ recordRuntimeEvent: (
129
+ category: string,
130
+ error: unknown,
131
+ details?: Record<string, unknown>,
132
+ ) => void;
133
+ getNowMs?: () => number;
134
+ getPid?: () => number;
135
+ }
136
+
137
+ export interface TelegramBusFollowerSessionRefreshHookDeps<TContext> {
138
+ registrationState: Pick<TelegramBusFollowerRegistrationState, "isRegistered">;
139
+ registrationRuntime: Pick<
140
+ TelegramBusFollowerRegistrationRuntime<TContext>,
141
+ "registerWithLeader" | "setContext"
142
+ >;
143
+ getLeaderState: () => TelegramLockState;
144
+ isSessionActive?: (ctx: TContext) => boolean;
145
+ updateStatus: (ctx: TContext) => void;
146
+ recordRuntimeEvent: (
147
+ category: string,
148
+ error: unknown,
149
+ details?: Record<string, unknown>,
150
+ ) => void;
151
+ }
152
+
153
+ export type TelegramBusFollowerControlLifecyclePhase = "electing";
154
+
155
+ export interface TelegramBusFollowerControlState {
156
+ getActiveAuthSecret: () => string | undefined;
157
+ setActiveAuthSecret: (secret: string | undefined) => void;
158
+ getLifecyclePhase: () => TelegramBusFollowerControlLifecyclePhase | undefined;
159
+ setLifecyclePhase: (
160
+ phase: TelegramBusFollowerControlLifecyclePhase | undefined,
161
+ ) => void;
162
+ }
163
+
164
+ export interface TelegramBusFollowerRegistrationState {
165
+ isRegistered: () => boolean;
166
+ getTarget: () => TelegramTarget | undefined;
167
+ getSlot: () => string | undefined;
168
+ getThreadName: () => string | undefined;
169
+ getGeneration: () => string | undefined;
170
+ getLeaderProtocol: () => TelegramBusProtocolIdentity | undefined;
171
+ getEligibleElectionSlots: () => readonly string[];
172
+ setEligibleElectionSlots: (slots: readonly string[]) => void;
173
+ setRegistered: (
174
+ registered: boolean,
175
+ target?: TelegramTarget,
176
+ metadata?: {
177
+ slot?: string;
178
+ threadName?: string;
179
+ generation?: string;
180
+ leaderProtocol?: TelegramBusProtocolIdentity;
181
+ },
182
+ ) => void;
183
+ }
184
+
185
+ export interface TelegramBusForwardedUpdateReceiverRuntime {
186
+ start: () => Promise<void>;
187
+ stop: () => Promise<void>;
188
+ }
189
+
190
+ export interface TelegramBusFollowerDurableAdmissionResult {
191
+ deliveryId: string;
192
+ sourceUpdateId: number;
193
+ }
194
+
195
+ export interface TelegramBusFollowerDurableAdmissionPort<TContext> {
196
+ admit: (
197
+ envelope: Extract<
198
+ TelegramBusEnvelope,
199
+ {
200
+ kind:
201
+ | "leader.forwardCallback"
202
+ | "leader.forwardReaction"
203
+ | "leader.forwardMessage"
204
+ | "leader.forwardEditedMessage";
205
+ }
206
+ >,
207
+ ctx: TContext,
208
+ ) => Promise<TelegramBusFollowerDurableAdmissionResult>;
209
+ }
210
+
211
+ export interface TelegramBusFollowerClientRuntimeDeps<TMessage = unknown> {
212
+ socketPath: TelegramBusSocketPathSource;
213
+ instanceId: string;
214
+ getApiAuthSecret?: () => string | undefined;
215
+ getForwardingAuthSecret?: () => string | undefined;
216
+ getRegistrationGeneration: () => string | undefined;
217
+ getForwardCommentBatchPosition?: (
218
+ message: TMessage,
219
+ ) => "comment" | "forward" | undefined;
220
+ recordRuntimeEvent?: (
221
+ category: string,
222
+ error: unknown,
223
+ details?: Record<string, unknown>,
224
+ ) => void;
225
+ timeoutMs?: number;
226
+ }
227
+
228
+ export interface TelegramBusFollowerApiCallerDeps {
229
+ socketPath: TelegramBusSocketPathSource;
230
+ instanceId: string;
231
+ createRequestId: () => string;
232
+ getAuthSecret?: () => string | undefined;
233
+ getRegistrationGeneration: () => string | undefined;
234
+ getNowMs?: () => number;
235
+ timeoutMs?: number;
236
+ }
237
+
238
+ export interface TelegramBusFollowerRegistrationRuntimeDeps<
239
+ TContext extends { cwd?: string },
240
+ > {
241
+ instanceId: string;
242
+ createRequestId: () => string;
243
+ protocolIdentity: TelegramBusProtocolIdentity;
244
+ getLeaderAuthSecret?: (leader: { busSecret?: string }) => string | undefined;
245
+ setActiveAuthSecret?: (secret: string | undefined) => void;
246
+ followerBusSocketPath?: string;
247
+ getFollowerBusSocketPath?: () => string;
248
+ getLeaderSocketPath?: () => string;
249
+ startReceiving?: () => Promise<void>;
250
+ stopReceiving?: () => Promise<void> | void;
251
+ registrationState?: TelegramBusFollowerRegistrationState;
252
+ isContextActive?: (ctx: TContext) => boolean;
253
+ getProfileKey?: (ctx: TContext) => string | undefined;
254
+ getThreadName?: (ctx: TContext) => string | undefined;
255
+ getNowMs?: () => number;
256
+ getPid?: () => number;
257
+ getProcessBirthId?: () => string;
258
+ getSessionGeneration?: () => number;
259
+ timeoutMs?: number;
260
+ registrationTimeoutMs?: number;
261
+ registrationRetryAttempts?: number;
262
+ registrationRetryDelayMs?: number;
263
+ heartbeatMs?: number;
264
+ recordRuntimeEvent?: (
265
+ category: string,
266
+ error: unknown,
267
+ details?: Record<string, unknown>,
268
+ ) => void;
269
+ onHeartbeatFailure?: (error: unknown, ctx: TContext) => Promise<void> | void;
270
+ onRegistered?: (ctx: TContext) => Promise<void> | void;
271
+ }
272
+
273
+ export function createTelegramManualFollowerProfileKeyResolver(input: {
274
+ getActiveProfileName: () => string | undefined;
275
+ manualFollowerOwnerId: string;
276
+ }): () => string {
277
+ return () =>
278
+ Threads.getTelegramThreadOwnerKey({
279
+ kind: "manual-follower",
280
+ instanceId: input.manualFollowerOwnerId,
281
+ telegramProfile: input.getActiveProfileName(),
282
+ });
283
+ }
284
+
285
+ export interface TelegramBusFollowerElection {
286
+ expectedOwner?: TelegramLockEntry;
287
+ }
288
+
289
+ export type TelegramBusFollowerPromotionHandler<TContext> = (
290
+ ctx: TContext,
291
+ binding: TelegramBusFollowerPromotedBinding,
292
+ election: TelegramBusFollowerElection,
293
+ ) => Promise<boolean>;
294
+
295
+ export function createTelegramBusFollowerPromotionHandler<
296
+ TContext extends { cwd: string },
297
+ >(input: {
298
+ topicTargetStore: Threads.TelegramTopicTargetStore;
299
+ instanceId: string;
300
+ getActiveProfileName: () => string | undefined;
301
+ startLeader: (
302
+ ctx: TContext,
303
+ election: TelegramBusFollowerElection,
304
+ onAcquired: () => Promise<void>,
305
+ ) => Promise<boolean>;
306
+ recordRuntimeEvent: (
307
+ category: string,
308
+ error: unknown,
309
+ details?: Record<string, unknown>,
310
+ ) => void;
311
+ getNowMs?: () => number;
312
+ getPid?: () => number;
313
+ }): TelegramBusFollowerPromotionHandler<TContext> {
314
+ return async (ctx, binding, election) => {
315
+ const promoted = await input.startLeader(ctx, election, async () => {
316
+ const promotedRecord =
317
+ await Threads.promoteTelegramFollowerBindingToLeader({
318
+ store: input.topicTargetStore,
319
+ instanceId: input.instanceId,
320
+ cwd: ctx.cwd,
321
+ telegramProfile: input.getActiveProfileName(),
322
+ target: binding.target,
323
+ slot: binding.slot,
324
+ threadName: binding.threadName,
325
+ });
326
+ if (promotedRecord) {
327
+ input.recordRuntimeEvent(
328
+ "bus",
329
+ "Follower thread binding promoted to leader",
330
+ {
331
+ phase: "follower-promoted-binding",
332
+ chatId: promotedRecord.target.chatId,
333
+ threadId: promotedRecord.target.threadId,
334
+ slot: promotedRecord.slot,
335
+ threadName: promotedRecord.threadName,
336
+ },
337
+ );
338
+ }
339
+ });
340
+ if (promoted && typeof binding.target?.threadId === "number") {
341
+ const profileKey = Threads.getTelegramThreadOwnerKey({
342
+ kind: "leader",
343
+ cwd: ctx.cwd,
344
+ instanceId: input.instanceId,
345
+ telegramProfile: input.getActiveProfileName(),
346
+ });
347
+ Threads.setTelegramLeaderSessionHandoff({
348
+ pid: input.getPid?.() ?? process.pid,
349
+ instanceId: input.instanceId,
350
+ createdAtMs: input.getNowMs?.() ?? Date.now(),
351
+ profileKey,
352
+ target: {
353
+ chatId: binding.target.chatId,
354
+ threadId: binding.target.threadId,
355
+ },
356
+ slot: binding.slot,
357
+ threadName: binding.threadName,
358
+ });
359
+ input.recordRuntimeEvent(
360
+ "bus",
361
+ "Promoted leader binding retained for session replacement",
362
+ {
363
+ phase: "follower-promoted-session-handoff",
364
+ chatId: binding.target.chatId,
365
+ threadId: binding.target.threadId,
366
+ slot: binding.slot,
367
+ threadName: binding.threadName,
368
+ },
369
+ );
370
+ }
371
+ return promoted;
372
+ };
373
+ }
374
+
375
+ export interface TelegramBusFollowerTargetReplacementHandlerDeps<TContext> {
376
+ topicTargetStore: Pick<
377
+ Threads.TelegramTopicTargetStore,
378
+ "load" | "list" | "markStaleByTarget" | "upsert" | "persist"
379
+ >;
380
+ registrationState: Pick<
381
+ TelegramBusFollowerRegistrationState,
382
+ "getTarget" | "setRegistered"
383
+ > &
384
+ Partial<Pick<TelegramBusFollowerRegistrationState, "getGeneration">>;
385
+ instanceId: string;
386
+ getManualFollowerProfileKey: () => string;
387
+ manualFollowerOwnerId: string;
388
+ getSyncState: () => Sync.TelegramSyncState;
389
+ setSyncState: (state: Sync.TelegramSyncState) => void;
390
+ getNowMs?: () => number;
391
+ updateStatus: (ctx: TContext) => void;
392
+ recordRuntimeEvent?: (
393
+ category: string,
394
+ error: unknown,
395
+ details?: Record<string, unknown>,
396
+ ) => void;
397
+ }
398
+
399
+ export type TelegramBusFollowerLeaderState =
400
+ | { kind: "inactive" }
401
+ | { kind: "active-here"; lock: TelegramBusFollowerLeaderLock }
402
+ | { kind: "active-elsewhere"; lock: TelegramBusFollowerLeaderLock }
403
+ | { kind: "stale"; lock: TelegramBusFollowerLeaderLock };
404
+
405
+ export type TelegramBusFollowerLeaderLock = TelegramLockEntry;
406
+
407
+ export interface TelegramBusFollowerPromotedBinding {
408
+ target?: TelegramTarget;
409
+ slot?: string;
410
+ threadName?: string;
411
+ }
412
+
413
+ export interface TelegramBusFollowerHeartbeatRecoveryHandlerDeps<TContext> {
414
+ registrationState: Pick<
415
+ TelegramBusFollowerRegistrationState,
416
+ | "getTarget"
417
+ | "getSlot"
418
+ | "getThreadName"
419
+ | "getEligibleElectionSlots"
420
+ | "setRegistered"
421
+ >;
422
+ getRegistrationRuntime: () => TelegramBusFollowerRegistrationRuntime<TContext>;
423
+ getLeaderState: () => TelegramBusFollowerLeaderState;
424
+ setLifecyclePhase: (phase: "electing" | undefined) => void;
425
+ updateStatus: (ctx: TContext) => void;
426
+ promoteToLeader: (
427
+ ctx: TContext,
428
+ binding: TelegramBusFollowerPromotedBinding,
429
+ election: TelegramBusFollowerElection,
430
+ ) => Promise<boolean>;
431
+ sleep?: (ms: number) => Promise<void>;
432
+ scheduleRetry?: (retry: () => void, delayMs: number) => void;
433
+ getActiveContext?: () => TContext | undefined;
434
+ promotionGraceMs?: number;
435
+ recordRuntimeEvent: (
436
+ category: string,
437
+ error: unknown,
438
+ details?: Record<string, unknown>,
439
+ ) => void;
440
+ }
441
+
442
+ export interface TelegramBusForwardedUpdateReceiverRuntimeDeps<TContext> {
443
+ socketPath: TelegramBusSocketPathSource;
444
+ instanceId: string;
445
+ getAuthSecret?: () => string | undefined;
446
+ getRegistrationGeneration: () => string | undefined;
447
+ getRecipientBindingKey: () => string | undefined;
448
+ durableAdmission: TelegramBusFollowerDurableAdmissionPort<TContext>;
449
+ getContext: () => TContext | undefined;
450
+ handleQueueHandoff?: (
451
+ envelope: Extract<
452
+ TelegramBusEnvelope,
453
+ { kind: "leader.offerQueueHandoff" }
454
+ >,
455
+ ctx: TContext,
456
+ ) => Promise<TelegramQueueHandoffStageResult> | TelegramQueueHandoffStageResult;
457
+ handleReplaceTarget?: (
458
+ input: {
459
+ target: TelegramTarget & { threadId: number };
460
+ oldTarget?: TelegramTarget & { threadId: number };
461
+ reason: "thread-restore";
462
+ },
463
+ ctx: TContext,
464
+ ) => Promise<void> | void;
465
+ recordRuntimeEvent?: (
466
+ category: string,
467
+ error: unknown,
468
+ details?: Record<string, unknown>,
469
+ ) => void;
470
+ }
471
+
472
+ export interface TelegramBusFollowerRuntimeAssembly<TContext> {
473
+ receiver: TelegramBusForwardedUpdateReceiverRuntime;
474
+ registration: TelegramBusFollowerRegistrationRuntime<TContext>;
475
+ }
476
+
477
+ export interface TelegramBusFollowerRuntimeAssemblyPorts<
478
+ TContext extends { cwd?: string },
479
+ > {
480
+ instanceId: string;
481
+ registrationState: TelegramBusFollowerRegistrationState;
482
+ recordRuntimeEvent: (
483
+ category: string,
484
+ error: unknown,
485
+ details?: Record<string, unknown>,
486
+ ) => void;
487
+ receiver: Omit<
488
+ TelegramBusForwardedUpdateReceiverRuntimeDeps<TContext>,
489
+ | "handleReplaceTarget"
490
+ | "instanceId"
491
+ | "recordRuntimeEvent"
492
+ | "getRegistrationGeneration"
493
+ >;
494
+ targetReplacement: Omit<
495
+ TelegramBusFollowerTargetReplacementHandlerDeps<TContext>,
496
+ "registrationState" | "instanceId" | "recordRuntimeEvent"
497
+ >;
498
+ recovery: Omit<
499
+ TelegramBusFollowerHeartbeatRecoveryHandlerDeps<TContext>,
500
+ "getRegistrationRuntime" | "registrationState" | "recordRuntimeEvent"
501
+ >;
502
+ registration: Omit<
503
+ TelegramBusFollowerRegistrationRuntimeDeps<TContext>,
504
+ | "startReceiving"
505
+ | "stopReceiving"
506
+ | "onHeartbeatFailure"
507
+ | "instanceId"
508
+ | "registrationState"
509
+ | "recordRuntimeEvent"
510
+ | "protocolIdentity"
511
+ > & {
512
+ protocolIdentity: TelegramBusProtocolIdentity;
513
+ };
514
+ }
515
+
516
+ export function createTelegramBusFollowerRuntimeAssembly<
517
+ TContext extends { cwd?: string },
518
+ >(
519
+ ports: TelegramBusFollowerRuntimeAssemblyPorts<TContext>,
520
+ ): TelegramBusFollowerRuntimeAssembly<TContext> {
521
+ const sharedRuntimeDeps = {
522
+ instanceId: ports.instanceId,
523
+ registrationState: ports.registrationState,
524
+ recordRuntimeEvent: ports.recordRuntimeEvent,
525
+ };
526
+ const receiver = createTelegramBusForwardedUpdateReceiverRuntime({
527
+ ...ports.receiver,
528
+ instanceId: ports.instanceId,
529
+ recordRuntimeEvent: ports.recordRuntimeEvent,
530
+ getRegistrationGeneration: ports.registrationState.getGeneration,
531
+ handleReplaceTarget: createTelegramBusFollowerTargetReplacementHandler({
532
+ ...ports.targetReplacement,
533
+ ...sharedRuntimeDeps,
534
+ }),
535
+ });
536
+ let registration: TelegramBusFollowerRegistrationRuntime<TContext>;
537
+ const recovery = createTelegramBusFollowerHeartbeatRecoveryHandler({
538
+ ...ports.recovery,
539
+ registrationState: ports.registrationState,
540
+ recordRuntimeEvent: ports.recordRuntimeEvent,
541
+ getRegistrationRuntime: () => registration,
542
+ });
543
+ registration = createTelegramBusFollowerRegistrationRuntime({
544
+ ...ports.registration,
545
+ ...sharedRuntimeDeps,
546
+ startReceiving: receiver.start,
547
+ stopReceiving: receiver.stop,
548
+ onHeartbeatFailure: recovery,
549
+ });
550
+ return { receiver, registration };
551
+ }
552
+
553
+ export function createTelegramBusFollowerTargetReplacementHandler<TContext>(
554
+ deps: TelegramBusFollowerTargetReplacementHandlerDeps<TContext>,
555
+ ): NonNullable<
556
+ TelegramBusForwardedUpdateReceiverRuntimeDeps<TContext>["handleReplaceTarget"]
557
+ > {
558
+ const getNowMs = deps.getNowMs ?? Date.now;
559
+ return async (input, ctx) => {
560
+ await deps.topicTargetStore.load();
561
+ const nowMs = getNowMs();
562
+ const currentRecord = Threads.findCurrentTelegramInstanceThreadRecord({
563
+ records: deps.topicTargetStore.list(),
564
+ instanceId: deps.instanceId,
565
+ preferredTarget: input.oldTarget ?? deps.registrationState.getTarget(),
566
+ });
567
+ if (input.oldTarget) {
568
+ deps.topicTargetStore.markStaleByTarget(
569
+ input.oldTarget,
570
+ "deleted",
571
+ "Follower thread was replaced by thread restore.",
572
+ );
573
+ } else if (currentRecord) {
574
+ deps.topicTargetStore.markStaleByTarget(
575
+ currentRecord.target,
576
+ "deleted",
577
+ "Follower thread was replaced by thread restore.",
578
+ );
579
+ }
580
+ const profileKey =
581
+ currentRecord?.profileKey ?? deps.getManualFollowerProfileKey();
582
+ deps.topicTargetStore.upsert({
583
+ profileKey,
584
+ owner: {
585
+ kind: "manual-follower",
586
+ instanceId: deps.manualFollowerOwnerId,
587
+ },
588
+ target: input.target,
589
+ status: "active",
590
+ syncStatus: "open",
591
+ createdAtMs: currentRecord?.createdAtMs ?? nowMs,
592
+ updatedAtMs: nowMs,
593
+ lastSyncObservedAtMs: nowMs,
594
+ lastReconcileAction: "follower-thread-restore",
595
+ instanceId: deps.instanceId,
596
+ slot: currentRecord?.slot,
597
+ threadName: currentRecord?.threadName,
598
+ rerouteConfirmedAtMs: nowMs,
599
+ });
600
+ deps.registrationState.setRegistered(true, input.target, {
601
+ slot: currentRecord?.slot,
602
+ threadName: currentRecord?.threadName,
603
+ generation: deps.registrationState.getGeneration?.(),
604
+ });
605
+ deps.setSyncState(
606
+ Sync.markTelegramSyncSliceFresh(deps.getSyncState(), "target-bindings", {
607
+ nowMs,
608
+ action: "follower-thread-restore",
609
+ }),
610
+ );
611
+ await deps.topicTargetStore.persist();
612
+ deps.updateStatus(ctx);
613
+ deps.recordRuntimeEvent?.(
614
+ "bus",
615
+ "Telegram follower thread target replaced",
616
+ {
617
+ phase: "follower-thread-restore",
618
+ chatId: input.target.chatId,
619
+ threadId: input.target.threadId,
620
+ oldThreadId:
621
+ input.oldTarget?.threadId ?? currentRecord?.target.threadId,
622
+ slot: currentRecord?.slot,
623
+ },
624
+ );
625
+ };
626
+ }
627
+
628
+ export function createTelegramBusFollowerClientRuntime<
629
+ TContext,
630
+ TReactionUpdate,
631
+ TCallbackQuery,
632
+ TMessage = unknown,
633
+ >(deps: TelegramBusFollowerClientRuntimeDeps<TMessage>) {
634
+ const createRequestId = createTelegramBusRequestIdFactory(deps.instanceId);
635
+ const timeoutMs =
636
+ deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS;
637
+ const sharedClientDeps = {
638
+ socketPath: deps.socketPath,
639
+ createRequestId,
640
+ timeoutMs,
641
+ };
642
+ return {
643
+ createRequestId,
644
+ callApi: createTelegramBusFollowerApiCaller({
645
+ ...sharedClientDeps,
646
+ instanceId: deps.instanceId,
647
+ getAuthSecret: deps.getApiAuthSecret,
648
+ getRegistrationGeneration: deps.getRegistrationGeneration,
649
+ }),
650
+ agentMessages: createTelegramBusAgentMessageClient({
651
+ ...sharedClientDeps,
652
+ instanceId: deps.instanceId,
653
+ getAuthSecret: deps.getApiAuthSecret,
654
+ getRegistrationGeneration: deps.getRegistrationGeneration,
655
+ }),
656
+ foreignOwnedUpdateForwarder: createTelegramBusForeignOwnedUpdateForwarder<
657
+ TContext,
658
+ TReactionUpdate,
659
+ TCallbackQuery,
660
+ TMessage
661
+ >({
662
+ ...sharedClientDeps,
663
+ getAuthSecret: deps.getForwardingAuthSecret,
664
+ getForwardCommentBatchPosition:
665
+ deps.getForwardCommentBatchPosition,
666
+ recordRuntimeEvent: deps.recordRuntimeEvent,
667
+ }),
668
+ queueHandoff: createTelegramBusFollowerQueueHandoffClient({
669
+ ...sharedClientDeps,
670
+ instanceId: deps.instanceId,
671
+ getAuthSecret: deps.getApiAuthSecret,
672
+ getRegistrationGeneration: deps.getRegistrationGeneration,
673
+ }),
674
+ targetController: createTelegramBusFollowerTargetController({
675
+ ...sharedClientDeps,
676
+ getAuthSecret: deps.getForwardingAuthSecret,
677
+ }),
678
+ };
679
+ }
680
+
681
+ export function createTelegramBusFollowerQueueHandoffClient(
682
+ deps: TelegramBusFollowerApiCallerDeps,
683
+ ): (input: {
684
+ recipientInstanceId: string;
685
+ recipientRegistrationGeneration: string;
686
+ donorProcessId: number;
687
+ donorProcessBirthId: string;
688
+ donorSessionGeneration: number;
689
+ donorAcquisitionId: string;
690
+ donorAcquiredAtMs: number;
691
+ handoffToken: string;
692
+ payload: TelegramQueueHandoffPayload;
693
+ }) => Promise<TelegramQueueHandoffStageResult> {
694
+ const getNowMs = deps.getNowMs ?? Date.now;
695
+ const timeoutMs =
696
+ deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS;
697
+ return async (input) => {
698
+ const registrationGeneration = deps.getRegistrationGeneration();
699
+ if (!registrationGeneration) {
700
+ throw new Error("Telegram bus follower is not registered.");
701
+ }
702
+ const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
703
+ const response = await sendTelegramBusLocalEnvelope({
704
+ socketPath,
705
+ timeoutMs,
706
+ retry: getTelegramBusTransportRetryPolicy({
707
+ endpoint: socketPath,
708
+ operation: "operation",
709
+ }),
710
+ envelope: {
711
+ kind: "follower.offerQueueHandoff",
712
+ requestId: deps.createRequestId(),
713
+ auth: deps.getAuthSecret?.(),
714
+ instanceId: deps.instanceId,
715
+ registrationGeneration,
716
+ ...input,
717
+ sentAtMs: getNowMs(),
718
+ },
719
+ });
720
+ const queueOwner =
721
+ response?.kind === "bus.ack" && isRecord(response.result)
722
+ ? parseTelegramUpdateJournalQueueOwner(response.result.queueOwner)
723
+ : undefined;
724
+ if (
725
+ response?.kind === "bus.ack" &&
726
+ response.ok &&
727
+ isRecord(response.result) &&
728
+ response.result.status === "staged" &&
729
+ typeof response.result.receiptId === "string" &&
730
+ Array.isArray(response.result.sourceUpdateIds) &&
731
+ response.result.sourceUpdateIds.every(Number.isSafeInteger) &&
732
+ input.payload.admissionReceipts.length === 1 &&
733
+ response.result.receiptId ===
734
+ input.payload.admissionReceipts[0]?.receiptId &&
735
+ response.result.sourceUpdateIds.length ===
736
+ input.payload.admissionReceipts[0].sourceUpdateIds.length &&
737
+ response.result.sourceUpdateIds.every(
738
+ (updateId, index) =>
739
+ updateId === input.payload.admissionReceipts[0]!.sourceUpdateIds[index],
740
+ ) &&
741
+ queueOwner
742
+ ) {
743
+ return {
744
+ status: "staged",
745
+ receiptId: response.result.receiptId,
746
+ sourceUpdateIds: response.result.sourceUpdateIds as number[],
747
+ queueOwner,
748
+ };
749
+ }
750
+ throw new Error(
751
+ response?.kind === "bus.ack"
752
+ ? response.message ?? "Telegram queue handoff was rejected."
753
+ : "Telegram queue handoff did not return an acknowledgement.",
754
+ );
755
+ };
756
+ }
757
+
758
+ export function createTelegramBusAgentMessageClient(
759
+ deps: TelegramBusFollowerApiCallerDeps,
760
+ ): {
761
+ resolveTarget: (
762
+ selector: TelegramBusAgentTargetSelector,
763
+ ) => Promise<TelegramTarget & { threadId: number }>;
764
+ routeMessage: (message: TelegramBusAgentMessage) => Promise<void>;
765
+ } {
766
+ const getNowMs = deps.getNowMs ?? Date.now;
767
+ const timeoutMs =
768
+ deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS;
769
+ const request = async (
770
+ envelope:
771
+ | Extract<TelegramBusEnvelope, { kind: "follower.resolveAgentTarget" }>
772
+ | Extract<TelegramBusEnvelope, { kind: "follower.routeAgentMessage" }>,
773
+ ): Promise<unknown> => {
774
+ const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
775
+ const response = await sendTelegramBusLocalEnvelope({
776
+ socketPath,
777
+ timeoutMs,
778
+ retry: getTelegramBusTransportRetryPolicy({
779
+ endpoint: socketPath,
780
+ operation: "operation",
781
+ }),
782
+ envelope,
783
+ });
784
+ if (response?.kind === "bus.ack" && response.ok) return response.result;
785
+ throw new Error(
786
+ response?.kind === "bus.ack"
787
+ ? response.message ?? "Telegram bus agent message failed."
788
+ : "Telegram bus agent message did not return an acknowledgement.",
789
+ );
790
+ };
791
+ const registrationFields = () => {
792
+ const registrationGeneration = deps.getRegistrationGeneration();
793
+ if (!registrationGeneration) {
794
+ throw new Error("Telegram bus follower is not registered.");
795
+ }
796
+ return {
797
+ auth: deps.getAuthSecret?.(),
798
+ instanceId: deps.instanceId,
799
+ registrationGeneration,
800
+ };
801
+ };
802
+ return {
803
+ async resolveTarget(selector) {
804
+ const result = await request({
805
+ kind: "follower.resolveAgentTarget",
806
+ requestId: deps.createRequestId(),
807
+ ...registrationFields(),
808
+ selector,
809
+ sentAtMs: getNowMs(),
810
+ });
811
+ if (!result || typeof result !== "object" || Array.isArray(result)) {
812
+ throw new Error("Telegram bus returned an invalid agent target.");
813
+ }
814
+ const target = result as Record<string, unknown>;
815
+ if (
816
+ typeof target.chatId !== "number" ||
817
+ typeof target.threadId !== "number"
818
+ ) {
819
+ throw new Error("Telegram bus returned an invalid agent target.");
820
+ }
821
+ return { chatId: target.chatId, threadId: target.threadId };
822
+ },
823
+ async routeMessage(message) {
824
+ await request({
825
+ kind: "follower.routeAgentMessage",
826
+ requestId: deps.createRequestId(),
827
+ ...registrationFields(),
828
+ message,
829
+ sentAtMs: getNowMs(),
830
+ });
831
+ },
832
+ };
833
+ }
834
+
835
+ export function createTelegramBusFollowerApiCaller(
836
+ deps: TelegramBusFollowerApiCallerDeps,
837
+ ): (method: string, args: unknown[]) => Promise<unknown> {
838
+ const getNowMs = deps.getNowMs ?? Date.now;
839
+ const timeoutMs =
840
+ deps.timeoutMs ?? TELEGRAM_BUS_FOLLOWER_CLIENT_TIMEOUT_MS;
841
+ return async (method, args) => {
842
+ const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
843
+ const registrationGeneration = deps.getRegistrationGeneration();
844
+ if (!registrationGeneration) {
845
+ throw new Error("Telegram bus follower is not registered.");
846
+ }
847
+ let response: TelegramBusEnvelope | undefined;
848
+ try {
849
+ response = await sendTelegramBusLocalEnvelope({
850
+ socketPath,
851
+ timeoutMs,
852
+ retry: getTelegramBusTransportRetryPolicy({
853
+ endpoint: socketPath,
854
+ operation: "operation",
855
+ }),
856
+ envelope: {
857
+ kind: "follower.callApi",
858
+ requestId: deps.createRequestId(),
859
+ auth: deps.getAuthSecret?.(),
860
+ instanceId: deps.instanceId,
861
+ registrationGeneration,
862
+ method,
863
+ args,
864
+ sentAtMs: getNowMs(),
865
+ },
866
+ });
867
+ } catch (error) {
868
+ const apiMethod =
869
+ (method === "call" || method === "callMultipart") &&
870
+ typeof args[0] === "string"
871
+ ? args[0]
872
+ : method;
873
+ if (!isTelegramApiMethodRetrySafe(apiMethod)) {
874
+ throw new TelegramApiCommitUnknownError(apiMethod, error);
875
+ }
876
+ throw error;
877
+ }
878
+ if (response?.kind === "bus.ack" && response.ok) return response.result;
879
+ const message =
880
+ response?.kind === "bus.ack"
881
+ ? response.message
882
+ : "Telegram bus API call did not return an acknowledgement.";
883
+ if (
884
+ response?.kind === "bus.ack" &&
885
+ response.error?.code === "commit-unknown"
886
+ ) {
887
+ throw new TelegramApiCommitUnknownError(
888
+ response.error.method ?? method,
889
+ new Error(message ?? "Telegram bus API call result is ambiguous."),
890
+ );
891
+ }
892
+ throw new Error(message ?? "Telegram bus API call failed.");
893
+ };
894
+ }
895
+
896
+ function isTelegramStaleContextError(error: unknown): boolean {
897
+ return (
898
+ error instanceof Error &&
899
+ (error.message.includes("stale after session") ||
900
+ error.message.includes("stale ctx"))
901
+ );
902
+ }
903
+
904
+ export function createTelegramBusFollowerSessionReplacementSuspender(
905
+ deps: TelegramBusFollowerSessionReplacementSuspenderDeps,
906
+ ): () => Promise<void> {
907
+ const getNowMs = deps.getNowMs ?? Date.now;
908
+ const getPid = deps.getPid ?? (() => process.pid);
909
+ return async () => {
910
+ const target = deps.registrationState.getTarget();
911
+ if (deps.registrationState.isRegistered() && target) {
912
+ setTelegramFollowerSessionHandoff({
913
+ pid: getPid(),
914
+ instanceId: deps.instanceId,
915
+ createdAtMs: getNowMs(),
916
+ target,
917
+ slot: deps.registrationState.getSlot(),
918
+ threadName: deps.registrationState.getThreadName(),
919
+ });
920
+ deps.recordRuntimeEvent(
921
+ "bus",
922
+ "Telegram follower registration suspended for session replacement",
923
+ {
924
+ phase: "follower-session-handoff",
925
+ instanceId: deps.instanceId,
926
+ chatId: target.chatId,
927
+ threadId: target.threadId,
928
+ },
929
+ );
930
+ } else if (deps.isLeader?.()) {
931
+ const leaderBinding = deps.getLeaderBinding?.();
932
+ if (typeof leaderBinding?.target?.threadId === "number") {
933
+ const activeContext = deps.getActiveContext?.();
934
+ const profileKey = Threads.getTelegramThreadOwnerKey({
935
+ kind: "leader",
936
+ cwd: activeContext?.cwd,
937
+ instanceId: deps.instanceId,
938
+ telegramProfile: deps.getActiveProfileName?.(),
939
+ });
940
+ Threads.setTelegramLeaderSessionHandoff({
941
+ pid: getPid(),
942
+ instanceId: deps.instanceId,
943
+ createdAtMs: getNowMs(),
944
+ profileKey,
945
+ target: {
946
+ chatId: leaderBinding.target.chatId,
947
+ threadId: leaderBinding.target.threadId,
948
+ },
949
+ slot: leaderBinding.slot,
950
+ threadName: leaderBinding.threadName,
951
+ });
952
+ deps.recordRuntimeEvent(
953
+ "bus",
954
+ "Telegram leader binding suspended for session replacement",
955
+ {
956
+ phase: "leader-session-handoff",
957
+ instanceId: deps.instanceId,
958
+ chatId: leaderBinding.target.chatId,
959
+ threadId: leaderBinding.target.threadId,
960
+ slot: leaderBinding.slot,
961
+ threadName: leaderBinding.threadName,
962
+ },
963
+ );
964
+ }
965
+ }
966
+ await deps.suspendPolling();
967
+ };
968
+ }
969
+
970
+ export function createTelegramBusFollowerSessionRefreshHook<TContext>(
971
+ deps: TelegramBusFollowerSessionRefreshHookDeps<TContext>,
972
+ ): (_event: unknown, ctx: TContext) => Promise<void> {
973
+ return async (_event, ctx) => {
974
+ if (deps.isSessionActive && !deps.isSessionActive(ctx)) return;
975
+ if (!deps.registrationState.isRegistered()) {
976
+ const handoff = getTelegramFollowerSessionHandoff();
977
+ const lockState = deps.getLeaderState();
978
+ const handoffIsFresh = isTelegramFollowerSessionHandoffFresh(handoff);
979
+ if (handoffIsFresh && lockState.kind === "active-elsewhere") {
980
+ try {
981
+ const restored = await deps.registrationRuntime.registerWithLeader(
982
+ ctx,
983
+ lockState.lock,
984
+ {
985
+ target: handoff.target,
986
+ previousInstanceId: handoff.instanceId,
987
+ },
988
+ );
989
+ if (deps.isSessionActive && !deps.isSessionActive(ctx)) return;
990
+ if (restored) {
991
+ setTelegramFollowerSessionHandoff(undefined);
992
+ deps.updateStatus(ctx);
993
+ deps.recordRuntimeEvent(
994
+ "bus",
995
+ "Telegram follower registration restored after session replacement",
996
+ {
997
+ phase: "follower-session-restore",
998
+ previousInstanceId: handoff.instanceId,
999
+ },
1000
+ );
1001
+ }
1002
+ } catch (error) {
1003
+ deps.recordRuntimeEvent("bus", error, {
1004
+ phase: "follower-session-restore",
1005
+ previousInstanceId: handoff?.instanceId,
1006
+ });
1007
+ }
1008
+ } else if (handoff) {
1009
+ setTelegramFollowerSessionHandoff(undefined);
1010
+ }
1011
+ }
1012
+ if (!deps.registrationState.isRegistered()) return;
1013
+ if (deps.isSessionActive && !deps.isSessionActive(ctx)) return;
1014
+ deps.registrationRuntime.setContext(ctx);
1015
+ deps.updateStatus(ctx);
1016
+ deps.recordRuntimeEvent(
1017
+ "bus",
1018
+ "Telegram follower session context refreshed",
1019
+ { phase: "follower-session-refresh" },
1020
+ );
1021
+ };
1022
+ }
1023
+
1024
+ export function createTelegramBusFollowerControlState(): TelegramBusFollowerControlState {
1025
+ let activeAuthSecret: string | undefined;
1026
+ let lifecyclePhase: TelegramBusFollowerControlLifecyclePhase | undefined;
1027
+ return {
1028
+ getActiveAuthSecret: () => activeAuthSecret,
1029
+ setActiveAuthSecret(secret) {
1030
+ activeAuthSecret = secret;
1031
+ },
1032
+ getLifecyclePhase: () => lifecyclePhase,
1033
+ setLifecyclePhase(phase) {
1034
+ lifecyclePhase = phase;
1035
+ },
1036
+ };
1037
+ }
1038
+
1039
+ export function createTelegramBusFollowerRegistrationState(
1040
+ options: { onAvailabilityChanged?: () => void } = {},
1041
+ ): TelegramBusFollowerRegistrationState {
1042
+ let registered = false;
1043
+ let target: TelegramTarget | undefined;
1044
+ let slot: string | undefined;
1045
+ let threadName: string | undefined;
1046
+ let generation: string | undefined;
1047
+ let leaderProtocol: TelegramBusProtocolIdentity | undefined;
1048
+ let eligibleElectionSlots: string[] = [];
1049
+ return {
1050
+ isRegistered: () => registered,
1051
+ getTarget: () => (target ? { ...target } : undefined),
1052
+ getSlot: () => slot,
1053
+ getThreadName: () => threadName,
1054
+ getGeneration: () => generation,
1055
+ getLeaderProtocol: () =>
1056
+ leaderProtocol
1057
+ ? { ...leaderProtocol, capabilities: [...leaderProtocol.capabilities] }
1058
+ : undefined,
1059
+ getEligibleElectionSlots: () => [...eligibleElectionSlots],
1060
+ setEligibleElectionSlots: (slots) => {
1061
+ eligibleElectionSlots = Array.from(
1062
+ new Set(slots.filter((slot) => /^[A-Z]$/.test(slot))),
1063
+ ).sort();
1064
+ },
1065
+ setRegistered: (next, nextTarget, metadata) => {
1066
+ const availabilityChanged = registered !== next;
1067
+ registered = next;
1068
+ target = next ? (nextTarget ? { ...nextTarget } : undefined) : undefined;
1069
+ slot = next ? metadata?.slot : undefined;
1070
+ threadName = next ? metadata?.threadName : undefined;
1071
+ generation = next ? metadata?.generation : undefined;
1072
+ leaderProtocol =
1073
+ next && metadata?.leaderProtocol
1074
+ ? {
1075
+ ...metadata.leaderProtocol,
1076
+ capabilities: [...metadata.leaderProtocol.capabilities],
1077
+ }
1078
+ : undefined;
1079
+ if (availabilityChanged) options.onAvailabilityChanged?.();
1080
+ },
1081
+ };
1082
+ }
1083
+
1084
+ export function createTelegramBusFollowerHeartbeatRecoveryHandler<TContext>(
1085
+ deps: TelegramBusFollowerHeartbeatRecoveryHandlerDeps<TContext>,
1086
+ ): (error: unknown, ctx: TContext) => Promise<void> {
1087
+ const promotionGraceMs =
1088
+ deps.promotionGraceMs ?? TELEGRAM_BUS_FOLLOWER_PROMOTION_GRACE_MS;
1089
+ const sleep =
1090
+ deps.sleep ??
1091
+ ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
1092
+ const scheduleRetry =
1093
+ deps.scheduleRetry ??
1094
+ ((retry: () => void, delayMs: number) => {
1095
+ const timer = setTimeout(retry, delayMs);
1096
+ timer.unref?.();
1097
+ });
1098
+ let promotionPending = false;
1099
+ const safeUpdateStatus = (ctx: TContext) => {
1100
+ try {
1101
+ deps.updateStatus(ctx);
1102
+ } catch (error) {
1103
+ if (!isTelegramStaleContextError(error)) throw error;
1104
+ deps.recordRuntimeEvent("bus", error, {
1105
+ phase: "follower-stale-context-status",
1106
+ });
1107
+ }
1108
+ };
1109
+ const clearRegisteredState = (ctx: TContext) => {
1110
+ deps.registrationState.setRegistered(false);
1111
+ safeUpdateStatus(ctx);
1112
+ };
1113
+ const tryRegisterWithLeader = async (
1114
+ ctx: TContext,
1115
+ leader: TelegramBusFollowerLeaderLock,
1116
+ phase: string,
1117
+ binding?: TelegramBusFollowerPromotedBinding,
1118
+ ) => {
1119
+ try {
1120
+ const restored = await deps
1121
+ .getRegistrationRuntime()
1122
+ .registerWithLeader(
1123
+ ctx,
1124
+ leader,
1125
+ binding?.target ? { target: binding.target } : undefined,
1126
+ );
1127
+ if (!restored) return false;
1128
+ deps.setLifecyclePhase(undefined);
1129
+ safeUpdateStatus(ctx);
1130
+ deps.recordRuntimeEvent(
1131
+ "bus",
1132
+ "Telegram follower registration restored",
1133
+ {
1134
+ phase,
1135
+ },
1136
+ );
1137
+ return true;
1138
+ } catch (error) {
1139
+ clearRegisteredState(ctx);
1140
+ deps.recordRuntimeEvent("bus", error, { phase });
1141
+ return false;
1142
+ }
1143
+ };
1144
+ const snapshotBinding = (): TelegramBusFollowerPromotedBinding => ({
1145
+ target: deps.registrationState.getTarget(),
1146
+ slot: deps.registrationState.getSlot(),
1147
+ threadName: deps.registrationState.getThreadName(),
1148
+ });
1149
+ const scheduleRecovery = (
1150
+ reason: unknown,
1151
+ fallbackCtx: TContext,
1152
+ binding: TelegramBusFollowerPromotedBinding,
1153
+ ) => {
1154
+ const retry = () => {
1155
+ const activeCtx = deps.getActiveContext
1156
+ ? deps.getActiveContext()
1157
+ : fallbackCtx;
1158
+ if (!activeCtx) {
1159
+ scheduleRetry(retry, promotionGraceMs);
1160
+ return;
1161
+ }
1162
+ void recover(reason, activeCtx, binding);
1163
+ };
1164
+ scheduleRetry(retry, promotionGraceMs);
1165
+ };
1166
+ const promoteToLeader = async (
1167
+ reason: unknown,
1168
+ ctx: TContext,
1169
+ binding: TelegramBusFollowerPromotedBinding,
1170
+ election: TelegramBusFollowerElection,
1171
+ ) => {
1172
+ const activeCtx = deps.getActiveContext?.();
1173
+ if (deps.getActiveContext && activeCtx !== ctx) {
1174
+ scheduleRecovery(reason, ctx, binding);
1175
+ return;
1176
+ }
1177
+ deps.setLifecyclePhase("electing");
1178
+ safeUpdateStatus(ctx);
1179
+ deps.recordRuntimeEvent("bus", reason, {
1180
+ phase: "follower-promotion-electing",
1181
+ });
1182
+ deps.getRegistrationRuntime().stop();
1183
+ deps.setLifecyclePhase("electing");
1184
+ safeUpdateStatus(ctx);
1185
+ deps.recordRuntimeEvent("bus", "Telegram follower attempting promotion", {
1186
+ phase: "follower-promotion-electing",
1187
+ });
1188
+ const promoted = await deps.promoteToLeader(ctx, binding, election);
1189
+ deps.setLifecyclePhase(undefined);
1190
+ safeUpdateStatus(ctx);
1191
+ deps.recordRuntimeEvent(
1192
+ "bus",
1193
+ promoted
1194
+ ? "Telegram follower promotion completed"
1195
+ : "Telegram follower promotion lost election",
1196
+ {
1197
+ phase: promoted
1198
+ ? "follower-promotion-complete"
1199
+ : "follower-promotion-lost",
1200
+ },
1201
+ );
1202
+ if (!promoted) scheduleRecovery(reason, ctx, binding);
1203
+ };
1204
+ const attemptPreferredPromotion = async (
1205
+ reason: unknown,
1206
+ ctx: TContext,
1207
+ binding: TelegramBusFollowerPromotedBinding,
1208
+ candidateState: TelegramBusFollowerLeaderState,
1209
+ ): Promise<void> => {
1210
+ const slot = binding.slot;
1211
+ const lowerEligibleSlot = slot
1212
+ ? deps.registrationState
1213
+ .getEligibleElectionSlots()
1214
+ .find((candidate) => candidate < slot)
1215
+ : undefined;
1216
+ if (lowerEligibleSlot) {
1217
+ deps.recordRuntimeEvent(
1218
+ "bus",
1219
+ "Telegram follower deferring to a lower-slot election candidate",
1220
+ {
1221
+ phase: "follower-promotion-slot-priority",
1222
+ slot,
1223
+ lowerEligibleSlot,
1224
+ },
1225
+ );
1226
+ await sleep(promotionGraceMs);
1227
+ candidateState = deps.getLeaderState();
1228
+ if (candidateState.kind === "active-elsewhere") {
1229
+ if (
1230
+ !(await tryRegisterWithLeader(
1231
+ ctx,
1232
+ candidateState.lock,
1233
+ "follower-register-preferred-successor",
1234
+ binding,
1235
+ ))
1236
+ ) {
1237
+ scheduleRecovery(reason, ctx, binding);
1238
+ }
1239
+ return;
1240
+ }
1241
+ }
1242
+ if (candidateState.kind !== "stale" && candidateState.kind !== "inactive")
1243
+ return;
1244
+ await promoteToLeader(reason, ctx, binding, {
1245
+ expectedOwner:
1246
+ candidateState.kind === "stale" ? candidateState.lock : undefined,
1247
+ });
1248
+ };
1249
+ const recover = async (
1250
+ error: unknown,
1251
+ ctx: TContext,
1252
+ carriedBinding?: TelegramBusFollowerPromotedBinding,
1253
+ ): Promise<void> => {
1254
+ if (promotionPending) return;
1255
+ promotionPending = true;
1256
+ try {
1257
+ const initialBinding = carriedBinding ?? snapshotBinding();
1258
+ const state = deps.getLeaderState();
1259
+ if (state.kind === "active-elsewhere") {
1260
+ clearRegisteredState(ctx);
1261
+ if (
1262
+ await tryRegisterWithLeader(
1263
+ ctx,
1264
+ state.lock,
1265
+ "follower-register-restore",
1266
+ initialBinding,
1267
+ )
1268
+ ) {
1269
+ return;
1270
+ }
1271
+ deps.setLifecyclePhase("electing");
1272
+ safeUpdateStatus(ctx);
1273
+ deps.recordRuntimeEvent(
1274
+ "bus",
1275
+ "Telegram follower waiting for leader reload recovery",
1276
+ { phase: "follower-promotion-grace" },
1277
+ );
1278
+ await sleep(promotionGraceMs);
1279
+ const graceState = deps.getLeaderState();
1280
+ if (graceState.kind === "active-elsewhere") {
1281
+ if (
1282
+ await tryRegisterWithLeader(
1283
+ ctx,
1284
+ graceState.lock,
1285
+ "follower-register-restore-grace",
1286
+ initialBinding,
1287
+ )
1288
+ ) {
1289
+ return;
1290
+ }
1291
+ deps.setLifecyclePhase(undefined);
1292
+ safeUpdateStatus(ctx);
1293
+ deps.recordRuntimeEvent(
1294
+ "bus",
1295
+ "Telegram follower promotion blocked by live leader lease",
1296
+ {
1297
+ phase: "follower-promotion-live-owner",
1298
+ leaderInstanceId: graceState.lock.instanceId,
1299
+ leaderEpoch: graceState.lock.leaderEpoch,
1300
+ },
1301
+ );
1302
+ scheduleRecovery(error, ctx, initialBinding);
1303
+ return;
1304
+ }
1305
+ await attemptPreferredPromotion(
1306
+ error,
1307
+ ctx,
1308
+ initialBinding,
1309
+ graceState,
1310
+ );
1311
+ return;
1312
+ }
1313
+ await attemptPreferredPromotion(error, ctx, initialBinding, state);
1314
+ } catch (promotionError) {
1315
+ deps.setLifecyclePhase(undefined);
1316
+ safeUpdateStatus(ctx);
1317
+ if (isTelegramStaleContextError(promotionError)) {
1318
+ deps.recordRuntimeEvent("bus", promotionError, {
1319
+ phase: "follower-heartbeat-stale-context",
1320
+ });
1321
+ return;
1322
+ }
1323
+ throw promotionError;
1324
+ } finally {
1325
+ promotionPending = false;
1326
+ }
1327
+ };
1328
+ return recover;
1329
+ }
1330
+
1331
+ export function createTelegramBusFollowerRegistrationRuntime<
1332
+ TContext extends { cwd?: string },
1333
+ >(
1334
+ deps: TelegramBusFollowerRegistrationRuntimeDeps<TContext>,
1335
+ ): TelegramBusFollowerRegistrationRuntime<TContext> {
1336
+ const getNowMs = deps.getNowMs ?? Date.now;
1337
+ const getPid = deps.getPid ?? (() => process.pid);
1338
+ const heartbeatMs = deps.heartbeatMs ?? 1000;
1339
+ const registrationTimeoutMs =
1340
+ deps.registrationTimeoutMs ?? deps.timeoutMs ?? 30000;
1341
+ const registrationRetryAttempts =
1342
+ deps.registrationRetryAttempts ??
1343
+ TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_ATTEMPTS;
1344
+ const registrationRetryDelayMs =
1345
+ deps.registrationRetryDelayMs ??
1346
+ TELEGRAM_BUS_FOLLOWER_REGISTRATION_RETRY_DELAY_MS;
1347
+ let heartbeatInterval: ReturnType<typeof setInterval> | undefined;
1348
+ let heartbeatPromise: Promise<void> | undefined;
1349
+ let heartbeatPromiseGeneration: string | undefined;
1350
+ let activeLeaderSocketPath: string | undefined;
1351
+ let activeAuthSecret: string | undefined;
1352
+ let activeRegistrationGeneration: string | undefined;
1353
+ let activeContext: TContext | undefined;
1354
+ let lastKnownTarget: TelegramTarget | undefined;
1355
+ let lastKnownSlot: string | undefined;
1356
+ let lastKnownThreadName: string | undefined;
1357
+ const stopHeartbeat = () => {
1358
+ if (!heartbeatInterval) return;
1359
+ clearInterval(heartbeatInterval);
1360
+ heartbeatInterval = undefined;
1361
+ };
1362
+ const stop = () => {
1363
+ stopHeartbeat();
1364
+ activeAuthSecret = undefined;
1365
+ activeRegistrationGeneration = undefined;
1366
+ heartbeatPromise = undefined;
1367
+ heartbeatPromiseGeneration = undefined;
1368
+ deps.setActiveAuthSecret?.(undefined);
1369
+ deps.registrationState?.setRegistered(false);
1370
+ lastKnownTarget = undefined;
1371
+ lastKnownSlot = undefined;
1372
+ lastKnownThreadName = undefined;
1373
+ activeContext = undefined;
1374
+ void Promise.resolve(deps.stopReceiving?.()).catch((error) => {
1375
+ try {
1376
+ deps.recordRuntimeEvent?.("bus", error, {
1377
+ phase: "follower-receiver-stop",
1378
+ });
1379
+ } catch {
1380
+ // Stop diagnostics cannot create an unhandled Promise.
1381
+ }
1382
+ });
1383
+ };
1384
+ const sendHeartbeat = async () => {
1385
+ const leaderSocketPath = activeLeaderSocketPath;
1386
+ const registrationGeneration = activeRegistrationGeneration;
1387
+ const heartbeatContext = activeContext;
1388
+ if (!leaderSocketPath || !registrationGeneration) return;
1389
+ const isCurrentHeartbeat = (): boolean =>
1390
+ activeLeaderSocketPath === leaderSocketPath &&
1391
+ activeRegistrationGeneration === registrationGeneration &&
1392
+ activeContext === heartbeatContext;
1393
+ try {
1394
+ const response = await sendTelegramBusLocalEnvelope({
1395
+ socketPath: leaderSocketPath,
1396
+ timeoutMs: deps.timeoutMs,
1397
+ retry: getTelegramBusTransportRetryPolicy({
1398
+ endpoint: leaderSocketPath,
1399
+ operation: "operation",
1400
+ }),
1401
+ envelope: {
1402
+ kind: "follower.heartbeat",
1403
+ requestId: deps.createRequestId(),
1404
+ auth: activeAuthSecret,
1405
+ instanceId: deps.instanceId,
1406
+ registrationGeneration,
1407
+ sentAtMs: getNowMs(),
1408
+ },
1409
+ });
1410
+ if (!isCurrentHeartbeat()) return;
1411
+ if (response?.kind === "bus.ack" && response.ok) {
1412
+ const heartbeatResult = isRecord(response.result)
1413
+ ? response.result
1414
+ : undefined;
1415
+ const slots = Array.isArray(heartbeatResult?.eligibleElectionSlots)
1416
+ ? heartbeatResult.eligibleElectionSlots.filter(
1417
+ (slot): slot is string => typeof slot === "string",
1418
+ )
1419
+ : [];
1420
+ deps.registrationState?.setEligibleElectionSlots(slots);
1421
+ }
1422
+ if (response?.kind === "bus.ack" && !response.ok) {
1423
+ throw new Error(
1424
+ response.message ?? "Telegram bus follower heartbeat was rejected.",
1425
+ );
1426
+ }
1427
+ } catch (error) {
1428
+ if (!isCurrentHeartbeat()) return;
1429
+ try {
1430
+ deps.recordRuntimeEvent?.("bus", error, {
1431
+ phase: "follower-heartbeat",
1432
+ });
1433
+ } catch {
1434
+ // Diagnostics cannot replace heartbeat recovery.
1435
+ }
1436
+ if (!heartbeatContext) return;
1437
+ try {
1438
+ await deps.onHeartbeatFailure?.(error, heartbeatContext);
1439
+ } catch (recoveryError) {
1440
+ try {
1441
+ deps.recordRuntimeEvent?.("bus", recoveryError, {
1442
+ phase: "follower-heartbeat-recovery",
1443
+ });
1444
+ } catch {
1445
+ // A diagnostic sink cannot create an unhandled interval rejection.
1446
+ }
1447
+ }
1448
+ }
1449
+ };
1450
+ const requestHeartbeat = (): Promise<void> => {
1451
+ const generation = activeRegistrationGeneration;
1452
+ if (heartbeatPromise && heartbeatPromiseGeneration === generation) {
1453
+ return heartbeatPromise;
1454
+ }
1455
+ let tracked: Promise<void>;
1456
+ tracked = sendHeartbeat().finally(() => {
1457
+ if (heartbeatPromise === tracked) {
1458
+ heartbeatPromise = undefined;
1459
+ heartbeatPromiseGeneration = undefined;
1460
+ }
1461
+ });
1462
+ heartbeatPromise = tracked;
1463
+ heartbeatPromiseGeneration = generation;
1464
+ return tracked;
1465
+ };
1466
+ const startHeartbeat = (socketPath: string) => {
1467
+ stopHeartbeat();
1468
+ activeLeaderSocketPath = socketPath;
1469
+ heartbeatInterval = setInterval(() => {
1470
+ void requestHeartbeat();
1471
+ }, heartbeatMs);
1472
+ heartbeatInterval.unref?.();
1473
+ };
1474
+ return {
1475
+ registerWithLeader: async (ctx, leader, options) => {
1476
+ const pendingHandoff = options
1477
+ ? undefined
1478
+ : getTelegramFollowerSessionHandoff();
1479
+ const pendingHandoffOptions = isTelegramFollowerSessionHandoffFresh(
1480
+ pendingHandoff,
1481
+ )
1482
+ ? {
1483
+ target: pendingHandoff.target,
1484
+ previousInstanceId: pendingHandoff.instanceId,
1485
+ }
1486
+ : undefined;
1487
+ const registrationOptions = options ?? pendingHandoffOptions;
1488
+ const leaderSocketPath =
1489
+ leader.busSocketPath ??
1490
+ deps.getLeaderSocketPath?.() ??
1491
+ getTelegramBusSocketPath();
1492
+ await deps.startReceiving?.();
1493
+ activeAuthSecret = deps.getLeaderAuthSecret
1494
+ ? deps.getLeaderAuthSecret(leader)
1495
+ : leader?.busSecret;
1496
+ deps.setActiveAuthSecret?.(activeAuthSecret);
1497
+ const registrationGeneration = deps.createRequestId();
1498
+ const registrationEnvelope: Extract<
1499
+ TelegramBusEnvelope,
1500
+ { kind: "follower.register" }
1501
+ > = {
1502
+ kind: "follower.register",
1503
+ requestId: registrationGeneration,
1504
+ auth: activeAuthSecret,
1505
+ registration: {
1506
+ instanceId: deps.instanceId,
1507
+ ...(registrationOptions?.previousInstanceId
1508
+ ? { previousInstanceId: registrationOptions.previousInstanceId }
1509
+ : {}),
1510
+ profileKey:
1511
+ deps.getProfileKey?.(ctx) ??
1512
+ (ctx.cwd ? `cwd:${ctx.cwd}` : undefined),
1513
+ threadName:
1514
+ deps.registrationState?.getThreadName() ??
1515
+ lastKnownThreadName ??
1516
+ deps.getThreadName?.(ctx) ??
1517
+ (ctx.cwd ? basename(ctx.cwd) : undefined),
1518
+ ...((deps.registrationState?.getSlot() ?? lastKnownSlot)
1519
+ ? { slot: deps.registrationState?.getSlot() ?? lastKnownSlot }
1520
+ : {}),
1521
+ cwd: ctx.cwd,
1522
+ pid: getPid(),
1523
+ processBirthId: deps.getProcessBirthId?.(),
1524
+ sessionGeneration: deps.getSessionGeneration?.(),
1525
+ target:
1526
+ registrationOptions?.target ??
1527
+ deps.registrationState?.getTarget() ??
1528
+ lastKnownTarget,
1529
+ busSocketPath:
1530
+ deps.getFollowerBusSocketPath?.() ?? deps.followerBusSocketPath,
1531
+ registrationGeneration,
1532
+ protocol: deps.protocolIdentity,
1533
+ connectedAtMs: getNowMs(),
1534
+ },
1535
+ };
1536
+ let response: TelegramBusEnvelope | undefined;
1537
+ try {
1538
+ response = await sendTelegramBusLocalEnvelope({
1539
+ socketPath: leaderSocketPath,
1540
+ timeoutMs: registrationTimeoutMs,
1541
+ envelope: registrationEnvelope,
1542
+ retry: getTelegramBusTransportRetryPolicy({
1543
+ endpoint: leaderSocketPath,
1544
+ operation: "registration",
1545
+ overrides: {
1546
+ attempts: registrationRetryAttempts,
1547
+ delayMs: registrationRetryDelayMs,
1548
+ },
1549
+ }),
1550
+ recordTransportEvent(phase, details) {
1551
+ deps.recordRuntimeEvent?.("bus", `Telegram bus ${phase}`, {
1552
+ phase: `follower-register-${phase}`,
1553
+ ...details,
1554
+ });
1555
+ },
1556
+ });
1557
+ } catch (error) {
1558
+ stopHeartbeat();
1559
+ activeLeaderSocketPath = undefined;
1560
+ activeAuthSecret = undefined;
1561
+ deps.registrationState?.setRegistered(false);
1562
+ deps.setActiveAuthSecret?.(undefined);
1563
+ await deps.stopReceiving?.();
1564
+ throw error;
1565
+ }
1566
+ if (deps.isContextActive && !deps.isContextActive(ctx)) {
1567
+ stopHeartbeat();
1568
+ activeLeaderSocketPath = undefined;
1569
+ activeAuthSecret = undefined;
1570
+ deps.setActiveAuthSecret?.(undefined);
1571
+ await deps.stopReceiving?.();
1572
+ return false;
1573
+ }
1574
+ const compatibility = getTelegramBusProtocolCompatibility({
1575
+ local: deps.protocolIdentity,
1576
+ remote: response?.kind === "bus.ack" ? response.protocol : undefined,
1577
+ });
1578
+ if (!compatibility.compatible) {
1579
+ stopHeartbeat();
1580
+ activeLeaderSocketPath = undefined;
1581
+ activeAuthSecret = undefined;
1582
+ deps.registrationState?.setRegistered(false);
1583
+ deps.setActiveAuthSecret?.(undefined);
1584
+ await deps.stopReceiving?.();
1585
+ throw new Error(
1586
+ `Incompatible Telegram bus leader protocol: ${compatibility.reason}.`,
1587
+ );
1588
+ }
1589
+ if (response?.kind === "bus.ack" && !response.ok) {
1590
+ stopHeartbeat();
1591
+ activeLeaderSocketPath = undefined;
1592
+ activeAuthSecret = undefined;
1593
+ deps.registrationState?.setRegistered(false);
1594
+ deps.setActiveAuthSecret?.(undefined);
1595
+ await deps.stopReceiving?.();
1596
+ throw new Error(
1597
+ response.message ??
1598
+ "Telegram bus follower registration was rejected.",
1599
+ );
1600
+ }
1601
+ if (response?.kind === "bus.ack" && response.ok) {
1602
+ const registrationResult = parseRegistrationResult(response.result);
1603
+ deps.registrationState?.setRegistered(true, registrationResult.target, {
1604
+ ...registrationResult,
1605
+ generation: registrationGeneration,
1606
+ ...(response.protocol
1607
+ ? { leaderProtocol: response.protocol }
1608
+ : {}),
1609
+ });
1610
+ lastKnownTarget = registrationResult.target;
1611
+ lastKnownSlot = registrationResult.slot;
1612
+ lastKnownThreadName = registrationResult.threadName;
1613
+ activeLeaderSocketPath = leaderSocketPath;
1614
+ activeRegistrationGeneration = registrationGeneration;
1615
+ activeContext = ctx;
1616
+ try {
1617
+ await deps.onRegistered?.(ctx);
1618
+ } catch (error) {
1619
+ stopHeartbeat();
1620
+ activeLeaderSocketPath = undefined;
1621
+ activeAuthSecret = undefined;
1622
+ activeRegistrationGeneration = undefined;
1623
+ deps.registrationState?.setRegistered(false);
1624
+ deps.setActiveAuthSecret?.(undefined);
1625
+ await deps.stopReceiving?.();
1626
+ throw error;
1627
+ }
1628
+ await requestHeartbeat();
1629
+ startHeartbeat(leaderSocketPath);
1630
+ if (
1631
+ pendingHandoffOptions &&
1632
+ getTelegramFollowerSessionHandoff()?.instanceId ===
1633
+ pendingHandoffOptions.previousInstanceId
1634
+ ) {
1635
+ setTelegramFollowerSessionHandoff(undefined);
1636
+ }
1637
+ return true;
1638
+ }
1639
+ stopHeartbeat();
1640
+ activeLeaderSocketPath = undefined;
1641
+ activeAuthSecret = undefined;
1642
+ deps.registrationState?.setRegistered(false);
1643
+ deps.setActiveAuthSecret?.(undefined);
1644
+ await deps.stopReceiving?.();
1645
+ return false;
1646
+ },
1647
+ setContext(ctx) {
1648
+ activeContext = ctx;
1649
+ },
1650
+ async disconnectFromLeader() {
1651
+ if (!activeLeaderSocketPath || !activeRegistrationGeneration) {
1652
+ return false;
1653
+ }
1654
+ const response = await sendTelegramBusLocalEnvelope({
1655
+ socketPath: activeLeaderSocketPath,
1656
+ timeoutMs: registrationTimeoutMs,
1657
+ retry: getTelegramBusTransportRetryPolicy({
1658
+ endpoint: activeLeaderSocketPath,
1659
+ operation: "operation",
1660
+ }),
1661
+ envelope: {
1662
+ kind: "follower.disconnect",
1663
+ requestId: deps.createRequestId(),
1664
+ auth: activeAuthSecret,
1665
+ instanceId: deps.instanceId,
1666
+ registrationGeneration: activeRegistrationGeneration,
1667
+ sentAtMs: getNowMs(),
1668
+ },
1669
+ });
1670
+ if (response?.kind === "bus.ack" && response.ok) return true;
1671
+ throw new Error(
1672
+ response?.kind === "bus.ack"
1673
+ ? (response.message ?? "Telegram follower disconnect was rejected.")
1674
+ : "Telegram follower disconnect was not acknowledged.",
1675
+ );
1676
+ },
1677
+ stop,
1678
+ };
1679
+ }
1680
+
1681
+ const TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD =
1682
+ "pi_telegram_forward_comment_batch_position";
1683
+
1684
+ export function prepareTelegramBusFollowerJournaledUpdateForExecution<
1685
+ TUpdate extends { message?: unknown } & Record<string, unknown>,
1686
+ >(
1687
+ update: TUpdate,
1688
+ prepareForwardedMessage: (
1689
+ message: NonNullable<TUpdate["message"]>,
1690
+ position: "comment" | "forward",
1691
+ ) => void,
1692
+ ): TUpdate {
1693
+ const position = update[TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD];
1694
+ if (
1695
+ update.message !== undefined &&
1696
+ (position === "comment" || position === "forward")
1697
+ ) {
1698
+ prepareForwardedMessage(
1699
+ update.message as NonNullable<TUpdate["message"]>,
1700
+ position,
1701
+ );
1702
+ }
1703
+ if (position === undefined) return update;
1704
+ const prepared = { ...update };
1705
+ delete prepared[TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD];
1706
+ return prepared;
1707
+ }
1708
+
1709
+ export function createTelegramBusFollowerDurableAdmissionRuntime<TContext>(deps: {
1710
+ journal: {
1711
+ appendBatch(
1712
+ updates: readonly ({ update_id: number } & Record<string, unknown>)[],
1713
+ ): unknown;
1714
+ };
1715
+ signalWorker: (ctx: TContext) => void;
1716
+ }): TelegramBusFollowerDurableAdmissionPort<TContext> {
1717
+ return {
1718
+ async admit(envelope, ctx) {
1719
+ const delivery = envelope.delivery;
1720
+ if (!delivery) {
1721
+ throw new Error("Telegram follower durable admission requires delivery identity.");
1722
+ }
1723
+ const expected = createTelegramBusFollowerDeliveryIdentity({
1724
+ kind: envelope.kind,
1725
+ recipientBindingKey: delivery.recipientBindingKey,
1726
+ sourceUpdateId: delivery.sourceUpdateId,
1727
+ });
1728
+ if (expected.deliveryId !== delivery.deliveryId) {
1729
+ throw new Error("Invalid Telegram follower delivery id.");
1730
+ }
1731
+ const carrier =
1732
+ envelope.kind === "leader.forwardCallback"
1733
+ ? envelope.query
1734
+ : envelope.kind === "leader.forwardReaction"
1735
+ ? envelope.reactionUpdate
1736
+ : envelope.message;
1737
+ if (
1738
+ !isRecord(carrier) ||
1739
+ carrier.pi_telegram_source_update_id !== delivery.sourceUpdateId
1740
+ ) {
1741
+ throw new Error("Telegram follower delivery source update id mismatch.");
1742
+ }
1743
+ const update = {
1744
+ update_id: delivery.sourceUpdateId,
1745
+ ...(envelope.kind === "leader.forwardMessage" &&
1746
+ envelope.forwardCommentBatchPosition
1747
+ ? {
1748
+ [TELEGRAM_FOLLOWER_FORWARD_BATCH_POSITION_FIELD]:
1749
+ envelope.forwardCommentBatchPosition,
1750
+ }
1751
+ : {}),
1752
+ ...(envelope.kind === "leader.forwardCallback"
1753
+ ? { callback_query: carrier }
1754
+ : envelope.kind === "leader.forwardReaction"
1755
+ ? { message_reaction: carrier }
1756
+ : envelope.kind === "leader.forwardMessage"
1757
+ ? { message: carrier }
1758
+ : { edited_message: carrier }),
1759
+ };
1760
+ deps.journal.appendBatch([update]);
1761
+ deps.signalWorker(ctx);
1762
+ return {
1763
+ deliveryId: delivery.deliveryId,
1764
+ sourceUpdateId: delivery.sourceUpdateId,
1765
+ };
1766
+ },
1767
+ };
1768
+ }
1769
+
1770
+ export function createTelegramBusForwardedUpdateReceiverRuntime<TContext>(
1771
+ deps: TelegramBusForwardedUpdateReceiverRuntimeDeps<TContext>,
1772
+ ): TelegramBusForwardedUpdateReceiverRuntime {
1773
+ const server = createTelegramBusLocalServer({
1774
+ socketPath: deps.socketPath,
1775
+ recordTransportEvent(phase, details) {
1776
+ deps.recordRuntimeEvent?.("bus", `Telegram bus ${phase}`, {
1777
+ phase: `follower-receiver-${phase}`,
1778
+ ...details,
1779
+ });
1780
+ },
1781
+ async handleEnvelope(envelope) {
1782
+ const authSecret = deps.getAuthSecret?.();
1783
+ if (
1784
+ deps.getAuthSecret &&
1785
+ (!authSecret || !isTelegramBusEnvelopeAuthorized(envelope, authSecret))
1786
+ ) {
1787
+ return createUnauthorizedBusAck(envelope.requestId);
1788
+ }
1789
+ if (
1790
+ (envelope.kind !== "leader.forwardCallback" &&
1791
+ envelope.kind !== "leader.forwardReaction" &&
1792
+ envelope.kind !== "leader.forwardMessage" &&
1793
+ envelope.kind !== "leader.forwardEditedMessage" &&
1794
+ envelope.kind !== "leader.replaceFollowerTarget" &&
1795
+ envelope.kind !== "leader.offerQueueHandoff") ||
1796
+ envelope.recipientInstanceId !== deps.instanceId
1797
+ ) {
1798
+ return {
1799
+ kind: "bus.ack",
1800
+ requestId: envelope.requestId,
1801
+ ok: false,
1802
+ message: "Telegram bus receiver cannot handle this envelope.",
1803
+ };
1804
+ }
1805
+ const registrationGeneration = deps.getRegistrationGeneration();
1806
+ if (
1807
+ !registrationGeneration ||
1808
+ envelope.recipientRegistrationGeneration !== registrationGeneration
1809
+ ) {
1810
+ return {
1811
+ kind: "bus.ack",
1812
+ requestId: envelope.requestId,
1813
+ ok: false,
1814
+ message: "Stale Telegram bus follower registration generation.",
1815
+ };
1816
+ }
1817
+ if (
1818
+ envelope.kind !== "leader.replaceFollowerTarget" &&
1819
+ envelope.kind !== "leader.offerQueueHandoff" &&
1820
+ (!envelope.delivery ||
1821
+ envelope.delivery.recipientBindingKey !==
1822
+ deps.getRecipientBindingKey())
1823
+ ) {
1824
+ return {
1825
+ kind: "bus.ack",
1826
+ requestId: envelope.requestId,
1827
+ ok: false,
1828
+ message: "Mismatched Telegram follower delivery identity.",
1829
+ };
1830
+ }
1831
+ const ctx = deps.getContext();
1832
+ if (!ctx) {
1833
+ return {
1834
+ kind: "bus.ack",
1835
+ requestId: envelope.requestId,
1836
+ ok: false,
1837
+ message: "Telegram bus follower has no active context.",
1838
+ };
1839
+ }
1840
+ try {
1841
+ if (
1842
+ envelope.kind !== "leader.replaceFollowerTarget" &&
1843
+ envelope.kind !== "leader.offerQueueHandoff"
1844
+ ) {
1845
+ const receipt = await deps.durableAdmission.admit(envelope, ctx);
1846
+ return {
1847
+ kind: "bus.ack",
1848
+ requestId: envelope.requestId,
1849
+ ok: true,
1850
+ result: receipt,
1851
+ };
1852
+ }
1853
+ if (envelope.kind === "leader.offerQueueHandoff") {
1854
+ if (!deps.handleQueueHandoff) {
1855
+ throw new Error(
1856
+ "Telegram bus receiver cannot accept queue handoff payloads.",
1857
+ );
1858
+ }
1859
+ const result = await deps.handleQueueHandoff(envelope, ctx);
1860
+ const receipt = envelope.payload.admissionReceipts[0];
1861
+ if (
1862
+ envelope.payload.admissionReceipts.length !== 1 ||
1863
+ !receipt ||
1864
+ result.status !== "staged" ||
1865
+ result.receiptId !== receipt.receiptId ||
1866
+ result.sourceUpdateIds.length !== receipt.sourceUpdateIds.length ||
1867
+ result.sourceUpdateIds.some(
1868
+ (updateId, index) => updateId !== receipt.sourceUpdateIds[index],
1869
+ )
1870
+ ) {
1871
+ throw new Error(
1872
+ "Telegram queue handoff staging returned a mismatched receipt.",
1873
+ );
1874
+ }
1875
+ return {
1876
+ kind: "bus.ack",
1877
+ requestId: envelope.requestId,
1878
+ ok: true,
1879
+ result,
1880
+ };
1881
+ }
1882
+ {
1883
+ if (!deps.handleReplaceTarget) {
1884
+ throw new Error(
1885
+ "Telegram bus receiver cannot replace follower target.",
1886
+ );
1887
+ }
1888
+ await deps.handleReplaceTarget(
1889
+ {
1890
+ target: envelope.target,
1891
+ ...(envelope.oldTarget ? { oldTarget: envelope.oldTarget } : {}),
1892
+ reason: envelope.reason,
1893
+ },
1894
+ ctx,
1895
+ );
1896
+ }
1897
+ return { kind: "bus.ack", requestId: envelope.requestId, ok: true };
1898
+ } catch (error) {
1899
+ deps.recordRuntimeEvent?.("bus", error, { phase: "follower-forward" });
1900
+ return {
1901
+ kind: "bus.ack",
1902
+ requestId: envelope.requestId,
1903
+ ok: false,
1904
+ message:
1905
+ error instanceof Error
1906
+ ? error.message
1907
+ : "Telegram bus follower dispatch failed.",
1908
+ };
1909
+ }
1910
+ },
1911
+ });
1912
+ return server;
1913
+ }
1914
+
1915
+ function parseRegistrationResult(value: unknown): {
1916
+ target?: TelegramTarget;
1917
+ slot?: string;
1918
+ threadName?: string;
1919
+ } {
1920
+ if (!isRecord(value)) return {};
1921
+ const target = parseTarget(isRecord(value.target) ? value.target : value);
1922
+ return {
1923
+ ...(target ? { target } : {}),
1924
+ ...(typeof value.slot === "string" ? { slot: value.slot } : {}),
1925
+ ...(typeof value.threadName === "string"
1926
+ ? { threadName: value.threadName }
1927
+ : {}),
1928
+ };
1929
+ }
1930
+
1931
+ function parseTarget(value: unknown): TelegramTarget | undefined {
1932
+ if (value === undefined) return undefined;
1933
+ if (!isRecord(value) || typeof value.chatId !== "number") return undefined;
1934
+ const target: TelegramTarget = { chatId: value.chatId };
1935
+ if (typeof value.threadId === "number") target.threadId = value.threadId;
1936
+ return target;
1937
+ }
1938
+
1939
+ function isRecord(value: unknown): value is Record<string, unknown> {
1940
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1941
+ }