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
@@ -1,12 +1,18 @@
1
+ import { issueIdentity } from "../issue-identity.js";
2
+ import { linksOf } from "../authority/handoff-chain.js";
1
3
  import { runView, unresolvedEffects, } from "../authority/status.js";
2
4
  import { record } from "../protocol.js";
3
5
  import { committed, failed, invalidRequest, isIdentifier, publicReceipt, rejection, result, wholeNumber, } from "./replies.js";
6
+ import { usageTotals } from "./worker-totals.js";
4
7
  import { feedbackOf } from "./decision-handler.js";
8
+ import { queueOverview } from "./submission-handler.js";
9
+ import { queueRunStatuses } from "./queue-runs.js";
5
10
  async function snapshot(context) {
6
11
  const { authority } = context;
7
12
  const state = await authority.state();
8
13
  const sequences = await authority.queries.sequences();
9
14
  return {
15
+ links: await authority.queries.runLinks(),
10
16
  state,
11
17
  operations: await authority.operations(),
12
18
  sequences: sequences.runs,
@@ -38,9 +44,7 @@ async function effectInput(context, runId) {
38
44
  const budget = await effects.budget(runId);
39
45
  const stored = await effects.operations(runId);
40
46
  return {
41
- budget: budget === undefined
42
- ? null
43
- : { limit: budget.limit, spent: budget.spent },
47
+ budget: budget ? { limit: budget.limit, spent: budget.spent } : null,
44
48
  effects: stored.map((operation) => ({
45
49
  operationId: operation.operationId,
46
50
  status: operation.status,
@@ -77,6 +81,8 @@ async function viewOf(context, scope) {
77
81
  };
78
82
  return {
79
83
  ...runView(input),
84
+ ...linksOf({ runId: run.runId, ...scope.snapshot.links[run.runId] }),
85
+ input: await submittedInput(context, details?.submission),
80
86
  workflowId: scope.snapshot.workflowIds[run.runId] ?? run.workflow,
81
87
  latestAt: times[run.runId] ?? null,
82
88
  unresolvedEffects: unresolvedEffects(input),
@@ -86,6 +92,8 @@ async function viewOf(context, scope) {
86
92
  function summary(view) {
87
93
  return {
88
94
  runId: view.runId,
95
+ ...linksOf(view),
96
+ input: view.input,
89
97
  workflowId: view.workflowId,
90
98
  state: view.state,
91
99
  condition: view.condition,
@@ -95,26 +103,6 @@ function summary(view) {
95
103
  pendingDecisions: view.pendingDecisions,
96
104
  };
97
105
  }
98
- function costOf(text) {
99
- try {
100
- const cost = record(JSON.parse(text ?? "null"))?.["costUsd"];
101
- return typeof cost === "number" ? cost : 0;
102
- }
103
- catch {
104
- return 0;
105
- }
106
- }
107
- async function claudeCost(context, runId) {
108
- const { effects, queries } = context.authority;
109
- const digests = (await effects.operations(runId))
110
- .filter((operation) => operation.capability === "claude")
111
- .map((operation) => operation.result)
112
- .filter((digest) => digest !== null);
113
- let total = 0;
114
- for (const digest of digests)
115
- total += costOf(await queries.document(digest));
116
- return Math.round(total * 100) / 100;
117
- }
118
106
  function full(view) {
119
107
  return {
120
108
  ...summary(view),
@@ -123,6 +111,9 @@ function full(view) {
123
111
  lastConfirmedObservation: view.lastConfirmedObservation,
124
112
  recovery: view.recovery,
125
113
  claudeCostUsd: view.claudeCostUsd,
114
+ estimatedCostUsd: view.estimatedCostUsd,
115
+ usage: view.usage,
116
+ workerTurns: view.workerTurns,
126
117
  budget: view.budget,
127
118
  effects: view.effects,
128
119
  nextAction: view.nextAction,
@@ -138,10 +129,18 @@ async function outcomeOf(context, view) {
138
129
  runId: view.runId,
139
130
  atOrBefore: view.latestSequence,
140
131
  });
141
- const kind = event?.payload["outcome"];
132
+ const payload = event?.payload ?? {};
133
+ const kind = payload["outcome"];
142
134
  if (kind === undefined)
143
135
  return null;
144
- return { kind, reason: event?.payload["outcomeReason"] ?? null };
136
+ const { ticketStatus, promotionId, outcomeReason, issueUpdate } = payload;
137
+ return {
138
+ kind,
139
+ reason: outcomeReason ?? null,
140
+ ...(promotionId === undefined ? {} : { promotionId }),
141
+ ...(ticketStatus === undefined ? {} : { ticketStatus }),
142
+ ...(issueUpdate === "failed" ? { issueUpdate } : {}),
143
+ };
145
144
  }
146
145
  function titleOf(input, workflowId) {
147
146
  const ticket = input?.["ticket"];
@@ -169,8 +168,18 @@ async function metadataOf(context, source) {
169
168
  return {
170
169
  ticketId: typeof ticketId === "string" && ticketId !== "" ? ticketId : null,
171
170
  title: titleOf(input, source.workflowId),
171
+ ...(source.workflowId === "develop-issue"
172
+ ? { issueIdentity: issueIdentity(input?.["ticket"]) }
173
+ : {}),
172
174
  };
173
175
  }
176
+ async function submittedInput(context, digest) {
177
+ const document = digest
178
+ ? await context.authority.queries.document(digest)
179
+ : undefined;
180
+ const request = record(JSON.parse(document ?? "null"));
181
+ return record(request?.["submission"]) ?? null;
182
+ }
174
183
  async function listed(context, page) {
175
184
  const identifiers = Object.keys(page.snapshot.state.runs)
176
185
  .sort()
@@ -181,29 +190,44 @@ async function listed(context, page) {
181
190
  views.push(await viewOf(context, { snapshot: page.snapshot, runId }));
182
191
  const runs = views.filter((view) => view !== undefined).map(summary);
183
192
  return result("run-list", {
193
+ ...(await queueOverview(context)),
184
194
  workflows: context.registry.workflowIds,
195
+ workflowRefusals: context.workflowRefusals ?? [],
196
+ ...(Object.keys(context.registry.workflowStages).length === 0
197
+ ? {}
198
+ : {
199
+ workflowStages: context.registry.workflowStages,
200
+ }),
185
201
  runs,
186
202
  next: identifiers.length > shown.length ? (shown.at(-1) ?? null) : null,
187
203
  worldReports: context.worldReports?.() ?? [],
188
204
  });
189
205
  }
190
- export async function status(context, parameters) {
191
- const { runId } = parameters;
206
+ function listParameters(parameters) {
192
207
  const after = parameters["after"] ?? "";
193
208
  const limit = parameters["limit"] ?? 50;
194
209
  if (typeof after !== "string" || !wholeNumber(limit, 1) || limit > 500)
210
+ return undefined;
211
+ return { after, limit };
212
+ }
213
+ export async function status(context, parameters) {
214
+ if (parameters["runIds"] !== undefined)
215
+ return queueRunStatuses(context, parameters["runIds"]);
216
+ const { runId } = parameters;
217
+ const page = listParameters(parameters);
218
+ if (page === undefined)
195
219
  return invalidRequest("The status request is not valid.");
196
220
  const current = await snapshot(context);
197
221
  if (runId === undefined)
198
- return listed(context, { snapshot: current, after, limit });
222
+ return listed(context, { snapshot: current, ...page });
199
223
  const named = typeof runId === "string" ? runId : "";
200
224
  const view = await viewOf(context, { snapshot: current, runId: named });
201
225
  if (view === undefined)
202
226
  return failed("unknown-run", { detail: `No run has the ID ${named}.` });
203
- const claudeCostUsd = await claudeCost(context, view.runId);
227
+ const totals = await usageTotals(context, view.runId);
204
228
  return result("run-status", {
205
229
  run: {
206
- ...full({ ...view, claudeCostUsd }),
230
+ ...full({ ...view, ...totals }),
207
231
  outcome: await outcomeOf(context, view),
208
232
  ...(await metadataOf(context, {
209
233
  workflowId: view.workflowId,
@@ -1,4 +1,5 @@
1
1
  import { type Authority, type AuthorityTiming, type Delivery, type EffectSettings, type Refusal } from "../broker/actor/authority.ts";
2
+ import { type WorkflowRefusal } from "../workflow-refusals.ts";
2
3
  import { type WorkflowRegistration } from "./registry.ts";
3
4
  import { type WorkerPort } from "./worker-port.ts";
4
5
  export type { WorkflowRegistration } from "./registry.ts";
@@ -38,6 +39,8 @@ export interface ServiceOptions {
38
39
  readonly stateDirectory: string;
39
40
  readonly runtimeDirectory: string;
40
41
  readonly workflows: readonly WorkflowRegistration[];
42
+ readonly retainedWorkflows?: readonly WorkflowRegistration[];
43
+ readonly workflowRefusals?: readonly WorkflowRefusal[];
41
44
  readonly delivery: Delivery;
42
45
  readonly host?: (ports: HostPorts) => Promise<ServiceHost>;
43
46
  readonly timing?: ServiceTiming;
@@ -2,6 +2,7 @@ import { lstat, rm } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { authorityTiming, openAuthority, } from "../broker/actor/authority.js";
4
4
  import { socketAddress } from "../broker/socket-address.js";
5
+ import { workflowRefusals, } from "../workflow-refusals.js";
5
6
  import { changeSignal } from "./changes.js";
6
7
  import { decision, questions } from "./decision-handler.js";
7
8
  import { events } from "./event-handler.js";
@@ -87,6 +88,7 @@ async function started(authority, setup) {
87
88
  handle: dispatcher({
88
89
  authority,
89
90
  registry,
91
+ workflowRefusals: workflowRefusals(options.workflowRefusals),
90
92
  gateway,
91
93
  clock,
92
94
  changed: changes.next,
@@ -133,7 +135,7 @@ export async function startService(options) {
133
135
  if (typeof path !== "string")
134
136
  return path;
135
137
  const timing = options.timing ?? serviceTiming;
136
- const registry = workflowRegistry(options.workflows);
138
+ const registry = workflowRegistry(options.workflows, options.retainedWorkflows);
137
139
  const opening = await openAuthority(options.stateDirectory, {
138
140
  delivery: options.delivery,
139
141
  timing: timing.authority,
@@ -1,4 +1,5 @@
1
1
  import { type Fields, type Reply } from "../protocol.ts";
2
2
  import { type ServiceContext } from "./replies.ts";
3
3
  export declare function runIdentity(commandId: string): string;
4
+ export declare function queueOverview(context: ServiceContext): Promise<Fields>;
4
5
  export declare function submissionHandler(context: ServiceContext): (parameters: Fields) => Promise<Reply>;
@@ -1,10 +1,12 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { handoffChain } from "../authority/handoff-chain.js";
3
+ import { executionCapabilities } from "../execution-pin.js";
2
4
  import { setTimeout as delay } from "node:timers/promises";
3
- import { isIdentifier as isWorkflowIdentifier } from "../authority/commands.js";
5
+ import { isCommandId, isIdentifier as isWorkflowIdentifier, } from "../authority/commands.js";
4
6
  import { documentDigest } from "../broker/decision-restore.js";
5
7
  import { record } from "../protocol.js";
6
8
  import { checkResolution, checkSubmission, normalizedRequest, } from "../submission.js";
7
- import { committed, failed, invalidRequest, isIdentifier, publicReceipt, rejection, result, } from "./replies.js";
9
+ import { committed, failed, invalidRequest, publicReceipt, rejection, result, } from "./replies.js";
8
10
  export function runIdentity(commandId) {
9
11
  return `run-${createHash("sha256").update(commandId).digest("hex").slice(0, 32)}`;
10
12
  }
@@ -49,7 +51,7 @@ function effectBudget(workflow) {
49
51
  return {};
50
52
  return {
51
53
  effectLimit: workflow.effectLimit,
52
- capabilities: JSON.stringify([...workflow.manifest.capabilities].sort()),
54
+ capabilities: executionCapabilities(workflow.manifest),
53
55
  };
54
56
  }
55
57
  function usable(workflow, workflowId) {
@@ -73,7 +75,13 @@ async function resolved(context, admission) {
73
75
  const { workflow, workflowId, commandId, submission } = admission;
74
76
  const limit = new AbortController();
75
77
  const timer = new AbortController();
76
- const request = { workflowId, submission, commandId, signal: limit.signal };
78
+ const request = {
79
+ workflowId,
80
+ submission,
81
+ commandId,
82
+ signal: limit.signal,
83
+ activeTickets: admission.activeTickets,
84
+ };
77
85
  const call = Promise.resolve(request)
78
86
  .then((given) => workflow.resolver.resolve(given))
79
87
  .then((value) => checkResolution(value) ?? "failure")
@@ -110,6 +118,9 @@ async function admit(context, admission) {
110
118
  return unresolved(context, { kind: resolution, commandId });
111
119
  if (resolution.kind === "rejected")
112
120
  return failed(resolution.code, { commandId, detail: resolution.detail });
121
+ const limited = capacityRefusal(workflow, admission);
122
+ if (limited !== undefined)
123
+ return limited;
113
124
  const { input } = resolution;
114
125
  if (Buffer.byteLength(input) > context.documentBytes)
115
126
  return unresolved(context, { kind: "failure", commandId });
@@ -124,6 +135,12 @@ async function admit(context, admission) {
124
135
  input: documentDigest(input),
125
136
  resolver: `${workflow.resolver.id}:${workflow.resolver.version}`,
126
137
  ...effectBudget(workflow),
138
+ ...(admission.chain.kind === "handoff"
139
+ ? {
140
+ parentRunId: admission.chain.parentRunId,
141
+ chainId: admission.chain.chainId,
142
+ }
143
+ : {}),
127
144
  },
128
145
  documents: [normalized, input],
129
146
  });
@@ -137,9 +154,32 @@ async function admit(context, admission) {
137
154
  replayed: receipt.kind === "duplicate",
138
155
  });
139
156
  }
157
+ function admissionRefusal(workflow, request) {
158
+ const { workflowId, commandId, submission, activeTickets } = request;
159
+ if (workflowId === "develop-issue" &&
160
+ typeof submission["ticket"] === "string" &&
161
+ activeTickets.some((input) => input["ticketId"] === submission["ticket"]))
162
+ return failed("ticket-active", {
163
+ commandId,
164
+ detail: "An active run already works on this ticket.",
165
+ });
166
+ if (workflowId === "develop-issue" && submission["ticket"] !== "next")
167
+ return undefined;
168
+ return capacityRefusal(workflow, request);
169
+ }
170
+ function capacityRefusal(workflow, request) {
171
+ if (workflow.admission !== undefined &&
172
+ request.activeTickets.length >=
173
+ (workflow.admission === "when-idle" ? 1 : workflow.admission))
174
+ return failed("workflow-active", {
175
+ commandId: request.commandId,
176
+ detail: `The workflow ${request.workflowId} already has an active run.`,
177
+ });
178
+ return undefined;
179
+ }
140
180
  async function submitted(context, parameters) {
141
181
  const { workflowId, commandId } = parameters;
142
- if (!isWorkflowIdentifier(workflowId) || !isIdentifier(commandId))
182
+ if (!isWorkflowIdentifier(workflowId) || !isCommandId(commandId))
143
183
  return invalidRequest("The workflow ID or the command ID is not valid.");
144
184
  const existing = await context.authority.queries.receipt(commandId);
145
185
  if (existing !== undefined)
@@ -147,6 +187,14 @@ async function submitted(context, parameters) {
147
187
  workflowId,
148
188
  submission: parameters["submission"],
149
189
  });
190
+ const chain = handoffChain(commandId, commandId.startsWith("handoff:")
191
+ ? await context.authority.queries.runOrigins()
192
+ : {});
193
+ if (chain.kind === "refused")
194
+ return failed(chain.reason, {
195
+ commandId,
196
+ detail: "A hand-off chain permits at most five runs and requires a recorded parent.",
197
+ });
150
198
  const found = usable(context.registry.byId(workflowId), workflowId);
151
199
  if (found.kind === "unusable")
152
200
  return found.reply;
@@ -161,32 +209,63 @@ async function submitted(context, parameters) {
161
209
  });
162
210
  if (check.kind === "rejected")
163
211
  return failed(check.code, { commandId, detail: check.detail });
164
- if (found.workflow.admission === "when-idle" &&
165
- (await activeWorkflow(context, workflowId)))
166
- return failed("workflow-active", {
167
- commandId,
168
- detail: `The workflow ${workflowId} already has an active run.`,
169
- });
212
+ const activeTickets = await activeWorkflow(context, workflowId);
213
+ const refusal = admissionRefusal(found.workflow, {
214
+ workflowId,
215
+ commandId,
216
+ submission: check.submission,
217
+ activeTickets,
218
+ });
219
+ if (refusal !== undefined)
220
+ return refusal;
170
221
  return admit(context, {
222
+ chain,
171
223
  workflow: found.workflow,
172
224
  workflowId,
173
225
  commandId,
174
226
  submission: check.submission,
175
227
  normalized: check.normalized,
228
+ activeTickets,
176
229
  });
177
230
  }
178
231
  async function activeWorkflow(context, workflowId) {
179
232
  const workflows = await context.authority.queries.workflowIds();
180
233
  const state = await context.authority.state();
181
- return Object.values(state.runs).some((run) => workflows[run.runId] === workflowId &&
234
+ const runs = Object.values(state.runs).filter((run) => workflows[run.runId] === workflowId &&
182
235
  !["completed", "failed", "cancelled"].includes(run.status));
236
+ return Promise.all(runs.map(async (run) => {
237
+ const digest = state.details[run.runId]?.input;
238
+ const text = digest
239
+ ? await context.authority.queries.document(digest)
240
+ : undefined;
241
+ return record(JSON.parse(text ?? "null")) ?? {};
242
+ }));
243
+ }
244
+ export async function queueOverview(context) {
245
+ const workflow = context.registry.byId("develop-issue");
246
+ if (workflow?.admission === undefined)
247
+ return {};
248
+ const active = await activeWorkflow(context, "develop-issue");
249
+ return {
250
+ queue: {
251
+ lanes: workflow.admission === "when-idle" ? 1 : workflow.admission,
252
+ busy: active.length,
253
+ skipped: [
254
+ ...active.flatMap((input) => typeof input["ticketId"] === "string"
255
+ ? [{ ticketId: input["ticketId"], reason: "ticket-active" }]
256
+ : []),
257
+ ...(workflow.resolver.skippedTickets?.() ?? []),
258
+ ],
259
+ },
260
+ };
183
261
  }
184
262
  export function submissionHandler(context) {
185
263
  const pending = new Map();
186
264
  return (parameters) => {
187
265
  const keys = [`command:${String(parameters["commandId"])}`];
188
266
  const workflowId = String(parameters["workflowId"]);
189
- if (context.registry.byId(workflowId)?.admission === "when-idle")
267
+ if (workflowId === "develop-issue" ||
268
+ context.registry.byId(workflowId)?.admission !== undefined)
190
269
  keys.push(`workflow:${workflowId}`);
191
270
  const next = () => submitted(context, parameters);
192
271
  const previous = keys
@@ -0,0 +1,3 @@
1
+ import { type WorkerTotals } from "../worker-usage.ts";
2
+ import type { ServiceContext } from "./replies.ts";
3
+ export declare function usageTotals(context: ServiceContext, runId: string): Promise<WorkerTotals>;
@@ -0,0 +1,52 @@
1
+ import { record } from "../protocol.js";
2
+ import { priceOf, usageOf, tokenCount, workerTotals, } from "../worker-usage.js";
3
+ function reportFields(text) {
4
+ try {
5
+ return record(JSON.parse(text ?? "null")) ?? {};
6
+ }
7
+ catch {
8
+ return {};
9
+ }
10
+ }
11
+ function attemptsOf(value) {
12
+ if (!Array.isArray(value))
13
+ return undefined;
14
+ return value.flatMap((entry) => {
15
+ const fields = record(entry) ?? {};
16
+ const attempt = tokenCount(fields["attempt"]);
17
+ const model = fields["model"];
18
+ if (attempt === null ||
19
+ attempt === 0 ||
20
+ (model !== null && typeof model !== "string"))
21
+ return [];
22
+ return [
23
+ {
24
+ attempt,
25
+ model,
26
+ usage: usageOf(fields["usage"]),
27
+ costUsd: priceOf(fields["costUsd"]),
28
+ estimatedCostUsd: priceOf(fields["estimatedCostUsd"]),
29
+ },
30
+ ];
31
+ });
32
+ }
33
+ export async function usageTotals(context, runId) {
34
+ const { effects, queries } = context.authority;
35
+ const operations = (await effects.operations(runId)).filter((operation) => operation.capability === "claude" &&
36
+ ["completed", "failed", "stopped", "cancelled", "refused"].includes(operation.status));
37
+ const turns = [];
38
+ for (const operation of operations) {
39
+ const report = reportFields(operation.result === null
40
+ ? undefined
41
+ : await queries.document(operation.result));
42
+ const attempts = attemptsOf(report["attempts"]);
43
+ turns.push({
44
+ ...(attempts === undefined ? {} : { attempts }),
45
+ operationId: operation.operationId,
46
+ usage: usageOf(report["usage"]),
47
+ costUsd: priceOf(report["costUsd"]),
48
+ estimatedCostUsd: priceOf(report["estimatedCostUsd"]),
49
+ });
50
+ }
51
+ return workerTotals(turns);
52
+ }
@@ -39,6 +39,14 @@ export interface GateRequest {
39
39
  readonly gateId: string;
40
40
  readonly kind: "question" | "approval";
41
41
  readonly prompt: string;
42
+ readonly expiresAt?: string;
43
+ }
44
+ export type HandoffValue = string | number | boolean | null | readonly HandoffValue[] | {
45
+ readonly [key: string]: HandoffValue;
46
+ };
47
+ export interface HandoffRequest {
48
+ readonly workflowId: string;
49
+ readonly input: Readonly<Record<string, HandoffValue>>;
42
50
  }
43
51
  export type GateResult = {
44
52
  readonly kind: "opened";
@@ -53,5 +61,6 @@ export type GateResult = {
53
61
  export interface StepBridge {
54
62
  readonly effect: (request: ProtectedRequest) => Promise<ProtectedResult>;
55
63
  readonly gate: (request: GateRequest) => Promise<GateResult>;
64
+ readonly handoff?: (request: HandoffRequest) => Promise<ProtectedResult>;
56
65
  }
57
66
  export declare function isStepBridge(value: unknown): value is StepBridge;
@@ -0,0 +1,2 @@
1
+ import { type HandoffRequest } from "../step-bridge.ts";
2
+ export declare function handoff(request: HandoffRequest): Promise<string>;
@@ -0,0 +1,13 @@
1
+ import { isStepBridge, stepBridgeKey, } from "../step-bridge.js";
2
+ export async function handoff(request) {
3
+ "use step";
4
+ const bridge = Reflect.get(globalThis, Symbol.for(stepBridgeKey));
5
+ if (!isStepBridge(bridge) || bridge.handoff === undefined)
6
+ throw new Error("cloving-host-unavailable");
7
+ const result = await bridge.handoff(request);
8
+ if (result.kind !== "completed")
9
+ throw new Error(result.reason ?? result.kind);
10
+ if (result.result === null)
11
+ throw new Error("handoff-result-missing");
12
+ return result.result;
13
+ }
@@ -0,0 +1 @@
1
+ export declare function hardDependencies(ticket: string): readonly string[];
@@ -0,0 +1,15 @@
1
+ export function hardDependencies(ticket) {
2
+ const lines = ticket.split(/\r?\n/);
3
+ const start = lines.findIndex((line) => /^## Dependencies\s*$/.test(line));
4
+ if (start === -1)
5
+ return [];
6
+ const following = lines.slice(start + 1);
7
+ const end = following.findIndex((line) => /^#{1,2} /.test(line));
8
+ const section = (end === -1 ? following : following.slice(0, end)).join("\n");
9
+ const sentences = section
10
+ .replace(/\b(?:e\.g|i\.e)\./g, "example")
11
+ .matchAll(/(?:^|[.!?]\s+|\n)\s*(?:[-*+]\s+|\d+\.\s+)?Needs\b([\s\S]*?)(?=[.!?](?:\s|$)|$)/g);
12
+ return [
13
+ ...new Set([...sentences].flatMap((sentence) => [...String(sentence[1]).matchAll(/#([1-9]\d*)\b/g)].map((match) => String(match[1])))),
14
+ ];
15
+ }
@@ -0,0 +1,8 @@
1
+ export type TicketStatus = {
2
+ readonly kind: "complete";
3
+ } | {
4
+ readonly kind: "partial";
5
+ readonly remaining: readonly string[];
6
+ };
7
+ export declare function ticketStatus(summary: string, preserved?: unknown): TicketStatus;
8
+ export declare function isTicketStatus(value: unknown): value is "complete" | "partial";
@@ -0,0 +1,48 @@
1
+ function parsedStatus(summary) {
2
+ const lines = summary.trimEnd().split(/\r?\n/);
3
+ if (lines.filter((line) => /^Ticket status:/i.test(line)).length !== 1)
4
+ return undefined;
5
+ const status = lines.pop();
6
+ if (status === "Ticket status: complete")
7
+ return { kind: "complete" };
8
+ if (status !== "Ticket status: partial")
9
+ return undefined;
10
+ const heading = lines.lastIndexOf("Remaining:");
11
+ const items = heading === -1
12
+ ? []
13
+ : lines.slice(heading + 1).filter((line) => line.trim() !== "");
14
+ if (items.length === 0 || items.some((line) => !/^- \S.*$/.test(line)))
15
+ return {
16
+ kind: "partial",
17
+ remaining: ["The worker report has an unreadable Remaining: list."],
18
+ };
19
+ return { kind: "partial", remaining: items.map((line) => line.slice(2)) };
20
+ }
21
+ function preservedStatus(value) {
22
+ if (typeof value !== "object" || value === null)
23
+ return undefined;
24
+ const kind = Reflect.get(value, "kind");
25
+ if (kind === "complete")
26
+ return { kind };
27
+ const remaining = Reflect.get(value, "remaining");
28
+ if (kind !== "partial" ||
29
+ !Array.isArray(remaining) ||
30
+ remaining.length === 0 ||
31
+ remaining.some((item) => typeof item !== "string" || item.trim() === "" || /[\r\n]/.test(item)))
32
+ return undefined;
33
+ return { kind, remaining: remaining.map(String) };
34
+ }
35
+ export function ticketStatus(summary, preserved) {
36
+ const parsed = preserved === undefined
37
+ ? parsedStatus(summary)
38
+ : preservedStatus(preserved);
39
+ if (parsed !== undefined)
40
+ return parsed;
41
+ return {
42
+ kind: "partial",
43
+ remaining: ["The worker report has no ticket status line."],
44
+ };
45
+ }
46
+ export function isTicketStatus(value) {
47
+ return value === "complete" || value === "partial";
48
+ }
@@ -0,0 +1,21 @@
1
+ import { type WorkerUsage } from "./worker-usage.ts";
2
+ export interface TokenPrice {
3
+ readonly input: number;
4
+ readonly cachedInput: number;
5
+ readonly output: number;
6
+ }
7
+ export type TokenPrices = Readonly<Record<string, TokenPrice>>;
8
+ export declare const builtInPriceTable: {
9
+ readonly date: string;
10
+ readonly source: string;
11
+ readonly prices: TokenPrices;
12
+ };
13
+ export declare function parseTokenPrices(value: string | undefined): TokenPrices;
14
+ export declare function parseFallbackModels(value: string | undefined): readonly string[];
15
+ export declare function priceForModel(prices: TokenPrices, model: string | null): TokenPrice | undefined;
16
+ export declare function commandModel(command: readonly string[]): string | null;
17
+ export declare function replaceCommandModel(command: readonly string[], model: string): readonly string[];
18
+ export declare function estimatedCostUsd(usage: WorkerUsage, pricing: {
19
+ readonly worker: "claude" | "codex";
20
+ readonly price: TokenPrice | undefined;
21
+ }): number | null;