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,22 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { MODEL_TIERS, BACKOFF_KEYS, BOOTSTRAP_KEYS, CHECK_KEYS, CUSTOM_ROLE_KEYS, CUSTOM_NAME_RE, CUSTOM_TASK_MAX_CHARS, REVIEW_KEYS, ROLE_ENTRY_KEYS, TOP_LEVEL_KEYS, ROLE_INSTRUCTIONS_MAX_CHARS, } from "./config-schema.js";
|
|
2
|
+
import { DIRECTOR_ROLE, allRoleIds } from "../roles/roles.js";
|
|
3
|
+
import { INSTANCE_ROLES, baseRoleOf } from "../roles/runner-ids.js";
|
|
4
|
+
import { isHarnessCodexConfigKey } from "../agent/codex/codex-args.js";
|
|
5
5
|
import { isJsonObject } from "../files/json-object.js";
|
|
6
6
|
import { expandHomeTilde } from "../files/files.js";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { isNonBlankString, tooLongMessage } from "../text/text.js";
|
|
10
|
-
import { typoSuffix } from "../text/suggest.js";
|
|
11
10
|
import { PREFILL_REASON } from "../text/phrases.js";
|
|
12
11
|
import { parseQuietHours } from "../scheduling/quiet-hours.js";
|
|
13
|
-
import {
|
|
12
|
+
import { isBackendId } from "./model-selector.js";
|
|
14
13
|
import { TUMWATER_SOURCE } from "../events/events.js";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
14
|
+
import { AT_LEAST_ONE, checkBooleanField, checkKnownKeys, checkKnownRoleId, checkModelTripleField, checkNumberField, checkObjectSection, codexConfigKey, checkStringArrayField, checkStringField, NON_NEGATIVE, NON_NEGATIVE_OR_DISABLED, DOLLAR_CAP, MAX_DURATION_SECONDS, POSITIVE, POSITIVE_INTEGER, show, typeName, } from "./config-field-checks.js";
|
|
15
|
+
import { checkEntryBackend, checkRetiredSelector, checkSelectorHalves, } from "./config-selector-checks.js";
|
|
16
|
+
import { validateAgentsBlock } from "./config-validation-agents.js";
|
|
17
|
+
import { validateFallbackBlock, validateModelBlock, } from "./config-validation-model.js";
|
|
17
18
|
/** Schema validation for tumwater.json: validateConfig, the gate every load and save passes
|
|
18
|
-
* through (config.ts's load/save, config-write.ts's budget editing and
|
|
19
|
-
* requests), judged against the key allow-lists and
|
|
19
|
+
* through (config.ts's load/save, config-write.ts's budget editing and
|
|
20
|
+
* config-request.ts's director config requests), judged against the key allow-lists and
|
|
21
|
+
* value shapes that live beside the types
|
|
20
22
|
* they mirror in config-schema.ts. Split out of config.ts — which keeps defaultConfig and the
|
|
21
23
|
* read side (load/save/cache, per-role views) — because this is a self-contained concern:
|
|
22
24
|
* it depends only on the schema, the role catalog (allRoleIds), and message wording, not on
|
|
@@ -38,7 +40,7 @@ const DURATION_OR_DISABLED = {
|
|
|
38
40
|
};
|
|
39
41
|
/** A per-role instance count (`roles.feature.instances`): an integer from 1 to 8. Bounded so
|
|
40
42
|
* a typo like `instances: 800` cannot spawn hundreds of runners, and integer so
|
|
41
|
-
* `feature-1.5` — which no
|
|
43
|
+
* `feature-1.5` — which no role id can name — is refused. */
|
|
42
44
|
const INSTANCE_COUNT = {
|
|
43
45
|
ok: (n) => Number.isInteger(n) && n >= 1 && n <= 8,
|
|
44
46
|
what: "an integer from 1 to 8",
|
|
@@ -66,8 +68,8 @@ function checkPerRoleMap(r, key, expectation, customNames, problems, checkValue)
|
|
|
66
68
|
/** Validate tumwater.json values, so a typo fails fast with an actionable message instead
|
|
67
69
|
* of misbehaving at runtime — e.g. a non-numeric runTimeoutSeconds becomes NaN and kills
|
|
68
70
|
* every pi run instantly, a non-numeric logMaxBytes rotates the event log on every write, an
|
|
69
|
-
* unknown role id (a misspelled entry under `roles`) spawns a phantom
|
|
70
|
-
*
|
|
71
|
+
* unknown role id (a misspelled entry under `roles`) spawns a phantom role that errors every
|
|
72
|
+
* run, and an unknown key is silently ignored so the intended setting never takes effect.
|
|
71
73
|
* Collects every problem so one edit can fix them all; throws a single Error listing them.
|
|
72
74
|
* `label` names the file in the thrown messages — validateConfig also gates the tracked
|
|
73
75
|
* example template, whose problems must not be misreported as tumwater.json's.
|
|
@@ -90,83 +92,91 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
90
92
|
const checkNumber = (obj, prefix, key, rule) => checkNumberField(problems, obj, prefix, key, rule);
|
|
91
93
|
const checkBoolean = (obj, prefix, key) => checkBooleanField(problems, obj, prefix, key);
|
|
92
94
|
const checkStringArray = (obj, prefix, key) => checkStringArrayField(problems, obj, prefix, key);
|
|
95
|
+
// claude's and codex's per-backend config dir (plans/notes-command.md "Keeping funes out
|
|
96
|
+
// of team runs" and plans/codex-backend.md §4): a leading `~` expands; the value must be an
|
|
97
|
+
// absolute path after expansion, or claude would resolve a relative CLAUDE_CONFIG_DIR (and
|
|
98
|
+
// codex a relative CODEX_HOME) from the run's cwd and write transcripts somewhere
|
|
99
|
+
// unexpected. Only claude and codex accept the key, so it is inert nowhere else. The nested
|
|
100
|
+
// reserve spelling (part 2b/9): the same 0 < f ≤ 1 rule the top-level `claudeWindowReserve`
|
|
101
|
+
// map gets, so a bad entry can never silently no-op a reserve.
|
|
102
|
+
const checkBackendConfig = (entry, prefix) => {
|
|
103
|
+
checkString(entry, `${prefix}.`, "configDir", false);
|
|
104
|
+
if (typeof entry.configDir === "string" && entry.configDir !== "")
|
|
105
|
+
if (!path.isAbsolute(expandHomeTilde(entry.configDir, os.homedir())))
|
|
106
|
+
problems.push(`${prefix}.configDir must be an absolute path after ~ expansion (got ${show(entry.configDir)})`);
|
|
107
|
+
if (entry.windowReserve === undefined)
|
|
108
|
+
return;
|
|
109
|
+
const wr = entry.windowReserve;
|
|
110
|
+
if (!isJsonObject(wr)) {
|
|
111
|
+
problems.push(`${prefix}.windowReserve must be an object mapping window types to fractions (got ${show(wr)})`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
for (const [type, value] of Object.entries(wr)) {
|
|
115
|
+
if (type.trim() === "")
|
|
116
|
+
problems.push(`${prefix}.windowReserve.<empty> — a window type must not be empty`);
|
|
117
|
+
else if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > 1)
|
|
118
|
+
problems.push(`${prefix}.windowReserve.${type} must be a fraction above 0 and at most 1 (got ${show(value)})`);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
// codex's `-c <key>=…` / `--config <key>=…` passthrough (plans/codex-backend.md §2/§3): a
|
|
122
|
+
// key tumwater sets on every run (HARNESS_CODEX_CONFIG_KEYS) — or one the write guard owns,
|
|
123
|
+
// `hooks` / `hooks.<…>` (part 3/9) — is appended AFTER the harness's own `-c` and Codex's
|
|
124
|
+
// parser is last-wins, so it would silently override the model, effort, sandbox mode,
|
|
125
|
+
// telemetry or the write guard the harness resolved. The standalone-flag screen above
|
|
126
|
+
// (reservedPassthroughFlag + checkReservedArgs) splits on `=` and cannot see a separate
|
|
127
|
+
// `-c` token, so codex gets its own arity-aware scan of the key. Any other key is the
|
|
128
|
+
// operator's own and passes.
|
|
129
|
+
const checkCodexConfigKeys = (entry, prefix) => {
|
|
130
|
+
const args = entry.args;
|
|
131
|
+
for (let i = 0; i < args.length; i++) {
|
|
132
|
+
const arg = args[i];
|
|
133
|
+
if (typeof arg !== "string")
|
|
134
|
+
continue;
|
|
135
|
+
const key = codexConfigKey(arg, typeof args[i + 1] === "string" ? args[i + 1] : undefined);
|
|
136
|
+
if (key !== undefined && isHarnessCodexConfigKey(key))
|
|
137
|
+
problems.push(`${prefix}.args[${i}] ${show(arg)} passes the config key ${key}, which tumwater manages — drop it or use another key`);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
93
140
|
const r = raw; // Narrowed to an object by isJsonObject above.
|
|
94
141
|
checkKnownKeys(r, TOP_LEVEL_KEYS, label, problems);
|
|
95
142
|
// The top level's `model` may be a selector string or a map by tier (plans/model-tiers.md);
|
|
96
143
|
// provider/thinking validate as before. A string selector that parses to an empty provider
|
|
97
144
|
// or model half is reported separately (checkSelectorHalves).
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const prefix = `backends.${id}`;
|
|
133
|
-
if (!isJsonObject(entry)) {
|
|
134
|
-
problems.push(`${prefix} must be an object with optional bin and args (got ${show(entry)})`);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
checkKnownKeys(entry, id === "claude" ? CLAUDE_AGENT_KEYS : AGENT_KEYS, prefix, problems);
|
|
138
|
-
checkString(entry, `${prefix}.`, "bin", false);
|
|
139
|
-
checkStringArray(entry, `${prefix}.`, "args");
|
|
140
|
-
// claude's per-backend config dir (plans/notes-command.md "Keeping funes out of team runs"):
|
|
141
|
-
// a leading `~` expands; the value must be an absolute path after expansion, or claude would
|
|
142
|
-
// resolve a relative CLAUDE_CONFIG_DIR from the tick's cwd and write transcripts somewhere
|
|
143
|
-
// unexpected. Only claude accepts the key (CLAUDE_AGENT_KEYS), so it is inert nowhere.
|
|
144
|
-
if (id === "claude") {
|
|
145
|
-
checkString(entry, `${prefix}.`, "configDir", false);
|
|
146
|
-
if (typeof entry.configDir === "string" && entry.configDir !== "")
|
|
147
|
-
if (!path.isAbsolute(expandHomeTilde(entry.configDir, os.homedir())))
|
|
148
|
-
problems.push(`${prefix}.configDir must be an absolute path after ~ expansion (got ${show(entry.configDir)})`);
|
|
149
|
-
}
|
|
150
|
-
if (!Array.isArray(entry.args))
|
|
151
|
-
continue; // Already reported by checkStringArray.
|
|
152
|
-
checkReservedArgs(problems, entry.args, `${prefix}.args`, (flag) => reservedPassthroughFlag(id, flag));
|
|
153
|
-
}
|
|
145
|
+
// The backend a selector with no `backend` of its own resolves to (retired-spelling checks).
|
|
146
|
+
const topBackend = typeof r.backend === "string" ? r.backend : "pi";
|
|
147
|
+
// The backend a tier's primary resolves to, which that tier's fallback steps inherit.
|
|
148
|
+
const tierBackend = (tier) => {
|
|
149
|
+
// A tier left out of the map takes the `default` entry, as resolveTopTier does.
|
|
150
|
+
const e = isJsonObject(r.model) ? (r.model[tier] ?? r.model.default) : undefined;
|
|
151
|
+
return isJsonObject(e) && typeof e.backend === "string" ? e.backend : topBackend;
|
|
152
|
+
};
|
|
153
|
+
// A string or list `fallback` serves every tier, each inheriting its own tier's backend, so it
|
|
154
|
+
// is checked against each distinct one; the same message from two backends is reported once.
|
|
155
|
+
const eachTierBackend = (check) => {
|
|
156
|
+
const out = [];
|
|
157
|
+
for (const b of new Set(MODEL_TIERS.map(tierBackend)))
|
|
158
|
+
check(b, out);
|
|
159
|
+
for (const m of new Set(out))
|
|
160
|
+
problems.push(m);
|
|
161
|
+
};
|
|
162
|
+
// The retired spelling doubled a backend id as the provider namespace, so a `provider` that IS
|
|
163
|
+
// a backend id other than `pi` on a section that otherwise runs on pi is the old back-compat
|
|
164
|
+
// form: configForRole still resolves the section to pi and hands the bare provider name to
|
|
165
|
+
// pi, which has no such provider — reject it with the edit the step check names (write the
|
|
166
|
+
// backend beside the model, drop the provider). checkRetiredSelector covers the no-provider
|
|
167
|
+
// spellings; this catches the pair a provider in scope used to make valid.
|
|
168
|
+
const checkLegacyProvider = (provider, at, backend) => {
|
|
169
|
+
if (typeof provider === "string" && isBackendId(provider) && provider !== "pi" && backend === "pi")
|
|
170
|
+
problems.push(`${at}provider "${provider}" is the retired backend namespace but this section runs on pi — write backend: "${provider}" beside the model and drop the provider`);
|
|
171
|
+
};
|
|
172
|
+
validateModelBlock(r, problems, { checkModelTriple, checkString, topBackend, checkLegacyProvider });
|
|
173
|
+
validateAgentsBlock(r, problems, rawFile, {
|
|
174
|
+
checkString,
|
|
175
|
+
checkStringArray,
|
|
176
|
+
checkKnownKeys,
|
|
177
|
+
checkBackendConfig,
|
|
178
|
+
checkCodexConfigKeys,
|
|
154
179
|
});
|
|
155
|
-
// Legacy back-compat (plans/agent-backends.md "Back-compat"): a file must not spell one pi
|
|
156
|
-
// field both ways. This is a property of the FILE's own keys — the merged config always
|
|
157
|
-
// carries a default `piArgs`, so only the raw pass (rawFile) can tell an explicit legacy
|
|
158
|
-
// value from the default. The effective value resolves through config-views.ts
|
|
159
|
-
// resolvedAgentConfig; a load-time mirror into `agents` would instead leak a duplicate
|
|
160
|
-
// spelling into every later config write.
|
|
161
|
-
if (rawFile && isJsonObject(r.backends)) {
|
|
162
|
-
const pi = r.backends.pi;
|
|
163
|
-
if (isJsonObject(pi)) {
|
|
164
|
-
if (r.agentBin !== undefined && pi.bin !== undefined)
|
|
165
|
-
problems.push("agentBin and backends.pi.bin set the same value — keep one (backends.pi.bin is the current spelling; agentBin is its old name)");
|
|
166
|
-
if (r.piArgs !== undefined && pi.args !== undefined)
|
|
167
|
-
problems.push("piArgs and backends.pi.args set the same value — keep one (backends.pi.args is the current spelling; piArgs is its old name)");
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
180
|
checkNumber(r, "", "maxConcurrent", POSITIVE_INTEGER);
|
|
171
181
|
checkNumber(r, "", "mergeBatchMax", POSITIVE_INTEGER);
|
|
172
182
|
checkNumber(r, "", "maxConcurrentChecks", POSITIVE_INTEGER);
|
|
@@ -204,7 +214,7 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
204
214
|
// New-project bootstrap (plans/work-ratio.md, "New-project bootstrap"): an absent section
|
|
205
215
|
// means no bootstrap. `untilPlansDone` is a count of plans that must reach `## Done`, so it is
|
|
206
216
|
// an integer of at least 1 — 0 or a fraction can never be reached and would hold the
|
|
207
|
-
// maintenance
|
|
217
|
+
// maintenance roles forever.
|
|
208
218
|
checkObjectSection(r, "bootstrap", problems, (o) => {
|
|
209
219
|
checkKnownKeys(o, BOOTSTRAP_KEYS, "bootstrap", problems);
|
|
210
220
|
checkNumber(o, "bootstrap.", "untilPlansDone", POSITIVE_INTEGER);
|
|
@@ -233,6 +243,9 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
233
243
|
// gateCommand is a string like command, but blank means off: falling back from it runs
|
|
234
244
|
// the FULL check at the gate — the stronger check, never a silently weaker one.
|
|
235
245
|
checkString(o, "check.", "gateCommand");
|
|
246
|
+
// mergeCommand likewise: an absent key falls back to the gate command (Fast merge
|
|
247
|
+
// "Defaults"), and a blank string means off, which runs the full check in the merge lock.
|
|
248
|
+
checkString(o, "check.", "mergeCommand");
|
|
236
249
|
checkString(o, "check.", "cwd");
|
|
237
250
|
checkNumber(o, "check.", "timeoutSeconds", DURATION_POSITIVE);
|
|
238
251
|
});
|
|
@@ -246,6 +259,7 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
246
259
|
checkKnownKeys(o, REVIEW_KEYS, "review", problems);
|
|
247
260
|
checkBoolean(o, "review.", "enabled");
|
|
248
261
|
checkStringArray(o, "review.", "exemptPaths");
|
|
262
|
+
checkStringArray(o, "review.", "afterMerge");
|
|
249
263
|
// An exemption pattern is matched against a repo-relative path (exemptions.ts), so a
|
|
250
264
|
// pattern shaped like something git never emits can never match: the diff it was meant
|
|
251
265
|
// to exempt gets a model review anyway, and the operator never learns the pattern was
|
|
@@ -269,117 +283,21 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
269
283
|
}
|
|
270
284
|
checkModelTriple(o, "review.", "tier-name");
|
|
271
285
|
checkSelectorHalves(o, "review.", "model", o.provider ?? r.provider, problems);
|
|
286
|
+
checkEntryBackend(o, "review", problems);
|
|
287
|
+
if ((o.provider ?? r.provider) === undefined)
|
|
288
|
+
checkRetiredSelector("review.model", o.model, typeof o.backend === "string" ? o.backend : topBackend, problems);
|
|
289
|
+
checkLegacyProvider(o.provider ?? r.provider, "review.", typeof o.backend === "string" ? o.backend : topBackend);
|
|
272
290
|
checkNumber(o, "review.", "timeoutSeconds", DURATION_POSITIVE);
|
|
273
291
|
});
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
// string; the map form takes per-tier entries whose values are selector strings or "pause"
|
|
280
|
-
// (each tier's entry is consulted when that tier runs at the cap; part 5a/8). Setting both
|
|
281
|
-
// forms — or the map form and `fallbackModel` — is an error naming both keys, since which
|
|
282
|
-
// one wins is not a question the file should ever pose.
|
|
283
|
-
if ("fallback" in r && r.fallback !== undefined) {
|
|
284
|
-
const f = r.fallback;
|
|
285
|
-
if (isJsonObject(f)) {
|
|
286
|
-
checkKnownKeys(f, TIER_MAP_KEYS, "fallback", problems);
|
|
287
|
-
for (const [tier, v] of Object.entries(f)) {
|
|
288
|
-
if (Array.isArray(v))
|
|
289
|
-
checkFallbackList(`fallback.${tier}`, v, problems);
|
|
290
|
-
else if (isJsonObject(v))
|
|
291
|
-
checkFallbackStep(`fallback.${tier}`, v, problems);
|
|
292
|
-
else if (!isNonBlankString(v))
|
|
293
|
-
problems.push(`fallback.${tier} must be a non-empty model id or "pause" (got ${show(v)})`);
|
|
294
|
-
else if (v !== "pause")
|
|
295
|
-
// An entry is a pure selector (config-views' fallbackSelectorFields parses it with
|
|
296
|
-
// no legacy provider in scope); "pause" names no model and stays valid.
|
|
297
|
-
checkSelectorValue(`fallback.${tier}`, v, undefined, problems);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
else if (Array.isArray(f)) {
|
|
301
|
-
checkFallbackList("fallback", f, problems);
|
|
302
|
-
}
|
|
303
|
-
else if (!isNonBlankString(f)) {
|
|
304
|
-
problems.push(`fallback must be a model id or a map by tier (got ${show(f)})`);
|
|
305
|
-
}
|
|
306
|
-
else if (f === "pause") {
|
|
307
|
-
problems.push(`fallback "pause" is only valid as a tier map value — a string fallback must name a model id`);
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
// A selector-string fallback still parses to halves: report an empty one.
|
|
311
|
-
checkSelectorHalves({ fallback: f }, "", "fallback", undefined, problems);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
if ("fallback" in r && "fallbackModel" in r)
|
|
315
|
-
problems.push(`fallback and fallbackModel both name the budget fallback — keep only one (got fallback ${show(r.fallback)} and fallbackModel ${show(r.fallbackModel)})`);
|
|
316
|
-
checkObjectSection(r, "fallbackModel", problems, (o) => {
|
|
317
|
-
checkKnownKeys(o, MODEL_TRIPLE_KEYS, "fallbackModel", problems);
|
|
318
|
-
checkModelTriple(o, "fallbackModel.");
|
|
319
|
-
if (!("provider" in o) && !("model" in o))
|
|
320
|
-
problems.push(`fallbackModel must name a provider or a model (got ${show(o)})`);
|
|
292
|
+
validateFallbackBlock(r, problems, {
|
|
293
|
+
checkModelTriple,
|
|
294
|
+
checkLegacyProvider,
|
|
295
|
+
tierBackend,
|
|
296
|
+
eachTierBackend,
|
|
321
297
|
});
|
|
322
|
-
|
|
323
|
-
* the non-empty-string rule but never reaches pi: piArgs skips an empty value when it builds
|
|
324
|
-
* its flags (config-field-checks' model-triple note), and fallbackPair drops an empty half, so
|
|
325
|
-
* the team silently runs pi's default model — the same silent-ignore class the empty-string
|
|
326
|
-
* rules exist to prevent. Checked only when no legacy provider is in scope: under one, the
|
|
327
|
-
* whole string is the id (config-views' parse), so a `/` in it is ordinary text and the parse
|
|
328
|
-
* cannot produce an empty half. */
|
|
329
|
-
function checkSelectorHalves(obj, prefix, key, legacyProvider, problems) {
|
|
330
|
-
checkSelectorValue(`${prefix}${key}`, obj[key], legacyProvider, problems);
|
|
331
|
-
}
|
|
332
|
-
/** Validate a `fallback` list (a tier's ordered chain): non-empty, each element a step — a
|
|
333
|
-
* non-blank model id that is not `"pause"` (a whole-tier value only) and parses to both
|
|
334
|
-
* halves, or a `{ model, maxDailyCostUsd? }` object (checkFallbackStep). */
|
|
335
|
-
function checkFallbackList(name, list, problems) {
|
|
336
|
-
if (list.length === 0)
|
|
337
|
-
problems.push(`${name} list must not be empty — name at least one model id`);
|
|
338
|
-
list.forEach((el, i) => {
|
|
339
|
-
const at = `${name}[${i}]`;
|
|
340
|
-
if (isJsonObject(el))
|
|
341
|
-
checkFallbackStep(at, el, problems);
|
|
342
|
-
else if (!isNonBlankString(el))
|
|
343
|
-
problems.push(`${name} list element ${i} must be a non-empty model id or a step object (got ${show(el)})`);
|
|
344
|
-
else if (el === "pause")
|
|
345
|
-
problems.push(`${name} list element ${i} is "pause" — "pause" is only valid as a whole-tier value`);
|
|
346
|
-
else
|
|
347
|
-
checkSelectorValue(at, el, undefined, problems);
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
/** Validate one object step `{ model, maxDailyCostUsd? }`: only those keys, `model` a non-blank
|
|
351
|
-
* selector (not `"pause"`) and `maxDailyCostUsd`, when present, a positive finite number. */
|
|
352
|
-
function checkFallbackStep(at, step, problems) {
|
|
353
|
-
checkKnownKeys(step, FALLBACK_STEP_KEYS, at, problems);
|
|
354
|
-
const m = step.model;
|
|
355
|
-
if (!isNonBlankString(m))
|
|
356
|
-
problems.push(`${at}.model must be a non-empty model id (got ${show(m)})`);
|
|
357
|
-
else if (m === "pause")
|
|
358
|
-
problems.push(`${at}.model is "pause" — "pause" is only valid as a whole-tier value`);
|
|
359
|
-
else
|
|
360
|
-
checkSelectorValue(`${at}.model`, m, undefined, problems);
|
|
361
|
-
const cap = step.maxDailyCostUsd;
|
|
362
|
-
if (cap !== undefined && !(typeof cap === "number" && Number.isFinite(cap) && cap > 0))
|
|
363
|
-
problems.push(`${at}.maxDailyCostUsd must be a positive number (got ${show(cap)})`);
|
|
364
|
-
}
|
|
365
|
-
/** Report one selector string that parses to an empty provider or model half. `name` is the
|
|
366
|
-
* config path the message names — `model`, a map entry's `model.small`, a section's
|
|
367
|
-
* `roles.feature.model`. Split out of checkSelectorHalves so the map forms, which enumerate
|
|
368
|
-
* their entries instead of looking one key up on an object, run the identical check: a
|
|
369
|
-
* selector's empty half is the same silent-ignore class wherever the string lives. */
|
|
370
|
-
function checkSelectorValue(name, v, legacyProvider, problems) {
|
|
371
|
-
if (!isNonBlankString(v))
|
|
372
|
-
return;
|
|
373
|
-
const legacy = isNonBlankString(legacyProvider) ? legacyProvider : undefined;
|
|
374
|
-
const sel = parseModelSelector(v, legacy);
|
|
375
|
-
if (sel.provider?.trim() === "")
|
|
376
|
-
problems.push(`${name} ${show(v)} parses to an empty provider half — pi would silently run its default model`);
|
|
377
|
-
if (sel.model.trim() === "")
|
|
378
|
-
problems.push(`${name} ${show(v)} parses to an empty model half — pi would silently run its default model`);
|
|
379
|
-
}
|
|
380
|
-
// Custom loops (plans/user-defined-loops.md): names become worktree dirs and git refs, so
|
|
298
|
+
// Custom roles (plans/user-defined-loops.md): names become worktree dirs and git refs, so
|
|
381
299
|
// they are validated strictly — a colliding name would silently shadow a built-in's prompt
|
|
382
|
-
// (roleById wins in
|
|
300
|
+
// (roleById wins in runPrompt), validation makes that impossible instead of subtle. The
|
|
383
301
|
// collected names feed the roles cross-check below: an id under `roles` is valid when it is
|
|
384
302
|
// in the catalog OR listed as a customRoles name.
|
|
385
303
|
const customNames = new Set();
|
|
@@ -395,7 +313,7 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
395
313
|
problems.push(`${where} must be an object with keys name and task (got ${show(entry)})`);
|
|
396
314
|
return;
|
|
397
315
|
}
|
|
398
|
-
checkKnownKeys(entry,
|
|
316
|
+
checkKnownKeys(entry, CUSTOM_ROLE_KEYS, where, problems);
|
|
399
317
|
const name = entry.name;
|
|
400
318
|
if (typeof name !== "string") {
|
|
401
319
|
problems.push(`${where}.name must be a string matching /^[a-z0-9][a-z0-9_-]{0,31}$/ (got ${show(name)})`);
|
|
@@ -420,7 +338,7 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
420
338
|
}
|
|
421
339
|
const task = entry.task;
|
|
422
340
|
// A whitespace-only task is as inert as an empty one — it rides into every one of
|
|
423
|
-
// this
|
|
341
|
+
// this role's run prefills as blank text, so the role has nothing to do and runs
|
|
424
342
|
// no_change forever. Reject both shapes with the same message; the model-triple
|
|
425
343
|
// checkString rule uses the same trim-based emptiness test.
|
|
426
344
|
if (!isNonBlankString(task)) {
|
|
@@ -439,8 +357,8 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
439
357
|
}
|
|
440
358
|
else {
|
|
441
359
|
for (const [id, rc] of Object.entries(roles)) {
|
|
442
|
-
// An id outside the catalog and customRoles cannot work:
|
|
443
|
-
// it and the
|
|
360
|
+
// An id outside the catalog and customRoles cannot work: runPrompt has no prompt for
|
|
361
|
+
// it and the role would error every run forever (checkKnownRoleId).
|
|
444
362
|
if (!checkKnownRoleId("roles", id, customNames, problems))
|
|
445
363
|
continue;
|
|
446
364
|
if (!isJsonObject(rc)) {
|
|
@@ -450,16 +368,20 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
450
368
|
const o = rc;
|
|
451
369
|
checkKnownKeys(o, ROLE_ENTRY_KEYS, `roles.${id}`, problems);
|
|
452
370
|
// Empty is a deliberate "no extra instructions". A non-empty value rides into every
|
|
453
|
-
// one of this role's
|
|
371
|
+
// one of this role's run prefills, so it is capped like a custom role's task.
|
|
454
372
|
checkString(o, `roles.${id}.`, "instructions");
|
|
455
373
|
const instructions = o.instructions;
|
|
456
374
|
if (typeof instructions === "string" && instructions.length > ROLE_INSTRUCTIONS_MAX_CHARS)
|
|
457
375
|
problems.push(tooLongMessage(`roles.${id}.instructions`, instructions.length, ROLE_INSTRUCTIONS_MAX_CHARS, PREFILL_REASON));
|
|
458
376
|
checkModelTriple(o, `roles.${id}.`, "tier-name");
|
|
459
377
|
checkSelectorHalves(o, `roles.${id}.`, "model", o.provider ?? r.provider, problems);
|
|
378
|
+
checkEntryBackend(o, `roles.${id}`, problems);
|
|
379
|
+
if ((o.provider ?? r.provider) === undefined)
|
|
380
|
+
checkRetiredSelector(`roles.${id}.model`, o.model, typeof o.backend === "string" ? o.backend : topBackend, problems);
|
|
381
|
+
checkLegacyProvider(o.provider ?? r.provider, `roles.${id}.`, typeof o.backend === "string" ? o.backend : topBackend);
|
|
460
382
|
checkBoolean(o, `roles.${id}.`, "enabled");
|
|
461
383
|
checkNumber(o, `roles.${id}.`, "minRunIntervalSeconds", DURATION_NON_NEGATIVE);
|
|
462
|
-
// Only feature and bugfix may split (src/roles/
|
|
384
|
+
// Only feature and bugfix may split (src/roles/runner-ids.ts INSTANCE_ROLES); an
|
|
463
385
|
// instances count under any other role would never spawn a runner, so reject it
|
|
464
386
|
// rather than let the operator believe it did something.
|
|
465
387
|
if ("instances" in o && !INSTANCE_ROLES.has(id)) {
|
|
@@ -471,10 +393,22 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
471
393
|
}
|
|
472
394
|
}
|
|
473
395
|
}
|
|
396
|
+
// review.afterMerge names roles, so a typo'd id would silently keep that role reviewed before
|
|
397
|
+
// merging; the director is refused because its changes always merge on a review first.
|
|
398
|
+
if (isJsonObject(r.review) && Array.isArray(r.review.afterMerge)) {
|
|
399
|
+
r.review.afterMerge.forEach((id, i) => {
|
|
400
|
+
if (typeof id !== "string" || id.trim() === "")
|
|
401
|
+
return; // Already reported by checkStringArray.
|
|
402
|
+
if (id === DIRECTOR_ROLE)
|
|
403
|
+
problems.push(`review.afterMerge[${i}] must not be "${DIRECTOR_ROLE}" — its changes are always reviewed before merging`);
|
|
404
|
+
else
|
|
405
|
+
checkKnownRoleId("review.afterMerge", id, customNames, problems);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
474
408
|
// Per-role daily cost caps (src/gates/role-cap-gates.ts): each key must name a known role —
|
|
475
409
|
// built-in or customRoles — because a typo'd id would silently no-op the cap, the exact
|
|
476
410
|
// silent-ignore class the `roles.<id>` check exists to prevent (the customNames set above
|
|
477
|
-
// is fully collected by this point, so a custom
|
|
411
|
+
// is fully collected by this point, so a custom role can be capped). Each value is a
|
|
478
412
|
// spend threshold with the NON_NEGATIVE_OR_DISABLED semantics: 0 disables that role's cap.
|
|
479
413
|
checkPerRoleMap(r, "maxDailyCostUsdPerRole", "USD caps", customNames, problems, (id) => {
|
|
480
414
|
// Same MAX_SAFE_INTEGER bound as maxDailyCostUsd's DOLLAR_CAP rule (BUGS.md
|
|
@@ -536,6 +470,14 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
536
470
|
}
|
|
537
471
|
}
|
|
538
472
|
}
|
|
473
|
+
// The reserve's two spellings are mutually exclusive (plans/codex-backend.md part 2b/9): the
|
|
474
|
+
// nested key is the current one; `claudeWindowReserve` is kept as claude's older spelling. A
|
|
475
|
+
// file holding both is ambiguous about which drives the window, so it is rejected naming both.
|
|
476
|
+
if (r.claudeWindowReserve !== undefined &&
|
|
477
|
+
isJsonObject(r.backends) &&
|
|
478
|
+
isJsonObject(r.backends.claude) &&
|
|
479
|
+
r.backends.claude.windowReserve !== undefined)
|
|
480
|
+
problems.push("claudeWindowReserve and backends.claude.windowReserve set the same value — keep one (backends.claude.windowReserve is the current spelling; claudeWindowReserve is its old name)");
|
|
539
481
|
// Cross-field (judged on the merged config, where both sides are always present; on a raw
|
|
540
482
|
// file only when the file itself names both): scheduleBackoff clamps every idle wait — the
|
|
541
483
|
// first included — to min(initialSeconds, maxSeconds), so a smaller max silently discards
|