@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,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The decision model a real run asks (#534): what its setup holds
|
|
3
|
+
* (`DecisionModelSetup`), and opening it for a run (`openDecisionModel`), off
|
|
4
|
+
* with a journaled reason when there are no credentials.
|
|
5
|
+
*/
|
|
6
|
+
import { decisionModelOf } from '../config/engine-config'
|
|
7
|
+
import { createClefClient, type DecisionModelFetch } from '../jev/clef-client'
|
|
8
|
+
import {
|
|
9
|
+
noCredentialsText,
|
|
10
|
+
type DecisionModelCredentials,
|
|
11
|
+
} from '../jev/decision-model-credentials'
|
|
12
|
+
import { DEFAULT_DECISION_MODEL } from '../jev/jev-schemas'
|
|
13
|
+
import type { JevShadow } from '../jev/jev-shadow'
|
|
14
|
+
import type { Journal } from '../journal/journal'
|
|
15
|
+
import { replayRun } from '../journal/replay'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* What a real run needs to ask the decision model (#534): its Cloudflare
|
|
19
|
+
* credentials (`loadDecisionModelCredentials`), and for tests a fake
|
|
20
|
+
* `fetch` and a shorter timeout. The model comes from the run's config.
|
|
21
|
+
*/
|
|
22
|
+
export type DecisionModelSetup = {
|
|
23
|
+
credentials: DecisionModelCredentials
|
|
24
|
+
/** Defaults to the global `fetch`. */
|
|
25
|
+
fetch?: DecisionModelFetch
|
|
26
|
+
/** Defaults to `DEFAULT_JEV_TIMEOUT_MS`. */
|
|
27
|
+
timeout_ms?: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The decision model for this run, from `setup` and the model in the run's
|
|
32
|
+
* own config (its `run_started`, so a resume keeps it). With no credentials
|
|
33
|
+
* it is off for the run: one `decision_model_off { reason: 'no_credentials' }`
|
|
34
|
+
* record and one line naming Luca's env file and the keys to add, and no
|
|
35
|
+
* shadow, so nothing is ever asked. Never logs the token.
|
|
36
|
+
*/
|
|
37
|
+
export const openDecisionModel = ({
|
|
38
|
+
setup,
|
|
39
|
+
journal,
|
|
40
|
+
log,
|
|
41
|
+
}: {
|
|
42
|
+
setup: DecisionModelSetup | undefined
|
|
43
|
+
journal: Journal
|
|
44
|
+
log: (line: string) => void
|
|
45
|
+
}): JevShadow | undefined => {
|
|
46
|
+
if (setup === undefined) return undefined
|
|
47
|
+
const { config } = replayRun({ records: journal.read() })
|
|
48
|
+
const model =
|
|
49
|
+
config === null ? DEFAULT_DECISION_MODEL : decisionModelOf({ config })
|
|
50
|
+
const { credentials } = setup
|
|
51
|
+
if (!credentials.ok) {
|
|
52
|
+
const detail = noCredentialsText(credentials)
|
|
53
|
+
journal.append({
|
|
54
|
+
kind: 'decision_model_off',
|
|
55
|
+
ticket: null,
|
|
56
|
+
role: null,
|
|
57
|
+
content: { model, reason: 'no_credentials', detail },
|
|
58
|
+
})
|
|
59
|
+
log(`[luca-run] decision model off: ${detail}`)
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
log(`[luca-run] decision model: Clef (${model}), in shadow mode`)
|
|
63
|
+
return {
|
|
64
|
+
client: createClefClient({ credentials, model, fetch: setup.fetch }),
|
|
65
|
+
timeout_ms: setup.timeout_ms,
|
|
66
|
+
log: (line) => {
|
|
67
|
+
log(`[luca-run] ${line}`)
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
package/engine/cli/doctor.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
reason,
|
|
15
15
|
type DoctorCheck,
|
|
16
16
|
} from './doctor-checks'
|
|
17
|
+
import { installLucaSkills } from './luca-skills'
|
|
17
18
|
import { ensureMuninnEntry, hideToken } from './muninn-entry'
|
|
18
19
|
import { repoChecks, runSetup, type SetupGitHub } from './setup'
|
|
19
20
|
import {
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
} from './v13-leftovers'
|
|
25
26
|
|
|
26
27
|
import type { V13Manifest } from '../doctor/v13-manifest'
|
|
28
|
+
import type { Env } from '../jev/decision-model-credentials'
|
|
27
29
|
import type { MemoryClient } from '../memory/memory-client'
|
|
28
30
|
import { runCommand } from '../shell/run-command'
|
|
29
31
|
|
|
@@ -31,13 +33,14 @@ import { runCommand } from '../shell/run-command'
|
|
|
31
33
|
* `luca doctor [--fix]`: checks this computer, the repo when run inside
|
|
32
34
|
* one, and what old Luca v13 left behind, and prints each check as OK, or
|
|
33
35
|
* the problem and its exact fix. It exits 1 when any check is a problem;
|
|
34
|
-
* warnings (memory off, planning-skill drift)
|
|
36
|
+
* warnings (memory off, the decision model off, planning-skill drift)
|
|
37
|
+
* don't fail it. When it finds
|
|
35
38
|
* v13 leftovers, it links the migration guide.
|
|
36
39
|
*
|
|
37
40
|
* `--fix` fixes what's safe, without asking: it cleans up v13's leftovers
|
|
38
41
|
* first (see `fixV13`), starts MuninnDB and repairs Claude Code's `muninn`
|
|
39
42
|
* entry (as `luca init` does), reloads the board and rewrites its paths,
|
|
40
|
-
* and runs `luca setup` in a repo. It never deletes and never commits: it
|
|
43
|
+
* installs Luca's own skills again, and runs `luca setup` in a repo. It never deletes and never commits: it
|
|
41
44
|
* lists the repo files to commit. Installs, sign-ins, Paseo's plugin
|
|
42
45
|
* consent, and other `luca` copies are only reported.
|
|
43
46
|
*/
|
|
@@ -126,8 +129,10 @@ const tryFix = async ({
|
|
|
126
129
|
|
|
127
130
|
/** Fixes what's safe on this computer, guided by the checks. */
|
|
128
131
|
const fixComputer = async ({
|
|
132
|
+
home,
|
|
129
133
|
checks,
|
|
130
134
|
board_dir,
|
|
135
|
+
skills_dir,
|
|
131
136
|
engine_path,
|
|
132
137
|
bun_path,
|
|
133
138
|
muninn,
|
|
@@ -136,8 +141,10 @@ const fixComputer = async ({
|
|
|
136
141
|
paseo,
|
|
137
142
|
log,
|
|
138
143
|
}: {
|
|
144
|
+
home: string
|
|
139
145
|
checks: DoctorCheck[]
|
|
140
146
|
board_dir: string
|
|
147
|
+
skills_dir: string
|
|
141
148
|
engine_path: string
|
|
142
149
|
bun_path: string
|
|
143
150
|
muninn: MuninnCli
|
|
@@ -190,6 +197,9 @@ const fixComputer = async ({
|
|
|
190
197
|
log,
|
|
191
198
|
})
|
|
192
199
|
}
|
|
200
|
+
if (statusOf({ checks, name: 'luca_skills' }) === 'problem') {
|
|
201
|
+
await installLucaSkills({ home, skills_dir, prefix: PREFIX, log })
|
|
202
|
+
}
|
|
193
203
|
}
|
|
194
204
|
|
|
195
205
|
/** Runs `luca setup` in the repo. */
|
|
@@ -243,7 +253,7 @@ const listToCommit = async ({
|
|
|
243
253
|
* then checks again. Never throws, and never shows the token.
|
|
244
254
|
*
|
|
245
255
|
* @example
|
|
246
|
-
* const end = await runDoctor({ home: homedir(), fix: false, luca_version, board_dir, engine_path, bun_path, computer, muninn, muninn_health, claude, paseo, repo: null, tmp_dir: '/tmp', log: console.log })
|
|
256
|
+
* const end = await runDoctor({ home: homedir(), fix: false, luca_version, board_dir, skills_dir, engine_path, bun_path, computer, muninn, muninn_health, claude, paseo, repo: null, tmp_dir: '/tmp', log: console.log })
|
|
247
257
|
* process.exit(end.exit_code)
|
|
248
258
|
*/
|
|
249
259
|
export const runDoctor = async ({
|
|
@@ -251,6 +261,7 @@ export const runDoctor = async ({
|
|
|
251
261
|
fix,
|
|
252
262
|
luca_version,
|
|
253
263
|
board_dir,
|
|
264
|
+
skills_dir,
|
|
254
265
|
engine_path,
|
|
255
266
|
bun_path,
|
|
256
267
|
computer,
|
|
@@ -261,6 +272,7 @@ export const runDoctor = async ({
|
|
|
261
272
|
repo,
|
|
262
273
|
tmp_dir,
|
|
263
274
|
v13_manifest,
|
|
275
|
+
env,
|
|
264
276
|
log,
|
|
265
277
|
}: LucaInstall & {
|
|
266
278
|
home: string
|
|
@@ -276,6 +288,8 @@ export const runDoctor = async ({
|
|
|
276
288
|
tmp_dir: string
|
|
277
289
|
/** The v13 fingerprint list; defaults to the committed one. */
|
|
278
290
|
v13_manifest?: V13Manifest
|
|
291
|
+
/** The process env, for the decision model's check. Left out, none. */
|
|
292
|
+
env?: Env
|
|
279
293
|
log: (line: string) => void
|
|
280
294
|
}): Promise<DoctorEnd> => {
|
|
281
295
|
const check = async (): Promise<DoctorCheck[]> => [
|
|
@@ -283,6 +297,7 @@ export const runDoctor = async ({
|
|
|
283
297
|
home,
|
|
284
298
|
luca_version,
|
|
285
299
|
board_dir,
|
|
300
|
+
skills_dir,
|
|
286
301
|
engine_path,
|
|
287
302
|
bun_path,
|
|
288
303
|
computer,
|
|
@@ -290,6 +305,7 @@ export const runDoctor = async ({
|
|
|
290
305
|
muninn_health,
|
|
291
306
|
claude,
|
|
292
307
|
paseo,
|
|
308
|
+
env,
|
|
293
309
|
})),
|
|
294
310
|
...(repo === null
|
|
295
311
|
? []
|
|
@@ -331,8 +347,10 @@ export const runDoctor = async ({
|
|
|
331
347
|
})
|
|
332
348
|
}
|
|
333
349
|
await fixComputer({
|
|
350
|
+
home,
|
|
334
351
|
checks,
|
|
335
352
|
board_dir,
|
|
353
|
+
skills_dir,
|
|
336
354
|
engine_path,
|
|
337
355
|
bun_path,
|
|
338
356
|
muninn,
|
package/engine/cli/going-runs.ts
CHANGED
|
@@ -3,7 +3,12 @@ import { join } from 'node:path'
|
|
|
3
3
|
|
|
4
4
|
import { z } from 'zod'
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
BOARD_MAX_AUTO_RESTARTS,
|
|
8
|
+
liveRunIds,
|
|
9
|
+
type ListProcesses,
|
|
10
|
+
} from './live-runs'
|
|
11
|
+
import { readRunStart, restartableRuns, type RestartableRun } from './run-modes'
|
|
7
12
|
|
|
8
13
|
import { boardStateDir } from '../board/board-state-dir'
|
|
9
14
|
import { decide } from '../core/decide'
|
|
@@ -12,7 +17,9 @@ import { createJournal, runJournalPath } from '../journal/journal'
|
|
|
12
17
|
|
|
13
18
|
/**
|
|
14
19
|
* The runs that are going, for the commands that must not change Luca under
|
|
15
|
-
* them (`luca upgrade`)
|
|
20
|
+
* them (`luca upgrade`) and for a new run's repo wait (#615), and the
|
|
21
|
+
* unfinished runs whose engine is gone, which can go on later on any
|
|
22
|
+
* version.
|
|
16
23
|
*/
|
|
17
24
|
|
|
18
25
|
/**
|
|
@@ -27,13 +34,37 @@ export const defaultRegistryPath = ({
|
|
|
27
34
|
home_dir: string
|
|
28
35
|
}): string => join(boardStateDir({ env, home_dir }), 'runs.json')
|
|
29
36
|
|
|
30
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Why a run counts as going, or `engine_gone` for an unfinished run nothing
|
|
39
|
+
* runs now:
|
|
40
|
+
*
|
|
41
|
+
* - `engine_running`: `ps` shows its engine.
|
|
42
|
+
* - `board_will_restart`: its engine is gone, but the board plugin will
|
|
43
|
+
* restart it (its registry entry hasn't ended and has restarts left).
|
|
44
|
+
* - `not_checked`: `ps` failed, so it counts as going to be safe.
|
|
45
|
+
* - `engine_gone`: not running, and the board won't restart it.
|
|
46
|
+
*/
|
|
47
|
+
export type RunState =
|
|
48
|
+
| 'engine_running'
|
|
49
|
+
| 'board_will_restart'
|
|
50
|
+
| 'not_checked'
|
|
51
|
+
| 'engine_gone'
|
|
52
|
+
|
|
53
|
+
/** An unfinished run, by spec and repo. */
|
|
31
54
|
export type GoingRun = {
|
|
32
55
|
run_id: string
|
|
33
56
|
/** `null` for a demo run. */
|
|
34
57
|
spec: number | null
|
|
35
58
|
/** `null` in journals from before the repo was kept. */
|
|
36
59
|
repo: string | null
|
|
60
|
+
/** Whether the board plugin started it (it is in the board's registry). */
|
|
61
|
+
board: boolean
|
|
62
|
+
state: RunState
|
|
63
|
+
/**
|
|
64
|
+
* When its `run_started` was journaled; `null` for a registry entry
|
|
65
|
+
* with no journal yet.
|
|
66
|
+
*/
|
|
67
|
+
started_at: string | null
|
|
37
68
|
}
|
|
38
69
|
|
|
39
70
|
const RegistrySchema = z.object({
|
|
@@ -42,11 +73,15 @@ const RegistrySchema = z.object({
|
|
|
42
73
|
run_id: z.string(),
|
|
43
74
|
repo: z.string(),
|
|
44
75
|
spec: z.number().nullable(),
|
|
76
|
+
demo: z.boolean().default(false),
|
|
45
77
|
ended: z.unknown().nullable().default(null),
|
|
78
|
+
restarts: z.number().default(0),
|
|
46
79
|
})
|
|
47
80
|
),
|
|
48
81
|
})
|
|
49
82
|
|
|
83
|
+
type RegistryEntry = z.infer<typeof RegistrySchema>['runs'][number]
|
|
84
|
+
|
|
50
85
|
/** Whether run `run_id` has a journal in `runs_dir` whose run is over. */
|
|
51
86
|
const journalIsOver = ({
|
|
52
87
|
runs_dir,
|
|
@@ -65,35 +100,16 @@ const journalIsOver = ({
|
|
|
65
100
|
}
|
|
66
101
|
}
|
|
67
102
|
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
* over (limit waits and stuck runs waiting for a reply included), and every
|
|
71
|
-
* run in the board registry at `registry_path` that has not ended and whose
|
|
72
|
-
* journal, if any, is not over. An error when the registry can't be read.
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* const going = await goingRuns({ runs_dir: defaultRunsDir(), registry_path })
|
|
76
|
-
* if (going.ok) console.log(going.runs.map(({ spec }) => spec))
|
|
77
|
-
*/
|
|
78
|
-
export const goingRuns = async ({
|
|
79
|
-
runs_dir,
|
|
103
|
+
/** The board registry's runs; none when it has no file yet. */
|
|
104
|
+
const readRegistry = async ({
|
|
80
105
|
registry_path,
|
|
81
106
|
}: {
|
|
82
|
-
runs_dir: string
|
|
83
107
|
registry_path: string
|
|
84
|
-
}): Promise<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return start.ok
|
|
88
|
-
? {
|
|
89
|
-
run_id,
|
|
90
|
-
spec: start.start.spec_number,
|
|
91
|
-
repo: start.start.repo,
|
|
92
|
-
}
|
|
93
|
-
: { run_id, spec: null, repo: null }
|
|
94
|
-
})
|
|
108
|
+
}): Promise<
|
|
109
|
+
{ ok: true; entries: RegistryEntry[] } | { ok: false; error: string }
|
|
110
|
+
> => {
|
|
95
111
|
const file = Bun.file(registry_path)
|
|
96
|
-
if (!(await file.exists())) return { ok: true,
|
|
112
|
+
if (!(await file.exists())) return { ok: true, entries: [] }
|
|
97
113
|
let parsed
|
|
98
114
|
try {
|
|
99
115
|
parsed = RegistrySchema.safeParse(JSON.parse(await file.text()))
|
|
@@ -109,22 +125,162 @@ export const goingRuns = async ({
|
|
|
109
125
|
error: `The board's run registry ${registry_path} is not valid: ${z.prettifyError(parsed.error)}`,
|
|
110
126
|
}
|
|
111
127
|
}
|
|
112
|
-
|
|
113
|
-
|
|
128
|
+
return { ok: true, entries: parsed.data.runs }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** An unfinished run before its engine is checked. */
|
|
132
|
+
type Candidate = {
|
|
133
|
+
run_id: string
|
|
134
|
+
spec: number | null
|
|
135
|
+
repo: string | null
|
|
136
|
+
started_at: string | null
|
|
137
|
+
/** What its journal says (`null` with no journal to go on from). */
|
|
138
|
+
reason: RestartableRun['reason'] | null
|
|
139
|
+
entry: RegistryEntry | null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Whether the board plugin will restart a run whose engine is gone: the
|
|
144
|
+
* plugin started it, its entry hasn't ended, it isn't a demo, it has
|
|
145
|
+
* restarts left, and its journal can go on. Pure.
|
|
146
|
+
*/
|
|
147
|
+
const boardWillRestart = ({ candidate }: { candidate: Candidate }) =>
|
|
148
|
+
candidate.entry !== null &&
|
|
149
|
+
candidate.entry.ended === null &&
|
|
150
|
+
!candidate.entry.demo &&
|
|
151
|
+
candidate.entry.restarts < BOARD_MAX_AUTO_RESTARTS &&
|
|
152
|
+
candidate.reason === 'resumable'
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The unfinished runs: every journal in `runs_dir` whose run is not over
|
|
156
|
+
* (limit waits and stuck runs waiting for a reply included), and every run
|
|
157
|
+
* in the board registry at `registry_path` that has not ended and whose
|
|
158
|
+
* journal, if any, is not over. Each is then checked with `ps` (see
|
|
159
|
+
* `liveRunIds`):
|
|
160
|
+
*
|
|
161
|
+
* - `runs`: the going ones. A run is going when its engine is running, or
|
|
162
|
+
* when its engine is gone but the board will restart it. When `ps` fails,
|
|
163
|
+
* every unfinished run is going, and `ps_error` says why.
|
|
164
|
+
* - `stopped`: the rest that can go on from their journal (a crash or a
|
|
165
|
+
* launcher stop). Nothing runs them now, so they can wait for a resume on
|
|
166
|
+
* any version. A billing stop never goes on, so it is in neither list.
|
|
167
|
+
*
|
|
168
|
+
* An error when the registry can't be read.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* const going = await goingRuns({ runs_dir: defaultRunsDir(), registry_path, list_processes: listProcesses })
|
|
172
|
+
* if (going.ok) console.log(going.runs.map(({ spec }) => spec))
|
|
173
|
+
*/
|
|
174
|
+
export const goingRuns = async ({
|
|
175
|
+
runs_dir,
|
|
176
|
+
registry_path,
|
|
177
|
+
list_processes,
|
|
178
|
+
}: {
|
|
179
|
+
runs_dir: string
|
|
180
|
+
registry_path: string
|
|
181
|
+
list_processes: ListProcesses
|
|
182
|
+
}): Promise<
|
|
183
|
+
| {
|
|
184
|
+
ok: true
|
|
185
|
+
runs: GoingRun[]
|
|
186
|
+
stopped: GoingRun[]
|
|
187
|
+
/** Why `ps` failed, or `null` when it worked. */
|
|
188
|
+
ps_error: string | null
|
|
189
|
+
}
|
|
190
|
+
| { ok: false; error: string }
|
|
191
|
+
> => {
|
|
192
|
+
const registry = await readRegistry({ registry_path })
|
|
193
|
+
if (!registry.ok) return registry
|
|
194
|
+
const entries = new Map(
|
|
195
|
+
registry.entries.map((entry) => [entry.run_id, entry])
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
const candidates: Candidate[] = restartableRuns({ runs_dir }).map(
|
|
199
|
+
({ run_id, reason }) => {
|
|
200
|
+
const start = readRunStart({ runs_dir, run_id })
|
|
201
|
+
const entry = entries.get(run_id) ?? null
|
|
202
|
+
return {
|
|
203
|
+
run_id,
|
|
204
|
+
spec: start.ok
|
|
205
|
+
? start.start.spec_number
|
|
206
|
+
: (entry?.spec ?? null),
|
|
207
|
+
repo: start.ok ? start.start.repo : (entry?.repo ?? null),
|
|
208
|
+
started_at: start.ok ? start.start.started_at : null,
|
|
209
|
+
reason,
|
|
210
|
+
entry,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
const known = new Set(candidates.map(({ run_id }) => run_id))
|
|
215
|
+
for (const entry of registry.entries) {
|
|
114
216
|
if (entry.ended !== null || known.has(entry.run_id)) continue
|
|
115
217
|
if (journalIsOver({ runs_dir, run_id: entry.run_id })) continue
|
|
116
218
|
known.add(entry.run_id)
|
|
117
|
-
|
|
219
|
+
candidates.push({
|
|
220
|
+
run_id: entry.run_id,
|
|
221
|
+
spec: entry.spec,
|
|
222
|
+
repo: entry.repo,
|
|
223
|
+
started_at: null,
|
|
224
|
+
reason: null,
|
|
225
|
+
entry,
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const runOf = ({
|
|
230
|
+
candidate,
|
|
231
|
+
state,
|
|
232
|
+
}: {
|
|
233
|
+
candidate: Candidate
|
|
234
|
+
state: RunState
|
|
235
|
+
}): GoingRun => ({
|
|
236
|
+
run_id: candidate.run_id,
|
|
237
|
+
spec: candidate.spec,
|
|
238
|
+
repo: candidate.repo,
|
|
239
|
+
board: candidate.entry !== null,
|
|
240
|
+
state,
|
|
241
|
+
started_at: candidate.started_at,
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
let live: Set<string>
|
|
245
|
+
try {
|
|
246
|
+
live = liveRunIds({ command_lines: await list_processes() })
|
|
247
|
+
} catch (error) {
|
|
248
|
+
// Never guess: an upgrade under a live engine is far worse than a
|
|
249
|
+
// refused one.
|
|
250
|
+
return {
|
|
251
|
+
ok: true,
|
|
252
|
+
runs: candidates.map((candidate) =>
|
|
253
|
+
runOf({ candidate, state: 'not_checked' })
|
|
254
|
+
),
|
|
255
|
+
stopped: [],
|
|
256
|
+
ps_error: error instanceof Error ? error.message : String(error),
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const runs: GoingRun[] = []
|
|
261
|
+
const stopped: GoingRun[] = []
|
|
262
|
+
for (const candidate of candidates) {
|
|
263
|
+
if (live.has(candidate.run_id)) {
|
|
264
|
+
runs.push(runOf({ candidate, state: 'engine_running' }))
|
|
265
|
+
} else if (boardWillRestart({ candidate })) {
|
|
266
|
+
runs.push(runOf({ candidate, state: 'board_will_restart' }))
|
|
267
|
+
} else if (
|
|
268
|
+
candidate.reason === 'resumable' ||
|
|
269
|
+
candidate.reason === 'launcher_stopped'
|
|
270
|
+
) {
|
|
271
|
+
stopped.push(runOf({ candidate, state: 'engine_gone' }))
|
|
272
|
+
}
|
|
118
273
|
}
|
|
119
|
-
return { ok: true, runs }
|
|
274
|
+
return { ok: true, runs, stopped, ps_error: null }
|
|
120
275
|
}
|
|
121
276
|
|
|
122
277
|
/**
|
|
123
|
-
* One
|
|
278
|
+
* One unfinished run as a list line, with why it is going when its engine
|
|
279
|
+
* isn't running. Pure.
|
|
124
280
|
*
|
|
125
281
|
* @example
|
|
126
|
-
* describeRun({ run_id: 'r1', spec: 5, repo: '/code/app' })
|
|
282
|
+
* describeRun({ run_id: 'r1', spec: 5, repo: '/code/app', board: false, state: 'engine_running', started_at: null })
|
|
127
283
|
* // '- spec #5 in /code/app (run r1)'
|
|
128
284
|
*/
|
|
129
|
-
export const describeRun = ({ run_id, spec, repo }: GoingRun): string =>
|
|
130
|
-
`- ${spec === null ? 'a demo' : `spec #${spec}`} in ${repo ?? 'an unknown repo'} (run ${run_id})`
|
|
285
|
+
export const describeRun = ({ run_id, spec, repo, state }: GoingRun): string =>
|
|
286
|
+
`- ${spec === null ? 'a demo' : `spec #${spec}`} in ${repo ?? 'an unknown repo'} (run ${run_id})${state === 'board_will_restart' ? ': its engine is gone, and the board will restart it' : ''}`
|
package/engine/cli/init.ts
CHANGED
|
@@ -19,9 +19,12 @@ import {
|
|
|
19
19
|
reason,
|
|
20
20
|
type DoctorCheck,
|
|
21
21
|
} from './doctor-checks'
|
|
22
|
+
import { installLucaSkills } from './luca-skills'
|
|
22
23
|
import { ensureMuninnEntry, hideToken } from './muninn-entry'
|
|
23
24
|
import { runSetup } from './setup'
|
|
24
25
|
|
|
26
|
+
import type { Env } from '../jev/decision-model-credentials'
|
|
27
|
+
|
|
25
28
|
/**
|
|
26
29
|
* `luca init`: sets up this computer for Luca, once, and is safe to run
|
|
27
30
|
* again. It has three parts, each run even when an earlier one failed, and
|
|
@@ -50,6 +53,11 @@ import { runSetup } from './setup'
|
|
|
50
53
|
* `mattpocock/skills` with the `skills` tool, skipping any already there.
|
|
51
54
|
* `skip_skills` skips this part.
|
|
52
55
|
*
|
|
56
|
+
* Luca's own skills: copies them (`/luca-unstick`, `/luca-retro`) from Luca's
|
|
57
|
+
* install folder into `~/.claude/skills`, over Luca's earlier copy (see
|
|
58
|
+
* `installLucaSkills`). They are part of Luca, so `skip_skills` doesn't
|
|
59
|
+
* skip them.
|
|
60
|
+
*
|
|
53
61
|
* The repo: inside a git repo, after the computer's checks, it runs `luca
|
|
54
62
|
* setup` for that repo (see `runSetup`), with the output setup gives on its
|
|
55
63
|
* own. Outside one, it stops after the computer part.
|
|
@@ -264,7 +272,8 @@ const setUpMemory = async ({
|
|
|
264
272
|
|
|
265
273
|
/**
|
|
266
274
|
* Runs `luca init` against `home` with these adapters: memory, then the
|
|
267
|
-
* board, then the planning skills, then
|
|
275
|
+
* board, then the planning skills, then Luca's own skills, then doctor's
|
|
276
|
+
* computer checks, then
|
|
268
277
|
* `luca setup` in `repo` when there is one. Never throws: a failure is
|
|
269
278
|
* logged (token hidden) and ends with `ok: false`, as does a check that is
|
|
270
279
|
* a problem or a setup to-do.
|
|
@@ -287,8 +296,10 @@ export const runInit = async ({
|
|
|
287
296
|
computer,
|
|
288
297
|
luca_version,
|
|
289
298
|
board_dir,
|
|
299
|
+
skills_dir,
|
|
290
300
|
engine_path,
|
|
291
301
|
bun_path,
|
|
302
|
+
env,
|
|
292
303
|
log,
|
|
293
304
|
}: LucaInstall & {
|
|
294
305
|
home: string
|
|
@@ -304,6 +315,8 @@ export const runInit = async ({
|
|
|
304
315
|
skills: SkillsTool
|
|
305
316
|
ask: Ask
|
|
306
317
|
computer: Computer
|
|
318
|
+
/** The process env, for the decision model's check. Left out, none. */
|
|
319
|
+
env?: Env
|
|
307
320
|
log: (line: string) => void
|
|
308
321
|
}): Promise<InitEnd> => {
|
|
309
322
|
const memory = await setUpMemory({
|
|
@@ -330,10 +343,17 @@ export const runInit = async ({
|
|
|
330
343
|
} else {
|
|
331
344
|
planning = await setUpSkills({ skills, log })
|
|
332
345
|
}
|
|
346
|
+
const own_skills = await installLucaSkills({
|
|
347
|
+
home,
|
|
348
|
+
skills_dir,
|
|
349
|
+
prefix: '[luca init]',
|
|
350
|
+
log,
|
|
351
|
+
})
|
|
333
352
|
const doctor = await computerChecks({
|
|
334
353
|
home,
|
|
335
354
|
luca_version,
|
|
336
355
|
board_dir,
|
|
356
|
+
skills_dir,
|
|
337
357
|
engine_path,
|
|
338
358
|
bun_path,
|
|
339
359
|
computer,
|
|
@@ -341,6 +361,7 @@ export const runInit = async ({
|
|
|
341
361
|
muninn_health,
|
|
342
362
|
claude,
|
|
343
363
|
paseo,
|
|
364
|
+
env,
|
|
344
365
|
})
|
|
345
366
|
for (const line of formatChecks({ checks: doctor })) log(line)
|
|
346
367
|
const setup =
|
|
@@ -358,6 +379,7 @@ export const runInit = async ({
|
|
|
358
379
|
memory.ok &&
|
|
359
380
|
board.ok &&
|
|
360
381
|
planning.ok &&
|
|
382
|
+
own_skills.ok &&
|
|
361
383
|
!hasProblem({ checks: doctor }) &&
|
|
362
384
|
(setup?.ok ?? true),
|
|
363
385
|
memory: memory.memory,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which runs have a live engine process, from `ps`. The board plugin checks
|
|
3
|
+
* the same way (`liveRunIds` and `listProcesses` in
|
|
4
|
+
* `packages/board/server/`). The plugin never imports the engine and the
|
|
5
|
+
* engine never imports the plugin, so this is a copy: change both together.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Every running process's full command line. Rejects when it can't list them. */
|
|
9
|
+
export type ListProcesses = () => Promise<string[]>
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How many times the board plugin restarts one run by itself before it gives
|
|
13
|
+
* up. A copy of the board's `MAX_AUTO_RESTARTS`: change both together.
|
|
14
|
+
*/
|
|
15
|
+
export const BOARD_MAX_AUTO_RESTARTS = 3
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The run ids that have a live engine, from every process's command line: a
|
|
19
|
+
* run is live when a command line has `--run-id <id>` or `--resume <id>` (or
|
|
20
|
+
* `--run-id=<id>`), as whole words, so one id never matches inside another.
|
|
21
|
+
* Pure.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* liveRunIds({ command_lines: ['bun luca-run.ts --resume r1 --repo /x'] }) // Set { 'r1' }
|
|
25
|
+
*/
|
|
26
|
+
export const liveRunIds = ({
|
|
27
|
+
command_lines,
|
|
28
|
+
}: {
|
|
29
|
+
command_lines: string[]
|
|
30
|
+
}): Set<string> => {
|
|
31
|
+
const live = new Set<string>()
|
|
32
|
+
for (const line of command_lines) {
|
|
33
|
+
const tokens = line.split(/\s+/)
|
|
34
|
+
tokens.forEach((token, index) => {
|
|
35
|
+
const next = tokens[index + 1]
|
|
36
|
+
if ((token === '--run-id' || token === '--resume') && next) {
|
|
37
|
+
live.add(next)
|
|
38
|
+
}
|
|
39
|
+
const joined = /^--(?:run-id|resume)=(.+)$/.exec(token)
|
|
40
|
+
if (joined?.[1]) live.add(joined[1])
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
return live
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Every running process's full command line, from `ps -axww -o command=`
|
|
48
|
+
* (`-ww` never cuts a line). Rejects when `ps` fails, so a caller can take
|
|
49
|
+
* the safe side rather than guess.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const live = liveRunIds({ command_lines: await listProcesses() })
|
|
53
|
+
*/
|
|
54
|
+
export const listProcesses: ListProcesses = async () => {
|
|
55
|
+
const child = Bun.spawn(['ps', '-axww', '-o', 'command='], {
|
|
56
|
+
stdout: 'pipe',
|
|
57
|
+
stderr: 'pipe',
|
|
58
|
+
})
|
|
59
|
+
const [stdout, stderr, exit_code] = await Promise.all([
|
|
60
|
+
new Response(child.stdout).text(),
|
|
61
|
+
new Response(child.stderr).text(),
|
|
62
|
+
child.exited,
|
|
63
|
+
])
|
|
64
|
+
if (exit_code !== 0) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`ps exited with ${exit_code}: ${stderr.trim().slice(0, 300)}`
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
return stdout
|
|
70
|
+
.split('\n')
|
|
71
|
+
.map((line) => line.trim())
|
|
72
|
+
.filter((line) => line !== '')
|
|
73
|
+
}
|