@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,48 @@
1
+ /**
2
+ * A run with no sign of life (#635): its engine is gone (the plugin's
3
+ * process check found none, for a run it didn't start), or its live file
4
+ * went stale (written more than a minute ago). Its busy cards say so, with
5
+ * a flat red heartbeat, and their timers stop at the last sign of life.
6
+ */
7
+ import {
8
+ elapsedText,
9
+ liveIsFresh,
10
+ needsEngine,
11
+ type BoardState,
12
+ } from '../shared/board-state'
13
+
14
+ /**
15
+ * Why the run shows no sign of life, in words, and the time of its last
16
+ * sign (the live file's write, else the latest record): `null` while the
17
+ * engine is alive, or when the run needs none.
18
+ *
19
+ * @example
20
+ * noLife({ state, now }) // { text: 'no sign of life for 1m 10s', at: 1790000000000 }
21
+ */
22
+ export const noLife = ({
23
+ state,
24
+ now,
25
+ }: {
26
+ state: BoardState
27
+ now: number
28
+ }): { text: string; at: number } | null => {
29
+ if (!needsEngine({ state })) return null
30
+ const written = state.live_written_at
31
+ if (state.engine_gone) {
32
+ const at = Date.parse(
33
+ written ??
34
+ state.latest_at ??
35
+ state.run.last_time ??
36
+ state.run.started_at
37
+ )
38
+ return {
39
+ text: `engine not running · last sign of life ${elapsedText({ ms: now - at })} ago`,
40
+ at,
41
+ }
42
+ }
43
+ if (written === null || liveIsFresh({ written_at: written, now })) {
44
+ return null
45
+ }
46
+ const at = Date.parse(written)
47
+ return { text: `no sign of life for ${elapsedText({ ms: now - at })}`, at }
48
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The final review as a stack of its lenses (Waiting, Reviewing, Fixing,
3
+ * Clean), with the round and fix round and a line saying where it stands.
4
+ */
5
+ import { Icon } from '@getpaseo/plugin/client/react-native'
6
+ import { Text, View } from 'react-native'
7
+
8
+ import { DIMMED, lensColor } from './board-look'
9
+ import { Counter, StageHeader } from './board-panel-parts'
10
+ import { LENS_STAGES } from './board-panel-stages'
11
+ import type { Look } from './board-panel-styles'
12
+
13
+ import { LOOP_CAP, type BoardState, type LensCard } from '../shared/board-state'
14
+
15
+ const findingsLine = ({ lens }: { lens: LensCard }): string | null => {
16
+ const parts = [
17
+ lens.findings.blocker > 0 ? `${lens.findings.blocker} blocker` : null,
18
+ lens.findings.should_fix > 0
19
+ ? `${lens.findings.should_fix} should-fix`
20
+ : null,
21
+ lens.findings.nit > 0 ? `${lens.findings.nit} nit` : null,
22
+ ].filter((part) => part !== null)
23
+ return parts.length > 0 ? parts.join(', ') : null
24
+ }
25
+
26
+ const LensCardView = ({ lens, theme, styles }: { lens: LensCard } & Look) => {
27
+ const color = lensColor({ state: lens.state, theme })
28
+ const findings = findingsLine({ lens })
29
+ return (
30
+ <View style={styles.card}>
31
+ <View style={styles.cardTop}>
32
+ <Text style={styles.cardTitle}>{lens.name}</Text>
33
+ <View style={styles.spacer} />
34
+ <Text style={[styles.pill, { color, borderColor: color }]}>
35
+ {lens.state}
36
+ </Text>
37
+ </View>
38
+ {findings ? (
39
+ <Text
40
+ style={[
41
+ styles.muted,
42
+ {
43
+ color:
44
+ lens.findings.blocker > 0
45
+ ? theme.colors.statusDanger
46
+ : theme.colors.statusWarning,
47
+ },
48
+ ]}
49
+ >
50
+ {findings}
51
+ </Text>
52
+ ) : null}
53
+ </View>
54
+ )
55
+ }
56
+
57
+ const reviewLine = ({ state }: { state: BoardState }): string => {
58
+ const review = state.final_review
59
+ const open = state.tickets.filter(
60
+ (ticket) => ticket.stage !== 'done' && ticket.stage !== 'skipped'
61
+ ).length
62
+ switch (review.state) {
63
+ case 'waiting':
64
+ return review.active
65
+ ? 'Every ticket is done or skipped. It starts next.'
66
+ : `Starts when every ticket is done or skipped (${open} still open).`
67
+ case 'reviewing':
68
+ return `Round ${review.round}: a fresh reviewer per lens reads the whole run branch.`
69
+ case 'fixing':
70
+ return `Fix round ${review.fix_round}/${LOOP_CAP}: the implementer fixes what the lenses found.`
71
+ case 'stuck':
72
+ return 'Stuck: it needs your reply (see Needs you).'
73
+ case 'passed':
74
+ return 'Passed.'
75
+ }
76
+ }
77
+
78
+ export const FinalReviewStack = ({
79
+ state,
80
+ theme,
81
+ styles,
82
+ }: { state: BoardState } & Look) => {
83
+ const review = state.final_review
84
+ return (
85
+ <View
86
+ style={[styles.block, review.active ? null : { opacity: DIMMED }]}
87
+ >
88
+ <View style={styles.row}>
89
+ <Icon
90
+ name="GitPullRequest"
91
+ size={15}
92
+ color={theme.colors.foreground}
93
+ />
94
+ <Text style={styles.reviewTitle}>Final review · 5 lenses</Text>
95
+ <View style={styles.spacer} />
96
+ <Counter
97
+ label="round"
98
+ value={review.round}
99
+ theme={theme}
100
+ styles={styles}
101
+ />
102
+ <Counter
103
+ label="fix"
104
+ value={review.fix_round}
105
+ theme={theme}
106
+ styles={styles}
107
+ />
108
+ </View>
109
+ <Text style={styles.muted}>{reviewLine({ state })}</Text>
110
+ {LENS_STAGES.map((entry) => {
111
+ const lenses = review.lenses.filter(
112
+ (lens) => lens.state === entry.stage
113
+ )
114
+ return (
115
+ <View key={entry.stage} style={styles.block}>
116
+ <StageHeader
117
+ title={entry.title}
118
+ hint=""
119
+ icon={entry.icon}
120
+ count={lenses.length}
121
+ color={lensColor({ state: entry.stage, theme })}
122
+ folded={null}
123
+ onPress={null}
124
+ theme={theme}
125
+ styles={styles}
126
+ />
127
+ {lenses.length > 0 ? (
128
+ <View style={styles.stageBody}>
129
+ {lenses.map((lens) => (
130
+ <LensCardView
131
+ key={lens.name}
132
+ lens={lens}
133
+ theme={theme}
134
+ styles={styles}
135
+ />
136
+ ))}
137
+ </View>
138
+ ) : null}
139
+ </View>
140
+ )
141
+ })}
142
+ </View>
143
+ )
144
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * A busy card's heartbeat (#631): 20 small bars at the end of its
3
+ * step-dots line, one per second, oldest on the left, each as tall as its
4
+ * agent's activity that second in the live file. Green while working; a
5
+ * flat line in the muted colour when nothing happened in those seconds; a
6
+ * flat red line when the run shows no sign of life (#635).
7
+ * Plain `View`s, redrawn with the card on the board's 1-second tick.
8
+ */
9
+ import { View } from 'react-native'
10
+
11
+ import type { Look } from './board-panel-styles'
12
+
13
+ import type { LiveAgent } from '../shared/board-state'
14
+
15
+ /** How many seconds the heartbeat shows. */
16
+ const BARS = 20
17
+
18
+ /** A quiet second's bar height, in points. */
19
+ const LOW = 2
20
+
21
+ /** A count this big or bigger fills a bar. */
22
+ const LOUD = 12
23
+
24
+ /** A bar's height for `count`: one point more per tick, up to `LOUD`. */
25
+ const heightOf = (count: number): number => LOW + Math.min(count, LOUD)
26
+
27
+ export const Heartbeat = ({
28
+ live,
29
+ gone = false,
30
+ theme,
31
+ styles,
32
+ }: {
33
+ /** `null` for a card with no live entry: a flat line. */
34
+ live: LiveAgent | null
35
+ /** The run shows no sign of life (#635): a flat red line. */
36
+ gone?: boolean
37
+ } & Look) => {
38
+ const counts = gone || live === null ? [] : live.activity.slice(-BARS)
39
+ const bars = [
40
+ ...Array.from({ length: BARS - counts.length }, () => 0),
41
+ ...counts,
42
+ ]
43
+ const color = gone
44
+ ? theme.colors.statusDanger
45
+ : bars.some((count) => count > 0)
46
+ ? theme.colors.statusSuccess
47
+ : theme.colors.foregroundMuted
48
+ return (
49
+ <View accessibilityLabel="Heartbeat" style={styles.heartbeat}>
50
+ {bars.map((count, index) => (
51
+ <View
52
+ key={index}
53
+ style={[
54
+ styles.heartbeatBar,
55
+ { height: heightOf(count), backgroundColor: color },
56
+ ]}
57
+ />
58
+ ))}
59
+ </View>
60
+ )
61
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * A card's helper lines (#636): under its step-dots line, one line per
3
+ * helper its agent has running, from the run's live file: "helper", the
4
+ * helper's model as a coloured tag, its latest action and how long ago,
5
+ * with a left edge in the model's colour. A helper that's done has no line.
6
+ */
7
+ import { Text, View } from 'react-native'
8
+
9
+ import { modelColor } from './board-look'
10
+ import type { Look } from './board-panel-styles'
11
+ import { callWords } from './live-line'
12
+ import { CutLine } from './one-line'
13
+
14
+ import { elapsedText, type LiveAgent } from '../shared/board-state'
15
+
16
+ export const HelperLines = ({
17
+ live,
18
+ now,
19
+ theme,
20
+ styles,
21
+ }: { live: LiveAgent | null; now: number } & Look) => (
22
+ <>
23
+ {(live?.helpers ?? [])
24
+ .filter((helper) => !helper.done)
25
+ .map((helper, index) => {
26
+ const model = helper.model ?? 'haiku'
27
+ const color = modelColor({ model, theme })
28
+ const { action } = helper
29
+ return (
30
+ <View
31
+ key={index}
32
+ style={[
33
+ styles.line,
34
+ styles.helperLine,
35
+ { borderLeftColor: color },
36
+ ]}
37
+ >
38
+ <Text
39
+ style={[styles.muted, styles.fixed]}
40
+ numberOfLines={1}
41
+ >
42
+ helper
43
+ </Text>
44
+ <Text
45
+ style={[styles.tag, { color, borderColor: color }]}
46
+ numberOfLines={1}
47
+ >
48
+ {model}
49
+ </Text>
50
+ {action !== null ? (
51
+ <>
52
+ <CutLine
53
+ text={callWords(action)}
54
+ style={styles.text}
55
+ styles={styles}
56
+ />
57
+ <Text
58
+ style={[styles.mono, styles.fixed]}
59
+ numberOfLines={1}
60
+ >
61
+ {`${elapsedText({ ms: now - Date.parse(action.at) })} ago`}
62
+ </Text>
63
+ </>
64
+ ) : null}
65
+ </View>
66
+ )
67
+ })}
68
+ </>
69
+ )
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The live feed (#634): a folded section at the bottom of the side panel,
3
+ * the run's last actions from its live file, across tickets. Open, it
4
+ * shows one line per action, newest last: the time, the ticket, the role
5
+ * in its model's colour, and the action. The panel keeps whether it is
6
+ * open, so a new read leaves it as it was.
7
+ */
8
+ import { Text, View } from 'react-native'
9
+
10
+ import { clockText, modelColor } from './board-look'
11
+ import { Section, StageHeader } from './board-panel-parts'
12
+ import type { Look } from './board-panel-styles'
13
+ import { callWords } from './live-line'
14
+ import { BubbleScope, CutLine, useBubbles } from './one-line'
15
+
16
+ import type { BoardState, LiveFeedEntry } from '../shared/board-state'
17
+
18
+ const FeedLine = ({
19
+ entry,
20
+ theme,
21
+ styles,
22
+ }: { entry: LiveFeedEntry } & Look) => (
23
+ <View style={styles.line}>
24
+ <Text style={[styles.mono, styles.fixed]} numberOfLines={1}>
25
+ {clockText({ iso: entry.at })}
26
+ </Text>
27
+ {entry.ticket === null ? null : (
28
+ <Text style={[styles.muted, styles.fixed]} numberOfLines={1}>
29
+ #{entry.ticket}
30
+ </Text>
31
+ )}
32
+ <Text
33
+ style={[
34
+ styles.muted,
35
+ styles.fixed,
36
+ { color: modelColor({ model: entry.model ?? '', theme }) },
37
+ ]}
38
+ numberOfLines={1}
39
+ >
40
+ {entry.role}
41
+ </Text>
42
+ <CutLine text={callWords(entry)} style={styles.muted} styles={styles} />
43
+ </View>
44
+ )
45
+
46
+ export const LiveFeed = ({
47
+ state,
48
+ folded,
49
+ onToggle,
50
+ theme,
51
+ styles,
52
+ }: {
53
+ state: BoardState
54
+ folded: boolean
55
+ onToggle: () => void
56
+ } & Look) => {
57
+ const bubbles = useBubbles()
58
+ const look = { theme, styles }
59
+ return (
60
+ <Section styles={styles}>
61
+ <StageHeader
62
+ title="Live feed"
63
+ hint="the latest actions, newest last"
64
+ icon="Activity"
65
+ count={state.feed.length}
66
+ color={theme.colors.foregroundMuted}
67
+ folded={folded}
68
+ onPress={onToggle}
69
+ {...look}
70
+ />
71
+ {folded || state.feed.length === 0 ? null : (
72
+ <View style={styles.stageBody}>
73
+ <BubbleScope bubbles={bubbles} styles={styles}>
74
+ {state.feed.map((entry, index) => (
75
+ <FeedLine
76
+ key={`${entry.at}-${index}`}
77
+ entry={entry}
78
+ {...look}
79
+ />
80
+ ))}
81
+ </BubbleScope>
82
+ </View>
83
+ )}
84
+ </Section>
85
+ )
86
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * A card's "doing now" words (#628), from its agent's entry in the run's
3
+ * live file: who is working and on what, and how long ago it last acted.
4
+ */
5
+ import { elapsedText, type LiveAgent } from '../shared/board-state'
6
+
7
+ /** A tool's icon and verb on the card; a tool not here gets "•" and its name. */
8
+ const TOOL_WORDS: Record<string, { icon: string; verb: string }> = {
9
+ Edit: { icon: '✎', verb: 'editing' },
10
+ MultiEdit: { icon: '✎', verb: 'editing' },
11
+ NotebookEdit: { icon: '✎', verb: 'editing' },
12
+ Write: { icon: '✎', verb: 'writing' },
13
+ Read: { icon: '≡', verb: 'reading' },
14
+ Grep: { icon: '⌕', verb: 'searching' },
15
+ Glob: { icon: '⌕', verb: 'searching' },
16
+ Bash: { icon: '▸', verb: '' },
17
+ Agent: { icon: '⇢', verb: 'helper' },
18
+ Task: { icon: '⇢', verb: 'helper' },
19
+ }
20
+
21
+ /** A tool call in words, with its icon first: "✎ editing gate-runner.ts". */
22
+ export const callWords = ({
23
+ tool,
24
+ target,
25
+ }: {
26
+ tool: string
27
+ target: string
28
+ }) => {
29
+ const { icon, verb } = TOOL_WORDS[tool] ?? { icon: '•', verb: tool }
30
+ return [icon, verb, target].filter((part) => part !== '').join(' ')
31
+ }
32
+
33
+ /**
34
+ * "<role> <icon> <action>", or, while a tool call is open, "<role> <icon>
35
+ * <action> · running <time>". `null` with no live entry or no action yet,
36
+ * so the card shows its step text.
37
+ *
38
+ * @example
39
+ * liveText({ live, now }) // 'implementer ✎ editing gate-runner.ts'
40
+ * liveText({ live: running, now }) // 'implementer ▸ bun test · running 3m 12s'
41
+ */
42
+ export const liveText = ({
43
+ live,
44
+ now,
45
+ }: {
46
+ live: LiveAgent | null
47
+ now: number
48
+ }): string | null => {
49
+ if (live === null) return null
50
+ const { open_call, action } = live
51
+ if (open_call !== null) {
52
+ const running = elapsedText({
53
+ ms: now - Date.parse(open_call.started_at),
54
+ })
55
+ return `${live.role} ${callWords(open_call)} · running ${running}`
56
+ }
57
+ return action === null ? null : `${live.role} ${callWords(action)}`
58
+ }
59
+
60
+ /**
61
+ * The latest action's age after a card's tokens, " · 2s ago", or nothing
62
+ * with no live action.
63
+ */
64
+ export const liveAgeText = ({
65
+ live,
66
+ now,
67
+ }: {
68
+ live: LiveAgent | null
69
+ now: number
70
+ }): string =>
71
+ live?.action
72
+ ? ` · ${elapsedText({ ms: now - Date.parse(live.action.at) })} ago`
73
+ : ''
@@ -0,0 +1,178 @@
1
+ /**
2
+ * A card's model line (#630): the agent's model as a coloured tag, then
3
+ * Clef's line saying why it got that model, from its `model_reason` and
4
+ * its `agent_model` answer. A pick from Clef's answer shows a bar filled to
5
+ * its confidence: green when sharp (at or over the config's
6
+ * `models.confidence_bar`), orange when split. A tap on the line shows a
7
+ * full sentence. An older journal, with no `model_reason`, shows the tag
8
+ * only.
9
+ */
10
+ import { Text, View } from 'react-native'
11
+
12
+ import { CONFIDENCE_COLORS, modelColor } from './board-look'
13
+ import type { Look } from './board-panel-styles'
14
+ import { CutLine } from './one-line'
15
+
16
+ import type { AgentModel } from '../shared/board-state'
17
+
18
+ /** Clef's line: its short text, its full sentence, and its bar, if any. */
19
+ type ClefLine = {
20
+ short: string
21
+ full: string
22
+ /** The confidence its bar is filled to; `null` for no bar. */
23
+ confidence: number | null
24
+ sharp: boolean
25
+ }
26
+
27
+ /** "ticket-reviewer" → "ticket-reviewers". */
28
+ const plural = ({ role }: { role: string }): string => `${role}s`
29
+
30
+ const capital = ({ text }: { text: string }): string =>
31
+ text.charAt(0).toUpperCase() + text.slice(1)
32
+
33
+ /**
34
+ * Clef's line for a model and its reason, judged against the bar; `null`
35
+ * for an older journal's start, with no reason, or a reason the board
36
+ * doesn't know.
37
+ *
38
+ * @example
39
+ * clefLine({ model: { role: 'implementer', name: 'opus', reason: 'unsure', answer: { role: 'implementer', pick: 'sonnet', confidence: 0.56 } }, bar: 0.8 })
40
+ * // { short: '0.56 split → stayed on opus', full: 'Clef picked sonnet but was only 0.56 sure (split: under 0.8), so Luca stayed on opus.', confidence: 0.56, sharp: false }
41
+ */
42
+ export const clefLine = ({
43
+ model,
44
+ bar,
45
+ }: {
46
+ model: AgentModel
47
+ bar: number
48
+ }): ClefLine | null => {
49
+ const { role, name, reason, answer } = model
50
+ const confidence = answer?.confidence ?? null
51
+ const pick = answer?.pick ?? 'no model'
52
+ const sharp = confidence !== null && confidence >= bar
53
+ const word = sharp ? 'sharp' : 'split'
54
+ const sure = confidence?.toFixed(2) ?? ''
55
+ const judged = `${word}: ${sharp ? 'at or over' : 'under'} ${bar}`
56
+ const plain = { confidence: null, sharp: false }
57
+ switch (reason) {
58
+ case 'decision_model':
59
+ return {
60
+ short: `${confidence === null ? 'clef' : `${sure} ${word}`} → ${name}`,
61
+ full:
62
+ confidence === null
63
+ ? `Clef picked ${name}, so Luca ran the ${role} on it.`
64
+ : `Clef picked ${name} and was ${sure} sure (${judged}), so Luca ran the ${role} on it.`,
65
+ confidence,
66
+ sharp,
67
+ }
68
+ case 'unsure':
69
+ return {
70
+ short: `${confidence === null ? 'unsure' : `${sure} ${word}`} → stayed on ${name}`,
71
+ full:
72
+ confidence === null
73
+ ? `Clef picked ${pick} but didn't say how sure it was, so Luca stayed on ${name}.`
74
+ : sharp
75
+ ? `Clef picked ${pick}, which the ${role} can't run on, so Luca stayed on ${name}.`
76
+ : `Clef picked ${pick} but was only ${sure} sure (${judged}), so Luca stayed on ${name}.`,
77
+ confidence,
78
+ sharp,
79
+ }
80
+ case 'floor':
81
+ return {
82
+ short: `floor ${name} → ${name}`,
83
+ full: `The ${role}'s model floor is ${name}, so it runs on ${name} whatever Clef picks.`,
84
+ ...plain,
85
+ }
86
+ case 'after_failure':
87
+ return {
88
+ short: `a try failed → ${name}`,
89
+ full: `The ${role} already failed a try on this ticket on a cheaper model, so Luca runs it on ${name}.`,
90
+ ...plain,
91
+ }
92
+ case 'no_answer':
93
+ return {
94
+ short: `clef didn't answer → ${name}`,
95
+ full: `Clef didn't answer (it was off, timed out, or failed), so Luca ran the ${role} on ${name}.`,
96
+ ...plain,
97
+ }
98
+ case 'locked':
99
+ return {
100
+ short: `${plural({ role })} always run on ${name}`,
101
+ full: `${capital({ text: plural({ role }) })} always run on ${name}: Clef doesn't pick their model.`,
102
+ ...plain,
103
+ }
104
+ default:
105
+ return null
106
+ }
107
+ }
108
+
109
+ /** Clef's confidence as a small bar, filled to it. */
110
+ const ConfidenceBar = ({
111
+ confidence,
112
+ sharp,
113
+ styles,
114
+ }: {
115
+ confidence: number
116
+ sharp: boolean
117
+ } & Pick<Look, 'styles'>) => {
118
+ const now = Math.round(confidence * 100)
119
+ return (
120
+ <View
121
+ accessibilityRole="progressbar"
122
+ accessibilityLabel={`Clef's confidence, ${sharp ? 'sharp' : 'split'}`}
123
+ accessibilityValue={{ min: 0, max: 100, now }}
124
+ style={styles.confidence}
125
+ >
126
+ <View
127
+ style={[
128
+ styles.confidenceFill,
129
+ {
130
+ width: `${now}%`,
131
+ backgroundColor: sharp
132
+ ? CONFIDENCE_COLORS.sharp
133
+ : CONFIDENCE_COLORS.split,
134
+ },
135
+ ]}
136
+ />
137
+ </View>
138
+ )
139
+ }
140
+
141
+ /** The model tag and Clef's line, on one line. */
142
+ export const ModelLine = ({
143
+ model,
144
+ bar,
145
+ theme,
146
+ styles,
147
+ }: {
148
+ model: AgentModel
149
+ bar: number
150
+ } & Look) => {
151
+ const color = modelColor({ model: model.name, theme })
152
+ const clef = clefLine({ model, bar })
153
+ return (
154
+ <View style={styles.line}>
155
+ <Text
156
+ style={[styles.tag, { color, borderColor: color }]}
157
+ numberOfLines={1}
158
+ >
159
+ {model.name}
160
+ </Text>
161
+ {clef !== null && clef.confidence !== null ? (
162
+ <ConfidenceBar
163
+ confidence={clef.confidence}
164
+ sharp={clef.sharp}
165
+ styles={styles}
166
+ />
167
+ ) : null}
168
+ {clef ? (
169
+ <CutLine
170
+ text={clef.short}
171
+ full={clef.full}
172
+ style={styles.muted}
173
+ styles={styles}
174
+ />
175
+ ) : null}
176
+ </View>
177
+ )
178
+ }