@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,1588 @@
1
+ /**
2
+ * Telegram polling runtime domain helpers
3
+ * Zones: telegram transport, polling runtime
4
+ * Owns polling request builders, stop conditions, and the long-poll loop runtime for Telegram updates
5
+ */
6
+
7
+ type MaybePromise<T> = T | Promise<T>;
8
+
9
+ export interface TelegramPollingConfig {
10
+ botToken?: string;
11
+ lastUpdateId?: number;
12
+ }
13
+
14
+ export interface TelegramUpdate {
15
+ update_id: number;
16
+ }
17
+
18
+ const TELEGRAM_INITIAL_SYNC_OFFSET = -1;
19
+ const TELEGRAM_INITIAL_SYNC_LIMIT = 1;
20
+ const TELEGRAM_INITIAL_SYNC_TIMEOUT_SECONDS = 0;
21
+ const TELEGRAM_LONG_POLL_LIMIT = 10;
22
+ const TELEGRAM_LONG_POLL_TIMEOUT_SECONDS = 30;
23
+ const TELEGRAM_THREAD_CAPABILITY_MONITOR_INTERVAL_MS = 2_500;
24
+ const TELEGRAM_THREAD_CAPABILITY_DISABLED_CONFIRMATION_PROBES = 2;
25
+ const TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT = 3;
26
+ const TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_MS = 1_000;
27
+ const TELEGRAM_GET_UPDATES_CONFLICT_SLOW_RETRY_MS = 3_000;
28
+ const TELEGRAM_POLLING_RETRY_MS = 3_000;
29
+ export const TELEGRAM_GET_UPDATES_GRACE_MS = 10_000;
30
+
31
+ // Standard Telegram DM polling does not expose ordinary message-deletion events,
32
+ // so queue removal stays reaction-driven while delete-like business updates remain defensive-only.
33
+ export const TELEGRAM_ALLOWED_UPDATES = [
34
+ "message",
35
+ "edited_message",
36
+ "callback_query",
37
+ "message_reaction",
38
+ "guest_message",
39
+ ] as const;
40
+
41
+ export function buildTelegramInitialSyncRequest(): {
42
+ offset: number;
43
+ limit: number;
44
+ timeout: number;
45
+ } {
46
+ return {
47
+ offset: TELEGRAM_INITIAL_SYNC_OFFSET,
48
+ limit: TELEGRAM_INITIAL_SYNC_LIMIT,
49
+ timeout: TELEGRAM_INITIAL_SYNC_TIMEOUT_SECONDS,
50
+ };
51
+ }
52
+
53
+ export function buildTelegramLongPollRequest(lastUpdateId?: number): {
54
+ offset?: number;
55
+ limit: number;
56
+ timeout: number;
57
+ allowed_updates: readonly string[];
58
+ } {
59
+ return {
60
+ offset: lastUpdateId !== undefined ? lastUpdateId + 1 : undefined,
61
+ limit: TELEGRAM_LONG_POLL_LIMIT,
62
+ timeout: TELEGRAM_LONG_POLL_TIMEOUT_SECONDS,
63
+ allowed_updates: TELEGRAM_ALLOWED_UPDATES,
64
+ };
65
+ }
66
+
67
+ export function getLatestTelegramUpdateId(
68
+ updates: readonly TelegramUpdate[],
69
+ ): number | undefined {
70
+ return updates.at(-1)?.update_id;
71
+ }
72
+
73
+ export class TelegramGetUpdatesTimeoutError extends Error {
74
+ readonly timeoutMs: number;
75
+
76
+ constructor(timeoutMs: number) {
77
+ super(`Telegram getUpdates timed out after ${timeoutMs} ms.`);
78
+ this.name = "TelegramGetUpdatesTimeoutError";
79
+ this.timeoutMs = timeoutMs;
80
+ }
81
+ }
82
+
83
+ export function getTelegramGetUpdatesRequestBudgetMs(
84
+ body: Record<string, unknown>,
85
+ graceMs = TELEGRAM_GET_UPDATES_GRACE_MS,
86
+ ): number {
87
+ const timeoutSeconds =
88
+ typeof body.timeout === "number" &&
89
+ Number.isFinite(body.timeout) &&
90
+ body.timeout >= 0
91
+ ? body.timeout
92
+ : 0;
93
+ const normalizedGraceMs =
94
+ Number.isFinite(graceMs) && graceMs > 0
95
+ ? Math.floor(graceMs)
96
+ : TELEGRAM_GET_UPDATES_GRACE_MS;
97
+ return Math.floor(timeoutSeconds * 1_000) + normalizedGraceMs;
98
+ }
99
+
100
+ export function shouldStopTelegramPolling(
101
+ signalAborted: boolean,
102
+ error: unknown,
103
+ ): boolean {
104
+ return (
105
+ signalAborted ||
106
+ (error instanceof DOMException && error.name === "AbortError")
107
+ );
108
+ }
109
+
110
+ export interface TelegramPollingStartState {
111
+ hasBotToken: boolean;
112
+ hasPollingPromise: boolean;
113
+ }
114
+
115
+ export type TelegramPollingWorkPhase =
116
+ | "long-poll"
117
+ | "persisting-journal"
118
+ | "persisting-offset"
119
+ | "retrying";
120
+
121
+ export type TelegramPollingPhase =
122
+ | "stopped"
123
+ | "starting"
124
+ | TelegramPollingWorkPhase;
125
+
126
+ export type TelegramPollingStopReason =
127
+ | "not-started"
128
+ | "requested"
129
+ | "completed"
130
+ | "failed";
131
+
132
+ export interface TelegramPollingStateSnapshot {
133
+ phase: TelegramPollingPhase;
134
+ phaseStartedAtMs?: number;
135
+ currentUpdateId?: number;
136
+ startedAtMs?: number;
137
+ stoppedAtMs?: number;
138
+ lastSuccessfulResponseAtMs?: number;
139
+ lastSuccessfulResponseUpdateCount?: number;
140
+ stopReason?: TelegramPollingStopReason;
141
+ }
142
+
143
+ export interface TelegramPollingControllerState
144
+ extends TelegramPollingStateSnapshot {
145
+ pollingPromise?: Promise<void>;
146
+ pollingController?: AbortController;
147
+ }
148
+
149
+ export function createTelegramPollingControllerState(): TelegramPollingControllerState {
150
+ return {
151
+ phase: "stopped",
152
+ stopReason: "not-started",
153
+ };
154
+ }
155
+
156
+ export function getTelegramPollingStateSnapshot(
157
+ state: TelegramPollingControllerState,
158
+ ): TelegramPollingStateSnapshot {
159
+ return {
160
+ phase: state.phase,
161
+ phaseStartedAtMs: state.phaseStartedAtMs,
162
+ currentUpdateId: state.currentUpdateId,
163
+ startedAtMs: state.startedAtMs,
164
+ stoppedAtMs: state.stoppedAtMs,
165
+ lastSuccessfulResponseAtMs: state.lastSuccessfulResponseAtMs,
166
+ lastSuccessfulResponseUpdateCount:
167
+ state.lastSuccessfulResponseUpdateCount,
168
+ stopReason: state.stopReason,
169
+ };
170
+ }
171
+
172
+ export function createTelegramPollingStateReader(
173
+ state: TelegramPollingControllerState,
174
+ ): () => TelegramPollingStateSnapshot {
175
+ return () => getTelegramPollingStateSnapshot(state);
176
+ }
177
+
178
+ export function isTelegramPollingControllerActive(
179
+ state: TelegramPollingControllerState,
180
+ ): boolean {
181
+ return !!state.pollingPromise;
182
+ }
183
+
184
+ export function createTelegramPollingActivityReader(
185
+ state: TelegramPollingControllerState,
186
+ ): () => boolean {
187
+ return () => isTelegramPollingControllerActive(state);
188
+ }
189
+
190
+ export interface TelegramPollingRuntimeDeps<
191
+ TContext,
192
+ > extends TelegramRuntimeEventRecorderPort {
193
+ hasBotToken: () => boolean;
194
+ getPollingPromise: () => Promise<void> | undefined;
195
+ setPollingPromise: (promise: Promise<void> | undefined) => void;
196
+ getPollingController: () => AbortController | undefined;
197
+ setPollingController: (controller: AbortController | undefined) => void;
198
+ stopTypingLoop: () => unknown;
199
+ runPollLoop: (ctx: TContext, signal: AbortSignal) => Promise<void>;
200
+ updateStatus: (ctx: TContext, message?: string) => void;
201
+ createAbortController?: () => AbortController;
202
+ getNowMs?: () => number;
203
+ onPollingStateChange?: () => void;
204
+ onPollingStarted?: () => void;
205
+ onPollingStopped?: (reason: TelegramPollingStopReason) => void;
206
+ }
207
+
208
+ export type TelegramPollingControllerDeps<TContext> = Omit<
209
+ TelegramPollingRuntimeDeps<TContext>,
210
+ | "getPollingPromise"
211
+ | "setPollingPromise"
212
+ | "getPollingController"
213
+ | "setPollingController"
214
+ > & { state?: TelegramPollingControllerState };
215
+
216
+ export interface TelegramPollingController<TContext> {
217
+ isActive: () => boolean;
218
+ start: (ctx: TContext) => void;
219
+ stop: () => Promise<void>;
220
+ }
221
+
222
+ export interface TelegramPollingAdmissionRuntime<TContext> {
223
+ isActive: () => boolean;
224
+ start: (ctx: TContext) => Promise<void>;
225
+ stop: () => Promise<void>;
226
+ }
227
+
228
+ export function createTelegramPollingAdmissionRuntime<TContext>(deps: {
229
+ polling: TelegramPollingController<TContext>;
230
+ validateStart?: () => void;
231
+ worker: {
232
+ onSessionStart: (ctx: TContext) => Promise<void>;
233
+ };
234
+ }): TelegramPollingAdmissionRuntime<TContext> {
235
+ return {
236
+ isActive: deps.polling.isActive,
237
+ async start(ctx) {
238
+ deps.validateStart?.();
239
+ await deps.worker.onSessionStart(ctx);
240
+ await deps.polling.start(ctx);
241
+ },
242
+ stop: deps.polling.stop,
243
+ };
244
+ }
245
+
246
+ export interface TelegramDurablePollingRuntimeAssembly<TContext> {
247
+ controller: TelegramPollingController<TContext>;
248
+ admission: TelegramPollingAdmissionRuntime<TContext>;
249
+ }
250
+
251
+ export type TelegramDurablePollingRuntimeAssemblyDeps<
252
+ TUpdate extends TelegramUpdate,
253
+ TContext,
254
+ > = Omit<
255
+ TelegramPollingControllerRuntimeDeps<TUpdate, TContext>,
256
+ "appendUpdateBatch" | "getJournalEntryCount" | "signalUpdateWorker"
257
+ > & {
258
+ journal: {
259
+ appendBatch: (updates: readonly TUpdate[]) => MaybePromise<unknown>;
260
+ getEntryCount: () => number;
261
+ signalWorker: () => void;
262
+ getBootstrapEntryCount: () => number;
263
+ onSessionStart: (ctx: TContext) => Promise<void>;
264
+ };
265
+ };
266
+
267
+ /** Own journal-first polling assembly and cursor bootstrap validation. */
268
+ export function createTelegramDurablePollingRuntimeAssembly<
269
+ TUpdate extends TelegramUpdate,
270
+ TContext,
271
+ >(
272
+ deps: TelegramDurablePollingRuntimeAssemblyDeps<TUpdate, TContext>,
273
+ ): TelegramDurablePollingRuntimeAssembly<TContext> {
274
+ const controller = createTelegramPollingControllerRuntime({
275
+ ...deps,
276
+ appendUpdateBatch: deps.journal.appendBatch,
277
+ getJournalEntryCount: deps.journal.getEntryCount,
278
+ signalUpdateWorker: deps.journal.signalWorker,
279
+ });
280
+ const admission = createTelegramPollingAdmissionRuntime({
281
+ polling: controller,
282
+ validateStart() {
283
+ if (deps.getConfig().lastUpdateId !== undefined) return;
284
+ if (deps.journal.getBootstrapEntryCount() === 0) return;
285
+ throw new TelegramPollingCursorBootstrapError(
286
+ "Telegram polling cursor is missing while the durable update journal is non-empty.",
287
+ );
288
+ },
289
+ worker: deps.journal,
290
+ });
291
+ return { controller, admission };
292
+ }
293
+
294
+ export type TelegramPollingControllerRuntimeDeps<
295
+ TUpdate extends TelegramUpdate,
296
+ TContext = unknown,
297
+ > = Omit<
298
+ TelegramPollLoopRunnerDeps<TUpdate, TContext>,
299
+ "onPhaseChange" | "onSuccessfulResponse"
300
+ > & {
301
+ state?: TelegramPollingControllerState;
302
+ hasBotToken: () => boolean;
303
+ stopTypingLoop: () => unknown;
304
+ createAbortController?: () => AbortController;
305
+ getNowMs?: () => number;
306
+ onPollingStateChange?: () => void;
307
+ };
308
+
309
+ function notifyTelegramPollingStateChange(
310
+ deps: Pick<
311
+ TelegramPollingRuntimeDeps<unknown>,
312
+ "onPollingStateChange" | "recordRuntimeEvent"
313
+ >,
314
+ ): void {
315
+ try {
316
+ deps.onPollingStateChange?.();
317
+ } catch (error) {
318
+ deps.recordRuntimeEvent?.("polling", error, {
319
+ phase: "state-observer",
320
+ });
321
+ }
322
+ }
323
+
324
+ function transitionTelegramPollingState(
325
+ state: TelegramPollingControllerState,
326
+ phase: TelegramPollingPhase,
327
+ nowMs: number,
328
+ currentUpdateId?: number,
329
+ ): void {
330
+ state.phase = phase;
331
+ state.phaseStartedAtMs = nowMs;
332
+ state.currentUpdateId = currentUpdateId;
333
+ }
334
+
335
+ export function createTelegramPollingControllerRuntime<
336
+ TUpdate extends TelegramUpdate,
337
+ TContext = unknown,
338
+ >(
339
+ deps: TelegramPollingControllerRuntimeDeps<TUpdate, TContext>,
340
+ ): TelegramPollingController<TContext> {
341
+ const state = deps.state ?? createTelegramPollingControllerState();
342
+ const getNowMs = deps.getNowMs ?? Date.now;
343
+ const notifyStateChange = () =>
344
+ notifyTelegramPollingStateChange({
345
+ onPollingStateChange: deps.onPollingStateChange,
346
+ recordRuntimeEvent: deps.recordRuntimeEvent,
347
+ });
348
+ return createTelegramPollingController({
349
+ state,
350
+ hasBotToken: deps.hasBotToken,
351
+ stopTypingLoop: deps.stopTypingLoop,
352
+ runPollLoop: createTelegramPollLoopRunner<TUpdate, TContext>({
353
+ getConfig: deps.getConfig,
354
+ deleteWebhook: deps.deleteWebhook,
355
+ getUpdates: deps.getUpdates,
356
+ getUpdatesRequestBudgetMs: deps.getUpdatesRequestBudgetMs,
357
+ persistConfig: deps.persistConfig,
358
+ appendUpdateBatch: deps.appendUpdateBatch,
359
+ getJournalEntryCount: deps.getJournalEntryCount,
360
+ signalUpdateWorker: deps.signalUpdateWorker,
361
+ prepareUpdateBatch: deps.prepareUpdateBatch,
362
+ updateStatus: deps.updateStatus,
363
+ sleep: deps.sleep,
364
+ onPhaseChange(phase, currentUpdateId) {
365
+ transitionTelegramPollingState(
366
+ state,
367
+ phase,
368
+ getNowMs(),
369
+ currentUpdateId,
370
+ );
371
+ notifyStateChange();
372
+ },
373
+ onSuccessfulResponse(updateCount) {
374
+ state.lastSuccessfulResponseAtMs = getNowMs();
375
+ state.lastSuccessfulResponseUpdateCount = updateCount;
376
+ notifyStateChange();
377
+ },
378
+ recordRuntimeEvent: deps.recordRuntimeEvent,
379
+ }),
380
+ updateStatus: deps.updateStatus,
381
+ createAbortController: deps.createAbortController,
382
+ getNowMs,
383
+ onPollingStateChange: deps.onPollingStateChange,
384
+ recordRuntimeEvent: deps.recordRuntimeEvent,
385
+ });
386
+ }
387
+
388
+ export function createTelegramPollingController<TContext>(
389
+ deps: TelegramPollingControllerDeps<TContext>,
390
+ ): TelegramPollingController<TContext> {
391
+ const state = deps.state ?? createTelegramPollingControllerState();
392
+ const getNowMs = deps.getNowMs ?? Date.now;
393
+ const notifyStateChange = () =>
394
+ notifyTelegramPollingStateChange({
395
+ onPollingStateChange: deps.onPollingStateChange,
396
+ recordRuntimeEvent: deps.recordRuntimeEvent,
397
+ });
398
+ const runtimeDeps: TelegramPollingRuntimeDeps<TContext> = {
399
+ ...deps,
400
+ getPollingPromise: () => state.pollingPromise,
401
+ setPollingPromise: (promise) => {
402
+ state.pollingPromise = promise;
403
+ },
404
+ getPollingController: () => state.pollingController,
405
+ setPollingController: (controller) => {
406
+ state.pollingController = controller;
407
+ },
408
+ onPollingStarted: () => {
409
+ const nowMs = getNowMs();
410
+ transitionTelegramPollingState(state, "starting", nowMs);
411
+ state.startedAtMs = nowMs;
412
+ state.stoppedAtMs = undefined;
413
+ state.lastSuccessfulResponseAtMs = undefined;
414
+ state.lastSuccessfulResponseUpdateCount = undefined;
415
+ state.stopReason = undefined;
416
+ notifyStateChange();
417
+ deps.onPollingStarted?.();
418
+ },
419
+ onPollingStopped: (reason) => {
420
+ const nowMs = getNowMs();
421
+ transitionTelegramPollingState(state, "stopped", nowMs);
422
+ state.stoppedAtMs = nowMs;
423
+ state.stopReason = reason;
424
+ notifyStateChange();
425
+ deps.onPollingStopped?.(reason);
426
+ },
427
+ };
428
+ return {
429
+ isActive: () => isTelegramPollingControllerActive(state),
430
+ start: (ctx) => startTelegramPollingRuntime(ctx, runtimeDeps),
431
+ stop: () => stopTelegramPollingRuntime(runtimeDeps),
432
+ };
433
+ }
434
+
435
+ export function shouldStartTelegramPolling(
436
+ state: TelegramPollingStartState,
437
+ ): boolean {
438
+ return state.hasBotToken && !state.hasPollingPromise;
439
+ }
440
+
441
+ export async function stopTelegramPollingRuntime<TContext>(
442
+ deps: TelegramPollingRuntimeDeps<TContext>,
443
+ ): Promise<void> {
444
+ const pollingPromise = deps.getPollingPromise();
445
+ const pollingController = deps.getPollingController();
446
+ try {
447
+ deps.stopTypingLoop();
448
+ } catch (error) {
449
+ deps.recordRuntimeEvent?.("polling", error, { phase: "typing-stop" });
450
+ }
451
+ pollingController?.abort();
452
+ await pollingPromise?.catch(() => undefined);
453
+ let cleared = false;
454
+ if (deps.getPollingPromise() === pollingPromise) {
455
+ deps.setPollingPromise(undefined);
456
+ cleared = pollingPromise !== undefined;
457
+ }
458
+ if (deps.getPollingController() === pollingController) {
459
+ deps.setPollingController(undefined);
460
+ cleared = cleared || pollingController !== undefined;
461
+ }
462
+ if (cleared) deps.onPollingStopped?.("requested");
463
+ }
464
+
465
+ function updateTelegramPollingStatusSafely<TContext>(
466
+ updateStatus: (ctx: TContext, message?: string) => void,
467
+ ctx: TContext,
468
+ options: {
469
+ message?: string;
470
+ recordRuntimeEvent?: TelegramRuntimeEventRecorderPort["recordRuntimeEvent"];
471
+ } = {},
472
+ ): void {
473
+ try {
474
+ updateStatus(ctx, options.message);
475
+ } catch (error) {
476
+ // The polling loop can outlive the session context it captured.
477
+ options.recordRuntimeEvent?.("polling", error, { phase: "status-update" });
478
+ }
479
+ }
480
+
481
+ export function startTelegramPollingRuntime<TContext>(
482
+ ctx: TContext,
483
+ deps: TelegramPollingRuntimeDeps<TContext>,
484
+ ): void {
485
+ if (
486
+ !shouldStartTelegramPolling({
487
+ hasBotToken: deps.hasBotToken(),
488
+ hasPollingPromise: !!deps.getPollingPromise(),
489
+ })
490
+ ) {
491
+ return;
492
+ }
493
+ const controller = deps.createAbortController?.() ?? new AbortController();
494
+ deps.setPollingController(controller);
495
+ deps.onPollingStarted?.();
496
+ let failed = false;
497
+ let runPromise: Promise<void>;
498
+ try {
499
+ runPromise = deps.runPollLoop(ctx, controller.signal);
500
+ } catch (error) {
501
+ runPromise = Promise.reject(error);
502
+ }
503
+ let promise: Promise<void>;
504
+ promise = runPromise
505
+ .catch((error) => {
506
+ if (shouldStopTelegramPolling(controller.signal.aborted, error)) return;
507
+ failed = true;
508
+ deps.recordRuntimeEvent?.("polling", error, {
509
+ phase: "controller",
510
+ });
511
+ })
512
+ .finally(() => {
513
+ const ownsPromise = deps.getPollingPromise() === promise;
514
+ const ownsController = deps.getPollingController() === controller;
515
+ if (ownsPromise) deps.setPollingPromise(undefined);
516
+ if (ownsController) deps.setPollingController(undefined);
517
+ if (ownsPromise || ownsController) {
518
+ deps.onPollingStopped?.(
519
+ failed
520
+ ? "failed"
521
+ : controller.signal.aborted
522
+ ? "requested"
523
+ : "completed",
524
+ );
525
+ }
526
+ updateTelegramPollingStatusSafely(deps.updateStatus, ctx, {
527
+ recordRuntimeEvent: deps.recordRuntimeEvent,
528
+ });
529
+ });
530
+ deps.setPollingPromise(promise);
531
+ updateTelegramPollingStatusSafely(deps.updateStatus, ctx, {
532
+ recordRuntimeEvent: deps.recordRuntimeEvent,
533
+ });
534
+ }
535
+
536
+ export interface TelegramRuntimeEventRecorderPort {
537
+ recordRuntimeEvent?: (
538
+ category: string,
539
+ error: unknown,
540
+ details?: Record<string, unknown>,
541
+ ) => void;
542
+ }
543
+
544
+ export type TelegramThreadCapabilityMode = "enabled" | "disabled" | "unknown";
545
+
546
+ export interface TelegramThreadCapabilityState {
547
+ threadMode?: TelegramThreadCapabilityMode;
548
+ updatedAtMs?: number;
549
+ lastSlot?: string;
550
+ lastReconcileAction?: string;
551
+ }
552
+
553
+ export interface TelegramThreadCapabilityRecordView {
554
+ status?: string;
555
+ target?: { chatId?: number; threadId?: number };
556
+ }
557
+
558
+ export interface TelegramThreadCapabilityStore {
559
+ load: () => Promise<void>;
560
+ refresh?: () => Promise<void>;
561
+ persist: () => Promise<void>;
562
+ getBotState: () => TelegramThreadCapabilityState;
563
+ setBotState: (state: TelegramThreadCapabilityState) => void;
564
+ list?: () => TelegramThreadCapabilityRecordView[];
565
+ }
566
+
567
+ export interface TelegramThreadCapabilityReaderDeps {
568
+ getAllowedUserId: () => number | undefined;
569
+ callApi: <TResponse>(
570
+ method: string,
571
+ body: Record<string, unknown>,
572
+ ) => Promise<TResponse>;
573
+ }
574
+
575
+ export interface TelegramStartupThreadCapabilityProbeDeps extends TelegramThreadCapabilityReaderDeps {
576
+ topicTargetStore: TelegramThreadCapabilityStore;
577
+ recordEvent: (
578
+ category: string,
579
+ message: unknown,
580
+ details?: Record<string, unknown>,
581
+ ) => void;
582
+ setTopicModeUnavailable: (unavailable: boolean) => void;
583
+ getNowMs?: () => number;
584
+ }
585
+
586
+ export interface TelegramThreadCapabilityRuntimeDeps<
587
+ TContext,
588
+ > extends TelegramThreadCapabilityReaderDeps {
589
+ topicTargetStore: TelegramThreadCapabilityStore;
590
+ ownsLock: (ctx: TContext) => boolean;
591
+ isFollowerRegistered?: () => boolean;
592
+ getPollingStartedWithTelegramBus: () => boolean;
593
+ setPollingStartedWithTelegramBus: (started: boolean) => void;
594
+ setTopicModeUnavailable: (unavailable: boolean) => void;
595
+ stopFollowerRegistration: () => void;
596
+ startClassicPolling: (ctx: TContext) => MaybePromise<void>;
597
+ stopClassicPolling: () => MaybePromise<void>;
598
+ startBusPolling: (ctx: TContext) => MaybePromise<void>;
599
+ stopBusPolling: () => MaybePromise<void>;
600
+ startLeaderHealth: () => void;
601
+ stopLeaderHealth: () => void;
602
+ isTopicModeUnavailableError?: (error: unknown) => boolean;
603
+ updateStatus: (ctx: TContext) => void;
604
+ recordEvent: (
605
+ category: string,
606
+ message: unknown,
607
+ details?: Record<string, unknown>,
608
+ ) => void;
609
+ getNowMs?: () => number;
610
+ intervalMs?: number;
611
+ }
612
+
613
+ export interface TelegramThreadCapabilityMonitor<TContext> {
614
+ start: (ctx: TContext) => void;
615
+ stop: () => void;
616
+ }
617
+
618
+ export interface TelegramThreadCapabilityStateRuntime {
619
+ isBusPollingStarted(): boolean;
620
+ setBusPollingStarted(started: boolean): void;
621
+ isTopicModeUnavailable(): boolean;
622
+ setTopicModeUnavailable(unavailable: boolean): void;
623
+ isBusRuntimeEnabled(): boolean;
624
+ shouldForceFreshLeaderThread(): boolean;
625
+ setForceFreshLeaderThread(forceFresh: boolean): void;
626
+ }
627
+
628
+ export type TelegramThreadTargetObservationHandler<TContext> = (
629
+ ctx: TContext,
630
+ ) => Promise<void>;
631
+
632
+ export interface TelegramThreadTargetObservationBinding<TContext> {
633
+ handle: TelegramThreadTargetObservationHandler<TContext>;
634
+ set(handler: TelegramThreadTargetObservationHandler<TContext>): void;
635
+ }
636
+
637
+ export function createTelegramThreadTargetObservationBinding<TContext>(): TelegramThreadTargetObservationBinding<TContext> {
638
+ let handler: TelegramThreadTargetObservationHandler<TContext> | undefined;
639
+ return {
640
+ async handle(ctx) {
641
+ await handler?.(ctx);
642
+ },
643
+ set(nextHandler) {
644
+ handler = nextHandler;
645
+ },
646
+ };
647
+ }
648
+
649
+ export interface TelegramThreadAwarePollingPorts<TContext, TOwner> {
650
+ startPolling: (
651
+ ctx: TContext,
652
+ options?: { forceFreshLeaderThread?: boolean },
653
+ ) => Promise<void>;
654
+ stopPolling: () => Promise<void>;
655
+ registerFollowerWithOwner: (
656
+ ctx: TContext,
657
+ owner: TOwner,
658
+ ) => Promise<boolean | undefined>;
659
+ stopFollowerRegistration: () => void;
660
+ }
661
+
662
+ export interface TelegramThreadAwarePollingDeps<
663
+ TContext,
664
+ TOwner,
665
+ > extends TelegramStartupThreadCapabilityProbeDeps {
666
+ isBusRuntimeEnabled: () => boolean;
667
+ isTopicModeUnavailableError: (error: unknown) => boolean;
668
+ getPollingStartedWithTelegramBus: () => boolean;
669
+ setPollingStartedWithTelegramBus: (started: boolean) => void;
670
+ setForceFreshLeaderThreadOnNextStart: (forceFresh: boolean) => void;
671
+ startClassicPolling: (ctx: TContext) => MaybePromise<void>;
672
+ stopClassicPolling: () => Promise<void>;
673
+ startBusLeaderPolling: (ctx: TContext) => Promise<void>;
674
+ stopBusLeaderPolling: () => Promise<void>;
675
+ startLeaderHealth: () => void;
676
+ stopLeaderHealth: () => void;
677
+ registerFollowerWithLeader: (
678
+ ctx: TContext,
679
+ owner: TOwner,
680
+ ) => Promise<boolean | undefined>;
681
+ stopFollowerRegistration: () => void;
682
+ }
683
+
684
+ export interface TelegramThreadCapabilityOrchestrationDeps<
685
+ TContext,
686
+ TOwner,
687
+ > extends TelegramThreadCapabilityReaderDeps {
688
+ state: TelegramThreadCapabilityStateRuntime;
689
+ topicTargetStore: TelegramThreadCapabilityStore;
690
+ isBusRuntimeEnabled: () => boolean;
691
+ ownsLock: (ctx: TContext) => boolean;
692
+ isFollowerRegistered?: () => boolean;
693
+ startClassicPolling: (ctx: TContext) => MaybePromise<void>;
694
+ stopClassicPolling: () => Promise<void>;
695
+ startBusLeaderPolling: (ctx: TContext) => Promise<void>;
696
+ stopBusLeaderPolling: () => Promise<void>;
697
+ startLeaderHealth: () => void;
698
+ stopLeaderHealth: () => void;
699
+ registerFollowerWithLeader: (
700
+ ctx: TContext,
701
+ owner: TOwner,
702
+ ) => Promise<boolean | undefined>;
703
+ stopFollowerRegistration: () => void;
704
+ isTopicModeUnavailableError: (error: unknown) => boolean;
705
+ updateStatus: (ctx: TContext) => void;
706
+ recordEvent: (
707
+ category: string,
708
+ message: unknown,
709
+ details?: Record<string, unknown>,
710
+ ) => void;
711
+ }
712
+
713
+ export interface TelegramThreadCapabilityOrchestration<TContext, TOwner> {
714
+ monitor: TelegramThreadCapabilityMonitor<TContext>;
715
+ observeTarget: TelegramThreadTargetObservationHandler<TContext>;
716
+ pollingPorts: TelegramThreadAwarePollingPorts<TContext, TOwner>;
717
+ }
718
+
719
+ export function createTelegramThreadCapabilityStateRuntime(): TelegramThreadCapabilityStateRuntime {
720
+ let busPollingStarted = false;
721
+ let topicModeUnavailable = false;
722
+ let forceFreshLeaderThread = false;
723
+ return {
724
+ isBusPollingStarted: () => busPollingStarted,
725
+ setBusPollingStarted(started) {
726
+ busPollingStarted = started;
727
+ },
728
+ isTopicModeUnavailable: () => topicModeUnavailable,
729
+ setTopicModeUnavailable(unavailable) {
730
+ topicModeUnavailable = unavailable;
731
+ },
732
+ isBusRuntimeEnabled: () => !topicModeUnavailable,
733
+ shouldForceFreshLeaderThread: () => forceFreshLeaderThread,
734
+ setForceFreshLeaderThread(forceFresh) {
735
+ forceFreshLeaderThread = forceFresh;
736
+ },
737
+ };
738
+ }
739
+
740
+ export function createTelegramThreadCapabilityOrchestration<TContext, TOwner>(
741
+ deps: TelegramThreadCapabilityOrchestrationDeps<TContext, TOwner>,
742
+ ): TelegramThreadCapabilityOrchestration<TContext, TOwner> {
743
+ const capabilityDeps: TelegramThreadCapabilityRuntimeDeps<TContext> = {
744
+ getAllowedUserId: deps.getAllowedUserId,
745
+ callApi: deps.callApi,
746
+ topicTargetStore: deps.topicTargetStore,
747
+ ownsLock: deps.ownsLock,
748
+ isFollowerRegistered: deps.isFollowerRegistered,
749
+ getPollingStartedWithTelegramBus: deps.state.isBusPollingStarted,
750
+ setPollingStartedWithTelegramBus: deps.state.setBusPollingStarted,
751
+ setTopicModeUnavailable: deps.state.setTopicModeUnavailable,
752
+ stopFollowerRegistration: deps.stopFollowerRegistration,
753
+ startClassicPolling: deps.startClassicPolling,
754
+ stopClassicPolling: deps.stopClassicPolling,
755
+ startBusPolling: deps.startBusLeaderPolling,
756
+ stopBusPolling: deps.stopBusLeaderPolling,
757
+ startLeaderHealth: deps.startLeaderHealth,
758
+ stopLeaderHealth: deps.stopLeaderHealth,
759
+ isTopicModeUnavailableError: deps.isTopicModeUnavailableError,
760
+ updateStatus: deps.updateStatus,
761
+ recordEvent: deps.recordEvent,
762
+ };
763
+ return {
764
+ monitor: createTelegramThreadCapabilityMonitor(capabilityDeps),
765
+ observeTarget: createTelegramThreadTargetObservationHandler(capabilityDeps),
766
+ pollingPorts: createTelegramThreadAwarePollingPorts({
767
+ getAllowedUserId: deps.getAllowedUserId,
768
+ callApi: deps.callApi,
769
+ topicTargetStore: deps.topicTargetStore,
770
+ isBusRuntimeEnabled: deps.isBusRuntimeEnabled,
771
+ isTopicModeUnavailableError: deps.isTopicModeUnavailableError,
772
+ getPollingStartedWithTelegramBus: deps.state.isBusPollingStarted,
773
+ setPollingStartedWithTelegramBus: deps.state.setBusPollingStarted,
774
+ setForceFreshLeaderThreadOnNextStart:
775
+ deps.state.setForceFreshLeaderThread,
776
+ startClassicPolling: deps.startClassicPolling,
777
+ stopClassicPolling: deps.stopClassicPolling,
778
+ startBusLeaderPolling: deps.startBusLeaderPolling,
779
+ stopBusLeaderPolling: deps.stopBusLeaderPolling,
780
+ startLeaderHealth: deps.startLeaderHealth,
781
+ stopLeaderHealth: deps.stopLeaderHealth,
782
+ registerFollowerWithLeader: deps.registerFollowerWithLeader,
783
+ stopFollowerRegistration: deps.stopFollowerRegistration,
784
+ recordEvent: deps.recordEvent,
785
+ setTopicModeUnavailable: deps.state.setTopicModeUnavailable,
786
+ }),
787
+ };
788
+ }
789
+
790
+ export async function readTelegramThreadCapability(
791
+ deps: TelegramThreadCapabilityReaderDeps,
792
+ ): Promise<boolean | undefined> {
793
+ const bot = await deps.callApi<{ has_topics_enabled?: boolean }>("getMe", {});
794
+ if (bot.has_topics_enabled === true) return true;
795
+ if (bot.has_topics_enabled === false) return false;
796
+ return undefined;
797
+ }
798
+
799
+ export async function probeTelegramStartupThreadCapability(
800
+ deps: TelegramStartupThreadCapabilityProbeDeps,
801
+ ): Promise<boolean | undefined> {
802
+ const threadModeEnabled = await readTelegramThreadCapability(deps);
803
+ const nowMs = (deps.getNowMs ?? Date.now)();
804
+ if (threadModeEnabled === false) {
805
+ deps.topicTargetStore.setBotState({
806
+ threadMode: "disabled",
807
+ updatedAtMs: nowMs,
808
+ lastReconcileAction: "startup-bot-topics-disabled",
809
+ });
810
+ await deps.topicTargetStore.persist();
811
+ deps.recordEvent("bus", "Telegram Threaded Mode unavailable on startup", {
812
+ phase: "startup-bot-topics-disabled",
813
+ });
814
+ deps.setTopicModeUnavailable(true);
815
+ return threadModeEnabled;
816
+ }
817
+ if (threadModeEnabled === true) {
818
+ deps.topicTargetStore.setBotState({
819
+ ...deps.topicTargetStore.getBotState(),
820
+ threadMode: "enabled",
821
+ updatedAtMs: nowMs,
822
+ lastReconcileAction: "startup-bot-topics-enabled",
823
+ });
824
+ await deps.topicTargetStore.persist();
825
+ deps.setTopicModeUnavailable(false);
826
+ }
827
+ return threadModeEnabled;
828
+ }
829
+
830
+ function hasTelegramClassicRestoreFailure(
831
+ state: TelegramThreadCapabilityState,
832
+ ): boolean {
833
+ return (
834
+ state.lastReconcileAction?.endsWith("-classic-restore-failed") ?? false
835
+ );
836
+ }
837
+
838
+ function hasTelegramThreadCapabilityBindings(
839
+ store: TelegramThreadCapabilityStore,
840
+ ): boolean {
841
+ return (
842
+ store.list?.().some((record) => {
843
+ return (
844
+ typeof record.target?.chatId === "number" &&
845
+ typeof record.target.threadId === "number" &&
846
+ record.status !== "deleted" &&
847
+ record.status !== "offline" &&
848
+ record.status !== "stale"
849
+ );
850
+ }) ?? false
851
+ );
852
+ }
853
+
854
+ export async function applyTelegramThreadCapability<TContext>(
855
+ ctx: TContext,
856
+ threadModeEnabled: boolean,
857
+ phase: string,
858
+ deps: TelegramThreadCapabilityRuntimeDeps<TContext>,
859
+ ): Promise<void> {
860
+ await deps.topicTargetStore.load();
861
+ const nowMs = (deps.getNowMs ?? Date.now)();
862
+ const previousBotState = deps.topicTargetStore.getBotState();
863
+ if (!threadModeEnabled) {
864
+ if (
865
+ hasTelegramThreadCapabilityBindings(deps.topicTargetStore) &&
866
+ !phase.endsWith("-confirmed")
867
+ ) {
868
+ deps.recordEvent("bus", "Telegram Threaded Mode probe deferred", {
869
+ phase,
870
+ reason: "active-thread-bindings-present",
871
+ });
872
+ return;
873
+ }
874
+ deps.topicTargetStore.setBotState({
875
+ threadMode: "disabled",
876
+ updatedAtMs: nowMs,
877
+ lastReconcileAction: phase,
878
+ });
879
+ await deps.topicTargetStore.persist();
880
+ deps.setTopicModeUnavailable(true);
881
+ deps.stopFollowerRegistration();
882
+ if (
883
+ deps.getPollingStartedWithTelegramBus() ||
884
+ hasTelegramClassicRestoreFailure(previousBotState)
885
+ ) {
886
+ deps.stopLeaderHealth();
887
+ await deps.stopBusPolling();
888
+ deps.setPollingStartedWithTelegramBus(false);
889
+ try {
890
+ await deps.startClassicPolling(ctx);
891
+ } catch (classicError) {
892
+ deps.topicTargetStore.setBotState({
893
+ threadMode: "disabled",
894
+ updatedAtMs: (deps.getNowMs ?? Date.now)(),
895
+ lastReconcileAction: `${phase}-classic-restore-failed`,
896
+ });
897
+ await deps.topicTargetStore.persist();
898
+ deps.recordEvent("bus", classicError, {
899
+ phase: `${phase}-classic-restore`,
900
+ });
901
+ }
902
+ }
903
+ deps.updateStatus(ctx);
904
+ return;
905
+ }
906
+ deps.topicTargetStore.setBotState({
907
+ ...deps.topicTargetStore.getBotState(),
908
+ threadMode: "enabled",
909
+ updatedAtMs: nowMs,
910
+ lastReconcileAction: phase,
911
+ });
912
+ await deps.topicTargetStore.persist();
913
+ deps.setTopicModeUnavailable(false);
914
+ if (!deps.getPollingStartedWithTelegramBus() && deps.ownsLock(ctx)) {
915
+ await deps.stopClassicPolling();
916
+ deps.setPollingStartedWithTelegramBus(true);
917
+ try {
918
+ await deps.startBusPolling(ctx);
919
+ deps.startLeaderHealth();
920
+ } catch (error) {
921
+ deps.setPollingStartedWithTelegramBus(false);
922
+ const threadModeUnavailable =
923
+ deps.isTopicModeUnavailableError?.(error) === true;
924
+ if (threadModeUnavailable) {
925
+ deps.topicTargetStore.setBotState({
926
+ threadMode: "disabled",
927
+ updatedAtMs: nowMs,
928
+ lastReconcileAction: `${phase}-unavailable`,
929
+ });
930
+ await deps.topicTargetStore.persist();
931
+ deps.setTopicModeUnavailable(true);
932
+ }
933
+ try {
934
+ await deps.startClassicPolling(ctx);
935
+ } catch (classicError) {
936
+ deps.topicTargetStore.setBotState({
937
+ threadMode: "disabled",
938
+ updatedAtMs: (deps.getNowMs ?? Date.now)(),
939
+ lastReconcileAction: `${phase}-classic-restore-failed`,
940
+ });
941
+ await deps.topicTargetStore.persist();
942
+ deps.recordEvent("bus", classicError, {
943
+ phase: `${phase}-classic-restore`,
944
+ });
945
+ }
946
+ deps.updateStatus(ctx);
947
+ if (threadModeUnavailable) return;
948
+ throw error;
949
+ }
950
+ }
951
+ deps.updateStatus(ctx);
952
+ }
953
+
954
+ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
955
+ deps: TelegramThreadAwarePollingDeps<TContext, TOwner>,
956
+ ): TelegramThreadAwarePollingPorts<TContext, TOwner> {
957
+ const startPolling = async (
958
+ ctx: TContext,
959
+ options?: { forceFreshLeaderThread?: boolean },
960
+ ): Promise<void> => {
961
+ await deps.topicTargetStore.load();
962
+ let startupThreadCapability: boolean | undefined;
963
+ try {
964
+ startupThreadCapability = await probeTelegramStartupThreadCapability(deps);
965
+ } catch (error) {
966
+ deps.recordEvent("bus", error, { phase: "startup-thread-mode-probe" });
967
+ }
968
+ deps.setTopicModeUnavailable(startupThreadCapability !== true);
969
+ if (deps.isBusRuntimeEnabled()) {
970
+ deps.setTopicModeUnavailable(false);
971
+ try {
972
+ deps.setPollingStartedWithTelegramBus(true);
973
+ deps.setForceFreshLeaderThreadOnNextStart(
974
+ !!options?.forceFreshLeaderThread,
975
+ );
976
+ await deps.startBusLeaderPolling(ctx);
977
+ deps.startLeaderHealth();
978
+ return;
979
+ } catch (error) {
980
+ deps.setPollingStartedWithTelegramBus(false);
981
+ if (!deps.isTopicModeUnavailableError(error)) throw error;
982
+ deps.setTopicModeUnavailable(true);
983
+ await deps.topicTargetStore.load();
984
+ deps.topicTargetStore.setBotState({
985
+ threadMode: "disabled",
986
+ updatedAtMs: Date.now(),
987
+ lastReconcileAction: "thread-mode-unavailable",
988
+ });
989
+ await deps.topicTargetStore.persist();
990
+ deps.recordEvent("bus", error, { phase: "thread-mode-unavailable" });
991
+ } finally {
992
+ deps.setForceFreshLeaderThreadOnNextStart(false);
993
+ }
994
+ }
995
+ deps.setPollingStartedWithTelegramBus(false);
996
+ await deps.startClassicPolling(ctx);
997
+ };
998
+ const stopPolling = async (): Promise<void> => {
999
+ if (deps.getPollingStartedWithTelegramBus()) {
1000
+ deps.stopLeaderHealth();
1001
+ await deps.stopBusLeaderPolling();
1002
+ deps.setPollingStartedWithTelegramBus(false);
1003
+ return;
1004
+ }
1005
+ await deps.stopClassicPolling();
1006
+ };
1007
+ const registerFollowerWithOwner = async (
1008
+ ctx: TContext,
1009
+ owner: TOwner,
1010
+ ): Promise<boolean | undefined> => {
1011
+ if (deps.topicTargetStore.refresh) {
1012
+ await deps.topicTargetStore.refresh();
1013
+ } else {
1014
+ await deps.topicTargetStore.load();
1015
+ }
1016
+ if (deps.topicTargetStore.getBotState().threadMode !== "enabled") {
1017
+ return undefined;
1018
+ }
1019
+ return deps.registerFollowerWithLeader(ctx, owner);
1020
+ };
1021
+ return {
1022
+ startPolling,
1023
+ stopPolling,
1024
+ registerFollowerWithOwner,
1025
+ stopFollowerRegistration: deps.stopFollowerRegistration,
1026
+ };
1027
+ }
1028
+
1029
+ export function createTelegramThreadTargetObservationHandler<TContext>(
1030
+ deps: TelegramThreadCapabilityRuntimeDeps<TContext>,
1031
+ ): TelegramThreadTargetObservationHandler<TContext> {
1032
+ let transitionPending = false;
1033
+ return async (ctx) => {
1034
+ if (transitionPending) return;
1035
+ if (deps.topicTargetStore.getBotState().threadMode === "enabled") return;
1036
+ transitionPending = true;
1037
+ try {
1038
+ await applyTelegramThreadCapability(
1039
+ ctx,
1040
+ true,
1041
+ "thread-target-observed",
1042
+ deps,
1043
+ );
1044
+ } catch (error) {
1045
+ deps.recordEvent("bus", error, { phase: "thread-target-observed" });
1046
+ } finally {
1047
+ transitionPending = false;
1048
+ }
1049
+ };
1050
+ }
1051
+
1052
+ export function canProbeTelegramThreadCapability<TContext>(
1053
+ ctx: TContext,
1054
+ deps: Pick<
1055
+ TelegramThreadCapabilityRuntimeDeps<TContext>,
1056
+ "ownsLock" | "isFollowerRegistered"
1057
+ >,
1058
+ ): boolean {
1059
+ return deps.ownsLock(ctx) || deps.isFollowerRegistered?.() === true;
1060
+ }
1061
+
1062
+ export function createTelegramThreadCapabilityMonitor<TContext>(
1063
+ deps: TelegramThreadCapabilityRuntimeDeps<TContext>,
1064
+ ): TelegramThreadCapabilityMonitor<TContext> {
1065
+ const intervalMs =
1066
+ deps.intervalMs ?? TELEGRAM_THREAD_CAPABILITY_MONITOR_INTERVAL_MS;
1067
+ let interval: ReturnType<typeof setInterval> | undefined;
1068
+ let generation = 0;
1069
+ let transitionPromise: Promise<void> | undefined;
1070
+ let consecutiveDisabledProbes = 0;
1071
+ const stop = (): void => {
1072
+ generation += 1;
1073
+ if (interval) clearInterval(interval);
1074
+ interval = undefined;
1075
+ };
1076
+ const check = (ctx: TContext): void => {
1077
+ if (transitionPromise || !canProbeTelegramThreadCapability(ctx, deps)) {
1078
+ return;
1079
+ }
1080
+ const expectedGeneration = generation;
1081
+ const isCurrent = (): boolean => generation === expectedGeneration;
1082
+ let tracked: Promise<void>;
1083
+ tracked = readTelegramThreadCapability(deps)
1084
+ .then(async (threadModeEnabled) => {
1085
+ if (!isCurrent()) return;
1086
+ if (threadModeEnabled === undefined) {
1087
+ if (
1088
+ deps.topicTargetStore.getBotState().threadMode !== "enabled" &&
1089
+ !deps.getPollingStartedWithTelegramBus() &&
1090
+ deps.ownsLock(ctx)
1091
+ ) {
1092
+ await applyTelegramThreadCapability(
1093
+ ctx,
1094
+ true,
1095
+ "capability-monitor-retry",
1096
+ deps,
1097
+ );
1098
+ }
1099
+ return;
1100
+ }
1101
+ if (threadModeEnabled) consecutiveDisabledProbes = 0;
1102
+ const botState = deps.topicTargetStore.getBotState();
1103
+ const current = botState.threadMode;
1104
+ if (threadModeEnabled && current === "enabled") return;
1105
+ if (!threadModeEnabled && current === "disabled") {
1106
+ if (
1107
+ !deps.ownsLock(ctx) ||
1108
+ !hasTelegramClassicRestoreFailure(botState)
1109
+ ) {
1110
+ return;
1111
+ }
1112
+ await applyTelegramThreadCapability(
1113
+ ctx,
1114
+ false,
1115
+ "capability-monitor-disabled-confirmed",
1116
+ deps,
1117
+ );
1118
+ return;
1119
+ }
1120
+ if (
1121
+ !threadModeEnabled &&
1122
+ hasTelegramThreadCapabilityBindings(deps.topicTargetStore)
1123
+ ) {
1124
+ consecutiveDisabledProbes += 1;
1125
+ if (
1126
+ consecutiveDisabledProbes <
1127
+ TELEGRAM_THREAD_CAPABILITY_DISABLED_CONFIRMATION_PROBES
1128
+ ) {
1129
+ deps.recordEvent("bus", "Telegram Threaded Mode probe deferred", {
1130
+ phase: "capability-monitor-disabled",
1131
+ reason: "active-thread-bindings-present",
1132
+ consecutiveDisabledProbes,
1133
+ });
1134
+ return;
1135
+ }
1136
+ }
1137
+ await applyTelegramThreadCapability(
1138
+ ctx,
1139
+ threadModeEnabled,
1140
+ threadModeEnabled
1141
+ ? "capability-monitor-enabled"
1142
+ : consecutiveDisabledProbes >=
1143
+ TELEGRAM_THREAD_CAPABILITY_DISABLED_CONFIRMATION_PROBES
1144
+ ? "capability-monitor-disabled-confirmed"
1145
+ : "capability-monitor-disabled",
1146
+ deps,
1147
+ );
1148
+ })
1149
+ .catch((error) => {
1150
+ if (!isCurrent()) return;
1151
+ try {
1152
+ deps.recordEvent("bus", error, { phase: "capability-monitor" });
1153
+ } catch {
1154
+ // Monitor diagnostics cannot create an unhandled interval rejection.
1155
+ }
1156
+ })
1157
+ .finally(() => {
1158
+ if (transitionPromise === tracked) transitionPromise = undefined;
1159
+ });
1160
+ transitionPromise = tracked;
1161
+ };
1162
+ return {
1163
+ start(ctx) {
1164
+ stop();
1165
+ interval = setInterval(() => {
1166
+ check(ctx);
1167
+ }, intervalMs);
1168
+ interval.unref?.();
1169
+ },
1170
+ stop,
1171
+ };
1172
+ }
1173
+
1174
+ export class TelegramPollingBatchValidationError extends Error {
1175
+ constructor(message: string) {
1176
+ super(message);
1177
+ this.name = "TelegramPollingBatchValidationError";
1178
+ }
1179
+ }
1180
+
1181
+ export class TelegramPollingCursorBootstrapError extends Error {
1182
+ constructor(message: string) {
1183
+ super(message);
1184
+ this.name = "TelegramPollingCursorBootstrapError";
1185
+ }
1186
+ }
1187
+
1188
+ export interface TelegramPollingBatchAdmissionResult {
1189
+ updateCount: number;
1190
+ latestUpdateId?: number;
1191
+ }
1192
+
1193
+ export interface TelegramPollingBatchAdmissionDeps<
1194
+ TUpdate extends TelegramUpdate,
1195
+ > extends TelegramRuntimeEventRecorderPort {
1196
+ updates: readonly TUpdate[];
1197
+ config: TelegramPollingConfig;
1198
+ appendBatch: (updates: readonly TUpdate[]) => MaybePromise<unknown>;
1199
+ persistConfig: (config: TelegramPollingConfig) => Promise<void>;
1200
+ signalWorker: () => void;
1201
+ onPhaseChange?: (
1202
+ phase: TelegramPollingWorkPhase,
1203
+ currentUpdateId?: number,
1204
+ ) => void;
1205
+ }
1206
+
1207
+ function validateTelegramPollingBatch(
1208
+ updates: readonly TelegramUpdate[],
1209
+ lastUpdateId?: number,
1210
+ ): void {
1211
+ let previousUpdateId = lastUpdateId;
1212
+ for (const update of updates) {
1213
+ if (
1214
+ !Number.isSafeInteger(update.update_id) ||
1215
+ update.update_id < 0 ||
1216
+ (previousUpdateId !== undefined && update.update_id <= previousUpdateId)
1217
+ ) {
1218
+ throw new TelegramPollingBatchValidationError(
1219
+ `Telegram getUpdates returned non-monotonic update id ${String(update.update_id)} after ${String(previousUpdateId)}`,
1220
+ );
1221
+ }
1222
+ previousUpdateId = update.update_id;
1223
+ }
1224
+ }
1225
+
1226
+ export async function admitTelegramPollingUpdateBatch<
1227
+ TUpdate extends TelegramUpdate,
1228
+ >(
1229
+ deps: TelegramPollingBatchAdmissionDeps<TUpdate>,
1230
+ ): Promise<TelegramPollingBatchAdmissionResult> {
1231
+ if (deps.updates.length === 0) return { updateCount: 0 };
1232
+ validateTelegramPollingBatch(deps.updates, deps.config.lastUpdateId);
1233
+ const latestUpdateId = getLatestTelegramUpdateId(deps.updates);
1234
+ if (latestUpdateId === undefined) return { updateCount: 0 };
1235
+ reportTelegramPollingPhase(
1236
+ deps,
1237
+ "persisting-journal",
1238
+ deps.updates[0]?.update_id,
1239
+ );
1240
+ await deps.appendBatch(deps.updates);
1241
+ reportTelegramPollingPhase(deps, "persisting-offset", latestUpdateId);
1242
+ const previousUpdateId = deps.config.lastUpdateId;
1243
+ deps.config.lastUpdateId = latestUpdateId;
1244
+ try {
1245
+ await deps.persistConfig(deps.config);
1246
+ } catch (error) {
1247
+ if (deps.config.lastUpdateId === latestUpdateId) {
1248
+ deps.config.lastUpdateId = previousUpdateId;
1249
+ }
1250
+ throw error;
1251
+ }
1252
+ try {
1253
+ deps.signalWorker();
1254
+ } catch (error) {
1255
+ deps.recordRuntimeEvent?.("polling", error, {
1256
+ phase: "worker-signal",
1257
+ updateCount: deps.updates.length,
1258
+ latestUpdateId,
1259
+ });
1260
+ }
1261
+ return { updateCount: deps.updates.length, latestUpdateId };
1262
+ }
1263
+
1264
+ export interface TelegramPollLoopDeps<
1265
+ TUpdate extends TelegramUpdate,
1266
+ TContext = unknown,
1267
+ > extends TelegramRuntimeEventRecorderPort {
1268
+ ctx: TContext;
1269
+ signal: AbortSignal;
1270
+ config: TelegramPollingConfig;
1271
+ deleteWebhook: (signal: AbortSignal) => Promise<unknown>;
1272
+ getUpdates: (
1273
+ body: Record<string, unknown>,
1274
+ signal: AbortSignal,
1275
+ ) => Promise<TUpdate[]>;
1276
+ getUpdatesRequestBudgetMs?: (body: Record<string, unknown>) => number;
1277
+ persistConfig: (config: TelegramPollingConfig) => Promise<void>;
1278
+ appendUpdateBatch: (updates: readonly TUpdate[]) => MaybePromise<unknown>;
1279
+ getJournalEntryCount: () => number;
1280
+ signalUpdateWorker: () => void;
1281
+ prepareUpdateBatch?: (updates: readonly TUpdate[]) => void;
1282
+ onErrorStatus: (message: string) => void;
1283
+ onStatusReset: () => void;
1284
+ sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
1285
+ onPhaseChange?: (
1286
+ phase: TelegramPollingWorkPhase,
1287
+ currentUpdateId?: number,
1288
+ ) => void;
1289
+ onSuccessfulResponse?: (updateCount: number) => void;
1290
+ }
1291
+
1292
+ export interface TelegramPollLoopRunnerDeps<
1293
+ TUpdate extends TelegramUpdate,
1294
+ TContext = unknown,
1295
+ > extends TelegramRuntimeEventRecorderPort {
1296
+ getConfig: () => TelegramPollingConfig;
1297
+ deleteWebhook: (signal: AbortSignal) => Promise<unknown>;
1298
+ getUpdates: (
1299
+ body: Record<string, unknown>,
1300
+ signal: AbortSignal,
1301
+ ) => Promise<TUpdate[]>;
1302
+ getUpdatesRequestBudgetMs?: (body: Record<string, unknown>) => number;
1303
+ persistConfig: (config: TelegramPollingConfig) => Promise<void>;
1304
+ appendUpdateBatch: (updates: readonly TUpdate[]) => MaybePromise<unknown>;
1305
+ getJournalEntryCount: () => number;
1306
+ signalUpdateWorker: () => void;
1307
+ prepareUpdateBatch?: (updates: readonly TUpdate[]) => void;
1308
+ updateStatus: (ctx: TContext, message?: string) => void;
1309
+ sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
1310
+ onPhaseChange?: (
1311
+ phase: TelegramPollingWorkPhase,
1312
+ currentUpdateId?: number,
1313
+ ) => void;
1314
+ onSuccessfulResponse?: (updateCount: number) => void;
1315
+ }
1316
+
1317
+ export function sleepTelegramPollingRetry(
1318
+ ms: number,
1319
+ signal?: AbortSignal,
1320
+ ): Promise<void> {
1321
+ if (ms <= 0 || signal?.aborted) return Promise.resolve();
1322
+ return new Promise<void>((resolve) => {
1323
+ let settled = false;
1324
+ let timer: ReturnType<typeof setTimeout>;
1325
+ const finish = () => {
1326
+ if (settled) return;
1327
+ settled = true;
1328
+ signal?.removeEventListener("abort", onAbort);
1329
+ resolve();
1330
+ };
1331
+ const onAbort = () => {
1332
+ clearTimeout(timer);
1333
+ finish();
1334
+ };
1335
+ timer = setTimeout(finish, ms);
1336
+ timer.unref?.();
1337
+ signal?.addEventListener("abort", onAbort, { once: true });
1338
+ if (signal?.aborted) onAbort();
1339
+ });
1340
+ }
1341
+
1342
+ export function createTelegramPollLoopRunner<
1343
+ TUpdate extends TelegramUpdate,
1344
+ TContext = unknown,
1345
+ >(
1346
+ deps: TelegramPollLoopRunnerDeps<TUpdate, TContext>,
1347
+ ): (ctx: TContext, signal: AbortSignal) => Promise<void> {
1348
+ const sleep = deps.sleep ?? sleepTelegramPollingRetry;
1349
+ return (ctx, signal) =>
1350
+ runTelegramPollLoop({
1351
+ ctx,
1352
+ signal,
1353
+ config: deps.getConfig(),
1354
+ deleteWebhook: deps.deleteWebhook,
1355
+ getUpdates: deps.getUpdates,
1356
+ getUpdatesRequestBudgetMs: deps.getUpdatesRequestBudgetMs,
1357
+ persistConfig: deps.persistConfig,
1358
+ appendUpdateBatch: deps.appendUpdateBatch,
1359
+ getJournalEntryCount: deps.getJournalEntryCount,
1360
+ signalUpdateWorker: deps.signalUpdateWorker,
1361
+ prepareUpdateBatch: deps.prepareUpdateBatch,
1362
+ onErrorStatus: (message) => {
1363
+ updateTelegramPollingStatusSafely(deps.updateStatus, ctx, {
1364
+ message,
1365
+ recordRuntimeEvent: deps.recordRuntimeEvent,
1366
+ });
1367
+ },
1368
+ onStatusReset: () => {
1369
+ updateTelegramPollingStatusSafely(deps.updateStatus, ctx, {
1370
+ recordRuntimeEvent: deps.recordRuntimeEvent,
1371
+ });
1372
+ },
1373
+ sleep,
1374
+ onPhaseChange: deps.onPhaseChange,
1375
+ onSuccessfulResponse: deps.onSuccessfulResponse,
1376
+ recordRuntimeEvent: deps.recordRuntimeEvent,
1377
+ });
1378
+ }
1379
+
1380
+ function getTelegramPollingErrorMessage(error: unknown): string {
1381
+ return error instanceof Error ? error.message : String(error);
1382
+ }
1383
+
1384
+ export function isTelegramGetUpdatesConflictError(error: unknown): boolean {
1385
+ return getTelegramPollingErrorMessage(error).includes(
1386
+ "Conflict: terminated by other getUpdates request",
1387
+ );
1388
+ }
1389
+
1390
+ function reportTelegramPollingPhase(
1391
+ deps: TelegramRuntimeEventRecorderPort & {
1392
+ onPhaseChange?: (
1393
+ phase: TelegramPollingWorkPhase,
1394
+ currentUpdateId?: number,
1395
+ ) => void;
1396
+ },
1397
+ phase: TelegramPollingWorkPhase,
1398
+ currentUpdateId?: number,
1399
+ ): void {
1400
+ try {
1401
+ deps.onPhaseChange?.(phase, currentUpdateId);
1402
+ } catch (error) {
1403
+ deps.recordRuntimeEvent?.("polling", error, {
1404
+ phase: "phase-observer",
1405
+ });
1406
+ }
1407
+ }
1408
+
1409
+ function reportTelegramPollingResponse<
1410
+ TUpdate extends TelegramUpdate,
1411
+ TContext,
1412
+ >(
1413
+ deps: TelegramPollLoopDeps<TUpdate, TContext>,
1414
+ updateCount: number,
1415
+ ): void {
1416
+ try {
1417
+ deps.onSuccessfulResponse?.(updateCount);
1418
+ } catch (error) {
1419
+ deps.recordRuntimeEvent?.("polling", error, {
1420
+ phase: "response-observer",
1421
+ });
1422
+ }
1423
+ }
1424
+
1425
+ function getTelegramPollingAbortReason(signal: AbortSignal): unknown {
1426
+ return signal.reason ?? new DOMException("Aborted", "AbortError");
1427
+ }
1428
+
1429
+ async function requestTelegramUpdatesWithinBudget<
1430
+ TUpdate extends TelegramUpdate,
1431
+ TContext,
1432
+ >(
1433
+ deps: TelegramPollLoopDeps<TUpdate, TContext>,
1434
+ body: Record<string, unknown>,
1435
+ ): Promise<TUpdate[]> {
1436
+ if (deps.signal.aborted) throw getTelegramPollingAbortReason(deps.signal);
1437
+ const configuredBudgetMs = deps.getUpdatesRequestBudgetMs?.(body);
1438
+ const timeoutMs =
1439
+ typeof configuredBudgetMs === "number" &&
1440
+ Number.isFinite(configuredBudgetMs) &&
1441
+ configuredBudgetMs > 0
1442
+ ? Math.floor(configuredBudgetMs)
1443
+ : getTelegramGetUpdatesRequestBudgetMs(body);
1444
+ const controller = new AbortController();
1445
+ const abortFromOwner = () => {
1446
+ controller.abort(getTelegramPollingAbortReason(deps.signal));
1447
+ };
1448
+ deps.signal.addEventListener("abort", abortFromOwner, { once: true });
1449
+ if (deps.signal.aborted) abortFromOwner();
1450
+ const timeout = setTimeout(() => {
1451
+ controller.abort(new TelegramGetUpdatesTimeoutError(timeoutMs));
1452
+ }, timeoutMs);
1453
+ let onRequestAbort: (() => void) | undefined;
1454
+ const aborted = new Promise<never>((_resolve, reject) => {
1455
+ onRequestAbort = () =>
1456
+ reject(getTelegramPollingAbortReason(controller.signal));
1457
+ controller.signal.addEventListener("abort", onRequestAbort, {
1458
+ once: true,
1459
+ });
1460
+ if (controller.signal.aborted) onRequestAbort();
1461
+ });
1462
+ const operation = Promise.resolve()
1463
+ .then(() => {
1464
+ if (controller.signal.aborted) {
1465
+ throw getTelegramPollingAbortReason(controller.signal);
1466
+ }
1467
+ return deps.getUpdates(body, controller.signal);
1468
+ })
1469
+ .catch((error) => {
1470
+ if (controller.signal.aborted) {
1471
+ throw getTelegramPollingAbortReason(controller.signal);
1472
+ }
1473
+ throw error;
1474
+ });
1475
+ try {
1476
+ return await Promise.race([operation, aborted]);
1477
+ } finally {
1478
+ clearTimeout(timeout);
1479
+ deps.signal.removeEventListener("abort", abortFromOwner);
1480
+ if (onRequestAbort) {
1481
+ controller.signal.removeEventListener("abort", onRequestAbort);
1482
+ }
1483
+ }
1484
+ }
1485
+
1486
+ export async function runTelegramPollLoop<
1487
+ TUpdate extends TelegramUpdate,
1488
+ TContext = unknown,
1489
+ >(deps: TelegramPollLoopDeps<TUpdate, TContext>): Promise<void> {
1490
+ if (!deps.config.botToken) return;
1491
+ try {
1492
+ await deps.deleteWebhook(deps.signal);
1493
+ } catch {
1494
+ // ignore
1495
+ }
1496
+ if (
1497
+ deps.config.lastUpdateId === undefined &&
1498
+ deps.getJournalEntryCount() > 0
1499
+ ) {
1500
+ throw new TelegramPollingCursorBootstrapError(
1501
+ "Telegram polling cursor is missing while the durable update journal is non-empty.",
1502
+ );
1503
+ }
1504
+ if (deps.config.lastUpdateId === undefined) {
1505
+ try {
1506
+ const request = buildTelegramInitialSyncRequest();
1507
+ reportTelegramPollingPhase(deps, "long-poll");
1508
+ const updates = await requestTelegramUpdatesWithinBudget(deps, request);
1509
+ reportTelegramPollingResponse(deps, updates.length);
1510
+ const lastUpdateId = getLatestTelegramUpdateId(updates);
1511
+ if (lastUpdateId !== undefined) {
1512
+ reportTelegramPollingPhase(
1513
+ deps,
1514
+ "persisting-offset",
1515
+ lastUpdateId,
1516
+ );
1517
+ deps.config.lastUpdateId = lastUpdateId;
1518
+ await deps.persistConfig(deps.config);
1519
+ deps.recordRuntimeEvent?.(
1520
+ "polling",
1521
+ new Error("Initialized Telegram cursor without executing history."),
1522
+ { phase: "cursor-bootstrap", lastUpdateId },
1523
+ );
1524
+ }
1525
+ } catch (error) {
1526
+ if (shouldStopTelegramPolling(deps.signal.aborted, error)) return;
1527
+ reportTelegramPollingPhase(deps, "retrying");
1528
+ deps.recordRuntimeEvent?.("polling", error, {
1529
+ phase: "initial-sync",
1530
+ ...(error instanceof TelegramGetUpdatesTimeoutError
1531
+ ? { timeoutMs: error.timeoutMs }
1532
+ : {}),
1533
+ });
1534
+ }
1535
+ }
1536
+ let consecutiveGetUpdatesConflicts = 0;
1537
+ let currentUpdateId: number | undefined;
1538
+ while (!deps.signal.aborted) {
1539
+ try {
1540
+ currentUpdateId = undefined;
1541
+ const request = buildTelegramLongPollRequest(deps.config.lastUpdateId);
1542
+ reportTelegramPollingPhase(deps, "long-poll");
1543
+ const updates = await requestTelegramUpdatesWithinBudget(deps, request);
1544
+ reportTelegramPollingResponse(deps, updates.length);
1545
+ deps.prepareUpdateBatch?.(updates);
1546
+ consecutiveGetUpdatesConflicts = 0;
1547
+ currentUpdateId = updates[0]?.update_id;
1548
+ await admitTelegramPollingUpdateBatch({
1549
+ updates,
1550
+ config: deps.config,
1551
+ appendBatch: deps.appendUpdateBatch,
1552
+ persistConfig: deps.persistConfig,
1553
+ signalWorker: deps.signalUpdateWorker,
1554
+ onPhaseChange: deps.onPhaseChange,
1555
+ recordRuntimeEvent: deps.recordRuntimeEvent,
1556
+ });
1557
+ currentUpdateId = undefined;
1558
+ } catch (error) {
1559
+ if (shouldStopTelegramPolling(deps.signal.aborted, error)) return;
1560
+ reportTelegramPollingPhase(deps, "retrying", currentUpdateId);
1561
+ deps.recordRuntimeEvent?.("polling", error, {
1562
+ phase:
1563
+ error instanceof TelegramGetUpdatesTimeoutError
1564
+ ? "long-poll"
1565
+ : "loop",
1566
+ ...(error instanceof TelegramGetUpdatesTimeoutError
1567
+ ? { timeoutMs: error.timeoutMs }
1568
+ : {}),
1569
+ });
1570
+ if (isTelegramGetUpdatesConflictError(error)) {
1571
+ consecutiveGetUpdatesConflicts += 1;
1572
+ await deps.sleep(
1573
+ consecutiveGetUpdatesConflicts <
1574
+ TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_LIMIT
1575
+ ? TELEGRAM_GET_UPDATES_CONFLICT_FAST_RETRY_MS
1576
+ : TELEGRAM_GET_UPDATES_CONFLICT_SLOW_RETRY_MS,
1577
+ deps.signal,
1578
+ );
1579
+ continue;
1580
+ }
1581
+ consecutiveGetUpdatesConflicts = 0;
1582
+ deps.onErrorStatus(getTelegramPollingErrorMessage(error));
1583
+ await deps.sleep(TELEGRAM_POLLING_RETRY_MS, deps.signal);
1584
+ if (deps.signal.aborted) return;
1585
+ deps.onStatusReset();
1586
+ }
1587
+ }
1588
+ }