@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,98 @@
1
+ /**
2
+ * The run tabs: one chip per recent run, with the older ones folded behind
3
+ * one chip.
4
+ */
5
+ import { useState } from 'react'
6
+
7
+ import { Pressable, Text, View } from 'react-native'
8
+
9
+ import { statusColor, statusText } from './board-look'
10
+ import type { Look } from './board-panel-styles'
11
+ import { useCloseBubble } from './one-line'
12
+
13
+ import { foldRuns, type RunSummary } from '../shared/board-state'
14
+
15
+ /** A tab per recent run; the older ones stay folded behind one chip. */
16
+ export const RunPicker = ({
17
+ runs,
18
+ selected,
19
+ onPick,
20
+ theme,
21
+ styles,
22
+ }: {
23
+ runs: RunSummary[]
24
+ selected: string
25
+ onPick: (run_id: string) => void
26
+ } & Look) => {
27
+ const [showOlder, setShowOlder] = useState(false)
28
+ const closeBubble = useCloseBubble()
29
+ const { shown, folded } = foldRuns({ runs, selected })
30
+ const pick = (run_id: string) => {
31
+ closeBubble()
32
+ onPick(run_id)
33
+ }
34
+ return (
35
+ <View style={styles.row}>
36
+ {(showOlder ? [...shown, ...folded] : shown).map((run) => (
37
+ <RunChip
38
+ key={run.run_id}
39
+ run={run}
40
+ on={run.run_id === selected}
41
+ onPick={pick}
42
+ theme={theme}
43
+ styles={styles}
44
+ />
45
+ ))}
46
+ {folded.length > 0 ? (
47
+ <Pressable
48
+ accessibilityRole="button"
49
+ accessibilityLabel={
50
+ showOlder
51
+ ? 'Fold the older runs'
52
+ : `Show ${folded.length} older runs`
53
+ }
54
+ onPress={() => {
55
+ closeBubble()
56
+ setShowOlder((open) => !open)
57
+ }}
58
+ style={styles.chip}
59
+ >
60
+ <Text style={styles.small}>
61
+ {showOlder ? 'fold older' : `${folded.length} older`}
62
+ </Text>
63
+ </Pressable>
64
+ ) : null}
65
+ </View>
66
+ )
67
+ }
68
+
69
+ const RunChip = ({
70
+ run,
71
+ on,
72
+ onPick,
73
+ theme,
74
+ styles,
75
+ }: {
76
+ run: RunSummary
77
+ on: boolean
78
+ onPick: (run_id: string) => void
79
+ } & Look) => (
80
+ <Pressable
81
+ accessibilityRole="button"
82
+ accessibilityLabel={`Show run ${run.run_id}`}
83
+ onPress={() => onPick(run.run_id)}
84
+ style={[styles.chip, on ? styles.chipOn : null]}
85
+ >
86
+ <Text style={styles.small}>
87
+ {run.spec_number === null ? 'demo' : `#${run.spec_number}`} ·{' '}
88
+ <Text
89
+ style={{
90
+ color: statusColor({ status: run.status, theme }),
91
+ }}
92
+ >
93
+ {statusText({ status: run.status })}
94
+ </Text>
95
+ {run.needs_you > 0 ? ` · ${run.needs_you} need you` : ''}
96
+ </Text>
97
+ </Pressable>
98
+ )
@@ -0,0 +1,131 @@
1
+ /**
2
+ * The status bar (#633): one line pinned at the bottom of the side panel,
3
+ * below the scrolling cards, such as
4
+ * "4/10 joined · 2·1 agents · 3.1M/15M tok · week 42% · latest 2s ago".
5
+ * Each agent count is in its model's color. A tap shows it all in words.
6
+ */
7
+ import { Fragment } from 'react'
8
+
9
+ import { Text, View } from 'react-native'
10
+
11
+ import { modelColor, percentText, tokensText, useNow } from './board-look'
12
+ import type { Look } from './board-panel-styles'
13
+ import { BubbleScope, CutLine, useBubbles } from './one-line'
14
+
15
+ import { elapsedText, type BoardState } from '../shared/board-state'
16
+
17
+ /** The models first in the bar, in this order; any other comes after. */
18
+ const MODEL_ORDER = ['opus', 'sonnet', 'haiku']
19
+
20
+ /**
21
+ * The agents in a turn and their running helpers, counted per model, in
22
+ * `MODEL_ORDER`.
23
+ *
24
+ * @example
25
+ * agentsPerModel({ state }) // [{ model: 'opus', count: 2 }, { model: 'sonnet', count: 1 }]
26
+ */
27
+ const agentsPerModel = ({
28
+ state,
29
+ }: {
30
+ state: BoardState
31
+ }): { model: string; count: number }[] => {
32
+ const counts = new Map<string, number>()
33
+ const count = (name: string | null) => {
34
+ const model = (name ?? 'unknown').toLowerCase()
35
+ counts.set(model, (counts.get(model) ?? 0) + 1)
36
+ }
37
+ for (const agent of state.live_agents) {
38
+ count(agent.model)
39
+ // Running helpers count too (#636), on their own model.
40
+ for (const helper of agent.helpers) {
41
+ if (!helper.done) count(helper.model)
42
+ }
43
+ }
44
+ const rank = (model: string) => {
45
+ const found = MODEL_ORDER.indexOf(model)
46
+ return found === -1 ? MODEL_ORDER.length : found
47
+ }
48
+ return [...counts.entries()]
49
+ .map(([model, count]) => ({ model, count }))
50
+ .sort(
51
+ (left, right) =>
52
+ rank(left.model) - rank(right.model) ||
53
+ left.model.localeCompare(right.model)
54
+ )
55
+ }
56
+
57
+ /** Tokens, short, with no ".0": 15000000 → "15M", 3100000 → "3.1M". */
58
+ const shortTokens = ({ tokens }: { tokens: number }): string =>
59
+ tokensText({ tokens }).replace(/\.0(?=[kM]$)/, '')
60
+
61
+ export const StatusBar = ({
62
+ state,
63
+ theme,
64
+ styles,
65
+ }: { state: BoardState } & Look) => {
66
+ const bubbles = useBubbles()
67
+ const now = useNow()
68
+ const joined = state.tickets.filter((card) => card.stage === 'done').length
69
+ const total = state.tickets.length
70
+ const models = agentsPerModel({ state })
71
+ const used = shortTokens({ tokens: state.run_tokens })
72
+ const budget = shortTokens({ tokens: state.run_budget_tokens })
73
+ const week = state.usage?.weekly_percent ?? null
74
+ const age =
75
+ state.latest_at === null
76
+ ? null
77
+ : elapsedText({ ms: now - Date.parse(state.latest_at) })
78
+
79
+ const head = `${joined}/${total} joined · `
80
+ const counts =
81
+ models.length === 0
82
+ ? '0'
83
+ : models.map(({ count }) => String(count)).join('·')
84
+ const tail = [
85
+ ' agents',
86
+ `${used}/${budget} tok`,
87
+ `week ${percentText({ percent: week })}`,
88
+ `latest ${age === null ? '–' : `${age} ago`}`,
89
+ ].join(' · ')
90
+ const full = [
91
+ `${joined} of ${total} tickets joined`,
92
+ models.length === 0
93
+ ? 'no agents running'
94
+ : `agents running: ${models.map(({ model, count }) => `${count} on ${model}`).join(', ')}`,
95
+ `${used} of the run's ${budget} tokens`,
96
+ week === null
97
+ ? 'no plan reading yet this week'
98
+ : `plan ${week}% used this week`,
99
+ age === null ? 'no event yet' : `latest event ${age} ago`,
100
+ ].join(' · ')
101
+
102
+ return (
103
+ <View style={styles.statusBar}>
104
+ <BubbleScope bubbles={bubbles} styles={styles}>
105
+ <CutLine
106
+ text={`${head}${counts}${tail}`}
107
+ full={full}
108
+ style={styles.mono}
109
+ styles={styles}
110
+ >
111
+ {head}
112
+ {models.length === 0
113
+ ? counts
114
+ : models.map(({ model, count }, index) => (
115
+ <Fragment key={model}>
116
+ {index > 0 ? '·' : ''}
117
+ <Text
118
+ style={{
119
+ color: modelColor({ model, theme }),
120
+ }}
121
+ >
122
+ {String(count)}
123
+ </Text>
124
+ </Fragment>
125
+ ))}
126
+ {tail}
127
+ </CutLine>
128
+ </BubbleScope>
129
+ </View>
130
+ )
131
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * A ticket card's step dots: one dot per step (tests, red check, code,
3
+ * checks, review), done, current, stopped, skipped, or still to do, with
4
+ * the current step's name.
5
+ */
6
+ import { Text, View } from 'react-native'
7
+
8
+ import type { Look } from './board-panel-styles'
9
+
10
+ import {
11
+ ALL_STEPS_DONE,
12
+ REFACTOR_SKIPS_STEPS,
13
+ STEP_NAMES,
14
+ type TicketCard,
15
+ } from '../shared/board-state'
16
+
17
+ export type DotKind = 'done' | 'current' | 'todo' | 'skip' | 'stopped'
18
+
19
+ export const dotKinds = ({ ticket }: { ticket: TicketCard }): DotKind[] =>
20
+ STEP_NAMES.map((_, index) => {
21
+ if (ticket.refactor && index < REFACTOR_SKIPS_STEPS) return 'skip'
22
+ if (index < ticket.step) return 'done'
23
+ if (index === ticket.step) {
24
+ return ticket.stage === 'skipped' || ticket.stage === 'stuck'
25
+ ? 'stopped'
26
+ : 'current'
27
+ }
28
+ return 'todo'
29
+ })
30
+
31
+ const stepLabel = ({ ticket }: { ticket: TicketCard }): string => {
32
+ if (ticket.step >= ALL_STEPS_DONE) return 'all steps done'
33
+ const name = STEP_NAMES[ticket.step]
34
+ if (!name) return ticket.activity
35
+ if (ticket.stage === 'stuck') return `stuck at ${name}`
36
+ if (ticket.stage === 'skipped') return `stopped at ${name}`
37
+ return name
38
+ }
39
+
40
+ export const StepDots = ({
41
+ ticket,
42
+ theme,
43
+ styles,
44
+ }: { ticket: TicketCard } & Look) => {
45
+ const kinds = dotKinds({ ticket })
46
+ const { colors } = theme
47
+ const highlight =
48
+ ticket.stage === 'stuck' ? colors.statusDanger : colors.accent
49
+ const labelColor =
50
+ ticket.step >= ALL_STEPS_DONE
51
+ ? colors.statusSuccess
52
+ : kinds.includes('current')
53
+ ? highlight
54
+ : colors.foregroundMuted
55
+ const described = STEP_NAMES.map(
56
+ (name, index) => `${name} ${kinds[index] ?? ''}`
57
+ ).join(', ')
58
+ return (
59
+ <View
60
+ style={styles.dots}
61
+ accessible
62
+ accessibilityLabel={`Steps: ${described}`}
63
+ >
64
+ {kinds.map((kind, index) => {
65
+ const reached = kind === 'done' || kind === 'current'
66
+ const look =
67
+ kind === 'done'
68
+ ? [
69
+ styles.dot,
70
+ {
71
+ backgroundColor: colors.statusSuccess,
72
+ borderColor: colors.statusSuccess,
73
+ },
74
+ ]
75
+ : kind === 'current'
76
+ ? [
77
+ styles.dotCurrent,
78
+ {
79
+ backgroundColor: highlight,
80
+ borderColor: highlight,
81
+ },
82
+ ]
83
+ : kind === 'stopped'
84
+ ? [
85
+ styles.dotCurrent,
86
+ {
87
+ backgroundColor: colors.surface2,
88
+ borderColor: highlight,
89
+ },
90
+ ]
91
+ : kind === 'skip'
92
+ ? [
93
+ styles.dot,
94
+ styles.dotSkip,
95
+ { borderColor: colors.foregroundMuted },
96
+ ]
97
+ : [
98
+ styles.dot,
99
+ { borderColor: colors.foregroundMuted },
100
+ ]
101
+ return (
102
+ <View key={STEP_NAMES[index]} style={styles.dotCell}>
103
+ {index > 0 ? (
104
+ <View
105
+ style={[
106
+ styles.link,
107
+ {
108
+ backgroundColor: reached
109
+ ? colors.statusSuccess
110
+ : colors.border,
111
+ },
112
+ ]}
113
+ />
114
+ ) : null}
115
+ <View style={look} />
116
+ </View>
117
+ )
118
+ })}
119
+ <Text
120
+ style={[styles.stepLabel, { color: labelColor }]}
121
+ numberOfLines={1}
122
+ >
123
+ {stepLabel({ ticket })}
124
+ </Text>
125
+ </View>
126
+ )
127
+ }
@@ -0,0 +1,269 @@
1
+ import { useMemo, useState } from 'react'
2
+
3
+ import type { PluginTheme } from '@getpaseo/plugin'
4
+ import { usePaseo, useRpc } from '@getpaseo/plugin/client'
5
+ import { copyText, useToast } from '@getpaseo/plugin/client/react-native'
6
+ import { Pressable, StyleSheet, Text, View } from 'react-native'
7
+
8
+ import { MONO } from './board-look'
9
+
10
+ import { replyPostRpc } from '../shared/board-rpc'
11
+ import {
12
+ confirmReplyText,
13
+ postedFor,
14
+ postedText,
15
+ replyButtons,
16
+ unstickCommand,
17
+ type PostedReply,
18
+ type ReplyButton,
19
+ } from '../shared/reply-actions'
20
+
21
+ /**
22
+ * The actions on stuck work (#503), in the side panel and in the chat's
23
+ * stuck rows: a button per reply the engine takes, which asks to confirm
24
+ * and then posts the reply on the spec issue through `reply.post`, and
25
+ * "Help me", which sends `/luca-unstick` to a chat's agent.
26
+ */
27
+
28
+ const makeStyles = ({ theme }: { theme: PluginTheme }) => {
29
+ const { colors } = theme
30
+ return StyleSheet.create({
31
+ box: { gap: 6 },
32
+ row: {
33
+ flexDirection: 'row',
34
+ flexWrap: 'wrap',
35
+ alignItems: 'center',
36
+ gap: 6,
37
+ },
38
+ label: {
39
+ color: colors.foregroundMuted,
40
+ fontSize: 12,
41
+ fontWeight: '700',
42
+ },
43
+ text: { color: colors.foreground, fontSize: 12 },
44
+ muted: { color: colors.foregroundMuted, fontSize: 12 },
45
+ danger: { color: colors.statusDanger, fontSize: 12 },
46
+ success: { color: colors.statusSuccess, fontSize: 12 },
47
+ button: {
48
+ borderWidth: 1,
49
+ borderColor: colors.border,
50
+ backgroundColor: colors.surface2,
51
+ borderRadius: 6,
52
+ paddingHorizontal: 9,
53
+ paddingVertical: 4,
54
+ },
55
+ buttonText: {
56
+ color: colors.foreground,
57
+ fontSize: 12,
58
+ fontFamily: MONO,
59
+ },
60
+ primary: {
61
+ borderRadius: 6,
62
+ paddingHorizontal: 10,
63
+ paddingVertical: 4,
64
+ backgroundColor: colors.accent,
65
+ },
66
+ primaryText: {
67
+ color: colors.accentForeground,
68
+ fontSize: 12,
69
+ fontWeight: '700',
70
+ },
71
+ posted: { borderColor: colors.statusSuccess },
72
+ postedText: { color: colors.statusSuccess },
73
+ locked: { opacity: 0.45 },
74
+ })
75
+ }
76
+
77
+ type Styles = ReturnType<typeof makeStyles>
78
+
79
+ /** What a reply button reads, for its state. */
80
+ const buttonLabel = ({ button }: { button: ReplyButton }): string => {
81
+ switch (button.state) {
82
+ case 'sending':
83
+ return `${button.reply} · posting…`
84
+ case 'posted':
85
+ return `${button.reply} · posted`
86
+ default:
87
+ return button.reply
88
+ }
89
+ }
90
+
91
+ const ReplyButtonView = ({
92
+ button,
93
+ onPress,
94
+ styles,
95
+ }: {
96
+ button: ReplyButton
97
+ onPress: () => void
98
+ styles: Styles
99
+ }) => (
100
+ <Pressable
101
+ accessibilityRole="button"
102
+ accessibilityLabel={`Post the reply ${button.reply}`}
103
+ accessibilityState={{ disabled: button.state !== 'ready' }}
104
+ disabled={button.state !== 'ready'}
105
+ onPress={onPress}
106
+ style={[
107
+ styles.button,
108
+ button.state === 'posted' ? styles.posted : null,
109
+ button.state === 'locked' ? styles.locked : null,
110
+ ]}
111
+ >
112
+ <Text
113
+ style={[
114
+ styles.buttonText,
115
+ button.state === 'posted' ? styles.postedText : null,
116
+ ]}
117
+ >
118
+ {buttonLabel({ button })}
119
+ </Text>
120
+ </Pressable>
121
+ )
122
+
123
+ /**
124
+ * A stuck item's reply buttons and "Help me". `posted` is what the board
125
+ * says it posted (from `board.read`); a post made here shows at once too.
126
+ * `chat_agent_id` is the chat "Help me" sends `/luca-unstick` to; without
127
+ * one, the command is copied instead.
128
+ */
129
+ export const StuckActions = ({
130
+ run_id,
131
+ item,
132
+ spec_number,
133
+ posted,
134
+ chat_agent_id,
135
+ theme,
136
+ }: {
137
+ run_id: string
138
+ item: { key: string; ticket: number | null; since: string }
139
+ spec_number: number | null
140
+ posted: PostedReply[]
141
+ chat_agent_id: string | null
142
+ theme: PluginTheme
143
+ }) => {
144
+ const styles = useMemo(() => makeStyles({ theme }), [theme])
145
+ const postReply = useRpc(replyPostRpc)
146
+ const paseo = usePaseo()
147
+ const toast = useToast()
148
+ const [confirming, setConfirming] = useState<string | null>(null)
149
+ const [sending, setSending] = useState<string | null>(null)
150
+ const [mine, setMine] = useState<PostedReply | null>(null)
151
+ const [note, setNote] = useState<{ ok: boolean; text: string } | null>(null)
152
+
153
+ const known = [...posted, ...(mine ? [mine] : [])]
154
+ const buttons = replyButtons({ item, posted: known, sending })
155
+ const done = postedFor({ item, posted: known })
156
+ const command = unstickCommand({ run_id, ticket: item.ticket })
157
+
158
+ const post = async ({ reply }: { reply: string }) => {
159
+ setConfirming(null)
160
+ setSending(reply)
161
+ setNote(null)
162
+ try {
163
+ const output = await postReply({ run_id, key: item.key, reply })
164
+ if (output.posted) setMine(output.posted)
165
+ setNote({ ok: output.ok, text: output.message })
166
+ } catch (error) {
167
+ setNote({
168
+ ok: false,
169
+ text: `Couldn't post \`${reply}\`: ${String(error)}`,
170
+ })
171
+ } finally {
172
+ setSending(null)
173
+ }
174
+ }
175
+
176
+ const helpMe = async () => {
177
+ if (chat_agent_id !== null) {
178
+ try {
179
+ await paseo.agents.ref(chat_agent_id).send(command)
180
+ toast.show(`Sent ${command} to the run's chat.`, {
181
+ variant: 'success',
182
+ })
183
+ return
184
+ } catch {
185
+ // The chat may be gone: copy it instead.
186
+ }
187
+ }
188
+ try {
189
+ await copyText(command)
190
+ toast.show(`Copied ${command}. Paste it in a chat.`, {
191
+ variant: 'success',
192
+ })
193
+ } catch {
194
+ toast.error(`Could not copy. Type ${command} in a chat.`)
195
+ }
196
+ }
197
+
198
+ return (
199
+ <View style={styles.box}>
200
+ <Text style={styles.label}>
201
+ Reply on{' '}
202
+ {spec_number === null
203
+ ? 'the spec issue'
204
+ : `spec issue #${spec_number}`}
205
+ </Text>
206
+ <View style={styles.row}>
207
+ {buttons.map((button) => (
208
+ <ReplyButtonView
209
+ key={button.reply}
210
+ button={button}
211
+ onPress={() => {
212
+ setNote(null)
213
+ setConfirming(button.reply)
214
+ }}
215
+ styles={styles}
216
+ />
217
+ ))}
218
+ </View>
219
+ {confirming !== null && done === null && sending === null ? (
220
+ <View style={styles.row}>
221
+ <Text style={styles.text}>
222
+ {confirmReplyText({ reply: confirming, spec_number })}
223
+ </Text>
224
+ <Pressable
225
+ accessibilityRole="button"
226
+ accessibilityLabel={`Yes, post ${confirming}`}
227
+ onPress={() => void post({ reply: confirming })}
228
+ style={styles.primary}
229
+ >
230
+ <Text style={styles.primaryText}>Post</Text>
231
+ </Pressable>
232
+ <Pressable
233
+ accessibilityRole="button"
234
+ accessibilityLabel="Cancel"
235
+ onPress={() => setConfirming(null)}
236
+ style={styles.button}
237
+ >
238
+ <Text style={styles.buttonText}>Cancel</Text>
239
+ </Pressable>
240
+ </View>
241
+ ) : null}
242
+ {note !== null && !(note.ok && done !== null) ? (
243
+ <Text style={note.ok ? styles.success : styles.danger}>
244
+ {note.text}
245
+ </Text>
246
+ ) : null}
247
+ {done !== null ? (
248
+ <Text style={styles.success}>
249
+ {postedText({ posted: done })}
250
+ </Text>
251
+ ) : null}
252
+ <View style={styles.row}>
253
+ <Pressable
254
+ accessibilityRole="button"
255
+ accessibilityLabel={`Help me: ${command}`}
256
+ onPress={() => void helpMe()}
257
+ style={styles.button}
258
+ >
259
+ <Text style={styles.buttonText}>Help me</Text>
260
+ </Pressable>
261
+ <Text style={styles.muted}>
262
+ {chat_agent_id === null
263
+ ? `copies ${command}`
264
+ : `sends ${command} to the run's chat`}
265
+ </Text>
266
+ </View>
267
+ </View>
268
+ )
269
+ }