cloving 1.3.0 → 1.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 (484) hide show
  1. package/README.md +329 -215
  2. package/dist/src/agent-harness-shell.d.ts +2 -1
  3. package/dist/src/agent-harness-shell.js +8 -5
  4. package/dist/src/authority/command-wait-checks.d.ts +8 -0
  5. package/dist/src/authority/command-wait-checks.js +88 -0
  6. package/dist/src/authority/command-wait-results.d.ts +15 -0
  7. package/dist/src/authority/command-wait-results.js +73 -0
  8. package/dist/src/authority/command-wait-view.d.ts +21 -0
  9. package/dist/src/authority/command-wait-view.js +34 -0
  10. package/dist/src/authority/command-waits.d.ts +39 -0
  11. package/dist/src/authority/command-waits.js +97 -0
  12. package/dist/src/authority/commands.js +22 -1
  13. package/dist/src/authority/dispatch-decision.d.ts +8 -0
  14. package/dist/src/authority/dispatch-decision.js +26 -1
  15. package/dist/src/authority/effect-requests.js +6 -3
  16. package/dist/src/authority/effect-results.js +4 -2
  17. package/dist/src/authority/history-state.d.ts +2 -0
  18. package/dist/src/authority/history-state.js +13 -1
  19. package/dist/src/authority/instants.d.ts +1 -0
  20. package/dist/src/authority/instants.js +21 -0
  21. package/dist/src/authority/reduction-state.d.ts +2 -0
  22. package/dist/src/authority/reduction-state.js +5 -0
  23. package/dist/src/authority/reduction.js +24 -0
  24. package/dist/src/authority/status-input.d.ts +2 -0
  25. package/dist/src/authority/status.d.ts +2 -0
  26. package/dist/src/authority/status.js +1 -0
  27. package/dist/src/authority/stop-sequence.d.ts +1 -0
  28. package/dist/src/authority/stop-sequence.js +2 -1
  29. package/dist/src/authority/transitions.d.ts +6 -4
  30. package/dist/src/authority/transitions.js +7 -0
  31. package/dist/src/broker/actor/authority-health.d.ts +9 -0
  32. package/dist/src/broker/actor/authority-health.js +24 -0
  33. package/dist/src/broker/actor/authority-queries.d.ts +2 -1
  34. package/dist/src/broker/actor/authority-queries.js +14 -5
  35. package/dist/src/broker/actor/authority-receipts.js +1 -1
  36. package/dist/src/broker/actor/authority-runtime.d.ts +4 -2
  37. package/dist/src/broker/actor/authority-runtime.js +29 -20
  38. package/dist/src/broker/actor/authority-session.d.ts +6 -19
  39. package/dist/src/broker/actor/authority-session.js +66 -88
  40. package/dist/src/broker/actor/authority-settings.d.ts +2 -1
  41. package/dist/src/broker/actor/authority-settings.js +6 -3
  42. package/dist/src/broker/actor/authority-upgrades.d.ts +23 -2
  43. package/dist/src/broker/actor/authority-upgrades.js +8 -2
  44. package/dist/src/broker/actor/authority-watchdog.d.ts +6 -2
  45. package/dist/src/broker/actor/authority-watchdog.js +31 -9
  46. package/dist/src/broker/actor/authority.js +3 -1
  47. package/dist/src/broker/actor/delivery-intents.js +2 -0
  48. package/dist/src/broker/actor/effect-delivery.d.ts +1 -1
  49. package/dist/src/broker/actor/effect-delivery.js +15 -2
  50. package/dist/src/broker/actor/effect-plan.js +11 -2
  51. package/dist/src/broker/actor/effect-queries.d.ts +4 -0
  52. package/dist/src/broker/actor/effect-queries.js +9 -1
  53. package/dist/src/broker/actor/effect-turn.js +2 -0
  54. package/dist/src/broker/actor/projection-memory.d.ts +43 -0
  55. package/dist/src/broker/actor/projection-memory.js +207 -0
  56. package/dist/src/broker/actor/runtime-parts.d.ts +5 -0
  57. package/dist/src/broker/actor/runtime-parts.js +8 -3
  58. package/dist/src/broker/actor/stop-plan.js +9 -3
  59. package/dist/src/broker/actor/stop-turn.d.ts +2 -0
  60. package/dist/src/broker/actor/stop-turn.js +24 -11
  61. package/dist/src/broker/actor/turn-draft.d.ts +1 -0
  62. package/dist/src/broker/actor/turn-draft.js +4 -0
  63. package/dist/src/broker/actor/turn-state.js +5 -1
  64. package/dist/src/broker/authority-admission.js +60 -0
  65. package/dist/src/broker/authority-restore.d.ts +2 -0
  66. package/dist/src/broker/authority-restore.js +35 -1
  67. package/dist/src/broker/command-wait-restore.d.ts +31 -0
  68. package/dist/src/broker/command-wait-restore.js +86 -0
  69. package/dist/src/broker/decision-restore.d.ts +1 -1
  70. package/dist/src/broker/decision-restore.js +1 -0
  71. package/dist/src/broker/effect-upgrade.d.ts +3 -0
  72. package/dist/src/broker/effect-upgrade.js +11 -2
  73. package/dist/src/cli/arguments.d.ts +13 -0
  74. package/dist/src/cli/arguments.js +43 -3
  75. package/dist/src/cli/chat-stores.d.ts +2 -0
  76. package/dist/src/cli/chat-stores.js +113 -0
  77. package/dist/src/cli/client.d.ts +7 -2
  78. package/dist/src/cli/client.js +13 -7
  79. package/dist/src/cli/command-requests.d.ts +46 -0
  80. package/dist/src/cli/command-requests.js +84 -0
  81. package/dist/src/cli/command-waits.d.ts +9 -0
  82. package/dist/src/cli/command-waits.js +51 -0
  83. package/dist/src/cli/commands.js +70 -88
  84. package/dist/src/cli/config-file.d.ts +2 -2
  85. package/dist/src/cli/config-file.js +4 -2
  86. package/dist/src/cli/config-notice.js +5 -2
  87. package/dist/src/cli/doctor-retention.d.ts +2 -0
  88. package/dist/src/cli/doctor-retention.js +93 -0
  89. package/dist/src/cli/doctor-workflows.d.ts +5 -2
  90. package/dist/src/cli/doctor-workflows.js +12 -7
  91. package/dist/src/cli/doctor.js +11 -9
  92. package/dist/src/cli/harness-probes.d.ts +2 -0
  93. package/dist/src/cli/harness-probes.js +1 -1
  94. package/dist/src/cli/harness-processes.d.ts +6 -0
  95. package/dist/src/cli/harness-processes.js +36 -0
  96. package/dist/src/cli/init-command.d.ts +9 -0
  97. package/dist/src/cli/init-command.js +52 -0
  98. package/dist/src/cli/init-prompt-command.d.ts +10 -0
  99. package/dist/src/cli/init-prompt-command.js +145 -0
  100. package/dist/src/cli/init-prompt.d.ts +35 -0
  101. package/dist/src/cli/init-prompt.js +74 -0
  102. package/dist/src/cli/init-selection.d.ts +13 -0
  103. package/dist/src/cli/init-selection.js +75 -0
  104. package/dist/src/cli/main.d.ts +2 -0
  105. package/dist/src/cli/main.js +37 -7
  106. package/dist/src/cli/mcp-server.d.ts +1 -0
  107. package/dist/src/cli/mcp-server.js +156 -0
  108. package/dist/src/cli/mcp-tools.d.ts +15 -0
  109. package/dist/src/cli/mcp-tools.js +242 -0
  110. package/dist/src/cli/queue-arguments.d.ts +15 -0
  111. package/dist/src/cli/queue-arguments.js +31 -0
  112. package/dist/src/cli/queue-render.d.ts +3 -0
  113. package/dist/src/cli/queue-render.js +26 -0
  114. package/dist/src/cli/reload-render.d.ts +2 -0
  115. package/dist/src/cli/reload-render.js +12 -0
  116. package/dist/src/cli/render.d.ts +1 -1
  117. package/dist/src/cli/render.js +54 -38
  118. package/dist/src/cli/report-ci-jobs.d.ts +23 -0
  119. package/dist/src/cli/report-ci-jobs.js +103 -0
  120. package/dist/src/cli/report-ci-usage.d.ts +9 -0
  121. package/dist/src/cli/report-ci-usage.js +185 -0
  122. package/dist/src/cli/report-ci.d.ts +13 -0
  123. package/dist/src/cli/report-ci.js +74 -0
  124. package/dist/src/cli/report-closures.d.ts +6 -0
  125. package/dist/src/cli/report-closures.js +97 -0
  126. package/dist/src/cli/report-flow.d.ts +7 -0
  127. package/dist/src/cli/report-flow.js +122 -0
  128. package/dist/src/cli/report-github-reader.d.ts +29 -0
  129. package/dist/src/cli/report-github-reader.js +157 -0
  130. package/dist/src/cli/report-github.d.ts +15 -8
  131. package/dist/src/cli/report-github.js +131 -150
  132. package/dist/src/cli/report-operator.d.ts +3 -0
  133. package/dist/src/cli/report-operator.js +40 -0
  134. package/dist/src/cli/report-ready.d.ts +3 -0
  135. package/dist/src/cli/report-ready.js +96 -0
  136. package/dist/src/cli/report-text.d.ts +9 -0
  137. package/dist/src/cli/report-text.js +33 -0
  138. package/dist/src/cli/report.d.ts +1 -0
  139. package/dist/src/cli/report.js +104 -27
  140. package/dist/src/cli/runtime-arguments.d.ts +18 -3
  141. package/dist/src/cli/runtime-arguments.js +29 -20
  142. package/dist/src/cli/schedule-arguments.d.ts +14 -0
  143. package/dist/src/cli/schedule-arguments.js +26 -0
  144. package/dist/src/cli/schedule-render.d.ts +22 -0
  145. package/dist/src/cli/schedule-render.js +73 -0
  146. package/dist/src/cli/terminal-text.d.ts +3 -0
  147. package/dist/src/cli/terminal-text.js +28 -0
  148. package/dist/src/cli/toolchain.d.ts +1 -0
  149. package/dist/src/cli/toolchain.js +1 -0
  150. package/dist/src/cli/usage-render.d.ts +1 -0
  151. package/dist/src/cli/usage-render.js +28 -1
  152. package/dist/src/cli/usage.d.ts +2 -2
  153. package/dist/src/cli/usage.js +89 -11
  154. package/dist/src/cli/watch-actions.js +2 -2
  155. package/dist/src/cli/watch-api-document.d.ts +430 -0
  156. package/dist/src/cli/watch-api-document.js +291 -0
  157. package/dist/src/cli/watch-api-run.d.ts +56 -0
  158. package/dist/src/cli/watch-api-run.js +112 -0
  159. package/dist/src/cli/watch-api.d.ts +17 -0
  160. package/dist/src/cli/watch-api.js +230 -0
  161. package/dist/src/cli/watch-board-view.js +19 -2
  162. package/dist/src/cli/watch-board.d.ts +4 -2
  163. package/dist/src/cli/watch-board.js +53 -43
  164. package/dist/src/cli/watch-card-forms.d.ts +1 -1
  165. package/dist/src/cli/watch-card-forms.js +11 -10
  166. package/dist/src/cli/watch-client.d.ts +1 -1
  167. package/dist/src/cli/watch-client.js +2 -11
  168. package/dist/src/cli/watch-header-view.d.ts +1 -5
  169. package/dist/src/cli/watch-header-view.js +17 -21
  170. package/dist/src/cli/watch-model.d.ts +5 -1
  171. package/dist/src/cli/watch-model.js +20 -11
  172. package/dist/src/cli/watch-page.js +15 -15
  173. package/dist/src/cli/watch-phases.d.ts +12 -3
  174. package/dist/src/cli/watch-phases.js +68 -11
  175. package/dist/src/cli/watch-pr-board.js +1 -1
  176. package/dist/src/cli/watch-pr-command.d.ts +1 -0
  177. package/dist/src/cli/watch-pull-request.js +18 -8
  178. package/dist/src/cli/watch-reads.d.ts +3 -0
  179. package/dist/src/cli/watch-reads.js +22 -20
  180. package/dist/src/cli/watch-render.d.ts +15 -0
  181. package/dist/src/cli/watch-run-input.d.ts +0 -1
  182. package/dist/src/cli/watch-run-input.js +0 -13
  183. package/dist/src/cli/watch-server.js +24 -10
  184. package/dist/src/cli/watch-view.d.ts +0 -3
  185. package/dist/src/cli/watch-view.js +13 -13
  186. package/dist/src/cli.js +73 -12
  187. package/dist/src/command-wait.d.ts +4 -0
  188. package/dist/src/command-wait.js +45 -0
  189. package/dist/src/configuration-change.d.ts +7 -0
  190. package/dist/src/configuration-change.js +14 -0
  191. package/dist/src/configuration-display.js +2 -0
  192. package/dist/src/configuration-schema.d.ts +14 -0
  193. package/dist/src/configuration-schema.js +6 -2
  194. package/dist/src/daemon/attachment-file.d.ts +6 -0
  195. package/dist/src/daemon/attachment-file.js +86 -0
  196. package/dist/src/daemon/attachments.d.ts +14 -0
  197. package/dist/src/daemon/attachments.js +17 -0
  198. package/dist/src/daemon/claude-recovery.d.ts +50 -0
  199. package/dist/src/daemon/claude-recovery.js +208 -0
  200. package/dist/src/daemon/claude-stream.d.ts +27 -0
  201. package/dist/src/daemon/claude-stream.js +63 -0
  202. package/dist/src/daemon/claude-turn.d.ts +11 -25
  203. package/dist/src/daemon/claude-turn.js +64 -81
  204. package/dist/src/daemon/claude-worker.d.ts +5 -4
  205. package/dist/src/daemon/claude-worker.js +127 -126
  206. package/dist/src/daemon/daemon-adapters.d.ts +9 -0
  207. package/dist/src/daemon/daemon-adapters.js +33 -5
  208. package/dist/src/daemon/daemon-loops.d.ts +5 -1
  209. package/dist/src/daemon/daemon-loops.js +18 -10
  210. package/dist/src/daemon/daemon.js +80 -61
  211. package/dist/src/daemon/github-attach.d.ts +2 -0
  212. package/dist/src/daemon/github-attach.js +222 -0
  213. package/dist/src/daemon/github-calls.js +1 -1
  214. package/dist/src/daemon/github-ci-filters.d.ts +6 -0
  215. package/dist/src/daemon/github-ci-filters.js +138 -0
  216. package/dist/src/daemon/github-ci-workflows.d.ts +10 -0
  217. package/dist/src/daemon/github-ci-workflows.js +148 -0
  218. package/dist/src/daemon/github-ci.js +22 -4
  219. package/dist/src/daemon/github-issue-methods.d.ts +2 -0
  220. package/dist/src/daemon/github-issue-methods.js +37 -2
  221. package/dist/src/daemon/github-merge-call.d.ts +2 -1
  222. package/dist/src/daemon/github-merge-call.js +10 -3
  223. package/dist/src/daemon/github-merge.js +52 -24
  224. package/dist/src/daemon/github-methods.js +18 -4
  225. package/dist/src/daemon/github-operation.d.ts +4 -0
  226. package/dist/src/daemon/github-operation.js +6 -0
  227. package/dist/src/daemon/github-worker.d.ts +4 -0
  228. package/dist/src/daemon/github-worker.js +6 -0
  229. package/dist/src/daemon/issue-queue.d.ts +4 -1
  230. package/dist/src/daemon/issue-queue.js +76 -78
  231. package/dist/src/daemon/merge-turns.d.ts +10 -0
  232. package/dist/src/daemon/merge-turns.js +37 -0
  233. package/dist/src/daemon/notify-events.d.ts +6 -0
  234. package/dist/src/daemon/notify-events.js +84 -0
  235. package/dist/src/daemon/notify-journal.js +1 -1
  236. package/dist/src/daemon/notify-message.d.ts +4 -1
  237. package/dist/src/daemon/notify-message.js +36 -0
  238. package/dist/src/daemon/notify.d.ts +1 -1
  239. package/dist/src/daemon/notify.js +5 -77
  240. package/dist/src/daemon/operation-journal.d.ts +1 -0
  241. package/dist/src/daemon/queue-control.d.ts +13 -0
  242. package/dist/src/daemon/queue-control.js +86 -0
  243. package/dist/src/daemon/queue-journal.d.ts +20 -0
  244. package/dist/src/daemon/queue-journal.js +89 -0
  245. package/dist/src/daemon/queue-status.d.ts +9 -3
  246. package/dist/src/daemon/queue-status.js +17 -4
  247. package/dist/src/daemon/record-archive.d.ts +19 -0
  248. package/dist/src/daemon/record-archive.js +82 -0
  249. package/dist/src/daemon/record-sweep.d.ts +24 -0
  250. package/dist/src/daemon/record-sweep.js +147 -0
  251. package/dist/src/daemon/resume-index-lock.d.ts +13 -0
  252. package/dist/src/daemon/resume-index-lock.js +160 -0
  253. package/dist/src/daemon/resume-worktree.d.ts +7 -0
  254. package/dist/src/daemon/resume-worktree.js +29 -0
  255. package/dist/src/daemon/retrying-turn.d.ts +2 -12
  256. package/dist/src/daemon/retrying-turn.js +98 -172
  257. package/dist/src/daemon/schedule-control.d.ts +26 -0
  258. package/dist/src/daemon/schedule-control.js +110 -0
  259. package/dist/src/daemon/schedule-journal.d.ts +2 -15
  260. package/dist/src/daemon/schedule-journal.js +67 -4
  261. package/dist/src/daemon/settings-reload.d.ts +26 -0
  262. package/dist/src/daemon/settings-reload.js +82 -0
  263. package/dist/src/daemon/ticket-registration.d.ts +2 -7
  264. package/dist/src/daemon/ticket-registration.js +15 -81
  265. package/dist/src/daemon/turn-attempt-journal.d.ts +38 -0
  266. package/dist/src/daemon/turn-attempt-journal.js +136 -0
  267. package/dist/src/daemon/turn-attempts.d.ts +47 -0
  268. package/dist/src/daemon/turn-attempts.js +166 -0
  269. package/dist/src/daemon/turn-details.d.ts +4 -0
  270. package/dist/src/daemon/turn-details.js +33 -0
  271. package/dist/src/daemon/turn-report.d.ts +8 -1
  272. package/dist/src/daemon/turn-report.js +39 -0
  273. package/dist/src/daemon/work-tree.d.ts +9 -0
  274. package/dist/src/daemon/work-tree.js +20 -0
  275. package/dist/src/daemon/worker-profile.d.ts +9 -0
  276. package/dist/src/daemon/worker-profile.js +44 -0
  277. package/dist/src/daemon/workflow-generations.d.ts +7 -4
  278. package/dist/src/daemon/workflow-generations.js +22 -14
  279. package/dist/src/daemon/workflow-reload.d.ts +41 -0
  280. package/dist/src/daemon/workflow-reload.js +244 -0
  281. package/dist/src/daemon/workflow-replay.d.ts +2 -1
  282. package/dist/src/daemon/workflow-replay.js +7 -3
  283. package/dist/src/daemon/workflow-schedules.d.ts +3 -0
  284. package/dist/src/daemon/workflow-schedules.js +98 -45
  285. package/dist/src/daemon/worktree-processes.d.ts +9 -0
  286. package/dist/src/daemon/worktree-processes.js +151 -0
  287. package/dist/src/doctor-report.d.ts +4 -6
  288. package/dist/src/doctor-report.js +8 -13
  289. package/dist/src/harness-start.d.ts +15 -0
  290. package/dist/src/harness-start.js +37 -0
  291. package/dist/src/host/command-wait.d.ts +38 -0
  292. package/dist/src/host/command-wait.js +115 -0
  293. package/dist/src/host/start-delivery.d.ts +5 -7
  294. package/dist/src/host/start-delivery.js +3 -15
  295. package/dist/src/host/step-bridge-host.d.ts +2 -0
  296. package/dist/src/host/step-bridge-host.js +21 -0
  297. package/dist/src/operation-identity.d.ts +1 -0
  298. package/dist/src/operation-identity.js +4 -0
  299. package/dist/src/private-config.schema.json +71 -0
  300. package/dist/src/project-config.schema.json +40 -0
  301. package/dist/src/project-settings.d.ts +16 -2
  302. package/dist/src/project-settings.js +35 -1
  303. package/dist/src/queue-spend.d.ts +4 -0
  304. package/dist/src/queue-spend.js +21 -0
  305. package/dist/src/queue-state.d.ts +26 -0
  306. package/dist/src/queue-state.js +34 -0
  307. package/dist/src/record-retention.d.ts +46 -0
  308. package/dist/src/record-retention.js +81 -0
  309. package/dist/src/relay/relay-service.d.ts +3 -0
  310. package/dist/src/relay/relay-service.js +5 -3
  311. package/dist/src/report-ci.d.ts +164 -0
  312. package/dist/src/report-ci.js +32 -0
  313. package/dist/src/report-effects.d.ts +15 -0
  314. package/dist/src/report-effects.js +212 -0
  315. package/dist/src/report-flow-types.d.ts +38 -0
  316. package/dist/src/report-flow-types.js +7 -0
  317. package/dist/src/report-flow.d.ts +43 -0
  318. package/dist/src/report-flow.js +193 -0
  319. package/dist/src/report-operator.d.ts +67 -0
  320. package/dist/src/report-operator.js +25 -0
  321. package/dist/src/report-quality.d.ts +221 -0
  322. package/dist/src/report-quality.js +94 -0
  323. package/dist/src/report-ready.d.ts +23 -0
  324. package/dist/src/report-ready.js +117 -0
  325. package/dist/src/report-statistics.d.ts +2 -1
  326. package/dist/src/report-statistics.js +40 -4
  327. package/dist/src/report.d.ts +588 -47
  328. package/dist/src/report.js +156 -41
  329. package/dist/src/schedule-progress.d.ts +33 -0
  330. package/dist/src/schedule-progress.js +45 -0
  331. package/dist/src/service/active-tickets.js +11 -6
  332. package/dist/src/service/command-waits.d.ts +34 -0
  333. package/dist/src/service/command-waits.js +86 -0
  334. package/dist/src/service/dispatcher.d.ts +4 -0
  335. package/dist/src/service/dispatcher.js +33 -0
  336. package/dist/src/service/expiry.js +2 -0
  337. package/dist/src/service/gateway.js +3 -1
  338. package/dist/src/service/queue-handlers.d.ts +4 -0
  339. package/dist/src/service/queue-handlers.js +32 -0
  340. package/dist/src/service/queue-spend.d.ts +6 -0
  341. package/dist/src/service/queue-spend.js +68 -0
  342. package/dist/src/service/registry.d.ts +28 -2
  343. package/dist/src/service/registry.js +48 -0
  344. package/dist/src/service/replies.d.ts +22 -8
  345. package/dist/src/service/replies.js +16 -2
  346. package/dist/src/service/run-handlers.js +37 -12
  347. package/dist/src/service/run-metadata.d.ts +15 -1
  348. package/dist/src/service/run-metadata.js +93 -8
  349. package/dist/src/service/service.d.ts +11 -3
  350. package/dist/src/service/service.js +22 -37
  351. package/dist/src/service/submission-handler.js +18 -4
  352. package/dist/src/service/worker-port.js +9 -1
  353. package/dist/src/service/worker-totals.d.ts +6 -0
  354. package/dist/src/service/worker-totals.js +8 -1
  355. package/dist/src/service/workflow-reload.d.ts +35 -0
  356. package/dist/src/service/workflow-reload.js +52 -0
  357. package/dist/src/step-bridge.d.ts +20 -0
  358. package/dist/src/steps/command-wait.d.ts +12 -0
  359. package/dist/src/steps/command-wait.js +26 -0
  360. package/dist/src/steps/open-decision-wait.d.ts +2 -0
  361. package/dist/src/steps/{decision-gate.js → open-decision-wait.js} +0 -7
  362. package/dist/src/steps/review-by-person.js +4 -1
  363. package/dist/src/steps/wait-for-command.d.ts +3 -0
  364. package/dist/src/steps/wait-for-command.js +17 -0
  365. package/dist/src/steps/wait-for-decision.js +1 -1
  366. package/dist/src/storage/command-wait-rows.d.ts +30 -0
  367. package/dist/src/storage/command-wait-rows.js +96 -0
  368. package/dist/src/storage/decision-snapshots.d.ts +8 -2
  369. package/dist/src/storage/decision-snapshots.js +16 -8
  370. package/dist/src/storage/domain-changes.d.ts +3 -1
  371. package/dist/src/storage/domain-changes.js +4 -0
  372. package/dist/src/storage/domain-schema.d.ts +16 -1
  373. package/dist/src/storage/domain-schema.js +35 -4
  374. package/dist/src/storage/effect-schema.d.ts +2 -0
  375. package/dist/src/storage/effect-schema.js +9 -0
  376. package/dist/src/storage/projection-changes.d.ts +12 -0
  377. package/dist/src/storage/projection-changes.js +194 -0
  378. package/dist/src/storage/projection-lookups.d.ts +7 -0
  379. package/dist/src/storage/projection-lookups.js +30 -0
  380. package/dist/src/testing-requests.d.ts +15 -0
  381. package/dist/src/testing-requests.js +93 -0
  382. package/dist/src/testing.d.ts +4 -10
  383. package/dist/src/testing.js +33 -86
  384. package/dist/src/worker-usage.d.ts +1 -0
  385. package/dist/src/worker-usage.js +3 -0
  386. package/dist/src/workflow-candidate.d.ts +1 -0
  387. package/dist/src/workflow-candidate.js +12 -1
  388. package/dist/src/workflow-contracts.d.ts +1 -0
  389. package/dist/src/workflow-contracts.js +9 -2
  390. package/dist/src/workflow-declaration.d.ts +1 -0
  391. package/dist/src/workflow-declaration.js +3 -0
  392. package/dist/src/workflow-discovery.d.ts +2 -13
  393. package/dist/src/workflow-discovery.js +1 -13
  394. package/dist/src/workflow-refusals.js +0 -1
  395. package/dist/src/workflows.d.ts +1 -1
  396. package/dist/src/workflows.js +1 -1
  397. package/dist/tools/compile-workflows.js +3 -0
  398. package/dist/tools/project-configuration.d.ts +7 -2
  399. package/dist/tools/project-configuration.js +30 -1
  400. package/dist/tools/red-green-command.d.ts +1 -0
  401. package/dist/tools/red-green-command.js +174 -0
  402. package/dist/tools/red-green-process.d.ts +10 -0
  403. package/dist/tools/red-green-process.js +59 -0
  404. package/dist/tools/self-host-processes.d.ts +2 -0
  405. package/dist/tools/self-host-processes.js +69 -26
  406. package/dist/tools/self-host-project.d.ts +2 -0
  407. package/dist/tools/self-host-project.js +30 -77
  408. package/dist/tools/self-host-runtime.js +1 -1
  409. package/dist/tools/self-host.js +76 -34
  410. package/dist/tools/template-command.d.ts +1 -0
  411. package/dist/tools/template-command.js +160 -0
  412. package/dist/tools/template-copy.d.ts +11 -0
  413. package/dist/tools/template-copy.js +130 -0
  414. package/dist/tools/template-files.d.ts +17 -0
  415. package/dist/tools/template-files.js +74 -0
  416. package/dist/tools/workflow-bundles.js +1 -0
  417. package/dist/tools/workflow-candidate-counts.js +2 -2
  418. package/dist/tools/workflow-candidate-program.d.ts +4 -2
  419. package/dist/tools/workflow-candidate-program.js +30 -11
  420. package/dist/tools/workflow-candidate-shape.d.ts +3 -0
  421. package/dist/tools/workflow-candidate-shape.js +90 -0
  422. package/dist/tools/workflow-candidate-source.d.ts +1 -0
  423. package/dist/tools/workflow-candidate-source.js +26 -4
  424. package/dist/tools/workflow-candidate-syntax.d.ts +2 -2
  425. package/dist/tools/workflow-candidate-syntax.js +24 -94
  426. package/dist/tools/workflow-command.js +1 -2
  427. package/dist/tools/workflow-folders.d.ts +2 -3
  428. package/dist/tools/workflow-folders.js +1 -1
  429. package/package.json +17 -9
  430. package/templates/develop/README.md +93 -4
  431. package/templates/develop/develop/flow-tests.ts +38 -1
  432. package/templates/develop/develop/interruption-tests.ts +157 -0
  433. package/templates/develop/develop/local.ts +1 -0
  434. package/templates/develop/develop/option-tests.ts +138 -9
  435. package/templates/develop/develop/pull.ts +5 -1
  436. package/templates/develop/develop/red-green.ts +55 -0
  437. package/templates/develop/develop/refusal-tests.ts +77 -0
  438. package/templates/develop/develop/report.ts +31 -11
  439. package/templates/develop/develop/round.ts +1 -0
  440. package/templates/develop/develop/scenarios.ts +27 -0
  441. package/templates/develop/develop/turn.ts +28 -7
  442. package/templates/develop/develop.declaration.json +1 -1
  443. package/templates/develop/develop.test.ts +4 -0
  444. package/templates/develop/template.json +29 -2
  445. package/templates/idea-to-issue/README.md +27 -0
  446. package/templates/idea-to-issue/idea-to-issue/effects.ts +52 -0
  447. package/templates/idea-to-issue/idea-to-issue/leave.ts +10 -0
  448. package/templates/idea-to-issue/idea-to-issue/next.ts +29 -0
  449. package/templates/idea-to-issue/idea-to-issue/post-person.ts +18 -0
  450. package/templates/idea-to-issue/idea-to-issue/post.ts +50 -0
  451. package/templates/idea-to-issue/idea-to-issue/queue.ts +27 -0
  452. package/templates/idea-to-issue/idea-to-issue/review.ts +42 -0
  453. package/templates/idea-to-issue/idea-to-issue/scenarios.ts +50 -0
  454. package/templates/idea-to-issue/idea-to-issue/turn.ts +40 -0
  455. package/templates/idea-to-issue/idea-to-issue.declaration.json +16 -0
  456. package/templates/idea-to-issue/idea-to-issue.test.ts +79 -0
  457. package/templates/idea-to-issue/idea-to-issue.ts +49 -0
  458. package/templates/idea-to-issue/template.json +81 -0
  459. package/templates/review-day/README.md +97 -0
  460. package/templates/review-day/review-day/critique.ts +58 -0
  461. package/templates/review-day/review-day/decision.ts +29 -0
  462. package/templates/review-day/review-day/facts.ts +69 -0
  463. package/templates/review-day/review-day/label.ts +29 -0
  464. package/templates/review-day/review-day/next.ts +28 -0
  465. package/templates/review-day/review-day/post.ts +27 -0
  466. package/templates/review-day/review-day/report.ts +98 -0
  467. package/templates/review-day/review-day/rules.ts +12 -0
  468. package/templates/review-day/review-day/scenarios.ts +76 -0
  469. package/templates/review-day/review-day/turn.ts +73 -0
  470. package/templates/review-day/review-day.declaration.json +16 -0
  471. package/templates/review-day/review-day.test.ts +362 -0
  472. package/templates/review-day/review-day.ts +37 -0
  473. package/templates/review-day/template.json +56 -0
  474. package/dist/src/cli/watch-start-view.d.ts +0 -18
  475. package/dist/src/cli/watch-start-view.js +0 -77
  476. package/dist/src/steps/decision-gate.d.ts +0 -3
  477. package/dist/workflows/develop-prd.d.ts +0 -7
  478. package/dist/workflows/develop-prd.js +0 -165
  479. package/dist/workflows/develop-ticket.d.ts +0 -9
  480. package/dist/workflows/develop-ticket.js +0 -207
  481. package/dist/workflows/issue-labels.d.ts +0 -16
  482. package/dist/workflows/issue-labels.js +0 -70
  483. package/dist/workflows/ticket-prompts.d.ts +0 -35
  484. package/dist/workflows/ticket-prompts.js +0 -147
@@ -1,57 +1,14 @@
1
- import { activeQueueRuns } from "./queue-status.js";
1
+ import { queueStatus, spendNotice } from "./queue-status.js";
2
2
  import { randomUUID } from "node:crypto";
3
- import { readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { readFile, rm, writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
5
  import { setTimeout as delay } from "node:timers/promises";
6
6
  import { send } from "../cli/client.js";
7
7
  import { record } from "../protocol.js";
8
8
  import { runIdentity } from "../service/submission-handler.js";
9
- function savedHold(value) {
10
- if (value === undefined)
11
- return undefined;
12
- const held = record(value);
13
- if (typeof held?.["mark"] !== "string" ||
14
- typeof held["startedAt"] !== "number" ||
15
- !Number.isFinite(held["startedAt"]) ||
16
- held["startedAt"] <= 0)
17
- throw new Error("invalid-queue-commands");
18
- return { mark: held["mark"], startedAt: held["startedAt"] };
19
- }
20
- function identifiers(value) {
21
- if (!Array.isArray(value) ||
22
- !value.every((id) => typeof id === "string" && id.length > 0))
23
- throw new Error("invalid-queue-commands");
24
- return value.map(String);
25
- }
26
- async function journal(directory) {
27
- const file = join(directory, "queue-commands.json");
28
- const text = await readFile(file, "utf8").catch((error) => {
29
- if (error instanceof Error && "code" in error && error.code === "ENOENT")
30
- return "[]";
31
- throw error;
32
- });
33
- const value = JSON.parse(text);
34
- const saved = record(value);
35
- const commands = new Set(identifiers(Array.isArray(value) ? value : saved?.["commands"]));
36
- const failures = Array.isArray(value) ? [] : identifiers(saved?.["failures"]);
37
- if (failures.length > 3)
38
- throw new Error("invalid-queue-commands");
39
- const pending = {
40
- commands,
41
- failures,
42
- retryCommandId: undefined,
43
- selfUpdateHold: savedHold(saved?.["selfUpdateHold"]),
44
- save: async () => {
45
- await writeFile(`${file}.tmp`, JSON.stringify({
46
- commands: [...commands],
47
- failures,
48
- selfUpdateHold: pending.selfUpdateHold,
49
- }) + "\n", { mode: 0o600, flush: true });
50
- await rename(`${file}.tmp`, file);
51
- },
52
- };
53
- return pending;
54
- }
9
+ import { queueFailureLimit } from "../queue-state.js";
10
+ import { queueJournal } from "./queue-journal.js";
11
+ import { queueControlPort, queueStopped, } from "./queue-control.js";
55
12
  function terminal(run) {
56
13
  return ["completed", "failed", "cancelled"].includes(String(run["state"]));
57
14
  }
@@ -111,11 +68,18 @@ function handedOff(run) {
111
68
  outcome?.["kind"] === "handoff_requested" &&
112
69
  outcome["issueUpdate"] !== "failed");
113
70
  }
114
- class QueueFailureLimit extends Error {
71
+ function limited(pending) {
72
+ return pending.failures.length >= queueFailureLimit;
115
73
  }
116
- function checkFailureLimit(pending) {
117
- if (pending.failures.length >= 3)
118
- throw new QueueFailureLimit(`3 runs in a row ended without handoff: ${pending.failures.join(", ")}`);
74
+ function stopNotice() {
75
+ let noticed = false;
76
+ return (pending) => {
77
+ const stopped = limited(pending);
78
+ if (stopped && !noticed)
79
+ process.stderr.write(`Queue stopped: 3 runs in a row ended without handoff: ${pending.failures.join(", ")}\n`);
80
+ noticed = stopped;
81
+ return stopped;
82
+ };
119
83
  }
120
84
  async function retired(pending, ending) {
121
85
  const { commandId, run } = ending;
@@ -125,14 +89,12 @@ async function retired(pending, ending) {
125
89
  else
126
90
  pending.failures.push(runIdentity(commandId));
127
91
  await pending.save();
128
- checkFailureLimit(pending);
129
- if (handedOff(run))
92
+ if (handedOff(run) || limited(pending))
130
93
  return;
131
94
  const outcome = record(run["outcome"]);
132
95
  process.stderr.write(`Queue run ended ${String(outcome?.["kind"] ?? run["state"])}${outcome?.["issueUpdate"] === "failed" ? "; issueUpdate: failed" : ""}: ${runIdentity(commandId)}; continuing\n`);
133
96
  }
134
97
  async function completed(settings, pending) {
135
- checkFailureLimit(pending);
136
98
  let pendingRuns = 0;
137
99
  const endings = [];
138
100
  const tracked = [...pending.commands].filter((commandId) => commandId !== pending.retryCommandId);
@@ -145,7 +107,8 @@ async function completed(settings, pending) {
145
107
  endings.sort((first, second) => Number(first.run["terminalSequence"] ?? first.run["latestSequence"] ?? 0) -
146
108
  Number(second.run["terminalSequence"] ?? second.run["latestSequence"] ?? 0));
147
109
  for (const ending of endings)
148
- await retired(pending, ending);
110
+ if (!limited(pending))
111
+ await retired(pending, ending);
149
112
  return pendingRuns;
150
113
  }
151
114
  async function submit(settings, pending) {
@@ -155,7 +118,7 @@ async function submit(settings, pending) {
155
118
  const reply = await send(settings.runtimeDirectory, {
156
119
  operation: "run",
157
120
  parameters: {
158
- workflowId: settings.workflowId ?? "develop-issue",
121
+ workflowId: settings.workflowId,
159
122
  source: "ticket",
160
123
  submission: { ticket: "next" },
161
124
  commandId,
@@ -178,6 +141,7 @@ async function submit(settings, pending) {
178
141
  "workflow-active",
179
142
  "ticket-active",
180
143
  "ticket-dependency-open",
144
+ "queue-spend-limit",
181
145
  ].includes(reply.error.code))
182
146
  throw new Error(reply.error.code);
183
147
  pending.commands.delete(commandId);
@@ -229,20 +193,54 @@ async function admission(settings, attempt) {
229
193
  const file = join(settings.directory, "queue-submission");
230
194
  await writeFile(file, "", { mode: 0o600 });
231
195
  try {
232
- if (!(await attempt.holding()) && !attempt.signal.aborted)
196
+ if (!(await attempt.holding()) &&
197
+ !attempt.signal.aborted &&
198
+ attempt.pending.drain === undefined)
233
199
  await submit(settings, attempt.pending);
234
200
  }
235
201
  finally {
236
202
  await rm(file, { force: true });
237
203
  }
238
204
  }
239
- async function cycle(settings, signal) {
240
- const pending = await journal(settings.directory);
205
+ async function drained(pending, busy) {
206
+ const { drain } = pending;
207
+ if (drain === undefined || drain.drainedAt !== null || busy > 0)
208
+ return;
209
+ pending.drain = { ...drain, drainedAt: Date.now() };
210
+ await pending.save();
211
+ process.stderr.write("Queue drained\n");
212
+ }
213
+ async function admissible(settings, checks) {
214
+ const { pending, stop } = checks;
215
+ const pendingRuns = stop(pending) ? 0 : await completed(settings, pending);
216
+ if (stop(pending) && pending.drain === undefined)
217
+ return false;
218
+ const status = await queueStatus(settings);
219
+ await drained(pending, status.busy);
220
+ if (stop(pending) || pending.drain !== undefined)
221
+ return false;
222
+ const spend = checks.notice(status);
223
+ const updating = await checks.update(status.busy);
224
+ const lanes = settings.lanes ?? 1;
225
+ if (Math.max(pendingRuns, status.busy) >= lanes || status.busy < pendingRuns)
226
+ return false;
227
+ return !updating && !spend;
228
+ }
229
+ async function cycle(settings, run) {
230
+ const { signal, pending } = run;
241
231
  if (settings.resetQueue === true) {
242
232
  pending.failures.length = 0;
233
+ pending.drain = undefined;
243
234
  await pending.save();
244
235
  }
245
- const holding = updateHold(settings.directory, pending);
236
+ const update = updateHold(settings.directory, pending);
237
+ const notice = spendNotice();
238
+ const stop = stopNotice();
239
+ const holding = async (activeRuns = 0) => {
240
+ const updating = await update(activeRuns);
241
+ const spend = notice(await queueStatus(settings));
242
+ return updating || spend;
243
+ };
246
244
  await rm(join(settings.directory, "queue-submission"), { force: true });
247
245
  await recover(settings, pending);
248
246
  const stopped = () => signal.aborted;
@@ -250,17 +248,7 @@ async function cycle(settings, signal) {
250
248
  await delay(settings.intervalMilliseconds, undefined, { signal }).catch(() => undefined);
251
249
  if (stopped())
252
250
  return;
253
- const pendingRuns = await completed(settings, pending);
254
- const lanes = settings.lanes ?? 1;
255
- if (pendingRuns >= lanes) {
256
- await holding(pendingRuns);
257
- continue;
258
- }
259
- const activeRuns = await activeQueueRuns(settings);
260
- const held = await holding(activeRuns);
261
- if (activeRuns >= lanes || activeRuns < pendingRuns)
262
- continue;
263
- if (held)
251
+ if (!(await admissible(settings, { pending, stop, notice, update })))
264
252
  continue;
265
253
  if (stopped())
266
254
  return;
@@ -268,16 +256,26 @@ async function cycle(settings, signal) {
268
256
  }
269
257
  }
270
258
  export function issueQueue(settings) {
271
- const stopping = new AbortController();
272
- const running = cycle(settings, stopping.signal).catch((error) => {
273
- const reason = error instanceof Error ? error.message : String(error);
274
- const diagnostic = error instanceof QueueFailureLimit ? reason : JSON.stringify(reason);
275
- process.stderr.write(`Queue stopped: ${diagnostic}\n`);
259
+ const runtime = {
260
+ pending: undefined,
261
+ error: undefined,
262
+ stopping: new AbortController(),
263
+ };
264
+ const loaded = queueJournal(settings.directory).then((pending) => {
265
+ runtime.pending = pending;
266
+ return pending;
267
+ });
268
+ const running = loaded
269
+ .then((pending) => cycle(settings, { signal: runtime.stopping.signal, pending }))
270
+ .catch((error) => {
271
+ queueStopped(runtime, error);
276
272
  });
277
273
  return {
278
274
  stop: async () => {
279
- stopping.abort();
275
+ runtime.stopping.abort();
280
276
  await running;
281
277
  },
278
+ port: queueControlPort(runtime),
279
+ ready: loaded.then(() => undefined, () => undefined),
282
280
  };
283
281
  }
@@ -0,0 +1,10 @@
1
+ export interface Turn {
2
+ readonly key: string;
3
+ readonly signal: AbortSignal;
4
+ }
5
+ export interface MergeTurn {
6
+ readonly repository: string;
7
+ readonly signal: AbortSignal;
8
+ }
9
+ export declare function inTurn<Result>(turn: Turn, work: () => Promise<Result>): Promise<Result>;
10
+ export declare function inMergeTurn<Result>(turn: MergeTurn, work: () => Promise<Result>): Promise<Result>;
@@ -0,0 +1,37 @@
1
+ const turns = new Map();
2
+ function waited(previous, signal) {
3
+ signal.throwIfAborted();
4
+ return new Promise((resolve, reject) => {
5
+ const aborted = () => {
6
+ const reason = signal.reason;
7
+ reject(reason instanceof Error ? reason : new Error("merge-turn-aborted"));
8
+ };
9
+ signal.addEventListener("abort", aborted, { once: true });
10
+ previous.then(() => {
11
+ signal.removeEventListener("abort", aborted);
12
+ resolve(undefined);
13
+ }, reject);
14
+ });
15
+ }
16
+ export async function inTurn(turn, work) {
17
+ const previous = turns.get(turn.key) ?? Promise.resolve(undefined);
18
+ const ended = Promise.withResolvers();
19
+ const queued = previous
20
+ .then(() => ended.promise)
21
+ .then(() => {
22
+ if (turns.get(turn.key) === queued)
23
+ turns.delete(turn.key);
24
+ return undefined;
25
+ });
26
+ turns.set(turn.key, queued);
27
+ try {
28
+ await waited(previous, turn.signal);
29
+ return await work();
30
+ }
31
+ finally {
32
+ ended.resolve(undefined);
33
+ }
34
+ }
35
+ export function inMergeTurn(turn, work) {
36
+ return inTurn({ key: turn.repository, signal: turn.signal }, work);
37
+ }
@@ -0,0 +1,6 @@
1
+ import { type NotifyEvent } from "./notify-message.ts";
2
+ export type OutcomeCache = Map<string, {
3
+ readonly latestSequence: number;
4
+ readonly event: NotifyEvent | undefined;
5
+ }>;
6
+ export declare function notificationEvents(runtimeDirectory: string, cache: OutcomeCache): Promise<readonly NotifyEvent[]>;
@@ -0,0 +1,84 @@
1
+ import { send } from "../cli/client.js";
2
+ import { record } from "../protocol.js";
3
+ import { decisionNotification, outcomeNotification, scheduleStopNotifications, } from "./notify-message.js";
4
+ async function request(runtimeDirectory, request) {
5
+ const reply = await send(runtimeDirectory, request);
6
+ if (reply.kind === "error")
7
+ throw new Error(reply.error.code);
8
+ return reply.result;
9
+ }
10
+ function rows(value) {
11
+ if (!Array.isArray(value))
12
+ throw new Error("invalid-notify-list");
13
+ return value.map((item) => {
14
+ const row = record(item);
15
+ if (row === undefined)
16
+ throw new Error("invalid-notify-row");
17
+ return row;
18
+ });
19
+ }
20
+ async function scheduleNotifications(runtimeDirectory) {
21
+ const reply = await send(runtimeDirectory, {
22
+ operation: "schedule",
23
+ parameters: { action: "list" },
24
+ });
25
+ if (reply.kind !== "error")
26
+ return rows(reply.result["stops"]).flatMap(scheduleStopNotifications);
27
+ if (reply.error.code === "schedules-unavailable")
28
+ return [];
29
+ throw new Error(reply.error.code);
30
+ }
31
+ async function observedOutcome(summary, source) {
32
+ const { runtimeDirectory, cache } = source;
33
+ const latestSequence = summary["latestSequence"];
34
+ if (typeof latestSequence !== "number")
35
+ throw new Error("invalid-notify-sequence");
36
+ const runId = String(summary["runId"]);
37
+ const previous = cache.get(runId);
38
+ if (previous !== undefined && previous.latestSequence === latestSequence)
39
+ return previous.event;
40
+ const status = await request(runtimeDirectory, {
41
+ operation: "status",
42
+ parameters: { runId },
43
+ });
44
+ const run = record(status["run"]);
45
+ if (run === undefined)
46
+ throw new Error("invalid-notify-status");
47
+ const event = outcomeNotification(run);
48
+ cache.set(runId, { latestSequence, event });
49
+ return event;
50
+ }
51
+ export async function notificationEvents(runtimeDirectory, cache) {
52
+ const found = [];
53
+ const runs = new Map();
54
+ let after = "";
55
+ do {
56
+ const page = await request(runtimeDirectory, {
57
+ operation: "status",
58
+ parameters: { after, limit: 500 },
59
+ });
60
+ for (const summary of rows(page["runs"])) {
61
+ runs.set(String(summary["runId"]), summary);
62
+ const outcome = await observedOutcome(summary, {
63
+ runtimeDirectory,
64
+ cache,
65
+ });
66
+ if (outcome !== undefined)
67
+ found.push(outcome);
68
+ }
69
+ const next = page["next"];
70
+ if (next !== null && (typeof next !== "string" || next <= after))
71
+ throw new Error("invalid-notify-page");
72
+ after = next;
73
+ } while (after !== null);
74
+ const questions = await request(runtimeDirectory, {
75
+ operation: "questions",
76
+ parameters: {},
77
+ });
78
+ for (const decision of rows(questions["decisions"])) {
79
+ const run = runs.get(String(decision["runId"]));
80
+ if (run !== undefined)
81
+ found.push(decisionNotification(run, decision));
82
+ }
83
+ return [...found, ...(await scheduleNotifications(runtimeDirectory))];
84
+ }
@@ -46,7 +46,7 @@ function notification(value) {
46
46
  const message = record(value);
47
47
  if (message === undefined)
48
48
  return false;
49
- return (["decision", "blocked", "failed"].includes(String(message["event"])) &&
49
+ return (["decision", "blocked", "failed", "schedule-stopped"].includes(String(message["event"])) &&
50
50
  ["event", "runId", "workflowId", "subject", "body"].every((field) => typeof message[field] === "string"));
51
51
  }
52
52
  function deliveryState(value) {
@@ -1,10 +1,11 @@
1
1
  import { type Fields } from "../protocol.ts";
2
2
  export interface Notification {
3
- readonly event: "decision" | "blocked" | "failed";
3
+ readonly event: "decision" | "blocked" | "failed" | "schedule-stopped";
4
4
  readonly runId: string;
5
5
  readonly workflowId: string;
6
6
  readonly subject: string;
7
7
  readonly body: string;
8
+ readonly failedRunIds?: readonly string[];
8
9
  }
9
10
  export interface NotifyEvent {
10
11
  readonly key: string;
@@ -12,3 +13,5 @@ export interface NotifyEvent {
12
13
  }
13
14
  export declare function decisionNotification(run: Fields, decision: Fields): NotifyEvent;
14
15
  export declare function outcomeNotification(run: Fields): NotifyEvent | undefined;
16
+ export declare function stoppedScheduleNotification(schedule: Fields): NotifyEvent | undefined;
17
+ export declare function scheduleStopNotifications(schedule: Fields): readonly NotifyEvent[];
@@ -47,3 +47,39 @@ export function outcomeNotification(run) {
47
47
  }),
48
48
  };
49
49
  }
50
+ export function stoppedScheduleNotification(schedule) {
51
+ if (schedule["state"] !== "stopped")
52
+ return undefined;
53
+ const failedRunIds = schedule["failedRunIds"];
54
+ if (!Array.isArray(failedRunIds) || failedRunIds.length !== 3)
55
+ throw new Error("invalid-stopped-schedule");
56
+ const ids = failedRunIds.map((runId) => String(runId));
57
+ const workflowId = String(schedule["workflowId"]);
58
+ return {
59
+ key: `schedule-stopped:${workflowId}:${String(schedule["stoppedSlot"])}`,
60
+ message: {
61
+ ...message({ workflowId, runId: ids[2] }, {
62
+ event: "schedule-stopped",
63
+ body: body(`Schedule stopped after three failed runs:\n${ids.join("\n")}`, [`cloving schedule resume ${workflowId}`]),
64
+ }),
65
+ failedRunIds: ids,
66
+ },
67
+ };
68
+ }
69
+ export function scheduleStopNotifications(schedule) {
70
+ const stops = schedule["stops"];
71
+ if (stops === undefined) {
72
+ const event = stoppedScheduleNotification(schedule);
73
+ return event === undefined ? [] : [event];
74
+ }
75
+ if (!Array.isArray(stops))
76
+ throw new Error("invalid-stopped-schedule");
77
+ return stops.flatMap((stop) => {
78
+ const event = stoppedScheduleNotification({
79
+ ...record(stop),
80
+ workflowId: schedule["workflowId"],
81
+ state: "stopped",
82
+ });
83
+ return event === undefined ? [] : [event];
84
+ });
85
+ }
@@ -1,4 +1,4 @@
1
- import { type Notification } from "./notify-message.ts";
1
+ import type { Notification } from "./notify-message.ts";
2
2
  interface NotifySettings {
3
3
  readonly directory: string;
4
4
  readonly runtimeDirectory: string;
@@ -1,27 +1,9 @@
1
1
  import { recordedNotifications, saveNotifications, } from "./notify-journal.js";
2
2
  import { spawn } from "node:child_process";
3
3
  import { join } from "node:path";
4
- import { send } from "../cli/client.js";
5
4
  import { deliveryBackoffMilliseconds } from "../delivery-backoff.js";
6
- import { record } from "../protocol.js";
7
- import { decisionNotification, outcomeNotification, } from "./notify-message.js";
5
+ import { notificationEvents } from "./notify-events.js";
8
6
  const retentionMilliseconds = 7 * 24 * 60 * 60_000;
9
- async function request(runtimeDirectory, request) {
10
- const reply = await send(runtimeDirectory, request);
11
- if (reply.kind === "error")
12
- throw new Error(reply.error.code);
13
- return reply.result;
14
- }
15
- function rows(value) {
16
- if (!Array.isArray(value))
17
- throw new Error("invalid-notify-list");
18
- return value.map((item) => {
19
- const row = record(item);
20
- if (row === undefined)
21
- throw new Error("invalid-notify-row");
22
- return row;
23
- });
24
- }
25
7
  async function flushNotifications(journal) {
26
8
  if (journal.unsaved.size === 0)
27
9
  return;
@@ -36,60 +18,6 @@ async function finishDelivery(journal, entry) {
36
18
  journal.unsaved.set(entry.key, entry);
37
19
  await flushNotifications(journal);
38
20
  }
39
- async function observedOutcome(summary, source) {
40
- const { runtimeDirectory, cache } = source;
41
- const latestSequence = summary["latestSequence"];
42
- if (typeof latestSequence !== "number")
43
- throw new Error("invalid-notify-sequence");
44
- const runId = String(summary["runId"]);
45
- const previous = cache.get(runId);
46
- if (previous !== undefined && previous.latestSequence === latestSequence)
47
- return previous.event;
48
- const status = await request(runtimeDirectory, {
49
- operation: "status",
50
- parameters: { runId },
51
- });
52
- const run = record(status["run"]);
53
- if (run === undefined)
54
- throw new Error("invalid-notify-status");
55
- const event = outcomeNotification(run);
56
- cache.set(runId, { latestSequence, event });
57
- return event;
58
- }
59
- async function events(runtimeDirectory, cache) {
60
- const found = [];
61
- const runs = new Map();
62
- let after = "";
63
- do {
64
- const page = await request(runtimeDirectory, {
65
- operation: "status",
66
- parameters: { after, limit: 500 },
67
- });
68
- for (const summary of rows(page["runs"])) {
69
- runs.set(String(summary["runId"]), summary);
70
- const outcome = await observedOutcome(summary, {
71
- runtimeDirectory,
72
- cache,
73
- });
74
- if (outcome !== undefined)
75
- found.push(outcome);
76
- }
77
- const next = page["next"];
78
- if (next !== null && (typeof next !== "string" || next <= after))
79
- throw new Error("invalid-notify-page");
80
- after = next;
81
- } while (after !== null);
82
- const questions = await request(runtimeDirectory, {
83
- operation: "questions",
84
- parameters: {},
85
- });
86
- for (const decision of rows(questions["decisions"])) {
87
- const run = runs.get(String(decision["runId"]));
88
- if (run !== undefined)
89
- found.push(decisionNotification(run, decision));
90
- }
91
- return found;
92
- }
93
21
  export function notifyCommand(command, notification) {
94
22
  return new Promise((resolve, reject) => {
95
23
  const [executable = "", ...argumentsList] = command;
@@ -210,7 +138,7 @@ async function check(settings, journal) {
210
138
  journal.retiredKeys.add(key);
211
139
  }
212
140
  }
213
- for (const event of await events(settings.runtimeDirectory, journal.cache)) {
141
+ for (const event of await notificationEvents(settings.runtimeDirectory, journal.cache)) {
214
142
  if (journal.entries.has(event.key) || journal.retiredKeys.has(event.key))
215
143
  continue;
216
144
  const entry = {
@@ -237,12 +165,12 @@ export async function notifyRuns(settings) {
237
165
  unsaved: new Map(),
238
166
  };
239
167
  if (recorded === undefined) {
240
- for (const event of await events(settings.runtimeDirectory, cache))
168
+ for (const event of await notificationEvents(settings.runtimeDirectory, cache))
241
169
  journal.entries.set(event.key, {
242
170
  ...event,
243
- state: "sent",
171
+ state: event.message.event === "schedule-stopped" ? "pending" : "sent",
244
172
  attempts: 0,
245
- lastAttemptAt: Date.now(),
173
+ lastAttemptAt: event.message.event === "schedule-stopped" ? null : Date.now(),
246
174
  lastFailure: null,
247
175
  });
248
176
  }
@@ -11,6 +11,7 @@ export interface WorkerReport {
11
11
  readonly reason?: string;
12
12
  }
13
13
  export interface Recorded {
14
+ readonly stoppedBy?: "daemon-shutdown";
14
15
  readonly outcome: WorkerReport["outcome"];
15
16
  readonly result?: string;
16
17
  readonly reason?: string;
@@ -0,0 +1,13 @@
1
+ import type { QueuePort } from "../queue-state.ts";
2
+ import { type QueueJournal } from "./queue-journal.ts";
3
+ export interface QueueRuntime {
4
+ pending: QueueJournal | undefined;
5
+ error: string | undefined;
6
+ readonly stopping: AbortController;
7
+ }
8
+ export declare function queueConnection(): {
9
+ readonly port: QueuePort;
10
+ readonly connect: (port: QueuePort) => void;
11
+ };
12
+ export declare function queueStopped(runtime: QueueRuntime, error: unknown): string;
13
+ export declare function queueControlPort(runtime: QueueRuntime): QueuePort;
@@ -0,0 +1,86 @@
1
+ import { queueControlLimit } from "./queue-journal.js";
2
+ const unavailable = {
3
+ kind: "unavailable",
4
+ detail: "The queue is not running.",
5
+ };
6
+ export function queueConnection() {
7
+ let connected;
8
+ return {
9
+ port: {
10
+ control: () => connected?.control(),
11
+ change: (request) => connected?.change(request) ?? Promise.resolve(unavailable),
12
+ },
13
+ connect: (port) => {
14
+ connected = port;
15
+ },
16
+ };
17
+ }
18
+ export function queueStopped(runtime, error) {
19
+ const reason = error instanceof Error ? error.message : String(error);
20
+ runtime.error = reason;
21
+ runtime.stopping.abort();
22
+ process.stderr.write(`Queue stopped: ${JSON.stringify(reason)}\n`);
23
+ return reason;
24
+ }
25
+ function stoppedChange(error) {
26
+ return {
27
+ kind: "unavailable",
28
+ detail: `The queue stopped: ${error}. Restart the service.`,
29
+ };
30
+ }
31
+ function recorded(pending, request) {
32
+ const prior = pending.controls.find((control) => control.commandId === request.commandId);
33
+ if (prior === undefined)
34
+ return undefined;
35
+ return {
36
+ kind: prior.command === request.command ? "replayed" : "conflicting-command",
37
+ };
38
+ }
39
+ function applied(pending, command) {
40
+ if (command === "drain") {
41
+ pending.drain ??= { startedAt: Date.now(), drainedAt: null };
42
+ return;
43
+ }
44
+ pending.failures.length = 0;
45
+ pending.drain = undefined;
46
+ }
47
+ async function changed(runtime, request) {
48
+ const { pending, error } = runtime;
49
+ if (error !== undefined)
50
+ return stoppedChange(error);
51
+ if (pending === undefined)
52
+ return unavailable;
53
+ const prior = recorded(pending, request);
54
+ if (prior !== undefined)
55
+ return prior;
56
+ applied(pending, request.command);
57
+ pending.controls.push(request);
58
+ pending.controls.splice(0, pending.controls.length - queueControlLimit);
59
+ try {
60
+ await pending.save();
61
+ return { kind: "changed" };
62
+ }
63
+ catch (failure) {
64
+ return stoppedChange(queueStopped(runtime, failure));
65
+ }
66
+ }
67
+ export function queueControlPort(runtime) {
68
+ let changing = Promise.resolve();
69
+ return {
70
+ control: () => {
71
+ const { pending, error } = runtime;
72
+ if (pending === undefined && error === undefined)
73
+ return undefined;
74
+ return {
75
+ failedRunIds: [...(pending?.failures ?? [])],
76
+ drain: pending?.drain,
77
+ error,
78
+ };
79
+ },
80
+ change: (request) => {
81
+ const next = changing.then(() => changed(runtime, request));
82
+ changing = next;
83
+ return next;
84
+ },
85
+ };
86
+ }