@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,132 @@
1
+ /**
2
+ * A ticket's start: its snapshot, its worktree, the install, the baseline
3
+ * tests, and a start over after a bad test.
4
+ */
5
+ import type { BoardRecord } from './board-vocabulary'
6
+ import { newTicket } from './reduce-board-create'
7
+ import { testCounts, updateTicket, withTried } from './reduce-board-update'
8
+
9
+ import type { BoardState } from '../shared/board-state'
10
+
11
+ /** The record kinds of a ticket's start. */
12
+ export const TICKET_START_KINDS = [
13
+ 'ticket_snapshot',
14
+ 'ticket_worktree_created',
15
+ 'dependencies_installed',
16
+ 'baseline_tests',
17
+ 'baseline_reused',
18
+ 'worktree_reset',
19
+ ] as const
20
+
21
+ type TicketStartRecord = Extract<
22
+ BoardRecord,
23
+ { kind: (typeof TICKET_START_KINDS)[number] }
24
+ >
25
+
26
+ /** Applies one record of TICKET_START_KINDS to the board. */
27
+ export const applyTicketStartKind = ({
28
+ state,
29
+ record,
30
+ }: {
31
+ state: BoardState
32
+ record: TicketStartRecord
33
+ }): BoardState => {
34
+ const { ticket } = record
35
+ switch (record.kind) {
36
+ case 'ticket_snapshot': {
37
+ const { number, title, labels, blockers } = record.content
38
+ const refactor = labels.includes('refactor')
39
+ const known = state.tickets.some((card) => card.number === number)
40
+ if (known) {
41
+ return updateTicket({
42
+ state,
43
+ number,
44
+ update: (card) => ({ ...card, title, refactor, blockers }),
45
+ })
46
+ }
47
+ return {
48
+ ...state,
49
+ tickets: [
50
+ ...state.tickets,
51
+ newTicket({ number, title, refactor, blockers }),
52
+ ],
53
+ }
54
+ }
55
+ case 'ticket_worktree_created':
56
+ return updateTicket({
57
+ state,
58
+ number: ticket,
59
+ update: (card) => ({
60
+ ...card,
61
+ started: true,
62
+ stage: 'building',
63
+ activity: 'starting',
64
+ }),
65
+ })
66
+ case 'dependencies_installed': {
67
+ const { check } = record.content
68
+ if (check === null || check.ok || ticket === null) return state
69
+ return updateTicket({
70
+ state,
71
+ number: ticket,
72
+ update: (card) => ({
73
+ ...card,
74
+ activity: 'install failed',
75
+ tried: withTried({
76
+ tried: card.tried,
77
+ line: `The install failed: \`${check.command}\``,
78
+ }),
79
+ }),
80
+ })
81
+ }
82
+ case 'baseline_tests': {
83
+ const tests = testCounts({ cases: record.content.cases })
84
+ return updateTicket({
85
+ state,
86
+ number: ticket,
87
+ update: (card) => ({
88
+ ...card,
89
+ started: true,
90
+ activity: 'baseline tests',
91
+ tests,
92
+ baseline_tests: tests,
93
+ }),
94
+ })
95
+ }
96
+ case 'baseline_reused': {
97
+ // Another ticket's baseline from the same run-branch commit (#404).
98
+ const { from_ticket } = record.content
99
+ const tests =
100
+ state.tickets.find((card) => card.number === from_ticket)
101
+ ?.baseline_tests ?? null
102
+ return updateTicket({
103
+ state,
104
+ number: ticket,
105
+ update: (card) => ({
106
+ ...card,
107
+ started: true,
108
+ activity: `baseline tests (reused from #${from_ticket})`,
109
+ tests,
110
+ baseline_tests: tests,
111
+ }),
112
+ })
113
+ }
114
+ case 'worktree_reset':
115
+ return updateTicket({
116
+ state,
117
+ number: ticket,
118
+ update: (card) => ({
119
+ ...card,
120
+ stage: 'building',
121
+ step: 0,
122
+ fix_round: 0,
123
+ open_check: null,
124
+ activity: 'starting over',
125
+ tried: withTried({
126
+ tried: card.tried,
127
+ line: 'Started over from the tests after a bad test',
128
+ }),
129
+ }),
130
+ })
131
+ }
132
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The small state updates every record handler uses: change one ticket card
3
+ * or the final review, add a "tried" line, add or resolve a Needs-you item,
4
+ * and count tests.
5
+ */
6
+ import { firstLine } from './reduce-board-text'
7
+
8
+ import type {
9
+ BoardState,
10
+ FinalReview,
11
+ NeedsYou,
12
+ TicketCard,
13
+ } from '../shared/board-state'
14
+
15
+ /** How many "tried" lines a ticket or the final review keeps. */
16
+ const TRIED_KEPT = 6
17
+
18
+ /** Longest stuck detail kept in state. */
19
+ export const DETAIL_MAX = 2000
20
+
21
+ /** The learner's role (#370): its records have no ticket. */
22
+ export const LEARNER = 'learner'
23
+
24
+ export const withLearner = ({
25
+ state,
26
+ learner,
27
+ }: {
28
+ state: BoardState
29
+ learner: BoardState['memory']['learner']
30
+ }): BoardState => ({ ...state, memory: { ...state.memory, learner } })
31
+
32
+ export const withTried = ({
33
+ tried,
34
+ line,
35
+ }: {
36
+ tried: string[]
37
+ line: string
38
+ }): string[] => [...tried, firstLine({ text: line })].slice(-TRIED_KEPT)
39
+
40
+ export const updateTicket = ({
41
+ state,
42
+ number,
43
+ update,
44
+ }: {
45
+ state: BoardState
46
+ number: number | null
47
+ update: (ticket: TicketCard) => TicketCard
48
+ }): BoardState => {
49
+ if (number === null) return state
50
+ return {
51
+ ...state,
52
+ tickets: state.tickets.map((ticket) =>
53
+ ticket.number === number ? update(ticket) : ticket
54
+ ),
55
+ }
56
+ }
57
+
58
+ export const updateFinal = ({
59
+ state,
60
+ update,
61
+ }: {
62
+ state: BoardState
63
+ update: (review: FinalReview) => FinalReview
64
+ }): BoardState => ({ ...state, final_review: update(state.final_review) })
65
+
66
+ export const addNeedsYou = ({
67
+ state,
68
+ item,
69
+ }: {
70
+ state: BoardState
71
+ item: NeedsYou
72
+ }): BoardState => ({
73
+ ...state,
74
+ needs_you: [
75
+ ...state.needs_you.filter((entry) => entry.key !== item.key),
76
+ item,
77
+ ],
78
+ })
79
+
80
+ export const resolveNeedsYou = ({
81
+ state,
82
+ key,
83
+ }: {
84
+ state: BoardState
85
+ key: string | null
86
+ }): BoardState => ({
87
+ ...state,
88
+ needs_you:
89
+ key === null
90
+ ? []
91
+ : state.needs_you.filter((entry) => entry.key !== key),
92
+ })
93
+
94
+ /** The final review with one more "tried" line. */
95
+ export const finalTried = ({
96
+ state,
97
+ line,
98
+ }: {
99
+ state: BoardState
100
+ line: string
101
+ }): BoardState =>
102
+ updateFinal({
103
+ state,
104
+ update: (review) => ({
105
+ ...review,
106
+ tried: withTried({ tried: review.tried, line }),
107
+ }),
108
+ })
109
+
110
+ export const testCounts = ({
111
+ cases,
112
+ }: {
113
+ cases: { status: string }[]
114
+ }): { failing: number; total: number } => ({
115
+ failing: cases.filter((entry) => entry.status === 'failed').length,
116
+ total: cases.length,
117
+ })
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Tokens and plan usage: what each agent session used, the plan's windows
3
+ * the sessions and usage records report, and the waits for a limit to reset.
4
+ */
5
+ import type { BoardRecord } from './board-vocabulary'
6
+ import { updateTicket } from './reduce-board-update'
7
+
8
+ import {
9
+ usageLevel,
10
+ windowRank,
11
+ windowWords,
12
+ type BoardState,
13
+ type PlanUsed,
14
+ type Usage,
15
+ } from '../shared/board-state'
16
+
17
+ type SessionContent = Extract<BoardRecord, { kind: 'agent_session' }>['content']
18
+
19
+ /** Every token a session read or wrote, cache reads included. */
20
+ const sessionTokens = ({ session }: SessionContent): number =>
21
+ session.usage.input_tokens +
22
+ session.usage.output_tokens +
23
+ session.usage.cache_read_input_tokens +
24
+ session.usage.cache_creation_input_tokens
25
+
26
+ /**
27
+ * A session's exact tokens: input, output, and cache-creation tokens over
28
+ * its tokens per model, subagents included. An older journal's session,
29
+ * with none, counts its main loop's `usage`. Cache reads are left out.
30
+ */
31
+ const countedTokens = ({ session }: SessionContent): number => {
32
+ const models = Object.values(session.model_usage)
33
+ return (models.length === 0 ? [session.usage] : models).reduce(
34
+ (total, each) =>
35
+ total +
36
+ each.input_tokens +
37
+ each.output_tokens +
38
+ each.cache_creation_input_tokens,
39
+ 0
40
+ )
41
+ }
42
+
43
+ /** A 0-to-1 utilization as a whole percent from 0 to 100. */
44
+ const percentOf = ({ utilization }: { utilization: number }): number =>
45
+ Math.min(100, Math.max(0, Math.round(utilization * 100)))
46
+
47
+ /** Epoch seconds as an ISO time. */
48
+ const isoOf = ({ seconds }: { seconds: number }): string =>
49
+ new Date(seconds * 1000).toISOString()
50
+
51
+ export type Reading = SessionContent['session']['rate_limit_events'][number]
52
+
53
+ /**
54
+ * One reading's windows as `{ type, utilization, resetsAt }`: its top-level
55
+ * `rateLimitType` form, then each of its `unifiedWindows`.
56
+ */
57
+ const windowsOf = ({
58
+ reading,
59
+ }: {
60
+ reading: Reading
61
+ }): { type: string; utilization: number; resetsAt: number | undefined }[] => {
62
+ const { rateLimitType, utilization, resetsAt, unifiedWindows } = reading
63
+ const top =
64
+ rateLimitType !== undefined && utilization !== undefined
65
+ ? [{ type: rateLimitType, utilization, resetsAt }]
66
+ : []
67
+ const unified = Object.entries(unifiedWindows ?? {}).flatMap(
68
+ ([type, window]) =>
69
+ window.utilization === undefined
70
+ ? []
71
+ : [
72
+ {
73
+ type,
74
+ utilization: window.utilization,
75
+ resetsAt: window.resetsAt,
76
+ },
77
+ ]
78
+ )
79
+ return [...top, ...unified]
80
+ }
81
+
82
+ /**
83
+ * The plan usage after rate-limit readings, such as a session's. The latest
84
+ * `five_hour` reading sets the five-hour window (and when it resets); the
85
+ * latest reading of any `seven_day*` type sets the weekly one, and inside
86
+ * one reading the highest `seven_day*` window wins, since the tightest
87
+ * weekly cap is the one that binds. A window with no reading keeps what it
88
+ * had. `null` while no window has a reading.
89
+ */
90
+ export const usageAfter = ({
91
+ usage,
92
+ readings,
93
+ time,
94
+ }: {
95
+ usage: Usage | null
96
+ readings: Reading[]
97
+ time: string
98
+ }): Usage | null => {
99
+ let five_hour = usage?.five_hour_percent ?? null
100
+ let weekly = usage?.weekly_percent ?? null
101
+ let resets_at = usage?.resets_at ?? null
102
+ let read = false
103
+ for (const reading of readings) {
104
+ const windows = windowsOf({ reading })
105
+ const fiveHour = windows.filter(({ type }) => type === 'five_hour')
106
+ const latest = fiveHour.at(-1)
107
+ if (latest) {
108
+ five_hour = percentOf({ utilization: latest.utilization })
109
+ resets_at =
110
+ latest.resetsAt === undefined
111
+ ? resets_at
112
+ : isoOf({ seconds: latest.resetsAt })
113
+ read = true
114
+ }
115
+ const weeklies = windows
116
+ .filter(({ type }) => type.startsWith('seven_day'))
117
+ .map(({ utilization }) => percentOf({ utilization }))
118
+ if (weeklies.length > 0) {
119
+ weekly = Math.max(...weeklies)
120
+ read = true
121
+ }
122
+ }
123
+ if (!read) return usage
124
+ return {
125
+ five_hour_percent: five_hour,
126
+ weekly_percent: weekly,
127
+ five_hour_level:
128
+ five_hour === null ? null : usageLevel({ percent: five_hour }),
129
+ weekly_level: weekly === null ? null : usageLevel({ percent: weekly }),
130
+ resets_at,
131
+ read_at: time,
132
+ }
133
+ }
134
+
135
+ /**
136
+ * A usage record's windows as whole percents in words: five-hour first,
137
+ * then the weekly ones, then the rest, each group in the record's order.
138
+ */
139
+ const planUsedOf = ({
140
+ windows,
141
+ }: {
142
+ windows: Record<string, { used: number }>
143
+ }): PlanUsed[] =>
144
+ Object.entries(windows)
145
+ .map(([window, { used }], index) => ({ window, used, index }))
146
+ .toSorted(
147
+ (left, right) =>
148
+ windowRank({ window: left.window }) -
149
+ windowRank({ window: right.window }) ||
150
+ left.index - right.index
151
+ )
152
+ .map(({ window, used }) => ({
153
+ window: windowWords({ window }),
154
+ percent: Math.round(used),
155
+ }))
156
+
157
+ /** The record kinds of tokens and plan usage. */
158
+ export const USAGE_KINDS = [
159
+ 'agent_session',
160
+ 'limit_wait_started',
161
+ 'limit_wait_ended',
162
+ 'usage_line_wait_started',
163
+ 'usage_line_wait_ended',
164
+ 'usage_recorded',
165
+ ] as const
166
+
167
+ type UsageRecord = Extract<BoardRecord, { kind: (typeof USAGE_KINDS)[number] }>
168
+
169
+ /** Applies one record of USAGE_KINDS to the board. */
170
+ export const applyUsageKind = ({
171
+ state,
172
+ record,
173
+ }: {
174
+ state: BoardState
175
+ record: UsageRecord
176
+ }): BoardState => {
177
+ const { ticket } = record
178
+ switch (record.kind) {
179
+ case 'agent_session': {
180
+ const tokens = sessionTokens(record.content)
181
+ const { role } = record.content
182
+ const withTokens = updateTicket({
183
+ state,
184
+ number: ticket,
185
+ update: (card) => ({
186
+ ...card,
187
+ tokens: card.tokens + tokens,
188
+ agent_tokens: {
189
+ ...card.agent_tokens,
190
+ [role]: (card.agent_tokens[role] ?? 0) + tokens,
191
+ },
192
+ }),
193
+ })
194
+ return {
195
+ ...withTokens,
196
+ run_tokens: state.run_tokens + countedTokens(record.content),
197
+ usage: usageAfter({
198
+ usage: state.usage,
199
+ readings: record.content.session.rate_limit_events,
200
+ time: record.time,
201
+ }),
202
+ }
203
+ }
204
+ case 'limit_wait_started': {
205
+ const { resets_at, until, rate_limit_type } = record.content
206
+ return {
207
+ ...state,
208
+ limit_wait: {
209
+ resets_at: resets_at ?? until,
210
+ window:
211
+ rate_limit_type === null
212
+ ? null
213
+ : windowWords({ window: rate_limit_type }),
214
+ since: record.time,
215
+ },
216
+ }
217
+ }
218
+ case 'limit_wait_ended':
219
+ return { ...state, limit_wait: null }
220
+ case 'usage_line_wait_started': {
221
+ const { window, line, percent, resets_at } = record.content
222
+ return {
223
+ ...state,
224
+ usage_line_wait: {
225
+ window: windowWords({ window }),
226
+ line,
227
+ percent,
228
+ resets_at,
229
+ since: record.time,
230
+ },
231
+ }
232
+ }
233
+ case 'usage_line_wait_ended':
234
+ return { ...state, usage_line_wait: null }
235
+ case 'usage_recorded': {
236
+ const plan_used = planUsedOf({ windows: record.content.windows })
237
+ if (record.content.scope === 'run') {
238
+ return { ...state, run_plan_used: plan_used }
239
+ }
240
+ return updateTicket({
241
+ state,
242
+ number: record.content.ticket ?? ticket,
243
+ update: (card) => ({ ...card, plan_used }),
244
+ })
245
+ }
246
+ }
247
+ }