cloving 1.0.1 → 1.2.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 (309) hide show
  1. package/README.md +251 -20
  2. package/assets/watch-fonts/fira-code-OFL.md +93 -0
  3. package/assets/watch-fonts/fira-code-latin-400-normal.woff2 +0 -0
  4. package/assets/watch-fonts/fira-code-latin-500-normal.woff2 +0 -0
  5. package/assets/watch-fonts/heebo-OFL.md +93 -0
  6. package/assets/watch-fonts/heebo-latin-500-normal.woff2 +0 -0
  7. package/assets/watch-fonts/heebo-latin-700-normal.woff2 +0 -0
  8. package/assets/watch-fonts/heebo-latin-800-normal.woff2 +0 -0
  9. package/assets/watch-fonts/manifest.json +112 -0
  10. package/assets/watch-fonts/space-grotesk-OFL.md +93 -0
  11. package/assets/watch-fonts/space-grotesk-latin-400-normal.woff2 +0 -0
  12. package/assets/watch-fonts/space-grotesk-latin-500-normal.woff2 +0 -0
  13. package/assets/watch-fonts/space-grotesk-latin-600-normal.woff2 +0 -0
  14. package/assets/watch-fonts/space-grotesk-latin-700-normal.woff2 +0 -0
  15. package/dist/src/authority/commands.d.ts +1 -0
  16. package/dist/src/authority/commands.js +33 -4
  17. package/dist/src/authority/dispatch-decision.d.ts +1 -0
  18. package/dist/src/authority/dispatch-decision.js +7 -4
  19. package/dist/src/authority/events.js +2 -2
  20. package/dist/src/authority/handoff-chain.d.ts +24 -0
  21. package/dist/src/authority/handoff-chain.js +46 -0
  22. package/dist/src/authority/reconciliation.d.ts +3 -0
  23. package/dist/src/authority/reconciliation.js +21 -5
  24. package/dist/src/authority/transitions.d.ts +1 -1
  25. package/dist/src/authority/transitions.js +5 -0
  26. package/dist/src/broker/actor/authority-queries.d.ts +4 -0
  27. package/dist/src/broker/actor/authority-queries.js +51 -2
  28. package/dist/src/broker/actor/authority-session.d.ts +2 -0
  29. package/dist/src/broker/actor/authority-session.js +3 -4
  30. package/dist/src/broker/actor/delivery-intents.d.ts +1 -0
  31. package/dist/src/broker/actor/effect-delivery.js +9 -0
  32. package/dist/src/broker/authority-admission.js +13 -5
  33. package/dist/src/chats/analysis-batches.d.ts +42 -0
  34. package/dist/src/chats/analysis-batches.js +97 -0
  35. package/dist/src/chats/analysis-checkpoint.d.ts +29 -0
  36. package/dist/src/chats/analysis-checkpoint.js +31 -0
  37. package/dist/src/chats/analysis-contract.d.ts +80 -0
  38. package/dist/src/chats/analysis-contract.js +16 -0
  39. package/dist/src/chats/analysis-data.d.ts +3 -0
  40. package/dist/src/chats/analysis-data.js +85 -0
  41. package/dist/src/chats/analysis-input.d.ts +18 -0
  42. package/dist/src/chats/analysis-input.js +97 -0
  43. package/dist/src/chats/analysis-links.d.ts +2 -0
  44. package/dist/src/chats/analysis-links.js +25 -0
  45. package/dist/src/chats/analysis-merge.d.ts +16 -0
  46. package/dist/src/chats/analysis-merge.js +134 -0
  47. package/dist/src/chats/analysis-prompt.d.ts +18 -0
  48. package/dist/src/chats/analysis-prompt.js +42 -0
  49. package/dist/src/chats/analysis-proposal.d.ts +4 -0
  50. package/dist/src/chats/analysis-proposal.js +41 -0
  51. package/dist/src/chats/analysis-provenance.d.ts +4 -0
  52. package/dist/src/chats/analysis-provenance.js +99 -0
  53. package/dist/src/chats/analysis-shape.d.ts +4 -0
  54. package/dist/src/chats/analysis-shape.js +235 -0
  55. package/dist/src/chats/analysis.d.ts +3 -0
  56. package/dist/src/chats/analysis.js +42 -0
  57. package/dist/src/chats/corpus-input.d.ts +5 -0
  58. package/dist/src/chats/corpus-input.js +288 -0
  59. package/dist/src/chats/destination.js +3 -1
  60. package/dist/src/chats/import.d.ts +8 -6
  61. package/dist/src/chats/import.js +195 -23
  62. package/dist/src/chats/input-file.d.ts +3 -0
  63. package/dist/src/chats/input-file.js +32 -0
  64. package/dist/src/chats/normalize.d.ts +11 -2
  65. package/dist/src/chats/normalize.js +18 -2
  66. package/dist/src/chats/previous-import.d.ts +5 -0
  67. package/dist/src/chats/previous-import.js +34 -0
  68. package/dist/src/chats/reader.d.ts +7 -1
  69. package/dist/src/chats/reader.js +46 -15
  70. package/dist/src/chats/redact-import.d.ts +14 -0
  71. package/dist/src/chats/redact-import.js +141 -0
  72. package/dist/src/chats/redaction.d.ts +41 -0
  73. package/dist/src/chats/redaction.js +161 -0
  74. package/dist/src/chats/reference-mapping.d.ts +16 -0
  75. package/dist/src/chats/reference-mapping.js +153 -0
  76. package/dist/src/chats/revision-input.d.ts +3 -0
  77. package/dist/src/chats/revision-input.js +180 -0
  78. package/dist/src/chats/revision.d.ts +48 -0
  79. package/dist/src/chats/revision.js +94 -0
  80. package/dist/src/chats/selection.d.ts +25 -0
  81. package/dist/src/chats/selection.js +109 -0
  82. package/dist/src/chats/source.d.ts +23 -0
  83. package/dist/src/chats/source.js +135 -0
  84. package/dist/src/chats/structure.d.ts +57 -0
  85. package/dist/src/chats/structure.js +216 -0
  86. package/dist/src/chats/workflow-generation-projection.d.ts +8 -0
  87. package/dist/src/chats/workflow-generation-projection.js +75 -0
  88. package/dist/src/chats/workflow-generation-source.d.ts +25 -0
  89. package/dist/src/chats/workflow-generation-source.js +124 -0
  90. package/dist/src/chats/workflow-generation-text.d.ts +23 -0
  91. package/dist/src/chats/workflow-generation-text.js +74 -0
  92. package/dist/src/chats/workflow-generation.d.ts +69 -0
  93. package/dist/src/chats/workflow-generation.js +142 -0
  94. package/dist/src/cli/arguments.d.ts +8 -0
  95. package/dist/src/cli/arguments.js +94 -13
  96. package/dist/src/cli/client.d.ts +1 -0
  97. package/dist/src/cli/client.js +38 -1
  98. package/dist/src/cli/commands.d.ts +3 -0
  99. package/dist/src/cli/commands.js +29 -11
  100. package/dist/src/cli/main.d.ts +2 -1
  101. package/dist/src/cli/main.js +61 -14
  102. package/dist/src/cli/render.d.ts +3 -0
  103. package/dist/src/cli/render.js +122 -7
  104. package/dist/src/cli/usage-render.d.ts +3 -0
  105. package/dist/src/cli/usage-render.js +28 -0
  106. package/dist/src/cli/usage.d.ts +3 -2
  107. package/dist/src/cli/usage.js +42 -7
  108. package/dist/src/cli/watch-actions.d.ts +10 -0
  109. package/dist/src/cli/watch-actions.js +58 -0
  110. package/dist/src/cli/watch-board-view.d.ts +44 -1
  111. package/dist/src/cli/watch-board-view.js +137 -18
  112. package/dist/src/cli/watch-board.d.ts +7 -1
  113. package/dist/src/cli/watch-board.js +48 -11
  114. package/dist/src/cli/watch-card-client.d.ts +1 -0
  115. package/dist/src/cli/watch-card-client.js +44 -0
  116. package/dist/src/cli/watch-card-forms.d.ts +2 -0
  117. package/dist/src/cli/watch-card-forms.js +33 -0
  118. package/dist/src/cli/watch-card-reply.d.ts +5 -0
  119. package/dist/src/cli/watch-card-reply.js +34 -0
  120. package/dist/src/cli/watch-card-view.d.ts +40 -0
  121. package/dist/src/cli/watch-card-view.js +86 -0
  122. package/dist/src/cli/watch-client.d.ts +1 -0
  123. package/dist/src/cli/watch-client.js +122 -0
  124. package/dist/src/cli/watch-model.d.ts +10 -4
  125. package/dist/src/cli/watch-model.js +61 -67
  126. package/dist/src/cli/watch-page.d.ts +1 -1
  127. package/dist/src/cli/watch-page.js +89 -86
  128. package/dist/src/cli/watch-phases.js +3 -3
  129. package/dist/src/cli/watch-pr-board.d.ts +3 -0
  130. package/dist/src/cli/watch-pr-board.js +26 -0
  131. package/dist/src/cli/watch-pr-command.d.ts +18 -0
  132. package/dist/src/cli/watch-pr-command.js +74 -0
  133. package/dist/src/cli/watch-pr-identity.d.ts +32 -0
  134. package/dist/src/cli/watch-pr-identity.js +87 -0
  135. package/dist/src/cli/watch-pr-observer.d.ts +20 -0
  136. package/dist/src/cli/watch-pr-observer.js +150 -0
  137. package/dist/src/cli/watch-pr-settings.d.ts +8 -0
  138. package/dist/src/cli/watch-pr-settings.js +47 -0
  139. package/dist/src/cli/watch-pull-request.d.ts +10 -0
  140. package/dist/src/cli/watch-pull-request.js +130 -0
  141. package/dist/src/cli/watch-reads.d.ts +7 -1
  142. package/dist/src/cli/watch-reads.js +44 -18
  143. package/dist/src/cli/watch-render.d.ts +29 -2
  144. package/dist/src/cli/watch-render.js +11 -3
  145. package/dist/src/cli/watch-run-input.d.ts +14 -0
  146. package/dist/src/cli/watch-run-input.js +49 -0
  147. package/dist/src/cli/watch-server.d.ts +3 -0
  148. package/dist/src/cli/watch-server.js +77 -8
  149. package/dist/src/cli/watch-start-view.d.ts +18 -0
  150. package/dist/src/cli/watch-start-view.js +77 -0
  151. package/dist/src/cli/watch-turn.d.ts +1 -0
  152. package/dist/src/cli/watch-turn.js +8 -3
  153. package/dist/src/cli/watch-usage.d.ts +6 -0
  154. package/dist/src/cli/watch-usage.js +65 -0
  155. package/dist/src/cli/watch-view.d.ts +22 -0
  156. package/dist/src/cli/watch-view.js +111 -19
  157. package/dist/src/cli.js +48 -6
  158. package/dist/src/daemon/analysis-launch.d.ts +5 -0
  159. package/dist/src/daemon/analysis-launch.js +81 -0
  160. package/dist/src/daemon/analysis-token.d.ts +6 -0
  161. package/dist/src/daemon/analysis-token.js +88 -0
  162. package/dist/src/daemon/analysis-worker.d.ts +20 -0
  163. package/dist/src/daemon/analysis-worker.js +271 -0
  164. package/dist/src/daemon/chats-batch-binding.d.ts +23 -0
  165. package/dist/src/daemon/chats-batch-binding.js +99 -0
  166. package/dist/src/daemon/chats-capture.d.ts +18 -0
  167. package/dist/src/daemon/chats-capture.js +194 -0
  168. package/dist/src/daemon/chats-checkpoints.d.ts +41 -0
  169. package/dist/src/daemon/chats-checkpoints.js +186 -0
  170. package/dist/src/daemon/chats-files.d.ts +42 -0
  171. package/dist/src/daemon/chats-files.js +252 -0
  172. package/dist/src/daemon/chats-follow-up.d.ts +38 -0
  173. package/dist/src/daemon/chats-follow-up.js +180 -0
  174. package/dist/src/daemon/chats-question.d.ts +15 -0
  175. package/dist/src/daemon/chats-question.js +93 -0
  176. package/dist/src/daemon/chats-worker.d.ts +13 -0
  177. package/dist/src/daemon/chats-worker.js +201 -0
  178. package/dist/src/daemon/claude-turn.d.ts +11 -0
  179. package/dist/src/daemon/claude-turn.js +96 -15
  180. package/dist/src/daemon/claude-worker.d.ts +2 -1
  181. package/dist/src/daemon/claude-worker.js +92 -45
  182. package/dist/src/daemon/codex-stream.js +16 -0
  183. package/dist/src/daemon/daemon.d.ts +8 -1
  184. package/dist/src/daemon/daemon.js +51 -14
  185. package/dist/src/daemon/delivery-router.js +6 -0
  186. package/dist/src/daemon/github-calls.d.ts +4 -0
  187. package/dist/src/daemon/github-calls.js +16 -7
  188. package/dist/src/daemon/github-ci.d.ts +6 -0
  189. package/dist/src/daemon/github-ci.js +170 -0
  190. package/dist/src/daemon/github-merge-call.d.ts +14 -0
  191. package/dist/src/daemon/github-merge-call.js +73 -0
  192. package/dist/src/daemon/github-merge.d.ts +3 -5
  193. package/dist/src/daemon/github-merge.js +52 -95
  194. package/dist/src/daemon/github-methods.d.ts +28 -0
  195. package/dist/src/daemon/github-methods.js +84 -16
  196. package/dist/src/daemon/github-operation.d.ts +2 -0
  197. package/dist/src/daemon/github-retry.d.ts +6 -0
  198. package/dist/src/daemon/github-retry.js +49 -0
  199. package/dist/src/daemon/github-ticket-status.d.ts +3 -0
  200. package/dist/src/daemon/github-ticket-status.js +294 -0
  201. package/dist/src/daemon/github-worker.js +2 -1
  202. package/dist/src/daemon/handoff-worker.d.ts +10 -0
  203. package/dist/src/daemon/handoff-worker.js +232 -0
  204. package/dist/src/daemon/issue-queue.d.ts +2 -0
  205. package/dist/src/daemon/issue-queue.js +186 -43
  206. package/dist/src/daemon/notify-journal.d.ts +18 -0
  207. package/dist/src/daemon/notify-journal.js +108 -0
  208. package/dist/src/daemon/notify-message.js +2 -1
  209. package/dist/src/daemon/notify.js +176 -37
  210. package/dist/src/daemon/operation-journal.d.ts +1 -0
  211. package/dist/src/daemon/operation-journal.js +4 -2
  212. package/dist/src/daemon/retrying-turn.d.ts +12 -0
  213. package/dist/src/daemon/retrying-turn.js +231 -0
  214. package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
  215. package/dist/src/daemon/ticket-dependency-waits.js +60 -0
  216. package/dist/src/daemon/ticket-registration.d.ts +8 -4
  217. package/dist/src/daemon/ticket-registration.js +76 -22
  218. package/dist/src/daemon/ticket-source.d.ts +4 -1
  219. package/dist/src/daemon/ticket-source.js +8 -2
  220. package/dist/src/decision-visibility.d.ts +2 -0
  221. package/dist/src/decision-visibility.js +10 -0
  222. package/dist/src/execution-pin.d.ts +4 -0
  223. package/dist/src/execution-pin.js +23 -2
  224. package/dist/src/host/start-delivery.d.ts +2 -1
  225. package/dist/src/host/start-delivery.js +1 -1
  226. package/dist/src/host/step-bridge-host.d.ts +1 -0
  227. package/dist/src/host/step-bridge-host.js +16 -0
  228. package/dist/src/host/world-reconciliation.d.ts +3 -0
  229. package/dist/src/host/world-reconciliation.js +9 -0
  230. package/dist/src/host/world-runs.js +14 -1
  231. package/dist/src/issue-identity.d.ts +6 -0
  232. package/dist/src/issue-identity.js +23 -0
  233. package/dist/src/service/queue-runs.d.ts +3 -0
  234. package/dist/src/service/queue-runs.js +39 -0
  235. package/dist/src/service/registry.d.ts +8 -2
  236. package/dist/src/service/registry.js +20 -8
  237. package/dist/src/service/replies.d.ts +2 -0
  238. package/dist/src/service/run-handlers.js +54 -30
  239. package/dist/src/service/service.d.ts +3 -0
  240. package/dist/src/service/service.js +3 -1
  241. package/dist/src/service/submission-handler.d.ts +1 -0
  242. package/dist/src/service/submission-handler.js +92 -13
  243. package/dist/src/service/worker-totals.d.ts +3 -0
  244. package/dist/src/service/worker-totals.js +52 -0
  245. package/dist/src/step-bridge.d.ts +9 -0
  246. package/dist/src/steps/handoff.d.ts +2 -0
  247. package/dist/src/steps/handoff.js +13 -0
  248. package/dist/src/ticket-dependencies.d.ts +1 -0
  249. package/dist/src/ticket-dependencies.js +15 -0
  250. package/dist/src/ticket-status.d.ts +8 -0
  251. package/dist/src/ticket-status.js +48 -0
  252. package/dist/src/token-prices.d.ts +21 -0
  253. package/dist/src/token-prices.js +108 -0
  254. package/dist/src/worker-usage.d.ts +31 -0
  255. package/dist/src/worker-usage.js +44 -0
  256. package/dist/src/workflow-admission.d.ts +1 -0
  257. package/dist/src/workflow-candidate.d.ts +29 -0
  258. package/dist/src/workflow-candidate.js +60 -0
  259. package/dist/src/workflow-contracts.d.ts +4 -0
  260. package/dist/src/workflow-contracts.js +32 -5
  261. package/dist/src/workflow-declaration.d.ts +12 -1
  262. package/dist/src/workflow-declaration.js +71 -13
  263. package/dist/src/workflow-discovery.d.ts +18 -0
  264. package/dist/src/workflow-discovery.js +33 -0
  265. package/dist/src/workflow-refusals.d.ts +6 -0
  266. package/dist/src/workflow-refusals.js +39 -0
  267. package/dist/src/workflows.d.ts +3 -1
  268. package/dist/src/workflows.js +2 -0
  269. package/dist/tools/admit-workflow.d.ts +13 -0
  270. package/dist/tools/admit-workflow.js +94 -0
  271. package/dist/tools/self-host-processes.js +7 -2
  272. package/dist/tools/self-host-project.d.ts +17 -0
  273. package/dist/tools/self-host-project.js +177 -0
  274. package/dist/tools/self-host-workflows.d.ts +1 -1
  275. package/dist/tools/self-host-workflows.js +2 -2
  276. package/dist/tools/self-host.js +30 -84
  277. package/dist/tools/self-update-host.d.ts +7 -0
  278. package/dist/tools/self-update-host.js +48 -1
  279. package/dist/tools/self-update.d.ts +1 -0
  280. package/dist/tools/self-update.js +85 -13
  281. package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
  282. package/dist/tools/workflow-bundle-dependencies.js +93 -0
  283. package/dist/tools/workflow-bundles.d.ts +20 -0
  284. package/dist/tools/workflow-bundles.js +173 -0
  285. package/dist/tools/workflow-candidate-counts.d.ts +8 -0
  286. package/dist/tools/workflow-candidate-counts.js +251 -0
  287. package/dist/tools/workflow-candidate-program.d.ts +11 -0
  288. package/dist/tools/workflow-candidate-program.js +78 -0
  289. package/dist/tools/workflow-candidate-source.d.ts +6 -0
  290. package/dist/tools/workflow-candidate-source.js +107 -0
  291. package/dist/tools/workflow-candidate-syntax.d.ts +15 -0
  292. package/dist/tools/workflow-candidate-syntax.js +219 -0
  293. package/dist/tools/workflow-folder-capture.d.ts +17 -0
  294. package/dist/tools/workflow-folder-capture.js +216 -0
  295. package/dist/tools/workflow-folders.d.ts +33 -0
  296. package/dist/tools/workflow-folders.js +150 -0
  297. package/dist/tools/workflow-source-effects.d.ts +10 -0
  298. package/dist/tools/workflow-source-effects.js +213 -0
  299. package/dist/workflows/analyze-chats.d.ts +15 -0
  300. package/dist/workflows/analyze-chats.declaration.json +22 -0
  301. package/dist/workflows/analyze-chats.js +250 -0
  302. package/dist/workflows/develop-prd.js +71 -3
  303. package/dist/workflows/develop-ticket.d.ts +1 -7
  304. package/dist/workflows/develop-ticket.js +47 -141
  305. package/dist/workflows/issue-labels.d.ts +3 -1
  306. package/dist/workflows/issue-labels.js +34 -0
  307. package/dist/workflows/ticket-prompts.d.ts +35 -0
  308. package/dist/workflows/ticket-prompts.js +143 -0
  309. package/package.json +25 -5
@@ -0,0 +1,94 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { mkdir, mkdtemp, readFile, realpath, rm, symlink, writeFile, } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { findPackageJSON } from "node:module";
6
+ import { dirname, extname, join, resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { checkWorkflowCandidate, } from "../src/workflow-candidate.js";
9
+ import { candidatePackageRoot, } from "./workflow-candidate-program.js";
10
+ import { analyzeWorkflowCandidate } from "./workflow-candidate-source.js";
11
+ import { isWorkflowManifest } from "./workflow-manifest.js";
12
+ export async function admitWorkflowCandidate(input, installed = {}) {
13
+ const { source, declaration } = input;
14
+ if (Buffer.from(source, "utf8").toString("utf8") !== source)
15
+ return { kind: "refused", codes: ["unsupported-source"] };
16
+ try {
17
+ const packageRoot = resolve(installed.packageRoot ?? candidatePackageRoot);
18
+ const facts = analyzeWorkflowCandidate(source, { packageRoot });
19
+ const codes = checkWorkflowCandidate({ declaration, ...facts });
20
+ if (codes.length > 0)
21
+ return { kind: "refused", codes };
22
+ return await compiledCandidate(source, packageRoot);
23
+ }
24
+ catch {
25
+ return { kind: "refused", codes: ["compile-failed"] };
26
+ }
27
+ }
28
+ async function compiledCandidate(source, packageRoot) {
29
+ const directory = await realpath(await mkdtemp(join(tmpdir(), "cloving-candidate-")));
30
+ try {
31
+ await prepareCandidateProject(directory, { source, packageRoot });
32
+ if (!(await compile(directory)))
33
+ return { kind: "refused", codes: ["compile-failed"] };
34
+ const artifactDirectory = join(directory, "artifacts");
35
+ const manifest = JSON.parse(await readFile(join(artifactDirectory, "manifest.json"), "utf8"));
36
+ if (!isWorkflowManifest(manifest) ||
37
+ Object.values(manifest.workflows ?? {}).flatMap((entries) => Object.values(entries)).length !== 1)
38
+ return { kind: "refused", codes: ["compile-failed"] };
39
+ const digest = createHash("sha256");
40
+ for (const name of [
41
+ "manifest.json",
42
+ "flow.mjs",
43
+ "steps.mjs",
44
+ "client.mjs",
45
+ ]) {
46
+ const bytes = await readFile(join(artifactDirectory, name));
47
+ digest.update(JSON.stringify([name, bytes.length]));
48
+ digest.update(bytes);
49
+ }
50
+ return { kind: "admitted", artifactDigest: digest.digest("hex") };
51
+ }
52
+ finally {
53
+ await rm(directory, { recursive: true, force: true });
54
+ }
55
+ }
56
+ async function prepareCandidateProject(directory, input) {
57
+ const { source, packageRoot } = input;
58
+ await mkdir(join(directory, "workflows"));
59
+ await mkdir(join(directory, "node_modules"));
60
+ await symlink(packageRoot, join(directory, "node_modules/cloving"));
61
+ const runtime = findPackageJSON("workflow", import.meta.url);
62
+ if (!runtime)
63
+ throw new Error("candidate-runtime-unavailable");
64
+ await symlink(dirname(runtime), join(directory, "node_modules/workflow"));
65
+ await writeFile(join(directory, "package.json"), '{"name":"candidate-project","private":true,"type":"module"}');
66
+ await writeFile(join(directory, "tsconfig.json"), JSON.stringify({
67
+ compilerOptions: {
68
+ target: "ES2024",
69
+ module: "ESNext",
70
+ moduleResolution: "Bundler",
71
+ types: [],
72
+ },
73
+ include: ["workflows/candidate.ts"],
74
+ }));
75
+ await writeFile(join(directory, "workflows/candidate.ts"), source, "utf8");
76
+ }
77
+ function compile(directory) {
78
+ const extension = extname(fileURLToPath(import.meta.url));
79
+ const executable = fileURLToPath(new URL(`./compile-workflows${extension}`, import.meta.url));
80
+ return new Promise((settle) => {
81
+ const child = spawn(process.execPath, [executable, "workflows", "artifacts"], {
82
+ cwd: directory,
83
+ env: { PATH: dirname(process.execPath), TMPDIR: tmpdir() },
84
+ stdio: "ignore",
85
+ timeout: 30000,
86
+ });
87
+ child.once("error", () => {
88
+ settle(false);
89
+ });
90
+ child.once("close", (code) => {
91
+ settle(code === 0);
92
+ });
93
+ });
94
+ }
@@ -4,7 +4,7 @@ import { extname, join } from "node:path";
4
4
  import { setTimeout as delay } from "node:timers/promises";
5
5
  import { promisify } from "node:util";
6
6
  import { selfUpdater } from "./self-update.js";
7
- import { workerTurnActive } from "./self-update-host.js";
7
+ import { selfUpdateActive } from "./self-update-host.js";
8
8
  function launched(argumentsList, settings) {
9
9
  const child = spawn(process.execPath, argumentsList, {
10
10
  cwd: settings.directory,
@@ -58,7 +58,11 @@ async function polled(settings, lifecycle) {
58
58
  directory: settings.directory,
59
59
  git: updatingGit(settings, lifecycle.stopping),
60
60
  active: async () => lifecycle.stopping.aborted ||
61
- (await workerTurnActive(settings.directory)),
61
+ (await selfUpdateActive({
62
+ directory: settings.directory,
63
+ runtimeDirectory: settings.runtime,
64
+ signal: lifecycle.stopping,
65
+ })),
62
66
  stop: async () => {
63
67
  if (lifecycle.stopping.aborted)
64
68
  throw new Error("self-update-stopped");
@@ -102,6 +106,7 @@ function hostedChildren(settings) {
102
106
  environment: {
103
107
  ...process.env,
104
108
  CLOVING_RUNTIME_DIR: settings.runtime,
109
+ CLOVING_WATCH_REPOSITORY: settings.repository,
105
110
  },
106
111
  }),
107
112
  ];
@@ -0,0 +1,17 @@
1
+ import { type DeclaredWorkflow } from "../src/workflow-declaration.ts";
2
+ import type { WorkflowRefusal } from "../src/workflow-refusals.ts";
3
+ interface ProjectSettings {
4
+ readonly product: string;
5
+ readonly project: string;
6
+ readonly repository: string;
7
+ readonly home: string;
8
+ readonly xdgConfigHome?: string | undefined;
9
+ }
10
+ export interface RegisteredWorkflows {
11
+ readonly repository: string;
12
+ readonly manifests: Readonly<Record<string, string>>;
13
+ readonly declarations: readonly DeclaredWorkflow[];
14
+ readonly workflowRefusals: readonly WorkflowRefusal[];
15
+ }
16
+ export declare function preparedProject(settings: ProjectSettings): Promise<RegisteredWorkflows>;
17
+ export {};
@@ -0,0 +1,177 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { copyFile, mkdir, readFile, rm, symlink, writeFile, } from "node:fs/promises";
4
+ import { findPackageJSON } from "node:module";
5
+ import { basename, dirname, extname, join, resolve } from "node:path";
6
+ import packageMetadata from "#package" with { type: "json" };
7
+ import { record } from "../src/protocol.js";
8
+ import { isWorkflowDeclaration, } from "../src/workflow-declaration.js";
9
+ import { bundledWorkflowIds, } from "../src/workflow-discovery.js";
10
+ import { workflowDeclarations, workflowFiles, workflowManifests, } from "./self-host-workflows.js";
11
+ import { loadWorkflowFolders, } from "./workflow-folders.js";
12
+ import { isWorkflowManifest } from "./workflow-manifest.js";
13
+ import { retainWorkflowBundle } from "./workflow-bundles.js";
14
+ import { workflowBundleDependencies, workflowPackageRoot, } from "./workflow-bundle-dependencies.js";
15
+ async function initializeProject(settings) {
16
+ const { project, product } = settings;
17
+ await rm(project, { recursive: true, force: true });
18
+ await mkdir(join(project, "workflows"), { recursive: true, mode: 0o700 });
19
+ await writeFile(join(project, "package.json"), '{"name":"cloving-self-host","private":true,"type":"module"}');
20
+ const files = await workflowFiles(join(product, "workflows"));
21
+ for (const file of files)
22
+ await copyWorkflow(settings, file);
23
+ const modules = join(project, "node_modules");
24
+ await mkdir(join(modules, "cloving"), { recursive: true });
25
+ for (const name of Object.keys(packageMetadata.dependencies)) {
26
+ const location = findPackageJSON(name, join(product, "package.json"));
27
+ if (location === undefined)
28
+ throw new Error(`missing-runtime-dependency: ${name}`);
29
+ await mkdir(dirname(join(modules, name)), { recursive: true });
30
+ await symlink(dirname(location), join(modules, name));
31
+ }
32
+ await symlink(join(product, "src"), join(modules, "cloving", "src"));
33
+ await writeFile(join(modules, "cloving", "package.json"), JSON.stringify({
34
+ name: packageMetadata.name,
35
+ version: packageMetadata.version,
36
+ type: "module",
37
+ exports: Object.fromEntries(Object.keys(packageMetadata.exports)
38
+ .filter((subpath) => subpath.startsWith("./workflows"))
39
+ .map((subpath) => [
40
+ subpath,
41
+ `./src/${subpath.slice("./workflows/".length) || "workflows"}${extname(import.meta.filename)}`,
42
+ ])),
43
+ }));
44
+ return files;
45
+ }
46
+ async function compile(settings) {
47
+ const { product, project } = settings;
48
+ await rm(join(project, "artifacts"), { recursive: true, force: true });
49
+ await new Promise((settle, reject) => {
50
+ const child = spawn(process.execPath, [
51
+ join(product, `tools/compile-workflows${extname(import.meta.filename)}`),
52
+ "workflows",
53
+ "artifacts",
54
+ ], { cwd: project, env: process.env, stdio: "ignore", timeout: 30000 });
55
+ child.once("error", reject);
56
+ child.once("close", (code) => {
57
+ if (code === 0)
58
+ settle();
59
+ else
60
+ reject(new Error("The workflows did not compile."));
61
+ });
62
+ });
63
+ const compiled = JSON.parse(await readFile(join(project, "artifacts/manifest.json"), "utf8"));
64
+ if (!isWorkflowManifest(compiled))
65
+ throw new Error("The compiled workflow manifest is invalid.");
66
+ return compiled;
67
+ }
68
+ function reservations(input) {
69
+ const { files, manifests, declarations } = input;
70
+ return [
71
+ ...files.map((file) => ({
72
+ baseName: basename(file, extname(file)),
73
+ })),
74
+ ...Object.entries(manifests).map(([functionName, manifest]) => ({
75
+ baseName: manifest.name,
76
+ workflowId: manifest.workflowId,
77
+ functionName,
78
+ })),
79
+ ...[
80
+ ...Object.values(bundledWorkflowIds),
81
+ ...declarations.map((item) => item.workflowId),
82
+ ].map((workflowId) => ({ workflowId })),
83
+ ];
84
+ }
85
+ async function stageCandidates(project, candidates) {
86
+ if (candidates.length === 0)
87
+ return [];
88
+ const directory = join(project, "workflows", "operators");
89
+ await mkdir(directory, { mode: 0o700 });
90
+ const staged = [];
91
+ for (const candidate of candidates) {
92
+ const name = createHash("sha256")
93
+ .update(JSON.stringify([
94
+ candidate.scope,
95
+ candidate.baseName,
96
+ candidate.source,
97
+ candidate.declaration,
98
+ ]))
99
+ .digest("hex");
100
+ const file = join(directory, `${name}.ts`);
101
+ await writeFile(file, candidate.source);
102
+ await writeFile(join(directory, `${name}.declaration.json`), candidate.declaration);
103
+ staged.push({ candidate, file });
104
+ }
105
+ return staged;
106
+ }
107
+ function operatorDeclaration(staged, compiled) {
108
+ const { candidate, project } = staged;
109
+ const declaration = record(JSON.parse(candidate.declaration))?.["workflow"];
110
+ const entries = Object.entries(compiled.workflows ?? {}).filter(([file]) => resolve(project, file) === staged.file);
111
+ const functions = entries.flatMap(([, functions]) => Object.values(functions));
112
+ const [workflow] = functions;
113
+ if (!isWorkflowDeclaration(declaration) ||
114
+ functions.length !== 1 ||
115
+ workflow === undefined)
116
+ throw new Error("The admitted workflow is missing from the compiled bundle.");
117
+ return {
118
+ ...declaration,
119
+ capabilities: [...new Set(declaration.capabilities)],
120
+ functionName: workflow.workflowId.slice(workflow.workflowId.lastIndexOf("//") + 2),
121
+ };
122
+ }
123
+ async function copyWorkflow(settings, file) {
124
+ const { product, project } = settings;
125
+ await copyFile(join(product, "workflows", file), join(project, "workflows", file));
126
+ const sidecar = `${basename(file, extname(file))}.declaration.json`;
127
+ const declaration = await readFile(join(product, "workflows", sidecar)).catch((error) => {
128
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
129
+ return undefined;
130
+ throw error;
131
+ });
132
+ if (declaration !== undefined)
133
+ await writeFile(join(project, "workflows", sidecar), declaration);
134
+ }
135
+ export async function preparedProject(settings) {
136
+ const { product, project } = settings;
137
+ const files = await initializeProject(settings);
138
+ let compiled = await compile(settings);
139
+ const bundled = workflowManifests(compiled);
140
+ const declarations = await workflowDeclarations(join(project, "workflows"), bundled);
141
+ const folders = await loadWorkflowFolders({
142
+ ...settings,
143
+ bundled: reservations({ files, manifests: bundled, declarations }),
144
+ }, { packageRoot: await workflowPackageRoot(product) });
145
+ const staged = await stageCandidates(project, folders.candidates);
146
+ if (staged.length > 0)
147
+ compiled = await compile(settings);
148
+ const combined = [
149
+ ...declarations,
150
+ ...staged.map((candidate) => operatorDeclaration({ ...candidate, project }, compiled)),
151
+ ];
152
+ const manifests = {};
153
+ for (const [functionName, manifest] of Object.entries(workflowManifests(compiled, combined))) {
154
+ const path = join(project, `${manifest.name}.workflow.json`);
155
+ await writeFile(path, JSON.stringify(manifest));
156
+ Object.defineProperty(manifests, functionName, {
157
+ value: path,
158
+ enumerable: true,
159
+ });
160
+ }
161
+ await writeFile(join(project, "declarations.json"), JSON.stringify(combined));
162
+ await writeFile(join(project, "dependencies.json"), await workflowBundleDependencies({
163
+ product,
164
+ packages: Object.keys(packageMetadata.dependencies),
165
+ }));
166
+ await retainWorkflowBundle({
167
+ project,
168
+ repository: folders.repository,
169
+ directory: join(dirname(project), "workflow-bundles"),
170
+ });
171
+ return {
172
+ repository: folders.repository,
173
+ manifests,
174
+ declarations: combined,
175
+ workflowRefusals: folders.refusals,
176
+ };
177
+ }
@@ -11,5 +11,5 @@ interface HostManifest {
11
11
  }
12
12
  export declare function workflowFiles(directory: string): Promise<string[]>;
13
13
  export declare function workflowDeclarations(directory: string, manifests: Readonly<Record<string, HostManifest>>): Promise<readonly DeclaredWorkflow[]>;
14
- export declare function workflowManifests(compiled: WorkflowManifest): Readonly<Record<string, HostManifest>>;
14
+ export declare function workflowManifests(compiled: WorkflowManifest, declarations?: readonly DeclaredWorkflow[]): Readonly<Record<string, HostManifest>>;
15
15
  export {};
@@ -45,7 +45,7 @@ export async function workflowDeclarations(directory, manifests) {
45
45
  }
46
46
  return declarations;
47
47
  }
48
- export function workflowManifests(compiled) {
48
+ export function workflowManifests(compiled, declarations = []) {
49
49
  const names = new Set();
50
50
  const steps = Object.values(compiled.steps ?? {})
51
51
  .flatMap((entries) => Object.values(entries))
@@ -68,7 +68,7 @@ export function workflowManifests(compiled) {
68
68
  name: basename(file, extname(file)),
69
69
  workflowId,
70
70
  steps,
71
- capabilities: ["claude", "github"],
71
+ capabilities: declarations.find((declaration) => declaration.functionName === functionName)?.capabilities ?? ["claude", "github"],
72
72
  resources: [],
73
73
  artifacts: "artifacts",
74
74
  },
@@ -1,12 +1,11 @@
1
1
  import { isProfilesFile } from "../src/turn-profile.js";
2
- import { execFileSync, spawn } from "node:child_process";
3
- import { copyFile, mkdir, readFile, rm, symlink, writeFile, } from "node:fs/promises";
2
+ import { parseFallbackModels, parseTokenPrices, } from "../src/token-prices.js";
3
+ import { execFileSync } from "node:child_process";
4
+ import { readFile, realpath, writeFile } from "node:fs/promises";
5
+ import { homedir } from "node:os";
4
6
  import { dirname, extname, join, resolve } from "node:path";
5
- import packageMetadata from "#package" with { type: "json" };
6
- import { findPackageJSON } from "node:module";
7
7
  import { pathToFileURL } from "node:url";
8
- import { workflowDeclarations, workflowFiles, workflowManifests, } from "./self-host-workflows.js";
9
- import { isWorkflowManifest } from "./workflow-manifest.js";
8
+ import { preparedProject, } from "./self-host-project.js";
10
9
  import { selfUpdateMilliseconds } from "./self-update-host.js";
11
10
  import { hostedPrograms } from "./self-host-processes.js";
12
11
  const product = resolve(import.meta.dirname, "..");
@@ -31,7 +30,7 @@ if (runtimeArgument === undefined)
31
30
  throw new Error("The start program needs --runtime. Use: cloving start");
32
31
  const runtime = resolve(runtimeArgument);
33
32
  const directory = dirname(runtime);
34
- const repository = topLevel(resolve(flag("--repository") ?? process.cwd()));
33
+ const repository = await realpath(topLevel(resolve(flag("--repository") ?? process.cwd())));
35
34
  const updateMilliseconds = await selfUpdateMilliseconds({
36
35
  seconds: process.env["CLOVING_SELF_UPDATE_SECONDS"],
37
36
  repository,
@@ -83,6 +82,16 @@ async function profilesFile() {
83
82
  }
84
83
  throw new Error("The daemon did not start: invalid-profiles");
85
84
  }
85
+ function queueLanes() {
86
+ const value = process.env["CLOVING_QUEUE_LANES"];
87
+ if (value === undefined)
88
+ return 1;
89
+ if (!/^[1-4]$/.test(value))
90
+ throw new Error("The daemon did not start: invalid-queue-lanes");
91
+ if (process.env["CLOVING_QUEUE_SECONDS"] === undefined)
92
+ throw new Error("The daemon did not start: queue-lanes-needs-queue");
93
+ return Number(value);
94
+ }
86
95
  function queueInterval(commands) {
87
96
  const value = process.env["CLOVING_QUEUE_SECONDS"];
88
97
  if (value === undefined)
@@ -108,65 +117,6 @@ function listed(name) {
108
117
  throw new Error(`${name} is not a JSON list of texts.`);
109
118
  return list.map(String);
110
119
  }
111
- function record(value) {
112
- return typeof value === "object" && value !== null ? { ...value } : {};
113
- }
114
- function ended(argumentsList, environment) {
115
- return new Promise((settle, reject) => {
116
- const child = spawn(process.execPath, argumentsList, {
117
- cwd: project,
118
- env: environment,
119
- stdio: "inherit",
120
- });
121
- for (const signal of ["SIGINT", "SIGTERM"])
122
- process.on(signal, () => child.kill(signal));
123
- child.once("error", reject);
124
- child.once("close", (status) => {
125
- settle(status ?? 1);
126
- });
127
- });
128
- }
129
- async function linkedPackages() {
130
- const modules = join(project, "node_modules");
131
- await mkdir(join(modules, "cloving"), { recursive: true });
132
- for (const name of Object.keys(packageMetadata.dependencies)) {
133
- const location = findPackageJSON(name, join(product, "package.json"));
134
- if (location === undefined)
135
- throw new Error(`missing-runtime-dependency: ${name}`);
136
- await mkdir(dirname(join(modules, name)), { recursive: true });
137
- await symlink(dirname(location), join(modules, name));
138
- }
139
- const manifest = record(packageMetadata);
140
- const exported = Object.keys(record(manifest["exports"])).filter((subpath) => subpath.startsWith("./workflows"));
141
- await symlink(join(product, "src"), join(modules, "cloving", "src"));
142
- await writeFile(join(modules, "cloving", "package.json"), JSON.stringify({
143
- name: manifest["name"],
144
- version: manifest["version"],
145
- type: "module",
146
- exports: Object.fromEntries(exported.map((subpath) => [
147
- subpath,
148
- `./src/${subpath.slice("./workflows/".length) || "workflows"}${extname(import.meta.filename)}`,
149
- ])),
150
- }));
151
- }
152
- async function registered() {
153
- const compiled = JSON.parse(await readFile(join(project, "artifacts/manifest.json"), "utf8"));
154
- if (!isWorkflowManifest(compiled))
155
- throw new Error("The compiled workflow manifest is invalid.");
156
- const manifests = workflowManifests(compiled);
157
- const declarations = await workflowDeclarations(join(product, "workflows"), manifests);
158
- const paths = new Map();
159
- for (const [functionName, manifest] of Object.entries(manifests)) {
160
- const path = join(project, `${manifest.name}.workflow.json`);
161
- const declared = declarations.find((entry) => entry.functionName === functionName);
162
- await writeFile(path, JSON.stringify({
163
- ...manifest,
164
- capabilities: declared?.capabilities ?? manifest.capabilities,
165
- }));
166
- paths.set(functionName, path);
167
- }
168
- return { manifests: Object.fromEntries(paths), declarations };
169
- }
170
120
  function source(file) {
171
121
  return JSON.stringify(pathToFileURL(join(product, file.replace(/\.ts$/, extname(import.meta.filename)))).href);
172
122
  }
@@ -174,7 +124,6 @@ async function launchers(commands, workflows) {
174
124
  const settings = {
175
125
  ...commands,
176
126
  directory,
177
- repository,
178
127
  concurrency,
179
128
  runtimeDirectory: runtime,
180
129
  ...workflows,
@@ -213,22 +162,8 @@ function guide() {
213
162
  "",
214
163
  ].join("\n");
215
164
  }
216
- async function prepared() {
217
- await rm(project, { recursive: true, force: true });
218
- await mkdir(join(project, "workflows"), { recursive: true });
219
- await writeFile(join(project, "package.json"), '{"name":"cloving-self-host","private":true,"type":"module"}');
220
- for (const file of await workflowFiles(join(product, "workflows")))
221
- await copyFile(join(product, "workflows", file), join(project, "workflows", file));
222
- await linkedPackages();
223
- const compiled = await ended([
224
- join(product, `tools/compile-workflows${extname(import.meta.filename)}`),
225
- "workflows",
226
- "artifacts",
227
- ], process.env);
228
- if (compiled !== 0)
229
- throw new Error("The workflows did not compile.");
230
- return registered();
231
- }
165
+ const tokenPrices = parseTokenPrices(process.env["CLOVING_TOKEN_PRICES"]);
166
+ const fallbackModels = parseFallbackModels(process.env["CLOVING_FALLBACK_MODELS"]);
232
167
  const profiles = await profilesFile();
233
168
  const worker = selectedWorker();
234
169
  const command = worker === "codex"
@@ -236,15 +171,26 @@ const command = worker === "codex"
236
171
  : (listed("CLOVING_CLAUDE_COMMAND") ?? defaultCommand);
237
172
  const nextCommand = listed("CLOVING_NEXT_COMMAND");
238
173
  const ticketCommand = listed("CLOVING_TICKET_COMMAND");
174
+ const lanes = queueLanes();
239
175
  const queueMilliseconds = queueInterval({ nextCommand, ticketCommand });
240
- const manifests = await prepared();
176
+ const manifests = await preparedProject({
177
+ product,
178
+ project,
179
+ repository,
180
+ home: homedir(),
181
+ xdgConfigHome: process.env["XDG_CONFIG_HOME"],
182
+ });
241
183
  await launchers({
184
+ tokenPrices,
185
+ fallbackModels,
242
186
  worker,
243
187
  profiles,
244
188
  command,
245
189
  ticketCommand,
246
190
  nextCommand,
247
191
  queueMilliseconds,
192
+ queueLanes: lanes,
193
+ resetQueue: flags.includes("--reset-queue"),
248
194
  notifyCommand: listed("CLOVING_NOTIFY_COMMAND"),
249
195
  githubCommand: listed("CLOVING_GH_COMMAND"),
250
196
  checkCommand: listed("CLOVING_CHECK_COMMAND"),
@@ -1,6 +1,13 @@
1
+ interface ActivitySettings {
2
+ readonly directory: string;
3
+ readonly runtimeDirectory: string;
4
+ readonly signal?: AbortSignal;
5
+ }
6
+ export declare function selfUpdateActive(settings: ActivitySettings): Promise<boolean>;
1
7
  export declare function selfUpdateMilliseconds(settings: {
2
8
  readonly seconds: string | undefined;
3
9
  readonly repository: string;
4
10
  readonly product: string;
5
11
  }): Promise<number | undefined>;
6
12
  export declare function workerTurnActive(directory: string): Promise<boolean>;
13
+ export {};
@@ -1,5 +1,52 @@
1
1
  import { access, readdir, realpath } from "node:fs/promises";
2
- import { join } from "node:path";
2
+ import { execFile } from "node:child_process";
3
+ import { extname, join } from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { record } from "../src/protocol.js";
6
+ async function runPage(settings, after) {
7
+ const output = await promisify(execFile)(process.execPath, [
8
+ join(import.meta.dirname, `../src/cli${extname(import.meta.filename)}`),
9
+ "status",
10
+ "--json",
11
+ "--limit",
12
+ "500",
13
+ ...(after === "" ? [] : ["--after", after]),
14
+ ], {
15
+ env: { ...process.env, CLOVING_RUNTIME_DIR: settings.runtimeDirectory },
16
+ encoding: "utf8",
17
+ timeout: 30000,
18
+ signal: settings.signal,
19
+ });
20
+ const page = record(JSON.parse(output.stdout));
21
+ if (!page)
22
+ throw new Error("invalid-run-list");
23
+ return page;
24
+ }
25
+ export async function selfUpdateActive(settings) {
26
+ if (await present(join(settings.directory, "queue-submission")))
27
+ return true;
28
+ if (await workerTurnActive(settings.directory))
29
+ return true;
30
+ let after = "";
31
+ do {
32
+ const page = await runPage(settings, after);
33
+ const runs = page["runs"];
34
+ if (!Array.isArray(runs))
35
+ throw new Error("invalid-run-list");
36
+ if (runs.some((value) => {
37
+ const run = record(value);
38
+ if (!run)
39
+ throw new Error("invalid-run-status");
40
+ return !["completed", "failed", "cancelled"].includes(String(run["state"]));
41
+ }))
42
+ return true;
43
+ const next = page["next"];
44
+ if (next !== null && (typeof next !== "string" || next <= after))
45
+ throw new Error("invalid-run-page");
46
+ after = next;
47
+ } while (after !== null);
48
+ return false;
49
+ }
3
50
  export async function selfUpdateMilliseconds(settings) {
4
51
  const { seconds } = settings;
5
52
  if (seconds === undefined)
@@ -5,6 +5,7 @@ export interface SelfUpdateSettings {
5
5
  readonly stop: () => Promise<void>;
6
6
  readonly error: (line: string) => void;
7
7
  }
8
+ export declare function lockRequiresInstall(before: string, after: string): boolean;
8
9
  export declare function selfUpdater(settings: SelfUpdateSettings): {
9
10
  readonly check: () => Promise<number | undefined>;
10
11
  };