@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.
- package/CHANGELOG.md +76 -0
- package/README.md +7 -2
- package/board/client/board-look.ts +31 -1
- package/board/client/board-panel-parts.tsx +150 -0
- package/board/client/board-panel-stages.ts +99 -0
- package/board/client/board-panel-styles.ts +223 -0
- package/board/client/board-panel.tsx +77 -1275
- package/board/client/engine-life.ts +48 -0
- package/board/client/final-review-stack.tsx +144 -0
- package/board/client/heartbeat.tsx +61 -0
- package/board/client/helper-lines.tsx +69 -0
- package/board/client/live-feed.tsx +86 -0
- package/board/client/live-line.ts +73 -0
- package/board/client/model-line.tsx +178 -0
- package/board/client/needs-you-section.tsx +133 -0
- package/board/client/one-line.tsx +193 -0
- package/board/client/repo-wait-banner.tsx +69 -0
- package/board/client/run-footer.tsx +68 -0
- package/board/client/run-header.tsx +189 -0
- package/board/client/run-picker.tsx +98 -0
- package/board/client/status-bar.tsx +131 -0
- package/board/client/step-dots.tsx +127 -0
- package/board/client/stuck-actions.tsx +269 -0
- package/board/client/ticket-card-view.tsx +238 -0
- package/board/client/ticket-detail.tsx +87 -0
- package/board/client/ticket-stack.tsx +119 -0
- package/board/client/ticket-wait.ts +188 -0
- package/board/client/timeline-rows.tsx +41 -14
- package/board/client/usage-line.tsx +51 -0
- package/board/client/wait-line.tsx +39 -0
- package/board/index.client.tsx +1 -1
- package/board/index.server.ts +7 -1
- package/board/package.json +1 -1
- package/board/server/board-panel-state-helpers.ts +67 -0
- package/board/server/board-server-check.ts +294 -0
- package/board/server/board-server-engine.ts +129 -0
- package/board/server/board-server-events.ts +134 -0
- package/board/server/board-server-read.ts +190 -0
- package/board/server/board-server-resume.ts +164 -0
- package/board/server/board-server-start.ts +174 -0
- package/board/server/board-server-store.ts +281 -0
- package/board/server/board-server-types.ts +114 -0
- package/board/server/board-server-util.ts +4 -0
- package/board/server/board-server.ts +52 -943
- package/board/server/board-vocabulary-engine.ts +144 -0
- package/board/server/board-vocabulary-review.ts +115 -0
- package/board/server/board-vocabulary-run.ts +145 -0
- package/board/server/board-vocabulary-shapes.ts +153 -0
- package/board/server/board-vocabulary.ts +20 -375
- package/board/server/current-step-panel-helpers.ts +108 -0
- package/board/server/describe-record.ts +403 -0
- package/board/server/engine-launch.ts +20 -3
- package/board/server/engine-restart-helpers.ts +105 -0
- package/board/server/engine-watch.ts +45 -5
- package/board/server/journal-rebuild-helpers.ts +169 -0
- package/board/server/live-file.ts +130 -0
- package/board/server/luca-version.ts +1 -1
- package/board/server/make-rows.ts +14 -629
- package/board/server/record-texts.ts +350 -0
- package/board/server/reduce-board-agent-start.ts +224 -0
- package/board/server/reduce-board-agents.ts +160 -0
- package/board/server/reduce-board-counters.ts +138 -0
- package/board/server/reduce-board-create.ts +148 -0
- package/board/server/reduce-board-final-review.ts +174 -0
- package/board/server/reduce-board-needs-you.ts +283 -0
- package/board/server/reduce-board-reasons.ts +59 -0
- package/board/server/reduce-board-run.ts +255 -0
- package/board/server/reduce-board-settle.ts +88 -0
- package/board/server/reduce-board-steps.ts +226 -0
- package/board/server/reduce-board-text.ts +139 -0
- package/board/server/reduce-board-ticket-checks.ts +168 -0
- package/board/server/reduce-board-ticket-join.ts +144 -0
- package/board/server/reduce-board-ticket-start.ts +132 -0
- package/board/server/reduce-board-update.ts +117 -0
- package/board/server/reduce-board-usage.ts +247 -0
- package/board/server/reduce-board.ts +100 -1917
- package/board/server/reply-post.ts +407 -0
- package/board/server/run-registry.ts +8 -4
- package/board/server/ticket-stages-panel-helpers.ts +296 -0
- package/board/shared/board-rows.ts +9 -0
- package/board/shared/board-rpc.ts +42 -0
- package/board/shared/board-state-counts.ts +77 -0
- package/board/shared/board-state-live.ts +99 -0
- package/board/shared/board-state-review.ts +59 -0
- package/board/shared/board-state-run.ts +236 -0
- package/board/shared/board-state-ticket.ts +257 -0
- package/board/shared/board-state-usage.ts +107 -0
- package/board/shared/board-state.ts +97 -567
- package/board/shared/reply-actions.ts +207 -0
- package/engine/agents/agent-launcher.ts +27 -4
- package/engine/agents/claude-launcher-helpers.ts +233 -0
- package/engine/agents/claude-launcher.ts +28 -669
- package/engine/agents/claude-messages.ts +212 -0
- package/engine/agents/claude-options.ts +43 -8
- package/engine/agents/common-instructions.ts +138 -0
- package/engine/agents/input-channel.ts +36 -0
- package/engine/agents/launch-paths.ts +29 -0
- package/engine/agents/launch-session.ts +227 -0
- package/engine/agents/learner-instructions.ts +31 -0
- package/engine/agents/open-session.ts +210 -0
- package/engine/agents/prompt-sections.ts +95 -0
- package/engine/agents/rejoin-prompts.ts +135 -0
- package/engine/agents/reviewer-instructions.ts +99 -0
- package/engine/agents/role-instructions.ts +31 -244
- package/engine/agents/role-prompts.ts +39 -109
- package/engine/agents/role-results.ts +106 -7
- package/engine/agents/scripted-launcher.ts +21 -6
- package/engine/agents/session-pool.ts +124 -0
- package/engine/agents/turn-progress.ts +283 -0
- package/engine/agents/writer-instructions.ts +151 -0
- package/engine/cli/computer-adapters-real.ts +15 -3
- package/engine/cli/computer-adapters.ts +2 -0
- package/engine/cli/computer-checks.ts +87 -4
- package/engine/cli/config-reload-text.ts +39 -0
- package/engine/cli/decision-model-setup.ts +70 -0
- package/engine/cli/doctor-command.ts +1 -0
- package/engine/cli/doctor.ts +21 -3
- package/engine/cli/going-runs.ts +192 -36
- package/engine/cli/init-command.ts +1 -0
- package/engine/cli/init.ts +23 -1
- package/engine/cli/live-runs.ts +73 -0
- package/engine/cli/luca-run.ts +42 -7
- package/engine/cli/luca-skills.ts +183 -0
- package/engine/cli/luca.ts +8 -1
- package/engine/cli/models-command.ts +119 -0
- package/engine/cli/read-run.ts +81 -0
- package/engine/cli/run-args.ts +33 -6
- package/engine/cli/run-demo.ts +143 -0
- package/engine/cli/run-end.ts +184 -0
- package/engine/cli/run-modes-helpers.ts +81 -0
- package/engine/cli/run-modes.ts +62 -439
- package/engine/cli/setup.ts +5 -48
- package/engine/cli/unfinished-runs.ts +122 -0
- package/engine/cli/upgrade-command.ts +212 -30
- package/engine/cli/upgrade-finish.ts +149 -0
- package/engine/cli/upgrade.ts +112 -71
- package/engine/config/config-fields.ts +127 -0
- package/engine/config/config-reload.ts +250 -0
- package/engine/config/engine-config-schema.ts +235 -0
- package/engine/config/engine-config.ts +72 -142
- package/engine/config/test-commands.ts +102 -0
- package/engine/core/agent-guards-helpers.ts +84 -0
- package/engine/core/agent-turn.ts +395 -0
- package/engine/core/already-done.ts +44 -1
- package/engine/core/build-action.ts +214 -0
- package/engine/core/build-context.ts +135 -0
- package/engine/core/build-launch.ts +259 -0
- package/engine/core/commit-in.ts +282 -0
- package/engine/core/decide-build-early.ts +108 -0
- package/engine/core/decide-build-ending.ts +145 -0
- package/engine/core/decide-build-helpers.ts +87 -0
- package/engine/core/decide-build-tickets.ts +207 -0
- package/engine/core/decide-build-view.ts +103 -0
- package/engine/core/decide-build.ts +101 -1368
- package/engine/core/decide-final-fix.ts +203 -0
- package/engine/core/decide-final-review.ts +55 -310
- package/engine/core/decide-red-green.ts +398 -0
- package/engine/core/decide-rejoin.ts +259 -0
- package/engine/core/decide-repo-wait.ts +129 -0
- package/engine/core/decide-run-budget.ts +86 -12
- package/engine/core/decide-stuck-helpers.ts +127 -0
- package/engine/core/decide-stuck.ts +60 -8
- package/engine/core/decide-ticket-review.ts +95 -0
- package/engine/core/decide-ticket.ts +267 -0
- package/engine/core/decide-waits.ts +47 -0
- package/engine/core/decide.ts +74 -23
- package/engine/core/engine-chores.ts +163 -0
- package/engine/core/environment-rerun.ts +83 -0
- package/engine/core/evidence-text.ts +84 -0
- package/engine/core/execute-action.ts +303 -0
- package/engine/core/execute-already-done-check.ts +133 -0
- package/engine/core/execute-build.ts +68 -944
- package/engine/core/execute-changeset.ts +5 -7
- package/engine/core/execute-final-review.ts +125 -13
- package/engine/core/execute-gates.ts +182 -0
- package/engine/core/execute-intake.ts +130 -0
- package/engine/core/execute-join.ts +276 -0
- package/engine/core/execute-memory.ts +12 -0
- package/engine/core/execute-pull-request.ts +84 -0
- package/engine/core/execute-red-check.ts +219 -0
- package/engine/core/execute-repo-wait.ts +129 -0
- package/engine/core/execute-stuck.ts +25 -10
- package/engine/core/execute-waits.ts +158 -0
- package/engine/core/execute-with-jev.ts +118 -0
- package/engine/core/execute.ts +124 -831
- package/engine/core/final-failure-cap.ts +38 -0
- package/engine/core/final-review-action.ts +125 -0
- package/engine/core/final-review-text.ts +63 -17
- package/engine/core/final-stuck-text.ts +86 -0
- package/engine/core/fix-loop-text.ts +49 -2
- package/engine/core/left-out.ts +120 -0
- package/engine/core/live-file.ts +372 -0
- package/engine/core/loop-caps.ts +25 -2
- package/engine/core/not-in-run.ts +7 -3
- package/engine/core/pull-request-text.ts +255 -6
- package/engine/core/repo-wait-text.ts +81 -0
- package/engine/core/review-text.ts +55 -1
- package/engine/core/run-crash-recovery-helpers.ts +16 -0
- package/engine/core/run-session-close-helpers.ts +96 -0
- package/engine/core/run-start.ts +148 -0
- package/engine/core/run-stuck-text.ts +86 -0
- package/engine/core/schedule-rules.ts +193 -0
- package/engine/core/stuck-detail-text.ts +89 -0
- package/engine/core/stuck-format.ts +22 -0
- package/engine/core/stuck-text.ts +29 -332
- package/engine/core/ticket-stuck-text.ts +192 -0
- package/engine/core/worktree-changes.ts +106 -0
- package/engine/gates/already-done-check.ts +120 -0
- package/engine/gates/feature-map.ts +96 -0
- package/engine/gates/gate-runner.ts +108 -108
- package/engine/gates/gate-schemas.ts +61 -2
- package/engine/gates/leftover-scan.ts +154 -31
- package/engine/gates/new-tests.ts +45 -0
- package/engine/gates/own-files-check.ts +98 -0
- package/engine/gates/prepare-check.ts +60 -0
- package/engine/gates/prepare-slots.ts +99 -0
- package/engine/gates/red-check.ts +184 -16
- package/engine/gates/shell-check.ts +64 -0
- package/engine/gates/slot-waits.ts +55 -0
- package/engine/gates/slow-test-runner.ts +59 -0
- package/engine/gates/test-checks.ts +161 -0
- package/engine/gates/test-runner.ts +164 -5
- package/engine/gates/test-slots.ts +48 -0
- package/engine/git/git-adapter.ts +55 -396
- package/engine/git/git-commits.ts +132 -0
- package/engine/git/git-join.ts +139 -0
- package/engine/git/git-read.ts +123 -0
- package/engine/git/git-run.ts +32 -0
- package/engine/git/git-worktrees.ts +143 -0
- package/engine/git/outside-links.ts +55 -0
- package/engine/guards/after-turn-check.ts +31 -5
- package/engine/guards/guard-hook.ts +60 -12
- package/engine/guards/guard-roles.ts +68 -0
- package/engine/guards/helper-rules.ts +92 -0
- package/engine/guards/permission-rules.ts +161 -0
- package/engine/guards/role-rules-helpers.ts +48 -0
- package/engine/guards/role-rules.ts +34 -606
- package/engine/guards/shell-rules.ts +344 -0
- package/engine/guards/worktree-git.ts +208 -0
- package/engine/guards/worktree-snapshot.ts +412 -0
- package/engine/guards/worktree-state.ts +28 -825
- package/engine/guards/worktree-undo.ts +260 -0
- package/engine/guards/write-rules.ts +149 -0
- package/engine/index.ts +30 -9
- package/engine/intake/criteria-checks.ts +114 -0
- package/engine/intake/intake-checks.ts +164 -30
- package/engine/intake/intake-schemas.ts +37 -2
- package/engine/jev/clef-client.ts +259 -0
- package/engine/jev/decision-model-credentials.ts +212 -0
- package/engine/jev/jev-jobs.ts +144 -60
- package/engine/jev/jev-schemas.ts +86 -5
- package/engine/jev/jev-shadow-helpers.ts +218 -0
- package/engine/jev/jev-shadow.ts +109 -64
- package/engine/jev/model-pick.ts +275 -0
- package/engine/journal/agent-entries.ts +167 -0
- package/engine/journal/agent-message-entries.ts +72 -0
- package/engine/journal/agent-turn-replay.ts +203 -0
- package/engine/journal/already-done-entries.ts +55 -0
- package/engine/journal/commit-entries.ts +56 -0
- package/engine/journal/cross-ticket-replay.ts +222 -0
- package/engine/journal/decision-model-entries.ts +86 -0
- package/engine/journal/entry-fields.ts +10 -0
- package/engine/journal/final-review-entries.ts +128 -0
- package/engine/journal/final-review-replay.ts +324 -0
- package/engine/journal/final-review-round.ts +320 -0
- package/engine/journal/final-review-state.ts +180 -0
- package/engine/journal/gate-entries.ts +81 -0
- package/engine/journal/intake-entries.ts +87 -0
- package/engine/journal/join-entries.ts +126 -0
- package/engine/journal/journal-entry-schema.ts +218 -0
- package/engine/journal/journal-kinds.ts +97 -0
- package/engine/journal/journal-record-schema.ts +218 -0
- package/engine/journal/journal-record.ts +43 -1400
- package/engine/journal/journal.ts +6 -1
- package/engine/journal/limit-entries.ts +90 -0
- package/engine/journal/memory-entries.ts +124 -0
- package/engine/journal/memory-replay.ts +157 -0
- package/engine/journal/plan-replay.ts +139 -0
- package/engine/journal/pull-request-entries.ts +41 -0
- package/engine/journal/replay.ts +157 -2144
- package/engine/journal/reply-replay.ts +144 -0
- package/engine/journal/repo-wait-entries.ts +53 -0
- package/engine/journal/run-entries.ts +172 -0
- package/engine/journal/run-state.ts +264 -0
- package/engine/journal/shared-failure-replay.ts +133 -0
- package/engine/journal/stuck-entries.ts +193 -0
- package/engine/journal/ticket-progress.ts +392 -0
- package/engine/journal/ticket-replay.ts +411 -0
- package/engine/journal/ticket-result-replay.ts +364 -0
- package/engine/journal/ticket-retry-replay.ts +142 -0
- package/engine/journal/worktree-entries.ts +122 -0
- package/engine/models/model-score.ts +512 -0
- package/engine/shell/run-command.ts +187 -18
- package/engine/testing/agent-turn-fixtures.ts +287 -0
- package/engine/testing/already-done-fixtures.ts +97 -0
- package/engine/testing/build-fixtures.ts +68 -973
- package/engine/testing/checkouts-fixtures.ts +48 -0
- package/engine/testing/final-review-fixtures.ts +62 -1
- package/engine/testing/final-review-steps.ts +143 -0
- package/engine/testing/limit-fixtures.ts +85 -0
- package/engine/testing/live-file-fixtures.ts +57 -0
- package/engine/testing/practice-repo.ts +10 -4
- package/engine/testing/practice-run.ts +1 -1
- package/engine/testing/reply-fixtures.ts +88 -0
- package/engine/testing/run-budget-fixtures.ts +165 -0
- package/engine/testing/run-fixtures.ts +219 -0
- package/engine/testing/sdk-play.ts +177 -0
- package/engine/testing/setup-repo.ts +194 -0
- package/engine/testing/step-record-fixtures.ts +55 -0
- package/engine/testing/ticket-step-fixtures.ts +272 -0
- package/engine/testing/wait-scenarios.ts +186 -0
- package/engine/tracker/gh-retry.ts +217 -0
- package/engine/tracker/github-tracker.ts +310 -90
- package/engine/tracker/in-memory-tracker.ts +8 -4
- package/engine/tracker/tracker.ts +131 -6
- package/package.json +2 -1
- package/skills/luca-retro/SKILL.md +74 -0
- package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
- package/skills/luca-unstick/SKILL.md +153 -0
- package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
- 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
|
-
/**
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/board/index.client.tsx
CHANGED
|
@@ -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 }) => {
|
package/board/index.server.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
package/board/package.json
CHANGED
|
@@ -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
|
+
}
|