@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,133 @@
1
+ /**
2
+ * "Needs you": the stuck work waiting for a reply on the spec issue, each as
3
+ * a card with its reason, detail, what was tried and its reply buttons, and
4
+ * what came of the replies the board already posted.
5
+ */
6
+ import { Icon } from '@getpaseo/plugin/client/react-native'
7
+ import { Text, View } from 'react-native'
8
+
9
+ import { Section, StageHeader } from './board-panel-parts'
10
+ import type { Look } from './board-panel-styles'
11
+ import { StuckActions } from './stuck-actions'
12
+
13
+ import type { BoardState, NeedsYou } from '../shared/board-state'
14
+ import { postedText, type PostedReply } from '../shared/reply-actions'
15
+
16
+ const NeedsYouCard = ({
17
+ item,
18
+ run_id,
19
+ spec_number,
20
+ posted,
21
+ chat_agent_id,
22
+ theme,
23
+ styles,
24
+ }: {
25
+ item: NeedsYou
26
+ run_id: string
27
+ spec_number: number | null
28
+ posted: PostedReply[]
29
+ chat_agent_id: string | null
30
+ } & Look) => (
31
+ <View style={[styles.card, styles.cardStuck]}>
32
+ <View style={styles.cardTop}>
33
+ <Icon
34
+ name="OctagonAlert"
35
+ size={14}
36
+ color={theme.colors.statusDanger}
37
+ />
38
+ <Text
39
+ style={[styles.cardTitle, { color: theme.colors.statusDanger }]}
40
+ >
41
+ {item.subject}
42
+ </Text>
43
+ </View>
44
+ <Text style={styles.text}>{item.reason}</Text>
45
+ {item.detail ? (
46
+ <Text style={styles.muted} numberOfLines={8} selectable>
47
+ {item.detail}
48
+ </Text>
49
+ ) : null}
50
+ {item.tried.length > 0 ? (
51
+ <>
52
+ <Text style={styles.label}>Tried</Text>
53
+ {item.tried.map((line, index) => (
54
+ <Text key={`${index}-${line}`} style={styles.muted}>
55
+ {index + 1}. {line}
56
+ </Text>
57
+ ))}
58
+ </>
59
+ ) : null}
60
+ <StuckActions
61
+ run_id={run_id}
62
+ item={item}
63
+ spec_number={spec_number}
64
+ posted={posted}
65
+ chat_agent_id={chat_agent_id}
66
+ theme={theme}
67
+ />
68
+ </View>
69
+ )
70
+
71
+ export const NeedsYouSection = ({
72
+ state,
73
+ posted,
74
+ chat_agent_id,
75
+ theme,
76
+ styles,
77
+ }: {
78
+ state: BoardState
79
+ posted: PostedReply[]
80
+ chat_agent_id: string | null
81
+ } & Look) => {
82
+ const { run } = state
83
+ const look = { theme, styles }
84
+ // Replies the board posted whose stuck item is gone: say what came of them.
85
+ const answered = posted.filter(
86
+ (entry) =>
87
+ !state.needs_you.some(
88
+ (item) => item.key === entry.key && item.since === entry.since
89
+ )
90
+ )
91
+
92
+ return (
93
+ <Section styles={styles}>
94
+ <StageHeader
95
+ title="Needs you"
96
+ hint="stuck work waiting for your reply on the spec issue"
97
+ icon="OctagonAlert"
98
+ count={state.needs_you.length}
99
+ color={theme.colors.statusDanger}
100
+ folded={null}
101
+ onPress={null}
102
+ {...look}
103
+ />
104
+ {state.needs_you.length > 0 ? (
105
+ <View style={styles.stageBody}>
106
+ {state.needs_you.map((item) => (
107
+ <NeedsYouCard
108
+ key={`${item.key}-${item.since}`}
109
+ item={item}
110
+ run_id={run.run_id}
111
+ spec_number={run.spec_number}
112
+ posted={posted}
113
+ chat_agent_id={chat_agent_id}
114
+ {...look}
115
+ />
116
+ ))}
117
+ </View>
118
+ ) : null}
119
+ {answered.length > 0 ? (
120
+ <View style={styles.stageBody}>
121
+ {answered.map((entry) => (
122
+ <Text
123
+ key={`${entry.key}-${entry.since}`}
124
+ style={styles.muted}
125
+ >
126
+ {postedText({ posted: entry })}
127
+ </Text>
128
+ ))}
129
+ </View>
130
+ ) : null}
131
+ </Section>
132
+ )
133
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The side panel's one-line rule (#626): every card line and the footer
3
+ * stay on one line, cut at the end with "…". A tap on a cut line opens a
4
+ * bubble with its full text; a second tap, or a tap anywhere else, closes
5
+ * it. On a computer, hovering opens it too.
6
+ *
7
+ * A card or the footer is a bubble scope (`useBubbles` and `BubbleScope`):
8
+ * it shows the open bubble of its lines under them. Inside the panel
9
+ * (`SharedBubbles`), one bubble is open at a time across every scope; a
10
+ * card or footer drawn on its own keeps its bubble itself.
11
+ */
12
+ import {
13
+ createContext,
14
+ useContext,
15
+ useId,
16
+ useState,
17
+ type Dispatch,
18
+ type ReactNode,
19
+ type SetStateAction,
20
+ } from 'react'
21
+
22
+ import {
23
+ Pressable,
24
+ Text,
25
+ type StyleProp,
26
+ type TextStyle,
27
+ type ViewStyle,
28
+ } from 'react-native'
29
+
30
+ import type { Styles } from './board-panel-styles'
31
+
32
+ type OpenBubble = {
33
+ /** The card or footer that shows it. */
34
+ scope: string
35
+ /** The cut line it belongs to. */
36
+ line: string
37
+ text: string
38
+ /** Opened by hovering: leaving the line closes it, a tap keeps it. */
39
+ hovered: boolean
40
+ }
41
+
42
+ type Shared = {
43
+ open: OpenBubble | null
44
+ show: Dispatch<SetStateAction<OpenBubble | null>>
45
+ }
46
+
47
+ export type Bubbles = Shared & { scope: string }
48
+
49
+ const SharedContext = createContext<Shared | null>(null)
50
+
51
+ const ScopeContext = createContext<Bubbles | null>(null)
52
+
53
+ /**
54
+ * One open bubble for everything under it: the scrolling panel and the
55
+ * status bar pinned below it.
56
+ */
57
+ export const BubbleProvider = ({ children }: { children: ReactNode }) => {
58
+ const [open, show] = useState<OpenBubble | null>(null)
59
+ return (
60
+ <SharedContext.Provider value={{ open, show }}>
61
+ {children}
62
+ </SharedContext.Provider>
63
+ )
64
+ }
65
+
66
+ const ClosesBubble = ({
67
+ style,
68
+ children,
69
+ }: {
70
+ style: StyleProp<ViewStyle>
71
+ children: ReactNode
72
+ }) => {
73
+ const shared = useContext(SharedContext)
74
+ return (
75
+ <Pressable
76
+ accessible={false}
77
+ onPress={() => shared?.show(null)}
78
+ style={style}
79
+ >
80
+ {children}
81
+ </Pressable>
82
+ )
83
+ }
84
+
85
+ /**
86
+ * One open bubble for the whole panel (a `BubbleProvider`'s, when inside
87
+ * one). A tap on any part of the panel that isn't a button of its own
88
+ * closes it.
89
+ */
90
+ export const SharedBubbles = ({
91
+ style,
92
+ children,
93
+ }: {
94
+ style: StyleProp<ViewStyle>
95
+ children: ReactNode
96
+ }) => {
97
+ const shared = useContext(SharedContext)
98
+ const body = <ClosesBubble style={style}>{children}</ClosesBubble>
99
+ return shared ? body : <BubbleProvider>{body}</BubbleProvider>
100
+ }
101
+
102
+ /** Closes the panel's open bubble: for a button's tap that does more. */
103
+ export const useCloseBubble = (): (() => void) => {
104
+ const shared = useContext(SharedContext)
105
+ return () => shared?.show(null)
106
+ }
107
+
108
+ /** A card's or the footer's bubbles: the panel's, or its own outside one. */
109
+ export const useBubbles = (): Bubbles => {
110
+ const shared = useContext(SharedContext)
111
+ const [open, show] = useState<OpenBubble | null>(null)
112
+ const scope = useId()
113
+ return { scope, ...(shared ?? { open, show }) }
114
+ }
115
+
116
+ /** Gives its cut lines their bubble, and shows it under them when open. */
117
+ export const BubbleScope = ({
118
+ bubbles,
119
+ styles,
120
+ children,
121
+ }: {
122
+ bubbles: Bubbles
123
+ styles: Styles
124
+ children: ReactNode
125
+ }) => (
126
+ <ScopeContext.Provider value={bubbles}>
127
+ {children}
128
+ {bubbles.open?.scope === bubbles.scope ? (
129
+ <Text style={styles.bubble} onPress={() => bubbles.show(null)}>
130
+ {bubbles.open.text}
131
+ </Text>
132
+ ) : null}
133
+ </ScopeContext.Provider>
134
+ )
135
+
136
+ /**
137
+ * A line of text cut at its end with "…". Inside a `BubbleScope`, a tap
138
+ * (or hovering) opens its full text in a bubble, or `full` when given: a
139
+ * short line's longer sentence. `children`, when given, draw the line in
140
+ * place of `text` (as nested, colored texts), which stays its plain words.
141
+ */
142
+ export const CutLine = ({
143
+ text,
144
+ full,
145
+ style,
146
+ styles,
147
+ children,
148
+ }: {
149
+ text: string
150
+ full?: string
151
+ style: StyleProp<TextStyle>
152
+ styles: Styles
153
+ children?: ReactNode
154
+ }) => {
155
+ const bubbles = useContext(ScopeContext)
156
+ const line = useId()
157
+ const shown = full ?? text
158
+ const label = (
159
+ <Text style={style} numberOfLines={1} ellipsizeMode="tail">
160
+ {children ?? text}
161
+ </Text>
162
+ )
163
+ if (!bubbles) return label
164
+ const { scope, show } = bubbles
165
+ return (
166
+ <Pressable
167
+ accessibilityRole="button"
168
+ accessibilityHint="Shows the full text"
169
+ onPress={() =>
170
+ show((open) =>
171
+ open?.line === line && !open.hovered
172
+ ? null
173
+ : { scope, line, text: shown, hovered: false }
174
+ )
175
+ }
176
+ onHoverIn={() =>
177
+ show((open) =>
178
+ open === null || open.hovered
179
+ ? { scope, line, text: shown, hovered: true }
180
+ : open
181
+ )
182
+ }
183
+ onHoverOut={() =>
184
+ show((open) =>
185
+ open?.line === line && open.hovered ? null : open
186
+ )
187
+ }
188
+ style={styles.cut}
189
+ >
190
+ {label}
191
+ </Pressable>
192
+ )
193
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The repo wait's banner (#632): the run waits for an earlier run on the
3
+ * same repo (#615), from `repo_wait_started` until `repo_wait_ended`. It
4
+ * says which run, how long it has waited, counting up by itself, and has
5
+ * the amber bar of a wait on purpose. In a run with no sign of life
6
+ * (#635), it says so instead, in red, and its time stops at the last sign.
7
+ */
8
+ import { Icon } from '@getpaseo/plugin/client/react-native'
9
+ import { Text, View } from 'react-native'
10
+
11
+ import { useNow } from './board-look'
12
+ import type { Look } from './board-panel-styles'
13
+
14
+ import { elapsedText, type BoardState } from '../shared/board-state'
15
+
16
+ /**
17
+ * The banner's words.
18
+ *
19
+ * @example
20
+ * repoWaitText({ runs: ['20260923t113000z-5b1e0c2a'], ms: 725_000 })
21
+ * // 'waiting for run 20260923t113000z-5b1e0c2a on this repo · 12m 5s'
22
+ */
23
+ export const repoWaitText = ({
24
+ runs,
25
+ ms,
26
+ }: {
27
+ runs: string[]
28
+ ms: number
29
+ }): string => {
30
+ const which =
31
+ runs.length === 0
32
+ ? 'an earlier run'
33
+ : runs.map((run_id) => `run ${run_id}`).join(', ')
34
+ return `waiting for ${which} on this repo · ${elapsedText({ ms })}`
35
+ }
36
+
37
+ export const RepoWaitBanner = ({
38
+ wait,
39
+ lifeless = null,
40
+ theme,
41
+ styles,
42
+ }: {
43
+ wait: NonNullable<BoardState['repo_wait']>
44
+ /** The run's lack of a sign of life (#635), from `noLife`. */
45
+ lifeless?: { text: string; at: number } | null
46
+ } & Look) => {
47
+ const ticking = useNow()
48
+ const now = lifeless?.at ?? ticking
49
+ const color = lifeless
50
+ ? theme.colors.statusDanger
51
+ : theme.colors.statusWarning
52
+ const text = repoWaitText({
53
+ runs: wait.runs,
54
+ ms: now - Date.parse(wait.since),
55
+ })
56
+ return (
57
+ <View style={[styles.banner, { borderColor: color }]}>
58
+ <Icon name="Hourglass" size={13} color={color} />
59
+ <Text style={[styles.bannerText, { color }]}>
60
+ {lifeless ? `${text} · ${lifeless.text}` : text}
61
+ </Text>
62
+ <View style={styles.spacer} />
63
+ <View
64
+ accessibilityLabel={lifeless ? 'no sign of life' : 'waiting'}
65
+ style={[styles.waitBar, { backgroundColor: color }]}
66
+ />
67
+ </View>
68
+ )
69
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The bottom of the side panel's scrolling part: the run id, the event
3
+ * count and engine state, and the counters (the decision model, memory,
4
+ * agent messages, outside changes to the shared .git). The latest event's
5
+ * age is in the status bar (`status-bar.tsx`). Each stays on one line
6
+ * (`one-line.tsx`); a tap shows it whole in a bubble.
7
+ */
8
+ import { View } from 'react-native'
9
+
10
+ import { useNow } from './board-look'
11
+ import type { Look } from './board-panel-styles'
12
+ import { noLife } from './engine-life'
13
+ import { BubbleScope, CutLine, useBubbles } from './one-line'
14
+
15
+ import type { BoardState } from '../shared/board-state'
16
+
17
+ export const RunFooter = ({ state, styles }: { state: BoardState } & Look) => {
18
+ const bubbles = useBubbles()
19
+ const now = useNow()
20
+ const { run, memory } = state
21
+ const line = (text: string) => (
22
+ <CutLine text={text} style={styles.small} styles={styles} />
23
+ )
24
+ // A run with no sign of life says so here too, not "engine running".
25
+ const engine =
26
+ noLife({ state, now })?.text ??
27
+ `engine ${
28
+ run.engine_ended
29
+ ? run.engine_ended.ok
30
+ ? 'finished'
31
+ : 'stopped'
32
+ : 'running'
33
+ }`
34
+ const memoryChanges =
35
+ memory.added + memory.updated + memory.refused + memory.failed > 0
36
+ ? `; ${memory.added} added, ${memory.updated} updated, ${memory.refused} refused, ${memory.failed} failed`
37
+ : ''
38
+ return (
39
+ <>
40
+ <View style={styles.rule} />
41
+ <BubbleScope bubbles={bubbles} styles={styles}>
42
+ {line(
43
+ `${run.run_id} · ${state.event_count} events · ${engine}`
44
+ )}
45
+ {state.jev.asked > 0
46
+ ? line(
47
+ `Decision model, in shadow mode: ${state.jev.asked} asked, ${state.jev.answered} answered, ${state.jev.failed} without an answer. The engine doesn't act on them.`
48
+ )
49
+ : null}
50
+ {memory.searches > 0 || memory.learner !== 'waiting'
51
+ ? line(
52
+ `Memory: ${memory.searches} searches (${memory.search_errors} vault errors), ${memory.shown} shown. Learner: ${memory.learner}${memoryChanges}.`
53
+ )
54
+ : null}
55
+ {state.messages.sent + state.messages.refused > 0
56
+ ? line(
57
+ `Agent messages: ${state.messages.sent} sent, ${state.messages.delivered} handed over, ${state.messages.refused} refused.`
58
+ )
59
+ : null}
60
+ {state.shared_git_changed > 0
61
+ ? line(
62
+ `Outside changes to the shared .git: ${state.shared_git_changed}. Other processes made them, not the agents.`
63
+ )
64
+ : null}
65
+ </BubbleScope>
66
+ </>
67
+ )
68
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * The top of the side panel: the title and status, the run tabs, the run's
3
+ * title and pull request, plan usage, what the engine is doing now, the
4
+ * run's tokens and plan used, and the banners (a repo wait, a limit wait, a
5
+ * version note, a stop, the engine ending, intake refusing the run).
6
+ */
7
+ import type { PluginWorkspacePanelProps } from '@getpaseo/plugin/client'
8
+ import { ExternalLink } from '@getpaseo/plugin/client/ui'
9
+ import { Text, View } from 'react-native'
10
+
11
+ import {
12
+ limitWaitText,
13
+ runTitle,
14
+ statusColor,
15
+ statusText,
16
+ tokensText,
17
+ usageLineWaitText,
18
+ useNow,
19
+ } from './board-look'
20
+ import { Banner, StepLine } from './board-panel-parts'
21
+ import type { Look } from './board-panel-styles'
22
+ import { noLife } from './engine-life'
23
+ import { RepoWaitBanner } from './repo-wait-banner'
24
+ import { RunPicker } from './run-picker'
25
+ import { UsageLine } from './usage-line'
26
+
27
+ import {
28
+ planUsedText,
29
+ stoppedText,
30
+ type BoardState,
31
+ type RunSummary,
32
+ } from '../shared/board-state'
33
+
34
+ export const RunHeader = ({
35
+ state,
36
+ runs,
37
+ setPicked,
38
+ layout,
39
+ theme,
40
+ styles,
41
+ }: {
42
+ state: BoardState
43
+ runs: RunSummary[]
44
+ setPicked: (run_id: string) => void
45
+ layout: PluginWorkspacePanelProps['layout']
46
+ } & Look) => {
47
+ const { run } = state
48
+ const look = { theme, styles }
49
+ // With no sign of life (#635), the step's time and the repo wait's stop.
50
+ const lifeless = noLife({ state, now: useNow() })
51
+ return (
52
+ <>
53
+ <View style={styles.row}>
54
+ <Text style={styles.title}>Luca board</Text>
55
+ <View style={styles.spacer} />
56
+ <Text
57
+ style={[
58
+ styles.pill,
59
+ {
60
+ color: statusColor({ status: run.status, theme }),
61
+ borderColor: statusColor({
62
+ status: run.status,
63
+ theme,
64
+ }),
65
+ },
66
+ ]}
67
+ >
68
+ {statusText({ status: run.status })}
69
+ </Text>
70
+ </View>
71
+ {runs.length > 1 ? (
72
+ <RunPicker
73
+ runs={runs}
74
+ selected={run.run_id}
75
+ onPick={setPicked}
76
+ {...look}
77
+ />
78
+ ) : null}
79
+ <Text style={styles.text} numberOfLines={layout.compact ? 3 : 2}>
80
+ {runTitle(run)}
81
+ {run.branch ? (
82
+ <Text style={styles.muted}> · {run.branch}</Text>
83
+ ) : null}
84
+ </Text>
85
+ {run.pr_url ? (
86
+ <ExternalLink href={run.pr_url}>
87
+ <Text style={styles.prLink}>
88
+ Pull request{run.pr_number ? ` #${run.pr_number}` : ''}:{' '}
89
+ {run.pr_url}
90
+ </Text>
91
+ </ExternalLink>
92
+ ) : null}
93
+ <UsageLine state={state} {...look} />
94
+ {state.current_step ? (
95
+ <StepLine
96
+ step={state.current_step}
97
+ prefix="Now: "
98
+ at={lifeless?.at ?? null}
99
+ styles={styles}
100
+ />
101
+ ) : null}
102
+ {state.run_tokens > 0 ? (
103
+ <Text style={styles.mono}>
104
+ This run&apos;s tokens:{' '}
105
+ {tokensText({ tokens: state.run_tokens })} of its run budget{' '}
106
+ {tokensText({ tokens: state.run_budget_tokens })}
107
+ </Text>
108
+ ) : null}
109
+ {state.run_plan_used.length > 0 ? (
110
+ <Text style={styles.mono}>
111
+ This run used:{' '}
112
+ {planUsedText({ used: state.run_plan_used, sign: '' })}
113
+ </Text>
114
+ ) : null}
115
+ {state.repo_wait && run.engine_ended === null ? (
116
+ <RepoWaitBanner
117
+ wait={state.repo_wait}
118
+ lifeless={lifeless}
119
+ {...look}
120
+ />
121
+ ) : null}
122
+ {state.limit_wait ? (
123
+ <Banner
124
+ icon="Hourglass"
125
+ color={theme.colors.statusWarning}
126
+ styles={styles}
127
+ >
128
+ {limitWaitText({
129
+ window: state.limit_wait.window,
130
+ resets_at: state.limit_wait.resets_at,
131
+ now: Date.now(),
132
+ })}
133
+ </Banner>
134
+ ) : null}
135
+ {state.usage_line_wait ? (
136
+ <Banner
137
+ icon="Hourglass"
138
+ color={theme.colors.statusWarning}
139
+ styles={styles}
140
+ >
141
+ {usageLineWaitText({
142
+ ...state.usage_line_wait,
143
+ now: Date.now(),
144
+ })}
145
+ </Banner>
146
+ ) : null}
147
+ {run.version_note ? (
148
+ <Banner
149
+ icon="Info"
150
+ color={theme.colors.statusWarning}
151
+ styles={styles}
152
+ >
153
+ {run.version_note}
154
+ </Banner>
155
+ ) : null}
156
+ {run.stopped ? (
157
+ <Banner
158
+ icon="OctagonX"
159
+ color={theme.colors.statusDanger}
160
+ styles={styles}
161
+ >
162
+ {stoppedText(run.stopped)}
163
+ </Banner>
164
+ ) : null}
165
+ {run.engine_ended && !run.engine_ended.ok ? (
166
+ <Banner
167
+ icon="OctagonX"
168
+ color={theme.colors.statusDanger}
169
+ styles={styles}
170
+ >
171
+ The engine stopped: {run.engine_ended.message}
172
+ </Banner>
173
+ ) : null}
174
+ {run.refusal.length > 0 ? (
175
+ <Banner
176
+ icon="Ban"
177
+ color={theme.colors.statusDanger}
178
+ styles={styles}
179
+ >
180
+ Intake refused the run. {run.refusal.join(' ')}
181
+ </Banner>
182
+ ) : null}
183
+ <Text style={styles.small}>
184
+ Step dots: tests → red check → code → checks → review. Tap a
185
+ card for details.
186
+ </Text>
187
+ </>
188
+ )
189
+ }