@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,129 @@
1
+ /**
2
+ * Carries out the repo wait (#615): a run started while an earlier run on
3
+ * the same repo is going waits for it, before intake.
4
+ */
5
+ import { basename, dirname } from 'node:path'
6
+
7
+ import {
8
+ earlierRuns,
9
+ REPO_WAIT_NAP_MS,
10
+ type RepoWaitAction,
11
+ type RepoWaitRun,
12
+ } from './decide-repo-wait'
13
+ import { repoBusyDetail, repoWaitComment } from './repo-wait-text'
14
+
15
+ import type { goingRuns } from '../cli/going-runs'
16
+ import type { Journal } from '../journal/journal'
17
+ import type { EngineClock } from '../limits/limit-wait'
18
+ import { postCommentOnce, type CommentStep } from '../tracker/post-comment-once'
19
+ import type { Tracker } from '../tracker/tracker'
20
+
21
+ /** Lists the unfinished runs on this computer, as `goingRuns` does. */
22
+ export type GoingRunsReader = () => ReturnType<typeof goingRuns>
23
+
24
+ /**
25
+ * The earlier runs going on this run's repo (see `earlierRuns`), by
26
+ * `going_runs`. None when the board's registry can't be read: the wait
27
+ * never holds a run up on a list it can't trust. `ps_error` says why `ps`
28
+ * failed, when it did: then every unfinished run counts as going (#624).
29
+ */
30
+ export const readEarlierRuns = async ({
31
+ going_runs,
32
+ journal,
33
+ }: {
34
+ going_runs: GoingRunsReader
35
+ journal: Journal
36
+ }): Promise<{ runs: RepoWaitRun[]; ps_error: string | null }> => {
37
+ const listing = await going_runs()
38
+ if (!listing.ok) return { runs: [], ps_error: null }
39
+ return {
40
+ runs: earlierRuns({
41
+ runs: listing.runs,
42
+ run_id: basename(dirname(journal.file)),
43
+ records: journal.read(),
44
+ }),
45
+ ps_error: listing.ps_error,
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Carries out a repo wait. Its start tells the spec issue once which runs
51
+ * it waits for, then is journaled. The wait reads the going runs, then
52
+ * naps by `clock` and reads them again, until no earlier run on the repo is
53
+ * going (`repo_wait_ended`) or `until` comes (the run is stuck as
54
+ * `repo_busy`). Without `going_runs`, none is going. A `ps` that fails is
55
+ * journaled once (`repo_wait_ps_failed`), and the wait goes on.
56
+ */
57
+ export const executeRepoWaitAction = async ({
58
+ action,
59
+ journal,
60
+ tracker,
61
+ clock,
62
+ going_runs,
63
+ step,
64
+ }: {
65
+ action: RepoWaitAction
66
+ journal: Journal
67
+ tracker: Tracker
68
+ clock: EngineClock
69
+ going_runs: GoingRunsReader | undefined
70
+ step: CommentStep
71
+ }): Promise<void> => {
72
+ if (action.type === 'start_repo_wait') {
73
+ await postCommentOnce({
74
+ tracker,
75
+ number: action.spec_number,
76
+ body: repoWaitComment({ runs: action.runs }),
77
+ step,
78
+ n: 0,
79
+ })
80
+ journal.append({
81
+ kind: 'repo_wait_started',
82
+ ticket: null,
83
+ role: null,
84
+ content: { runs: action.runs },
85
+ })
86
+ return
87
+ }
88
+ const end = Date.parse(action.until)
89
+ let ps_error: string | null = null
90
+ for (;;) {
91
+ const read =
92
+ going_runs === undefined
93
+ ? { runs: [], ps_error: null }
94
+ : await readEarlierRuns({ going_runs, journal })
95
+ const { runs } = read
96
+ if (read.ps_error !== null && ps_error === null) {
97
+ ps_error = read.ps_error
98
+ journal.append({
99
+ kind: 'repo_wait_ps_failed',
100
+ ticket: null,
101
+ role: null,
102
+ content: { error: ps_error },
103
+ })
104
+ }
105
+ if (runs.length === 0) {
106
+ journal.append({
107
+ kind: 'repo_wait_ended',
108
+ ticket: null,
109
+ role: null,
110
+ content: {},
111
+ })
112
+ return
113
+ }
114
+ const left = end - clock.now()
115
+ if (left <= 0) {
116
+ journal.append({
117
+ kind: 'run_stuck',
118
+ ticket: null,
119
+ role: null,
120
+ content: {
121
+ reason: 'repo_busy',
122
+ detail: repoBusyDetail({ runs, ps_error }),
123
+ },
124
+ })
125
+ return
126
+ }
127
+ await clock.sleep(Math.min(left, REPO_WAIT_NAP_MS))
128
+ }
129
+ }
@@ -3,7 +3,6 @@ import sortBy from 'lodash/sortBy'
3
3
  import type { RunBudgetAction } from './decide-run-budget'
4
4
  import type { StuckAction } from './decide-stuck'
5
5
  import { need, ticketWorktree, type BuildContext } from './execute-build'
6
- import { shipFinalReview } from './execute-final-review'
7
6
 
8
7
  import { checkIntake } from '../intake/intake-checks'
9
8
  import type { TicketSnapshot } from '../intake/intake-schemas'
@@ -15,7 +14,11 @@ import {
15
14
  postCommentOnce,
16
15
  type CommentStep,
17
16
  } from '../tracker/post-comment-once'
18
- import type { Tracker, TrackerIssue } from '../tracker/tracker'
17
+ import {
18
+ HUMAN_LABEL,
19
+ type Tracker,
20
+ type TrackerIssue,
21
+ } from '../tracker/tracker'
19
22
 
20
23
  /**
21
24
  * How long the engine waits between reads of the spec issue while stuck
@@ -40,8 +43,8 @@ export const ticketChanged = ({
40
43
  * marking the run stuck on its budget, telling the spec issue a ticket, the
41
44
  * run, or the final review is stuck, waiting for
42
45
  * and reading its comments, taking or sending back a reply, skipping a
43
- * ticket (with a comment on it), and shipping (`shipFinalReview`) or
44
- * retrying the stuck final review. Its comments are posted once
46
+ * ticket (with a comment on it), and retrying the stuck final review
47
+ * (shipping it is `shipFinalReviewIn`'s). Its comments are posted once
45
48
  * (`postCommentOnce`), even when a crash cut off the step after posting.
46
49
  */
47
50
  export const executeStuckAction = async ({
@@ -52,8 +55,12 @@ export const executeStuckAction = async ({
52
55
  reply_poll_ms,
53
56
  step,
54
57
  }: {
58
+ /** Shipping is `shipFinalReviewIn`'s: it commits edits left over. */
55
59
  action:
56
- | Exclude<StuckAction, { type: 'undo_join' | 'retry_ticket' }>
60
+ | Exclude<
61
+ StuckAction,
62
+ { type: 'undo_join' | 'retry_ticket' | 'ship_final_review' }
63
+ >
57
64
  | RunBudgetAction
58
65
  journal: Journal
59
66
  tracker: Tracker
@@ -115,6 +122,9 @@ export const executeStuckAction = async ({
115
122
  ticket: action.ticket,
116
123
  comment_id: action.comment_id,
117
124
  author: comment?.author ?? '',
125
+ ...(action.top_up === undefined
126
+ ? {}
127
+ : { top_up: action.top_up }),
118
128
  },
119
129
  })
120
130
  return
@@ -158,11 +168,6 @@ export const executeStuckAction = async ({
158
168
  })
159
169
  return
160
170
  }
161
- case 'ship_final_review': {
162
- const shipped = shipFinalReview({ journal })
163
- if (!shipped.ok) throw new Error(shipped.reason)
164
- return
165
- }
166
171
  case 'retry_final_review':
167
172
  journal.append({
168
173
  kind: 'final_review_retried',
@@ -303,6 +308,16 @@ export const retryTicket = async ({
303
308
  (ticket) => ticket.number === number
304
309
  )
305
310
  : undefined
311
+ const leftOut = (
312
+ checked.outcome === 'passed'
313
+ ? checked.snapshot.left_out
314
+ : checked.left_out
315
+ ).some((ticket) => ticket.number === number)
316
+ if (fresh === undefined && leftOut) {
317
+ return refuse([
318
+ `The ticket is now for a person (\`${HUMAN_LABEL}\`), or waits on one, so a run can't build it.`,
319
+ ])
320
+ }
306
321
  if (fresh === undefined) return refuse(['The ticket is no longer open.'])
307
322
  const sha = await resetToRunBranch()
308
323
  journal.append({
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Carries out the run's waits: a plan limit wait or a billing stop, and a
3
+ * usage-line wait.
4
+ */
5
+ import type { PlanAction } from './decide-plan'
6
+ import type { UsageLineAction } from './decide-usage-line'
7
+
8
+ import type { Journal } from '../journal/journal'
9
+ import {
10
+ LIMIT_WAIT_NAP_MS,
11
+ limitWaitComment,
12
+ waitUntil,
13
+ type EngineClock,
14
+ } from '../limits/limit-wait'
15
+ import {
16
+ LINE_OF,
17
+ usageLineEndedComment,
18
+ usageLineWaitComment,
19
+ type UsageLines,
20
+ } from '../limits/usage-line'
21
+ import { postCommentOnce, type CommentStep } from '../tracker/post-comment-once'
22
+ import type { Tracker } from '../tracker/tracker'
23
+
24
+ /**
25
+ * Carries out a limit wait or a billing stop. A limit wait's start tells the
26
+ * spec issue (unless it already heard of this reset) before it is journaled;
27
+ * the wait itself sleeps by `clock` until its end, so a restarted engine
28
+ * waits out only what is left.
29
+ */
30
+ export const executePlanAction = async ({
31
+ action,
32
+ journal,
33
+ tracker,
34
+ clock,
35
+ step,
36
+ }: {
37
+ action: Exclude<PlanAction, { type: 'done' }>
38
+ journal: Journal
39
+ tracker: Tracker
40
+ clock: EngineClock
41
+ step: CommentStep
42
+ }): Promise<void> => {
43
+ switch (action.type) {
44
+ case 'start_limit_wait': {
45
+ const { rate_limit_type, resets_at, until, ticket, role } = action
46
+ if (action.announce) {
47
+ await postCommentOnce({
48
+ tracker,
49
+ number: action.spec_number,
50
+ body: limitWaitComment({
51
+ rate_limit_type,
52
+ resets_at,
53
+ until,
54
+ }),
55
+ step,
56
+ n: 0,
57
+ })
58
+ }
59
+ journal.append({
60
+ kind: 'limit_wait_started',
61
+ ticket: null,
62
+ role: null,
63
+ content: {
64
+ resets_at,
65
+ until,
66
+ rate_limit_type,
67
+ hit_ticket: ticket,
68
+ hit_role: role,
69
+ },
70
+ })
71
+ return
72
+ }
73
+ case 'wait_for_limit':
74
+ await waitUntil({ clock, until: action.until })
75
+ journal.append({
76
+ kind: 'limit_wait_ended',
77
+ ticket: null,
78
+ role: null,
79
+ content: { until: action.until },
80
+ })
81
+ return
82
+ case 'stop_for_billing':
83
+ journal.append({
84
+ kind: 'run_stopped',
85
+ ticket: null,
86
+ role: null,
87
+ content: { reason: action.reason, role: null, billing: true },
88
+ })
89
+ return
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Carries out a usage-line wait. Its start tells the spec issue, then is
95
+ * journaled. The wait sleeps by `clock` in naps until its end, re-reading
96
+ * the lines before each nap: a line raised above the reading ends it at
97
+ * once. Its end tells the spec issue why the run carries on, then is
98
+ * journaled.
99
+ */
100
+ export const executeUsageLineAction = async ({
101
+ action,
102
+ journal,
103
+ tracker,
104
+ clock,
105
+ read_usage_lines,
106
+ step,
107
+ }: {
108
+ action: UsageLineAction
109
+ journal: Journal
110
+ tracker: Tracker
111
+ clock: EngineClock
112
+ read_usage_lines: () => Promise<UsageLines>
113
+ step: CommentStep
114
+ }): Promise<void> => {
115
+ const { spec_number, window, line, percent, until } = action
116
+ if (action.type === 'start_usage_line_wait') {
117
+ const { resets_at } = action
118
+ await postCommentOnce({
119
+ tracker,
120
+ number: spec_number,
121
+ body: usageLineWaitComment({ window, line, percent, resets_at }),
122
+ step,
123
+ n: 0,
124
+ })
125
+ journal.append({
126
+ kind: 'usage_line_wait_started',
127
+ ticket: null,
128
+ role: null,
129
+ content: { window, line, percent, resets_at, until },
130
+ })
131
+ return
132
+ }
133
+ const end = Date.parse(until)
134
+ let reason: 'reset' | 'line_raised' = 'reset'
135
+ for (;;) {
136
+ const lines = await read_usage_lines()
137
+ if (lines[LINE_OF[window]] > percent) {
138
+ reason = 'line_raised'
139
+ break
140
+ }
141
+ const left = end - clock.now()
142
+ if (left <= 0) break
143
+ await clock.sleep(Math.min(left, LIMIT_WAIT_NAP_MS))
144
+ }
145
+ await postCommentOnce({
146
+ tracker,
147
+ number: spec_number,
148
+ body: usageLineEndedComment({ window, percent, reason }),
149
+ step,
150
+ n: 0,
151
+ })
152
+ journal.append({
153
+ kind: 'usage_line_wait_ended',
154
+ ticket: null,
155
+ role: null,
156
+ content: { until, reason },
157
+ })
158
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Carries out one action with the decision model asked around it in shadow
3
+ * mode.
4
+ */
5
+ import type { EngineAction } from './decide'
6
+ import { isFinalReviewAction } from './decide-final-review'
7
+ import { executeAction, type StepTry } from './execute-action'
8
+ import type { BuildDeps } from './execute-build'
9
+ import type { GoingRunsReader } from './execute-repo-wait'
10
+ import { ticketOf } from './schedule-rules'
11
+
12
+ import { jevAsksAfter, jevAsksBefore } from '../jev/jev-jobs'
13
+ import type { ShadowAsker } from '../jev/jev-shadow'
14
+ import { asksModel } from '../jev/model-pick'
15
+ import type { Journal } from '../journal/journal'
16
+ import type { JournalRecord } from '../journal/journal-record'
17
+ import { replayRun } from '../journal/replay'
18
+ import type { EngineClock } from '../limits/limit-wait'
19
+ import type { UsageLines } from '../limits/usage-line'
20
+ import type { MemoryDeps } from '../memory/memory-client'
21
+ import type { Tracker } from '../tracker/tracker'
22
+
23
+ /**
24
+ * Carries out one action with the decision model in shadow mode: asks it
25
+ * before the action (ticket order, model, skills), then after it about the
26
+ * records it appended (failure kinds, finding severities). Only the action's
27
+ * own ticket's new records count, since other tickets append at the same
28
+ * time. The answers change nothing, but for the model pick of a role whose
29
+ * floor is below Opus, which the launch step reads (#603). A launch whose
30
+ * model is set without the decision model (after a failed try on a cheaper
31
+ * model, or a redo) gets no model ask (#604).
32
+ */
33
+ export const executeWithJev = async ({
34
+ ask,
35
+ action,
36
+ journal,
37
+ tracker,
38
+ build,
39
+ clock,
40
+ reply_poll_ms,
41
+ memory,
42
+ step,
43
+ read_usage_lines,
44
+ going_runs,
45
+ }: {
46
+ ask: ShadowAsker
47
+ action: EngineAction
48
+ journal: Journal
49
+ tracker: Tracker
50
+ build?: BuildDeps
51
+ clock?: EngineClock
52
+ reply_poll_ms?: number
53
+ memory?: MemoryDeps
54
+ step?: StepTry
55
+ read_usage_lines?: () => Promise<UsageLines>
56
+ going_runs?: GoingRunsReader
57
+ }): Promise<void> => {
58
+ const before = journal.read()
59
+ const state = replayRun({ records: before })
60
+ await ask(
61
+ jevAsksBefore({ action, state }).filter(
62
+ // No model ask once the launch's model is set without one (#604).
63
+ ({ job, ticket, role }) =>
64
+ job !== 'agent_model' ||
65
+ role === null ||
66
+ asksModel({ records: before, state, ticket, role, step })
67
+ )
68
+ )
69
+ const lastSeq = journal.read().at(-1)?.seq ?? 0
70
+ await executeAction({
71
+ action,
72
+ journal,
73
+ tracker,
74
+ build,
75
+ clock,
76
+ reply_poll_ms,
77
+ memory,
78
+ step,
79
+ read_usage_lines,
80
+ going_runs,
81
+ })
82
+ const records = journal.read()
83
+ await ask(
84
+ jevAsksAfter({
85
+ records: records.filter(
86
+ (record) =>
87
+ record.seq > lastSeq && ownsRecord({ action, record })
88
+ ),
89
+ state: replayRun({ records }),
90
+ })
91
+ )
92
+ }
93
+
94
+ /**
95
+ * Whether a record appended while `action` ran is that action's own, since
96
+ * other tickets (or other lenses) append at the same time: a ticket's
97
+ * records for a ticket's step, a lens's for its lens, and the final
98
+ * review's other ticket-less records for its other steps.
99
+ */
100
+ const ownsRecord = ({
101
+ action,
102
+ record,
103
+ }: {
104
+ action: EngineAction
105
+ record: JournalRecord
106
+ }): boolean => {
107
+ if (action.type === 'launch_lens') {
108
+ return (
109
+ record.ticket === null &&
110
+ (record.role === null || record.role === action.role)
111
+ )
112
+ }
113
+ if (isFinalReviewAction(action)) {
114
+ return record.ticket === null && !record.role?.endsWith('-lens')
115
+ }
116
+ const ticket = ticketOf(action)
117
+ return ticket === null || record.ticket === ticket
118
+ }