@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,227 @@
1
+ /**
2
+ * How the Claude launcher starts one agent session: checks the model, builds
3
+ * the role's locked-down options (guard hook, sandbox, clean environment),
4
+ * starts the SDK session, checks the account BEFORE the prompt is sent, then
5
+ * runs the first turn on the pool's open session.
6
+ */
7
+ import { randomUUID } from 'node:crypto'
8
+ import { realpathSync } from 'node:fs'
9
+ import { homedir } from 'node:os'
10
+
11
+ import type {
12
+ EffortLevel,
13
+ Options,
14
+ SDKUserMessage,
15
+ } from '@anthropic-ai/claude-agent-sdk'
16
+
17
+ import {
18
+ AgentSessionSchema,
19
+ type AgentLauncher,
20
+ type AgentSession,
21
+ type AgentTurn,
22
+ } from './agent-launcher'
23
+ import {
24
+ AccountSchema,
25
+ accountProblem,
26
+ errorText,
27
+ stop,
28
+ type Outcome,
29
+ } from './claude-messages'
30
+ import { agentEnv, agentOptions, checkLaunchModel } from './claude-options'
31
+ import { createInputChannel } from './input-channel'
32
+ import { commonGitDir, realClaudePath } from './launch-paths'
33
+ import { createDeliveryHook, createLucaServer } from './message-tool'
34
+ import {
35
+ nextOutcome,
36
+ pumpMessages,
37
+ type AgentQuerySession,
38
+ type OpenSession,
39
+ } from './open-session'
40
+ import type { SessionPool } from './session-pool'
41
+ import { createTurnProgress } from './turn-progress'
42
+
43
+ import { createGuardHook } from '../guards/guard-hook'
44
+ import { guardRoleOf } from '../guards/role-rules'
45
+
46
+ /** Starts an SDK session: the SDK's `query`, or a fake in tests. */
47
+ export type AgentQuery = (params: {
48
+ prompt: AsyncIterable<SDKUserMessage>
49
+ options: Options
50
+ }) => AgentQuerySession
51
+
52
+ /**
53
+ * Makes the launcher's `launch`: it starts an SDK session with `start_query`
54
+ * and runs the session's first turn through the pool.
55
+ *
56
+ * @example
57
+ * const launch = createLaunch({ effort, start_query, claude_path, turn_timeout_ms, pool })
58
+ */
59
+ export const createLaunch = ({
60
+ effort: useEffort,
61
+ start_query: startQuery,
62
+ claude_path,
63
+ turn_timeout_ms: timeoutMs,
64
+ pool,
65
+ }: {
66
+ effort: EffortLevel
67
+ start_query: AgentQuery
68
+ claude_path: string | undefined
69
+ turn_timeout_ms: number
70
+ pool: Pick<SessionPool, 'close' | 'runTurn'>
71
+ }): AgentLauncher['launch'] => {
72
+ const { close, runTurn } = pool
73
+
74
+ const launch: AgentLauncher['launch'] = async ({
75
+ role,
76
+ ticket,
77
+ prompt,
78
+ cwd,
79
+ may_edit_tests,
80
+ config,
81
+ messaging,
82
+ model,
83
+ on_progress,
84
+ }) => {
85
+ const refused = checkLaunchModel({ model })
86
+ if (refused !== null) return stop(refused)
87
+ const started = Date.now()
88
+ const fallback_id = `claude-launch-${randomUUID()}`
89
+
90
+ let claudePath: string
91
+ try {
92
+ claudePath = claude_path ?? realClaudePath()
93
+ } catch (error) {
94
+ return stop(errorText(error))
95
+ }
96
+ let worktree: string
97
+ let gitDir: string
98
+ try {
99
+ worktree = realpathSync(cwd)
100
+ gitDir = await commonGitDir({ cwd: worktree })
101
+ } catch (error) {
102
+ return { ok: false, failure: 'engine', error: errorText(error) }
103
+ }
104
+
105
+ const summary: AgentSession = AgentSessionSchema.parse({})
106
+ const stderr: string[] = []
107
+ const helpers = new Set<string>()
108
+ const progress = createTurnProgress({
109
+ worktree,
110
+ readings: () => open.summary.rate_limit_events,
111
+ })
112
+ progress.start(on_progress)
113
+ const options = agentOptions({
114
+ role,
115
+ may_edit_tests,
116
+ cwd: worktree,
117
+ model,
118
+ effort: useEffort,
119
+ claude_path: claudePath,
120
+ config,
121
+ common_git_dir: gitDir,
122
+ home: realpathSync(homedir()),
123
+ env: agentEnv({ source: process.env }),
124
+ guard_hook: createGuardHook({
125
+ role: guardRoleOf({ role }),
126
+ may_edit_tests,
127
+ worktree,
128
+ config,
129
+ // The summary is swapped each turn, so this reads it late.
130
+ on_deny: (denial) => open.summary.guard_denials.push(denial),
131
+ helpers,
132
+ on_helper: () => {
133
+ open.summary.helpers_started += 1
134
+ },
135
+ on_allow: progress.opened,
136
+ }),
137
+ // Kept in the session's options, so its follow-ups have them too.
138
+ luca_server:
139
+ messaging === null ? null : createLucaServer({ messaging }),
140
+ delivery_hook:
141
+ messaging === null ? null : createDeliveryHook({ messaging }),
142
+ stderr: (data) => {
143
+ stderr.push(data)
144
+ if (stderr.length > 50) stderr.shift()
145
+ },
146
+ })
147
+
148
+ const input = createInputChannel()
149
+ let running: AgentQuerySession
150
+ try {
151
+ running = startQuery({ prompt: input.iterable, options })
152
+ } catch (error) {
153
+ return {
154
+ ok: false,
155
+ failure: 'engine',
156
+ error: `The SDK did not start: ${errorText(error)}`,
157
+ session: { ...summary, duration_ms: Date.now() - started },
158
+ }
159
+ }
160
+ const open: OpenSession = {
161
+ role,
162
+ ticket,
163
+ cwd,
164
+ may_edit_tests,
165
+ running,
166
+ input,
167
+ summary,
168
+ helpers,
169
+ progress,
170
+ model_totals: {},
171
+ waiter: null,
172
+ ended: null,
173
+ pump: Promise.resolve(),
174
+ idle_timer: undefined,
175
+ stderr,
176
+ }
177
+ open.pump = pumpMessages({ open, on_end: () => void close(open) })
178
+
179
+ const ended = async (outcome: Outcome): Promise<AgentTurn> => {
180
+ await close(open)
181
+ return {
182
+ ...outcome,
183
+ session_id: open.summary.session_id ?? fallback_id,
184
+ session: { ...open.summary, duration_ms: Date.now() - started },
185
+ }
186
+ }
187
+
188
+ let timer: ReturnType<typeof setTimeout> | undefined
189
+ const timedOut = new Promise<'timeout'>((resolve) => {
190
+ timer = setTimeout(() => resolve('timeout'), timeoutMs)
191
+ })
192
+ const account = await Promise.race([
193
+ running.accountInfo().then(
194
+ (info) => ({ info }),
195
+ (error: unknown) => ({ error })
196
+ ),
197
+ nextOutcome(open).then((outcome) => ({ outcome })),
198
+ timedOut,
199
+ ])
200
+ clearTimeout(timer)
201
+ open.waiter = null
202
+ if (account === 'timeout') {
203
+ return ended({
204
+ ok: false,
205
+ failure: 'engine',
206
+ error: 'accountInfo() did not answer in time.',
207
+ })
208
+ }
209
+ if ('outcome' in account) return ended(account.outcome)
210
+ if ('error' in account) {
211
+ return ended({
212
+ ok: false,
213
+ failure: 'engine',
214
+ error: `accountInfo() failed: ${errorText(account.error)}`,
215
+ })
216
+ }
217
+ const accountParsed = AccountSchema.safeParse(account.info)
218
+ summary.subscription_type = accountParsed.success
219
+ ? (accountParsed.data.subscriptionType ?? null)
220
+ : null
221
+ const problem = accountProblem(account.info)
222
+ if (problem !== null) return ended(stop(`credential check: ${problem}`))
223
+ return runTurn({ open, message: prompt, started, fallback_id })
224
+ }
225
+
226
+ return launch
227
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The learner's instructions (#370): its task, and its own rules in place of
3
+ * the shell and the rules for every agent.
4
+ */
5
+
6
+ export const LEARNER = `# Your role: learner
7
+
8
+ You run once, at the end of a run, even a stuck or stopped one. Your prompt is a digest of the run's journal: failures, fix loops, failed tries, review findings, stuck points, assumptions, run notes, and the memories other agents were shown. You propose lessons for FUTURE runs, which the engine saves to memory.
9
+
10
+ - Propose only durable lessons: something a later run, maybe on another spec, would do better for knowing. Not a story of this run, not facts only this spec needs, and nothing a linter or the gates already enforce.
11
+ - Pick each one's type:
12
+ - "pattern": a way of building that worked and is worth repeating.
13
+ - "pitfall": a trap that cost a fix loop, a failed try, or a stuck ticket, and how to avoid it.
14
+ - "procedure": steps that reliably get something done.
15
+ - "decision": a choice made for THIS project that later runs on it must follow.
16
+ Any other type is refused.
17
+ - Mark each one's scope by asking "would this memory be useful in a completely different repo?":
18
+ - "repo": no, it only helps on this repo (its code, docs, or setup). It is saved to this project's memory.
19
+ - "anywhere": yes, it helps on any repo. It is saved to the shared memory.
20
+ A decision is always saved to this project's memory. A missing or other scope is refused.
21
+ - Keep each memory atomic: one lesson each, a short "concept" name in kebab-case (such as bun-junit-reporter-flags), the lesson in "content" (a few plain sentences), and a one-line "summary".
22
+ - At most 10. Fewer, better ones beat many. An empty list is a fine answer when the run taught nothing new.
23
+ - In "helped", list the ids of the shown memories that actually helped this run (the digest lists them with their ids). Leave out the ones that didn't matter or were wrong.
24
+ - You may read the repo (Read, Grep, Glob) to check a lesson. You have no shell, and you write nothing.
25
+
26
+ Your result (structured output): memories (each with type, scope, concept, content, summary) and helped (memory ids).`
27
+
28
+ export const LEARNER_RULES = `## Rules for the learner
29
+ - Plain code (the engine) drives this run and saves what you propose. Only the engine talks to memory; you have no memory tools.
30
+ - Never create, edit, or delete a file. The network is off.
31
+ - Finish by giving your structured result. A turn without one counts as failed.`
@@ -0,0 +1,210 @@
1
+ /**
2
+ * One agent session the Claude launcher keeps open between turns, and how it
3
+ * is read: each SDK message goes into the session's summary, and an outcome
4
+ * (a result, a stop, a plan cut-off, an engine failure) goes to the turn
5
+ * waiting for it.
6
+ */
7
+ import type { AgentSession, ModelTokens } from './agent-launcher'
8
+ import {
9
+ AssistantSchema,
10
+ errorText,
11
+ InitSchema,
12
+ initProblem,
13
+ planCut,
14
+ RateLimitSchema,
15
+ ResultSchema,
16
+ modelTotalsOf,
17
+ stop,
18
+ toolResultIds,
19
+ turnTokens,
20
+ type Outcome,
21
+ } from './claude-messages'
22
+ import type { createInputChannel } from './input-channel'
23
+ import type { AgentRole } from './role-results'
24
+ import type { TurnProgressKeeper } from './turn-progress'
25
+
26
+ import { planSignal } from '../limits/plan-signals'
27
+
28
+ /**
29
+ * One running SDK session, as the launcher uses it. The SDK's own `Query`
30
+ * fits this; tests hand in a fake. Messages are read as `unknown` and parsed
31
+ * with the launcher's own schemas.
32
+ */
33
+ export type AgentQuerySession = AsyncIterable<unknown> & {
34
+ accountInfo: () => Promise<unknown>
35
+ interrupt: () => Promise<unknown>
36
+ close: () => void
37
+ }
38
+
39
+ /**
40
+ * One agent session the launcher keeps open between turns, so a follow-up
41
+ * reaches the same agent. Its guard hook, sandbox, and options are the ones
42
+ * it was launched with.
43
+ */
44
+ export type OpenSession = {
45
+ role: AgentRole
46
+ ticket: number
47
+ cwd: string
48
+ may_edit_tests: boolean
49
+ running: AgentQuerySession
50
+ input: ReturnType<typeof createInputChannel>
51
+ /** What the launcher has seen of the session; per-turn fields reset each turn. */
52
+ summary: AgentSession
53
+ /**
54
+ * The helpers running now, by the tool call that started each: the guard
55
+ * hook adds one, and its result (or the turn's end) takes it out.
56
+ */
57
+ helpers: Set<string>
58
+ /** The turn's live progress (#628), told to the turn's `on_progress`. */
59
+ progress: TurnProgressKeeper
60
+ /** The SDK's running tokens per model, as of the session's last result. */
61
+ model_totals: Record<string, ModelTokens>
62
+ /** Resolves the turn waiting for the session's next outcome. */
63
+ waiter: ((outcome: Outcome) => void) | null
64
+ /** A stop or an engine failure: the session is over, with this outcome. */
65
+ ended: Outcome | null
66
+ /** Reads the session's messages until it ends. */
67
+ pump: Promise<void>
68
+ idle_timer: ReturnType<typeof setTimeout> | undefined
69
+ stderr: string[]
70
+ }
71
+
72
+ /** The session's next outcome: a result, a stop, or its end. */
73
+ export const nextOutcome = (open: OpenSession): Promise<Outcome> =>
74
+ open.ended !== null
75
+ ? Promise.resolve(open.ended)
76
+ : new Promise<Outcome>((resolve) => {
77
+ open.waiter = resolve
78
+ })
79
+
80
+ export const deliver = ({
81
+ open,
82
+ outcome,
83
+ }: {
84
+ open: OpenSession
85
+ outcome: Outcome
86
+ }): void => {
87
+ if (!outcome.ok && outcome.failure !== 'agent') open.ended = outcome
88
+ const waiter = open.waiter
89
+ open.waiter = null
90
+ waiter?.(outcome)
91
+ }
92
+
93
+ export const stderrTail = (open: OpenSession): string =>
94
+ open.stderr.length === 0 ? '' : `\n${open.stderr.join('').slice(-2000)}`
95
+
96
+ /** Reads one message into the session's summary; an outcome ends the turn. */
97
+ export const watch = ({
98
+ open,
99
+ raw,
100
+ }: {
101
+ open: OpenSession
102
+ raw: unknown
103
+ }): Outcome | null => {
104
+ const { summary } = open
105
+ const init = InitSchema.safeParse(raw)
106
+ if (init.success) {
107
+ summary.session_id = init.data.session_id
108
+ summary.model = init.data.model
109
+ summary.claude_code_version = init.data.claude_code_version
110
+ summary.api_key_source = init.data.apiKeySource
111
+ const problem = initProblem(init.data)
112
+ return problem === null ? null : stop(problem)
113
+ }
114
+ const rateLimit = RateLimitSchema.safeParse(raw)
115
+ if (rateLimit.success) {
116
+ const info = rateLimit.data.rate_limit_info
117
+ summary.rate_limit_events.push({
118
+ ...info,
119
+ arrived_at: new Date().toISOString(),
120
+ })
121
+ const signal = planSignal({ info })
122
+ switch (signal.kind) {
123
+ case 'ok':
124
+ return null
125
+ case 'billing':
126
+ return planCut(signal.reason)
127
+ case 'limit':
128
+ return planCut(
129
+ `rate_limit_event status=rejected (${signal.rate_limit_type ?? 'unknown'})`
130
+ )
131
+ }
132
+ }
133
+ const assistant = AssistantSchema.safeParse(raw)
134
+ if (assistant.success) {
135
+ if (assistant.data.error !== 'billing_error') return null
136
+ summary.billing_error = true
137
+ return planCut('assistant error billing_error')
138
+ }
139
+ for (const id of toolResultIds(raw)) open.helpers.delete(id)
140
+ const result = ResultSchema.safeParse(raw)
141
+ if (!result.success) return null
142
+ open.helpers.clear()
143
+ const done = result.data
144
+ summary.num_turns = done.num_turns
145
+ summary.usage = done.usage
146
+ const totals = modelTotalsOf(done.modelUsage)
147
+ summary.model_usage = turnTokens({
148
+ totals,
149
+ before: open.model_totals,
150
+ })
151
+ open.model_totals = { ...open.model_totals, ...totals }
152
+ summary.total_cost_usd = done.total_cost_usd
153
+ summary.permission_denials = done.permission_denials.map(
154
+ ({ tool_name, tool_use_id }) => ({ tool_name, tool_use_id })
155
+ )
156
+ if (done.subtype === 'success' && !done.is_error) {
157
+ return { ok: true, structured_output: done.structured_output }
158
+ }
159
+ const detail = done.errors.length > 0 ? done.errors.join('; ') : done.result
160
+ return {
161
+ ok: false,
162
+ failure: 'agent',
163
+ error: `The agent's turn ended with ${done.subtype}: ${detail}`,
164
+ ...(done.subtype === 'error_max_turns' ? { out_of_turns: true } : {}),
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Reads a session's messages until it ends, handing each outcome to the turn
170
+ * waiting for it, then calls `on_end`.
171
+ */
172
+ export const pumpMessages = async ({
173
+ open,
174
+ on_end,
175
+ }: {
176
+ open: OpenSession
177
+ on_end: () => void
178
+ }): Promise<void> => {
179
+ try {
180
+ for await (const raw of open.running) {
181
+ const outcome = watch({ open, raw })
182
+ open.progress.heard(raw)
183
+ if (outcome === null) continue
184
+ deliver({ open, outcome })
185
+ if (open.ended !== null) break
186
+ }
187
+ if (open.ended !== null) {
188
+ on_end()
189
+ return
190
+ }
191
+ deliver({
192
+ open,
193
+ outcome: {
194
+ ok: false,
195
+ failure: 'engine',
196
+ error: `The agent session ended with no result.${stderrTail(open)}`,
197
+ },
198
+ })
199
+ } catch (error) {
200
+ deliver({
201
+ open,
202
+ outcome: {
203
+ ok: false,
204
+ failure: 'engine',
205
+ error: `The agent session failed: ${errorText(error)}${stderrTail(open)}`,
206
+ },
207
+ })
208
+ }
209
+ on_end()
210
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The sections the decision step adds to an agent's prompt: the notes earlier
3
+ * agents left, the prepare command, the feature map, and the changesets. Each
4
+ * is plain text.
5
+ */
6
+ import type { AgentRole } from './role-results'
7
+
8
+ import { isFeatureMapFolder } from '../config/engine-config'
9
+
10
+ /** A run note as a later agent's prompt lists it. */
11
+ export type PromptRunNote = { ticket: number; role: AgentRole; note: string }
12
+
13
+ /** The notes earlier agents left, oldest first; nothing when there are none. */
14
+ export const runNotesSection = ({
15
+ run_notes,
16
+ }: {
17
+ run_notes: PromptRunNote[]
18
+ }): string[] =>
19
+ run_notes.length === 0
20
+ ? []
21
+ : [
22
+ '## Run notes from earlier agents in this run',
23
+ run_notes
24
+ .map(
25
+ ({ ticket, role, note }) =>
26
+ `- ${note} (${role}, #${ticket})`
27
+ )
28
+ .join('\n'),
29
+ ]
30
+
31
+ /**
32
+ * The section a test-writer or implementer gets when the repo has a prepare
33
+ * command: the engine runs it before its test runs, so tests that need its
34
+ * outputs may fail when the agent runs them itself.
35
+ *
36
+ * @example
37
+ * prepareSection({ command: 'bun run build:rom' })
38
+ * // '## This repo has a prepare command\n\n...'
39
+ */
40
+ export const prepareSection = ({ command }: { command: string }): string =>
41
+ [
42
+ '## This repo has a prepare command',
43
+ `The engine runs \`${command}\` in the worktree before each of its test runs (the red check and the gates), so tests that need its outputs find them up to date. ` +
44
+ "Never run it yourself. Tests that need its outputs may fail when you run them, and still pass at the engine's checks. " +
45
+ "If the engine's prepare command fails after your turn, its output comes back to you like a failed check.",
46
+ ].join('\n\n')
47
+
48
+ /**
49
+ * The section a test-writer gets when the repo has a feature map (#583): the
50
+ * one file besides test files it may edit. It comes after the task's "Edit
51
+ * test files only", so it reads as that line's exception. A folder map
52
+ * (#597) is its `*.md` pages, and a new test file goes on its area's page.
53
+ *
54
+ * @param path - The map's path, as `featureMapPath` gives it.
55
+ *
56
+ * @example
57
+ * featureMapSection({ path: 'docs/feature-map.md' })
58
+ * // '## The feature map\n\nBesides test files, you may edit the feature map, `docs/feature-map.md`. ...'
59
+ */
60
+ export const featureMapSection = ({ path }: { path: string }): string =>
61
+ [
62
+ '## The feature map',
63
+ isFeatureMapFolder({ path })
64
+ ? `Besides test files, you may edit the feature map's pages, the \`*.md\` files under \`${path}/\`. ` +
65
+ 'Read the pages first: they list each feature and the test files that prove it. ' +
66
+ "Put new tests in a feature's existing test files when you can; " +
67
+ "a new test file must be listed under its feature on its area's page (add a page for a new area)."
68
+ : `Besides test files, you may edit the feature map, \`${path}\`. ` +
69
+ 'Read it first: it lists each feature and the test files that prove it. ' +
70
+ "Put new tests in a feature's existing test files when you can; " +
71
+ 'a new test file must be listed under its feature in the map.',
72
+ ].join('\n\n')
73
+
74
+ /**
75
+ * The section a test-writer or implementer (a ticket's or the final
76
+ * review's fixers) gets in a repo with changesets: the engine writes the
77
+ * run's one changeset, so an agent's own is extra.
78
+ */
79
+ export const CHANGESETS_SECTION = [
80
+ '## The engine writes the changeset',
81
+ "Don't write changesets, even if the repo's rules ask for one: the engine writes the run's one changeset when it opens the PR. " +
82
+ 'A new file you add in `.changeset/` is removed before the commit. ' +
83
+ 'A finding that only asks for a changeset is answered by this: answer it "wont_fix" and say the engine adds it.',
84
+ ].join('\n\n')
85
+
86
+ /**
87
+ * The section every reviewer (the ticket-reviewer and each lens) gets in a
88
+ * repo with changesets, so a rule like "every PR needs a changeset" raises
89
+ * no finding.
90
+ */
91
+ export const CHANGESETS_LENS_SECTION = [
92
+ '## The engine writes the changeset',
93
+ "The engine adds the run's one changeset when it opens the PR, so a rule that asks for a changeset is met. " +
94
+ 'Raise no finding that only asks for a changeset.',
95
+ ].join('\n\n')