@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
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What needs a person: a stuck ticket or run, a skipped or retried ticket,
|
|
3
|
+
* and the replies that resolve them.
|
|
4
|
+
*/
|
|
5
|
+
import type { BoardRecord } from './board-vocabulary'
|
|
6
|
+
import {
|
|
7
|
+
RETRY_REFUSED_TEXT,
|
|
8
|
+
reasonText,
|
|
9
|
+
runReasonText,
|
|
10
|
+
} from './reduce-board-reasons'
|
|
11
|
+
import { clip } from './reduce-board-text'
|
|
12
|
+
import {
|
|
13
|
+
DETAIL_MAX,
|
|
14
|
+
addNeedsYou,
|
|
15
|
+
resolveNeedsYou,
|
|
16
|
+
updateFinal,
|
|
17
|
+
updateTicket,
|
|
18
|
+
withTried,
|
|
19
|
+
} from './reduce-board-update'
|
|
20
|
+
|
|
21
|
+
import type { BoardState } from '../shared/board-state'
|
|
22
|
+
import {
|
|
23
|
+
FINAL_KEY,
|
|
24
|
+
RUN_KEY,
|
|
25
|
+
allowedReplies,
|
|
26
|
+
ticketKey,
|
|
27
|
+
} from '../shared/reply-actions'
|
|
28
|
+
|
|
29
|
+
const skipTicket = ({
|
|
30
|
+
state,
|
|
31
|
+
ticket,
|
|
32
|
+
because,
|
|
33
|
+
}: {
|
|
34
|
+
state: BoardState
|
|
35
|
+
ticket: number | null
|
|
36
|
+
because: number | null
|
|
37
|
+
}): BoardState => {
|
|
38
|
+
if (ticket === null) return state
|
|
39
|
+
return resolveNeedsYou({
|
|
40
|
+
state: updateTicket({
|
|
41
|
+
state,
|
|
42
|
+
number: ticket,
|
|
43
|
+
update: (card) => ({
|
|
44
|
+
...card,
|
|
45
|
+
stage: 'skipped',
|
|
46
|
+
role: null,
|
|
47
|
+
activity:
|
|
48
|
+
because === null
|
|
49
|
+
? 'skipped'
|
|
50
|
+
: `skipped: waits on skipped #${because}`,
|
|
51
|
+
}),
|
|
52
|
+
}),
|
|
53
|
+
key: ticketKey({ ticket }),
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* How the engine took a `retry`: `resume` changes nothing more than the
|
|
59
|
+
* reply did; `restart` builds the edited ticket from scratch (its new
|
|
60
|
+
* snapshot came just before); `refused` leaves it stuck, back in Needs you.
|
|
61
|
+
*/
|
|
62
|
+
const ticketRetried = ({
|
|
63
|
+
state,
|
|
64
|
+
record,
|
|
65
|
+
}: {
|
|
66
|
+
state: BoardState
|
|
67
|
+
record: Extract<BoardRecord, { kind: 'ticket_retried' }>
|
|
68
|
+
}): BoardState => {
|
|
69
|
+
const { ticket } = record
|
|
70
|
+
if (ticket === null) return state
|
|
71
|
+
switch (record.content.mode) {
|
|
72
|
+
case 'resume':
|
|
73
|
+
return state
|
|
74
|
+
case 'restart':
|
|
75
|
+
return updateTicket({
|
|
76
|
+
state,
|
|
77
|
+
number: ticket,
|
|
78
|
+
update: (card) => ({
|
|
79
|
+
...card,
|
|
80
|
+
stage: 'building',
|
|
81
|
+
step: 0,
|
|
82
|
+
role: null,
|
|
83
|
+
fix_round: 0,
|
|
84
|
+
review_fix_round: 0,
|
|
85
|
+
findings: null,
|
|
86
|
+
open_check: null,
|
|
87
|
+
failed_turn: null,
|
|
88
|
+
activity: 'starting over from the edited ticket',
|
|
89
|
+
tried: withTried({
|
|
90
|
+
tried: card.tried,
|
|
91
|
+
line: 'Started over from the edited ticket',
|
|
92
|
+
}),
|
|
93
|
+
}),
|
|
94
|
+
})
|
|
95
|
+
case 'refused': {
|
|
96
|
+
const card = state.tickets.find((entry) => entry.number === ticket)
|
|
97
|
+
return addNeedsYou({
|
|
98
|
+
state: updateTicket({
|
|
99
|
+
state,
|
|
100
|
+
number: ticket,
|
|
101
|
+
update: (entry) => ({
|
|
102
|
+
...entry,
|
|
103
|
+
stage: 'stuck',
|
|
104
|
+
role: null,
|
|
105
|
+
activity: 'stuck',
|
|
106
|
+
}),
|
|
107
|
+
}),
|
|
108
|
+
item: {
|
|
109
|
+
key: ticketKey({ ticket }),
|
|
110
|
+
ticket,
|
|
111
|
+
subject: `Retry of #${ticket} was refused${card ? `: ${card.title}` : ''}`,
|
|
112
|
+
reason: RETRY_REFUSED_TEXT,
|
|
113
|
+
detail: clip({
|
|
114
|
+
text: record.content.problems.join(' '),
|
|
115
|
+
max: DETAIL_MAX,
|
|
116
|
+
}),
|
|
117
|
+
tried: card?.tried ?? [],
|
|
118
|
+
replies: allowedReplies({
|
|
119
|
+
key: ticketKey({ ticket }),
|
|
120
|
+
ticket,
|
|
121
|
+
}),
|
|
122
|
+
since: record.time,
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const replyReceived = ({
|
|
130
|
+
state,
|
|
131
|
+
record,
|
|
132
|
+
}: {
|
|
133
|
+
state: BoardState
|
|
134
|
+
record: Extract<BoardRecord, { kind: 'reply_received' }>
|
|
135
|
+
}): BoardState => {
|
|
136
|
+
const { word } = record.content
|
|
137
|
+
const ticket = record.content.ticket ?? record.ticket
|
|
138
|
+
switch (word) {
|
|
139
|
+
case 'retry':
|
|
140
|
+
if (ticket !== null) {
|
|
141
|
+
return resolveNeedsYou({
|
|
142
|
+
state: updateTicket({
|
|
143
|
+
state,
|
|
144
|
+
number: ticket,
|
|
145
|
+
update: (card) => ({
|
|
146
|
+
...card,
|
|
147
|
+
stage: 'building',
|
|
148
|
+
activity: 'retrying',
|
|
149
|
+
}),
|
|
150
|
+
}),
|
|
151
|
+
key: ticketKey({ ticket }),
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
// A bare `retry` while the run is stuck on its budget adds its
|
|
155
|
+
// top-up (one whole budget for a retry journaled with none,
|
|
156
|
+
// #543); stuck for another reason, no budget. The final review
|
|
157
|
+
// is left as it is.
|
|
158
|
+
if (state.needs_you.some((item) => item.key === RUN_KEY)) {
|
|
159
|
+
const top_up =
|
|
160
|
+
state.run_stuck_reason === 'run_budget'
|
|
161
|
+
? (record.content.top_up ?? state.run_budget_each)
|
|
162
|
+
: 0
|
|
163
|
+
return resolveNeedsYou({
|
|
164
|
+
state: {
|
|
165
|
+
...state,
|
|
166
|
+
run_budget_tokens: state.run_budget_tokens + top_up,
|
|
167
|
+
run_stuck_reason: null,
|
|
168
|
+
},
|
|
169
|
+
key: RUN_KEY,
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
return resolveNeedsYou({
|
|
173
|
+
state: updateFinal({
|
|
174
|
+
state,
|
|
175
|
+
update: (review) => ({ ...review, state: 'reviewing' }),
|
|
176
|
+
}),
|
|
177
|
+
key: FINAL_KEY,
|
|
178
|
+
})
|
|
179
|
+
case 'skip':
|
|
180
|
+
return skipTicket({ state, ticket, because: null })
|
|
181
|
+
case 'ship':
|
|
182
|
+
return resolveNeedsYou({
|
|
183
|
+
state: updateFinal({
|
|
184
|
+
state,
|
|
185
|
+
update: (review) => ({ ...review, state: 'passed' }),
|
|
186
|
+
}),
|
|
187
|
+
key: FINAL_KEY,
|
|
188
|
+
})
|
|
189
|
+
case 'stop':
|
|
190
|
+
return resolveNeedsYou({ state, key: null })
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The record kinds of what needs a person. */
|
|
195
|
+
export const NEEDS_YOU_KINDS = [
|
|
196
|
+
'ticket_stuck',
|
|
197
|
+
'run_stuck',
|
|
198
|
+
'reply_received',
|
|
199
|
+
'ticket_skipped',
|
|
200
|
+
'ticket_retried',
|
|
201
|
+
'reply_ignored',
|
|
202
|
+
] as const
|
|
203
|
+
|
|
204
|
+
type NeedsYouRecord = Extract<
|
|
205
|
+
BoardRecord,
|
|
206
|
+
{ kind: (typeof NEEDS_YOU_KINDS)[number] }
|
|
207
|
+
>
|
|
208
|
+
|
|
209
|
+
/** Applies one record of NEEDS_YOU_KINDS to the board. */
|
|
210
|
+
export const applyNeedsYouKind = ({
|
|
211
|
+
state,
|
|
212
|
+
record,
|
|
213
|
+
}: {
|
|
214
|
+
state: BoardState
|
|
215
|
+
record: NeedsYouRecord
|
|
216
|
+
}): BoardState => {
|
|
217
|
+
const { ticket } = record
|
|
218
|
+
switch (record.kind) {
|
|
219
|
+
case 'ticket_stuck': {
|
|
220
|
+
if (ticket === null) return state
|
|
221
|
+
const card = state.tickets.find((entry) => entry.number === ticket)
|
|
222
|
+
const stuck = updateTicket({
|
|
223
|
+
state,
|
|
224
|
+
number: ticket,
|
|
225
|
+
update: (entry) => ({
|
|
226
|
+
...entry,
|
|
227
|
+
stage: 'stuck',
|
|
228
|
+
role: null,
|
|
229
|
+
activity: 'stuck',
|
|
230
|
+
}),
|
|
231
|
+
})
|
|
232
|
+
return addNeedsYou({
|
|
233
|
+
state: stuck,
|
|
234
|
+
item: {
|
|
235
|
+
key: ticketKey({ ticket }),
|
|
236
|
+
ticket,
|
|
237
|
+
subject: `Ticket #${ticket} is stuck${card ? `: ${card.title}` : ''}`,
|
|
238
|
+
reason: reasonText({ reason: record.content.reason }),
|
|
239
|
+
detail: clip({
|
|
240
|
+
text: record.content.detail,
|
|
241
|
+
max: DETAIL_MAX,
|
|
242
|
+
}),
|
|
243
|
+
tried: card?.tried ?? [],
|
|
244
|
+
replies: allowedReplies({
|
|
245
|
+
key: ticketKey({ ticket }),
|
|
246
|
+
ticket,
|
|
247
|
+
}),
|
|
248
|
+
since: record.time,
|
|
249
|
+
},
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
case 'run_stuck':
|
|
253
|
+
return addNeedsYou({
|
|
254
|
+
state: { ...state, run_stuck_reason: record.content.reason },
|
|
255
|
+
item: {
|
|
256
|
+
key: RUN_KEY,
|
|
257
|
+
ticket: null,
|
|
258
|
+
subject: 'The run is stuck',
|
|
259
|
+
reason: runReasonText({ reason: record.content.reason }),
|
|
260
|
+
detail: clip({
|
|
261
|
+
text: record.content.detail,
|
|
262
|
+
max: DETAIL_MAX,
|
|
263
|
+
}),
|
|
264
|
+
tried: [],
|
|
265
|
+
replies: allowedReplies({ key: RUN_KEY, ticket: null }),
|
|
266
|
+
since: record.time,
|
|
267
|
+
},
|
|
268
|
+
})
|
|
269
|
+
case 'reply_received':
|
|
270
|
+
return replyReceived({ state, record })
|
|
271
|
+
case 'ticket_skipped':
|
|
272
|
+
return skipTicket({
|
|
273
|
+
state,
|
|
274
|
+
ticket,
|
|
275
|
+
because: record.content.because,
|
|
276
|
+
})
|
|
277
|
+
case 'ticket_retried':
|
|
278
|
+
return ticketRetried({ state, record })
|
|
279
|
+
// An ignored reply is only a chat row; the engine answered it.
|
|
280
|
+
case 'reply_ignored':
|
|
281
|
+
return state
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a ticket, the final review, or the whole run is stuck, as codes and as
|
|
3
|
+
* words: the text a stuck card shows for each reason the engine journals.
|
|
4
|
+
*/
|
|
5
|
+
import { LOOP_CAP } from '../shared/board-state'
|
|
6
|
+
|
|
7
|
+
const STUCK_REASONS: Record<string, string> = {
|
|
8
|
+
agent_failed: 'An agent failed on its last try.',
|
|
9
|
+
red_check_failed: 'The red check failed.',
|
|
10
|
+
nothing_new_to_test:
|
|
11
|
+
'The test-writer says the ticket changes no behavior, so there is nothing new to test. Label it refactor and retry it.',
|
|
12
|
+
leftovers_found:
|
|
13
|
+
'The leftover scan found files that must not be committed.',
|
|
14
|
+
gates_failed: 'The checks failed.',
|
|
15
|
+
bad_test:
|
|
16
|
+
'A test was sent back as bad, by the implementer or because it checks nothing.',
|
|
17
|
+
changes_requested: 'The ticket review asked for changes.',
|
|
18
|
+
join_failed: "The ticket couldn't join the run branch.",
|
|
19
|
+
join_gates_failed: 'The checks failed after the ticket joined.',
|
|
20
|
+
install_failed: 'Installing the dependencies failed.',
|
|
21
|
+
prepare_failed: "The repo's prepare command failed.",
|
|
22
|
+
setup_change_needed:
|
|
23
|
+
'An agent needs a test setup file changed; only you may change one.',
|
|
24
|
+
crashed: 'The engine crashed in the same step again and again.',
|
|
25
|
+
undo_refused:
|
|
26
|
+
'Undoing its join would have rewound the run branch over other work.',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Why a retry of an edited ticket was refused, in words. */
|
|
30
|
+
export const RETRY_REFUSED_TEXT =
|
|
31
|
+
"Its new text or labels aren't ready to build."
|
|
32
|
+
|
|
33
|
+
/** A stuck reason code in words. */
|
|
34
|
+
export const reasonText = ({ reason }: { reason: string }): string =>
|
|
35
|
+
STUCK_REASONS[reason] ?? `${reason.replaceAll('_', ' ')}.`
|
|
36
|
+
|
|
37
|
+
/** The final review's stuck reasons that read differently from a ticket's. */
|
|
38
|
+
const FINAL_STUCK_REASONS: Record<string, string> = {
|
|
39
|
+
changes_requested: `The lenses still ask for changes after ${LOOP_CAP} fix rounds.`,
|
|
40
|
+
gates_failed: "The checks still fail on the final review's fixes.",
|
|
41
|
+
bad_test:
|
|
42
|
+
"The implementer sent back a bad test while fixing the final review's findings.",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A final review's stuck reason code in words. */
|
|
46
|
+
export const finalReasonText = ({ reason }: { reason: string }): string =>
|
|
47
|
+
FINAL_STUCK_REASONS[reason] ?? reasonText({ reason })
|
|
48
|
+
|
|
49
|
+
/** Why the whole run is stuck (`run_stuck`'s reason), in words. */
|
|
50
|
+
const RUN_STUCK_REASONS: Record<string, string> = {
|
|
51
|
+
run_budget:
|
|
52
|
+
'The run used up its run budget of tokens. `retry` adds half a budget more, and `retry +3M` the amount named.',
|
|
53
|
+
pull_request_failed:
|
|
54
|
+
"The run's pull request didn't open. `retry` tries again, reusing a PR already open from the run branch.",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A run stuck reason code in words. */
|
|
58
|
+
export const runReasonText = ({ reason }: { reason: string }): string =>
|
|
59
|
+
RUN_STUCK_REASONS[reason] ?? `${reason.replaceAll('_', ' ')}.`
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run's own records: how it starts and resumes, its repo wait, intake
|
|
3
|
+
* and the spec's snapshot, the run branch, the pull request, and a stop.
|
|
4
|
+
*/
|
|
5
|
+
import type { BoardRecord, LeftOutList } from './board-vocabulary'
|
|
6
|
+
import { numbersText } from './reduce-board-text'
|
|
7
|
+
import { updateTicket, withTried } from './reduce-board-update'
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_CONFIDENCE_BAR,
|
|
11
|
+
DEFAULT_RUN_BUDGET_TOKENS,
|
|
12
|
+
type BoardState,
|
|
13
|
+
type PersonTicket,
|
|
14
|
+
type RunInfo,
|
|
15
|
+
} from '../shared/board-state'
|
|
16
|
+
import { repoOfIssueUrl } from '../shared/reply-actions'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The run's version note after a resume on Luca `resumed_on`: both versions
|
|
20
|
+
* when it differs from the one the run started on, else the note so far (a
|
|
21
|
+
* run that once changed version keeps saying so). Nothing when the journal
|
|
22
|
+
* doesn't say what the run started on.
|
|
23
|
+
*/
|
|
24
|
+
const versionNote = ({
|
|
25
|
+
run,
|
|
26
|
+
resumed_on,
|
|
27
|
+
}: {
|
|
28
|
+
run: RunInfo
|
|
29
|
+
resumed_on: string
|
|
30
|
+
}): string | null =>
|
|
31
|
+
run.started_on === null || run.started_on === resumed_on
|
|
32
|
+
? run.version_note
|
|
33
|
+
: `This run started on Luca ${run.started_on} and resumed on Luca ${resumed_on}. It keeps going.`
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The tickets left out for a person (#499), with why in words, as the
|
|
37
|
+
* engine's PR says it.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* personTickets({ left_out: [{ number: 13, title: 'Link', reason: 'waits_on_person', waits_on: [12], through: [] }] })
|
|
41
|
+
* // [{ number: 13, title: 'Link', why: 'waits on #12, which is for a person' }]
|
|
42
|
+
*/
|
|
43
|
+
const personTickets = ({
|
|
44
|
+
left_out,
|
|
45
|
+
}: {
|
|
46
|
+
left_out: LeftOutList
|
|
47
|
+
}): PersonTicket[] =>
|
|
48
|
+
left_out.map(({ number, title, reason, waits_on, through }) => {
|
|
49
|
+
if (reason === 'for_a_person') {
|
|
50
|
+
return { number, title, why: 'for a person' }
|
|
51
|
+
}
|
|
52
|
+
const which =
|
|
53
|
+
waits_on.length === 1
|
|
54
|
+
? 'which is for a person'
|
|
55
|
+
: 'which are for a person'
|
|
56
|
+
const via =
|
|
57
|
+
through.length === 0
|
|
58
|
+
? ''
|
|
59
|
+
: ` (through ${numbersText({ numbers: through })})`
|
|
60
|
+
return {
|
|
61
|
+
number,
|
|
62
|
+
title,
|
|
63
|
+
why: `waits on ${numbersText({ numbers: waits_on })}, ${which}${via}`,
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The spec's owner and repo from intake's copy of the spec issue, keeping
|
|
69
|
+
* what the board knew when the record lacks them (older journals).
|
|
70
|
+
*/
|
|
71
|
+
const specOwner = ({
|
|
72
|
+
run,
|
|
73
|
+
spec,
|
|
74
|
+
}: {
|
|
75
|
+
run: RunInfo
|
|
76
|
+
spec: { author?: string; url?: string }
|
|
77
|
+
}): Pick<RunInfo, 'spec_author' | 'spec_repo'> => ({
|
|
78
|
+
spec_author:
|
|
79
|
+
spec.author === undefined || spec.author === ''
|
|
80
|
+
? run.spec_author
|
|
81
|
+
: spec.author,
|
|
82
|
+
spec_repo:
|
|
83
|
+
(spec.url === undefined ? null : repoOfIssueUrl({ url: spec.url })) ??
|
|
84
|
+
run.spec_repo,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
/** The record kinds the run's own records. */
|
|
88
|
+
export const RUN_KINDS = [
|
|
89
|
+
'run_started',
|
|
90
|
+
'repo_wait_started',
|
|
91
|
+
'repo_wait_ended',
|
|
92
|
+
'engine_resumed',
|
|
93
|
+
'config_reloaded',
|
|
94
|
+
'config_reload_refused',
|
|
95
|
+
'intake_read',
|
|
96
|
+
'intake_refused',
|
|
97
|
+
'nothing_to_do',
|
|
98
|
+
'spec_snapshot',
|
|
99
|
+
'run_branch_created',
|
|
100
|
+
'run_stopped',
|
|
101
|
+
'pull_request_opened',
|
|
102
|
+
'worktrees_removed',
|
|
103
|
+
] as const
|
|
104
|
+
|
|
105
|
+
type RunRecord = Extract<BoardRecord, { kind: (typeof RUN_KINDS)[number] }>
|
|
106
|
+
|
|
107
|
+
/** Applies one record of RUN_KINDS to the board. */
|
|
108
|
+
export const applyRunKind = ({
|
|
109
|
+
state,
|
|
110
|
+
record,
|
|
111
|
+
}: {
|
|
112
|
+
state: BoardState
|
|
113
|
+
record: RunRecord
|
|
114
|
+
}): BoardState => {
|
|
115
|
+
const { ticket } = record
|
|
116
|
+
switch (record.kind) {
|
|
117
|
+
case 'run_started': {
|
|
118
|
+
const budget =
|
|
119
|
+
record.content.config?.run_budget_tokens ??
|
|
120
|
+
DEFAULT_RUN_BUDGET_TOKENS
|
|
121
|
+
return {
|
|
122
|
+
...state,
|
|
123
|
+
run_budget_tokens: budget,
|
|
124
|
+
run_budget_each: budget,
|
|
125
|
+
confidence_bar:
|
|
126
|
+
record.content.config?.models?.confidence_bar ??
|
|
127
|
+
DEFAULT_CONFIDENCE_BAR,
|
|
128
|
+
run: {
|
|
129
|
+
...state.run,
|
|
130
|
+
spec_number: record.content.spec_number,
|
|
131
|
+
phase: 'intake',
|
|
132
|
+
started_on: record.content.luca_version,
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
case 'repo_wait_started':
|
|
137
|
+
return {
|
|
138
|
+
...state,
|
|
139
|
+
repo_wait: {
|
|
140
|
+
runs: record.content.runs.map(({ run_id }) => run_id),
|
|
141
|
+
since: record.time,
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
case 'repo_wait_ended':
|
|
145
|
+
return { ...state, repo_wait: null }
|
|
146
|
+
case 'engine_resumed':
|
|
147
|
+
return {
|
|
148
|
+
...state,
|
|
149
|
+
run: {
|
|
150
|
+
...state.run,
|
|
151
|
+
version_note: versionNote({
|
|
152
|
+
run: state.run,
|
|
153
|
+
resumed_on: record.content.luca_version,
|
|
154
|
+
}),
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
// A resume's config reload (#516) is only a chat row.
|
|
158
|
+
case 'config_reloaded':
|
|
159
|
+
case 'config_reload_refused':
|
|
160
|
+
return state
|
|
161
|
+
case 'intake_read':
|
|
162
|
+
return {
|
|
163
|
+
...state,
|
|
164
|
+
run: {
|
|
165
|
+
...state.run,
|
|
166
|
+
spec_title:
|
|
167
|
+
state.run.spec_title ?? record.content.spec.title,
|
|
168
|
+
phase: 'intake',
|
|
169
|
+
...specOwner({ run: state.run, spec: record.content.spec }),
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
case 'intake_refused':
|
|
173
|
+
return {
|
|
174
|
+
...state,
|
|
175
|
+
run: {
|
|
176
|
+
...state.run,
|
|
177
|
+
phase: 'refused',
|
|
178
|
+
refusal: record.content.problems.map(
|
|
179
|
+
(problem) =>
|
|
180
|
+
`${problem.ticket === null ? 'The run' : `#${problem.ticket}`}: missing ${problem.missing.join(', ')}`
|
|
181
|
+
),
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
case 'nothing_to_do':
|
|
185
|
+
return {
|
|
186
|
+
...state,
|
|
187
|
+
for_a_person: personTickets({
|
|
188
|
+
left_out: record.content.left_out,
|
|
189
|
+
}),
|
|
190
|
+
run: { ...state.run, phase: 'nothing_to_do' },
|
|
191
|
+
}
|
|
192
|
+
case 'spec_snapshot':
|
|
193
|
+
return {
|
|
194
|
+
...state,
|
|
195
|
+
for_a_person: personTickets({
|
|
196
|
+
left_out: record.content.left_out,
|
|
197
|
+
}),
|
|
198
|
+
run: {
|
|
199
|
+
...state.run,
|
|
200
|
+
spec_number: record.content.spec.number,
|
|
201
|
+
spec_title: record.content.spec.title,
|
|
202
|
+
phase: 'building',
|
|
203
|
+
...specOwner({ run: state.run, spec: record.content.spec }),
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
case 'run_branch_created':
|
|
207
|
+
return {
|
|
208
|
+
...state,
|
|
209
|
+
run: { ...state.run, branch: record.content.branch },
|
|
210
|
+
}
|
|
211
|
+
case 'run_stopped': {
|
|
212
|
+
const { reason, role, billing, crashed } = record.content
|
|
213
|
+
const stopped = updateTicket({
|
|
214
|
+
state,
|
|
215
|
+
number: ticket,
|
|
216
|
+
update: (card) => ({
|
|
217
|
+
...card,
|
|
218
|
+
role: null,
|
|
219
|
+
activity: 'run stopped',
|
|
220
|
+
tried: withTried({
|
|
221
|
+
tried: card.tried,
|
|
222
|
+
line: `${billing ? 'Stopped for billing' : crashed ? 'Stopped after crashes' : 'The run stopped'}: ${reason}`,
|
|
223
|
+
}),
|
|
224
|
+
}),
|
|
225
|
+
})
|
|
226
|
+
return {
|
|
227
|
+
...stopped,
|
|
228
|
+
run: {
|
|
229
|
+
...stopped.run,
|
|
230
|
+
stopped: {
|
|
231
|
+
reason,
|
|
232
|
+
role,
|
|
233
|
+
ticket,
|
|
234
|
+
billing,
|
|
235
|
+
crashed,
|
|
236
|
+
since: record.time,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
case 'pull_request_opened':
|
|
242
|
+
return {
|
|
243
|
+
...state,
|
|
244
|
+
run: {
|
|
245
|
+
...state.run,
|
|
246
|
+
phase: 'done',
|
|
247
|
+
pr_url: record.content.url,
|
|
248
|
+
pr_number: record.content.number,
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
// Removing the worktrees at the end changes nothing on the board.
|
|
252
|
+
case 'worktrees_removed':
|
|
253
|
+
return state
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What depends on the whole board: which unstarted tickets are blocked,
|
|
3
|
+
* whether the final review is active, and the run's status.
|
|
4
|
+
*/
|
|
5
|
+
import type {
|
|
6
|
+
BoardState,
|
|
7
|
+
RunStatus,
|
|
8
|
+
TicketCard,
|
|
9
|
+
TicketStage,
|
|
10
|
+
} from '../shared/board-state'
|
|
11
|
+
import { RUN_KEY } from '../shared/reply-actions'
|
|
12
|
+
|
|
13
|
+
/** A ticket's blockers inside this run that aren't done yet. */
|
|
14
|
+
export const unfinishedBlockers = ({
|
|
15
|
+
state,
|
|
16
|
+
ticket,
|
|
17
|
+
}: {
|
|
18
|
+
state: BoardState
|
|
19
|
+
ticket: TicketCard
|
|
20
|
+
}): number[] =>
|
|
21
|
+
ticket.blockers.filter((number) => {
|
|
22
|
+
const blocker = state.tickets.find((card) => card.number === number)
|
|
23
|
+
return blocker !== undefined && blocker.stage !== 'done'
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
/** The stages of a ticket the engine is working on. */
|
|
27
|
+
const ACTIVE_STAGES = new Set<TicketStage>([
|
|
28
|
+
'building',
|
|
29
|
+
'reviewing',
|
|
30
|
+
'testing',
|
|
31
|
+
'fixing',
|
|
32
|
+
'joining',
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
const isActive = ({ state }: { state: BoardState }): boolean =>
|
|
36
|
+
state.tickets.some(
|
|
37
|
+
(ticket) => ticket.started && ACTIVE_STAGES.has(ticket.stage)
|
|
38
|
+
) ||
|
|
39
|
+
state.final_review.state === 'reviewing' ||
|
|
40
|
+
state.final_review.state === 'fixing'
|
|
41
|
+
|
|
42
|
+
const runStatus = ({ state }: { state: BoardState }): RunStatus => {
|
|
43
|
+
const { phase, engine_ended, stopped } = state.run
|
|
44
|
+
if (phase === 'done' || phase === 'refused' || phase === 'nothing_to_do')
|
|
45
|
+
return phase
|
|
46
|
+
if (stopped) return 'stopped'
|
|
47
|
+
if (engine_ended && !engine_ended.ok) return 'ended_with_error'
|
|
48
|
+
if (state.limit_wait) return 'limit_wait'
|
|
49
|
+
if (state.usage_line_wait) return 'usage_line_wait'
|
|
50
|
+
// Nothing new starts while the run is stuck on its budget.
|
|
51
|
+
if (state.needs_you.some((item) => item.key === RUN_KEY)) return 'stuck'
|
|
52
|
+
if (state.needs_you.length > 0 && !isActive({ state })) return 'stuck'
|
|
53
|
+
return phase
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Recomputes what depends on the whole board: which unstarted tickets are
|
|
58
|
+
* blocked, whether the final review is active, and the run's status.
|
|
59
|
+
*/
|
|
60
|
+
export const settle = ({ state }: { state: BoardState }): BoardState => {
|
|
61
|
+
const tickets = state.tickets.map((ticket): TicketCard => {
|
|
62
|
+
if (ticket.started) return ticket
|
|
63
|
+
if (ticket.stage !== 'blocked' && ticket.stage !== 'building')
|
|
64
|
+
return ticket
|
|
65
|
+
const waiting = unfinishedBlockers({ state, ticket })
|
|
66
|
+
return waiting.length > 0
|
|
67
|
+
? {
|
|
68
|
+
...ticket,
|
|
69
|
+
stage: 'blocked',
|
|
70
|
+
activity: `waits on ${waiting.map((number) => `#${number}`).join(', ')}`,
|
|
71
|
+
}
|
|
72
|
+
: { ...ticket, stage: 'building', activity: 'queued' }
|
|
73
|
+
})
|
|
74
|
+
const settledTickets = { ...state, tickets }
|
|
75
|
+
const active =
|
|
76
|
+
tickets.length > 0 &&
|
|
77
|
+
tickets.every(
|
|
78
|
+
(ticket) => ticket.stage === 'done' || ticket.stage === 'skipped'
|
|
79
|
+
)
|
|
80
|
+
const withReview = {
|
|
81
|
+
...settledTickets,
|
|
82
|
+
final_review: { ...state.final_review, active },
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
...withReview,
|
|
86
|
+
run: { ...withReview.run, status: runStatus({ state: withReview }) },
|
|
87
|
+
}
|
|
88
|
+
}
|