@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,1747 @@
1
+ /**
2
+ * Telegram API transport helpers
3
+ * Zones: telegram transport, filesystem, runtime diagnostics
4
+ *
5
+ * Wraps bot API calls, file uploads/downloads (including voice messages),
6
+ * multipart sending, runtime transport binding, and Telegram temp-file lifecycle.
7
+ */
8
+
9
+ import { randomUUID } from "node:crypto";
10
+ import { createWriteStream, openAsBlob } from "node:fs";
11
+ import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
12
+ import { request as requestHttps } from "node:https";
13
+ import { join } from "node:path";
14
+ import { resolveTelegramTempDir } from "./paths.ts";
15
+ import { Readable, Transform } from "node:stream";
16
+ import { pipeline } from "node:stream/promises";
17
+
18
+ export const TELEGRAM_API_BASE = "https://api.telegram.org";
19
+
20
+ export const TELEGRAM_FILE_MAX_BYTES = 50 * 1024 * 1024;
21
+
22
+ export function getTelegramInboundFileByteLimitFromEnv(
23
+ env: NodeJS.ProcessEnv,
24
+ names: string[],
25
+ defaultValue = TELEGRAM_FILE_MAX_BYTES,
26
+ ): number {
27
+ for (const name of names) {
28
+ const rawValue = env[name]?.trim();
29
+ if (!rawValue) continue;
30
+ const parsed = Number(rawValue);
31
+ if (Number.isSafeInteger(parsed) && parsed > 0) return parsed;
32
+ }
33
+ return defaultValue;
34
+ }
35
+
36
+ function getTelegramApiTempDir(): string {
37
+ return resolveTelegramTempDir();
38
+ }
39
+ const TELEGRAM_TEMP_FILE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
40
+ const TELEGRAM_INBOUND_FILE_MAX_BYTES = getTelegramInboundFileByteLimitFromEnv(
41
+ process.env,
42
+ ["PI_TELEGRAM_INBOUND_FILE_MAX_BYTES", "TELEGRAM_MAX_FILE_SIZE_BYTES"],
43
+ TELEGRAM_FILE_MAX_BYTES,
44
+ );
45
+
46
+ export type TelegramNetworkFamilyPolicy =
47
+ "auto" | "ipv4" | "ipv6" | "ipv4-fallback";
48
+
49
+ const TELEGRAM_NETWORK_FAMILY_ENV = "PI_TELEGRAM_NETWORK_FAMILY";
50
+ const TELEGRAM_NETWORK_FAMILY_VALUES = new Set<TelegramNetworkFamilyPolicy>([
51
+ "auto",
52
+ "ipv4",
53
+ "ipv6",
54
+ "ipv4-fallback",
55
+ ]);
56
+ type TelegramNetworkFamily = 4 | 6;
57
+
58
+ export interface TelegramUser {
59
+ id: number;
60
+ is_bot: boolean;
61
+ first_name: string;
62
+ username?: string;
63
+ }
64
+
65
+ export interface TelegramChat {
66
+ id: number;
67
+ type: string;
68
+ }
69
+
70
+ export interface TelegramPhotoSize {
71
+ file_id: string;
72
+ file_size?: number;
73
+ }
74
+
75
+ export interface TelegramDocument {
76
+ file_id: string;
77
+ file_name?: string;
78
+ mime_type?: string;
79
+ file_size?: number;
80
+ }
81
+
82
+ export interface TelegramVideo {
83
+ file_id: string;
84
+ file_name?: string;
85
+ mime_type?: string;
86
+ file_size?: number;
87
+ }
88
+
89
+ export interface TelegramAudio {
90
+ file_id: string;
91
+ file_name?: string;
92
+ mime_type?: string;
93
+ file_size?: number;
94
+ }
95
+
96
+ export interface TelegramVoice {
97
+ file_id: string;
98
+ mime_type?: string;
99
+ file_size?: number;
100
+ }
101
+
102
+ export interface TelegramAnimation {
103
+ file_id: string;
104
+ file_name?: string;
105
+ mime_type?: string;
106
+ file_size?: number;
107
+ }
108
+
109
+ export interface TelegramSticker {
110
+ file_id: string;
111
+ emoji?: string;
112
+ }
113
+
114
+ export interface TelegramRichMessage {
115
+ blocks?: unknown[];
116
+ is_rtl?: boolean;
117
+ }
118
+
119
+ export interface TelegramMessage {
120
+ message_id: number;
121
+ chat: TelegramChat;
122
+ from?: TelegramUser;
123
+ text?: string;
124
+ caption?: string;
125
+ rich_message?: TelegramRichMessage;
126
+ media_group_id?: string;
127
+ photo?: TelegramPhotoSize[];
128
+ document?: TelegramDocument;
129
+ video?: TelegramVideo;
130
+ audio?: TelegramAudio;
131
+ voice?: TelegramVoice;
132
+ animation?: TelegramAnimation;
133
+ sticker?: TelegramSticker;
134
+ }
135
+
136
+ export interface TelegramCallbackQuery {
137
+ id: string;
138
+ from: TelegramUser;
139
+ message?: TelegramMessage;
140
+ data?: string;
141
+ }
142
+
143
+ export interface TelegramReactionTypeEmoji {
144
+ type: "emoji";
145
+ emoji: string;
146
+ }
147
+
148
+ export interface TelegramReactionTypeCustomEmoji {
149
+ type: "custom_emoji";
150
+ custom_emoji_id: string;
151
+ }
152
+
153
+ export interface TelegramReactionTypePaid {
154
+ type: "paid";
155
+ }
156
+
157
+ export type TelegramReactionType =
158
+ | TelegramReactionTypeEmoji
159
+ | TelegramReactionTypeCustomEmoji
160
+ | TelegramReactionTypePaid;
161
+
162
+ export interface TelegramMessageReactionUpdated {
163
+ chat: TelegramChat;
164
+ message_id: number;
165
+ user?: TelegramUser;
166
+ actor_chat?: TelegramChat;
167
+ old_reaction: TelegramReactionType[];
168
+ new_reaction: TelegramReactionType[];
169
+ date: number;
170
+ }
171
+
172
+ export interface TelegramGuestMessage {
173
+ message_id: number;
174
+ from?: TelegramUser;
175
+ chat: TelegramChat;
176
+ date: number;
177
+ text?: string;
178
+ caption?: string;
179
+ rich_message?: TelegramRichMessage;
180
+ guest_query_id: string;
181
+ guest_bot_caller_user?: TelegramUser;
182
+ guest_bot_caller_chat?: TelegramChat;
183
+ reply_to_message?: TelegramMessage;
184
+ }
185
+
186
+ export interface TelegramUpdate {
187
+ update_id: number;
188
+ message?: TelegramMessage;
189
+ edited_message?: TelegramMessage;
190
+ callback_query?: TelegramCallbackQuery;
191
+ message_reaction?: TelegramMessageReactionUpdated;
192
+ guest_message?: TelegramGuestMessage;
193
+ deleted_business_messages?: { message_ids?: unknown };
194
+ }
195
+
196
+ export interface TelegramSentMessage {
197
+ message_id: number;
198
+ }
199
+
200
+ export interface TelegramReplyParameters {
201
+ message_id: number;
202
+ allow_sending_without_reply?: boolean;
203
+ chat_id?: number;
204
+ message_thread_id?: number;
205
+ }
206
+
207
+ export interface TelegramLinkPreviewOptions {
208
+ is_disabled?: boolean;
209
+ }
210
+
211
+ export type TelegramSendMessageBody = Record<string, unknown> & {
212
+ chat_id: number;
213
+ text: string;
214
+ parse_mode?: "HTML";
215
+ link_preview_options?: TelegramLinkPreviewOptions;
216
+ reply_markup?: unknown;
217
+ reply_parameters?: TelegramReplyParameters;
218
+ };
219
+
220
+ export interface TelegramInputMediaPhoto extends Record<string, unknown> {
221
+ type: "photo";
222
+ media: string;
223
+ has_spoiler?: boolean;
224
+ }
225
+
226
+ export interface TelegramInputMediaVideo extends Record<string, unknown> {
227
+ type: "video";
228
+ media: string;
229
+ thumbnail?: string;
230
+ width?: number;
231
+ height?: number;
232
+ duration?: number;
233
+ supports_streaming?: boolean;
234
+ has_spoiler?: boolean;
235
+ }
236
+
237
+ export interface TelegramInputMediaAnimation extends Record<string, unknown> {
238
+ type: "animation";
239
+ media: string;
240
+ thumbnail?: string;
241
+ width?: number;
242
+ height?: number;
243
+ duration?: number;
244
+ has_spoiler?: boolean;
245
+ }
246
+
247
+ export interface TelegramInputMediaAudio extends Record<string, unknown> {
248
+ type: "audio";
249
+ media: string;
250
+ thumbnail?: string;
251
+ duration?: number;
252
+ performer?: string;
253
+ title?: string;
254
+ }
255
+
256
+ export interface TelegramInputMediaVoiceNote extends Record<string, unknown> {
257
+ type: "voice_note";
258
+ media: string;
259
+ caption?: string;
260
+ parse_mode?: string;
261
+ caption_entities?: unknown[];
262
+ duration?: number;
263
+ }
264
+
265
+ export type TelegramInputRichMessageMediaValue =
266
+ | TelegramInputMediaAnimation
267
+ | TelegramInputMediaAudio
268
+ | TelegramInputMediaPhoto
269
+ | TelegramInputMediaVideo
270
+ | TelegramInputMediaVoiceNote;
271
+
272
+ export interface TelegramInputRichMessageMedia {
273
+ id: string;
274
+ media: TelegramInputRichMessageMediaValue;
275
+ }
276
+
277
+ type TelegramInputRichMessageCommon = {
278
+ is_rtl?: boolean;
279
+ skip_entity_detection?: boolean;
280
+ };
281
+
282
+ export type TelegramRichText =
283
+ | string
284
+ | TelegramRichText[]
285
+ | { type: "bold" | "code"; text: TelegramRichText };
286
+
287
+ export type TelegramInputRichBlock =
288
+ | { type: "pre"; text: TelegramRichText; language?: string }
289
+ | {
290
+ type: "details";
291
+ summary: TelegramRichText;
292
+ blocks: TelegramInputRichBlock[];
293
+ is_open?: true;
294
+ };
295
+
296
+ export type TelegramInputRichDraftBlock =
297
+ | TelegramInputRichBlock
298
+ | { type: "thinking"; text: TelegramRichText };
299
+
300
+ export type TelegramInputRichMessage = TelegramInputRichMessageCommon &
301
+ (
302
+ | {
303
+ markdown: string;
304
+ html?: never;
305
+ blocks?: never;
306
+ media?: TelegramInputRichMessageMedia[];
307
+ }
308
+ | {
309
+ html: string;
310
+ markdown?: never;
311
+ blocks?: never;
312
+ media?: TelegramInputRichMessageMedia[];
313
+ }
314
+ | {
315
+ blocks: TelegramInputRichBlock[];
316
+ markdown?: never;
317
+ html?: never;
318
+ media?: never;
319
+ }
320
+ );
321
+
322
+ export type TelegramSendRichMessageBody = Record<string, unknown> & {
323
+ chat_id: number;
324
+ rich_message: TelegramInputRichMessage;
325
+ reply_markup?: unknown;
326
+ reply_parameters?: TelegramReplyParameters;
327
+ };
328
+
329
+ export type TelegramEditMessageTextBody = Record<string, unknown> & {
330
+ chat_id: number;
331
+ message_id: number;
332
+ text?: string;
333
+ rich_message?: TelegramInputRichMessage;
334
+ parse_mode?: "HTML";
335
+ link_preview_options?: TelegramLinkPreviewOptions;
336
+ reply_markup?: unknown;
337
+ };
338
+
339
+ export type TelegramSendMessageDraftBody = Record<string, unknown> & {
340
+ chat_id: number;
341
+ draft_id: number;
342
+ text?: string;
343
+ parse_mode?: string;
344
+ entities?: unknown[];
345
+ message_thread_id?: number;
346
+ };
347
+
348
+ export type TelegramSendRichMessageDraftBody = Record<string, unknown> & {
349
+ chat_id: number;
350
+ draft_id: number;
351
+ rich_message:
352
+ | TelegramInputRichMessage
353
+ | (TelegramInputRichMessageCommon & {
354
+ blocks: TelegramInputRichDraftBlock[];
355
+ markdown?: never;
356
+ html?: never;
357
+ media?: never;
358
+ });
359
+ message_thread_id?: number;
360
+ };
361
+
362
+ interface TelegramApiResponse<T> {
363
+ ok: boolean;
364
+ result?: T;
365
+ description?: string;
366
+ error_code?: number;
367
+ parameters?: { retry_after?: number };
368
+ }
369
+
370
+ export interface TelegramApiCallOptions {
371
+ signal?: AbortSignal;
372
+ maxAttempts?: number;
373
+ retryRateLimit?: boolean;
374
+ retrySafety?: "safe" | "non-idempotent";
375
+ retryBaseDelayMs?: number;
376
+ sleep?: (ms: number) => Promise<void>;
377
+ }
378
+
379
+ interface TelegramGetFileResult {
380
+ file_path: string;
381
+ file_size?: number;
382
+ }
383
+
384
+ export interface TelegramFileDownloadOptions {
385
+ signal?: AbortSignal;
386
+ maxFileSizeBytes?: number;
387
+ }
388
+
389
+ export type TelegramGuestCachedMediaResult =
390
+ | {
391
+ type: "document";
392
+ id: string;
393
+ title: string;
394
+ document_file_id: string;
395
+ caption?: string;
396
+ parse_mode?: string;
397
+ }
398
+ | {
399
+ type: "photo";
400
+ id: string;
401
+ photo_file_id: string;
402
+ caption?: string;
403
+ parse_mode?: string;
404
+ }
405
+ | {
406
+ type: "audio";
407
+ id: string;
408
+ audio_file_id: string;
409
+ caption?: string;
410
+ parse_mode?: string;
411
+ }
412
+ | {
413
+ type: "voice";
414
+ id: string;
415
+ voice_file_id: string;
416
+ title: string;
417
+ caption?: string;
418
+ parse_mode?: string;
419
+ };
420
+
421
+ export interface TelegramAnswerGuestQueryOptions {
422
+ parseMode?: string;
423
+ richMessage?: TelegramInputRichMessage;
424
+ result?: TelegramGuestCachedMediaResult;
425
+ }
426
+
427
+ export interface TelegramAnswerCallbackQueryOptions {
428
+ recordRuntimeEvent?: (
429
+ kind: "api",
430
+ error: unknown,
431
+ details?: Record<string, unknown>,
432
+ ) => void;
433
+ }
434
+
435
+ export interface TelegramApiClient {
436
+ call: <TResponse>(
437
+ method: string,
438
+ body: Record<string, unknown>,
439
+ options?: TelegramApiCallOptions,
440
+ ) => Promise<TResponse>;
441
+ callMultipart: <TResponse>(
442
+ method: string,
443
+ fields: Record<string, string>,
444
+ fileField: string,
445
+ filePath: string,
446
+ fileName: string,
447
+ options?: TelegramApiCallOptions,
448
+ ) => Promise<TResponse>;
449
+ downloadFile: (
450
+ fileId: string,
451
+ suggestedName: string,
452
+ tempDir: string,
453
+ options?: TelegramFileDownloadOptions,
454
+ ) => Promise<string>;
455
+ answerCallbackQuery: (
456
+ callbackQueryId: string,
457
+ text?: string,
458
+ ) => Promise<void>;
459
+ answerGuestQuery?: (
460
+ guestQueryId: string,
461
+ text?: string,
462
+ options?: TelegramAnswerGuestQueryOptions,
463
+ ) => Promise<void>;
464
+ }
465
+
466
+ export interface TelegramBridgeApiRuntimeDeps {
467
+ client: TelegramApiClient;
468
+ tempDir: string;
469
+ maxFileSizeBytes: number;
470
+ tempFileMaxAgeMs: number;
471
+ recordRuntimeEvent: (
472
+ kind: "api" | "multipart" | "download",
473
+ error: unknown,
474
+ details?: Record<string, unknown>,
475
+ ) => void;
476
+ now?: () => number;
477
+ chatActionMinIntervalMs?: number;
478
+ chatActionMaxGates?: number;
479
+ }
480
+
481
+ export interface TelegramBridgeApiRuntime {
482
+ call: <TResponse>(
483
+ method: string,
484
+ body: Record<string, unknown>,
485
+ options?: TelegramApiCallOptions,
486
+ ) => Promise<TResponse>;
487
+ callMultipart: <TResponse>(
488
+ method: string,
489
+ fields: Record<string, string>,
490
+ fileField: string,
491
+ filePath: string,
492
+ fileName: string,
493
+ options?: TelegramApiCallOptions,
494
+ ) => Promise<TResponse>;
495
+ downloadFile: (fileId: string, suggestedName: string) => Promise<string>;
496
+ deleteWebhook: (signal?: AbortSignal) => Promise<boolean>;
497
+ getUpdates: (
498
+ body: Record<string, unknown>,
499
+ signal?: AbortSignal,
500
+ ) => Promise<TelegramUpdate[]>;
501
+ setMyCommands: (
502
+ commands: readonly { command: string; description: string }[],
503
+ ) => Promise<boolean>;
504
+ sendChatAction: (
505
+ chatId: number,
506
+ action: string,
507
+ options?: { message_thread_id?: number },
508
+ ) => Promise<boolean>;
509
+ sendTypingAction: (
510
+ chatId: number,
511
+ options?: { message_thread_id?: number },
512
+ ) => Promise<unknown>;
513
+ sendRecordVoiceAction: (
514
+ chatId: number,
515
+ options?: { message_thread_id?: number },
516
+ ) => Promise<unknown>;
517
+ sendMessageDraft: (
518
+ chatId: number,
519
+ draftId: number,
520
+ text?: string,
521
+ options?: {
522
+ parse_mode?: string;
523
+ entities?: unknown[];
524
+ message_thread_id?: number;
525
+ },
526
+ ) => Promise<boolean>;
527
+ sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
528
+ sendRichMessage: (
529
+ body: TelegramSendRichMessageBody,
530
+ ) => Promise<TelegramSentMessage>;
531
+ sendRichMessageDraft: (
532
+ body: TelegramSendRichMessageDraftBody,
533
+ ) => Promise<boolean>;
534
+ editMessageText: (
535
+ body: TelegramEditMessageTextBody,
536
+ ) => Promise<"edited" | "unchanged">;
537
+ editMessageReplyMarkup: (
538
+ chatId: number,
539
+ messageId: number,
540
+ replyMarkup: unknown,
541
+ ) => Promise<void>;
542
+ answerCallbackQuery: (
543
+ callbackQueryId: string,
544
+ text?: string,
545
+ ) => Promise<void>;
546
+ answerGuestQuery: (
547
+ guestQueryId: string,
548
+ text?: string,
549
+ options?: TelegramAnswerGuestQueryOptions,
550
+ ) => Promise<void>;
551
+ deleteMessage: (chatId: number, messageId: number) => Promise<void>;
552
+ prepareTempDir: () => Promise<number>;
553
+ }
554
+
555
+ function sanitizeFileName(name: string): string {
556
+ return name.replace(/[^a-zA-Z0-9._-]+/g, "_");
557
+ }
558
+
559
+ class TelegramApiMalformedSuccessError extends Error {
560
+ constructor(method: string, detail: string) {
561
+ super(`Telegram API ${method} ${detail}`);
562
+ this.name = "TelegramApiMalformedSuccessError";
563
+ }
564
+ }
565
+
566
+ export class TelegramApiCommitUnknownError extends Error {
567
+ readonly kind = "commit-unknown" as const;
568
+ readonly method: string;
569
+ override readonly cause: unknown;
570
+
571
+ constructor(method: string, cause: unknown) {
572
+ super(`Telegram API ${method} may have committed before transport failed.`);
573
+ this.name = "TelegramApiCommitUnknownError";
574
+ this.method = method;
575
+ this.cause = cause;
576
+ }
577
+ }
578
+
579
+ export function isTelegramApiCommitUnknownError(
580
+ error: unknown,
581
+ ): error is TelegramApiCommitUnknownError {
582
+ return error instanceof TelegramApiCommitUnknownError;
583
+ }
584
+
585
+ class TelegramApiHttpError extends Error {
586
+ readonly status: number | undefined;
587
+ readonly retryAfterSeconds: number | undefined;
588
+ constructor(
589
+ message: string,
590
+ status: number | undefined,
591
+ retryAfterSeconds: number | undefined,
592
+ ) {
593
+ super(message);
594
+ this.status = status;
595
+ this.retryAfterSeconds = retryAfterSeconds;
596
+ }
597
+ }
598
+
599
+ export function isTelegramMessageNotModifiedError(error: unknown): boolean {
600
+ return (
601
+ error instanceof Error && error.message.includes("message is not modified")
602
+ );
603
+ }
604
+
605
+ const TELEGRAM_RETRY_SAFE_METHODS = new Set([
606
+ "answerCallbackQuery",
607
+ "closeForumTopic",
608
+ "deleteForumTopic",
609
+ "deleteMessage",
610
+ "deleteWebhook",
611
+ "editForumTopic",
612
+ "editMessageCaption",
613
+ "editMessageReplyMarkup",
614
+ "editMessageText",
615
+ "getChat",
616
+ "getFile",
617
+ "getMe",
618
+ "getUpdates",
619
+ "sendChatAction",
620
+ "sendMessageDraft",
621
+ "sendRichMessageDraft",
622
+ "setMyCommands",
623
+ ]);
624
+
625
+ export function isTelegramApiMethodRetrySafe(method: string): boolean {
626
+ return TELEGRAM_RETRY_SAFE_METHODS.has(method);
627
+ }
628
+
629
+ function isRetryableTelegramApiError(error: unknown): boolean {
630
+ return (
631
+ error instanceof TelegramApiHttpError &&
632
+ (error.status === 429 ||
633
+ (error.status !== undefined && error.status >= 500))
634
+ );
635
+ }
636
+
637
+ function getTelegramRetryDelayMs(
638
+ error: unknown,
639
+ attempt: number,
640
+ baseDelayMs: number,
641
+ ): number {
642
+ if (
643
+ error instanceof TelegramApiHttpError &&
644
+ error.retryAfterSeconds !== undefined
645
+ ) {
646
+ return Math.max(0, error.retryAfterSeconds * 1000);
647
+ }
648
+ return Math.max(0, baseDelayMs * 2 ** attempt);
649
+ }
650
+
651
+ function getTelegramApiAbortReason(signal: AbortSignal): unknown {
652
+ return signal.reason ?? new DOMException("Aborted", "AbortError");
653
+ }
654
+
655
+ function throwIfTelegramApiCallAborted(signal: AbortSignal | undefined): void {
656
+ if (signal?.aborted) throw getTelegramApiAbortReason(signal);
657
+ }
658
+
659
+ function sleepTelegramRetry(
660
+ ms: number,
661
+ signal?: AbortSignal,
662
+ ): Promise<void> {
663
+ if (signal?.aborted) return Promise.reject(getTelegramApiAbortReason(signal));
664
+ return new Promise((resolve, reject) => {
665
+ const timeout = setTimeout(() => {
666
+ signal?.removeEventListener("abort", onAbort);
667
+ resolve();
668
+ }, ms);
669
+ const onAbort = () => {
670
+ clearTimeout(timeout);
671
+ reject(getTelegramApiAbortReason(signal!));
672
+ };
673
+ signal?.addEventListener("abort", onAbort, { once: true });
674
+ if (signal?.aborted) onAbort();
675
+ });
676
+ }
677
+
678
+ function assertTelegramFileSizeWithinLimit(
679
+ size: number | undefined,
680
+ maxFileSizeBytes: number | undefined,
681
+ ): void {
682
+ if (size === undefined || maxFileSizeBytes === undefined) return;
683
+ if (size <= maxFileSizeBytes) return;
684
+ throw new Error(
685
+ `Telegram file exceeds size limit (${size} bytes > ${maxFileSizeBytes} bytes)`,
686
+ );
687
+ }
688
+
689
+ function createTelegramDownloadLimitTransform(
690
+ maxFileSizeBytes: number | undefined,
691
+ ): Transform {
692
+ let downloadedBytes = 0;
693
+ return new Transform({
694
+ transform(chunk: Buffer, _encoding, callback) {
695
+ downloadedBytes += chunk.byteLength;
696
+ try {
697
+ assertTelegramFileSizeWithinLimit(downloadedBytes, maxFileSizeBytes);
698
+ callback(undefined, chunk);
699
+ } catch (error) {
700
+ callback(error instanceof Error ? error : new Error(String(error)));
701
+ }
702
+ },
703
+ });
704
+ }
705
+
706
+ async function writeTelegramDownloadResponse(
707
+ response: Response,
708
+ targetPath: string,
709
+ maxFileSizeBytes: number | undefined,
710
+ ): Promise<void> {
711
+ if (!response.body) {
712
+ const buffer = Buffer.from(await response.arrayBuffer());
713
+ assertTelegramFileSizeWithinLimit(buffer.byteLength, maxFileSizeBytes);
714
+ await writeFile(targetPath, buffer, { mode: 0o600 });
715
+ return;
716
+ }
717
+ await pipeline(
718
+ Readable.from(response.body, { objectMode: false }),
719
+ createTelegramDownloadLimitTransform(maxFileSizeBytes),
720
+ createWriteStream(targetPath, { mode: 0o600 }),
721
+ );
722
+ }
723
+
724
+ async function removeTelegramPartialDownload(path: string): Promise<void> {
725
+ try {
726
+ await unlink(path);
727
+ } catch {
728
+ // ignore
729
+ }
730
+ }
731
+
732
+ async function parseTelegramApiResponse<TResponse>(
733
+ response: Response,
734
+ method: string,
735
+ ): Promise<TelegramApiResponse<TResponse>> {
736
+ let data: TelegramApiResponse<TResponse> | undefined;
737
+ try {
738
+ if (typeof response.text === "function") {
739
+ const text = await response.text();
740
+ data = text
741
+ ? (JSON.parse(text) as TelegramApiResponse<TResponse>)
742
+ : undefined;
743
+ } else {
744
+ data = (await response.json()) as TelegramApiResponse<TResponse>;
745
+ }
746
+ } catch {
747
+ data = undefined;
748
+ }
749
+ if (response.ok === false) {
750
+ const status = `HTTP ${response.status}`;
751
+ const description = data?.description ? `: ${data.description}` : "";
752
+ const retryAfterHeader = response.headers?.get("retry-after");
753
+ const retryAfterSeconds =
754
+ data?.parameters?.retry_after ??
755
+ (retryAfterHeader ? Number.parseInt(retryAfterHeader, 10) : undefined);
756
+ throw new TelegramApiHttpError(
757
+ `Telegram API ${method} failed: ${status}${description}`,
758
+ response.status,
759
+ Number.isFinite(retryAfterSeconds) ? retryAfterSeconds : undefined,
760
+ );
761
+ }
762
+ if (!data) {
763
+ throw new TelegramApiMalformedSuccessError(method, "returned invalid JSON");
764
+ }
765
+ return data;
766
+ }
767
+
768
+ function unwrapTelegramApiResult<TResponse>(
769
+ method: string,
770
+ data: TelegramApiResponse<TResponse>,
771
+ ): TResponse {
772
+ if (data.ok && data.result === undefined) {
773
+ throw new TelegramApiMalformedSuccessError(method, "returned no result");
774
+ }
775
+ if (!data.ok) {
776
+ throw new Error(data.description || `Telegram API ${method} failed`);
777
+ }
778
+ return data.result as TResponse;
779
+ }
780
+
781
+ function getTelegramNetworkFamilyPolicy(
782
+ env: NodeJS.ProcessEnv = process.env,
783
+ ): TelegramNetworkFamilyPolicy {
784
+ const value = env[TELEGRAM_NETWORK_FAMILY_ENV]?.trim().toLowerCase();
785
+ if (
786
+ TELEGRAM_NETWORK_FAMILY_VALUES.has(value as TelegramNetworkFamilyPolicy)
787
+ ) {
788
+ return value as TelegramNetworkFamilyPolicy;
789
+ }
790
+ return "ipv4-fallback";
791
+ }
792
+
793
+ function getTelegramNetworkFamily(
794
+ policy: TelegramNetworkFamilyPolicy,
795
+ ): TelegramNetworkFamily | undefined {
796
+ if (policy === "ipv4") return 4;
797
+ if (policy === "ipv6") return 6;
798
+ return undefined;
799
+ }
800
+
801
+ function isTelegramTransportFailure(error: unknown): boolean {
802
+ if (!(error instanceof Error)) return false;
803
+ if (error.name === "AbortError") return false;
804
+ if (error instanceof TypeError && /fetch failed/i.test(error.message)) {
805
+ return true;
806
+ }
807
+ if (error instanceof AggregateError) return true;
808
+ const code = getErrorCode(error);
809
+ if (
810
+ code === "ECONNREFUSED" ||
811
+ code === "ETIMEDOUT" ||
812
+ code === "ENETUNREACH" ||
813
+ code === "EHOSTUNREACH" ||
814
+ code === "ECONNRESET" ||
815
+ code === "EAI_AGAIN"
816
+ ) {
817
+ return true;
818
+ }
819
+ return isTelegramTransportFailure(error.cause);
820
+ }
821
+
822
+ function getTelegramRequestBodyBuffer(
823
+ body: BodyInit | null | undefined,
824
+ ): Buffer | undefined {
825
+ if (body === undefined || body === null) return undefined;
826
+ if (typeof body === "string") return Buffer.from(body);
827
+ if (body instanceof Uint8Array) return Buffer.from(body);
828
+ throw new Error("Unsupported Telegram HTTPS request body");
829
+ }
830
+
831
+ async function buildTelegramMultipartBody(
832
+ fields: Record<string, string>,
833
+ fileField: string,
834
+ fileBlob: Blob,
835
+ fileName: string,
836
+ ): Promise<{ body: Buffer; contentType: string }> {
837
+ const boundary = `pi-telegram-${randomUUID()}`;
838
+ const chunks: Buffer[] = [];
839
+ for (const [key, value] of Object.entries(fields)) {
840
+ chunks.push(
841
+ Buffer.from(
842
+ `--${boundary}\r\nContent-Disposition: form-data; name="${key}"\r\n\r\n${value}\r\n`,
843
+ ),
844
+ );
845
+ }
846
+ chunks.push(
847
+ Buffer.from(
848
+ `--${boundary}\r\nContent-Disposition: form-data; name="${fileField}"; filename="${fileName}"\r\nContent-Type: ${fileBlob.type || "application/octet-stream"}\r\n\r\n`,
849
+ ),
850
+ Buffer.from(await fileBlob.arrayBuffer()),
851
+ Buffer.from(`\r\n--${boundary}--\r\n`),
852
+ );
853
+ return {
854
+ body: Buffer.concat(chunks),
855
+ contentType: `multipart/form-data; boundary=${boundary}`,
856
+ };
857
+ }
858
+
859
+ async function telegramHttpsFetch(
860
+ input: string | URL | Request,
861
+ init: RequestInit,
862
+ family: TelegramNetworkFamily,
863
+ ): Promise<Response> {
864
+ const url = new URL(
865
+ typeof input === "string" || input instanceof URL ? input : input.url,
866
+ );
867
+ const body = getTelegramRequestBodyBuffer(init.body);
868
+ const headers = new Headers(init.headers);
869
+ if (body && !headers.has("content-length")) {
870
+ headers.set("content-length", String(body.byteLength));
871
+ }
872
+ return new Promise<Response>((resolve, reject) => {
873
+ const req = requestHttps(
874
+ url,
875
+ {
876
+ method: init.method ?? "GET",
877
+ family,
878
+ headers: Object.fromEntries(headers.entries()),
879
+ },
880
+ (res) => {
881
+ const responseHeaders = new Headers();
882
+ for (const [key, value] of Object.entries(res.headers)) {
883
+ if (Array.isArray(value)) responseHeaders.set(key, value.join(", "));
884
+ else if (value !== undefined) responseHeaders.set(key, String(value));
885
+ }
886
+ resolve(
887
+ new Response(Readable.toWeb(res) as ReadableStream<Uint8Array>, {
888
+ status: res.statusCode ?? 200,
889
+ statusText: res.statusMessage,
890
+ headers: responseHeaders,
891
+ }),
892
+ );
893
+ },
894
+ );
895
+ req.on("error", reject);
896
+ if (init.signal) {
897
+ if (init.signal.aborted)
898
+ req.destroy(new DOMException("Aborted", "AbortError"));
899
+ else {
900
+ init.signal.addEventListener(
901
+ "abort",
902
+ () => req.destroy(new DOMException("Aborted", "AbortError")),
903
+ { once: true },
904
+ );
905
+ }
906
+ }
907
+ req.end(body);
908
+ });
909
+ }
910
+
911
+ let telegramHttpsFetchForTesting: typeof telegramHttpsFetch | undefined;
912
+
913
+ export function setTelegramApiHttpsFetchForTesting(
914
+ fetchImpl: typeof telegramHttpsFetch | undefined,
915
+ ): () => void {
916
+ const previous = telegramHttpsFetchForTesting;
917
+ telegramHttpsFetchForTesting = fetchImpl;
918
+ return () => {
919
+ telegramHttpsFetchForTesting = previous;
920
+ };
921
+ }
922
+
923
+ async function telegramFetch(
924
+ input: string | URL | Request,
925
+ init: RequestInit = {},
926
+ family?: TelegramNetworkFamily,
927
+ ): Promise<Response> {
928
+ if (!family) return fetch(input, init);
929
+ return (telegramHttpsFetchForTesting ?? telegramHttpsFetch)(
930
+ input,
931
+ init,
932
+ family,
933
+ );
934
+ }
935
+
936
+ async function callTelegramTransportRequest(
937
+ request: (family?: TelegramNetworkFamily) => Promise<Response>,
938
+ allowFallback = true,
939
+ ): Promise<Response> {
940
+ const policy = getTelegramNetworkFamilyPolicy();
941
+ if (policy === "auto") return request();
942
+ const family = getTelegramNetworkFamily(policy);
943
+ if (family) return request(family);
944
+ if (!allowFallback) return request();
945
+ try {
946
+ return await request();
947
+ } catch (error) {
948
+ if (!isTelegramTransportFailure(error)) throw error;
949
+ return request(4);
950
+ }
951
+ }
952
+
953
+ function getErrorCode(error: Error): string | undefined {
954
+ const maybeCode = (error as { code?: unknown }).code;
955
+ return typeof maybeCode === "string" ? maybeCode : undefined;
956
+ }
957
+
958
+ function getErrorAddress(error: Error): string | undefined {
959
+ const maybeAddress = (error as { address?: unknown }).address;
960
+ return typeof maybeAddress === "string" ? maybeAddress : undefined;
961
+ }
962
+
963
+ function getErrorPort(error: Error): number | undefined {
964
+ const maybePort = (error as { port?: unknown }).port;
965
+ return typeof maybePort === "number" ? maybePort : undefined;
966
+ }
967
+
968
+ function getErrorFamily(error: Error): number | string | undefined {
969
+ const maybeFamily = (error as { family?: unknown }).family;
970
+ if (typeof maybeFamily === "number" || typeof maybeFamily === "string") {
971
+ return maybeFamily;
972
+ }
973
+ return undefined;
974
+ }
975
+
976
+ function describeTelegramErrorSummary(error: Error): {
977
+ name: string;
978
+ message: string;
979
+ code?: string;
980
+ } {
981
+ return {
982
+ name: error.name,
983
+ message: error.message,
984
+ ...(getErrorCode(error) ? { code: getErrorCode(error) } : {}),
985
+ };
986
+ }
987
+
988
+ function describeTelegramTransportAttempt(error: Error): {
989
+ name: string;
990
+ code?: string;
991
+ address?: string;
992
+ port?: number;
993
+ family?: number | string;
994
+ } {
995
+ return {
996
+ name: error.name,
997
+ ...(getErrorCode(error) ? { code: getErrorCode(error) } : {}),
998
+ ...(getErrorAddress(error) ? { address: getErrorAddress(error) } : {}),
999
+ ...(getErrorPort(error) ? { port: getErrorPort(error) } : {}),
1000
+ ...(getErrorFamily(error) ? { family: getErrorFamily(error) } : {}),
1001
+ };
1002
+ }
1003
+
1004
+ function describeTelegramTransportError(error: unknown):
1005
+ | {
1006
+ error: { name: string; message: string; code?: string };
1007
+ cause?: { name: string; message: string; code?: string };
1008
+ attempts?: Array<{
1009
+ name: string;
1010
+ code?: string;
1011
+ address?: string;
1012
+ port?: number;
1013
+ family?: number | string;
1014
+ }>;
1015
+ }
1016
+ | undefined {
1017
+ if (!isTelegramTransportFailure(error) || !(error instanceof Error)) {
1018
+ return undefined;
1019
+ }
1020
+ const cause = error.cause instanceof Error ? error.cause : undefined;
1021
+ const aggregate =
1022
+ error instanceof AggregateError
1023
+ ? error
1024
+ : cause instanceof AggregateError
1025
+ ? cause
1026
+ : undefined;
1027
+ const attempts = aggregate?.errors
1028
+ .filter((attempt): attempt is Error => attempt instanceof Error)
1029
+ .map(describeTelegramTransportAttempt);
1030
+ return {
1031
+ error: describeTelegramErrorSummary(error),
1032
+ ...(cause ? { cause: describeTelegramErrorSummary(cause) } : {}),
1033
+ ...(attempts && attempts.length > 0 ? { attempts } : {}),
1034
+ };
1035
+ }
1036
+
1037
+ function withTelegramTransportDiagnostics(
1038
+ error: unknown,
1039
+ details: Record<string, unknown>,
1040
+ ): Record<string, unknown> {
1041
+ const transport = describeTelegramTransportError(error);
1042
+ return transport ? { ...details, transport } : details;
1043
+ }
1044
+
1045
+ async function callTelegramWithRetry<TResponse>(
1046
+ method: string,
1047
+ request: (family?: TelegramNetworkFamily) => Promise<Response>,
1048
+ options: TelegramApiCallOptions | undefined,
1049
+ ): Promise<TResponse> {
1050
+ const retrySafe =
1051
+ options?.retrySafety === "safe" ||
1052
+ (options?.retrySafety !== "non-idempotent" &&
1053
+ isTelegramApiMethodRetrySafe(method));
1054
+ const maxAttempts = Math.max(1, options?.maxAttempts ?? 3);
1055
+ const retryBaseDelayMs = options?.retryBaseDelayMs ?? 500;
1056
+ const waitBeforeRetry = async (ms: number): Promise<void> => {
1057
+ if (options?.sleep) await options.sleep(ms);
1058
+ else await sleepTelegramRetry(ms, options?.signal);
1059
+ throwIfTelegramApiCallAborted(options?.signal);
1060
+ };
1061
+ for (let attempt = 0; ; attempt += 1) {
1062
+ throwIfTelegramApiCallAborted(options?.signal);
1063
+ try {
1064
+ return unwrapTelegramApiResult(
1065
+ method,
1066
+ await parseTelegramApiResponse<TResponse>(
1067
+ await callTelegramTransportRequest(request, retrySafe),
1068
+ method,
1069
+ ),
1070
+ );
1071
+ } catch (error) {
1072
+ const retryable =
1073
+ isRetryableTelegramApiError(error) &&
1074
+ !(
1075
+ options?.retryRateLimit === false &&
1076
+ error instanceof TelegramApiHttpError &&
1077
+ error.status === 429
1078
+ );
1079
+ if (!retrySafe) {
1080
+ if (error instanceof TelegramApiHttpError && error.status === 429) {
1081
+ if (attempt >= maxAttempts - 1) throw error;
1082
+ await waitBeforeRetry(
1083
+ getTelegramRetryDelayMs(error, attempt, retryBaseDelayMs),
1084
+ );
1085
+ continue;
1086
+ }
1087
+ if (
1088
+ error instanceof TelegramApiMalformedSuccessError ||
1089
+ isTelegramTransportFailure(error) ||
1090
+ (error instanceof TelegramApiHttpError &&
1091
+ error.status !== undefined &&
1092
+ error.status >= 500)
1093
+ ) {
1094
+ throw new TelegramApiCommitUnknownError(method, error);
1095
+ }
1096
+ throw error;
1097
+ }
1098
+ if (attempt >= maxAttempts - 1 || !retryable) throw error;
1099
+ await waitBeforeRetry(
1100
+ getTelegramRetryDelayMs(error, attempt, retryBaseDelayMs),
1101
+ );
1102
+ }
1103
+ }
1104
+ }
1105
+
1106
+ export async function cleanupTelegramTempFiles(
1107
+ tempDir: string,
1108
+ maxAgeMs: number,
1109
+ now = Date.now(),
1110
+ ): Promise<number> {
1111
+ let removedCount = 0;
1112
+ let entries: Array<{ isFile(): boolean; name: string }>;
1113
+ try {
1114
+ entries = await readdir(tempDir, { withFileTypes: true });
1115
+ } catch {
1116
+ return 0;
1117
+ }
1118
+ for (const entry of entries) {
1119
+ if (!entry.isFile()) continue;
1120
+ const path = join(tempDir, entry.name);
1121
+ try {
1122
+ const stats = await stat(path);
1123
+ if (now - stats.mtimeMs <= maxAgeMs) continue;
1124
+ await unlink(path);
1125
+ removedCount += 1;
1126
+ } catch {
1127
+ // ignore
1128
+ }
1129
+ }
1130
+ return removedCount;
1131
+ }
1132
+
1133
+ export async function prepareTelegramTempDir(
1134
+ tempDir: string,
1135
+ maxAgeMs: number,
1136
+ ): Promise<number> {
1137
+ await mkdir(tempDir, { recursive: true, mode: 0o700 });
1138
+ return cleanupTelegramTempFiles(tempDir, maxAgeMs);
1139
+ }
1140
+
1141
+ function assertTelegramBotTokenConfigured(
1142
+ botToken: string | undefined,
1143
+ ): string {
1144
+ if (!botToken) throw new Error("Telegram bot token is not configured");
1145
+ return botToken;
1146
+ }
1147
+
1148
+ export async function callTelegram<TResponse>(
1149
+ botToken: string | undefined,
1150
+ method: string,
1151
+ body: Record<string, unknown>,
1152
+ options?: TelegramApiCallOptions,
1153
+ ): Promise<TResponse> {
1154
+ const configuredBotToken = assertTelegramBotTokenConfigured(botToken);
1155
+ return callTelegramWithRetry(
1156
+ method,
1157
+ async (family) =>
1158
+ telegramFetch(
1159
+ `${TELEGRAM_API_BASE}/bot${configuredBotToken}/${method}`,
1160
+ {
1161
+ method: "POST",
1162
+ headers: { "content-type": "application/json" },
1163
+ body: JSON.stringify(body),
1164
+ signal: options?.signal,
1165
+ },
1166
+ family,
1167
+ ),
1168
+ options,
1169
+ );
1170
+ }
1171
+
1172
+ export type TelegramBotIdentityResponse = Pick<
1173
+ TelegramApiResponse<TelegramUser>,
1174
+ "ok" | "result" | "description"
1175
+ >;
1176
+
1177
+ export async function fetchTelegramBotIdentity(
1178
+ botToken: string,
1179
+ fetchImpl: typeof fetch = fetch,
1180
+ ): Promise<TelegramBotIdentityResponse> {
1181
+ const url = `${TELEGRAM_API_BASE}/bot${botToken}/getMe`;
1182
+ const response = await callTelegramTransportRequest((family) =>
1183
+ fetchImpl === fetch ? telegramFetch(url, {}, family) : fetchImpl(url),
1184
+ );
1185
+ return response.json() as Promise<TelegramBotIdentityResponse>;
1186
+ }
1187
+
1188
+ /**
1189
+ * Low-level helper to send a multipart/form-data request to the Telegram Bot API.
1190
+ * This is the core implementation used for uploading voice messages, photos,
1191
+ * documents, animations, etc. It handles FormData construction, retry logic
1192
+ * (via callTelegramWithRetry), and error recording under the "multipart" category.
1193
+ */
1194
+ export async function callTelegramMultipart<TResponse>(
1195
+ botToken: string | undefined,
1196
+ method: string,
1197
+ fields: Record<string, string>,
1198
+ fileField: string,
1199
+ filePath: string,
1200
+ fileName: string,
1201
+ options?: TelegramApiCallOptions,
1202
+ ): Promise<TResponse> {
1203
+ const configuredBotToken = assertTelegramBotTokenConfigured(botToken);
1204
+ const fileBlob = await openAsBlob(filePath);
1205
+ return callTelegramWithRetry(
1206
+ method,
1207
+ async (family) => {
1208
+ if (family) {
1209
+ const multipart = await buildTelegramMultipartBody(
1210
+ fields,
1211
+ fileField,
1212
+ fileBlob,
1213
+ fileName,
1214
+ );
1215
+ return telegramFetch(
1216
+ `${TELEGRAM_API_BASE}/bot${configuredBotToken}/${method}`,
1217
+ {
1218
+ method: "POST",
1219
+ headers: { "content-type": multipart.contentType },
1220
+ body: multipart.body as unknown as BodyInit,
1221
+ signal: options?.signal,
1222
+ },
1223
+ family,
1224
+ );
1225
+ }
1226
+ const form = new FormData();
1227
+ for (const [key, value] of Object.entries(fields)) {
1228
+ form.set(key, value);
1229
+ }
1230
+ form.set(fileField, fileBlob, fileName);
1231
+ return telegramFetch(
1232
+ `${TELEGRAM_API_BASE}/bot${configuredBotToken}/${method}`,
1233
+ {
1234
+ method: "POST",
1235
+ body: form,
1236
+ signal: options?.signal,
1237
+ },
1238
+ );
1239
+ },
1240
+ options,
1241
+ );
1242
+ }
1243
+
1244
+ export async function downloadTelegramFile(
1245
+ botToken: string | undefined,
1246
+ fileId: string,
1247
+ suggestedName: string,
1248
+ tempDir: string,
1249
+ options?: TelegramFileDownloadOptions,
1250
+ ): Promise<string> {
1251
+ const configuredBotToken = assertTelegramBotTokenConfigured(botToken);
1252
+ const file = await callTelegram<TelegramGetFileResult>(
1253
+ configuredBotToken,
1254
+ "getFile",
1255
+ { file_id: fileId },
1256
+ { signal: options?.signal },
1257
+ );
1258
+ assertTelegramFileSizeWithinLimit(file.file_size, options?.maxFileSizeBytes);
1259
+ await mkdir(tempDir, { recursive: true, mode: 0o700 });
1260
+ const targetPath = join(
1261
+ tempDir,
1262
+ `${randomUUID()}-${sanitizeFileName(suggestedName)}`,
1263
+ );
1264
+ const response = await callTelegramTransportRequest((family) =>
1265
+ telegramFetch(
1266
+ `${TELEGRAM_API_BASE}/file/bot${configuredBotToken}/${file.file_path}`,
1267
+ { signal: options?.signal },
1268
+ family,
1269
+ ),
1270
+ );
1271
+ if (!response.ok) {
1272
+ throw new Error(`Failed to download Telegram file: ${response.status}`);
1273
+ }
1274
+ const contentLength = response.headers?.get("content-length");
1275
+ assertTelegramFileSizeWithinLimit(
1276
+ contentLength ? Number.parseInt(contentLength, 10) : undefined,
1277
+ options?.maxFileSizeBytes,
1278
+ );
1279
+ try {
1280
+ await writeTelegramDownloadResponse(
1281
+ response,
1282
+ targetPath,
1283
+ options?.maxFileSizeBytes,
1284
+ );
1285
+ } catch (error) {
1286
+ await removeTelegramPartialDownload(targetPath);
1287
+ throw error;
1288
+ }
1289
+ return targetPath;
1290
+ }
1291
+
1292
+ export async function answerTelegramCallbackQuery(
1293
+ botToken: string | undefined,
1294
+ callbackQueryId: string,
1295
+ text?: string,
1296
+ options: TelegramAnswerCallbackQueryOptions = {},
1297
+ ): Promise<void> {
1298
+ try {
1299
+ await callTelegram<boolean>(
1300
+ botToken,
1301
+ "answerCallbackQuery",
1302
+ text
1303
+ ? { callback_query_id: callbackQueryId, text }
1304
+ : { callback_query_id: callbackQueryId },
1305
+ );
1306
+ } catch (error) {
1307
+ options.recordRuntimeEvent?.(
1308
+ "api",
1309
+ error,
1310
+ withTelegramTransportDiagnostics(error, {
1311
+ method: "answerCallbackQuery",
1312
+ }),
1313
+ );
1314
+ }
1315
+ }
1316
+
1317
+ export async function deleteTelegramMessage(
1318
+ botToken: string | undefined,
1319
+ chatId: number,
1320
+ messageId: number,
1321
+ ): Promise<void> {
1322
+ try {
1323
+ await callTelegram<boolean>(botToken, "deleteMessage", {
1324
+ chat_id: chatId,
1325
+ message_id: messageId,
1326
+ });
1327
+ } catch {
1328
+ // ignore
1329
+ }
1330
+ }
1331
+
1332
+ export function createTelegramChatActionSender<TAction extends string>(
1333
+ sendChatAction: (
1334
+ chatId: number,
1335
+ action: TAction,
1336
+ options?: { message_thread_id?: number },
1337
+ ) => Promise<unknown>,
1338
+ action: TAction,
1339
+ ): (
1340
+ chatId: number,
1341
+ options?: { message_thread_id?: number },
1342
+ ) => Promise<unknown> {
1343
+ return (chatId, options) => sendChatAction(chatId, action, options);
1344
+ }
1345
+
1346
+ export function createTelegramNativeMarkdownDraftSender(deps: {
1347
+ sendMessageDraft: TelegramBridgeApiRuntime["sendMessageDraft"];
1348
+ sendRichMessageDraft: TelegramBridgeApiRuntime["sendRichMessageDraft"];
1349
+ }): TelegramBridgeApiRuntime["sendMessageDraft"] {
1350
+ return (chatId, draftId, text, options) => {
1351
+ if (text === undefined) {
1352
+ return deps.sendMessageDraft(chatId, draftId, text, options);
1353
+ }
1354
+ return deps.sendRichMessageDraft({
1355
+ chat_id: chatId,
1356
+ draft_id: draftId,
1357
+ rich_message: { markdown: text, skip_entity_detection: true },
1358
+ ...(options?.message_thread_id !== undefined
1359
+ ? { message_thread_id: options.message_thread_id }
1360
+ : {}),
1361
+ });
1362
+ };
1363
+ }
1364
+
1365
+ export function createTelegramAssistantDraftSender(deps: {
1366
+ getAssistantRenderingMode: () => "rich" | "html";
1367
+ renderMarkdownToHtmlDraft: (markdown: string) => string;
1368
+ sendMessageDraft: TelegramBridgeApiRuntime["sendMessageDraft"];
1369
+ sendRichMessageDraft: TelegramBridgeApiRuntime["sendRichMessageDraft"];
1370
+ }): TelegramBridgeApiRuntime["sendMessageDraft"] {
1371
+ const sendNativeDraft = createTelegramNativeMarkdownDraftSender(deps);
1372
+ return (chatId, draftId, text, options) => {
1373
+ if (text === undefined || deps.getAssistantRenderingMode() === "rich") {
1374
+ return sendNativeDraft(chatId, draftId, text, options);
1375
+ }
1376
+ return deps.sendMessageDraft(
1377
+ chatId,
1378
+ draftId,
1379
+ deps.renderMarkdownToHtmlDraft(text),
1380
+ {
1381
+ ...options,
1382
+ parse_mode: "HTML",
1383
+ },
1384
+ );
1385
+ };
1386
+ }
1387
+
1388
+ export function createDefaultTelegramBridgeApiRuntime(deps: {
1389
+ getBotToken: () => string | undefined;
1390
+ recordRuntimeEvent: TelegramBridgeApiRuntimeDeps["recordRuntimeEvent"];
1391
+ }): TelegramBridgeApiRuntime {
1392
+ return createTelegramBridgeApiRuntime({
1393
+ client: createTelegramApiClient(deps.getBotToken, {
1394
+ recordRuntimeEvent: deps.recordRuntimeEvent,
1395
+ }),
1396
+ tempDir: getTelegramApiTempDir(),
1397
+ maxFileSizeBytes: TELEGRAM_INBOUND_FILE_MAX_BYTES,
1398
+ tempFileMaxAgeMs: TELEGRAM_TEMP_FILE_MAX_AGE_MS,
1399
+ recordRuntimeEvent: deps.recordRuntimeEvent,
1400
+ });
1401
+ }
1402
+
1403
+ export function createTelegramBridgeApiRuntime(
1404
+ deps: TelegramBridgeApiRuntimeDeps,
1405
+ ): TelegramBridgeApiRuntime {
1406
+ const now = deps.now ?? Date.now;
1407
+ const chatActionMinIntervalMs = Math.max(
1408
+ 0,
1409
+ deps.chatActionMinIntervalMs ?? 2_000,
1410
+ );
1411
+ const chatActionMaxGates = Math.max(1, deps.chatActionMaxGates ?? 256);
1412
+ const chatActionGates = new Map<
1413
+ string,
1414
+ { inFlight?: Promise<unknown>; notBeforeMs: number }
1415
+ >();
1416
+ const getChatActionKey = (
1417
+ method: string,
1418
+ body: Record<string, unknown>,
1419
+ ): string | undefined => {
1420
+ if (method !== "sendChatAction") return undefined;
1421
+ const chatId = body.chat_id;
1422
+ const action = body.action;
1423
+ if (
1424
+ (typeof chatId !== "number" && typeof chatId !== "string") ||
1425
+ typeof action !== "string"
1426
+ ) {
1427
+ return undefined;
1428
+ }
1429
+ const threadId = body.message_thread_id;
1430
+ return `${String(chatId)}:${
1431
+ typeof threadId === "number" || typeof threadId === "string"
1432
+ ? String(threadId)
1433
+ : "all"
1434
+ }:${action}`;
1435
+ };
1436
+ const callRecorded = async <TResponse>(
1437
+ method: string,
1438
+ body: Record<string, unknown>,
1439
+ options?: TelegramApiCallOptions,
1440
+ ): Promise<TResponse> => {
1441
+ const chatActionKey = getChatActionKey(method, body);
1442
+ if (chatActionKey) {
1443
+ const nowMs = now();
1444
+ for (const [key, candidate] of chatActionGates) {
1445
+ if (!candidate.inFlight && nowMs >= candidate.notBeforeMs) {
1446
+ chatActionGates.delete(key);
1447
+ }
1448
+ }
1449
+ let gate = chatActionGates.get(chatActionKey);
1450
+ if (!gate) {
1451
+ if (chatActionGates.size >= chatActionMaxGates) return true as TResponse;
1452
+ gate = { notBeforeMs: 0 };
1453
+ chatActionGates.set(chatActionKey, gate);
1454
+ }
1455
+ if (gate.inFlight) return (await gate.inFlight) as TResponse;
1456
+ if (now() < gate.notBeforeMs) return true as TResponse;
1457
+ let request: Promise<TResponse>;
1458
+ request = Promise.resolve()
1459
+ .then(() =>
1460
+ deps.client.call<TResponse>(method, body, {
1461
+ ...options,
1462
+ retryRateLimit: false,
1463
+ }),
1464
+ )
1465
+ .then((result) => {
1466
+ gate.notBeforeMs = now() + chatActionMinIntervalMs;
1467
+ return result;
1468
+ })
1469
+ .catch((error: unknown) => {
1470
+ if (error instanceof TelegramApiHttpError && error.status === 429) {
1471
+ const retryAfterMs = Math.max(
1472
+ chatActionMinIntervalMs,
1473
+ (error.retryAfterSeconds ?? 0) * 1_000,
1474
+ );
1475
+ gate.notBeforeMs = now() + retryAfterMs;
1476
+ deps.recordRuntimeEvent(
1477
+ "api",
1478
+ error,
1479
+ withTelegramTransportDiagnostics(error, {
1480
+ method,
1481
+ rateLimited: true,
1482
+ retryAfterMs,
1483
+ }),
1484
+ );
1485
+ return true as TResponse;
1486
+ }
1487
+ deps.recordRuntimeEvent(
1488
+ "api",
1489
+ error,
1490
+ withTelegramTransportDiagnostics(error, { method }),
1491
+ );
1492
+ throw error;
1493
+ })
1494
+ .finally(() => {
1495
+ if (gate.inFlight === request) gate.inFlight = undefined;
1496
+ });
1497
+ gate.inFlight = request;
1498
+ return request;
1499
+ }
1500
+ try {
1501
+ return await deps.client.call<TResponse>(method, body, options);
1502
+ } catch (error) {
1503
+ deps.recordRuntimeEvent(
1504
+ "api",
1505
+ error,
1506
+ withTelegramTransportDiagnostics(error, { method }),
1507
+ );
1508
+ throw error;
1509
+ }
1510
+ };
1511
+ return {
1512
+ call: callRecorded,
1513
+
1514
+ /**
1515
+ * Sends a multipart/form-data request (used for sending voice messages,
1516
+ * photos, documents, animations, etc.).
1517
+ * Errors are recorded under the "multipart" category for diagnostics.
1518
+ */
1519
+ callMultipart: async (
1520
+ method,
1521
+ fields,
1522
+ fileField,
1523
+ filePath,
1524
+ fileName,
1525
+ options,
1526
+ ) => {
1527
+ try {
1528
+ return await deps.client.callMultipart(
1529
+ method,
1530
+ fields,
1531
+ fileField,
1532
+ filePath,
1533
+ fileName,
1534
+ options,
1535
+ );
1536
+ } catch (error) {
1537
+ deps.recordRuntimeEvent(
1538
+ "multipart",
1539
+ error,
1540
+ withTelegramTransportDiagnostics(error, { method, fileName }),
1541
+ );
1542
+ throw error;
1543
+ }
1544
+ },
1545
+
1546
+ /**
1547
+ * Downloads a file from the Telegram servers into the local temp directory.
1548
+ * Used for inbound voice messages, photos, documents, etc.
1549
+ */
1550
+ downloadFile: async (fileId, suggestedName) => {
1551
+ try {
1552
+ return await deps.client.downloadFile(
1553
+ fileId,
1554
+ suggestedName,
1555
+ deps.tempDir,
1556
+ {
1557
+ maxFileSizeBytes: deps.maxFileSizeBytes,
1558
+ },
1559
+ );
1560
+ } catch (error) {
1561
+ deps.recordRuntimeEvent(
1562
+ "download",
1563
+ error,
1564
+ withTelegramTransportDiagnostics(error, { suggestedName }),
1565
+ );
1566
+ throw error;
1567
+ }
1568
+ },
1569
+ deleteWebhook: (signal) =>
1570
+ callRecorded<boolean>(
1571
+ "deleteWebhook",
1572
+ { drop_pending_updates: false },
1573
+ { signal },
1574
+ ),
1575
+ getUpdates: (body, signal) =>
1576
+ callRecorded<TelegramUpdate[]>("getUpdates", body, { signal }),
1577
+ setMyCommands: (commands) =>
1578
+ callRecorded<boolean>("setMyCommands", { commands }),
1579
+ sendChatAction: (chatId, action, options) =>
1580
+ callRecorded<boolean>("sendChatAction", {
1581
+ chat_id: chatId,
1582
+ action,
1583
+ ...(options?.message_thread_id !== undefined
1584
+ ? { message_thread_id: options.message_thread_id }
1585
+ : {}),
1586
+ }),
1587
+ sendTypingAction: createTelegramChatActionSender(
1588
+ (chatId, action, options) =>
1589
+ callRecorded<boolean>("sendChatAction", {
1590
+ chat_id: chatId,
1591
+ action,
1592
+ ...(options?.message_thread_id !== undefined
1593
+ ? { message_thread_id: options.message_thread_id }
1594
+ : {}),
1595
+ }),
1596
+ "typing",
1597
+ ),
1598
+ sendRecordVoiceAction: createTelegramChatActionSender(
1599
+ (chatId, action, options) =>
1600
+ callRecorded<boolean>("sendChatAction", {
1601
+ chat_id: chatId,
1602
+ action,
1603
+ ...(options?.message_thread_id !== undefined
1604
+ ? { message_thread_id: options.message_thread_id }
1605
+ : {}),
1606
+ }),
1607
+ "record_voice",
1608
+ ),
1609
+ sendMessageDraft: (chatId, draftId, text, options) => {
1610
+ const body: Record<string, unknown> = {
1611
+ chat_id: chatId,
1612
+ draft_id: draftId,
1613
+ };
1614
+ if (text !== undefined) body.text = text;
1615
+ if (options?.parse_mode !== undefined)
1616
+ body.parse_mode = options.parse_mode;
1617
+ if (options?.entities !== undefined) body.entities = options.entities;
1618
+ if (options?.message_thread_id !== undefined)
1619
+ body.message_thread_id = options.message_thread_id;
1620
+ return callRecorded<boolean>("sendMessageDraft", body);
1621
+ },
1622
+ sendMessage: (body) =>
1623
+ callRecorded<TelegramSentMessage>("sendMessage", body),
1624
+ sendRichMessage: (body) =>
1625
+ callRecorded<TelegramSentMessage>("sendRichMessage", body),
1626
+ sendRichMessageDraft: (body) =>
1627
+ callRecorded<boolean>("sendRichMessageDraft", body),
1628
+ editMessageText: async (body) => {
1629
+ try {
1630
+ await deps.client.call("editMessageText", body);
1631
+ return "edited";
1632
+ } catch (error) {
1633
+ if (isTelegramMessageNotModifiedError(error)) return "unchanged";
1634
+ deps.recordRuntimeEvent(
1635
+ "api",
1636
+ error,
1637
+ withTelegramTransportDiagnostics(error, {
1638
+ method: "editMessageText",
1639
+ }),
1640
+ );
1641
+ throw error;
1642
+ }
1643
+ },
1644
+ editMessageReplyMarkup: async (chatId, messageId, replyMarkup) => {
1645
+ await callRecorded("editMessageReplyMarkup", {
1646
+ chat_id: chatId,
1647
+ message_id: messageId,
1648
+ reply_markup: replyMarkup,
1649
+ });
1650
+ },
1651
+ answerCallbackQuery: async (callbackQueryId, text) => {
1652
+ try {
1653
+ await deps.client.answerCallbackQuery(callbackQueryId, text);
1654
+ } catch (error) {
1655
+ deps.recordRuntimeEvent(
1656
+ "api",
1657
+ error,
1658
+ withTelegramTransportDiagnostics(error, {
1659
+ method: "answerCallbackQuery",
1660
+ }),
1661
+ );
1662
+ }
1663
+ },
1664
+ answerGuestQuery: (
1665
+ guestQueryId: string,
1666
+ text: string | undefined,
1667
+ options: TelegramAnswerGuestQueryOptions | undefined,
1668
+ ) => {
1669
+ const body: Record<string, unknown> = { guest_query_id: guestQueryId };
1670
+ if (options?.result) {
1671
+ body.result = options.result;
1672
+ } else if (text !== undefined || options?.richMessage) {
1673
+ const inputContent: Record<string, unknown> = options?.richMessage
1674
+ ? { rich_message: options.richMessage }
1675
+ : { message_text: text };
1676
+ if (!options?.richMessage && options?.parseMode) {
1677
+ inputContent.parse_mode = options.parseMode;
1678
+ }
1679
+ body.result = {
1680
+ type: "article",
1681
+ id: "1",
1682
+ title: "Response",
1683
+ input_message_content: inputContent,
1684
+ };
1685
+ }
1686
+ return callRecorded<void>("answerGuestQuery", body);
1687
+ },
1688
+ prepareTempDir: () =>
1689
+ prepareTelegramTempDir(deps.tempDir, deps.tempFileMaxAgeMs),
1690
+ deleteMessage: (chatId, messageId) =>
1691
+ callRecorded<boolean>("deleteMessage", {
1692
+ chat_id: chatId,
1693
+ message_id: messageId,
1694
+ }).then(() => {}),
1695
+ };
1696
+ }
1697
+
1698
+ /**
1699
+ * Creates a low-level Telegram Bot API client.
1700
+ * This is the main entry point for all direct Bot API communication
1701
+ * (both JSON calls and multipart uploads for files/voice).
1702
+ */
1703
+ export function createTelegramApiClient(
1704
+ getBotToken: () => string | undefined,
1705
+ options: TelegramAnswerCallbackQueryOptions = {},
1706
+ ): TelegramApiClient {
1707
+ return {
1708
+ call: async (method, body, options) => {
1709
+ return callTelegram(getBotToken(), method, body, options);
1710
+ },
1711
+ callMultipart: async (
1712
+ method,
1713
+ fields,
1714
+ fileField,
1715
+ filePath,
1716
+ fileName,
1717
+ options,
1718
+ ) => {
1719
+ return callTelegramMultipart(
1720
+ getBotToken(),
1721
+ method,
1722
+ fields,
1723
+ fileField,
1724
+ filePath,
1725
+ fileName,
1726
+ options,
1727
+ );
1728
+ },
1729
+ downloadFile: async (fileId, suggestedName, tempDir, options) => {
1730
+ return downloadTelegramFile(
1731
+ getBotToken(),
1732
+ fileId,
1733
+ suggestedName,
1734
+ tempDir,
1735
+ options,
1736
+ );
1737
+ },
1738
+ answerCallbackQuery: async (callbackQueryId, text) => {
1739
+ await answerTelegramCallbackQuery(
1740
+ getBotToken(),
1741
+ callbackQueryId,
1742
+ text,
1743
+ options,
1744
+ );
1745
+ },
1746
+ };
1747
+ }