tumwater 0.2.0 → 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 +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +42 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +4 -3
- 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 +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -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 +246 -55
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +85 -274
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +175 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- 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 +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- 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 +139 -32
- 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 +9 -11
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget.js +30 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +1 -2
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +118 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +150 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +308 -590
- package/dist/src/cli/cli-repo-gate.js +11 -1
- 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-run.js → cli-start.js} +165 -237
- package/dist/src/cli/completion.js +149 -22
- package/dist/src/cli/config-commands.js +41 -18
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +189 -73
- package/dist/src/cli/log-commands.js +148 -82
- package/dist/src/cli/question-commands.js +205 -10
- package/dist/src/cli.js +106 -43
- package/dist/src/collections.js +26 -10
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +13 -1
- package/dist/src/config/config-fallbacks.js +93 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- 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 +141 -199
- package/dist/src/config/config-views.js +127 -53
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +131 -264
- package/dist/src/config/config.js +48 -38
- package/dist/src/config/model-selector.js +34 -34
- 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 +100 -496
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +52 -579
- 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 +82 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +236 -402
- package/dist/src/doctor/doctor-notify.js +3 -4
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor.js +121 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +7 -11
- package/dist/src/events/event-format.js +181 -86
- package/dist/src/events/event-labels.js +12 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +39 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +60 -24
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +56 -56
- package/dist/src/failure/failure-render.js +31 -23
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +99 -35
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +177 -84
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +108 -58
- package/dist/src/gates/maintenance-quota.js +10 -13
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +77 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +36 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +42 -18
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +8 -13
- package/dist/src/gui/gui-config-commands.js +94 -72
- package/dist/src/gui/gui-endpoint-commands.js +4 -3
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +108 -13
- package/dist/src/gui/gui-prompt-commands.js +11 -14
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +11 -4
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +193 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +25 -9
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +81 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +150 -11
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/merge/merge-queue-render.js +49 -11
- package/dist/src/merge/merge-queue.js +31 -15
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +33 -11
- package/dist/src/merge/merge-to-main.js +86 -27
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +12 -10
- package/dist/src/notes-cli.js +30 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
- package/dist/src/orchestrator/orchestrator.js +128 -369
- package/dist/src/orchestrator/retention.js +42 -8
- 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 +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +116 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +18 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +121 -211
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +129 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +161 -77
- package/dist/src/report/report-fold.js +129 -52
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +133 -64
- 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 +100 -1
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +4 -5
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +155 -360
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +69 -12
- package/dist/src/roles/role-guidance.js +55 -20
- package/dist/src/roles/role-labels.js +1 -2
- package/dist/src/roles/role-render.js +67 -50
- package/dist/src/roles/role-view.js +59 -47
- package/dist/src/roles/roles.js +31 -31
- 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 +9 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- 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/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
- 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/{loop → runner}/leftover.js +80 -25
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +43 -16
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +28 -28
- package/dist/src/status/status-roles.js +39 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +5 -6
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +63 -17
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +57 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +67 -13
- package/dist/src/ui/badges.js +65 -37
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +32 -26
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +126 -285
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +434 -67
- package/dist/src/ui/gui/gui-client-labels.js +1 -2
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +55 -51
- package/dist/src/ui/gui/gui-client-pending.js +227 -23
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +87 -427
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +56 -4
- package/dist/src/ui/gui/gui-client-team.js +25 -465
- package/dist/src/ui/gui/gui-client.js +27 -25
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +9 -7
- package/dist/src/ui/gui/gui-styles.js +34 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +136 -49
- package/dist/src/ui/status-render.js +101 -82
- package/dist/src/ui/team-alerts.js +248 -57
- package/dist/src/ui/tone.js +5 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +51 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +3 -5
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +10 -12
- package/dist/src/ui/tui/tui-editor-budget.js +35 -31
- 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-entry.js +36 -47
- 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 +11 -9
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +3 -5
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
- package/dist/src/ui/tui/tui-editor-retire.js +4 -6
- 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 +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +60 -34
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +35 -2
- package/dist/src/ui/tui/tui-frame.js +56 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +24 -9
- package/dist/src/ui/tui/tui-pane-activity.js +14 -11
- package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +58 -8
- package/dist/src/ui/tui/tui-pane-history.js +402 -82
- package/dist/src/ui/tui/tui-pane-pending.js +308 -13
- package/dist/src/ui/tui/tui-pane-queued.js +71 -55
- package/dist/src/ui/tui/tui-pane-report.js +46 -14
- package/dist/src/ui/tui/tui-pane-session.js +25 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +80 -23
- package/dist/src/ui/tui/tui-panes.js +100 -30
- package/dist/src/ui/tui/tui.js +14 -14
- package/dist/src/verdict/fix-claim.js +82 -32
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +42 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -1,87 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { cancelScheduledStop, isTeamPaused, pausedRoles, scheduledStopAt } from "../team/team-state.js";
|
|
1
|
+
import { baseRoleOf, runnerIds } from "../roles/runner-ids.js";
|
|
2
|
+
import { knownRoleIds } from "../config/config.js";
|
|
3
|
+
import { unknownRoleMessage } from "../roles/roles.js";
|
|
4
|
+
import { fail, say, sayJson } from "./cli-output.js";
|
|
5
|
+
import { durationLabel, failRivalShapes, parseBranchFlag, parsePortFlag, parseRoleListFlag, tokenValueProblem, } from "./cli-args.js";
|
|
6
|
+
import { cancelScheduledStop, scheduledStopAt } from "../team/team-state.js";
|
|
8
7
|
import { orchestratorAlive, readOrchestratorInfo } from "../team/orchestrator-info.js";
|
|
9
8
|
import { runStartupCheck, runStartupProblem } from "../gates/startup-gate.js";
|
|
10
|
-
import { initProject } from "../init/init.js";
|
|
11
|
-
import { templateCatalog } from "../init/init-templates.js";
|
|
12
9
|
import { repairInterruptedSwap } from "../build/build-stage.js";
|
|
13
10
|
import { logEvent, subscribeEvents } from "../events/events.js";
|
|
14
11
|
import { notifyTeamDown } from "../events/notify.js";
|
|
15
12
|
import { formatEvent } from "../events/event-format.js";
|
|
16
13
|
import { RESTART_EXIT_CODE } from "../redeploy/redeploy-policy.js";
|
|
17
14
|
import { runStartupMigrations } from "../orchestrator/startup-migration-steps.js";
|
|
18
|
-
import {
|
|
19
|
-
import { plural } from "../text/phrases.js";
|
|
15
|
+
import { loadRunnerState } from "../runner/runner-state-load.js";
|
|
20
16
|
import { shortSha } from "../text/format.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
/** `tumwater
|
|
25
|
-
* work; this prints the report). --dry-run prints the would-be actions without writing
|
|
26
|
-
* anything; an already-initialized repo is reported as a no-op, never an error. */
|
|
27
|
-
export async function cmdInit(root, args) {
|
|
28
|
-
const { prompt, branch, template, listTemplates, adopt, dryRun } = parseInitArgs(args);
|
|
29
|
-
if (listTemplates) {
|
|
30
|
-
for (const { id, description } of templateCatalog())
|
|
31
|
-
say(`${id} — ${description}`);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const result = await initProject(root, prompt, branch ?? undefined, {
|
|
35
|
-
adopt,
|
|
36
|
-
dryRun,
|
|
37
|
-
template: template ?? undefined,
|
|
38
|
-
});
|
|
39
|
-
if (result.template !== "blank")
|
|
40
|
-
say(`template: ${result.template}`);
|
|
41
|
-
if (result.adopted) {
|
|
42
|
-
say(`${result.dryRun ? "would adopt" : "adopting"} an existing repo: the project brief goes in TUMWATER.md and README.md is left untouched`);
|
|
43
|
-
}
|
|
44
|
-
if (result.dryRun) {
|
|
45
|
-
if (result.repoInitialized && result.branch) {
|
|
46
|
-
say(`dry run — would initialize a new git repository on branch ${result.branch}`);
|
|
47
|
-
}
|
|
48
|
-
const list = (names) => (names.length > 0 ? names.join(", ") : "nothing");
|
|
49
|
-
say(`dry run — would create: ${list(result.created)}`);
|
|
50
|
-
say(`would leave alone: ${list(result.leftAlone)}`);
|
|
51
|
-
say("nothing written; re-run without --dry-run to apply");
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (result.created.length === 0) {
|
|
55
|
-
say("already initialized; nothing to do");
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (result.repoInitialized && result.branch) {
|
|
59
|
-
say(`initialized a new git repository on branch ${result.branch}`);
|
|
60
|
-
}
|
|
61
|
-
say(`created ${result.created.join(", ")}${result.committed ? " (committed)" : ""}`);
|
|
62
|
-
if (result.bootstrap !== null) {
|
|
63
|
-
say(`fresh project — maintenance roles wait until ${result.bootstrap} plans are done; remove "bootstrap" from tumwater.json to end that early`);
|
|
64
|
-
}
|
|
65
|
-
say("next: `tumwater start` in one terminal, `tumwater tui` in another");
|
|
66
|
-
}
|
|
67
|
-
/** `tumwater run`: boot the team and stream its event feed to this terminal until it stops.
|
|
17
|
+
import { readRunRowsSince, summarizeRunRows } from "../history/history-data.js";
|
|
18
|
+
import { ONCE_FAILURE_RESULTS, inheritedWindowStart, onceFailures, onceReport, onceSummary, parseRunWindow, renderOnceSummary, windowReport, windowSummary, } from "./cli-start-summary.js";
|
|
19
|
+
export { ONCE_FAILURE_RESULTS, inheritedWindowStart, onceFailures, onceReport, onceSummary, parseRunWindow, renderOnceSummary, windowReport, windowSummary, };
|
|
20
|
+
/** `tumwater start`: boot the team and stream its event feed to this terminal until it stops.
|
|
68
21
|
* Three layers cooperate here: without SUPERVISED_ENV this process becomes the supervisor
|
|
69
|
-
* (
|
|
22
|
+
* (superviseStartCommand below), spawning the real orchestrator as a child generation; the child
|
|
70
23
|
* runs runOrchestrator to completion, handing the terminal back via RESTART_EXIT_CODE after a
|
|
71
24
|
* self-redeploy so the supervisor respawns on the new build; --once collapses the whole thing
|
|
72
|
-
* into one round of
|
|
25
|
+
* into one round of runs and a summary line (onceSummary below), for cron-style invocations.
|
|
73
26
|
* `--for <duration>` keeps the daemon shape (redeployer, LaunchServicesWatch, supervisor hand-off
|
|
74
27
|
* all stay) but arms a deadline: at the deadline the same graceful stop a Ctrl+C runs fires, and
|
|
75
28
|
* the process exits after printing the summary. A mid-run self-redeploy hands the same args
|
|
76
29
|
* (including `--for`) to the supervisor's next child, and the supervisor hands every generation
|
|
77
30
|
* the same window start (RUN_WINDOW_START_ENV), so the deadline and the summary span the whole
|
|
78
31
|
* window; only the final exit prints the summary. */
|
|
79
|
-
export async function
|
|
32
|
+
export async function cmdStart(root, args) {
|
|
80
33
|
// The one function the self-redeploy asks before swapping onto a successor and the supervisor
|
|
81
34
|
// asks when a generation dies, so the three cannot disagree about what boots. The supervisor
|
|
82
35
|
// half runs it too, so a start that cannot boot fails before any child spawns. The flag
|
|
83
|
-
// vocabulary is validated by the dispatcher (cli.ts, from the same
|
|
36
|
+
// vocabulary is validated by the dispatcher (cli.ts, from the same START_FLAG_SPECS).
|
|
84
37
|
const once = args.includes("--once");
|
|
38
|
+
const json = args.includes("--json");
|
|
85
39
|
// The TUI cannot be hosted by a run that is about to stop, like the dashboard: a --once round
|
|
86
40
|
// drains and exits within the same invocation, so the two shapes are refused together before
|
|
87
41
|
// any boot. --for stays allowed — the TUI runs for the window, like the dashboard.
|
|
@@ -92,22 +46,57 @@ export async function cmdRun(root, args) {
|
|
|
92
46
|
// stays allowed — the dashboard serves for the window.
|
|
93
47
|
if (once && args.includes("--gui"))
|
|
94
48
|
failRivalShapes("--gui", "--once", "a one-round run cannot serve a dashboard it is about to kill");
|
|
49
|
+
// `--port` chooses the `--gui` dashboard's port; without `--gui` it names no port for this
|
|
50
|
+
// command, so refuse it (a valid value already passed the spec's shape gate) before any boot.
|
|
51
|
+
if (args.includes("--port") && !args.includes("--gui"))
|
|
52
|
+
fail("--port is only valid with --gui (e.g. `tumwater start --gui --port 8080`); the standalone dashboard is `tumwater gui --port <n>`");
|
|
53
|
+
// `--all-interfaces` and `--token` are dashboard-only flags too: without `--gui` each names
|
|
54
|
+
// nothing this command starts, so refuse them (valid values already passed the spec's shape
|
|
55
|
+
// gate) before any boot, with the same shape as the --port refusal above and fixing the
|
|
56
|
+
// copyable one-command form in the message.
|
|
57
|
+
if (args.includes("--all-interfaces") && !args.includes("--gui"))
|
|
58
|
+
fail("--all-interfaces is only valid with --gui (e.g. `tumwater start --gui --all-interfaces`); the standalone dashboard is `tumwater gui --all-interfaces`");
|
|
59
|
+
if (args.includes("--token") && !args.includes("--gui"))
|
|
60
|
+
fail("--token is only valid with --gui (e.g. `tumwater start --gui --token s3cret`); the standalone dashboard is `tumwater gui --token <secret>`");
|
|
61
|
+
// A malformed --token (valueless, empty, or flag-looking) must fail here before any boot, so
|
|
62
|
+
// a bad secret never leaves the team running behind a dashboard that exited. The same
|
|
63
|
+
// tokenValueProblem `tumwater gui` runs, named for the one-command form the operator typed.
|
|
64
|
+
const tokenProblem = tokenValueProblem(args, "tumwater start --gui");
|
|
65
|
+
if (tokenProblem)
|
|
66
|
+
fail(tokenProblem);
|
|
95
67
|
const forMs = parseRunWindow(args, once);
|
|
96
68
|
const branchArg = parseBranchFlag(args);
|
|
97
69
|
const startup = await runStartupCheck(root, branchArg);
|
|
98
70
|
if ("problem" in startup)
|
|
99
71
|
fail(startup.problem);
|
|
100
72
|
const { config, mainBranch } = startup;
|
|
101
|
-
// A scoped once round: `--role <id
|
|
102
|
-
// live config is readable
|
|
103
|
-
// so
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
const roleFilter = parseRoleFlag(args, loopIds(config));
|
|
73
|
+
// A scoped once round: `--role <id>[,<id>...]` names the runners that run. Parsed here, where the
|
|
74
|
+
// live config is readable. The parser accepts every known id (custom roles included, enabled
|
|
75
|
+
// or not) so a typo still fails with the did-you-mean hint; an id that is known but has no
|
|
76
|
+
// runner is then named as disabled rather than read as unknown — a scoped round that booted
|
|
77
|
+
// a disabled role's runner would run nothing while claiming to serve the operator who just
|
|
78
|
+
// queued that role a prompt. Daemon `run --role` stays an error: scoping is a once-round concept.
|
|
79
|
+
const roleFilter = parseRoleListFlag(args, knownRoleIds(config));
|
|
109
80
|
if (roleFilter !== null && !once)
|
|
110
81
|
fail("--role is only valid with --once");
|
|
82
|
+
if (json && !once && forMs === null)
|
|
83
|
+
fail("--json is only valid with --once or --for");
|
|
84
|
+
// Both interactive modes write their own banner to the stdout a JSON document must own, so a
|
|
85
|
+
// --json round/window refuses them before any boot.
|
|
86
|
+
if (json && args.includes("--gui"))
|
|
87
|
+
failRivalShapes("--json", "--gui", "the dashboard writes its banner to the stdout a JSON document must own");
|
|
88
|
+
if (json && args.includes("--tui"))
|
|
89
|
+
failRivalShapes("--json", "--tui", "a TUI owns the terminal");
|
|
90
|
+
if (roleFilter !== null) {
|
|
91
|
+
const runners = runnerIds(config);
|
|
92
|
+
const missing = roleFilter.find((id) => !runners.includes(id));
|
|
93
|
+
if (missing !== undefined) {
|
|
94
|
+
const base = baseRoleOf(missing);
|
|
95
|
+
if (config.roles[base]?.enabled === false)
|
|
96
|
+
fail(`${missing} is disabled, so a --once round cannot run it — enable it with \`tumwater role ${base} --enable\``);
|
|
97
|
+
fail(unknownRoleMessage(missing, runners));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
111
100
|
// The refusal an operator hits by trying to start a second team in one repo: name the
|
|
112
101
|
// running pid and the two ways to stop it, so the fix is in the message instead of a
|
|
113
102
|
// separate `tumwater status`/help lookup. The info file is read once and reused for the
|
|
@@ -126,7 +115,7 @@ export async function cmdRun(root, args) {
|
|
|
126
115
|
// targets the same branch (or whatever else the invocation named).
|
|
127
116
|
// A stop marker left by a team that died before its deadline must not stop this run.
|
|
128
117
|
cancelScheduledStop(root);
|
|
129
|
-
await
|
|
118
|
+
await superviseStartCommand(root, args, branchArg, forMs);
|
|
130
119
|
return;
|
|
131
120
|
}
|
|
132
121
|
const controller = new AbortController();
|
|
@@ -135,7 +124,11 @@ export async function cmdRun(root, args) {
|
|
|
135
124
|
if (stopping)
|
|
136
125
|
process.exit(130);
|
|
137
126
|
stopping = true;
|
|
138
|
-
|
|
127
|
+
// A --once --json round's stdout must hold exactly its one JSON document, so a signal,
|
|
128
|
+
// parent death, or scheduled stop quietens the stop prose too; the non-zero exit below
|
|
129
|
+
// is what a cron/CI caller sees instead of the words. Non-json runs keep the prose.
|
|
130
|
+
if (!json)
|
|
131
|
+
say("\nstopping — finishing current runs (Ctrl+C again to force)");
|
|
139
132
|
controller.abort();
|
|
140
133
|
};
|
|
141
134
|
process.on("SIGINT", stop);
|
|
@@ -145,16 +138,19 @@ export async function cmdRun(root, args) {
|
|
|
145
138
|
// retracts it when the run ends first (an early Ctrl+C), so no stray timer outlives the run.
|
|
146
139
|
const windowStart = inheritedWindowStart(process.env[RUN_WINDOW_START_ENV], Date.now());
|
|
147
140
|
const deadlineTimer = forMs !== null ? armDeadlineTimer(Math.max(0, windowStart + forMs - Date.now()), () => {
|
|
148
|
-
|
|
141
|
+
// A --for --json window's stdout must hold exactly its one JSON document, so the deadline
|
|
142
|
+
// prose is suppressed too; the document's non-zero-absent facts carry the window.
|
|
143
|
+
if (!json)
|
|
144
|
+
say("deadline reached — stopping");
|
|
149
145
|
stop();
|
|
150
146
|
}) : null;
|
|
151
147
|
const scheduledStopWatch = armScheduledStopWatch(root, () => {
|
|
152
148
|
if (!stopping)
|
|
153
149
|
stop(); // A drain already under way must not be turned into a force-kill.
|
|
154
|
-
});
|
|
150
|
+
}, json);
|
|
155
151
|
// A supervisor that dies without forwarding — SIGKILL (the OOM killer, `kill -9`) cannot be
|
|
156
152
|
// trapped or forwarded, and an uncaught supervisor crash forwards nothing either — leaves this
|
|
157
|
-
// generation reparented and
|
|
153
|
+
// generation reparented and running the team unattended. The parent-death watch polls for the
|
|
158
154
|
// reparent and runs the same graceful stop a forwarded SIGTERM would have run, so a killed
|
|
159
155
|
// supervisor takes its team down with it (BUGS.md 2026-09-30). The spawner hands its pid
|
|
160
156
|
// down (SUPERVISOR_PID_ENV), so a supervisor killed during this child's startup — before the
|
|
@@ -162,10 +158,10 @@ export async function cmdRun(root, args) {
|
|
|
162
158
|
// the supervised generation: an unsupervised `run` became the supervisor above and returned.
|
|
163
159
|
const expectedPpid = expectedSupervisorPid();
|
|
164
160
|
startParentDeathWatch(stop, expectedPpid !== undefined ? { expectedPpid } : {});
|
|
165
|
-
const enabled =
|
|
166
|
-
// A scoped round's
|
|
167
|
-
const roles = roleFilter
|
|
168
|
-
// Main's green verdicts outlive this generation: the redeployer and the
|
|
161
|
+
const enabled = runnerIds(config);
|
|
162
|
+
// A scoped round's role list is exactly the filter; otherwise every enabled role runs.
|
|
163
|
+
const roles = roleFilter ?? enabled;
|
|
164
|
+
// Main's green verdicts outlive this generation: the redeployer and the roles' red-main gate
|
|
169
165
|
// start from the ones the previous generation verified instead of re-running the suite.
|
|
170
166
|
(await import("../baseline/main-baseline.js")).persistBaselineVerdicts(root);
|
|
171
167
|
// A once round never self-redeploys: it runs one round on the build it booted and exits,
|
|
@@ -181,16 +177,22 @@ export async function cmdRun(root, args) {
|
|
|
181
177
|
// from a subdirectory must see where .tumwater/ actually lives.
|
|
182
178
|
const rootNote = root !== process.cwd() ? ` · root ${root}` : "";
|
|
183
179
|
const forNote = forMs !== null ? ` · for ${durationLabel(forMs)}` : "";
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
180
|
+
// A --once --json round's stdout must hold exactly its one JSON document, so its banner
|
|
181
|
+
// and event stream are suppressed: the caller parses the round's facts directly instead
|
|
182
|
+
// of the prose, and the orchestrator/merge pipeline logs events through logEvent (file
|
|
183
|
+
// plus subscription), not stdout, with nothing else printed on stdout for a once round.
|
|
184
|
+
if (!json) {
|
|
185
|
+
say(once
|
|
186
|
+
? `tumwater once on branch ${mainBranch}${rootNote} — one round, then exit`
|
|
187
|
+
: `tumwater running on branch ${mainBranch}${forNote}${build}${rootNote} — Ctrl+C to stop`);
|
|
188
|
+
say(`roles: ${roles.join(", ")}`);
|
|
189
|
+
say("watch: `tumwater tui` or `tumwater logs -f` in another terminal; events stream below\n");
|
|
190
|
+
}
|
|
191
|
+
const unsubscribe = json ? () => { } : subscribeEvents((e) => say(formatEvent(e)));
|
|
192
|
+
// Snapshot each role's run counter so the once summary can tell this round's runs from
|
|
191
193
|
// the persisted history (the state file accumulates across rounds).
|
|
192
194
|
await runStartupMigrations(root); // so the snapshot reads a migrated state/<new>.json
|
|
193
|
-
const
|
|
195
|
+
const runsBefore = new Map(roles.map((role) => [role, loadRunnerState(root, role).runs]));
|
|
194
196
|
let exit;
|
|
195
197
|
try {
|
|
196
198
|
exit = await (await import("../orchestrator/orchestrator.js")).runOrchestrator({
|
|
@@ -211,48 +213,42 @@ export async function cmdRun(root, args) {
|
|
|
211
213
|
scheduledStopWatch.clear();
|
|
212
214
|
unsubscribe();
|
|
213
215
|
}
|
|
214
|
-
if (once)
|
|
215
|
-
|
|
216
|
+
if (once) {
|
|
217
|
+
if (json)
|
|
218
|
+
sayJson(onceReport(root, roles, runsBefore, exit.settled, exit.runsDone));
|
|
219
|
+
else
|
|
220
|
+
say(onceSummary(root, roles, runsBefore, exit.settled, exit.runsDone));
|
|
221
|
+
}
|
|
216
222
|
else if (forMs !== null && !exit.restart) {
|
|
217
|
-
const { rows, covered } =
|
|
218
|
-
|
|
223
|
+
const { rows, covered } = readRunRowsSince(root, Date.now() - windowStart, null);
|
|
224
|
+
const summary = summarizeRunRows(rows);
|
|
225
|
+
if (json)
|
|
226
|
+
sayJson(windowReport(durationLabel(forMs), summary, covered));
|
|
227
|
+
else
|
|
228
|
+
say(windowSummary(durationLabel(forMs), summary, covered));
|
|
219
229
|
}
|
|
220
230
|
if (once) {
|
|
221
|
-
const failures = onceFailures(root, roles,
|
|
231
|
+
const failures = onceFailures(root, roles, runsBefore, exit.runsDone);
|
|
222
232
|
if (failures.length > 0) {
|
|
223
|
-
|
|
233
|
+
// In --json mode the failure facts already ride in the report's `failures`; the exit
|
|
234
|
+
// code is the contract a CI caller needs, so only the prose line is suppressed.
|
|
235
|
+
if (!json)
|
|
236
|
+
say(`once: failed — ${failures.map((f) => `${f.role} ${f.result}`).join(", ")}`);
|
|
224
237
|
process.exitCode = ONCE_FAILED_EXIT_CODE;
|
|
225
238
|
}
|
|
239
|
+
// A round a signal, parent death, or scheduled stop cut short before it completed must
|
|
240
|
+
// not read as success (0) to the CI caller parsing the JSON: the document still carries
|
|
241
|
+
// the round's partial facts, but the truncated round exits non-zero. Non-json once keeps
|
|
242
|
+
// its historical behavior (prose + 0 unless a role failed).
|
|
243
|
+
if (json && stopping)
|
|
244
|
+
process.exitCode = ONCE_FAILED_EXIT_CODE;
|
|
226
245
|
}
|
|
227
246
|
// A self-redeploy swapped the new build into dist/: hand the terminal back to the supervisor,
|
|
228
247
|
// which respawns this same script — now the new code — as the next generation.
|
|
229
248
|
if (exit.restart)
|
|
230
249
|
process.exit(RESTART_EXIT_CODE);
|
|
231
250
|
}
|
|
232
|
-
/**
|
|
233
|
-
* positive integer no later than `now`, else `now` (absent, malformed, or in the future). */
|
|
234
|
-
export function inheritedWindowStart(raw, now) {
|
|
235
|
-
const n = Number(raw);
|
|
236
|
-
return raw !== undefined && Number.isInteger(n) && n > 0 && n <= now ? n : now;
|
|
237
|
-
}
|
|
238
|
-
/** The `run --for` exit summary: a window line, then the `history --summary` table for the ticks
|
|
239
|
-
* that ended since the window opened — every tick counted once by its outcome class. Only the
|
|
240
|
-
* final exit prints it; a mid-window self-redeploy hands off silently and the next generation
|
|
241
|
-
* inherits the window start, so the table spans every generation. */
|
|
242
|
-
export function windowSummary(label, summary, covered) {
|
|
243
|
-
const { total } = summary;
|
|
244
|
-
const lines = [
|
|
245
|
-
total.runs === 0
|
|
246
|
-
? `for ${label}: 0 runs — nothing ran`
|
|
247
|
-
: `for ${label}: ${plural(total.runs, "run")} — ${total.changes} changes, ${total.none} none, ${total.refused} refused, ${total.problems} problems`,
|
|
248
|
-
];
|
|
249
|
-
if (total.runs > 0)
|
|
250
|
-
lines.push(renderSummary(summary));
|
|
251
|
-
if (!covered)
|
|
252
|
-
lines.push(SPARSE_WINDOW_NOTE);
|
|
253
|
-
return lines.join("\n");
|
|
254
|
-
}
|
|
255
|
-
/** Arm the windowed run's deadline timer: the one home of the arming `cmdRun` does for
|
|
251
|
+
/** Arm the windowed run's deadline timer: the one home of the arming `cmdStart` does for
|
|
256
252
|
* `run --for <duration>`. A duration over setTimeout's own 32-bit signed ceiling
|
|
257
253
|
* (2_147_483_647 ms ≈ 24.8 days) is clamped by Node to 1ms with a TimeoutOverflowWarning, so
|
|
258
254
|
* a raw setTimeout would fire the deadline the instant the run booted — and `run --for`
|
|
@@ -260,7 +256,7 @@ export function windowSummary(label, summary, covered) {
|
|
|
260
256
|
* input, not an error. The timer is therefore armed in ≤32-bit chunks: each setTimeout covers
|
|
261
257
|
* at most the ceiling, and a chunk that expires with time still left re-arms for the
|
|
262
258
|
* remainder, so the callback runs exactly once, after the full duration, whatever its size.
|
|
263
|
-
* Returns a clear() handle (the finally in
|
|
259
|
+
* Returns a clear() handle (the finally in cmdStart retracts the timer when the run ends first —
|
|
264
260
|
* an early Ctrl+C — so no stray timer outlives the run); clear() is safe after the callback
|
|
265
261
|
* has run. Exported for tests. */
|
|
266
262
|
export function armDeadlineTimer(ms, onFired) {
|
|
@@ -280,133 +276,65 @@ export function armDeadlineTimer(ms, onFired) {
|
|
|
280
276
|
}
|
|
281
277
|
const SCHEDULED_STOP_POLL_MS = 5000;
|
|
282
278
|
/** Poll for a `tumwater stop --in` marker: once its `until` has passed, consume the marker,
|
|
283
|
-
* stop polling, and call `onStop` (
|
|
279
|
+
* stop polling, and call `onStop` (cmdStart's Ctrl+C closure) exactly once. The interval is
|
|
284
280
|
* unref'd; clear() retracts it. Exported for tests. */
|
|
285
|
-
export function armScheduledStopWatch(root, onStop) {
|
|
281
|
+
export function armScheduledStopWatch(root, onStop, quiet = false) {
|
|
286
282
|
const timer = setInterval(() => {
|
|
287
283
|
const at = scheduledStopAt(root);
|
|
288
284
|
if (at === undefined || at > Date.now())
|
|
289
285
|
return;
|
|
290
286
|
clearInterval(timer);
|
|
291
287
|
cancelScheduledStop(root);
|
|
292
|
-
|
|
288
|
+
// quiet suppresses the "scheduled stop reached" prose for a --once --json round so its
|
|
289
|
+
// stdout holds exactly the one JSON document; the non-zero exit carries the interruption.
|
|
290
|
+
if (!quiet)
|
|
291
|
+
say("scheduled stop reached — stopping");
|
|
293
292
|
onStop();
|
|
294
293
|
}, SCHEDULED_STOP_POLL_MS);
|
|
295
294
|
timer.unref();
|
|
296
295
|
return { clear: () => clearInterval(timer) };
|
|
297
296
|
}
|
|
298
|
-
/** Parse `run --for <duration>` (the windowed run): the parsed duration in milliseconds, or null
|
|
299
|
-
* when the flag is absent. The two body-level rules fail fast, before any repo gate or boot: the
|
|
300
|
-
* value must parse as a duration (parseDurationFlag's own wording, the same messages the
|
|
301
|
-
* dispatcher's durationFlagSpec("run --for") gate already ran), and `--for` and `--once` are rivals
|
|
302
|
-
* — a one-round run and a windowed run cannot both apply. The 90-day ceiling is `pause --for`'s
|
|
303
|
-
* (PAUSE_FOR_MAX_MS), the same cap every scheduled window in the harness honors. Exported for
|
|
304
|
-
* tests, exactly as onceSummary below is. */
|
|
305
|
-
export function parseRunWindow(args, once) {
|
|
306
|
-
const raw = flagValue(args, "--for");
|
|
307
|
-
if (raw === null)
|
|
308
|
-
return null;
|
|
309
|
-
const ms = parseDurationFlag("start --for", raw);
|
|
310
|
-
if (once)
|
|
311
|
-
failRivalShapes("--for", "--once", "a one-round run and a windowed run cannot both apply");
|
|
312
|
-
failOverDurationCap("--for", ms, PAUSE_FOR_MAX_MS);
|
|
313
|
-
return ms;
|
|
314
|
-
}
|
|
315
|
-
/** Results that mean the harness or the project's check failed and the round's work did not
|
|
316
|
-
* merge: a `--once` round with a role ending in one exits ONCE_FAILED_EXIT_CODE. `rejected` and
|
|
317
|
-
* `refused` are the review gate and the agent working as designed; `aborted`, `quiet_killed`, and
|
|
318
|
-
* `user_aborted` only reach a once round on a shutdown or operator abort, which exit
|
|
319
|
-
* their own way. */
|
|
320
|
-
export const ONCE_FAILURE_RESULTS = new Set([
|
|
321
|
-
"error",
|
|
322
|
-
"review_error",
|
|
323
|
-
"merge_conflict",
|
|
324
|
-
"merge_blocked",
|
|
325
|
-
"main_red",
|
|
326
|
-
]);
|
|
327
|
-
function ticksThisRound(s, role, ticksBefore, ticksRun) {
|
|
328
|
-
return ticksRun ? (ticksRun.get(role) ?? 0) : s.runs - (ticksBefore.get(role) ?? 0);
|
|
329
|
-
}
|
|
330
|
-
/** Every role that could have ticked in this once round — `roles` in order plus any role that
|
|
331
|
-
* appears only in `ticksRun` (enabled mid-round after `ticksBefore` was taken) — paired with
|
|
332
|
-
* its loaded loop state and the number of ticks it ran this round. The same per-role setup
|
|
333
|
-
* onceFailures and onceSummary both repeat, folded here so the two summary passes cannot
|
|
334
|
-
* drift on which roles they visit or how they count a round's work. */
|
|
335
|
-
function onceRoundRoles(root, roles, ticksBefore, ticksRun) {
|
|
336
|
-
const extra = ticksRun ? [...ticksRun.keys()].filter((r) => !roles.includes(r)) : [];
|
|
337
|
-
return [...roles, ...extra].map((role) => {
|
|
338
|
-
const state = loadLoopState(root, role);
|
|
339
|
-
return { role, state, ran: ticksThisRound(state, role, ticksBefore, ticksRun) };
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
/** The roles that ticked this round and whose last result is in ONCE_FAILURE_RESULTS. */
|
|
343
|
-
export function onceFailures(root, roles, ticksBefore, ticksRun) {
|
|
344
|
-
const out = [];
|
|
345
|
-
for (const { role, state, ran } of onceRoundRoles(root, roles, ticksBefore, ticksRun)) {
|
|
346
|
-
if (ran > 0 && state.lastResult && ONCE_FAILURE_RESULTS.has(state.lastResult)) {
|
|
347
|
-
out.push({ role, result: state.lastResult });
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return out;
|
|
351
|
-
}
|
|
352
|
-
/** The once round's one-line summary, read from the runners' persisted loop state plus the
|
|
353
|
-
* orchestrator's own settle reasons — a cron job's log shows what the round did without parsing
|
|
354
|
-
* events. Roles whose tick counter advanced bucket by their last completed result; the rest are
|
|
355
|
-
* skipped with the reason the orchestrator settled them for (`paused`, `resume pending`,
|
|
356
|
-
* `backoff`, `deferred`, `disabled`, `idle` — handed back on OrchestratorExit.settled, so a
|
|
357
|
-
* deferred role reads as set aside, not as "nothing was due"); a role the map lacks (defensively
|
|
358
|
-
* — the round only exits once every role is settled) falls back to deriving the reason from
|
|
359
|
-
* state: a pause marker when one is held, a pending resume when one is flagged, backoff when
|
|
360
|
-
* `backoffSeconds` is raised, otherwise idle — the same classification settleSkipped draws,
|
|
361
|
-
* and keyed on the same signal: backoffSeconds is the backoff indicator, while nextRunAt is
|
|
362
|
-
* shared with the scheduled clock a productive tick also writes (with backoffSeconds 0), so a
|
|
363
|
-
* future nextRunAt alone reads as a not-yet-due role, not as backoff (BUGS.md 2026-09-25,
|
|
364
|
-
* the same conflation settleSkipped itself carried).
|
|
365
|
-
*
|
|
366
|
-
* `ticksRun`, when the caller hands it back (the orchestrator's OnceRound snapshot deltas),
|
|
367
|
-
* is the tick count's source of truth and also names roles the pre-round list never had —
|
|
368
|
-
* a role enabled mid-round joins the runners array after `ticksBefore` was taken, so its
|
|
369
|
-
* whole persisted history must not be counted as this round's work. Without it the old
|
|
370
|
-
* `ticksBefore` delta path runs, for callers (and tests) without a round object. */
|
|
371
|
-
export function onceSummary(root, roles, ticksBefore, settled, ticksRun) {
|
|
372
|
-
let ticks = 0;
|
|
373
|
-
const outcomes = new Map();
|
|
374
|
-
const skipped = [];
|
|
375
|
-
for (const { role, state, ran } of onceRoundRoles(root, roles, ticksBefore, ticksRun)) {
|
|
376
|
-
if (ran > 0) {
|
|
377
|
-
ticks += ran;
|
|
378
|
-
const key = state.lastResult ?? "no_change";
|
|
379
|
-
increment(outcomes, key);
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
const reason = settled?.get(role) ??
|
|
383
|
-
(isTeamPaused(root) || loopPaused(new Set(pausedRoles(root)), role)
|
|
384
|
-
? "paused"
|
|
385
|
-
: stateSkipReason(state));
|
|
386
|
-
skipped.push(reason);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
// Sort by result key only. The comparator makes that explicit: a comparator-less sort
|
|
390
|
-
// would order `[key, count]` tuples by their "key,count" string form, which happens to
|
|
391
|
-
// agree only while each key is unique.
|
|
392
|
-
const counts = [...outcomes.entries()]
|
|
393
|
-
.sort(([a], [b]) => compareCodeUnits(a, b))
|
|
394
|
-
.map(([k, n]) => `${n} ${k}`)
|
|
395
|
-
.join(", ");
|
|
396
|
-
const skipNote = skipped.length === 0 ? "" : `, ${skipped.length} skipped (${skipped.join(", ")})`;
|
|
397
|
-
return `once: ${plural(ticks, "run")} — ${counts || "nothing ran"}${skipNote}`;
|
|
398
|
-
}
|
|
399
297
|
/** Split `run`'s args for `run --gui` and `run --tui`: whether the supervisor should also start
|
|
400
298
|
* the dashboard and/or host the observer TUI, and the args the supervised orchestrator
|
|
401
|
-
* generation gets with both stripped so the generation's own
|
|
299
|
+
* generation gets with both stripped so the generation's own cmdStart never tries to supervise a
|
|
402
300
|
* second dashboard or re-attach a TUI in a terminal the parent owns (a self-redeploy spawns a
|
|
403
301
|
* fresh generation each time). Exported so the unit tests pin the stripping that removing the
|
|
404
302
|
* spawn wiring would otherwise silently drop. */
|
|
405
303
|
export function guiChildPlan(runArgs) {
|
|
304
|
+
const portIdx = runArgs.indexOf("--port");
|
|
305
|
+
const portRaw = portIdx >= 0 ? runArgs[portIdx + 1] : undefined;
|
|
306
|
+
const port = portRaw !== undefined ? parsePortFlag(portRaw) : null;
|
|
307
|
+
const tokenIdx = runArgs.indexOf("--token");
|
|
308
|
+
const tokenRaw = tokenIdx >= 0 ? runArgs[tokenIdx + 1] : undefined;
|
|
309
|
+
// Drop --port/--token and their value tokens from the supervized orchestrator generation
|
|
310
|
+
// along with --gui/--tui (and --all-interfaces by name): the child's own cmdStart would
|
|
311
|
+
// otherwise hit the dashboard-only-flag refusals (its args no longer carry --gui) or supervise
|
|
312
|
+
// a second dashboard, and a self-redeploy hand-off must never forward a dashboard flag it is
|
|
313
|
+
// not serving itself. --all-interfaces and --token have no value token to skip, so the name
|
|
314
|
+
// filter below drops them; the valued pair is skipped by index like --port's.
|
|
315
|
+
const skip = new Set();
|
|
316
|
+
if (portIdx >= 0) {
|
|
317
|
+
skip.add(portIdx);
|
|
318
|
+
if (portIdx + 1 < runArgs.length)
|
|
319
|
+
skip.add(portIdx + 1);
|
|
320
|
+
}
|
|
321
|
+
if (tokenIdx >= 0) {
|
|
322
|
+
skip.add(tokenIdx);
|
|
323
|
+
if (tokenIdx + 1 < runArgs.length)
|
|
324
|
+
skip.add(tokenIdx + 1);
|
|
325
|
+
}
|
|
326
|
+
const guiArgs = [];
|
|
327
|
+
if (port !== null)
|
|
328
|
+
guiArgs.push("--port", String(port));
|
|
329
|
+
if (runArgs.includes("--all-interfaces"))
|
|
330
|
+
guiArgs.push("--all-interfaces");
|
|
331
|
+
if (tokenRaw !== undefined)
|
|
332
|
+
guiArgs.push("--token", tokenRaw);
|
|
406
333
|
return {
|
|
407
334
|
spawnGui: runArgs.includes("--gui"),
|
|
408
335
|
spawnTui: runArgs.includes("--tui"),
|
|
409
|
-
|
|
336
|
+
guiArgs,
|
|
337
|
+
orchestratorArgs: runArgs.filter((arg, i) => arg !== "--gui" && arg !== "--tui" && arg !== "--all-interfaces" && !skip.has(i)),
|
|
410
338
|
childStdio: runArgs.includes("--tui") ? ["ignore", "ignore", "ignore"] : "inherit",
|
|
411
339
|
};
|
|
412
340
|
}
|
|
@@ -416,30 +344,30 @@ export function guiChildPlan(runArgs) {
|
|
|
416
344
|
* redeploy and exits on its own if this supervisor dies outright (SIGKILL, a crash) — a killed
|
|
417
345
|
* supervisor never leaves a dashboard holding its port. Any other dashboard exit (a failed bind
|
|
418
346
|
* prints its own message and exits) ends this loop and leaves the team running. */
|
|
419
|
-
async function superviseDashboard(signal, stdio = "inherit") {
|
|
347
|
+
async function superviseDashboard(signal, stdio = "inherit", guiArgs = []) {
|
|
420
348
|
const { DASHBOARD_CHILD_ENV } = await import("../redeploy/self-reload.js");
|
|
421
349
|
const { spawnGuiChild } = await import("../process/supervisor.js");
|
|
422
350
|
for (;;) {
|
|
423
|
-
const exit = await spawnGuiChild(signal, { [DASHBOARD_CHILD_ENV]: String(process.pid) }, stdio);
|
|
351
|
+
const exit = await spawnGuiChild(signal, { [DASHBOARD_CHILD_ENV]: String(process.pid) }, stdio, guiArgs);
|
|
424
352
|
if (signal.aborted || exit.code !== RESTART_EXIT_CODE)
|
|
425
353
|
return;
|
|
426
354
|
}
|
|
427
355
|
}
|
|
428
|
-
/** The supervisor half of `tumwater
|
|
356
|
+
/** The supervisor half of `tumwater start` (src/process/supervisor.ts): spawn the orchestrator as a
|
|
429
357
|
* child generation and respawn it whenever it exits RESTART_EXIT_CODE after redeploying itself.
|
|
430
358
|
* Ctrl+C reaches the child directly from the terminal, so only SIGTERM is forwarded; the
|
|
431
359
|
* supervisor's own exit code is whatever the last generation's was. A team that goes down without
|
|
432
360
|
* the operator asking leaves a `supervisor_exit` event behind (BUGS.md 2026-09-23): the dead
|
|
433
361
|
* generation's stderr reached only this terminal, which nobody may be watching. */
|
|
434
|
-
async function
|
|
435
|
-
// The supervisor machinery loads lazily, like
|
|
362
|
+
async function superviseStartCommand(root, runArgs, branchArg, forMs) {
|
|
363
|
+
// The supervisor machinery loads lazily, like cmdStart's orchestrator import above: a command
|
|
436
364
|
// that never boots the team never compiles it.
|
|
437
|
-
const { RUN_WINDOW_START_ENV, teamDownEvent,
|
|
365
|
+
const { RUN_WINDOW_START_ENV, teamDownEvent, spawnStartChild, superviseStart } = await import("../process/supervisor.js");
|
|
438
366
|
const plan = guiChildPlan(runArgs);
|
|
439
367
|
// A TUI owns the terminal, so it can only be hosted when this supervisor's own stdin and
|
|
440
368
|
// stdout are a TTY — the same guard runTui enforces. Check before booting a team a TUI
|
|
441
369
|
// could never drive, surfacing the actionable tuiTerminalError instead of letting runTui's
|
|
442
|
-
// rejection sit unhandled until
|
|
370
|
+
// rejection sit unhandled until superviseStart settles and then crash the supervisor after
|
|
443
371
|
// the team has (pointlessly) started.
|
|
444
372
|
if (plan.spawnTui && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
445
373
|
const { tuiTerminalError } = await import("../ui/tui/tui-input.js");
|
|
@@ -462,8 +390,8 @@ async function superviseRunCommand(root, runArgs, branchArg, forMs) {
|
|
|
462
390
|
});
|
|
463
391
|
// The TUI is hosted in the supervisor process, beside the orchestrator generations it must
|
|
464
392
|
// outlive. Quitting it (q or Ctrl+C handled by ink's key handler) is the run's Ctrl+C:
|
|
465
|
-
// runTui resolves and the .then sets `stopping` and aborts the controller, so
|
|
466
|
-
// drains in-flight
|
|
393
|
+
// runTui resolves and the .then sets `stopping` and aborts the controller, so superviseStart
|
|
394
|
+
// drains in-flight runs and the team shuts down exactly as Ctrl+C on a plain `run` does;
|
|
467
395
|
// the team-down teardown (dashboard abort, onDone, terminal restore) is unchanged.
|
|
468
396
|
// reloadDisabled keeps the hosted TUI off its self-reload watch: the team's own first
|
|
469
397
|
// self-redeploy dist swap must not resolve runTui (which would read as a quit and abort
|
|
@@ -481,16 +409,16 @@ async function superviseRunCommand(root, runArgs, branchArg, forMs) {
|
|
|
481
409
|
guiController.abort();
|
|
482
410
|
})
|
|
483
411
|
: Promise.resolve();
|
|
484
|
-
const gui = plan.spawnGui ? superviseDashboard(guiController.signal, plan.childStdio) : Promise.resolve();
|
|
485
|
-
const code = await
|
|
486
|
-
spawnChild: (signal) =>
|
|
412
|
+
const gui = plan.spawnGui ? superviseDashboard(guiController.signal, plan.childStdio, plan.guiArgs) : Promise.resolve();
|
|
413
|
+
const code = await superviseStart({
|
|
414
|
+
spawnChild: (signal) => spawnStartChild(signal, plan.orchestratorArgs, windowEnv, plan.childStdio),
|
|
487
415
|
stopping: () => stopping,
|
|
488
416
|
onRespawn: (generation) => {
|
|
489
417
|
// With a hosted TUI the supervisor owns no free stdout: its own say() would paint the
|
|
490
418
|
// restart line over the ink full-screen render and corrupt it — the same interleaving
|
|
491
419
|
// childStdio silences for the children. The TUI renders status from on-disk state, so
|
|
492
420
|
// the redeploy is visible there as the team coming back.
|
|
493
|
-
if (!plan.spawnTui)
|
|
421
|
+
if (!plan.spawnTui && !runArgs.includes("--json"))
|
|
494
422
|
say(`\nrestarting onto the update (restart ${generation})\n`);
|
|
495
423
|
},
|
|
496
424
|
onCrashLoop: () => process.stderr.write("tumwater restarted itself too many times in a minute — giving up\n"),
|