toolroll 0.5.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 (626) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1113 -0
  3. package/THIRD_PARTY_NOTICES.md +449 -0
  4. package/dist/accent-colors.d.ts +48 -0
  5. package/dist/accent-colors.js +122 -0
  6. package/dist/action-ledger.d.ts +26 -0
  7. package/dist/action-ledger.js +75 -0
  8. package/dist/agent-fence.d.ts +42 -0
  9. package/dist/agent-fence.js +183 -0
  10. package/dist/agentconfig.d.ts +216 -0
  11. package/dist/agentconfig.js +454 -0
  12. package/dist/api-tokens.d.ts +16 -0
  13. package/dist/api-tokens.js +27 -0
  14. package/dist/approval-policy.d.ts +69 -0
  15. package/dist/approval-policy.js +137 -0
  16. package/dist/approval-rules-ui.d.ts +25 -0
  17. package/dist/approval-rules-ui.js +39 -0
  18. package/dist/assignment-adapters.d.ts +180 -0
  19. package/dist/assignment-adapters.js +239 -0
  20. package/dist/assignment-brief.d.ts +71 -0
  21. package/dist/assignment-brief.js +129 -0
  22. package/dist/assignment-delivery.d.ts +56 -0
  23. package/dist/assignment-delivery.js +160 -0
  24. package/dist/assignment-presentation.d.ts +26 -0
  25. package/dist/assignment-presentation.js +80 -0
  26. package/dist/assignment-status.d.ts +62 -0
  27. package/dist/assignment-status.js +152 -0
  28. package/dist/assignment-ui.d.ts +71 -0
  29. package/dist/assignment-ui.js +103 -0
  30. package/dist/assignment.d.ts +222 -0
  31. package/dist/assignment.js +399 -0
  32. package/dist/attest.d.ts +56 -0
  33. package/dist/attest.js +153 -0
  34. package/dist/backend.d.ts +97 -0
  35. package/dist/backend.js +166 -0
  36. package/dist/backup-ui.d.ts +22 -0
  37. package/dist/backup-ui.js +59 -0
  38. package/dist/backup.d.ts +84 -0
  39. package/dist/backup.js +421 -0
  40. package/dist/beads.d.ts +34 -0
  41. package/dist/beads.js +135 -0
  42. package/dist/bin.d.ts +2 -0
  43. package/dist/bin.js +22 -0
  44. package/dist/board.d.ts +150 -0
  45. package/dist/board.js +210 -0
  46. package/dist/boot-identity.d.ts +63 -0
  47. package/dist/boot-identity.js +99 -0
  48. package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
  49. package/dist/browser/workspace.css +4 -0
  50. package/dist/browser/workspace.js +225 -0
  51. package/dist/browser-crew.d.ts +11 -0
  52. package/dist/browser-crew.js +50 -0
  53. package/dist/browser-shell.d.ts +8 -0
  54. package/dist/browser-shell.js +60 -0
  55. package/dist/browser-workspace.d.ts +780 -0
  56. package/dist/browser-workspace.js +47 -0
  57. package/dist/budget-alerts.d.ts +19 -0
  58. package/dist/budget-alerts.js +46 -0
  59. package/dist/builder.d.ts +370 -0
  60. package/dist/builder.js +3262 -0
  61. package/dist/capscan.d.ts +35 -0
  62. package/dist/capscan.js +113 -0
  63. package/dist/chat-acceptance.d.ts +71 -0
  64. package/dist/chat-acceptance.js +120 -0
  65. package/dist/chat-actions.d.ts +265 -0
  66. package/dist/chat-actions.js +1273 -0
  67. package/dist/chat-channel.d.ts +109 -0
  68. package/dist/chat-channel.js +652 -0
  69. package/dist/chat-continuity.d.ts +32 -0
  70. package/dist/chat-continuity.js +317 -0
  71. package/dist/chat-controls.d.ts +113 -0
  72. package/dist/chat-controls.js +53 -0
  73. package/dist/chat-delivery-state.d.ts +159 -0
  74. package/dist/chat-delivery-state.js +309 -0
  75. package/dist/chat-delivery.d.ts +38 -0
  76. package/dist/chat-delivery.js +693 -0
  77. package/dist/chat-display.d.ts +1 -0
  78. package/dist/chat-display.js +18 -0
  79. package/dist/chat-evidence.d.ts +123 -0
  80. package/dist/chat-evidence.js +207 -0
  81. package/dist/chat-flow.d.ts +44 -0
  82. package/dist/chat-flow.js +215 -0
  83. package/dist/chat-inbox.d.ts +57 -0
  84. package/dist/chat-inbox.js +123 -0
  85. package/dist/chat-polish.d.ts +16 -0
  86. package/dist/chat-polish.js +135 -0
  87. package/dist/chat-review.d.ts +46 -0
  88. package/dist/chat-review.js +98 -0
  89. package/dist/chat-rooms.d.ts +123 -0
  90. package/dist/chat-rooms.js +216 -0
  91. package/dist/chat-task-actions.d.ts +44 -0
  92. package/dist/chat-task-actions.js +79 -0
  93. package/dist/check-progress.d.ts +51 -0
  94. package/dist/check-progress.js +290 -0
  95. package/dist/child-database.d.ts +12 -0
  96. package/dist/child-database.js +30 -0
  97. package/dist/claim.d.ts +688 -0
  98. package/dist/claim.js +1740 -0
  99. package/dist/cli.d.ts +137 -0
  100. package/dist/cli.js +1461 -0
  101. package/dist/codex-limits.d.ts +15 -0
  102. package/dist/codex-limits.js +145 -0
  103. package/dist/coding-context.d.ts +55 -0
  104. package/dist/coding-context.js +92 -0
  105. package/dist/coding-handoff.d.ts +57 -0
  106. package/dist/coding-handoff.js +277 -0
  107. package/dist/coding-provider.d.ts +72 -0
  108. package/dist/coding-provider.js +424 -0
  109. package/dist/coding-shipping-ui.d.ts +4 -0
  110. package/dist/coding-shipping-ui.js +12 -0
  111. package/dist/coding-types.d.ts +62 -0
  112. package/dist/coding-types.js +1 -0
  113. package/dist/coding-ui.d.ts +17 -0
  114. package/dist/coding-ui.js +364 -0
  115. package/dist/coding-update.d.ts +15 -0
  116. package/dist/coding-update.js +149 -0
  117. package/dist/coding-workspace.d.ts +124 -0
  118. package/dist/coding-workspace.js +768 -0
  119. package/dist/container-state.d.ts +5 -0
  120. package/dist/container-state.js +62 -0
  121. package/dist/containment.d.ts +197 -0
  122. package/dist/containment.js +559 -0
  123. package/dist/contest.d.ts +318 -0
  124. package/dist/contest.js +753 -0
  125. package/dist/control-setup.d.ts +35 -0
  126. package/dist/control-setup.js +40 -0
  127. package/dist/control-ui.d.ts +29 -0
  128. package/dist/control-ui.js +53 -0
  129. package/dist/controller-service.d.ts +1 -0
  130. package/dist/controller-service.js +19 -0
  131. package/dist/controller-supervisor.d.ts +30 -0
  132. package/dist/controller-supervisor.js +104 -0
  133. package/dist/converse.d.ts +304 -0
  134. package/dist/converse.js +849 -0
  135. package/dist/coordinator-proposals.d.ts +29 -0
  136. package/dist/coordinator-proposals.js +137 -0
  137. package/dist/coordinator.d.ts +186 -0
  138. package/dist/coordinator.js +447 -0
  139. package/dist/credentials-ui.d.ts +25 -0
  140. package/dist/credentials-ui.js +48 -0
  141. package/dist/daemon.d.ts +209 -0
  142. package/dist/daemon.js +604 -0
  143. package/dist/decision.d.ts +120 -0
  144. package/dist/decision.js +388 -0
  145. package/dist/demo.d.ts +55 -0
  146. package/dist/demo.js +992 -0
  147. package/dist/desktop-access.d.ts +28 -0
  148. package/dist/desktop-access.js +88 -0
  149. package/dist/desktop-bundle.d.ts +16 -0
  150. package/dist/desktop-bundle.js +74 -0
  151. package/dist/desktop-host.d.ts +54 -0
  152. package/dist/desktop-host.js +508 -0
  153. package/dist/desktop-update-gate.d.ts +12 -0
  154. package/dist/desktop-update-gate.js +91 -0
  155. package/dist/desktop-update-recovery.d.ts +14 -0
  156. package/dist/desktop-update-recovery.js +243 -0
  157. package/dist/desktop-update.d.ts +110 -0
  158. package/dist/desktop-update.js +740 -0
  159. package/dist/discord-api.d.ts +20 -0
  160. package/dist/discord-api.js +138 -0
  161. package/dist/discord-chat.d.ts +16 -0
  162. package/dist/discord-chat.js +381 -0
  163. package/dist/discord-settings.d.ts +7 -0
  164. package/dist/discord-settings.js +75 -0
  165. package/dist/discord.d.ts +10 -0
  166. package/dist/discord.js +169 -0
  167. package/dist/discover.d.ts +75 -0
  168. package/dist/discover.js +150 -0
  169. package/dist/dispatch.d.ts +100 -0
  170. package/dist/dispatch.js +311 -0
  171. package/dist/dispose.d.ts +161 -0
  172. package/dist/dispose.js +803 -0
  173. package/dist/email-settings.d.ts +34 -0
  174. package/dist/email-settings.js +50 -0
  175. package/dist/envelope.d.ts +47 -0
  176. package/dist/envelope.js +105 -0
  177. package/dist/evidence-pack.d.ts +185 -0
  178. package/dist/evidence-pack.js +258 -0
  179. package/dist/evidence.d.ts +345 -0
  180. package/dist/evidence.js +782 -0
  181. package/dist/exec.d.ts +285 -0
  182. package/dist/exec.js +1841 -0
  183. package/dist/exhaustion.d.ts +85 -0
  184. package/dist/exhaustion.js +141 -0
  185. package/dist/export-ui.d.ts +4 -0
  186. package/dist/export-ui.js +17 -0
  187. package/dist/export.d.ts +48 -0
  188. package/dist/export.js +305 -0
  189. package/dist/flow-actions.d.ts +77 -0
  190. package/dist/flow-actions.js +203 -0
  191. package/dist/flow-code.d.ts +50 -0
  192. package/dist/flow-code.js +159 -0
  193. package/dist/flow-draft.d.ts +38 -0
  194. package/dist/flow-draft.js +80 -0
  195. package/dist/flow-engine.d.ts +66 -0
  196. package/dist/flow-engine.js +416 -0
  197. package/dist/flow-insights.d.ts +95 -0
  198. package/dist/flow-insights.js +149 -0
  199. package/dist/flow-live.d.ts +24 -0
  200. package/dist/flow-live.js +150 -0
  201. package/dist/flow-people.d.ts +24 -0
  202. package/dist/flow-people.js +91 -0
  203. package/dist/flow-replies.d.ts +48 -0
  204. package/dist/flow-replies.js +113 -0
  205. package/dist/flow-scripts.d.ts +38 -0
  206. package/dist/flow-scripts.js +73 -0
  207. package/dist/flow-secrets.d.ts +13 -0
  208. package/dist/flow-secrets.js +45 -0
  209. package/dist/flow-sort.d.ts +82 -0
  210. package/dist/flow-sort.js +153 -0
  211. package/dist/flow-steps.d.ts +40 -0
  212. package/dist/flow-steps.js +408 -0
  213. package/dist/flow-triggers.d.ts +244 -0
  214. package/dist/flow-triggers.js +959 -0
  215. package/dist/flows-ui.d.ts +20 -0
  216. package/dist/flows-ui.js +175 -0
  217. package/dist/flows.d.ts +229 -0
  218. package/dist/flows.js +968 -0
  219. package/dist/fonts.d.ts +19 -0
  220. package/dist/fonts.js +19 -0
  221. package/dist/gaps.d.ts +35 -0
  222. package/dist/gaps.js +101 -0
  223. package/dist/gate-failure.d.ts +40 -0
  224. package/dist/gate-failure.js +66 -0
  225. package/dist/git.d.ts +54 -0
  226. package/dist/git.js +94 -0
  227. package/dist/google-mail.d.ts +58 -0
  228. package/dist/google-mail.js +151 -0
  229. package/dist/grant.d.ts +136 -0
  230. package/dist/grant.js +238 -0
  231. package/dist/graph.d.ts +164 -0
  232. package/dist/graph.js +383 -0
  233. package/dist/guides.d.ts +22 -0
  234. package/dist/guides.js +363 -0
  235. package/dist/held.d.ts +150 -0
  236. package/dist/held.js +799 -0
  237. package/dist/invoke.d.ts +112 -0
  238. package/dist/invoke.js +780 -0
  239. package/dist/issues.d.ts +41 -0
  240. package/dist/issues.js +131 -0
  241. package/dist/job-object-helper.ps1 +252 -0
  242. package/dist/jsonl-discriminants.d.ts +11 -0
  243. package/dist/jsonl-discriminants.js +93 -0
  244. package/dist/keys.d.ts +104 -0
  245. package/dist/keys.js +229 -0
  246. package/dist/kits-ui.d.ts +17 -0
  247. package/dist/kits-ui.js +46 -0
  248. package/dist/kits.d.ts +86 -0
  249. package/dist/kits.js +215 -0
  250. package/dist/knowledge-cli.d.ts +101 -0
  251. package/dist/knowledge-cli.js +92 -0
  252. package/dist/knowledge-ui.d.ts +24 -0
  253. package/dist/knowledge-ui.js +40 -0
  254. package/dist/lead-context.d.ts +8 -0
  255. package/dist/lead-context.js +56 -0
  256. package/dist/lead-follow.d.ts +22 -0
  257. package/dist/lead-follow.js +167 -0
  258. package/dist/lead-status.d.ts +75 -0
  259. package/dist/lead-status.js +285 -0
  260. package/dist/ledger-chain.d.ts +46 -0
  261. package/dist/ledger-chain.js +187 -0
  262. package/dist/ledger-csv.d.ts +4 -0
  263. package/dist/ledger-csv.js +11 -0
  264. package/dist/ledger-view.d.ts +24 -0
  265. package/dist/ledger-view.js +58 -0
  266. package/dist/limits-ui.d.ts +26 -0
  267. package/dist/limits-ui.js +67 -0
  268. package/dist/link.d.ts +72 -0
  269. package/dist/link.js +217 -0
  270. package/dist/live.d.ts +96 -0
  271. package/dist/live.js +366 -0
  272. package/dist/liveness.d.ts +30 -0
  273. package/dist/liveness.js +42 -0
  274. package/dist/log.d.ts +7 -0
  275. package/dist/log.js +25 -0
  276. package/dist/mailbox.d.ts +57 -0
  277. package/dist/mailbox.js +150 -0
  278. package/dist/maintenance.d.ts +11 -0
  279. package/dist/maintenance.js +35 -0
  280. package/dist/mate-cli.d.ts +52 -0
  281. package/dist/mate-cli.js +345 -0
  282. package/dist/mate-contract.d.ts +10 -0
  283. package/dist/mate-contract.js +30 -0
  284. package/dist/mate-doors.d.ts +69 -0
  285. package/dist/mate-doors.js +548 -0
  286. package/dist/mate-progress.d.ts +29 -0
  287. package/dist/mate-progress.js +121 -0
  288. package/dist/mate-tools.d.ts +94 -0
  289. package/dist/mate-tools.js +1878 -0
  290. package/dist/mate.d.ts +92 -0
  291. package/dist/mate.js +435 -0
  292. package/dist/mcp-connect.d.ts +103 -0
  293. package/dist/mcp-connect.js +252 -0
  294. package/dist/mcp.d.ts +27 -0
  295. package/dist/mcp.js +651 -0
  296. package/dist/memory-cli.d.ts +466 -0
  297. package/dist/memory-cli.js +111 -0
  298. package/dist/memory-pass.d.ts +160 -0
  299. package/dist/memory-pass.js +409 -0
  300. package/dist/metrics.d.ts +3 -0
  301. package/dist/metrics.js +56 -0
  302. package/dist/mobile-viewport.d.ts +3 -0
  303. package/dist/mobile-viewport.js +37 -0
  304. package/dist/model-catalog.d.ts +118 -0
  305. package/dist/model-catalog.js +376 -0
  306. package/dist/models-cli.d.ts +102 -0
  307. package/dist/models-cli.js +66 -0
  308. package/dist/models-ui.d.ts +34 -0
  309. package/dist/models-ui.js +68 -0
  310. package/dist/modes.d.ts +80 -0
  311. package/dist/modes.js +182 -0
  312. package/dist/monitoring-settings.d.ts +44 -0
  313. package/dist/monitoring-settings.js +173 -0
  314. package/dist/monitoring-ui.d.ts +20 -0
  315. package/dist/monitoring-ui.js +51 -0
  316. package/dist/monitoring.d.ts +34 -0
  317. package/dist/monitoring.js +221 -0
  318. package/dist/names.d.ts +67 -0
  319. package/dist/names.js +102 -0
  320. package/dist/observations.d.ts +40 -0
  321. package/dist/observations.js +211 -0
  322. package/dist/oidc.d.ts +71 -0
  323. package/dist/oidc.js +182 -0
  324. package/dist/onboard.d.ts +108 -0
  325. package/dist/onboard.js +325 -0
  326. package/dist/openrouter-models.d.ts +30 -0
  327. package/dist/openrouter-models.js +120 -0
  328. package/dist/operate.d.ts +118 -0
  329. package/dist/operate.js +11321 -0
  330. package/dist/peek-cli.d.ts +97 -0
  331. package/dist/peek-cli.js +215 -0
  332. package/dist/peek.d.ts +134 -0
  333. package/dist/peek.js +578 -0
  334. package/dist/phase-routing.d.ts +307 -0
  335. package/dist/phase-routing.js +658 -0
  336. package/dist/plan-auto.d.ts +14 -0
  337. package/dist/plan-auto.js +92 -0
  338. package/dist/plan.d.ts +186 -0
  339. package/dist/plan.js +401 -0
  340. package/dist/planner-source.d.ts +251 -0
  341. package/dist/planner-source.js +460 -0
  342. package/dist/planner.d.ts +75 -0
  343. package/dist/planner.js +992 -0
  344. package/dist/policy-ui.d.ts +20 -0
  345. package/dist/policy-ui.js +41 -0
  346. package/dist/policy.d.ts +111 -0
  347. package/dist/policy.js +231 -0
  348. package/dist/prepared-evidence.d.ts +16 -0
  349. package/dist/prepared-evidence.js +96 -0
  350. package/dist/pricing.d.ts +45 -0
  351. package/dist/pricing.js +77 -0
  352. package/dist/principal.d.ts +42 -0
  353. package/dist/principal.js +82 -0
  354. package/dist/probe.d.ts +46 -0
  355. package/dist/probe.js +79 -0
  356. package/dist/process-custody.d.ts +17 -0
  357. package/dist/process-custody.js +91 -0
  358. package/dist/process-liveness.d.ts +10 -0
  359. package/dist/process-liveness.js +81 -0
  360. package/dist/process-recovery-anchor.d.ts +75 -0
  361. package/dist/process-recovery-anchor.js +188 -0
  362. package/dist/process-recovery-coalition.d.ts +25 -0
  363. package/dist/process-recovery-coalition.js +105 -0
  364. package/dist/process-recovery-eligibility.d.ts +178 -0
  365. package/dist/process-recovery-eligibility.js +170 -0
  366. package/dist/process-recovery-native.d.ts +172 -0
  367. package/dist/process-recovery-native.js +592 -0
  368. package/dist/process-recovery-provenance.d.ts +163 -0
  369. package/dist/process-recovery-provenance.js +292 -0
  370. package/dist/process-recovery-services.d.ts +56 -0
  371. package/dist/process-recovery-services.js +191 -0
  372. package/dist/process-recovery-settlement.d.ts +32 -0
  373. package/dist/process-recovery-settlement.js +132 -0
  374. package/dist/process-recovery.d.ts +41 -0
  375. package/dist/process-recovery.js +91 -0
  376. package/dist/process-tree.d.ts +41 -0
  377. package/dist/process-tree.js +264 -0
  378. package/dist/project-access.d.ts +14 -0
  379. package/dist/project-access.js +34 -0
  380. package/dist/project-cli.d.ts +18 -0
  381. package/dist/project-cli.js +104 -0
  382. package/dist/project-delete-ui.d.ts +22 -0
  383. package/dist/project-delete-ui.js +48 -0
  384. package/dist/project-delete.d.ts +55 -0
  385. package/dist/project-delete.js +413 -0
  386. package/dist/project-knowledge.d.ts +111 -0
  387. package/dist/project-knowledge.js +241 -0
  388. package/dist/project-learning.d.ts +100 -0
  389. package/dist/project-learning.js +438 -0
  390. package/dist/project-memory.d.ts +81 -0
  391. package/dist/project-memory.js +264 -0
  392. package/dist/project-skills.d.ts +152 -0
  393. package/dist/project-skills.js +660 -0
  394. package/dist/project-tools.d.ts +219 -0
  395. package/dist/project-tools.js +796 -0
  396. package/dist/project.d.ts +61 -0
  397. package/dist/project.js +125 -0
  398. package/dist/prompt.d.ts +22 -0
  399. package/dist/prompt.js +72 -0
  400. package/dist/proof.d.ts +513 -0
  401. package/dist/proof.js +1140 -0
  402. package/dist/proposal.d.ts +82 -0
  403. package/dist/proposal.js +210 -0
  404. package/dist/provider-connection.d.ts +22 -0
  405. package/dist/provider-connection.js +98 -0
  406. package/dist/provider-limits.d.ts +35 -0
  407. package/dist/provider-limits.js +95 -0
  408. package/dist/provider.d.ts +279 -0
  409. package/dist/provider.js +935 -0
  410. package/dist/publish.d.ts +107 -0
  411. package/dist/publish.js +650 -0
  412. package/dist/pulls.d.ts +118 -0
  413. package/dist/pulls.js +240 -0
  414. package/dist/push.d.ts +95 -0
  415. package/dist/push.js +353 -0
  416. package/dist/quality.d.ts +8 -0
  417. package/dist/quality.js +6 -0
  418. package/dist/recipe-ui.d.ts +18 -0
  419. package/dist/recipe-ui.js +152 -0
  420. package/dist/recipes.d.ts +81 -0
  421. package/dist/recipes.js +332 -0
  422. package/dist/remote.d.ts +67 -0
  423. package/dist/remote.js +122 -0
  424. package/dist/render.d.ts +64 -0
  425. package/dist/render.js +587 -0
  426. package/dist/report-summary.d.ts +13 -0
  427. package/dist/report-summary.js +15 -0
  428. package/dist/repos.d.ts +65 -0
  429. package/dist/repos.js +277 -0
  430. package/dist/repository-context-ui.d.ts +2 -0
  431. package/dist/repository-context-ui.js +10 -0
  432. package/dist/repository-context.d.ts +67 -0
  433. package/dist/repository-context.js +376 -0
  434. package/dist/restart-certification.d.ts +134 -0
  435. package/dist/restart-certification.js +226 -0
  436. package/dist/result-actions.d.ts +28 -0
  437. package/dist/result-actions.js +270 -0
  438. package/dist/result-completion.d.ts +18 -0
  439. package/dist/result-completion.js +66 -0
  440. package/dist/result-review.d.ts +213 -0
  441. package/dist/result-review.js +430 -0
  442. package/dist/retention-ui.d.ts +12 -0
  443. package/dist/retention-ui.js +39 -0
  444. package/dist/retention.d.ts +72 -0
  445. package/dist/retention.js +288 -0
  446. package/dist/review-context.d.ts +243 -0
  447. package/dist/review-context.js +1043 -0
  448. package/dist/review-evidence.d.ts +33 -0
  449. package/dist/review-evidence.js +56 -0
  450. package/dist/reviewer.d.ts +97 -0
  451. package/dist/reviewer.js +202 -0
  452. package/dist/routine.d.ts +228 -0
  453. package/dist/routine.js +764 -0
  454. package/dist/runner.d.ts +266 -0
  455. package/dist/runner.js +399 -0
  456. package/dist/scan.d.ts +32 -0
  457. package/dist/scan.js +92 -0
  458. package/dist/scope.d.ts +695 -0
  459. package/dist/scope.js +1176 -0
  460. package/dist/scout-report.d.ts +42 -0
  461. package/dist/scout-report.js +108 -0
  462. package/dist/scout.d.ts +69 -0
  463. package/dist/scout.js +351 -0
  464. package/dist/serve.d.ts +313 -0
  465. package/dist/serve.js +21776 -0
  466. package/dist/session-brief.d.ts +8 -0
  467. package/dist/session-brief.js +24 -0
  468. package/dist/session-cli.d.ts +13 -0
  469. package/dist/session-cli.js +284 -0
  470. package/dist/session-contract.d.ts +183 -0
  471. package/dist/session-contract.js +122 -0
  472. package/dist/session-http.d.ts +19 -0
  473. package/dist/session-http.js +154 -0
  474. package/dist/session-server.d.ts +12 -0
  475. package/dist/session-server.js +37 -0
  476. package/dist/session-service.d.ts +22 -0
  477. package/dist/session-service.js +157 -0
  478. package/dist/setup-guide.d.ts +39 -0
  479. package/dist/setup-guide.js +93 -0
  480. package/dist/sign-in-guard.d.ts +45 -0
  481. package/dist/sign-in-guard.js +74 -0
  482. package/dist/skills-ui.d.ts +11 -0
  483. package/dist/skills-ui.js +58 -0
  484. package/dist/skills.d.ts +97 -0
  485. package/dist/skills.js +276 -0
  486. package/dist/slack-api.d.ts +55 -0
  487. package/dist/slack-api.js +229 -0
  488. package/dist/slack-chat.d.ts +28 -0
  489. package/dist/slack-chat.js +413 -0
  490. package/dist/slack-settings.d.ts +7 -0
  491. package/dist/slack-settings.js +75 -0
  492. package/dist/slack-state.d.ts +13 -0
  493. package/dist/slack-state.js +9 -0
  494. package/dist/slack.d.ts +13 -0
  495. package/dist/slack.js +167 -0
  496. package/dist/spend-ui.d.ts +28 -0
  497. package/dist/spend-ui.js +95 -0
  498. package/dist/spend.d.ts +142 -0
  499. package/dist/spend.js +313 -0
  500. package/dist/sqlite-runtime.d.ts +3 -0
  501. package/dist/sqlite-runtime.js +6 -0
  502. package/dist/sso-settings.d.ts +33 -0
  503. package/dist/sso-settings.js +103 -0
  504. package/dist/sso-ui.d.ts +22 -0
  505. package/dist/sso-ui.js +39 -0
  506. package/dist/storage.d.ts +15 -0
  507. package/dist/storage.js +84 -0
  508. package/dist/store.d.ts +7754 -0
  509. package/dist/store.js +23279 -0
  510. package/dist/structured-output.d.ts +58 -0
  511. package/dist/structured-output.js +103 -0
  512. package/dist/style-asset.d.ts +7 -0
  513. package/dist/style-asset.js +41 -0
  514. package/dist/subscription-chat.d.ts +46 -0
  515. package/dist/subscription-chat.js +275 -0
  516. package/dist/summary.d.ts +33 -0
  517. package/dist/summary.js +71 -0
  518. package/dist/supervisor.mjs +297 -0
  519. package/dist/surface.d.ts +63 -0
  520. package/dist/surface.js +352 -0
  521. package/dist/sync.d.ts +94 -0
  522. package/dist/sync.js +279 -0
  523. package/dist/task-composer.d.ts +13 -0
  524. package/dist/task-composer.js +52 -0
  525. package/dist/task-control.d.ts +165 -0
  526. package/dist/task-control.js +214 -0
  527. package/dist/task-outcome-cli.d.ts +10 -0
  528. package/dist/task-outcome-cli.js +92 -0
  529. package/dist/task-text.d.ts +30 -0
  530. package/dist/task-text.js +41 -0
  531. package/dist/team-cli.d.ts +15 -0
  532. package/dist/team-cli.js +618 -0
  533. package/dist/team-contract.d.ts +92 -0
  534. package/dist/team-contract.js +1 -0
  535. package/dist/team-http.d.ts +17 -0
  536. package/dist/team-http.js +144 -0
  537. package/dist/team-leads.d.ts +81 -0
  538. package/dist/team-leads.js +565 -0
  539. package/dist/team-runtime.d.ts +27 -0
  540. package/dist/team-runtime.js +264 -0
  541. package/dist/team-ui.d.ts +3 -0
  542. package/dist/team-ui.js +9 -0
  543. package/dist/team-updates.d.ts +8 -0
  544. package/dist/team-updates.js +57 -0
  545. package/dist/teammate-admin.d.ts +53 -0
  546. package/dist/teammate-admin.js +111 -0
  547. package/dist/teammate-desk.d.ts +60 -0
  548. package/dist/teammate-desk.js +154 -0
  549. package/dist/teammate-memory.d.ts +64 -0
  550. package/dist/teammate-memory.js +196 -0
  551. package/dist/teammate-question.d.ts +59 -0
  552. package/dist/teammate-question.js +175 -0
  553. package/dist/teammate-tools.d.ts +108 -0
  554. package/dist/teammate-tools.js +271 -0
  555. package/dist/teammate-week.d.ts +68 -0
  556. package/dist/teammate-week.js +132 -0
  557. package/dist/teammate-work.d.ts +76 -0
  558. package/dist/teammate-work.js +280 -0
  559. package/dist/teammates-ui.d.ts +20 -0
  560. package/dist/teammates-ui.js +166 -0
  561. package/dist/teammates.d.ts +180 -0
  562. package/dist/teammates.js +253 -0
  563. package/dist/teams-api.d.ts +33 -0
  564. package/dist/teams-api.js +204 -0
  565. package/dist/teams-chat.d.ts +26 -0
  566. package/dist/teams-chat.js +215 -0
  567. package/dist/teams-settings.d.ts +12 -0
  568. package/dist/teams-settings.js +57 -0
  569. package/dist/teams.d.ts +21 -0
  570. package/dist/teams.js +124 -0
  571. package/dist/telegram-flow.d.ts +49 -0
  572. package/dist/telegram-flow.js +110 -0
  573. package/dist/telegram-mate.d.ts +137 -0
  574. package/dist/telegram-mate.js +707 -0
  575. package/dist/telegram-progress.d.ts +14 -0
  576. package/dist/telegram-progress.js +129 -0
  577. package/dist/telegram-settings.d.ts +9 -0
  578. package/dist/telegram-settings.js +36 -0
  579. package/dist/telegram-status.d.ts +75 -0
  580. package/dist/telegram-status.js +265 -0
  581. package/dist/telegram-team.d.ts +85 -0
  582. package/dist/telegram-team.js +359 -0
  583. package/dist/telegram.d.ts +230 -0
  584. package/dist/telegram.js +1931 -0
  585. package/dist/templates.d.ts +65 -0
  586. package/dist/templates.js +118 -0
  587. package/dist/tool-launcher.d.ts +1 -0
  588. package/dist/tool-launcher.js +101 -0
  589. package/dist/tools-ui.d.ts +30 -0
  590. package/dist/tools-ui.js +40 -0
  591. package/dist/transitions-recipes.d.ts +1 -0
  592. package/dist/transitions-recipes.js +203 -0
  593. package/dist/tree-proof.d.ts +34 -0
  594. package/dist/tree-proof.js +58 -0
  595. package/dist/verification-evidence.d.ts +44 -0
  596. package/dist/verification-evidence.js +238 -0
  597. package/dist/version.d.ts +2 -0
  598. package/dist/version.js +10 -0
  599. package/dist/webhooks.d.ts +91 -0
  600. package/dist/webhooks.js +283 -0
  601. package/dist/work-index.d.ts +68 -0
  602. package/dist/work-index.js +384 -0
  603. package/dist/work-summary.d.ts +57 -0
  604. package/dist/work-summary.js +116 -0
  605. package/dist/workspace-motion.d.ts +4 -0
  606. package/dist/workspace-motion.js +133 -0
  607. package/dist/workspace-revision.d.ts +19 -0
  608. package/dist/workspace-revision.js +92 -0
  609. package/dist/workspace-ui.d.ts +204 -0
  610. package/dist/workspace-ui.js +411 -0
  611. package/dist/worktree-notices.d.ts +13 -0
  612. package/dist/worktree-notices.js +17 -0
  613. package/dist/worktree.d.ts +249 -0
  614. package/dist/worktree.js +720 -0
  615. package/package.json +122 -0
  616. package/scripts/canary-assertions.mjs +42 -0
  617. package/scripts/crash-canary.mjs +253 -0
  618. package/scripts/fixtures/crash-process.mjs +74 -0
  619. package/scripts/fixtures/pilot-scenarios.mjs +85 -0
  620. package/scripts/fixtures/restart-service.mjs +27 -0
  621. package/scripts/launchd-certification.mjs +143 -0
  622. package/scripts/pilot.mjs +121 -0
  623. package/scripts/proof-preflight.mjs +140 -0
  624. package/scripts/provider-canary.mjs +379 -0
  625. package/scripts/recovery-canary.mjs +135 -0
  626. package/scripts/restart-certification.mjs +98 -0
@@ -0,0 +1,707 @@
1
+ import { CHAT_ACTIONS, sharedActionAllowsChallenge, sharedActionPayload } from "./chat-actions.js";
2
+ import { taskInCeiling, mirrorToTaskChat, channelRepos as telegramConversationRepos, resolveChannelMate as resolveTelegramMate, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText } from "./chat-channel.js";
3
+ export { channelRepos as telegramConversationRepos, resolveChannelMate as resolveTelegramMate, tooLongText, whichTaskText, replyContextFor, NO_PHONE_LINK, NO_TASK_LINK, proposalLink, confirmedLink, proposalPreview, proposalOutcomeText, handoffCardText, confirmedCardText, CHAT_ACTION_PARITY as TELEGRAM_ACTION_PARITY, parityGaps } from "./chat-channel.js";
4
+ /**
5
+ * The paired phone as one more way to use the same assistant.
6
+ *
7
+ * Transport only. An ordinary private Telegram message becomes a mate turn
8
+ * through `runMateTurn` — the same saved thread, proposal rows, confirm
9
+ * doors and result actions the console and `toolroll chat` use —
10
+ * and every act is still a card the operator confirms. This module owns
11
+ * what is specific to the wire: proving the pairing (chat AND immutable
12
+ * sender AND approver generation AND the enrolled ceiling) before any
13
+ * model call and again after every wait; the durable inbound row that
14
+ * precedes polling acknowledgement; the request identity the engine
15
+ * receipts a turn under so a replay or a restart never dispatches the
16
+ * provider twice; concise previews with opaque one-tap tokens; and the
17
+ * honest words for what a phone cannot do (a password, a cancel, a
18
+ * console-only control).
19
+ *
20
+ * Nothing here mints authority: the principal comes from
21
+ * `verifyApproverStanding` against the live binding row, the session is
22
+ * the approver's own compatible one (or one minted under the same terms
23
+ * the console and CLI state), and the door re-proves everything again.
24
+ */
25
+ import { createHash, randomBytes } from "node:crypto";
26
+ import { MATE_MESSAGE_MAX_CHARS, runMateTurn } from "./mate.js";
27
+ import { confirmMateProposal, dismissMateProposal } from "./mate-doors.js";
28
+ import { ceilingDigestOf, verifyApproverStanding } from "./principal.js";
29
+ import { phoneText } from "./telegram-status.js";
30
+ import { TeamLeads } from "./team-leads.js";
31
+ import { chatResultHref } from "./chat-controls.js";
32
+ import { resultImageFileName, safeResultImageCaption, verifyResultImage } from "./chat-evidence.js";
33
+ /** How long one claimed turn may go without a heartbeat before another poller may take it over. */
34
+ export const CONVERSATION_CLAIM_MS = 2 * 60_000;
35
+ /** A busy engine (one turn at a time) defers a queued message this long, up to the age bound below. */
36
+ export const CONVERSATION_RETRY_MS = 5_000;
37
+ export const CONVERSATION_MAX_AGE_MS = 10 * 60_000;
38
+ /** A card's buttons and an armed irreversible challenge live this long. */
39
+ export const CARD_TTL_MS = 24 * 3_600_000;
40
+ export const CHALLENGE_TTL_MS = 10 * 60_000;
41
+ /** Telegram's own message ceiling, with room for our part headers (the same bound telegram.ts splits at). */
42
+ const PART_CAP = 3_900;
43
+ // ---- the ceiling and the principal --------------------------------------------
44
+ /**
45
+ * The phone's project ceiling: the enrolled registry, canonicalized and
46
+ * deduplicated IN ORDER exactly as the console's managed list is, then
47
+ * narrowed to what this approver's account may access. Order matters —
48
+ * `r1..rN` is an index and the ceiling digest hashes the order — so a
49
+ * console session over the same enrollment shares its digest here.
50
+ */
51
+ /** The engine's request identity for one inbound message: exact bot, binding and update, never text. */
52
+ export function telegramRequestId(botId, bindingId, updateId) {
53
+ return createHash("sha256").update(`telegram:${botId}:${bindingId}:${updateId}`).digest("hex").slice(0, 32);
54
+ }
55
+ /** The one channel problem that is transient: the registry could not be read now. Retried, never a refusal. */
56
+ export const UNREADABLE_REGISTRY = "current project access could not be read";
57
+ /**
58
+ * Is the channel still what a turn opened under? The live binding must be
59
+ * the SAME row and generation, its approver still an approver, and the
60
+ * enrolled ceiling (reloaded now) still the exact list the principal
61
+ * holds — or, for a reply recovered after a restart, the ceiling digest
62
+ * its session was minted under. A string names what changed; null means
63
+ * nothing did.
64
+ */
65
+ export async function telegramChannelProblem(store, expected, readProjects) {
66
+ let registry;
67
+ try {
68
+ registry = await readProjects();
69
+ }
70
+ catch {
71
+ return UNREADABLE_REGISTRY;
72
+ }
73
+ const binding = store.liveTelegramBindingById(expected.bindingId);
74
+ if (binding === null || binding.botId !== expected.botId || binding.approverGeneration !== expected.approverGeneration)
75
+ return "this chat is no longer paired";
76
+ if (store.accountOf(binding.approver)?.role !== "approver")
77
+ return "the paired account is no longer an approver";
78
+ const repos = telegramConversationRepos(store, binding.approver, registry);
79
+ const same = "repos" in expected
80
+ ? repos.length === expected.repos.length && repos.every((one, index) => one === expected.repos[index])
81
+ : ceilingDigestOf(repos) === expected.ceilingDigest;
82
+ if (!same)
83
+ return "the connected projects changed";
84
+ return null;
85
+ }
86
+ // ---- words ---------------------------------------------------------------------
87
+ export function phoneLinkButton(origin, link) {
88
+ if (origin === null || link === null)
89
+ return null;
90
+ return [{ text: link.label, url: `${origin}${link.path}` }];
91
+ }
92
+ /** Why a card that wanted a link carries none — null when it carries one. */
93
+ function linkNote(origin, link) {
94
+ if (origin === null)
95
+ return NO_PHONE_LINK;
96
+ return link === null ? NO_TASK_LINK : null;
97
+ }
98
+ /** The keyboard as sent: the persisted callback rows, then the link row minted now. */
99
+ function keyboardWith(callbacks, link) {
100
+ const rows = [...(callbacks ?? []), ...(link === null ? [] : [link])];
101
+ return rows.length === 0 ? undefined : rows;
102
+ }
103
+ /** Mint the card's tokens before the send, so a tap can never name a token that does not exist. */
104
+ export function mintCardTokens(store, binding, proposal, now, messageId, chatId = binding.chatId) {
105
+ const confirm = randomBytes(16).toString("hex");
106
+ const dismiss = randomBytes(16).toString("hex");
107
+ for (const [token, phase] of [[confirm, "confirm"], [dismiss, "dismiss"]]) {
108
+ store.createTelegramProposalAction({ token, binding: binding.id, proposal, phase, chatId, ttlMs: CARD_TTL_MS, ...(messageId === undefined ? {} : { messageId }) }, now);
109
+ }
110
+ return { keyboard: [[{ text: "Confirm", callback_data: confirm }, { text: "Dismiss", callback_data: dismiss }]], tokens: [confirm, dismiss] };
111
+ }
112
+ /**
113
+ * One tap on a proposal card, applied inside the update's own transaction
114
+ * by the bridge (which already proved the live binding, the exact sender
115
+ * and the exact chat). Returns what to tell Telegram afterwards. The
116
+ * principal is minted from the binding row against the CURRENT enrolled
117
+ * ceiling; the door re-proves standing, ceiling, session and the
118
+ * proposal's own terms again inside its transaction.
119
+ */
120
+ export function applyProposalTap(store, binding, token, message, repos, options, now) {
121
+ const effects = [];
122
+ const ack = (text) => { effects.push({ kind: "ack", text }); };
123
+ const edit = (text, keyboard) => { effects.push({ kind: "edit", text, ...(keyboard === undefined ? {} : { keyboard }) }); };
124
+ const action = store.getTelegramProposalAction(token);
125
+ // The tap must land where the card was placed. In a paired private chat
126
+ // that is the tapper's own binding; in a group that follows a team
127
+ // conversation any paired member may tap, and the door then proves that
128
+ // person's own membership and consent.
129
+ const tapChatId = message.chat === undefined ? binding.chatId : String(message.chat.id);
130
+ const selected = store.telegramTeamChat(binding.botId, tapChatId);
131
+ const groupCard = tapChatId !== binding.chatId && selected?.kind === "group";
132
+ if (action === null || action.chatId !== tapChatId ||
133
+ (tapChatId !== binding.chatId && !groupCard) ||
134
+ (action.binding !== binding.id && (!groupCard || action.phase === "yes" || action.phase === "cancel")) ||
135
+ (action.messageId !== null && action.messageId !== String(message.message_id))) {
136
+ ack("that button is stale — send /status to see what still waits");
137
+ return { effects, confirmed: false, ignored: true };
138
+ }
139
+ const proposal = store.getMateProposal(action.proposal);
140
+ if (proposal === null) {
141
+ store.consumeTelegramProposalAction(token, now);
142
+ ack("that card no longer exists");
143
+ edit("This card no longer exists.");
144
+ return { effects, confirmed: false, ignored: true };
145
+ }
146
+ // A card belongs to the selected conversation, not merely the same
147
+ // Telegram group. Check the tapper before rendering even an outcome or a
148
+ // challenge: a paired outsider must learn none of the proposal's details.
149
+ let actionRepos = repos;
150
+ const shared = store.handle.prepare("SELECT id FROM team_conversation WHERE thread = ?").get(proposal.thread);
151
+ if (groupCard || shared !== undefined) {
152
+ let allowed = false;
153
+ if (repos !== null && selected !== null && shared !== undefined && String(shared["id"]) === selected.conversation) {
154
+ try {
155
+ const domain = new TeamLeads(store, () => repos);
156
+ const member = domain.access({ name: binding.approver, generation: binding.approverGeneration }, selected.conversation, "contributor");
157
+ actionRepos = member.conversation.projects;
158
+ const grant = store.liveTelegramBindingById(selected.binding);
159
+ if (grant !== null && grant.botId === binding.botId && grant.approver === selected.boundBy) {
160
+ const audience = domain.access({ name: grant.approver, generation: grant.approverGeneration }, selected.conversation, groupCard ? "manager" : "viewer");
161
+ allowed = groupCard ? audience.conversation.visibility === "team" : grant.id === binding.id;
162
+ }
163
+ }
164
+ catch { /* Refuse without disclosing a proposal outside current access. */ }
165
+ }
166
+ if (!allowed) {
167
+ ack("that button is no longer available in this conversation");
168
+ return { effects, confirmed: false, ignored: true };
169
+ }
170
+ }
171
+ if (proposal.state !== "pending") {
172
+ // Already acted on — here, on the console, or from the terminal. The
173
+ // card shows the recorded outcome; nothing runs twice.
174
+ store.consumeTelegramProposalActions(proposal.id, now);
175
+ ack(proposal.state === "confirmed" ? "already done" : proposal.state === "dismissed" ? "already dismissed" : "already acted on");
176
+ edit(proposalOutcomeText(proposal));
177
+ return { effects, confirmed: false, ignored: true };
178
+ }
179
+ if (actionRepos === null) {
180
+ // The registry could not be read: the token stays live for a retry.
181
+ ack("project access could not be read — tap again in a moment");
182
+ return { effects, confirmed: false, ignored: true };
183
+ }
184
+ const verified = verifyApproverStanding(store, binding.approver, binding.approverGeneration, actionRepos);
185
+ if (!verified.ok) {
186
+ ack("this chat no longer answers as an approver");
187
+ return { effects, confirmed: false, ignored: true };
188
+ }
189
+ const who = verified.who;
190
+ const preview = proposalPreview(store, proposal, actionRepos, "telegram");
191
+ const origin = options.phoneOrigin?.() ?? null;
192
+ if (action.phase === "dismiss") {
193
+ if (!store.consumeTelegramProposalAction(token, now)) {
194
+ ack("that button was already used");
195
+ return { effects, confirmed: false, ignored: true };
196
+ }
197
+ const done = dismissMateProposal(store, who, proposal.id, now);
198
+ store.consumeTelegramProposalActions(proposal.id, now);
199
+ ack(done ? "dismissed" : "that proposal was already acted on");
200
+ edit(done ? "Dismissed." : proposalOutcomeText(store.getMateProposal(proposal.id) ?? proposal));
201
+ return { effects, confirmed: false, ignored: !done };
202
+ }
203
+ if (action.phase === "cancel") {
204
+ // Cancel means cancelled: the armed yes dies with it and the card is restored.
205
+ store.consumeTelegramProposalAction(token, now);
206
+ store.consumeTelegramProposalActions(proposal.id, now, ["yes", "cancel"]);
207
+ const fresh = mintCardTokens(store, binding, proposal.id, now, String(message.message_id), tapChatId);
208
+ ack("cancelled");
209
+ edit(preview.text, fresh.keyboard);
210
+ return { effects, confirmed: false, ignored: false };
211
+ }
212
+ if (!preview.buttons) {
213
+ // A forged keyboard on a handoff card: nothing acts; the card is
214
+ // repainted with its current link (or the honest absence of one).
215
+ store.consumeTelegramProposalActions(proposal.id, now);
216
+ const wanted = proposalLink(store, proposal, actionRepos, "telegram");
217
+ const link = phoneLinkButton(origin, wanted);
218
+ ack(link === null ? "this step finishes on the computer" : "open the button below to finish this step");
219
+ edit(handoffCardText(preview.text, linkNote(origin, wanted)), keyboardWith(null, link));
220
+ return { effects, confirmed: false, ignored: true };
221
+ }
222
+ const irreversible = proposal.kind === "answer" && proposal.payload["reversible"] === false;
223
+ const sharedAction = proposal.kind === "action" ? sharedActionPayload(proposal.payload) : null;
224
+ const challenged = sharedAction !== null && sharedActionAllowsChallenge(sharedAction);
225
+ // The arm: two fresh one-time tokens make a real challenge, exactly as a
226
+ // decision button does. Nothing is answered or recorded here.
227
+ const arm = () => {
228
+ store.consumeTelegramProposalActions(proposal.id, now, ["yes", "cancel"]);
229
+ const yes = randomBytes(16).toString("hex");
230
+ const cancel = randomBytes(16).toString("hex");
231
+ const placedOn = String(message.message_id);
232
+ store.createTelegramProposalAction({ token: yes, binding: binding.id, proposal: proposal.id, phase: "yes", chatId: tapChatId, messageId: placedOn, ttlMs: CHALLENGE_TTL_MS }, now);
233
+ store.createTelegramProposalAction({ token: cancel, binding: binding.id, proposal: proposal.id, phase: "cancel", chatId: tapChatId, messageId: placedOn, ttlMs: CHALLENGE_TTL_MS }, now);
234
+ const body = preview.text.split("\n\nConfirm or Dismiss below")[0];
235
+ if (challenged) {
236
+ ack("confirm it");
237
+ edit(`${body}\n\nThis records that you handled this exact result. Confirm?`, [
238
+ [{ text: `✓ Yes, ${CHAT_ACTIONS[sharedAction.operation].label.toLowerCase()}`, callback_data: yes }],
239
+ [{ text: "Cancel", callback_data: cancel }],
240
+ ]);
241
+ return;
242
+ }
243
+ ack("irreversible — confirm it");
244
+ edit(`⚠ This answer is IRREVERSIBLE.\n\n${body}\n\nConfirm?`, [
245
+ [{ text: "⚠ Yes, answer it", callback_data: yes }],
246
+ [{ text: "Cancel", callback_data: cancel }],
247
+ ]);
248
+ };
249
+ if (action.phase === "confirm" && (irreversible || challenged)) {
250
+ if (!store.consumeTelegramProposalAction(token, now)) {
251
+ ack("that button was already used");
252
+ return { effects, confirmed: false, ignored: true };
253
+ }
254
+ arm();
255
+ return { effects, confirmed: false, ignored: false };
256
+ }
257
+ if (!store.consumeTelegramProposalAction(token, now)) {
258
+ ack(action.phase === "yes" ? "that confirmation expired — start again from Confirm" : "that button was already used");
259
+ return { effects, confirmed: false, ignored: true };
260
+ }
261
+ const outcome = confirmMateProposal(store, who, proposal.id, now, {
262
+ via: "telegram",
263
+ evidenceRoot: options.evidenceRoot,
264
+ confirm: action.phase === "yes",
265
+ ...(options.held === undefined ? {} : { held: options.held }),
266
+ deferSignal: signal => effects.push({ kind: "signal", run: signal }),
267
+ });
268
+ if (!outcome.ok && outcome.reason === "needs-confirm") {
269
+ // Not armed yet (a card drafted without the reversible mark): arm now, the tap is not lost.
270
+ arm();
271
+ return { effects, confirmed: false, ignored: false };
272
+ }
273
+ store.consumeTelegramProposalActions(proposal.id, now);
274
+ ack(outcome.ok ? "✓ done" : "not done");
275
+ const wanted = confirmedLink(store, outcome, proposal, actionRepos);
276
+ const link = phoneLinkButton(origin, wanted);
277
+ edit(confirmedCardText(store, outcome, proposal, linkNote(origin, wanted)), keyboardWith(null, link));
278
+ return { effects, confirmed: outcome.ok, ignored: false };
279
+ }
280
+ /** A part's next attempt after a failed send: bounded backoff by attempt, or Telegram's own retry_after when it named a longer one. */
281
+ export const PART_RETRY_MS = [5_000, 15_000, 60_000, 300_000];
282
+ /** Unsent parts are retried this long after the message arrived (the card's own lifetime); then the row fails, explicitly unsent. */
283
+ export const DELIVERY_MAX_AGE_MS = CARD_TTL_MS;
284
+ export function splitParts(text) {
285
+ if (text.length <= PART_CAP)
286
+ return [text];
287
+ const parts = [];
288
+ for (let at = 0; at < text.length;) {
289
+ let end = Math.min(at + PART_CAP, text.length);
290
+ if (end < text.length && /[\uD800-\uDBFF]/.test(text[end - 1]))
291
+ end--;
292
+ parts.push(text.slice(at, end));
293
+ at = end;
294
+ }
295
+ return parts;
296
+ }
297
+ /**
298
+ * Run every claimable queued message for this bot, one at a time, each
299
+ * outside any SQLite transaction. The caller holds the poll lease and
300
+ * renews it; the row's own claim is renewed here. Bounded per call so a
301
+ * flood cannot pin one pass forever.
302
+ */
303
+ export async function processTelegramConversations(args) {
304
+ const limit = args.limit ?? 20;
305
+ for (let count = 0; count < limit && args.signal?.aborted !== true; count++) {
306
+ const row = args.store.claimTelegramConversation(args.botId, args.owner, CONVERSATION_CLAIM_MS, args.clock());
307
+ if (row === null)
308
+ return;
309
+ try {
310
+ await runTelegramConversation(row, args);
311
+ }
312
+ catch (error) {
313
+ // The claim lapses on its own; the receipt and the persisted parts make the retry safe.
314
+ args.report.problems.push(`telegram chat for update ${row.updateId}: ${error instanceof Error ? error.message : String(error)}`);
315
+ return;
316
+ }
317
+ }
318
+ }
319
+ class PlanRefused extends Error {
320
+ }
321
+ /**
322
+ * One claimed message, in order of what is already known about it:
323
+ *
324
+ * 1. Unsent parts exist — the turn already answered; send what remains.
325
+ * No registry resolution beyond the fence, no model, no proposal.
326
+ * 2. A session was bound before a dispatch — read the engine's receipt
327
+ * THERE. A receipt names the original turn: recover its outcome, even
328
+ * if that session has since been ended and replaced from the console.
329
+ * 3. Otherwise resolve the session and thread, bind the session to the
330
+ * row BEFORE dispatching, run the turn, persist its reply and cards
331
+ * as parts in one transaction, then send them.
332
+ *
333
+ * Every outgoing part is fenced on the row's claim, the live pairing and
334
+ * the ceiling the turn opened under; only a confirmed message id counts.
335
+ */
336
+ async function runTelegramConversation(row, args) {
337
+ const { store, botId, transport, owner, clock, readProjects, options, report } = args;
338
+ const finish = (result) => store.finishTelegramConversation(row.id, owner, result, clock());
339
+ const held = () => store.renewTelegramConversation(row.id, owner, CONVERSATION_CLAIM_MS, clock());
340
+ const ageMs = clock().getTime() - Date.parse(row.createdAt);
341
+ const requeue = (outcome) => {
342
+ if (ageMs > CONVERSATION_MAX_AGE_MS) {
343
+ finish({ state: "failed", outcome: `${outcome}:gave-up` });
344
+ report.refused++;
345
+ return;
346
+ }
347
+ finish({ state: "queued", outcome, nextAttemptAt: new Date(clock().getTime() + CONVERSATION_RETRY_MS).toISOString() });
348
+ };
349
+ // 1. The exact binding the message arrived under must still be the live one.
350
+ const binding = store.liveTelegramBindingById(row.binding);
351
+ if (binding === null || binding.botId !== botId || binding.approverGeneration !== row.approverGeneration || store.accountOf(binding.approver)?.role !== "approver") {
352
+ finish({ state: "failed", outcome: "unpaired" });
353
+ report.refused++;
354
+ return;
355
+ }
356
+ const chatId = binding.chatId;
357
+ const pairingProblem = () => {
358
+ const live = store.liveTelegramBindingById(binding.id);
359
+ if (live === null || live.approverGeneration !== binding.approverGeneration)
360
+ return "this chat is no longer paired";
361
+ if (store.accountOf(live.approver)?.role !== "approver")
362
+ return "the paired account is no longer an approver";
363
+ return null;
364
+ };
365
+ /** A notice that carries no project data — "this changed, nothing happened" — sent once, best effort, under the pairing fence alone. */
366
+ const notify = async (text, keyboard) => {
367
+ if (pairingProblem() !== null || !held())
368
+ return;
369
+ try {
370
+ const answer = await transport("sendMessage", {
371
+ chat_id: chatId, text, link_preview_options: { is_disabled: true }, reply_parameters: { message_id: Number(row.messageId) },
372
+ ...(keyboard === undefined ? {} : { reply_markup: { inline_keyboard: keyboard } }),
373
+ });
374
+ if (!answer.ok)
375
+ report.problems.push(`telegram chat notice for update ${row.updateId} could not be sent: ${answer.description ?? "sendMessage failed"}`);
376
+ }
377
+ catch {
378
+ report.problems.push(`telegram chat notice for update ${row.updateId} could not be sent: Telegram transport failed`);
379
+ }
380
+ };
381
+ /** The outbound half: send every pending part in order, each fenced; done only when every part is sent or moot. */
382
+ const deliver = async (outcomeWord) => {
383
+ // The row as it is NOW: the session was bound after this claim read it.
384
+ const bound = store.getTelegramConversation(row.id)?.session ?? null;
385
+ const session = bound === null ? null : store.getMateSession(bound);
386
+ if (session === null) {
387
+ finish({ state: "failed", outcome: "unsent:no-session" });
388
+ report.refused++;
389
+ return;
390
+ }
391
+ const defer = (until, why) => {
392
+ report.problems.push(`telegram chat reply for update ${row.updateId} is waiting to be sent: ${why}`);
393
+ finish({ state: "queued", outcome: "delivering", nextAttemptAt: until.toISOString() });
394
+ };
395
+ for (const part of store.listTelegramConversationParts(row.id)) {
396
+ if (part.state !== "pending")
397
+ continue;
398
+ const now = clock();
399
+ if (part.kind === "card") {
400
+ const proposal = part.proposal === null ? null : store.getMateProposal(part.proposal);
401
+ if (proposal === null || proposal.state !== "pending") {
402
+ // Confirmed or dismissed from the console or the terminal before the card went out: moot, not lost.
403
+ if (!store.dropTelegramConversationPart(row.id, part.ordinal, owner, proposal === null ? "the proposal is gone" : `the proposal was already ${proposal.state}`, now))
404
+ return;
405
+ continue;
406
+ }
407
+ }
408
+ if (now.getTime() - Date.parse(row.createdAt) > DELIVERY_MAX_AGE_MS) {
409
+ report.problems.push(`telegram chat reply for update ${row.updateId} was not sent within a day: ${part.lastError ?? "unsent"}`);
410
+ finish({ state: "failed", outcome: `unsent:gave-up:${part.lastError ?? "unsent"}` });
411
+ report.refused++;
412
+ return;
413
+ }
414
+ // Telegram's own retry_after, shared with the outbox: nothing goes out while it holds.
415
+ const limitedUntil = store.telegramRetryAt(botId);
416
+ if (limitedUntil > now.toISOString()) {
417
+ defer(new Date(limitedUntil), "Telegram asked for a pause");
418
+ return;
419
+ }
420
+ // The channel the reply was composed under, re-proved after ONE registry read; then the claim.
421
+ let registry = null;
422
+ try {
423
+ registry = await readProjects();
424
+ }
425
+ catch {
426
+ registry = null;
427
+ }
428
+ const read = registry;
429
+ const problem = read === null ? UNREADABLE_REGISTRY : await telegramChannelProblem(store, { botId, bindingId: binding.id, approverGeneration: binding.approverGeneration, ceilingDigest: session.ceilingDigest }, async () => read);
430
+ if (problem === UNREADABLE_REGISTRY) {
431
+ defer(new Date(clock().getTime() + PART_RETRY_MS[0]), problem);
432
+ return;
433
+ }
434
+ if (problem !== null) {
435
+ report.problems.push(`telegram chat reply for update ${row.updateId} was not sent: ${problem}`);
436
+ await notify(`${problem.charAt(0).toUpperCase()}${problem.slice(1)}, so the assistant's reply was not sent. Nothing was changed. Send your message again if you still want it.`);
437
+ finish({ state: "failed", outcome: `unsent:${problem}` });
438
+ report.refused++;
439
+ return;
440
+ }
441
+ if (!held())
442
+ return;
443
+ const repos = telegramConversationRepos(store, binding.approver, read ?? []);
444
+ const backoff = new Date(clock().getTime() + PART_RETRY_MS[Math.min(part.attempts, PART_RETRY_MS.length - 1)]);
445
+ let send;
446
+ let method;
447
+ /** What Telegram's confirmed message id makes of the part: sent (the reply, a card, an image), or dropped (an image whose refusal notice is now confirmed). */
448
+ let confirm = messageId => store.settleTelegramConversationPart(row.id, part.ordinal, owner, { ok: true, messageId }, clock());
449
+ if (part.kind === "image") {
450
+ // The image, re-proved NOW — after the registry await, under the
451
+ // held claim, immediately before the upload, on every attempt: the
452
+ // task still in this phone's projects, the run still its own, the
453
+ // artifact row still the one planned from, the bytes read again and
454
+ // still a bounded PNG/JPEG. Anything less is refused in plain words
455
+ // and never sent; the exact result is one button away when a
456
+ // trusted origin can carry it. The original bytes travel untouched.
457
+ const image = part.taskId === null || part.run === null || part.artifact === null || part.sha256 === null
458
+ ? { ok: false, problem: "the saved file's record changed" }
459
+ : verifyResultImage(store, options.evidenceRoot, repos, { taskId: part.taskId, run: part.run, artifact: part.artifact, sha256: part.sha256 });
460
+ // The caption as persisted, checked again on every attempt: a row
461
+ // that carries a credential-shaped task id is rebuilt from its typed
462
+ // identity before a word of it reaches Telegram.
463
+ const caption = safeResultImageCaption(part.text, part.taskId ?? "", part.run ?? 0);
464
+ if (!image.ok) {
465
+ // The refusal is the part's own message now: it rides the same
466
+ // pending row, retry schedule and uncertain count as the upload
467
+ // it replaces, and the part is dropped only once Telegram confirms
468
+ // the notice. A lost or failed notice is retried after a restart
469
+ // like any part; the image is re-verified first each time, so a
470
+ // file repaired meanwhile is sent and one still wrong is refused
471
+ // again. The row is never done while the notice is unconfirmed.
472
+ report.problems.push(`telegram chat image for update ${row.updateId} was not sent: ${image.problem}`);
473
+ const link = part.taskId !== null && part.run !== null && taskInCeiling(store, part.taskId, repos) ? { label: "Open result", path: chatResultHref(part.taskId, part.run) } : null;
474
+ const button = phoneLinkButton(options.phoneOrigin?.() ?? null, link);
475
+ const problem = image.problem;
476
+ method = "sendMessage";
477
+ send = () => transport("sendMessage", {
478
+ chat_id: chatId,
479
+ text: phoneText(`${caption} was not sent: ${problem}. Open the result to view it.`, 1_000),
480
+ link_preview_options: { is_disabled: true },
481
+ reply_parameters: { message_id: Number(row.messageId) },
482
+ ...(button === null ? {} : { reply_markup: { inline_keyboard: [button] } }),
483
+ });
484
+ confirm = () => store.dropTelegramConversationPart(row.id, part.ordinal, owner, problem, clock());
485
+ }
486
+ else {
487
+ const upload = {
488
+ field: "document",
489
+ fileName: resultImageFileName(part.taskId, part.run, part.artifact, image.format),
490
+ contentType: image.format === "png" ? "image/png" : "image/jpeg",
491
+ bytes: image.bytes,
492
+ };
493
+ method = "sendDocument";
494
+ send = () => transport("sendDocument", { chat_id: chatId, caption }, undefined, upload);
495
+ }
496
+ }
497
+ else {
498
+ // A card's link is minted NOW, from the persisted proposal and the
499
+ // origin configured at this moment: a retry after the setting changed
500
+ // or went away carries the current truth, never a stored URL.
501
+ const proposal = part.kind === "card" && part.proposal !== null ? store.getMateProposal(part.proposal) : null;
502
+ const origin = proposal === null ? null : options.phoneOrigin?.() ?? null;
503
+ const wanted = proposal === null ? null : proposalLink(store, proposal, repos);
504
+ const keyboard = keyboardWith(part.keyboard, phoneLinkButton(origin, wanted));
505
+ const text = proposal !== null && part.keyboard === null ? handoffCardText(part.text, linkNote(origin, wanted)) : part.text;
506
+ method = "sendMessage";
507
+ send = () => transport("sendMessage", {
508
+ chat_id: chatId,
509
+ text,
510
+ link_preview_options: { is_disabled: true },
511
+ ...(part.replyTo === null ? {} : { reply_parameters: { message_id: Number(part.replyTo) } }),
512
+ ...(keyboard === undefined ? {} : { reply_markup: { inline_keyboard: keyboard } }),
513
+ });
514
+ }
515
+ let answer;
516
+ try {
517
+ answer = await send();
518
+ }
519
+ catch {
520
+ // The answer was lost: Telegram may or may not have the message. Said so, retried, counted.
521
+ const error = "Telegram transport failed; delivery may be uncertain";
522
+ if (store.settleTelegramConversationPart(row.id, part.ordinal, owner, { ok: false, error, uncertain: true, retryAt: backoff.toISOString() }, clock()))
523
+ defer(backoff, error);
524
+ return;
525
+ }
526
+ if (!answer.ok) {
527
+ const retry = answer.parameters?.retry_after;
528
+ const retryAfter = typeof retry === "number" && Number.isFinite(retry) && retry > 0 ? Math.ceil(retry) : null;
529
+ const uncertain = answer.uncertain === true;
530
+ const error = `${answer.description ?? `${method} failed`}${uncertain ? "; delivery may be uncertain" : ""}${retryAfter === null ? "" : ` (retry after ${retryAfter}s)`}`;
531
+ let until = backoff;
532
+ if (retryAfter !== null) {
533
+ const paused = new Date(clock().getTime() + retryAfter * 1_000);
534
+ store.deferTelegram(botId, paused.toISOString());
535
+ if (paused > until)
536
+ until = paused;
537
+ }
538
+ if (store.settleTelegramConversationPart(row.id, part.ordinal, owner, { ok: false, error, uncertain, retryAt: until.toISOString() }, clock()))
539
+ defer(until, error);
540
+ return;
541
+ }
542
+ const id = answer.result?.message_id;
543
+ if (!Number.isSafeInteger(id) || id <= 0) {
544
+ const error = "Telegram returned no confirmed message identity";
545
+ if (store.settleTelegramConversationPart(row.id, part.ordinal, owner, { ok: false, error, uncertain: true, retryAt: backoff.toISOString() }, clock()))
546
+ defer(backoff, error);
547
+ return;
548
+ }
549
+ const messageId = String(id);
550
+ if (!confirm(messageId))
551
+ return;
552
+ // Placement names the persisted callback tokens only; a url row is not a token.
553
+ if (part.keyboard !== null)
554
+ store.placeTelegramProposalActions(part.keyboard.flat().map(one => one.callback_data), messageId);
555
+ }
556
+ if (finish({ state: "done", outcome: outcomeWord }))
557
+ report.answered++;
558
+ };
559
+ /** The reply, one image per screenshot the ANSWERED turn selected (typed identity only — bytes are read and re-verified at send time), and one card per pending proposal, persisted with the cards' tokens in ONE transaction before any send. */
560
+ const plan = (session, turn, reply, proposals, repos) => {
561
+ try {
562
+ return store.transact(() => {
563
+ const now = clock();
564
+ const parts = splitParts(reply).map((text, index) => ({ kind: "reply", text, replyTo: index === 0 ? row.messageId : null }));
565
+ // Only a turn that answered may have its selection sent: a failed or revoked turn's rows were deleted with its drafts, and the state is read again here.
566
+ const selected = store.getMateTurn(turn)?.state === "answered" ? store.listMateTurnEvidence(turn) : [];
567
+ for (const image of selected)
568
+ parts.push({ kind: "image", text: image.caption, taskId: image.taskId, run: image.run, artifact: image.artifact, sha256: image.sha256 });
569
+ for (const proposal of proposals) {
570
+ const preview = proposalPreview(store, proposal, repos, "telegram");
571
+ const keyboard = preview.buttons ? mintCardTokens(store, binding, proposal.id, now).keyboard : null;
572
+ parts.push({ kind: "card", text: preview.text, proposal: proposal.id, keyboard });
573
+ }
574
+ // A lapsed claim (or parts already planned by another claimant) keeps nothing minted here.
575
+ if (!store.planTelegramConversationParts(row.id, owner, { session, turn }, parts, now))
576
+ throw new PlanRefused();
577
+ return true;
578
+ });
579
+ }
580
+ catch (error) {
581
+ if (error instanceof PlanRefused)
582
+ return false;
583
+ throw error;
584
+ }
585
+ };
586
+ /** The recorded turn's outcome — nothing dispatched — from the session it was receipted under. */
587
+ const recover = async (session, turnId, repos) => {
588
+ // A turn that died past its deadline is swept to failed first, so a
589
+ // restart reports the truth instead of waiting on a ghost.
590
+ store.sweepStaleMateTurns(clock());
591
+ const turn = store.getMateTurn(turnId);
592
+ if (turn === null) {
593
+ finish({ state: "failed", outcome: "replayed:missing" });
594
+ report.refused++;
595
+ return;
596
+ }
597
+ store.bindTelegramConversationTurn(row.id, owner, session, turnId);
598
+ if (turn.state === "queued" || turn.state === "running") {
599
+ requeue("replayed:running");
600
+ return;
601
+ }
602
+ if (turn.state !== "answered") {
603
+ await notify(`Your earlier message was received, but the assistant's reply did not complete (${phoneText(turn.failureReason ?? "unknown", 40)}). Nothing was changed. Send it again if you still want it.`);
604
+ finish({ state: "failed", outcome: `replayed:${turn.failureReason ?? "failed"}` });
605
+ report.refused++;
606
+ return;
607
+ }
608
+ const reply = store.listMateMessages(turn.thread, 200).find(one => one.turn === turn.id && one.role === "assistant")?.text ?? "(the reply text is no longer in the thread)";
609
+ const proposals = store.listMateProposals(turn.thread, ["pending"]).filter(one => one.turn === turn.id);
610
+ if (!plan(session, turnId, reply, proposals, repos) && store.listTelegramConversationParts(row.id).length === 0)
611
+ return;
612
+ await deliver("replayed");
613
+ };
614
+ // 2. Parts already planned: the turn answered; only the sending remains.
615
+ if (store.listTelegramConversationParts(row.id).length > 0) {
616
+ await deliver("replayed");
617
+ return;
618
+ }
619
+ // 3. The ceiling, reloaded now.
620
+ let registry;
621
+ try {
622
+ registry = await readProjects();
623
+ }
624
+ catch {
625
+ report.problems.push("telegram chat could not read the current project records");
626
+ requeue("registry-unavailable");
627
+ return;
628
+ }
629
+ const repos = telegramConversationRepos(store, binding.approver, registry);
630
+ // 4. A session bound before an earlier dispatch: the receipt lives there,
631
+ // whatever session is live today.
632
+ if (row.session !== null) {
633
+ const receipt = store.mateRequestReceipt(row.session, row.request);
634
+ if (receipt !== null) {
635
+ await recover(row.session, receipt.turn, repos);
636
+ return;
637
+ }
638
+ }
639
+ // 5. The session and thread — shared with the console and the CLI.
640
+ const resolved = resolveTelegramMate(store, binding, repos, clock());
641
+ if (!resolved.ok) {
642
+ if (resolved.said !== null)
643
+ await notify(resolved.said);
644
+ finish({ state: "failed", outcome: `refused:${resolved.reason}` });
645
+ report.refused++;
646
+ return;
647
+ }
648
+ const { who, session, thread, config } = resolved;
649
+ const revalidate = async () => {
650
+ const problem = await telegramChannelProblem(store, { botId, bindingId: binding.id, approverGeneration: binding.approverGeneration, repos: who.repos }, readProjects);
651
+ return problem === null ? { ok: true } : { ok: false, reason: problem };
652
+ };
653
+ // Bound BEFORE the dispatch, under the claim: a crash from here on finds
654
+ // the receipt in this session, not in whichever session is live later.
655
+ if (!store.bindTelegramConversationSession(row.id, owner, session.id))
656
+ return;
657
+ // 6. The turn. The claim is renewed while the model works; the poll lease
658
+ // is the caller's. The request id is the receipt: a second pass after a
659
+ // crash gets the original turn back, never a second dispatch.
660
+ const heartbeat = setInterval(() => { held(); }, 30_000);
661
+ heartbeat.unref?.();
662
+ let outcome;
663
+ try {
664
+ outcome = await runMateTurn({
665
+ store, who, session, thread, config, key: null, message: row.text, requestId: row.request,
666
+ ...(row.context === null ? {} : { context: row.context }),
667
+ ...(options.subscriptionRunner === undefined ? {} : { subscriptionRunner: options.subscriptionRunner }),
668
+ clock, evidenceRoot: options.evidenceRoot, revalidate, mediaDelivery: "documents",
669
+ });
670
+ }
671
+ finally {
672
+ clearInterval(heartbeat);
673
+ }
674
+ const receipt = store.mateRequestReceipt(session.id, row.request);
675
+ if (receipt !== null)
676
+ store.bindTelegramConversationTurn(row.id, owner, session.id, receipt.turn);
677
+ if (!outcome.ok && "refused" in outcome) {
678
+ if (outcome.refused === "concurrent") {
679
+ requeue("busy");
680
+ return;
681
+ }
682
+ await notify(`${outcome.message.charAt(0).toUpperCase()}${outcome.message.slice(1)}. Nothing was changed.`);
683
+ finish({ state: "failed", outcome: `refused:${outcome.refused}` });
684
+ report.refused++;
685
+ return;
686
+ }
687
+ if (!outcome.ok) {
688
+ // Truthful: what failed, that nothing was kept, and that a new message
689
+ // is a new turn. A failed turn's drafts are already gone.
690
+ const words = outcome.failed === "revoked" ? `${outcome.message.charAt(0).toUpperCase()}${outcome.message.slice(1)}.` : `The assistant's reply did not complete: ${outcome.message}. Nothing it proposed was kept. Send your message again if you still want it.`;
691
+ await notify(phoneText(words, 1_000));
692
+ finish({ state: "failed", outcome: `failed:${outcome.failed}` });
693
+ report.refused++;
694
+ return;
695
+ }
696
+ if (outcome.replayed) {
697
+ await recover(session.id, outcome.turn, who.repos);
698
+ return;
699
+ }
700
+ if (row.taskId !== null)
701
+ mirrorToTaskChat(store, who, row.taskId, "Telegram", row.text, outcome.reply, clock());
702
+ // 7. The reply and its cards: durable first, then sent.
703
+ const proposals = store.listMateProposals(thread.id, ["pending"]).filter(one => one.turn === outcome.turn);
704
+ if (!plan(session.id, outcome.turn, outcome.reply, proposals, who.repos) && store.listTelegramConversationParts(row.id).length === 0)
705
+ return;
706
+ await deliver("answered");
707
+ }