tumwater 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -28
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +23 -0
- package/dist/src/agent/agent-backend.js +9 -7
- package/dist/src/agent/backend-traits.js +77 -0
- package/dist/src/agent/claude/claude-account.js +81 -161
- package/dist/src/agent/claude/claude-args.js +19 -19
- package/dist/src/agent/claude/claude-backend.js +58 -48
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +11 -19
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +112 -0
- package/dist/src/agent/notes-shim.js +40 -0
- package/dist/src/agent/opencode/opencode-args.js +60 -0
- package/dist/src/agent/opencode/opencode-backend.js +258 -0
- package/dist/src/agent/opencode/opencode-catalog.js +210 -0
- package/dist/src/agent/opencode/opencode-config.js +193 -0
- package/dist/src/agent/opencode/opencode-env.js +35 -0
- package/dist/src/agent/opencode/opencode-session-id.js +36 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +16 -33
- package/dist/src/agent/run-agent.js +475 -381
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +142 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +487 -0
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +384 -0
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +354 -132
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +132 -22
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +89 -11
- package/dist/src/backlog/backlog-structure.js +271 -125
- package/dist/src/backlog/backlog-write.js +121 -471
- package/dist/src/backlog/backlog.js +129 -17
- package/dist/src/backlog/question-answer.js +345 -0
- package/dist/src/baseline/main-baseline.js +157 -101
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +164 -55
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +41 -0
- package/dist/src/{brief.js → brief/brief.js} +38 -33
- package/dist/src/budget/budget.js +55 -28
- package/dist/src/budget/fallback-breaker.js +31 -31
- package/dist/src/build/build-check-detect.js +25 -16
- package/dist/src/build/build-check-events.js +87 -22
- package/dist/src/build/build-check-report.js +66 -27
- package/dist/src/build/build-check-scoped.js +64 -68
- package/dist/src/build/build-check.js +17 -15
- package/dist/src/build/build-info.js +3 -3
- package/dist/src/build/build-stage.js +31 -35
- package/dist/src/build/dep-install.js +21 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +131 -37
- package/dist/src/change/change-render.js +56 -14
- package/dist/src/cli/brief-command.js +9 -8
- package/dist/src/cli/cli-args.js +133 -28
- package/dist/src/cli/cli-command-args.js +82 -21
- package/dist/src/cli/cli-editor.js +15 -2
- package/dist/src/cli/cli-flag-specs.js +180 -50
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +14 -11
- package/dist/src/cli/cli-query-commands.js +325 -224
- package/dist/src/cli/cli-repo-gate.js +12 -2
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/cli-start.js +453 -0
- package/dist/src/cli/completion.js +202 -17
- package/dist/src/cli/config-commands.js +51 -25
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +413 -157
- package/dist/src/cli/log-commands.js +234 -62
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +215 -231
- package/dist/src/cli.js +154 -49
- package/dist/src/collections.js +84 -18
- package/dist/src/concurrency/check-permit.js +35 -19
- package/dist/src/concurrency/lock.js +22 -17
- package/dist/src/concurrency/provider-lease.js +105 -0
- package/dist/src/concurrency/semaphore.js +344 -46
- package/dist/src/config/config-aliases.js +105 -0
- package/dist/src/config/config-editable-keys.js +16 -4
- package/dist/src/config/config-fallbacks.js +416 -0
- package/dist/src/config/config-field-checks.js +58 -19
- package/dist/src/config/config-keys.js +182 -0
- package/dist/src/config/config-live.js +113 -22
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +58 -39
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +201 -175
- package/dist/src/config/config-views.js +202 -346
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +219 -391
- package/dist/src/config/config.js +102 -81
- package/dist/src/config/model-selector.js +44 -18
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +191 -161
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +229 -305
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +181 -104
- package/dist/src/doctor/doctor-install.js +31 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +496 -270
- package/dist/src/doctor/doctor-notify.js +5 -7
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +189 -69
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +145 -50
- package/dist/src/doctor/image-probe.js +257 -0
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +60 -0
- package/dist/src/events/event-format.js +363 -155
- package/dist/src/events/event-labels.js +123 -0
- package/dist/src/events/event-legacy.js +164 -0
- package/dist/src/events/event-read.js +74 -38
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +83 -27
- package/dist/src/events/events.js +47 -26
- package/dist/src/events/notify.js +85 -35
- package/dist/src/failure/failure-cluster.js +23 -20
- package/dist/src/failure/failure-data.js +140 -92
- package/dist/src/failure/failure-render.js +97 -78
- package/dist/src/failure/failure-state-change.js +45 -33
- package/dist/src/failure/time-spend.js +166 -82
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +129 -41
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +45 -9
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +2 -2
- package/dist/src/gates/bootstrap-gates.js +8 -8
- package/dist/src/gates/budget-gates.js +361 -52
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +201 -103
- package/dist/src/gates/maintenance-quota.js +34 -37
- package/dist/src/gates/pause-gates.js +33 -15
- package/dist/src/gates/readiness.js +8 -3
- package/dist/src/gates/role-cap-gates.js +23 -28
- package/dist/src/gates/startup-gate.js +154 -48
- package/dist/src/gates/streak-gate.js +67 -41
- package/dist/src/git/commit-message.js +80 -48
- package/dist/src/git/git-diff.js +29 -23
- package/dist/src/git/git-run.js +87 -46
- package/dist/src/git/git.js +114 -92
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +18 -9
- package/dist/src/git/worktree-pool.js +150 -49
- package/dist/src/git/worktree-use.js +14 -20
- package/dist/src/git/worktree.js +108 -51
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +88 -21
- package/dist/src/gui/gui-backlog-commands.js +297 -0
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +9 -14
- package/dist/src/gui/gui-config-commands.js +462 -0
- package/dist/src/gui/gui-endpoint-commands.js +17 -716
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +13 -331
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +240 -0
- package/dist/src/gui/gui-prompt-commands.js +231 -0
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +33 -9
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +166 -203
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +96 -0
- package/dist/src/history/history.js +206 -103
- package/dist/src/inbox/inbox-attachments.js +94 -39
- package/dist/src/inbox/inbox-cancel.js +16 -15
- package/dist/src/inbox/inbox-edit.js +10 -10
- package/dist/src/inbox/inbox-submit.js +54 -14
- package/dist/src/inbox/inbox-top.js +99 -0
- package/dist/src/inbox/inbox.js +49 -44
- package/dist/src/inbox/pending-prompt.js +27 -27
- package/dist/src/inbox/prompt-commands.js +154 -100
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +15 -14
- package/dist/src/init/init.js +35 -29
- package/dist/src/merge/backlog-conflicts.js +335 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +100 -93
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +209 -0
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +183 -110
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +6 -5
- package/dist/src/merge/merge-drain.js +351 -0
- package/dist/src/merge/merge-git.js +403 -0
- package/dist/src/merge/merge-pipeline.js +111 -0
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +113 -0
- package/dist/src/merge/merge-queue.js +224 -0
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +385 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +97 -71
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +151 -82
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +214 -0
- package/dist/src/notes-cli.js +112 -0
- package/dist/src/operator/operator-commands.js +268 -89
- package/dist/src/operator/operator-intent.js +130 -82
- package/dist/src/operator/operator-requests.js +82 -87
- package/dist/src/operator/retire.js +49 -31
- package/dist/src/operator/revert.js +179 -0
- package/dist/src/orchestrator/orchestrator-launch.js +116 -48
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +78 -348
- package/dist/src/orchestrator/orchestrator.js +173 -361
- package/dist/src/orchestrator/retention.js +66 -15
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +52 -0
- package/dist/src/orchestrator/startup-migrations.js +44 -0
- package/dist/src/paths.js +228 -75
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +30 -5
- package/dist/src/pi/pi-backend.js +19 -27
- package/dist/src/pi/pi-bin.js +64 -67
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +163 -42
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +137 -113
- package/dist/src/pi/pi-watchdogs.js +20 -20
- package/dist/src/pi-extension/bounded-output.js +25 -8
- package/dist/src/pi-extension/context-budget.js +5 -3
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +36 -0
- package/dist/src/pi-extension/full-output.js +5 -12
- package/dist/src/pi-extension/strip-thinking.js +79 -0
- package/dist/src/pi-extension/trim-system-prompt.js +37 -0
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +11 -11
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +48 -9
- package/dist/src/process/run-marker.js +42 -7
- package/dist/src/process/supervisor.js +47 -35
- package/dist/src/prompt/dialect.js +46 -11
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +201 -188
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -1
- package/dist/src/prompt/prompt-followup.js +43 -29
- package/dist/src/prompt/prompt.js +255 -172
- package/dist/src/redeploy/redeploy-policy.js +29 -24
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +7 -6
- package/dist/src/redeploy/redeployer.js +78 -70
- package/dist/src/redeploy/self-reload.js +3 -2
- package/dist/src/report/report-data.js +374 -285
- package/dist/src/report/report-fold.js +430 -0
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +321 -61
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +133 -14
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +7 -7
- package/dist/src/review/review-followup.js +21 -9
- package/dist/src/review/review-precheck.js +77 -33
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +16 -2
- package/dist/src/review/review.js +172 -356
- package/dist/src/review/secret-leaks.js +147 -0
- package/dist/src/review/suite-rerun.js +3 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +135 -0
- package/dist/src/roles/role-catalog.js +172 -137
- package/dist/src/roles/role-guidance.js +112 -44
- package/dist/src/roles/role-labels.js +9 -0
- package/dist/src/roles/role-render.js +221 -28
- package/dist/src/roles/role-view.js +244 -33
- package/dist/src/roles/roles.js +37 -35
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +10 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +166 -136
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/run/run-finalize.js +154 -0
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -6
- package/dist/src/run/run-prompt.js +304 -0
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +80 -35
- package/dist/src/run/run-stage.js +295 -0
- package/dist/src/run/run-timing.js +198 -0
- package/dist/src/run/run-usage.js +180 -0
- package/dist/src/run/run-verdict.js +301 -0
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/runner/leftover.js +185 -0
- package/dist/src/{loop → runner}/model-fallback.js +13 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +10 -10
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/runner/runner-pi.js +433 -0
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +34 -28
- package/dist/src/runner/runner-run.js +625 -0
- package/dist/src/runner/runner-state-load.js +268 -0
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/runner/runner.js +350 -0
- package/dist/src/scheduling/backoff.js +64 -54
- package/dist/src/scheduling/claims.js +46 -26
- package/dist/src/scheduling/once-round.js +30 -30
- package/dist/src/scheduling/quiet-hours.js +26 -34
- package/dist/src/scheduling/scheduling.js +87 -74
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +123 -0
- package/dist/src/status/status-data.js +106 -191
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +26 -0
- package/dist/src/status/status-polls.js +70 -59
- package/dist/src/status/status-roles.js +131 -0
- package/dist/src/{fleet → team}/error-storm.js +16 -16
- package/dist/src/{fleet → team}/failure-spread.js +10 -10
- package/dist/src/{fleet → team}/orchestrator-info.js +97 -25
- package/dist/src/{fleet → team}/reclaim.js +9 -9
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +60 -26
- package/dist/src/team/team-polls.js +299 -0
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +47 -40
- package/dist/src/text/datetime.js +102 -16
- package/dist/src/text/format.js +49 -3
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +89 -54
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +68 -14
- package/dist/src/ui/badges.js +165 -93
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +221 -42
- package/dist/src/ui/gui/gui-client-composer.js +105 -27
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +226 -130
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +492 -68
- package/dist/src/ui/gui/gui-client-labels.js +23 -0
- package/dist/src/ui/gui/gui-client-markdown.js +1 -1
- package/dist/src/ui/gui/gui-client-model.js +84 -76
- package/dist/src/ui/gui/gui-client-operator.js +112 -78
- package/dist/src/ui/gui/gui-client-pending.js +248 -41
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +122 -179
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +118 -76
- package/dist/src/ui/gui/gui-client-settings.js +180 -20
- package/dist/src/ui/gui/gui-client-team.js +252 -0
- package/dist/src/ui/gui/gui-client.js +48 -40
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +25 -24
- package/dist/src/ui/gui/gui-styles.js +46 -38
- package/dist/src/ui/progress-data.js +11 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +40 -40
- package/dist/src/ui/status-model.js +276 -194
- package/dist/src/ui/status-payload.js +227 -62
- package/dist/src/ui/status-render.js +196 -108
- package/dist/src/ui/team-alerts.js +532 -0
- package/dist/src/ui/tone.js +11 -11
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -19
- package/dist/src/ui/transcript.js +85 -38
- package/dist/src/ui/tui/tui-backlog.js +132 -21
- package/dist/src/ui/tui/tui-editor-answer.js +93 -0
- package/dist/src/ui/tui/tui-editor-attach.js +105 -0
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +105 -0
- package/dist/src/ui/tui/tui-editor-budget.js +159 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +219 -0
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +124 -0
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +112 -0
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +255 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +90 -0
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +94 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +170 -0
- package/dist/src/ui/tui/tui-editor-retire.js +81 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +85 -0
- package/dist/src/ui/tui/tui-editor-session.js +123 -0
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +185 -0
- package/dist/src/ui/tui/tui-frame.js +107 -58
- package/dist/src/ui/tui/tui-history.js +16 -12
- package/dist/src/ui/tui/tui-input.js +56 -60
- package/dist/src/ui/tui/tui-keys.js +106 -1825
- package/dist/src/ui/tui/tui-pane-activity.js +102 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +269 -0
- package/dist/src/ui/tui/tui-pane-brief.js +29 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +174 -0
- package/dist/src/ui/tui/tui-pane-history.js +636 -0
- package/dist/src/ui/tui/tui-pane-pending.js +369 -0
- package/dist/src/ui/tui/tui-pane-queued.js +159 -0
- package/dist/src/ui/tui/tui-pane-report.js +154 -0
- package/dist/src/ui/tui/tui-pane-session.js +120 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +224 -0
- package/dist/src/ui/tui/tui-pane.js +125 -44
- package/dist/src/ui/tui/tui-panes.js +364 -0
- package/dist/src/ui/tui/tui.js +52 -34
- package/dist/src/verdict/fix-claim.js +135 -62
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +80 -24
- package/dist/src/verdict/reply-contract.js +51 -28
- package/dist/src/version.js +2 -2
- package/package.json +5 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/brief-edit.js +0 -42
- package/dist/src/cli/cli-run.js +0 -469
- package/dist/src/fleet/fleet-polls.js +0 -207
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-check-failures.js +0 -141
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-git.js +0 -190
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -56
- package/dist/src/landing/landing-queue.js +0 -149
- package/dist/src/landing/landing-slot.js +0 -256
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/loop/leftover.js +0 -129
- package/dist/src/loop/loop-pi.js +0 -370
- package/dist/src/loop/loop-state.js +0 -204
- package/dist/src/loop/loop-tick.js +0 -545
- package/dist/src/loop/loop.js +0 -315
- package/dist/src/loop/revision.js +0 -61
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/roles/loop-ids.js +0 -60
- package/dist/src/tick/stage-check.js +0 -302
- package/dist/src/tick/telemetry-digest.js +0 -24
- package/dist/src/tick/tick-detail-data.js +0 -107
- package/dist/src/tick/tick-detail.js +0 -150
- package/dist/src/tick/tick-finalize.js +0 -135
- package/dist/src/tick/tick-prompt.js +0 -201
- package/dist/src/tick/tick-stage.js +0 -223
- package/dist/src/tick/tick-timing.js +0 -195
- package/dist/src/tick/tick-usage.js +0 -142
- package/dist/src/tick/tick-verdict.js +0 -201
- package/dist/src/ui/fleet-alerts.js +0 -174
- package/dist/src/ui/gui/gui-client-fleet.js +0 -576
- package/dist/src/ui/gui/gui-client-sound.js +0 -52
package/README.md
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
# tumwater
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
> ### ⚠️ Warning: tumwater eats tokens
|
|
4
|
+
>
|
|
5
|
+
> tumwater runs models continuously, all day, on every role. A busy team can burn through
|
|
6
|
+
> **on the order of a billion tokens a day or more**. On pay-per-token API keys that is real,
|
|
7
|
+
> unbounded money; on subscription backends it burns through your plan's usage windows just as
|
|
8
|
+
> fast. Set `maxDailyCostUsd` before you start, and watch actual spend with `tumwater report`
|
|
9
|
+
> and `tumwater status`.
|
|
10
|
+
|
|
11
|
+
tumwater is an opinionated autonomous development team, built on
|
|
12
|
+
[pi](https://github.com/badlogic/pi-mono). You write a short project brief, and a team of
|
|
13
|
+
roles (feature, bugfix, planning, tests, cleanup, docs, and more) builds the project
|
|
6
14
|
one small, reviewed commit at a time. It came from wanting to write only the brief and let a team
|
|
7
|
-
of always-on specialists do the rest: each
|
|
15
|
+
of always-on specialists do the rest: each role owns one concern, merges one change per run,
|
|
8
16
|
sleeps when it has nothing to do, and wakes when main moves. All project state lives in the
|
|
9
|
-
local git repo, and no remote is ever touched.
|
|
17
|
+
local git repo, and no remote is ever touched. The words used here (team, role, run, merge) are
|
|
18
|
+
defined in [docs/glossary.md](docs/glossary.md).
|
|
10
19
|
|
|
11
|
-

|
|
12
21
|
|
|
13
22
|
## Install
|
|
14
23
|
|
|
@@ -23,13 +32,19 @@ instead: `npm install && npm run build && npm link`.
|
|
|
23
32
|
|
|
24
33
|
With `claude` installed and logged in, `npm run smoke:claude` is an opt-in live check of the
|
|
25
34
|
claude backend: it drives the real CLI through a throwaway repo (a few cents of API-equivalent
|
|
26
|
-
spend on a subscription) and is not part of `npm test`, which stays green without claude.
|
|
35
|
+
spend on a subscription) and is not part of `npm test`, which stays green without claude. `npm run
|
|
36
|
+
smoke:opencode` is the same live check for the opencode backend, driving the real `opencode` CLI
|
|
37
|
+
against its free DeepSeek tier; it needs an OpenCode V2 install (`brew install opencode`) and is
|
|
38
|
+
also outside `npm test`. `npm run smoke:codex` is the same live check for the codex backend,
|
|
39
|
+
driving the real `codex` CLI against a logged-in ChatGPT subscription; it needs a Codex install
|
|
40
|
+
and is also outside `npm test`.
|
|
27
41
|
|
|
28
42
|
## Status
|
|
29
43
|
|
|
30
44
|
<!-- tumwater:status:start -->
|
|
31
|
-
**v0.
|
|
32
|
-
which files
|
|
45
|
+
**v0.3.0**: working tumwater. The director and 14 of the 16 roles are enabled by default; `selfcheck`,
|
|
46
|
+
which files tumwater bugs from tumwater's own event log, and `feedback`, which files them as GitHub
|
|
47
|
+
issues on znation/tumwater, are opt-in (`roles.selfcheck.enabled`, `roles.feedback.enabled`).
|
|
33
48
|
|
|
34
49
|
Open work: [PLANS.md](PLANS.md) (planned), [BUGS.md](BUGS.md) (open bugs),
|
|
35
50
|
[QUESTIONS.md](QUESTIONS.md) (open questions).
|
|
@@ -41,40 +56,74 @@ Open work: [PLANS.md](PLANS.md) (planned), [BUGS.md](BUGS.md) (open bugs),
|
|
|
41
56
|
cd your-project # a new or existing directory
|
|
42
57
|
tumwater init "Build a tiny markdown-to-html converter CLI in Python."
|
|
43
58
|
# --template, --file, and --adopt pick a starting point (see docs/cli.md)
|
|
44
|
-
tumwater
|
|
45
|
-
tumwater
|
|
46
|
-
tumwater
|
|
47
|
-
tumwater
|
|
59
|
+
tumwater start # start the team (Ctrl+C to stop)
|
|
60
|
+
tumwater start --gui # ... and serve the browser dashboard at http://127.0.0.1:7180
|
|
61
|
+
tumwater start --tui # ... and attach the terminal TUI in the same window
|
|
62
|
+
tumwater start --for 2h # run for a bounded window, then finish current work, summarize, and exit
|
|
63
|
+
tumwater start --once # one round of runs for cron/CI; exits 3 if a run failed
|
|
48
64
|
```
|
|
49
65
|
|
|
50
66
|
A fresh or empty project is seeded with `"bootstrap": {"untilPlansDone": 5}`, which holds the
|
|
51
|
-
maintenance
|
|
67
|
+
maintenance roles until 5 plans are done; remove it from `tumwater.json` to end that early.
|
|
52
68
|
|
|
53
|
-
From another terminal, `tumwater tui` or the browser dashboard watches the
|
|
54
|
-
and `tumwater logs -f` check it, `tumwater brief --file <path>` replaces the
|
|
55
|
-
`tumwater prompt "<text>"`
|
|
56
|
-
file backlog entries,
|
|
57
|
-
`tumwater doctor` audits the
|
|
69
|
+
From another terminal, `tumwater tui` or the browser dashboard watches the team, `tumwater status`
|
|
70
|
+
and `tumwater logs -f` check it, `tumwater brief --file <path>` replaces the brief,
|
|
71
|
+
`tumwater prompt "<text>"` sends a message to the director's inbox, `tumwater plan` and
|
|
72
|
+
`tumwater bug` file backlog entries, `tumwater revert <sha>` undoes one merged change on main,
|
|
73
|
+
and `tumwater pause`, `resume`, `wake`, and `stop` control the roles. `tumwater doctor` audits the team and `tumwater report` shows usage and cost. `gui
|
|
58
74
|
--all-interfaces` exposes the dashboard, and with it the director prompt, to your whole network,
|
|
59
75
|
so pair it with `--token <secret>`.
|
|
60
76
|
|
|
77
|
+
`tumwater prompt --attach <image>` gives a role an image to read at its next run. When a role's
|
|
78
|
+
model cannot read images, the command refuses the attachment up front (naming the fix) unless
|
|
79
|
+
the role reroutes a blind read to a vision model, in which case it says where the message will
|
|
80
|
+
run; `tumwater doctor` probes each claimed image-input model once (cached 7 days, `--reprobe` to
|
|
81
|
+
force) and the dashboards and `tumwater role <id>` say which roles read images and where a blind
|
|
82
|
+
run reroutes.
|
|
83
|
+
|
|
61
84
|
Every command, flag, and TUI key is in [docs/cli.md](docs/cli.md); `tumwater help` prints the
|
|
62
|
-
same list, and `tumwater completion bash|zsh` prints shell completion. Settings live in
|
|
63
|
-
`tumwater.json` and edits apply live while the
|
|
85
|
+
same list, and `tumwater completion bash|zsh|fish` prints shell completion. Settings live in
|
|
86
|
+
`tumwater.json` and edits apply live while the team runs; see [docs/config.md](docs/config.md).
|
|
64
87
|
|
|
65
88
|
**Backends:** any OpenAI-compatible model pi can reach works, chosen by `model` in `tumwater.json`
|
|
66
|
-
with a `fallback` that a failing role switches to. `
|
|
67
|
-
|
|
68
|
-
|
|
89
|
+
with a `fallback` that a failing role switches to. `backend` beside `model` picks who runs it:
|
|
90
|
+
`"backend": "claude"` runs a model id on Claude Code, `"backend": "opencode"` on the OpenCode CLI — its free
|
|
91
|
+
DeepSeek tier needs no login or API key — and `"backend": "codex"` on the Codex CLI's ChatGPT subscription
|
|
92
|
+
(no API key; its two usage windows behave like a claude subscription's). Requirements, the fallback, free-ness, and usage-window
|
|
93
|
+
behavior, and how subscription runs are billed (as estimated spend, outside the daily cap) are in
|
|
94
|
+
[docs/backends.md](docs/backends.md).
|
|
95
|
+
|
|
96
|
+
**Mixing providers:** `model` says which model, `backend` says which program runs it (`pi`, the
|
|
97
|
+
default, `claude`, `opencode` or `codex`). A tier entry, a role, a review and a fallback step each take an
|
|
98
|
+
object with both, so one config can mix a Claude strong tier with pi providers:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"model": {
|
|
103
|
+
"default": "huggingface/deepseek-ai/DeepSeek-V4.1-Flash:deepinfra",
|
|
104
|
+
"strong": { "backend": "claude", "model": "claude-opus-5-5" }
|
|
105
|
+
},
|
|
106
|
+
"fallback": "omlx/Qwen3.8-27B-MLX-oQ4e-mtp"
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The default tier runs on pi through Hugging Face, the strong model jobs (the director, the plan
|
|
111
|
+
role, the reviewer, and the conflict resolver) run on the claude CLI's included usage, and the fallback runs on pi through
|
|
112
|
+
oMLX — all concurrently, under each provider's cap (set it with `maxConcurrentPerProvider`;
|
|
113
|
+
`tumwater status` shows how many runs each key has going out of its cap). An OpenCode tier is
|
|
114
|
+
`{ "backend": "opencode", "model": "deepseek-v4.1-flash-free" }`, so its free tier can sit beside pi
|
|
115
|
+
and claude in one config; a codex tier is `{ "backend": "codex", "model": "gpt-6.1-sol" }`, so a ChatGPT
|
|
116
|
+
subscription can cover the team's default tier beside pi and claude. A Claude model that should stay on pi (billed per token) is the plain
|
|
117
|
+
`anthropic/claude-…` selector. See [plans/agent-backends.md §3](plans/agent-backends.md).
|
|
69
118
|
|
|
70
|
-
For how the
|
|
119
|
+
For how the roles, review gate, scheduling, and updates work, see
|
|
71
120
|
[docs/how-it-works.md](docs/how-it-works.md). For a measured comparison of tumwater's own
|
|
72
121
|
code against human-written open source, see [docs/code-metrics.md](docs/code-metrics.md).
|
|
73
122
|
|
|
74
|
-
## Appendix:
|
|
123
|
+
## Appendix: the brief
|
|
75
124
|
|
|
76
|
-
The brief this repository was started from, kept for history.
|
|
77
|
-
between the markers below on every
|
|
125
|
+
The brief this repository was started from, kept for history. tumwater still reads the brief from
|
|
126
|
+
between the markers below on every run.
|
|
78
127
|
|
|
79
128
|
<!-- tumwater:prompt:start -->
|
|
80
129
|
Idea: agentic harness
|
package/dist/build-info.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Appending a backend's configured passthrough args to an already-built argv. Shared by the
|
|
2
|
+
* claude, opencode and codex argv builders (claude-args.ts, opencode-args.ts and codex-args.ts),
|
|
3
|
+
* whose passthrough handling is otherwise identical, so the three backends reject a reserved
|
|
4
|
+
* flag by the same rule and the same message shape. A passthrough arg equal to a flag the
|
|
5
|
+
* harness itself sets is appended AFTER the harness's own copy, and all three backends' parsers
|
|
6
|
+
* are last-wins, so it would silently override the model, the session choice, or the isolation
|
|
7
|
+
* flags — each such arg is rejected here with the offending arg named. `moduleName` names the
|
|
8
|
+
* blocking backend in the error (e.g. `claudeArgs`, `opencodeArgs` or `codexArgs`); `reserved`
|
|
9
|
+
* is that backend's HARNESS_*_FLAGS set. */
|
|
10
|
+
export function appendPassthroughArgs(args, passthrough, reserved, moduleName) {
|
|
11
|
+
for (const arg of passthrough ?? []) {
|
|
12
|
+
// Match the flag itself even when it carries its value as `--flag=value`.
|
|
13
|
+
const flag = arg.split("=", 1)[0];
|
|
14
|
+
if (reserved.has(flag)) {
|
|
15
|
+
// Name the reserved flag (the arg may carry it as `--flag=value`, so `flag` here is the
|
|
16
|
+
// normalized token) and the fix, so the operator knows exactly which passthrough to drop
|
|
17
|
+
// rather than which of several harness-set flags the arg collided with.
|
|
18
|
+
throw new Error(`${moduleName}: passthrough arg ${JSON.stringify(arg)} repeats a flag tumwater sets (` +
|
|
19
|
+
`${flag} is reserved) — drop it from your passthrough args`);
|
|
20
|
+
}
|
|
21
|
+
args.push(arg);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** The backend seam (plans/agent-backends.md "The seam"): what one non-interactive agent
|
|
2
2
|
* run needs from its backend — how to build the command, how to translate its native stdout
|
|
3
3
|
* into tumwater's canonical event stream, and how to classify its exit. src/agent/run-agent.ts
|
|
4
|
-
* owns everything backend-neutral (spawn, marker, watchdogs, abort,
|
|
5
|
-
*
|
|
4
|
+
* owns everything backend-neutral (spawn, marker, watchdogs, abort, stderr capture); the
|
|
5
|
+
* post-run settlement — the group-kill and marker sweeps on exit, the spawn-error result and
|
|
6
|
+
* the close-time reconcile/classify that builds the `PiRunResult` — lives in
|
|
7
|
+
* src/agent/run-settle.ts. A backend registered here supplies the rest. src/agent/run-agent.ts
|
|
6
8
|
* registers the pi and claude adapters here.
|
|
7
9
|
*
|
|
8
10
|
* The registry is keyed by id and fed by explicit registration (src/agent/run-agent.ts
|
|
@@ -10,7 +12,7 @@
|
|
|
10
12
|
* real one. */
|
|
11
13
|
import { hasResumableSession } from "./session-ledger.js";
|
|
12
14
|
/** The backend id used when a config names none: pi, the harness's default backend. */
|
|
13
|
-
export const
|
|
15
|
+
export const DEFAULT_BACKEND = "pi";
|
|
14
16
|
const registry = new Map();
|
|
15
17
|
/** Register one backend under its id, replacing any previous entry with the same id. */
|
|
16
18
|
export function registerAgentBackend(backend) {
|
|
@@ -18,7 +20,7 @@ export function registerAgentBackend(backend) {
|
|
|
18
20
|
}
|
|
19
21
|
/** The backend registered under `id`, or undefined when none is — the caller decides whether
|
|
20
22
|
* an unknown id is a hard error. */
|
|
21
|
-
export function
|
|
23
|
+
export function backendById(id) {
|
|
22
24
|
return registry.get(id);
|
|
23
25
|
}
|
|
24
26
|
/** Whether the backend for `opts.agent` can resume a session in `dir` for a run of
|
|
@@ -26,9 +28,9 @@ export function agentBackend(id) {
|
|
|
26
28
|
* "Sessions"), so a claude author or gate run resumes through its ledger id instead of being
|
|
27
29
|
* read as having no session. Falls back to the backend-neutral session-file check when the
|
|
28
30
|
* backend is unregistered or supplies no `resumable` hook. */
|
|
29
|
-
export function
|
|
30
|
-
const backend =
|
|
31
|
+
export function backendSessionResumable(dir, opts, configDir) {
|
|
32
|
+
const backend = backendById(opts.agent);
|
|
31
33
|
if (backend?.resumable)
|
|
32
|
-
return backend.resumable(dir, opts);
|
|
34
|
+
return backend.resumable(dir, opts, configDir);
|
|
33
35
|
return hasResumableSession(dir);
|
|
34
36
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-backend capabilities that the config, prompt, watchdog, progress and doctor layers
|
|
3
|
+
* read (plans/opencode-backend.md "Groundwork: the claude-only branches become backend
|
|
4
|
+
* traits"). Pure — imports nothing from the runtime registry (agent-backend.ts) — so those
|
|
5
|
+
* layers can read a backend's traits without pulling the registry in. That is the same reason
|
|
6
|
+
* `BACKEND_DEFAULT_MODELS` lives in src/config/model-selector.ts.
|
|
7
|
+
*
|
|
8
|
+
* An id with no entry reads as pi, because src/agent/run-agent.ts routes an unregistered id to
|
|
9
|
+
* the pi backend.
|
|
10
|
+
*/
|
|
11
|
+
import { HARNESS_CLAUDE_FLAGS } from "./claude/claude-args.js";
|
|
12
|
+
import { HARNESS_OPENCODE_FLAGS } from "./opencode/opencode-args.js";
|
|
13
|
+
import { HARNESS_CODEX_FLAGS } from "./codex/codex-args.js";
|
|
14
|
+
import { HARNESS_PI_FLAGS } from "../config/config-schema.js";
|
|
15
|
+
/** Whether the backend streams tool output. pi streams content-bearing tool updates, so its
|
|
16
|
+
* calls are classified per command; claude, opencode and codex send none, so silence carries no
|
|
17
|
+
* hang signal and every call is judged by the buffered threshold (plans/agent-backends.md
|
|
18
|
+
* "Watchdogs and progress"). Only claude, opencode and codex are false; an unknown id reads as
|
|
19
|
+
* pi. */
|
|
20
|
+
export function streamsToolOutput(backend) {
|
|
21
|
+
// Only claude, opencode and codex stream no tool output; every other id (including an
|
|
22
|
+
// unregistered one) reads as pi, which does stream.
|
|
23
|
+
return backend !== "claude" && backend !== "opencode" && backend !== "codex";
|
|
24
|
+
}
|
|
25
|
+
/** Whether the backend's model jobs resolve through pi's models.json. True for pi only (and an
|
|
26
|
+
* unknown id, which reads as pi); a claude, opencode or codex job reads no pi pricing and needs
|
|
27
|
+
* no definitions file. */
|
|
28
|
+
export function usesPiModels(backend) {
|
|
29
|
+
// Only claude, opencode and codex read no pi models.json; every other id (including an
|
|
30
|
+
// unregistered one) reads as pi, which does.
|
|
31
|
+
return backend !== "claude" && backend !== "opencode" && backend !== "codex";
|
|
32
|
+
}
|
|
33
|
+
/** The reason string config validation prints for a reserved `backends.<id>.args` flag, or
|
|
34
|
+
* undefined when the flag is not reserved for that backend. Wraps the HARNESS_*_FLAGS sets for
|
|
35
|
+
* claude (HARNESS_CLAUDE_FLAGS), opencode (HARNESS_OPENCODE_FLAGS), codex (HARNESS_CODEX_FLAGS)
|
|
36
|
+
* and pi (HARNESS_PI_FLAGS), keeping each backend's reason wording. The flag is the bare name —
|
|
37
|
+
* checkReservedArgs splits `--mode=x` on `=` before calling. */
|
|
38
|
+
export function reservedPassthroughFlag(backend, flag) {
|
|
39
|
+
switch (backend) {
|
|
40
|
+
case "pi":
|
|
41
|
+
return HARNESS_PI_FLAGS.get(flag);
|
|
42
|
+
case "claude":
|
|
43
|
+
return HARNESS_CLAUDE_FLAGS.has(flag) ? "it is managed by tumwater" : undefined;
|
|
44
|
+
case "opencode":
|
|
45
|
+
return HARNESS_OPENCODE_FLAGS.has(flag) ? "it is managed by tumwater" : undefined;
|
|
46
|
+
case "codex":
|
|
47
|
+
// The standalone-flag screen; the `-c <key>=…` / `--config <key>=…` config-key screen is
|
|
48
|
+
// arity-aware (a `-c` token and its key are different argv elements), so config-validation
|
|
49
|
+
// owns it against HARNESS_CODEX_CONFIG_KEYS (codex-args.ts) rather than here.
|
|
50
|
+
return HARNESS_CODEX_FLAGS.has(flag) ? "it is managed by tumwater" : undefined;
|
|
51
|
+
default:
|
|
52
|
+
// An unknown backend has no known harness argv to clash with, so nothing is reserved.
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** A seam's model as one readable token: `<provider>/<model>` for a pi seam (and any unknown
|
|
57
|
+
* id, which reads as pi), the bare model for claude — or "claude backend" when there is none —
|
|
58
|
+
* `opencode/<model>` for opencode, whose ids all read under the opencode namespace, and the
|
|
59
|
+
* bare model for codex — or "codex default" when there is none. The one home of the label,
|
|
60
|
+
* shared by the doctor report and the image-input surface strings so they cannot drift. */
|
|
61
|
+
export function modelLabel(backend, provider, model) {
|
|
62
|
+
if (backend === "claude")
|
|
63
|
+
return model ?? "claude backend";
|
|
64
|
+
if (backend === "opencode")
|
|
65
|
+
return `opencode/${model ?? "?"}`;
|
|
66
|
+
if (backend === "codex")
|
|
67
|
+
return model ?? "codex default";
|
|
68
|
+
return `${provider ?? "?"}/${model ?? "?"}`;
|
|
69
|
+
}
|
|
70
|
+
/** A backend id's display name as the status surfaces show it: title-cased
|
|
71
|
+
* (`id.charAt(0).toUpperCase() + id.slice(1)`, so `"claude"` → `"Claude"`, `"codex"` →
|
|
72
|
+
* `"Codex"`). The one home of the spelling the subscription window's per-backend block label
|
|
73
|
+
* (part 3a/9) and the later display parts share, so a backend's name cannot drift between
|
|
74
|
+
* them. */
|
|
75
|
+
export function backendDisplayName(id) {
|
|
76
|
+
return id.charAt(0).toUpperCase() + id.slice(1);
|
|
77
|
+
}
|
|
@@ -1,172 +1,79 @@
|
|
|
1
|
-
/** The claude account state:
|
|
1
|
+
/** The claude account state: the claude backend's subscription account (src/agent/
|
|
2
|
+
* subscription-account.ts) — which billing pays for claude runs, and the newest subscription
|
|
2
3
|
* usage-window reading each run reported (plans/agent-backends.md §7 "Billing" and §8 "The
|
|
3
4
|
* subscription usage window"). Persisted at `.tumwater/state/claude-account.json`, written only
|
|
4
5
|
* by the orchestrator process, so the sibling parts that throttle or display the subscription
|
|
5
6
|
* window (6b/7–6e/7) read one answer instead of re-deriving it per surface.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Everything here is a thin wrapper over the generic per-backend store (found
|
|
9
|
+
* grounds/codex-backend.md "Groundwork: the subscription account is per backend", item 1),
|
|
10
|
+
* specialized to the backend id "claude", plus the claude-only seed (auth status, the API-key
|
|
11
|
+
* and third-party-provider billing checks) that no other backend shares. The file is a cache,
|
|
12
|
+
* never a source of truth: a run that reports billing overwrites it, and a missing or
|
|
13
|
+
* unreadable file reads as "no subscription". `resetsAt` is stored as epoch milliseconds (the
|
|
14
|
+
* run parser reports unix seconds; the store converts once), so consumers compare it to
|
|
15
|
+
* `Date.now()` directly. */
|
|
11
16
|
import { spawnSync } from "node:child_process";
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import { resolvedBackendConfig } from "../../config/config-views.js";
|
|
19
|
+
import { parseJsonObject } from "../../files/json-object.js";
|
|
20
|
+
import { observeAccount, readAccount, subscriptionWindowRejected, utilizationPct, windowEntries, windowLabel, windowReserved, windowUsedUp, writeAccount, } from "../subscription-account.js";
|
|
21
|
+
import { claudeBackend, claudeConfigDir } from "./claude-backend.js";
|
|
16
22
|
import { claudeEnv, claudeThirdPartyProvider } from "./claude-env.js";
|
|
17
|
-
/**
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* partial work or reset. API-billed claude and overage are budget/fallback matters, not a
|
|
22
|
-
* window hold, so they read false; a pi 429 reports no window and also reads false. */
|
|
23
|
-
export function subscriptionWindowRejected(run) {
|
|
24
|
-
return (run.transientRateLimit &&
|
|
25
|
-
run.billing === "subscription" &&
|
|
26
|
-
(run.rateWindows ?? []).some((w) => w.status === "rejected" && w.isUsingOverage !== true));
|
|
27
|
-
}
|
|
28
|
-
/** Status severity for ordering: a rejected window limits, an allowed_warning nears its limit,
|
|
29
|
-
* every other reading (allowed/ok) rides at the bottom, and a reset one no longer limits. */
|
|
30
|
-
const WINDOW_RANK = { rejected: 3, allowed_warning: 2, reset: 0 };
|
|
31
|
-
/** The window type as a compact label: `five_hour` → `5h`, `seven_day` → `7d`; an unknown
|
|
32
|
-
* type falls back to its words with underscores as spaces. The one home of the window spelling
|
|
33
|
-
* the status badge and the role view share. */
|
|
34
|
-
export function claudeWindowLabel(type) {
|
|
35
|
-
const m = /^(\w+)_(hour|day|week)$/.exec(type);
|
|
36
|
-
if (!m)
|
|
37
|
-
return type.replace(/_/g, " ");
|
|
38
|
-
const words = { one: "1", two: "2", three: "3", four: "4", five: "5", six: "6", seven: "7", eight: "8", nine: "9", ten: "10" };
|
|
39
|
-
const unit = m[2] === "hour" ? "h" : m[2] === "day" ? "d" : "w";
|
|
40
|
-
const word = m[1] ?? "";
|
|
41
|
-
return `${words[word] ?? word}${unit}`;
|
|
42
|
-
}
|
|
43
|
-
/** The account's windows as a list, most constrained first: by status severity, then the
|
|
44
|
-
* reported utilization (a missing reading sorts below any number), then the earlier reset, then
|
|
45
|
-
* the type name — so a tie always orders the same way. Empty when the account reported none. */
|
|
46
|
-
export function claudeWindowEntries(account) {
|
|
47
|
-
return Object.entries(account.windows)
|
|
48
|
-
.map(([type, w]) => ({ type, ...w }))
|
|
49
|
-
.sort((a, b) => {
|
|
50
|
-
const rank = (WINDOW_RANK[b.status] ?? 1) - (WINDOW_RANK[a.status] ?? 1);
|
|
51
|
-
if (rank !== 0)
|
|
52
|
-
return rank;
|
|
53
|
-
const util = (b.utilization ?? -1) - (a.utilization ?? -1);
|
|
54
|
-
if (util !== 0)
|
|
55
|
-
return util;
|
|
56
|
-
const reset = (a.resetsAt ?? Number.POSITIVE_INFINITY) - (b.resetsAt ?? Number.POSITIVE_INFINITY);
|
|
57
|
-
if (reset !== 0)
|
|
58
|
-
return reset;
|
|
59
|
-
return a.type.localeCompare(b.type);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
/** Read the persisted account state, or null when it is absent or unreadable. A file without a
|
|
63
|
-
* usable top-level `observedAt` reads as absent (the plan's torn-file rule), and a window whose
|
|
64
|
-
* `resetsAt` is at or before `now` reads back with `status: "reset"` — the reader, not every
|
|
65
|
-
* consumer, decides that the window no longer limits. */
|
|
23
|
+
/** Read the persisted claude account state, or null when it is absent or unreadable. A file
|
|
24
|
+
* without a usable top-level `observedAt` reads as absent (the plan's torn-file rule), and a
|
|
25
|
+
* window whose `resetsAt` is at or before `now` reads back with `status: "reset"` — the
|
|
26
|
+
* reader, not every consumer, decides that the window no longer limits. */
|
|
66
27
|
export function readClaudeAccount(root, now = Date.now()) {
|
|
67
|
-
|
|
68
|
-
if (doc === null)
|
|
69
|
-
return null;
|
|
70
|
-
const billing = doc.billing === "subscription" || doc.billing === "api" ? doc.billing : undefined;
|
|
71
|
-
const observedAt = finiteNumber(doc.observedAt, Number.NaN);
|
|
72
|
-
if (billing === undefined || !Number.isFinite(observedAt))
|
|
73
|
-
return null;
|
|
74
|
-
const windows = {};
|
|
75
|
-
if (isJsonObject(doc.windows)) {
|
|
76
|
-
for (const [type, value] of Object.entries(doc.windows)) {
|
|
77
|
-
const window = readWindow(value, observedAt, now);
|
|
78
|
-
if (window !== undefined)
|
|
79
|
-
setWindow(windows, type, window);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
billing,
|
|
84
|
-
...(typeof doc.subscriptionType === "string" ? { subscriptionType: doc.subscriptionType } : {}),
|
|
85
|
-
observedAt,
|
|
86
|
-
windows,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
/** One window entry from the file, healed: a non-object or a status-less entry is dropped, a
|
|
90
|
-
* missing per-window `observedAt` falls back to the file's, and a past `resetsAt` flips the
|
|
91
|
-
* status to `reset`. */
|
|
92
|
-
function readWindow(value, fileObservedAt, now) {
|
|
93
|
-
if (!isJsonObject(value) || typeof value.status !== "string")
|
|
94
|
-
return undefined;
|
|
95
|
-
const observedAt = finiteNumber(value.observedAt, fileObservedAt);
|
|
96
|
-
const resetsAt = finiteNumber(value.resetsAt, Number.NaN);
|
|
97
|
-
const resets = Number.isFinite(resetsAt);
|
|
98
|
-
// utilization heals like the two stamps above: JSON's `1e999` parses to Infinity, and a
|
|
99
|
-
// non-finite fraction would render as `Infinity%` in the badges and poison the
|
|
100
|
-
// claudeWindowEntries comparator (Infinity - Infinity is NaN).
|
|
101
|
-
const utilization = finiteNumber(value.utilization, undefined);
|
|
102
|
-
const window = {
|
|
103
|
-
status: resets && resetsAt <= now ? "reset" : value.status,
|
|
104
|
-
...(resets ? { resetsAt } : {}),
|
|
105
|
-
...(utilization !== undefined ? { utilization } : {}),
|
|
106
|
-
...(typeof value.overageStatus === "string" ? { overageStatus: value.overageStatus } : {}),
|
|
107
|
-
...(value.isUsingOverage === true ? { isUsingOverage: true } : {}),
|
|
108
|
-
observedAt,
|
|
109
|
-
};
|
|
110
|
-
return window;
|
|
28
|
+
return readAccount(root, "claude", now);
|
|
111
29
|
}
|
|
112
|
-
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
|
|
118
|
-
Object.defineProperty(windows, type, { value: window, enumerable: true, writable: true, configurable: true });
|
|
119
|
-
}
|
|
120
|
-
/** Overwrite the account file with `account`, creating the state directory. The orchestrator's
|
|
121
|
-
* one writer; a failure to write is the caller's to handle (best-effort at the tick boundary).
|
|
122
|
-
* Written through writeJsonAtomic (tmp + rename): a crash or ENOSPC mid-write leaves the
|
|
123
|
-
* previous file whole, not a torn one the reader heals to "no subscription" — which would
|
|
124
|
-
* silently drop the billing answer and the window hold until the next run re-observed them. */
|
|
30
|
+
/** Overwrite the claude account file with `account`, creating the state directory. The
|
|
31
|
+
* orchestrator's one writer; a failure to write is the caller's to handle (best-effort at the
|
|
32
|
+
* run boundary). Written through writeJsonAtomic (tmp + rename): a crash or ENOSPC mid-write
|
|
33
|
+
* leaves the previous file whole, not a torn one the reader heals to "no subscription" — which
|
|
34
|
+
* would silently drop the billing answer and the window hold until the next run re-observed
|
|
35
|
+
* them. */
|
|
125
36
|
export function writeClaudeAccount(root, account) {
|
|
126
|
-
|
|
37
|
+
writeAccount(root, "claude", account);
|
|
127
38
|
}
|
|
128
|
-
/** Fold one run's billing and rate-window readings into the account file: the run's
|
|
129
|
-
* overwrites the stored answer, and each reported window replaces the stored reading
|
|
130
|
-
* `type`, stamped with `observedAt`. A run that reported neither billing nor windows
|
|
131
|
-
* file untouched (and returns whatever was already there). */
|
|
39
|
+
/** Fold one run's billing and rate-window readings into the claude account file: the run's
|
|
40
|
+
* `billing` overwrites the stored answer, and each reported window replaces the stored reading
|
|
41
|
+
* for its `type`, stamped with `observedAt`. A run that reported neither billing nor windows
|
|
42
|
+
* leaves the file untouched (and returns whatever was already there). */
|
|
132
43
|
export function observeClaudeAccount(root, run, now = Date.now()) {
|
|
133
|
-
|
|
134
|
-
const billing = run.billing ?? existing?.billing;
|
|
135
|
-
const reported = run.rateWindows ?? [];
|
|
136
|
-
if (billing === undefined || (run.billing === undefined && reported.length === 0))
|
|
137
|
-
return existing;
|
|
138
|
-
const windows = { ...(existing?.windows ?? {}) };
|
|
139
|
-
for (const w of reported)
|
|
140
|
-
setWindow(windows, w.type, windowFromRun(w, now));
|
|
141
|
-
const account = {
|
|
142
|
-
billing,
|
|
143
|
-
...(existing?.subscriptionType !== undefined ? { subscriptionType: existing.subscriptionType } : {}),
|
|
144
|
-
observedAt: now,
|
|
145
|
-
windows,
|
|
146
|
-
};
|
|
147
|
-
writeClaudeAccount(root, account);
|
|
148
|
-
return account;
|
|
44
|
+
return observeAccount(root, "claude", run, now);
|
|
149
45
|
}
|
|
150
|
-
/**
|
|
151
|
-
*
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
46
|
+
/** The subscription account's used-up usage window, or null — see subscription-account.ts's
|
|
47
|
+
* windowUsedUp for the rule. */
|
|
48
|
+
export function claudeWindowUsedUp(account, now, includeOverage = true, reserve) {
|
|
49
|
+
return windowUsedUp(account, now, includeOverage, reserve);
|
|
50
|
+
}
|
|
51
|
+
/** Re-export of the per-backend helpers under their historical claude names for callers that
|
|
52
|
+
* predate the per-backend store (budget gates, team holds, the doctor, the role view, the
|
|
53
|
+
* status poll). */
|
|
54
|
+
export { subscriptionWindowRejected, windowReserved };
|
|
55
|
+
/** The window type as a compact label — subscription-account.ts's windowLabel, under its
|
|
56
|
+
* historical claude name. */
|
|
57
|
+
export function claudeWindowLabel(type) {
|
|
58
|
+
return windowLabel(type);
|
|
59
|
+
}
|
|
60
|
+
/** The fraction of a subscription usage window consumed as a whole-number percent —
|
|
61
|
+
* subscription-account.ts's utilizationPct, under its historical claude name. */
|
|
62
|
+
export function claudeUtilizationPct(utilization) {
|
|
63
|
+
return utilizationPct(utilization);
|
|
164
64
|
}
|
|
165
|
-
/**
|
|
166
|
-
*
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
65
|
+
/** The account's windows as a list, most constrained first — subscription-account.ts's
|
|
66
|
+
* windowEntries, under its historical claude name. */
|
|
67
|
+
export function claudeWindowEntries(account) {
|
|
68
|
+
return windowEntries(account);
|
|
69
|
+
}
|
|
70
|
+
/** Seed the claude account file before the first run (plans/agent-backends.md §7):
|
|
71
|
+
* `ANTHROPIC_API_KEY` set means claude prefers the key, so billing is API; otherwise
|
|
72
|
+
* `claude auth status` decides — a claude.ai OAuth login is a subscription, any other login is
|
|
73
|
+
* API. An existing file is left alone, and a probe that cannot run or is not logged in leaves
|
|
74
|
+
* the file absent (readClaudeAccount then reads "no subscription"). Returns the account
|
|
75
|
+
* written, or null. A failed write also returns null and never throws, so the first claude
|
|
76
|
+
* run's observeClaudeAccount fills the file in. */
|
|
170
77
|
export function seedClaudeAccount(root, config, now = Date.now()) {
|
|
171
78
|
const existing = readClaudeAccount(root, now);
|
|
172
79
|
if (existing !== null)
|
|
@@ -176,11 +83,16 @@ export function seedClaudeAccount(root, config, now = Date.now()) {
|
|
|
176
83
|
return null;
|
|
177
84
|
const account = {
|
|
178
85
|
billing: billing.billing,
|
|
179
|
-
...(billing.
|
|
86
|
+
...(billing.plan !== undefined ? { plan: billing.plan } : {}),
|
|
180
87
|
observedAt: now,
|
|
181
88
|
windows: {},
|
|
182
89
|
};
|
|
183
|
-
|
|
90
|
+
try {
|
|
91
|
+
writeClaudeAccount(root, account);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
184
96
|
return account;
|
|
185
97
|
}
|
|
186
98
|
/** The billing the environment implies, or null when it cannot be decided: an API key wins, a
|
|
@@ -197,12 +109,12 @@ function seedBilling(config) {
|
|
|
197
109
|
return null;
|
|
198
110
|
const subscription = claudeAuthUsesSubscription(auth);
|
|
199
111
|
return subscription
|
|
200
|
-
? { billing: "subscription", ...(auth.subscriptionType !== undefined ? {
|
|
112
|
+
? { billing: "subscription", ...(auth.subscriptionType !== undefined ? { plan: auth.subscriptionType } : {}) }
|
|
201
113
|
: { billing: "api" };
|
|
202
114
|
}
|
|
203
115
|
/** Parse `claude auth status` stdout, returning null when it is not a JSON object or does not
|
|
204
116
|
* report a boolean `loggedIn` — the one home of the read, shared by this module's
|
|
205
|
-
* probeAuthStatus and doctor's claudeAuthProbe (doctor-
|
|
117
|
+
* probeAuthStatus and doctor's claudeAuthProbe (doctor-claude-checks.ts). An optional string
|
|
206
118
|
* field reads as absent when the reply names something else. */
|
|
207
119
|
export function parseClaudeAuthStatus(stdout) {
|
|
208
120
|
const doc = parseJsonObject(stdout);
|
|
@@ -216,7 +128,7 @@ export function parseClaudeAuthStatus(stdout) {
|
|
|
216
128
|
}
|
|
217
129
|
/** True when an auth-status reply names claude.ai subscription billing — a subscription type
|
|
218
130
|
* (its presence alone signals one) or the explicit claude.ai auth method — rather than API
|
|
219
|
-
* billing. Shared by seedBilling below and doctor's checkClaudeAuth (doctor-
|
|
131
|
+
* billing. Shared by seedBilling below and doctor's checkClaudeAuth (doctor-claude-checks.ts),
|
|
220
132
|
* which must not disagree about which seams are subscription-billed. */
|
|
221
133
|
export function claudeAuthUsesSubscription(auth) {
|
|
222
134
|
return auth.subscriptionType !== undefined || auth.authMethod === "claude.ai";
|
|
@@ -225,7 +137,15 @@ export function claudeAuthUsesSubscription(auth) {
|
|
|
225
137
|
* short deadline. Null when the probe could not run or its reply was unreadable. */
|
|
226
138
|
function probeAuthStatus(config) {
|
|
227
139
|
const resolved = claudeBackend.resolveBin(config);
|
|
228
|
-
const
|
|
140
|
+
const configDir = resolvedBackendConfig(config, "claude").configDir;
|
|
141
|
+
const env = claudeEnv(process.env, {
|
|
142
|
+
quietTimeoutSeconds: 0,
|
|
143
|
+
// The seed probes auth under the same env a real run gets, so a team config dir's login
|
|
144
|
+
// is what it reports (plans/notes-command.md "Keeping funes out of team runs").
|
|
145
|
+
...(configDir !== undefined
|
|
146
|
+
? { configDir: claudeConfigDir(process.env, os.homedir(), configDir) }
|
|
147
|
+
: {}),
|
|
148
|
+
});
|
|
229
149
|
try {
|
|
230
150
|
const result = spawnSync(resolved.bin, ["auth", "status"], {
|
|
231
151
|
env,
|