@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
@@ -20,6 +20,7 @@ import {
20
20
  usageColor,
21
21
  useNow,
22
22
  } from './board-look'
23
+ import { StuckActions } from './stuck-actions'
23
24
 
24
25
  import type { EventRow, LimitRow, RunRow, StuckRow } from '../shared/board-rows'
25
26
  import { currentStepText, USAGE_LABEL } from '../shared/board-state'
@@ -294,9 +295,14 @@ export const EventRowLine = ({
294
295
  )
295
296
  }
296
297
 
297
- /** Stuck work, with the exact replies; resolved in place. */
298
+ /**
299
+ * Stuck work, with a button per reply and "Help me", which sends
300
+ * `/luca-unstick` to this chat (#503); resolved in place. A row added
301
+ * before the buttons shows the replies as text.
302
+ */
298
303
  export const StuckRowCard = ({
299
304
  item,
305
+ agentId,
300
306
  theme,
301
307
  layout,
302
308
  }: PluginTimelineItemProps<StuckRow>) => {
@@ -333,20 +339,41 @@ export const StuckRowCard = ({
333
339
  · {line}
334
340
  </Text>
335
341
  ))}
336
- <View style={styles.replyRow}>
337
- <Text style={styles.muted}>
338
- Reply with a comment on{' '}
339
- {row.spec_number === null
340
- ? 'the spec issue'
341
- : `spec issue #${row.spec_number}`}
342
- :
343
- </Text>
344
- {row.replies.map((reply) => (
345
- <Text key={reply} style={styles.reply} selectable>
346
- {reply}
342
+ {row.run_id !== null &&
343
+ row.key !== null &&
344
+ row.since !== null ? (
345
+ <StuckActions
346
+ run_id={row.run_id}
347
+ item={{
348
+ key: row.key,
349
+ ticket: row.ticket,
350
+ since: row.since,
351
+ }}
352
+ spec_number={row.spec_number}
353
+ posted={[]}
354
+ chat_agent_id={agentId}
355
+ theme={theme}
356
+ />
357
+ ) : (
358
+ <View style={styles.replyRow}>
359
+ <Text style={styles.muted}>
360
+ Reply with a comment on{' '}
361
+ {row.spec_number === null
362
+ ? 'the spec issue'
363
+ : `spec issue #${row.spec_number}`}
364
+ :
347
365
  </Text>
348
- ))}
349
- </View>
366
+ {row.replies.map((reply) => (
367
+ <Text
368
+ key={reply}
369
+ style={styles.reply}
370
+ selectable
371
+ >
372
+ {reply}
373
+ </Text>
374
+ ))}
375
+ </View>
376
+ )}
350
377
  </>
351
378
  ) : (
352
379
  <Text style={styles.muted}>{row.resolution}</Text>
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The plan usage line: the five-hour and weekly windows, colored by level.
3
+ */
4
+ import { Icon } from '@getpaseo/plugin/client/react-native'
5
+ import { Text, View } from 'react-native'
6
+
7
+ import { percentText, usageColor } from './board-look'
8
+ import type { Look } from './board-panel-styles'
9
+
10
+ import type { BoardState } from '../shared/board-state'
11
+
12
+ export const UsageLine = ({
13
+ state,
14
+ theme,
15
+ styles,
16
+ }: { state: BoardState } & Look) => {
17
+ const { usage, usage_label } = state
18
+ return (
19
+ <View style={styles.row}>
20
+ <Icon name="Gauge" size={13} color={theme.colors.foregroundMuted} />
21
+ {usage ? (
22
+ <Text style={styles.mono}>
23
+ {usage_label}: 5h{' '}
24
+ <Text
25
+ style={{
26
+ color: usageColor({
27
+ level: usage.five_hour_level,
28
+ theme,
29
+ }),
30
+ }}
31
+ >
32
+ {percentText({ percent: usage.five_hour_percent })}
33
+ </Text>{' '}
34
+ · week{' '}
35
+ <Text
36
+ style={{
37
+ color: usageColor({
38
+ level: usage.weekly_level,
39
+ theme,
40
+ }),
41
+ }}
42
+ >
43
+ {percentText({ percent: usage.weekly_percent })}
44
+ </Text>
45
+ </Text>
46
+ ) : (
47
+ <Text style={styles.mono}>{usage_label}: no reading yet</Text>
48
+ )}
49
+ </View>
50
+ )
51
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * A waiting card's step-dots line (#559): what it waits for, cut to fit,
3
+ * how long it has waited, counting up by itself, and the amber bar that
4
+ * takes the heartbeat's place.
5
+ */
6
+ import { Text, View } from 'react-native'
7
+
8
+ import { useNow } from './board-look'
9
+ import type { Look } from './board-panel-styles'
10
+ import { CutLine } from './one-line'
11
+ import type { ShownWait } from './ticket-wait'
12
+
13
+ import { elapsedText } from '../shared/board-state'
14
+
15
+ export const WaitLine = ({
16
+ wait,
17
+ theme,
18
+ styles,
19
+ }: { wait: ShownWait } & Look) => {
20
+ const now = useNow()
21
+ return (
22
+ <>
23
+ <CutLine text={wait.text} style={styles.muted} styles={styles} />
24
+ {wait.since === null ? null : (
25
+ <Text style={[styles.mono, styles.fixed]} numberOfLines={1}>
26
+ {`· ${elapsedText({ ms: now - Date.parse(wait.since) })}`}
27
+ </Text>
28
+ )}
29
+ <View style={styles.spacer} />
30
+ <View
31
+ accessibilityLabel="waiting"
32
+ style={[
33
+ styles.waitBar,
34
+ { backgroundColor: theme.colors.statusWarning },
35
+ ]}
36
+ />
37
+ </>
38
+ )
39
+ }
@@ -31,7 +31,7 @@ export default function contribute(client: PluginClientContext) {
31
31
  client.addSlashCommand({
32
32
  name: 'luca-run',
33
33
  description:
34
- 'Start a Luca run for a spec: live rows here, and the Luca board panel',
34
+ 'Start a Luca run for a spec, or resume one: live rows here, and the Luca board panel',
35
35
  argumentHint: RUN_USAGE.replace('/luca-run ', ''),
36
36
  context: 'agent',
37
37
  onSubmit: async ({ args, agent, workspace, rpc, openPanel }) => {
@@ -23,6 +23,7 @@ import {
23
23
  boardReadRpc,
24
24
  boardVersionRpc,
25
25
  engineEventRpc,
26
+ replyPostRpc,
26
27
  runStartRpc,
27
28
  } from './shared/board-rpc'
28
29
  import { PLUGIN_ID } from './shared/board-state'
@@ -40,7 +41,8 @@ const log = (message: string) => console.error(`[${PLUGIN_ID}] ${message}`)
40
41
  * The board plugin's daemon side: the engine settings (their usage lines
41
42
  * kept in a file for the engine), `run.start` (launch a
42
43
  * run from `/luca-run`), `engine.event` (the engine's journal records in),
43
- * and `board.read` (the side panel's poll). On start, and every 15 s after,
44
+ * `board.read` (the side panel's poll), and `reply.post` (a reply button on
45
+ * stuck work posts its word on the spec, #503). On start, and every 15 s after,
44
46
  * it checks for runs whose engine is gone and restarts the ones that can go
45
47
  * on (#375). The logic lives in `createBoardServer`; this entry only wires it
46
48
  * to Paseo.
@@ -130,6 +132,10 @@ export default function contribute(server: PluginServerContext) {
130
132
  connect({ context })
131
133
  return board.readBoard(input)
132
134
  })
135
+ server.handle(replyPostRpc, (input, context) => {
136
+ connect({ context })
137
+ return board.postReply(input)
138
+ })
133
139
  // Fixed as loaded: after an upgrade it differs until a reload.
134
140
  server.handle(boardVersionRpc, () => ({ version: LUCA_VERSION }))
135
141
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luca/board",
3
- "version": "14.0.0-alpha.2",
3
+ "version": "14.0.0-alpha.21",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -0,0 +1,67 @@
1
+ import { createHarness, type Harness } from './testing/board-harness'
2
+ import {
3
+ agentFinished,
4
+ agentStarted,
5
+ gatesRun,
6
+ intakeOfThree,
7
+ redCheck,
8
+ sessionOf,
9
+ ticketWorktreeCreated,
10
+ type Entry,
11
+ } from './testing/journal-fixtures'
12
+
13
+ /**
14
+ * Helpers shared by the panel state tests (`board-panel-state.test.ts` and
15
+ * the `board-panel-state-*.test.ts` files beside it).
16
+ *
17
+ * The harness is the one the running test started; `runWith` sets it, and
18
+ * each test file cleans it up after every test.
19
+ */
20
+
21
+ export let harness: Harness
22
+
23
+ /** Starts a run and sends intake plus `entries` in one go. */
24
+ export const runWith = async ({ entries }: { entries: Entry[] }) => {
25
+ harness = await createHarness()
26
+ const { run_id, token } = await harness.start()
27
+ const reply = await harness.send({
28
+ run_id,
29
+ token,
30
+ entries: [...intakeOfThree(), ...entries],
31
+ })
32
+ return { run_id, token, reply, next: reply.next_seq }
33
+ }
34
+
35
+ export const rowsOfKind = ({ kind }: { kind: string }) =>
36
+ harness.latestRows().filter(({ row }) => row.kind === kind)
37
+
38
+ /** The chat's event rows, as text and tone, oldest first. */
39
+ export const eventRows = () =>
40
+ harness
41
+ .latestRows()
42
+ .flatMap(({ row }) =>
43
+ row.kind === 'luca-board-event'
44
+ ? [{ text: row.data.text, tone: row.data.tone }]
45
+ : []
46
+ )
47
+
48
+ /** Gates that failed, then the implementer's follow-up in its session. */
49
+ export const failedGatesRound = ({ ticket }: { ticket: number }): Entry[] => [
50
+ gatesRun({ ticket, ok: false }),
51
+ agentStarted({
52
+ ticket,
53
+ role: 'implementer',
54
+ follow_up_of: sessionOf({ ticket, role: 'implementer' }),
55
+ }),
56
+ agentFinished({ ticket, role: 'implementer' }),
57
+ ]
58
+
59
+ /** Ticket 13 up to its first implementer result. */
60
+ export const codedTicket13 = (): Entry[] => [
61
+ ticketWorktreeCreated({ ticket: 13 }),
62
+ agentStarted({ ticket: 13, role: 'test-writer' }),
63
+ agentFinished({ ticket: 13, role: 'test-writer' }),
64
+ redCheck({ ticket: 13, ok: true, failing: 1, passing: 3 }),
65
+ agentStarted({ ticket: 13, role: 'implementer' }),
66
+ agentFinished({ ticket: 13, role: 'implementer' }),
67
+ ]
@@ -0,0 +1,294 @@
1
+ /**
2
+ * The engine check: finds runs whose engine process is gone although the run
3
+ * hasn't ended, restarts the ones that can go on from their journal, and
4
+ * marks the rest as stopped, with why. The runs the plugin didn't start are
5
+ * only labelled (#635), never restarted.
6
+ */
7
+
8
+ import type { EngineProcess } from './board-server-engine'
9
+ import type { RunStore } from './board-server-store'
10
+ import type {
11
+ BoardServerConfig,
12
+ EngineCheck,
13
+ OutsideRun,
14
+ RunMemory,
15
+ } from './board-server-types'
16
+ import { errorText } from './board-server-util'
17
+ import {
18
+ engineStoppedText,
19
+ liveRunIds,
20
+ MAX_AUTO_RESTARTS,
21
+ parseUnfinished,
22
+ restartRow,
23
+ restartText,
24
+ UNFINISHED_TIMEOUT_MS,
25
+ type StopWhy,
26
+ type UnfinishedRun,
27
+ } from './engine-watch'
28
+ import { readLiveFile } from './live-file'
29
+
30
+ import { liveIsFresh, needsEngine } from '../shared/board-state'
31
+
32
+ export const createEngineChecks = ({
33
+ config,
34
+ store,
35
+ engine,
36
+ reads,
37
+ }: {
38
+ config: BoardServerConfig
39
+ store: RunStore
40
+ engine: EngineProcess
41
+ /** The runs the plugin didn't start, and how to read them again. */
42
+ reads: {
43
+ outside: Map<string, OutsideRun>
44
+ readOutside: () => Promise<void>
45
+ }
46
+ }) => {
47
+ const {
48
+ list_processes,
49
+ run_command,
50
+ home_dir,
51
+ log,
52
+ now,
53
+ runs_dir = null,
54
+ } = config
55
+ const { runs, enqueue, appendRows, header, updateEntry, endRun, ready } =
56
+ store
57
+ const { childEnv, findEngine, exclusive, spawnResume } = engine
58
+
59
+ /**
60
+ * Labels each run the plugin didn't start whose engine is gone: no
61
+ * process names it, and its live file (if any) isn't fresh, as a run
62
+ * started from the command line without `--run-id` has no id on its
63
+ * command line. Never restarts them.
64
+ */
65
+ const labelOutside = async ({
66
+ others,
67
+ live,
68
+ }: {
69
+ others: OutsideRun[]
70
+ live: Set<string>
71
+ }) => {
72
+ let at: number | null = null
73
+ await Promise.all(
74
+ others.map(async (run) => {
75
+ const { run_id } = run.state.run
76
+ if (live.has(run_id) || runs_dir === null) {
77
+ run.engine_gone = false
78
+ return
79
+ }
80
+ const file = await readLiveFile({ runs_dir, run_id })
81
+ at ??= now().getTime()
82
+ run.engine_gone =
83
+ file === null ||
84
+ !liveIsFresh({ written_at: file.written_at, now: at })
85
+ })
86
+ )
87
+ }
88
+
89
+ /**
90
+ * Asks the engine which unfinished runs may be restarted
91
+ * (`luca-run --unfinished`), with the engine to restart them with.
92
+ */
93
+ const listUnfinished = async (): Promise<
94
+ | {
95
+ ok: true
96
+ runs: Map<string, UnfinishedRun>
97
+ engine: { command: string; lead_args: string[] }
98
+ }
99
+ | { ok: false; error: string }
100
+ > => {
101
+ const engine = await findEngine()
102
+ if (!engine.ok) return { ok: false, error: engine.message }
103
+ const result = await run_command({
104
+ command: engine.command,
105
+ args: [...engine.lead_args, '--unfinished'],
106
+ cwd: home_dir,
107
+ env: childEnv({ token: null }),
108
+ timeout_ms: UNFINISHED_TIMEOUT_MS,
109
+ })
110
+ if (result.exit_code === null) {
111
+ return {
112
+ ok: false,
113
+ error: `luca-run --unfinished did not finish in ${UNFINISHED_TIMEOUT_MS / 1000} s (it timed out or was killed)`,
114
+ }
115
+ }
116
+ if (result.exit_code !== 0) {
117
+ return {
118
+ ok: false,
119
+ error: `luca-run --unfinished exited with ${result.exit_code}: ${result.stderr.trim().slice(0, 300)}`,
120
+ }
121
+ }
122
+ const parsed = parseUnfinished({ stdout: result.stdout })
123
+ if (!parsed.ok) {
124
+ return {
125
+ ok: false,
126
+ error: `luca-run --unfinished ${parsed.error}`,
127
+ }
128
+ }
129
+ return { ok: true, runs: parsed.runs, engine }
130
+ }
131
+
132
+ /**
133
+ * Restarts a run from its journal (`--resume`) with its token, appending
134
+ * to its log, and counts the restart. An error when the spawn threw.
135
+ */
136
+ const restartRun = async ({
137
+ memory,
138
+ engine,
139
+ }: {
140
+ memory: RunMemory
141
+ engine: { command: string; lead_args: string[] }
142
+ }): Promise<{ ok: true } | { ok: false; error: string }> => {
143
+ const { run_id, log_path } = memory.entry
144
+ let pid: number | null = null
145
+ try {
146
+ pid = spawnResume({
147
+ memory,
148
+ engine,
149
+ on_failed: 'the restarted engine failed',
150
+ })
151
+ } catch (error) {
152
+ return { ok: false, error: errorText({ error }) }
153
+ }
154
+ const restart = memory.entry.restarts + 1
155
+ updateEntry({ memory, change: { restarts: restart } })
156
+ const time = now().toISOString()
157
+ const row = restartRow({ run_id, restart, time })
158
+ memory.state = {
159
+ ...memory.state,
160
+ run: { ...memory.state.run, engine_ended: null },
161
+ latest: restartText({ restart }),
162
+ latest_at: time,
163
+ }
164
+ log(
165
+ `[${run_id}] restarted ${engine.command} with --resume (pid ${pid ?? '?'}, restart ${restart} of ${MAX_AUTO_RESTARTS}), log ${log_path}`
166
+ )
167
+ await appendRows({ memory, rows: [row, header({ memory })] })
168
+ return { ok: true }
169
+ }
170
+
171
+ /** One check: see `checkEngines`. */
172
+ const runCheck = async (): Promise<EngineCheck> => {
173
+ await ready
174
+ await reads.readOutside()
175
+ const summary: EngineCheck = { restarted: [], stopped: [] }
176
+ const candidates = [...runs.values()].filter((memory) =>
177
+ needsEngine({ state: memory.state })
178
+ )
179
+ const others = [...reads.outside.values()].filter((run) =>
180
+ needsEngine({ state: run.state })
181
+ )
182
+ if (candidates.length === 0 && others.length === 0) return summary
183
+ let command_lines: string[]
184
+ try {
185
+ command_lines = await list_processes()
186
+ } catch (error) {
187
+ // Never restart when unsure: two engines on one run is far worse
188
+ // than a late restart.
189
+ log(
190
+ `Couldn't list the processes, so no engine was checked: ${errorText({ error })}`
191
+ )
192
+ return summary
193
+ }
194
+ const live = liveRunIds({ command_lines })
195
+ await labelOutside({ others, live })
196
+ const dead = candidates.filter(
197
+ (memory) => !live.has(memory.entry.run_id)
198
+ )
199
+ if (dead.length === 0) return summary
200
+ const unfinished = dead.some((memory) => !memory.entry.demo)
201
+ ? await listUnfinished()
202
+ : null
203
+ if (unfinished && !unfinished.ok) {
204
+ log(`Couldn't check which runs can go on: ${unfinished.error}`)
205
+ }
206
+
207
+ const settle = async ({ memory }: { memory: RunMemory }) => {
208
+ // The engine may have ended the run while this check ran.
209
+ if (!needsEngine({ state: memory.state })) return
210
+ const { run_id, log_path } = memory.entry
211
+ const stop = async ({ why }: { why: StopWhy }) => {
212
+ const message = engineStoppedText({ why, run_id, log_path })
213
+ log(`[${run_id}] the engine is gone: ${message}`)
214
+ await endRun({ memory, ended: { ok: false, message } })
215
+ summary.stopped.push(run_id)
216
+ }
217
+ if (memory.entry.demo) return stop({ why: { kind: 'demo' } })
218
+ if (!unfinished?.ok) {
219
+ return stop({
220
+ why: {
221
+ kind: 'check_failed',
222
+ error: unfinished?.error ?? 'no answer',
223
+ },
224
+ })
225
+ }
226
+ const listed = unfinished.runs.get(run_id)
227
+ if (!listed) return stop({ why: { kind: 'not_listed' } })
228
+ if (!listed.restart) {
229
+ const reason = listed.message ?? 'no reason given'
230
+ return stop({
231
+ why:
232
+ listed.reason === 'billing_stopped'
233
+ ? { kind: 'billing_stopped', reason }
234
+ : { kind: 'launcher_stopped', reason },
235
+ })
236
+ }
237
+ if (memory.entry.restarts >= MAX_AUTO_RESTARTS) {
238
+ return stop({ why: { kind: 'restarts_used_up' } })
239
+ }
240
+ const restarted = await restartRun({
241
+ memory,
242
+ engine: unfinished.engine,
243
+ })
244
+ if (!restarted.ok) {
245
+ return stop({
246
+ why: { kind: 'spawn_failed', error: restarted.error },
247
+ })
248
+ }
249
+ summary.restarted.push(run_id)
250
+ }
251
+
252
+ await Promise.all(
253
+ dead.map((memory) =>
254
+ enqueue({
255
+ run_id: memory.entry.run_id,
256
+ work: () => settle({ memory }),
257
+ })
258
+ )
259
+ )
260
+ return summary
261
+ }
262
+
263
+ let checking: Promise<EngineCheck> | null = null
264
+
265
+ /**
266
+ * Finds runs whose engine process is gone although the run hasn't ended,
267
+ * and restarts the ones that can go on from their journal
268
+ * (`--resume`, at most `MAX_AUTO_RESTARTS` times per run). The rest show
269
+ * "engine stopped" with why. The runs the plugin didn't start are only
270
+ * labelled when their engine is gone (#635). When the processes can't be listed, nothing
271
+ * is done. Only one check runs at a time: a call while one runs gets the
272
+ * same promise. Never rejects.
273
+ *
274
+ * @example
275
+ * const { restarted, stopped } = await board.checkEngines()
276
+ */
277
+ const checkEngines = (): Promise<EngineCheck> => {
278
+ checking ??= exclusive(runCheck)
279
+ .catch((error: unknown) => {
280
+ log(`The engine check failed: ${errorText({ error })}`)
281
+ return { restarted: [], stopped: [] }
282
+ })
283
+ .finally(() => {
284
+ checking = null
285
+ })
286
+ return checking
287
+ }
288
+
289
+ return {
290
+ checkEngines,
291
+ /** The check in flight, `null` when none runs. */
292
+ checking: () => checking,
293
+ }
294
+ }