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,49 @@
1
+ import { record } from "../protocol.js";
2
+ export function builtinCardForms() {
3
+ const entries = [
4
+ ["develop-issue", "ticket", "Issue number"],
5
+ ["develop-ticket", "ticket", "Ticket ID"],
6
+ ["develop-prd", "prompt", "Idea"],
7
+ ["develop-prompt", "prompt", "Task"],
8
+ ];
9
+ return entries.map(([workflowId, name, label]) => ({
10
+ workflowId,
11
+ source: name === "ticket" ? "ticket" : "prompt",
12
+ fields: [{ name, label, multiline: name === "prompt", optional: false }],
13
+ }));
14
+ }
15
+ function validFields(form, fields) {
16
+ if (Object.keys(fields).some((name) => !form.fields.some((field) => field.name === name)))
17
+ return false;
18
+ return form.fields.every((field) => {
19
+ if (field.optional && !Object.hasOwn(fields, field.name))
20
+ return true;
21
+ const text = fields[field.name];
22
+ return typeof text === "string" && text.trim() !== "";
23
+ });
24
+ }
25
+ export function cardRunRequest(forms, value) {
26
+ const body = record(value);
27
+ if (body === undefined ||
28
+ Object.keys(body).some((key) => !["workflowId", "fields", "commandId"].includes(key)))
29
+ return null;
30
+ const { workflowId, commandId } = body;
31
+ const form = forms.find((item) => item.workflowId === workflowId);
32
+ const fields = record(body["fields"]);
33
+ if (form === undefined ||
34
+ fields === undefined ||
35
+ typeof commandId !== "string" ||
36
+ !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(commandId))
37
+ return null;
38
+ if (!validFields(form, fields))
39
+ return null;
40
+ return {
41
+ operation: "run",
42
+ parameters: {
43
+ workflowId,
44
+ source: form.source,
45
+ submission: fields,
46
+ commandId,
47
+ },
48
+ };
49
+ }
@@ -1,5 +1,8 @@
1
+ import { type WatchPrSettings } from "./watch-pr-settings.ts";
1
2
  export declare const defaultWatchPort = 3663;
2
3
  export interface WatchSettings {
4
+ readonly actions?: string;
5
+ readonly pullRequests?: WatchPrSettings;
3
6
  readonly runtimeDirectory: string;
4
7
  readonly port: number;
5
8
  readonly client: string;
@@ -1,3 +1,6 @@
1
+ import { watchAction, watchActions, watchHost, } from "./watch-actions.js";
2
+ import { observedBoard } from "./watch-pr-board.js";
3
+ import { watchPrObserver } from "./watch-pr-settings.js";
1
4
  import { readdir, readFile, stat } from "node:fs/promises";
2
5
  import { createServer, } from "node:http";
3
6
  import { homedir } from "node:os";
@@ -8,7 +11,22 @@ import { turnOf, watchModel } from "./watch-model.js";
8
11
  import { pageDocument } from "./watch-page.js";
9
12
  import { watchStream } from "./watch-stream.js";
10
13
  export const defaultWatchPort = 3663;
14
+ const forbidden = { status: 403, type: "text/plain", body: "refused" };
11
15
  const notFound = { status: 404, type: "text/plain", body: "not found" };
16
+ const fontFiles = new Map([
17
+ "fira-code-latin-400-normal.woff2",
18
+ "fira-code-latin-500-normal.woff2",
19
+ "heebo-latin-500-normal.woff2",
20
+ "heebo-latin-700-normal.woff2",
21
+ "heebo-latin-800-normal.woff2",
22
+ "space-grotesk-latin-400-normal.woff2",
23
+ "space-grotesk-latin-500-normal.woff2",
24
+ "space-grotesk-latin-600-normal.woff2",
25
+ "space-grotesk-latin-700-normal.woff2",
26
+ ].map((name) => [
27
+ `/watch-fonts-v1/${name}`,
28
+ new URL(`assets/watch-fonts/${name}`, import.meta.resolve("#package")),
29
+ ]));
12
30
  const libraryFile = fileURLToPath(import.meta.resolve("preact"));
13
31
  function parsed(text) {
14
32
  try {
@@ -75,25 +93,59 @@ async function snapshot(watch, runId) {
75
93
  turns(watch),
76
94
  githubRecords(watch),
77
95
  ]);
78
- return watchModel({
96
+ const model = watchModel({
79
97
  ...read,
98
+ repository: watch.observer.repository ?? null,
80
99
  home: homedir(),
81
100
  client: watch.settings.client,
82
101
  updatedAt,
83
102
  turns: journal,
84
103
  githubRecords: github,
85
104
  });
105
+ const shown = runId === null ? observedBoard(model, watch.observer) : model;
106
+ return {
107
+ ...shown,
108
+ cardForms: watch.actions?.forms.filter((form) => shown.workflows.some((workflow) => workflow.workflowId === form.workflowId)) ?? [],
109
+ };
110
+ }
111
+ async function fontReply(request) {
112
+ const fontFile = fontFiles.get(request.url ?? "");
113
+ if (fontFile === undefined)
114
+ return request.url?.startsWith("/watch-fonts-v1/") ? notFound : null;
115
+ if (request.method !== "GET")
116
+ return notFound;
117
+ return {
118
+ status: 200,
119
+ type: "font/woff2",
120
+ body: await readFile(fontFile),
121
+ cache: "public, max-age=31536000, immutable",
122
+ };
86
123
  }
87
124
  async function answered(watch, request) {
125
+ if (!watchHost(request))
126
+ return forbidden;
127
+ const font = await fontReply(request);
128
+ if (font !== null)
129
+ return font;
88
130
  const url = new URL(String(request.url), "http://localhost");
89
- const path = url.pathname;
90
- if (request.method !== "GET")
131
+ if (request.method === "GET")
132
+ return readReply(watch, url);
133
+ if (request.method !== "POST" ||
134
+ url.pathname !== "/actions/run" ||
135
+ watch.actions === null)
91
136
  return notFound;
137
+ const body = await watchAction(watch.actions, request).catch(() => null);
138
+ return body === null
139
+ ? forbidden
140
+ : { status: 200, type: "application/json", body };
141
+ }
142
+ async function readReply(watch, url) {
143
+ const path = url.pathname;
92
144
  if (path === "/")
93
145
  return {
94
146
  status: 200,
95
147
  type: "text/html; charset=utf-8",
96
- body: pageDocument(),
148
+ body: pageDocument(watch.actions?.token ?? null),
97
149
  };
98
150
  if (path === "/preact.mjs")
99
151
  return {
@@ -116,7 +168,7 @@ async function answered(watch, request) {
116
168
  function replied(response, reply) {
117
169
  response.writeHead(reply.status, {
118
170
  "content-type": reply.type,
119
- "cache-control": "no-store",
171
+ "cache-control": reply.cache ?? "no-store",
120
172
  });
121
173
  response.end(reply.body);
122
174
  }
@@ -132,10 +184,15 @@ function subscribed(stream, exchange) {
132
184
  changed();
133
185
  request.once("close", stream.subscribe(changed));
134
186
  }
135
- export function watchServer(settings) {
187
+ export async function watchServer(settings) {
188
+ const observer = await observerFor(settings);
136
189
  const watch = {
190
+ actions: settings.actions === "off"
191
+ ? null
192
+ : await watchActions(settings.runtimeDirectory),
137
193
  settings,
138
- reader: watchReader(settings.runtimeDirectory),
194
+ observer,
195
+ reader: watchReader(settings.runtimeDirectory, observer.repository ?? null),
139
196
  streams: new Map(),
140
197
  github: new Map(),
141
198
  };
@@ -145,7 +202,9 @@ export function watchServer(settings) {
145
202
  retryMilliseconds: 1000,
146
203
  });
147
204
  const server = createServer((request, response) => {
148
- if (request.method === "GET" && request.url === "/events") {
205
+ if (watchHost(request) &&
206
+ request.method === "GET" &&
207
+ request.url === "/events") {
149
208
  subscribed(stream, { request, response });
150
209
  return;
151
210
  }
@@ -157,6 +216,7 @@ export function watchServer(settings) {
157
216
  });
158
217
  return new Promise((settle) => {
159
218
  server.once("error", (error) => {
219
+ observer.close();
160
220
  stream.close();
161
221
  settings.complain(`watch-not-started: ${error.message}\n`);
162
222
  settle(1);
@@ -166,6 +226,7 @@ export function watchServer(settings) {
166
226
  settings.announce(`Watch page: http://127.0.0.1:${String(bound)}/\n`);
167
227
  });
168
228
  const closed = () => {
229
+ observer.close();
169
230
  stream.close();
170
231
  server.close();
171
232
  server.closeAllConnections();
@@ -174,3 +235,11 @@ export function watchServer(settings) {
174
235
  settings.stopped.then(closed, closed);
175
236
  });
176
237
  }
238
+ function observerFor(settings) {
239
+ if (settings.pullRequests !== undefined)
240
+ return watchPrObserver(settings.pullRequests);
241
+ return Promise.resolve({
242
+ observe: () => ({ status: "unavailable", failure: "configuration" }),
243
+ close: () => undefined,
244
+ });
245
+ }
@@ -0,0 +1,18 @@
1
+ import { type Shown } from "./watch-element.ts";
2
+ import type { Copying, WatchModel } from "./watch-render.ts";
3
+ interface StartPrompt {
4
+ readonly workflowId: string;
5
+ readonly sentence: string;
6
+ readonly prompt: string;
7
+ }
8
+ export declare function startPrompts(): readonly StartPrompt[];
9
+ export declare function StartCard(props: {
10
+ readonly item: StartPrompt;
11
+ readonly copying: Copying;
12
+ }): Shown;
13
+ export declare function StartRun(props: {
14
+ readonly workflows: WatchModel["workflows"];
15
+ readonly copying: Copying;
16
+ readonly shown?: boolean;
17
+ }): Shown;
18
+ export {};
@@ -0,0 +1,77 @@
1
+ import { component, element } from "./watch-element.js";
2
+ import { Command } from "./watch-turn.js";
3
+ export function startPrompts() {
4
+ return [
5
+ {
6
+ workflowId: "develop-issue",
7
+ sentence: "Work on a GitHub issue through a pull request.",
8
+ prompt: `Start a Cloving run for a GitHub issue in this repository.
9
+ Run: cloving run develop-issue --ticket ISSUE_NUMBER
10
+ Use the issue number that I give on the last line.
11
+ Report the run ID. Then run cloving status RUN_ID until the run completes or stops, and tell me the result and the pull request.
12
+ Do not push, merge or change labels yourself.
13
+ Issue number:`,
14
+ },
15
+ {
16
+ workflowId: "develop-ticket",
17
+ sentence: "Work on a ticket with your review of the diff.",
18
+ prompt: `Start a Cloving run for a ticket in this repository.
19
+ Run: cloving run develop-ticket --ticket TICKET_ID
20
+ Use the ticket ID that I give on the last line.
21
+ When the run waits for a decision, run cloving questions, show me the diff and the decision, and give me the approve and deny commands.
22
+ Run only the command that I choose. Do not decide for me.
23
+ Ticket ID:`,
24
+ },
25
+ {
26
+ workflowId: "develop-prd",
27
+ sentence: "Turn an idea into a GitHub issue.",
28
+ prompt: `Start a Cloving run that turns an idea into a GitHub issue.
29
+ Run: cloving run develop-prd --prompt "IDEA"
30
+ Use the idea that I give on the last line as IDEA.
31
+ When the run completes, show me the new issue.
32
+ Add the label cloving: ready only when I tell you to.
33
+ Idea:`,
34
+ },
35
+ {
36
+ workflowId: "develop-prompt",
37
+ sentence: "Do a small task with your review of the diff.",
38
+ prompt: `Start a Cloving run for a small task in this repository.
39
+ Run: cloving run develop-prompt --prompt "TASK"
40
+ Use the task that I give on the last line as TASK.
41
+ When the run waits for a decision, run cloving questions, show me the diff, and give me the approve and deny commands.
42
+ Run only the command that I choose. Do not decide for me.
43
+ Task:`,
44
+ },
45
+ ];
46
+ }
47
+ export function StartCard(props) {
48
+ const { item, copying } = props;
49
+ return element("article", {
50
+ class: "start-card",
51
+ children: [
52
+ element("h3", { children: item.workflowId }),
53
+ element("p", { children: item.sentence }),
54
+ component(Command, {
55
+ label: "Agent prompt",
56
+ command: item.prompt,
57
+ copyLabel: "Copy the prompt",
58
+ copying,
59
+ }),
60
+ ],
61
+ });
62
+ }
63
+ export function StartRun(props) {
64
+ const registered = startPrompts().filter((item) => props.workflows.some((workflow) => workflow.workflowId === item.workflowId));
65
+ const cards = registered.map((item) => component(StartCard, {
66
+ key: item.workflowId,
67
+ item,
68
+ copying: props.copying,
69
+ }));
70
+ return element("section", {
71
+ class: "start-run",
72
+ id: "start-run",
73
+ "aria-label": "Start a run",
74
+ hidden: props.shown !== true,
75
+ children: element("div", { class: "start-cards", children: cards }),
76
+ });
77
+ }
@@ -17,6 +17,7 @@ export declare function Work(props: {
17
17
  export declare function Command(props: {
18
18
  readonly label: string;
19
19
  readonly command: string;
20
+ readonly copyLabel?: string;
20
21
  readonly copying: Copying;
21
22
  }): Shown;
22
23
  export declare function Decision(props: {
@@ -1,3 +1,4 @@
1
+ import { priceText, usageText } from "./usage-render.js";
1
2
  import { component, element } from "./watch-element.js";
2
3
  import { clockTime, counted, groupActivity, turnTime, } from "./watch-render.js";
3
4
  export function Item(props) {
@@ -30,8 +31,8 @@ export function Turn(props) {
30
31
  const { turn, now } = props;
31
32
  const groups = groupActivity(turn.activity);
32
33
  const working = turn.outcome === "in work";
33
- const cost = turn.costUsd === null ? "" : `${turn.costUsd.toFixed(2)} USD`;
34
- const heading = ["Claude turn", turn.outcome, turnTime(turn, now), cost];
34
+ const cost = priceText(turn.costUsd, turn.estimatedCostUsd);
35
+ const heading = ["Agent turn", turn.outcome, turnTime(turn, now), cost];
35
36
  const place = `Branch ${turn.branch} · work tree ${turn.worktree}`;
36
37
  const activity = groups.map((items, index) => component(Group, { items, open: working && index === groups.length - 1 }));
37
38
  return element("section", {
@@ -42,6 +43,10 @@ export function Turn(props) {
42
43
  children: heading.filter((part) => part !== "").join(" · "),
43
44
  }),
44
45
  element("p", { class: "meta", children: place }),
46
+ element("p", {
47
+ class: "turn-usage",
48
+ children: usageText(turn.usage),
49
+ }),
45
50
  element("div", { class: "activity", children: activity }),
46
51
  ],
47
52
  });
@@ -77,7 +82,7 @@ export function Command(props) {
77
82
  const onClick = () => {
78
83
  copying.onCopy(command);
79
84
  };
80
- const shown = copying.command === command ? copying.label : "Copy";
85
+ const shown = copying.command === command ? copying.label : (props.copyLabel ?? "Copy");
81
86
  return element("div", {
82
87
  class: "command code-window",
83
88
  children: [
@@ -0,0 +1,6 @@
1
+ import { type Fields } from "../protocol.ts";
2
+ import type { WatchTurn } from "./watch-render.ts";
3
+ export declare function priceFields(value: unknown): {
4
+ readonly costUsd?: number;
5
+ };
6
+ export declare function statusTurns(turns: readonly WatchTurn[], status: Fields): readonly WatchTurn[];
@@ -0,0 +1,65 @@
1
+ import { record } from "../protocol.js";
2
+ import { priceOf, usageOf } from "../worker-usage.js";
3
+ function text(value) {
4
+ return typeof value === "string" ? value : "";
5
+ }
6
+ function emptyTurn(operationId, runId) {
7
+ return {
8
+ operationId,
9
+ runId,
10
+ key: operationId,
11
+ usage: usageOf(null),
12
+ costUsd: null,
13
+ estimatedCostUsd: null,
14
+ target: "",
15
+ branch: "",
16
+ worktree: "",
17
+ startedAt: "",
18
+ endedAt: null,
19
+ activeAt: null,
20
+ outcome: "ended",
21
+ activity: [],
22
+ commits: [],
23
+ files: [],
24
+ uncommitted: [],
25
+ summary: "",
26
+ };
27
+ }
28
+ export function priceFields(value) {
29
+ const cost = priceOf(value);
30
+ return cost === null ? {} : { costUsd: cost };
31
+ }
32
+ export function statusTurns(turns, status) {
33
+ const runId = text(status["runId"]);
34
+ const journal = turns.filter((turn) => turn.runId === runId);
35
+ if (!Array.isArray(status["workerTurns"]))
36
+ return journal;
37
+ const ended = status["workerTurns"].map((value) => {
38
+ const fields = record(value) ?? {};
39
+ const operationId = text(fields["operationId"]);
40
+ const known = journal.find((turn) => turn.operationId === operationId) ??
41
+ emptyTurn(operationId, runId);
42
+ return {
43
+ ...known,
44
+ runId,
45
+ operationId,
46
+ outcome: known.outcome === "in work" ? "ended" : known.outcome,
47
+ activeAt: null,
48
+ usage: usageOf(fields["usage"]),
49
+ costUsd: priceOf(fields["costUsd"]),
50
+ estimatedCostUsd: priceOf(fields["estimatedCostUsd"]),
51
+ activity: known.activity.map((item) => item.kind === "result"
52
+ ? {
53
+ kind: item.kind,
54
+ text: item.text,
55
+ ...priceFields(fields["costUsd"]),
56
+ }
57
+ : item),
58
+ };
59
+ });
60
+ return [
61
+ ...ended,
62
+ ...journal.filter((turn) => turn.outcome === "in work" &&
63
+ !ended.some((item) => item.operationId === turn.operationId)),
64
+ ];
65
+ }
@@ -1,5 +1,10 @@
1
+ import type { CardControls } from "./watch-card-view.ts";
1
2
  import { type Shown } from "./watch-element.ts";
2
3
  import { type Copying, type WatchModel, type WatchPhase, type WatchRun } from "./watch-render.ts";
4
+ import { type ColumnControls } from "./watch-board-view.ts";
5
+ export declare function Queue(props: {
6
+ readonly model: WatchModel;
7
+ }): Shown;
3
8
  interface Timed {
4
9
  readonly run: WatchRun;
5
10
  readonly now: string;
@@ -24,16 +29,33 @@ export declare function PullRequest(props: {
24
29
  export declare function Run(props: Timed & {
25
30
  readonly copying: Copying;
26
31
  }): Shown;
32
+ export interface StartControls {
33
+ readonly shown: boolean;
34
+ readonly onToggle: () => void;
35
+ }
27
36
  export declare function Header(props: {
28
37
  readonly model: WatchModel | null;
29
38
  readonly stale: boolean;
39
+ readonly start?: StartControls;
30
40
  }): Shown;
31
41
  export interface PageProps {
42
+ readonly cards?: CardControls;
32
43
  readonly model: WatchModel | null;
44
+ readonly observationNow?: string | undefined;
33
45
  readonly selected: string;
34
46
  readonly now: string;
35
47
  readonly stale: boolean;
36
48
  readonly copying: Copying;
49
+ readonly columns?: ColumnControls;
50
+ readonly start?: StartControls;
37
51
  }
52
+ export declare function RefusedWorkflows(props: {
53
+ readonly model: WatchModel;
54
+ }): Shown;
38
55
  export declare function Page(props: PageProps): Shown;
56
+ export declare function BoardPage(props: PageProps & {
57
+ readonly model: WatchModel;
58
+ readonly workflowId: string | null;
59
+ readonly down: Shown;
60
+ }): Shown;
39
61
  export {};
@@ -1,8 +1,36 @@
1
+ import { priceText, usageText, totalsSuffix } from "./usage-render.js";
1
2
  import { component, element } from "./watch-element.js";
2
3
  import { clockTime, completedDetail, counted, elapsed, phaseTime, runName, runTone, waitingDetail, } from "./watch-render.js";
3
- import { Board, WorkflowList } from "./watch-board-view.js";
4
+ import { Board, HandoffLinks, WorkflowList, } from "./watch-board-view.js";
4
5
  import { boardWorkflow, watchHash, watchLocation } from "./watch-navigation.js";
5
6
  import { Decision, Events, Turn } from "./watch-turn.js";
7
+ import { StartRun } from "./watch-start-view.js";
8
+ import { record } from "../protocol.js";
9
+ export function Queue(props) {
10
+ const queue = props.model.queue;
11
+ if (queue === undefined)
12
+ return null;
13
+ const skipped = Array.isArray(queue["skipped"])
14
+ ? queue["skipped"]
15
+ : [];
16
+ return [
17
+ element("p", {
18
+ class: "meta",
19
+ children: `Queue: ${String(queue["busy"])} of ${String(queue["lanes"])} lanes busy`,
20
+ }),
21
+ ...skipped.map((value) => {
22
+ const ticket = record(value) ?? {};
23
+ const dependencies = ticket["dependencies"];
24
+ const needs = Array.isArray(dependencies)
25
+ ? `; needs ${dependencies.map(String).join(", ")}`
26
+ : "";
27
+ return element("p", {
28
+ class: "meta",
29
+ children: `Skipped ticket ${String(ticket["ticketId"])}: ${String(ticket["reason"])}${needs}`,
30
+ });
31
+ }),
32
+ ];
33
+ }
6
34
  export const clove = {
7
35
  viewBox: "0.6625858545303345 0.6630800366401672 46.674827575683594 46.673831939697266",
8
36
  paths: [
@@ -96,7 +124,7 @@ export function PullRequest(props) {
96
124
  children: `#${String(pullRequest.number)}`,
97
125
  });
98
126
  const scores = reviews.map((review) => element("li", {
99
- children: `Round ${String(review.round)} · Greptile ${String(review.score)}/5 · commit ${review.commit.slice(0, 7)}`,
127
+ children: `Round ${String(review.round)} · Review ${String(review.score)}/5 · commit ${review.commit.slice(0, 7)}`,
100
128
  }));
101
129
  const merge = mergeCommit === null
102
130
  ? null
@@ -122,7 +150,7 @@ export function Run(props) {
122
150
  const budget = run.spent === null || run.limit === null
123
151
  ? ""
124
152
  : ` · ${String(run.spent)} of ${String(run.limit)} effect units`;
125
- const none = "No Claude turn for this run.";
153
+ const none = "No agent turn for this run.";
126
154
  const turns = run.turns.length === 0
127
155
  ? element("p", { class: "meta", children: none })
128
156
  : run.turns.map((item, index) => [
@@ -140,6 +168,17 @@ export function Run(props) {
140
168
  ];
141
169
  return [
142
170
  component(Banner, { run, now }),
171
+ component(HandoffLinks, { run }),
172
+ element("section", {
173
+ class: "run-usage",
174
+ children: [
175
+ element("h3", { children: `Run totals${totalsSuffix(run.state)}` }),
176
+ element("p", { children: usageText(run.usage) }),
177
+ element("p", {
178
+ children: priceText(run.claudeCostUsd, run.estimatedCostUsd),
179
+ }),
180
+ ],
181
+ }),
143
182
  component(Phases, { phases: run.phases, now }),
144
183
  component(PullRequest, { run }),
145
184
  run.decisions.map((decision) => component(Decision, { decision, turn, copying })),
@@ -173,6 +212,32 @@ export function Header(props) {
173
212
  ],
174
213
  }),
175
214
  stamp,
215
+ props.start === undefined
216
+ ? null
217
+ : element("button", {
218
+ type: "button",
219
+ class: "start-toggle",
220
+ "aria-controls": "start-run",
221
+ "aria-expanded": String(props.start.shown),
222
+ onClick: props.start.onToggle,
223
+ children: "Start a run",
224
+ }),
225
+ ],
226
+ });
227
+ }
228
+ export function RefusedWorkflows(props) {
229
+ const refusals = props.model.workflowRefusals ?? [];
230
+ if (refusals.length === 0)
231
+ return null;
232
+ return element("section", {
233
+ class: "workflow-refusals",
234
+ children: [
235
+ element("h2", { children: "Refused workflows" }),
236
+ element("ul", {
237
+ children: refusals.map((entry) => element("li", {
238
+ children: `${entry.scope} ${entry.label}: ${entry.codes.join(", ")}`,
239
+ })),
240
+ }),
176
241
  ],
177
242
  });
178
243
  }
@@ -193,6 +258,8 @@ export function Page(props) {
193
258
  return [
194
259
  head,
195
260
  down,
261
+ component(Queue, { model }),
262
+ component(RefusedWorkflows, { model }),
196
263
  element("a", {
197
264
  class: "back",
198
265
  href: watchHash({ workflowId: back, runId: null }),
@@ -203,21 +270,46 @@ export function Page(props) {
203
270
  : component(Run, { key: run.runId, run, now, copying }),
204
271
  ];
205
272
  }
273
+ return component(BoardPage, { ...props, model, workflowId, down });
274
+ }
275
+ export function BoardPage(props) {
276
+ const { model, workflowId, down, now, stale, copying } = props;
206
277
  const workflow = model.workflows.find((item) => item.workflowId === workflowId);
207
- return [
208
- head,
209
- down,
210
- element("div", {
211
- class: "workspace",
212
- children: [
213
- component(WorkflowList, {
214
- workflows: model.workflows,
215
- selected: workflowId,
216
- }),
217
- workflow === undefined
218
- ? text("meta", "No workflows registered.")
219
- : component(Board, { workflow, now }),
220
- ],
221
- }),
222
- ];
278
+ const start = props.start ?? { shown: false, onToggle: () => undefined };
279
+ return element("div", {
280
+ class: "board-page",
281
+ children: [
282
+ component(Header, { model, stale, start }),
283
+ component(Queue, { model }),
284
+ down,
285
+ component(RefusedWorkflows, { model }),
286
+ component(StartRun, {
287
+ workflows: model.workflows,
288
+ copying,
289
+ shown: start.shown,
290
+ }),
291
+ component(WorkflowList, {
292
+ workflows: model.workflows,
293
+ selected: workflowId,
294
+ }),
295
+ element("div", {
296
+ class: "workspace",
297
+ children: [
298
+ workflow === undefined
299
+ ? element("p", {
300
+ class: "meta",
301
+ children: "No workflows registered.",
302
+ })
303
+ : component(Board, {
304
+ workflow,
305
+ now,
306
+ observationNow: props.observationNow,
307
+ controls: props.columns,
308
+ cardForm: model.cardForms?.find((form) => form.workflowId === workflowId),
309
+ cards: props.cards,
310
+ }),
311
+ ],
312
+ }),
313
+ ],
314
+ });
223
315
  }