tumwater 0.1.3 → 0.3.0
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 +77 -28
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +23 -0
- package/dist/src/agent/agent-backend.js +9 -7
- package/dist/src/agent/backend-traits.js +77 -0
- package/dist/src/agent/claude/claude-account.js +81 -161
- package/dist/src/agent/claude/claude-args.js +19 -19
- package/dist/src/agent/claude/claude-backend.js +58 -48
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +11 -19
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +112 -0
- package/dist/src/agent/notes-shim.js +40 -0
- package/dist/src/agent/opencode/opencode-args.js +60 -0
- package/dist/src/agent/opencode/opencode-backend.js +258 -0
- package/dist/src/agent/opencode/opencode-catalog.js +210 -0
- package/dist/src/agent/opencode/opencode-config.js +193 -0
- package/dist/src/agent/opencode/opencode-env.js +35 -0
- package/dist/src/agent/opencode/opencode-session-id.js +36 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +16 -33
- package/dist/src/agent/run-agent.js +475 -381
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +142 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +487 -0
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +384 -0
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +354 -132
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +132 -22
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +89 -11
- package/dist/src/backlog/backlog-structure.js +271 -125
- package/dist/src/backlog/backlog-write.js +121 -471
- package/dist/src/backlog/backlog.js +129 -17
- package/dist/src/backlog/question-answer.js +345 -0
- package/dist/src/baseline/main-baseline.js +157 -101
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +164 -55
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +41 -0
- package/dist/src/{brief.js → brief/brief.js} +38 -33
- package/dist/src/budget/budget.js +55 -28
- package/dist/src/budget/fallback-breaker.js +31 -31
- package/dist/src/build/build-check-detect.js +25 -16
- package/dist/src/build/build-check-events.js +87 -22
- package/dist/src/build/build-check-report.js +66 -27
- package/dist/src/build/build-check-scoped.js +64 -68
- package/dist/src/build/build-check.js +17 -15
- package/dist/src/build/build-info.js +3 -3
- package/dist/src/build/build-stage.js +31 -35
- package/dist/src/build/dep-install.js +21 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +131 -37
- package/dist/src/change/change-render.js +56 -14
- package/dist/src/cli/brief-command.js +9 -8
- package/dist/src/cli/cli-args.js +133 -28
- package/dist/src/cli/cli-command-args.js +82 -21
- package/dist/src/cli/cli-editor.js +15 -2
- package/dist/src/cli/cli-flag-specs.js +180 -50
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +14 -11
- package/dist/src/cli/cli-query-commands.js +325 -224
- package/dist/src/cli/cli-repo-gate.js +12 -2
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/cli-start.js +453 -0
- package/dist/src/cli/completion.js +202 -17
- package/dist/src/cli/config-commands.js +51 -25
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +413 -157
- package/dist/src/cli/log-commands.js +234 -62
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +215 -231
- package/dist/src/cli.js +154 -49
- package/dist/src/collections.js +84 -18
- package/dist/src/concurrency/check-permit.js +35 -19
- package/dist/src/concurrency/lock.js +22 -17
- package/dist/src/concurrency/provider-lease.js +105 -0
- package/dist/src/concurrency/semaphore.js +344 -46
- package/dist/src/config/config-aliases.js +105 -0
- package/dist/src/config/config-editable-keys.js +16 -4
- package/dist/src/config/config-fallbacks.js +416 -0
- package/dist/src/config/config-field-checks.js +58 -19
- package/dist/src/config/config-keys.js +182 -0
- package/dist/src/config/config-live.js +113 -22
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +58 -39
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +201 -175
- package/dist/src/config/config-views.js +202 -346
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +219 -391
- package/dist/src/config/config.js +102 -81
- package/dist/src/config/model-selector.js +44 -18
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +191 -161
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +229 -305
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +181 -104
- package/dist/src/doctor/doctor-install.js +31 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +496 -270
- package/dist/src/doctor/doctor-notify.js +5 -7
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +189 -69
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +145 -50
- package/dist/src/doctor/image-probe.js +257 -0
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +60 -0
- package/dist/src/events/event-format.js +363 -155
- package/dist/src/events/event-labels.js +123 -0
- package/dist/src/events/event-legacy.js +164 -0
- package/dist/src/events/event-read.js +74 -38
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +83 -27
- package/dist/src/events/events.js +47 -26
- package/dist/src/events/notify.js +85 -35
- package/dist/src/failure/failure-cluster.js +23 -20
- package/dist/src/failure/failure-data.js +140 -92
- package/dist/src/failure/failure-render.js +97 -78
- package/dist/src/failure/failure-state-change.js +45 -33
- package/dist/src/failure/time-spend.js +166 -82
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +129 -41
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +45 -9
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +2 -2
- package/dist/src/gates/bootstrap-gates.js +8 -8
- package/dist/src/gates/budget-gates.js +361 -52
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +201 -103
- package/dist/src/gates/maintenance-quota.js +34 -37
- package/dist/src/gates/pause-gates.js +33 -15
- package/dist/src/gates/readiness.js +8 -3
- package/dist/src/gates/role-cap-gates.js +23 -28
- package/dist/src/gates/startup-gate.js +154 -48
- package/dist/src/gates/streak-gate.js +67 -41
- package/dist/src/git/commit-message.js +80 -48
- package/dist/src/git/git-diff.js +29 -23
- package/dist/src/git/git-run.js +87 -46
- package/dist/src/git/git.js +114 -92
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +18 -9
- package/dist/src/git/worktree-pool.js +150 -49
- package/dist/src/git/worktree-use.js +14 -20
- package/dist/src/git/worktree.js +108 -51
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +88 -21
- package/dist/src/gui/gui-backlog-commands.js +297 -0
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +9 -14
- package/dist/src/gui/gui-config-commands.js +462 -0
- package/dist/src/gui/gui-endpoint-commands.js +17 -716
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +13 -331
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +240 -0
- package/dist/src/gui/gui-prompt-commands.js +231 -0
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +33 -9
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +166 -203
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +96 -0
- package/dist/src/history/history.js +206 -103
- package/dist/src/inbox/inbox-attachments.js +94 -39
- package/dist/src/inbox/inbox-cancel.js +16 -15
- package/dist/src/inbox/inbox-edit.js +10 -10
- package/dist/src/inbox/inbox-submit.js +54 -14
- package/dist/src/inbox/inbox-top.js +99 -0
- package/dist/src/inbox/inbox.js +49 -44
- package/dist/src/inbox/pending-prompt.js +27 -27
- package/dist/src/inbox/prompt-commands.js +154 -100
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +15 -14
- package/dist/src/init/init.js +35 -29
- package/dist/src/merge/backlog-conflicts.js +335 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +100 -93
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +209 -0
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +183 -110
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +6 -5
- package/dist/src/merge/merge-drain.js +351 -0
- package/dist/src/merge/merge-git.js +403 -0
- package/dist/src/merge/merge-pipeline.js +111 -0
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +113 -0
- package/dist/src/merge/merge-queue.js +224 -0
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +385 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +97 -71
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +151 -82
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +214 -0
- package/dist/src/notes-cli.js +112 -0
- package/dist/src/operator/operator-commands.js +268 -89
- package/dist/src/operator/operator-intent.js +130 -82
- package/dist/src/operator/operator-requests.js +82 -87
- package/dist/src/operator/retire.js +49 -31
- package/dist/src/operator/revert.js +179 -0
- package/dist/src/orchestrator/orchestrator-launch.js +116 -48
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +78 -348
- package/dist/src/orchestrator/orchestrator.js +173 -361
- package/dist/src/orchestrator/retention.js +66 -15
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +52 -0
- package/dist/src/orchestrator/startup-migrations.js +44 -0
- package/dist/src/paths.js +228 -75
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +30 -5
- package/dist/src/pi/pi-backend.js +19 -27
- package/dist/src/pi/pi-bin.js +64 -67
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +163 -42
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +137 -113
- package/dist/src/pi/pi-watchdogs.js +20 -20
- package/dist/src/pi-extension/bounded-output.js +25 -8
- package/dist/src/pi-extension/context-budget.js +5 -3
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +36 -0
- package/dist/src/pi-extension/full-output.js +5 -12
- package/dist/src/pi-extension/strip-thinking.js +79 -0
- package/dist/src/pi-extension/trim-system-prompt.js +37 -0
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +11 -11
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +48 -9
- package/dist/src/process/run-marker.js +42 -7
- package/dist/src/process/supervisor.js +47 -35
- package/dist/src/prompt/dialect.js +46 -11
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +201 -188
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -1
- package/dist/src/prompt/prompt-followup.js +43 -29
- package/dist/src/prompt/prompt.js +255 -172
- package/dist/src/redeploy/redeploy-policy.js +29 -24
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +7 -6
- package/dist/src/redeploy/redeployer.js +78 -70
- package/dist/src/redeploy/self-reload.js +3 -2
- package/dist/src/report/report-data.js +374 -285
- package/dist/src/report/report-fold.js +430 -0
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +321 -61
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +133 -14
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +7 -7
- package/dist/src/review/review-followup.js +21 -9
- package/dist/src/review/review-precheck.js +77 -33
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +16 -2
- package/dist/src/review/review.js +172 -356
- package/dist/src/review/secret-leaks.js +147 -0
- package/dist/src/review/suite-rerun.js +3 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +135 -0
- package/dist/src/roles/role-catalog.js +172 -137
- package/dist/src/roles/role-guidance.js +112 -44
- package/dist/src/roles/role-labels.js +9 -0
- package/dist/src/roles/role-render.js +221 -28
- package/dist/src/roles/role-view.js +244 -33
- package/dist/src/roles/roles.js +37 -35
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +10 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +166 -136
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/run/run-finalize.js +154 -0
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -6
- package/dist/src/run/run-prompt.js +304 -0
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +80 -35
- package/dist/src/run/run-stage.js +295 -0
- package/dist/src/run/run-timing.js +198 -0
- package/dist/src/run/run-usage.js +180 -0
- package/dist/src/run/run-verdict.js +301 -0
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/runner/leftover.js +185 -0
- package/dist/src/{loop → runner}/model-fallback.js +13 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +10 -10
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/runner/runner-pi.js +433 -0
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +34 -28
- package/dist/src/runner/runner-run.js +625 -0
- package/dist/src/runner/runner-state-load.js +268 -0
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/runner/runner.js +350 -0
- package/dist/src/scheduling/backoff.js +64 -54
- package/dist/src/scheduling/claims.js +46 -26
- package/dist/src/scheduling/once-round.js +30 -30
- package/dist/src/scheduling/quiet-hours.js +26 -34
- package/dist/src/scheduling/scheduling.js +87 -74
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +123 -0
- package/dist/src/status/status-data.js +106 -191
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +26 -0
- package/dist/src/status/status-polls.js +70 -59
- package/dist/src/status/status-roles.js +131 -0
- package/dist/src/{fleet → team}/error-storm.js +16 -16
- package/dist/src/{fleet → team}/failure-spread.js +10 -10
- package/dist/src/{fleet → team}/orchestrator-info.js +97 -25
- package/dist/src/{fleet → team}/reclaim.js +9 -9
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +60 -26
- package/dist/src/team/team-polls.js +299 -0
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +47 -40
- package/dist/src/text/datetime.js +102 -16
- package/dist/src/text/format.js +49 -3
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +89 -54
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +68 -14
- package/dist/src/ui/badges.js +165 -93
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +221 -42
- package/dist/src/ui/gui/gui-client-composer.js +105 -27
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +226 -130
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +492 -68
- package/dist/src/ui/gui/gui-client-labels.js +23 -0
- package/dist/src/ui/gui/gui-client-markdown.js +1 -1
- package/dist/src/ui/gui/gui-client-model.js +84 -76
- package/dist/src/ui/gui/gui-client-operator.js +112 -78
- package/dist/src/ui/gui/gui-client-pending.js +248 -41
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +122 -179
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +118 -76
- package/dist/src/ui/gui/gui-client-settings.js +180 -20
- package/dist/src/ui/gui/gui-client-team.js +252 -0
- package/dist/src/ui/gui/gui-client.js +48 -40
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +25 -24
- package/dist/src/ui/gui/gui-styles.js +46 -38
- package/dist/src/ui/progress-data.js +11 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +40 -40
- package/dist/src/ui/status-model.js +276 -194
- package/dist/src/ui/status-payload.js +227 -62
- package/dist/src/ui/status-render.js +196 -108
- package/dist/src/ui/team-alerts.js +532 -0
- package/dist/src/ui/tone.js +11 -11
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -19
- package/dist/src/ui/transcript.js +85 -38
- package/dist/src/ui/tui/tui-backlog.js +132 -21
- package/dist/src/ui/tui/tui-editor-answer.js +93 -0
- package/dist/src/ui/tui/tui-editor-attach.js +105 -0
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +105 -0
- package/dist/src/ui/tui/tui-editor-budget.js +159 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +219 -0
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +124 -0
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +112 -0
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +255 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +90 -0
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +94 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +170 -0
- package/dist/src/ui/tui/tui-editor-retire.js +81 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +85 -0
- package/dist/src/ui/tui/tui-editor-session.js +123 -0
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +185 -0
- package/dist/src/ui/tui/tui-frame.js +107 -58
- package/dist/src/ui/tui/tui-history.js +16 -12
- package/dist/src/ui/tui/tui-input.js +56 -60
- package/dist/src/ui/tui/tui-keys.js +106 -1825
- package/dist/src/ui/tui/tui-pane-activity.js +102 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +269 -0
- package/dist/src/ui/tui/tui-pane-brief.js +29 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +174 -0
- package/dist/src/ui/tui/tui-pane-history.js +636 -0
- package/dist/src/ui/tui/tui-pane-pending.js +369 -0
- package/dist/src/ui/tui/tui-pane-queued.js +159 -0
- package/dist/src/ui/tui/tui-pane-report.js +154 -0
- package/dist/src/ui/tui/tui-pane-session.js +120 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +224 -0
- package/dist/src/ui/tui/tui-pane.js +125 -44
- package/dist/src/ui/tui/tui-panes.js +364 -0
- package/dist/src/ui/tui/tui.js +52 -34
- package/dist/src/verdict/fix-claim.js +135 -62
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +80 -24
- package/dist/src/verdict/reply-contract.js +51 -28
- package/dist/src/version.js +2 -2
- package/package.json +5 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/brief-edit.js +0 -42
- package/dist/src/cli/cli-run.js +0 -469
- package/dist/src/fleet/fleet-polls.js +0 -207
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-check-failures.js +0 -141
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-git.js +0 -190
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -56
- package/dist/src/landing/landing-queue.js +0 -149
- package/dist/src/landing/landing-slot.js +0 -256
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/loop/leftover.js +0 -129
- package/dist/src/loop/loop-pi.js +0 -370
- package/dist/src/loop/loop-state.js +0 -204
- package/dist/src/loop/loop-tick.js +0 -545
- package/dist/src/loop/loop.js +0 -315
- package/dist/src/loop/revision.js +0 -61
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/roles/loop-ids.js +0 -60
- package/dist/src/tick/stage-check.js +0 -302
- package/dist/src/tick/telemetry-digest.js +0 -24
- package/dist/src/tick/tick-detail-data.js +0 -107
- package/dist/src/tick/tick-detail.js +0 -150
- package/dist/src/tick/tick-finalize.js +0 -135
- package/dist/src/tick/tick-prompt.js +0 -201
- package/dist/src/tick/tick-stage.js +0 -223
- package/dist/src/tick/tick-timing.js +0 -195
- package/dist/src/tick/tick-usage.js +0 -142
- package/dist/src/tick/tick-verdict.js +0 -201
- package/dist/src/ui/fleet-alerts.js +0 -174
- package/dist/src/ui/gui/gui-client-fleet.js +0 -576
- package/dist/src/ui/gui/gui-client-sound.js +0 -52
|
@@ -1,66 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readOrchestratorInfo, writeOrchestratorInfo } from "../team/orchestrator-info.js";
|
|
2
|
+
import { runnerIds } from "../roles/runner-ids.js";
|
|
3
3
|
import { newLiveConfigReload } from "../config/config-live.js";
|
|
4
|
-
import { FALLBACK_BREAKER_POLICY,
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { eligibleEntries } from "../backlog/backlog-eligibility.js";
|
|
12
|
-
import { LoopRunner } from "../loop/loop.js";
|
|
13
|
-
import { branchHead, currentBranch } from "../git/git.js";
|
|
14
|
-
import { removeLegacyLandWorktrees } from "../git/worktree.js";
|
|
15
|
-
import { retireLegacyRoleWorktrees } from "../git/worktree-pool.js";
|
|
16
|
-
import { queuedLandingFiles, queuedLandingRoles } from "../landing/landing-queue.js";
|
|
17
|
-
import { drainLandings, settleAbortedVetted } from "../landing/landing-drain.js";
|
|
18
|
-
import { abortableLandings, landingTasks, newLandingPipeline } from "../landing/landing-pipeline.js";
|
|
19
|
-
import { bestEffortEventLogger, logEventBestEffort } from "../events/events.js";
|
|
4
|
+
import { FALLBACK_BREAKER_POLICY, } from "../budget/fallback-breaker.js";
|
|
5
|
+
import { newPollState, pollGatesAndHolds, pollMergeDrain, pollRedeploy, pollRequestsAndWatch, pollReserveAndExit, pollSchedule, } from "./orchestrator-poll.js";
|
|
6
|
+
import { RoleRunner } from "../runner/runner.js";
|
|
7
|
+
import { dropDanglingTumwaterPins, removeLegacyLandWorktrees } from "../git/worktree.js";
|
|
8
|
+
import { pinCrashedRuns, retireLegacyRoleWorktrees } from "../git/worktree-pool.js";
|
|
9
|
+
import { mergeTasks, newMergePipeline } from "../merge/merge-pipeline.js";
|
|
10
|
+
import { bestEffortEventLogger, logEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
20
11
|
import { removeQuiet } from "../files/files.js";
|
|
21
12
|
import { OnceRound } from "../scheduling/once-round.js";
|
|
22
13
|
import { newNotifier } from "../events/notify.js";
|
|
23
|
-
import { consumeAbortRequests, consumeReclaimRequest, consumeRestartRequest, consumeResetRequest, consumeWakeRequest, } from "../operator/operator-requests.js";
|
|
24
14
|
import { piModelsPath } from "../pi/pi-models.js";
|
|
15
|
+
import { piSettingsPath, readPiDefaultModel } from "../pi/pi-settings.js";
|
|
25
16
|
import { Semaphore } from "../concurrency/semaphore.js";
|
|
26
17
|
import { orchestratorStatePath } from "../paths.js";
|
|
18
|
+
import { createMainWatch } from "../baseline/main-watch.js";
|
|
19
|
+
import { routeMainCulprit } from "../baseline/main-revert.js";
|
|
27
20
|
import { RetentionPruner } from "./retention.js";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
21
|
+
import { newOpencodeCatalogRefresh } from "../gates/startup-gate.js";
|
|
22
|
+
import { WorkMergedCache } from "../scheduling/work-merged-cache.js";
|
|
23
|
+
import { drainInFlightWork, HANDOFF_MERGE_WINDOW_MS, sleepInterruptible, } from "../run/run-timing.js";
|
|
24
|
+
import { newTeamGateStates } from "../gates/gate-polls.js";
|
|
25
|
+
import { loggedWindowKeys } from "../team/team-polls.js";
|
|
26
|
+
import { ReclaimController } from "../team/reclaim.js";
|
|
27
|
+
import { reapPreviousTeamOrphans } from "../doctor/doctor-orphans.js";
|
|
28
|
+
import { runStartupMigrations } from "./startup-migration-steps.js";
|
|
29
|
+
import { archiveAtTeamStart } from "../backlog/backlog-archive.js";
|
|
34
30
|
const POLL_MS = 2000;
|
|
35
|
-
/** Run all enabled
|
|
36
|
-
* the
|
|
31
|
+
/** Run all enabled roles until the signal aborts — or until a pending self-redeploy has drained
|
|
32
|
+
* the team and swapped the new build into dist/ (then `restart` is true). */
|
|
37
33
|
export async function runOrchestrator(opts) {
|
|
38
34
|
const { root, config, mainBranch, signal: externalSignal } = opts;
|
|
39
35
|
const pollMs = opts.pollMs ?? POLL_MS;
|
|
40
36
|
const modelsPath = opts.modelsPath ?? piModelsPath();
|
|
37
|
+
// pi's saved default model — the same stat-cached read status-roles.ts makes — used as the
|
|
38
|
+
// fallback provider key for a pi seam that names no model (plans/provider-concurrency.md §2).
|
|
39
|
+
const piDefault = readPiDefaultModel(piSettingsPath(modelsPath), root);
|
|
41
40
|
const breakerPolicy = opts.fallbackBreakerPolicy ?? FALLBACK_BREAKER_POLICY;
|
|
42
41
|
const breakerNow = opts.breakerNow ?? Date.now;
|
|
43
|
-
// Parallel work instances (part 5b/7): the runner list is every
|
|
42
|
+
// Parallel work instances (part 5b/7): the runner list is every runner id the config enables
|
|
44
43
|
// (`feature`, `feature-2`, …), so `instances` creates real runners. A `--once --role` filter
|
|
45
|
-
// targets exactly that
|
|
46
|
-
const enabled = opts.roleFilter !== undefined ? [opts.roleFilter] :
|
|
44
|
+
// targets exactly that runner id.
|
|
45
|
+
const enabled = opts.roleFilter !== undefined ? [...opts.roleFilter] : runnerIds(config);
|
|
47
46
|
// Name the fix, not just the failure: an operator who disabled the last role (or hand-edited
|
|
48
|
-
// a roles map to all-false) gets the exact edit that unblocks `tumwater
|
|
49
|
-
// defaults they can fall back to. With a role filter this is unreachable —
|
|
47
|
+
// a roles map to all-false) gets the exact edit that unblocks `tumwater start`, and the
|
|
48
|
+
// defaults they can fall back to. With a role filter this is unreachable — cmdStart validated
|
|
50
49
|
// the id against the enabled set before booting — but the guard stays for in-process callers.
|
|
51
50
|
if (enabled.length === 0)
|
|
52
51
|
throw new Error('no roles enabled in tumwater.json — enable at least one role in its "roles" section (e.g. `"feature": { "enabled": true }`), or remove that section to restore every role\'s default');
|
|
53
52
|
// Two internal stop controllers ride beside the caller's (Ctrl+C/SIGTERM): internalStop, the
|
|
54
53
|
// graceful shutdown the once-mode exit and the hand-off's deadline abort fire — it cuts role
|
|
55
|
-
//
|
|
56
|
-
// gives up on its permit-holding ROLE
|
|
54
|
+
// runs and merges alike — and internalRoleStop, which ONLY the restart drain fires when it
|
|
55
|
+
// gives up on its permit-holding ROLE runs (they then end as `aborted`, resumable on the new
|
|
57
56
|
// build, exactly like a shutdown). The split is the point: the drain's abort must not cut off
|
|
58
|
-
// an in-flight
|
|
59
|
-
// (BUGS.md 2026-09-30: the
|
|
60
|
-
//
|
|
61
|
-
// tasks keep wiring the full signal alone. A director
|
|
62
|
-
// controller: poll holds for it without a cap, so by the time `restart`
|
|
63
|
-
// and the
|
|
57
|
+
// an in-flight merge, which then never reaches the hand-off window the wait announces
|
|
58
|
+
// (BUGS.md 2026-09-30: the merge died 13 ms after the warning). So the runners and the timed
|
|
59
|
+
// run wrapper watch roleSignal — the full signal PLUS the role-only stop — while the merge
|
|
60
|
+
// tasks keep wiring the full signal alone. A director run is never aborted by either
|
|
61
|
+
// controller: poll holds for it without a cap, so by the time `restart` takes effect only
|
|
62
|
+
// role runs and the merge can remain.
|
|
64
63
|
const internalStop = new AbortController();
|
|
65
64
|
const internalRoleStop = new AbortController();
|
|
66
65
|
const signal = AbortSignal.any([externalSignal, internalStop.signal]);
|
|
@@ -72,399 +71,212 @@ export async function runOrchestrator(opts) {
|
|
|
72
71
|
const launchServicesWatch = opts.once ? null : (opts.launchServicesWatch ?? null);
|
|
73
72
|
// Pressure reclaim (plans/disk-floor.md, part 2/4): one background pass at a time, driven by
|
|
74
73
|
// the disk gate's poll below. In-process state (the use registry) is enough, because this
|
|
75
|
-
// process is the one that runs
|
|
74
|
+
// process is the one that starts runs and merges.
|
|
76
75
|
const reclaim = new ReclaimController(root);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
// resolver — landing-drain.ts) all hold a permit; see its LandingPipelineContext for why a
|
|
81
|
-
// landing is not exempt.
|
|
76
|
+
// The shared concurrency cap: role runs and the merges (each verify, and the merge's conflict
|
|
77
|
+
// resolver — merge-drain.ts) all hold a permit; see its MergePipelineContext for why a
|
|
78
|
+
// merge is not exempt.
|
|
82
79
|
const semaphore = new Semaphore(Math.max(1, config.maxConcurrent));
|
|
80
|
+
// Seed the pool's per-provider caps from the startup config (plans/provider-concurrency.md
|
|
81
|
+
// §7): a role run's lease is bounded by its provider key from the first launch onward, not
|
|
82
|
+
// just after a live edit. A config without the key passes an empty map, the no-change case.
|
|
83
|
+
semaphore.setKeyCaps(config.maxConcurrentPerProvider ?? {});
|
|
83
84
|
const infoFile = orchestratorStatePath(root);
|
|
84
85
|
const info = { pid: process.pid, startedAt: Date.now(), roles: enabled };
|
|
85
86
|
if (redeploy)
|
|
86
87
|
info.build = redeploy.status();
|
|
87
88
|
if (opts.stopsAt !== undefined)
|
|
88
89
|
info.stopsAt = opts.stopsAt;
|
|
90
|
+
const previous = readOrchestratorInfo(root);
|
|
89
91
|
writeOrchestratorInfo(root, info);
|
|
90
92
|
// Best-effort, like every other event write in this function (see `emit` below): this runs
|
|
91
93
|
// BEFORE the try/finally and its cleanup, so a raw logEvent throw here would not just stop
|
|
92
|
-
// the
|
|
94
|
+
// the team from starting over an informational event — it would also skip the finally that
|
|
93
95
|
// disposes the notifier and removes the info file just written. An unwritable feed
|
|
94
|
-
// (ENOSPC, EACCES, the path replaced by a directory) must not turn a
|
|
96
|
+
// (ENOSPC, EACCES, the path replaced by a directory) must not turn a team that can run
|
|
95
97
|
// into one that refuses to start; logEventBestEffort reports the write failure on stderr so
|
|
96
98
|
// it stays visible, and startup proceeds to the poll loop, which is already best-effort.
|
|
97
99
|
logEventBestEffort(root, {
|
|
98
|
-
|
|
100
|
+
source: TUMWATER_SOURCE,
|
|
99
101
|
type: "orchestrator_start",
|
|
100
102
|
pid: process.pid,
|
|
101
103
|
roles: enabled,
|
|
102
104
|
...(redeploy ? { build: redeploy.build.sha } : {}),
|
|
103
105
|
});
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
+
// The first per-runner state read is each RoleRunner constructor's loadRunnerState, so the
|
|
107
|
+
// persisted-name migrations run before it. (After the info write and start event above, which
|
|
108
|
+
// an in-process test or a second `tumwater start` probe reads synchronously.)
|
|
109
|
+
await runStartupMigrations(root);
|
|
110
|
+
// The mergeRefs migration moved legacy landing pins but keeps a dangling one (setRef refuses a
|
|
111
|
+
// missing object); this drops what is left before any run.
|
|
112
|
+
await dropDanglingTumwaterPins(root);
|
|
113
|
+
// A clean stop removes the info file, so only a crashed predecessor leaves a pid to reap for.
|
|
114
|
+
if (previous !== null && previous.pid !== process.pid)
|
|
115
|
+
void reapPreviousTeamOrphans(root, previous.pid);
|
|
116
|
+
// Finished backlog entries still in the live files move to the archives now
|
|
117
|
+
// (plans/backlog-archive.md), while no merge is in progress and no run is in flight: a
|
|
118
|
+
// repository migrates on its first start with this build, and an entry a hand commit moved is
|
|
119
|
+
// swept. One commit on main, or a warning; the team starts either way.
|
|
120
|
+
await archiveAtTeamStart(root);
|
|
121
|
+
const runners = enabled.map((role) => new RoleRunner(root, role, config, mainBranch, roleSignal));
|
|
122
|
+
// Legacy merger checkouts (`_land-<role>`) from a pre-pool build hold nothing now that verifies
|
|
123
|
+
// and merges lease pooled slots: remove them once, before any run, so the retired layout
|
|
106
124
|
// never lingers on disk. (After the info write and start event above, so startup's published
|
|
107
|
-
// state is on disk synchronously for an in-process test or a second `tumwater
|
|
125
|
+
// state is on disk synchronously for an in-process test or a second `tumwater start` probe.)
|
|
108
126
|
await removeLegacyLandWorktrees(root);
|
|
109
127
|
// Legacy role checkouts (`<role>` from a pre-pool build) are retired in the same startup
|
|
110
|
-
// window, right after the
|
|
111
|
-
// pinned slot, every other legacy dir goes. Pass every configured
|
|
128
|
+
// window, right after the RoleRunners above: a role with a pending resume keeps its dir as a
|
|
129
|
+
// pinned slot, every other legacy dir goes. Pass every configured runner id, not `enabled` (a
|
|
112
130
|
// `--once --role` filter): a scoped run must not retire or unpin the other roles, whose
|
|
113
|
-
// pending resumes the next full-
|
|
114
|
-
await retireLegacyRoleWorktrees(root,
|
|
131
|
+
// pending resumes the next full-team start still owns. See retireLegacyRoleWorktrees.
|
|
132
|
+
await retireLegacyRoleWorktrees(root, runnerIds(config));
|
|
133
|
+
// A run the previous process died in resumes in its own slot: pin it before any run leases.
|
|
134
|
+
pinCrashedRuns(root, runnerIds(config));
|
|
115
135
|
// Session retention (src/orchestrator/retention.ts owns the whole concern): construction runs the
|
|
116
|
-
// startup prune, seeding the once-per-day gate so an unchanged
|
|
136
|
+
// startup prune, seeding the once-per-day gate so an unchanged team prunes at most once
|
|
117
137
|
// per day — and 0 disables pruning, the same convention as quietTimeoutSeconds.
|
|
118
138
|
const retentionPruner = new RetentionPruner(root, config.sessionRetentionDays);
|
|
119
|
-
// In-flight tasks, split by who requested them: the redeploy drain caps role
|
|
120
|
-
// window but waits for a director
|
|
121
|
-
// self-redeploy (BUGS.md 2026-09-08). The
|
|
122
|
-
// in-process tasks (the
|
|
123
|
-
// permits as role
|
|
124
|
-
// bounded hand-off on a restart (drainInFlightWork) — an aborted
|
|
125
|
-
// drops its entry, recovering on next start. roleInFlight holds every RESERVED role
|
|
139
|
+
// In-flight tasks, split by who requested them: the redeploy drain caps role runs at its
|
|
140
|
+
// window but waits for a director run without one — an explicit human prompt outranks the
|
|
141
|
+
// self-redeploy (BUGS.md 2026-09-08). The merge tasks are deliberately OUTSIDE this split:
|
|
142
|
+
// in-process tasks (the verifies and the one merge, since 2026-09-18 under the same maxConcurrent
|
|
143
|
+
// permits as role runs) that shutdown still awaits — to the end on an operator stop, and for a
|
|
144
|
+
// bounded hand-off on a restart (drainInFlightWork) — an aborted merge keeps its ref and
|
|
145
|
+
// drops its entry, recovering on next start. roleInFlight holds every RESERVED role run
|
|
126
146
|
// (permit holders and waiters parked in the semaphore queue alike), since shutdown awaits
|
|
127
|
-
// both; what the restart drain waits on, aborts, and reports as
|
|
128
|
-
// rolePermitHolders, the role
|
|
129
|
-
// drain — the start gate (
|
|
130
|
-
// pending — and counting it held drains open for
|
|
131
|
-
//
|
|
147
|
+
// both; what the restart drain waits on, aborts, and reports as abortedRuns is only
|
|
148
|
+
// rolePermitHolders, the role runs actually holding a permit. A parked waiter has nothing to
|
|
149
|
+
// drain — the start gate (runStartHeld below) keeps it from starting while a restart is
|
|
150
|
+
// pending — and counting it held drains open for runs that had not begun and inflated
|
|
151
|
+
// abortedRuns (12 reported against 3 aborted run_ends on 2026-09-21; BUGS.md 2026-09-23).
|
|
132
152
|
const roleInFlight = new Set();
|
|
133
153
|
const rolePermitHolders = new Set();
|
|
134
154
|
const directorInFlight = new Set();
|
|
135
|
-
// The restart drain's window tracks the
|
|
136
|
-
// durations of recent COMPLETED role
|
|
137
|
-
// cold-start constant. Bounded (orchestrator-launch.ts's
|
|
138
|
-
// long-running
|
|
155
|
+
// The restart drain's window tracks the team's real run duration (BUGS.md 2026-09-18): the
|
|
156
|
+
// durations of recent COMPLETED role runs feed a p75 that poll uses in place of the
|
|
157
|
+
// cold-start constant. Bounded (orchestrator-launch.ts's ROLE_RUN_DURATION_SAMPLES) so a
|
|
158
|
+
// long-running team's memory stays flat; aborted runs are
|
|
139
159
|
// excluded — their short cut-off durations would drag the window down and cause more aborts.
|
|
140
|
-
const
|
|
141
|
-
// Every
|
|
142
|
-
// changes they
|
|
143
|
-
const
|
|
144
|
-
// Once-mode bookkeeping (`tumwater
|
|
160
|
+
const roleRunDurationsMs = [];
|
|
161
|
+
// Every merge task (merge-drain.ts's MergePipeline): the verification stage's tasks, the
|
|
162
|
+
// changes they approved, and the merge slot.
|
|
163
|
+
const merges = newMergePipeline();
|
|
164
|
+
// Once-mode bookkeeping (`tumwater start --once`) — the run-count snapshot, the per-role
|
|
145
165
|
// settle reasons, and the quiet-poll exit rule — lives in src/scheduling/once-round.ts.
|
|
146
166
|
const once = new OnceRound(runners, opts.once === true);
|
|
147
|
-
// The
|
|
167
|
+
// The team-wide gates' and alarms' cross-poll memory (src/gates/gate-polls.ts owns the family's
|
|
148
168
|
// wiring — budget, pause, quiet hours, failure hold, and the two storm alarms): the gates'
|
|
149
169
|
// previous values for the edge-triggered events, the fallback breaker, and the hold's own
|
|
150
170
|
// memory (deadline, kind, relapse count). In memory only — a restart re-trusts the
|
|
151
171
|
// fallback, starts with the hold open, and can re-log at most one event per gate. The
|
|
152
|
-
// budget gate's breaker is also the fallback
|
|
153
|
-
// records every
|
|
154
|
-
// the start gate reads gateStates.
|
|
172
|
+
// budget gate's breaker is also the fallback runs' evidence sink: the start pass below
|
|
173
|
+
// records every run's outcome into it and runs its probe (src/budget/fallback-breaker.ts), and
|
|
174
|
+
// the start gate reads gateStates.teamHold at permit time, so its closures always see
|
|
155
175
|
// the latest poll's hold verdict rather than the poll that created them.
|
|
156
|
-
const gateStates =
|
|
176
|
+
const gateStates = newTeamGateStates(config);
|
|
177
|
+
// A restart must not re-announce a window that is still open.
|
|
178
|
+
gateStates.claudeWindowWarned = loggedWindowKeys(root, "subscription_window_warning", Date.now());
|
|
179
|
+
gateStates.claudeWindowReserved = loggedWindowKeys(root, "subscription_window_reserved", Date.now());
|
|
157
180
|
// The live config the last successful reload produced (last-known-good while the file is
|
|
158
181
|
// broken or missing). The reload bookkeeping itself lives in src/config/config-live.ts.
|
|
159
182
|
const liveReload = newLiveConfigReload({ root, config, mainBranch, runnerSignal: roleSignal, runners, semaphore, roleFilter: opts.roleFilter });
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// Need-based deferral: whether qualifying work has landed
|
|
169
|
-
// since a role's last-seen main head, cached per head (see scheduling/work-landed-cache.ts).
|
|
170
|
-
const workLandedSince = new WorkLandedCache(root, mainBranch);
|
|
183
|
+
// Need-based deferral: whether qualifying work has merged
|
|
184
|
+
// since a role's last-seen main head, cached per head (see scheduling/work-merged-cache.ts).
|
|
185
|
+
const workMergedSince = new WorkMergedCache(root, mainBranch);
|
|
186
|
+
const mainWatch = createMainWatch(root, mainBranch, (culprit) => {
|
|
187
|
+
void routeMainCulprit(root, mainBranch, culprit, (c) => {
|
|
188
|
+
merges.revert = c;
|
|
189
|
+
});
|
|
190
|
+
});
|
|
171
191
|
// Per-role deferred-due state for one-shot tick_deferred events: the previous poll's
|
|
172
192
|
// deferral per role (like prevBudgetPaused/prevUserPaused, but per role), so each episode
|
|
173
193
|
// logs exactly once — on the transition in, never while merely not-due and never on exit.
|
|
174
194
|
// In-memory only: a restart mid-episode can re-log at most one event.
|
|
175
195
|
const deferredDue = new Map();
|
|
176
|
-
// Whether the last poll found a self-redeploy pending (`hold`). Hoisted out of the poll loop
|
|
177
|
-
// because it is read at two points: at scheduling (no new tick is reserved) and — through
|
|
178
|
-
// tickStartHeld — whenever a parked waiter is granted its permit, which happens between polls.
|
|
179
|
-
let holdForRestart = false;
|
|
180
|
-
// Whether the last poll's disk gate held new work (plans/disk-floor.md, part 1/4). Hoisted
|
|
181
|
-
// beside holdForRestart because tickStartHeld reads it at permit time, between polls — the
|
|
182
|
-
// disk hold closes the same gate point every fleet-wide hold uses.
|
|
183
|
-
let diskHeld = false;
|
|
184
|
-
// The start gate every reserved tick passes the moment its permit is granted
|
|
185
|
-
// (runTimedRoleTick's `held`): closed while a restart is pending, and after one is decided so
|
|
186
|
-
// no waiter the shutdown hands a permit to starts on the build being replaced. One predicate,
|
|
187
|
-
// so another fleet-wide hold on new ticks can close the same gate point.
|
|
188
|
-
const tickStartHeld = () => holdForRestart || restart || diskHeld;
|
|
189
196
|
// The operator notify hook (src/events/notify.ts owns the whole concern): one configured shell
|
|
190
|
-
// command run on notable events (budget_paused, role_streak_paused,
|
|
197
|
+
// command run on notable events (budget_paused, role_streak_paused, merge_failed,
|
|
191
198
|
// restart_blocked). Subscribed here so it sees every event this process logs from start;
|
|
192
199
|
// disposed beside the orchestrator_stop event below.
|
|
193
200
|
const notifier = newNotifier(root);
|
|
194
201
|
// Best-effort event logging in the poll loop (bestEffortEventLogger, events.ts): this loop
|
|
195
202
|
// and its finally run with no catch, so an unwritable feed (ENOSPC, EACCES, the path replaced
|
|
196
|
-
// by a directory) must not end the
|
|
203
|
+
// by a directory) must not end the team — and, in the finally, must not skip the
|
|
197
204
|
// notifier/quiet-file cleanup or mask an in-flight error. The feed is the failure; the poll
|
|
198
205
|
// and the shutdown still run.
|
|
199
206
|
const { emit, warn } = bestEffortEventLogger(root);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
207
|
+
const opencodeCatalogRefresh = newOpencodeCatalogRefresh(root, {}, warn);
|
|
208
|
+
const ctx = {
|
|
209
|
+
root,
|
|
210
|
+
mainBranch,
|
|
211
|
+
onceMode: opts.once === true,
|
|
212
|
+
signal,
|
|
213
|
+
roleSignal,
|
|
214
|
+
internalStop,
|
|
215
|
+
internalRoleStop,
|
|
216
|
+
runners,
|
|
217
|
+
semaphore,
|
|
218
|
+
merges,
|
|
219
|
+
gateStates,
|
|
220
|
+
once,
|
|
221
|
+
info,
|
|
222
|
+
modelsPath,
|
|
223
|
+
piDefault,
|
|
224
|
+
redeploy,
|
|
225
|
+
launchServicesWatch,
|
|
226
|
+
reclaim,
|
|
227
|
+
liveReload,
|
|
228
|
+
notifier,
|
|
229
|
+
retentionPruner,
|
|
230
|
+
opencodeCatalogRefresh,
|
|
231
|
+
mainWatch,
|
|
232
|
+
workMergedSince,
|
|
233
|
+
deferredDue,
|
|
234
|
+
breakerPolicy,
|
|
235
|
+
breakerNow,
|
|
236
|
+
roleInFlight,
|
|
237
|
+
rolePermitHolders,
|
|
238
|
+
directorInFlight,
|
|
239
|
+
roleRunDurationsMs,
|
|
240
|
+
emit,
|
|
241
|
+
warn,
|
|
242
|
+
};
|
|
243
|
+
// The flags that outlive one poll (orchestrator-poll.ts's PollState).
|
|
244
|
+
const state = newPollState();
|
|
245
|
+
// Why the poll loop ended, for the orchestrator_stop event: a throw leaves "error".
|
|
246
|
+
let stopCause = "error";
|
|
203
247
|
try {
|
|
204
248
|
while (!signal.aborted) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
// `config set notify` edit takes effect on the next poll, no restart.
|
|
211
|
-
notifier.update(liveConfig);
|
|
212
|
-
// Live session retention (the last restart-only setting): the edge-triggered
|
|
213
|
-
// retention_changed event, the once-per-day gate, and the prune itself live in
|
|
214
|
-
// src/orchestrator/retention.ts. The live config (last-known-good while the file is broken or
|
|
215
|
-
// missing) drives the check, like the budget gate.
|
|
216
|
-
retentionPruner.poll(root, liveConfig.sessionRetentionDays);
|
|
217
|
-
// launchservicesd's Mach-port budget (src/process/launch-services.ts owns it):
|
|
218
|
-
// a background sample at most every 15 minutes, and a warning days before the kernel
|
|
219
|
-
// kills the daemon and wedges the Mac's GUI session. Never awaited — a poll does not
|
|
220
|
-
// wait on `top`.
|
|
221
|
-
void launchServicesWatch?.poll();
|
|
222
|
-
// Consume CLI request markers: a reset-counters request, a wake request, a forced-restart
|
|
223
|
-
// request, and per-role abort requests.
|
|
224
|
-
consumeResetRequest(root, runners);
|
|
225
|
-
consumeWakeRequest(root, runners);
|
|
226
|
-
consumeRestartRequest(root, redeploy);
|
|
227
|
-
consumeReclaimRequest(root, reclaim);
|
|
228
|
-
consumeAbortRequests(root, runners, abortableLandings(landings));
|
|
229
|
-
await settleAbortedVetted(root, landings);
|
|
230
|
-
// branchHead reads the ref files first (microsecond-scale; this runs every poll) and
|
|
231
|
-
// spawns `git rev-parse` only when they cannot resolve it. "" means main does not exist
|
|
232
|
-
// yet — isEligible treats an empty head as "no wake".
|
|
233
|
-
const mainHead = (await branchHead(root, mainBranch)) ?? "";
|
|
234
|
-
// Branch-divergence watch, once per poll (see warnedBranchDivergence above): the
|
|
235
|
-
// fleet keeps fast-forwarding the branch it resolved at startup — the safe behavior,
|
|
236
|
-
// since role worktrees are based on it — and one warning names the divergence
|
|
237
|
-
// instead of leaving it mysterious. Back on the target branch re-arms the check.
|
|
238
|
-
const liveBranch = await currentBranch(root);
|
|
239
|
-
if (liveBranch !== null && liveBranch !== mainBranch) {
|
|
240
|
-
if (!warnedBranchDivergence) {
|
|
241
|
-
warnedBranchDivergence = true;
|
|
242
|
-
warn(`primary checkout moved to ${liveBranch} — the fleet keeps merging into ${mainBranch}`);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
warnedBranchDivergence = false;
|
|
247
|
-
}
|
|
248
|
-
const now = Date.now();
|
|
249
|
-
// The fleet's gates and alarms, one poll of the family (src/gates/gate-polls.ts): the daily
|
|
250
|
-
// budget gate — with its derived-state publish and the fallback handback to the
|
|
251
|
-
// primary — the operator and per-role pause gates, quiet hours, the fleet-wide failure
|
|
252
|
-
// hold, and the two observational storm alarms. The family's cross-poll memory lives
|
|
253
|
-
// in gateStates, advanced in place; this returns this poll's verdicts for the
|
|
254
|
-
// scheduling and start passes. The failure hold is deliberately not among them — the
|
|
255
|
-
// start gate and the landings' startHeld read the LATEST poll's verdict at permit
|
|
256
|
-
// time from gateStates.fleetHold, so a waiter granted its permit after later polls
|
|
257
|
-
// ran meets the hold as it stands then, not as this poll left it.
|
|
258
|
-
const { roleConfig, userPaused, pausedRoles: pausedRolesSet, capPaused, maintenanceQuota, bootstrapHeld, bootstrapActive, budgetPausedRoles, servingResolved, priceResolved, budgetActive, roleQuietHeld, quietNow, diskHeld: polledDiskHeld, } = pollFleetGates(gateStates, {
|
|
259
|
-
root,
|
|
260
|
-
runners,
|
|
261
|
-
liveConfig,
|
|
262
|
-
modelsPath,
|
|
263
|
-
now,
|
|
264
|
-
info,
|
|
265
|
-
reclaim,
|
|
266
|
-
});
|
|
267
|
-
// The latest poll's disk verdict, hoisted for tickStartHeld's permit-time reads
|
|
268
|
-
// (plans/disk-floor.md, part 1/4).
|
|
269
|
-
diskHeld = polledDiskHeld;
|
|
270
|
-
// The failure hold, keyed per provider (PLANS.md 2026-10-05): a role is held when ITS
|
|
271
|
-
// tick model's provider stands under a hold, and every role is held when the
|
|
272
|
-
// reviewer's provider is (with the review gate on, nothing could land — and the land
|
|
273
|
-
// queue below reads the same reviewHeld verdict). Key presence in the map is relapse
|
|
274
|
-
// memory, not a hold: heldProviders() reads only standing holds (`until` non-null),
|
|
275
|
-
// so a lifted hold never keeps blocking. Read fresh from gateStates.fleetHold — the
|
|
276
|
-
// permit-time closures below see the latest poll's verdicts, not this poll's snapshot.
|
|
277
|
-
const fleetHeldProviders = heldProviders(gateStates.fleetHold);
|
|
278
|
-
// Fresh reads for the permit-time closures: they run after later polls have advanced
|
|
279
|
-
// gateStates.fleetHold, so they re-derive from the LATEST poll's map, not this snapshot.
|
|
280
|
-
const fleetHeldNow = () => heldProviders(gateStates.fleetHold);
|
|
281
|
-
const reviewHeldNow = () => liveConfig.review.enabled && fleetHeldNow().has(seamHoldKey(reviewRunConfig(liveConfig)));
|
|
282
|
-
const reviewHeld = liveConfig.review.enabled && fleetHeldProviders.has(seamHoldKey(reviewRunConfig(liveConfig)));
|
|
283
|
-
const roleProviders = new Map(runners.map((r) => [r.role, r.runProvider(now)]));
|
|
284
|
-
// Self-redeploy (src/redeploy/redeployer.ts): with main's head in hand, let the state
|
|
285
|
-
// machine observe it. `hold` starts no new ticks at all — director included; a restart
|
|
286
|
-
// lands within the drain's window plus a bounded landing hand-off, and its prompt waits
|
|
287
|
-
// in the inbox for the new build — while the green check/compile/drain run in the
|
|
288
|
-
// background. That covers ticks
|
|
289
|
-
// reserved before the hold too: a waiter parked in the semaphore queue that is granted a
|
|
290
|
-
// permit mid-drain meets the closed start gate (tickStartHeld), releases the permit, and
|
|
291
|
-
// hands its reservation back so it re-schedules once the hold lifts or on the new build.
|
|
292
|
-
// `restart` means dist/ already holds the new
|
|
293
|
-
// build: stop scheduling, abort whatever role ticks the drain gave up waiting for — the
|
|
294
|
-
// permit holders; they resume on the new build — and return. A director tick can never be
|
|
295
|
-
// in flight here — poll only returns `restart` once it has finished. (holdForRestart keeps
|
|
296
|
-
// the previous poll's verdict until this one's is in: resetting it before the await
|
|
297
|
-
// would open the gate for a waiter granted a permit while poll runs.)
|
|
298
|
-
if (redeploy) {
|
|
299
|
-
const action = await redeploy.poll(mainHead, {
|
|
300
|
-
roleInFlight: rolePermitHolders.size,
|
|
301
|
-
directorInFlight: directorInFlight.size,
|
|
302
|
-
roleTickP75Ms: p75TickDurationMs(roleTickDurationsMs),
|
|
303
|
-
}, liveConfig.autoRestart, now);
|
|
304
|
-
const build = redeploy.status(now);
|
|
305
|
-
if (assignInfoFieldIfChanged(info, "build", build))
|
|
306
|
-
writeOrchestratorInfoBestEffort(root, info);
|
|
307
|
-
if (action === "restart") {
|
|
308
|
-
restart = true;
|
|
309
|
-
// Only permit holders have a pi run to cut off (the director is guaranteed finished by
|
|
310
|
-
// then): a parked waiter meets the closed start gate whenever the shutdown hands it a
|
|
311
|
-
// permit, so it needs no abort — and aborting for it alone would also cut off an
|
|
312
|
-
// in-flight landing that no permit-holding tick put at stake. The role-only controller
|
|
313
|
-
// keeps that promise: the landing, wired to the full signal, runs on into the hand-off
|
|
314
|
-
// window the wait announces (BUGS.md 2026-09-30).
|
|
315
|
-
if (rolePermitHolders.size > 0)
|
|
316
|
-
internalRoleStop.abort();
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
holdForRestart = action === "hold";
|
|
320
|
-
}
|
|
321
|
-
// Merge queue 3/5 — drain the durable land queue while neither a restart nor a failure
|
|
322
|
-
// hold is pending (the scheduler's WHEN; landing-drain.ts owns the HOW — the vetting
|
|
323
|
-
// stage, its merge slot, and the dedupe against main). A held poll starts no vet and no
|
|
324
|
-
// merge, exactly as it starts no tick; what is already in flight runs on, and a vet
|
|
325
|
-
// parked for its permit meets the same start gate as a parked tick when the permit comes.
|
|
326
|
-
if (!holdForRestart && !reviewHeld && !diskHeld) {
|
|
327
|
-
await drainLandings({
|
|
328
|
-
root,
|
|
329
|
-
mainBranch,
|
|
330
|
-
signal,
|
|
331
|
-
semaphore,
|
|
332
|
-
runners,
|
|
333
|
-
liveConfig,
|
|
334
|
-
roleConfig,
|
|
335
|
-
startHeld: () => tickStartHeld() || reviewHeldNow(),
|
|
336
|
-
}, landings);
|
|
337
|
-
}
|
|
338
|
-
// Backlog-aware deferral: while PLANS.md `## Planned` or BUGS.md `## Open` on main
|
|
339
|
-
// holds an entry feature or bugfix may take (eligibleEntries), idle maintenance ticks stay deferred — queued feature/bugfix work outranks
|
|
340
|
-
// them regardless of what landed. Stat-cached reads (backlog.ts): one stat per file per
|
|
341
|
-
// poll while the files are unchanged. bugfix is not a maintenance role, but while BUGS.md
|
|
342
|
-
// `## Open` is empty it defers like one (deferTick) — openBugsNow is that emptiness, read
|
|
343
|
-
// once here and reused for both predicates.
|
|
344
|
-
const openBugsNow = openBugs(root).length > 0;
|
|
345
|
-
const workBacklogOpen = eligibleEntries(root, "feature").length > 0 || eligibleEntries(root, "bugfix").length > 0;
|
|
346
|
-
const plansFull = planBacklogFull(root, liveConfig);
|
|
347
|
-
// A demoted fallback's half-open window (part 5c/8): the pair whose probe is due, and
|
|
348
|
-
// the roles whose model tier WOULD run on that pair absent the demotion (the price-based
|
|
349
|
-
// resolution — the serving resolution has already dropped the pair, which is exactly
|
|
350
|
-
// why it is being probed). Only those roles pierce the budget hold, and the launch pass
|
|
351
|
-
// admits exactly one of them as the probe — a probe blind to the pair's tier would lift
|
|
352
|
-
// the pause for every role (objection to an earlier draft), and a probe with no
|
|
353
|
-
// eligible runner must not hijack the poll's other launches (the launch pass continues
|
|
354
|
-
// only the eligible runners it cannot admit, never the rest of the fleet).
|
|
355
|
-
const probePair = fallbackProbeDuePair(gateStates.budget.breakers, breakerNow());
|
|
356
|
-
const probeRoles = new Set();
|
|
357
|
-
if (probePair !== null) {
|
|
358
|
-
for (const r of runners) {
|
|
359
|
-
if (r.role === DIRECTOR_ROLE)
|
|
360
|
-
continue;
|
|
361
|
-
const p = priceResolved[roleSeamTier(liveConfig, r.role)].pair;
|
|
362
|
-
if (p !== null && modelPairName(p) === probePair)
|
|
363
|
-
probeRoles.add(r.role);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
// The review-on strong pause (budgetGate): with the review gate on, an unresolvable
|
|
367
|
-
// strong tier means nothing could land, so EVERY role is held — the per-tier pause set
|
|
368
|
-
// alone would let small/default roles author changes no reviewer could judge. The probe
|
|
369
|
-
// of the strong pair pierces this hold for exactly the probed tier's roles too.
|
|
370
|
-
const reviewStrongPaused = budgetActive && liveConfig.review.enabled && servingResolved.strong.pair === null;
|
|
371
|
-
const reasons = await pollRunnerReasons({
|
|
372
|
-
root,
|
|
373
|
-
runners,
|
|
374
|
-
once,
|
|
375
|
-
now,
|
|
376
|
-
mainHead,
|
|
377
|
-
userPaused,
|
|
378
|
-
quietNow,
|
|
379
|
-
pausedRoles: pausedRolesSet,
|
|
380
|
-
capPaused,
|
|
381
|
-
maintenanceQuota,
|
|
382
|
-
bootstrapHeld,
|
|
383
|
-
bootstrapActive,
|
|
384
|
-
budgetPausedRoles,
|
|
385
|
-
probeRoles,
|
|
386
|
-
reviewStrongPaused,
|
|
387
|
-
roleQuietHeld,
|
|
388
|
-
holdForRestart,
|
|
389
|
-
diskHeld,
|
|
390
|
-
heldProviders: fleetHeldProviders,
|
|
391
|
-
reviewHeld,
|
|
392
|
-
roleProviders,
|
|
393
|
-
openBugsNow,
|
|
394
|
-
workBacklogOpen,
|
|
395
|
-
planBacklogFull: plansFull,
|
|
396
|
-
deferredDue,
|
|
397
|
-
workLandedSince,
|
|
398
|
-
});
|
|
399
|
-
launchDueTicks({
|
|
400
|
-
root,
|
|
401
|
-
reasons,
|
|
402
|
-
// roleSignal, not signal: the timed tick wrapper watches the full signal PLUS the
|
|
403
|
-
// role-only stop, exactly as the pre-extraction inline pass did — the restart drain's
|
|
404
|
-
// give-up abort must also turn away a waiter granted its permit at that moment.
|
|
405
|
-
signal: roleSignal,
|
|
406
|
-
gateStates,
|
|
407
|
-
breakerPolicy,
|
|
408
|
-
breakerNow,
|
|
409
|
-
probePair,
|
|
410
|
-
probeRoles,
|
|
411
|
-
budgetActive,
|
|
412
|
-
semaphore,
|
|
413
|
-
rolePermitHolders,
|
|
414
|
-
roleInFlight,
|
|
415
|
-
directorInFlight,
|
|
416
|
-
roleTickDurationsMs,
|
|
417
|
-
startHeld: (role) => tickStartHeld() ||
|
|
418
|
-
(role !== DIRECTOR_ROLE &&
|
|
419
|
-
(reviewHeldNow() || fleetHeldNow().has(roleProviders.get(role)))),
|
|
420
|
-
});
|
|
421
|
-
// Priority headroom (plans/parallel-work-instances.md part 7/7): withhold a few permits
|
|
422
|
-
// from maintenance while a work loop has work to take, so a due work tick never waits out
|
|
423
|
-
// a whole maintenance tick. Sized after the launch pass, so a runner launched this poll
|
|
424
|
-
// already reads as running and reserves nothing for itself.
|
|
425
|
-
const reserve = workReserveCount(root, runners, {
|
|
426
|
-
maxConcurrent: liveConfig.maxConcurrent,
|
|
427
|
-
enabled: new Set(runners.filter((r) => loopEnabled(r.config, r.role)).map((r) => r.role)),
|
|
428
|
-
queuedLandingRoles: queuedLandingRoles(root),
|
|
429
|
-
});
|
|
430
|
-
semaphore.setReserve(reserve);
|
|
431
|
-
if ((lastReserve === 0) !== (reserve === 0)) {
|
|
432
|
-
emit({ loop: "harness", type: "permit_reserve", reserve });
|
|
249
|
+
const inputs = await pollRequestsAndWatch(ctx, state);
|
|
250
|
+
const gates = pollGatesAndHolds(ctx, state, inputs);
|
|
251
|
+
if (await pollRedeploy(ctx, state, inputs)) {
|
|
252
|
+
stopCause = "restart";
|
|
253
|
+
break;
|
|
433
254
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
// in-flight landing tasks to the end). The one-poll-cycle requirement is the guard
|
|
439
|
-
// against dropping a queued-but-not-yet-started landing: a stop that lands during the
|
|
440
|
-
// shutdown drain drops the queue entry, so the slot gets its poll to pick the entry up
|
|
441
|
-
// before the stop is trusted.
|
|
442
|
-
if (once.exitReady({
|
|
443
|
-
roleTicks: roleInFlight.size,
|
|
444
|
-
directorTicks: directorInFlight.size,
|
|
445
|
-
landings: landingTasks(landings).length,
|
|
446
|
-
queuedLandings: queuedLandingFiles(root).length,
|
|
447
|
-
})) {
|
|
448
|
-
internalStop.abort();
|
|
255
|
+
await pollMergeDrain(ctx, state, inputs, gates);
|
|
256
|
+
await pollSchedule(ctx, state, inputs, gates);
|
|
257
|
+
if (pollReserveAndExit(ctx, state, inputs)) {
|
|
258
|
+
stopCause = "once";
|
|
449
259
|
break;
|
|
450
260
|
}
|
|
451
261
|
await sleepInterruptible(pollMs, signal);
|
|
452
262
|
}
|
|
263
|
+
if (stopCause === "error")
|
|
264
|
+
stopCause = "signal";
|
|
453
265
|
}
|
|
454
266
|
finally {
|
|
455
|
-
await drainInFlightWork(root, roleInFlight, directorInFlight,
|
|
456
|
-
emit({
|
|
267
|
+
await drainInFlightWork(root, roleInFlight, directorInFlight, mergeTasks(merges), state.restart, opts.handoffMergeWindowMs ?? HANDOFF_MERGE_WINDOW_MS, () => internalStop.abort());
|
|
268
|
+
emit({ source: TUMWATER_SOURCE, type: "orchestrator_stop", cause: stopCause });
|
|
457
269
|
notifier.dispose();
|
|
458
270
|
removeQuiet(infoFile);
|
|
459
271
|
}
|
|
460
|
-
// Only once mode carries the settle reasons and the per-role
|
|
272
|
+
// Only once mode carries the settle reasons and the per-role run counts: the daemon return
|
|
461
273
|
// keeps its exact shape (an e2e test deep-equals it), and a daemon caller has no summary to
|
|
462
274
|
// feed.
|
|
463
275
|
return once.active
|
|
464
276
|
? {
|
|
465
|
-
restart,
|
|
277
|
+
restart: state.restart,
|
|
466
278
|
settled: once.reasons,
|
|
467
|
-
|
|
279
|
+
runsDone: new Map(runners.map((r) => [r.role, once.runsDone(r)])),
|
|
468
280
|
}
|
|
469
|
-
: { restart };
|
|
281
|
+
: { restart: state.restart };
|
|
470
282
|
}
|