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
package/dist/push.js ADDED
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Web push, zero-dependency (arc 3).
3
+ *
4
+ * Three layers, kept apart: CRYPTO (RFC 8291 aes128gcm message encryption
5
+ * and RFC 8292 VAPID authorization, byte-exact, node:crypto only),
6
+ * TRANSPORT (one hardened fetch to an allow-listed push service), and the
7
+ * PASS (claim pairs from the store's ledger, fence, send, settle by the
8
+ * full outcome table). The payload that transits Apple's and Google's
9
+ * servers is a fixed phrase keyed on the notification's CLASS plus a
10
+ * machine-minted console path. Check progress may also send one validated
11
+ * suite/count line; task titles, branches and repository names stay local.
12
+ *
13
+ * 'accepted' means the push service took the message (RFC 8030). Nothing
14
+ * here ever claims a phone displayed anything, and delivery is
15
+ * AT-LEAST-ONCE: a crash between acceptance and settlement re-sends after
16
+ * the pair's lease expires (the payload's tag collapses the duplicate on
17
+ * the lock screen).
18
+ */
19
+ import { closeSync, constants as fsConstants, fsyncSync, openSync, readFileSync, writeSync } from "node:fs";
20
+ import { join } from "node:path";
21
+ import { createCipheriv, createECDH, createPrivateKey, hkdfSync, randomBytes, createHash, sign as cryptoSign } from "node:crypto";
22
+ import { isCheckProgressLine } from "./check-progress.js";
23
+ export const VAPID_FILE = "vapid-keys.json";
24
+ /** RFC 8291 §4: rs 4096 leaves room for the record's delimiter and tag. */
25
+ const RECORD_SIZE = 4096;
26
+ /** The plaintext ceiling that keeps one record inside rs (RFC 8291 §4). */
27
+ export const PLAINTEXT_CEILING = 3993;
28
+ const REQUEST_TIMEOUT_MS = 10_000;
29
+ /** The pair lease strictly exceeds the request timeout (finding 12). */
30
+ export const PUSH_CLAIM_TTL_MS = 30_000;
31
+ const BACKOFF_MS = [60_000, 300_000, 1_500_000, 7_200_000];
32
+ const MAX_ATTEMPTS = 8;
33
+ const b64url = (buffer) => buffer.toString("base64url");
34
+ const fromB64url = (text) => Buffer.from(text, "base64url");
35
+ export function vapidFingerprint(publicKey) {
36
+ return createHash("sha256").update(publicKey, "utf8").digest("hex").slice(0, 16);
37
+ }
38
+ /**
39
+ * Mint-or-load, atomically across processes (finding 29): exclusive
40
+ * creation; the loser of a race re-reads the winner's file; and a loaded
41
+ * pair is VALIDATED — the stored public key must derive from the stored
42
+ * private key, or the file is refused rather than trusted.
43
+ */
44
+ export function loadOrCreateVapidKeys(dir) {
45
+ const path = join(dir, VAPID_FILE);
46
+ const read = () => {
47
+ let raw;
48
+ try {
49
+ raw = readFileSync(path, "utf8");
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ const parsed = JSON.parse(raw);
55
+ if (typeof parsed.publicKey !== "string" || typeof parsed.privateKey !== "string") {
56
+ throw new Error(`${VAPID_FILE} is not a key file`);
57
+ }
58
+ const check = createECDH("prime256v1");
59
+ check.setPrivateKey(fromB64url(parsed.privateKey));
60
+ if (b64url(check.getPublicKey()) !== parsed.publicKey) {
61
+ throw new Error(`${VAPID_FILE} holds a mismatched key pair — refusing to sign with it`);
62
+ }
63
+ return { publicKey: parsed.publicKey, privateKey: parsed.privateKey, fingerprint: vapidFingerprint(parsed.publicKey) };
64
+ };
65
+ const existing = read();
66
+ if (existing !== null)
67
+ return existing;
68
+ const minted = createECDH("prime256v1");
69
+ minted.generateKeys();
70
+ const fresh = { publicKey: b64url(minted.getPublicKey()), privateKey: b64url(minted.getPrivateKey()) };
71
+ try {
72
+ const fd = openSync(path, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 0o600);
73
+ try {
74
+ writeSync(fd, JSON.stringify(fresh, null, 2));
75
+ fsyncSync(fd);
76
+ }
77
+ finally {
78
+ closeSync(fd);
79
+ }
80
+ return { ...fresh, fingerprint: vapidFingerprint(fresh.publicKey) };
81
+ }
82
+ catch {
83
+ // Another process won the exclusive create — its keys are the keys.
84
+ const winner = read();
85
+ if (winner === null)
86
+ throw new Error(`could not create or read ${VAPID_FILE}`);
87
+ return winner;
88
+ }
89
+ }
90
+ // ---- RFC 8291 encryption ----------------------------------------------------
91
+ /**
92
+ * The deterministic core, exported for the Appendix A vector test: given
93
+ * the subscriber's materials, an application-server ECDH object, and a
94
+ * salt, produce the complete aes128gcm body — header || ciphertext.
95
+ */
96
+ export function encryptWithMaterials(uaPublic, authSecret, asKeys, salt, plaintext) {
97
+ if (uaPublic.length !== 65 || uaPublic[0] !== 0x04)
98
+ throw new Error("subscriber key is not a 65-byte uncompressed P-256 point");
99
+ if (authSecret.length !== 16)
100
+ throw new Error("auth secret is not 16 bytes");
101
+ if (salt.length !== 16)
102
+ throw new Error("salt is not 16 bytes");
103
+ if (plaintext.length > PLAINTEXT_CEILING)
104
+ throw new Error(`payload over ${PLAINTEXT_CEILING} bytes`);
105
+ const asPublic = asKeys.getPublicKey();
106
+ // computeSecret rejects off-curve points — the caller maps that throw to
107
+ // an invalid subscription, never a crash.
108
+ const ecdhSecret = asKeys.computeSecret(uaPublic);
109
+ const keyInfo = Buffer.concat([Buffer.from("WebPush: info", "ascii"), Buffer.from([0]), uaPublic, asPublic]);
110
+ const ikm = Buffer.from(hkdfSync("sha256", ecdhSecret, authSecret, keyInfo, 32));
111
+ const cek = Buffer.from(hkdfSync("sha256", ikm, salt, Buffer.concat([Buffer.from("Content-Encoding: aes128gcm", "ascii"), Buffer.from([0])]), 16));
112
+ const nonce = Buffer.from(hkdfSync("sha256", ikm, salt, Buffer.concat([Buffer.from("Content-Encoding: nonce", "ascii"), Buffer.from([0])]), 12));
113
+ // One record: plaintext, the last-record delimiter 0x02, zero padding.
114
+ const record = Buffer.concat([plaintext, Buffer.from([2])]);
115
+ const cipher = createCipheriv("aes-128-gcm", cek, nonce);
116
+ const ciphertext = Buffer.concat([cipher.update(record), cipher.final(), cipher.getAuthTag()]);
117
+ const header = Buffer.alloc(16 + 4 + 1);
118
+ salt.copy(header, 0);
119
+ header.writeUInt32BE(RECORD_SIZE, 16);
120
+ header.writeUInt8(65, 20);
121
+ return Buffer.concat([header, asPublic, ciphertext]);
122
+ }
123
+ /** The production wrapper: fresh ephemeral keys and a fresh salt per message. */
124
+ export function encryptPushPayload(p256dh, auth, plaintext) {
125
+ const ephemeral = createECDH("prime256v1");
126
+ ephemeral.generateKeys();
127
+ return encryptWithMaterials(fromB64url(p256dh), fromB64url(auth), ephemeral, randomBytes(16), Buffer.from(plaintext, "utf8"));
128
+ }
129
+ // ---- RFC 8292 VAPID ---------------------------------------------------------
130
+ /**
131
+ * The VAPID authorization header: an ES256 JWT over { aud, exp, sub? },
132
+ * signed with the STABLE server key (distinct from every message's
133
+ * ephemeral key), in JWS P1363 form — never DER (finding 4).
134
+ */
135
+ export function vapidAuthorization(endpoint, keys, now, contact) {
136
+ const aud = new URL(endpoint).origin;
137
+ const exp = Math.floor(now.getTime() / 1000) + 12 * 60 * 60; // ≤ 24h per RFC 8292
138
+ const claims = { aud, exp };
139
+ if (contact !== undefined && (contact.startsWith("mailto:") || contact.startsWith("https:")))
140
+ claims["sub"] = contact;
141
+ const header = b64url(Buffer.from(JSON.stringify({ typ: "JWT", alg: "ES256" }), "utf8"));
142
+ const body = b64url(Buffer.from(JSON.stringify(claims), "utf8"));
143
+ const signingInput = `${header}.${body}`;
144
+ const publicPoint = fromB64url(keys.publicKey);
145
+ const privateKey = createPrivateKey({
146
+ format: "jwk",
147
+ key: {
148
+ kty: "EC",
149
+ crv: "P-256",
150
+ d: keys.privateKey,
151
+ x: b64url(publicPoint.subarray(1, 33)),
152
+ y: b64url(publicPoint.subarray(33, 65)),
153
+ },
154
+ });
155
+ const signature = cryptoSign("sha256", Buffer.from(signingInput, "utf8"), { key: privateKey, dsaEncoding: "ieee-p1363" });
156
+ return `vapid t=${signingInput}.${b64url(signature)}, k=${keys.publicKey}`;
157
+ }
158
+ // ---- endpoint discipline ----------------------------------------------------
159
+ /** The closed allow-list of push services (finding 7): SSRF dies here. */
160
+ const PUSH_HOSTS = [
161
+ { exact: "fcm.googleapis.com" },
162
+ { suffix: ".push.apple.com" },
163
+ { exact: "updates.push.services.mozilla.com" },
164
+ { suffix: ".push.services.mozilla.com" },
165
+ { suffix: ".notify.windows.com" },
166
+ ];
167
+ export function validatePushEndpoint(raw) {
168
+ if (raw.length > 1024)
169
+ return { ok: false, problem: "the endpoint is too long" };
170
+ let url;
171
+ try {
172
+ url = new URL(raw);
173
+ }
174
+ catch {
175
+ return { ok: false, problem: "the endpoint is not a URL" };
176
+ }
177
+ if (url.protocol !== "https:")
178
+ return { ok: false, problem: "push endpoints are https" };
179
+ if (url.username !== "" || url.password !== "" || url.hash !== "")
180
+ return { ok: false, problem: "the endpoint carries parts a push URL never has" };
181
+ if (url.port !== "" && url.port !== "443")
182
+ return { ok: false, problem: "push services answer on 443" };
183
+ if (/^[0-9.[]/.test(url.hostname))
184
+ return { ok: false, problem: "push endpoints are named services, not addresses" };
185
+ const host = url.hostname.toLowerCase();
186
+ const known = PUSH_HOSTS.some(one => (one.exact !== undefined ? host === one.exact : host.endsWith(one.suffix)));
187
+ if (!known)
188
+ return { ok: false, problem: "that host is not a known push service — extending the list is a code change, deliberately" };
189
+ return { ok: true, url };
190
+ }
191
+ // ---- the payload ------------------------------------------------------------
192
+ /** Fixed words, except for one validated machine-only suite/count line. */
193
+ export const PUSH_WORDS = {
194
+ decision: { title: "Toolroll", body: "a decision needs you" },
195
+ pick: { title: "Toolroll", body: "agents finished — pick a winner" },
196
+ merge: { title: "Toolroll", body: "a pull request needs a person" },
197
+ attention: { title: "Toolroll", body: "the plane needs attention" },
198
+ progress: { title: "Toolroll", body: "checks in progress" },
199
+ };
200
+ const LINK_SHAPES = [/^\/next$/, /^\/review$/, /^\/system$/, /^\/routines$/, /^\/routines\/[0-9]{1,15}$/, /^\/d\/[0-9]{1,15}$/, /^\/contest\/[0-9]{1,15}$/, /^\/r\/[0-9]{1,15}$/];
201
+ const CLASS_FALLBACK = {
202
+ decision: "/next",
203
+ pick: "/next",
204
+ merge: "/review",
205
+ attention: "/next",
206
+ progress: "/next",
207
+ };
208
+ /** Static words and machine-minted numbers only — re-proved at send time. */
209
+ export function safePushLink(pushClass, link) {
210
+ if (link !== null && LINK_SHAPES.some(shape => shape.test(link)))
211
+ return link;
212
+ return CLASS_FALLBACK[pushClass];
213
+ }
214
+ export function buildPushPayload(notification, waiting = null) {
215
+ const pushClass = notification.pushClass;
216
+ const words = PUSH_WORDS[pushClass];
217
+ return JSON.stringify({
218
+ title: words.title,
219
+ body: pushClass === "progress" && isCheckProgressLine(notification.body) ? notification.body : words.body,
220
+ url: safePushLink(pushClass, notification.link),
221
+ // The opaque tag collapses a crash-duplicate on the lock screen.
222
+ tag: pushClass === "progress" && notification.run !== null ? `so-run-${notification.run}` : `so-${notification.id}`,
223
+ // The app-icon badge (Phase 2E, v2 S4): the server-computed
224
+ // waiting-on-you COUNT at send time — a number, never content, so the
225
+ // closed-class discipline holds. The console page recomputes and
226
+ // clears; a stale push never outranks a fresh page.
227
+ ...(waiting === null ? {} : { waiting }),
228
+ });
229
+ }
230
+ function backoffFor(attempts, retryAfter, now) {
231
+ if (retryAfter !== null) {
232
+ const seconds = Number(retryAfter);
233
+ if (Number.isFinite(seconds) && seconds > 0)
234
+ return new Date(now.getTime() + Math.min(seconds, 7_200) * 1000);
235
+ }
236
+ const step = BACKOFF_MS[Math.min(Math.max(attempts - 1, 0), BACKOFF_MS.length - 1)];
237
+ return new Date(now.getTime() + step);
238
+ }
239
+ /**
240
+ * One pass: seed, claim, and for each pair — renew, fence, encrypt, send,
241
+ * settle by the outcome table. Every settle is conditional on the claim's
242
+ * owner AND generation; a lapsed claim writes nothing.
243
+ */
244
+ export async function pushPass(store, options) {
245
+ const clock = options.clock ?? (() => new Date());
246
+ const owner = options.owner ?? `push-${randomBytes(4).toString("hex")}`;
247
+ const doFetch = options.fetchImpl ?? (async (url, init) => fetch(url, init));
248
+ const report = { seeded: 0, accepted: 0, rescheduled: 0, rejected: 0, retired: 0, credentialProblem: false };
249
+ if (store.isDemo())
250
+ return report; // a sandbox never pushes
251
+ const keys = loadOrCreateVapidKeys(options.configDir);
252
+ // Subscriptions are bound to the key they enrolled under (finding 27):
253
+ // after a regeneration they reject every message — retire them visibly.
254
+ report.retired += store.retirePushSubscriptionsOfOtherKeys(keys.fingerprint, clock());
255
+ report.seeded = store.seedPushPairs(clock());
256
+ const pairs = store.claimPushPairs(owner, PUSH_CLAIM_TTL_MS, 32, clock());
257
+ let sawAccepted = false;
258
+ for (const pair of pairs) {
259
+ // Renew and re-fence immediately before the network (finding 12).
260
+ if (!store.renewPushClaim(pair.id, owner, pair.claimGeneration, PUSH_CLAIM_TTL_MS, clock()))
261
+ continue;
262
+ const fenced = store.pushSendFence(pair.id, owner, pair.claimGeneration);
263
+ if (fenced === null)
264
+ continue; // resolved, retired, rotated, or lost — nothing to send
265
+ const subscription = fenced.subscriptionRow;
266
+ const endpoint = validatePushEndpoint(subscription.endpoint);
267
+ if (!endpoint.ok) {
268
+ store.retirePushSubscription(subscription.id, "invalid-endpoint", clock());
269
+ report.retired += 1;
270
+ continue;
271
+ }
272
+ let body;
273
+ try {
274
+ body = encryptPushPayload(subscription.p256dh, subscription.auth,
275
+ // The badge count rides every push: the same inbox classifier the
276
+ // console's own sidebar badge reads, unscoped (a phone has no
277
+ // project pane), computed at send time.
278
+ buildPushPayload(fenced.notificationRow, store.countInboxScoped(null, clock(), 100, null).count));
279
+ }
280
+ catch {
281
+ // Off-curve or malformed subscriber material: the device, not us.
282
+ store.retirePushSubscription(subscription.id, "invalid-keys", clock());
283
+ report.retired += 1;
284
+ continue;
285
+ }
286
+ let status;
287
+ let retryAfter = null;
288
+ try {
289
+ const answer = await doFetch(subscription.endpoint, {
290
+ method: "POST",
291
+ redirect: "error",
292
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
293
+ headers: {
294
+ Authorization: vapidAuthorization(subscription.endpoint, keys, clock(), options.contact),
295
+ "Content-Encoding": "aes128gcm",
296
+ "Content-Type": "application/octet-stream",
297
+ TTL: "300",
298
+ Urgency: "high",
299
+ },
300
+ body,
301
+ });
302
+ status = answer.status;
303
+ retryAfter = answer.headers.get("retry-after");
304
+ }
305
+ catch {
306
+ status = 0; // network / timeout / redirect refusal
307
+ }
308
+ // The outcome table (finding 18).
309
+ if (status >= 200 && status < 300) {
310
+ if (store.settlePushPair(pair.id, owner, pair.claimGeneration, { kind: "accepted" }, clock()))
311
+ report.accepted += 1;
312
+ sawAccepted = true;
313
+ }
314
+ else if (status === 404 || status === 410) {
315
+ store.retirePushSubscription(subscription.id, "gone", clock());
316
+ report.retired += 1;
317
+ }
318
+ else if (status === 400 || status === 413) {
319
+ if (store.settlePushPair(pair.id, owner, pair.claimGeneration, { kind: "rejected", error: `push service answered ${status}` }, clock())) {
320
+ report.rejected += 1;
321
+ }
322
+ }
323
+ else if (status === 401 || status === 403) {
324
+ // A CREDENTIAL problem, never a device problem: no retirement, and
325
+ // one deduped, UNSTAMPED operator episode (finding 27).
326
+ report.credentialProblem = true;
327
+ store.enqueueEpisode(`push-credentials:${keys.fingerprint}`, {
328
+ source: { installation: true },
329
+ kind: "push-credentials",
330
+ subject: "push credentials were rejected",
331
+ body: "the push service refused this console's signing key — if the key file was replaced, phones must re-enroll from /settings",
332
+ }, clock().toISOString(), clock());
333
+ settleRetryOrGiveUp(store, pair.id, owner, pair.claimGeneration, pair.attempts, `push service answered ${status}`, retryAfter, clock(), report);
334
+ }
335
+ else {
336
+ settleRetryOrGiveUp(store, pair.id, owner, pair.claimGeneration, pair.attempts, status === 0 ? "the push service could not be reached" : `push service answered ${status}`, retryAfter, clock(), report);
337
+ }
338
+ }
339
+ // A working credential closes the episode (finding 27).
340
+ if (sawAccepted)
341
+ store.resolveEpisodes(`push-credentials:${keys.fingerprint}`, clock());
342
+ return report;
343
+ }
344
+ function settleRetryOrGiveUp(store, pairId, owner, generation, attempts, error, retryAfter, now, report) {
345
+ if (attempts + 1 >= MAX_ATTEMPTS) {
346
+ if (store.settlePushPair(pairId, owner, generation, { kind: "undeliverable", error }, now))
347
+ report.rejected += 1;
348
+ return;
349
+ }
350
+ if (store.settlePushPair(pairId, owner, generation, { kind: "backoff", nextAttemptAt: backoffFor(attempts + 1, retryAfter, now), error }, now)) {
351
+ report.rescheduled += 1;
352
+ }
353
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * How much evidence work a task asks Toolroll to do after the
3
+ * builder finishes. This is deliberately separate from operating modes
4
+ * (authority/autonomy) and unattended permissions (provider sandboxing).
5
+ */
6
+ export type QualityMode = "default" | "strict";
7
+ export declare function isQualityMode(value: unknown): value is QualityMode;
8
+ export declare function qualityModeTitle(mode: QualityMode): string;
@@ -0,0 +1,6 @@
1
+ export function isQualityMode(value) {
2
+ return value === "default" || value === "strict";
3
+ }
4
+ export function qualityModeTitle(mode) {
5
+ return mode === "strict" ? "Strict / release" : "Default";
6
+ }
@@ -0,0 +1,18 @@
1
+ import { type Recipe, type RecipeDocument, type WorkflowPreview } from "./recipes.js";
2
+ export declare function recipeFromForm(body: URLSearchParams): RecipeDocument;
3
+ export declare function recipeLibraryHtml(starters: Recipe[], saved: Recipe[], project: string | null, csrf: string, revision: number, recent: {
4
+ name: string;
5
+ href: string;
6
+ state: string;
7
+ }[]): string;
8
+ export declare function recipeEditorHtml(recipe: Recipe, repo: string, csrf: string, revision: number, problem?: string | null, values?: URLSearchParams, creator?: boolean): string;
9
+ export declare function workflowPreviewHtml(preview: WorkflowPreview, csrf: string, revision: number, now: Date, readiness: {
10
+ title: string;
11
+ detail: string;
12
+ href?: string;
13
+ }[], auto: boolean): string;
14
+ export declare function recipeDefinitionPreviewHtml(preview: WorkflowPreview, csrf: string, revision: number): string;
15
+ export declare function recipeRunHtml(recipe: Recipe, repo: string, csrf: string, revision: number, problem?: string | null, values?: URLSearchParams, saved?: boolean): string;
16
+ export declare function recipeAnswersFromForm(body: URLSearchParams): Map<string, string>;
17
+ export declare function recipeScript(): string;
18
+ export declare const RECIPE_CSS = "\n.recipe-heading { margin: .5rem 0 1.75rem; max-width: 46rem; }\n.recipe-heading h1 { font-size: clamp(1.65rem,3vw,2.5rem); letter-spacing:-.035em; margin:.35rem 0 .6rem; }\n.recipe-kicker { font-size:.7rem; text-transform:uppercase; letter-spacing:.12em; color:var(--muted-foreground); }\n.recipe-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:.8rem; margin:1rem 0 2rem; }\n.recipe-card { border:1px solid var(--border); border-radius:var(--radius); padding:1.1rem; background:var(--card); display:flex; flex-direction:column; gap:.5rem; }\n.recipe-card h3 { font-size:1.05rem; margin:0; letter-spacing:-.015em; }\n.recipe-card p { color:var(--muted-foreground); margin:.2rem 0; font-size:.85rem; line-height:1.5; }\n.recipe-card-link { margin-top:auto; padding-top:.75rem; display:flex; justify-content:space-between; }\n.recipe-editor { max-width:52rem; }.recipe-editor h2 { margin-top:0; }\n.recipe-criterion { border:1px solid var(--border); border-radius:calc(var(--radius) - 3px); padding:.8rem; margin:.8rem 0; }\n.recipe-evidence { display:flex; flex-wrap:wrap; gap:.5rem 1rem; }.recipe-evidence label { display:flex; align-items:center; gap:.4rem; font-size:.8rem; }.recipe-evidence input { width:auto; margin:0; }\n.recipe-options { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }\n.recipe-preview-grid { display:grid; grid-template-columns:minmax(0,1.7fr) minmax(16rem,1fr); gap:1rem; }\n.recipe-steps { padding-left:1.4rem; }.recipe-steps li { padding:.4rem 0 .6rem .3rem; }.recipe-steps p { margin:.3rem 0 0; font-size:.85rem; color:var(--muted-foreground); }\n.recipe-launch { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }.recipe-launch form { margin:0 0 .6rem; }\n.recipe-history a { display:flex; justify-content:space-between; gap:1rem; padding:.6rem 0; border-bottom:1px solid var(--border); }.recipe-history span { color:var(--muted-foreground); }\n.recipe-error { border-color:var(--destructive); }\n.recipe-card[hidden] { display:none; }.recipe-search { max-width:28rem; }.recipe-question { border:1px solid var(--border); border-radius:calc(var(--radius) - 3px); margin:1rem 0; padding:.8rem; min-width:0; }.recipe-run { max-width:42rem; }.recipe-run button { margin-top:1rem; }\n@media(max-width:1000px){.recipe-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }}\n@media(max-width:760px){.recipe-grid,.recipe-options,.recipe-preview-grid,.recipe-launch { grid-template-columns:1fr; }.recipe-heading { margin-bottom:1rem; }.recipe-history a { flex-direction:column; gap:.2rem; }}\n";
@@ -0,0 +1,152 @@
1
+ import { composerSchedule, scheduleEditorHtml, scheduleEditorScript } from "./task-composer.js";
2
+ import { parseRecipe, workflowSteps, recipeScheduleWords, RecipeError, RECIPE_INPUT_LIMIT } from "./recipes.js";
3
+ import { ACCEPTANCE_LIMITS } from "./scope.js";
4
+ const e = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5
+ const hidden = (name, value) => `<input type="hidden" name="${e(name)}" value="${e(value)}">`;
6
+ const evidence = [["check", "Passing tests or commands"], ["changed-path", "Changed files"], ["screenshot", "Screenshot"], ["manual-review", "Reviewer assessment"]];
7
+ export function recipeFromForm(body) {
8
+ for (const name of ["name", "description", "goal", "not", "touches", "planning", "deliverable", "cadence", "repeat", "time", "timezone", "weekday", "interval", "interval-unit", "ceiling", "source", "sourceRevision", "projectRevision", "repo", "preview", "purpose", "sourceFormat"]) {
9
+ if (body.getAll(name).length > 1)
10
+ throw new RecipeError(`Choose one ${name} value.`);
11
+ }
12
+ if (!["once", "repeat"].includes(body.get("cadence") ?? ""))
13
+ throw new RecipeError("Choose run once or repeat.");
14
+ const schedule = body.get("cadence") === "once" ? { ok: true, schedule: null } : composerSchedule(body);
15
+ if (!schedule.ok)
16
+ throw new RecipeError(schedule.message);
17
+ if (body.get("cadence") === "repeat" && schedule.schedule === null)
18
+ throw new RecipeError("Choose a repeating schedule.");
19
+ const acceptance = [];
20
+ for (let i = 0; i < ACCEPTANCE_LIMITS.criteria; i++) {
21
+ for (const key of ["statement", "how", "id"])
22
+ if (body.getAll(`criterion-${i}-${key}`).length > 1)
23
+ throw new RecipeError("A success check has duplicated fields.");
24
+ const statement = (body.get(`criterion-${i}-statement`) ?? "").trim();
25
+ const how = (body.get(`criterion-${i}-how`) ?? "").trim();
26
+ if (!statement && !how)
27
+ continue;
28
+ acceptance.push({ id: body.get(`criterion-${i}-id`) || `c${i + 1}`, statement, how: how || null, evidence: body.getAll(`criterion-${i}-evidence`) });
29
+ }
30
+ // Never silently omit submitted checks beyond the editor's bounded size.
31
+ for (const key of body.keys())
32
+ if (/^criterion-/.test(key) && (!/^criterion-(?:[0-9]|1[01])-(?:statement|how|id|evidence)$/.test(key)))
33
+ throw new RecipeError("A recipe supports at most 12 success checks.");
34
+ const inputs = [];
35
+ for (let i = 0; i < RECIPE_INPUT_LIMIT; i++) {
36
+ for (const key of ["key", "label", "default"])
37
+ if (body.getAll(`input-${i}-${key}`).length > 1)
38
+ throw new RecipeError("A question has duplicated fields.");
39
+ const key = (body.get(`input-${i}-key`) ?? "").trim(), label = (body.get(`input-${i}-label`) ?? "").trim(), defaultValue = (body.get(`input-${i}-default`) ?? "").trim();
40
+ if (key || label || defaultValue)
41
+ inputs.push({ key, label, defaultValue: defaultValue || null });
42
+ }
43
+ for (const key of body.keys())
44
+ if (key.startsWith("input-") && !/^input-[0-7]-(?:key|label|default)$/.test(key))
45
+ throw new RecipeError("A recipe supports at most 8 questions.");
46
+ const sourceFormat = body.get("sourceFormat") ?? "1";
47
+ if (!["1", "2"].includes(sourceFormat))
48
+ throw new RecipeError("Choose a supported source recipe format.");
49
+ if (sourceFormat === "2" && inputs.length === 0 && [body.get("name"), body.get("description"), body.get("goal"), body.get("not"), ...acceptance.map(one => one.statement)].some(text => /\{\{|\}\}/.test(text ?? "")))
50
+ throw new RecipeError("Remove the input placeholders before saving a fixed recipe, or keep a question for each one.");
51
+ const ceiling = (body.get("ceiling") ?? "").trim();
52
+ return parseRecipe({ format: "standing-orders-recipe", version: inputs.length ? 2 : 1, ...(inputs.length ? { inputs } : {}), name: body.get("name") ?? "", description: body.get("description") ?? "", goal: body.get("goal") ?? "", outOfScope: body.get("not") || null,
53
+ touches: (body.get("touches") ?? "").split(/[\n,]/).map(one => one.trim()).filter(Boolean), acceptance, planning: body.get("planning") ?? "auto", deliverable: body.get("deliverable") ?? "branch", schedule: schedule.schedule, costCeilingUsd: ceiling === "" ? null : Number(ceiling) });
54
+ }
55
+ export function recipeLibraryHtml(starters, saved, project, csrf, revision, recent) {
56
+ const cards = (recipes, reusable = false) => `<div class="recipe-grid"${reusable ? ' data-saved-recipes' : ''}>${recipes.map(recipe => `<article class="recipe-card" data-recipe-card data-recipe-search="${e(recipe.document.name + " " + recipe.document.description)}"><p class="recipe-kicker">${recipe.document.deliverable === "report" ? "Report" : recipe.document.schedule === null ? "One-time work" : "Recurring care"}${recipe.document.version === 2 ? ` · ${recipe.document.inputs.length} question${recipe.document.inputs.length === 1 ? "" : "s"}` : ""}</p><h3>${e(recipe.document.name)}</h3><p>${e(recipe.document.description)}</p><a class="recipe-card-link" href="/recipes/${reusable ? "run" : "start"}?recipe=${e(recipe.id)}&revision=${recipe.revision}">${reusable ? "Use recipe" : "Customize recipe"} <span aria-hidden="true">→</span></a>${reusable ? `<a class="meta" href="/recipes/start?recipe=${e(recipe.id)}&revision=${recipe.revision}">Edit a copy</a>` : ""}</article>`).join("")}</div>`;
57
+ const savedSection = `<h2>Your project’s recipes</h2>${saved.length >= 100 ? `<p class="meta">Showing 100 recipes, with recently used recipes first.</p>` : ""}${saved.length ? `<p class="hint">Your usual work, ready to go. Recently used recipes come first.</p><label class="recipe-search">Find a recipe<input type="search" data-recipe-search-input placeholder="Search your recipes…"></label>${cards(saved, true)}<p data-recipe-empty hidden>No recipes match. Try another search.</p>` : `<p class="hint">Save work you do often. Teammates with project access can use the same recipe.</p>`}`;
58
+ return `<div class="recipe-heading"><p class="recipe-kicker">Workflows</p><h1>Good work, on repeat.</h1><p class="hint">Save the work you do often. Answer what changes this time. Kick it off.</p><p><a href="/recipes/new" class="new-task">Create a recipe</a> <a href="/routines">Manage scheduled work →</a></p></div>` +
59
+ (project === null ? `<div class="card"><strong>Choose a project to get started.</strong><p>Browse recipes now; <a href="/projects">open a project</a> to customize, save, or run one.</p></div>` : "") +
60
+ (saved.length ? savedSection : "") + `<h2>Start here</h2>${cards(starters)}` + (saved.length ? "" : savedSection) +
61
+ (recent.length ? `<h2>Recent workflows</h2><div class="card recipe-history">${recent.map(one => `<a href="${e(one.href)}"><strong>${e(one.name)}</strong><span>${e(one.state)} →</span></a>`).join("")}</div>` : "") +
62
+ (project === null ? "" : `<details class="card"><summary>Import a shared recipe</summary><p class="hint">Paste exported recipe JSON. You’ll review it in this project before creating any work.</p><form method="post" action="/recipes/import">${hidden("csrf", csrf)}${hidden("repo", project)}${hidden("projectRevision", String(revision))}<label>Recipe JSON<textarea name="document" rows="7" required maxlength="32768" spellcheck="false"></textarea></label><button type="submit">Review imported recipe</button></form></details>`);
63
+ }
64
+ function criterionHtml(index, criterion, values) {
65
+ const prefix = `criterion-${index}-`;
66
+ const value = (name, fallback) => values?.get(prefix + name) ?? fallback;
67
+ const selected = values ? values.getAll(prefix + "evidence") : criterion?.evidence ?? ["check"];
68
+ return `<fieldset class="recipe-criterion"><legend>${criterion || index === 0 ? `Success check ${index + 1}` : "Additional success check (optional)"}</legend>${hidden(prefix + "id", value("id", criterion?.id ?? `c${index + 1}`))}<label>What should be true?<textarea name="${prefix}statement" rows="2" maxlength="300">${e(value("statement", criterion?.statement ?? ""))}</textarea></label><div class="recipe-evidence">${evidence.map(([id, label]) => `<label><input type="checkbox" name="${prefix}evidence" value="${id}"${selected.includes(id) ? " checked" : ""}>${label}</label>`).join("")}</div><label>How to check it <span class="meta">optional; command or review instructions</span><input type="text" name="${prefix}how" value="${e(value("how", criterion?.how ?? ""))}" maxlength="500"></label></fieldset>`;
69
+ }
70
+ export function recipeEditorHtml(recipe, repo, csrf, revision, problem = null, values, creator = false) {
71
+ const d = recipe.document;
72
+ const value = (name, fallback) => values?.get(name) ?? fallback;
73
+ const cadence = value("cadence", d.schedule === null ? "once" : "repeat");
74
+ const planning = value("planning", d.planning), deliverable = value("deliverable", d.deliverable);
75
+ const count = values ? Math.min(ACCEPTANCE_LIMITS.criteria, Math.max(d.acceptance.length + 1, ...[...values.keys()].map(key => Number(/^criterion-(\d+)-statement$/.exec(key)?.[1] ?? -1) + 1))) : Math.min(ACCEPTANCE_LIMITS.criteria, d.acceptance.length + 1);
76
+ return `<a href="/recipes" class="meta">← All recipes</a><div class="recipe-heading"><p class="recipe-kicker">${creator ? "1 · Describe → 2 · Preview → 3 · Save" : "1 · Choose → 2 · Customize → 3 · Preview"}</p><h1>${creator ? "Create a reusable recipe" : "Make this workflow yours"}</h1><p class="hint">${e(repo.split(/[\\/]/).pop() || repo)} · Choose the outcome, boundaries, and proof you want back.</p></div>` +
77
+ (problem === null ? "" : `<p role="alert" class="card recipe-error">${e(problem)}</p>`) +
78
+ `<form method="post" action="/recipes/preview" class="recipe-editor">${hidden("csrf", csrf)}${hidden("repo", repo)}${hidden("projectRevision", String(revision))}${hidden("source", recipe.id)}${hidden("sourceRevision", String(recipe.revision))}${hidden("sourceFormat", value("sourceFormat", String(d.version)))}` +
79
+ `<section class="card"><h2>The outcome</h2><label>${creator ? "Recipe name" : "Workflow name"}<input type="text" name="name" value="${e(value("name", d.name))}" maxlength="200" required></label><label>Short description<input type="text" name="description" value="${e(value("description", d.description))}" maxlength="400"></label><label>What should the agent do?<textarea name="goal" rows="5" maxlength="2000" required>${e(value("goal", d.goal))}</textarea></label><label>What should it leave alone?<textarea name="not" rows="3" maxlength="2000">${e(value("not", d.outOfScope ?? ""))}</textarea></label><label>Allowed files or folders <span class="meta">optional; one per line</span><textarea name="touches" rows="2" placeholder="src/&#10;tests/">${e(value("touches", d.touches.join("\n")))}</textarea></label><p class="meta">Blank allows work across this project. Add paths to keep the workflow focused.</p></section>` +
80
+ recipeQuestionsHtml(d, values) +
81
+ `<section class="card"><h2>What does success look like?</h2><p class="hint">Give each outcome the evidence you want to see. The workflow checks these before calling the work done.</p><div data-recipe-criteria>${Array.from({ length: count }, (_, i) => criterionHtml(i, d.acceptance[i], values)).join("")}</div><button type="button" data-add-criterion hidden>Add another success check</button><template data-criterion-template>${criterionHtml(11).replaceAll("criterion-11-", "criterion-INDEX-").replaceAll('value="c12"', 'value="cNEXT"')}</template></section>` +
82
+ `<section class="card"><h2>How it should run</h2><div class="recipe-options"><label>Result<select name="deliverable"><option value="branch"${deliverable === "branch" ? " selected" : ""}>Code change with evidence</option><option value="report"${deliverable === "report" ? " selected" : ""}>Report without code changes</option></select></label><label>Planning<select name="planning">${[["auto", "Plan when helpful"], ["required", "Always plan first"], ["skip", "Use this scope directly"]].map(([id, label]) => `<option value="${id}"${planning === id ? " selected" : ""}>${label}</option>`).join("")}</select></label></div><p class="meta">Report work investigates directly. Repeating work reuses its approved scope; plan-first workflows run once.</p><label>Frequency<select name="cadence" data-recipe-cadence><option value="once"${cadence === "once" ? " selected" : ""}>Run once</option><option value="repeat"${cadence === "repeat" ? " selected" : ""}>Repeat on a schedule</option></select></label><div data-recipe-recurring>${scheduleEditorHtml(d.schedule, values)}<label>Weekly spending limit <span class="meta">optional, USD</span><input type="number" name="ceiling" min="0.01" step="0.01" value="${e(value("ceiling", d.costCeilingUsd === null ? "" : String(d.costCeilingUsd)))}"></label><p class="meta">One instance at a time. A spending limit requires an agent that reports dollar costs; approval checks this.</p></div></section>` +
83
+ `<div class="sticky-actions"><button type="submit" name="purpose" value="${creator ? "recipe" : "workflow"}">${creator ? "Preview recipe →" : "Preview workflow →"}</button>${creator ? "" : `<button type="submit" name="purpose" value="recipe" class="quiet">Preview recipe to save</button>`}<p class="meta">${creator ? "Save the instructions once. You can use the recipe whenever this work comes up." : "Previewing runs no agents. You’ll see the approval and scheduling steps next."}</p></div></form>`;
84
+ }
85
+ export function workflowPreviewHtml(preview, csrf, revision, now, readiness, auto) {
86
+ const d = preview.document;
87
+ const common = hidden("csrf", csrf) + hidden("repo", preview.repo) + hidden("projectRevision", String(revision)) + hidden("preview", preview.token);
88
+ const target = preview.taskId === null ? preview.routineId === null ? null : `/routines/${preview.routineId}` : `/t/${encodeURIComponent(preview.taskId)}`;
89
+ return `<a href="/recipes/edit?preview=${preview.token}" class="meta">← Edit this workflow</a><div class="recipe-heading"><p class="recipe-kicker">3 · Preview</p><h1>${e(d.name)}</h1><p class="hint">${e(d.description)}</p></div>` +
90
+ `<div class="recipe-preview-grid"><div><section class="card"><h2>What will happen</h2><ol class="recipe-steps">${workflowSteps(d).map(step => `<li><strong>${e(step.title)}</strong><p>${e(step.detail)}</p></li>`).join("")}</ol></section><section class="card"><h2>The agreed work</h2><p class="recap">${e(d.goal)}</p><h3>Leave alone</h3><p class="recap">${e(d.outOfScope ?? "No additional exclusions.")}</p><h3>Allowed paths</h3><p>${d.touches.length ? d.touches.map(e).join(", ") : "This project"}</p><h3>Success checks</h3><ul>${d.acceptance.map(c => `<li><strong>${e(c.statement)}</strong><p class="meta">${c.evidence.map(kind => e(evidence.find(([id]) => id === kind)?.[1] ?? kind)).join(" · ")}${c.how ? ` · ${e(c.how)}` : ""}</p></li>`).join("")}</ul></section></div>` +
91
+ `<aside><section class="card"><h2>Before it starts</h2>${readiness.map(one => `<p><strong>${e(one.title)}</strong><br><span class="meta">${e(one.detail)}${one.href ? ` <a href="${e(one.href)}">Open settings →</a>` : ""}</span></p>`).join("")}<p class="meta">These checks describe current setup. Agent availability and approval are checked again when work starts.</p></section><section class="card"><h2>Schedule & control</h2><p>${e(recipeScheduleWords(d, now))}</p><p>${d.costCeilingUsd === null ? "Uses existing project and agent budget controls." : `Up to $${d.costCeilingUsd.toFixed(2)} per rolling seven days.`}</p><p>${d.schedule !== null ? "Approve the routine once on the next screen. Pause it any time from scheduled work." : auto ? "Your signed project policy will be evaluated when you create this workflow." : "Review and approve on the task page. You can stop or resume there."}</p></section></aside></div>` +
92
+ `<div class="card recipe-launch"><div>${target ? `<a class="new-task" href="${target}">Open created workflow →</a>` : `<form method="post" action="/recipes/launch">${common}<button type="submit">${d.schedule !== null ? "Create scheduled workflow" : auto ? "Create workflow" : "Create task for approval"}</button></form>`}<p class="meta">${d.schedule !== null ? "Nothing repeats until the routine is approved." : "A matching signed policy may start work automatically."} Creating this workflow twice returns the same work.</p></div><div>${preview.savedId ? `<a href="/recipes/start?recipe=${preview.savedId}&revision=${preview.savedRevision}">Recipe saved · reuse it →</a>` : `<form method="post" action="/recipes/save">${common}<button type="submit" class="quiet">Save as a project recipe</button></form>`}<a href="/recipes/export?preview=${preview.token}">Export recipe JSON</a><p class="meta">Save or export without running anything. Sharing includes the work definition, never project access or approvals.</p></div></div>`;
93
+ }
94
+ function inputHtml(index, input, values) {
95
+ const value = (field, fallback) => values?.get(`input-${index}-${field}`) ?? fallback;
96
+ return `<fieldset class="recipe-question"><legend>Question ${index + 1}</legend><div class="recipe-options"><label>Short key<input type="text" name="input-${index}-key" value="${e(value("key", input?.key ?? ""))}" maxlength="32" placeholder="module" pattern="[a-z][a-z0-9_]{0,31}"></label><label>What should we ask?<input type="text" name="input-${index}-label" value="${e(value("label", input?.label ?? ""))}" maxlength="80" placeholder="Which module should we work on?"></label></div><label>Default answer <span class="meta">optional</span><input type="text" name="input-${index}-default" value="${e(value("default", input?.defaultValue ?? ""))}" maxlength="500"></label><p class="meta">Use <code data-question-token>{{${e(value("key", input?.key ?? "module")) || "module"}}}</code> in your instructions or a success check. Clear this row and its placeholders to remove the question.</p><button type="button" data-insert-question hidden>Insert into instructions</button></fieldset>`;
97
+ }
98
+ function recipeQuestionsHtml(document, values) {
99
+ const inputs = document.version === 2 ? document.inputs : [];
100
+ const submitted = values ? [...values.keys()].filter(key => /^input-[0-7]-/.test(key)).map(key => Number(key.split("-")[1]) + 1) : [];
101
+ const count = Math.min(RECIPE_INPUT_LIMIT, Math.max(1, inputs.length, ...submitted));
102
+ return `<details class="card"${inputs.length || submitted.some(i => values?.get(`input-${i - 1}-key`)) ? " open" : ""}><summary>Ask something each time <span class="meta">optional</span></summary><p class="hint">For example: “Test {{module}} and cover {{scenario}}.” Add a question for each placeholder. People answer these when they use the recipe; the saved instructions stay the same.</p><p class="meta">Use answers in the name, instructions, exclusions, or success checks. Keep allowed paths and check commands fixed. Don’t ask for passwords or keys.</p><div data-recipe-questions>${Array.from({ length: count }, (_, i) => inputHtml(i, inputs[i], values)).join("")}</div><button type="button" data-add-question hidden>Add a question</button><template data-question-template>${inputHtml(7).replaceAll("input-7-", "input-INDEX-").replace("Question 8", "Question NEXT")}</template></details>`;
103
+ }
104
+ export function recipeDefinitionPreviewHtml(preview, csrf, revision) {
105
+ const d = preview.document;
106
+ const inputs = d.version === 2 ? d.inputs : [];
107
+ return `<a class="meta" href="/recipes/edit?preview=${preview.token}&purpose=recipe">← Edit recipe</a><div class="recipe-heading"><p class="recipe-kicker">2 · Preview your recipe</p><h1>${e(d.name)}</h1><p class="hint">${e(d.description || "A reusable starting point for work you do often.")}</p></div><section class="card"><h2>When someone uses this recipe</h2>${inputs.length ? `<ol>${inputs.map(input => `<li><strong>${e(input.label)}</strong><p class="meta">{{${e(input.key)}}}${input.defaultValue ? ` · Default: ${e(input.defaultValue)}` : " · Answer required each time"}</p></li>`).join("")}</ol>` : `<p>No questions to answer. The saved scope is ready to preview and use.</p>`}<p>Each use previews the exact work before creating it. The project’s approval and recovery settings still apply.</p></section><section class="card"><h2>Saved instructions</h2><p class="recap">${e(d.goal)}</p><h3>Leave alone</h3><p class="recap">${e(d.outOfScope ?? "No additional exclusions.")}</p><h3>Allowed paths</h3><p>${d.touches.map(e).join(", ") || "This project"}</p><h3>Success checks</h3><ul>${d.acceptance.map(one => `<li>${e(one.statement)}<p class="meta">${one.evidence.map(kind => e(evidence.find(([id]) => id === kind)?.[1] ?? kind)).join(" · ")}${one.how ? ` · ${e(one.how)}` : ""}</p></li>`).join("")}</ul><p class="meta">${d.deliverable === "report" ? "Report" : "Code change"} · ${d.schedule === null ? "Run once" : e(d.schedule)} · ${d.planning === "required" ? "Plan first" : d.planning === "auto" ? "Plan when helpful" : "Use the approved scope directly"}${d.costCeilingUsd === null ? "" : ` · $${d.costCeilingUsd} weekly limit`}</p></section><section class="card recipe-launch"><div>${preview.savedId ? `<a class="new-task" href="/recipes/run?recipe=${preview.savedId}&revision=${preview.savedRevision}">Use recipe →</a>` : `<form method="post" action="/recipes/save">${hidden("csrf", csrf)}${hidden("repo", preview.repo)}${hidden("projectRevision", String(revision))}${hidden("preview", preview.token)}${hidden("next", "use")}<button type="submit">Save recipe</button></form>`}<p class="meta">Saving creates no work. Everyone with project access can reuse this copy.</p></div><a href="/recipes/export?preview=${preview.token}">Export recipe JSON</a></section>`;
108
+ }
109
+ export function recipeRunHtml(recipe, repo, csrf, revision, problem = null, values, saved = false) {
110
+ const d = recipe.document, inputs = d.version === 2 ? d.inputs : [];
111
+ return `<a class="meta" href="/recipes">← Your recipes</a><div class="recipe-heading"><p class="recipe-kicker">${saved ? "Recipe saved · ready when you are" : "Use a recipe"}</p><h1>${e(d.name)}</h1><p class="hint">${e(d.description || "Your saved instructions, ready to use again.")}</p></div>${problem ? `<p role="alert" class="card recipe-error">${e(problem)}</p>` : ""}<form class="recipe-run card" method="post" action="/recipes/prepare">${hidden("csrf", csrf)}${hidden("repo", repo)}${hidden("projectRevision", String(revision))}${hidden("recipe", recipe.id)}${hidden("recipeRevision", String(recipe.revision))}<h2>${inputs.length ? "What changes this time?" : "Ready to go"}</h2>${inputs.map(input => `<label>${e(input.label)}<textarea name="answer-${e(input.key)}" rows="2" maxlength="500" required>${e(values?.get(`answer-${input.key}`) ?? input.defaultValue ?? "")}</textarea></label>`).join("")}${inputs.length ? "" : `<p>No setup to repeat. Preview the saved scope and create the work.</p>`}<p class="meta">${d.schedule === null ? "Creates one task each time you use this recipe." : "Creates a new schedule with these answers fixed for every firing. Manage an existing schedule from Routines."}</p><button type="submit">Preview this run →</button><p class="meta">Previewing starts no agents. Existing project approvals apply when you create work.</p></form><details class="card"><summary>Saved instructions and boundaries</summary><p class="recap">${e(d.goal)}</p><p class="recap">Leave alone: ${e(d.outOfScope ?? "No additional exclusions.")}</p><p>Allowed paths: ${d.touches.map(e).join(", ") || "This project"}</p><ul>${d.acceptance.map(one => `<li>${e(one.statement)}</li>`).join("")}</ul><a href="/recipes/start?recipe=${e(recipe.id)}&revision=${recipe.revision}">Edit a copy of this recipe →</a></details>`;
112
+ }
113
+ export function recipeAnswersFromForm(body) {
114
+ const answers = new Map();
115
+ for (const key of body.keys())
116
+ if (key.startsWith("answer-")) {
117
+ if (body.getAll(key).length !== 1)
118
+ throw new RecipeError("A question has more than one answer.");
119
+ answers.set(key.slice(7), body.get(key));
120
+ }
121
+ return answers;
122
+ }
123
+ export function recipeScript() {
124
+ return scheduleEditorScript() + `(function(){
125
+ document.querySelectorAll('[data-recipe-cadence]').forEach(function(choice){var root=choice.form.querySelector('[data-recipe-recurring]');function update(){root.hidden=choice.value!=='repeat';root.querySelector('[name=ceiling]').disabled=choice.value!=='repeat';}choice.addEventListener('change',update);update();});
126
+ var button=document.querySelector('[data-add-criterion]');if(button){button.hidden=false;button.addEventListener('click',function(){var root=document.querySelector('[data-recipe-criteria]'),index=root.children.length;if(index>=12)return;var html=document.querySelector('[data-criterion-template]').innerHTML.replaceAll('criterion-INDEX-','criterion-'+index+'-').replaceAll('value="cNEXT"','value="c'+(index+1)+'"');root.insertAdjacentHTML('beforeend',html);root.lastElementChild.querySelector('textarea').focus();button.hidden=index>=11;});}
127
+ var addQuestion=document.querySelector('[data-add-question]');if(addQuestion){var questions=document.querySelector('[data-recipe-questions]');function enhanceQuestion(row){var insert=row.querySelector('[data-insert-question]');insert.hidden=false;insert.addEventListener('click',function(){var key=row.querySelector('input[name$="-key"]');if(!/^[a-z][a-z0-9_]{0,31}$/.test(key.value)){key.focus();return;}var goal=addQuestion.form.querySelector('[name=goal]');goal.setRangeText(' {{'+key.value+'}} ',goal.selectionStart,goal.selectionEnd,'end');goal.dispatchEvent(new Event('input',{bubbles:true}));goal.focus();});}questions.querySelectorAll('fieldset').forEach(enhanceQuestion);addQuestion.hidden=questions.children.length>=8;addQuestion.addEventListener('click',function(){var index=questions.children.length;if(index>=8)return;questions.insertAdjacentHTML('beforeend',document.querySelector('[data-question-template]').innerHTML.replaceAll('input-INDEX-','input-'+index+'-').replace('Question NEXT','Question '+(index+1)));enhanceQuestion(questions.lastElementChild);questions.lastElementChild.querySelector('input').focus();addQuestion.hidden=index>=7;});questions.addEventListener('input',function(event){if(event.target.name.endsWith('-key'))event.target.closest('fieldset').querySelector('[data-question-token]').textContent='{{'+(event.target.value||'module')+'}}';});}
128
+ var search=document.querySelector('[data-recipe-search-input]');if(search)search.addEventListener('input',function(){var query=search.value.trim().toLowerCase(),shown=0;document.querySelectorAll('[data-saved-recipes] [data-recipe-card]').forEach(function(card){var visible=card.dataset.recipeSearch.toLowerCase().includes(query);card.hidden=!visible;if(visible)shown++;});document.querySelector('[data-recipe-empty]').hidden=shown>0;});
129
+ })();`;
130
+ }
131
+ export const RECIPE_CSS = `
132
+ .recipe-heading { margin: .5rem 0 1.75rem; max-width: 46rem; }
133
+ .recipe-heading h1 { font-size: clamp(1.65rem,3vw,2.5rem); letter-spacing:-.035em; margin:.35rem 0 .6rem; }
134
+ .recipe-kicker { font-size:.7rem; text-transform:uppercase; letter-spacing:.12em; color:var(--muted-foreground); }
135
+ .recipe-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:.8rem; margin:1rem 0 2rem; }
136
+ .recipe-card { border:1px solid var(--border); border-radius:var(--radius); padding:1.1rem; background:var(--card); display:flex; flex-direction:column; gap:.5rem; }
137
+ .recipe-card h3 { font-size:1.05rem; margin:0; letter-spacing:-.015em; }
138
+ .recipe-card p { color:var(--muted-foreground); margin:.2rem 0; font-size:.85rem; line-height:1.5; }
139
+ .recipe-card-link { margin-top:auto; padding-top:.75rem; display:flex; justify-content:space-between; }
140
+ .recipe-editor { max-width:52rem; }.recipe-editor h2 { margin-top:0; }
141
+ .recipe-criterion { border:1px solid var(--border); border-radius:calc(var(--radius) - 3px); padding:.8rem; margin:.8rem 0; }
142
+ .recipe-evidence { display:flex; flex-wrap:wrap; gap:.5rem 1rem; }.recipe-evidence label { display:flex; align-items:center; gap:.4rem; font-size:.8rem; }.recipe-evidence input { width:auto; margin:0; }
143
+ .recipe-options { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }
144
+ .recipe-preview-grid { display:grid; grid-template-columns:minmax(0,1.7fr) minmax(16rem,1fr); gap:1rem; }
145
+ .recipe-steps { padding-left:1.4rem; }.recipe-steps li { padding:.4rem 0 .6rem .3rem; }.recipe-steps p { margin:.3rem 0 0; font-size:.85rem; color:var(--muted-foreground); }
146
+ .recipe-launch { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }.recipe-launch form { margin:0 0 .6rem; }
147
+ .recipe-history a { display:flex; justify-content:space-between; gap:1rem; padding:.6rem 0; border-bottom:1px solid var(--border); }.recipe-history span { color:var(--muted-foreground); }
148
+ .recipe-error { border-color:var(--destructive); }
149
+ .recipe-card[hidden] { display:none; }.recipe-search { max-width:28rem; }.recipe-question { border:1px solid var(--border); border-radius:calc(var(--radius) - 3px); margin:1rem 0; padding:.8rem; min-width:0; }.recipe-run { max-width:42rem; }.recipe-run button { margin-top:1rem; }
150
+ @media(max-width:1000px){.recipe-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }}
151
+ @media(max-width:760px){.recipe-grid,.recipe-options,.recipe-preview-grid,.recipe-launch { grid-template-columns:1fr; }.recipe-heading { margin-bottom:1rem; }.recipe-history a { flex-direction:column; gap:.2rem; }}
152
+ `;