tumwater 0.2.0 → 0.4.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 +55 -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 +6 -4
- 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 +404 -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 +281 -55
- package/dist/src/backlog/backlog-cli-view.js +255 -0
- package/dist/src/backlog/backlog-cli.js +91 -335
- package/dist/src/backlog/backlog-commit.js +20 -13
- package/dist/src/backlog/backlog-eligibility.js +193 -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 +264 -0
- package/dist/src/backlog/backlog-release.js +134 -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-guardrails.js +136 -0
- package/dist/src/baseline/main-red.js +153 -36
- package/dist/src/baseline/main-watch.js +232 -0
- package/dist/src/brief/brief-edit.js +12 -12
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget-fallback-view.js +288 -0
- package/dist/src/budget/budget-wiring.js +158 -0
- package/dist/src/budget/budget.js +31 -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 +65 -3
- 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 +181 -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 +183 -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 +291 -598
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-review-commands.js +113 -0
- package/dist/src/cli/cli-role-command.js +525 -0
- package/dist/src/cli/cli-start-summary.js +167 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
- package/dist/src/cli/completion.js +169 -22
- package/dist/src/cli/config-commands.js +58 -26
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +258 -82
- package/dist/src/cli/log-commands.js +164 -85
- package/dist/src/cli/question-commands.js +211 -10
- package/dist/src/cli.js +173 -45
- package/dist/src/collections.js +56 -13
- 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 +16 -1
- package/dist/src/config/config-fallbacks.js +94 -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 +169 -222
- package/dist/src/config/config-views.js +136 -54
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +164 -265
- package/dist/src/config/config.js +53 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
- package/dist/src/doctor/doctor-backlog-plans.js +398 -0
- package/dist/src/doctor/doctor-backlog.js +249 -512
- package/dist/src/doctor/doctor-check-names.js +85 -0
- package/dist/src/doctor/doctor-checks.js +61 -593
- package/dist/src/doctor/doctor-claude-checks.js +142 -0
- package/dist/src/doctor/doctor-codex-checks.js +276 -0
- package/dist/src/doctor/doctor-contention-checks.js +506 -0
- package/dist/src/doctor/doctor-fallback-checks.js +130 -0
- package/dist/src/doctor/doctor-holds.js +80 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +216 -537
- package/dist/src/doctor/doctor-notify.js +9 -5
- package/dist/src/doctor/doctor-opencode-checks.js +164 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-pi-checks.js +62 -0
- package/dist/src/doctor/doctor-project-checks.js +397 -0
- package/dist/src/doctor/doctor.js +126 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +21 -12
- package/dist/src/events/event-format.js +185 -89
- package/dist/src/events/event-labels.js +11 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +52 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +80 -26
- 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 +65 -60
- package/dist/src/failure/failure-render.js +29 -21
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +35 -35
- package/dist/src/files/file-queue.js +17 -0
- 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 +88 -225
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +101 -143
- package/dist/src/gates/maintenance-quota.js +37 -30
- 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 +89 -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 +54 -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 +43 -19
- package/dist/src/gui/gui-backlog-endpoints.js +156 -0
- package/dist/src/gui/gui-command.js +26 -13
- package/dist/src/gui/gui-config-commands.js +168 -80
- package/dist/src/gui/gui-endpoint-commands.js +5 -4
- 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 +235 -13
- package/dist/src/gui/gui-prompt-commands.js +26 -20
- package/dist/src/gui/gui-report-endpoints.js +97 -0
- package/dist/src/gui/gui-server.js +26 -5
- 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 +155 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +190 -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 +28 -17
- 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 +58 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +45 -19
- 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 +214 -0
- package/dist/src/merge/merge-queue-render.js +48 -11
- package/dist/src/merge/merge-queue.js +52 -15
- package/dist/src/merge/merge-requeue.js +103 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +27 -11
- package/dist/src/merge/merge-to-main.js +77 -26
- package/dist/src/merge/merge-top.js +83 -0
- package/dist/src/merge/merge-verify.js +75 -18
- package/dist/src/notes-cli.js +35 -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 +352 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
- package/dist/src/orchestrator/orchestrator.js +129 -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 +245 -0
- package/dist/src/orchestrator/scheduling-skip.js +195 -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 +126 -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 +26 -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 +150 -217
- package/dist/src/prompt/notes-backlog.js +184 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +131 -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 +188 -124
- package/dist/src/report/report-fold.js +130 -54
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +136 -65
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +23 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/diff-rules.js +53 -0
- package/dist/src/review/known-flakes.js +112 -1
- package/dist/src/review/post-merge-review-accept.js +51 -0
- package/dist/src/review/post-merge-review-render.js +112 -0
- package/dist/src/review/post-merge-review.js +239 -0
- package/dist/src/review/protected-edits.js +24 -10
- package/dist/src/review/quarantine-render.js +65 -0
- 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 +180 -401
- 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 +127 -12
- package/dist/src/roles/role-guidance.js +65 -20
- package/dist/src/roles/role-labels.js +7 -5
- package/dist/src/roles/role-render.js +108 -56
- package/dist/src/roles/role-view.js +95 -56
- package/dist/src/roles/roles.js +33 -33
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/ci-parity.js +121 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/run/qa-coverage.js +125 -0
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +175 -0
- package/dist/src/run/run-detail-render.js +91 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -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} +122 -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} +170 -83
- 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} +105 -57
- 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 +178 -0
- package/dist/src/run/stage-checks-shared.js +65 -0
- package/dist/src/run/stage-name-checks.js +204 -0
- package/dist/src/run/stage-path-checks.js +231 -0
- package/dist/src/run/stage-prose-checks.js +261 -0
- package/dist/src/runner/leftover.js +216 -0
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +191 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +109 -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} +39 -18
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +103 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claim-entries.js +49 -0
- 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 +49 -17
- 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 +43 -28
- package/dist/src/status/status-roles.js +50 -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 +7 -8
- 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 +65 -18
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +60 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +78 -13
- package/dist/src/ui/badges.js +99 -38
- package/dist/src/ui/gui/gui-client-backlog.js +314 -0
- package/dist/src/ui/gui/gui-client-boot.js +89 -61
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
- package/dist/src/ui/gui/gui-client-drawer.js +155 -289
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +437 -68
- package/dist/src/ui/gui/gui-client-labels.js +6 -3
- 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 +60 -52
- package/dist/src/ui/gui/gui-client-pending.js +405 -24
- package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
- package/dist/src/ui/gui/gui-client-report.js +105 -430
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +163 -5
- package/dist/src/ui/gui/gui-client-team.js +36 -465
- package/dist/src/ui/gui/gui-client.js +38 -26
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +10 -7
- package/dist/src/ui/gui/gui-styles.js +38 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +364 -49
- package/dist/src/ui/status-render.js +109 -82
- package/dist/src/ui/team-alerts.js +728 -62
- package/dist/src/ui/tone.js +12 -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 +53 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +4 -12
- package/dist/src/ui/tui/tui-editor-backends.js +127 -0
- package/dist/src/ui/tui/tui-editor-brief.js +14 -36
- package/dist/src/ui/tui/tui-editor-budget.js +36 -39
- package/dist/src/ui/tui/tui-editor-check.js +29 -0
- package/dist/src/ui/tui/tui-editor-checks.js +28 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
- package/dist/src/ui/tui/tui-editor-config.js +99 -0
- package/dist/src/ui/tui/tui-editor-entry.js +39 -64
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +46 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
- package/dist/src/ui/tui/tui-editor-interval.js +29 -16
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +9 -29
- 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 +95 -41
- package/dist/src/ui/tui/tui-editor-retire.js +5 -13
- package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +71 -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 +101 -34
- package/dist/src/ui/tui/tui-editor-slots.js +24 -0
- package/dist/src/ui/tui/tui-editors.js +44 -2
- package/dist/src/ui/tui/tui-frame.js +68 -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 +33 -9
- package/dist/src/ui/tui/tui-pane-activity.js +35 -19
- package/dist/src/ui/tui/tui-pane-backlog.js +198 -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 +168 -8
- package/dist/src/ui/tui/tui-pane-history.js +446 -82
- package/dist/src/ui/tui/tui-pane-pending.js +539 -15
- package/dist/src/ui/tui/tui-pane-queued.js +78 -57
- package/dist/src/ui/tui/tui-pane-report.js +54 -16
- package/dist/src/ui/tui/tui-pane-session.js +34 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +91 -28
- package/dist/src/ui/tui/tui-panes.js +153 -34
- package/dist/src/ui/tui/tui.js +18 -15
- package/dist/src/verdict/fix-claim.js +129 -33
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +77 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/leftover.js +0 -130
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/qa-coverage.js +0 -108
- 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,40 +1,43 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/** The budget/tier FALLBACK side of the config views, split out of config-views.ts: how the
|
|
3
2
|
* team falls back at the daily spend cap — which free pair each tier runs on
|
|
4
3
|
* (resolveTierFallbacks and its BORROW_ORDER), the per-role fallback config a model-failure
|
|
5
|
-
* or cap episode hands a
|
|
4
|
+
* or cap episode hands a role (fallbackRoleConfig, applyFallbackModel), and the surfaces
|
|
6
5
|
* that name those pairs (seamsInUse, modelFallbackView, tierFallbackLabels). The per-seam
|
|
7
6
|
* resolution these build on — how one role/reviewer/resolver provider/model/thinking resolves
|
|
8
7
|
* (configForRole, reviewConfig, withModelOverrides, …) — stays in config-views.ts, so the
|
|
9
8
|
* one-way dependency runs fallback → seam and never back. Pure, like its sibling: no I/O and
|
|
10
9
|
* no caching. */
|
|
11
10
|
import { MODEL_TIERS, } from "./config-schema.js";
|
|
12
|
-
import { formatModelSelector,
|
|
11
|
+
import { formatModelSelector, parseModelSelector, routeModel } from "./model-selector.js";
|
|
13
12
|
import { modelPairName } from "../budget/budget.js";
|
|
14
13
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
15
|
-
import { baseRoleOf } from "../roles/
|
|
14
|
+
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
16
15
|
import { enabledRoleIds } from "./config.js";
|
|
17
|
-
import { modelFallbackActive } from "../
|
|
18
|
-
import { FALLBACK_REVIEW_TIMEOUT_S, REVIEW_TIMEOUT_S, configForRole, isTierName, resolvedModelFields, resolverConfig, reviewConfig, reviewRunConfig, roleSeamTier, topTierSelector, withModelOverrides, } from "./config-views.js";
|
|
16
|
+
import { modelFallbackActive } from "../runner/model-fallback.js";
|
|
17
|
+
import { FALLBACK_REVIEW_TIMEOUT_S, REVIEW_TIMEOUT_S, configForRole, isTierName, pairOverrides, resolvedModelFields, resolverConfig, reviewConfig, reviewRunConfig, roleSeamTier, topTierBackend, topTierSelector, withModelOverrides, } from "./config-views.js";
|
|
18
|
+
/** The resolved fields of one fallback step, keyed by the seam it routes to (PLANS.md "Mixed
|
|
19
|
+
* claude + pi fleets, part 2/4"): the backend is the step's own, else its tier primary's
|
|
20
|
+
* resolved backend (the tier entry's, else the top level's, else pi). A pair routed to a
|
|
21
|
+
* non-pi backend carries that backend as `backend` and its name as `provider` — the key the
|
|
22
|
+
* budget gate's handback (src/budget/budget-wiring.ts's runOnPair against the run's
|
|
23
|
+
* seamHoldKey capture, runner-run.ts's setRunPair) matches on — and the id passes to it as
|
|
24
|
+
* written (routeModel); a
|
|
25
|
+
* pi pair keeps its own provider (a bare pi selector keeps undefined). */
|
|
26
|
+
function seamKeyedPairFields(config, tier, provider, model, thinking, stepBackend) {
|
|
27
|
+
const backend = stepBackend ?? topTierBackend(config, tier) ?? config.backend ?? "pi";
|
|
28
|
+
const routed = model === undefined ? undefined : routeModel(provider, model, "pi", tier, backend);
|
|
29
|
+
return {
|
|
30
|
+
...resolvedModelFields(backend !== "pi" ? backend : provider, routed?.model, thinking),
|
|
31
|
+
...(backend !== "pi" ? { backend } : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
19
34
|
/** The resolved model fields of a pure `fallback` selector string: parsed as a pure selector
|
|
20
35
|
* (no legacy provider in scope), with the ambient top-level thinking when the selector
|
|
21
36
|
* carries none. fallbackPair's map and string branches and tierOwnChain share this
|
|
22
37
|
* assembly, so all three read one rule for how a `fallback` selector resolves. */
|
|
23
|
-
function fallbackSelectorFields(selector, config) {
|
|
38
|
+
function fallbackSelectorFields(selector, config, tier, backend) {
|
|
24
39
|
const parsed = parseModelSelector(selector);
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
/** A fallback pair's provider keyed by the seam it routes to (PLANS.md "Mixed claude + pi
|
|
28
|
-
* fleets, part 2/4"): a pair that routes to a non-pi backend carries that backend's name as
|
|
29
|
-
* its provider — `claude/claude-sonnet-5-5` and a bare `claude-sonnet-5-5` on a claude
|
|
30
|
-
* team both read `claude` — so the budget gate's handback (gate-polls.ts's tickOnPair
|
|
31
|
-
* against the tick's seamHoldKey capture, loop-tick.ts's setTickPair) matches a tick on
|
|
32
|
-
* either selector form. The same routing rule as routeModel: a provider naming an agent is
|
|
33
|
-
* that agent, no provider runs on the config's `agent`, anything else is pi, which keeps
|
|
34
|
-
* its own provider (a bare pi selector keeps undefined, exactly as before). */
|
|
35
|
-
function seamKeyedFallbackProvider(config, provider) {
|
|
36
|
-
const agent = provider === undefined ? config.backend : isAgentId(provider) ? provider : "pi";
|
|
37
|
-
return agent !== "pi" ? agent : provider;
|
|
40
|
+
return seamKeyedPairFields(config, tier, parsed.provider, parsed.model, parsed.thinking ?? config.thinking, backend);
|
|
38
41
|
}
|
|
39
42
|
/** The team-level fallback pair: the `default` tier's resolved pair under
|
|
40
43
|
* resolveTierFallbacks' borrow order, or null when the tier resolves to pause. The
|
|
@@ -61,10 +64,12 @@ function legacyFallbackPair(config) {
|
|
|
61
64
|
// The top-level model is a selector too (parsed the same way withModelOverrides does), so a
|
|
62
65
|
// fallback field that omits model borrows the parsed id, never the raw selector string.
|
|
63
66
|
const topSel = topTierSelector(config, "default");
|
|
64
|
-
const provider =
|
|
67
|
+
const provider = fbo.provider ?? topSel?.provider ?? config.provider;
|
|
65
68
|
const model = fbo.model ?? topSel?.model;
|
|
66
69
|
const thinking = fbo.thinking ?? topSel?.thinking ?? config.thinking;
|
|
67
|
-
|
|
70
|
+
// The legacy object's own `provider` is a pi provider (claude takes none), so naming one
|
|
71
|
+
// keeps the pair on pi whatever the team's backend; without one it inherits like any step.
|
|
72
|
+
return seamKeyedPairFields(config, "default", provider, model, thinking, fbo.provider !== undefined ? "pi" : undefined);
|
|
68
73
|
}
|
|
69
74
|
/** The order a tier borrows other tiers' own fallbacks in, after trying its own: small
|
|
70
75
|
* climbs (small → default → strong), default keeps near itself (default → strong → small),
|
|
@@ -77,7 +82,7 @@ const BORROW_ORDER = {
|
|
|
77
82
|
};
|
|
78
83
|
/** Exported for imageRerouteTarget (src/agent/image-input.ts), which walks a rerouting role's
|
|
79
84
|
* tier through the same borrow order the budget fallback uses (skipping the tier itself), and
|
|
80
|
-
* for the doctor's `image input` check naming the tier a text-only
|
|
85
|
+
* for the doctor's `image input` check naming the tier a text-only role reroutes to. */
|
|
81
86
|
export { BORROW_ORDER };
|
|
82
87
|
/** A tier's OWN fallback chain, first step first — what the `fallback` key declares at that
|
|
83
88
|
* tier's map entry (a selector or a list of them), or (default only) the string/list form of
|
|
@@ -104,7 +109,7 @@ function tierOwnChain(config, tier) {
|
|
|
104
109
|
if (legacy === null)
|
|
105
110
|
return [];
|
|
106
111
|
const primaryClaude = withModelOverrides(config, tier, {}).agent === "claude";
|
|
107
|
-
return [legacy.
|
|
112
|
+
return [legacy.backend === "claude" && primaryClaude ? { ...legacy, extraUsage: true } : legacy];
|
|
108
113
|
}
|
|
109
114
|
if (raw === undefined || raw === "pause")
|
|
110
115
|
return [];
|
|
@@ -115,9 +120,9 @@ function tierOwnChain(config, tier) {
|
|
|
115
120
|
.filter((s) => s !== "pause")
|
|
116
121
|
.map((s) => {
|
|
117
122
|
const step = typeof s === "string" ? { model: s } : s;
|
|
118
|
-
const fields = fallbackSelectorFields(step.model, config);
|
|
119
|
-
const extraUsage = fields.
|
|
120
|
-
if (fields.
|
|
123
|
+
const fields = fallbackSelectorFields(step.model, config, tier, step.backend);
|
|
124
|
+
const extraUsage = fields.backend === "claude" && claudeSeen;
|
|
125
|
+
if (fields.backend === "claude")
|
|
121
126
|
claudeSeen = true;
|
|
122
127
|
return {
|
|
123
128
|
...fields,
|
|
@@ -162,7 +167,7 @@ export function resolveTierFallbacks(config, usable) {
|
|
|
162
167
|
};
|
|
163
168
|
return { small: resolve("small"), default: resolve("default"), strong: resolve("strong") };
|
|
164
169
|
}
|
|
165
|
-
/** The config a role's
|
|
170
|
+
/** The config a role's runs use while a model-fallback episode is active (PLANS.md "Model
|
|
166
171
|
* failure fallback, part 1/2"): the role's tier pair from resolveTierFallbacks (the tier's own
|
|
167
172
|
* pair, else BORROW_ORDER's borrow), shaped like configForRole — provider/model/thinking over
|
|
168
173
|
* the role's own and top-level values, plus the role's minRunIntervalSeconds. Null when the
|
|
@@ -174,11 +179,7 @@ export function fallbackRoleConfig(config, role) {
|
|
|
174
179
|
return null;
|
|
175
180
|
const rc = config.roles[baseRoleOf(role)];
|
|
176
181
|
return {
|
|
177
|
-
...withModelOverrides(config, tier,
|
|
178
|
-
provider: pair.provider,
|
|
179
|
-
model: pair.model,
|
|
180
|
-
thinking: pair.thinking,
|
|
181
|
-
}),
|
|
182
|
+
...withModelOverrides(config, tier, pairOverrides(pair)),
|
|
182
183
|
minRunIntervalSeconds: rc?.minRunIntervalSeconds ?? config.minRunIntervalSeconds,
|
|
183
184
|
};
|
|
184
185
|
}
|
|
@@ -210,13 +211,13 @@ export function seamsInUse(config) {
|
|
|
210
211
|
add("resolver", resolverConfig(config));
|
|
211
212
|
add("director", configForRole(config, DIRECTOR_ROLE));
|
|
212
213
|
for (const { tier, step, pair } of tierChainSteps(config)) {
|
|
213
|
-
add(step === 0 ? `fallback:${tier}` : `fallback:${tier}#${step}`, withModelOverrides(config, tier,
|
|
214
|
+
add(step === 0 ? `fallback:${tier}` : `fallback:${tier}#${step}`, withModelOverrides(config, tier, pairOverrides(pair)));
|
|
214
215
|
}
|
|
215
216
|
return [...seams.values()];
|
|
216
217
|
}
|
|
217
218
|
/** Build a ModelFallbackView for an episode, or null while the episode is not running the
|
|
218
219
|
* fallback (no episode, or a due probe on the primary). role-view.ts's
|
|
219
|
-
* RoleViewPayload.modelFallback and status/status-data.ts's per-
|
|
220
|
+
* RoleViewPayload.modelFallback and status/status-data.ts's per-role row both build this
|
|
220
221
|
* shape, so one function keeps the two surfaces from disagreeing about the pair, its start,
|
|
221
222
|
* and its reason. `now` is the caller's clock (the status snapshot passes its own poll
|
|
222
223
|
* clock), so a row and its poll agree on what "active" means. */
|
|
@@ -259,11 +260,17 @@ export function tiersResolveDistinctPairs(resolved) {
|
|
|
259
260
|
.map((p) => modelPairName(p));
|
|
260
261
|
return new Set(pairs).size >= 2;
|
|
261
262
|
}
|
|
262
|
-
/** Drop a model-override seam's provider/model/thinking fields — the one rule the fallback
|
|
263
|
+
/** Drop a model-override seam's provider/model/backend/thinking fields — the one rule the fallback
|
|
263
264
|
* view's role and reviewer strips share, so a field on that triple cannot be dropped from one
|
|
264
265
|
* and left on the other (where it would reach a priced model after the cap). */
|
|
265
266
|
function stripModel(o) {
|
|
266
|
-
const { provider: _p, model: _m, thinking: _t, ...rest } = o;
|
|
267
|
+
const { provider: _p, model: _m, backend: _b, thinking: _t, ...rest } = o;
|
|
268
|
+
return rest;
|
|
269
|
+
}
|
|
270
|
+
/** Drop only a seam's explicit backend, for a tier-name entry kept through the cap: the tier
|
|
271
|
+
* reference stays valid, but a paid `backend` pinned beside it must not outlive the cap. */
|
|
272
|
+
function stripBackend(o) {
|
|
273
|
+
const { backend: _b, ...rest } = o;
|
|
267
274
|
return rest;
|
|
268
275
|
}
|
|
269
276
|
/** The reviewer config as the fallback view sees it: model seam stripped, and its time budget
|
|
@@ -282,7 +289,7 @@ function fallbackReview(review) {
|
|
|
282
289
|
function stripRoleModels(roles, keepTierNames) {
|
|
283
290
|
return Object.fromEntries(Object.entries(roles).map(([id, rc]) => [
|
|
284
291
|
id,
|
|
285
|
-
keepTierNames && isTierName(rc?.model) ? rc : stripModel(rc),
|
|
292
|
+
keepTierNames && isTierName(rc?.model) ? stripBackend(rc) : stripModel(rc),
|
|
286
293
|
]));
|
|
287
294
|
}
|
|
288
295
|
/** Every `roles.<id>` entry with its model seam stripped, except a seam `free` calls free — a
|
|
@@ -294,10 +301,10 @@ function freeRoleModels(config, free) {
|
|
|
294
301
|
return Object.fromEntries(Object.entries(config.roles).map(([id, rc]) => {
|
|
295
302
|
if (rc && free?.(withModelOverrides(config, roleSeamTier(config, id), rc)))
|
|
296
303
|
return [id, rc];
|
|
297
|
-
return [id, isTierName(rc?.model) ? rc : stripModel(rc)];
|
|
304
|
+
return [id, isTierName(rc?.model) ? stripBackend(rc) : stripModel(rc)];
|
|
298
305
|
}));
|
|
299
306
|
}
|
|
300
|
-
/** The config as seen by a role
|
|
307
|
+
/** The config as seen by a role running on the free fallback model
|
|
301
308
|
* (plans/fallback-model.md): the fallback's provider/model/thinking installed as the top-level
|
|
302
309
|
* values AND every per-role and reviewer model override dropped, so that EVERY seam that could
|
|
303
310
|
* otherwise reach a priced model — an author run, its reviewer, a conflict resolver — resolves
|
|
@@ -320,13 +327,26 @@ function freeRoleModels(config, free) {
|
|
|
320
327
|
* model, while every priced seam still lands on its tier's fallback pair. */
|
|
321
328
|
export function applyFallbackModel(config, resolved, free) {
|
|
322
329
|
if (resolved) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
+
// A pair's selector keeps pi's provider; a non-pi pair's model is already the whole id. On a
|
|
331
|
+
// team whose backend is not pi — or a pair routed off pi — the entry names the pair's
|
|
332
|
+
// backend, so the team's own backend cannot take a pi pair's id as its own.
|
|
333
|
+
const selectorOf = (f) => {
|
|
334
|
+
const pair = f.pair;
|
|
335
|
+
if (!pair)
|
|
336
|
+
return undefined;
|
|
337
|
+
const selector = formatModelSelector({
|
|
338
|
+
provider: pair.backend === undefined ? pair.provider : undefined,
|
|
339
|
+
model: pair.model ?? "",
|
|
340
|
+
thinking: pair.backend === undefined ? pair.thinking : undefined,
|
|
341
|
+
});
|
|
342
|
+
if (pair.backend === undefined && (config.backend ?? "pi") === "pi")
|
|
343
|
+
return formatModelSelector({ provider: pair.provider, model: pair.model ?? "", thinking: pair.thinking });
|
|
344
|
+
return {
|
|
345
|
+
backend: pair.backend ?? "pi",
|
|
346
|
+
model: selector,
|
|
347
|
+
...(pair.thinking !== undefined ? { thinking: pair.thinking } : {}),
|
|
348
|
+
};
|
|
349
|
+
};
|
|
330
350
|
const model = {};
|
|
331
351
|
const stepCaps = {};
|
|
332
352
|
for (const tier of MODEL_TIERS) {
|
|
@@ -336,16 +356,33 @@ export function applyFallbackModel(config, resolved, free) {
|
|
|
336
356
|
const seam = withModelOverrides(config, tier, {});
|
|
337
357
|
if (free?.(seam)) {
|
|
338
358
|
const own = topTierSelector(config, tier);
|
|
339
|
-
|
|
340
|
-
|
|
359
|
+
// A tier entry that names a backend keeps it as an object entry: a bare selector string
|
|
360
|
+
// would route by namespace and drop the backend the entry chose.
|
|
361
|
+
const backend = topTierBackend(config, tier);
|
|
362
|
+
if (own !== undefined) {
|
|
363
|
+
const thinking = own.thinking ?? config.thinking;
|
|
364
|
+
if (backend !== undefined)
|
|
365
|
+
model[tier] = {
|
|
366
|
+
backend,
|
|
367
|
+
model: formatModelSelector({ ...own, thinking: undefined }),
|
|
368
|
+
...(thinking !== undefined ? { thinking } : {}),
|
|
369
|
+
};
|
|
370
|
+
else
|
|
371
|
+
model[tier] = formatModelSelector({ ...own, thinking });
|
|
372
|
+
}
|
|
341
373
|
// A tier with no top-level selector — `agent: "claude"` riding its agent default —
|
|
342
374
|
// has no own selector to carry the level, so name the routed default plus it.
|
|
343
|
-
else if (seam.model !== undefined)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
375
|
+
else if (seam.model !== undefined) {
|
|
376
|
+
const routed = { provider: seam.provider, model: seam.model };
|
|
377
|
+
model[tier] =
|
|
378
|
+
backend !== undefined
|
|
379
|
+
? {
|
|
380
|
+
backend,
|
|
381
|
+
model: formatModelSelector(routed),
|
|
382
|
+
...(seam.thinking !== undefined ? { thinking: seam.thinking } : {}),
|
|
383
|
+
}
|
|
384
|
+
: formatModelSelector({ ...routed, thinking: seam.thinking });
|
|
385
|
+
}
|
|
349
386
|
continue;
|
|
350
387
|
}
|
|
351
388
|
const sel = selectorOf(resolved[tier]);
|
|
@@ -370,7 +407,8 @@ export function applyFallbackModel(config, resolved, free) {
|
|
|
370
407
|
return config;
|
|
371
408
|
return {
|
|
372
409
|
...config,
|
|
373
|
-
|
|
410
|
+
...(pair.backend !== undefined || config.backend !== undefined ? { backend: pair.backend ?? "pi" } : {}),
|
|
411
|
+
provider: pair.backend === undefined ? pair.provider : undefined,
|
|
374
412
|
model: pair.model,
|
|
375
413
|
thinking: pair.thinking,
|
|
376
414
|
review: fallbackReview(config.review),
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
import { MODEL_TRIPLE_KEYS, MODEL_TIERS, TIER_MAP_KEYS, THINKING_LEVELS, } from "./config-schema.js";
|
|
3
2
|
import { allRoleIds } from "../roles/roles.js";
|
|
4
3
|
import { isJsonObject } from "../files/json-object.js";
|
|
5
|
-
import { typoSuffix } from "../text/suggest.js";
|
|
4
|
+
import { didYouMean, suggestClosest, typoSuffix } from "../text/suggest.js";
|
|
6
5
|
import { truncate, isNonBlankString } from "../text/text.js";
|
|
7
6
|
/** The generic field-check machinery behind validateConfig (config-validation.ts): error
|
|
8
7
|
* message rendering (show, typeName), the unknown-key and known-role guards, the NumberRule
|
|
@@ -43,7 +42,7 @@ export function typeName(v) {
|
|
|
43
42
|
export function checkKnownKeys(obj, known, where, problems) {
|
|
44
43
|
for (const key of Object.keys(obj)) {
|
|
45
44
|
if (!known.includes(key))
|
|
46
|
-
problems.push(`unknown key "${key}" in ${where} (valid keys: ${known.join(", ")})${typoSuffix(key, known)}`);
|
|
45
|
+
problems.push(`unknown key "${key}" in ${where} (valid keys: ${known.join(", ")})${typoSuffix(key, known)}${key === "agent" ? ' — "agent" is retired, write "backend"' : ""}`);
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
/** Read an optional field once and hand it to `check` — the rule every per-field checker and
|
|
@@ -99,9 +98,9 @@ export const AT_LEAST_ONE = { ok: (n) => n >= 1, what: "a number of at least 1"
|
|
|
99
98
|
export const MAX_DURATION_SECONDS = 2147483;
|
|
100
99
|
/** Shared known-role guard for the per-role config sections (`roles.<id>` entries and
|
|
101
100
|
* `maxDailyCostUsdPerRole.<id>` keys): an id outside the role catalog and customRoles cannot
|
|
102
|
-
* work — a `roles` typo spawns a phantom
|
|
101
|
+
* work — a `roles` typo spawns a phantom role that errors every run forever; a caps typo
|
|
103
102
|
* silently no-ops the cap — so both reject it here with the valid ids, the same message shape
|
|
104
|
-
* `tumwater logs --
|
|
103
|
+
* `tumwater logs --transcript` uses for a bad flag value. Returns true when the id is known. */
|
|
105
104
|
export function checkKnownRoleId(section, id, customNames, problems) {
|
|
106
105
|
if (allRoleIds().includes(id) || customNames.has(id))
|
|
107
106
|
return true;
|
|
@@ -124,7 +123,7 @@ export function checkStringField(problems, obj, prefix, key, allowEmpty = true)
|
|
|
124
123
|
/** Every model-override section validates the same provider/model/thinking triple the same
|
|
125
124
|
* way: empty strings are rejected (see checkStringField) and thinking is value-checked —
|
|
126
125
|
* pi only recognizes THINKING_LEVELS and silently drops anything else, so a typo would run
|
|
127
|
-
* the
|
|
126
|
+
* the roles at pi's default depth and never say so.
|
|
128
127
|
*
|
|
129
128
|
* `modelShape` widens `model` per plans/model-tiers.md: "selector" (the default, for the
|
|
130
129
|
* legacy `fallbackModel` object) keeps it a plain selector string; "tier-map" (top level)
|
|
@@ -132,9 +131,10 @@ export function checkStringField(problems, obj, prefix, key, allowEmpty = true)
|
|
|
132
131
|
* selector strings — a map cannot coexist with a legacy provider in the same section, since
|
|
133
132
|
* the provider's old meaning (the whole string is a bare id under it) has no per-tier
|
|
134
133
|
* reading; "tier-name" (roles.<id>.model, review.model) also accepts a bare tier name as a
|
|
135
|
-
* reference into the top-level map.
|
|
136
|
-
*
|
|
137
|
-
*
|
|
134
|
+
* reference into the top-level map. A bare value within two edits of a tier name (ignoring
|
|
135
|
+
* case) is rejected as a misspelled tier reference. Tier names are valid ONLY where a tier
|
|
136
|
+
* reference means something: elsewhere (top level, fallbackModel) one is rejected with the two
|
|
137
|
+
* places that accept it, so a misplaced `"model": "strong"` is an error naming its fix instead of a
|
|
138
138
|
* bare model pattern pi can never resolve. */
|
|
139
139
|
export function checkModelTripleField(problems, obj, prefix, modelShape = "selector") {
|
|
140
140
|
for (const key of MODEL_TRIPLE_KEYS)
|
|
@@ -151,7 +151,8 @@ export function checkModelTripleField(problems, obj, prefix, modelShape = "selec
|
|
|
151
151
|
if (obj.provider !== undefined)
|
|
152
152
|
problems.push(`${prefix}model as a map by tier cannot coexist with ${prefix ? prefix : "the legacy top-level "}provider — move the provider into each tier's model id (got provider ${show(obj.provider)})`);
|
|
153
153
|
for (const [tier, v] of Object.entries(m))
|
|
154
|
-
|
|
154
|
+
// An object entry (`{ backend?, model?, thinking? }`) is validated by validateConfig.
|
|
155
|
+
if (!isNonBlankString(v) && !isJsonObject(v))
|
|
155
156
|
problems.push(`${prefix}model.${tier} must be a non-empty model id (got ${show(v)})`);
|
|
156
157
|
return;
|
|
157
158
|
}
|
|
@@ -172,6 +173,12 @@ export function checkModelTripleField(problems, obj, prefix, modelShape = "selec
|
|
|
172
173
|
if (modelShape === "tier-name")
|
|
173
174
|
return; // A valid tier reference.
|
|
174
175
|
problems.push(`${prefix}model must name a model id — tier names are valid only as roles.<id>.model or review.model values (got ${show(m)})`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (modelShape === "tier-name" && !m.includes("/") && obj.provider === undefined) {
|
|
179
|
+
const tier = suggestClosest(m, MODEL_TIERS);
|
|
180
|
+
if (tier !== null)
|
|
181
|
+
problems.push(`${prefix}model ${show(m)} is not a tier name or a model id pi can resolve${didYouMean(tier)}`);
|
|
175
182
|
}
|
|
176
183
|
}
|
|
177
184
|
/** Validate one numeric field against a NumberRule when present. */
|
|
@@ -223,3 +230,26 @@ export function checkReservedArgs(problems, args, argsPath, whyFor) {
|
|
|
223
230
|
problems.push(`${argsPath}[${i}] ${show(arg)} duplicates a flag tumwater sets — ${why}`);
|
|
224
231
|
});
|
|
225
232
|
}
|
|
233
|
+
/** The config <key> a `-c <key>=…` / `--config <key>=…` / `-c<key>=…` / `-c=<key>=…` /
|
|
234
|
+
* `--config=<key>=…` argv element names, or undefined when the element is not a keyed codex
|
|
235
|
+
* config passthrough. Codex takes the key and value in one token (`-c key=value`), and accepts a
|
|
236
|
+
* joined `-c=<key>=<value>` identically to `-c <key>=<value>`; `-c` and `--config` alone are
|
|
237
|
+
* followed by the `<key>=<value>` token as the next element, so the scanner passes both. Kept
|
|
238
|
+
* here beside its one caller (the codex `backends.args` screen); the tokens actually split by
|
|
239
|
+
* checkReservedArgs cannot see this form, which is why the scan is its own loop. */
|
|
240
|
+
export function codexConfigKey(arg, next) {
|
|
241
|
+
let rest;
|
|
242
|
+
if (arg === "-c" || arg === "--config")
|
|
243
|
+
rest = next;
|
|
244
|
+
else if (arg.startsWith("--config="))
|
|
245
|
+
rest = arg.slice("--config=".length);
|
|
246
|
+
else if (arg.startsWith("-c="))
|
|
247
|
+
rest = arg.slice(3);
|
|
248
|
+
else if (arg.startsWith("-c") && arg.length > 2 && !arg.startsWith("--"))
|
|
249
|
+
rest = arg.slice(2);
|
|
250
|
+
if (rest === undefined)
|
|
251
|
+
return undefined;
|
|
252
|
+
const eq = rest.indexOf("=");
|
|
253
|
+
const key = (eq === -1 ? rest : rest.slice(0, eq)).trim();
|
|
254
|
+
return key === "" ? undefined : key;
|
|
255
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/** The config KEY language — how `tumwater config get/set/unset` and the dashboard editors
|
|
3
2
|
* name a config value and what makes one legitimate, apart from the writes that change it.
|
|
4
3
|
* One family, in its own module because it serves both verbs AND the read side: `config get`,
|
|
5
4
|
* shell completion, doctor's holds and the dashboard controllers all address keys and screen
|
|
6
5
|
* per-key values without performing a write, so burying this in the config-write module made
|
|
7
6
|
* read-side callers import from the "config WRITES" layer. The mutators (the load →
|
|
8
|
-
* validate → atomic-write idiom, the cross-process lock) live in src/config/config-write.ts
|
|
7
|
+
* validate → atomic-write idiom, the cross-process lock) live in src/config/config-write-core.ts
|
|
8
|
+
* (the operator-editor verbs in src/config/config-write.ts;
|
|
9
|
+
* the director's staged request in src/config/config-request.ts);
|
|
9
10
|
* the per-key validators and key-shape parsing here are the one home of the wording both the
|
|
10
11
|
* writers and the read side show. */
|
|
11
|
-
import {
|
|
12
|
+
import { BACKEND_KEYS, BACKOFF_KEYS, BOOTSTRAP_KEYS, CHECK_KEYS, CLAUDE_AGENT_KEYS, CODEX_BACKEND_KEYS, REVIEW_KEYS, ROLE_ENTRY_KEYS, TIER_MAP_KEYS, TOP_LEVEL_KEYS } from "./config-schema.js";
|
|
12
13
|
import { typoSuffix } from "../text/suggest.js";
|
|
13
14
|
import { MAX_DURATION_SECONDS, POSITIVE_INTEGER, show } from "./config-field-checks.js";
|
|
14
15
|
import { parseQuietHours } from "../scheduling/quiet-hours.js";
|
|
@@ -83,11 +84,19 @@ export const PER_KEY_VALIDATORS = {
|
|
|
83
84
|
* check the per-role maps apply. The per-key value validator runs before the write, the same
|
|
84
85
|
* screening a bare top-level key gets. */
|
|
85
86
|
export const DOTTED_MAP_KEYS = ["maxDailyCostUsdPerRole", "quietHoursPerRole", "maxConcurrentPerProvider"];
|
|
87
|
+
/** The fixed-shape top-level sections `config get/set/unset` reaches one field of with a
|
|
88
|
+
* two-segment key (`review.model`, `bootstrap.untilPlansDone`), each with its field list. */
|
|
89
|
+
export const DOTTED_SECTION_FIELDS = {
|
|
90
|
+
review: REVIEW_KEYS,
|
|
91
|
+
check: CHECK_KEYS,
|
|
92
|
+
idleBackoff: BACKOFF_KEYS,
|
|
93
|
+
bootstrap: BOOTSTRAP_KEYS,
|
|
94
|
+
};
|
|
86
95
|
/** The valid first segments of a dotted config key — the per-role maps plus the `roles.` and
|
|
87
96
|
* `model.` sections. The generic dotted-shape error suggests against these so a typo'd head
|
|
88
97
|
* (`modle.small`, `maxDailyCostUsdPerRoll.feature`) gets the same did-you-mean the sibling
|
|
89
98
|
* unknown-key errors attach. */
|
|
90
|
-
const DOTTED_KEY_HEADS = [...DOTTED_MAP_KEYS, "roles", "model", "backends"];
|
|
99
|
+
const DOTTED_KEY_HEADS = [...DOTTED_MAP_KEYS, "roles", "model", "backends", ...Object.keys(DOTTED_SECTION_FIELDS)];
|
|
91
100
|
/** Split a config key into its write/get shape: `foo` is top-level,
|
|
92
101
|
* `maxDailyCostUsdPerRole.feature` / `quietHoursPerRole.qa` name one map entry,
|
|
93
102
|
* `roles.qa.model` names one role-entry field, and anything else dotted is an error
|
|
@@ -107,9 +116,10 @@ export function parseConfigKey(key) {
|
|
|
107
116
|
// `roles.<id>.<field>` names one role field). The id is shape-checked only — routing owns
|
|
108
117
|
// which agent ids exist (part 4b); a typo'd field is refused here with the valid ones named.
|
|
109
118
|
if (parts.length === 3 && first === "backends" && second !== undefined && second !== "" && third !== undefined) {
|
|
110
|
-
// configDir
|
|
111
|
-
// write a key nothing reads, so it is refused
|
|
112
|
-
|
|
119
|
+
// configDir/windowReserve are claude/codex-only (CLAUDE_AGENT_KEYS / CODEX_BACKEND_KEYS):
|
|
120
|
+
// a config set backends.pi.configDir would write a key nothing reads, so it is refused
|
|
121
|
+
// everywhere but claude and codex.
|
|
122
|
+
const validKeys = second === "claude" ? CLAUDE_AGENT_KEYS : second === "codex" ? CODEX_BACKEND_KEYS : BACKEND_KEYS;
|
|
113
123
|
if (validKeys.includes(third))
|
|
114
124
|
return { kind: "agent", id: second, field: third };
|
|
115
125
|
return {
|
|
@@ -129,6 +139,18 @@ export function parseConfigKey(key) {
|
|
|
129
139
|
error: `unknown model tier "${second ?? ""}" — a dotted \`model.<tier>\` names one of ${TIER_MAP_KEYS.join(", ")} (e.g. \`config set model.strong huggingface/zai-org/GLM-5.3-Flash:together:low\`)${typoSuffix(second ?? "", TIER_MAP_KEYS)}`,
|
|
130
140
|
};
|
|
131
141
|
}
|
|
142
|
+
// `<section>.<field>` names one field of a fixed-shape section (review, check, idleBackoff,
|
|
143
|
+
// bootstrap); the field must be one the schema lists, or the write would be a dead key.
|
|
144
|
+
if (parts.length === 2 && Object.hasOwn(DOTTED_SECTION_FIELDS, first)) {
|
|
145
|
+
const section = first;
|
|
146
|
+
const fields = DOTTED_SECTION_FIELDS[section];
|
|
147
|
+
if (fields.includes(second ?? ""))
|
|
148
|
+
return { kind: "section", section, field: second ?? "" };
|
|
149
|
+
return {
|
|
150
|
+
kind: "error",
|
|
151
|
+
error: `unknown ${first} field "${second ?? ""}" (valid fields: ${fields.join(", ")})${typoSuffix(second ?? "", fields)}`,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
132
154
|
// A typo'd first segment is the common miss (`maxDailyCostUsdPerRoll.feature`,
|
|
133
155
|
// `modle.small`); when the head is one of the valid ones the shape itself is wrong, so
|
|
134
156
|
// suggesting that head back would mislead — the generic wording is the whole story there.
|
|
@@ -137,7 +159,7 @@ export function parseConfigKey(key) {
|
|
|
137
159
|
: typoSuffix(first, DOTTED_KEY_HEADS);
|
|
138
160
|
return {
|
|
139
161
|
kind: "error",
|
|
140
|
-
error: `unknown config key "${key}" — dotted keys name one entry of a per-role map or one field of a role entry (e.g. \`maxDailyCostUsdPerRole.feature 1.5\`, \`roles.qa.model x\`); a bare key names a whole top-level value${headHint}`,
|
|
162
|
+
error: `unknown config key "${key}" — dotted keys name one entry of a per-role map or one field of a role entry (e.g. \`maxDailyCostUsdPerRole.feature 1.5\`, \`roles.qa.model x\`, \`review.model x\`); a bare key names a whole top-level value${headHint}`,
|
|
141
163
|
};
|
|
142
164
|
}
|
|
143
165
|
/** The error `tumwater config get <key>` and `config set <key> <value>` both surface for a
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { changedConfigKeys, loadConfigCached } from "./config.js";
|
|
2
|
-
import {
|
|
2
|
+
import { runnerIds } from "../roles/runner-ids.js";
|
|
3
3
|
import { configForRole, teamModelLabel, modelSelectorField } from "./config-views.js";
|
|
4
|
-
import { seamsInUse } from "./config-fallbacks.js";
|
|
5
4
|
import { bestEffortEventLogger, TUMWATER_SOURCE } from "../events/events.js";
|
|
6
|
-
import {
|
|
5
|
+
import { RoleRunner } from "../runner/runner.js";
|
|
7
6
|
import { configPath } from "../paths.js";
|
|
7
|
+
import { errorMessage } from "../text/text.js";
|
|
8
|
+
import { backendsInUse, seedOpencodeCatalog } from "../gates/startup-gate.js";
|
|
8
9
|
import { seedClaudeAccount } from "../agent/claude/claude-account.js";
|
|
9
|
-
import {
|
|
10
|
+
import { seedCodexAccount, readCodexAccount } from "../agent/codex/codex-account.js";
|
|
11
|
+
import { backendMissingMessage, findBackendBinary, resolveBackendBin } from "../pi/pi-bin.js";
|
|
10
12
|
/** A live config reloader over the given wiring; poll it once per scheduler cycle (see
|
|
11
13
|
* LiveConfigReload for the contract). */
|
|
12
14
|
export function newLiveConfigReload(deps) {
|
|
@@ -16,7 +18,7 @@ export function newLiveConfigReload(deps) {
|
|
|
16
18
|
// transition warnings).
|
|
17
19
|
let lastConfigError = null;
|
|
18
20
|
let configMissing = false;
|
|
19
|
-
let prevEnabled = new Set(
|
|
21
|
+
let prevEnabled = new Set(runnerIds(deps.config));
|
|
20
22
|
let live = deps.config;
|
|
21
23
|
// The previous successful reload's config, for the one-shot config_changed event. Seeded from
|
|
22
24
|
// the startup config, so the first poll of an unchanged file logs nothing.
|
|
@@ -32,6 +34,13 @@ export function newLiveConfigReload(deps) {
|
|
|
32
34
|
// reload block below), so repeated polls of a config that keeps a claude seam cannot respawn
|
|
33
35
|
// `claude auth status` on every cycle.
|
|
34
36
|
const liveClaudeSeedAttempted = new Set();
|
|
37
|
+
// Likewise for the codex-account seed: one `codex login status` per root per process.
|
|
38
|
+
const liveCodexSeedAttempted = new Set();
|
|
39
|
+
// Set while a live edit routing a codex seam is being refused for an API-key login, so the
|
|
40
|
+
// refusal warns once on the transition in (and once again when it re-enters after clearing).
|
|
41
|
+
let codexLiveRefused = false;
|
|
42
|
+
// Likewise for the opencode models catalog: one models.dev refresh per root per process.
|
|
43
|
+
const liveOpencodeSeedAttempted = new Set();
|
|
35
44
|
// Best-effort event logging: poll() runs inside the orchestrator's poll loop
|
|
36
45
|
// (src/orchestrator/orchestrator.ts), which has no catch, so an unwritable feed (ENOSPC,
|
|
37
46
|
// EACCES, the path replaced by a directory) would otherwise end the team — and, worse, skip
|
|
@@ -59,8 +68,32 @@ export function newLiveConfigReload(deps) {
|
|
|
59
68
|
configMissing = false;
|
|
60
69
|
}
|
|
61
70
|
if (reloaded.config) {
|
|
62
|
-
live = reloaded.config;
|
|
63
71
|
const nextConfig = reloaded.config;
|
|
72
|
+
// A live edit that routes a seam to a codex logged in with an API key is refused before
|
|
73
|
+
// the reload is applied: startup refuses the same codex seam (decision 5), and here a
|
|
74
|
+
// warn-only would let the running team spend per-token Codex the budget cap cannot see
|
|
75
|
+
// (review objection 1). Refusing keeps the last-known-good config, like a missing or
|
|
76
|
+
// broken file. The seed runs once per root (`codex login status` is a real probe); the
|
|
77
|
+
// persisted account file is the voice of the refusal on later polls, re-probing a stale
|
|
78
|
+
// api answer (codex-account.ts) so the operator's `codex login --device-auth` remedy
|
|
79
|
+
// clears it at the next seed. A subscription codex passes through to the backend loop.
|
|
80
|
+
const prevBackends = new Set(backendsInUse(prevLive));
|
|
81
|
+
const newlyRoutedCodex = !prevBackends.has("codex") && backendsInUse(nextConfig).includes("codex");
|
|
82
|
+
if (newlyRoutedCodex && !liveCodexSeedAttempted.has(deps.root)) {
|
|
83
|
+
liveCodexSeedAttempted.add(deps.root);
|
|
84
|
+
seedCodexAccount(deps.root, nextConfig);
|
|
85
|
+
}
|
|
86
|
+
if (newlyRoutedCodex && readCodexAccount(deps.root)?.billing === "api") {
|
|
87
|
+
if (codexLiveRefused)
|
|
88
|
+
return prevLive;
|
|
89
|
+
codexLiveRefused = true;
|
|
90
|
+
warn("refusing live edit: Codex is logged in with an API key; tumwater runs Codex on a ChatGPT subscription only — run `CODEX_HOME=<home> codex login --device-auth`");
|
|
91
|
+
return prevLive;
|
|
92
|
+
}
|
|
93
|
+
// A poll that did not refuse clears the warn-once marker so a later re-entry into the
|
|
94
|
+
// refusal (the operator reverts, then re-adds the API-billed codex seam) warns again.
|
|
95
|
+
codexLiveRefused = false;
|
|
96
|
+
live = reloaded.config;
|
|
64
97
|
// A live edit that changes behavior elsewhere logs one event naming the settings that
|
|
65
98
|
// changed (maxConcurrent and sessionRetentionDays have their own events elsewhere).
|
|
66
99
|
const changedKeys = changedConfigKeys(prevLive, reloaded.config);
|
|
@@ -89,31 +122,37 @@ export function newLiveConfigReload(deps) {
|
|
|
89
122
|
});
|
|
90
123
|
}
|
|
91
124
|
// A live edit that routes a seam to a backend the previous config did not use: the
|
|
92
|
-
// team keeps running, but that backend must be verified and (
|
|
93
|
-
// seeded, exactly as startup would — so a claude reviewer
|
|
94
|
-
// start uncheckable. A missing binary is a warning event,
|
|
95
|
-
// logs its verdict as a backend_binary event. The seed is
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
125
|
+
// team keeps running, but that backend must be verified and its account (claude) or
|
|
126
|
+
// models catalog (opencode) seeded, exactly as startup would — so a claude reviewer
|
|
127
|
+
// added to a pi team does not start uncheckable. A missing binary is a warning event,
|
|
128
|
+
// never a crash; a present one logs its verdict as a backend_binary event. The seed is
|
|
129
|
+
// attempted once per root. (The codex seed moved to the refusal check at the top of
|
|
130
|
+
// this block, which also refuses an API-key-logged-in codex seam.)
|
|
131
|
+
for (const backend of backendsInUse(reloaded.config)) {
|
|
132
|
+
if (prevBackends.has(backend))
|
|
99
133
|
continue;
|
|
100
|
-
const resolved =
|
|
101
|
-
if (
|
|
102
|
-
emit({ source: TUMWATER_SOURCE, type: "backend_binary", agent, found: true });
|
|
134
|
+
const resolved = resolveBackendBin(reloaded.config, backend);
|
|
135
|
+
if (findBackendBinary(resolved)) {
|
|
136
|
+
emit({ source: TUMWATER_SOURCE, type: "backend_binary", agent: backend, found: true });
|
|
103
137
|
}
|
|
104
138
|
else {
|
|
105
|
-
warn(`newly routed ${
|
|
139
|
+
warn(`newly routed ${backend} backend: ${backendMissingMessage(resolved, backend)}`);
|
|
106
140
|
}
|
|
107
|
-
if (
|
|
141
|
+
if (backend === "claude" && !liveClaudeSeedAttempted.has(deps.root)) {
|
|
108
142
|
liveClaudeSeedAttempted.add(deps.root);
|
|
109
143
|
seedClaudeAccount(deps.root, reloaded.config);
|
|
110
144
|
}
|
|
145
|
+
if (backend === "opencode" && !liveOpencodeSeedAttempted.has(deps.root)) {
|
|
146
|
+
liveOpencodeSeedAttempted.add(deps.root);
|
|
147
|
+
// Not awaited: poll() is synchronous and a models.dev fetch must not stall the loop.
|
|
148
|
+
void seedOpencodeCatalog(deps.root, reloaded.config, { ...deps.opencodeSeed, warn }).catch((err) => warn(`open code model catalog seed failed: ${errorMessage(err)}`));
|
|
149
|
+
}
|
|
111
150
|
}
|
|
112
151
|
prevLive = reloaded.config;
|
|
113
152
|
for (const r of deps.runners)
|
|
114
153
|
r.config = reloaded.config;
|
|
115
154
|
// Live-resize the concurrency cap: a mid-run edit changes how many pi runs execute
|
|
116
|
-
// concurrently within this poll — no restart. Growing admits already-queued
|
|
155
|
+
// concurrently within this poll — no restart. Growing admits already-queued runs;
|
|
117
156
|
// shrinking never preempts in-flight work, it only caps future grants.
|
|
118
157
|
const newMaxConcurrent = Math.max(1, reloaded.config.maxConcurrent);
|
|
119
158
|
if (newMaxConcurrent !== lastMaxConcurrent) {
|
|
@@ -141,17 +180,17 @@ export function newLiveConfigReload(deps) {
|
|
|
141
180
|
}
|
|
142
181
|
}
|
|
143
182
|
lastKeyCaps = nextKeyCaps;
|
|
144
|
-
const nowEnabled =
|
|
183
|
+
const nowEnabled = runnerIds(reloaded.config);
|
|
145
184
|
// Enabling a role — or raising `roles.<id>.instances` — mid-run starts it: create the
|
|
146
|
-
// runner for each newly present
|
|
147
|
-
// instance count keeps its runner; `
|
|
185
|
+
// runner for each newly present role id (its persisted state survives). A lowered
|
|
186
|
+
// instance count keeps its runner; `runnerEnabled` skips its future runs. A scoped round
|
|
148
187
|
// skips every other id: the enabling is logged (the warn loop below), but its runner
|
|
149
188
|
// waits for the next unscoped round.
|
|
150
189
|
for (const role of nowEnabled) {
|
|
151
|
-
if (deps.roleFilter !== undefined &&
|
|
190
|
+
if (deps.roleFilter !== undefined && !deps.roleFilter.includes(role))
|
|
152
191
|
continue;
|
|
153
192
|
if (!deps.runners.some((r) => r.role === role))
|
|
154
|
-
deps.runners.push(new
|
|
193
|
+
deps.runners.push(new RoleRunner(deps.root, role, reloaded.config, deps.mainBranch, deps.runnerSignal));
|
|
155
194
|
}
|
|
156
195
|
for (const role of prevEnabled)
|
|
157
196
|
if (!nowEnabled.includes(role))
|