tumwater 0.1.2 → 0.2.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 +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- 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 +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- 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 +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
package/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# tumwater
|
|
2
2
|
|
|
3
|
-
tumwater is an opinionated autonomous development
|
|
4
|
-
[pi](https://github.com/badlogic/pi-mono). You write a short project brief, and a
|
|
5
|
-
|
|
3
|
+
tumwater is an opinionated autonomous development team, built on
|
|
4
|
+
[pi](https://github.com/badlogic/pi-mono). You write a short project brief, and a team of
|
|
5
|
+
roles (feature, bugfix, planning, tests, cleanup, docs, and more) builds the project
|
|
6
6
|
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
|
|
7
|
+
of always-on specialists do the rest: each role owns one concern, merges one change per run,
|
|
8
8
|
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.
|
|
9
|
+
local git repo, and no remote is ever touched. The words used here (team, role, run, merge) are
|
|
10
|
+
defined in [docs/glossary.md](docs/glossary.md).
|
|
10
11
|
|
|
11
|
-

|
|
12
13
|
|
|
13
14
|
## Install
|
|
14
15
|
|
|
@@ -23,13 +24,16 @@ instead: `npm install && npm run build && npm link`.
|
|
|
23
24
|
|
|
24
25
|
With `claude` installed and logged in, `npm run smoke:claude` is an opt-in live check of the
|
|
25
26
|
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.
|
|
27
|
+
spend on a subscription) and is not part of `npm test`, which stays green without claude. `npm run
|
|
28
|
+
smoke:opencode` is the same live check for the opencode backend, driving the real `opencode` CLI
|
|
29
|
+
against its free DeepSeek tier; it needs an OpenCode V2 install (`brew install opencode`) and is
|
|
30
|
+
also outside `npm test`.
|
|
27
31
|
|
|
28
32
|
## Status
|
|
29
33
|
|
|
30
34
|
<!-- tumwater:status:start -->
|
|
31
|
-
**v0.1.
|
|
32
|
-
which files
|
|
35
|
+
**v0.1.4**: working tumwater. The director and 14 of the 15 roles are enabled by default; `selfcheck`,
|
|
36
|
+
which files tumwater bugs from tumwater's own event log, is opt-in (`roles.selfcheck.enabled`).
|
|
33
37
|
|
|
34
38
|
Open work: [PLANS.md](PLANS.md) (planned), [BUGS.md](BUGS.md) (open bugs),
|
|
35
39
|
[QUESTIONS.md](QUESTIONS.md) (open questions).
|
|
@@ -38,112 +42,74 @@ Open work: [PLANS.md](PLANS.md) (planned), [BUGS.md](BUGS.md) (open bugs),
|
|
|
38
42
|
## Usage
|
|
39
43
|
|
|
40
44
|
```bash
|
|
41
|
-
cd your-project
|
|
45
|
+
cd your-project # a new or existing directory
|
|
42
46
|
tumwater init "Build a tiny markdown-to-html converter CLI in Python."
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# "bootstrap": {"untilPlansDone": 5}, holding the maintenance
|
|
50
|
-
# loops until 5 plans are done; remove "bootstrap" from
|
|
51
|
-
# tumwater.json to end that early
|
|
52
|
-
tumwater run # start the loops (Ctrl+C to stop)
|
|
53
|
-
tumwater run --gui # ... and serve the browser dashboard at http://127.0.0.1:7180 from the same process
|
|
54
|
-
tumwater run --for 2h # run for a bounded window (capped at 90d), then drain and exit like Ctrl+C would
|
|
55
|
-
tumwater run --once # one full round of ticks, drains its landings, and exits (for cron/CI);
|
|
56
|
-
# add --role <id> to scope the round to one loop
|
|
47
|
+
# --template, --file, and --adopt pick a starting point (see docs/cli.md)
|
|
48
|
+
tumwater start # start the team (Ctrl+C to stop)
|
|
49
|
+
tumwater start --gui # ... and serve the browser dashboard at http://127.0.0.1:7180
|
|
50
|
+
tumwater start --tui # ... and attach the terminal TUI in the same window
|
|
51
|
+
tumwater start --for 2h # run for a bounded window, then finish current work, summarize, and exit
|
|
52
|
+
tumwater start --once # one round of runs for cron/CI; exits 3 if a run failed
|
|
57
53
|
```
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
[
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
(`maxDailyCostUsdPerRole.feature 1.5`, `roles.qa.model x`) merge one entry into the existing
|
|
111
|
-
map or role entry, while bare keys replace the whole value. `roles` always merges the built-in
|
|
112
|
-
catalog per entry at load, so a bare `config set roles` replaces the file's section but cannot
|
|
113
|
-
disable a role by omission — disable one with `tumwater config set roles.<id>.enabled false`.
|
|
114
|
-
|
|
115
|
-
**Backends:** any OpenAI-compatible model pi can reach works; set `model` in `tumwater.json` to
|
|
116
|
-
one `provider/id[:thinking]` selector (plus `fallback` to a free selector), or a map of tiers
|
|
117
|
-
`small`/`default`/`strong`. A role whose primary keeps failing with provider-class errors
|
|
118
|
-
(three consecutive 429s or backend failures) runs its next ticks on its tier's resolved
|
|
119
|
-
`fallback` pair and probes the primary once the 5-minute cooldown elapses, returning to it
|
|
120
|
-
when a probe answers; `tumwater status`, the TUI, the dashboard, and `tumwater role <id>` name
|
|
121
|
-
the off-model episode. Set `agent` to run bare selectors and the no-model default on another
|
|
122
|
-
backend — `claude` runs on Claude Code, with per-tier defaults (`claude-sonnet-5-5` and
|
|
123
|
-
friends) and per-backend `agents.claude.bin`/`agents.claude.args` overrides; a selector whose
|
|
124
|
-
provider names a backend id (`claude/claude-opus-5-5`) always routes there. A claude run's
|
|
125
|
-
cost comes from the CLI's own price table. A run paid by a claude.ai subscription is exempt
|
|
126
|
-
from the daily dollar cap: its API-equivalent cost is tracked as notional spend and shown on
|
|
127
|
-
its own line in `tumwater report`, never in the cap; a run billed to an API key — or one that
|
|
128
|
-
saw `isUsingOverage` — counts against the cap like a priced pi model. A subscription claude
|
|
129
|
-
model with no overage also counts as free for the budget badge and fallback decisions, so at
|
|
130
|
-
the cap it stays put while priced pi seams move to their fallback. A rejected claude.ai
|
|
131
|
-
subscription usage window holds subscription claude seams until it resets (a first
|
|
132
|
-
`allowed_warning` is logged too); `tumwater status`, the TUI, the dashboard, and
|
|
133
|
-
`tumwater role <id>` show the window in place of the daily spend on a subscription-only fleet
|
|
134
|
-
(or beside it on a mixed one, where the daily spend covers priced seams only), and a run cut off
|
|
135
|
-
by a rejected window keeps its session and worktree edits and resumes once the window resets.
|
|
136
|
-
See
|
|
137
|
-
[docs/backends.md](docs/backends.md) for requirements and a worked setup.
|
|
138
|
-
|
|
139
|
-
For how the loops, review gate, scheduling, and self-redeploy work, see
|
|
55
|
+
A fresh or empty project is seeded with `"bootstrap": {"untilPlansDone": 5}`, which holds the
|
|
56
|
+
maintenance roles until 5 plans are done; remove it from `tumwater.json` to end that early.
|
|
57
|
+
|
|
58
|
+
From another terminal, `tumwater tui` or the browser dashboard watches the team, `tumwater status`
|
|
59
|
+
and `tumwater logs -f` check it, `tumwater brief --file <path>` replaces the brief,
|
|
60
|
+
`tumwater prompt "<text>"` sends a message to the director's inbox, `tumwater plan` and
|
|
61
|
+
`tumwater bug` file backlog entries, `tumwater revert <sha>` undoes one merged change on main,
|
|
62
|
+
and `tumwater pause`, `resume`, `wake`, and `stop` control the roles. `tumwater doctor` audits the team and `tumwater report` shows usage and cost. `gui
|
|
63
|
+
--all-interfaces` exposes the dashboard, and with it the director prompt, to your whole network,
|
|
64
|
+
so pair it with `--token <secret>`.
|
|
65
|
+
|
|
66
|
+
`tumwater prompt --attach <image>` gives a role an image to read at its next run. When a role's
|
|
67
|
+
model cannot read images, the command refuses the attachment up front (naming the fix) unless
|
|
68
|
+
the role reroutes a blind read to a vision model, in which case it says where the message will
|
|
69
|
+
run; `tumwater doctor` probes each claimed image-input model once (cached 7 days, `--reprobe` to
|
|
70
|
+
force) and the dashboards and `tumwater role <id>` say which roles read images and where a blind
|
|
71
|
+
run reroutes.
|
|
72
|
+
|
|
73
|
+
Every command, flag, and TUI key is in [docs/cli.md](docs/cli.md); `tumwater help` prints the
|
|
74
|
+
same list, and `tumwater completion bash|zsh|fish` prints shell completion. Settings live in
|
|
75
|
+
`tumwater.json` and edits apply live while the team runs; see [docs/config.md](docs/config.md).
|
|
76
|
+
|
|
77
|
+
**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: "claude"` runs bare model ids on Claude
|
|
79
|
+
Code instead, and a model id that starts with `opencode/` runs on the OpenCode CLI — its free
|
|
80
|
+
DeepSeek tier needs no login or API key. Requirements, the fallback, free-ness, and usage-window
|
|
81
|
+
behavior, and how subscription runs are billed (as estimated spend, outside the daily cap) are in
|
|
82
|
+
[docs/backends.md](docs/backends.md).
|
|
83
|
+
|
|
84
|
+
**Mixing providers:** a bare model id that starts with `claude-` runs on the claude CLI even
|
|
85
|
+
under the default `backend: "pi"`, so one config can mix a Claude strong tier with pi providers:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"model": {
|
|
90
|
+
"default": "huggingface/deepseek-ai/DeepSeek-V4.1-Flash:deepinfra",
|
|
91
|
+
"strong": "claude-opus-5-5"
|
|
92
|
+
},
|
|
93
|
+
"fallback": "omlx/Qwen3.8-27B-MLX-oQ4e-mtp"
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The default tier runs on pi through Hugging Face, the strong model jobs (the director, the plan
|
|
98
|
+
role, the reviewer, and the conflict resolver) run on the claude CLI's included usage, and the fallback runs on pi through
|
|
99
|
+
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). A model id that starts
|
|
101
|
+
with `opencode/` routes to the OpenCode CLI under any backend, so its free tier can sit beside pi
|
|
102
|
+
and claude in one config. Prefix `anthropic/` (`anthropic/claude-…`)
|
|
103
|
+
to keep a Claude model on pi instead (billed per token). See [plans/agent-backends.md §3](plans/agent-backends.md).
|
|
104
|
+
|
|
105
|
+
For how the roles, review gate, scheduling, and updates work, see
|
|
140
106
|
[docs/how-it-works.md](docs/how-it-works.md). For a measured comparison of tumwater's own
|
|
141
107
|
code against human-written open source, see [docs/code-metrics.md](docs/code-metrics.md).
|
|
142
108
|
|
|
143
|
-
## Appendix:
|
|
109
|
+
## Appendix: the brief
|
|
144
110
|
|
|
145
|
-
The brief this repository was started from, kept for history.
|
|
146
|
-
between the markers below on every
|
|
111
|
+
The brief this repository was started from, kept for history. tumwater still reads the brief from
|
|
112
|
+
between the markers below on every run.
|
|
147
113
|
|
|
148
114
|
<!-- tumwater:prompt:start -->
|
|
149
115
|
Idea: agentic harness
|
package/dist/build-info.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
/** Appending a backend's configured passthrough args to an already-built argv. Shared by the
|
|
3
|
+
* claude and opencode argv builders (claude-args.ts and opencode-args.ts), whose passthrough
|
|
4
|
+
* handling is otherwise identical, so the two backends reject a reserved flag by the same
|
|
5
|
+
* rule and the same message shape. A passthrough arg equal to a flag the harness itself sets
|
|
6
|
+
* is appended AFTER the harness's own copy, and both backends' parsers are last-wins, so it
|
|
7
|
+
* would silently override the model, the session choice, or the isolation flags — each such
|
|
8
|
+
* arg is rejected here with the offending arg named. `moduleName` names the blocking backend
|
|
9
|
+
* in the error (e.g. `claudeArgs` or `opencodeArgs`); `reserved` is that backend's
|
|
10
|
+
* HARNESS_*_FLAGS set. */
|
|
11
|
+
export function appendPassthroughArgs(args, passthrough, reserved, moduleName) {
|
|
12
|
+
for (const arg of passthrough ?? []) {
|
|
13
|
+
// Match the flag itself even when it carries its value as `--flag=value`.
|
|
14
|
+
const flag = arg.split("=", 1)[0];
|
|
15
|
+
if (reserved.has(flag)) {
|
|
16
|
+
// Name the reserved flag (the arg may carry it as `--flag=value`, so `flag` here is the
|
|
17
|
+
// normalized token) and the fix, so the operator knows exactly which passthrough to drop
|
|
18
|
+
// rather than which of several harness-set flags the arg collided with.
|
|
19
|
+
throw new Error(`${moduleName}: passthrough arg ${JSON.stringify(arg)} repeats a flag tumwater sets (` +
|
|
20
|
+
`${flag} is reserved) — drop it from your passthrough args`);
|
|
21
|
+
}
|
|
22
|
+
args.push(arg);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -26,9 +26,9 @@ export function agentBackend(id) {
|
|
|
26
26
|
* "Sessions"), so a claude author or gate run resumes through its ledger id instead of being
|
|
27
27
|
* read as having no session. Falls back to the backend-neutral session-file check when the
|
|
28
28
|
* backend is unregistered or supplies no `resumable` hook. */
|
|
29
|
-
export function agentSessionResumable(dir, opts) {
|
|
29
|
+
export function agentSessionResumable(dir, opts, configDir) {
|
|
30
30
|
const backend = agentBackend(opts.agent);
|
|
31
31
|
if (backend?.resumable)
|
|
32
|
-
return backend.resumable(dir, opts);
|
|
32
|
+
return backend.resumable(dir, opts, configDir);
|
|
33
33
|
return hasResumableSession(dir);
|
|
34
34
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
/**
|
|
3
|
+
* The per-backend capabilities that the config, prompt, watchdog, progress and doctor layers
|
|
4
|
+
* read (plans/opencode-backend.md "Groundwork: the claude-only branches become backend
|
|
5
|
+
* traits"). Pure — imports nothing from the runtime registry (agent-backend.ts) — so those
|
|
6
|
+
* layers can read a backend's traits without pulling the registry in. That is the same reason
|
|
7
|
+
* `AGENT_DEFAULT_MODELS` lives in src/config/model-selector.ts.
|
|
8
|
+
*
|
|
9
|
+
* An id with no entry reads as pi, because src/agent/run-agent.ts routes an unregistered id to
|
|
10
|
+
* the pi backend.
|
|
11
|
+
*/
|
|
12
|
+
import { HARNESS_CLAUDE_FLAGS } from "./claude/claude-args.js";
|
|
13
|
+
import { HARNESS_OPENCODE_FLAGS } from "./opencode/opencode-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 and opencode send none, so silence carries no hang
|
|
17
|
+
* signal and every call is judged by the buffered threshold (plans/agent-backends.md
|
|
18
|
+
* "Watchdogs and progress"). Only claude and opencode are false; an unknown id reads as pi. */
|
|
19
|
+
export function streamsToolOutput(agent) {
|
|
20
|
+
// Only claude and opencode stream no tool output; every other id (including an unregistered
|
|
21
|
+
// one) reads as pi, which does stream.
|
|
22
|
+
return agent !== "claude" && agent !== "opencode";
|
|
23
|
+
}
|
|
24
|
+
/** 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 opencode job reads no pi pricing and needs no
|
|
26
|
+
* definitions file. */
|
|
27
|
+
export function usesPiModels(agent) {
|
|
28
|
+
// Only claude and opencode read no pi models.json; every other id (including an unregistered
|
|
29
|
+
// one) reads as pi, which does.
|
|
30
|
+
return agent !== "claude" && agent !== "opencode";
|
|
31
|
+
}
|
|
32
|
+
/** 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 claude's HARNESS_CLAUDE_FLAGS
|
|
34
|
+
* and pi's HARNESS_PI_FLAGS, keeping each backend's reason wording. The flag is the bare name —
|
|
35
|
+
* checkReservedArgs splits `--mode=x` on `=` before calling. */
|
|
36
|
+
export function reservedPassthroughFlag(agent, flag) {
|
|
37
|
+
switch (agent) {
|
|
38
|
+
case "pi":
|
|
39
|
+
return HARNESS_PI_FLAGS.get(flag);
|
|
40
|
+
case "claude":
|
|
41
|
+
return HARNESS_CLAUDE_FLAGS.has(flag) ? "it is managed by tumwater" : undefined;
|
|
42
|
+
case "opencode":
|
|
43
|
+
return HARNESS_OPENCODE_FLAGS.has(flag) ? "it is managed by tumwater" : undefined;
|
|
44
|
+
default:
|
|
45
|
+
// An unknown backend has no known harness argv to clash with, so nothing is reserved.
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** A seam's model as one readable token: `<provider>/<model>` for a pi seam (and any unknown
|
|
50
|
+
* id, which reads as pi), the bare model for claude — or "claude backend" when there is none —
|
|
51
|
+
* and `opencode/<model>` for opencode, whose ids all read under the opencode namespace. The
|
|
52
|
+
* one home of the label, shared by the doctor report and the image-input surface strings so
|
|
53
|
+
* they cannot drift. */
|
|
54
|
+
export function modelLabel(agent, provider, model) {
|
|
55
|
+
if (agent === "claude")
|
|
56
|
+
return model ?? "claude backend";
|
|
57
|
+
if (agent === "opencode")
|
|
58
|
+
return `opencode/${model ?? "?"}`;
|
|
59
|
+
return `${provider ?? "?"}/${model ?? "?"}`;
|
|
60
|
+
}
|
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
* milliseconds (the run parser reports unix seconds; observeClaudeAccount converts once here),
|
|
10
10
|
* so consumers compare it to `Date.now()` directly. */
|
|
11
11
|
import { spawnSync } from "node:child_process";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import { resolvedAgentConfig } from "../../config/config-views.js";
|
|
12
14
|
import { readJsonFile, writeJsonAtomic } from "../../files/json-files.js";
|
|
13
15
|
import { finiteNumber, isJsonObject, parseJsonObject } from "../../files/json-object.js";
|
|
14
16
|
import { claudeAccountPath } from "../../paths.js";
|
|
15
|
-
import { claudeBackend } from "./claude-backend.js";
|
|
17
|
+
import { claudeBackend, claudeConfigDir } from "./claude-backend.js";
|
|
16
18
|
import { claudeEnv, claudeThirdPartyProvider } from "./claude-env.js";
|
|
17
19
|
/** True when a failed run ended on a subscription usage window the account reported as
|
|
18
|
-
* `rejected` without overage — the signal the
|
|
20
|
+
* `rejected` without overage — the signal the team hold throttles on (plans/agent-backends.md
|
|
19
21
|
* §8). Such a run is a transient of the world, not a failure of the session: its session and
|
|
20
22
|
* worktree edits are kept and resumed after `resetsAt` (part 6e) rather than committed as
|
|
21
23
|
* partial work or reset. API-billed claude and overage are budget/fallback matters, not a
|
|
@@ -25,6 +27,39 @@ export function subscriptionWindowRejected(run) {
|
|
|
25
27
|
run.billing === "subscription" &&
|
|
26
28
|
(run.rateWindows ?? []).some((w) => w.status === "rejected" && w.isUsingOverage !== true));
|
|
27
29
|
}
|
|
30
|
+
/** The subscription account's used-up usage window, or null: a window reading `rejected` with no
|
|
31
|
+
* overage, or (with `includeOverage`, the default) a window whose current reading shows
|
|
32
|
+
* `isUsingOverage`, and a `resetsAt` still ahead of `now`; among several, the one that resets
|
|
33
|
+
* last. With a `reserve` (config `claudeWindowReserve`), a window whose utilization has reached
|
|
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;
|
|
57
|
+
}
|
|
58
|
+
/** Has this window's utilization reached its reserve fraction? False without a fraction or a
|
|
59
|
+
* reading. */
|
|
60
|
+
export function windowReserved(w, fraction) {
|
|
61
|
+
return fraction !== undefined && typeof w.utilization === "number" && w.utilization >= fraction;
|
|
62
|
+
}
|
|
28
63
|
/** Status severity for ordering: a rejected window limits, an allowed_warning nears its limit,
|
|
29
64
|
* every other reading (allowed/ok) rides at the bottom, and a reset one no longer limits. */
|
|
30
65
|
const WINDOW_RANK = { rejected: 3, allowed_warning: 2, reset: 0 };
|
|
@@ -40,6 +75,14 @@ export function claudeWindowLabel(type) {
|
|
|
40
75
|
const word = m[1] ?? "";
|
|
41
76
|
return `${words[word] ?? word}${unit}`;
|
|
42
77
|
}
|
|
78
|
+
/** The fraction of a subscription usage window consumed (0–1) as a whole-number percent: a
|
|
79
|
+
* `0.64` utilization reads `64`. The one home of the fraction→percent conversion, shared by
|
|
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). */
|
|
83
|
+
export function claudeUtilizationPct(utilization) {
|
|
84
|
+
return Math.round(utilization * 100);
|
|
85
|
+
}
|
|
43
86
|
/** The account's windows as a list, most constrained first: by status severity, then the
|
|
44
87
|
* reported utilization (a missing reading sorts below any number), then the earlier reset, then
|
|
45
88
|
* the type name — so a tie always orders the same way. Empty when the account reported none. */
|
|
@@ -67,7 +110,7 @@ export function readClaudeAccount(root, now = Date.now()) {
|
|
|
67
110
|
const doc = readJsonFile(claudeAccountPath(root));
|
|
68
111
|
if (doc === null)
|
|
69
112
|
return null;
|
|
70
|
-
const billing = doc.billing === "subscription" || doc.billing === "api" ? doc.billing : undefined;
|
|
113
|
+
const billing = doc.billing === "subscription" || doc.billing === "api" ? (doc.billing) : undefined;
|
|
71
114
|
const observedAt = finiteNumber(doc.observedAt, Number.NaN);
|
|
72
115
|
if (billing === undefined || !Number.isFinite(observedAt))
|
|
73
116
|
return null;
|
|
@@ -88,7 +131,7 @@ export function readClaudeAccount(root, now = Date.now()) {
|
|
|
88
131
|
}
|
|
89
132
|
/** One window entry from the file, healed: a non-object or a status-less entry is dropped, a
|
|
90
133
|
* missing per-window `observedAt` falls back to the file's, and a past `resetsAt` flips the
|
|
91
|
-
* status to `reset
|
|
134
|
+
* status to `reset` and drops the overage flags. */
|
|
92
135
|
function readWindow(value, fileObservedAt, now) {
|
|
93
136
|
if (!isJsonObject(value) || typeof value.status !== "string")
|
|
94
137
|
return undefined;
|
|
@@ -99,12 +142,14 @@ function readWindow(value, fileObservedAt, now) {
|
|
|
99
142
|
// non-finite fraction would render as `Infinity%` in the badges and poison the
|
|
100
143
|
// claudeWindowEntries comparator (Infinity - Infinity is NaN).
|
|
101
144
|
const utilization = finiteNumber(value.utilization, undefined);
|
|
145
|
+
const reset = resets && resetsAt <= now;
|
|
102
146
|
const window = {
|
|
103
|
-
status:
|
|
147
|
+
status: reset ? "reset" : value.status,
|
|
104
148
|
...(resets ? { resetsAt } : {}),
|
|
105
149
|
...(utilization !== undefined ? { utilization } : {}),
|
|
106
|
-
|
|
107
|
-
...(value.
|
|
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 } : {}),
|
|
108
153
|
observedAt,
|
|
109
154
|
};
|
|
110
155
|
return window;
|
|
@@ -225,7 +270,15 @@ export function claudeAuthUsesSubscription(auth) {
|
|
|
225
270
|
* short deadline. Null when the probe could not run or its reply was unreadable. */
|
|
226
271
|
function probeAuthStatus(config) {
|
|
227
272
|
const resolved = claudeBackend.resolveBin(config);
|
|
228
|
-
const
|
|
273
|
+
const configDir = resolvedAgentConfig(config, "claude").configDir;
|
|
274
|
+
const env = claudeEnv(process.env, {
|
|
275
|
+
quietTimeoutSeconds: 0,
|
|
276
|
+
// The seed probes auth under the same env a real run gets, so a team config dir's login
|
|
277
|
+
// is what it reports (plans/notes-command.md "Keeping funes out of team runs").
|
|
278
|
+
...(configDir !== undefined
|
|
279
|
+
? { configDir: claudeConfigDir(process.env, os.homedir(), configDir) }
|
|
280
|
+
: {}),
|
|
281
|
+
});
|
|
229
282
|
try {
|
|
230
283
|
const result = spawnSync(resolved.bin, ["auth", "status"], {
|
|
231
284
|
env,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
/** Building the claude command line for one tick (plans/agent-backends.md "The claude
|
|
2
3
|
* invocation") — pure argv construction plus the prompt-on-stdin pairing, with no subprocess,
|
|
3
4
|
* backend registration, or session ledger. Part 3c supplies the session decision (which
|
|
4
|
-
* `--session-id` or `--resume` to pass)
|
|
5
|
-
*
|
|
5
|
+
* `--session-id` or `--resume` to pass); this module turns those inputs into the invocation's
|
|
6
|
+
* argv.
|
|
6
7
|
*
|
|
7
8
|
* The environment is built separately (claude-env.ts) so each concern has one module.
|
|
8
9
|
*
|
|
@@ -15,12 +16,13 @@
|
|
|
15
16
|
* fallback the design holds in reserve is not needed. */
|
|
16
17
|
import fs from "node:fs";
|
|
17
18
|
import path from "node:path";
|
|
19
|
+
import { appendPassthroughArgs } from "../agent-args.js";
|
|
18
20
|
/** Flags the harness passes itself. A passthrough arg equal to one of these is appended AFTER
|
|
19
21
|
* the harness's own copy, and claude's argument parser is last-wins — so it would silently
|
|
20
22
|
* override the isolation flags, the session choice, or the model the harness resolved. Each is
|
|
21
23
|
* rejected by claudeArgs with the offending arg named. This is exactly the design's reserved
|
|
22
24
|
* set (plans/agent-backends.md "Config"); config validation (config-validation.ts) mirrors this
|
|
23
|
-
* set for `
|
|
25
|
+
* set for `backends.claude.args`, so a value that passes validation is never accepted by
|
|
24
26
|
* validation and then thrown here. */
|
|
25
27
|
export const HARNESS_CLAUDE_FLAGS = new Set([
|
|
26
28
|
"-p",
|
|
@@ -68,14 +70,18 @@ function shouldAppendAgents(cwd) {
|
|
|
68
70
|
* each would otherwise silently change what the harness asked for. */
|
|
69
71
|
export function claudeArgs(opts) {
|
|
70
72
|
if ((opts.sessionId === undefined) === (opts.resumeSessionId === undefined)) {
|
|
71
|
-
|
|
73
|
+
// Name which side fired so the invariant that would otherwise silently change the run's
|
|
74
|
+
// session is diagnosable at a glance: a conflicting pair (both set) differs from a missing
|
|
75
|
+
// choice (neither) and the two bugs have different fixes.
|
|
76
|
+
const state = opts.sessionId === undefined ? "neither is set" : "both are set";
|
|
77
|
+
throw new Error(`claudeArgs: pass exactly one of sessionId or resumeSessionId (${state})`);
|
|
72
78
|
}
|
|
73
79
|
const args = ["-p", "--output-format", "stream-json", "--verbose", "--model", opts.model];
|
|
74
80
|
if (opts.thinking !== undefined) {
|
|
75
81
|
// Own-property test, not a bare index: a level like `constructor` or `toString` would
|
|
76
82
|
// otherwise inherit a function from Object.prototype and bypass the rejection below.
|
|
77
83
|
if (!Object.hasOwn(EFFORT_BY_THINKING, opts.thinking)) {
|
|
78
|
-
throw new Error(`claudeArgs: unknown thinking level ${JSON.stringify(opts.thinking)}`);
|
|
84
|
+
throw new Error(`claudeArgs: unknown thinking level ${JSON.stringify(opts.thinking)} (known: ${Object.keys(EFFORT_BY_THINKING).join(", ")})`);
|
|
79
85
|
}
|
|
80
86
|
args.push("--effort", EFFORT_BY_THINKING[opts.thinking]);
|
|
81
87
|
}
|
|
@@ -84,21 +90,12 @@ export function claudeArgs(opts) {
|
|
|
84
90
|
else
|
|
85
91
|
args.push("--resume", opts.resumeSessionId);
|
|
86
92
|
args.push("--permission-mode", "bypassPermissions", "--tools", "Bash,Read,Edit,Write,Glob,Grep", "--setting-sources", "project", "--strict-mcp-config");
|
|
87
|
-
if (opts.mcpConfig !== undefined)
|
|
88
|
-
args.push("--mcp-config", opts.mcpConfig);
|
|
89
93
|
args.push("--disable-slash-commands", "--exclude-dynamic-system-prompt-sections");
|
|
90
94
|
if (shouldAppendAgents(opts.cwd))
|
|
91
95
|
args.push("--append-system-prompt-file", "AGENTS.md");
|
|
92
96
|
if (opts.maxBudgetUsd !== undefined && opts.maxBudgetUsd > 0) {
|
|
93
97
|
args.push("--max-budget-usd", String(opts.maxBudgetUsd));
|
|
94
98
|
}
|
|
95
|
-
|
|
96
|
-
// Match the flag itself even when it carries its value as `--flag=value`.
|
|
97
|
-
const flag = arg.split("=", 1)[0];
|
|
98
|
-
if (HARNESS_CLAUDE_FLAGS.has(flag)) {
|
|
99
|
-
throw new Error(`claudeArgs: passthrough arg ${JSON.stringify(arg)} repeats a harness flag`);
|
|
100
|
-
}
|
|
101
|
-
args.push(arg);
|
|
102
|
-
}
|
|
99
|
+
appendPassthroughArgs(args, opts.args, HARNESS_CLAUDE_FLAGS, "claudeArgs");
|
|
103
100
|
return { args, stdin: opts.prompt };
|
|
104
101
|
}
|