@alecsibilia/luca 14.0.0-alpha.2 → 14.0.0-alpha.21

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 (321) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +7 -2
  3. package/board/client/board-look.ts +31 -1
  4. package/board/client/board-panel-parts.tsx +150 -0
  5. package/board/client/board-panel-stages.ts +99 -0
  6. package/board/client/board-panel-styles.ts +223 -0
  7. package/board/client/board-panel.tsx +77 -1275
  8. package/board/client/engine-life.ts +48 -0
  9. package/board/client/final-review-stack.tsx +144 -0
  10. package/board/client/heartbeat.tsx +61 -0
  11. package/board/client/helper-lines.tsx +69 -0
  12. package/board/client/live-feed.tsx +86 -0
  13. package/board/client/live-line.ts +73 -0
  14. package/board/client/model-line.tsx +178 -0
  15. package/board/client/needs-you-section.tsx +133 -0
  16. package/board/client/one-line.tsx +193 -0
  17. package/board/client/repo-wait-banner.tsx +69 -0
  18. package/board/client/run-footer.tsx +68 -0
  19. package/board/client/run-header.tsx +189 -0
  20. package/board/client/run-picker.tsx +98 -0
  21. package/board/client/status-bar.tsx +131 -0
  22. package/board/client/step-dots.tsx +127 -0
  23. package/board/client/stuck-actions.tsx +269 -0
  24. package/board/client/ticket-card-view.tsx +238 -0
  25. package/board/client/ticket-detail.tsx +87 -0
  26. package/board/client/ticket-stack.tsx +119 -0
  27. package/board/client/ticket-wait.ts +188 -0
  28. package/board/client/timeline-rows.tsx +41 -14
  29. package/board/client/usage-line.tsx +51 -0
  30. package/board/client/wait-line.tsx +39 -0
  31. package/board/index.client.tsx +1 -1
  32. package/board/index.server.ts +7 -1
  33. package/board/package.json +1 -1
  34. package/board/server/board-panel-state-helpers.ts +67 -0
  35. package/board/server/board-server-check.ts +294 -0
  36. package/board/server/board-server-engine.ts +129 -0
  37. package/board/server/board-server-events.ts +134 -0
  38. package/board/server/board-server-read.ts +190 -0
  39. package/board/server/board-server-resume.ts +164 -0
  40. package/board/server/board-server-start.ts +174 -0
  41. package/board/server/board-server-store.ts +281 -0
  42. package/board/server/board-server-types.ts +114 -0
  43. package/board/server/board-server-util.ts +4 -0
  44. package/board/server/board-server.ts +52 -943
  45. package/board/server/board-vocabulary-engine.ts +144 -0
  46. package/board/server/board-vocabulary-review.ts +115 -0
  47. package/board/server/board-vocabulary-run.ts +145 -0
  48. package/board/server/board-vocabulary-shapes.ts +153 -0
  49. package/board/server/board-vocabulary.ts +20 -375
  50. package/board/server/current-step-panel-helpers.ts +108 -0
  51. package/board/server/describe-record.ts +403 -0
  52. package/board/server/engine-launch.ts +20 -3
  53. package/board/server/engine-restart-helpers.ts +105 -0
  54. package/board/server/engine-watch.ts +45 -5
  55. package/board/server/journal-rebuild-helpers.ts +169 -0
  56. package/board/server/live-file.ts +130 -0
  57. package/board/server/luca-version.ts +1 -1
  58. package/board/server/make-rows.ts +14 -629
  59. package/board/server/record-texts.ts +350 -0
  60. package/board/server/reduce-board-agent-start.ts +224 -0
  61. package/board/server/reduce-board-agents.ts +160 -0
  62. package/board/server/reduce-board-counters.ts +138 -0
  63. package/board/server/reduce-board-create.ts +148 -0
  64. package/board/server/reduce-board-final-review.ts +174 -0
  65. package/board/server/reduce-board-needs-you.ts +283 -0
  66. package/board/server/reduce-board-reasons.ts +59 -0
  67. package/board/server/reduce-board-run.ts +255 -0
  68. package/board/server/reduce-board-settle.ts +88 -0
  69. package/board/server/reduce-board-steps.ts +226 -0
  70. package/board/server/reduce-board-text.ts +139 -0
  71. package/board/server/reduce-board-ticket-checks.ts +168 -0
  72. package/board/server/reduce-board-ticket-join.ts +144 -0
  73. package/board/server/reduce-board-ticket-start.ts +132 -0
  74. package/board/server/reduce-board-update.ts +117 -0
  75. package/board/server/reduce-board-usage.ts +247 -0
  76. package/board/server/reduce-board.ts +100 -1917
  77. package/board/server/reply-post.ts +407 -0
  78. package/board/server/run-registry.ts +8 -4
  79. package/board/server/ticket-stages-panel-helpers.ts +296 -0
  80. package/board/shared/board-rows.ts +9 -0
  81. package/board/shared/board-rpc.ts +42 -0
  82. package/board/shared/board-state-counts.ts +77 -0
  83. package/board/shared/board-state-live.ts +99 -0
  84. package/board/shared/board-state-review.ts +59 -0
  85. package/board/shared/board-state-run.ts +236 -0
  86. package/board/shared/board-state-ticket.ts +257 -0
  87. package/board/shared/board-state-usage.ts +107 -0
  88. package/board/shared/board-state.ts +97 -567
  89. package/board/shared/reply-actions.ts +207 -0
  90. package/engine/agents/agent-launcher.ts +27 -4
  91. package/engine/agents/claude-launcher-helpers.ts +233 -0
  92. package/engine/agents/claude-launcher.ts +28 -669
  93. package/engine/agents/claude-messages.ts +212 -0
  94. package/engine/agents/claude-options.ts +43 -8
  95. package/engine/agents/common-instructions.ts +138 -0
  96. package/engine/agents/input-channel.ts +36 -0
  97. package/engine/agents/launch-paths.ts +29 -0
  98. package/engine/agents/launch-session.ts +227 -0
  99. package/engine/agents/learner-instructions.ts +31 -0
  100. package/engine/agents/open-session.ts +210 -0
  101. package/engine/agents/prompt-sections.ts +95 -0
  102. package/engine/agents/rejoin-prompts.ts +135 -0
  103. package/engine/agents/reviewer-instructions.ts +99 -0
  104. package/engine/agents/role-instructions.ts +31 -244
  105. package/engine/agents/role-prompts.ts +39 -109
  106. package/engine/agents/role-results.ts +106 -7
  107. package/engine/agents/scripted-launcher.ts +21 -6
  108. package/engine/agents/session-pool.ts +124 -0
  109. package/engine/agents/turn-progress.ts +283 -0
  110. package/engine/agents/writer-instructions.ts +151 -0
  111. package/engine/cli/computer-adapters-real.ts +15 -3
  112. package/engine/cli/computer-adapters.ts +2 -0
  113. package/engine/cli/computer-checks.ts +87 -4
  114. package/engine/cli/config-reload-text.ts +39 -0
  115. package/engine/cli/decision-model-setup.ts +70 -0
  116. package/engine/cli/doctor-command.ts +1 -0
  117. package/engine/cli/doctor.ts +21 -3
  118. package/engine/cli/going-runs.ts +192 -36
  119. package/engine/cli/init-command.ts +1 -0
  120. package/engine/cli/init.ts +23 -1
  121. package/engine/cli/live-runs.ts +73 -0
  122. package/engine/cli/luca-run.ts +42 -7
  123. package/engine/cli/luca-skills.ts +183 -0
  124. package/engine/cli/luca.ts +8 -1
  125. package/engine/cli/models-command.ts +119 -0
  126. package/engine/cli/read-run.ts +81 -0
  127. package/engine/cli/run-args.ts +33 -6
  128. package/engine/cli/run-demo.ts +143 -0
  129. package/engine/cli/run-end.ts +184 -0
  130. package/engine/cli/run-modes-helpers.ts +81 -0
  131. package/engine/cli/run-modes.ts +62 -439
  132. package/engine/cli/setup.ts +5 -48
  133. package/engine/cli/unfinished-runs.ts +122 -0
  134. package/engine/cli/upgrade-command.ts +212 -30
  135. package/engine/cli/upgrade-finish.ts +149 -0
  136. package/engine/cli/upgrade.ts +112 -71
  137. package/engine/config/config-fields.ts +127 -0
  138. package/engine/config/config-reload.ts +250 -0
  139. package/engine/config/engine-config-schema.ts +235 -0
  140. package/engine/config/engine-config.ts +72 -142
  141. package/engine/config/test-commands.ts +102 -0
  142. package/engine/core/agent-guards-helpers.ts +84 -0
  143. package/engine/core/agent-turn.ts +395 -0
  144. package/engine/core/already-done.ts +44 -1
  145. package/engine/core/build-action.ts +214 -0
  146. package/engine/core/build-context.ts +135 -0
  147. package/engine/core/build-launch.ts +259 -0
  148. package/engine/core/commit-in.ts +282 -0
  149. package/engine/core/decide-build-early.ts +108 -0
  150. package/engine/core/decide-build-ending.ts +145 -0
  151. package/engine/core/decide-build-helpers.ts +87 -0
  152. package/engine/core/decide-build-tickets.ts +207 -0
  153. package/engine/core/decide-build-view.ts +103 -0
  154. package/engine/core/decide-build.ts +101 -1368
  155. package/engine/core/decide-final-fix.ts +203 -0
  156. package/engine/core/decide-final-review.ts +55 -310
  157. package/engine/core/decide-red-green.ts +398 -0
  158. package/engine/core/decide-rejoin.ts +259 -0
  159. package/engine/core/decide-repo-wait.ts +129 -0
  160. package/engine/core/decide-run-budget.ts +86 -12
  161. package/engine/core/decide-stuck-helpers.ts +127 -0
  162. package/engine/core/decide-stuck.ts +60 -8
  163. package/engine/core/decide-ticket-review.ts +95 -0
  164. package/engine/core/decide-ticket.ts +267 -0
  165. package/engine/core/decide-waits.ts +47 -0
  166. package/engine/core/decide.ts +74 -23
  167. package/engine/core/engine-chores.ts +163 -0
  168. package/engine/core/environment-rerun.ts +83 -0
  169. package/engine/core/evidence-text.ts +84 -0
  170. package/engine/core/execute-action.ts +303 -0
  171. package/engine/core/execute-already-done-check.ts +133 -0
  172. package/engine/core/execute-build.ts +68 -944
  173. package/engine/core/execute-changeset.ts +5 -7
  174. package/engine/core/execute-final-review.ts +125 -13
  175. package/engine/core/execute-gates.ts +182 -0
  176. package/engine/core/execute-intake.ts +130 -0
  177. package/engine/core/execute-join.ts +276 -0
  178. package/engine/core/execute-memory.ts +12 -0
  179. package/engine/core/execute-pull-request.ts +84 -0
  180. package/engine/core/execute-red-check.ts +219 -0
  181. package/engine/core/execute-repo-wait.ts +129 -0
  182. package/engine/core/execute-stuck.ts +25 -10
  183. package/engine/core/execute-waits.ts +158 -0
  184. package/engine/core/execute-with-jev.ts +118 -0
  185. package/engine/core/execute.ts +124 -831
  186. package/engine/core/final-failure-cap.ts +38 -0
  187. package/engine/core/final-review-action.ts +125 -0
  188. package/engine/core/final-review-text.ts +63 -17
  189. package/engine/core/final-stuck-text.ts +86 -0
  190. package/engine/core/fix-loop-text.ts +49 -2
  191. package/engine/core/left-out.ts +120 -0
  192. package/engine/core/live-file.ts +372 -0
  193. package/engine/core/loop-caps.ts +25 -2
  194. package/engine/core/not-in-run.ts +7 -3
  195. package/engine/core/pull-request-text.ts +255 -6
  196. package/engine/core/repo-wait-text.ts +81 -0
  197. package/engine/core/review-text.ts +55 -1
  198. package/engine/core/run-crash-recovery-helpers.ts +16 -0
  199. package/engine/core/run-session-close-helpers.ts +96 -0
  200. package/engine/core/run-start.ts +148 -0
  201. package/engine/core/run-stuck-text.ts +86 -0
  202. package/engine/core/schedule-rules.ts +193 -0
  203. package/engine/core/stuck-detail-text.ts +89 -0
  204. package/engine/core/stuck-format.ts +22 -0
  205. package/engine/core/stuck-text.ts +29 -332
  206. package/engine/core/ticket-stuck-text.ts +192 -0
  207. package/engine/core/worktree-changes.ts +106 -0
  208. package/engine/gates/already-done-check.ts +120 -0
  209. package/engine/gates/feature-map.ts +96 -0
  210. package/engine/gates/gate-runner.ts +108 -108
  211. package/engine/gates/gate-schemas.ts +61 -2
  212. package/engine/gates/leftover-scan.ts +154 -31
  213. package/engine/gates/new-tests.ts +45 -0
  214. package/engine/gates/own-files-check.ts +98 -0
  215. package/engine/gates/prepare-check.ts +60 -0
  216. package/engine/gates/prepare-slots.ts +99 -0
  217. package/engine/gates/red-check.ts +184 -16
  218. package/engine/gates/shell-check.ts +64 -0
  219. package/engine/gates/slot-waits.ts +55 -0
  220. package/engine/gates/slow-test-runner.ts +59 -0
  221. package/engine/gates/test-checks.ts +161 -0
  222. package/engine/gates/test-runner.ts +164 -5
  223. package/engine/gates/test-slots.ts +48 -0
  224. package/engine/git/git-adapter.ts +55 -396
  225. package/engine/git/git-commits.ts +132 -0
  226. package/engine/git/git-join.ts +139 -0
  227. package/engine/git/git-read.ts +123 -0
  228. package/engine/git/git-run.ts +32 -0
  229. package/engine/git/git-worktrees.ts +143 -0
  230. package/engine/git/outside-links.ts +55 -0
  231. package/engine/guards/after-turn-check.ts +31 -5
  232. package/engine/guards/guard-hook.ts +60 -12
  233. package/engine/guards/guard-roles.ts +68 -0
  234. package/engine/guards/helper-rules.ts +92 -0
  235. package/engine/guards/permission-rules.ts +161 -0
  236. package/engine/guards/role-rules-helpers.ts +48 -0
  237. package/engine/guards/role-rules.ts +34 -606
  238. package/engine/guards/shell-rules.ts +344 -0
  239. package/engine/guards/worktree-git.ts +208 -0
  240. package/engine/guards/worktree-snapshot.ts +412 -0
  241. package/engine/guards/worktree-state.ts +28 -825
  242. package/engine/guards/worktree-undo.ts +260 -0
  243. package/engine/guards/write-rules.ts +149 -0
  244. package/engine/index.ts +30 -9
  245. package/engine/intake/criteria-checks.ts +114 -0
  246. package/engine/intake/intake-checks.ts +164 -30
  247. package/engine/intake/intake-schemas.ts +37 -2
  248. package/engine/jev/clef-client.ts +259 -0
  249. package/engine/jev/decision-model-credentials.ts +212 -0
  250. package/engine/jev/jev-jobs.ts +144 -60
  251. package/engine/jev/jev-schemas.ts +86 -5
  252. package/engine/jev/jev-shadow-helpers.ts +218 -0
  253. package/engine/jev/jev-shadow.ts +109 -64
  254. package/engine/jev/model-pick.ts +275 -0
  255. package/engine/journal/agent-entries.ts +167 -0
  256. package/engine/journal/agent-message-entries.ts +72 -0
  257. package/engine/journal/agent-turn-replay.ts +203 -0
  258. package/engine/journal/already-done-entries.ts +55 -0
  259. package/engine/journal/commit-entries.ts +56 -0
  260. package/engine/journal/cross-ticket-replay.ts +222 -0
  261. package/engine/journal/decision-model-entries.ts +86 -0
  262. package/engine/journal/entry-fields.ts +10 -0
  263. package/engine/journal/final-review-entries.ts +128 -0
  264. package/engine/journal/final-review-replay.ts +324 -0
  265. package/engine/journal/final-review-round.ts +320 -0
  266. package/engine/journal/final-review-state.ts +180 -0
  267. package/engine/journal/gate-entries.ts +81 -0
  268. package/engine/journal/intake-entries.ts +87 -0
  269. package/engine/journal/join-entries.ts +126 -0
  270. package/engine/journal/journal-entry-schema.ts +218 -0
  271. package/engine/journal/journal-kinds.ts +97 -0
  272. package/engine/journal/journal-record-schema.ts +218 -0
  273. package/engine/journal/journal-record.ts +43 -1400
  274. package/engine/journal/journal.ts +6 -1
  275. package/engine/journal/limit-entries.ts +90 -0
  276. package/engine/journal/memory-entries.ts +124 -0
  277. package/engine/journal/memory-replay.ts +157 -0
  278. package/engine/journal/plan-replay.ts +139 -0
  279. package/engine/journal/pull-request-entries.ts +41 -0
  280. package/engine/journal/replay.ts +157 -2144
  281. package/engine/journal/reply-replay.ts +144 -0
  282. package/engine/journal/repo-wait-entries.ts +53 -0
  283. package/engine/journal/run-entries.ts +172 -0
  284. package/engine/journal/run-state.ts +264 -0
  285. package/engine/journal/shared-failure-replay.ts +133 -0
  286. package/engine/journal/stuck-entries.ts +193 -0
  287. package/engine/journal/ticket-progress.ts +392 -0
  288. package/engine/journal/ticket-replay.ts +411 -0
  289. package/engine/journal/ticket-result-replay.ts +364 -0
  290. package/engine/journal/ticket-retry-replay.ts +142 -0
  291. package/engine/journal/worktree-entries.ts +122 -0
  292. package/engine/models/model-score.ts +512 -0
  293. package/engine/shell/run-command.ts +187 -18
  294. package/engine/testing/agent-turn-fixtures.ts +287 -0
  295. package/engine/testing/already-done-fixtures.ts +97 -0
  296. package/engine/testing/build-fixtures.ts +68 -973
  297. package/engine/testing/checkouts-fixtures.ts +48 -0
  298. package/engine/testing/final-review-fixtures.ts +62 -1
  299. package/engine/testing/final-review-steps.ts +143 -0
  300. package/engine/testing/limit-fixtures.ts +85 -0
  301. package/engine/testing/live-file-fixtures.ts +57 -0
  302. package/engine/testing/practice-repo.ts +10 -4
  303. package/engine/testing/practice-run.ts +1 -1
  304. package/engine/testing/reply-fixtures.ts +88 -0
  305. package/engine/testing/run-budget-fixtures.ts +165 -0
  306. package/engine/testing/run-fixtures.ts +219 -0
  307. package/engine/testing/sdk-play.ts +177 -0
  308. package/engine/testing/setup-repo.ts +194 -0
  309. package/engine/testing/step-record-fixtures.ts +55 -0
  310. package/engine/testing/ticket-step-fixtures.ts +272 -0
  311. package/engine/testing/wait-scenarios.ts +186 -0
  312. package/engine/tracker/gh-retry.ts +217 -0
  313. package/engine/tracker/github-tracker.ts +310 -90
  314. package/engine/tracker/in-memory-tracker.ts +8 -4
  315. package/engine/tracker/tracker.ts +131 -6
  316. package/package.json +2 -1
  317. package/skills/luca-retro/SKILL.md +74 -0
  318. package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
  319. package/skills/luca-unstick/SKILL.md +153 -0
  320. package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
  321. package/engine/jev/jev-client.ts +0 -173
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The owner's replies and the engine's stuck reports in replay: a reply
3
+ * taken for the run, the final review, or a ticket; a report for the stuck
4
+ * run, the final review, or a ticket; and a ticket's `retry`.
5
+ */
6
+ import omit from 'lodash/omit'
7
+
8
+ import type { JournalRecord } from './journal-record'
9
+ import { engineCommentsAfter, type RunState } from './run-state'
10
+ import { applyTicketRecord } from './ticket-replay'
11
+
12
+ import { runBudgetOf } from '../limits/run-budget'
13
+
14
+ /**
15
+ * An owner's reply: `stop` ends the run, a bare `retry` or `ship` is the
16
+ * stuck run's or the final review's, and the rest go to its ticket.
17
+ */
18
+ export const replyReceivedAfter = ({
19
+ state,
20
+ next,
21
+ record,
22
+ }: {
23
+ /** The state before the record. */
24
+ state: RunState
25
+ /** The state with the record's seq and crash counts. */
26
+ next: RunState
27
+ record: Extract<JournalRecord, { kind: 'reply_received' }>
28
+ }): RunState => {
29
+ const handled = {
30
+ ...next,
31
+ handled_comments: [
32
+ ...state.handled_comments,
33
+ record.content.comment_id,
34
+ ],
35
+ }
36
+ const { word, comment_id, ticket } = record.content
37
+ if (word === 'stop') return { ...handled, stop: { comment_id } }
38
+ // A bare `retry` while the run is stuck on its budget adds its top-up
39
+ // (one whole budget for a retry journaled before top-ups, #543), and
40
+ // the run carries on; stuck on its PR, the PR is tried again; stuck on
41
+ // its repo wait, it waits 24 hours more.
42
+ const { stuck } = state.run_budget
43
+ if (ticket === null && word === 'retry' && stuck !== null) {
44
+ const top_up =
45
+ stuck.reason === 'run_budget'
46
+ ? (record.content.top_up ??
47
+ runBudgetOf({ config: state.config }))
48
+ : 0
49
+ return {
50
+ ...handled,
51
+ repo_wait:
52
+ stuck.reason === 'repo_busy'
53
+ ? { status: 'waiting', since: record.time }
54
+ : state.repo_wait,
55
+ run_budget: {
56
+ stuck: null,
57
+ report: null,
58
+ top_ups: state.run_budget.top_ups + top_up,
59
+ },
60
+ }
61
+ }
62
+ if (ticket === null && (word === 'retry' || word === 'ship')) {
63
+ return { ...handled, final_reply: { word, comment_id } }
64
+ }
65
+ return applyTicketRecord({ state: handled, record })
66
+ }
67
+
68
+ /** A stuck report the engine posted, for the run, the final review, or a ticket. */
69
+ export const stuckReportedAfter = ({
70
+ state,
71
+ next,
72
+ record,
73
+ }: {
74
+ state: RunState
75
+ next: RunState
76
+ record: Extract<JournalRecord, { kind: 'stuck_reported' }>
77
+ }): RunState => {
78
+ const reported = {
79
+ ...next,
80
+ engine_comments: engineCommentsAfter({
81
+ state,
82
+ comment_id: record.content.comment_id,
83
+ }),
84
+ }
85
+ // A report with no ticket is the stuck run's, while it is not
86
+ // told yet, else the final review's.
87
+ const { run_budget } = state
88
+ if (
89
+ record.ticket === null &&
90
+ run_budget.stuck !== null &&
91
+ run_budget.report === null
92
+ ) {
93
+ return {
94
+ ...reported,
95
+ run_budget: {
96
+ ...run_budget,
97
+ report: { comment_id: record.content.comment_id },
98
+ },
99
+ }
100
+ }
101
+ return record.ticket === null
102
+ ? {
103
+ ...reported,
104
+ final_stuck_report: {
105
+ comment_id: record.content.comment_id,
106
+ },
107
+ }
108
+ : applyTicketRecord({ state: reported, record })
109
+ }
110
+
111
+ /** A ticket's `retry`, with the run-wide parts it resets. */
112
+ export const ticketRetriedAfter = ({
113
+ state,
114
+ next,
115
+ record,
116
+ }: {
117
+ state: RunState
118
+ next: RunState
119
+ record: Extract<JournalRecord, { kind: 'ticket_retried' }>
120
+ }): RunState => {
121
+ const { answer_id, mode } = record.content
122
+ const install = state.run_branch_install?.check
123
+ // A run branch whose install failed is installed again on a retry.
124
+ const run_branch_install =
125
+ mode === 'refused' || install === undefined || install?.ok !== false
126
+ ? state.run_branch_install
127
+ : null
128
+ return applyTicketRecord({
129
+ state: {
130
+ ...next,
131
+ // A retry gives the ticket fresh crash counts too.
132
+ crashes:
133
+ record.ticket === null
134
+ ? next.crashes
135
+ : omit(next.crashes, String(record.ticket)),
136
+ run_branch_install,
137
+ engine_comments: engineCommentsAfter({
138
+ state,
139
+ comment_id: answer_id,
140
+ }),
141
+ },
142
+ record,
143
+ })
144
+ }
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod'
2
+
3
+ import { ENTRY_FIELDS } from './entry-fields'
4
+
5
+ /**
6
+ * The journal entries of the repo wait (#615): a run started while an
7
+ * earlier run on the same repo is going waits for it, before intake.
8
+ */
9
+
10
+ /**
11
+ * The run waits for the earlier runs on its repo that are going, named by
12
+ * run id and spec (`null` for a demo). The spec issue was told once.
13
+ */
14
+ export const RepoWaitStartedEntrySchema = z.object({
15
+ ...ENTRY_FIELDS,
16
+ kind: z.literal('repo_wait_started'),
17
+ content: z.object({
18
+ runs: z.array(
19
+ z.object({
20
+ run_id: z.string().min(1),
21
+ spec: z.number().int().positive().nullable(),
22
+ })
23
+ ),
24
+ }),
25
+ })
26
+
27
+ /** No earlier run on the repo is going anymore: the run goes on to intake. */
28
+ export const RepoWaitEndedEntrySchema = z.object({
29
+ ...ENTRY_FIELDS,
30
+ kind: z.literal('repo_wait_ended'),
31
+ content: z.object({}),
32
+ })
33
+
34
+ /**
35
+ * The repo wait couldn't check whether the earlier runs are going, since
36
+ * `ps` failed (#624): `error` says how. Journaled once per wait, which goes
37
+ * on, counting every unfinished run as going.
38
+ */
39
+ export const RepoWaitPsFailedEntrySchema = z.object({
40
+ ...ENTRY_FIELDS,
41
+ kind: z.literal('repo_wait_ps_failed'),
42
+ content: z.object({ error: z.string() }),
43
+ })
44
+
45
+ /**
46
+ * The run was started with `luca-run --alongside`, so it never waits for
47
+ * another run on its repo. Journaled right after `run_started`.
48
+ */
49
+ export const RepoWaitSkippedEntrySchema = z.object({
50
+ ...ENTRY_FIELDS,
51
+ kind: z.literal('repo_wait_skipped'),
52
+ content: z.object({}),
53
+ })
@@ -0,0 +1,172 @@
1
+ import { z } from 'zod'
2
+
3
+ import { ENTRY_FIELDS } from './entry-fields'
4
+
5
+ import { AgentRoleSchema } from '../agents/role-results'
6
+ import { ConfigChangeSchema } from '../config/config-reload'
7
+ import { EngineConfigSchema } from '../config/engine-config'
8
+
9
+ /**
10
+ * The journal entries of the run's own life: its start, resumes and config
11
+ * reloads, a stop, and the scheduler's step records.
12
+ */
13
+
14
+ export const RunStartedEntrySchema = z.object({
15
+ ...ENTRY_FIELDS,
16
+ kind: z.literal('run_started'),
17
+ content: z.object({
18
+ spec_number: z.number().int().positive(),
19
+ config: EngineConfigSchema,
20
+ /** The branch the run branch starts from and its PR merges into. */
21
+ base_branch: z.string().min(1).default('main'),
22
+ /**
23
+ * Memory for this run (#370): on, with the project's vault (`null`
24
+ * searches only `default`), or `null` for off. Off leaves the run
25
+ * exactly as it was before memory.
26
+ */
27
+ memory: z
28
+ .object({ project_vault: z.string().min(1).nullable() })
29
+ .nullable()
30
+ .default(null),
31
+ /**
32
+ * The repo the run is on, as an absolute path, so `luca-run --resume`
33
+ * can find it. `null` in older journals.
34
+ */
35
+ repo: z.string().min(1).nullable().default(null),
36
+ /**
37
+ * Luca's version the run started on (#460). `null` in older
38
+ * journals.
39
+ */
40
+ luca_version: z.string().min(1).nullable().default(null),
41
+ }),
42
+ })
43
+
44
+ /**
45
+ * The engine started again on the run's journal (#460), on Luca's version
46
+ * `luca_version`, which may differ from the one in `run_started`. Journaled
47
+ * once per resume, before the engine's first step. Nothing reads it but
48
+ * the board.
49
+ */
50
+ export const EngineResumedEntrySchema = z.object({
51
+ ...ENTRY_FIELDS,
52
+ kind: z.literal('engine_resumed'),
53
+ content: z.object({ luca_version: z.string().min(1) }),
54
+ })
55
+
56
+ /**
57
+ * A resume read the repo's `.luca/config.json` again (#516), and the
58
+ * build fields it reloads (`RELOADED_FIELDS`) changed: each one, old to
59
+ * new. Journaled right after the resume's `engine_resumed`, only when
60
+ * something changed. Replay puts the new values on the run's config.
61
+ */
62
+ export const ConfigReloadedEntrySchema = z.object({
63
+ ...ENTRY_FIELDS,
64
+ kind: z.literal('config_reloaded'),
65
+ content: z.object({ changes: z.array(ConfigChangeSchema).min(1) }),
66
+ })
67
+
68
+ /**
69
+ * A resume could not take the repo's config (#516): it is missing, not
70
+ * JSON, not a valid config, or one intake would refuse. The run keeps the
71
+ * config it had and goes on. `reason` says what is wrong.
72
+ */
73
+ export const ConfigReloadRefusedEntrySchema = z.object({
74
+ ...ENTRY_FIELDS,
75
+ kind: z.literal('config_reload_refused'),
76
+ content: z.object({ reason: z.string().min(1) }),
77
+ })
78
+
79
+ /**
80
+ * The run stopped because going on was unsafe: the wrong credentials or
81
+ * plan, the wrong model, or a foreign MCP server. A later `runEngine` on the
82
+ * same journal picks the step up again.
83
+ *
84
+ * A `billing` stop (overage, or a billing error) sticks: the run never goes
85
+ * on, however often the engine is started again on its journal.
86
+ */
87
+ export const RunStoppedEntrySchema = z.object({
88
+ ...ENTRY_FIELDS,
89
+ kind: z.literal('run_stopped'),
90
+ content: z.object({
91
+ reason: z.string(),
92
+ role: AgentRoleSchema.nullable().default(null),
93
+ billing: z.boolean().default(false),
94
+ /**
95
+ * The same step was cut off by a crash `MAX_CRASHES` times in a row
96
+ * on a run-level key: like a billing stop, it sticks.
97
+ */
98
+ crashed: z.boolean().default(false),
99
+ }),
100
+ })
101
+
102
+ /**
103
+ * The scheduler is about to carry out one step: an action it started, other
104
+ * than a wait. `key` is the scheduler's key for it (a ticket's number,
105
+ * `final`, `lens:<lens>`, `replies`, or `run`), and `step` its action type,
106
+ * plus `:<role>` for an agent's turn. `first_seq` is the `step_started` seq
107
+ * of this step's first try when a crash cut off an earlier try of it, else
108
+ * `null`. `ticket` is the action's ticket, and `role` the agent's role.
109
+ */
110
+ export const StepStartedEntrySchema = z.object({
111
+ ...ENTRY_FIELDS,
112
+ kind: z.literal('step_started'),
113
+ content: z.object({
114
+ key: z.string().min(1),
115
+ step: z.string().min(1),
116
+ first_seq: z.number().int().min(1).nullable().default(null),
117
+ }),
118
+ })
119
+
120
+ /**
121
+ * The step under `key` settled: its own records are in the journal, so it
122
+ * is a checkpoint. Not written when the step threw.
123
+ */
124
+ export const StepEndedEntrySchema = z.object({
125
+ ...ENTRY_FIELDS,
126
+ kind: z.literal('step_ended'),
127
+ content: z.object({
128
+ key: z.string().min(1),
129
+ step: z.string().min(1),
130
+ }),
131
+ })
132
+
133
+ /**
134
+ * A ticket began to wait on purpose between steps (#559): `baseline` for
135
+ * another ticket's shared baseline tests, `join` for the join queue,
136
+ * `start` for the start queue, each on `on_ticket`. Journaled once when the
137
+ * wait starts, since `since`; the ticket's next `step_started` ends it.
138
+ * Only the board reads it.
139
+ */
140
+ export const TicketWaitingEntrySchema = z.object({
141
+ ...ENTRY_FIELDS,
142
+ kind: z.literal('ticket_waiting'),
143
+ content: z.object({
144
+ for: z.enum(['baseline', 'join', 'start']),
145
+ on_ticket: z.number().int().positive().nullable(),
146
+ since: z.iso.datetime(),
147
+ }),
148
+ })
149
+
150
+ /** One step a crash cut off: its `step_started` had no `step_ended`. */
151
+ export const InterruptedStepSchema = z.object({
152
+ key: z.string().min(1),
153
+ step: z.string().min(1),
154
+ ticket: z.number().int().positive().nullable(),
155
+ role: z.string().nullable(),
156
+ /** The seq of the cut-off step's `step_started`. */
157
+ started_seq: z.number().int().min(1),
158
+ /** That record's `first_seq`: the step's first try, if it was a redo. */
159
+ first_seq: z.number().int().min(1).nullable(),
160
+ })
161
+
162
+ export type InterruptedStep = z.infer<typeof InterruptedStepSchema>
163
+
164
+ /**
165
+ * A restarted engine found steps a crash cut off, journaled once before its
166
+ * first step. Each is taken again, an agent's turn in a fresh session.
167
+ */
168
+ export const RunResumedEntrySchema = z.object({
169
+ ...ENTRY_FIELDS,
170
+ kind: z.literal('run_resumed'),
171
+ content: z.object({ interrupted: z.array(InterruptedStepSchema) }),
172
+ })
@@ -0,0 +1,264 @@
1
+ /**
2
+ * The shape of a run's state as replay rebuilds it, with its run-wide parts
3
+ * (snapshot, comments, PR, notes, budget), its empty value, and the small
4
+ * run-wide updates: the snapshot's phase, run notes, and engine comments.
5
+ */
6
+ import { EMPTY_FINAL_REVIEW, type FinalReviewState } from './final-review-state'
7
+ import type { JournalRecord, RunStuckReason } from './journal-record'
8
+ import { EMPTY_MEMORY, type MemoryState } from './memory-replay'
9
+ import type { PlanState } from './plan-replay'
10
+ import type { CrashCounts } from './step-records'
11
+ import type {
12
+ ReplayedGates,
13
+ ReplayedInstall,
14
+ ReplayedWorktree,
15
+ TicketProgress,
16
+ } from './ticket-progress'
17
+
18
+ import type { AgentRole } from '../agents/role-results'
19
+ import type { EngineConfig } from '../config/engine-config'
20
+ import type { TestCaseRef } from '../gates/gate-schemas'
21
+ import type {
22
+ IntakeProblem,
23
+ IntakeRead,
24
+ LeftOutTicket,
25
+ SpecSnapshot,
26
+ TicketSnapshot,
27
+ } from '../intake/intake-schemas'
28
+
29
+ /** Where a run stands, as far as intake goes. */
30
+ export type RunPhase =
31
+ | 'new'
32
+ | 'started'
33
+ | 'intake_read'
34
+ | 'refused'
35
+ | 'nothing_to_do'
36
+ /** The spec snapshot is written but some ticket snapshots are not. */
37
+ | 'snapshotting'
38
+ | 'intake_passed'
39
+
40
+ /** The snapshot as replayed from `spec_snapshot` and `ticket_snapshot`. */
41
+ export type ReplayedSnapshot = {
42
+ spec: SpecSnapshot
43
+ ticket_order: number[]
44
+ closed_tickets: number[]
45
+ /** The latest snapshot of each ticket, keyed by ticket number. */
46
+ tickets: Record<number, TicketSnapshot>
47
+ /** The open tickets intake left out for a person, and why (#499). */
48
+ left_out: LeftOutTicket[]
49
+ }
50
+
51
+ /** A comment the engine read on the spec issue. */
52
+ export type ReplayedComment = {
53
+ comment_id: number
54
+ author: string
55
+ body: string
56
+ }
57
+
58
+ /** The run's pull request, once opened. */
59
+ export type ReplayedPullRequest = { number: number; url: string }
60
+
61
+ /** One run note, with the agent that wrote it. */
62
+ export type ReplayedRunNote = { ticket: number; role: AgentRole; note: string }
63
+
64
+ /**
65
+ * The run budget (#435): whether the run is stuck on it (or on its PR,
66
+ * #508) and told, and how many tokens the owner's `retry` replies added.
67
+ */
68
+ export type RunBudgetState = {
69
+ /** The run is stuck, until the owner's `retry`. */
70
+ stuck: { reason: RunStuckReason; detail: string } | null
71
+ /** The spec issue's comment that told the owner the run is stuck. */
72
+ report: { comment_id: number } | null
73
+ /**
74
+ * Tokens added by `retry` (#543): the run may use its budget plus these.
75
+ * A retry journaled with no top-up added one whole budget.
76
+ */
77
+ top_ups: number
78
+ }
79
+
80
+ /**
81
+ * The repo wait (#615), before intake: not begun (`none`), waiting for an
82
+ * earlier run on the repo, over, or skipped (`luca-run --alongside`).
83
+ */
84
+ export type RepoWaitState = {
85
+ status: 'none' | 'waiting' | 'ended' | 'skipped'
86
+ /**
87
+ * When the wait began, or the owner's `retry` of a run stuck on it
88
+ * (`repo_busy`): its 24 hours count from here.
89
+ */
90
+ since: string | null
91
+ }
92
+
93
+ /** An old test that failed in a ticket's check, at its record's time (#544). */
94
+ export type OldTestFailure = TestCaseRef & { ticket: number; time: string }
95
+
96
+ /** A run's state, rebuilt only from its journal. There is no status file. */
97
+ export type RunState = {
98
+ phase: RunPhase
99
+ spec_number: number | null
100
+ config: EngineConfig | null
101
+ base_branch: string | null
102
+ intake: IntakeRead | null
103
+ problems: IntakeProblem[] | null
104
+ snapshot: ReplayedSnapshot | null
105
+ run_branch: ReplayedWorktree | null
106
+ /** The install in the run branch's checkout, once it ran. */
107
+ run_branch_install: ReplayedInstall | null
108
+ tickets: Record<number, TicketProgress>
109
+ /**
110
+ * The repo had a changesets config when the run branch was made, so a
111
+ * changeset is written before the PR (#461).
112
+ */
113
+ changesets: boolean
114
+ /** The run's changeset, once written and pushed. */
115
+ changeset: { path: string | null; sha: string } | null
116
+ pull_request: ReplayedPullRequest | null
117
+ plan: PlanState
118
+ /**
119
+ * The seq of each ticket's latest usage record (a retried ticket that
120
+ * finishes again gets another), and whether the run's is recorded.
121
+ */
122
+ usage_recorded: { tickets: Record<number, number>; run: boolean }
123
+ /** The worktrees the engine removed at the end of the run. */
124
+ removed_worktrees: string[]
125
+ /**
126
+ * Every session journaled as closed (`agent_session_closed`), or found
127
+ * gone by a follow-up (#521). None is ever a follow-up target again.
128
+ */
129
+ closed_sessions: string[]
130
+ /**
131
+ * Every run note from every finished agent in the run, oldest first
132
+ * (journal order), word for word.
133
+ */
134
+ run_notes: ReplayedRunNote[]
135
+ /** The latest gates on the run branch: after a join, or a final review fix. */
136
+ run_branch_gates: ReplayedGates | null
137
+ /**
138
+ * Every untracked path a prepare run made in any of the run's checkouts
139
+ * (`prepare_made`), in journal order. Never an agent's work: the engine
140
+ * leaves them out of commits, the leftover scan, and role rules.
141
+ */
142
+ prepare_made: string[]
143
+ /**
144
+ * Every old test (one that passed at its ticket's baseline) that failed
145
+ * in a ticket's red check, gates or join gates, oldest first (#544).
146
+ */
147
+ old_test_failures: OldTestFailure[]
148
+ final_review: FinalReviewState
149
+ /** Comments read on the spec issue while waiting for replies, oldest first. */
150
+ comments: ReplayedComment[]
151
+ /** Comments already taken as a reply, or sent back. */
152
+ handled_comments: number[]
153
+ /** Comments the engine itself posted on the spec issue. */
154
+ engine_comments: number[]
155
+ /** The owner replied `stop`: nothing new starts, and the run ends without a PR. */
156
+ stop: { comment_id: number } | null
157
+ /** The spec issue's comment that told the owner the final review is stuck. */
158
+ final_stuck_report: { comment_id: number } | null
159
+ /** The owner's reply to the stuck final review, until the engine acts on it. */
160
+ final_reply: { word: 'retry' | 'ship'; comment_id: number } | null
161
+ /** The run's budget of tokens: stuck on it, and the budgets added (#435). */
162
+ run_budget: RunBudgetState
163
+ /** The wait for earlier runs on the same repo (#615). */
164
+ repo_wait: RepoWaitState
165
+ /** Memory's recalls, the learner, and the saves (#370). */
166
+ memory: MemoryState
167
+ /**
168
+ * Crashes in a row per scheduler key (`run_resumed`), until a step under
169
+ * the key ends. A ticket's `retry`, or the final review's, clears its own.
170
+ */
171
+ crashes: CrashCounts
172
+ /** Set once a stop for crashes is journaled. It sticks. */
173
+ crash_stopped: { reason: string } | null
174
+ last_seq: number
175
+ }
176
+
177
+ export const EMPTY_STATE: RunState = {
178
+ phase: 'new',
179
+ spec_number: null,
180
+ config: null,
181
+ base_branch: null,
182
+ intake: null,
183
+ problems: null,
184
+ snapshot: null,
185
+ run_branch: null,
186
+ run_branch_install: null,
187
+ tickets: {},
188
+ changesets: false,
189
+ changeset: null,
190
+ pull_request: null,
191
+ plan: {
192
+ hit: null,
193
+ wait: null,
194
+ resets_announced: [],
195
+ billing: null,
196
+ billing_stopped: null,
197
+ usage_wait: null,
198
+ waited_until: null,
199
+ },
200
+ usage_recorded: { tickets: {}, run: false },
201
+ removed_worktrees: [],
202
+ closed_sessions: [],
203
+ run_notes: [],
204
+ run_branch_gates: null,
205
+ prepare_made: [],
206
+ old_test_failures: [],
207
+ final_review: EMPTY_FINAL_REVIEW,
208
+ comments: [],
209
+ handled_comments: [],
210
+ engine_comments: [],
211
+ stop: null,
212
+ final_stuck_report: null,
213
+ final_reply: null,
214
+ run_budget: { stuck: null, report: null, top_ups: 0 },
215
+ repo_wait: { status: 'none', since: null },
216
+ memory: EMPTY_MEMORY,
217
+ crashes: {},
218
+ crash_stopped: null,
219
+ last_seq: 0,
220
+ }
221
+
222
+ export const snapshotPhase = ({
223
+ snapshot,
224
+ }: {
225
+ snapshot: ReplayedSnapshot
226
+ }): RunPhase =>
227
+ snapshot.ticket_order.every((number) => number in snapshot.tickets)
228
+ ? 'intake_passed'
229
+ : 'snapshotting'
230
+
231
+ /** The run's notes after an agent finished: its own go at the end. */
232
+ export const notesAfter = ({
233
+ state,
234
+ record,
235
+ }: {
236
+ state: RunState
237
+ record: Extract<JournalRecord, { kind: 'agent_finished' }>
238
+ }): ReplayedRunNote[] => {
239
+ const { ticket, content } = record
240
+ // Reviewers (the ticket reviewer and the lenses) leave no run notes.
241
+ if (ticket === null || !('run_notes' in content.result)) {
242
+ return state.run_notes
243
+ }
244
+ return [
245
+ ...state.run_notes,
246
+ ...content.result.run_notes.map((note) => ({
247
+ ticket,
248
+ role: content.role,
249
+ note,
250
+ })),
251
+ ]
252
+ }
253
+
254
+ /** The spec comments the engine posted, plus this one if it posted one. */
255
+ export const engineCommentsAfter = ({
256
+ state,
257
+ comment_id,
258
+ }: {
259
+ state: RunState
260
+ comment_id: number | null
261
+ }): number[] =>
262
+ comment_id === null
263
+ ? state.engine_comments
264
+ : [...state.engine_comments, comment_id]