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,7 +1,12 @@
1
1
  import { join } from "node:path";
2
+ import { ticketStatus } from "../ticket-status.js";
3
+ import { sumPrices } from "../worker-usage.js";
4
+ import { analysisWorker } from "./analysis-worker.js";
5
+ import { analysisMethod } from "./analysis-launch.js";
2
6
  import { profileCommand, } from "../turn-profile.js";
3
- import { startTurn, } from "./claude-turn.js";
4
- import { interrupted, located as locatedIn, operationKey, readRecord as read, replied as repliedWith, reportOf, writeRecord as written, } from "./operation-journal.js";
7
+ import {} from "./claude-turn.js";
8
+ import { startRetriedTurn } from "./retrying-turn.js";
9
+ import { interrupted, located, operationKey, readRecord as read, replied, reportOf, writeRecord as written, } from "./operation-journal.js";
5
10
  import { changes, treeFailure, workTree } from "./work-tree.js";
6
11
  export const claudeLimits = {
7
12
  summaryCharacters: 16_000,
@@ -9,9 +14,6 @@ export const claudeLimits = {
9
14
  stopMilliseconds: 2_000,
10
15
  };
11
16
  const worker = "claude";
12
- function located(settings, operationId) {
13
- return locatedIn(settings.journal, operationId);
14
- }
15
17
  function alive(processId) {
16
18
  if (typeof processId !== "number")
17
19
  return false;
@@ -23,14 +25,11 @@ function alive(processId) {
23
25
  return Reflect.get(Object(error), "code") === "EPERM";
24
26
  }
25
27
  }
26
- function replied(key, recorded) {
27
- return repliedWith({ receipt: key, worker }, recorded);
28
- }
29
28
  async function reconciled(parts, intent) {
30
- const { key, folder } = located(parts.settings, intent.operationId);
29
+ const { key, folder } = located(parts.settings.journal, intent.operationId);
31
30
  const result = await read(folder, "result.json");
32
31
  if (result !== undefined)
33
- return replied(key, result);
32
+ return replied({ receipt: key, worker }, result);
34
33
  if (parts.active.has(key))
35
34
  return { kind: "accepted", receipt: key, worker };
36
35
  const started = await read(folder, "started.json");
@@ -40,64 +39,73 @@ async function reconciled(parts, intent) {
40
39
  return { kind: "unknown", evidence: `claude-turn:${key}` };
41
40
  const ending = interrupted("claude-turn-interrupted");
42
41
  await written(folder, { name: "result.json", value: ending });
43
- return replied(key, ending);
42
+ return replied({ receipt: key, worker }, ending);
44
43
  }
45
44
  async function resultText(tree, result) {
46
45
  const fields = {
46
+ ticketStatus: ticketStatus(result.text),
47
47
  summary: result.text.slice(0, claudeLimits.summaryCharacters),
48
48
  ...(await changes(tree)),
49
49
  worktree: tree.path,
50
50
  costUsd: result.costUsd,
51
+ usage: result.usage,
52
+ model: result.model,
53
+ ...(result.attempts === undefined ? {} : { attempts: result.attempts }),
54
+ estimatedCostUsd: sumPrices((result.attempts ?? []).map((attempt) => attempt.estimatedCostUsd)),
51
55
  sessionId: result.sessionId,
52
56
  turns: result.turns,
53
57
  };
54
58
  const text = JSON.stringify(fields);
55
59
  const excess = Buffer.byteLength(text) - claudeLimits.resultBytes;
56
60
  if (excess <= 0)
57
- return text;
61
+ return { text };
58
62
  const kept = Math.max(0, fields.summary.length - excess);
59
- return JSON.stringify({ ...fields, summary: fields.summary.slice(0, kept) });
63
+ const shortened = JSON.stringify({
64
+ ...fields,
65
+ summary: fields.summary.slice(0, kept),
66
+ });
67
+ if (Buffer.byteLength(shortened) <= claudeLimits.resultBytes)
68
+ return { text: shortened };
69
+ const metadata = JSON.stringify({
70
+ ...fields,
71
+ ticketStatus: undefined,
72
+ summary: "",
73
+ });
74
+ return {
75
+ reason: "claude-result-too-large",
76
+ ...(Buffer.byteLength(metadata) <= claudeLimits.resultBytes
77
+ ? { text: metadata }
78
+ : {}),
79
+ };
60
80
  }
61
81
  async function outcome(running, end) {
62
82
  const endedAt = new Date().toISOString();
63
83
  const { result } = end;
64
- const text = result === undefined ? undefined : await resultText(running.tree, result);
65
- if (text !== undefined && end.exitCode === 0 && result?.failed === false)
84
+ const report = result === undefined ? {} : await resultText(running.tree, result);
85
+ const { text, reason } = report;
86
+ if (reason === undefined &&
87
+ text !== undefined &&
88
+ end.exitCode === 0 &&
89
+ result?.failed === false)
66
90
  return { outcome: "completed", result: text, endedAt };
67
91
  if (running.stopping.requested)
68
92
  return { outcome: "stopped", endedAt };
69
93
  return {
70
94
  outcome: "failed",
71
- reason: "claude-turn-failed",
95
+ reason: reason ?? "claude-turn-failed",
72
96
  ...(text === undefined ? {} : { result: text }),
73
97
  endedAt,
74
98
  };
75
99
  }
76
- function reported(started, recorded) {
77
- const { intent, key } = started;
78
- return reportOf({ intent, commandId: `claude-turn:${key}`, worker }, recorded);
79
- }
80
100
  async function concluded(parts, running) {
101
+ const { intent, key } = running;
81
102
  const recorded = await outcome(running, await running.turn.ended);
82
103
  await written(running.folder, { name: "result.json", value: recorded });
83
- await parts.settings.report(reported(running, recorded));
104
+ await parts.settings.report(reportOf({ intent, commandId: `claude-turn:${key}`, worker }, recorded));
84
105
  }
85
106
  async function begun(parts, start) {
86
107
  const { intent, key, folder, tree, prompt } = start;
87
- const record = (processId) => written(folder, {
88
- name: "started.json",
89
- value: {
90
- operationId: intent.operationId,
91
- target: intent.target,
92
- branch: tree.branch,
93
- worktree: tree.path,
94
- startCommit: tree.startCommit,
95
- processId,
96
- startedAt: new Date().toISOString(),
97
- },
98
- });
99
- await record(null);
100
- const turn = startTurn({
108
+ const turn = startRetriedTurn({
101
109
  worker: parts.settings.worker ?? "claude",
102
110
  command: profileCommand(parts.settings.command, start.profile),
103
111
  environment: start.profile.env ?? {},
@@ -105,16 +113,26 @@ async function begun(parts, start) {
105
113
  directory: tree.path,
106
114
  folder,
107
115
  prompt,
116
+ }, {
117
+ ...parts.settings,
118
+ metadata: {
119
+ operationId: intent.operationId,
120
+ target: intent.target,
121
+ branch: tree.branch,
122
+ worktree: tree.path,
123
+ startCommit: tree.startCommit,
124
+ },
108
125
  });
109
126
  const stopping = { requested: false };
110
127
  parts.active.set(key, {
111
128
  turn,
112
129
  stopping,
113
- concluded: concluded(parts, { ...start, turn, stopping })
130
+ concluded: turn
131
+ .then((running) => concluded(parts, { ...start, turn: running, stopping }))
114
132
  .catch(() => undefined)
115
133
  .finally(() => parts.active.delete(key)),
116
134
  });
117
- await record(turn.processId);
135
+ await turn;
118
136
  }
119
137
  async function firstTurn(settings, opening) {
120
138
  const { tree, intent } = opening;
@@ -171,8 +189,11 @@ async function selectedProfile(settings, intent) {
171
189
  async function delivered(parts, call) {
172
190
  const { intent, signal } = call;
173
191
  const { settings } = parts;
192
+ const aborted = () => signal.aborted;
174
193
  if (!turnIntent(intent))
175
194
  return failure("unsupported-effect");
195
+ if (aborted())
196
+ return failure("claude-start-aborted");
176
197
  const known = await reconciled(parts, intent);
177
198
  if (known.kind !== "absent")
178
199
  return known;
@@ -188,22 +209,25 @@ async function delivered(parts, call) {
188
209
  target: intent.target,
189
210
  signal,
190
211
  }).catch(treeFailure);
191
- if (signal.aborted)
212
+ if (aborted())
192
213
  return failure("claude-start-aborted");
193
214
  if (typeof tree === "string")
194
215
  return failure(tree);
195
- const place = located(settings, intent.operationId);
216
+ const place = located(settings.journal, intent.operationId);
196
217
  const first = await firstTurn(settings, { tree, intent });
218
+ if (aborted())
219
+ return failure("claude-start-aborted");
197
220
  await begun(parts, { ...place, intent, tree: first, prompt, profile });
198
221
  return { receipt: place.key, worker };
199
222
  }
200
223
  async function halted(parts, active) {
201
224
  active.stopping.requested = true;
202
- active.turn.stop(parts.settings.stopMilliseconds ?? claudeLimits.stopMilliseconds);
225
+ const turn = await active.turn.catch(() => undefined);
226
+ turn?.stop(parts.settings.stopMilliseconds ?? claudeLimits.stopMilliseconds);
203
227
  await active.concluded;
204
228
  }
205
229
  async function stopped(parts, intent) {
206
- const { key } = located(parts.settings, intent.operationId);
230
+ const { key } = located(parts.settings.journal, intent.operationId);
207
231
  const active = parts.active.get(key);
208
232
  if (active !== undefined)
209
233
  await halted(parts, active);
@@ -212,12 +236,35 @@ async function stopped(parts, intent) {
212
236
  export function claudeWorker(settings) {
213
237
  if (settings.command.length === 0)
214
238
  throw new Error("invalid-claude-command");
215
- const parts = { settings, active: new Map() };
239
+ const parts = {
240
+ settings,
241
+ active: new Map(),
242
+ stopping: new AbortController(),
243
+ };
244
+ const analysis = analysisWorker(settings);
245
+ const guarded = async (intent, run) => {
246
+ if (!turnIntent(intent) ||
247
+ Object.hasOwn(settings.profiles ?? {}, "analyze-chats") ||
248
+ (await runWorkflow(settings, intent.runId)) !== "analyze-chats")
249
+ return failure("analysis-worker-unavailable");
250
+ return run();
251
+ };
216
252
  return {
217
- deliver: (intent, signal) => delivered(parts, { intent, signal }),
218
- reconcile: (intent) => reconciled(parts, intent),
219
- stop: (intent) => stopped(parts, intent),
253
+ deliver: (intent, signal) => intent.method === analysisMethod
254
+ ? guarded(intent, () => analysis.deliver(intent, signal))
255
+ : delivered(parts, {
256
+ intent,
257
+ signal: AbortSignal.any([signal, parts.stopping.signal]),
258
+ }),
259
+ reconcile: (intent, signal) => intent.method === analysisMethod
260
+ ? guarded(intent, () => analysis.reconcile(intent, signal))
261
+ : reconciled(parts, intent),
262
+ stop: (intent, signal) => intent.method === analysisMethod
263
+ ? guarded(intent, () => analysis.stop(intent, signal))
264
+ : stopped(parts, intent),
220
265
  close: async () => {
266
+ parts.stopping.abort();
267
+ await analysis.close();
221
268
  await Promise.all([...parts.active.values()].map((active) => halted(parts, active)));
222
269
  },
223
270
  };
@@ -1,3 +1,4 @@
1
+ import { sumUsage, usageOf } from "../worker-usage.js";
1
2
  function record(value) {
2
3
  return typeof value === "object" && value !== null ? { ...value } : {};
3
4
  }
@@ -43,9 +44,20 @@ function completed(item) {
43
44
  }
44
45
  function updated(event, state) {
45
46
  const type = event["type"];
47
+ if (typeof type === "string" &&
48
+ ["thread.started", "turn.started", "turn.completed"].includes(type))
49
+ state.model = text(event["model"]) || state.model;
46
50
  if (type === "thread.started")
47
51
  state.sessionId = text(event["thread_id"]) || null;
48
52
  if (type === "turn.completed") {
53
+ const usage = record(event["usage"]);
54
+ const current = usageOf({
55
+ input_tokens: usage["input_tokens"],
56
+ cache_read_input_tokens: usage["cached_input_tokens"],
57
+ output_tokens: usage["output_tokens"],
58
+ });
59
+ state.usage =
60
+ state.turns === 0 ? current : sumUsage([state.usage, current]);
49
61
  state.turns += 1;
50
62
  state.error = null;
51
63
  }
@@ -82,8 +94,10 @@ function translated(event, state) {
82
94
  }
83
95
  export function codexStream() {
84
96
  const state = {
97
+ model: null,
85
98
  sessionId: null,
86
99
  turns: 0,
100
+ usage: usageOf(null),
87
101
  failure: null,
88
102
  error: null,
89
103
  message: "",
@@ -105,6 +119,8 @@ export function codexStream() {
105
119
  state.error ||
106
120
  `codex ended with the code ${String(exitCode)}`,
107
121
  total_cost_usd: null,
122
+ ...(state.model === null ? {} : { model: state.model }),
123
+ usage: state.usage,
108
124
  session_id: state.sessionId,
109
125
  num_turns: state.turns,
110
126
  });
@@ -1,9 +1,13 @@
1
1
  import { type WorkflowFunction } from "../host/start-delivery.ts";
2
2
  import { type WorkflowHandler } from "../host/world-host.ts";
3
3
  import type { TurnProfiles } from "../turn-profile.ts";
4
+ import type { TokenPrices } from "../token-prices.ts";
4
5
  import type { DeclaredWorkflow } from "../workflow-declaration.ts";
6
+ import type { WorkflowRefusal } from "../workflow-refusals.ts";
5
7
  import type { Worker } from "./claude-turn.ts";
6
8
  export interface DaemonSettings {
9
+ readonly fallbackModels?: readonly string[];
10
+ readonly tokenPrices?: TokenPrices;
7
11
  readonly worker?: Worker;
8
12
  readonly profiles?: TurnProfiles;
9
13
  readonly directory: string;
@@ -11,6 +15,7 @@ export interface DaemonSettings {
11
15
  readonly repository: string;
12
16
  readonly manifests: Readonly<Record<string, string | undefined>>;
13
17
  readonly declarations?: readonly DeclaredWorkflow[];
18
+ readonly workflowRefusals?: readonly WorkflowRefusal[];
14
19
  readonly workflows: Readonly<Record<string, WorkflowFunction | undefined>>;
15
20
  readonly handler: WorkflowHandler;
16
21
  readonly command: readonly string[];
@@ -19,6 +24,8 @@ export interface DaemonSettings {
19
24
  readonly notifyCommand?: readonly string[];
20
25
  readonly notifyMilliseconds?: number;
21
26
  readonly queueMilliseconds?: number;
27
+ readonly queueLanes?: number;
28
+ readonly resetQueue?: boolean;
22
29
  readonly githubCommand?: readonly string[];
23
30
  readonly checkCommand?: readonly string[];
24
31
  readonly privateTexts?: readonly string[];
@@ -43,4 +50,4 @@ export declare const bridgeTiming: {
43
50
  pollMilliseconds: number;
44
51
  retryMilliseconds: number;
45
52
  };
46
- export declare function startDaemon(settings: DaemonSettings): Promise<DaemonOpening>;
53
+ export declare function startDaemon(givenSettings: DaemonSettings): Promise<DaemonOpening>;
@@ -1,4 +1,4 @@
1
- import { mkdir } from "node:fs/promises";
1
+ import { mkdir, realpath } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { startDelivery, } from "../host/start-delivery.js";
4
4
  import { registerStepBridge, sdkStepCall, stepBridge, } from "../host/step-bridge-host.js";
@@ -6,9 +6,12 @@ import { startHost } from "../host/world-host.js";
6
6
  import { worldLoop } from "../host/world-reconciliation.js";
7
7
  import { cancelled, workflowOutcome, taggedRun, taggedRuns, } from "../host/world-runs.js";
8
8
  import { startService, } from "../service/service.js";
9
+ import { workflowPin } from "../broker/workflow-pin.js";
9
10
  import { claudeWorker } from "./claude-worker.js";
10
11
  import { deliveryRouter } from "./delivery-router.js";
12
+ import { handoffWorker } from "./handoff-worker.js";
11
13
  import { githubWorker } from "./github-worker.js";
14
+ import { chatsWorker } from "./chats-worker.js";
12
15
  import { worldDirectory } from "./host-environment.js";
13
16
  import { issueQueue } from "./issue-queue.js";
14
17
  import { notifyRuns } from "./notify.js";
@@ -71,11 +74,17 @@ function githubOf(settings, given) {
71
74
  }
72
75
  function adapters(settings, options) {
73
76
  const { directory, repository, command } = settings;
74
- const { given, table } = options;
75
- const workflows = Object.fromEntries(table.map((row) => [row.workflowId, settings.workflows[row.functionName]]));
77
+ const { given, table, registrations } = options;
78
+ const functions = Object.fromEntries(table.map((row) => [row.workflowId, settings.workflows[row.functionName]]));
79
+ const workflows = Object.fromEntries(registrations.map((registration) => [
80
+ workflowPin(registration.manifest),
81
+ functions[registration.workflowId],
82
+ ]));
76
83
  const document = async (digest) => (await given).document(digest);
77
84
  const report = async (effect) => (await given).reportEffect(effect);
78
85
  const worker = claudeWorker({
86
+ fallbackModels: settings.fallbackModels ?? [],
87
+ tokenPrices: settings.tokenPrices ?? {},
79
88
  worker: settings.worker ?? "claude",
80
89
  profiles: settings.profiles ?? {},
81
90
  workflowId: async (runId) => (await given).workflowId(runId),
@@ -87,23 +96,46 @@ function adapters(settings, options) {
87
96
  journal: join(directory, "journal"),
88
97
  });
89
98
  const github = githubOf(settings, given);
99
+ const chats = chatsWorker({
100
+ runtimeDirectory: settings.runtimeDirectory,
101
+ document,
102
+ workflowId: async (runId) => (await given).workflowId(runId),
103
+ });
90
104
  const runs = startDelivery({
91
105
  document,
92
106
  runs: taggedRuns,
93
107
  cancel: cancelled,
94
108
  input: async (runId) => (await given).input(runId),
95
- workflow: async (runId) => workflows[String(await (await given).workflowId(runId))],
109
+ workflow: ({ workflow }) => Promise.resolve(workflows[workflow]),
96
110
  });
97
- return { worker, github, runs };
111
+ const handoff = handoffOf(settings, given);
112
+ return { worker, github, runs, chats, handoff };
98
113
  }
99
- export async function startDaemon(settings) {
100
- const { directory } = settings;
114
+ function handoffOf(settings, given) {
115
+ return handoffWorker({
116
+ runtimeDirectory: settings.runtimeDirectory,
117
+ journal: join(settings.directory, "handoffs"),
118
+ document: async (digest) => (await given).document(digest),
119
+ runStatus: async (runId) => (await (await given).state()).runs[runId]?.status,
120
+ });
121
+ }
122
+ async function daemonRegistrations(settings) {
101
123
  const table = ticketRegistrationTable({
102
124
  repository: settings.repository,
103
125
  command: settings.ticketCommand,
104
126
  nextCommand: settings.nextCommand,
127
+ githubCommand: settings.githubCommand,
105
128
  }, settings.declarations);
106
129
  const admitted = await ticketRegistrations(settings.manifests, { table });
130
+ return { table, admitted };
131
+ }
132
+ export async function startDaemon(givenSettings) {
133
+ const settings = {
134
+ ...givenSettings,
135
+ repository: await realpath(givenSettings.repository),
136
+ };
137
+ const { directory } = settings;
138
+ const { table, admitted } = await daemonRegistrations(settings);
107
139
  if (admitted.kind === "rejected")
108
140
  return {
109
141
  kind: "refused",
@@ -111,25 +143,27 @@ export async function startDaemon(settings) {
111
143
  detail: admitted.detail,
112
144
  };
113
145
  const given = Promise.withResolvers();
114
- const { worker, github, runs } = adapters(settings, {
146
+ const { worker, github, runs, chats, handoff } = adapters(settings, {
115
147
  given: given.promise,
116
148
  table,
149
+ registrations: admitted.registrations,
117
150
  });
118
151
  await mkdir(join(directory, "state"), { recursive: true });
119
152
  await mkdir(settings.runtimeDirectory, { recursive: true, mode: 0o700 });
120
153
  const opening = await startService({
121
154
  stateDirectory: join(directory, "state"),
122
155
  runtimeDirectory: settings.runtimeDirectory,
156
+ workflowRefusals: settings.workflowRefusals ?? [],
123
157
  workflows: admitted.registrations.map((registration) => ({
124
158
  ...registration,
125
159
  ...(settings.queueMilliseconds !== undefined &&
126
160
  registration.workflowId === "develop-issue"
127
- ? { admission: "when-idle" }
161
+ ? { admission: settings.queueLanes ?? 1 }
128
162
  : {}),
129
163
  })),
130
164
  delivery: deliveryRouter({
131
165
  runs,
132
- effects: { claude: worker, github },
166
+ effects: { claude: worker, github, chats, handoff },
133
167
  }),
134
168
  effects: { leaseMilliseconds: turnLeaseMilliseconds },
135
169
  host: (ports) => {
@@ -138,7 +172,7 @@ export async function startDaemon(settings) {
138
172
  },
139
173
  });
140
174
  if (opening.kind === "refused") {
141
- await Promise.all([worker.close(), github.close()]);
175
+ await Promise.all([worker.close(), github.close(), chats.close()]);
142
176
  return opening;
143
177
  }
144
178
  return {
@@ -147,11 +181,12 @@ export async function startDaemon(settings) {
147
181
  service: opening.service,
148
182
  worker,
149
183
  github,
184
+ chats,
150
185
  }),
151
186
  };
152
187
  }
153
188
  async function runningDaemon(settings, resources) {
154
- const { service, worker, github } = resources;
189
+ const { service, worker, github, chats } = resources;
155
190
  await github.scan();
156
191
  const notifier = settings.notifyCommand === undefined
157
192
  ? undefined
@@ -163,7 +198,7 @@ async function runningDaemon(settings, resources) {
163
198
  ? {}
164
199
  : { intervalMilliseconds: settings.notifyMilliseconds }),
165
200
  }).catch(async (error) => {
166
- await Promise.all([worker.close(), github.close()]);
201
+ await Promise.all([worker.close(), github.close(), chats.close()]);
167
202
  await service.close();
168
203
  throw error;
169
204
  });
@@ -173,13 +208,15 @@ async function runningDaemon(settings, resources) {
173
208
  directory: settings.directory,
174
209
  runtimeDirectory: settings.runtimeDirectory,
175
210
  intervalMilliseconds: settings.queueMilliseconds,
211
+ lanes: settings.queueLanes ?? 1,
212
+ resetQueue: settings.resetQueue ?? false,
176
213
  });
177
214
  return {
178
215
  socket: service.socket,
179
216
  close: async () => {
180
217
  await notifier?.stop();
181
218
  await queue?.stop();
182
- await Promise.all([worker.close(), github.close()]);
219
+ await Promise.all([worker.close(), github.close(), chats.close()]);
183
220
  await service.close();
184
221
  },
185
222
  };
@@ -25,6 +25,12 @@ export function deliveryRouter(routes) {
25
25
  return runs.reconcile(intent);
26
26
  return workerOf(routes, intent)?.reconcile(intent, signal) ?? refusal();
27
27
  },
28
+ retry: (intent, signal) => {
29
+ if (!protectedEffect(intent))
30
+ return refusal();
31
+ return (workerOf(routes, intent)?.retry?.(intent, signal) ??
32
+ Promise.resolve({ kind: "unknown" }));
33
+ },
28
34
  stop: (intent, signal) => {
29
35
  if (!protectedEffect(intent))
30
36
  return runs.stop(intent);
@@ -24,6 +24,10 @@ export interface Finding {
24
24
  readonly line: number | null;
25
25
  readonly body: string;
26
26
  }
27
+ export declare function githubText(text: string, settings: {
28
+ readonly environment?: NodeJS.ProcessEnv;
29
+ readonly privateTexts?: readonly string[];
30
+ }): string;
27
31
  export declare function calls(settings: CallSettings, signal: AbortSignal): Calls;
28
32
  export declare function scoreOf(comments: unknown, commit: string): Scored | undefined;
29
33
  export declare function findingsOf(comments: unknown, commit: string): readonly Finding[];
@@ -22,17 +22,26 @@ function privateEnvironment(environment) {
22
22
  function failureDetail(error, place) {
23
23
  const stderr = field(error, "stderr");
24
24
  const text = typeof stderr === "string" ? stderr : "";
25
- const secrets = Object.entries(place.environment)
25
+ return {
26
+ detail: githubText(text, place).slice(-2_000),
27
+ status: Number(/\(HTTP (\d{3})\)/.exec(text)?.[1]),
28
+ };
29
+ }
30
+ export function githubText(text, settings) {
31
+ const environment = { ...process.env, ...settings.environment };
32
+ const secrets = Object.entries(environment)
26
33
  .filter(([name]) => /TOKEN|SECRET|PASSWORD|KEY/i.test(name))
27
34
  .map(([, value]) => value);
28
- const hidden = [...secrets, ...place.privateTexts]
35
+ const privateTexts = [
36
+ ...(settings.privateTexts ?? []),
37
+ ...privateEnvironment(environment),
38
+ ];
39
+ const hidden = [...secrets, ...privateTexts]
29
40
  .filter((value) => typeof value === "string" && value !== "")
30
41
  .sort((left, right) => right.length - left.length);
31
- const detail = hidden.reduce((redacted, value) => redacted.replaceAll(value, "[hidden]"), text);
32
- return {
33
- detail: detail.slice(-2_000),
34
- status: Number(/\(HTTP (\d{3})\)/.exec(text)?.[1]),
35
- };
42
+ return hidden.reduce((redacted, value) => redacted.replaceAll(privateTexts.includes(value)
43
+ ? new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi")
44
+ : value, "[hidden]"), text);
36
45
  }
37
46
  async function output(command, place) {
38
47
  try {
@@ -0,0 +1,6 @@
1
+ import { type Operation } from "./github-operation.ts";
2
+ export interface CiResult {
3
+ readonly reason: "ci-failed" | "ci-pending";
4
+ readonly output: string;
5
+ }
6
+ export declare function waitForCi(operation: Operation, commit: string): Promise<CiResult | undefined>;