tumwater 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +42 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +4 -3
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +246 -55
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +85 -274
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +175 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +139 -32
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +9 -11
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget.js +30 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +1 -2
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +118 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +150 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +308 -590
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
- package/dist/src/cli/completion.js +149 -22
- package/dist/src/cli/config-commands.js +41 -18
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +189 -73
- package/dist/src/cli/log-commands.js +148 -82
- package/dist/src/cli/question-commands.js +205 -10
- package/dist/src/cli.js +106 -43
- package/dist/src/collections.js +26 -10
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +13 -1
- package/dist/src/config/config-fallbacks.js +93 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +141 -199
- package/dist/src/config/config-views.js +127 -53
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +131 -264
- package/dist/src/config/config.js +48 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +100 -496
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +52 -579
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +82 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +236 -402
- package/dist/src/doctor/doctor-notify.js +3 -4
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor.js +121 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +7 -11
- package/dist/src/events/event-format.js +181 -86
- package/dist/src/events/event-labels.js +12 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +39 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +60 -24
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +56 -56
- package/dist/src/failure/failure-render.js +31 -23
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +99 -35
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +177 -84
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +108 -58
- package/dist/src/gates/maintenance-quota.js +10 -13
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +77 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +36 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +42 -18
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +8 -13
- package/dist/src/gui/gui-config-commands.js +94 -72
- package/dist/src/gui/gui-endpoint-commands.js +4 -3
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +108 -13
- package/dist/src/gui/gui-prompt-commands.js +11 -14
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +11 -4
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +193 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +25 -9
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +81 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +150 -11
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/merge/merge-queue-render.js +49 -11
- package/dist/src/merge/merge-queue.js +31 -15
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +33 -11
- package/dist/src/merge/merge-to-main.js +86 -27
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +12 -10
- package/dist/src/notes-cli.js +30 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
- package/dist/src/orchestrator/orchestrator.js +128 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +116 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +18 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +121 -211
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +129 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +161 -77
- package/dist/src/report/report-fold.js +129 -52
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +133 -64
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +100 -1
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +4 -5
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +155 -360
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +69 -12
- package/dist/src/roles/role-guidance.js +55 -20
- package/dist/src/roles/role-labels.js +1 -2
- package/dist/src/roles/role-render.js +67 -50
- package/dist/src/roles/role-view.js +59 -47
- package/dist/src/roles/roles.js +31 -31
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +9 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/{loop → runner}/leftover.js +80 -25
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +43 -16
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +28 -28
- package/dist/src/status/status-roles.js +39 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +5 -6
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +63 -17
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +57 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +67 -13
- package/dist/src/ui/badges.js +65 -37
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +32 -26
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +126 -285
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +434 -67
- package/dist/src/ui/gui/gui-client-labels.js +1 -2
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +55 -51
- package/dist/src/ui/gui/gui-client-pending.js +227 -23
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +87 -427
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +56 -4
- package/dist/src/ui/gui/gui-client-team.js +25 -465
- package/dist/src/ui/gui/gui-client.js +27 -25
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +9 -7
- package/dist/src/ui/gui/gui-styles.js +34 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +136 -49
- package/dist/src/ui/status-render.js +101 -82
- package/dist/src/ui/team-alerts.js +248 -57
- package/dist/src/ui/tone.js +5 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +51 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +3 -5
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +10 -12
- package/dist/src/ui/tui/tui-editor-budget.js +35 -31
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-entry.js +36 -47
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +11 -9
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +3 -5
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
- package/dist/src/ui/tui/tui-editor-retire.js +4 -6
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +60 -34
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +35 -2
- package/dist/src/ui/tui/tui-frame.js +56 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +24 -9
- package/dist/src/ui/tui/tui-pane-activity.js +14 -11
- package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +58 -8
- package/dist/src/ui/tui/tui-pane-history.js +402 -82
- package/dist/src/ui/tui/tui-pane-pending.js +308 -13
- package/dist/src/ui/tui/tui-pane-queued.js +71 -55
- package/dist/src/ui/tui/tui-pane-report.js +46 -14
- package/dist/src/ui/tui/tui-pane-session.js +25 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +80 -23
- package/dist/src/ui/tui/tui-panes.js +100 -30
- package/dist/src/ui/tui/tui.js +14 -14
- package/dist/src/verdict/fix-claim.js +82 -32
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +42 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
package/README.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# tumwater
|
|
2
2
|
|
|
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
|
+
|
|
3
11
|
tumwater is an opinionated autonomous development team, built on
|
|
4
12
|
[pi](https://github.com/badlogic/pi-mono). You write a short project brief, and a team of
|
|
5
13
|
roles (feature, bugfix, planning, tests, cleanup, docs, and more) builds the project
|
|
@@ -27,13 +35,16 @@ claude backend: it drives the real CLI through a throwaway repo (a few cents of
|
|
|
27
35
|
spend on a subscription) and is not part of `npm test`, which stays green without claude. `npm run
|
|
28
36
|
smoke:opencode` is the same live check for the opencode backend, driving the real `opencode` CLI
|
|
29
37
|
against its free DeepSeek tier; it needs an OpenCode V2 install (`brew install opencode`) and is
|
|
30
|
-
also outside `npm test`.
|
|
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`.
|
|
31
41
|
|
|
32
42
|
## Status
|
|
33
43
|
|
|
34
44
|
<!-- tumwater:status:start -->
|
|
35
|
-
**v0.
|
|
36
|
-
which files tumwater bugs from tumwater's own event log,
|
|
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`).
|
|
37
48
|
|
|
38
49
|
Open work: [PLANS.md](PLANS.md) (planned), [BUGS.md](BUGS.md) (open bugs),
|
|
39
50
|
[QUESTIONS.md](QUESTIONS.md) (open questions).
|
|
@@ -75,20 +86,22 @@ same list, and `tumwater completion bash|zsh|fish` prints shell completion. Sett
|
|
|
75
86
|
`tumwater.json` and edits apply live while the team runs; see [docs/config.md](docs/config.md).
|
|
76
87
|
|
|
77
88
|
**Backends:** any OpenAI-compatible model pi can reach works, chosen by `model` in `tumwater.json`
|
|
78
|
-
with a `fallback` that a failing role switches to. `backend
|
|
79
|
-
|
|
80
|
-
DeepSeek tier needs no login or API key
|
|
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
|
|
81
93
|
behavior, and how subscription runs are billed (as estimated spend, outside the daily cap) are in
|
|
82
94
|
[docs/backends.md](docs/backends.md).
|
|
83
95
|
|
|
84
|
-
**Mixing providers:**
|
|
85
|
-
|
|
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:
|
|
86
99
|
|
|
87
100
|
```json
|
|
88
101
|
{
|
|
89
102
|
"model": {
|
|
90
103
|
"default": "huggingface/deepseek-ai/DeepSeek-V4.1-Flash:deepinfra",
|
|
91
|
-
"strong": "claude-opus-5-5"
|
|
104
|
+
"strong": { "backend": "claude", "model": "claude-opus-5-5" }
|
|
92
105
|
},
|
|
93
106
|
"fallback": "omlx/Qwen3.8-27B-MLX-oQ4e-mtp"
|
|
94
107
|
}
|
|
@@ -97,10 +110,11 @@ under the default `backend: "pi"`, so one config can mix a Claude strong tier wi
|
|
|
97
110
|
The default tier runs on pi through Hugging Face, the strong model jobs (the director, the plan
|
|
98
111
|
role, the reviewer, and the conflict resolver) run on the claude CLI's included usage, and the fallback runs on pi through
|
|
99
112
|
oMLX — all concurrently, under each provider's cap (set it with `maxConcurrentPerProvider`;
|
|
100
|
-
`tumwater status` shows how many runs each key has going out of its cap).
|
|
101
|
-
|
|
102
|
-
and claude in one config
|
|
103
|
-
|
|
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).
|
|
104
118
|
|
|
105
119
|
For how the roles, review gate, scheduling, and updates work, see
|
|
106
120
|
[docs/how-it-works.md](docs/how-it-works.md). For a measured comparison of tumwater's own
|
package/dist/build-info.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/** Appending a backend's configured passthrough args to an already-built argv. Shared by the
|
|
3
|
-
* claude and
|
|
4
|
-
* handling is otherwise identical, so the
|
|
5
|
-
* rule and the same message shape. A passthrough arg equal to a flag the
|
|
6
|
-
* is appended AFTER the harness's own copy, and
|
|
7
|
-
* would silently override the model, the session choice, or the isolation
|
|
8
|
-
* arg is rejected here with the offending arg named. `moduleName` names the
|
|
9
|
-
* in the error (e.g. `claudeArgs` or `
|
|
10
|
-
* HARNESS_*_FLAGS set. */
|
|
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. */
|
|
11
10
|
export function appendPassthroughArgs(args, passthrough, reserved, moduleName) {
|
|
12
11
|
for (const arg of passthrough ?? []) {
|
|
13
12
|
// Match the flag itself even when it carries its value as `--flag=value`.
|
|
@@ -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,8 +28,8 @@ 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
34
|
return backend.resumable(dir, opts, configDir);
|
|
33
35
|
return hasResumableSession(dir);
|
|
@@ -1,46 +1,53 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/**
|
|
3
2
|
* The per-backend capabilities that the config, prompt, watchdog, progress and doctor layers
|
|
4
3
|
* read (plans/opencode-backend.md "Groundwork: the claude-only branches become backend
|
|
5
4
|
* traits"). Pure — imports nothing from the runtime registry (agent-backend.ts) — so those
|
|
6
5
|
* layers can read a backend's traits without pulling the registry in. That is the same reason
|
|
7
|
-
* `
|
|
6
|
+
* `BACKEND_DEFAULT_MODELS` lives in src/config/model-selector.ts.
|
|
8
7
|
*
|
|
9
8
|
* An id with no entry reads as pi, because src/agent/run-agent.ts routes an unregistered id to
|
|
10
9
|
* the pi backend.
|
|
11
10
|
*/
|
|
12
11
|
import { HARNESS_CLAUDE_FLAGS } from "./claude/claude-args.js";
|
|
13
12
|
import { HARNESS_OPENCODE_FLAGS } from "./opencode/opencode-args.js";
|
|
13
|
+
import { HARNESS_CODEX_FLAGS } from "./codex/codex-args.js";
|
|
14
14
|
import { HARNESS_PI_FLAGS } from "../config/config-schema.js";
|
|
15
15
|
/** Whether the backend streams tool output. pi streams content-bearing tool updates, so its
|
|
16
|
-
* calls are classified per command; claude and
|
|
17
|
-
* signal and every call is judged by the buffered threshold (plans/agent-backends.md
|
|
18
|
-
* "Watchdogs and progress"). Only claude and
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
//
|
|
22
|
-
|
|
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";
|
|
23
24
|
}
|
|
24
25
|
/** Whether the backend's model jobs resolve through pi's models.json. True for pi only (and an
|
|
25
|
-
* unknown id, which reads as pi); a claude or
|
|
26
|
-
* definitions file. */
|
|
27
|
-
export function usesPiModels(
|
|
28
|
-
// Only claude and
|
|
29
|
-
// one) reads as pi, which does.
|
|
30
|
-
return
|
|
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";
|
|
31
32
|
}
|
|
32
33
|
/** The reason string config validation prints for a reserved `backends.<id>.args` flag, or
|
|
33
|
-
* undefined when the flag is not reserved for that backend. Wraps
|
|
34
|
-
*
|
|
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 —
|
|
35
37
|
* checkReservedArgs splits `--mode=x` on `=` before calling. */
|
|
36
|
-
export function reservedPassthroughFlag(
|
|
37
|
-
switch (
|
|
38
|
+
export function reservedPassthroughFlag(backend, flag) {
|
|
39
|
+
switch (backend) {
|
|
38
40
|
case "pi":
|
|
39
41
|
return HARNESS_PI_FLAGS.get(flag);
|
|
40
42
|
case "claude":
|
|
41
43
|
return HARNESS_CLAUDE_FLAGS.has(flag) ? "it is managed by tumwater" : undefined;
|
|
42
44
|
case "opencode":
|
|
43
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;
|
|
44
51
|
default:
|
|
45
52
|
// An unknown backend has no known harness argv to clash with, so nothing is reserved.
|
|
46
53
|
return undefined;
|
|
@@ -48,13 +55,23 @@ export function reservedPassthroughFlag(agent, flag) {
|
|
|
48
55
|
}
|
|
49
56
|
/** A seam's model as one readable token: `<provider>/<model>` for a pi seam (and any unknown
|
|
50
57
|
* id, which reads as pi), the bare model for claude — or "claude backend" when there is none —
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* they cannot drift. */
|
|
54
|
-
export function modelLabel(
|
|
55
|
-
if (
|
|
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")
|
|
56
63
|
return model ?? "claude backend";
|
|
57
|
-
if (
|
|
64
|
+
if (backend === "opencode")
|
|
58
65
|
return `opencode/${model ?? "?"}`;
|
|
66
|
+
if (backend === "codex")
|
|
67
|
+
return model ?? "codex default";
|
|
59
68
|
return `${provider ?? "?"}/${model ?? "?"}`;
|
|
60
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,217 +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
17
|
import os from "node:os";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { claudeAccountPath } from "../../paths.js";
|
|
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";
|
|
17
21
|
import { claudeBackend, claudeConfigDir } from "./claude-backend.js";
|
|
18
22
|
import { claudeEnv, claudeThirdPartyProvider } from "./claude-env.js";
|
|
19
|
-
/**
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* its type's fraction counts too, and comes back with `reserve: true`; a rejected or overage
|
|
35
|
-
* window outranks a reserve one, so the flag is set only when no window is used up for real.
|
|
36
|
-
* The budget gate's window move (pollBudgetGate) reads the default; the team hold
|
|
37
|
-
* (claudeWindowObservation) passes `includeOverage: false`, because overage never holds a seam.
|
|
38
|
-
* Only subscription billing produces one; a missing account reads null. */
|
|
39
|
-
export function claudeWindowUsedUp(account, now, includeOverage = true, reserve) {
|
|
40
|
-
if (account?.billing !== "subscription")
|
|
41
|
-
return null;
|
|
42
|
-
let furthest = null;
|
|
43
|
-
let reserved = null;
|
|
44
|
-
for (const [type, w] of Object.entries(account.windows)) {
|
|
45
|
-
if (typeof w.resetsAt !== "number" || w.resetsAt <= now)
|
|
46
|
-
continue;
|
|
47
|
-
const used = w.isUsingOverage === true ? includeOverage : w.status === "rejected";
|
|
48
|
-
if (used) {
|
|
49
|
-
if (furthest === null || w.resetsAt > furthest.resetsAt)
|
|
50
|
-
furthest = { type, resetsAt: w.resetsAt };
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
if (windowReserved(w, reserve?.[type]) && (reserved === null || w.resetsAt > reserved.resetsAt))
|
|
54
|
-
reserved = { type, resetsAt: w.resetsAt, reserve: true };
|
|
55
|
-
}
|
|
56
|
-
return furthest ?? reserved;
|
|
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. */
|
|
27
|
+
export function readClaudeAccount(root, now = Date.now()) {
|
|
28
|
+
return readAccount(root, "claude", now);
|
|
29
|
+
}
|
|
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. */
|
|
36
|
+
export function writeClaudeAccount(root, account) {
|
|
37
|
+
writeAccount(root, "claude", account);
|
|
57
38
|
}
|
|
58
|
-
/**
|
|
59
|
-
* reading
|
|
60
|
-
|
|
61
|
-
|
|
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). */
|
|
43
|
+
export function observeClaudeAccount(root, run, now = Date.now()) {
|
|
44
|
+
return observeAccount(root, "claude", run, now);
|
|
62
45
|
}
|
|
63
|
-
/**
|
|
64
|
-
*
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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. */
|
|
69
57
|
export function claudeWindowLabel(type) {
|
|
70
|
-
|
|
71
|
-
if (!m)
|
|
72
|
-
return type.replace(/_/g, " ");
|
|
73
|
-
const words = { one: "1", two: "2", three: "3", four: "4", five: "5", six: "6", seven: "7", eight: "8", nine: "9", ten: "10" };
|
|
74
|
-
const unit = m[2] === "hour" ? "h" : m[2] === "day" ? "d" : "w";
|
|
75
|
-
const word = m[1] ?? "";
|
|
76
|
-
return `${words[word] ?? word}${unit}`;
|
|
58
|
+
return windowLabel(type);
|
|
77
59
|
}
|
|
78
|
-
/** The fraction of a subscription usage window consumed
|
|
79
|
-
*
|
|
80
|
-
* the status badge and the doctor's hold reason so the two render a window's fill
|
|
81
|
-
* identically (the browser dashboard's own window line is embedded client-side and keeps its
|
|
82
|
-
* inline copy). */
|
|
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. */
|
|
83
62
|
export function claudeUtilizationPct(utilization) {
|
|
84
|
-
return
|
|
63
|
+
return utilizationPct(utilization);
|
|
85
64
|
}
|
|
86
|
-
/** The account's windows as a list, most constrained first
|
|
87
|
-
*
|
|
88
|
-
* the type name — so a tie always orders the same way. Empty when the account reported none. */
|
|
65
|
+
/** The account's windows as a list, most constrained first — subscription-account.ts's
|
|
66
|
+
* windowEntries, under its historical claude name. */
|
|
89
67
|
export function claudeWindowEntries(account) {
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const reset = (a.resetsAt ?? Number.POSITIVE_INFINITY) - (b.resetsAt ?? Number.POSITIVE_INFINITY);
|
|
100
|
-
if (reset !== 0)
|
|
101
|
-
return reset;
|
|
102
|
-
return a.type.localeCompare(b.type);
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
/** Read the persisted account state, or null when it is absent or unreadable. A file without a
|
|
106
|
-
* usable top-level `observedAt` reads as absent (the plan's torn-file rule), and a window whose
|
|
107
|
-
* `resetsAt` is at or before `now` reads back with `status: "reset"` — the reader, not every
|
|
108
|
-
* consumer, decides that the window no longer limits. */
|
|
109
|
-
export function readClaudeAccount(root, now = Date.now()) {
|
|
110
|
-
const doc = readJsonFile(claudeAccountPath(root));
|
|
111
|
-
if (doc === null)
|
|
112
|
-
return null;
|
|
113
|
-
const billing = doc.billing === "subscription" || doc.billing === "api" ? (doc.billing) : undefined;
|
|
114
|
-
const observedAt = finiteNumber(doc.observedAt, Number.NaN);
|
|
115
|
-
if (billing === undefined || !Number.isFinite(observedAt))
|
|
116
|
-
return null;
|
|
117
|
-
const windows = {};
|
|
118
|
-
if (isJsonObject(doc.windows)) {
|
|
119
|
-
for (const [type, value] of Object.entries(doc.windows)) {
|
|
120
|
-
const window = readWindow(value, observedAt, now);
|
|
121
|
-
if (window !== undefined)
|
|
122
|
-
setWindow(windows, type, window);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return {
|
|
126
|
-
billing,
|
|
127
|
-
...(typeof doc.subscriptionType === "string" ? { subscriptionType: doc.subscriptionType } : {}),
|
|
128
|
-
observedAt,
|
|
129
|
-
windows,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
/** One window entry from the file, healed: a non-object or a status-less entry is dropped, a
|
|
133
|
-
* missing per-window `observedAt` falls back to the file's, and a past `resetsAt` flips the
|
|
134
|
-
* status to `reset` and drops the overage flags. */
|
|
135
|
-
function readWindow(value, fileObservedAt, now) {
|
|
136
|
-
if (!isJsonObject(value) || typeof value.status !== "string")
|
|
137
|
-
return undefined;
|
|
138
|
-
const observedAt = finiteNumber(value.observedAt, fileObservedAt);
|
|
139
|
-
const resetsAt = finiteNumber(value.resetsAt, Number.NaN);
|
|
140
|
-
const resets = Number.isFinite(resetsAt);
|
|
141
|
-
// utilization heals like the two stamps above: JSON's `1e999` parses to Infinity, and a
|
|
142
|
-
// non-finite fraction would render as `Infinity%` in the badges and poison the
|
|
143
|
-
// claudeWindowEntries comparator (Infinity - Infinity is NaN).
|
|
144
|
-
const utilization = finiteNumber(value.utilization, undefined);
|
|
145
|
-
const reset = resets && resetsAt <= now;
|
|
146
|
-
const window = {
|
|
147
|
-
status: reset ? "reset" : value.status,
|
|
148
|
-
...(resets ? { resetsAt } : {}),
|
|
149
|
-
...(utilization !== undefined ? { utilization } : {}),
|
|
150
|
-
// a reset window is no longer in overage: dropping both keeps every reader in agreement
|
|
151
|
-
...(!reset && typeof value.overageStatus === "string" ? { overageStatus: value.overageStatus } : {}),
|
|
152
|
-
...(!reset && value.isUsingOverage === true ? { isUsingOverage: true } : {}),
|
|
153
|
-
observedAt,
|
|
154
|
-
};
|
|
155
|
-
return window;
|
|
156
|
-
}
|
|
157
|
-
/** Store one window under its `rateLimitType`. A plain `windows[type] = window` loses a type of
|
|
158
|
-
* `__proto__`: the assignment hits Object.prototype's setter and rewrites the map's prototype
|
|
159
|
-
* instead of adding an own entry, so the window vanishes from every `Object.entries`/`values`
|
|
160
|
-
* reader (the display, the hold, and accountUsesOverage). Defining the own property keeps even
|
|
161
|
-
* that key — the same inherited-key guard claude-stream's TOOL_NAMES lookup uses. */
|
|
162
|
-
function setWindow(windows, type, window) {
|
|
163
|
-
Object.defineProperty(windows, type, { value: window, enumerable: true, writable: true, configurable: true });
|
|
164
|
-
}
|
|
165
|
-
/** Overwrite the account file with `account`, creating the state directory. The orchestrator's
|
|
166
|
-
* one writer; a failure to write is the caller's to handle (best-effort at the tick boundary).
|
|
167
|
-
* Written through writeJsonAtomic (tmp + rename): a crash or ENOSPC mid-write leaves the
|
|
168
|
-
* previous file whole, not a torn one the reader heals to "no subscription" — which would
|
|
169
|
-
* silently drop the billing answer and the window hold until the next run re-observed them. */
|
|
170
|
-
export function writeClaudeAccount(root, account) {
|
|
171
|
-
writeJsonAtomic(claudeAccountPath(root), account, true);
|
|
172
|
-
}
|
|
173
|
-
/** Fold one run's billing and rate-window readings into the account file: the run's `billing`
|
|
174
|
-
* overwrites the stored answer, and each reported window replaces the stored reading for its
|
|
175
|
-
* `type`, stamped with `observedAt`. A run that reported neither billing nor windows leaves the
|
|
176
|
-
* file untouched (and returns whatever was already there). */
|
|
177
|
-
export function observeClaudeAccount(root, run, now = Date.now()) {
|
|
178
|
-
const existing = readClaudeAccount(root, now);
|
|
179
|
-
const billing = run.billing ?? existing?.billing;
|
|
180
|
-
const reported = run.rateWindows ?? [];
|
|
181
|
-
if (billing === undefined || (run.billing === undefined && reported.length === 0))
|
|
182
|
-
return existing;
|
|
183
|
-
const windows = { ...(existing?.windows ?? {}) };
|
|
184
|
-
for (const w of reported)
|
|
185
|
-
setWindow(windows, w.type, windowFromRun(w, now));
|
|
186
|
-
const account = {
|
|
187
|
-
billing,
|
|
188
|
-
...(existing?.subscriptionType !== undefined ? { subscriptionType: existing.subscriptionType } : {}),
|
|
189
|
-
observedAt: now,
|
|
190
|
-
windows,
|
|
191
|
-
};
|
|
192
|
-
writeClaudeAccount(root, account);
|
|
193
|
-
return account;
|
|
194
|
-
}
|
|
195
|
-
/** One run's rate-window reading in the file's shape: unix-seconds `resetsAt` becomes epoch ms,
|
|
196
|
-
* and absent optional fields stay absent. */
|
|
197
|
-
function windowFromRun(w, now) {
|
|
198
|
-
// The write choke point heals utilization like readWindow does, so a non-finite value
|
|
199
|
-
// handed straight to observeClaudeAccount can never re-enter the file through a save.
|
|
200
|
-
const utilization = finiteNumber(w.utilization, undefined);
|
|
201
|
-
return {
|
|
202
|
-
status: w.status,
|
|
203
|
-
...(typeof w.resetsAt === "number" ? { resetsAt: w.resetsAt * 1000 } : {}),
|
|
204
|
-
...(utilization !== undefined ? { utilization } : {}),
|
|
205
|
-
...(typeof w.overageStatus === "string" ? { overageStatus: w.overageStatus } : {}),
|
|
206
|
-
...(w.isUsingOverage === true ? { isUsingOverage: true } : {}),
|
|
207
|
-
observedAt: now,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
/** Seed the account file before the first run (plans/agent-backends.md §7): `ANTHROPIC_API_KEY`
|
|
211
|
-
* set means claude prefers the key, so billing is API; otherwise `claude auth status` decides —
|
|
212
|
-
* a claude.ai OAuth login is a subscription, any other login is API. An existing file is left
|
|
213
|
-
* alone, and a probe that cannot run or is not logged in leaves the file absent
|
|
214
|
-
* (readClaudeAccount then reads "no subscription"). Returns the account written, or null. */
|
|
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. */
|
|
215
77
|
export function seedClaudeAccount(root, config, now = Date.now()) {
|
|
216
78
|
const existing = readClaudeAccount(root, now);
|
|
217
79
|
if (existing !== null)
|
|
@@ -221,11 +83,16 @@ export function seedClaudeAccount(root, config, now = Date.now()) {
|
|
|
221
83
|
return null;
|
|
222
84
|
const account = {
|
|
223
85
|
billing: billing.billing,
|
|
224
|
-
...(billing.
|
|
86
|
+
...(billing.plan !== undefined ? { plan: billing.plan } : {}),
|
|
225
87
|
observedAt: now,
|
|
226
88
|
windows: {},
|
|
227
89
|
};
|
|
228
|
-
|
|
90
|
+
try {
|
|
91
|
+
writeClaudeAccount(root, account);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
229
96
|
return account;
|
|
230
97
|
}
|
|
231
98
|
/** The billing the environment implies, or null when it cannot be decided: an API key wins, a
|
|
@@ -242,12 +109,12 @@ function seedBilling(config) {
|
|
|
242
109
|
return null;
|
|
243
110
|
const subscription = claudeAuthUsesSubscription(auth);
|
|
244
111
|
return subscription
|
|
245
|
-
? { billing: "subscription", ...(auth.subscriptionType !== undefined ? {
|
|
112
|
+
? { billing: "subscription", ...(auth.subscriptionType !== undefined ? { plan: auth.subscriptionType } : {}) }
|
|
246
113
|
: { billing: "api" };
|
|
247
114
|
}
|
|
248
115
|
/** Parse `claude auth status` stdout, returning null when it is not a JSON object or does not
|
|
249
116
|
* report a boolean `loggedIn` — the one home of the read, shared by this module's
|
|
250
|
-
* probeAuthStatus and doctor's claudeAuthProbe (doctor-
|
|
117
|
+
* probeAuthStatus and doctor's claudeAuthProbe (doctor-claude-checks.ts). An optional string
|
|
251
118
|
* field reads as absent when the reply names something else. */
|
|
252
119
|
export function parseClaudeAuthStatus(stdout) {
|
|
253
120
|
const doc = parseJsonObject(stdout);
|
|
@@ -261,7 +128,7 @@ export function parseClaudeAuthStatus(stdout) {
|
|
|
261
128
|
}
|
|
262
129
|
/** True when an auth-status reply names claude.ai subscription billing — a subscription type
|
|
263
130
|
* (its presence alone signals one) or the explicit claude.ai auth method — rather than API
|
|
264
|
-
* billing. Shared by seedBilling below and doctor's checkClaudeAuth (doctor-
|
|
131
|
+
* billing. Shared by seedBilling below and doctor's checkClaudeAuth (doctor-claude-checks.ts),
|
|
265
132
|
* which must not disagree about which seams are subscription-billed. */
|
|
266
133
|
export function claudeAuthUsesSubscription(auth) {
|
|
267
134
|
return auth.subscriptionType !== undefined || auth.authMethod === "claude.ai";
|
|
@@ -270,7 +137,7 @@ export function claudeAuthUsesSubscription(auth) {
|
|
|
270
137
|
* short deadline. Null when the probe could not run or its reply was unreadable. */
|
|
271
138
|
function probeAuthStatus(config) {
|
|
272
139
|
const resolved = claudeBackend.resolveBin(config);
|
|
273
|
-
const configDir =
|
|
140
|
+
const configDir = resolvedBackendConfig(config, "claude").configDir;
|
|
274
141
|
const env = claudeEnv(process.env, {
|
|
275
142
|
quietTimeoutSeconds: 0,
|
|
276
143
|
// The seed probes auth under the same env a real run gets, so a team config dir's login
|