@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,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run's **live file** (#628): `live.json` in the run folder, what each
|
|
3
|
+
* agent in a turn is doing now, and each check waiting for a prepare slot
|
|
4
|
+
* or a test slot (#632), for the board to show between journal records,
|
|
5
|
+
* and the run's last 50 actions across tickets, its live feed (#634). It
|
|
6
|
+
* is never journaled and never replayed: each write replaces
|
|
7
|
+
* the whole file (written aside, then renamed), at most once a second
|
|
8
|
+
* while something changed, and at least every 5 seconds while the engine
|
|
9
|
+
* runs, each with its `written_at` time. Each agent's entry carries its
|
|
10
|
+
* activity for each of the last 30 seconds (#631), for the board's
|
|
11
|
+
* heartbeat, and its helpers (#636), whose actions go in the feed too.
|
|
12
|
+
*/
|
|
13
|
+
import { mkdirSync, renameSync, writeFileSync } from 'node:fs'
|
|
14
|
+
import { dirname, join } from 'node:path'
|
|
15
|
+
|
|
16
|
+
import { z } from 'zod'
|
|
17
|
+
|
|
18
|
+
import type { AgentLauncher } from '../agents/agent-launcher'
|
|
19
|
+
import type {
|
|
20
|
+
ProgressAction,
|
|
21
|
+
ProgressListener,
|
|
22
|
+
TurnProgress,
|
|
23
|
+
} from '../agents/turn-progress'
|
|
24
|
+
import type { SlotWaitListener } from '../gates/slot-waits'
|
|
25
|
+
|
|
26
|
+
/** The live file's name inside a run folder. */
|
|
27
|
+
export const LIVE_FILE = 'live.json'
|
|
28
|
+
|
|
29
|
+
/** The live file is written at most this often while something changes. */
|
|
30
|
+
export const LIVE_MIN_GAP_MS = 1000
|
|
31
|
+
|
|
32
|
+
/** The live file is written at least this often, changed or not. */
|
|
33
|
+
export const LIVE_MAX_GAP_MS = 5000
|
|
34
|
+
|
|
35
|
+
/** How often the writer looks for something to write. */
|
|
36
|
+
const LIVE_CHECK_MS = 200
|
|
37
|
+
|
|
38
|
+
/** How many seconds of activity each agent's entry keeps (#631). */
|
|
39
|
+
export const ACTIVITY_SECONDS = 30
|
|
40
|
+
|
|
41
|
+
/** The live file of one run: `<runs_dir>/<run_id>/live.json`. */
|
|
42
|
+
export const runLiveFilePath = ({
|
|
43
|
+
runs_dir,
|
|
44
|
+
run_id,
|
|
45
|
+
}: {
|
|
46
|
+
runs_dir: string
|
|
47
|
+
run_id: string
|
|
48
|
+
}): string => join(runs_dir, run_id, LIVE_FILE)
|
|
49
|
+
|
|
50
|
+
/** The live feed keeps this many of the run's latest actions (#634). */
|
|
51
|
+
export const LIVE_FEED_SIZE = 50
|
|
52
|
+
|
|
53
|
+
/** An action: a tool and a short target, with its time (ISO). */
|
|
54
|
+
const LiveActionSchema = z.object({
|
|
55
|
+
tool: z.string(),
|
|
56
|
+
target: z.string(),
|
|
57
|
+
at: z.string(),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
/** A tool call whose result hasn't come back yet. */
|
|
61
|
+
const LiveOpenCallSchema = z.object({
|
|
62
|
+
tool: z.string(),
|
|
63
|
+
target: z.string(),
|
|
64
|
+
started_at: z.string(),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
/** One of an agent's helpers (#636). */
|
|
68
|
+
const LiveHelperSchema = z.object({
|
|
69
|
+
model: z.string(),
|
|
70
|
+
action: LiveActionSchema.nullable(),
|
|
71
|
+
done: z.boolean(),
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
/** One agent in a turn, as the live file has it. */
|
|
75
|
+
const LiveAgentSchema = z.object({
|
|
76
|
+
ticket: z.number().int(),
|
|
77
|
+
role: z.string(),
|
|
78
|
+
model: z.string().nullable(),
|
|
79
|
+
tokens: z.number().min(0),
|
|
80
|
+
action: LiveActionSchema.nullable(),
|
|
81
|
+
open_call: LiveOpenCallSchema.nullable(),
|
|
82
|
+
/** The helpers the agent started this turn (#636). */
|
|
83
|
+
helpers: z.array(LiveHelperSchema),
|
|
84
|
+
/** One count for each of the last 30 seconds, oldest first (#631). */
|
|
85
|
+
activity: z.array(z.number().int().min(0)),
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
/** A ticket's check waiting for a slot (#632), as the live file has it. */
|
|
89
|
+
const LiveWaitSchema = z.object({
|
|
90
|
+
ticket: z.number().int(),
|
|
91
|
+
for: z.enum(['prepare_slot', 'test_slot']),
|
|
92
|
+
in_use: z.number().int().min(0),
|
|
93
|
+
slots: z.number().int().min(0),
|
|
94
|
+
since: z.string(),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
/** One action of the live feed: who did it, on which model, and what. */
|
|
98
|
+
const LiveFeedEntrySchema = z.object({
|
|
99
|
+
at: z.string(),
|
|
100
|
+
ticket: z.number().int(),
|
|
101
|
+
role: z.string(),
|
|
102
|
+
model: z.string().nullable(),
|
|
103
|
+
tool: z.string(),
|
|
104
|
+
target: z.string(),
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
/** The whole live file, as each write has it. */
|
|
108
|
+
export const LiveFileSchema = z.object({
|
|
109
|
+
written_at: z.string(),
|
|
110
|
+
/** The run's first plan-usage reading, as its `rate_limit_event` sent it. */
|
|
111
|
+
plan_usage: z.record(z.string(), z.unknown()).nullable(),
|
|
112
|
+
agents: z.array(LiveAgentSchema),
|
|
113
|
+
waits: z.array(LiveWaitSchema),
|
|
114
|
+
feed: z.array(LiveFeedEntrySchema),
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
export type LiveFile = z.infer<typeof LiveFileSchema>
|
|
118
|
+
|
|
119
|
+
export type LiveAgent = z.infer<typeof LiveAgentSchema>
|
|
120
|
+
|
|
121
|
+
export type LiveHelper = z.infer<typeof LiveHelperSchema>
|
|
122
|
+
|
|
123
|
+
export type LiveWait = z.infer<typeof LiveWaitSchema>
|
|
124
|
+
|
|
125
|
+
type LiveFeedEntry = z.infer<typeof LiveFeedEntrySchema>
|
|
126
|
+
|
|
127
|
+
/** An agent's entry before its activity is added at write time. */
|
|
128
|
+
type HeldAgent = Omit<LiveAgent, 'activity'>
|
|
129
|
+
|
|
130
|
+
const sameAction = (
|
|
131
|
+
left: ProgressAction | null,
|
|
132
|
+
right: ProgressAction
|
|
133
|
+
): boolean =>
|
|
134
|
+
left !== null &&
|
|
135
|
+
left.tool === right.tool &&
|
|
136
|
+
left.target === right.target &&
|
|
137
|
+
left.at === right.at
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* One agent's activity (#631): a count per second, by the second it
|
|
141
|
+
* happened in. Each progress heard is one tick, and so is each second a
|
|
142
|
+
* tool call is open, a low, steady ripple while a long command runs.
|
|
143
|
+
*/
|
|
144
|
+
const createActivity = () => {
|
|
145
|
+
const counts = new Map<number, number>()
|
|
146
|
+
/** The last second an open tool call ticked in. */
|
|
147
|
+
let rippled = -1
|
|
148
|
+
|
|
149
|
+
const secondOf = (ms: number): number => Math.floor(ms / 1000)
|
|
150
|
+
|
|
151
|
+
const tick = (at: number): void => {
|
|
152
|
+
const second = secondOf(at)
|
|
153
|
+
counts.set(second, (counts.get(second) ?? 0) + 1)
|
|
154
|
+
for (const old of counts.keys()) {
|
|
155
|
+
if (old <= second - ACTIVITY_SECONDS) counts.delete(old)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** One low tick for this second, once, while a tool call is open. */
|
|
160
|
+
const ripple = (at: number): boolean => {
|
|
161
|
+
if (secondOf(at) === rippled) return false
|
|
162
|
+
rippled = secondOf(at)
|
|
163
|
+
tick(at)
|
|
164
|
+
return true
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The counts of the last 30 seconds up to `at`, oldest first. */
|
|
168
|
+
const recent = (at: number): number[] =>
|
|
169
|
+
Array.from(
|
|
170
|
+
{ length: ACTIVITY_SECONDS },
|
|
171
|
+
(_, index) =>
|
|
172
|
+
counts.get(secondOf(at) - ACTIVITY_SECONDS + 1 + index) ?? 0
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return { tick, ripple, recent }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Keeps the live file at `file` from the moment it is made until `close`,
|
|
180
|
+
* which writes it one last time with no agents. `launcher` wraps an agent
|
|
181
|
+
* launcher so each turn's progress (`on_progress`) goes into the file while
|
|
182
|
+
* the turn runs; `slotWaits` hears a ticket step's slot waits (see
|
|
183
|
+
* `hearSlotWaits`). A write that fails is skipped; the run never stops for it.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* const live = createLiveFile({ file: runLiveFilePath({ runs_dir, run_id }) })
|
|
187
|
+
* await runSteps({ launcher: live.launcher(launcher) })
|
|
188
|
+
* live.close()
|
|
189
|
+
*/
|
|
190
|
+
export const createLiveFile = ({ file }: { file: string }) => {
|
|
191
|
+
const agents = new Map<number, HeldAgent>()
|
|
192
|
+
const activities = new Map<number, ReturnType<typeof createActivity>>()
|
|
193
|
+
const waits = new Map<number, LiveWait>()
|
|
194
|
+
let plan_usage: Record<string, unknown> | null = null
|
|
195
|
+
let feed: LiveFeedEntry[] = []
|
|
196
|
+
let changed = false
|
|
197
|
+
let last = 0
|
|
198
|
+
let next = 0
|
|
199
|
+
|
|
200
|
+
const write = (): void => {
|
|
201
|
+
const at = Date.now()
|
|
202
|
+
last = at
|
|
203
|
+
changed = false
|
|
204
|
+
const live: LiveFile = {
|
|
205
|
+
written_at: new Date(at).toISOString(),
|
|
206
|
+
plan_usage,
|
|
207
|
+
agents: [...agents].map(([key, agent]) => ({
|
|
208
|
+
...agent,
|
|
209
|
+
activity: activities.get(key)?.recent(at) ?? [],
|
|
210
|
+
})),
|
|
211
|
+
waits: [...waits.values()],
|
|
212
|
+
feed,
|
|
213
|
+
}
|
|
214
|
+
const text = JSON.stringify(live)
|
|
215
|
+
try {
|
|
216
|
+
mkdirSync(dirname(file), { recursive: true })
|
|
217
|
+
const aside = `${file}.${process.pid}.tmp`
|
|
218
|
+
writeFileSync(aside, text)
|
|
219
|
+
renameSync(aside, file)
|
|
220
|
+
} catch {
|
|
221
|
+
// The live file is only for the board; the run goes on without it.
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const timer = setInterval(() => {
|
|
226
|
+
const at = Date.now()
|
|
227
|
+
for (const [key, agent] of agents) {
|
|
228
|
+
if (agent.open_call === null) continue
|
|
229
|
+
if (activities.get(key)?.ripple(at)) changed = true
|
|
230
|
+
}
|
|
231
|
+
const gap = at - last
|
|
232
|
+
if ((changed && gap >= LIVE_MIN_GAP_MS) || gap >= LIVE_MAX_GAP_MS) {
|
|
233
|
+
write()
|
|
234
|
+
}
|
|
235
|
+
}, LIVE_CHECK_MS)
|
|
236
|
+
timer.unref?.()
|
|
237
|
+
write()
|
|
238
|
+
|
|
239
|
+
const heard = (key: number, progress: TurnProgress): void => {
|
|
240
|
+
const agent = agents.get(key)
|
|
241
|
+
if (agent === undefined) return
|
|
242
|
+
activities.get(key)?.tick(Date.now())
|
|
243
|
+
const { action, helpers = [] } = progress
|
|
244
|
+
const { ticket, role, model } = agent
|
|
245
|
+
// A message that only adds tokens repeats the action it was on.
|
|
246
|
+
const fresh = [
|
|
247
|
+
...(action !== null && !sameAction(agent.action, action)
|
|
248
|
+
? [{ ...action, ticket, role, model }]
|
|
249
|
+
: []),
|
|
250
|
+
// A helper's actions go in the feed too, on its own model.
|
|
251
|
+
...helpers.flatMap((helper, index) =>
|
|
252
|
+
helper.action !== null &&
|
|
253
|
+
!sameAction(agent.helpers[index]?.action ?? null, helper.action)
|
|
254
|
+
? [
|
|
255
|
+
{
|
|
256
|
+
...helper.action,
|
|
257
|
+
ticket,
|
|
258
|
+
role: 'helper',
|
|
259
|
+
model: helper.model,
|
|
260
|
+
},
|
|
261
|
+
]
|
|
262
|
+
: []
|
|
263
|
+
),
|
|
264
|
+
]
|
|
265
|
+
if (fresh.length > 0) feed = [...feed, ...fresh].slice(-LIVE_FEED_SIZE)
|
|
266
|
+
agents.set(key, {
|
|
267
|
+
...agent,
|
|
268
|
+
tokens: progress.tokens,
|
|
269
|
+
action: progress.action,
|
|
270
|
+
open_call: progress.open_call,
|
|
271
|
+
helpers,
|
|
272
|
+
})
|
|
273
|
+
plan_usage ??= progress.rate_limit_events[0] ?? null
|
|
274
|
+
changed = true
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Shows the agent `who` in the file while `turn` runs. */
|
|
278
|
+
const during = async <T>({
|
|
279
|
+
who,
|
|
280
|
+
on_progress,
|
|
281
|
+
turn,
|
|
282
|
+
}: {
|
|
283
|
+
who: Pick<HeldAgent, 'ticket' | 'role' | 'model'>
|
|
284
|
+
on_progress: ProgressListener | undefined
|
|
285
|
+
turn: (hear: ProgressListener) => Promise<T>
|
|
286
|
+
}): Promise<T> => {
|
|
287
|
+
const key = next
|
|
288
|
+
next += 1
|
|
289
|
+
agents.set(key, {
|
|
290
|
+
...who,
|
|
291
|
+
tokens: 0,
|
|
292
|
+
action: null,
|
|
293
|
+
open_call: null,
|
|
294
|
+
helpers: [],
|
|
295
|
+
})
|
|
296
|
+
activities.set(key, createActivity())
|
|
297
|
+
changed = true
|
|
298
|
+
try {
|
|
299
|
+
return await turn((progress) => {
|
|
300
|
+
heard(key, progress)
|
|
301
|
+
on_progress?.(progress)
|
|
302
|
+
})
|
|
303
|
+
} finally {
|
|
304
|
+
agents.delete(key)
|
|
305
|
+
activities.delete(key)
|
|
306
|
+
changed = true
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const launcher = <L extends AgentLauncher>(inner: L): L => {
|
|
311
|
+
// A follow-up runs on its session's model.
|
|
312
|
+
const models = new Map<string, string>()
|
|
313
|
+
return {
|
|
314
|
+
...inner,
|
|
315
|
+
launch: async (args) => {
|
|
316
|
+
const turn = await during({
|
|
317
|
+
who: {
|
|
318
|
+
ticket: args.ticket,
|
|
319
|
+
role: args.role,
|
|
320
|
+
model: args.model,
|
|
321
|
+
},
|
|
322
|
+
on_progress: args.on_progress,
|
|
323
|
+
turn: (hear) =>
|
|
324
|
+
inner.launch({ ...args, on_progress: hear }),
|
|
325
|
+
})
|
|
326
|
+
if (turn.session_id !== undefined) {
|
|
327
|
+
models.set(turn.session_id, args.model)
|
|
328
|
+
}
|
|
329
|
+
return turn
|
|
330
|
+
},
|
|
331
|
+
followUp: (args) =>
|
|
332
|
+
during({
|
|
333
|
+
who: {
|
|
334
|
+
ticket: args.ticket,
|
|
335
|
+
role: args.role,
|
|
336
|
+
model: models.get(args.session_id) ?? null,
|
|
337
|
+
},
|
|
338
|
+
on_progress: args.on_progress,
|
|
339
|
+
turn: (hear) =>
|
|
340
|
+
inner.followUp({ ...args, on_progress: hear }),
|
|
341
|
+
}),
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Shows each slot wait of `ticket`'s step in the file while it lasts. */
|
|
346
|
+
const slotWaits =
|
|
347
|
+
({ ticket }: { ticket: number }): SlotWaitListener =>
|
|
348
|
+
(wait) => {
|
|
349
|
+
const key = next
|
|
350
|
+
next += 1
|
|
351
|
+
waits.set(key, {
|
|
352
|
+
ticket,
|
|
353
|
+
...wait,
|
|
354
|
+
since: new Date().toISOString(),
|
|
355
|
+
})
|
|
356
|
+
changed = true
|
|
357
|
+
return () => {
|
|
358
|
+
waits.delete(key)
|
|
359
|
+
changed = true
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const close = (): void => {
|
|
364
|
+
clearInterval(timer)
|
|
365
|
+
agents.clear()
|
|
366
|
+
activities.clear()
|
|
367
|
+
waits.clear()
|
|
368
|
+
write()
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return { launcher, slotWaits, close }
|
|
372
|
+
}
|
package/engine/core/loop-caps.ts
CHANGED
|
@@ -11,11 +11,34 @@
|
|
|
11
11
|
export const MAX_FIX_ROUNDS = 3
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Reruns a ticket (or the final review) gets when a writer answers
|
|
15
|
+
* `environment` (#613): each reruns the failed check without using a fix
|
|
16
|
+
* round. The `environment` answer after these makes it stuck.
|
|
17
|
+
*/
|
|
18
|
+
export const MAX_ENVIRONMENT_RERUNS = 2
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Times a test may be sent back as bad, each to a fresh test-writer: by the
|
|
22
|
+
* implementer, or by the engine when a mapped test passes with no `expect()`
|
|
23
|
+
* (#581). The two share this cap. The bounce after these makes the ticket
|
|
24
|
+
* stuck.
|
|
16
25
|
*/
|
|
17
26
|
export const MAX_BAD_TEST_BOUNCES = 1
|
|
18
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Times a ticket's tests may go back to its test-writer after a rebase
|
|
30
|
+
* (#489): the implementer sent a test back as bad, because tickets that
|
|
31
|
+
* joined the run branch made it wrong. The bad test after these makes the
|
|
32
|
+
* ticket stuck.
|
|
33
|
+
*/
|
|
34
|
+
export const MAX_TEST_UPDATES = 2
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The most tests a test-writer may map to one acceptance criterion (#581):
|
|
38
|
+
* the red check refuses more. Write the fewest tests that prove it.
|
|
39
|
+
*/
|
|
40
|
+
export const MAX_TESTS_PER_CRITERION = 3
|
|
41
|
+
|
|
19
42
|
/**
|
|
20
43
|
* Engine failures in a row (the SDK crashed, or a follow-up's session was
|
|
21
44
|
* gone) after which a ticket is stuck. Each one before it starts a fresh
|
|
@@ -6,7 +6,8 @@ import type { Tracker } from '../tracker/tracker'
|
|
|
6
6
|
* again just before the PR opens (#484): a ticket reopened while the run
|
|
7
7
|
* went, or one that was closed at intake and reopened since. The PR names
|
|
8
8
|
* them (`withNotInRun`), so they are no surprise later. Oldest first, as
|
|
9
|
-
* the tracker lists them.
|
|
9
|
+
* the tracker lists them. The tickets intake left out for a person (#499)
|
|
10
|
+
* aren't among them: the PR names those in their own sections.
|
|
10
11
|
*
|
|
11
12
|
* @example
|
|
12
13
|
* const open = await openTicketsNotInRun({ tracker, state })
|
|
@@ -21,9 +22,12 @@ export const openTicketsNotInRun = async ({
|
|
|
21
22
|
}): Promise<{ number: number; title: string }[]> => {
|
|
22
23
|
const { spec_number, snapshot } = state
|
|
23
24
|
if (spec_number === null) return []
|
|
24
|
-
const
|
|
25
|
+
const known = new Set([
|
|
26
|
+
...(snapshot?.ticket_order ?? []),
|
|
27
|
+
...(snapshot?.left_out ?? []).map(({ number }) => number),
|
|
28
|
+
])
|
|
25
29
|
const tickets = await tracker.listSubTickets({ spec_number })
|
|
26
30
|
return tickets
|
|
27
|
-
.filter(({ number, state }) => state === 'open' && !
|
|
31
|
+
.filter(({ number, state }) => state === 'open' && !known.has(number))
|
|
28
32
|
.map(({ number, title }) => ({ number, title }))
|
|
29
33
|
}
|