@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
@@ -1,157 +1,54 @@
1
- import { existsSync, readdirSync } from 'node:fs'
2
- import { mkdtemp, rm } from 'node:fs/promises'
3
- import { tmpdir } from 'node:os'
4
- import { join } from 'node:path'
5
-
6
1
  import isEmpty from 'lodash/isEmpty'
7
- import { z } from 'zod'
8
2
 
9
- import type { AgentLauncher } from '../agents/agent-launcher'
10
- import { createScriptedLauncher } from '../agents/scripted-launcher'
3
+ import { configReloadText } from './config-reload-text'
4
+ import {
5
+ openDecisionModel,
6
+ type DecisionModelSetup,
7
+ } from './decision-model-setup'
8
+ import { readRunStart } from './read-run'
9
+ import { driveRun, errorText, type RunEnd, type RunLauncher } from './run-end'
10
+
11
11
  import type { BoardSync } from '../board/board-sync'
12
12
  import { loadEngineConfig } from '../config/engine-config'
13
13
  import { lucaVersion } from '../config/luca-version'
14
- import { decide, type EngineAction } from '../core/decide'
15
14
  import {
15
+ recordConfigReload,
16
16
  recordResume,
17
17
  runEngine,
18
18
  startRun,
19
- STOP_ACTIONS,
20
19
  } from '../core/execute'
20
+ import type { GoingRunsReader } from '../core/execute-repo-wait'
21
+ import { runLiveFilePath } from '../core/live-file'
21
22
  import { createGitAdapter } from '../git/git-adapter'
22
- import { createTypeSafeJev } from '../jev/jev-client'
23
- import type { JevShadow } from '../jev/jev-shadow'
24
- import { createJournal, runJournalPath, type Journal } from '../journal/journal'
25
- import type { JournalRecord } from '../journal/journal-record'
23
+ import { createJournal, runJournalPath } from '../journal/journal'
26
24
  import type { UsageLineDeps } from '../limits/usage-line'
27
25
  import type { MemoryDeps } from '../memory/memory-client'
28
- import {
29
- DEMO_TURNS,
30
- demoMuninn,
31
- demoTracker,
32
- makePracticeRepo,
33
- PRACTICE_SPEC_NUMBER,
34
- } from '../testing/practice-run'
35
- import type { InMemoryPullRequest } from '../tracker/in-memory-tracker'
36
26
  import type { Tracker } from '../tracker/tracker'
37
27
 
38
- /** How a `luca-run` process ended; the board gets it as `ended`. */
39
- export type RunEnd = { ok: boolean; message: string }
40
-
41
- /**
42
- * An agent launcher a run may close at its end, such as the Claude launcher,
43
- * which keeps sessions open for follow-ups.
44
- */
45
- export type RunLauncher = AgentLauncher & { closeAll?: () => Promise<void> }
46
-
47
- /** How long each scripted agent turn of the demo takes, so the board is watchable. */
48
- export const DEMO_TURN_DELAY_MS = 1500
49
-
50
- const errorText = (error: unknown): string =>
51
- error instanceof Error ? error.message : String(error)
52
-
53
- /** What a run's last action means for the person watching it. */
54
- const endOf = ({ action }: { action: EngineAction }): RunEnd => {
55
- if (action.type === 'invalid_journal') {
56
- return {
57
- ok: false,
58
- message: `The journal is not a run: ${action.reason}`,
59
- }
60
- }
61
- if (action.type !== 'done') {
62
- return {
63
- ok: false,
64
- message: `The engine stopped before ${action.type}.`,
65
- }
66
- }
67
- switch (action.outcome) {
68
- case 'pr_opened':
69
- return {
70
- ok: true,
71
- message: `PR opened: ${action.pull_request.url}`,
72
- }
73
- case 'nothing_to_do':
74
- return {
75
- ok: true,
76
- message: 'Nothing to do: every ticket of the spec is closed.',
77
- }
78
- case 'refused':
79
- return {
80
- ok: false,
81
- message:
82
- 'Intake refused the run; the spec and tickets have comments saying what is missing.',
83
- }
84
- case 'stopped_by_user':
85
- return {
86
- ok: false,
87
- message:
88
- "Stopped by your `stop` reply: no PR. The run branch and the unfinished tickets' worktrees are kept.",
89
- }
90
- case 'all_skipped':
91
- return {
92
- ok: true,
93
- message:
94
- 'Every ticket was skipped, so there is no PR. The skipped tickets stay open.',
95
- }
96
- case 'stopped':
97
- return {
98
- ok: false,
99
- message: `Run stopped for billing: ${action.reason}. It will not go on; start a new run once per-token billing is off.`,
100
- }
101
- case 'crashed':
102
- return {
103
- ok: false,
104
- message: `Run stopped for good after crashes: ${action.reason} It will not go on; read the engine's log for why it crashed, then start a new run.`,
105
- }
106
- }
107
- }
108
-
109
- /**
110
- * Runs the engine to its end and turns what happened into a `RunEnd`, then
111
- * closes the launcher's open sessions and tells the board. A crash is caught
112
- * and reported, never thrown.
113
- */
114
- const driveRun = async ({
115
- journal,
116
- run,
117
- launcher,
118
- memory,
119
- board,
120
- log,
121
- }: {
122
- journal: Journal
123
- run: () => Promise<EngineAction>
124
- launcher: RunLauncher
125
- /** Closed at the end too. */
126
- memory?: MemoryDeps
127
- board: BoardSync | null
128
- log: (line: string) => void
129
- }): Promise<RunEnd> => {
130
- let end: RunEnd
131
- try {
132
- end = endOf({ action: await run() })
133
- } catch (error) {
134
- const message = errorText(error)
135
- // A launcher stop is journaled as run_stopped, then thrown.
136
- end = message.startsWith('Run stopped:')
137
- ? { ok: false, message }
138
- : { ok: false, message: `The engine crashed: ${message}` }
139
- } finally {
140
- await launcher.closeAll?.()
141
- await memory?.client.close().catch(() => undefined)
142
- }
143
- log(`[luca-run] ${end.ok ? 'finished' : 'stopped'}: ${end.message}`)
144
- await board?.end({ records: journal.read(), ...end })
145
- return end
146
- }
28
+ export { configReloadText } from './config-reload-text'
29
+ export type { DecisionModelSetup } from './decision-model-setup'
30
+ export { readRunStart, type RunStart } from './read-run'
31
+ export { DEMO_TURN_DELAY_MS, runDemo } from './run-demo'
32
+ export { resumeCommand, type RunEnd, type RunLauncher } from './run-end'
33
+ export {
34
+ restartableRuns,
35
+ RestartableRunSchema,
36
+ unfinishedRuns,
37
+ UnfinishedOutputSchema,
38
+ type RestartableRun,
39
+ } from './unfinished-runs'
147
40
 
148
41
  /**
149
42
  * A real run of `spec_number` on `repo`: loads the repo's engine config,
150
- * opens (or resumes) the run's journal at `<runs_dir>/<run_id>`, and runs the
151
- * engine with the board kept in step, the agents from `launcher`, and Jev
152
- * in shadow mode if given. With `memory`, a new run turns memory on (#370),
43
+ * opens (or resumes) the run's journal at `<runs_dir>/<run_id>` (its live
44
+ * file, `live.json`, goes beside it), and runs the
45
+ * engine with the board kept in step, the agents from `launcher`, and the
46
+ * decision model in shadow mode if given (`decision_model`; with no
47
+ * credentials it is off for the run, see `openDecisionModel`). With `memory`, a new run turns memory on (#370),
153
48
  * with the engine config's `muninn.vault` as the project vault (none:
154
- * `default` only); a resumed run keeps what its journal says. Never throws;
49
+ * `default` only); a resumed run keeps what its journal says. A new run
50
+ * stops on a bad config; a resume takes the config's build fields again
51
+ * and keeps its own on a bad one (`recordConfigReload`, #516). Never throws;
155
52
  * the launcher's sessions and the memory client are closed and the board
156
53
  * always gets the run's end.
157
54
  *
@@ -159,7 +56,8 @@ const driveRun = async ({
159
56
  * const end = await runSpec({
160
57
  * spec_number: 374, repo: '/code/app', run_id, base_branch: null,
161
58
  * runs_dir: defaultRunsDir(), tracker: createGitHubTracker({ repo: 'acme/app' }),
162
- * launcher: createClaudeLauncher({}), jev: { client: createTypeSafeJev() },
59
+ * launcher: createClaudeLauncher({}),
60
+ * decision_model: { credentials: await loadDecisionModelCredentials({ env: process.env, home: homedir() }) },
163
61
  * board, log: console.log,
164
62
  * })
165
63
  */
@@ -171,9 +69,11 @@ export const runSpec = async ({
171
69
  runs_dir,
172
70
  tracker,
173
71
  launcher,
174
- jev,
72
+ decision_model,
175
73
  memory,
176
74
  usage,
75
+ going_runs,
76
+ alongside,
177
77
  board,
178
78
  log,
179
79
  luca_version = lucaVersion(),
@@ -186,12 +86,16 @@ export const runSpec = async ({
186
86
  runs_dir: string
187
87
  tracker: Tracker
188
88
  launcher: RunLauncher
189
- /** Jev in shadow mode. Leave it out to run without Jev. */
190
- jev?: JevShadow
89
+ /** The decision model's setup (#534). Leave it out to run without one. */
90
+ decision_model?: DecisionModelSetup
191
91
  /** MuninnDB (#370). Leave it out to run without memory. */
192
92
  memory?: MemoryDeps
193
93
  /** The usage line (#434). Leave it out to run without one. */
194
94
  usage?: UsageLineDeps
95
+ /** The going runs, for the repo wait (#615). Leave it out for no wait. */
96
+ going_runs?: GoingRunsReader
97
+ /** A new run skips the repo wait (`--alongside`). */
98
+ alongside?: boolean
195
99
  board: BoardSync | null
196
100
  log: (line: string) => void
197
101
  /** Luca's version, recorded on start and resume. Defaults to the engine's own. */
@@ -203,7 +107,9 @@ export const runSpec = async ({
203
107
  log(`[luca-run] spec #${spec_number} in ${repo}, run ${run_id}`)
204
108
  log(`[luca-run] journal: ${journal.file}`)
205
109
  const loaded = await loadEngineConfig({ repo_root: repo })
206
- if (!loaded.ok) {
110
+ const fresh = isEmpty(journal.read())
111
+ // A new run needs a good config; a resume keeps its own (#516).
112
+ if (fresh && !loaded.ok) {
207
113
  log(`[luca-run] stopped: ${loaded.error}`)
208
114
  await launcher.closeAll?.()
209
115
  await memory?.client.close().catch(() => undefined)
@@ -211,7 +117,7 @@ export const runSpec = async ({
211
117
  await board?.end({ records: journal.read(), ...end })
212
118
  return end
213
119
  }
214
- if (isEmpty(journal.read())) {
120
+ if (fresh && loaded.ok) {
215
121
  startRun({
216
122
  journal,
217
123
  spec_number,
@@ -222,14 +128,19 @@ export const runSpec = async ({
222
128
  ? undefined
223
129
  : { project_vault: loaded.config.muninn?.vault ?? null },
224
130
  repo,
131
+ alongside,
225
132
  luca_version,
226
133
  })
227
134
  } else {
228
135
  log('[luca-run] resuming the run from its journal')
229
136
  recordResume({ journal, luca_version })
137
+ const reload = recordConfigReload({ journal, loaded })
138
+ if (reload !== null) log(`[luca-run] ${configReloadText({ reload })}`)
230
139
  }
140
+ const jev = openDecisionModel({ setup: decision_model, journal, log })
231
141
  return driveRun({
232
142
  journal,
143
+ run_id,
233
144
  launcher,
234
145
  memory,
235
146
  board,
@@ -243,185 +154,13 @@ export const runSpec = async ({
243
154
  jev,
244
155
  memory,
245
156
  usage,
157
+ going_runs,
246
158
  board: board ?? undefined,
159
+ live_file: runLiveFilePath({ runs_dir, run_id }),
247
160
  }),
248
161
  })
249
162
  }
250
163
 
251
- /** What a run's `run_started` says: its spec, base branch, and repo. */
252
- export type RunStart = {
253
- spec_number: number
254
- base_branch: string
255
- /** `null` in journals from before the repo was kept. */
256
- repo: string | null
257
- }
258
-
259
- /**
260
- * The records of run `run_id`, or why there are none to go on from: no
261
- * journal, an empty one, or one that cannot be read. Never makes the run's
262
- * folder.
263
- */
264
- const readRun = ({
265
- runs_dir,
266
- run_id,
267
- }: {
268
- runs_dir: string
269
- run_id: string
270
- }): { ok: true; records: JournalRecord[] } | { ok: false; error: string } => {
271
- const file = runJournalPath({ runs_dir, run_id })
272
- if (!existsSync(file)) {
273
- return { ok: false, error: `No run ${run_id}: there is no ${file}.` }
274
- }
275
- try {
276
- const records = createJournal({ file }).read()
277
- return records.length === 0
278
- ? {
279
- ok: false,
280
- error: `Run ${run_id} has an empty journal (${file}), so there is nothing to go on from; start a new run with --spec.`,
281
- }
282
- : { ok: true, records }
283
- } catch (error) {
284
- return { ok: false, error: errorText(error) }
285
- }
286
- }
287
-
288
- /**
289
- * Reads run `run_id`'s `run_started`: the spec, base branch, and repo a
290
- * resume goes on with. An error when the run has no journal, an empty one,
291
- * or one that does not start with `run_started`.
292
- *
293
- * @example
294
- * const start = readRunStart({ runs_dir: defaultRunsDir(), run_id })
295
- * if (start.ok) console.log(start.start.spec_number)
296
- */
297
- export const readRunStart = ({
298
- runs_dir,
299
- run_id,
300
- }: {
301
- runs_dir: string
302
- run_id: string
303
- }): { ok: true; start: RunStart } | { ok: false; error: string } => {
304
- const read = readRun({ runs_dir, run_id })
305
- if (!read.ok) return read
306
- const [first] = read.records
307
- if (first?.kind !== 'run_started') {
308
- return {
309
- ok: false,
310
- error: `Run ${run_id}'s journal does not start with run_started, so it is not a run.`,
311
- }
312
- }
313
- const { spec_number, base_branch, repo } = first.content
314
- return { ok: true, start: { spec_number, base_branch, repo } }
315
- }
316
-
317
- /**
318
- * The ids of the runs in `runs_dir` that are not over: runs whose journal's
319
- * next action (by the decision step) is not a stop (`done` or
320
- * `invalid_journal`), such as a run that crashed, was killed, or waits for a
321
- * reply. Folders with no journal, an empty one, or one that cannot be read
322
- * are left out. Sorted, so oldest first for made-up run ids. Paseo can
323
- * `resumeRun` each one when it starts (#375).
324
- *
325
- * @example
326
- * for (const run_id of unfinishedRuns({ runs_dir: defaultRunsDir() })) console.log(run_id)
327
- */
328
- export const unfinishedRuns = ({
329
- runs_dir,
330
- }: {
331
- runs_dir: string
332
- }): string[] => {
333
- if (!existsSync(runs_dir)) return []
334
- return readdirSync(runs_dir, { withFileTypes: true })
335
- .filter((entry) => entry.isDirectory())
336
- .map(({ name }) => name)
337
- .filter((run_id) => {
338
- const read = readRun({ runs_dir, run_id })
339
- return (
340
- read.ok &&
341
- !STOP_ACTIONS.has(decide({ records: read.records }).type)
342
- )
343
- })
344
- .toSorted()
345
- }
346
-
347
- /**
348
- * One unfinished run, as `luca-run --unfinished` prints it for the board
349
- * plugin (#375): whether Paseo may restart it with `--resume`, and why.
350
- *
351
- * - `resumable`: the engine was cut off (a crash, a kill); `restart` is true.
352
- * - `launcher_stopped`: its last record is a launcher stop (the wrong login,
353
- * plan, or model). A restart would stop the same way, so `restart` is
354
- * false; `message` is the stop's reason.
355
- * - `billing_stopped`: it stopped for billing, which sticks; `restart` is
356
- * false and `message` is the stop's reason.
357
- */
358
- export const RestartableRunSchema = z.object({
359
- run_id: z.string(),
360
- restart: z.boolean(),
361
- reason: z.enum(['resumable', 'launcher_stopped', 'billing_stopped']),
362
- message: z.string().nullable(),
363
- })
364
-
365
- export type RestartableRun = z.infer<typeof RestartableRunSchema>
366
-
367
- /** What `luca-run --unfinished` prints: one JSON object, snake_case keys. */
368
- export const UnfinishedOutputSchema = z.object({
369
- runs: z.array(RestartableRunSchema),
370
- })
371
-
372
- /** Whether Paseo may restart one unfinished run, from its records. */
373
- const restartOf = ({
374
- run_id,
375
- records,
376
- }: {
377
- run_id: string
378
- records: JournalRecord[]
379
- }): RestartableRun => {
380
- // A billing stop sticks, wherever it is in the journal.
381
- const billing = records.find(
382
- (record) => record.kind === 'run_stopped' && record.content.billing
383
- )
384
- if (billing?.kind === 'run_stopped') {
385
- return {
386
- run_id,
387
- restart: false,
388
- reason: 'billing_stopped',
389
- message: billing.content.reason,
390
- }
391
- }
392
- const last = records.at(-1)
393
- if (last?.kind === 'run_stopped' && !last.content.crashed) {
394
- return {
395
- run_id,
396
- restart: false,
397
- reason: 'launcher_stopped',
398
- message: last.content.reason,
399
- }
400
- }
401
- return { run_id, restart: true, reason: 'resumable', message: null }
402
- }
403
-
404
- /**
405
- * The unfinished runs in `runs_dir` (`unfinishedRuns`), each with whether
406
- * Paseo may restart it with `--resume` (#375). A run whose last record is a
407
- * launcher stop is not restarted, since it would stop the same way; a run
408
- * with a billing stop anywhere in its journal never is. Everything else was
409
- * cut off and can go on from its journal.
410
- *
411
- * @example
412
- * const runs = restartableRuns({ runs_dir: defaultRunsDir() })
413
- * // [{ run_id: 'luca-20260923-141500-ab12', restart: true, reason: 'resumable', message: null }]
414
- */
415
- export const restartableRuns = ({
416
- runs_dir,
417
- }: {
418
- runs_dir: string
419
- }): RestartableRun[] =>
420
- unfinishedRuns({ runs_dir }).flatMap((run_id) => {
421
- const read = readRun({ runs_dir, run_id })
422
- return read.ok ? [restartOf({ run_id, records: read.records })] : []
423
- })
424
-
425
164
  /**
426
165
  * Goes on with run `run_id` from its journal, as `luca-run --resume` does:
427
166
  * the spec and base branch come from its `run_started`, and the repo from
@@ -446,9 +185,10 @@ export const resumeRun = async ({
446
185
  repo,
447
186
  tracker,
448
187
  launcher,
449
- jev,
188
+ decision_model,
450
189
  memory,
451
190
  usage,
191
+ going_runs,
452
192
  board,
453
193
  log,
454
194
  luca_version,
@@ -459,8 +199,8 @@ export const resumeRun = async ({
459
199
  repo: string | null
460
200
  tracker: ({ repo }: { repo: string }) => Tracker | Promise<Tracker>
461
201
  launcher: RunLauncher
462
- /** Jev in shadow mode. Leave it out to run without Jev. */
463
- jev?: JevShadow
202
+ /** The decision model's setup (#534). Leave it out to go on without one. */
203
+ decision_model?: DecisionModelSetup
464
204
  /**
465
205
  * MuninnDB (#370), used when the run's `run_started` turned memory on.
466
206
  * Leave it out to go on without memory.
@@ -468,6 +208,8 @@ export const resumeRun = async ({
468
208
  memory?: MemoryDeps
469
209
  /** The usage line (#434). Leave it out to go on without one. */
470
210
  usage?: UsageLineDeps
211
+ /** The going runs, so a repo wait cut off goes on (#615). */
212
+ going_runs?: GoingRunsReader
471
213
  board: BoardSync | null
472
214
  log: (line: string) => void
473
215
  /** Luca's version, recorded on the resume. Defaults to the engine's own. */
@@ -499,131 +241,12 @@ export const resumeRun = async ({
499
241
  runs_dir,
500
242
  tracker: made,
501
243
  launcher,
502
- jev,
244
+ decision_model,
503
245
  memory,
504
246
  usage,
247
+ going_runs,
505
248
  board,
506
249
  log,
507
250
  luca_version,
508
251
  })
509
252
  }
510
-
511
- /** Wraps a launcher so each agent turn takes at least `delay_ms`. */
512
- const slowLauncher = ({
513
- launcher,
514
- delay_ms,
515
- log,
516
- }: {
517
- launcher: AgentLauncher
518
- delay_ms: number
519
- log: (line: string) => void
520
- }): AgentLauncher => ({
521
- launch: async (args) => {
522
- log(`[luca-run] ${args.role} on #${args.ticket}`)
523
- await Bun.sleep(delay_ms)
524
- return launcher.launch(args)
525
- },
526
- followUp: async (args) => {
527
- log(`[luca-run] ${args.role} on #${args.ticket} (follow-up)`)
528
- await Bun.sleep(delay_ms)
529
- return launcher.followUp(args)
530
- },
531
- closeSession: (args) => launcher.closeSession(args),
532
- })
533
-
534
- /**
535
- * The demo's Jev: the real client with no key, so every ask is journaled as
536
- * `jev_failed` (`missing_key`) and nothing is sent, as in a real run without
537
- * `TYPESAFE_API_KEY`.
538
- */
539
- const OFFLINE_JEV: JevShadow = { client: createTypeSafeJev({ api_key: '' }) }
540
-
541
- /**
542
- * A practice run, safe to try the board with: a throwaway repo with a local
543
- * bare origin in a temp folder, the practice spec with two tickets (#12
544
- * blocked by #11) in an in-memory tracker, and scripted agents that take
545
- * `turn_delay_ms` per turn. No GitHub, no models, no network: Jev is asked in
546
- * shadow mode with no key, and memory (#370) is a fake MuninnDB seeded with
547
- * a few memories, so the board shows searches, the learner, and a save
548
- * with nothing leaving the machine. The temp folder (which also holds the run's
549
- * journal) is removed at the end.
550
- *
551
- * @returns The run's end, the PRs the in-memory tracker opened, and the
552
- * (removed) temp folder.
553
- *
554
- * @example
555
- * const { ok, pull_requests } = await runDemo({ run_id, board: null, log: console.log, turn_delay_ms: DEMO_TURN_DELAY_MS })
556
- */
557
- export const runDemo = async ({
558
- run_id,
559
- board,
560
- log,
561
- turn_delay_ms,
562
- }: {
563
- run_id: string
564
- board: BoardSync | null
565
- log: (line: string) => void
566
- turn_delay_ms: number
567
- }): Promise<
568
- RunEnd & { pull_requests: InMemoryPullRequest[]; root: string }
569
- > => {
570
- const root = await mkdtemp(join(tmpdir(), 'luca-demo-'))
571
- try {
572
- const { repo, origin } = await makePracticeRepo({ root })
573
- const journal = createJournal({
574
- file: runJournalPath({ runs_dir: join(root, 'runs'), run_id }),
575
- })
576
- log(`[luca-run] demo run ${run_id} in ${root}`)
577
- log(`[luca-run] repo: ${repo}`)
578
- log(`[luca-run] origin: ${origin}`)
579
- log(`[luca-run] journal: ${journal.file}`)
580
- const loaded = await loadEngineConfig({ repo_root: repo })
581
- if (!loaded.ok) throw new Error(loaded.error)
582
- const tracker = demoTracker()
583
- startRun({
584
- journal,
585
- spec_number: PRACTICE_SPEC_NUMBER,
586
- config: loaded.config,
587
- base_branch: 'main',
588
- memory: { project_vault: null },
589
- repo,
590
- })
591
- const memory = { client: demoMuninn() }
592
- const launcher = slowLauncher({
593
- launcher: createScriptedLauncher({
594
- turns: DEMO_TURNS,
595
- }),
596
- delay_ms: turn_delay_ms,
597
- log,
598
- })
599
- const end = await driveRun({
600
- journal,
601
- launcher,
602
- memory,
603
- board,
604
- log,
605
- run: () =>
606
- runEngine({
607
- journal,
608
- tracker,
609
- git: createGitAdapter({ repo_root: repo }),
610
- launcher,
611
- jev: OFFLINE_JEV,
612
- memory,
613
- board: board ?? undefined,
614
- }),
615
- })
616
- return { ...end, pull_requests: tracker.pullRequests(), root }
617
- } catch (error) {
618
- const end = {
619
- ok: false,
620
- message: `The demo crashed: ${errorText(error)}`,
621
- }
622
- log(`[luca-run] stopped: ${end.message}`)
623
- await board?.end(end)
624
- return { ...end, pull_requests: [], root }
625
- } finally {
626
- await rm(root, { recursive: true, force: true })
627
- log(`[luca-run] removed ${root}`)
628
- }
629
- }
@@ -25,10 +25,9 @@ import {
25
25
  import { safeMemory, type MemoryClient } from '../memory/memory-client'
26
26
  import { runCommand } from '../shell/run-command'
27
27
  import {
28
- NEEDS_INFO_LABEL,
29
- READY_LABEL,
30
- REFACTOR_LABEL,
28
+ RELEASE_LABEL_DEFINITIONS,
31
29
  RELEASE_LABELS,
30
+ RUN_LABELS,
32
31
  type Tracker,
33
32
  } from '../tracker/tracker'
34
33
 
@@ -86,56 +85,14 @@ export type SetupEnd = {
86
85
  doctor: DoctorCheck[]
87
86
  }
88
87
 
89
- /** The labels a run needs, with what they're for. */
90
- const LABELS = [
91
- {
92
- name: READY_LABEL,
93
- color: '0e8a16',
94
- description: 'Ready for a Luca run',
95
- },
96
- {
97
- name: REFACTOR_LABEL,
98
- color: '5319e7',
99
- description: 'Changes shape, not behavior: no new tests',
100
- },
101
- {
102
- name: NEEDS_INFO_LABEL,
103
- color: 'd93f0b',
104
- description: 'Luca needs more detail before it can build this',
105
- },
106
- ]
107
-
108
- /** The version-bump labels a repo with changesets gets, one per bump. */
109
- const RELEASE_LABEL_INFO: Record<
110
- (typeof RELEASE_LABELS)[number],
111
- { color: string; description: string }
112
- > = {
113
- 'release:patch': {
114
- color: 'c2e0c6',
115
- description: "A patch bump for this spec's changeset (the default)",
116
- },
117
- 'release:minor': {
118
- color: 'fbca04',
119
- description: "A minor bump for this spec's changeset",
120
- },
121
- 'release:major': {
122
- color: 'b60205',
123
- description: "A major bump for this spec's changeset",
124
- },
125
- 'release:none': {
126
- color: 'ededed',
127
- description: 'No version bump: an empty changeset',
128
- },
129
- }
130
-
131
88
  /** Where a repo that uses changesets keeps their config. */
132
89
  const CHANGESET_CONFIG_FILE = join('.changeset', 'config.json')
133
90
 
134
91
  /** The labels `repo` needs: the run's, plus the release ones with changesets. */
135
92
  const labelsFor = async ({ repo }: { repo: string }) => [
136
- ...LABELS,
93
+ ...RUN_LABELS,
137
94
  ...((await Bun.file(join(repo, CHANGESET_CONFIG_FILE)).exists())
138
- ? RELEASE_LABELS.map((name) => ({ name, ...RELEASE_LABEL_INFO[name] }))
95
+ ? RELEASE_LABELS.map((name) => RELEASE_LABEL_DEFINITIONS[name])
139
96
  : []),
140
97
  ]
141
98
 
@@ -601,7 +558,7 @@ const list = (lines: string[]): string =>
601
558
 
602
559
  /**
603
560
  * Gets `repo` ready for Luca. It creates the missing `ready-for-agent`,
604
- * `refactor`, and `needs-info` labels, and in a repo with
561
+ * `ready-for-human`, `refactor`, and `needs-info` labels, and in a repo with
605
562
  * `.changeset/config.json` the `release:*` ones too; writes a starting
606
563
  * `.luca/config.json` from `package.json` when there is none (see
607
564
  * `guessChecks`), with the repo's GitHub name as its `muninn.vault`, rewrites an old-Luca config into the new shape keeping