@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,2645 @@
1
+ /**
2
+ * Telegram multi-instance bus protocol and IPC helpers
3
+ * Zones: multi-instance bus, local IPC contract, live instance routing
4
+ * Owns serializable bus envelopes, socket/auth helpers, local IPC client/server primitives,
5
+ * cross-instance forwarding helpers, and the live follower registry model.
6
+ */
7
+
8
+ import { execFileSync } from "node:child_process";
9
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
10
+ import {
11
+ chmodSync,
12
+ existsSync,
13
+ lstatSync,
14
+ mkdirSync,
15
+ readFileSync,
16
+ readlinkSync,
17
+ renameSync,
18
+ symlinkSync,
19
+ unlinkSync,
20
+ } from "node:fs";
21
+ import {
22
+ createConnection,
23
+ createServer,
24
+ type Server,
25
+ type Socket,
26
+ } from "node:net";
27
+ import { createRequire } from "node:module";
28
+ import { platform as getPlatform, tmpdir } from "node:os";
29
+ import { basename, dirname, join } from "node:path";
30
+
31
+ import {
32
+ classifyTelegramBusTransportError,
33
+ createTelegramBusTransportTimeoutError,
34
+ delayTelegramBusTransportRetry,
35
+ getTelegramBusEndpointDiagnostics,
36
+ getTelegramBusFollowerEndpoint,
37
+ getTelegramBusLeaderEndpoint,
38
+ getTelegramBusPipePath,
39
+ getTelegramBusTransportRetryPolicy,
40
+ isTelegramBusPipePath,
41
+ isRetryableTelegramBusTransportError,
42
+ probeTelegramBusEndpoint,
43
+ type TelegramBusTransportEventRecorder,
44
+ type TelegramBusTransportRetryPolicy,
45
+ } from "./bus-transport.ts";
46
+ import {
47
+ TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS,
48
+ TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES,
49
+ type TelegramQueueHandoffPayload,
50
+ } from "./queue.ts";
51
+ import type { TelegramTarget } from "./target.ts";
52
+ import { isProcessAlive } from "./locks.ts";
53
+ import { resolveAgentDir } from "./paths.ts";
54
+
55
+ export interface TelegramBusProcessRuntime {
56
+ instanceId: string;
57
+ processId: number;
58
+ processBirthId: string;
59
+ manualFollowerOwnerId: string;
60
+ getLeaderSocketPath: () => string;
61
+ getFollowerSocketPath: () => string;
62
+ }
63
+
64
+ export interface TelegramProcessBirthIdentityOptions {
65
+ platform?: NodeJS.Platform;
66
+ readProcStat?: (pid: number) => string;
67
+ readDarwinProcessStart?: (pid: number) => string;
68
+ }
69
+
70
+ export type TelegramProcessBirthProof =
71
+ | { status: "proven"; identity: string }
72
+ | { status: "unverifiable" };
73
+
74
+ export type TelegramProcessLiveness = "alive" | "dead" | "unverifiable";
75
+
76
+ export interface TelegramProcessLivenessOptions
77
+ extends TelegramProcessBirthIdentityOptions {
78
+ isProcessAlive?: (pid: number) => boolean;
79
+ }
80
+
81
+ function readDarwinProcessStart(pid: number): string {
82
+ return execFileSync(
83
+ "/bin/ps",
84
+ ["-o", "lstart=", "-p", String(pid)],
85
+ {
86
+ encoding: "utf8",
87
+ stdio: ["ignore", "pipe", "ignore"],
88
+ },
89
+ ).trim();
90
+ }
91
+
92
+ export function getTelegramProcessBirthProof(
93
+ pid: number,
94
+ options: TelegramProcessBirthIdentityOptions = {},
95
+ ): TelegramProcessBirthProof {
96
+ if (pid <= 0) return { status: "unverifiable" };
97
+ const platform = options.platform ?? getPlatform();
98
+ if (platform === "linux") {
99
+ try {
100
+ const stat = (options.readProcStat ?? ((targetPid) =>
101
+ readFileSync(`/proc/${targetPid}/stat`, "utf8")))(pid);
102
+ const closeParen = stat.lastIndexOf(")");
103
+ const fields = stat
104
+ .slice(closeParen + 2)
105
+ .trim()
106
+ .split(/\s+/u);
107
+ const startTicks = fields[19];
108
+ if (startTicks) {
109
+ return { status: "proven", identity: `${pid}:start:${startTicks}` };
110
+ }
111
+ } catch {
112
+ /* inaccessible process metadata */
113
+ }
114
+ } else if (platform === "darwin") {
115
+ try {
116
+ const startedAt = (
117
+ options.readDarwinProcessStart ?? readDarwinProcessStart
118
+ )(pid);
119
+ if (startedAt) {
120
+ const fingerprint = createHash("sha256")
121
+ .update(startedAt)
122
+ .digest("hex")
123
+ .slice(0, 16);
124
+ return { status: "proven", identity: `${pid}:start:${fingerprint}` };
125
+ }
126
+ } catch {
127
+ /* inaccessible process metadata */
128
+ }
129
+ }
130
+ return { status: "unverifiable" };
131
+ }
132
+
133
+ export function getTelegramProcessBirthIdentity(
134
+ pid: number,
135
+ fallbackGeneration: number | string,
136
+ options: TelegramProcessBirthIdentityOptions = {},
137
+ ): string {
138
+ const proof = getTelegramProcessBirthProof(pid, options);
139
+ return proof.status === "proven"
140
+ ? proof.identity
141
+ : `${pid}:generation:${fallbackGeneration}`;
142
+ }
143
+
144
+ export function getTelegramProcessLiveness(
145
+ owner: { processId: number; processBirthId: string },
146
+ options: TelegramProcessLivenessOptions = {},
147
+ ): TelegramProcessLiveness {
148
+ const processAlive = options.isProcessAlive ?? isProcessAlive;
149
+ if (!processAlive(owner.processId)) return "dead";
150
+ const proof = getTelegramProcessBirthProof(owner.processId, options);
151
+ if (proof.status === "unverifiable") return "unverifiable";
152
+ return proof.identity === owner.processBirthId ? "alive" : "dead";
153
+ }
154
+
155
+ export function createCurrentTelegramBusProcessRuntime(input: {
156
+ getActiveProfileName: () => string | undefined;
157
+ pid?: number;
158
+ parentPid?: number;
159
+ createdAtMs?: number;
160
+ }): TelegramBusProcessRuntime {
161
+ return createTelegramBusProcessRuntime({
162
+ getActiveProfileName: input.getActiveProfileName,
163
+ pid: input.pid ?? process.pid,
164
+ parentPid: input.parentPid ?? process.ppid,
165
+ createdAtMs: input.createdAtMs ?? Date.now(),
166
+ });
167
+ }
168
+
169
+ export function createTelegramBusProcessRuntime(input: {
170
+ getActiveProfileName: () => string | undefined;
171
+ pid: number;
172
+ parentPid: number;
173
+ parentProcessIdentity?: string;
174
+ createdAtMs: number;
175
+ }): TelegramBusProcessRuntime {
176
+ const instanceId = `${input.pid}:${input.createdAtMs}`;
177
+ const ownerPid = input.parentPid || input.pid;
178
+ const manualFollowerOwnerId =
179
+ input.parentProcessIdentity ??
180
+ getTelegramProcessBirthIdentity(ownerPid, input.createdAtMs);
181
+ return {
182
+ instanceId,
183
+ processId: input.pid,
184
+ processBirthId: getTelegramProcessBirthIdentity(input.pid, instanceId),
185
+ manualFollowerOwnerId,
186
+ getLeaderSocketPath: () =>
187
+ getTelegramBusSocketPath(
188
+ undefined,
189
+ undefined,
190
+ input.getActiveProfileName(),
191
+ ),
192
+ getFollowerSocketPath: () =>
193
+ getTelegramBusFollowerSocketPath(
194
+ instanceId,
195
+ undefined,
196
+ undefined,
197
+ input.getActiveProfileName(),
198
+ ),
199
+ };
200
+ }
201
+
202
+ export function createTelegramBusAuthSecret(): string {
203
+ return randomBytes(32).toString("base64url");
204
+ }
205
+
206
+ export const TELEGRAM_BUS_PROTOCOL_VERSION = 1 as const;
207
+ export const TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION =
208
+ "durable-follower-admission-v1" as const;
209
+ export const TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF =
210
+ "queue-handoff-v1" as const;
211
+
212
+ export interface TelegramBusProtocolIdentity {
213
+ protocolVersion: number;
214
+ runtimeBuild: string;
215
+ capabilities: string[];
216
+ }
217
+
218
+ export interface TelegramBusProtocolCompatibility {
219
+ compatible: boolean;
220
+ reason?: "missing-identity" | "version-mismatch" | "missing-capability";
221
+ missingCapabilities: string[];
222
+ }
223
+
224
+ export function createTelegramBusProtocolIdentity(input: {
225
+ runtimeBuild: string;
226
+ capabilities?: readonly string[];
227
+ }): TelegramBusProtocolIdentity {
228
+ const runtimeBuild = input.runtimeBuild.trim();
229
+ if (!runtimeBuild || runtimeBuild.length > 128) {
230
+ throw new Error("Telegram bus runtime build identity is invalid.");
231
+ }
232
+ const capabilities = [...new Set(input.capabilities ?? [])].sort();
233
+ if (
234
+ capabilities.length > 32 ||
235
+ capabilities.some(
236
+ (capability) =>
237
+ capability.length > 128 ||
238
+ !/^[a-z0-9][a-z0-9._-]*$/u.test(capability),
239
+ )
240
+ ) {
241
+ throw new Error("Telegram bus capabilities must be canonical identifiers.");
242
+ }
243
+ return {
244
+ protocolVersion: TELEGRAM_BUS_PROTOCOL_VERSION,
245
+ runtimeBuild,
246
+ capabilities,
247
+ };
248
+ }
249
+
250
+ export function createTelegramCurrentBusProtocolIdentity(
251
+ capabilities: readonly string[] = [],
252
+ ): TelegramBusProtocolIdentity {
253
+ const packageMetadata = createRequire(import.meta.url)("../package.json") as {
254
+ version?: unknown;
255
+ };
256
+ if (typeof packageMetadata.version !== "string") {
257
+ throw new Error("Telegram package build identity is unavailable.");
258
+ }
259
+ return createTelegramBusProtocolIdentity({
260
+ runtimeBuild: packageMetadata.version,
261
+ capabilities,
262
+ });
263
+ }
264
+
265
+ export function hasTelegramBusCapability(
266
+ identity: TelegramBusProtocolIdentity | undefined,
267
+ capability: string,
268
+ ): boolean {
269
+ return identity?.capabilities.includes(capability) ?? false;
270
+ }
271
+
272
+ export function getTelegramBusProtocolCompatibility(input: {
273
+ local: TelegramBusProtocolIdentity;
274
+ remote?: TelegramBusProtocolIdentity;
275
+ }): TelegramBusProtocolCompatibility {
276
+ if (!input.remote) {
277
+ return {
278
+ compatible: false,
279
+ reason: "missing-identity",
280
+ missingCapabilities: [],
281
+ };
282
+ }
283
+ if (input.remote.protocolVersion !== input.local.protocolVersion) {
284
+ return {
285
+ compatible: false,
286
+ reason: "version-mismatch",
287
+ missingCapabilities: [],
288
+ };
289
+ }
290
+ const remoteCapabilities = new Set(input.remote.capabilities);
291
+ const missingCapabilities = input.local.capabilities
292
+ .filter(
293
+ (capability) =>
294
+ capability === TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
295
+ )
296
+ .filter((capability) => !remoteCapabilities.has(capability));
297
+ return missingCapabilities.length > 0
298
+ ? {
299
+ compatible: false,
300
+ reason: "missing-capability",
301
+ missingCapabilities,
302
+ }
303
+ : { compatible: true, missingCapabilities: [] };
304
+ }
305
+
306
+ export function getTelegramBusSocketPath(
307
+ agentDir = resolveAgentDir(),
308
+ platform = getPlatform(),
309
+ profileName?: string,
310
+ ): string {
311
+ return getTelegramBusLeaderEndpoint({ agentDir, platform, profileName });
312
+ }
313
+
314
+ export function getTelegramBusFollowerSocketPath(
315
+ instanceId: string,
316
+ agentDir = resolveAgentDir(),
317
+ platform = getPlatform(),
318
+ profileName?: string,
319
+ ): string {
320
+ return getTelegramBusFollowerEndpoint({
321
+ agentDir,
322
+ platform,
323
+ instanceId,
324
+ profileName,
325
+ });
326
+ }
327
+
328
+ export interface TelegramBusInstanceRegistration {
329
+ instanceId: string;
330
+ previousInstanceId?: string;
331
+ profileKey?: string;
332
+ threadName?: string;
333
+ slot?: string;
334
+ cwd?: string;
335
+ pid?: number;
336
+ target?: TelegramTarget;
337
+ busSocketPath?: string;
338
+ registrationGeneration?: string;
339
+ protocol?: TelegramBusProtocolIdentity;
340
+ sessionGeneration?: number;
341
+ processBirthId?: string;
342
+ connectedAtMs: number;
343
+ }
344
+
345
+ export interface TelegramBusFollowerView extends TelegramBusInstanceRegistration {
346
+ lastHeartbeatMs: number;
347
+ }
348
+
349
+ export function getTelegramFollowerTargetOwnership(input: {
350
+ target: TelegramTarget;
351
+ followers: readonly TelegramBusFollowerView[];
352
+ activeThreadRecords?: readonly {
353
+ status?: string;
354
+ instanceId?: string;
355
+ profileKey?: string;
356
+ owner?: { kind?: string };
357
+ target: TelegramTarget;
358
+ }[];
359
+ currentInstanceId?: string;
360
+ }): {
361
+ instanceId: string;
362
+ ownerGeneration: string;
363
+ recipientBindingKey: string;
364
+ } | undefined {
365
+ const liveFollower = input.followers.find((follower) => {
366
+ return (
367
+ follower.target?.chatId === input.target.chatId &&
368
+ follower.target.threadId === input.target.threadId
369
+ );
370
+ });
371
+ if (
372
+ liveFollower?.registrationGeneration &&
373
+ liveFollower.profileKey &&
374
+ liveFollower.protocol?.capabilities.includes(
375
+ TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
376
+ )
377
+ ) {
378
+ return {
379
+ instanceId: liveFollower.instanceId,
380
+ ownerGeneration: liveFollower.registrationGeneration,
381
+ recipientBindingKey: liveFollower.profileKey,
382
+ };
383
+ }
384
+ // Persisted records are restart hints, not live routing authority. Only an
385
+ // authenticated current follower registration may receive forwarded work.
386
+ return undefined;
387
+ }
388
+
389
+ const TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD = "__piTelegramAggregateDelivery";
390
+ const TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD =
391
+ "__piTelegramCrossTargetDelivery";
392
+
393
+ export function markTelegramBusAggregateDelivery<
394
+ T extends Record<string, unknown>,
395
+ >(body: T): T {
396
+ return {
397
+ ...body,
398
+ [TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD]: true,
399
+ };
400
+ }
401
+
402
+ export function isTelegramBusAggregateDelivery(body: unknown): boolean {
403
+ return Boolean(
404
+ body &&
405
+ typeof body === "object" &&
406
+ !Array.isArray(body) &&
407
+ (body as Record<string, unknown>)[TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD] ===
408
+ true,
409
+ );
410
+ }
411
+
412
+ export function markTelegramBusCrossTargetDelivery<
413
+ T extends Record<string, unknown>,
414
+ >(body: T): T {
415
+ return {
416
+ ...body,
417
+ [TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD]: true,
418
+ };
419
+ }
420
+
421
+ export function isTelegramBusCrossTargetDelivery(body: unknown): boolean {
422
+ return Boolean(
423
+ body &&
424
+ typeof body === "object" &&
425
+ !Array.isArray(body) &&
426
+ (body as Record<string, unknown>)[
427
+ TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD
428
+ ] === true,
429
+ );
430
+ }
431
+
432
+ export function stripTelegramBusApiMetadata<T extends Record<string, unknown>>(
433
+ body: T,
434
+ ): T {
435
+ if (
436
+ !(TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD in body) &&
437
+ !(TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD in body)
438
+ ) {
439
+ return body;
440
+ }
441
+ const clean = { ...body };
442
+ delete clean[TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD];
443
+ delete clean[TELEGRAM_BUS_CROSS_TARGET_DELIVERY_FIELD];
444
+ return clean;
445
+ }
446
+
447
+ export function isTelegramFollowerApiCallAllowed(input: {
448
+ follower: TelegramBusFollowerView;
449
+ method: string;
450
+ args: unknown[];
451
+ isMessageOwned?: (chatId: number, messageId: number) => boolean;
452
+ }): boolean {
453
+ const allowedCallMethods = new Set([
454
+ "answerCallbackQuery",
455
+ "answerGuestQuery",
456
+ "closeForumTopic",
457
+ "deleteForumTopic",
458
+ "deleteMessage",
459
+ "editForumTopic",
460
+ "editMessageReplyMarkup",
461
+ "editMessageText",
462
+ "sendChatAction",
463
+ "sendMessage",
464
+ "sendMessageDraft",
465
+ "sendRichMessage",
466
+ "sendRichMessageDraft",
467
+ ]);
468
+ const allowedMultipartMethods = new Set([
469
+ "sendAudio",
470
+ "sendDocument",
471
+ "sendMediaGroup",
472
+ "sendPhoto",
473
+ "sendRichMessage",
474
+ "sendVoice",
475
+ ]);
476
+ const target = input.follower.target;
477
+ const matchesId = (value: unknown, expected: number): boolean =>
478
+ value === expected || value === String(expected);
479
+ const isTargetScoped = (body: unknown): boolean => {
480
+ if (!target) return false;
481
+ if (!body || typeof body !== "object" || Array.isArray(body)) return false;
482
+ const record = body as Record<string, unknown>;
483
+ if (!matchesId(record.chat_id, target.chatId)) return false;
484
+ if (target.threadId === undefined) return true;
485
+ return matchesId(record.message_thread_id, target.threadId);
486
+ };
487
+ const isTargetChatScoped = (body: unknown): boolean => {
488
+ if (!target) return false;
489
+ if (!body || typeof body !== "object" || Array.isArray(body)) return false;
490
+ const record = body as Record<string, unknown>;
491
+ return matchesId(record.chat_id, target.chatId);
492
+ };
493
+ const isDifferentTargetScoped = (body: unknown): boolean => {
494
+ if (!target || !isTargetChatScoped(body)) return false;
495
+ const threadId = (body as Record<string, unknown>).message_thread_id;
496
+ if (threadId === undefined) return target.threadId !== undefined;
497
+ const parsedThreadId =
498
+ typeof threadId === "number" ? threadId : Number(threadId);
499
+ return (
500
+ Number.isInteger(parsedThreadId) &&
501
+ (target.threadId === undefined ||
502
+ !matchesId(threadId, target.threadId))
503
+ );
504
+ };
505
+ const isTargetMessageScoped = (body: unknown): boolean => {
506
+ if (!isTargetChatScoped(body)) return false;
507
+ const messageId = (body as Record<string, unknown>).message_id;
508
+ const parsedMessageId =
509
+ typeof messageId === "number" ? messageId : Number(messageId);
510
+ return (
511
+ Number.isInteger(parsedMessageId) && matchesId(messageId, parsedMessageId)
512
+ );
513
+ };
514
+ const isBotCommandRegistration = (body: unknown): boolean => {
515
+ if (!body || typeof body !== "object" || Array.isArray(body)) return false;
516
+ const commands = (body as Record<string, unknown>).commands;
517
+ return (
518
+ Array.isArray(commands) &&
519
+ commands.every(
520
+ (command) =>
521
+ command &&
522
+ typeof command === "object" &&
523
+ !Array.isArray(command) &&
524
+ typeof (command as Record<string, unknown>).command === "string" &&
525
+ typeof (command as Record<string, unknown>).description === "string",
526
+ )
527
+ );
528
+ };
529
+ if (input.method === "downloadFile") return true;
530
+ if (input.method === "call") {
531
+ const apiMethod = input.args[0];
532
+ if (typeof apiMethod !== "string") return false;
533
+ if (
534
+ apiMethod === "answerCallbackQuery" ||
535
+ apiMethod === "answerGuestQuery"
536
+ ) {
537
+ return true;
538
+ }
539
+ if (apiMethod === "getMe") return true;
540
+ if (apiMethod === "setMyCommands")
541
+ return isBotCommandRegistration(input.args[1]);
542
+ if (apiMethod === "sendChatAction")
543
+ return isTargetChatScoped(input.args[1]);
544
+ if (
545
+ apiMethod === "sendMessage" &&
546
+ isTelegramBusAggregateDelivery(input.args[1])
547
+ ) {
548
+ const body = input.args[1] as Record<string, unknown>;
549
+ return body.message_thread_id === undefined && isTargetChatScoped(body);
550
+ }
551
+ if (
552
+ (apiMethod === "sendMessage" || apiMethod === "sendRichMessage") &&
553
+ isTelegramBusCrossTargetDelivery(input.args[1])
554
+ ) {
555
+ return isDifferentTargetScoped(input.args[1]);
556
+ }
557
+ if (
558
+ apiMethod === "deleteMessage" ||
559
+ apiMethod === "editMessageReplyMarkup" ||
560
+ apiMethod === "editMessageText"
561
+ ) {
562
+ if (!isTargetMessageScoped(input.args[1])) return false;
563
+ const body = input.args[1] as Record<string, unknown>;
564
+ const messageId =
565
+ typeof body.message_id === "number"
566
+ ? body.message_id
567
+ : Number(body.message_id);
568
+ return input.isMessageOwned?.(target!.chatId, messageId) === true;
569
+ }
570
+ return allowedCallMethods.has(apiMethod) && isTargetScoped(input.args[1]);
571
+ }
572
+ if (input.method === "callMultipart") {
573
+ const apiMethod = input.args[0];
574
+ return (
575
+ typeof apiMethod === "string" &&
576
+ allowedMultipartMethods.has(apiMethod) &&
577
+ isTargetScoped(input.args[1])
578
+ );
579
+ }
580
+ return false;
581
+ }
582
+
583
+ export interface TelegramFollowerApiCallAuthorizationInput {
584
+ follower: TelegramBusFollowerView;
585
+ method: string;
586
+ args: unknown[];
587
+ }
588
+
589
+ export function createTelegramFollowerApiCallAuthorizer(deps: {
590
+ isMessageOwned(input: {
591
+ chatId: number;
592
+ messageId: number;
593
+ follower: TelegramBusFollowerView;
594
+ }): boolean;
595
+ }): (input: TelegramFollowerApiCallAuthorizationInput) => boolean {
596
+ return (input) =>
597
+ isTelegramFollowerApiCallAllowed({
598
+ ...input,
599
+ isMessageOwned(chatId, messageId) {
600
+ return deps.isMessageOwned({
601
+ chatId,
602
+ messageId,
603
+ follower: input.follower,
604
+ });
605
+ },
606
+ });
607
+ }
608
+
609
+ export interface TelegramBusAgentTargetSelector {
610
+ chatId?: number;
611
+ threadId?: number;
612
+ threadName?: string;
613
+ }
614
+
615
+ export interface TelegramBusAgentMessage {
616
+ target: TelegramTarget & { threadId: number };
617
+ messageId: number;
618
+ text: string;
619
+ }
620
+
621
+ export interface TelegramBusFollowerDeliveryIdentity {
622
+ deliveryId: string;
623
+ sourceUpdateId: number;
624
+ recipientBindingKey: string;
625
+ }
626
+
627
+ export type TelegramBusForeignUpdateFailureClass =
628
+ | "source-update-identity-missing"
629
+ | "recipient-binding-missing"
630
+ | "recipient-generation-missing"
631
+ | "transport-failed"
632
+ | "acknowledgement-missing"
633
+ | "acknowledgement-rejected"
634
+ | "acknowledgement-mismatched"
635
+ | "durable-receipt-missing"
636
+ | "durable-receipt-mismatched";
637
+
638
+ export type TelegramBusForeignUpdateSettlement =
639
+ | {
640
+ status: "accepted";
641
+ delivery: TelegramBusFollowerDeliveryIdentity;
642
+ }
643
+ | {
644
+ status: "retryable" | "terminal-rejected";
645
+ failureClass: TelegramBusForeignUpdateFailureClass;
646
+ message: string;
647
+ delivery?: TelegramBusFollowerDeliveryIdentity;
648
+ sourceUpdateId?: number;
649
+ };
650
+
651
+ export function createTelegramBusFollowerDeliveryIdentity(input: {
652
+ kind:
653
+ | "leader.forwardCallback"
654
+ | "leader.forwardReaction"
655
+ | "leader.forwardMessage"
656
+ | "leader.forwardEditedMessage";
657
+ recipientBindingKey: string;
658
+ sourceUpdateId: number;
659
+ }): TelegramBusFollowerDeliveryIdentity {
660
+ if (
661
+ !input.recipientBindingKey ||
662
+ !Number.isSafeInteger(input.sourceUpdateId) ||
663
+ input.sourceUpdateId < 0
664
+ ) {
665
+ throw new Error("Telegram follower delivery identity is incomplete.");
666
+ }
667
+ const deliveryId = createHash("sha256")
668
+ .update(
669
+ JSON.stringify({
670
+ version: 1,
671
+ kind: input.kind,
672
+ recipientBindingKey: input.recipientBindingKey,
673
+ sourceUpdateId: input.sourceUpdateId,
674
+ }),
675
+ )
676
+ .digest("hex");
677
+ return {
678
+ deliveryId: `telegram-follower-v1-${deliveryId}`,
679
+ sourceUpdateId: input.sourceUpdateId,
680
+ recipientBindingKey: input.recipientBindingKey,
681
+ };
682
+ }
683
+
684
+ export type TelegramBusEnvelope = (
685
+ | {
686
+ kind: "follower.register";
687
+ requestId: string;
688
+ registration: TelegramBusInstanceRegistration;
689
+ }
690
+ | {
691
+ kind: "follower.heartbeat";
692
+ requestId: string;
693
+ instanceId: string;
694
+ registrationGeneration?: string;
695
+ sentAtMs: number;
696
+ }
697
+ | {
698
+ kind: "follower.disconnect";
699
+ requestId: string;
700
+ instanceId: string;
701
+ registrationGeneration?: string;
702
+ sentAtMs: number;
703
+ }
704
+ | {
705
+ kind: "leader.forwardCallback";
706
+ requestId: string;
707
+ recipientInstanceId: string;
708
+ recipientRegistrationGeneration: string;
709
+ delivery: TelegramBusFollowerDeliveryIdentity;
710
+ query: unknown;
711
+ sentAtMs: number;
712
+ }
713
+ | {
714
+ kind: "leader.forwardReaction";
715
+ requestId: string;
716
+ recipientInstanceId: string;
717
+ recipientRegistrationGeneration: string;
718
+ delivery: TelegramBusFollowerDeliveryIdentity;
719
+ reactionUpdate: unknown;
720
+ sentAtMs: number;
721
+ }
722
+ | {
723
+ kind: "leader.forwardMessage";
724
+ requestId: string;
725
+ recipientInstanceId: string;
726
+ recipientRegistrationGeneration: string;
727
+ delivery: TelegramBusFollowerDeliveryIdentity;
728
+ message: unknown;
729
+ forwardCommentBatchPosition?: "comment" | "forward";
730
+ sentAtMs: number;
731
+ }
732
+ | {
733
+ kind: "leader.forwardEditedMessage";
734
+ requestId: string;
735
+ recipientInstanceId: string;
736
+ recipientRegistrationGeneration: string;
737
+ delivery: TelegramBusFollowerDeliveryIdentity;
738
+ message: unknown;
739
+ sentAtMs: number;
740
+ }
741
+ | {
742
+ kind: "leader.replaceFollowerTarget";
743
+ requestId: string;
744
+ recipientInstanceId: string;
745
+ recipientRegistrationGeneration?: string;
746
+ target: TelegramTarget & { threadId: number };
747
+ oldTarget?: TelegramTarget & { threadId: number };
748
+ reason: "thread-restore";
749
+ sentAtMs: number;
750
+ }
751
+ | {
752
+ kind: "leader.offerQueueHandoff";
753
+ requestId: string;
754
+ recipientInstanceId: string;
755
+ recipientRegistrationGeneration: string;
756
+ donorInstanceId: string;
757
+ donorProcessId: number;
758
+ donorProcessBirthId: string;
759
+ donorSessionGeneration: number;
760
+ donorAcquisitionId: string;
761
+ donorAcquiredAtMs: number;
762
+ handoffToken: string;
763
+ payload: TelegramQueueHandoffPayload;
764
+ sentAtMs: number;
765
+ }
766
+ | {
767
+ kind: "follower.offerQueueHandoff";
768
+ requestId: string;
769
+ instanceId: string;
770
+ registrationGeneration: string;
771
+ recipientInstanceId: string;
772
+ recipientRegistrationGeneration: string;
773
+ donorProcessId: number;
774
+ donorProcessBirthId: string;
775
+ donorSessionGeneration: number;
776
+ donorAcquisitionId: string;
777
+ donorAcquiredAtMs: number;
778
+ handoffToken: string;
779
+ payload: TelegramQueueHandoffPayload;
780
+ sentAtMs: number;
781
+ }
782
+ | {
783
+ kind: "follower.resolveAgentTarget";
784
+ requestId: string;
785
+ instanceId: string;
786
+ registrationGeneration?: string;
787
+ selector: TelegramBusAgentTargetSelector;
788
+ sentAtMs: number;
789
+ }
790
+ | {
791
+ kind: "follower.routeAgentMessage";
792
+ requestId: string;
793
+ instanceId: string;
794
+ registrationGeneration?: string;
795
+ message: TelegramBusAgentMessage;
796
+ sentAtMs: number;
797
+ }
798
+ | {
799
+ kind: "follower.callApi";
800
+ requestId: string;
801
+ instanceId: string;
802
+ registrationGeneration?: string;
803
+ method: string;
804
+ args: unknown[];
805
+ sentAtMs: number;
806
+ }
807
+ | {
808
+ kind: "bus.ack";
809
+ requestId: string;
810
+ ok: boolean;
811
+ message?: string;
812
+ result?: unknown;
813
+ protocol?: TelegramBusProtocolIdentity;
814
+ error?: {
815
+ code:
816
+ | "commit-unknown"
817
+ | "request-id-collision"
818
+ | "ledger-overloaded"
819
+ | "incompatible-protocol";
820
+ method?: string;
821
+ };
822
+ }
823
+ ) & { auth?: string };
824
+
825
+ export type TelegramBusEnvelopeTrafficClass =
826
+ | "bootstrap"
827
+ | "generation-fenced"
828
+ | "response";
829
+
830
+ export function getTelegramBusEnvelopeTrafficClass(
831
+ envelope: TelegramBusEnvelope,
832
+ ): TelegramBusEnvelopeTrafficClass {
833
+ if (envelope.kind === "follower.register") return "bootstrap";
834
+ if (envelope.kind === "bus.ack") return "response";
835
+ return "generation-fenced";
836
+ }
837
+
838
+ export function createTelegramBusRequestId(input: {
839
+ instanceId: string;
840
+ sequence: number;
841
+ }): string {
842
+ return `${input.instanceId}:${input.sequence}`;
843
+ }
844
+
845
+ export function createTelegramBusRequestIdFactory(
846
+ instanceId: string,
847
+ ): () => string {
848
+ let sequence = 0;
849
+ return () => {
850
+ sequence += 1;
851
+ return createTelegramBusRequestId({ instanceId, sequence });
852
+ };
853
+ }
854
+
855
+ export function encodeTelegramBusEnvelope(
856
+ envelope: TelegramBusEnvelope,
857
+ ): string {
858
+ return `${JSON.stringify(envelope)}\n`;
859
+ }
860
+
861
+ export function parseTelegramBusEnvelope(
862
+ line: string,
863
+ ): TelegramBusEnvelope | undefined {
864
+ let value: unknown;
865
+ try {
866
+ value = JSON.parse(line);
867
+ } catch {
868
+ return undefined;
869
+ }
870
+ if (!isRecord(value)) return undefined;
871
+ const kind = value.kind;
872
+ const requestId = value.requestId;
873
+ if (typeof kind !== "string" || typeof requestId !== "string") {
874
+ return undefined;
875
+ }
876
+ let envelope: TelegramBusEnvelope | undefined;
877
+ switch (kind) {
878
+ case "follower.register":
879
+ envelope = parseRegisterEnvelope(value, requestId);
880
+ break;
881
+ case "follower.heartbeat":
882
+ envelope = parseHeartbeatEnvelope(value, requestId);
883
+ break;
884
+ case "follower.disconnect":
885
+ envelope = parseDisconnectEnvelope(value, requestId);
886
+ break;
887
+ case "leader.forwardCallback":
888
+ envelope = parseForwardCallbackEnvelope(value, requestId);
889
+ break;
890
+ case "leader.forwardReaction":
891
+ envelope = parseForwardReactionEnvelope(value, requestId);
892
+ break;
893
+ case "leader.forwardMessage":
894
+ envelope = parseForwardMessageEnvelope(
895
+ value,
896
+ requestId,
897
+ "leader.forwardMessage",
898
+ );
899
+ break;
900
+ case "leader.forwardEditedMessage":
901
+ envelope = parseForwardMessageEnvelope(
902
+ value,
903
+ requestId,
904
+ "leader.forwardEditedMessage",
905
+ );
906
+ break;
907
+ case "leader.replaceFollowerTarget":
908
+ envelope = parseReplaceFollowerTargetEnvelope(value, requestId);
909
+ break;
910
+ case "leader.offerQueueHandoff":
911
+ envelope = parseQueueHandoffEnvelope(
912
+ value,
913
+ requestId,
914
+ "leader.offerQueueHandoff",
915
+ );
916
+ break;
917
+ case "follower.offerQueueHandoff":
918
+ envelope = parseQueueHandoffEnvelope(
919
+ value,
920
+ requestId,
921
+ "follower.offerQueueHandoff",
922
+ );
923
+ break;
924
+ case "follower.resolveAgentTarget":
925
+ envelope = parseResolveAgentTargetEnvelope(value, requestId);
926
+ break;
927
+ case "follower.routeAgentMessage":
928
+ envelope = parseRouteAgentMessageEnvelope(value, requestId);
929
+ break;
930
+ case "follower.callApi":
931
+ envelope = parseCallApiEnvelope(value, requestId);
932
+ break;
933
+ case "bus.ack":
934
+ envelope = parseAckEnvelope(value, requestId);
935
+ break;
936
+ default:
937
+ return undefined;
938
+ }
939
+ const auth = value.auth;
940
+ if (envelope && typeof auth === "string") envelope.auth = auth;
941
+ return envelope;
942
+ }
943
+
944
+ export interface TelegramBusLocalServer {
945
+ start: () => Promise<void>;
946
+ stop: () => Promise<void>;
947
+ ensureEndpoint: () => Promise<boolean>;
948
+ }
949
+
950
+ export type TelegramBusSocketPathSource = string | (() => string);
951
+
952
+ const TELEGRAM_BUS_MAX_DIRECT_UNIX_ENDPOINT_BYTES = 80;
953
+
954
+ export function resolveTelegramBusSocketPath(
955
+ source: TelegramBusSocketPathSource,
956
+ platform: NodeJS.Platform | string = getPlatform(),
957
+ ): string {
958
+ const endpoint = typeof source === "function" ? source() : source;
959
+ if (platform === "win32") {
960
+ if (isTelegramBusPipePath(endpoint)) return endpoint;
961
+ return getTelegramBusPipePath({
962
+ agentDir: dirname(endpoint),
963
+ scope: basename(endpoint),
964
+ });
965
+ }
966
+ const ownerScope = process.getuid?.() ?? "user";
967
+ const fallbackDir = join(tmpdir(), `pi-telegram-${ownerScope}`);
968
+ if (
969
+ dirname(endpoint) === fallbackDir &&
970
+ /^[0-9a-f]{16}\.sock$/u.test(basename(endpoint))
971
+ ) {
972
+ return endpoint;
973
+ }
974
+ if (
975
+ Buffer.byteLength(endpoint) <= TELEGRAM_BUS_MAX_DIRECT_UNIX_ENDPOINT_BYTES
976
+ ) {
977
+ return endpoint;
978
+ }
979
+ const digest = createHash("sha256")
980
+ .update(endpoint)
981
+ .digest("hex")
982
+ .slice(0, 16);
983
+ return join(fallbackDir, `${digest}.sock`);
984
+ }
985
+
986
+ export interface TelegramBusLocalServerDeps {
987
+ socketPath: TelegramBusSocketPathSource;
988
+ handleEnvelope: (
989
+ envelope: TelegramBusEnvelope,
990
+ ) =>
991
+ Promise<TelegramBusEnvelope | undefined> | TelegramBusEnvelope | undefined;
992
+ recordTransportEvent?: TelegramBusTransportEventRecorder;
993
+ beforeEndpointPublication?: () => Promise<void> | void;
994
+ commitEndpointPublication?: (commit: () => void) => boolean;
995
+ requestLedgerMaxEntries?: number;
996
+ shouldDropResponse?: (
997
+ request: TelegramBusEnvelope,
998
+ response: TelegramBusEnvelope,
999
+ ) => boolean;
1000
+ }
1001
+
1002
+ export interface TelegramBusLocalClientOptions {
1003
+ socketPath: string;
1004
+ envelope: TelegramBusEnvelope;
1005
+ timeoutMs?: number;
1006
+ retry?: TelegramBusTransportRetryPolicy;
1007
+ recordTransportEvent?: TelegramBusTransportEventRecorder;
1008
+ }
1009
+
1010
+ export interface TelegramBusForeignOwnedForwarderDeps<TMessage = unknown> {
1011
+ socketPath: TelegramBusSocketPathSource;
1012
+ createRequestId: () => string;
1013
+ getNowMs?: () => number;
1014
+ timeoutMs?: number;
1015
+ getAuthSecret?: () => string | undefined;
1016
+ getForwardCommentBatchPosition?: (
1017
+ message: TMessage,
1018
+ ) => "comment" | "forward" | undefined;
1019
+ recordRuntimeEvent?: (
1020
+ category: string,
1021
+ error: unknown,
1022
+ details?: Record<string, unknown>,
1023
+ ) => void;
1024
+ }
1025
+
1026
+ interface TelegramBusForwardOwnership {
1027
+ instanceId: string;
1028
+ ownerGeneration?: string;
1029
+ recipientBindingKey?: string;
1030
+ }
1031
+
1032
+ type TelegramBusDurableForwardEnvelope = Extract<
1033
+ TelegramBusEnvelope,
1034
+ {
1035
+ kind:
1036
+ | "leader.forwardCallback"
1037
+ | "leader.forwardReaction"
1038
+ | "leader.forwardMessage"
1039
+ | "leader.forwardEditedMessage";
1040
+ }
1041
+ > & {
1042
+ recipientRegistrationGeneration: string;
1043
+ delivery: TelegramBusFollowerDeliveryIdentity;
1044
+ };
1045
+
1046
+ function getTelegramBusForwardSourceUpdateId(
1047
+ value: unknown,
1048
+ ): number | undefined {
1049
+ if (!isRecord(value)) return undefined;
1050
+ const updateId = value.pi_telegram_source_update_id;
1051
+ return Number.isSafeInteger(updateId) && (updateId as number) >= 0
1052
+ ? (updateId as number)
1053
+ : undefined;
1054
+ }
1055
+
1056
+ function createTelegramBusForwardDelivery(
1057
+ kind:
1058
+ | "leader.forwardCallback"
1059
+ | "leader.forwardReaction"
1060
+ | "leader.forwardMessage"
1061
+ | "leader.forwardEditedMessage",
1062
+ sourceUpdateId: number,
1063
+ recipientBindingKey: string,
1064
+ ): TelegramBusFollowerDeliveryIdentity {
1065
+ return createTelegramBusFollowerDeliveryIdentity({
1066
+ kind,
1067
+ recipientBindingKey,
1068
+ sourceUpdateId,
1069
+ });
1070
+ }
1071
+
1072
+ export function createTelegramBusForeignOwnedUpdateForwarder<
1073
+ TContext,
1074
+ TReactionUpdate,
1075
+ TCallbackQuery,
1076
+ TMessage = unknown,
1077
+ >(
1078
+ deps: TelegramBusForeignOwnedForwarderDeps<TMessage>,
1079
+ ): {
1080
+ forwardCallback: (input: {
1081
+ query: TCallbackQuery;
1082
+ ownership: TelegramBusForwardOwnership;
1083
+ ctx: TContext;
1084
+ }) => Promise<TelegramBusForeignUpdateSettlement>;
1085
+ forwardReaction: (input: {
1086
+ reactionUpdate: TReactionUpdate;
1087
+ ownership: TelegramBusForwardOwnership;
1088
+ ctx: TContext;
1089
+ }) => Promise<TelegramBusForeignUpdateSettlement>;
1090
+ forwardMessage: (input: {
1091
+ message: TMessage;
1092
+ ownership: TelegramBusForwardOwnership;
1093
+ ctx: TContext;
1094
+ }) => Promise<TelegramBusForeignUpdateSettlement>;
1095
+ forwardEditedMessage: (input: {
1096
+ message: TMessage;
1097
+ ownership: TelegramBusForwardOwnership;
1098
+ ctx: TContext;
1099
+ }) => Promise<TelegramBusForeignUpdateSettlement>;
1100
+ } {
1101
+ const getNowMs = deps.getNowMs ?? Date.now;
1102
+ const reject = (input: {
1103
+ status: "retryable" | "terminal-rejected";
1104
+ failureClass: TelegramBusForeignUpdateFailureClass;
1105
+ message: string;
1106
+ envelopeKind: TelegramBusDurableForwardEnvelope["kind"];
1107
+ ownership: TelegramBusForwardOwnership;
1108
+ delivery?: TelegramBusFollowerDeliveryIdentity;
1109
+ sourceUpdateId?: number;
1110
+ }): TelegramBusForeignUpdateSettlement => {
1111
+ const settlement: TelegramBusForeignUpdateSettlement = {
1112
+ status: input.status,
1113
+ failureClass: input.failureClass,
1114
+ message: input.message,
1115
+ ...(input.delivery ? { delivery: input.delivery } : {}),
1116
+ ...(input.sourceUpdateId !== undefined
1117
+ ? { sourceUpdateId: input.sourceUpdateId }
1118
+ : {}),
1119
+ };
1120
+ deps.recordRuntimeEvent?.("bus", input.message, {
1121
+ phase: "foreign-update-forward-rejected",
1122
+ settlement: input.status,
1123
+ failureClass: input.failureClass,
1124
+ envelopeKind: input.envelopeKind,
1125
+ recipientInstanceId: input.ownership.instanceId,
1126
+ deliveryId: input.delivery?.deliveryId,
1127
+ sourceUpdateId:
1128
+ input.delivery?.sourceUpdateId ?? input.sourceUpdateId,
1129
+ });
1130
+ return settlement;
1131
+ };
1132
+ const prepare = (
1133
+ kind: TelegramBusDurableForwardEnvelope["kind"],
1134
+ value: unknown,
1135
+ ownership: TelegramBusForwardOwnership,
1136
+ ):
1137
+ | {
1138
+ delivery: TelegramBusFollowerDeliveryIdentity;
1139
+ recipientRegistrationGeneration: string;
1140
+ }
1141
+ | { settlement: TelegramBusForeignUpdateSettlement } => {
1142
+ const sourceUpdateId = getTelegramBusForwardSourceUpdateId(value);
1143
+ if (sourceUpdateId === undefined) {
1144
+ return {
1145
+ settlement: reject({
1146
+ status: "terminal-rejected",
1147
+ failureClass: "source-update-identity-missing",
1148
+ message: "Forwarded Telegram update has no durable source identity.",
1149
+ envelopeKind: kind,
1150
+ ownership,
1151
+ }),
1152
+ };
1153
+ }
1154
+ if (!ownership.recipientBindingKey) {
1155
+ return {
1156
+ settlement: reject({
1157
+ status: "terminal-rejected",
1158
+ failureClass: "recipient-binding-missing",
1159
+ message: "Forwarded Telegram update has no stable recipient binding.",
1160
+ envelopeKind: kind,
1161
+ ownership,
1162
+ sourceUpdateId,
1163
+ }),
1164
+ };
1165
+ }
1166
+ const delivery = createTelegramBusForwardDelivery(
1167
+ kind,
1168
+ sourceUpdateId,
1169
+ ownership.recipientBindingKey,
1170
+ );
1171
+ if (!ownership.ownerGeneration) {
1172
+ return {
1173
+ settlement: reject({
1174
+ status: "retryable",
1175
+ failureClass: "recipient-generation-missing",
1176
+ message: "Forwarded Telegram update has no live recipient generation.",
1177
+ envelopeKind: kind,
1178
+ ownership,
1179
+ delivery,
1180
+ }),
1181
+ };
1182
+ }
1183
+ return {
1184
+ delivery,
1185
+ recipientRegistrationGeneration: ownership.ownerGeneration,
1186
+ };
1187
+ };
1188
+ const send = async (
1189
+ envelope: TelegramBusDurableForwardEnvelope,
1190
+ ownership: TelegramBusForwardOwnership,
1191
+ ): Promise<TelegramBusForeignUpdateSettlement> => {
1192
+ if (deps.getAuthSecret) envelope.auth = deps.getAuthSecret();
1193
+ const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
1194
+ let response: TelegramBusEnvelope | undefined;
1195
+ try {
1196
+ response = await sendTelegramBusLocalEnvelope({
1197
+ socketPath,
1198
+ envelope,
1199
+ timeoutMs: deps.timeoutMs,
1200
+ retry: getTelegramBusTransportRetryPolicy({
1201
+ endpoint: socketPath,
1202
+ operation: "operation",
1203
+ }),
1204
+ });
1205
+ } catch (error) {
1206
+ return reject({
1207
+ status: "retryable",
1208
+ failureClass: "transport-failed",
1209
+ message:
1210
+ error instanceof Error
1211
+ ? error.message
1212
+ : "Telegram follower forwarding transport failed.",
1213
+ envelopeKind: envelope.kind,
1214
+ ownership,
1215
+ delivery: envelope.delivery,
1216
+ });
1217
+ }
1218
+ if (response?.kind !== "bus.ack") {
1219
+ return reject({
1220
+ status: "retryable",
1221
+ failureClass: "acknowledgement-missing",
1222
+ message: "Follower returned no forwarding acknowledgement.",
1223
+ envelopeKind: envelope.kind,
1224
+ ownership,
1225
+ delivery: envelope.delivery,
1226
+ });
1227
+ }
1228
+ if (response.requestId !== envelope.requestId) {
1229
+ return reject({
1230
+ status: "terminal-rejected",
1231
+ failureClass: "acknowledgement-mismatched",
1232
+ message: "Follower returned a mismatched forwarding acknowledgement.",
1233
+ envelopeKind: envelope.kind,
1234
+ ownership,
1235
+ delivery: envelope.delivery,
1236
+ });
1237
+ }
1238
+ if (!response.ok) {
1239
+ return reject({
1240
+ status: "retryable",
1241
+ failureClass: "acknowledgement-rejected",
1242
+ message:
1243
+ response.message ?? "Follower rejected forwarded Telegram update.",
1244
+ envelopeKind: envelope.kind,
1245
+ ownership,
1246
+ delivery: envelope.delivery,
1247
+ });
1248
+ }
1249
+ const receipt = response.result;
1250
+ if (
1251
+ !isRecord(receipt) ||
1252
+ typeof receipt.deliveryId !== "string" ||
1253
+ !Number.isSafeInteger(receipt.sourceUpdateId)
1254
+ ) {
1255
+ return reject({
1256
+ status: "terminal-rejected",
1257
+ failureClass: "durable-receipt-missing",
1258
+ message: "Follower acknowledgement omitted the durable receipt.",
1259
+ envelopeKind: envelope.kind,
1260
+ ownership,
1261
+ delivery: envelope.delivery,
1262
+ });
1263
+ }
1264
+ if (
1265
+ receipt.deliveryId !== envelope.delivery.deliveryId ||
1266
+ receipt.sourceUpdateId !== envelope.delivery.sourceUpdateId
1267
+ ) {
1268
+ return reject({
1269
+ status: "terminal-rejected",
1270
+ failureClass: "durable-receipt-mismatched",
1271
+ message: "Follower acknowledgement returned a mismatched durable receipt.",
1272
+ envelopeKind: envelope.kind,
1273
+ ownership,
1274
+ delivery: envelope.delivery,
1275
+ });
1276
+ }
1277
+ return { status: "accepted", delivery: envelope.delivery };
1278
+ };
1279
+ return {
1280
+ forwardCallback: ({ query, ownership }) => {
1281
+ const prepared = prepare("leader.forwardCallback", query, ownership);
1282
+ if ("settlement" in prepared) return Promise.resolve(prepared.settlement);
1283
+ return send(
1284
+ {
1285
+ kind: "leader.forwardCallback",
1286
+ requestId: deps.createRequestId(),
1287
+ recipientInstanceId: ownership.instanceId,
1288
+ recipientRegistrationGeneration:
1289
+ prepared.recipientRegistrationGeneration,
1290
+ delivery: prepared.delivery,
1291
+ query,
1292
+ sentAtMs: getNowMs(),
1293
+ },
1294
+ ownership,
1295
+ );
1296
+ },
1297
+ forwardReaction: ({ reactionUpdate, ownership }) => {
1298
+ const prepared = prepare(
1299
+ "leader.forwardReaction",
1300
+ reactionUpdate,
1301
+ ownership,
1302
+ );
1303
+ if ("settlement" in prepared) return Promise.resolve(prepared.settlement);
1304
+ return send(
1305
+ {
1306
+ kind: "leader.forwardReaction",
1307
+ requestId: deps.createRequestId(),
1308
+ recipientInstanceId: ownership.instanceId,
1309
+ recipientRegistrationGeneration:
1310
+ prepared.recipientRegistrationGeneration,
1311
+ delivery: prepared.delivery,
1312
+ reactionUpdate,
1313
+ sentAtMs: getNowMs(),
1314
+ },
1315
+ ownership,
1316
+ );
1317
+ },
1318
+ forwardMessage: ({ message, ownership }) => {
1319
+ const prepared = prepare("leader.forwardMessage", message, ownership);
1320
+ if ("settlement" in prepared) return Promise.resolve(prepared.settlement);
1321
+ return send(
1322
+ {
1323
+ kind: "leader.forwardMessage",
1324
+ requestId: deps.createRequestId(),
1325
+ recipientInstanceId: ownership.instanceId,
1326
+ recipientRegistrationGeneration:
1327
+ prepared.recipientRegistrationGeneration,
1328
+ delivery: prepared.delivery,
1329
+ message,
1330
+ ...(deps.getForwardCommentBatchPosition?.(message) !== undefined
1331
+ ? {
1332
+ forwardCommentBatchPosition:
1333
+ deps.getForwardCommentBatchPosition(message),
1334
+ }
1335
+ : {}),
1336
+ sentAtMs: getNowMs(),
1337
+ },
1338
+ ownership,
1339
+ );
1340
+ },
1341
+ forwardEditedMessage: ({ message, ownership }) => {
1342
+ const prepared = prepare(
1343
+ "leader.forwardEditedMessage",
1344
+ message,
1345
+ ownership,
1346
+ );
1347
+ if ("settlement" in prepared) return Promise.resolve(prepared.settlement);
1348
+ return send(
1349
+ {
1350
+ kind: "leader.forwardEditedMessage",
1351
+ requestId: deps.createRequestId(),
1352
+ recipientInstanceId: ownership.instanceId,
1353
+ recipientRegistrationGeneration:
1354
+ prepared.recipientRegistrationGeneration,
1355
+ delivery: prepared.delivery,
1356
+ message,
1357
+ sentAtMs: getNowMs(),
1358
+ },
1359
+ ownership,
1360
+ );
1361
+ },
1362
+ };
1363
+ }
1364
+
1365
+ export interface TelegramBusFollowerThreadRestoreHandlerDeps {
1366
+ followerRegistry: Pick<TelegramBusFollowerRegistry, "get" | "register">;
1367
+ followerTargetController: ReturnType<
1368
+ typeof createTelegramBusFollowerTargetController
1369
+ >;
1370
+ onRestored?: () => void;
1371
+ }
1372
+
1373
+ export function listTelegramBusLiveThreadTargets(input: {
1374
+ leaderTarget?: TelegramTarget;
1375
+ followers: readonly TelegramBusFollowerView[];
1376
+ }): TelegramTarget[] {
1377
+ const targets: TelegramTarget[] = [];
1378
+ if (input.leaderTarget?.threadId !== undefined) {
1379
+ targets.push(input.leaderTarget);
1380
+ }
1381
+ for (const follower of input.followers) {
1382
+ if (follower.target?.threadId !== undefined) targets.push(follower.target);
1383
+ }
1384
+ return targets;
1385
+ }
1386
+
1387
+ export function createTelegramBusFollowerTargetController(
1388
+ deps: TelegramBusForeignOwnedForwarderDeps,
1389
+ ): {
1390
+ replaceTarget: (input: {
1391
+ follower: TelegramBusFollowerView;
1392
+ target: TelegramTarget & { threadId: number };
1393
+ oldTarget?: TelegramTarget & { threadId: number };
1394
+ reason: "thread-restore";
1395
+ }) => Promise<boolean>;
1396
+ } {
1397
+ const getNowMs = deps.getNowMs ?? Date.now;
1398
+ return {
1399
+ async replaceTarget({ follower, target, oldTarget, reason }) {
1400
+ if (!follower.busSocketPath || !follower.registrationGeneration) {
1401
+ return false;
1402
+ }
1403
+ const envelope: TelegramBusEnvelope = {
1404
+ kind: "leader.replaceFollowerTarget",
1405
+ requestId: deps.createRequestId(),
1406
+ recipientInstanceId: follower.instanceId,
1407
+ recipientRegistrationGeneration: follower.registrationGeneration,
1408
+ target,
1409
+ ...(oldTarget ? { oldTarget } : {}),
1410
+ reason,
1411
+ sentAtMs: getNowMs(),
1412
+ };
1413
+ if (deps.getAuthSecret) envelope.auth = deps.getAuthSecret();
1414
+ const response = await sendTelegramBusLocalEnvelope({
1415
+ socketPath: follower.busSocketPath,
1416
+ envelope,
1417
+ timeoutMs: deps.timeoutMs,
1418
+ retry: getTelegramBusTransportRetryPolicy({
1419
+ endpoint: follower.busSocketPath,
1420
+ operation: "operation",
1421
+ }),
1422
+ });
1423
+ return response?.kind === "bus.ack" && response.ok;
1424
+ },
1425
+ };
1426
+ }
1427
+
1428
+ export function createTelegramBusFollowerThreadRestoreHandler(
1429
+ deps: TelegramBusFollowerThreadRestoreHandlerDeps,
1430
+ ): (input: {
1431
+ record: { instanceId?: string };
1432
+ target: TelegramTarget & { threadId: number };
1433
+ oldTarget?: TelegramTarget & { threadId: number };
1434
+ }) => Promise<boolean> {
1435
+ return async ({ record, target, oldTarget }) => {
1436
+ if (!record.instanceId) return false;
1437
+ const follower = deps.followerRegistry.get(record.instanceId);
1438
+ if (!follower) return false;
1439
+ const replaced = await deps.followerTargetController.replaceTarget({
1440
+ follower,
1441
+ target,
1442
+ oldTarget,
1443
+ reason: "thread-restore",
1444
+ });
1445
+ if (!replaced) return false;
1446
+ deps.followerRegistry.register({
1447
+ ...follower,
1448
+ target,
1449
+ connectedAtMs: follower.connectedAtMs,
1450
+ });
1451
+ deps.onRestored?.();
1452
+ return true;
1453
+ };
1454
+ }
1455
+
1456
+ export function isTelegramBusEnvelopeAuthorized(
1457
+ envelope: TelegramBusEnvelope,
1458
+ secret: string | undefined,
1459
+ ): boolean {
1460
+ if (!secret) return true;
1461
+ if (typeof envelope.auth !== "string") return false;
1462
+ const auth = Buffer.from(envelope.auth);
1463
+ const expected = Buffer.from(secret);
1464
+ return auth.length === expected.length && timingSafeEqual(auth, expected);
1465
+ }
1466
+
1467
+ export function createUnauthorizedBusAck(
1468
+ requestId: string,
1469
+ ): TelegramBusEnvelope {
1470
+ return {
1471
+ kind: "bus.ack",
1472
+ requestId,
1473
+ ok: false,
1474
+ message: "Unauthorized Telegram bus envelope.",
1475
+ };
1476
+ }
1477
+
1478
+ export function createTelegramBusLocalServer(
1479
+ deps: TelegramBusLocalServerDeps,
1480
+ ): TelegramBusLocalServer {
1481
+ type LedgerEntry = {
1482
+ fingerprint: string;
1483
+ settled: boolean;
1484
+ result: Promise<TelegramBusEnvelope | undefined>;
1485
+ };
1486
+ const requestLedger = new Map<string, LedgerEntry>();
1487
+ const requestLedgerMaxEntries = Math.max(
1488
+ 1,
1489
+ deps.requestLedgerMaxEntries ?? 4096,
1490
+ );
1491
+ const getRequestLedgerKey = (envelope: TelegramBusEnvelope): string => {
1492
+ const identity =
1493
+ envelope.kind === "follower.register"
1494
+ ? envelope.registration.instanceId
1495
+ : "instanceId" in envelope
1496
+ ? envelope.instanceId
1497
+ : "recipientInstanceId" in envelope
1498
+ ? envelope.recipientInstanceId
1499
+ : "ack";
1500
+ return `${envelope.auth ?? ""}:${identity}:${envelope.requestId}`;
1501
+ };
1502
+ const handleEnvelopeOnce = (
1503
+ envelope: TelegramBusEnvelope,
1504
+ ): Promise<TelegramBusEnvelope | undefined> => {
1505
+ const key = getRequestLedgerKey(envelope);
1506
+ const fingerprint = JSON.stringify(envelope);
1507
+ const existing = requestLedger.get(key);
1508
+ if (existing) {
1509
+ if (existing.fingerprint === fingerprint) return existing.result;
1510
+ return Promise.resolve({
1511
+ kind: "bus.ack",
1512
+ requestId: envelope.requestId,
1513
+ ok: false,
1514
+ message: "Telegram bus request id was reused with a different payload.",
1515
+ error: { code: "request-id-collision" },
1516
+ });
1517
+ }
1518
+ if (requestLedger.size >= requestLedgerMaxEntries) {
1519
+ const settledKey = Array.from(requestLedger.entries()).find(
1520
+ ([, entry]) => entry.settled,
1521
+ )?.[0];
1522
+ if (settledKey) requestLedger.delete(settledKey);
1523
+ }
1524
+ if (requestLedger.size >= requestLedgerMaxEntries) {
1525
+ return Promise.resolve({
1526
+ kind: "bus.ack",
1527
+ requestId: envelope.requestId,
1528
+ ok: false,
1529
+ message: "Telegram bus request ledger is full.",
1530
+ error: { code: "ledger-overloaded" },
1531
+ });
1532
+ }
1533
+ const entry: LedgerEntry = {
1534
+ fingerprint,
1535
+ settled: false,
1536
+ result: Promise.resolve().then(() => deps.handleEnvelope(envelope)),
1537
+ };
1538
+ requestLedger.set(key, entry);
1539
+ void entry.result.then(
1540
+ () => {
1541
+ entry.settled = true;
1542
+ },
1543
+ () => {
1544
+ entry.settled = true;
1545
+ },
1546
+ );
1547
+ return entry.result;
1548
+ };
1549
+ let server: Server | undefined;
1550
+ let activeSocketPath: string | undefined;
1551
+ let activeListenPath: string | undefined;
1552
+ let endpointRecovery: Promise<boolean> | undefined;
1553
+ let stopGeneration = 0;
1554
+ const sockets = new Set<Socket>();
1555
+ const closeSocket = (socket: Socket) => {
1556
+ sockets.delete(socket);
1557
+ socket.destroy();
1558
+ };
1559
+ const runtime: TelegramBusLocalServer = {
1560
+ start: async () => {
1561
+ if (server) return;
1562
+ const socketPath = resolveTelegramBusSocketPath(deps.socketPath);
1563
+ const usesWindowsPipe = isTelegramBusPipePath(socketPath);
1564
+ const endpointGeneration = randomBytes(8).toString("hex");
1565
+ const listenPath = usesWindowsPipe
1566
+ ? socketPath
1567
+ : join(dirname(socketPath), `.pt-${endpointGeneration}.sock`);
1568
+ activeSocketPath = socketPath;
1569
+ activeListenPath = listenPath;
1570
+ deps.recordTransportEvent?.(
1571
+ "server-start",
1572
+ getTelegramBusEndpointDiagnostics(socketPath),
1573
+ );
1574
+ if (!usesWindowsPipe) {
1575
+ const socketDir = dirname(socketPath);
1576
+ mkdirSync(socketDir, { recursive: true, mode: 0o700 });
1577
+ chmodSync(socketDir, 0o700);
1578
+ if (existsSync(listenPath)) unlinkSync(listenPath);
1579
+ const legacyDeadlineMs = Date.now() + 2000;
1580
+ while (true) {
1581
+ let isLegacySocket = false;
1582
+ try {
1583
+ isLegacySocket = lstatSync(socketPath).isSocket();
1584
+ } catch {
1585
+ /* endpoint does not exist */
1586
+ }
1587
+ if (!isLegacySocket) break;
1588
+ const probe = await probeTelegramBusEndpoint({
1589
+ endpoint: socketPath,
1590
+ timeoutMs: 50,
1591
+ });
1592
+ if (!probe.reachable) break;
1593
+ if (Date.now() >= legacyDeadlineMs) {
1594
+ throw new Error(
1595
+ `Timed out waiting for legacy Telegram bus endpoint: ${socketPath}`,
1596
+ );
1597
+ }
1598
+ await delayTelegramBusTransportRetry(25);
1599
+ }
1600
+ }
1601
+ server = createServer((socket) => {
1602
+ sockets.add(socket);
1603
+ let buffer = "";
1604
+ socket.setEncoding("utf8");
1605
+ socket.on("data", (chunk) => {
1606
+ buffer += chunk;
1607
+ const lines = buffer.split("\n");
1608
+ buffer = lines.pop() ?? "";
1609
+ for (const line of lines) {
1610
+ void handleTelegramBusSocketLine(
1611
+ line,
1612
+ socket,
1613
+ handleEnvelopeOnce,
1614
+ deps.recordTransportEvent,
1615
+ socketPath,
1616
+ deps.shouldDropResponse,
1617
+ );
1618
+ }
1619
+ });
1620
+ socket.on("close", () => sockets.delete(socket));
1621
+ socket.on("error", (error) => {
1622
+ deps.recordTransportEvent?.("server-socket-error", {
1623
+ ...getTelegramBusEndpointDiagnostics(socketPath),
1624
+ ...classifyTelegramBusTransportError(error),
1625
+ });
1626
+ closeSocket(socket);
1627
+ });
1628
+ });
1629
+ try {
1630
+ await new Promise<void>((resolve, reject) => {
1631
+ server?.once("error", reject);
1632
+ server?.listen(listenPath, resolve);
1633
+ });
1634
+ deps.recordTransportEvent?.(
1635
+ "server-started",
1636
+ getTelegramBusEndpointDiagnostics(socketPath),
1637
+ );
1638
+ } catch (error) {
1639
+ server = undefined;
1640
+ activeSocketPath = undefined;
1641
+ activeListenPath = undefined;
1642
+ deps.recordTransportEvent?.("server-start-failed", {
1643
+ ...getTelegramBusEndpointDiagnostics(socketPath),
1644
+ ...classifyTelegramBusTransportError(error),
1645
+ });
1646
+ throw error;
1647
+ }
1648
+ if (!usesWindowsPipe) {
1649
+ chmodSync(listenPath, 0o600);
1650
+ const linkPath = `${socketPath}.link.${endpointGeneration}`;
1651
+ try {
1652
+ symlinkSync(basename(listenPath), linkPath);
1653
+ await deps.beforeEndpointPublication?.();
1654
+ const committed = deps.commitEndpointPublication
1655
+ ? deps.commitEndpointPublication(() =>
1656
+ renameSync(linkPath, socketPath),
1657
+ )
1658
+ : (renameSync(linkPath, socketPath), true);
1659
+ if (!committed) {
1660
+ throw new Error(
1661
+ "Telegram bus endpoint publication lost transport ownership.",
1662
+ );
1663
+ }
1664
+ } catch (error) {
1665
+ try {
1666
+ if (lstatSync(linkPath).isSymbolicLink()) unlinkSync(linkPath);
1667
+ } catch {
1668
+ /* no unpublished link to remove */
1669
+ }
1670
+ const failedServer = server;
1671
+ server = undefined;
1672
+ activeSocketPath = undefined;
1673
+ activeListenPath = undefined;
1674
+ if (failedServer) {
1675
+ await new Promise<void>((resolve) =>
1676
+ failedServer.close(() => resolve()),
1677
+ );
1678
+ }
1679
+ throw error;
1680
+ }
1681
+ }
1682
+ },
1683
+ stop: async () => {
1684
+ stopGeneration += 1;
1685
+ requestLedger.clear();
1686
+ const activeServer = server;
1687
+ const socketPath = activeSocketPath;
1688
+ const listenPath = activeListenPath;
1689
+ server = undefined;
1690
+ activeSocketPath = undefined;
1691
+ activeListenPath = undefined;
1692
+ for (const socket of sockets) closeSocket(socket);
1693
+ if (activeServer) {
1694
+ await new Promise<void>((resolve) =>
1695
+ activeServer.close(() => resolve()),
1696
+ );
1697
+ }
1698
+ if (socketPath && listenPath && !isTelegramBusPipePath(socketPath)) {
1699
+ try {
1700
+ if (
1701
+ lstatSync(socketPath).isSymbolicLink() &&
1702
+ readlinkSync(socketPath) === basename(listenPath)
1703
+ ) {
1704
+ // Leave the generation link in place. Node removes only the unique
1705
+ // listen path on close; a later generation atomically replaces this
1706
+ // link, and existsSync() treats the stopped dangling link as missing.
1707
+ }
1708
+ } catch {
1709
+ /* endpoint already moved or removed */
1710
+ }
1711
+ }
1712
+ if (socketPath) {
1713
+ deps.recordTransportEvent?.(
1714
+ "server-stopped",
1715
+ getTelegramBusEndpointDiagnostics(socketPath),
1716
+ );
1717
+ }
1718
+ },
1719
+ ensureEndpoint: async () => {
1720
+ const socketPath = activeSocketPath;
1721
+ if (
1722
+ !server ||
1723
+ !socketPath ||
1724
+ isTelegramBusPipePath(socketPath) ||
1725
+ existsSync(socketPath)
1726
+ ) {
1727
+ return false;
1728
+ }
1729
+ if (endpointRecovery) return endpointRecovery;
1730
+ endpointRecovery = (async () => {
1731
+ deps.recordTransportEvent?.(
1732
+ "server-endpoint-missing",
1733
+ getTelegramBusEndpointDiagnostics(socketPath),
1734
+ );
1735
+ const recoveryStopGeneration = stopGeneration + 1;
1736
+ await runtime.stop();
1737
+ if (stopGeneration !== recoveryStopGeneration) return false;
1738
+ await runtime.start();
1739
+ if (stopGeneration !== recoveryStopGeneration) {
1740
+ await runtime.stop();
1741
+ return false;
1742
+ }
1743
+ deps.recordTransportEvent?.(
1744
+ "server-endpoint-recovered",
1745
+ getTelegramBusEndpointDiagnostics(socketPath),
1746
+ );
1747
+ return true;
1748
+ })();
1749
+ try {
1750
+ return await endpointRecovery;
1751
+ } finally {
1752
+ endpointRecovery = undefined;
1753
+ }
1754
+ },
1755
+ };
1756
+ return runtime;
1757
+ }
1758
+
1759
+ function getTelegramBusEnvelopeDiagnostics(
1760
+ envelope: TelegramBusEnvelope,
1761
+ ): Record<string, unknown> {
1762
+ return {
1763
+ envelopeKind: envelope.kind,
1764
+ requestId: envelope.requestId,
1765
+ };
1766
+ }
1767
+
1768
+ function sendTelegramBusLocalEnvelopeOnce(
1769
+ options: TelegramBusLocalClientOptions,
1770
+ ): Promise<TelegramBusEnvelope | undefined> {
1771
+ const timeoutMs = options.timeoutMs ?? 1000;
1772
+ return new Promise((resolve, reject) => {
1773
+ const socket = createConnection(options.socketPath);
1774
+ let settled = false;
1775
+ let buffer = "";
1776
+ const settle = (callback: () => void) => {
1777
+ if (settled) return;
1778
+ settled = true;
1779
+ clearTimeout(timeout);
1780
+ socket.destroy();
1781
+ callback();
1782
+ };
1783
+ const timeout = setTimeout(() => {
1784
+ settle(() =>
1785
+ reject(
1786
+ createTelegramBusTransportTimeoutError(
1787
+ "Timed out waiting for Telegram bus response",
1788
+ ),
1789
+ ),
1790
+ );
1791
+ }, timeoutMs);
1792
+ socket.setEncoding("utf8");
1793
+ socket.once("connect", () => {
1794
+ socket.write(encodeTelegramBusEnvelope(options.envelope));
1795
+ });
1796
+ socket.on("data", (chunk) => {
1797
+ buffer += chunk;
1798
+ const newlineIndex = buffer.indexOf("\n");
1799
+ if (newlineIndex < 0) return;
1800
+ const line = buffer.slice(0, newlineIndex);
1801
+ settle(() => resolve(parseTelegramBusEnvelope(line)));
1802
+ });
1803
+ socket.once("error", (error) => settle(() => reject(error)));
1804
+ socket.once("end", () => settle(() => resolve(undefined)));
1805
+ });
1806
+ }
1807
+
1808
+ export async function sendTelegramBusLocalEnvelope(
1809
+ options: TelegramBusLocalClientOptions,
1810
+ ): Promise<TelegramBusEnvelope | undefined> {
1811
+ const resolvedOptions = {
1812
+ ...options,
1813
+ socketPath: resolveTelegramBusSocketPath(options.socketPath),
1814
+ };
1815
+ const attempts = Math.max(1, resolvedOptions.retry?.attempts ?? 1);
1816
+ const delayMs = Math.max(0, resolvedOptions.retry?.delayMs ?? 0);
1817
+ for (let attempt = 1; ; attempt += 1) {
1818
+ try {
1819
+ return await sendTelegramBusLocalEnvelopeOnce(resolvedOptions);
1820
+ } catch (error) {
1821
+ const info = classifyTelegramBusTransportError(error);
1822
+ resolvedOptions.recordTransportEvent?.("client-failed", {
1823
+ ...getTelegramBusEndpointDiagnostics(resolvedOptions.socketPath),
1824
+ ...getTelegramBusEnvelopeDiagnostics(resolvedOptions.envelope),
1825
+ attempt,
1826
+ attempts,
1827
+ ...info,
1828
+ });
1829
+ if (attempt >= attempts || !isRetryableTelegramBusTransportError(error)) {
1830
+ throw error;
1831
+ }
1832
+ resolvedOptions.recordTransportEvent?.("client-retry", {
1833
+ ...getTelegramBusEndpointDiagnostics(resolvedOptions.socketPath),
1834
+ ...getTelegramBusEnvelopeDiagnostics(resolvedOptions.envelope),
1835
+ attempt,
1836
+ attempts,
1837
+ delayMs,
1838
+ ...info,
1839
+ });
1840
+ await delayTelegramBusTransportRetry(delayMs);
1841
+ }
1842
+ }
1843
+ }
1844
+
1845
+ export interface TelegramBusFollowerRegistry {
1846
+ register: (
1847
+ registration: TelegramBusInstanceRegistration,
1848
+ ) => TelegramBusFollowerView;
1849
+ heartbeat: (
1850
+ instanceId: string,
1851
+ nowMs: number,
1852
+ ) => TelegramBusFollowerView | undefined;
1853
+ get: (instanceId: string) => TelegramBusFollowerView | undefined;
1854
+ getByTarget: (target: TelegramTarget) => TelegramBusFollowerView | undefined;
1855
+ list: () => TelegramBusFollowerView[];
1856
+ remove: (instanceId: string) => boolean;
1857
+ clear: () => void;
1858
+ pruneStale: (
1859
+ nowMs: number,
1860
+ staleAfterMs: number,
1861
+ ) => TelegramBusFollowerView[];
1862
+ }
1863
+
1864
+ export function createTelegramBusFollowerRegistry(): TelegramBusFollowerRegistry {
1865
+ const followers = new Map<string, TelegramBusFollowerView>();
1866
+ const clone = (
1867
+ follower: TelegramBusFollowerView,
1868
+ ): TelegramBusFollowerView => ({
1869
+ ...follower,
1870
+ target: follower.target ? { ...follower.target } : undefined,
1871
+ ...(follower.protocol
1872
+ ? {
1873
+ protocol: {
1874
+ ...follower.protocol,
1875
+ capabilities: [...follower.protocol.capabilities],
1876
+ },
1877
+ }
1878
+ : {}),
1879
+ });
1880
+ return {
1881
+ register: (registration) => {
1882
+ const existing = followers.get(registration.instanceId);
1883
+ for (const [instanceId, follower] of followers.entries()) {
1884
+ if (instanceId === registration.instanceId) continue;
1885
+ const sameProfile =
1886
+ registration.profileKey !== undefined &&
1887
+ registration.profileKey === follower.profileKey;
1888
+ const sameTarget =
1889
+ registration.target !== undefined &&
1890
+ follower.target?.chatId === registration.target.chatId &&
1891
+ follower.target.threadId === registration.target.threadId;
1892
+ if (sameProfile || sameTarget) followers.delete(instanceId);
1893
+ }
1894
+ const next: TelegramBusFollowerView = {
1895
+ ...registration,
1896
+ target: registration.target ? { ...registration.target } : undefined,
1897
+ ...(registration.protocol
1898
+ ? {
1899
+ protocol: {
1900
+ ...registration.protocol,
1901
+ capabilities: [...registration.protocol.capabilities],
1902
+ },
1903
+ }
1904
+ : {}),
1905
+ lastHeartbeatMs:
1906
+ existing?.lastHeartbeatMs ?? registration.connectedAtMs,
1907
+ };
1908
+ followers.set(registration.instanceId, next);
1909
+ return clone(next);
1910
+ },
1911
+ heartbeat: (instanceId, nowMs) => {
1912
+ const existing = followers.get(instanceId);
1913
+ if (!existing) return undefined;
1914
+ const next = { ...existing, lastHeartbeatMs: nowMs };
1915
+ followers.set(instanceId, next);
1916
+ return clone(next);
1917
+ },
1918
+ get: (instanceId) => {
1919
+ const existing = followers.get(instanceId);
1920
+ return existing ? clone(existing) : undefined;
1921
+ },
1922
+ getByTarget: (target) => {
1923
+ for (const follower of followers.values()) {
1924
+ if (
1925
+ follower.target?.chatId === target.chatId &&
1926
+ follower.target.threadId === target.threadId
1927
+ ) {
1928
+ return clone(follower);
1929
+ }
1930
+ }
1931
+ return undefined;
1932
+ },
1933
+ list: () => [...followers.values()].map(clone),
1934
+ remove: (instanceId) => followers.delete(instanceId),
1935
+ clear: () => followers.clear(),
1936
+ pruneStale: (nowMs, staleAfterMs) => {
1937
+ const removed: TelegramBusFollowerView[] = [];
1938
+ for (const [instanceId, follower] of followers.entries()) {
1939
+ if (nowMs - follower.lastHeartbeatMs <= staleAfterMs) continue;
1940
+ followers.delete(instanceId);
1941
+ removed.push(clone(follower));
1942
+ }
1943
+ return removed;
1944
+ },
1945
+ };
1946
+ }
1947
+
1948
+ async function handleTelegramBusSocketLine(
1949
+ line: string,
1950
+ socket: Socket,
1951
+ handleEnvelope: TelegramBusLocalServerDeps["handleEnvelope"],
1952
+ recordTransportEvent: TelegramBusTransportEventRecorder | undefined,
1953
+ socketPath: string,
1954
+ shouldDropResponse?: (
1955
+ request: TelegramBusEnvelope,
1956
+ response: TelegramBusEnvelope,
1957
+ ) => boolean,
1958
+ ): Promise<void> {
1959
+ const envelope = parseTelegramBusEnvelope(line);
1960
+ if (!envelope) {
1961
+ recordTransportEvent?.("server-invalid-envelope", {
1962
+ ...getTelegramBusEndpointDiagnostics(socketPath),
1963
+ byteLength: Buffer.byteLength(line),
1964
+ });
1965
+ socket.write(
1966
+ encodeTelegramBusEnvelope({
1967
+ kind: "bus.ack",
1968
+ requestId: "invalid",
1969
+ ok: false,
1970
+ message: "Invalid Telegram bus envelope.",
1971
+ }),
1972
+ );
1973
+ return;
1974
+ }
1975
+ try {
1976
+ const response = await handleEnvelope(envelope);
1977
+ if (response && !shouldDropResponse?.(envelope, response)) {
1978
+ socket.write(encodeTelegramBusEnvelope(response));
1979
+ }
1980
+ } catch (error) {
1981
+ recordTransportEvent?.("server-handler-failed", {
1982
+ ...getTelegramBusEndpointDiagnostics(socketPath),
1983
+ ...getTelegramBusEnvelopeDiagnostics(envelope),
1984
+ ...classifyTelegramBusTransportError(error),
1985
+ });
1986
+ socket.write(
1987
+ encodeTelegramBusEnvelope({
1988
+ kind: "bus.ack",
1989
+ requestId: envelope.requestId,
1990
+ ok: false,
1991
+ message: "Telegram bus handler failed.",
1992
+ }),
1993
+ );
1994
+ }
1995
+ }
1996
+
1997
+ function parseRegisterEnvelope(
1998
+ value: Record<string, unknown>,
1999
+ requestId: string,
2000
+ ): TelegramBusEnvelope | undefined {
2001
+ const registration = parseRegistration(value.registration);
2002
+ return registration
2003
+ ? { kind: "follower.register", requestId, registration }
2004
+ : undefined;
2005
+ }
2006
+
2007
+ function parseHeartbeatEnvelope(
2008
+ value: Record<string, unknown>,
2009
+ requestId: string,
2010
+ ): TelegramBusEnvelope | undefined {
2011
+ return typeof value.instanceId === "string" &&
2012
+ typeof value.sentAtMs === "number"
2013
+ ? {
2014
+ kind: "follower.heartbeat",
2015
+ requestId,
2016
+ instanceId: value.instanceId,
2017
+ ...(typeof value.registrationGeneration === "string"
2018
+ ? { registrationGeneration: value.registrationGeneration }
2019
+ : {}),
2020
+ sentAtMs: value.sentAtMs,
2021
+ }
2022
+ : undefined;
2023
+ }
2024
+
2025
+ function parseDisconnectEnvelope(
2026
+ value: Record<string, unknown>,
2027
+ requestId: string,
2028
+ ): TelegramBusEnvelope | undefined {
2029
+ return typeof value.instanceId === "string" &&
2030
+ typeof value.sentAtMs === "number"
2031
+ ? {
2032
+ kind: "follower.disconnect",
2033
+ requestId,
2034
+ instanceId: value.instanceId,
2035
+ ...(typeof value.registrationGeneration === "string"
2036
+ ? { registrationGeneration: value.registrationGeneration }
2037
+ : {}),
2038
+ sentAtMs: value.sentAtMs,
2039
+ }
2040
+ : undefined;
2041
+ }
2042
+
2043
+ function parseTelegramBusFollowerDeliveryIdentity(
2044
+ value: unknown,
2045
+ ): TelegramBusFollowerDeliveryIdentity | undefined {
2046
+ if (!isRecord(value)) return undefined;
2047
+ if (
2048
+ typeof value.deliveryId !== "string" ||
2049
+ !/^telegram-follower-v1-[a-f0-9]{64}$/u.test(value.deliveryId) ||
2050
+ !Number.isSafeInteger(value.sourceUpdateId) ||
2051
+ (value.sourceUpdateId as number) < 0 ||
2052
+ typeof value.recipientBindingKey !== "string" ||
2053
+ !value.recipientBindingKey
2054
+ ) {
2055
+ return undefined;
2056
+ }
2057
+ return {
2058
+ deliveryId: value.deliveryId,
2059
+ sourceUpdateId: value.sourceUpdateId as number,
2060
+ recipientBindingKey: value.recipientBindingKey,
2061
+ };
2062
+ }
2063
+
2064
+ function parseForwardCallbackEnvelope(
2065
+ value: Record<string, unknown>,
2066
+ requestId: string,
2067
+ ): TelegramBusEnvelope | undefined {
2068
+ const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
2069
+ return delivery &&
2070
+ typeof value.recipientInstanceId === "string" &&
2071
+ typeof value.recipientRegistrationGeneration === "string" &&
2072
+ typeof value.sentAtMs === "number"
2073
+ ? {
2074
+ kind: "leader.forwardCallback",
2075
+ requestId,
2076
+ recipientInstanceId: value.recipientInstanceId,
2077
+ recipientRegistrationGeneration: value.recipientRegistrationGeneration,
2078
+ delivery,
2079
+ query: value.query,
2080
+ sentAtMs: value.sentAtMs,
2081
+ }
2082
+ : undefined;
2083
+ }
2084
+
2085
+ function parseForwardReactionEnvelope(
2086
+ value: Record<string, unknown>,
2087
+ requestId: string,
2088
+ ): TelegramBusEnvelope | undefined {
2089
+ const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
2090
+ return delivery &&
2091
+ typeof value.recipientInstanceId === "string" &&
2092
+ typeof value.recipientRegistrationGeneration === "string" &&
2093
+ typeof value.sentAtMs === "number"
2094
+ ? {
2095
+ kind: "leader.forwardReaction",
2096
+ requestId,
2097
+ recipientInstanceId: value.recipientInstanceId,
2098
+ recipientRegistrationGeneration: value.recipientRegistrationGeneration,
2099
+ delivery,
2100
+ reactionUpdate: value.reactionUpdate,
2101
+ sentAtMs: value.sentAtMs,
2102
+ }
2103
+ : undefined;
2104
+ }
2105
+
2106
+ function parseForwardMessageEnvelope(
2107
+ value: Record<string, unknown>,
2108
+ requestId: string,
2109
+ kind: "leader.forwardMessage" | "leader.forwardEditedMessage",
2110
+ ): TelegramBusEnvelope | undefined {
2111
+ const delivery = parseTelegramBusFollowerDeliveryIdentity(value.delivery);
2112
+ return delivery &&
2113
+ typeof value.recipientInstanceId === "string" &&
2114
+ typeof value.recipientRegistrationGeneration === "string" &&
2115
+ typeof value.sentAtMs === "number"
2116
+ ? {
2117
+ kind,
2118
+ requestId,
2119
+ recipientInstanceId: value.recipientInstanceId,
2120
+ recipientRegistrationGeneration: value.recipientRegistrationGeneration,
2121
+ delivery,
2122
+ message: value.message,
2123
+ ...(kind === "leader.forwardMessage" &&
2124
+ (value.forwardCommentBatchPosition === "comment" ||
2125
+ value.forwardCommentBatchPosition === "forward")
2126
+ ? {
2127
+ forwardCommentBatchPosition: value.forwardCommentBatchPosition,
2128
+ }
2129
+ : {}),
2130
+ sentAtMs: value.sentAtMs,
2131
+ }
2132
+ : undefined;
2133
+ }
2134
+
2135
+ function parseReplaceFollowerTargetEnvelope(
2136
+ value: Record<string, unknown>,
2137
+ requestId: string,
2138
+ ): TelegramBusEnvelope | undefined {
2139
+ const target = parseThreadTarget(value.target);
2140
+ const oldTarget = parseThreadTarget(value.oldTarget);
2141
+ if (
2142
+ typeof value.recipientInstanceId !== "string" ||
2143
+ !target ||
2144
+ (value.oldTarget !== undefined && !oldTarget) ||
2145
+ value.reason !== "thread-restore" ||
2146
+ typeof value.sentAtMs !== "number"
2147
+ ) {
2148
+ return undefined;
2149
+ }
2150
+ return {
2151
+ kind: "leader.replaceFollowerTarget",
2152
+ requestId,
2153
+ recipientInstanceId: value.recipientInstanceId,
2154
+ ...(typeof value.recipientRegistrationGeneration === "string"
2155
+ ? {
2156
+ recipientRegistrationGeneration:
2157
+ value.recipientRegistrationGeneration,
2158
+ }
2159
+ : {}),
2160
+ target,
2161
+ ...(oldTarget ? { oldTarget } : {}),
2162
+ reason: value.reason,
2163
+ sentAtMs: value.sentAtMs,
2164
+ };
2165
+ }
2166
+
2167
+ function parseQueueAdmissionReceipt(
2168
+ value: unknown,
2169
+ queueKind: "prompt" | "control",
2170
+ ):
2171
+ | {
2172
+ queueKind: "prompt" | "control";
2173
+ receiptId: string;
2174
+ sourceUpdateIds: number[];
2175
+ }
2176
+ | undefined {
2177
+ if (!isRecord(value) || value.queueKind !== queueKind) return undefined;
2178
+ const sourceUpdateIds = Array.isArray(value.sourceUpdateIds)
2179
+ ? value.sourceUpdateIds
2180
+ : undefined;
2181
+ if (
2182
+ typeof value.receiptId !== "string" ||
2183
+ !value.receiptId ||
2184
+ (value.journalBindingKey !== undefined &&
2185
+ (typeof value.journalBindingKey !== "string" ||
2186
+ !value.journalBindingKey.trim())) ||
2187
+ !sourceUpdateIds ||
2188
+ sourceUpdateIds.length === 0 ||
2189
+ sourceUpdateIds.some(
2190
+ (updateId, index) =>
2191
+ !Number.isSafeInteger(updateId) ||
2192
+ (updateId as number) < 0 ||
2193
+ (index > 0 &&
2194
+ (updateId as number) <=
2195
+ (sourceUpdateIds[index - 1] as number)),
2196
+ )
2197
+ ) {
2198
+ return undefined;
2199
+ }
2200
+ return {
2201
+ queueKind,
2202
+ receiptId: value.receiptId,
2203
+ sourceUpdateIds: sourceUpdateIds as number[],
2204
+ ...(typeof value.journalBindingKey === "string"
2205
+ ? { journalBindingKey: value.journalBindingKey }
2206
+ : {}),
2207
+ };
2208
+ }
2209
+
2210
+ function parseQueueHandoffPayload(
2211
+ value: unknown,
2212
+ ): TelegramQueueHandoffPayload | undefined {
2213
+ if (!isRecord(value) || (value.kind !== "prompt" && value.kind !== "control")) {
2214
+ return undefined;
2215
+ }
2216
+ const queueKind = value.kind;
2217
+ const target = parseTarget(value.target);
2218
+ const transportStamp = isRecord(value.transportStamp) &&
2219
+ typeof value.transportStamp.profile === "string" &&
2220
+ typeof value.transportStamp.generation === "string"
2221
+ ? {
2222
+ profile: value.transportStamp.profile,
2223
+ generation: value.transportStamp.generation,
2224
+ }
2225
+ : undefined;
2226
+ if (
2227
+ !Number.isSafeInteger(value.chatId) ||
2228
+ (value.target !== undefined && !target) ||
2229
+ (value.transportStamp !== undefined && !transportStamp) ||
2230
+ !Number.isSafeInteger(value.replyToMessageId) ||
2231
+ (value.guestQueryId !== undefined &&
2232
+ typeof value.guestQueryId !== "string") ||
2233
+ !Number.isSafeInteger(value.queueOrder) ||
2234
+ (value.queueLane !== "control" &&
2235
+ value.queueLane !== "priority" &&
2236
+ value.queueLane !== "default") ||
2237
+ !Number.isSafeInteger(value.laneOrder) ||
2238
+ typeof value.statusSummary !== "string" ||
2239
+ !Array.isArray(value.admissionReceipts)
2240
+ ) {
2241
+ return undefined;
2242
+ }
2243
+ const admissionReceipts = value.admissionReceipts.map((receipt) =>
2244
+ parseQueueAdmissionReceipt(receipt, queueKind),
2245
+ );
2246
+ if (
2247
+ admissionReceipts.length === 0 ||
2248
+ admissionReceipts.length > TELEGRAM_QUEUE_HANDOFF_MAX_RECEIPTS ||
2249
+ admissionReceipts.some((receipt) => receipt === undefined)
2250
+ ) {
2251
+ return undefined;
2252
+ }
2253
+ const queueLane = value.queueLane as "control" | "priority" | "default";
2254
+ const base = {
2255
+ chatId: value.chatId as number,
2256
+ ...(target ? { target } : {}),
2257
+ ...(transportStamp ? { transportStamp } : {}),
2258
+ replyToMessageId: value.replyToMessageId as number,
2259
+ ...(typeof value.guestQueryId === "string"
2260
+ ? { guestQueryId: value.guestQueryId }
2261
+ : {}),
2262
+ queueOrder: value.queueOrder as number,
2263
+ queueLane,
2264
+ laneOrder: value.laneOrder as number,
2265
+ statusSummary: value.statusSummary,
2266
+ admissionReceipts: admissionReceipts as NonNullable<
2267
+ (typeof admissionReceipts)[number]
2268
+ >[],
2269
+ };
2270
+ if (queueKind === "control") {
2271
+ if (
2272
+ value.queueLane !== "control" ||
2273
+ (value.controlType !== "status" && value.controlType !== "model")
2274
+ ) {
2275
+ return undefined;
2276
+ }
2277
+ return { kind: "control", controlType: value.controlType, ...base };
2278
+ }
2279
+ if (
2280
+ value.queueLane === "control" ||
2281
+ !Array.isArray(value.sourceMessageIds) ||
2282
+ value.sourceMessageIds.some((id) => !Number.isSafeInteger(id)) ||
2283
+ !Array.isArray(value.queuedAttachments) ||
2284
+ value.queuedAttachments.some(
2285
+ (attachment) =>
2286
+ !isRecord(attachment) ||
2287
+ typeof attachment.path !== "string" ||
2288
+ typeof attachment.fileName !== "string",
2289
+ ) ||
2290
+ !Array.isArray(value.content) ||
2291
+ value.content.some(
2292
+ (content) =>
2293
+ !isRecord(content) ||
2294
+ (content.type === "text"
2295
+ ? typeof content.text !== "string"
2296
+ : content.type === "image"
2297
+ ? typeof content.data !== "string" ||
2298
+ typeof content.mimeType !== "string"
2299
+ : true),
2300
+ ) ||
2301
+ typeof value.historyText !== "string" ||
2302
+ (value.priorityEmoji !== undefined &&
2303
+ typeof value.priorityEmoji !== "string") ||
2304
+ (value.reactionSuppressionEmoji !== undefined &&
2305
+ typeof value.reactionSuppressionEmoji !== "string") ||
2306
+ (value.voiceReplyPreferred !== undefined &&
2307
+ typeof value.voiceReplyPreferred !== "boolean") ||
2308
+ (value.voiceReplyRequired !== undefined &&
2309
+ typeof value.voiceReplyRequired !== "boolean")
2310
+ ) {
2311
+ return undefined;
2312
+ }
2313
+ return {
2314
+ kind: "prompt",
2315
+ ...base,
2316
+ sourceMessageIds: value.sourceMessageIds as number[],
2317
+ queuedAttachments: value.queuedAttachments as Array<{
2318
+ path: string;
2319
+ fileName: string;
2320
+ }>,
2321
+ content: value.content as Array<
2322
+ | { type: "text"; text: string }
2323
+ | { type: "image"; data: string; mimeType: string }
2324
+ >,
2325
+ historyText: value.historyText,
2326
+ ...(typeof value.priorityEmoji === "string"
2327
+ ? { priorityEmoji: value.priorityEmoji }
2328
+ : {}),
2329
+ ...(typeof value.reactionSuppressionEmoji === "string"
2330
+ ? { reactionSuppressionEmoji: value.reactionSuppressionEmoji }
2331
+ : {}),
2332
+ ...(typeof value.voiceReplyPreferred === "boolean"
2333
+ ? { voiceReplyPreferred: value.voiceReplyPreferred }
2334
+ : {}),
2335
+ ...(typeof value.voiceReplyRequired === "boolean"
2336
+ ? { voiceReplyRequired: value.voiceReplyRequired }
2337
+ : {}),
2338
+ };
2339
+ }
2340
+
2341
+ function parseQueueHandoffEnvelope(
2342
+ value: Record<string, unknown>,
2343
+ requestId: string,
2344
+ kind: "leader.offerQueueHandoff" | "follower.offerQueueHandoff",
2345
+ ): TelegramBusEnvelope | undefined {
2346
+ let serializedPayloadBytes: number;
2347
+ try {
2348
+ serializedPayloadBytes = Buffer.byteLength(JSON.stringify(value.payload));
2349
+ } catch {
2350
+ return undefined;
2351
+ }
2352
+ if (serializedPayloadBytes > TELEGRAM_QUEUE_HANDOFF_PAYLOAD_MAX_BYTES) {
2353
+ return undefined;
2354
+ }
2355
+ const payload = parseQueueHandoffPayload(value.payload);
2356
+ if (
2357
+ !payload ||
2358
+ typeof value.recipientInstanceId !== "string" ||
2359
+ typeof value.recipientRegistrationGeneration !== "string" ||
2360
+ !Number.isSafeInteger(value.donorProcessId) ||
2361
+ (value.donorProcessId as number) <= 0 ||
2362
+ typeof value.donorProcessBirthId !== "string" ||
2363
+ !value.donorProcessBirthId ||
2364
+ !Number.isSafeInteger(value.donorSessionGeneration) ||
2365
+ (value.donorSessionGeneration as number) <= 0 ||
2366
+ typeof value.donorAcquisitionId !== "string" ||
2367
+ !value.donorAcquisitionId ||
2368
+ !Number.isSafeInteger(value.donorAcquiredAtMs) ||
2369
+ (value.donorAcquiredAtMs as number) < 0 ||
2370
+ typeof value.handoffToken !== "string" ||
2371
+ value.handoffToken.length < 32 ||
2372
+ value.handoffToken.length > 256 ||
2373
+ value.donorProcessBirthId.length > 256 ||
2374
+ value.donorAcquisitionId.length > 256 ||
2375
+ typeof value.sentAtMs !== "number"
2376
+ ) {
2377
+ return undefined;
2378
+ }
2379
+ const fields = {
2380
+ requestId,
2381
+ recipientInstanceId: value.recipientInstanceId,
2382
+ recipientRegistrationGeneration: value.recipientRegistrationGeneration,
2383
+ donorProcessId: value.donorProcessId as number,
2384
+ donorProcessBirthId: value.donorProcessBirthId,
2385
+ donorSessionGeneration: value.donorSessionGeneration as number,
2386
+ donorAcquisitionId: value.donorAcquisitionId,
2387
+ donorAcquiredAtMs: value.donorAcquiredAtMs as number,
2388
+ handoffToken: value.handoffToken,
2389
+ payload,
2390
+ sentAtMs: value.sentAtMs,
2391
+ };
2392
+ if (kind === "leader.offerQueueHandoff") {
2393
+ return typeof value.donorInstanceId === "string"
2394
+ ? { kind, donorInstanceId: value.donorInstanceId, ...fields }
2395
+ : undefined;
2396
+ }
2397
+ return typeof value.instanceId === "string" &&
2398
+ typeof value.registrationGeneration === "string"
2399
+ ? {
2400
+ kind,
2401
+ instanceId: value.instanceId,
2402
+ registrationGeneration: value.registrationGeneration,
2403
+ ...fields,
2404
+ }
2405
+ : undefined;
2406
+ }
2407
+
2408
+ function parseResolveAgentTargetEnvelope(
2409
+ value: Record<string, unknown>,
2410
+ requestId: string,
2411
+ ): TelegramBusEnvelope | undefined {
2412
+ const selectorValue = isRecord(value.selector) ? value.selector : undefined;
2413
+ if (
2414
+ typeof value.instanceId !== "string" ||
2415
+ !selectorValue ||
2416
+ typeof value.sentAtMs !== "number"
2417
+ ) {
2418
+ return undefined;
2419
+ }
2420
+ const chatId =
2421
+ typeof selectorValue.chatId === "number" &&
2422
+ Number.isInteger(selectorValue.chatId)
2423
+ ? selectorValue.chatId
2424
+ : undefined;
2425
+ const threadId =
2426
+ typeof selectorValue.threadId === "number" &&
2427
+ Number.isInteger(selectorValue.threadId) &&
2428
+ selectorValue.threadId > 0
2429
+ ? selectorValue.threadId
2430
+ : undefined;
2431
+ const threadName =
2432
+ typeof selectorValue.threadName === "string" &&
2433
+ selectorValue.threadName.trim()
2434
+ ? selectorValue.threadName.trim()
2435
+ : undefined;
2436
+ if ((threadId === undefined) === (threadName === undefined)) return undefined;
2437
+ return {
2438
+ kind: "follower.resolveAgentTarget",
2439
+ requestId,
2440
+ instanceId: value.instanceId,
2441
+ ...(typeof value.registrationGeneration === "string"
2442
+ ? { registrationGeneration: value.registrationGeneration }
2443
+ : {}),
2444
+ selector: {
2445
+ ...(chatId !== undefined ? { chatId } : {}),
2446
+ ...(threadId !== undefined ? { threadId } : {}),
2447
+ ...(threadName !== undefined ? { threadName } : {}),
2448
+ },
2449
+ sentAtMs: value.sentAtMs,
2450
+ };
2451
+ }
2452
+
2453
+ function parseRouteAgentMessageEnvelope(
2454
+ value: Record<string, unknown>,
2455
+ requestId: string,
2456
+ ): TelegramBusEnvelope | undefined {
2457
+ const messageValue = isRecord(value.message) ? value.message : undefined;
2458
+ const target = parseThreadTarget(messageValue?.target);
2459
+ if (
2460
+ typeof value.instanceId !== "string" ||
2461
+ !messageValue ||
2462
+ !target ||
2463
+ typeof messageValue.messageId !== "number" ||
2464
+ !Number.isInteger(messageValue.messageId) ||
2465
+ messageValue.messageId <= 0 ||
2466
+ typeof messageValue.text !== "string" ||
2467
+ !messageValue.text.trim() ||
2468
+ typeof value.sentAtMs !== "number"
2469
+ ) {
2470
+ return undefined;
2471
+ }
2472
+ return {
2473
+ kind: "follower.routeAgentMessage",
2474
+ requestId,
2475
+ instanceId: value.instanceId,
2476
+ ...(typeof value.registrationGeneration === "string"
2477
+ ? { registrationGeneration: value.registrationGeneration }
2478
+ : {}),
2479
+ message: {
2480
+ target,
2481
+ messageId: messageValue.messageId,
2482
+ text: messageValue.text,
2483
+ },
2484
+ sentAtMs: value.sentAtMs,
2485
+ };
2486
+ }
2487
+
2488
+ function parseCallApiEnvelope(
2489
+ value: Record<string, unknown>,
2490
+ requestId: string,
2491
+ ): TelegramBusEnvelope | undefined {
2492
+ return typeof value.instanceId === "string" &&
2493
+ typeof value.method === "string" &&
2494
+ Array.isArray(value.args) &&
2495
+ typeof value.sentAtMs === "number"
2496
+ ? {
2497
+ kind: "follower.callApi",
2498
+ requestId,
2499
+ instanceId: value.instanceId,
2500
+ ...(typeof value.registrationGeneration === "string"
2501
+ ? { registrationGeneration: value.registrationGeneration }
2502
+ : {}),
2503
+ method: value.method,
2504
+ args: value.args,
2505
+ sentAtMs: value.sentAtMs,
2506
+ }
2507
+ : undefined;
2508
+ }
2509
+
2510
+ function parseAckEnvelope(
2511
+ value: Record<string, unknown>,
2512
+ requestId: string,
2513
+ ): TelegramBusEnvelope | undefined {
2514
+ if (typeof value.ok !== "boolean") return undefined;
2515
+ const envelope: TelegramBusEnvelope = {
2516
+ kind: "bus.ack",
2517
+ requestId,
2518
+ ok: value.ok,
2519
+ message: typeof value.message === "string" ? value.message : undefined,
2520
+ };
2521
+ if (Object.hasOwn(value, "result")) envelope.result = value.result;
2522
+ const protocol = parseTelegramBusProtocolIdentity(value.protocol);
2523
+ if (protocol) envelope.protocol = protocol;
2524
+ if (isRecord(value.error)) {
2525
+ const code = value.error.code;
2526
+ if (
2527
+ code === "commit-unknown" ||
2528
+ code === "request-id-collision" ||
2529
+ code === "ledger-overloaded" ||
2530
+ code === "incompatible-protocol"
2531
+ ) {
2532
+ envelope.error = {
2533
+ code,
2534
+ ...(typeof value.error.method === "string"
2535
+ ? { method: value.error.method }
2536
+ : {}),
2537
+ };
2538
+ }
2539
+ }
2540
+ return envelope;
2541
+ }
2542
+
2543
+ function parseTelegramBusProtocolIdentity(
2544
+ value: unknown,
2545
+ ): TelegramBusProtocolIdentity | undefined {
2546
+ if (!isRecord(value)) return undefined;
2547
+ if (
2548
+ !Number.isSafeInteger(value.protocolVersion) ||
2549
+ (value.protocolVersion as number) <= 0 ||
2550
+ typeof value.runtimeBuild !== "string" ||
2551
+ !value.runtimeBuild.trim() ||
2552
+ value.runtimeBuild !== value.runtimeBuild.trim() ||
2553
+ value.runtimeBuild.length > 128 ||
2554
+ !Array.isArray(value.capabilities) ||
2555
+ value.capabilities.length > 32 ||
2556
+ value.capabilities.some(
2557
+ (capability) =>
2558
+ typeof capability !== "string" ||
2559
+ capability.length > 128 ||
2560
+ !/^[a-z0-9][a-z0-9._-]*$/u.test(capability),
2561
+ )
2562
+ ) {
2563
+ return undefined;
2564
+ }
2565
+ const capabilities = value.capabilities as string[];
2566
+ if (
2567
+ capabilities.some(
2568
+ (capability, index) =>
2569
+ index > 0 && capability <= capabilities[index - 1]!,
2570
+ )
2571
+ ) {
2572
+ return undefined;
2573
+ }
2574
+ return {
2575
+ protocolVersion: value.protocolVersion as number,
2576
+ runtimeBuild: value.runtimeBuild,
2577
+ capabilities: [...capabilities],
2578
+ };
2579
+ }
2580
+
2581
+ function parseRegistration(
2582
+ value: unknown,
2583
+ ): TelegramBusInstanceRegistration | undefined {
2584
+ if (!isRecord(value)) return undefined;
2585
+ if (typeof value.instanceId !== "string") return undefined;
2586
+ if (typeof value.connectedAtMs !== "number") return undefined;
2587
+ const target = parseTarget(value.target);
2588
+ if (value.target !== undefined && !target) return undefined;
2589
+ const registration: TelegramBusInstanceRegistration = {
2590
+ instanceId: value.instanceId,
2591
+ connectedAtMs: value.connectedAtMs,
2592
+ };
2593
+ if (typeof value.previousInstanceId === "string") {
2594
+ registration.previousInstanceId = value.previousInstanceId;
2595
+ }
2596
+ if (typeof value.profileKey === "string")
2597
+ registration.profileKey = value.profileKey;
2598
+ if (typeof value.threadName === "string")
2599
+ registration.threadName = value.threadName;
2600
+ if (typeof value.slot === "string" && /^[A-Z]$/.test(value.slot)) {
2601
+ registration.slot = value.slot;
2602
+ }
2603
+ if (typeof value.cwd === "string") registration.cwd = value.cwd;
2604
+ if (typeof value.pid === "number") registration.pid = value.pid;
2605
+ if (typeof value.busSocketPath === "string") {
2606
+ registration.busSocketPath = value.busSocketPath;
2607
+ }
2608
+ if (typeof value.registrationGeneration === "string") {
2609
+ registration.registrationGeneration = value.registrationGeneration;
2610
+ }
2611
+ if (
2612
+ Number.isSafeInteger(value.sessionGeneration) &&
2613
+ (value.sessionGeneration as number) > 0
2614
+ ) {
2615
+ registration.sessionGeneration = value.sessionGeneration as number;
2616
+ }
2617
+ if (typeof value.processBirthId === "string" && value.processBirthId) {
2618
+ registration.processBirthId = value.processBirthId;
2619
+ }
2620
+ const protocol = parseTelegramBusProtocolIdentity(value.protocol);
2621
+ if (protocol) registration.protocol = protocol;
2622
+ if (target) registration.target = target;
2623
+ return registration;
2624
+ }
2625
+
2626
+ function parseTarget(value: unknown): TelegramTarget | undefined {
2627
+ if (value === undefined) return undefined;
2628
+ if (!isRecord(value) || typeof value.chatId !== "number") return undefined;
2629
+ return typeof value.threadId === "number"
2630
+ ? { chatId: value.chatId, threadId: value.threadId }
2631
+ : { chatId: value.chatId };
2632
+ }
2633
+
2634
+ function parseThreadTarget(
2635
+ value: unknown,
2636
+ ): (TelegramTarget & { threadId: number }) | undefined {
2637
+ const target = parseTarget(value);
2638
+ return target && typeof target.threadId === "number"
2639
+ ? { chatId: target.chatId, threadId: target.threadId }
2640
+ : undefined;
2641
+ }
2642
+
2643
+ function isRecord(value: unknown): value is Record<string, unknown> {
2644
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2645
+ }