@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,143 @@
1
+ /**
2
+ * The practice run, `luca-run --demo` (`runDemo`): a throwaway repo, scripted
3
+ * agents, and a fake MuninnDB, safe to try the board with.
4
+ */
5
+ import { mkdtemp, rm } from 'node:fs/promises'
6
+ import { tmpdir } from 'node:os'
7
+ import { join } from 'node:path'
8
+
9
+ import { errorText, driveRun, type RunEnd } from './run-end'
10
+
11
+ import type { AgentLauncher } from '../agents/agent-launcher'
12
+ import { createScriptedLauncher } from '../agents/scripted-launcher'
13
+ import type { BoardSync } from '../board/board-sync'
14
+ import { loadEngineConfig } from '../config/engine-config'
15
+ import { runEngine, startRun } from '../core/execute'
16
+ import { createGitAdapter } from '../git/git-adapter'
17
+ import { createJournal, runJournalPath } from '../journal/journal'
18
+ import {
19
+ DEMO_TURNS,
20
+ demoMuninn,
21
+ demoTracker,
22
+ makePracticeRepo,
23
+ PRACTICE_SPEC_NUMBER,
24
+ } from '../testing/practice-run'
25
+ import type { InMemoryPullRequest } from '../tracker/in-memory-tracker'
26
+
27
+ /** How long each scripted agent turn of the demo takes, so the board is watchable. */
28
+ export const DEMO_TURN_DELAY_MS = 1500
29
+
30
+ /** Wraps a launcher so each agent turn takes at least `delay_ms`. */
31
+ const slowLauncher = ({
32
+ launcher,
33
+ delay_ms,
34
+ log,
35
+ }: {
36
+ launcher: AgentLauncher
37
+ delay_ms: number
38
+ log: (line: string) => void
39
+ }): AgentLauncher => ({
40
+ launch: async (args) => {
41
+ log(`[luca-run] ${args.role} on #${args.ticket}`)
42
+ await Bun.sleep(delay_ms)
43
+ return launcher.launch(args)
44
+ },
45
+ followUp: async (args) => {
46
+ log(`[luca-run] ${args.role} on #${args.ticket} (follow-up)`)
47
+ await Bun.sleep(delay_ms)
48
+ return launcher.followUp(args)
49
+ },
50
+ closeSession: (args) => launcher.closeSession(args),
51
+ })
52
+
53
+ /**
54
+ * A practice run, safe to try the board with: a throwaway repo with a local
55
+ * bare origin in a temp folder, the practice spec with two tickets (#12
56
+ * blocked by #11) in an in-memory tracker, and scripted agents that take
57
+ * `turn_delay_ms` per turn. No GitHub, no models, no network: the decision
58
+ * model is off (its credentials are never read), and memory (#370) is a fake MuninnDB seeded with
59
+ * a few memories, so the board shows searches, the learner, and a save
60
+ * with nothing leaving the machine. The temp folder (which also holds the run's
61
+ * journal) is removed at the end.
62
+ *
63
+ * @returns The run's end, the PRs the in-memory tracker opened, and the
64
+ * (removed) temp folder.
65
+ *
66
+ * @example
67
+ * const { ok, pull_requests } = await runDemo({ run_id, board: null, log: console.log, turn_delay_ms: DEMO_TURN_DELAY_MS })
68
+ */
69
+ export const runDemo = async ({
70
+ run_id,
71
+ board,
72
+ log,
73
+ turn_delay_ms,
74
+ }: {
75
+ run_id: string
76
+ board: BoardSync | null
77
+ log: (line: string) => void
78
+ turn_delay_ms: number
79
+ }): Promise<
80
+ RunEnd & { pull_requests: InMemoryPullRequest[]; root: string }
81
+ > => {
82
+ const root = await mkdtemp(join(tmpdir(), 'luca-demo-'))
83
+ try {
84
+ const { repo, origin } = await makePracticeRepo({ root })
85
+ const journal = createJournal({
86
+ file: runJournalPath({ runs_dir: join(root, 'runs'), run_id }),
87
+ })
88
+ log(`[luca-run] demo run ${run_id} in ${root}`)
89
+ log(`[luca-run] repo: ${repo}`)
90
+ log(`[luca-run] origin: ${origin}`)
91
+ log(`[luca-run] journal: ${journal.file}`)
92
+ log('[luca-run] decision model off: the demo never asks it.')
93
+ const loaded = await loadEngineConfig({ repo_root: repo })
94
+ if (!loaded.ok) throw new Error(loaded.error)
95
+ const tracker = demoTracker()
96
+ startRun({
97
+ journal,
98
+ spec_number: PRACTICE_SPEC_NUMBER,
99
+ config: loaded.config,
100
+ base_branch: 'main',
101
+ memory: { project_vault: null },
102
+ repo,
103
+ })
104
+ const memory = { client: demoMuninn() }
105
+ const launcher = slowLauncher({
106
+ launcher: createScriptedLauncher({
107
+ turns: DEMO_TURNS,
108
+ }),
109
+ delay_ms: turn_delay_ms,
110
+ log,
111
+ })
112
+ const end = await driveRun({
113
+ journal,
114
+ // A demo's journal is in a temp folder that is removed at its end.
115
+ run_id: null,
116
+ launcher,
117
+ memory,
118
+ board,
119
+ log,
120
+ run: () =>
121
+ runEngine({
122
+ journal,
123
+ tracker,
124
+ git: createGitAdapter({ repo_root: repo }),
125
+ launcher,
126
+ memory,
127
+ board: board ?? undefined,
128
+ }),
129
+ })
130
+ return { ...end, pull_requests: tracker.pullRequests(), root }
131
+ } catch (error) {
132
+ const end = {
133
+ ok: false,
134
+ message: `The demo crashed: ${errorText(error)}`,
135
+ }
136
+ log(`[luca-run] stopped: ${end.message}`)
137
+ await board?.end(end)
138
+ return { ...end, pull_requests: [], root }
139
+ } finally {
140
+ await rm(root, { recursive: true, force: true })
141
+ log(`[luca-run] removed ${root}`)
142
+ }
143
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * How a run ends for the person watching it: what a run's last action means
3
+ * (`RunEnd`), how to go on from its journal (`resumeCommand`), and driving the
4
+ * engine to its end without ever throwing (`driveRun`).
5
+ */
6
+ import type { AgentLauncher } from '../agents/agent-launcher'
7
+ import type { BoardSync } from '../board/board-sync'
8
+ import type { EngineAction } from '../core/decide'
9
+ import { nothingToDoText } from '../core/left-out'
10
+ import type { Journal } from '../journal/journal'
11
+ import type { JournalRecord } from '../journal/journal-record'
12
+ import type { MemoryDeps } from '../memory/memory-client'
13
+
14
+ /** How a `luca-run` process ended; the board gets it as `ended`. */
15
+ export type RunEnd = { ok: boolean; message: string }
16
+
17
+ /**
18
+ * An agent launcher a run may close at its end, such as the Claude launcher,
19
+ * which keeps sessions open for follow-ups.
20
+ */
21
+ export type RunLauncher = AgentLauncher & { closeAll?: () => Promise<void> }
22
+
23
+ /** An error's message, or any other thrown value as text. */
24
+ export const errorText = (error: unknown): string =>
25
+ error instanceof Error ? error.message : String(error)
26
+
27
+ /**
28
+ * What a run's last action means for the person watching it. A run with
29
+ * nothing to do says why, from its `nothing_to_do` record in `records`.
30
+ */
31
+ const endOf = ({
32
+ action,
33
+ records,
34
+ }: {
35
+ action: EngineAction
36
+ records: JournalRecord[]
37
+ }): RunEnd => {
38
+ if (action.type === 'invalid_journal') {
39
+ return {
40
+ ok: false,
41
+ message: `The journal is not a run: ${action.reason}`,
42
+ }
43
+ }
44
+ if (action.type !== 'done') {
45
+ return {
46
+ ok: false,
47
+ message: `The engine stopped before ${action.type}.`,
48
+ }
49
+ }
50
+ switch (action.outcome) {
51
+ case 'pr_opened':
52
+ return {
53
+ ok: true,
54
+ message: `PR opened: ${action.pull_request.url}`,
55
+ }
56
+ case 'nothing_to_do': {
57
+ const end = records.findLast(
58
+ (record) => record.kind === 'nothing_to_do'
59
+ )
60
+ return {
61
+ ok: true,
62
+ message: nothingToDoText({
63
+ content:
64
+ end?.kind === 'nothing_to_do'
65
+ ? end.content
66
+ : {
67
+ closed_tickets: [],
68
+ already_done: [],
69
+ left_out: [],
70
+ },
71
+ }),
72
+ }
73
+ }
74
+ case 'refused':
75
+ return {
76
+ ok: false,
77
+ message:
78
+ 'Intake refused the run; the spec and tickets have comments saying what is missing.',
79
+ }
80
+ case 'stopped_by_user':
81
+ return {
82
+ ok: false,
83
+ message:
84
+ "Stopped by your `stop` reply: no PR. The run branch and the unfinished tickets' worktrees are kept.",
85
+ }
86
+ case 'all_skipped':
87
+ return {
88
+ ok: true,
89
+ message:
90
+ 'Every ticket was skipped, so there is no PR. The skipped tickets stay open.',
91
+ }
92
+ case 'stopped':
93
+ return {
94
+ ok: false,
95
+ message: `Run stopped for billing: ${action.reason}. It will not go on; start a new run once per-token billing is off.`,
96
+ }
97
+ case 'crashed':
98
+ return {
99
+ ok: false,
100
+ 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.`,
101
+ }
102
+ }
103
+ }
104
+
105
+ /**
106
+ * How to go on with run `run_id` from its journal: `/luca-run resume <id>`
107
+ * in Paseo when the board plugin started it (so the board shows it live),
108
+ * else `luca-run --resume <id>`. Pure.
109
+ *
110
+ * @example
111
+ * resumeCommand({ run_id: 'r1', board: true }) // 'type /luca-run resume r1 in a Paseo chat'
112
+ * resumeCommand({ run_id: 'r1', board: false }) // 'run luca-run --resume r1'
113
+ */
114
+ export const resumeCommand = ({
115
+ run_id,
116
+ board,
117
+ }: {
118
+ run_id: string
119
+ board: boolean
120
+ }): string =>
121
+ board
122
+ ? `type /luca-run resume ${run_id} in a Paseo chat`
123
+ : `run luca-run --resume ${run_id}`
124
+
125
+ /** `text` as a sentence, then `more`. Pure. */
126
+ const thenSay = ({ text, more }: { text: string; more: string }): string =>
127
+ `${text}${/[.!?]$/.test(text) ? '' : '.'} ${more}`
128
+
129
+ /**
130
+ * Runs the engine to its end and turns what happened into a `RunEnd`, then
131
+ * closes the launcher's open sessions and tells the board. A crash is caught
132
+ * and reported, never thrown. With `run_id`, a crash or a launcher stop says
133
+ * how to go on from the journal (see `resumeCommand`).
134
+ */
135
+ export const driveRun = async ({
136
+ journal,
137
+ run,
138
+ run_id,
139
+ launcher,
140
+ memory,
141
+ board,
142
+ log,
143
+ }: {
144
+ journal: Journal
145
+ run: () => Promise<EngineAction>
146
+ /** The run's id, or `null` for one that can't go on again (the demo). */
147
+ run_id: string | null
148
+ launcher: RunLauncher
149
+ /** Closed at the end too. */
150
+ memory?: MemoryDeps
151
+ board: BoardSync | null
152
+ log: (line: string) => void
153
+ }): Promise<RunEnd> => {
154
+ let end: RunEnd
155
+ try {
156
+ const action = await run()
157
+ end = endOf({ action, records: journal.read() })
158
+ } catch (error) {
159
+ const message = errorText(error)
160
+ // A launcher stop is journaled as run_stopped, then thrown.
161
+ const stopped = message.startsWith('Run stopped:')
162
+ const text = stopped ? message : `The engine crashed: ${message}`
163
+ if (run_id === null) {
164
+ end = { ok: false, message: text }
165
+ } else {
166
+ const how = resumeCommand({ run_id, board: board !== null })
167
+ end = {
168
+ ok: false,
169
+ message: thenSay({
170
+ text,
171
+ more: stopped
172
+ ? `Fix that, then, to go on from its journal, ${how}.`
173
+ : `To go on from its journal, ${how}.`,
174
+ }),
175
+ }
176
+ }
177
+ } finally {
178
+ await launcher.closeAll?.()
179
+ await memory?.client.close().catch(() => undefined)
180
+ }
181
+ log(`[luca-run] ${end.ok ? 'finished' : 'stopped'}: ${end.message}`)
182
+ await board?.end({ records: journal.read(), ...end })
183
+ return end
184
+ }
@@ -0,0 +1,81 @@
1
+ import type { RunLauncher } from './run-modes'
2
+
3
+ import {
4
+ createScriptedLauncher,
5
+ type ScriptedTurn,
6
+ } from '../agents/scripted-launcher'
7
+ import {
8
+ createBoardSync,
9
+ type BoardEnded,
10
+ type BoardLink,
11
+ } from '../board/board-sync'
12
+ import { createJournal, runJournalPath } from '../journal/journal'
13
+ import type { JournalRecord } from '../journal/journal-record'
14
+
15
+ /**
16
+ * What the run modes' end-to-end tests share: a stand-in for the Claude
17
+ * launcher, a board that keeps what it is sent, and readers of a run's journal.
18
+ */
19
+
20
+ /**
21
+ * A stand-in for the Claude launcher: scripted turns, plus a `closeAll`
22
+ * that counts its calls.
23
+ */
24
+ export const fakeClaudeLauncher = ({ turns }: { turns: ScriptedTurn[] }) => {
25
+ const scripted = createScriptedLauncher({ turns })
26
+ let closed = 0
27
+ const launcher: RunLauncher = {
28
+ launch: scripted.launch,
29
+ followUp: scripted.followUp,
30
+ closeSession: scripted.closeSession,
31
+ closeAll: async () => {
32
+ closed += 1
33
+ },
34
+ }
35
+ return { launcher, closed: () => closed, launches: scripted.launches }
36
+ }
37
+
38
+ /** A board that keeps everything it is sent. */
39
+ export const recordingBoard = () => {
40
+ const kinds: string[] = []
41
+ const seqs: number[] = []
42
+ const endings: BoardEnded[] = []
43
+ const link: BoardLink = {
44
+ send: async ({ records, ended }) => {
45
+ for (const record of records) {
46
+ if (record.seq === seqs.length + 1) {
47
+ seqs.push(record.seq)
48
+ kinds.push(record.kind)
49
+ }
50
+ }
51
+ if (ended !== null) endings.push(ended)
52
+ return { ok: true, next_seq: seqs.length + 1, message: 'ok' }
53
+ },
54
+ }
55
+ return {
56
+ board: createBoardSync({ link }),
57
+ /** The kinds it got, without the scheduler's step records. */
58
+ kinds: () =>
59
+ kinds.filter(
60
+ (kind) =>
61
+ kind !== 'step_started' &&
62
+ kind !== 'step_ended' &&
63
+ kind !== 'run_resumed'
64
+ ),
65
+ endings: () => endings,
66
+ }
67
+ }
68
+
69
+ /** The records of run `run_id` in `runs_dir`. */
70
+ export const recordsOf = ({
71
+ runs_dir,
72
+ run_id,
73
+ }: {
74
+ runs_dir: string
75
+ run_id: string
76
+ }): JournalRecord[] =>
77
+ createJournal({ file: runJournalPath({ runs_dir, run_id }) }).read()
78
+
79
+ /** The kinds of a run's records, oldest first. */
80
+ export const kindsIn = (records: JournalRecord[]) =>
81
+ records.map(({ kind }) => kind)