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,150 @@
1
+ import { prArguments, prCommandResult, } from "./watch-pr-command.js";
2
+ import { prIdentity, } from "./watch-pr-identity.js";
3
+ const systemClock = {
4
+ now: Date.now,
5
+ after: (expired, milliseconds) => {
6
+ const timer = setTimeout(expired, milliseconds);
7
+ return () => {
8
+ clearTimeout(timer);
9
+ };
10
+ },
11
+ };
12
+ function observationOf(entry, reply) {
13
+ const { result, now } = reply;
14
+ if (result.status !== "unavailable")
15
+ return { ...result, lastSuccess: { status: result.status, readAt: now } };
16
+ const lastSuccess = entry.result?.lastSuccess;
17
+ return { ...result, ...(lastSuccess === undefined ? {} : { lastSuccess }) };
18
+ }
19
+ async function readPr(state, attempt) {
20
+ const { entry, controller } = attempt;
21
+ const deadline = { expired: false };
22
+ const timeout = { status: "unavailable", failure: "timeout" };
23
+ const cancel = state.clock.after(() => {
24
+ deadline.expired = true;
25
+ entry.result = observationOf(entry, {
26
+ result: timeout,
27
+ now: state.clock.now(),
28
+ });
29
+ controller.abort();
30
+ }, 5000);
31
+ controller.signal.addEventListener("abort", cancel, { once: true });
32
+ try {
33
+ const output = await Promise.resolve().then(() => state.settings.execute({
34
+ command: [
35
+ ...(state.settings.command ?? []),
36
+ ...prArguments(entry.identity),
37
+ ],
38
+ directory: state.settings.directory,
39
+ signal: controller.signal,
40
+ }));
41
+ return deadline.expired
42
+ ? timeout
43
+ : prCommandResult(entry.identity, { output, now: state.clock.now() });
44
+ }
45
+ catch {
46
+ return deadline.expired
47
+ ? timeout
48
+ : { status: "unavailable", failure: "command" };
49
+ }
50
+ finally {
51
+ cancel();
52
+ controller.signal.removeEventListener("abort", cancel);
53
+ }
54
+ }
55
+ function pump(state) {
56
+ if (state.closed || state.active !== null)
57
+ return;
58
+ state.wake?.();
59
+ state.wake = null;
60
+ if (state.queue.length === 0)
61
+ return;
62
+ const now = state.clock.now();
63
+ const position = state.queue.findIndex((entry) => (state.retryDeadlines.get(entry.identity.host) ?? 0) <= now);
64
+ if (position === -1) {
65
+ const next = Math.min(...state.queue.map((entry) => state.retryDeadlines.get(entry.identity.host) ?? now));
66
+ state.wake = state.clock.after(() => {
67
+ state.wake = null;
68
+ pump(state);
69
+ }, Math.min(next - now, 2147483647));
70
+ return;
71
+ }
72
+ const [entry] = state.queue.splice(position, 1);
73
+ if (entry === undefined)
74
+ return;
75
+ const controller = new AbortController();
76
+ state.active = controller;
77
+ entry.started = state.clock.now();
78
+ const completed = (result) => {
79
+ if (state.closed)
80
+ return;
81
+ entry.result = observationOf(entry, { result, now: state.clock.now() });
82
+ if (result.status === "unavailable" && result.failure === "rate-limit")
83
+ state.retryDeadlines.set(entry.identity.host, Math.max(state.retryDeadlines.get(entry.identity.host) ?? 0, result.retryAt ?? state.clock.now() + 60000));
84
+ entry.pending = false;
85
+ state.active = null;
86
+ pump(state);
87
+ };
88
+ readPr(state, { entry, controller }).then(completed, () => {
89
+ completed({ status: "unavailable", failure: "command" });
90
+ });
91
+ }
92
+ function currentObservation(state, entry) {
93
+ const result = entry.result ?? { status: "pending" };
94
+ const retryAt = state.retryDeadlines.get(entry.identity.host) ?? 0;
95
+ return retryAt > state.clock.now() ? { ...result, retryAt } : result;
96
+ }
97
+ function needsRead(entry, now) {
98
+ if (entry === undefined)
99
+ return true;
100
+ if (entry.pending || entry.result?.status === "merged")
101
+ return false;
102
+ return now - entry.started >= 60000;
103
+ }
104
+ export function prObserver(settings) {
105
+ const state = {
106
+ settings,
107
+ clock: settings.clock ?? systemClock,
108
+ cache: new Map(),
109
+ queue: [],
110
+ retryDeadlines: new Map(),
111
+ wake: null,
112
+ active: null,
113
+ closed: false,
114
+ };
115
+ const configured = settings.repository !== null && Boolean(settings.command?.[0]?.trim());
116
+ const observe = (pullRequest) => {
117
+ if (!configured)
118
+ return { status: "unavailable", failure: "configuration" };
119
+ const identity = prIdentity(settings.repository, pullRequest);
120
+ if (identity === null)
121
+ return { status: "unavailable", failure: "identity" };
122
+ const key = JSON.stringify(identity);
123
+ const known = state.cache.get(key);
124
+ if (state.closed || !needsRead(known, state.clock.now()))
125
+ return known === undefined
126
+ ? { status: "pending" }
127
+ : currentObservation(state, known);
128
+ const entry = {
129
+ identity,
130
+ result: known?.result ?? null,
131
+ started: Infinity,
132
+ pending: true,
133
+ };
134
+ state.cache.set(key, entry);
135
+ state.queue.push(entry);
136
+ pump(state);
137
+ return currentObservation(state, entry);
138
+ };
139
+ return {
140
+ repository: settings.repository,
141
+ observe,
142
+ close: () => {
143
+ state.closed = true;
144
+ state.queue.length = 0;
145
+ state.wake?.();
146
+ state.wake = null;
147
+ state.active?.abort();
148
+ },
149
+ };
150
+ }
@@ -0,0 +1,8 @@
1
+ import type { WatchCommandRunner } from "./watch-pr-command.ts";
2
+ import { type PrObserver } from "./watch-pr-observer.ts";
3
+ export interface WatchPrSettings {
4
+ readonly repository: string | undefined;
5
+ readonly githubCommand: string | undefined;
6
+ readonly execute: WatchCommandRunner;
7
+ }
8
+ export declare function watchPrObserver(settings: WatchPrSettings): Promise<PrObserver>;
@@ -0,0 +1,47 @@
1
+ import { prRepository } from "./watch-pr-identity.js";
2
+ import { prObserver } from "./watch-pr-observer.js";
3
+ function githubCommand(given) {
4
+ if (given === undefined)
5
+ return ["gh"];
6
+ try {
7
+ const parsed = JSON.parse(given);
8
+ if (!Array.isArray(parsed) ||
9
+ !parsed.every((part) => typeof part === "string"))
10
+ return null;
11
+ return parsed.map(String);
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ export async function watchPrObserver(settings) {
18
+ const directory = settings.repository;
19
+ const command = githubCommand(settings.githubCommand);
20
+ const output = directory === undefined
21
+ ? null
22
+ : await settings
23
+ .execute({
24
+ command: [
25
+ "git",
26
+ "config",
27
+ "--local",
28
+ "--get-all",
29
+ "remote.origin.url",
30
+ ],
31
+ directory,
32
+ signal: AbortSignal.timeout(5000),
33
+ })
34
+ .catch(() => null);
35
+ if (output?.errorCode === "ENOENT" || output?.errorCode === "EACCES")
36
+ return {
37
+ observe: () => ({ status: "unavailable", failure: "command" }),
38
+ close: () => undefined,
39
+ };
40
+ const repository = output?.status === 0 ? prRepository(output.stdout) : null;
41
+ return prObserver({
42
+ repository,
43
+ directory: directory ?? "",
44
+ command,
45
+ execute: settings.execute,
46
+ });
47
+ }
@@ -0,0 +1,10 @@
1
+ import type { WatchRun } from "./watch-render.ts";
2
+ import { type PrRepository } from "./watch-pr-identity.ts";
3
+ export interface GithubRecords {
4
+ readonly started: unknown;
5
+ readonly result: unknown;
6
+ }
7
+ export declare function pullOf(inputs: {
8
+ readonly records: readonly GithubRecords[];
9
+ readonly repository: PrRepository | null;
10
+ }, runId: string): Pick<WatchRun, "pullRequest" | "reviews" | "mergeCommit" | "mergeProof">;
@@ -0,0 +1,130 @@
1
+ import { operationIdentity } from "../operation-identity.js";
2
+ import { prIdentity, } from "./watch-pr-identity.js";
3
+ function record(value) {
4
+ return typeof value === "object" && value !== null ? { ...value } : {};
5
+ }
6
+ function text(value) {
7
+ return typeof value === "string" ? value : "";
8
+ }
9
+ function stamp(value) {
10
+ return typeof value === "string" ? value : null;
11
+ }
12
+ function parsed(line) {
13
+ try {
14
+ return JSON.parse(line);
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ }
20
+ function list(value) {
21
+ return Array.isArray(value) ? value.map((item) => item) : [];
22
+ }
23
+ function count(value) {
24
+ return typeof value === "number" ? value : null;
25
+ }
26
+ function attributed(started, operation) {
27
+ const stage = list(operation[5]);
28
+ const checked = operationIdentity({
29
+ round: operation[1],
30
+ run: operation[2],
31
+ phase: operation[3],
32
+ operation: operation[4],
33
+ ...(operation[5] === null
34
+ ? {}
35
+ : { stage: { id: stage[0], visit: stage[1], branch: stage[2] } }),
36
+ ...(operation[6] === null ? {} : { key: operation[6] }),
37
+ });
38
+ return (checked.kind === "valid" &&
39
+ checked.value === JSON.stringify(operation) &&
40
+ operation[3] === "pull-request" &&
41
+ operation[4] === `github-${text(started["method"])}` &&
42
+ (started["runId"] === undefined || started["runId"] === operation[2]));
43
+ }
44
+ function githubOf(records) {
45
+ const started = record(records.started);
46
+ const operation = list(parsed(text(started["operationId"])));
47
+ const result = record(records.result);
48
+ const completed = result["outcome"] === "completed";
49
+ return {
50
+ runId: text(operation[2]),
51
+ round: Number(operation[1]),
52
+ method: text(started["method"]),
53
+ completed,
54
+ attributed: attributed(started, operation) && !Object.hasOwn(result, "reason"),
55
+ report: completed ? record(parsed(text(result["result"]))) : {},
56
+ };
57
+ }
58
+ function pullRequestOf(results) {
59
+ const opened = results
60
+ .filter((item) => item.method === "open-pr")
61
+ .map((item) => item.report)
62
+ .findLast((report) => text(report["url"]).startsWith("https://"));
63
+ if (opened === undefined)
64
+ return null;
65
+ return { number: Number(opened["number"]), url: text(opened["url"]) };
66
+ }
67
+ function reviewsOf(results) {
68
+ return results
69
+ .filter((item) => item.method === "wait-review")
70
+ .flatMap(({ round, report }) => {
71
+ const score = count(report["score"]);
72
+ if (score === null)
73
+ return [];
74
+ return [{ round, commit: text(report["commit"]), score }];
75
+ });
76
+ }
77
+ function mergeCommitOf(results) {
78
+ const merged = results
79
+ .filter((item) => item.method === "merge")
80
+ .map((item) => stamp(item.report["merged"]));
81
+ return merged.find((commit) => commit !== null) ?? null;
82
+ }
83
+ function mergeProofOf(results, context) {
84
+ if (context.repository === null || context.pullRequest === null)
85
+ return null;
86
+ const repository = context.repository;
87
+ const identity = prIdentity(repository, context.pullRequest);
88
+ if (identity === null)
89
+ return null;
90
+ const opened = results.filter((item) => item.method === "open-pr" && item.completed && item.attributed);
91
+ if (opened.length === 0 ||
92
+ opened.some(({ report }) => report["number"] !== identity.number ||
93
+ typeof report["url"] !== "string" ||
94
+ prIdentity(repository, {
95
+ number: identity.number,
96
+ url: report["url"],
97
+ }) === null))
98
+ return null;
99
+ const merged = results.filter((item) => item.method === "merge" &&
100
+ item.completed &&
101
+ item.attributed &&
102
+ (typeof item.report["reason"] !== "string" ||
103
+ Object.hasOwn(item.report, "merged")));
104
+ if (merged.length === 0 ||
105
+ merged.some(({ report }) => report["number"] !== identity.number ||
106
+ typeof report["merged"] !== "string" ||
107
+ report["merged"].trim() === "" ||
108
+ Object.hasOwn(report, "reason")))
109
+ return null;
110
+ return new Set(merged.map(({ report }) => report["merged"])).size === 1
111
+ ? identity
112
+ : null;
113
+ }
114
+ export function pullOf(inputs, runId) {
115
+ const results = inputs.records
116
+ .map(githubOf)
117
+ .filter((item) => item.runId === runId)
118
+ .toSorted((first, second) => first.round - second.round);
119
+ const pullRequest = pullRequestOf(results);
120
+ const mergeProof = mergeProofOf(results, {
121
+ repository: inputs.repository,
122
+ pullRequest,
123
+ });
124
+ return {
125
+ pullRequest,
126
+ reviews: reviewsOf(results),
127
+ mergeCommit: mergeCommitOf(results),
128
+ ...(mergeProof === null ? {} : { mergeProof }),
129
+ };
130
+ }
@@ -1,4 +1,7 @@
1
+ import { type PrRepository } from "./watch-pr-identity.ts";
2
+ import { type WorkflowStages } from "../workflow-declaration.ts";
1
3
  import { type Fields } from "../protocol.ts";
4
+ import { type WorkflowRefusal } from "../workflow-refusals.ts";
2
5
  interface ReadStatus extends Fields {
3
6
  readonly runId: string;
4
7
  readonly latestSequence: number;
@@ -13,8 +16,11 @@ interface ReadRun {
13
16
  readonly events: readonly ReadEvent[];
14
17
  }
15
18
  export interface WatchRead {
19
+ readonly queue?: Fields;
16
20
  readonly service: string;
17
21
  readonly workflows: readonly string[];
22
+ readonly workflowRefusals: readonly WorkflowRefusal[];
23
+ readonly workflowStages?: WorkflowStages;
18
24
  readonly summaries: readonly ReadStatus[];
19
25
  readonly runs: readonly ReadRun[];
20
26
  readonly completionCandidates: readonly ReadRun[];
@@ -24,5 +30,5 @@ export interface WatchReader {
24
30
  readonly read: (now: string) => Promise<WatchRead>;
25
31
  readonly run: (runId: string) => Promise<WatchRead>;
26
32
  }
27
- export declare function watchReader(runtimeDirectory: string): WatchReader;
33
+ export declare function watchReader(runtimeDirectory: string, repository?: PrRepository | null): WatchReader;
28
34
  export {};
@@ -1,9 +1,13 @@
1
+ import { establishedIssue } from "./watch-pr-identity.js";
2
+ import { isWorkflowStages, } from "../workflow-declaration.js";
1
3
  import { record } from "../protocol.js";
2
4
  import { send } from "./client.js";
3
5
  import { activeRun, recentRun } from "./watch-board.js";
6
+ import { workflowRefusals, } from "../workflow-refusals.js";
4
7
  const unavailable = {
5
8
  service: "down",
6
9
  workflows: [],
10
+ workflowRefusals: [],
7
11
  summaries: [],
8
12
  runs: [],
9
13
  completionCandidates: [],
@@ -79,13 +83,15 @@ async function creationOf(reader, runId) {
79
83
  }
80
84
  return null;
81
85
  }
82
- function matchingCompletion(status, ticketIds) {
83
- const ticketId = status["ticketId"];
86
+ function matchingCompletion(status, context) {
87
+ const issue = establishedIssue(context.repository, status["issueIdentity"]);
84
88
  return (status.state === "completed" &&
85
89
  status["workflowId"] === "develop-issue" &&
86
90
  record(status["outcome"])?.["kind"] === "handoff_requested" &&
87
- typeof ticketId === "string" &&
88
- ticketIds.has(ticketId));
91
+ record(status["outcome"])?.["ticketStatus"] !== "partial" &&
92
+ record(status["outcome"])?.["issueUpdate"] !== "failed" &&
93
+ issue !== null &&
94
+ context.issues.has(JSON.stringify(issue)));
89
95
  }
90
96
  async function completionOf(reader, search) {
91
97
  const { runId, latestSequence } = search.summary;
@@ -104,12 +110,16 @@ async function completionOf(reader, search) {
104
110
  state: status.state,
105
111
  workflowId: status["workflowId"],
106
112
  ticketId: status["ticketId"],
113
+ issueIdentity: status["issueIdentity"],
107
114
  outcome: status["outcome"],
108
115
  },
109
116
  };
110
117
  reader.completions.set(runId, known);
111
118
  }
112
- if (!matchingCompletion(known.status, search.ticketIds))
119
+ if (!matchingCompletion(known.status, {
120
+ issues: search.issues,
121
+ repository: reader.repository,
122
+ }))
113
123
  return null;
114
124
  const creation = known.creation === undefined
115
125
  ? await creationOf(reader, runId)
@@ -117,11 +127,20 @@ async function completionOf(reader, search) {
117
127
  reader.completions.set(runId, { ...known, creation });
118
128
  return { status: known.status, events: creation === null ? [] : [creation] };
119
129
  }
130
+ function refreshedLinks(run, status) {
131
+ const links = Object.fromEntries(["parentRunId", "chainId", "childRunIds"]
132
+ .filter((name) => Object.hasOwn(status, name))
133
+ .map((name) => [name, status[name]]));
134
+ return {
135
+ ...run,
136
+ status: { ...run.status, ...links },
137
+ };
138
+ }
120
139
  async function detailed(reader, summary) {
121
140
  const { runId, latestSequence: sequence } = summary;
122
141
  const known = reader.details.get(runId);
123
142
  if (known !== undefined && known.sequence === sequence)
124
- return known.value;
143
+ return refreshedLinks(known.value, summary);
125
144
  const shown = await asked(reader, {
126
145
  operation: "status",
127
146
  parameters: { runId },
@@ -158,10 +177,10 @@ async function decisionsOf(reader) {
158
177
  });
159
178
  return list(questions["decisions"]);
160
179
  }
161
- function stoppedTicketIds(runs, now) {
180
+ function stoppedIssues(runs, context) {
162
181
  const stopped = runs
163
182
  .map((run) => run.status)
164
- .filter((status) => recentRun(status.latestAt, now) &&
183
+ .filter((status) => recentRun(status.latestAt, context.now) &&
165
184
  status["workflowId"] === "develop-issue" &&
166
185
  (["failed", "cancelled"].includes(status.state) ||
167
186
  (status.state === "completed" &&
@@ -169,14 +188,15 @@ function stoppedTicketIds(runs, now) {
169
188
  .toSorted((first, second) => Date.parse(second.latestAt ?? "") - Date.parse(first.latestAt ?? ""))
170
189
  .slice(0, 20);
171
190
  return new Set(stopped.flatMap((status) => {
172
- const ticketId = status["ticketId"];
173
- return status.state === "completed" &&
174
- typeof ticketId === "string" &&
175
- ticketId !== ""
176
- ? [ticketId]
191
+ const issue = establishedIssue(context.repository, status["issueIdentity"]);
192
+ return status.state === "completed" && issue !== null
193
+ ? [JSON.stringify(issue)]
177
194
  : [];
178
195
  }));
179
196
  }
197
+ function stageMetadata(value) {
198
+ return Object.fromEntries(Object.entries(record(value) ?? {}).flatMap(([workflowId, stages]) => isWorkflowStages(stages) ? [[workflowId, stages]] : []));
199
+ }
180
200
  async function service(reader, now) {
181
201
  const status = await asked(reader, {
182
202
  operation: "status",
@@ -190,20 +210,25 @@ async function service(reader, now) {
190
210
  const current = summaries.filter((summary) => activeRun(summary.state) || recentRun(summary.latestAt, now));
191
211
  for (const summary of current)
192
212
  runs.push(await detailed(reader, summary));
193
- const ticketIds = stoppedTicketIds(runs, now);
194
- const older = ticketIds.size === 0
213
+ const issues = stoppedIssues(runs, { now, repository: reader.repository });
214
+ const older = issues.size === 0
195
215
  ? []
196
216
  : summaries.filter((summary) => !recentRun(summary.latestAt, now) &&
197
217
  summary["workflowId"] === "develop-issue" &&
198
218
  summary.state === "completed");
199
219
  for (const summary of older) {
200
- const candidate = await completionOf(reader, { summary, ticketIds });
220
+ const candidate = await completionOf(reader, { summary, issues });
201
221
  if (candidate !== null)
202
222
  completionCandidates.push(candidate);
203
223
  }
204
224
  return {
205
225
  service: "up",
226
+ ...(record(status["queue"]) === undefined
227
+ ? {}
228
+ : { queue: record(status["queue"]) ?? {} }),
206
229
  workflows: list(status["workflows"]).filter((value) => typeof value === "string"),
230
+ workflowStages: stageMetadata(status["workflowStages"]),
231
+ workflowRefusals: workflowRefusals(status["workflowRefusals"]),
207
232
  summaries,
208
233
  runs,
209
234
  completionCandidates,
@@ -222,7 +247,7 @@ async function singleRun(reader, runId) {
222
247
  const status = statusOf(reply.result["run"]);
223
248
  const known = reader.details.get(runId);
224
249
  const value = known !== undefined && known.sequence === status.latestSequence
225
- ? known.value
250
+ ? refreshedLinks(known.value, status)
226
251
  : await remember(reader, status);
227
252
  return {
228
253
  ...unavailable,
@@ -231,9 +256,10 @@ async function singleRun(reader, runId) {
231
256
  decisions: await decisionsOf(reader),
232
257
  };
233
258
  }
234
- export function watchReader(runtimeDirectory) {
259
+ export function watchReader(runtimeDirectory, repository = null) {
235
260
  const reader = {
236
261
  runtimeDirectory,
262
+ repository,
237
263
  details: new Map(),
238
264
  completions: new Map(),
239
265
  };
@@ -1,9 +1,17 @@
1
+ import type { CardForm } from "./watch-run-input.ts";
2
+ import type { WorkerUsage } from "../worker-usage.ts";
3
+ import type { PrIdentity, PrObservation } from "./watch-pr-identity.ts";
4
+ import type { WorkflowRefusal } from "../workflow-refusals.ts";
5
+ import type { Fields } from "../protocol.ts";
1
6
  export interface WatchItem {
2
7
  readonly kind: string;
3
8
  readonly text: string;
4
9
  readonly costUsd?: number;
5
10
  }
6
11
  export interface WatchTurn {
12
+ readonly estimatedCostUsd: number | null;
13
+ readonly operationId: string;
14
+ readonly usage: WorkerUsage;
7
15
  readonly key: string;
8
16
  readonly runId: string;
9
17
  readonly target: string;
@@ -56,9 +64,14 @@ export interface WatchStep {
56
64
  readonly startedAt: string | null;
57
65
  }
58
66
  export interface WatchCard {
67
+ readonly parentRunId?: string | null;
68
+ readonly chainId?: string;
69
+ readonly childRunIds?: readonly string[];
70
+ readonly estimatedCostUsd: number | null;
59
71
  readonly runId: string;
60
72
  readonly title: string | null;
61
- readonly claudeCostUsd: number;
73
+ readonly claudeCostUsd: number | null;
74
+ readonly usage: WorkerUsage;
62
75
  readonly currentStep: WatchStep | null;
63
76
  readonly endedAt: string | null;
64
77
  readonly decisionWaiting: boolean;
@@ -66,6 +79,7 @@ export interface WatchCard {
66
79
  readonly greptileScore: number | null;
67
80
  readonly pullRequestNumber: number | null;
68
81
  readonly completionMark: WatchCompletionMark | null;
82
+ readonly pullRequestObservation: PrObservation | null;
69
83
  }
70
84
  export interface WatchCompletionMark {
71
85
  readonly text: string;
@@ -83,11 +97,21 @@ export interface WatchWorkflow {
83
97
  readonly columns: readonly WatchColumn[];
84
98
  }
85
99
  export interface WatchRun {
100
+ readonly parentRunId?: string | null;
101
+ readonly chainId?: string;
102
+ readonly childRunIds?: readonly string[];
103
+ readonly mergeProof?: PrIdentity | null;
104
+ readonly issueIdentity?: PrIdentity | null;
105
+ readonly issueUpdate?: "failed";
106
+ readonly ticketStatus?: "complete" | "partial";
107
+ readonly promotionId?: string;
108
+ readonly estimatedCostUsd: number | null;
86
109
  readonly ticketId: string | null;
87
110
  readonly title: string | null;
88
111
  readonly outcome: string | null;
89
112
  readonly latestAt: string | null;
90
- readonly claudeCostUsd: number;
113
+ readonly claudeCostUsd: number | null;
114
+ readonly usage: WorkerUsage;
91
115
  readonly currentStep: WatchStep;
92
116
  readonly round: number;
93
117
  readonly runId: string;
@@ -108,6 +132,9 @@ export interface WatchRun {
108
132
  readonly mergeCommit: string | null;
109
133
  }
110
134
  export interface WatchModel {
135
+ readonly queue?: Fields;
136
+ readonly workflowRefusals?: readonly WorkflowRefusal[];
137
+ readonly cardForms?: readonly CardForm[];
111
138
  readonly workflows: readonly WatchWorkflow[];
112
139
  readonly startWorkflowId: string | null;
113
140
  readonly updatedAt: string;
@@ -1,3 +1,4 @@
1
+ import { priceText } from "./usage-render.js";
1
2
  export function counted(count, noun) {
2
3
  return `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
3
4
  }
@@ -49,7 +50,7 @@ export function runTone(run) {
49
50
  return toneOf(run.state);
50
51
  }
51
52
  export function runName(run) {
52
- return run.turns[0]?.target ?? run.runId.slice(0, 12);
53
+ return run.turns[0]?.target || run.runId.slice(0, 12);
53
54
  }
54
55
  export function waitingDetail(run, now) {
55
56
  const opened = run.phases.find((phase) => phase.state === "active");
@@ -60,7 +61,7 @@ export function waitingDetail(run, now) {
60
61
  export function completedDetail(run) {
61
62
  const [first] = run.events;
62
63
  const last = run.events.at(-1);
63
- const cost = run.turns.reduce((sum, turn) => sum + (turn.costUsd ?? 0), 0);
64
+ const cost = priceText(run.claudeCostUsd, run.estimatedCostUsd);
64
65
  const time = first === undefined || last === undefined
65
66
  ? ""
66
67
  : ` in ${elapsed(first.recordedAt, last.recordedAt)}`;
@@ -68,7 +69,14 @@ export function completedDetail(run) {
68
69
  approved: "You approved the work. ",
69
70
  denied: "You denied the work. ",
70
71
  };
71
- return `${verdicts[run.decision] ?? ""}The run completed${time} · cost ${cost.toFixed(2)} USD.`;
72
+ const ticket = run.ticketStatus === undefined
73
+ ? ""
74
+ : ` Ticket status: ${run.ticketStatus}.`;
75
+ const update = run.issueUpdate === "failed"
76
+ ? " Issue status update failed; check the GitHub operation."
77
+ : "";
78
+ const promotion = run.promotionId === undefined ? "" : ` Promotion: ${run.promotionId}.`;
79
+ return `${verdicts[run.decision] ?? ""}The run completed${time} · cost ${cost}.${promotion}${ticket}${update}`;
72
80
  }
73
81
  export function phaseTime(phase, now) {
74
82
  if (phase.startedAt === null)
@@ -0,0 +1,14 @@
1
+ import { type ServiceRequest } from "../protocol.ts";
2
+ export interface CardField {
3
+ readonly name: string;
4
+ readonly label: string;
5
+ readonly multiline: boolean;
6
+ readonly optional: boolean;
7
+ }
8
+ export interface CardForm {
9
+ readonly workflowId: string;
10
+ readonly source: "ticket" | "prompt" | "input";
11
+ readonly fields: readonly CardField[];
12
+ }
13
+ export declare function builtinCardForms(): readonly CardForm[];
14
+ export declare function cardRunRequest(forms: readonly CardForm[], value: unknown): ServiceRequest | null;