tumwater 0.4.2 → 0.4.3
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/README.md +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +25 -12
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +1 -8
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +9 -0
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +50 -27
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +1 -1
- package/dist/src/agent/subscription-account.js +3 -3
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +58 -102
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +1 -1
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-scoped.js +40 -16
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +34 -12
- package/dist/src/cli/cli-query-commands.js +1 -1
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-start.js +91 -58
- package/dist/src/cli/completion.js +82 -46
- package/dist/src/cli/config-commands.js +82 -71
- package/dist/src/cli/help.js +4 -6
- package/dist/src/cli/log-commands.js +43 -23
- package/dist/src/cli/notify-command.js +2 -2
- package/dist/src/cli/question-commands.js +86 -60
- package/dist/src/cli.js +181 -135
- package/dist/src/collections.js +31 -16
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +22 -0
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +5 -20
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +153 -102
- package/dist/src/config/config-schema.js +9 -11
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +4 -52
- package/dist/src/config/config-validation.js +4 -38
- package/dist/src/config/config-views.js +9 -34
- package/dist/src/config/config-write.js +6 -23
- package/dist/src/config/config.js +4 -5
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +24 -24
- package/dist/src/doctor/doctor-backlog.js +7 -5
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +5 -5
- package/dist/src/doctor/doctor-claude-checks.js +44 -28
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +44 -112
- package/dist/src/doctor/doctor-fallback-checks.js +2 -2
- package/dist/src/doctor/doctor-holds.js +6 -45
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +120 -20
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +3 -3
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +30 -54
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +1 -3
- package/dist/src/events/{notify.js → event-notify.js} +2 -2
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/failure/failure-cluster.js +6 -6
- package/dist/src/failure/failure-data.js +8 -4
- package/dist/src/failure/failure-render.js +44 -34
- package/dist/src/failure/failure-state-change.js +4 -34
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +27 -68
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +7 -7
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +22 -23
- package/dist/src/gui/gui-prompt-commands.js +23 -23
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +51 -33
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +8 -11
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +10 -13
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-verify.js +8 -18
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +76 -44
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +27 -32
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +1 -1
- package/dist/src/orchestrator/scheduling-skip.js +1 -1
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +8 -7
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +1 -1
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +32 -6
- package/dist/src/pi/pi-watchdogs.js +12 -5
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +49 -5
- package/dist/src/process/process.js +8 -8
- package/dist/src/process/run-marker.js +31 -13
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +1 -1
- package/dist/src/review/review-precheck.js +11 -1
- package/dist/src/review/review.js +40 -153
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +1 -14
- package/dist/src/roles/role-view.js +9 -21
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/run-apply.js +12 -6
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +135 -84
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +119 -50
- package/dist/src/run/stage-check.js +49 -32
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/runner-outcome.js +6 -1
- package/dist/src/runner/runner-pi.js +3 -10
- package/dist/src/runner/runner-recovery.js +1 -1
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -1
- package/dist/src/runner/runner-run.js +97 -35
- package/dist/src/runner/runner-state-load.js +12 -9
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-data.js +2 -2
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -17
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +98 -65
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +2 -15
- package/dist/src/team/team-polls.js +28 -126
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +20 -15
- package/dist/src/text/text.js +3 -3
- package/dist/src/ui/alert-feeds.js +49 -73
- package/dist/src/ui/badges.js +3 -13
- package/dist/src/ui/gui/gui-client-boot.js +2 -2
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +7 -17
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +4 -4
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +53 -40
- package/dist/src/ui/run-progress-model.js +9 -2
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +3 -7
- package/dist/src/ui/status-render.js +5 -9
- package/dist/src/ui/team-alert-backlog.js +6 -6
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +7 -8
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +1 -7
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +159 -92
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +3 -3
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -228
- package/dist/src/ui/tui/tui-pane-history.js +61 -70
- package/dist/src/ui/tui/tui-pane-pending.js +284 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +11 -7
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +29 -32
- package/dist/src/ui/tui/tui.js +3 -4
- package/dist/src/verdict/fix-claim.js +76 -54
- package/dist/src/verdict/refusal.js +1 -1
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
- /package/dist/src/runner/{leftover.js → runner-leftover.js} +0 -0
|
@@ -196,64 +196,79 @@ function spanningGarbled(joined, joinAt, patterns) {
|
|
|
196
196
|
}
|
|
197
197
|
return null;
|
|
198
198
|
}
|
|
199
|
-
/**
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
export function garbledWordingFindings(diff) {
|
|
203
|
-
const hits = [];
|
|
199
|
+
/** The added lines of a diff, in order, each with the added line that precedes it. */
|
|
200
|
+
function addedDiffLines(diff) {
|
|
201
|
+
const out = [];
|
|
204
202
|
let file = null;
|
|
205
203
|
let lineNo = 0;
|
|
206
|
-
let
|
|
204
|
+
let prev = null;
|
|
207
205
|
for (const line of diff.split("\n")) {
|
|
208
206
|
const header = /^diff --git a\/.* b\/(.*)$/.exec(line);
|
|
209
207
|
if (header) {
|
|
210
208
|
file = header[1];
|
|
211
|
-
|
|
209
|
+
prev = null;
|
|
212
210
|
continue;
|
|
213
211
|
}
|
|
214
212
|
const hunk = /^@@ .*\+(\d+)(?:,\d+)? @@/.exec(line);
|
|
215
213
|
if (hunk) {
|
|
216
214
|
lineNo = Number(hunk[1]);
|
|
217
|
-
|
|
215
|
+
prev = null;
|
|
218
216
|
continue;
|
|
219
217
|
}
|
|
220
218
|
if (!line.startsWith("+") || line.startsWith("+++ ")) {
|
|
221
|
-
|
|
219
|
+
prev = null;
|
|
222
220
|
continue;
|
|
223
221
|
}
|
|
224
|
-
const
|
|
225
|
-
if (file !== null
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const m = re.exec(cur);
|
|
229
|
-
if (m) {
|
|
230
|
-
hits.push(`${file}:${lineNo} "${m[0]}"`);
|
|
231
|
-
hit = true;
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
const prose = proseOf(file, cur);
|
|
236
|
-
if (!hit && prose !== null) {
|
|
237
|
-
const m = DOUBLED_WORD.exec(prose);
|
|
238
|
-
if (m) {
|
|
239
|
-
hits.push(`${file}:${lineNo} "${m[0]}"`);
|
|
240
|
-
hit = true;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (!hit && prevAdded !== null) {
|
|
244
|
-
const head = prevAdded.trimEnd();
|
|
245
|
-
const joined = head + " " + cur.replace(/^\s*(?:\/\/+|\*|#)\s*/, "");
|
|
246
|
-
let split = spanningGarbled(joined, head.length, GARBLED_WORDING);
|
|
247
|
-
if (!split && prose !== null && proseOf(file, prevAdded) !== null) {
|
|
248
|
-
split = spanningGarbled(joined, head.length, [DOUBLED_WORD]);
|
|
249
|
-
}
|
|
250
|
-
if (split)
|
|
251
|
-
hits.push(`${file}:${lineNo - 1}-${lineNo} "${split}"`);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
prevAdded = cur;
|
|
222
|
+
const text = line.slice(1);
|
|
223
|
+
if (file !== null)
|
|
224
|
+
out.push({ file, lineNo, text, prev });
|
|
225
|
+
prev = text;
|
|
255
226
|
lineNo++;
|
|
256
227
|
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
/** The first GARBLED_WORDING hit on a single added line, or the DOUBLED_WORD hit in its prose. */
|
|
231
|
+
function singleLineGarbledHit(file, lineNo, cur) {
|
|
232
|
+
for (const re of GARBLED_WORDING) {
|
|
233
|
+
const m = re.exec(cur);
|
|
234
|
+
if (m)
|
|
235
|
+
return `${file}:${lineNo} "${m[0]}"`;
|
|
236
|
+
}
|
|
237
|
+
const prose = proseOf(file, cur);
|
|
238
|
+
if (prose !== null) {
|
|
239
|
+
const m = DOUBLED_WORD.exec(prose);
|
|
240
|
+
if (m)
|
|
241
|
+
return `${file}:${lineNo} "${m[0]}"`;
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
/** A garbled or doubled phrase split across two consecutive added lines, joined at their seam. */
|
|
246
|
+
function spanningGarbledHit(file, lineNo, cur, prevAdded) {
|
|
247
|
+
if (prevAdded === null)
|
|
248
|
+
return null;
|
|
249
|
+
const head = prevAdded.trimEnd();
|
|
250
|
+
const joined = head + " " + cur.replace(/^\s*(?:\/\/+|\*|#)\s*/, "");
|
|
251
|
+
let split = spanningGarbled(joined, head.length, GARBLED_WORDING);
|
|
252
|
+
const prose = proseOf(file, cur);
|
|
253
|
+
if (!split && prose !== null && proseOf(file, prevAdded) !== null) {
|
|
254
|
+
split = spanningGarbled(joined, head.length, [DOUBLED_WORD]);
|
|
255
|
+
}
|
|
256
|
+
if (split)
|
|
257
|
+
return `${file}:${lineNo - 1}-${lineNo} "${split}"`;
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
/** (e) Added lines, outside record prose, that match GARBLED_WORDING: at most one hit per line,
|
|
261
|
+
* reported as `file:line "matched text"` in a single finding. A phrase split across two
|
|
262
|
+
* consecutive added lines is reported as `file:N-M`. */
|
|
263
|
+
export function garbledWordingFindings(diff) {
|
|
264
|
+
const hits = [];
|
|
265
|
+
for (const { file, lineNo, text, prev } of addedDiffLines(diff)) {
|
|
266
|
+
if (isRecordProse(file))
|
|
267
|
+
continue;
|
|
268
|
+
const hit = singleLineGarbledHit(file, lineNo, text) ?? spanningGarbledHit(file, lineNo, text, prev);
|
|
269
|
+
if (hit !== null)
|
|
270
|
+
hits.push(hit);
|
|
271
|
+
}
|
|
257
272
|
if (hits.length === 0)
|
|
258
273
|
return [];
|
|
259
274
|
const lead = 'added lines carry garbled rename wording (reword them; a rote word swap such as "tick" → "run" reads wrong here or leaves a word doubled): '; // vocabulary-ok: quotes the retired word
|
|
@@ -10,7 +10,7 @@ import { mergeToMain } from "../merge/merge-to-main.js";
|
|
|
10
10
|
import { MERGE_LOCK_WAIT_MS } from "../paths.js";
|
|
11
11
|
import { resolveRunVerdict } from "../run/run-verdict.js";
|
|
12
12
|
import { modelTrailerLine } from "../git/commit-message.js";
|
|
13
|
-
import { seamAgent } from "../config/model-selector.js";
|
|
13
|
+
import { seamAgent } from "../config/config-model-selector.js";
|
|
14
14
|
import { stageRunMerge } from "../run/run-stage.js";
|
|
15
15
|
import { stageCheckFindings } from "../run/stage-check.js";
|
|
16
16
|
/** Merge the worktree branch on main (see src/merge/merge-to-main.ts for the
|
|
@@ -62,6 +62,11 @@ export async function handlePiResultPhase(ctx, pi, userPrompt, revisionRound, wt
|
|
|
62
62
|
flow,
|
|
63
63
|
filed,
|
|
64
64
|
ci,
|
|
65
|
+
// The image-reroute arm resolves its target from the live config and the current
|
|
66
|
+
// regime, and names the model this run ran on as the handoff's `from`.
|
|
67
|
+
config: ctx.config,
|
|
68
|
+
rerouteRegime: ctx.rerouteRegime(),
|
|
69
|
+
runModel: cfg.model,
|
|
65
70
|
warn: (message) => ctx.warn(message),
|
|
66
71
|
merge: (w, sum) => mergePhase(ctx, w, sum),
|
|
67
72
|
finishAbortedRun: () => ctx.finishAbortedRun(userPrompt, wt),
|
|
@@ -3,7 +3,6 @@ import { backendSessionResumable } from "../agent/agent-backend.js";
|
|
|
3
3
|
import { HOLD_BASE_MS } from "../team/team-hold.js";
|
|
4
4
|
import { backendKindPhrase } from "../text/phrases.js";
|
|
5
5
|
import { configForRole, resolvedBackendConfig, seamHoldKey, seamPairName } from "../config/config-views.js";
|
|
6
|
-
import { ensureProviderLease } from "../concurrency/provider-lease.js";
|
|
7
6
|
import { buildSummaryRequestPrompt } from "../prompt/prompt-followup.js";
|
|
8
7
|
import { readClaudeAccount, subscriptionWindowRejected } from "../agent/claude/claude-account.js";
|
|
9
8
|
import { observeOpencodeRun, opencodeModelKey } from "../agent/opencode/opencode-catalog.js";
|
|
@@ -223,12 +222,6 @@ export class RunnerPi {
|
|
|
223
222
|
// Names the seam in per-run provider warnings; omitted when pi picks its own default model.
|
|
224
223
|
const on = opts.config.model !== undefined ? ` on ${pair}` : "";
|
|
225
224
|
const fold = (run) => authoring ? this.host.foldUsage(run, provider, pair) : this.host.foldMergeUsage(run, provider, pair);
|
|
226
|
-
// The agent run re-keys the calling run's provider lease to the provider THIS agent run uses,
|
|
227
|
-
// if it is not already there (plans/provider-concurrency.md §5): a budget handback or a
|
|
228
|
-
// model-failure switch that moved this agent run onto another seam moves the run's lease with
|
|
229
|
-
// it, so a claude-authored run whose gate agent run lands on omlx is counted there. No-op when
|
|
230
|
-
// the run carries no lease (a bare RunnerPi unit test, `tumwater history`).
|
|
231
|
-
await ensureProviderLease(opts.config);
|
|
232
225
|
const pi = await runPi(opts);
|
|
233
226
|
this.warnFallbackClone(pi);
|
|
234
227
|
this.observeFreeModelCost(pi, opts.config);
|
|
@@ -358,7 +351,7 @@ export class RunnerPi {
|
|
|
358
351
|
}
|
|
359
352
|
/** Run pi for this runner's authoring run in worktree `wt`: the shared per-runner wiring and
|
|
360
353
|
* the transient-failure retry, with usage folded into the run's counters. It is the ONE
|
|
361
|
-
* entry point that carries the role notes (
|
|
354
|
+
* entry point that carries the role notes (TUMWATER_NOTES_PATH,
|
|
362
355
|
* read by the `tumwater-notes` command): every other agent run (runRolePi's merge/conflict path,
|
|
363
356
|
* runMergePi, runGatePi) leaves it unset, and the director is excluded because its work is
|
|
364
357
|
* the operator's prompt, not a recurring search.
|
|
@@ -409,8 +402,8 @@ export class RunnerPi {
|
|
|
409
402
|
* abort.
|
|
410
403
|
*
|
|
411
404
|
* The follow-up continues this run's own session, so it must run on the same pair that
|
|
412
|
-
* session ran on: the caller passes the run's effective config (
|
|
413
|
-
*
|
|
405
|
+
* session ran on: the caller passes the run's effective config (a budget cap's fallback
|
|
406
|
+
* pair while a cap episode is active). A caller without one rides the role's own config. The
|
|
414
407
|
* shared per-runner wiring (runnerPiOpts) takes the follow-up's hard caps overriding the
|
|
415
408
|
* authoring run's budget: one short reply on a warm session. The agent run takes the runner's
|
|
416
409
|
* SHARED transient-failure retry (BUGS.md 2026-10-01): a 429 on the follow-up turn waits its
|
|
@@ -48,7 +48,7 @@ export async function pinAndResetPhase(ctx, wt, sha) {
|
|
|
48
48
|
await resetWorktreeToMain(wt, ctx.mainBranch);
|
|
49
49
|
return true;
|
|
50
50
|
}
|
|
51
|
-
/** End a run whose leftover recovery found work to salvage (
|
|
51
|
+
/** End a run whose leftover recovery found work to salvage (runner-leftover.ts) without an
|
|
52
52
|
* authoring run — the leftover owns the role's one merge ref until its merge resolves. A
|
|
53
53
|
* run's dequeued user prompt goes back to its queue, since nothing ran it. A pin put on
|
|
54
54
|
* the merge queue ends the run `queued` exactly like a fresh changed run (the merge-queue
|
|
@@ -39,7 +39,7 @@ export const REVISION_BACKSTOP = 6;
|
|
|
39
39
|
* sees exactly what would not converge. PLANS.md plans only, by design: the convergence hold
|
|
40
40
|
* parks a revised plan whose author hit the round cap, so only the plan role needs to clear it;
|
|
41
41
|
* a role's own too-large Needs-review note on a BUGS.md entry is the bug path (the
|
|
42
|
-
* stalledEntries
|
|
42
|
+
* stalledEntries comment in src/backlog/backlog-eligibility.ts), which the
|
|
43
43
|
* plan role resolves by splitting it. A bug that cannot converge stays with the existing re-pick
|
|
44
44
|
* behaviour (reviewer-feedback delivery, part 4/4, addresses that churn).
|
|
45
45
|
* Written through the primary worktree lock, as every other backlog write is, and then committed
|
|
@@ -6,23 +6,25 @@ import { abortSync, ensureWorktree, resetWorktreeToMain } from "../git/worktree.
|
|
|
6
6
|
import { leaseSlot } from "../git/worktree-pool.js";
|
|
7
7
|
import { slotCount } from "../config/config.js";
|
|
8
8
|
import { useWorktree } from "../git/worktree-use.js";
|
|
9
|
+
import { readPiListedModels } from "../pi/pi-listed-models.js";
|
|
9
10
|
import { branchName, worktreePath, mergeRefName, rejectedRefName } from "../paths.js";
|
|
10
11
|
import { logEventBestEffort } from "../events/events.js";
|
|
11
12
|
import { buildConflictHandbackNote, buildSalvageRestoreNote } from "../prompt/gate-prompts.js";
|
|
12
13
|
import { buildConflictDiscardNote, buildMainKnownRedNote, buildRevisionNote, buildRejectedReviewNote, } from "../prompt/gate-notes.js";
|
|
13
|
-
import { modelSelectorField, resolvedBackendConfig, reviewRunConfig, reviewedAfterMerge, scheduleConfigForRole, seamHoldKey, } from "../config/config-views.js";
|
|
14
|
-
import {
|
|
14
|
+
import { modelSelectorField, resolvedBackendConfig, reviewRunConfig, reviewedAfterMerge, scheduleConfigForRole, seamHoldKey, withModelOverrides, } from "../config/config-views.js";
|
|
15
|
+
import { imageReroute, seamImageInput } from "../agent/image-input.js";
|
|
16
|
+
import { piModelsPath, readPiProviders } from "../pi/pi-models.js";
|
|
17
|
+
import { peekPrompt, peekRolePrompt } from "../inbox/inbox.js";
|
|
15
18
|
import { planRunStart, planWithSalvagedWork, takesSalvagedWork } from "../run/run-resume.js";
|
|
16
19
|
import { restoreSalvagedWork } from "../git/slot-salvage.js";
|
|
17
20
|
import { DEFAULT_BACKEND } from "../agent/agent-backend.js";
|
|
18
21
|
import { foldAgentCommitsSince } from "../run/run-stage.js";
|
|
19
22
|
import { planBacklogFull } from "../scheduling/claims.js";
|
|
20
|
-
import { modelFallbackActive, modelFallbackProbe } from "./model-fallback.js";
|
|
21
23
|
import { finalizeRun } from "../run/run-finalize.js";
|
|
22
24
|
import { errorMessage } from "../text/text.js";
|
|
23
25
|
import { appendPromptNote, prependPromptNote } from "../text/format.js";
|
|
24
|
-
import { hasUnmergedPin, recoverLeftover } from "./leftover.js";
|
|
25
|
-
import { applyRevision, applyWithConflicts } from "./revision.js";
|
|
26
|
+
import { hasUnmergedPin, recoverLeftover } from "./runner-leftover.js";
|
|
27
|
+
import { applyRevision, applyWithConflicts } from "./runner-revision.js";
|
|
26
28
|
import { conflictedFileHunks, hasConflictMarkers, mainCommitsTouching } from "../merge/merge-git.js";
|
|
27
29
|
import { changedFiles } from "../git/git-diff.js";
|
|
28
30
|
import { handlePiResultPhase } from "./runner-outcome.js";
|
|
@@ -57,10 +59,9 @@ async function markerBearingFiles(wt) {
|
|
|
57
59
|
const files = await changedFiles(wt);
|
|
58
60
|
return files.filter((f) => hasConflictMarkers(wt, [f]));
|
|
59
61
|
}
|
|
60
|
-
/** Run one full run of a role runner: resolve the config
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* resumable and observable. */
|
|
62
|
+
/** Run one full run of a role runner: resolve the config, log the start, run the pipeline, then
|
|
63
|
+
* let finalizeRun schedule the next run. Never throws: a failed run is an "error" result,
|
|
64
|
+
* saved and logged like any other so the runner stays resumable and observable. */
|
|
64
65
|
export async function runPhase(ctx) {
|
|
65
66
|
const s = ctx.state;
|
|
66
67
|
// This role's view of the config (per-role provider/model/thinking + minRunIntervalSeconds
|
|
@@ -73,20 +74,67 @@ export async function runPhase(ctx) {
|
|
|
73
74
|
// the one home of the rule, so the scheduler that admits plan and the nextRunAt this run
|
|
74
75
|
// writes cannot disagree.
|
|
75
76
|
const planBacklogOpen = !planBacklogFull(ctx.root, ctx.config);
|
|
77
|
+
// This run's effective config: the role's own resolved config (a run always resolves its
|
|
78
|
+
// role's own config — the model-fallback episode is gone, Subtract part 5o/6) unless an image
|
|
79
|
+
// reroute below moves it. The budget gate's fallback pair, when a cap episode is active, is
|
|
80
|
+
// installed into the live config itself, so the config here already names the pair the run
|
|
81
|
+
// uses.
|
|
76
82
|
const primaryCfg = scheduleConfigForRole(ctx.config, ctx.role, planBacklogOpen);
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
let cfg = primaryCfg;
|
|
84
|
+
// Image-input reroute (plans/image-input.md part 4/5). A pending one-shot marker means
|
|
85
|
+
// THIS run is the blind-read reroute: start directly on the image-capable target the
|
|
86
|
+
// prior run recorded, over the kept worktree, in a fresh session. The marker is consumed
|
|
87
|
+
// (cleared) here so it does not route a later run; imageRerouteActive stays true for the
|
|
88
|
+
// whole run so the verdict's one-hop guard fails a second blind read instead of
|
|
89
|
+
// rerouting again. A preflight (below) sets the same active flag without a marker.
|
|
90
|
+
s.imageRerouteActive = false;
|
|
91
|
+
let preflightBlocked = false;
|
|
92
|
+
if (s.imageReroute) {
|
|
93
|
+
const r = s.imageReroute;
|
|
94
|
+
cfg = withModelOverrides(ctx.config, r.tier, {
|
|
95
|
+
backend: r.backend,
|
|
96
|
+
provider: r.provider,
|
|
97
|
+
model: r.model,
|
|
98
|
+
thinking: r.thinking,
|
|
99
|
+
});
|
|
100
|
+
// The marker is NOT cleared here: planRunStart reads its `from`/`path` when it builds
|
|
101
|
+
// the handoff note (and clears it then), so a run skipped before the plan (an empty
|
|
102
|
+
// inbox) leaves the reroute pending for the next eligible run.
|
|
103
|
+
s.imageRerouteActive = true;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Preflight (plans/image-input.md "Attachments"): a queued prompt carrying an
|
|
107
|
+
// `[image attached: …]` line needs a model that can read images BEFORE any run starts,
|
|
108
|
+
// so a text-only run hops straight to the target instead of spending a failed run
|
|
109
|
+
// learning it; with no target the run errors without spawning pi.
|
|
110
|
+
const nextPrompt = ctx.role === DIRECTOR_ROLE ? peekPrompt(ctx.root) : peekRolePrompt(ctx.root, ctx.role);
|
|
111
|
+
const carriesImages = nextPrompt !== null && /^\[image attached: .+\]$/m.test(nextPrompt);
|
|
112
|
+
if (carriesImages) {
|
|
113
|
+
const providers = readPiProviders(piModelsPath());
|
|
114
|
+
const listed = readPiListedModels(ctx.root);
|
|
115
|
+
if (seamImageInput(cfg, providers, listed) !== true) {
|
|
116
|
+
const target = imageReroute(ctx.config, ctx.role, ctx.rerouteRegime(), undefined, null, null, listed);
|
|
117
|
+
if (target !== null) {
|
|
118
|
+
cfg = target.config;
|
|
119
|
+
s.imageRerouteActive = true;
|
|
120
|
+
logEventBestEffort(ctx.root, {
|
|
121
|
+
source: ctx.role,
|
|
122
|
+
type: "image_reroute",
|
|
123
|
+
run: s.runs + 1,
|
|
124
|
+
from: primaryCfg.model,
|
|
125
|
+
to: target.config.model,
|
|
126
|
+
tier: target.tier,
|
|
127
|
+
preflight: true,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
preflightBlocked = true;
|
|
132
|
+
s.lastError =
|
|
133
|
+
`an inbox message for ${ctx.role} attaches an image, but no configured tier can read it`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
90
138
|
// Capture what this run runs on (the orchestrator's budget handback matches it against
|
|
91
139
|
// the fallback pair) and clear any stale handback flag: an abort request that lands while
|
|
92
140
|
// the runner is idle must not name a later run's resume.
|
|
@@ -122,11 +170,17 @@ export async function runPhase(ctx) {
|
|
|
122
170
|
source: ctx.role,
|
|
123
171
|
type: "run_start",
|
|
124
172
|
run: runNumber,
|
|
173
|
+
// A run routed onto the image-capable target — a blind-read handoff or an attachment
|
|
174
|
+
// preflight — is stamped so the feed shows the reroute and the model it landed on.
|
|
175
|
+
...(s.imageRerouteActive ? { reroute: "image" } : {}),
|
|
125
176
|
...modelSelectorField(cfg),
|
|
126
177
|
});
|
|
127
178
|
let outcome;
|
|
128
179
|
try {
|
|
129
|
-
|
|
180
|
+
// The preflight can end the run before any pi spawns: a text-only runner with an image
|
|
181
|
+
// attachment and no image-capable target has nothing to run, so it errors without
|
|
182
|
+
// spending a model call. Everything else runs the pipeline as usual.
|
|
183
|
+
outcome = preflightBlocked ? { result: "error" } : await ctx.runOnce(cfg);
|
|
130
184
|
}
|
|
131
185
|
catch (err) {
|
|
132
186
|
outcome = { result: "error" };
|
|
@@ -170,7 +224,7 @@ export async function runPhase(ctx) {
|
|
|
170
224
|
/** The run pipeline's start half: resume-or-fresh policy, then run in the director's own
|
|
171
225
|
* worktree or on a leased pool slot. Returns the setup's outcome; a null plan means nothing to
|
|
172
226
|
* run. */
|
|
173
|
-
export async function runOncePhase(ctx, cfg
|
|
227
|
+
export async function runOncePhase(ctx, cfg) {
|
|
174
228
|
const s = ctx.state;
|
|
175
229
|
// How this run starts — resume the interrupted session or run fresh, and which prompt —
|
|
176
230
|
// is the resume policy, not runner mechanics: it lives in src/run/run-resume.ts, which also
|
|
@@ -192,14 +246,25 @@ export async function runOncePhase(ctx, cfg, fallbackCtx) {
|
|
|
192
246
|
if (plan === null)
|
|
193
247
|
return { result: "skipped" };
|
|
194
248
|
if (ctx.role === DIRECTOR_ROLE) {
|
|
249
|
+
// The image-reroute one-shot marker routes exactly ONE run onto the image-capable
|
|
250
|
+
// target (plans/image-input.md part 4/5, INV-J2): for a role runner, planRunStart consumes
|
|
251
|
+
// it after reading its handoff-note facts. The director never resumes, so planRunStart's
|
|
252
|
+
// image-reroute branch never runs for it and the marker would otherwise linger and
|
|
253
|
+
// re-route every later director run off its own tier. It is cleared here — after this
|
|
254
|
+
// run is confirmed to run (an empty-inbox skip above returns before this, leaving the
|
|
255
|
+
// reroute pending for the next eligible run) — so the rerouted director prompt is
|
|
256
|
+
// delivered exactly once and the director is back on its own tier next.
|
|
257
|
+
// A promptless recovery run delivers no prompt, so it leaves a pending reroute for the next.
|
|
258
|
+
if (plan.prompt !== null)
|
|
259
|
+
s.imageReroute = undefined;
|
|
195
260
|
// The director keeps its dedicated worktree (it runs outside the permit, consumes its
|
|
196
261
|
// config-request file at that exact path, and never resumes). Hold it from before
|
|
197
262
|
// ensureWorktree — creating or resetting it is part of the use — to the run's end
|
|
198
263
|
// (plans/disk-floor.md, part 2/4), so a concurrent pressure reclaim never cleans a tree
|
|
199
264
|
// this run is creating or working in.
|
|
200
|
-
return useWorktree(ctx.root, worktreePath(ctx.root, ctx.role), () => runWithWorktreePhase(ctx, plan, cfg
|
|
265
|
+
return useWorktree(ctx.root, worktreePath(ctx.root, ctx.role), () => runWithWorktreePhase(ctx, plan, cfg));
|
|
201
266
|
}
|
|
202
|
-
return runOnceInLeasePhase(ctx, plan, cfg
|
|
267
|
+
return runOnceInLeasePhase(ctx, plan, cfg);
|
|
203
268
|
}
|
|
204
269
|
/** Whether a finished role run's leased slot must stay pinned for the next run's resume —
|
|
205
270
|
* the resume flag a verdict armed inside the run (`resumePending`), an abort or quiet kill,
|
|
@@ -220,7 +285,7 @@ export function slotPinnedForResume(state, outcome) {
|
|
|
220
285
|
* leases next. A run an exception ends releases its slot unsettled, so edits it leaves there
|
|
221
286
|
* are saved for the role's next run, and a run on a fresh checkout first takes back any such
|
|
222
287
|
* work an earlier run of the role left (restoreSalvagedPhase). */
|
|
223
|
-
async function runOnceInLeasePhase(ctx, plan, cfg
|
|
288
|
+
async function runOnceInLeasePhase(ctx, plan, cfg) {
|
|
224
289
|
const lease = await leaseSlot(ctx.root, {
|
|
225
290
|
role: ctx.role,
|
|
226
291
|
purpose: "run",
|
|
@@ -238,7 +303,7 @@ async function runOnceInLeasePhase(ctx, plan, cfg, fallbackCtx) {
|
|
|
238
303
|
await checkoutRoleBranchPhase(ctx, lease.dir);
|
|
239
304
|
run = await restoreSalvagedPhase(ctx, plan, lease.dir);
|
|
240
305
|
}
|
|
241
|
-
outcome = await runWithWorktreePhase(ctx, run, cfg,
|
|
306
|
+
outcome = await runWithWorktreePhase(ctx, run, cfg, lease.dir);
|
|
242
307
|
return outcome;
|
|
243
308
|
}
|
|
244
309
|
finally {
|
|
@@ -288,7 +353,7 @@ async function checkoutRoleBranchPhase(ctx, dir) {
|
|
|
288
353
|
}
|
|
289
354
|
await git(dir, "clean", "-fd");
|
|
290
355
|
}
|
|
291
|
-
async function runWithWorktreePhase(ctx, plan, cfg,
|
|
356
|
+
async function runWithWorktreePhase(ctx, plan, cfg,
|
|
292
357
|
/** The leased slot the run runs in; omitted by the director, which ensures its own. */
|
|
293
358
|
leasedDir) {
|
|
294
359
|
const s = ctx.state;
|
|
@@ -309,7 +374,7 @@ leasedDir) {
|
|
|
309
374
|
});
|
|
310
375
|
}
|
|
311
376
|
else {
|
|
312
|
-
// Salvage a commit a previous run left unmerged (
|
|
377
|
+
// Salvage a commit a previous run left unmerged (runner-leftover.ts): recovery puts it
|
|
313
378
|
// back on the merge queue, so it merges through the orchestrator's merge pipeline — the
|
|
314
379
|
// same gate as a fresh run's change, and main keeps exactly one writer (an in-run
|
|
315
380
|
// recovery merge raced the slot's batches into `merge_blocked`). A salvaged leftover
|
|
@@ -526,17 +591,14 @@ leasedDir) {
|
|
|
526
591
|
await foldAgentCommitsSince(ctx, wt, runBase);
|
|
527
592
|
// A write that landed in the primary checkout never reaches the worktree's commit, so the run
|
|
528
593
|
// can read as no_change with its work orphaned (BUGS.md, 2026-10-20). The before/after diff
|
|
529
|
-
// cannot attribute paths to this run when roles overlap, so only warn: the verdict
|
|
530
|
-
// handling
|
|
594
|
+
// cannot attribute paths to this run when roles overlap, so only warn: the verdict and
|
|
595
|
+
// resume handling below stay as they were.
|
|
531
596
|
const primaryAfter = primaryBefore === null ? null : await changedFiles(ctx.root).catch(() => null);
|
|
532
597
|
const strayed = primaryAfter === null ? [] : primaryAfter.filter((f) => !primaryBefore.includes(f));
|
|
533
598
|
if (strayed.length > 0 && !pi.aborted) {
|
|
534
599
|
const shown = strayed.slice(0, 10).join(", ") + (strayed.length > 10 ? `, … (${strayed.length} paths)` : "");
|
|
535
600
|
ctx.warn(`wrote outside the worktree (possibly another writer's): ${shown}`);
|
|
536
601
|
}
|
|
537
|
-
// Fold the authoring run's verdict into the role's model-fallback episode NOW, before the
|
|
538
|
-
// post-run handlers spend any more pi runs: only this run is evidence about the primary.
|
|
539
|
-
ctx.foldModelFallback(fallbackCtx, pi);
|
|
540
602
|
// The `qa` observer's reply ends with a result-carrying `FLOW:` line (plans/observer-roles.md
|
|
541
603
|
// 2/2). Extracted once here, recorded only at the success returns below — an interrupted or
|
|
542
604
|
// failed run must not advance the rotation. The harness records it, never pi.
|
|
@@ -139,8 +139,8 @@ export function loadRunnerState(root, role) {
|
|
|
139
139
|
r && typeof r.patchId === "string" && typeof r.sessionDir === "string" ? r : undefined;
|
|
140
140
|
}
|
|
141
141
|
// The nested scheduling records carry the same numeric class — epoch-ms stamps, recovery
|
|
142
|
-
// counts, and
|
|
143
|
-
// (`ctx.now - claim.at > CLAIM_IDLE_MAX_MS`, `prior.count + 1`, `
|
|
142
|
+
// counts, and retry/review counts — and are read by comparison or arithmetic
|
|
143
|
+
// (`ctx.now - claim.at > CLAIM_IDLE_MAX_MS`, `prior.count + 1`, `attempts + 1`), so a
|
|
144
144
|
// foreign value there is healed the same way the top-level fields are.
|
|
145
145
|
if (s.lastReview !== undefined)
|
|
146
146
|
s.lastReview = healRecord(s.lastReview, (v) => {
|
|
@@ -201,13 +201,16 @@ export function loadRunnerState(root, role) {
|
|
|
201
201
|
});
|
|
202
202
|
if (s.mergeFlakeExcused !== undefined && typeof s.mergeFlakeExcused !== "string")
|
|
203
203
|
delete s.mergeFlakeExcused;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
204
|
+
// The image-reroute marker is a one-shot nested record consumed by the next run; a torn
|
|
205
|
+
// scalar is dropped (its consumers read/mutate fields), and the tier is repaired to a
|
|
206
|
+
// known tier name so a corrupt tier cannot route a rerouted run to a phantom seam.
|
|
207
|
+
if (s.imageReroute !== undefined) {
|
|
208
|
+
const healed = healRecord(s.imageReroute, () => { });
|
|
209
|
+
s.imageReroute =
|
|
210
|
+
healed && (healed.tier === "small" || healed.tier === "default" || healed.tier === "strong")
|
|
211
|
+
? healed
|
|
212
|
+
: undefined;
|
|
213
|
+
}
|
|
211
214
|
return s;
|
|
212
215
|
}
|
|
213
216
|
/** Per-role cache of the persisted live claim, keyed by the state file's stat — one stat per
|
|
@@ -6,15 +6,12 @@ import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
|
|
|
6
6
|
import { promptPreview } from "../inbox/inbox.js";
|
|
7
7
|
import { assembleRunPrompt } from "../run/run-prompt.js";
|
|
8
8
|
import { RunnerPi } from "./runner-pi.js";
|
|
9
|
-
import { configForRole, seamHoldKey, } from "../config/config-views.js";
|
|
10
|
-
import { fallbackRoleConfig } from "../config/config-fallbacks.js";
|
|
9
|
+
import { configForRole, seamHoldKey, withModelOverrides, } from "../config/config-views.js";
|
|
11
10
|
import { PendingPrompt } from "../inbox/pending-prompt.js";
|
|
12
|
-
import { modelFallbackActive } from "./model-fallback.js";
|
|
13
11
|
import { clearBackoff } from "../scheduling/backoff.js";
|
|
14
12
|
import { RunUsage } from "../run/run-usage.js";
|
|
15
13
|
import { runOncePhase, runPhase, } from "./runner-run.js";
|
|
16
14
|
import { finishAbortedRunPhase, finishRecoveryRunPhase, pinAndResetPhase, } from "./runner-recovery.js";
|
|
17
|
-
import { foldModelFallbackPhase } from "./runner-fallback.js";
|
|
18
15
|
/** One role runner: owns a persistent worktree + branch and runs one run at a time. */
|
|
19
16
|
export class RoleRunner {
|
|
20
17
|
root;
|
|
@@ -73,6 +70,13 @@ export class RoleRunner {
|
|
|
73
70
|
* resolve to, since different roles may run on different pairs.
|
|
74
71
|
* Transient — never persisted — and captured fresh at every run start. */
|
|
75
72
|
runPair;
|
|
73
|
+
/** Whether the budget gate currently has the team on its free fallback pairs (src/gates/
|
|
74
|
+
* budget-gates.ts onFallback, set alongside `config` by the orchestrator's poll): an
|
|
75
|
+
* image reroute may then look only among the free fallback pairs, never a priced primary
|
|
76
|
+
* (plans/image-input.md "Reroute target" "Regime"). Pure scheduler input, never whether
|
|
77
|
+
* the runner itself is mid-fallback; defaults false, so standalone-runner tests stay on the
|
|
78
|
+
* primary regime unless they set it. */
|
|
79
|
+
budgetFallbackActive = false;
|
|
76
80
|
/** The merge failure this run's leftover recovery is retrying, if any (a retriable merger
|
|
77
81
|
* outcome that kept the pin, which recovery re-queued): set in finishRecoveryRun and
|
|
78
82
|
* attached to the returned RunOutcome so applyRunOutcome can feed it into the error streak
|
|
@@ -117,7 +121,6 @@ export class RoleRunner {
|
|
|
117
121
|
this.state.resumePending = true;
|
|
118
122
|
this.state.running = false;
|
|
119
123
|
this.state.parkedSince = undefined;
|
|
120
|
-
this.state.parkedOn = undefined;
|
|
121
124
|
}
|
|
122
125
|
save() {
|
|
123
126
|
saveRunnerState(this.root, this.state);
|
|
@@ -174,22 +177,39 @@ export class RoleRunner {
|
|
|
174
177
|
runModelPair() {
|
|
175
178
|
return this.state.running ? (this.runPair ?? null) : null;
|
|
176
179
|
}
|
|
177
|
-
/**
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
180
|
+
/** Which pair set an image reroute may look among (plans/image-input.md "Reroute target"
|
|
181
|
+
* "Regime"): the free fallback pairs while the budget gate is on fallback; otherwise the
|
|
182
|
+
* primary tiers. One home for the rule, shared by the target-resolution sites (the verdict's
|
|
183
|
+
* reroute arm, the attachment preflight). */
|
|
184
|
+
rerouteRegime() {
|
|
185
|
+
return this.budgetFallbackActive ? "fallback" : "primary";
|
|
186
|
+
}
|
|
187
|
+
/** The config this role's NEXT run will run on: a pending image reroute's target, else the
|
|
188
|
+
* role's own resolved config (configForRole). The hold and start passes read this so a role
|
|
189
|
+
* is keyed by the provider it will use. The budget gate's fallback pair, when a cap episode
|
|
190
|
+
* is active, is installed into the live config itself, so this read already names the pair
|
|
191
|
+
* the run uses. */
|
|
192
|
+
runConfig() {
|
|
193
|
+
// A pending image-input reroute (plans/image-input.md part 4/5) outranks the role's own
|
|
194
|
+
// tier: the next run starts on the image-capable target the blind-read run recorded,
|
|
195
|
+
// rebuilt from the stored seam over the live config.
|
|
196
|
+
if (this.state.imageReroute) {
|
|
197
|
+
const r = this.state.imageReroute;
|
|
198
|
+
return withModelOverrides(this.config, r.tier, {
|
|
199
|
+
backend: r.backend,
|
|
200
|
+
provider: r.provider,
|
|
201
|
+
model: r.model,
|
|
202
|
+
thinking: r.thinking,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
185
205
|
return configForRole(this.config, this.role);
|
|
186
206
|
}
|
|
187
|
-
/** The seam key `runConfig(
|
|
207
|
+
/** The seam key `runConfig()` resolves to (config-views.ts seamHoldKey): the agent id
|
|
188
208
|
* for a non-pi backend (claude seams carry no pi provider), else the pi provider. The hold
|
|
189
209
|
* and start passes read only this, so a claude seam is gated by a `claude` hold and pi's
|
|
190
210
|
* default seams (undefined) are not. */
|
|
191
|
-
runProvider(
|
|
192
|
-
return seamHoldKey(this.runConfig(
|
|
211
|
+
runProvider() {
|
|
212
|
+
return seamHoldKey(this.runConfig());
|
|
193
213
|
}
|
|
194
214
|
/** The signal every pi run of the current run watches: harness shutdown OR a per-run user
|
|
195
215
|
* abort (Node ≥ 20's AbortSignal.any). */
|
|
@@ -258,19 +278,15 @@ export class RoleRunner {
|
|
|
258
278
|
this.usage.fold(this.state, run, false, provider, pair);
|
|
259
279
|
}
|
|
260
280
|
/** Run one full run of this role runner (the pipeline lives in src/runner/runner-run.ts):
|
|
261
|
-
* `runPhase` resolves the
|
|
281
|
+
* `runPhase` resolves the role's own config, logs run_start, runs the pipeline,
|
|
262
282
|
* and finalizes. Never throws: a failed run is an "error" result, saved and logged. */
|
|
263
283
|
async run() {
|
|
264
284
|
return runPhase(this.runContext());
|
|
265
285
|
}
|
|
266
|
-
/** Thin delegate to foldModelFallbackPhase (src/runner/runner-fallback.ts). */
|
|
267
|
-
foldModelFallback(fallbackCtx, pi) {
|
|
268
|
-
foldModelFallbackPhase(this.runContext(), fallbackCtx, pi);
|
|
269
|
-
}
|
|
270
286
|
/** The run pipeline's entry (src/runner/runner-run.ts): a thin delegate so `run` assembles
|
|
271
287
|
* phase context once and tests can still override `runOnce` to bypass the pipeline. */
|
|
272
|
-
async runOnce(cfg
|
|
273
|
-
return runOncePhase(this.runContext(), cfg
|
|
288
|
+
async runOnce(cfg) {
|
|
289
|
+
return runOncePhase(this.runContext(), cfg);
|
|
274
290
|
}
|
|
275
291
|
/** Assemble the run pipeline's host context (src/runner/runner-run.ts) from this runner's
|
|
276
292
|
* fields. `config` and `recoveryFailure` are live getters, so a mid-run change is seen as
|
|
@@ -293,13 +309,14 @@ export class RoleRunner {
|
|
|
293
309
|
warn: (message) => this.warn(message),
|
|
294
310
|
runSignal: () => this.runSignal(),
|
|
295
311
|
runPrompt: (backend) => this.runPrompt(backend),
|
|
312
|
+
rerouteRegime: () => this.rerouteRegime(),
|
|
296
313
|
get recoveryFailure() {
|
|
297
314
|
return self.recoveryFailure;
|
|
298
315
|
},
|
|
299
316
|
setRecoveryFailure: (message) => {
|
|
300
317
|
self.recoveryFailure = message;
|
|
301
318
|
},
|
|
302
|
-
runOnce: (cfg
|
|
319
|
+
runOnce: (cfg) => this.runOnce(cfg),
|
|
303
320
|
setRunPair: (pair) => {
|
|
304
321
|
self.runPair = pair;
|
|
305
322
|
},
|
|
@@ -312,7 +329,6 @@ export class RoleRunner {
|
|
|
312
329
|
},
|
|
313
330
|
finishAbortedRun: (userPrompt, wt) => this.finishAbortedRun(userPrompt, wt),
|
|
314
331
|
pinAndReset: (wt, sha) => this.pinAndReset(wt, sha),
|
|
315
|
-
foldModelFallback: (fallbackCtx, pi) => this.foldModelFallback(fallbackCtx, pi),
|
|
316
332
|
finishRecoveryRun: (recovered, userPrompt, wt, priorMergeFailure) => this.finishRecoveryRun(recovered, userPrompt, wt, priorMergeFailure),
|
|
317
333
|
};
|
|
318
334
|
}
|
|
@@ -152,10 +152,6 @@ export function workReserveCount(root, runners, opts) {
|
|
|
152
152
|
continue;
|
|
153
153
|
if (r.state.running || opts.queuedMergeRoles.has(r.role))
|
|
154
154
|
continue;
|
|
155
|
-
// A work role whose provider is full (capped and at its cap) cannot use a reserved permit
|
|
156
|
-
// anyway, so it must not withhold one from another provider's maintenance run (§6).
|
|
157
|
-
if (opts.providerHasHeadroom !== undefined && !opts.providerHasHeadroom(r))
|
|
158
|
-
continue;
|
|
159
155
|
const eligible = eligibleFor(base);
|
|
160
156
|
const holdsClaim = r.state.claim !== undefined && eligible.has(r.state.claim.key);
|
|
161
157
|
const hasFreeEntry = [...eligible].some((k) => !(heldByBase.get(base)?.has(k) ?? false));
|