tumwater 0.4.2 → 0.4.4
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} +27 -13
- 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 +6 -27
- 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 +8 -0
- package/dist/src/agent/codex/codex-args.js +5 -5
- 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 +26 -24
- package/dist/src/agent/opencode/opencode-catalog.js +85 -36
- 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 +23 -27
- package/dist/src/agent/subscription-account.js +2 -83
- package/dist/src/backlog/backlog-archive.js +3 -4
- 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 +56 -122
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +3 -2
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/baseline/main-guardrails.js +2 -2
- package/dist/src/baseline/main-watch.js +84 -50
- package/dist/src/brief/brief.js +26 -13
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/budget.js +4 -5
- 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-report.js +10 -0
- package/dist/src/build/build-check-scoped.js +40 -18
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/build/build-stage.js +6 -4
- package/dist/src/change/change-data.js +1 -4
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-completion-command.js +75 -0
- package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
- package/dist/src/cli/cli-doctor-command.js +103 -0
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +36 -14
- package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
- package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
- package/dist/src/cli/cli-query-commands.js +2 -2
- package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-role-command.js +2 -41
- package/dist/src/cli/cli-start-summary.js +4 -3
- package/dist/src/cli/cli-start-supervisor.js +190 -0
- package/dist/src/cli/cli-start.js +94 -244
- package/dist/src/cli/completion.js +85 -49
- package/dist/src/cli/follow-view.js +20 -2
- package/dist/src/cli/help.js +6 -13
- package/dist/src/cli.js +70 -176
- package/dist/src/collections.js +39 -27
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +23 -1
- 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 +66 -40
- 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 +163 -102
- package/dist/src/config/config-schema.js +10 -12
- 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 +8 -72
- package/dist/src/config/config-validation.js +4 -39
- package/dist/src/config/config-views.js +26 -50
- package/dist/src/config/config-write.js +128 -102
- package/dist/src/config/config.js +5 -14
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +53 -120
- package/dist/src/doctor/doctor-backlog.js +16 -14
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +8 -8
- package/dist/src/doctor/doctor-claude-checks.js +70 -36
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +57 -117
- package/dist/src/doctor/doctor-fallback-checks.js +48 -26
- package/dist/src/doctor/doctor-holds.js +8 -47
- 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 +122 -21
- 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 +24 -11
- 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 +44 -64
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +3 -5
- package/dist/src/events/{notify.js → event-notify.js} +6 -6
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +8 -0
- package/dist/src/failure/failure-cluster.js +9 -18
- package/dist/src/failure/failure-data.js +12 -8
- package/dist/src/failure/failure-render.js +46 -36
- package/dist/src/failure/failure-state-change.js +54 -120
- 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 +32 -74
- 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} +9 -8
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git-diff.js +6 -2
- 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-args.js +12 -0
- 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-config-commands.js +2 -64
- package/dist/src/gui/gui-endpoint-commands.js +1 -1
- package/dist/src/gui/gui-endpoint-common.js +19 -1
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +43 -48
- package/dist/src/gui/gui-prompt-commands.js +25 -25
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/gui/gui-server.js +1 -2
- package/dist/src/gui/gui-team-commands.js +2 -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 +54 -36
- 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-check-failures.js +12 -9
- package/dist/src/merge/merge-core.js +7 -32
- package/dist/src/merge/merge-drain.js +11 -13
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +11 -14
- package/dist/src/merge/merge-queue-render.js +3 -3
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-stack.js +17 -18
- package/dist/src/merge/merge-to-main.js +13 -58
- package/dist/src/merge/merge-verify.js +12 -21
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +80 -47
- 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 +44 -69
- package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +18 -27
- package/dist/src/orchestrator/scheduling-skip.js +10 -14
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +10 -9
- 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 +28 -1
- package/dist/src/pi/pi-listed-models.js +13 -0
- 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 +41 -8
- package/dist/src/pi/pi-watchdogs.js +43 -15
- 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 +51 -26
- package/dist/src/process/process.js +20 -12
- package/dist/src/process/run-marker.js +33 -15
- 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/gate-prompts.js +1 -44
- 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/redeploy/redeployer.js +21 -1
- 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/diff-rules.js +35 -21
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +48 -41
- package/dist/src/review/review-precheck.js +14 -4
- package/dist/src/review/review.js +48 -170
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/legacy-role-migration.js +6 -3
- 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 +7 -46
- package/dist/src/roles/role-view.js +10 -33
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/ci-parity.js +19 -8
- package/dist/src/run/run-apply.js +12 -15
- 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 +141 -90
- 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 +128 -50
- package/dist/src/run/stage-check.js +60 -50
- 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/{leftover.js → runner-leftover.js} +9 -26
- package/dist/src/runner/runner-outcome.js +7 -2
- package/dist/src/runner/runner-pi.js +9 -22
- package/dist/src/runner/runner-recovery.js +5 -13
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
- package/dist/src/runner/runner-run.js +103 -50
- package/dist/src/runner/runner-state-load.js +18 -28
- package/dist/src/runner/runner-state.js +0 -14
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/backoff.js +8 -3
- package/dist/src/scheduling/claim-entries.js +2 -1
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-budget.js +4 -43
- package/dist/src/status/status-data.js +35 -39
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -18
- 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 +105 -70
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
- package/dist/src/team/team-polls.js +53 -201
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/datetime.js +16 -7
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +29 -15
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +6 -5
- package/dist/src/ui/alert-feeds.js +46 -111
- package/dist/src/ui/badges.js +17 -88
- package/dist/src/ui/gui/gui-client-boot.js +2 -8
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-feed.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-operator.js +7 -5
- 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 +12 -6
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +107 -44
- package/dist/src/ui/run-progress-model.js +20 -4
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +11 -30
- package/dist/src/ui/status-render.js +13 -24
- package/dist/src/ui/team-alert-backlog.js +14 -204
- package/dist/src/ui/team-alert-shared.js +6 -29
- 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 +8 -15
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-backlog.js +2 -2
- 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 +5 -10
- 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 +167 -98
- 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 +5 -4
- 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 -230
- package/dist/src/ui/tui/tui-pane-history.js +67 -74
- package/dist/src/ui/tui/tui-pane-pending.js +288 -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 +15 -10
- 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 +38 -33
- package/dist/src/ui/tui/tui.js +4 -7
- package/dist/src/verdict/fix-claim.js +107 -56
- package/dist/src/verdict/refusal.js +1 -1
- package/dist/src/verdict/reply-contract.js +3 -4
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/review/review-followup.js +0 -87
- package/dist/src/review/review-run.js +0 -395
- 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/cli/{brief-command.js → cli-brief-command.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
|
@@ -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";
|
|
@@ -11,7 +10,7 @@ import { dailyCost, teamDailyCost, pairDailyCost } from "../budget/budget.js";
|
|
|
11
10
|
import { loadRunnerState } from "./runner-state-load.js";
|
|
12
11
|
import { baseRoleOf, runnerIds } from "../roles/runner-ids.js";
|
|
13
12
|
import { buildStageFixPrompt } from "../prompt/gate-prompts.js";
|
|
14
|
-
import {
|
|
13
|
+
import { logBlindRead } from "../events/events.js";
|
|
15
14
|
import { nativeLogPath, piLogPath, roleNotesPath, sessionDir } from "../paths.js";
|
|
16
15
|
import { piModelsPath } from "../pi/pi-models.js";
|
|
17
16
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
@@ -116,9 +115,10 @@ export class RunnerPi {
|
|
|
116
115
|
notesPath,
|
|
117
116
|
// A tool call silent for the configured stall threshold names itself in the event feed
|
|
118
117
|
// while the quiet watchdog still counts down (BUGS.md 2026-09-13 sibling): before this,
|
|
119
|
-
// a hung command was invisible until the kill.
|
|
120
|
-
// the
|
|
121
|
-
|
|
118
|
+
// a hung command was invisible until the kill. A model request unanswered that long does
|
|
119
|
+
// the same (BUGS.md 2026-10-29). The dashboards derive their own flags from the raw log,
|
|
120
|
+
// so only the harness event needs wiring here.
|
|
121
|
+
onStallWarning: (message) => this.host.warn(message),
|
|
122
122
|
onLeftoversSwept: (message) => this.host.warn(message),
|
|
123
123
|
// The translator's unmapped-native-type and cost_unknown warnings reach the event feed
|
|
124
124
|
// as their own warning events, once per agent run.
|
|
@@ -127,14 +127,7 @@ export class RunnerPi {
|
|
|
127
127
|
// blind read"): the runner killed the authoring run (action "fail") or a gate run
|
|
128
128
|
// merely recorded it ("logged") — each logged here with this runner's identity so the
|
|
129
129
|
// feed names who, on which run, read which file on which blind model.
|
|
130
|
-
onBlindRead: (path, action) =>
|
|
131
|
-
source: this.host.role,
|
|
132
|
-
type: "image_blind",
|
|
133
|
-
run: this.host.runNumber(),
|
|
134
|
-
path,
|
|
135
|
-
model: resolved.model,
|
|
136
|
-
action,
|
|
137
|
-
}),
|
|
130
|
+
onBlindRead: (path, action) => logBlindRead(this.host.root, this.host.role, this.host.runNumber(), path, resolved.model, action),
|
|
138
131
|
};
|
|
139
132
|
}
|
|
140
133
|
/** Run the orchestrator's merge slot's pi (merge queue 3/5): the shared per-runner wiring
|
|
@@ -223,12 +216,6 @@ export class RunnerPi {
|
|
|
223
216
|
// Names the seam in per-run provider warnings; omitted when pi picks its own default model.
|
|
224
217
|
const on = opts.config.model !== undefined ? ` on ${pair}` : "";
|
|
225
218
|
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
219
|
const pi = await runPi(opts);
|
|
233
220
|
this.warnFallbackClone(pi);
|
|
234
221
|
this.observeFreeModelCost(pi, opts.config);
|
|
@@ -358,7 +345,7 @@ export class RunnerPi {
|
|
|
358
345
|
}
|
|
359
346
|
/** Run pi for this runner's authoring run in worktree `wt`: the shared per-runner wiring and
|
|
360
347
|
* the transient-failure retry, with usage folded into the run's counters. It is the ONE
|
|
361
|
-
* entry point that carries the role notes (
|
|
348
|
+
* entry point that carries the role notes (TUMWATER_NOTES_PATH,
|
|
362
349
|
* read by the `tumwater-notes` command): every other agent run (runRolePi's merge/conflict path,
|
|
363
350
|
* runMergePi, runGatePi) leaves it unset, and the director is excluded because its work is
|
|
364
351
|
* the operator's prompt, not a recurring search.
|
|
@@ -409,8 +396,8 @@ export class RunnerPi {
|
|
|
409
396
|
* abort.
|
|
410
397
|
*
|
|
411
398
|
* 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
|
-
*
|
|
399
|
+
* session ran on: the caller passes the run's effective config (a budget cap's fallback
|
|
400
|
+
* pair while a cap episode is active). A caller without one rides the role's own config. The
|
|
414
401
|
* shared per-runner wiring (runnerPiOpts) takes the follow-up's hard caps overriding the
|
|
415
402
|
* authoring run's budget: one short reply on a warm session. The agent run takes the runner's
|
|
416
403
|
* 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
|
|
@@ -56,10 +56,10 @@ export async function pinAndResetPhase(ctx, wt, sha) {
|
|
|
56
56
|
* holding the commit. When the pin is left over from a merge that failed non-terminally
|
|
57
57
|
* (`priorMergeFailure`), re-queuing it is a retry of a PERSISTENT failure the `queued` result
|
|
58
58
|
* cannot express, so it rides the outcome's recoveryFailure into the error streak and the
|
|
59
|
-
* warning that names the stuck gate (BUGS.md 2026-09-21)
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
59
|
+
* warning that names the stuck gate (BUGS.md 2026-09-21); an already-queued pin never reaches
|
|
60
|
+
* this phase (runner-run routes that arm to the authoring run). A queued entry whose branch
|
|
61
|
+
* also holds a relief commit ahead of main ends `skipped`, leaving both untouched. A leftover
|
|
62
|
+
* that could
|
|
63
63
|
* not be pinned stays on the branch and the run fails like a fresh run's failed pin. Every
|
|
64
64
|
* arm is `recoveredLeftover`: no model ran, so the run is no evidence about the backend. */
|
|
65
65
|
export async function finishRecoveryRunPhase(ctx, recovered, userPrompt, wt, priorMergeFailure) {
|
|
@@ -69,14 +69,6 @@ export async function finishRecoveryRunPhase(ctx, recovered, userPrompt, wt, pri
|
|
|
69
69
|
ctx.state.lastError = `failed to pin leftover ${shortSha(recovered.sha)} by its merge ref; left for the next run's recovery`;
|
|
70
70
|
return { result: "error", summary: ctx.state.lastError, recoveredLeftover: true };
|
|
71
71
|
}
|
|
72
|
-
if (recovered.kind === "held") {
|
|
73
|
-
// A permanent reviewer configuration error holds the pin: do not re-queue (and pay
|
|
74
|
-
// another gate check) until the model config changes. Ending `error` schedules the error
|
|
75
|
-
// ladder, so the hold is re-checked at backoff pace rather than at suite speed
|
|
76
|
-
// (BUGS.md 2026-10-06).
|
|
77
|
-
ctx.state.lastError = recovered.message;
|
|
78
|
-
return { result: "error", summary: recovered.message, recoveredLeftover: true };
|
|
79
|
-
}
|
|
80
72
|
if (recovered.kind === "main_red_wait") {
|
|
81
73
|
// The pin waits for the orchestrator poll to re-queue it once main moves; `main_red` takes
|
|
82
74
|
// the idle ladder, and the worktree stays as it is.
|
|
@@ -38,8 +38,7 @@ export const REVISION_BACKSTOP = 6;
|
|
|
38
38
|
* objections verbatim (they are already in `RunnerState.lastReview.reasons`), so the plan role
|
|
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
|
-
* a role's own too-large Needs-review note on a BUGS.md entry is the bug path
|
|
42
|
-
* stalledEntries/operatorHeldEntries comments in src/backlog/backlog-eligibility.ts), which the
|
|
41
|
+
* a role's own too-large Needs-review note on a BUGS.md entry is the bug path, which the
|
|
43
42
|
* plan role resolves by splitting it. A bug that cannot converge stays with the existing re-pick
|
|
44
43
|
* behaviour (reviewer-feedback delivery, part 4/4, addresses that churn).
|
|
45
44
|
* 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,
|
|
14
|
-
import {
|
|
14
|
+
import { modelSelectorField, resolvedBackendConfig, 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,19 +374,13 @@ 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
|
|
316
381
|
// ENDS the run: the role holds one merge ref, and the leftover owns it until its
|
|
317
382
|
// merge resolves. With nothing to salvage the branch holds nothing either, so the reset
|
|
318
383
|
// below leaves pristine main.
|
|
319
|
-
// A permanent reviewer config error — held from re-queueing — clears once the resolved
|
|
320
|
-
// reviewer selector changes, so fixing the model config recovers the pin automatically
|
|
321
|
-
// (BUGS.md 2026-10-06).
|
|
322
|
-
const reviewSelector = modelSelectorField(reviewRunConfig(ctx.config)).model;
|
|
323
|
-
if (s.mergeReviewError && s.mergeReviewError.selector !== reviewSelector)
|
|
324
|
-
s.mergeReviewError = undefined;
|
|
325
384
|
const recovered = await recoverLeftover({
|
|
326
385
|
root: ctx.root,
|
|
327
386
|
role: ctx.role,
|
|
@@ -330,7 +389,6 @@ leasedDir) {
|
|
|
330
389
|
wt,
|
|
331
390
|
mergeConflicts: s.mergeConflicts,
|
|
332
391
|
conflictHandback: s.conflictHandback,
|
|
333
|
-
mergeReviewError: s.mergeReviewError,
|
|
334
392
|
mainRedRetry: s.mainRedRetry,
|
|
335
393
|
});
|
|
336
394
|
if (recovered?.kind === "handback" && plan.prompt === null) {
|
|
@@ -361,14 +419,12 @@ leasedDir) {
|
|
|
361
419
|
s.conflictDiscard = { sha: recovered.sha, summary: recovered.summary, attempts: recovered.attempts, at: Date.now() };
|
|
362
420
|
prompt = appendPromptNote(prompt, buildConflictDiscardNote(recovered.summary, recovered.attempts));
|
|
363
421
|
}
|
|
364
|
-
else if (recovered &&
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
// other kind, `relief_wait` (a held commit ahead of main beside a queued entry) included,
|
|
371
|
-
// ends the run.
|
|
422
|
+
else if (recovered && recovered.kind !== "already_queued") {
|
|
423
|
+
// Every recovery kind ends the run here — EXCEPT `already_queued`, whose arm the
|
|
424
|
+
// scheduler admits (the interlock relief applies team-wide) and whose branch is at main:
|
|
425
|
+
// that one falls through to author below instead of ending. Every other kind,
|
|
426
|
+
// `relief_wait` (a held commit ahead of main beside a queued entry) included, ends the
|
|
427
|
+
// run.
|
|
372
428
|
return ctx.finishRecoveryRun(recovered, userPrompt, wt, priorMergeFailure);
|
|
373
429
|
}
|
|
374
430
|
await resetWorktreeToMain(wt, ctx.mainBranch);
|
|
@@ -526,17 +582,14 @@ leasedDir) {
|
|
|
526
582
|
await foldAgentCommitsSince(ctx, wt, runBase);
|
|
527
583
|
// A write that landed in the primary checkout never reaches the worktree's commit, so the run
|
|
528
584
|
// 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
|
|
585
|
+
// cannot attribute paths to this run when roles overlap, so only warn: the verdict and
|
|
586
|
+
// resume handling below stay as they were.
|
|
531
587
|
const primaryAfter = primaryBefore === null ? null : await changedFiles(ctx.root).catch(() => null);
|
|
532
588
|
const strayed = primaryAfter === null ? [] : primaryAfter.filter((f) => !primaryBefore.includes(f));
|
|
533
589
|
if (strayed.length > 0 && !pi.aborted) {
|
|
534
590
|
const shown = strayed.slice(0, 10).join(", ") + (strayed.length > 10 ? `, … (${strayed.length} paths)` : "");
|
|
535
591
|
ctx.warn(`wrote outside the worktree (possibly another writer's): ${shown}`);
|
|
536
592
|
}
|
|
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
593
|
// The `qa` observer's reply ends with a result-carrying `FLOW:` line (plans/observer-roles.md
|
|
541
594
|
// 2/2). Extracted once here, recorded only at the success returns below — an interrupted or
|
|
542
595
|
// failed run must not advance the rotation. The harness records it, never pi.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { freshRunnerState, SKIP_REASONS } from "./runner-state.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { dirEntryNames } from "../files/files.js";
|
|
4
4
|
import { readJsonFile } from "../files/json-files.js";
|
|
@@ -58,13 +58,17 @@ const LEGACY_STATE_KEYS = {
|
|
|
58
58
|
lastTickStartedAt: "lastRunStartedAt", // retired-name-ok: legacy name
|
|
59
59
|
lastTickEndedAt: "lastRunEndedAt", // retired-name-ok: legacy name
|
|
60
60
|
// retired-name-ok:start old state keys
|
|
61
|
-
landingReviewError: "mergeReviewError", // vocabulary-ok: legacy state key read from old files
|
|
62
61
|
landingCheckFailures: "mergeCheckFailures", // vocabulary-ok: legacy state key read from old files
|
|
63
62
|
lastLandedAt: "lastMergedAt", // vocabulary-ok: legacy state key read from old files
|
|
64
63
|
// retired-name-ok:end
|
|
65
64
|
};
|
|
66
65
|
/** Apply LEGACY_STATE_KEYS to a parsed state object in place. */
|
|
67
66
|
function migrateLegacyStateKeys(saved) {
|
|
67
|
+
// The pre-merge reviewer-config hold is gone (part 3j/3): a state file may still name it under
|
|
68
|
+
// either its old (landingReviewError) or its recent (mergeReviewError) key, and with no field
|
|
69
|
+
// to load it into — no reader consumes it — drop both here so a later save writes neither.
|
|
70
|
+
delete saved.landingReviewError; // retired-name-ok: legacy state key read from old files
|
|
71
|
+
delete saved.mergeReviewError;
|
|
68
72
|
for (const [oldKey, newKey] of Object.entries(LEGACY_STATE_KEYS)) {
|
|
69
73
|
if (!(oldKey in saved))
|
|
70
74
|
continue;
|
|
@@ -121,26 +125,9 @@ export function loadRunnerState(root, role) {
|
|
|
121
125
|
healOptionalNumber(s, "parkedSince");
|
|
122
126
|
if (s.skipReason !== undefined && !SKIP_REASONS.includes(s.skipReason))
|
|
123
127
|
delete s.skipReason;
|
|
124
|
-
// The one-id field an older state file carries folds into the list.
|
|
125
|
-
const legacy = saved?.lastApprovedPatchId;
|
|
126
|
-
delete s.lastApprovedPatchId;
|
|
127
|
-
if (s.approvedPatchIds !== undefined || typeof legacy === "string") {
|
|
128
|
-
const ids = [...(Array.isArray(s.approvedPatchIds) ? s.approvedPatchIds : []), ...(typeof legacy === "string" ? [legacy] : [])];
|
|
129
|
-
s.approvedPatchIds = ids.filter((id) => typeof id === "string").slice(-APPROVED_PATCH_IDS_MAX);
|
|
130
|
-
}
|
|
131
|
-
if (s.reviewResume !== undefined) {
|
|
132
|
-
const r = healRecord(s.reviewResume, (v) => {
|
|
133
|
-
v.attempts = healNumber(v.attempts, 0);
|
|
134
|
-
v.at = healNumber(v.at, 0);
|
|
135
|
-
});
|
|
136
|
-
// A marker without the two strings it keys on names no session to resume, so a malformed
|
|
137
|
-
// one is dropped here rather than left to throw in sessionResumable.
|
|
138
|
-
s.reviewResume =
|
|
139
|
-
r && typeof r.patchId === "string" && typeof r.sessionDir === "string" ? r : undefined;
|
|
140
|
-
}
|
|
141
128
|
// 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`, `
|
|
129
|
+
// counts, and retry/review counts — and are read by comparison or arithmetic
|
|
130
|
+
// (`ctx.now - claim.at > CLAIM_IDLE_MAX_MS`, `prior.count + 1`, `attempts + 1`), so a
|
|
144
131
|
// foreign value there is healed the same way the top-level fields are.
|
|
145
132
|
if (s.lastReview !== undefined)
|
|
146
133
|
s.lastReview = healRecord(s.lastReview, (v) => {
|
|
@@ -201,13 +188,16 @@ export function loadRunnerState(root, role) {
|
|
|
201
188
|
});
|
|
202
189
|
if (s.mergeFlakeExcused !== undefined && typeof s.mergeFlakeExcused !== "string")
|
|
203
190
|
delete s.mergeFlakeExcused;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
191
|
+
// The image-reroute marker is a one-shot nested record consumed by the next run; a torn
|
|
192
|
+
// scalar is dropped (its consumers read/mutate fields), and the tier is repaired to a
|
|
193
|
+
// known tier name so a corrupt tier cannot route a rerouted run to a phantom seam.
|
|
194
|
+
if (s.imageReroute !== undefined) {
|
|
195
|
+
const healed = healRecord(s.imageReroute, () => { });
|
|
196
|
+
s.imageReroute =
|
|
197
|
+
healed && (healed.tier === "small" || healed.tier === "default" || healed.tier === "strong")
|
|
198
|
+
? healed
|
|
199
|
+
: undefined;
|
|
200
|
+
}
|
|
211
201
|
return s;
|
|
212
202
|
}
|
|
213
203
|
/** Per-role cache of the persisted live claim, keyed by the state file's stat — one stat per
|
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
import { writeJsonAtomic } from "../files/json-files.js";
|
|
2
2
|
import { statePath } from "../paths.js";
|
|
3
3
|
import { tryOrWarn } from "../events/events.js";
|
|
4
|
-
/** The runner's persisted state file — one JSON object per role under .tumwater/ — and the
|
|
5
|
-
* observation-window counter reset: fresh-state defaults, the atomic save, and zeroCounters
|
|
6
|
-
* (the tolerant load and the live-claim scan are runner-state-load.ts). The scheduling POLICY
|
|
7
|
-
* that mutates this state from run outcomes and operator wakes — the outcome application and
|
|
8
|
-
* resume limits in run-apply.ts, the clock arithmetic (backoff ladders, wakes, yield ring) in
|
|
9
|
-
* backoff.ts; the generic file helpers are src/files/files.ts and the JSON convention itself is
|
|
10
|
-
* src/files/json-files.ts. */
|
|
11
|
-
/** How many times a progressing-timeout review may be resumed before the next attempt
|
|
12
|
-
* reviews the change from scratch (RunnerState.reviewResume). The budget bounds a reviewer that
|
|
13
|
-
* keeps timing out mid-progress, so a wedged review converges to a fresh run instead of
|
|
14
|
-
* resuming the same session forever. */
|
|
15
|
-
export const REVIEW_RESUME_LIMIT = 2;
|
|
16
|
-
/** Approved patch-ids a runner remembers (RunnerState.approvedPatchIds). */
|
|
17
|
-
export const APPROVED_PATCH_IDS_MAX = 8;
|
|
18
4
|
export const SKIP_REASONS = [
|
|
19
5
|
"no-free-entry",
|
|
20
6
|
"claim-hold",
|