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,4 +1,5 @@
|
|
|
1
|
-
import { gateRoleConfig, newBudgetGateState, pollBudgetGate,
|
|
1
|
+
import { gateRoleConfig, newBudgetGateState, pollBudgetGate, runOnPair, subscriptionBackendOf, } from "./budget-gates.js";
|
|
2
|
+
import { backendsInUse } from "./startup-gate.js";
|
|
2
3
|
import { fallbackDemotion } from "../budget/fallback-breaker.js";
|
|
3
4
|
import { newPauseGateState, pollPauseGates } from "./pause-gates.js";
|
|
4
5
|
import { newStreakGateState, pollStreakGate } from "./streak-gate.js";
|
|
@@ -7,18 +8,18 @@ import { newMaintenanceQuotaGateState, pollMaintenanceQuotaGate, } from "./maint
|
|
|
7
8
|
import { newBootstrapGateState, pollBootstrapGate, } from "./bootstrap-gates.js";
|
|
8
9
|
import { newQuietHoursGateState, pollQuietHoursGate, roleQuietHold, } from "../scheduling/quiet-hours.js";
|
|
9
10
|
import { BYTES_PER_GB, newDiskGateState, pollDiskGate, sampleFreeBytes, } from "./disk-gate.js";
|
|
10
|
-
import { pollErrorStorm, pollFailureSpread, pollTeamHold, pollClaudeWindowWarning, pollClaudeWindowReserve } from "../team/team-polls.js";
|
|
11
|
-
import {
|
|
11
|
+
import { pollErrorStorm, pollFailureSpread, pollTeamHold, pollClaudeWindowWarning, pollClaudeWindowReserve, windowObservation } from "../team/team-polls.js";
|
|
12
|
+
import { readAccount } from "../agent/subscription-account.js";
|
|
12
13
|
import { standingHolds } from "../team/team-hold.js";
|
|
13
14
|
import { ERROR_STORM_QUIET } from "../team/error-storm.js";
|
|
14
15
|
import { FAILURE_SPREAD_QUIET } from "../team/failure-spread.js";
|
|
15
16
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
16
|
-
import { baseRoleOf, instancePauseKey,
|
|
17
|
+
import { baseRoleOf, instancePauseKey, runnerEnabled } from "../roles/runner-ids.js";
|
|
17
18
|
import { queuedMergeRoles } from "../merge/merge-queue.js";
|
|
18
19
|
import { MODEL_TIERS } from "../config/config-schema.js";
|
|
19
20
|
import { logEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
20
21
|
import { publishInfoFields } from "../team/orchestrator-info.js";
|
|
21
|
-
import { roleSeamTier } from "../config/config-views.js";
|
|
22
|
+
import { configForRole, roleSeamTier, windowReserveFor, withModelOverrides } from "../config/config-views.js";
|
|
22
23
|
/** A fresh gate family: nothing seen yet, so the first poll of an unpaused, unheld team
|
|
23
24
|
* logs nothing. The budget gate seeds its breaker and derived fallback view from the
|
|
24
25
|
* startup config; a live reload replaces the config on the next poll. */
|
|
@@ -33,8 +34,8 @@ export function newTeamGateStates(config) {
|
|
|
33
34
|
quiet: newQuietHoursGateState(),
|
|
34
35
|
disk: newDiskGateState(),
|
|
35
36
|
teamHold: new Map(),
|
|
36
|
-
claudeWindowWarned:
|
|
37
|
-
claudeWindowReserved:
|
|
37
|
+
claudeWindowWarned: new Set(),
|
|
38
|
+
claudeWindowReserved: new Set(),
|
|
38
39
|
errorStorm: ERROR_STORM_QUIET,
|
|
39
40
|
failureSpread: FAILURE_SPREAD_QUIET,
|
|
40
41
|
};
|
|
@@ -42,11 +43,16 @@ export function newTeamGateStates(config) {
|
|
|
42
43
|
/** Poll every team-wide gate and alarm, advancing `states` in place. */
|
|
43
44
|
export function pollTeamGates(states, ctx) {
|
|
44
45
|
const { root, runners, liveConfig, modelsPath, now, info } = ctx;
|
|
46
|
+
// The per-backend subscription account lookup (part 2e/9): each backend's account is read on
|
|
47
|
+
// demand — the window hold/reserve/warning loop, the window-move and the handback reads each
|
|
48
|
+
// call accounts fresh — so a second subscription backend is judged from its own account, never
|
|
49
|
+
// from claude's.
|
|
50
|
+
const accounts = (backend) => readAccount(root, backend, now);
|
|
45
51
|
// Daily cost budget gate (src/gates/budget-gates.ts owns the reads, the edge-triggered
|
|
46
52
|
// budget_* events, the breaker re-key, and the fallback config view): the orchestrator
|
|
47
53
|
// owns only the wiring — the demotion publish below and the per-runner assignment at
|
|
48
54
|
// the bottom of this block.
|
|
49
|
-
const { gate, roleConfig, spentUsd, capUsd, onFallback, resumed: gateResumed, pairs: fallbackPairs, priceResolved, servingResolved, budgetActive, windowTiers,
|
|
55
|
+
const { gate, roleConfig, spentUsd, capUsd, onFallback, resumed: gateResumed, pairs: fallbackPairs, priceResolved, servingResolved, budgetActive, windowTiers, windowResumedBackends, directorConfig, chain, } = pollBudgetGate(states.budget, {
|
|
50
56
|
root,
|
|
51
57
|
states: runners.map((r) => r.state),
|
|
52
58
|
liveConfig,
|
|
@@ -54,7 +60,7 @@ export function pollTeamGates(states, ctx) {
|
|
|
54
60
|
});
|
|
55
61
|
// Publish what observers cannot derive themselves: every pair's demotion — a per-tier
|
|
56
62
|
// fallback can demote a pair the legacy single `fallback` never names, and a dashboard that
|
|
57
|
-
// only sees the engaged pair's verdict would show a tier's roles
|
|
63
|
+
// only sees the engaged pair's verdict would show a tier's roles running while the
|
|
58
64
|
// scheduler holds them (part 5c/8) — plus the engaged pair's own (the legacy
|
|
59
65
|
// `fallbackDemoted` field, what `tumwater doctor` and the failure digest read) and the
|
|
60
66
|
// gate's own spend/cap pair — summed over the runners' live states, which every
|
|
@@ -92,26 +98,42 @@ export function pollTeamGates(states, ctx) {
|
|
|
92
98
|
// The director keeps the live config — an explicit human prompt outranks the
|
|
93
99
|
// autonomous-spend cap (gateRoleConfig). Assigned every poll (not only on transitions)
|
|
94
100
|
// so a runner created mid-gate, or one left behind by a broken-file poll that skipped
|
|
95
|
-
// the reload, can never
|
|
101
|
+
// the reload, can never run on the wrong model.
|
|
96
102
|
for (const r of runners) {
|
|
97
103
|
r.config = gateRoleConfig(r.role, liveConfig, roleConfig, directorConfig);
|
|
98
104
|
// An image reroute must never spend past the daily cap: while the gate has the team on
|
|
99
105
|
// its free fallback pairs, a reroute looks only among those pairs (plans/image-input.md
|
|
100
106
|
// "Reroute target" "Regime"). Set beside the config assignment so the scheduler's regime
|
|
101
|
-
// and the
|
|
107
|
+
// and the roles' never disagree.
|
|
102
108
|
r.budgetFallbackActive = onFallback;
|
|
103
109
|
}
|
|
104
|
-
// The budget reopened: a
|
|
105
|
-
// those
|
|
106
|
-
//
|
|
110
|
+
// The budget reopened: a run that started on a fallback pair keeps it until it ends, so hand
|
|
111
|
+
// those runs back — abort them resumably (session and worktree edits kept) and let their next
|
|
112
|
+
// run continue the same session on the primary, whose config the assignment above already
|
|
107
113
|
// installed (PLANS.md 2026-09-30). Per-tier fallbacks put different roles on different pairs
|
|
108
114
|
// (part 5c/8), so the match runs over EVERY pair the price-based resolution hands out, not only
|
|
109
|
-
// one. A
|
|
115
|
+
// one. A run started on the primary keeps running, the director is exempt, and the merges (the
|
|
110
116
|
// slot's own runs) are untouched. One event per pair names who was handed back, so the resulting
|
|
111
|
-
// aborted
|
|
117
|
+
// aborted runs read as the budget reopening, not as unexplained failures. The subscription
|
|
112
118
|
// window reset (below the cap): the same hand-back for the pairs the window move used. A window
|
|
113
119
|
// that resets while the cap still holds hands back nothing.
|
|
114
|
-
const handBack = gateResumed || (
|
|
120
|
+
const handBack = gateResumed || (windowResumedBackends.length > 0 && !budgetActive);
|
|
121
|
+
// The window handback names the backend whose window resumed (the entry's goal) — not the
|
|
122
|
+
// handed-back pair's own backend: for claude-only the fallback pair carries no backend (it would
|
|
123
|
+
// read undefined), and with two subscription backends it would name the opposite one. A pair
|
|
124
|
+
// belongs to those resumed backends whose tiers' price-resolved pair it is.
|
|
125
|
+
const resumedBackendSet = new Set(windowResumedBackends);
|
|
126
|
+
const resumedBackendForPair = (leftPair) => {
|
|
127
|
+
for (const tier of MODEL_TIERS) {
|
|
128
|
+
const p = priceResolved[tier]?.pair;
|
|
129
|
+
if (p && p.provider === leftPair.provider && p.model === leftPair.model) {
|
|
130
|
+
const b = subscriptionBackendOf(withModelOverrides(liveConfig, tier, {}), accounts);
|
|
131
|
+
if (b !== null && resumedBackendSet.has(b))
|
|
132
|
+
return b;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
};
|
|
115
137
|
if (handBack && fallbackPairs.length > 0) {
|
|
116
138
|
// A cap reopen while the window is still used up leaves the window's tiers on their
|
|
117
139
|
// fallback pairs: handing those runs back would abort them to restart on the same pair.
|
|
@@ -122,7 +144,7 @@ export function pollTeamGates(states, ctx) {
|
|
|
122
144
|
for (const leftPair of fallbackPairs) {
|
|
123
145
|
if (stillMoved.some((p) => p.provider === leftPair.provider && p.model === leftPair.model))
|
|
124
146
|
continue;
|
|
125
|
-
const matching = runners.filter((r) => r.role !== DIRECTOR_ROLE &&
|
|
147
|
+
const matching = runners.filter((r) => r.role !== DIRECTOR_ROLE && runOnPair(r.runModelPair(), leftPair));
|
|
126
148
|
if (matching.length > 0) {
|
|
127
149
|
logEventBestEffort(root, {
|
|
128
150
|
source: TUMWATER_SOURCE,
|
|
@@ -130,10 +152,12 @@ export function pollTeamGates(states, ctx) {
|
|
|
130
152
|
roles: matching.map((r) => r.role),
|
|
131
153
|
provider: leftPair.provider,
|
|
132
154
|
model: leftPair.model,
|
|
133
|
-
...(gateResumed
|
|
155
|
+
...(gateResumed
|
|
156
|
+
? {}
|
|
157
|
+
: { reason: "window", backend: resumedBackendForPair(leftPair) }),
|
|
134
158
|
});
|
|
135
159
|
for (const r of matching)
|
|
136
|
-
r.
|
|
160
|
+
r.handBackRun();
|
|
137
161
|
}
|
|
138
162
|
}
|
|
139
163
|
}
|
|
@@ -142,7 +166,7 @@ export function pollTeamGates(states, ctx) {
|
|
|
142
166
|
// set, both read fresh per cycle so a marker change takes effect on the next poll.
|
|
143
167
|
const { userPaused, pausedRoles } = pollPauseGates(root, states.pause);
|
|
144
168
|
// The error-streak circuit breaker (src/gates/streak-gate.ts): a role past ERROR_STREAK_BREAKER
|
|
145
|
-
// consecutive failed
|
|
169
|
+
// consecutive failed runs is paused through the same per-role marker the operator's
|
|
146
170
|
// `pause --role` writes — act-on-it where the warn bar and the storm alarms only talk. The
|
|
147
171
|
// director is not exempt; a failing director cannot process prompts anyway. Roles it just
|
|
148
172
|
// paused fold into this poll's paused-roles view, so the scheduler blocks them on this very
|
|
@@ -157,22 +181,36 @@ export function pollTeamGates(states, ctx) {
|
|
|
157
181
|
states.pause.prevPausedRoles.add(role);
|
|
158
182
|
const pausedRolesNow = streakPaused.length > 0 ? new Set([...pausedRoles, ...streakPaused]) : pausedRoles;
|
|
159
183
|
// The per-tier budget pause (part 5c/8): a role whose model tier resolved to no usable free
|
|
160
|
-
// pair while the cap is reached starts no new
|
|
184
|
+
// pair while the cap is reached starts no new runs — the stateless verdict recomputed every
|
|
161
185
|
// poll from the SAME resolution the gate's value came from, beside `capPaused` (the
|
|
162
186
|
// scheduling pass folds the sets; the director is exempt, as under every autonomous gate).
|
|
163
187
|
// The demoted pair's probe pierces the set for its own tier's roles only — probeRoles in the
|
|
164
188
|
// orchestrator's scheduling pass.
|
|
165
189
|
const budgetPausedRoles = new Set();
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
190
|
+
const windowMovedRoles = new Set();
|
|
191
|
+
const movedTiers = Object.keys(windowTiers);
|
|
192
|
+
// The backend whose used-up window moved a given tier: the tier's own seam's backend — the same
|
|
193
|
+
// read budget-gates made to put it in `windowTiers`. A role on a moved tier whose own backend is
|
|
194
|
+
// DIFFERENT (a healthy backend riding the same tier) keeps its pair: only the role whose own
|
|
195
|
+
// backend owns the out window is moved.
|
|
196
|
+
const tierMovedBackend = (tier) => subscriptionBackendOf(withModelOverrides(liveConfig, tier, {}), accounts);
|
|
197
|
+
for (const r of runners) {
|
|
198
|
+
if (r.role === DIRECTOR_ROLE)
|
|
199
|
+
continue;
|
|
200
|
+
const tier = roleSeamTier(liveConfig, r.role);
|
|
201
|
+
// A role pinned to the moved tier's own subscription backend keeps it through the window move
|
|
202
|
+
// (it is not on a pair); a role on the same tier pinning a healthy backend does not.
|
|
203
|
+
if (movedTiers.includes(tier) &&
|
|
204
|
+
subscriptionBackendOf(configForRole(liveConfig, r.role), accounts) === tierMovedBackend(tier))
|
|
205
|
+
windowMovedRoles.add(r.role);
|
|
206
|
+
// Below the cap only a window-moved role can sit on a demoted fallback.
|
|
207
|
+
if (!budgetActive && !windowMovedRoles.has(r.role))
|
|
208
|
+
continue;
|
|
209
|
+
if (servingResolved[tier].pair === null)
|
|
210
|
+
budgetPausedRoles.add(r.role);
|
|
173
211
|
}
|
|
174
|
-
// The per-role daily cost cap (src/gates/role-cap-gates.ts): a
|
|
175
|
-
// reached its own maxDailyCostUsdPerRole entry starts no new
|
|
212
|
+
// The per-role daily cost cap (src/gates/role-cap-gates.ts): a role whose local-day spend has
|
|
213
|
+
// reached its own maxDailyCostUsdPerRole entry starts no new runs — the stateless verdict
|
|
176
214
|
// recomputed every poll, no pause marker written (the marker is anonymous; a cap pause must
|
|
177
215
|
// never masquerade as an operator's), so this returns its own set beside pausedRoles and the
|
|
178
216
|
// pause gates' edge bookkeeping never sees it. A live config edit applies on the next poll;
|
|
@@ -180,27 +218,26 @@ export function pollTeamGates(states, ctx) {
|
|
|
180
218
|
const capPaused = pollRoleCapGate(root, states.cap, runners, liveConfig.maxDailyCostUsdPerRole, now);
|
|
181
219
|
// The maintenance allowance (plans/work-ratio.md, part 1b/4): code-maintenance merges in a
|
|
182
220
|
// rolling 24 h window are capped at `maintenancePerWorkMerge × work + MAINTENANCE_DAILY_FLOOR`.
|
|
183
|
-
// The gate counts the window, adds the maintenance
|
|
221
|
+
// The gate counts the window, adds the maintenance roles already in flight (running or
|
|
184
222
|
// holding a queued merge, so six concurrent permits cannot overshoot), and logs the one
|
|
185
|
-
// hold/resume event per transition. Enabled only: a disabled
|
|
186
|
-
// scheduling pass spends the returned headroom one due maintenance
|
|
223
|
+
// hold/resume event per transition. Enabled only: a disabled role has no runs to count. The
|
|
224
|
+
// scheduling pass spends the returned headroom one due maintenance role at a time, beside
|
|
187
225
|
// capPaused, with the fresh-wake/queued-prompt override the need-based deferral honors.
|
|
188
226
|
const queuedRoles = queuedMergeRoles(root);
|
|
189
227
|
const maintenanceQuota = pollMaintenanceQuotaGate(root, states.quota, runners.map((r) => ({
|
|
190
228
|
role: r.role,
|
|
191
|
-
enabled:
|
|
229
|
+
enabled: runnerEnabled(r.config, r.role),
|
|
192
230
|
running: r.state.running === true,
|
|
193
231
|
hasQueuedMerge: queuedRoles.has(r.role),
|
|
194
232
|
})), liveConfig.maintenancePerWorkMerge, now);
|
|
195
233
|
// New-project bootstrap (src/gates/bootstrap-gates.ts, plans/work-ratio.md part 2/2): a fresh
|
|
196
|
-
// project's maintenance
|
|
234
|
+
// project's maintenance roles start no new runs until `bootstrap.untilPlansDone` plans are
|
|
197
235
|
// Done. The gate owns the latch write and the one `bootstrap_complete` event; the scheduling
|
|
198
236
|
// pass folds the held set in beside capPaused (with the fresh-wake/queued-prompt override the
|
|
199
237
|
// need-based deferral honors).
|
|
200
238
|
const bootstrapHeld = pollBootstrapGate(root, states.bootstrap, runners, liveConfig, now);
|
|
201
|
-
const bootstrapActive = states.bootstrap.active;
|
|
202
239
|
// Per-role quiet hours (src/scheduling/quiet-hours.ts): a role whose own quietHoursPerRole window
|
|
203
|
-
// covers `now` starts no new
|
|
240
|
+
// covers `now` starts no new runs — the team window's semantics scoped to one role, the
|
|
204
241
|
// director exempt like every autonomous gate. Stateless, recomputed per poll exactly like
|
|
205
242
|
// capPaused: no crossing events, no state — a live config edit applies on the next poll
|
|
206
243
|
// by construction. A role held by BOTH windows is held once: the scheduling pass folds
|
|
@@ -213,9 +250,9 @@ export function pollTeamGates(states, ctx) {
|
|
|
213
250
|
roleQuietHeld.add(r.role);
|
|
214
251
|
}
|
|
215
252
|
// Quiet hours (src/scheduling/quiet-hours.ts): the config-driven daily local-time window during
|
|
216
|
-
// which
|
|
253
|
+
// which roles start no new runs — the operator pause's semantics on a schedule.
|
|
217
254
|
// The config value is read fresh per cycle, so a live edit applies on the next poll;
|
|
218
|
-
// exactly one quiet_hours_started/ended event per crossing. In-flight
|
|
255
|
+
// exactly one quiet_hours_started/ended event per crossing. In-flight runs finish;
|
|
219
256
|
// the gate folds into the same hold site as the pause gates.
|
|
220
257
|
const quietNow = pollQuietHoursGate(root, liveConfig.quietHours, states.quiet, new Date(now));
|
|
221
258
|
const freeBytes = (ctx.sampleFree ?? sampleFreeBytes)(root);
|
|
@@ -245,7 +282,7 @@ export function pollTeamGates(states, ctx) {
|
|
|
245
282
|
};
|
|
246
283
|
publishInfoFields(root, info, [["disk", disk]]);
|
|
247
284
|
// Each runner as the two provider-failure polls read it (HoldInputs): the role, its two
|
|
248
|
-
// episodic fields, and the provider its NEXT
|
|
285
|
+
// episodic fields, and the provider its NEXT run will run on (runProvider — the tier
|
|
249
286
|
// fallback pair while a model-fallback episode is active, else the role's resolved config;
|
|
250
287
|
// undefined when pi's default is in charge). Built ONCE per poll so the hold and the spread
|
|
251
288
|
// read the same list, like holdObservations on the reducer side. Keying on the effective
|
|
@@ -253,7 +290,7 @@ export function pollTeamGates(states, ctx) {
|
|
|
253
290
|
// primary stays clear (PLANS.md "Model failure fallback, part 1/2").
|
|
254
291
|
//
|
|
255
292
|
// Each stamp (lastRateLimit/lastBackendFailure) carries the hold key of the backend that
|
|
256
|
-
// ACTUALLY failed — a reviewer run folded through the author's
|
|
293
|
+
// ACTUALLY failed — a reviewer run folded through the author's role stamps `claude`, not
|
|
257
294
|
// the pi author's provider — and the polls' observation mapper files each stamp under that
|
|
258
295
|
// key, falling back to runProvider here for a stamp without one (PLANS.md "Mixed claude +
|
|
259
296
|
// pi fleets, part 2/4"). The spreads below carry the stamps wholesale, provider included.
|
|
@@ -265,24 +302,32 @@ export function pollTeamGates(states, ctx) {
|
|
|
265
302
|
}));
|
|
266
303
|
// Team-wide failure hold, per provider (src/team/team-hold.ts, PLANS.md 2026-10-05): once two
|
|
267
304
|
// roles' runs have ended on the SAME provider AND failure kind within a short window — 429s, or a
|
|
268
|
-
// connection, timeout, 5xx, or model-load backend failure —
|
|
269
|
-
// new
|
|
305
|
+
// connection, timeout, 5xx, or model-load backend failure — roles on THAT provider start no
|
|
306
|
+
// new runs, and the merge queue starts no new verify, whose reviewer run has no retry and would
|
|
270
307
|
// spend a review strike on the storm — until the hold re-opens at its own deadline (Retry-After
|
|
271
308
|
// honored on the rate-limit kind, doubling on a relapse, capped). Roles on a healthy provider
|
|
272
|
-
// keep
|
|
309
|
+
// keep running; a hold on the reviewer's provider (with the review gate on) still blocks
|
|
273
310
|
// everything, since nothing could merge — the orchestrator's scheduling and start passes derive
|
|
274
|
-
// that verdict from this map. The director's
|
|
311
|
+
// that verdict from this map. The director's runs are exempt, as under the budget gate and the
|
|
275
312
|
// operator pause: an explicit human prompt outranks an autonomous gate, one director run is not
|
|
276
313
|
// the concurrency that sustains a storm, its 429 runs keep the per-run transient retry
|
|
277
|
-
// (backend-failure kinds ride this hold alone), and a prompt its
|
|
278
|
-
// to the inbox. In-flight
|
|
279
|
-
// a role
|
|
314
|
+
// (backend-failure kinds ride this hold alone), and a prompt its run fails to fulfill goes back
|
|
315
|
+
// to the inbox. In-flight runs finish; NEW runs are gated at scheduling like both siblings, and
|
|
316
|
+
// a role run already parked in the semaphore meets the same hold at its permit (the start gate)
|
|
280
317
|
// and hands its reservation back instead of starting into the storm. The verdicts are written
|
|
281
318
|
// back to states.teamHold — one hold per provider, lifted holds kept keyed for their relapse
|
|
282
319
|
// memory but never reading as held — and the permit-time closures read the LATEST poll's map
|
|
283
320
|
// there, so a waiter granted its permit after a later poll sees that poll's world.
|
|
284
|
-
const
|
|
285
|
-
|
|
321
|
+
const windowAccountReads = backendsInUse(liveConfig).map((backend) => [backend, accounts(backend)]);
|
|
322
|
+
// The subscription-window hold, reserve and warning, read per backend in use (part 2e/9): a
|
|
323
|
+
// second subscription backend gets its own hold, reserve and warning memory keyed by its own
|
|
324
|
+
// `backend`, so claude's roles, hold and events are untouched by another backend's window.
|
|
325
|
+
// One account read per backend feeds both the hold observations and the reserve/warning polls;
|
|
326
|
+
// a backend whose account implies no hold contributes no observation.
|
|
327
|
+
const windowObservations = windowAccountReads
|
|
328
|
+
.map(([backend, account]) => windowObservation(backend, account, now, windowReserveFor(liveConfig, backend)))
|
|
329
|
+
.filter((o) => o !== null);
|
|
330
|
+
states.teamHold = pollTeamHold(root, states.teamHold, holdInputs(runners), now, windowObservations, windowTiers);
|
|
286
331
|
// The standings hold, published for observers (PLANS.md 2026-10-17 "the dashboards and
|
|
287
332
|
// `tumwater status` name a standing provider failure hold"): `standingHolds` turns the holds
|
|
288
333
|
// map into the per-provider list the status snapshot and the alert banner name, the same
|
|
@@ -292,12 +337,17 @@ export function pollTeamGates(states, ctx) {
|
|
|
292
337
|
publishInfoFields(root, info, [
|
|
293
338
|
["providerHolds", providerHolds.length > 0 ? providerHolds : undefined],
|
|
294
339
|
]);
|
|
295
|
-
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
340
|
+
// The reserve and warning polls, one per subscription backend in use: each logs its own
|
|
341
|
+
// events keyed by its own backend (the `backend:type:resetsAt` keys), so claude's own reserve
|
|
342
|
+
// and warning memory is unaffected by another backend's crossings (part 2e/9).
|
|
343
|
+
for (const [backend, account] of windowAccountReads) {
|
|
344
|
+
states.claudeWindowReserved = pollClaudeWindowReserve(root, states.claudeWindowReserved, account, now, backend, windowReserveFor(liveConfig, backend));
|
|
345
|
+
// The subscription usage window's warning is observational (the hold above is the gate): the
|
|
346
|
+
// first allowed_warning logs once per window, and the memory lives beside the holds map.
|
|
347
|
+
states.claudeWindowWarned = pollClaudeWindowWarning(root, states.claudeWindowWarned, account, now, backend);
|
|
348
|
+
}
|
|
349
|
+
// Team-wide error-storm warning (src/team/error-storm.ts): when several roles' run streaks
|
|
350
|
+
// fail consecutively on one shared cause, each role's own "consecutive run failures"
|
|
301
351
|
// warning still fires alone — this adds the one team-level warning that names the
|
|
302
352
|
// cause (and the config knob, when the cause has one) instead of leaving the operator
|
|
303
353
|
// to diff 14 streak lines. Observational only: it gates nothing, so unlike the failure
|
|
@@ -319,11 +369,11 @@ export function pollTeamGates(states, ctx) {
|
|
|
319
369
|
capPaused,
|
|
320
370
|
maintenanceQuota,
|
|
321
371
|
bootstrapHeld,
|
|
322
|
-
bootstrapActive,
|
|
323
372
|
budgetPausedRoles,
|
|
324
373
|
servingResolved,
|
|
325
374
|
priceResolved,
|
|
326
375
|
budgetActive,
|
|
376
|
+
windowMovedRoles,
|
|
327
377
|
roleQuietHeld,
|
|
328
378
|
quietNow,
|
|
329
379
|
diskHeld,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* setting, and the gate behind the maintenance allowance — code-maintenance merges in a rolling
|
|
3
3
|
* 24 h window are capped at `maintenancePerWorkMerge × work merges + MAINTENANCE_DAILY_FLOOR`, and
|
|
4
4
|
* `pollMaintenanceQuotaGate` puts that verdict in front of the scheduler so the enabled maintenance
|
|
5
|
-
*
|
|
5
|
+
* roles stop starting new runs past it.
|
|
6
6
|
*
|
|
7
7
|
* The window counts `merged` events by `commitTier` (src/roles/roles.ts): feature/bugfix/director
|
|
8
8
|
* are the work count, the code-maintenance roles plus readme the maintenance count, and every other
|
|
@@ -16,21 +16,21 @@
|
|
|
16
16
|
* The verdict is pure and lives here beside the count so the scheduler and every status reader
|
|
17
17
|
* share one definition; `pollMaintenanceQuotaGate` is the one writer (the edge-triggered events)
|
|
18
18
|
* and the one reader the scheduler holds from. */
|
|
19
|
-
import { sameStatIdentity, statOrNull } from "../files/files.js";
|
|
19
|
+
import { sameStatIdentity, statIdentityOrZero, statOrNull } from "../files/files.js";
|
|
20
20
|
import { readCompleteLines } from "../files/tail.js";
|
|
21
21
|
import { eventsLogPath } from "../paths.js";
|
|
22
22
|
import { readWindowEvents } from "../events/event-window.js";
|
|
23
23
|
import { eventRole, parseEventLine } from "../events/event-read.js";
|
|
24
24
|
import { finiteNumber } from "../files/json-object.js";
|
|
25
|
-
import { dayKey } from "../text/datetime.js";
|
|
25
|
+
import { dayKey, DAY_MS } from "../text/datetime.js";
|
|
26
26
|
import { commitTier } from "../roles/roles.js";
|
|
27
|
-
import { baseRoleOf } from "../roles/
|
|
27
|
+
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
28
28
|
import { logEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
29
29
|
/** The fixed test-independent part of the allowance: a project whose backlog is empty still gets
|
|
30
30
|
* this many maintenance merges in a rolling day, so hygiene never stops entirely. */
|
|
31
31
|
export const MAINTENANCE_DAILY_FLOOR = 12;
|
|
32
32
|
/** The rolling window length: 24 h in ms. */
|
|
33
|
-
const MAINTENANCE_WINDOW_MS =
|
|
33
|
+
const MAINTENANCE_WINDOW_MS = DAY_MS;
|
|
34
34
|
/** The pure allowance verdict: `allowance = perWorkMerge × work + MAINTENANCE_DAILY_FLOOR`;
|
|
35
35
|
* `used = maint + inFlight`; held while `used >= allowance`. No I/O — the counts and the
|
|
36
36
|
* in-flight figure come from the caller, so the same verdict serves the scheduler and the
|
|
@@ -95,13 +95,13 @@ function seed(counter, root, cutoff, stat) {
|
|
|
95
95
|
export function newMaintenanceQuotaGateState() {
|
|
96
96
|
return { counter: newMaintenanceWindowCounter(), prevHeld: false };
|
|
97
97
|
}
|
|
98
|
-
/** Step the gate by one orchestrator poll: count the rolling window, add the maintenance
|
|
98
|
+
/** Step the gate by one orchestrator poll: count the rolling window, add the maintenance roles in
|
|
99
99
|
* flight, and put 1a's verdict in front of the scheduler. Returns the verdict — `allowance`, `used`
|
|
100
|
-
* (merged maintenance plus the
|
|
100
|
+
* (merged maintenance plus the roles already in flight), and `held` (`used >= allowance`) — for the
|
|
101
101
|
* scheduling pass's per-poll admission: it spends the remaining `allowance - used` headroom one due
|
|
102
|
-
* maintenance
|
|
102
|
+
* maintenance role at a time, so two due roles cannot start in the same poll. Logs exactly one
|
|
103
103
|
* team-level `maintenance_quota_hold` when the gate goes from open to held and one
|
|
104
|
-
* `maintenance_quota_resumed` when it re-opens — never one per
|
|
104
|
+
* `maintenance_quota_resumed` when it re-opens — never one per role. The director is never
|
|
105
105
|
* maintenance-tier, so no exemption is needed. */
|
|
106
106
|
export function pollMaintenanceQuotaGate(root, state, observers, perWorkMerge, now) {
|
|
107
107
|
const { work, maint } = countMaintenanceWindow(root, state.counter, now);
|
|
@@ -150,10 +150,7 @@ export function countMaintenanceWindow(root, counter, now) {
|
|
|
150
150
|
// mid-poll (replaced between the stat and the read), so the folding itself is guarded too.
|
|
151
151
|
const stat = statOrNull(eventsLogPath(root));
|
|
152
152
|
const readable = stat?.isFile() ? stat : null;
|
|
153
|
-
const size = readable
|
|
154
|
-
const mtimeMs = readable?.mtimeMs ?? 0;
|
|
155
|
-
const dev = readable?.dev ?? 0;
|
|
156
|
-
const ino = readable?.ino ?? 0;
|
|
153
|
+
const { size, mtimeMs, dev, ino } = statIdentityOrZero(readable);
|
|
157
154
|
const unchanged = sameStatIdentity({ dev, ino, mtimeMs, size }, { dev: counter.dev, ino: counter.ino, mtimeMs: counter.mtimeMs, size: counter.offset });
|
|
158
155
|
if (readable) {
|
|
159
156
|
try {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* reads, the edge-triggered pause/resume events, and the cross-poll bookkeeping are one concern
|
|
4
4
|
* beside the other team gates (src/budget/budget.ts's budget gate, src/team/team-hold.ts's team
|
|
5
5
|
* hold) — the orchestrator owns only the wiring. */
|
|
6
|
-
import {
|
|
6
|
+
import { standingRolePause, standingTeamPause } from "../team/team-state.js";
|
|
7
7
|
import { logEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
8
8
|
/** A fresh pause-gate state: nothing seen paused yet, so the first poll of an unpaused team
|
|
9
9
|
* logs nothing. */
|
|
@@ -14,28 +14,46 @@ export function newPauseGateState() {
|
|
|
14
14
|
* human intent instead of spend. The marker is persistent state (presence means paused until
|
|
15
15
|
* `resume` removes it), so one existsSync per cycle reads it fresh. The director is exempt for
|
|
16
16
|
* the same reason as under the budget gate — a human typing prompts outranks an operator gate
|
|
17
|
-
* (queued prompts simply wait in the inbox if full silence is wanted). In-flight
|
|
18
|
-
* only NEW
|
|
17
|
+
* (queued prompts simply wait in the inbox if full silence is wanted). In-flight runs finish;
|
|
18
|
+
* only NEW runs are blocked, because the gate sits before isEligible.
|
|
19
19
|
*
|
|
20
20
|
* Per-role pause (`tumwater pause --role <id>`): the operator pause's narrower sibling — the
|
|
21
|
-
* same persistent marker read fresh per cycle, but one named
|
|
21
|
+
* same persistent marker read fresh per cycle, but one named role instead of the team. Unlike
|
|
22
22
|
* the team pause the director is NOT exempt: the operator named the role deliberately, and its
|
|
23
23
|
* queued prompts simply wait in the inbox (the same effect the team pause has on the director).
|
|
24
|
-
* In-flight
|
|
24
|
+
* In-flight runs finish; only NEW runs are blocked (the gate sits before isEligible).
|
|
25
|
+
* A timed role pause's `role_paused` events carry its shared deadline as `until`:
|
|
26
|
+
* `standingRolePause` returns it only while the set is non-empty and an expired marker reads as
|
|
27
|
+
* empty, so a logged `until` is the live deadline. */
|
|
25
28
|
export function pollPauseGates(root, state) {
|
|
26
|
-
const
|
|
29
|
+
const marker = standingTeamPause(root);
|
|
30
|
+
const userPaused = marker !== null;
|
|
27
31
|
if (userPaused !== state.prevUserPaused) {
|
|
28
|
-
logEventBestEffort(root,
|
|
32
|
+
logEventBestEffort(root, marker
|
|
33
|
+
? {
|
|
34
|
+
source: TUMWATER_SOURCE,
|
|
35
|
+
type: "team_paused",
|
|
36
|
+
...(marker.until !== undefined ? { until: marker.until } : {}),
|
|
37
|
+
...(marker.reason ? { reason: marker.reason } : {}),
|
|
38
|
+
}
|
|
39
|
+
: {
|
|
40
|
+
source: TUMWATER_SOURCE,
|
|
41
|
+
type: "team_resumed",
|
|
42
|
+
...(state.prevTeamUntil !== undefined && state.prevTeamUntil <= Date.now() ? { expired: true } : {}),
|
|
43
|
+
});
|
|
29
44
|
state.prevUserPaused = userPaused;
|
|
30
45
|
}
|
|
31
|
-
|
|
46
|
+
state.prevTeamUntil = marker?.until;
|
|
47
|
+
const { roles: pausedRolesNow, until: rolesUntil } = standingRolePause(root);
|
|
48
|
+
const rolesExpired = state.prevRolesUntil !== undefined && state.prevRolesUntil <= Date.now();
|
|
32
49
|
const pausedRolesSet = new Set(pausedRolesNow);
|
|
33
50
|
for (const r of pausedRolesNow)
|
|
34
51
|
if (!state.prevPausedRoles.has(r))
|
|
35
|
-
logEventBestEffort(root, { source: TUMWATER_SOURCE, type: "role_paused", role: r });
|
|
52
|
+
logEventBestEffort(root, { source: TUMWATER_SOURCE, type: "role_paused", role: r, ...(rolesUntil !== undefined ? { until: rolesUntil } : {}) });
|
|
36
53
|
for (const r of state.prevPausedRoles)
|
|
37
54
|
if (!pausedRolesSet.has(r))
|
|
38
|
-
logEventBestEffort(root, { source: TUMWATER_SOURCE, type: "role_resumed", role: r });
|
|
55
|
+
logEventBestEffort(root, { source: TUMWATER_SOURCE, type: "role_resumed", role: r, ...(rolesExpired ? { expired: true } : {}) });
|
|
39
56
|
state.prevPausedRoles = pausedRolesSet;
|
|
57
|
+
state.prevRolesUntil = rolesUntil;
|
|
40
58
|
return { userPaused, pausedRoles: pausedRolesSet };
|
|
41
59
|
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/** The repo preconditions' shared wording behind the readiness gate. startup-gate.ts answers
|
|
3
|
-
* with the first unmet precondition (
|
|
2
|
+
* with the first unmet precondition (cmdStart and requireReadyRepo fail on it, the self-redeploy
|
|
4
3
|
* refuses a swap on it) and doctor.ts reports each one individually, but every surface
|
|
5
4
|
* describes the same problems — so the wording lives here once and cannot drift.
|
|
6
5
|
* `GIT_MISSING_MESSAGE` (git/git-run.ts) is the sibling for a machine with no git binary; the
|
|
7
|
-
* agent-binary preconditions (
|
|
6
|
+
* agent-binary preconditions (resolveBackendBin, findBackendBinary, piMissingMessage) live beside
|
|
8
7
|
* the spawn in pi/pi-bin.ts. */
|
|
9
8
|
export const NOT_A_REPO_MESSAGE = "not a git repository (run `git init` first)";
|
|
10
9
|
export const NO_COMMITS_MESSAGE = "the repo has no commits yet; `tumwater init` creates the first one";
|
|
11
10
|
export const DETACHED_HEAD_MESSAGE = "your checkout is detached; check out your main branch first";
|
|
12
11
|
export const NOT_INITIALIZED_MESSAGE = "not initialized (run `tumwater init <prompt>` first — or a bare `tumwater init` when the project brief (TUMWATER.md or README.md) already carries the prompt)";
|
|
12
|
+
/** What a command run inside a team workspace (`<root>/.tumwater/worktrees/<slot>`) says in place
|
|
13
|
+
* of NOT_INITIALIZED_MESSAGE: the workspace is no project of its own, the team root is. */
|
|
14
|
+
export function teamWorkspaceMessage(teamRoot) {
|
|
15
|
+
return `this is a tumwater workspace of the team at ${teamRoot}, not a project — run tumwater from ${teamRoot} (\`cd ${teamRoot}\`)`;
|
|
16
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/** The per-role daily cost cap (PLANS.md 2026-09-30, part 1/2): a
|
|
2
|
-
* has reached its own `maxDailyCostUsdPerRole` entry starts no new
|
|
1
|
+
/** The per-role daily cost cap (PLANS.md 2026-09-30, part 1/2): a role whose local-day spend
|
|
2
|
+
* has reached its own `maxDailyCostUsdPerRole` entry starts no new runs until the next local
|
|
3
3
|
* day or a live config edit — so one runaway role cannot eat the team-wide cap and starve
|
|
4
|
-
* every other
|
|
4
|
+
* every other role for the rest of the day. The team budget gate (src/gates/budget-gates.ts)
|
|
5
5
|
* bounds the SUM; this bounds each member. The stateless verdict follows that gate's own rule
|
|
6
6
|
* (budgetPaused's doc: "resume is stateless, so … crossing midnight flips it on the next cycle
|
|
7
|
-
* and nothing can get stuck"): the verdict is recomputed from the
|
|
7
|
+
* and nothing can get stuck"): the verdict is recomputed from the runner state every poll, no
|
|
8
8
|
* marker is written, and local midnight lifts it by itself.
|
|
9
9
|
*
|
|
10
10
|
* Deliberate differences from the streak gate's shape (src/gates/streak-gate.ts), stated in
|
|
@@ -30,7 +30,7 @@ import { groupBy } from "../collections.js";
|
|
|
30
30
|
import { budgetWarning, dailyCost, todayStamp } from "../budget/budget.js";
|
|
31
31
|
import { logEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
32
32
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
33
|
-
import { baseRoleOf } from "../roles/
|
|
33
|
+
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
34
34
|
/** The stateless verdict: is this role over its own per-role cap as of `now`? True iff a cap
|
|
35
35
|
* is configured, it is a finite number above zero (0 disables that role's cap, like the team
|
|
36
36
|
* cap), and today's spend has reached it. The role→cap lookup stays with the caller
|
|
@@ -44,9 +44,9 @@ export function roleCapPaused(state, cap, now = Date.now()) {
|
|
|
44
44
|
/** The grouped, stateless per-role cap verdict — the single definition the scheduler's gate
|
|
45
45
|
* (`pollRoleCapGate`) and the snapshot's `capPaused` observer (src/status/status-data.ts) both
|
|
46
46
|
* fold, so the hold an operator sees cannot diverge from the one the scheduler enforces.
|
|
47
|
-
* Instance
|
|
47
|
+
* Instance role ids share their base role's cap and its SUMMED spend (`feature` and `feature-2`
|
|
48
48
|
* at $0.60 each cross a `maxDailyCostUsdPerRole.feature` cap of $1 together), and the director
|
|
49
|
-
* is exempt. Keyed by base role in first-appearance order; each entry carries the group's
|
|
49
|
+
* is exempt. Keyed by base role in first-appearance order; each entry carries the group's role
|
|
50
50
|
* ids so a caller can log the base (the gate) or flatten the ids (the observer). */
|
|
51
51
|
export function roleCapVerdicts(runners, caps, now) {
|
|
52
52
|
// Group by base role via the shared Map-of-arrays step (collections.groupBy, which
|
|
@@ -80,7 +80,7 @@ export function newRoleCapGateState() {
|
|
|
80
80
|
export function pollRoleCapGate(root, state, runners, caps, now) {
|
|
81
81
|
// Grouping and the verdict itself live in roleCapVerdicts, shared with the snapshot's
|
|
82
82
|
// `capPaused` observer (src/status/status-data.ts) so the two cannot diverge. The returned
|
|
83
|
-
// set holds the paused
|
|
83
|
+
// set holds the paused role ids so the scheduler can keep checking `capPaused.has(runner.role)`
|
|
84
84
|
// unchanged.
|
|
85
85
|
const verdicts = roleCapVerdicts(runners, caps, now);
|
|
86
86
|
const paused = new Set();
|