@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
|
@@ -1,71 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { runNotesSection, type PromptRunNote } from './prompt-sections'
|
|
2
|
+
import type { AgentRole, BadTest, BadTestSource } from './role-results'
|
|
2
3
|
|
|
3
4
|
import type { SpecSnapshot, TicketSnapshot } from '../intake/intake-schemas'
|
|
4
|
-
import type { RejoinCause } from '../journal/journal-record'
|
|
5
5
|
import { agentAddress, canMessage } from '../messages/agent-messages'
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
files.map((file) => `- ${file}`).join('\n')
|
|
23
|
-
|
|
24
|
-
/** One line on what happened to the ticket, for every rejoin section. */
|
|
25
|
-
export const rejoinOpening = ({ rejoin }: { rejoin: RejoinContext }): string =>
|
|
26
|
-
rejoin.cause === 'clash'
|
|
27
|
-
? `Other tickets joined the run branch after this ticket started, and this ticket's change clashed with them. ` +
|
|
28
|
-
`The engine put this ticket's whole change back on top of the run branch (commit ${rejoin.base_sha}), as uncommitted changes in the worktree.`
|
|
29
|
-
: `The gates failed on the run branch after this ticket joined it. ` +
|
|
30
|
-
`The engine undid the join and put this ticket's whole change back on top of the run branch (commit ${rejoin.base_sha}), as uncommitted changes in the worktree.`
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The section a fresh test-writer or implementer gets while it fixes a
|
|
34
|
-
* ticket on top of the run branch: the clashed test files, or the clashed
|
|
35
|
-
* code (or the failed gates). `null` for a role with nothing to fix there.
|
|
36
|
-
* The reviewer's re-review is the review's own (`reviewSections`).
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* const section = rejoinSection({ role: 'test-writer', rejoin })
|
|
40
|
-
*/
|
|
41
|
-
export const rejoinSection = ({
|
|
42
|
-
role,
|
|
43
|
-
rejoin,
|
|
44
|
-
}: {
|
|
45
|
-
role: AgentRole
|
|
46
|
-
rejoin: RejoinContext
|
|
47
|
-
}): string | null => {
|
|
48
|
-
if (role === 'test-writer') {
|
|
49
|
-
if (rejoin.tests.length === 0) return null
|
|
50
|
-
return [
|
|
51
|
-
'## This ticket clashed with the run branch',
|
|
52
|
-
rejoinOpening({ rejoin }),
|
|
53
|
-
`These test files have conflict markers:\n\n${fileList(rejoin.tests)}`,
|
|
54
|
-
"Resolve the markers so the tests check both what the run branch already has and this ticket's criteria. " +
|
|
55
|
-
'Change nothing else. Answer with the full criterion mapping again.',
|
|
56
|
-
].join('\n\n')
|
|
57
|
-
}
|
|
58
|
-
if (role === 'implementer') {
|
|
59
|
-
return [
|
|
60
|
-
'## This ticket is being fixed on top of the run branch',
|
|
61
|
-
rejoinOpening({ rejoin }),
|
|
62
|
-
rejoin.code.length > 0
|
|
63
|
-
? `These files have conflict markers:\n\n${fileList(rejoin.code)}\n\nResolve them so the code keeps both what the run branch has and what this ticket adds, and make every gate pass.`
|
|
64
|
-
: 'Make every gate pass on top of the run branch.',
|
|
65
|
-
].join('\n\n')
|
|
66
|
-
}
|
|
67
|
-
return null
|
|
68
|
-
}
|
|
7
|
+
export {
|
|
8
|
+
rejoinOpening,
|
|
9
|
+
rejoinSection,
|
|
10
|
+
testUpdateSection,
|
|
11
|
+
type JoinedSince,
|
|
12
|
+
type RejoinContext,
|
|
13
|
+
} from './rejoin-prompts'
|
|
14
|
+
export {
|
|
15
|
+
CHANGESETS_LENS_SECTION,
|
|
16
|
+
CHANGESETS_SECTION,
|
|
17
|
+
featureMapSection,
|
|
18
|
+
prepareSection,
|
|
19
|
+
runNotesSection,
|
|
20
|
+
type PromptRunNote,
|
|
21
|
+
} from './prompt-sections'
|
|
69
22
|
|
|
70
23
|
/** A refactor ticket's implementer's task, in place of the usual one. */
|
|
71
24
|
const REFACTOR_TASK =
|
|
@@ -73,11 +26,21 @@ const REFACTOR_TASK =
|
|
|
73
26
|
'Make every gate pass. You may follow renames and moves into test files, but you must not change what a test checks. ' +
|
|
74
27
|
'If a test is wrong, answer "bad_test" with your reason.'
|
|
75
28
|
|
|
76
|
-
/**
|
|
77
|
-
|
|
29
|
+
/**
|
|
30
|
+
* What a fresh test-writer is told when it replaces a bad test: the
|
|
31
|
+
* implementer sent it back, or the engine found it passes with no
|
|
32
|
+
* `expect()`, so it checks nothing (#581).
|
|
33
|
+
*/
|
|
34
|
+
const badTestSection = ({
|
|
35
|
+
bad_test,
|
|
36
|
+
by,
|
|
37
|
+
}: {
|
|
38
|
+
bad_test: BadTest
|
|
39
|
+
by: BadTestSource
|
|
40
|
+
}): string =>
|
|
78
41
|
[
|
|
79
42
|
'## A test was sent back as a bad test',
|
|
80
|
-
|
|
43
|
+
`${by === 'engine' ? 'The engine found this test checks nothing' : 'The implementer says this test is wrong'}:\n\n- File: ${bad_test.file}\n- Test: ${bad_test.name}\n- Reason: ${bad_test.reason}`,
|
|
81
44
|
'Fix or replace it so it checks what its criterion asks. Keep the other tests unless they are wrong too. ' +
|
|
82
45
|
'Answer with the full criterion mapping again: every criterion, the kept tests included.',
|
|
83
46
|
].join('\n\n')
|
|
@@ -88,52 +51,14 @@ const REFACTOR_REVIEW_TASK =
|
|
|
88
51
|
"No test's meaning changed, no test was weakened or dropped, and no new behavior was added. " +
|
|
89
52
|
'A behavior change is a blocker.'
|
|
90
53
|
|
|
91
|
-
/** A run note as a later agent's prompt lists it. */
|
|
92
|
-
export type PromptRunNote = { ticket: number; role: AgentRole; note: string }
|
|
93
|
-
|
|
94
|
-
/** The notes earlier agents left, oldest first; nothing when there are none. */
|
|
95
|
-
export const runNotesSection = ({
|
|
96
|
-
run_notes,
|
|
97
|
-
}: {
|
|
98
|
-
run_notes: PromptRunNote[]
|
|
99
|
-
}): string[] =>
|
|
100
|
-
run_notes.length === 0
|
|
101
|
-
? []
|
|
102
|
-
: [
|
|
103
|
-
'## Run notes from earlier agents in this run',
|
|
104
|
-
run_notes
|
|
105
|
-
.map(
|
|
106
|
-
({ ticket, role, note }) =>
|
|
107
|
-
`- ${note} (${role}, #${ticket})`
|
|
108
|
-
)
|
|
109
|
-
.join('\n'),
|
|
110
|
-
]
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* The section a test-writer or implementer gets when the repo has a prepare
|
|
114
|
-
* command: the engine runs it before its test runs, so tests that need its
|
|
115
|
-
* outputs may fail when the agent runs them itself.
|
|
116
|
-
*
|
|
117
|
-
* @example
|
|
118
|
-
* prepareSection({ command: 'bun run build:rom' })
|
|
119
|
-
* // '## This repo has a prepare command\n\n...'
|
|
120
|
-
*/
|
|
121
|
-
export const prepareSection = ({ command }: { command: string }): string =>
|
|
122
|
-
[
|
|
123
|
-
'## This repo has a prepare command',
|
|
124
|
-
`The engine runs \`${command}\` in the worktree before each of its test runs (the red check and the gates), so tests that need its outputs find them up to date. ` +
|
|
125
|
-
"Never run it yourself. Tests that need its outputs may fail when you run them, and still pass at the engine's checks. " +
|
|
126
|
-
"If the engine's prepare command fails after your turn, its output comes back to you like a failed check.",
|
|
127
|
-
].join('\n\n')
|
|
128
|
-
|
|
129
54
|
/** What each lens judges the whole run branch on, in one paragraph. */
|
|
130
55
|
const LENS_TASK_TEXT =
|
|
131
56
|
'You are one lens of the final review: a fresh, read-only reviewer of the WHOLE run branch, every ticket of the spec together. Write nothing.'
|
|
132
57
|
|
|
133
58
|
const ROLE_TASKS: Record<AgentRole, string> = {
|
|
134
59
|
'test-writer':
|
|
135
|
-
'Write failing tests for every acceptance criterion below. Edit test files only. ' +
|
|
136
|
-
'Map
|
|
60
|
+
'Write failing tests for every acceptance criterion below: the fewest that prove each one. Edit test files only. ' +
|
|
61
|
+
'Map every test you add to the criterion it checks.',
|
|
137
62
|
implementer:
|
|
138
63
|
'Make every gate pass. Never edit a test file. ' +
|
|
139
64
|
'If a test is wrong, answer "bad_test" with your reason. ' +
|
|
@@ -172,7 +97,10 @@ export const roleTask = ({ role }: { role: AgentRole }): string =>
|
|
|
172
97
|
* @param refactor - The ticket is a refactor ticket: the implementer gets
|
|
173
98
|
* the refactor task (no new behavior; it may follow renames into tests).
|
|
174
99
|
* @param bad_test - For a fresh test-writer after a bad-test bounce: the
|
|
175
|
-
* test the implementer sent back,
|
|
100
|
+
* test the implementer sent back, or the engine found checks nothing
|
|
101
|
+
* (#581), and why.
|
|
102
|
+
* @param bad_test_by - Who sent `bad_test` back: the implementer (the
|
|
103
|
+
* default) or the engine.
|
|
176
104
|
* @param sections - More sections for the end, such as a reviewer's diff
|
|
177
105
|
* and gate results, or a review fixer's findings.
|
|
178
106
|
* @param run_notes - Notes earlier agents in the run left, oldest first,
|
|
@@ -187,6 +115,7 @@ export const rolePrompt = ({
|
|
|
187
115
|
ticket,
|
|
188
116
|
refactor,
|
|
189
117
|
bad_test,
|
|
118
|
+
bad_test_by,
|
|
190
119
|
sections,
|
|
191
120
|
run_notes,
|
|
192
121
|
}: {
|
|
@@ -195,6 +124,7 @@ export const rolePrompt = ({
|
|
|
195
124
|
ticket: TicketSnapshot
|
|
196
125
|
refactor?: boolean
|
|
197
126
|
bad_test?: BadTest | null
|
|
127
|
+
bad_test_by?: BadTestSource
|
|
198
128
|
sections?: string[]
|
|
199
129
|
run_notes?: PromptRunNote[]
|
|
200
130
|
}): string =>
|
|
@@ -216,7 +146,7 @@ export const rolePrompt = ({
|
|
|
216
146
|
'## Acceptance criteria',
|
|
217
147
|
ticket.criteria.map(({ id, text }) => `- ${id}: ${text}`).join('\n'),
|
|
218
148
|
...(role === 'test-writer' && bad_test
|
|
219
|
-
? [badTestSection({ bad_test })]
|
|
149
|
+
? [badTestSection({ bad_test, by: bad_test_by ?? 'implementer' })]
|
|
220
150
|
: []),
|
|
221
151
|
...(sections ?? []),
|
|
222
152
|
// Recalled memories (#370) are added after these by the decision step.
|
|
@@ -82,12 +82,14 @@ export const CriterionTestsSchema = z.object({
|
|
|
82
82
|
export type CriterionTests = z.infer<typeof CriterionTestsSchema>
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* A review fixer's answer to one finding it was sent: `fixed`,
|
|
86
|
-
*
|
|
85
|
+
* A review fixer's answer to one finding it was sent: `fixed`, `wont_fix`
|
|
86
|
+
* with its reason (pushback the next fresh reviewer rules on), or
|
|
87
|
+
* `other_role` with its reason: the other writer role must fix it, or must
|
|
88
|
+
* fix it too (#616).
|
|
87
89
|
*/
|
|
88
90
|
export const FindingResponseSchema = z.object({
|
|
89
91
|
finding_id: z.string().min(1),
|
|
90
|
-
response: z.enum(['fixed', 'wont_fix']),
|
|
92
|
+
response: z.enum(['fixed', 'wont_fix', 'other_role']),
|
|
91
93
|
reason: z.string().default(''),
|
|
92
94
|
})
|
|
93
95
|
|
|
@@ -118,6 +120,43 @@ const SETUP_FIELDS = {
|
|
|
118
120
|
setup_change: SetupChangeSchema.nullable().default(null),
|
|
119
121
|
}
|
|
120
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Why a writer says a failed red check or failed gates are not its
|
|
125
|
+
* ticket's fault (#613): a port already in use, an old test that fails at
|
|
126
|
+
* baseline too. The engine checks the claim by rerunning the check.
|
|
127
|
+
*/
|
|
128
|
+
export const EnvironmentSchema = z.object({
|
|
129
|
+
evidence: z.string().trim().min(1),
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
export type Environment = z.infer<typeof EnvironmentSchema>
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* What a writer that blames the environment adds to its result, with the
|
|
136
|
+
* outcome `environment`.
|
|
137
|
+
*/
|
|
138
|
+
const ENVIRONMENT_FIELDS = {
|
|
139
|
+
environment: EnvironmentSchema.nullable().default(null),
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** A file mode a writer asks the engine to set (#618). */
|
|
143
|
+
export const FileModeSchema = z.object({
|
|
144
|
+
/** Worktree-relative path of the file. */
|
|
145
|
+
path: z.string().min(1),
|
|
146
|
+
executable: z.boolean(),
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
export type FileMode = z.infer<typeof FileModeSchema>
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The owner chores a writer asks the engine to do after its turn (#618):
|
|
153
|
+
* file modes to set, and the repo's generators to run, by name.
|
|
154
|
+
*/
|
|
155
|
+
const CHORE_FIELDS = {
|
|
156
|
+
file_modes: z.array(FileModeSchema).default([]),
|
|
157
|
+
run_generators: z.array(z.string().min(1)).default([]),
|
|
158
|
+
}
|
|
159
|
+
|
|
121
160
|
const SHARED_FIELDS = {
|
|
122
161
|
summary: z.string().default(''),
|
|
123
162
|
/** Calls the agent made by itself when something was unclear. */
|
|
@@ -143,7 +182,8 @@ export type DoneCommit = z.infer<typeof DoneCommitSchema>
|
|
|
143
182
|
* it and `criteria` the existing tests that cover each criterion; the engine
|
|
144
183
|
* counts the ticket as done. `needs_setup_change` says the tests can't be
|
|
145
184
|
* written without a change to a test setup file (`setup_change`), which
|
|
146
|
-
* only the user may make.
|
|
185
|
+
* only the user may make. `environment` says a failed red check is not the
|
|
186
|
+
* ticket's fault (#613), with its evidence; the engine reruns the check.
|
|
147
187
|
*/
|
|
148
188
|
export const TestWriterResultSchema = z.object({
|
|
149
189
|
outcome: z.enum([
|
|
@@ -151,6 +191,7 @@ export const TestWriterResultSchema = z.object({
|
|
|
151
191
|
'nothing_new_to_test',
|
|
152
192
|
'already_done',
|
|
153
193
|
'needs_setup_change',
|
|
194
|
+
'environment',
|
|
154
195
|
]),
|
|
155
196
|
/**
|
|
156
197
|
* For each criterion id (AC1, AC2, ...), the tests that check it: new
|
|
@@ -160,6 +201,8 @@ export const TestWriterResultSchema = z.object({
|
|
|
160
201
|
/** With `already_done`: the base branch's commits that did the work. */
|
|
161
202
|
done_by: z.array(DoneCommitSchema).default([]),
|
|
162
203
|
...SETUP_FIELDS,
|
|
204
|
+
...ENVIRONMENT_FIELDS,
|
|
205
|
+
...CHORE_FIELDS,
|
|
163
206
|
...SHARED_FIELDS,
|
|
164
207
|
...FIXER_FIELDS,
|
|
165
208
|
})
|
|
@@ -175,15 +218,25 @@ export const BadTestSchema = z.object({
|
|
|
175
218
|
|
|
176
219
|
export type BadTest = z.infer<typeof BadTestSchema>
|
|
177
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Who sent a test back as bad: the implementer, who says it is wrong, or the
|
|
223
|
+
* engine, which found a mapped test that passes with no `expect()` (#581).
|
|
224
|
+
*/
|
|
225
|
+
export type BadTestSource = 'implementer' | 'engine'
|
|
226
|
+
|
|
178
227
|
/**
|
|
179
228
|
* The implementer's result. `bad_test` sends a wrong test back;
|
|
180
229
|
* `needs_setup_change` says the gates can't pass without a change to a test
|
|
181
|
-
* setup file (`setup_change`), which only the user may make
|
|
230
|
+
* setup file (`setup_change`), which only the user may make;
|
|
231
|
+
* `environment` says failed gates are not the ticket's fault (#613), with
|
|
232
|
+
* its evidence, and the engine reruns them.
|
|
182
233
|
*/
|
|
183
234
|
export const ImplementerResultSchema = z.object({
|
|
184
|
-
outcome: z.enum(['done', 'bad_test', 'needs_setup_change']),
|
|
235
|
+
outcome: z.enum(['done', 'bad_test', 'needs_setup_change', 'environment']),
|
|
185
236
|
bad_test: BadTestSchema.nullable().default(null),
|
|
186
237
|
...SETUP_FIELDS,
|
|
238
|
+
...ENVIRONMENT_FIELDS,
|
|
239
|
+
...CHORE_FIELDS,
|
|
187
240
|
...SHARED_FIELDS,
|
|
188
241
|
...FIXER_FIELDS,
|
|
189
242
|
})
|
|
@@ -251,6 +304,37 @@ export const LensReviewResultSchema = TicketReviewResultSchema
|
|
|
251
304
|
|
|
252
305
|
export type LensReviewResult = z.infer<typeof LensReviewResultSchema>
|
|
253
306
|
|
|
307
|
+
/**
|
|
308
|
+
* How dangerous the branch is to merge (#513), as the integration lens
|
|
309
|
+
* judges it. A **one-way door** is a change a revert can't undo out in the
|
|
310
|
+
* world: a migration that drops data, a publish, sent messages, a deploy.
|
|
311
|
+
* A two-way door a revert undoes. The **blast radius** is how much breaks
|
|
312
|
+
* if the change is wrong. Each comes with a one-line reason.
|
|
313
|
+
*/
|
|
314
|
+
export const MergeDangerSchema = z.object({
|
|
315
|
+
door: z.enum(['one_way', 'two_way']),
|
|
316
|
+
door_reason: z.string(),
|
|
317
|
+
blast_radius: z.enum(['small', 'medium', 'large']),
|
|
318
|
+
blast_radius_reason: z.string(),
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
export type MergeDanger = z.infer<typeof MergeDangerSchema>
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* The integration lens's result (#513): a lens result, plus, on its first
|
|
325
|
+
* round, the PR's Summary picture and Merge Danger. `summary_picture` is the
|
|
326
|
+
* smallest visual that makes the change clear (a call tree, a file tree, a
|
|
327
|
+
* Mermaid diagram, or a diff sketch), as markdown with its fenced block. A
|
|
328
|
+
* re-review gives null for both. Both default to null, so older journals
|
|
329
|
+
* still parse.
|
|
330
|
+
*/
|
|
331
|
+
export const IntegrationLensResultSchema = LensReviewResultSchema.extend({
|
|
332
|
+
summary_picture: z.string().nullable().default(null),
|
|
333
|
+
merge_danger: MergeDangerSchema.nullable().default(null),
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
export type IntegrationLensResult = z.infer<typeof IntegrationLensResultSchema>
|
|
337
|
+
|
|
254
338
|
/** The most memories one learner may propose. */
|
|
255
339
|
export const MAX_PROPOSED_MEMORIES = 10
|
|
256
340
|
|
|
@@ -306,7 +390,10 @@ export const RoleResultSchema = z.discriminatedUnion('role', [
|
|
|
306
390
|
lensResult('architecture-lens'),
|
|
307
391
|
lensResult('simplification-lens'),
|
|
308
392
|
lensResult('security-lens'),
|
|
309
|
-
|
|
393
|
+
z.object({
|
|
394
|
+
role: z.literal('integration-lens'),
|
|
395
|
+
result: IntegrationLensResultSchema,
|
|
396
|
+
}),
|
|
310
397
|
lensResult('rules-lens'),
|
|
311
398
|
z.object({ role: z.literal('learner'), result: LearnerResultSchema }),
|
|
312
399
|
])
|
|
@@ -336,6 +423,18 @@ export const parseRoleResult = ({
|
|
|
336
423
|
}
|
|
337
424
|
}
|
|
338
425
|
const { result } = parsed.data
|
|
426
|
+
// "Environment" is only an answer with its evidence (#613).
|
|
427
|
+
if (
|
|
428
|
+
(parsed.data.role === 'test-writer' ||
|
|
429
|
+
parsed.data.role === 'implementer') &&
|
|
430
|
+
parsed.data.result.outcome === 'environment' &&
|
|
431
|
+
parsed.data.result.environment === null
|
|
432
|
+
) {
|
|
433
|
+
return {
|
|
434
|
+
ok: false,
|
|
435
|
+
error: `The ${role}'s "environment" gives no evidence: say in "environment.evidence" why the failure is not this ticket's fault.`,
|
|
436
|
+
}
|
|
437
|
+
}
|
|
339
438
|
// "Already done" is only an answer with its evidence (#484).
|
|
340
439
|
if (parsed.data.role === 'test-writer') {
|
|
341
440
|
const { outcome, done_by, criteria } = parsed.data.result
|
|
@@ -50,6 +50,8 @@ export type ScriptedTurn = {
|
|
|
50
50
|
result?: unknown
|
|
51
51
|
failure?: LauncherFailure
|
|
52
52
|
error?: string
|
|
53
|
+
/** With `failure`: the turn reached its role's turn limit (#543). */
|
|
54
|
+
out_of_turns?: boolean
|
|
53
55
|
/** A session summary for the engine to journal. */
|
|
54
56
|
session?: AgentSession
|
|
55
57
|
}
|
|
@@ -64,6 +66,11 @@ export type ScriptedCall = {
|
|
|
64
66
|
session_id: string
|
|
65
67
|
/** Only on launches. */
|
|
66
68
|
may_edit_tests?: boolean
|
|
69
|
+
/**
|
|
70
|
+
* The model a launch asked for; a follow-up's is its session's, `null`
|
|
71
|
+
* for a session the launcher doesn't know.
|
|
72
|
+
*/
|
|
73
|
+
model: string | null
|
|
67
74
|
/** What each of the turn's tool calls handed over, in order. */
|
|
68
75
|
delivered: string[]
|
|
69
76
|
}
|
|
@@ -100,8 +107,11 @@ export const createScriptedLauncher = ({
|
|
|
100
107
|
}): ScriptedLauncher => {
|
|
101
108
|
const remaining = [...turns]
|
|
102
109
|
const calls: ScriptedCall[] = []
|
|
103
|
-
/** Each open session's messaging, kept for its follow-ups. */
|
|
104
|
-
const sessions = new Map<
|
|
110
|
+
/** Each open session's messaging and model, kept for its follow-ups. */
|
|
111
|
+
const sessions = new Map<
|
|
112
|
+
string,
|
|
113
|
+
{ messaging: AgentMessaging | null; model: string }
|
|
114
|
+
>()
|
|
105
115
|
|
|
106
116
|
const toolsFor = ({
|
|
107
117
|
messaging,
|
|
@@ -161,6 +171,7 @@ export const createScriptedLauncher = ({
|
|
|
161
171
|
failure: turn.failure,
|
|
162
172
|
session_id,
|
|
163
173
|
error: turn.error ?? `The scripted ${role} failed.`,
|
|
174
|
+
...(turn.out_of_turns === true ? { out_of_turns: true } : {}),
|
|
164
175
|
...session,
|
|
165
176
|
}
|
|
166
177
|
}
|
|
@@ -180,9 +191,10 @@ export const createScriptedLauncher = ({
|
|
|
180
191
|
cwd,
|
|
181
192
|
may_edit_tests,
|
|
182
193
|
messaging,
|
|
194
|
+
model,
|
|
183
195
|
}) => {
|
|
184
196
|
const session_id = `scripted-${role}-${ticket}-${calls.length + 1}`
|
|
185
|
-
sessions.set(session_id, messaging)
|
|
197
|
+
sessions.set(session_id, { messaging, model })
|
|
186
198
|
const call: ScriptedCall = {
|
|
187
199
|
kind: 'launch',
|
|
188
200
|
role,
|
|
@@ -190,6 +202,7 @@ export const createScriptedLauncher = ({
|
|
|
190
202
|
prompt,
|
|
191
203
|
session_id,
|
|
192
204
|
may_edit_tests,
|
|
205
|
+
model,
|
|
193
206
|
delivered: [],
|
|
194
207
|
}
|
|
195
208
|
calls.push(call)
|
|
@@ -197,25 +210,27 @@ export const createScriptedLauncher = ({
|
|
|
197
210
|
return play({ role, ticket, cwd, session_id, tools })
|
|
198
211
|
},
|
|
199
212
|
followUp: async ({ session_id, role, ticket, message, cwd }) => {
|
|
213
|
+
const open = sessions.get(session_id)
|
|
200
214
|
const call: ScriptedCall = {
|
|
201
215
|
kind: 'follow_up',
|
|
202
216
|
role,
|
|
203
217
|
ticket,
|
|
204
218
|
prompt: message,
|
|
205
219
|
session_id,
|
|
220
|
+
model: open?.model ?? null,
|
|
206
221
|
delivered: [],
|
|
207
222
|
}
|
|
208
223
|
calls.push(call)
|
|
209
|
-
|
|
210
|
-
if (messaging === undefined) {
|
|
224
|
+
if (open === undefined) {
|
|
211
225
|
return {
|
|
212
226
|
ok: false,
|
|
213
227
|
failure: 'engine',
|
|
214
228
|
session_id,
|
|
229
|
+
session_gone: true,
|
|
215
230
|
error: `No scripted session ${session_id}.`,
|
|
216
231
|
}
|
|
217
232
|
}
|
|
218
|
-
const tools = toolsFor({ messaging, call })
|
|
233
|
+
const tools = toolsFor({ messaging: open.messaging, call })
|
|
219
234
|
return play({ role, ticket, cwd, session_id, tools })
|
|
220
235
|
},
|
|
221
236
|
closeSession: async ({ session_id }) => {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent sessions the Claude launcher keeps open between turns: the pool
|
|
3
|
+
* of them by session id, a turn on one (`runTurn`), and closing one or all.
|
|
4
|
+
* A success or an agent failure keeps a session open for follow-ups until it
|
|
5
|
+
* sits idle too long; a stop, an engine failure, or a timeout closes it.
|
|
6
|
+
*/
|
|
7
|
+
import { AgentSessionSchema, type AgentTurn } from './agent-launcher'
|
|
8
|
+
import type { Outcome } from './claude-messages'
|
|
9
|
+
import { nextOutcome, type OpenSession } from './open-session'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Makes the pool of open sessions. A turn gives up after `turn_timeout_ms`;
|
|
13
|
+
* a session left open closes after `idle_timeout_ms` of sitting idle.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const pool = createSessionPool({ turn_timeout_ms: 40 * 60_000, idle_timeout_ms: 30 * 60_000 })
|
|
17
|
+
* const turn = await pool.runTurn({ open, message, started: Date.now(), fallback_id })
|
|
18
|
+
* await pool.closeAll()
|
|
19
|
+
*/
|
|
20
|
+
export const createSessionPool = ({
|
|
21
|
+
turn_timeout_ms: timeoutMs,
|
|
22
|
+
idle_timeout_ms: idleMs,
|
|
23
|
+
}: {
|
|
24
|
+
turn_timeout_ms: number
|
|
25
|
+
idle_timeout_ms: number
|
|
26
|
+
}) => {
|
|
27
|
+
const sessions = new Map<string, OpenSession>()
|
|
28
|
+
|
|
29
|
+
const close = async (open: OpenSession): Promise<void> => {
|
|
30
|
+
clearTimeout(open.idle_timer)
|
|
31
|
+
for (const [id, each] of sessions) {
|
|
32
|
+
if (each === open) sessions.delete(id)
|
|
33
|
+
}
|
|
34
|
+
open.input.close()
|
|
35
|
+
open.running.close()
|
|
36
|
+
await Promise.race([open.pump, Bun.sleep(5_000)])
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Keeps a session open for follow-ups until it sits idle too long. */
|
|
40
|
+
const keepOpen = (open: OpenSession, session_id: string): void => {
|
|
41
|
+
sessions.set(session_id, open)
|
|
42
|
+
clearTimeout(open.idle_timer)
|
|
43
|
+
open.idle_timer = setTimeout(() => void close(open), idleMs)
|
|
44
|
+
open.idle_timer.unref?.()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Resets the summary's per-turn fields; the session's own stay. */
|
|
48
|
+
const startTurn = (open: OpenSession): void => {
|
|
49
|
+
clearTimeout(open.idle_timer)
|
|
50
|
+
const fresh = AgentSessionSchema.parse({})
|
|
51
|
+
open.summary = {
|
|
52
|
+
...fresh,
|
|
53
|
+
session_id: open.summary.session_id,
|
|
54
|
+
model: open.summary.model,
|
|
55
|
+
claude_code_version: open.summary.claude_code_version,
|
|
56
|
+
api_key_source: open.summary.api_key_source,
|
|
57
|
+
subscription_type: open.summary.subscription_type,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sends one message and waits for the turn's outcome. A success or an
|
|
63
|
+
* agent failure keeps the session open for follow-ups; a stop, an
|
|
64
|
+
* engine failure, or a timeout closes it.
|
|
65
|
+
*/
|
|
66
|
+
const runTurn = async ({
|
|
67
|
+
open,
|
|
68
|
+
message,
|
|
69
|
+
started,
|
|
70
|
+
fallback_id,
|
|
71
|
+
}: {
|
|
72
|
+
open: OpenSession
|
|
73
|
+
message: string
|
|
74
|
+
started: number
|
|
75
|
+
/** The id to report if the session never named itself. */
|
|
76
|
+
fallback_id: string
|
|
77
|
+
}): Promise<AgentTurn> => {
|
|
78
|
+
const waiting = nextOutcome(open)
|
|
79
|
+
open.input.push({
|
|
80
|
+
type: 'user',
|
|
81
|
+
message: { role: 'user', content: message },
|
|
82
|
+
parent_tool_use_id: null,
|
|
83
|
+
})
|
|
84
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
85
|
+
const timedOut = new Promise<'timeout'>((resolve) => {
|
|
86
|
+
timer = setTimeout(() => resolve('timeout'), timeoutMs)
|
|
87
|
+
})
|
|
88
|
+
const raced = await Promise.race([waiting, timedOut])
|
|
89
|
+
clearTimeout(timer)
|
|
90
|
+
const outcome: Outcome =
|
|
91
|
+
raced === 'timeout'
|
|
92
|
+
? {
|
|
93
|
+
ok: false,
|
|
94
|
+
failure: 'agent',
|
|
95
|
+
error: `No result within ${timeoutMs} ms; the agent was interrupted.`,
|
|
96
|
+
}
|
|
97
|
+
: raced
|
|
98
|
+
if (raced === 'timeout') {
|
|
99
|
+
await open.running.interrupt().catch(() => undefined)
|
|
100
|
+
}
|
|
101
|
+
const session_id = open.summary.session_id ?? fallback_id
|
|
102
|
+
const session = {
|
|
103
|
+
...open.summary,
|
|
104
|
+
duration_ms: Date.now() - started,
|
|
105
|
+
}
|
|
106
|
+
const keep =
|
|
107
|
+
raced !== 'timeout' &&
|
|
108
|
+
open.ended === null &&
|
|
109
|
+
open.summary.session_id !== null
|
|
110
|
+
if (keep) keepOpen(open, session_id)
|
|
111
|
+
else await close(open)
|
|
112
|
+
return { ...outcome, session_id, session }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Closes every session still open. */
|
|
116
|
+
const closeAll = async (): Promise<void> => {
|
|
117
|
+
await Promise.all([...new Set(sessions.values())].map(close))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { sessions, close, startTurn, runTurn, closeAll }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The pool `createSessionPool` makes. */
|
|
124
|
+
export type SessionPool = ReturnType<typeof createSessionPool>
|