tumwater 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -28
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +23 -0
- package/dist/src/agent/agent-backend.js +9 -7
- package/dist/src/agent/backend-traits.js +77 -0
- package/dist/src/agent/claude/claude-account.js +81 -161
- package/dist/src/agent/claude/claude-args.js +19 -19
- package/dist/src/agent/claude/claude-backend.js +58 -48
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +11 -19
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +112 -0
- package/dist/src/agent/notes-shim.js +40 -0
- package/dist/src/agent/opencode/opencode-args.js +60 -0
- package/dist/src/agent/opencode/opencode-backend.js +258 -0
- package/dist/src/agent/opencode/opencode-catalog.js +210 -0
- package/dist/src/agent/opencode/opencode-config.js +193 -0
- package/dist/src/agent/opencode/opencode-env.js +35 -0
- package/dist/src/agent/opencode/opencode-session-id.js +36 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +16 -33
- package/dist/src/agent/run-agent.js +475 -381
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +142 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +487 -0
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +384 -0
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +354 -132
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +132 -22
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +89 -11
- package/dist/src/backlog/backlog-structure.js +271 -125
- package/dist/src/backlog/backlog-write.js +121 -471
- package/dist/src/backlog/backlog.js +129 -17
- package/dist/src/backlog/question-answer.js +345 -0
- package/dist/src/baseline/main-baseline.js +157 -101
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +164 -55
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +41 -0
- package/dist/src/{brief.js → brief/brief.js} +38 -33
- package/dist/src/budget/budget.js +55 -28
- package/dist/src/budget/fallback-breaker.js +31 -31
- package/dist/src/build/build-check-detect.js +25 -16
- package/dist/src/build/build-check-events.js +87 -22
- package/dist/src/build/build-check-report.js +66 -27
- package/dist/src/build/build-check-scoped.js +64 -68
- package/dist/src/build/build-check.js +17 -15
- package/dist/src/build/build-info.js +3 -3
- package/dist/src/build/build-stage.js +31 -35
- package/dist/src/build/dep-install.js +21 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +131 -37
- package/dist/src/change/change-render.js +56 -14
- package/dist/src/cli/brief-command.js +9 -8
- package/dist/src/cli/cli-args.js +133 -28
- package/dist/src/cli/cli-command-args.js +82 -21
- package/dist/src/cli/cli-editor.js +15 -2
- package/dist/src/cli/cli-flag-specs.js +180 -50
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +14 -11
- package/dist/src/cli/cli-query-commands.js +325 -224
- package/dist/src/cli/cli-repo-gate.js +12 -2
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/cli-start.js +453 -0
- package/dist/src/cli/completion.js +202 -17
- package/dist/src/cli/config-commands.js +51 -25
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +413 -157
- package/dist/src/cli/log-commands.js +234 -62
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +215 -231
- package/dist/src/cli.js +154 -49
- package/dist/src/collections.js +84 -18
- package/dist/src/concurrency/check-permit.js +35 -19
- package/dist/src/concurrency/lock.js +22 -17
- package/dist/src/concurrency/provider-lease.js +105 -0
- package/dist/src/concurrency/semaphore.js +344 -46
- package/dist/src/config/config-aliases.js +105 -0
- package/dist/src/config/config-editable-keys.js +16 -4
- package/dist/src/config/config-fallbacks.js +416 -0
- package/dist/src/config/config-field-checks.js +58 -19
- package/dist/src/config/config-keys.js +182 -0
- package/dist/src/config/config-live.js +113 -22
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +58 -39
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +201 -175
- package/dist/src/config/config-views.js +202 -346
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +219 -391
- package/dist/src/config/config.js +102 -81
- package/dist/src/config/model-selector.js +44 -18
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +191 -161
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +229 -305
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +181 -104
- package/dist/src/doctor/doctor-install.js +31 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +496 -270
- package/dist/src/doctor/doctor-notify.js +5 -7
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +189 -69
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +145 -50
- package/dist/src/doctor/image-probe.js +257 -0
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +60 -0
- package/dist/src/events/event-format.js +363 -155
- package/dist/src/events/event-labels.js +123 -0
- package/dist/src/events/event-legacy.js +164 -0
- package/dist/src/events/event-read.js +74 -38
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +83 -27
- package/dist/src/events/events.js +47 -26
- package/dist/src/events/notify.js +85 -35
- package/dist/src/failure/failure-cluster.js +23 -20
- package/dist/src/failure/failure-data.js +140 -92
- package/dist/src/failure/failure-render.js +97 -78
- package/dist/src/failure/failure-state-change.js +45 -33
- package/dist/src/failure/time-spend.js +166 -82
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +129 -41
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +45 -9
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +2 -2
- package/dist/src/gates/bootstrap-gates.js +8 -8
- package/dist/src/gates/budget-gates.js +361 -52
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +201 -103
- package/dist/src/gates/maintenance-quota.js +34 -37
- package/dist/src/gates/pause-gates.js +33 -15
- package/dist/src/gates/readiness.js +8 -3
- package/dist/src/gates/role-cap-gates.js +23 -28
- package/dist/src/gates/startup-gate.js +154 -48
- package/dist/src/gates/streak-gate.js +67 -41
- package/dist/src/git/commit-message.js +80 -48
- package/dist/src/git/git-diff.js +29 -23
- package/dist/src/git/git-run.js +87 -46
- package/dist/src/git/git.js +114 -92
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +18 -9
- package/dist/src/git/worktree-pool.js +150 -49
- package/dist/src/git/worktree-use.js +14 -20
- package/dist/src/git/worktree.js +108 -51
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +88 -21
- package/dist/src/gui/gui-backlog-commands.js +297 -0
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +9 -14
- package/dist/src/gui/gui-config-commands.js +462 -0
- package/dist/src/gui/gui-endpoint-commands.js +17 -716
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +13 -331
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +240 -0
- package/dist/src/gui/gui-prompt-commands.js +231 -0
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +33 -9
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +166 -203
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +96 -0
- package/dist/src/history/history.js +206 -103
- package/dist/src/inbox/inbox-attachments.js +94 -39
- package/dist/src/inbox/inbox-cancel.js +16 -15
- package/dist/src/inbox/inbox-edit.js +10 -10
- package/dist/src/inbox/inbox-submit.js +54 -14
- package/dist/src/inbox/inbox-top.js +99 -0
- package/dist/src/inbox/inbox.js +49 -44
- package/dist/src/inbox/pending-prompt.js +27 -27
- package/dist/src/inbox/prompt-commands.js +154 -100
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +15 -14
- package/dist/src/init/init.js +35 -29
- package/dist/src/merge/backlog-conflicts.js +335 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +100 -93
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +209 -0
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +183 -110
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +6 -5
- package/dist/src/merge/merge-drain.js +351 -0
- package/dist/src/merge/merge-git.js +403 -0
- package/dist/src/merge/merge-pipeline.js +111 -0
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +113 -0
- package/dist/src/merge/merge-queue.js +224 -0
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +385 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +97 -71
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +151 -82
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +214 -0
- package/dist/src/notes-cli.js +112 -0
- package/dist/src/operator/operator-commands.js +268 -89
- package/dist/src/operator/operator-intent.js +130 -82
- package/dist/src/operator/operator-requests.js +82 -87
- package/dist/src/operator/retire.js +49 -31
- package/dist/src/operator/revert.js +179 -0
- package/dist/src/orchestrator/orchestrator-launch.js +116 -48
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +78 -348
- package/dist/src/orchestrator/orchestrator.js +173 -361
- package/dist/src/orchestrator/retention.js +66 -15
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +52 -0
- package/dist/src/orchestrator/startup-migrations.js +44 -0
- package/dist/src/paths.js +228 -75
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +30 -5
- package/dist/src/pi/pi-backend.js +19 -27
- package/dist/src/pi/pi-bin.js +64 -67
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +163 -42
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +137 -113
- package/dist/src/pi/pi-watchdogs.js +20 -20
- package/dist/src/pi-extension/bounded-output.js +25 -8
- package/dist/src/pi-extension/context-budget.js +5 -3
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +36 -0
- package/dist/src/pi-extension/full-output.js +5 -12
- package/dist/src/pi-extension/strip-thinking.js +79 -0
- package/dist/src/pi-extension/trim-system-prompt.js +37 -0
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +11 -11
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +48 -9
- package/dist/src/process/run-marker.js +42 -7
- package/dist/src/process/supervisor.js +47 -35
- package/dist/src/prompt/dialect.js +46 -11
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +201 -188
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -1
- package/dist/src/prompt/prompt-followup.js +43 -29
- package/dist/src/prompt/prompt.js +255 -172
- package/dist/src/redeploy/redeploy-policy.js +29 -24
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +7 -6
- package/dist/src/redeploy/redeployer.js +78 -70
- package/dist/src/redeploy/self-reload.js +3 -2
- package/dist/src/report/report-data.js +374 -285
- package/dist/src/report/report-fold.js +430 -0
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +321 -61
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +133 -14
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +7 -7
- package/dist/src/review/review-followup.js +21 -9
- package/dist/src/review/review-precheck.js +77 -33
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +16 -2
- package/dist/src/review/review.js +172 -356
- package/dist/src/review/secret-leaks.js +147 -0
- package/dist/src/review/suite-rerun.js +3 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +135 -0
- package/dist/src/roles/role-catalog.js +172 -137
- package/dist/src/roles/role-guidance.js +112 -44
- package/dist/src/roles/role-labels.js +9 -0
- package/dist/src/roles/role-render.js +221 -28
- package/dist/src/roles/role-view.js +244 -33
- package/dist/src/roles/roles.js +37 -35
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +10 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +166 -136
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/run/run-finalize.js +154 -0
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -6
- package/dist/src/run/run-prompt.js +304 -0
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +80 -35
- package/dist/src/run/run-stage.js +295 -0
- package/dist/src/run/run-timing.js +198 -0
- package/dist/src/run/run-usage.js +180 -0
- package/dist/src/run/run-verdict.js +301 -0
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/runner/leftover.js +185 -0
- package/dist/src/{loop → runner}/model-fallback.js +13 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +10 -10
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/runner/runner-pi.js +433 -0
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +34 -28
- package/dist/src/runner/runner-run.js +625 -0
- package/dist/src/runner/runner-state-load.js +268 -0
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/runner/runner.js +350 -0
- package/dist/src/scheduling/backoff.js +64 -54
- package/dist/src/scheduling/claims.js +46 -26
- package/dist/src/scheduling/once-round.js +30 -30
- package/dist/src/scheduling/quiet-hours.js +26 -34
- package/dist/src/scheduling/scheduling.js +87 -74
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +123 -0
- package/dist/src/status/status-data.js +106 -191
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +26 -0
- package/dist/src/status/status-polls.js +70 -59
- package/dist/src/status/status-roles.js +131 -0
- package/dist/src/{fleet → team}/error-storm.js +16 -16
- package/dist/src/{fleet → team}/failure-spread.js +10 -10
- package/dist/src/{fleet → team}/orchestrator-info.js +97 -25
- package/dist/src/{fleet → team}/reclaim.js +9 -9
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +60 -26
- package/dist/src/team/team-polls.js +299 -0
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +47 -40
- package/dist/src/text/datetime.js +102 -16
- package/dist/src/text/format.js +49 -3
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +89 -54
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +68 -14
- package/dist/src/ui/badges.js +165 -93
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +221 -42
- package/dist/src/ui/gui/gui-client-composer.js +105 -27
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +226 -130
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +492 -68
- package/dist/src/ui/gui/gui-client-labels.js +23 -0
- package/dist/src/ui/gui/gui-client-markdown.js +1 -1
- package/dist/src/ui/gui/gui-client-model.js +84 -76
- package/dist/src/ui/gui/gui-client-operator.js +112 -78
- package/dist/src/ui/gui/gui-client-pending.js +248 -41
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +122 -179
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +118 -76
- package/dist/src/ui/gui/gui-client-settings.js +180 -20
- package/dist/src/ui/gui/gui-client-team.js +252 -0
- package/dist/src/ui/gui/gui-client.js +48 -40
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +25 -24
- package/dist/src/ui/gui/gui-styles.js +46 -38
- package/dist/src/ui/progress-data.js +11 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +40 -40
- package/dist/src/ui/status-model.js +276 -194
- package/dist/src/ui/status-payload.js +227 -62
- package/dist/src/ui/status-render.js +196 -108
- package/dist/src/ui/team-alerts.js +532 -0
- package/dist/src/ui/tone.js +11 -11
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -19
- package/dist/src/ui/transcript.js +85 -38
- package/dist/src/ui/tui/tui-backlog.js +132 -21
- package/dist/src/ui/tui/tui-editor-answer.js +93 -0
- package/dist/src/ui/tui/tui-editor-attach.js +105 -0
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +105 -0
- package/dist/src/ui/tui/tui-editor-budget.js +159 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +219 -0
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +124 -0
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +112 -0
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +255 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +90 -0
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +94 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +170 -0
- package/dist/src/ui/tui/tui-editor-retire.js +81 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +85 -0
- package/dist/src/ui/tui/tui-editor-session.js +123 -0
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +185 -0
- package/dist/src/ui/tui/tui-frame.js +107 -58
- package/dist/src/ui/tui/tui-history.js +16 -12
- package/dist/src/ui/tui/tui-input.js +56 -60
- package/dist/src/ui/tui/tui-keys.js +106 -1825
- package/dist/src/ui/tui/tui-pane-activity.js +102 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +269 -0
- package/dist/src/ui/tui/tui-pane-brief.js +29 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +174 -0
- package/dist/src/ui/tui/tui-pane-history.js +636 -0
- package/dist/src/ui/tui/tui-pane-pending.js +369 -0
- package/dist/src/ui/tui/tui-pane-queued.js +159 -0
- package/dist/src/ui/tui/tui-pane-report.js +154 -0
- package/dist/src/ui/tui/tui-pane-session.js +120 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +224 -0
- package/dist/src/ui/tui/tui-pane.js +125 -44
- package/dist/src/ui/tui/tui-panes.js +364 -0
- package/dist/src/ui/tui/tui.js +52 -34
- package/dist/src/verdict/fix-claim.js +135 -62
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +80 -24
- package/dist/src/verdict/reply-contract.js +51 -28
- package/dist/src/version.js +2 -2
- package/package.json +5 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/brief-edit.js +0 -42
- package/dist/src/cli/cli-run.js +0 -469
- package/dist/src/fleet/fleet-polls.js +0 -207
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-check-failures.js +0 -141
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-git.js +0 -190
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -56
- package/dist/src/landing/landing-queue.js +0 -149
- package/dist/src/landing/landing-slot.js +0 -256
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/loop/leftover.js +0 -129
- package/dist/src/loop/loop-pi.js +0 -370
- package/dist/src/loop/loop-state.js +0 -204
- package/dist/src/loop/loop-tick.js +0 -545
- package/dist/src/loop/loop.js +0 -315
- package/dist/src/loop/revision.js +0 -61
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/roles/loop-ids.js +0 -60
- package/dist/src/tick/stage-check.js +0 -302
- package/dist/src/tick/telemetry-digest.js +0 -24
- package/dist/src/tick/tick-detail-data.js +0 -107
- package/dist/src/tick/tick-detail.js +0 -150
- package/dist/src/tick/tick-finalize.js +0 -135
- package/dist/src/tick/tick-prompt.js +0 -201
- package/dist/src/tick/tick-stage.js +0 -223
- package/dist/src/tick/tick-timing.js +0 -195
- package/dist/src/tick/tick-usage.js +0 -142
- package/dist/src/tick/tick-verdict.js +0 -201
- package/dist/src/ui/fleet-alerts.js +0 -174
- package/dist/src/ui/gui/gui-client-fleet.js +0 -576
- package/dist/src/ui/gui/gui-client-sound.js +0 -52
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/** Building the claude command line for one
|
|
1
|
+
/** Building the claude command line for one run (plans/agent-backends.md "The claude
|
|
2
2
|
* invocation") — pure argv construction plus the prompt-on-stdin pairing, with no subprocess,
|
|
3
3
|
* backend registration, or session ledger. Part 3c supplies the session decision (which
|
|
4
|
-
* `--session-id` or `--resume` to pass)
|
|
5
|
-
*
|
|
4
|
+
* `--session-id` or `--resume` to pass); this module turns those inputs into the invocation's
|
|
5
|
+
* argv.
|
|
6
6
|
*
|
|
7
7
|
* The environment is built separately (claude-env.ts) so each concern has one module.
|
|
8
8
|
*
|
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
* fallback the design holds in reserve is not needed. */
|
|
16
16
|
import fs from "node:fs";
|
|
17
17
|
import path from "node:path";
|
|
18
|
+
import { appendPassthroughArgs } from "../agent-args.js";
|
|
19
|
+
import { writeGuardSettings } from "./claude-write-guard.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
|
-
* set (plans/agent-backends.md "Config"); config validation (config-validation.ts)
|
|
23
|
-
* set for `
|
|
24
|
-
* validation and then thrown here. */
|
|
24
|
+
* set (plans/agent-backends.md "Config"); config validation (config-validation-agents.ts)
|
|
25
|
+
* mirrors this set for `backends.claude.args`, so a value that passes validation is never
|
|
26
|
+
* accepted by validation and then thrown here. */
|
|
25
27
|
export const HARNESS_CLAUDE_FLAGS = new Set([
|
|
26
28
|
"-p",
|
|
27
29
|
"--print",
|
|
@@ -37,6 +39,7 @@ export const HARNESS_CLAUDE_FLAGS = new Set([
|
|
|
37
39
|
"--permission-mode",
|
|
38
40
|
"--tools",
|
|
39
41
|
"--setting-sources",
|
|
42
|
+
"--settings",
|
|
40
43
|
"--strict-mcp-config",
|
|
41
44
|
"--mcp-config",
|
|
42
45
|
"--disable-slash-commands",
|
|
@@ -68,14 +71,18 @@ function shouldAppendAgents(cwd) {
|
|
|
68
71
|
* each would otherwise silently change what the harness asked for. */
|
|
69
72
|
export function claudeArgs(opts) {
|
|
70
73
|
if ((opts.sessionId === undefined) === (opts.resumeSessionId === undefined)) {
|
|
71
|
-
|
|
74
|
+
// Name which side fired so the invariant that would otherwise silently change the run's
|
|
75
|
+
// session is diagnosable at a glance: a conflicting pair (both set) differs from a missing
|
|
76
|
+
// choice (neither) and the two bugs have different fixes.
|
|
77
|
+
const state = opts.sessionId === undefined ? "neither is set" : "both are set";
|
|
78
|
+
throw new Error(`claudeArgs: pass exactly one of sessionId or resumeSessionId (${state})`);
|
|
72
79
|
}
|
|
73
80
|
const args = ["-p", "--output-format", "stream-json", "--verbose", "--model", opts.model];
|
|
74
81
|
if (opts.thinking !== undefined) {
|
|
75
82
|
// Own-property test, not a bare index: a level like `constructor` or `toString` would
|
|
76
83
|
// otherwise inherit a function from Object.prototype and bypass the rejection below.
|
|
77
84
|
if (!Object.hasOwn(EFFORT_BY_THINKING, opts.thinking)) {
|
|
78
|
-
throw new Error(`claudeArgs: unknown thinking level ${JSON.stringify(opts.thinking)}`);
|
|
85
|
+
throw new Error(`claudeArgs: unknown thinking level ${JSON.stringify(opts.thinking)} (known: ${Object.keys(EFFORT_BY_THINKING).join(", ")})`);
|
|
79
86
|
}
|
|
80
87
|
args.push("--effort", EFFORT_BY_THINKING[opts.thinking]);
|
|
81
88
|
}
|
|
@@ -83,22 +90,15 @@ export function claudeArgs(opts) {
|
|
|
83
90
|
args.push("--session-id", opts.sessionId);
|
|
84
91
|
else
|
|
85
92
|
args.push("--resume", opts.resumeSessionId);
|
|
86
|
-
args.push("--permission-mode", "bypassPermissions", "--tools", "Bash,Read,Edit,Write,Glob,Grep", "--setting-sources", "project",
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
args.push("--permission-mode", "bypassPermissions", "--tools", "Bash,Read,Edit,Write,Glob,Grep", "--setting-sources", "project",
|
|
94
|
+
// The write guard's PreToolUse hook (claude-write-guard.ts).
|
|
95
|
+
"--settings", writeGuardSettings(opts.cwd), "--strict-mcp-config");
|
|
89
96
|
args.push("--disable-slash-commands", "--exclude-dynamic-system-prompt-sections");
|
|
90
97
|
if (shouldAppendAgents(opts.cwd))
|
|
91
98
|
args.push("--append-system-prompt-file", "AGENTS.md");
|
|
92
99
|
if (opts.maxBudgetUsd !== undefined && opts.maxBudgetUsd > 0) {
|
|
93
100
|
args.push("--max-budget-usd", String(opts.maxBudgetUsd));
|
|
94
101
|
}
|
|
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
|
-
}
|
|
102
|
+
appendPassthroughArgs(args, opts.args, HARNESS_CLAUDE_FLAGS, "claudeArgs");
|
|
103
103
|
return { args, stdin: opts.prompt };
|
|
104
104
|
}
|
|
@@ -16,34 +16,49 @@
|
|
|
16
16
|
import fs from "node:fs";
|
|
17
17
|
import os from "node:os";
|
|
18
18
|
import path from "node:path";
|
|
19
|
-
import { fileURLToPath } from "node:url";
|
|
20
19
|
import { randomUUID } from "node:crypto";
|
|
21
|
-
import {
|
|
20
|
+
import { resolvedBackendConfig } from "../../config/config-views.js";
|
|
22
21
|
import { MODEL_TIERS } from "../../config/config-schema.js";
|
|
23
22
|
import { routeModel } from "../../config/model-selector.js";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
23
|
+
import { backendBaseEnv, withNotesPath } from "../../process/run-marker.js";
|
|
24
|
+
import { resolveBinForBackend } from "../../pi/pi-bin.js";
|
|
25
|
+
import { expandHomeTilde } from "../../files/files.js";
|
|
26
26
|
import { claudeArgs } from "./claude-args.js";
|
|
27
27
|
import { claudeEnv, claudeThirdPartyProvider } from "./claude-env.js";
|
|
28
28
|
import { claudeTranslator } from "./claude-stream.js";
|
|
29
29
|
import { resumableSession } from "../session-ledger.js";
|
|
30
|
-
/** claude's per-cwd project directory name under
|
|
31
|
-
* is not a letter or digit becomes `-`. Exported with
|
|
32
|
-
* and the tests create and look for exactly the file
|
|
30
|
+
/** claude's per-cwd project directory name under the claude config dir's `projects/`:
|
|
31
|
+
* every character that is not a letter or digit becomes `-`. Exported with
|
|
32
|
+
* `claudeTranscriptPath` so the PATH fake and the tests create and look for exactly the file
|
|
33
|
+
* the resume check reads. */
|
|
33
34
|
export function claudeProjectSlug(cwd) {
|
|
34
35
|
return cwd.replace(/[^A-Za-z0-9]/g, "-");
|
|
35
36
|
}
|
|
37
|
+
/** claude's config directory: `$CLAUDE_CONFIG_DIR` when non-empty (a leading `~` expands
|
|
38
|
+
* against the home dir), else `~/.claude`. A configured team `configDir` (the expanded
|
|
39
|
+
* `backends.claude.configDir`) wins over any inherited env var — the team's own directory is
|
|
40
|
+
* never overridden by the operator's shell. This mirrors claude's own lookup and pi's
|
|
41
|
+
* `piAgentDir`; exported so doctor reads the same dir the adapter writes transcripts into. */
|
|
42
|
+
export function claudeConfigDir(env = process.env, homeDir = os.homedir(), configured) {
|
|
43
|
+
if (configured)
|
|
44
|
+
return expandHomeTilde(configured, homeDir);
|
|
45
|
+
const dir = env.CLAUDE_CONFIG_DIR;
|
|
46
|
+
if (!dir)
|
|
47
|
+
return path.join(homeDir, ".claude");
|
|
48
|
+
return expandHomeTilde(dir, homeDir);
|
|
49
|
+
}
|
|
36
50
|
/** The transcript file claude writes for a session started in `cwd`: its resume check must find
|
|
37
51
|
* a real file before passing `--resume`. claude's own layout is
|
|
38
|
-
*
|
|
39
|
-
* cannot drift.
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
* `<config>/projects/<slug>/<sessionId>.jsonl`; exported so the fake and the resume logic
|
|
53
|
+
* cannot drift. `configDir` is the configured team dir, threaded through so the resume check
|
|
54
|
+
* looks where the run's own env will write. */
|
|
55
|
+
export function claudeTranscriptPath(cwd, id, env = process.env, homeDir = os.homedir(), configDir) {
|
|
56
|
+
return path.join(claudeConfigDir(env, homeDir, configDir), "projects", claudeProjectSlug(cwd), `${id}.jsonl`);
|
|
42
57
|
}
|
|
43
58
|
/** The CLI version every gated claude model needs (plans/agent-backends.md finding 9): on
|
|
44
|
-
* 2.1.216 Opus 5.5 is refused outright and Haiku 5.5 is mispriced about 250×, and
|
|
59
|
+
* 2.1.216 Opus 5.5 is refused outright and Haiku 5.5 is mispriced about 250×, and team runs
|
|
45
60
|
* set DISABLE_AUTOUPDATER so they never update themselves. The gated models are the three
|
|
46
|
-
* tier defaults, derived from
|
|
61
|
+
* tier defaults, derived from BACKEND_DEFAULT_MODELS through the same routing the config uses
|
|
47
62
|
* rather than listed again, so this table cannot drift from the models a seam resolves. */
|
|
48
63
|
const CLAUDE_MIN_VERSION = "2.1.280";
|
|
49
64
|
const CLAUDE_GATED_MODELS = new Set(MODEL_TIERS.map((tier) => routeModel(undefined, undefined, "claude", tier).model).filter((model) => model !== undefined));
|
|
@@ -65,37 +80,27 @@ function classifyClaudeExit(info) {
|
|
|
65
80
|
}
|
|
66
81
|
return {};
|
|
67
82
|
}
|
|
68
|
-
/** The inline `--mcp-config` JSON that runs the bundled role-notes server (part 5/7): the
|
|
69
|
-
* same dist module pi's extension resolves to, spawned by the same node running this harness
|
|
70
|
-
* so it needs no PATH lookup. */
|
|
71
|
-
function roleNotesMcpConfig() {
|
|
72
|
-
const server = fileURLToPath(new URL("../mcp/role-notes-server.js", import.meta.url));
|
|
73
|
-
return JSON.stringify({ mcpServers: { tumwater: { command: process.execPath, args: [server] } } });
|
|
74
|
-
}
|
|
75
83
|
/** The ledger record this run may resume, or null: the newest record of `opts.kind` for
|
|
76
84
|
* claude, at `opts.cwd` when one is given, whose transcript file exists. Shared by
|
|
77
85
|
* `resolveClaudeSession` and the backend's `resumable` hook, so the resume guard and the
|
|
78
86
|
* invocation can never drift on what counts as resumable. */
|
|
79
|
-
function claudeResumableRecord(dir, opts) {
|
|
87
|
+
function claudeResumableRecord(dir, opts, configDir) {
|
|
80
88
|
const record = resumableSession(dir, opts);
|
|
81
89
|
if (record === null)
|
|
82
90
|
return null;
|
|
83
91
|
if (opts.cwd !== undefined && record.cwd !== opts.cwd)
|
|
84
92
|
return null;
|
|
85
|
-
if (!fs.existsSync(claudeTranscriptPath(record.cwd, record.id)))
|
|
93
|
+
if (!fs.existsSync(claudeTranscriptPath(record.cwd, record.id, process.env, os.homedir(), configDir)))
|
|
86
94
|
return null;
|
|
87
95
|
return record;
|
|
88
96
|
}
|
|
89
97
|
/** The session id this run uses: for a continue, the ledger's newest record of this kind when
|
|
90
98
|
* it is claude's, this cwd's, and its transcript file exists; otherwise a fresh UUID. Returns
|
|
91
|
-
* the id and the `--resume` id when resuming.
|
|
92
|
-
|
|
99
|
+
* the id and the `--resume` id when resuming. `configDir` is the configured team dir, so the
|
|
100
|
+
* resume check looks where the run's own env will write transcripts. */
|
|
101
|
+
function resolveClaudeSession(run, configDir) {
|
|
93
102
|
if (run.continueSession) {
|
|
94
|
-
const record = claudeResumableRecord(run.sessionDir, {
|
|
95
|
-
kind: run.kind,
|
|
96
|
-
agent: "claude",
|
|
97
|
-
cwd: run.cwd,
|
|
98
|
-
});
|
|
103
|
+
const record = claudeResumableRecord(run.sessionDir, { kind: run.kind, agent: "claude", cwd: run.cwd }, configDir);
|
|
99
104
|
if (record !== null)
|
|
100
105
|
return { sessionId: record.id, resumeSessionId: record.id };
|
|
101
106
|
}
|
|
@@ -109,7 +114,7 @@ export const claudeBackend = {
|
|
|
109
114
|
binEnv: "TUMWATER_CLAUDE_BIN",
|
|
110
115
|
minVersion: claudeMinVersion,
|
|
111
116
|
resolveBin(config) {
|
|
112
|
-
return
|
|
117
|
+
return resolveBinForBackend(config, this);
|
|
113
118
|
},
|
|
114
119
|
invocation(run) {
|
|
115
120
|
// Part 4b/7's config routing fills a claude tier default before the seam, so a nameless
|
|
@@ -117,35 +122,35 @@ export const claudeBackend = {
|
|
|
117
122
|
if (run.config.model === undefined) {
|
|
118
123
|
throw new Error("claude backend: no model resolved for this run");
|
|
119
124
|
}
|
|
120
|
-
|
|
125
|
+
// The configured team config dir (plans/notes-command.md "Keeping funes out of team runs"):
|
|
126
|
+
// the run's env, its resume lookup, and the account seed all follow it; unset, nothing changes.
|
|
127
|
+
const claudeCfg = resolvedBackendConfig(run.config, "claude");
|
|
128
|
+
const configDir = claudeCfg.configDir;
|
|
129
|
+
const expandedConfigDir = configDir !== undefined ? claudeConfigDir(process.env, os.homedir(), configDir) : undefined;
|
|
130
|
+
const { sessionId, resumeSessionId } = resolveClaudeSession(run, configDir);
|
|
121
131
|
const { args, stdin } = claudeArgs({
|
|
122
132
|
cwd: run.cwd,
|
|
123
133
|
model: run.config.model,
|
|
124
134
|
thinking: run.config.thinking,
|
|
125
135
|
...(resumeSessionId !== undefined ? { resumeSessionId } : { sessionId }),
|
|
126
|
-
// The operator's `
|
|
127
|
-
// builder rejects any entry that repeats a harness-managed flag).
|
|
136
|
+
// The operator's `backends.claude.args` passthrough, appended after the harness flags (the
|
|
137
|
+
// builder rejects any entry that repeats a harness-managed flag). resolvedBackendConfig
|
|
128
138
|
// owns the view so a caller cannot mutate the loaded config through the returned array.
|
|
129
|
-
args:
|
|
139
|
+
args: claudeCfg.args,
|
|
130
140
|
prompt: run.prompt,
|
|
131
141
|
// The caller computes the remaining daily budget for an API-billed claude run and the
|
|
132
142
|
// adapter forwards it; a subscription run or the director passes none (part 7a/7).
|
|
133
143
|
...(run.maxBudgetUsd !== undefined ? { maxBudgetUsd: run.maxBudgetUsd } : {}),
|
|
134
|
-
// claude does not load the bundled pi extensions, so a run carrying a notebook gets the
|
|
135
|
-
// role_notes MCP server instead. Passed on EVERY claude invocation that carries a
|
|
136
|
-
// notebook — fresh or resumed, a transient retry, a SUMMARY follow-up — because claude
|
|
137
|
-
// keeps no flags across --resume, so a resume without this would lose the tool.
|
|
138
|
-
...(run.notesPath !== undefined ? { mcpConfig: roleNotesMcpConfig() } : {}),
|
|
139
144
|
});
|
|
140
|
-
//
|
|
141
|
-
// claudeEnv layers the backend's own scrub-and-set on top. The
|
|
142
|
-
// the marker the same way pi's adapter carries it (
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
: { ...agentBaseEnv(run.runMarker), TUMWATER_NOTES_PATH: run.notesPath };
|
|
145
|
+
// backendBaseEnv applies the run marker and the macOS preload (shared with the pi adapter);
|
|
146
|
+
// claudeEnv layers the backend's own scrub-and-set on top. The notes path rides beside
|
|
147
|
+
// the marker the same way pi's adapter carries it (withNotesPath); from part 2/4 the run
|
|
148
|
+
// saves its note through the `tumwater-notes` command on PATH, so no MCP server reaches the
|
|
149
|
+
// run and `--strict-mcp-config` (always in claude-args.ts) keeps it that way.
|
|
150
|
+
const base = withNotesPath(backendBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
|
|
147
151
|
const env = claudeEnv(base, {
|
|
148
152
|
quietTimeoutSeconds: run.config.quietTimeoutSeconds,
|
|
153
|
+
...(expandedConfigDir !== undefined ? { configDir: expandedConfigDir } : {}),
|
|
149
154
|
});
|
|
150
155
|
return { args, stdin, env, sessionId };
|
|
151
156
|
},
|
|
@@ -155,8 +160,13 @@ export const claudeBackend = {
|
|
|
155
160
|
// seed the account file, so both must classify it the same way.
|
|
156
161
|
return claudeTranslator({ thirdPartyProvider: claudeThirdPartyProvider(process.env) });
|
|
157
162
|
},
|
|
158
|
-
resumable(dir, opts) {
|
|
159
|
-
return claudeResumableRecord(dir, opts) !== null;
|
|
163
|
+
resumable(dir, opts, configDir) {
|
|
164
|
+
return claudeResumableRecord(dir, opts, configDir) !== null;
|
|
165
|
+
},
|
|
166
|
+
// Every Claude model reads images (Claude Code's Read tool returns them as image blocks), so
|
|
167
|
+
// a claude seam is always capable — the doctor probes nothing and never works around it.
|
|
168
|
+
imageInput() {
|
|
169
|
+
return true;
|
|
160
170
|
},
|
|
161
171
|
classifyExit: classifyClaudeExit,
|
|
162
172
|
};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/** Building the environment for one claude run (plans/agent-backends.md "The claude
|
|
2
2
|
* invocation", "Findings" 2 and 4) — pure scrub-and-set over a base environment, with no
|
|
3
3
|
* subprocess. The caller layers the run-marker, the macOS LaunchServices preload, and the
|
|
4
|
-
*
|
|
5
|
-
* same), then hands the result here.
|
|
4
|
+
* notes on first (src/process/run-marker.ts's `withNotesPath`), then hands the result here.
|
|
6
5
|
*
|
|
7
6
|
* Inherited Claude Code session variables break auth: spawned from inside a Claude Code
|
|
8
7
|
* session, a child that keeps `CLAUDECODE`, `CLAUDE_CODE_*`, `CLAUDE_AGENT_SDK_*` or the host's
|
|
@@ -13,7 +12,7 @@
|
|
|
13
12
|
*
|
|
14
13
|
* The set half disables auto-memory, backgrounded Bash tasks (finding 4: claude refuses a long
|
|
15
14
|
* foreground command, backgrounds it, and abandons it when `-p` ends, which would report work
|
|
16
|
-
* never verified), the auto-updater (a mid-
|
|
15
|
+
* never verified), the auto-updater (a mid-team self-update must not swap the binary under a
|
|
17
16
|
* run), telemetry, and error reporting; and raises the Bash timeouts so a slow suite runs in
|
|
18
17
|
* the foreground. `BASH_MAX_TIMEOUT_MS` is derived from `quietTimeoutSeconds` so claude's own
|
|
19
18
|
* Bash timeout fires before tumwater's quiet kill and the model sees a tool error rather than a
|
|
@@ -80,5 +79,10 @@ export function claudeEnv(base, opts) {
|
|
|
80
79
|
env.DISABLE_AUTOUPDATER = "1";
|
|
81
80
|
env.DISABLE_TELEMETRY = "1";
|
|
82
81
|
env.DISABLE_ERROR_REPORTING = "1";
|
|
82
|
+
// A team config dir (plans/notes-command.md "Keeping funes out of team runs"): force claude's
|
|
83
|
+
// own config and transcripts under it, so a funes hook on ~/.claude/projects never indexes
|
|
84
|
+
// team runs. Unset leaves any inherited CLAUDE_CONFIG_DIR (or claude's default) untouched.
|
|
85
|
+
if (opts.configDir)
|
|
86
|
+
env.CLAUDE_CONFIG_DIR = opts.configDir;
|
|
83
87
|
return env;
|
|
84
88
|
}
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* on the seam's optional `warnings()` — src/agent/run-agent.ts forwards each as an
|
|
15
15
|
* `agent_unknown_event` warning after the run. `cost_unknown` is recorded the same way when a
|
|
16
16
|
* run produced no authoritative `run_cost`. */
|
|
17
|
-
import {
|
|
17
|
+
import { joinParts } from "../../text/text.js";
|
|
18
|
+
import { canonicalUsageBlock, isJsonObject, objectOrEmpty, nonNegativeNumber, parseJsonObject } from "../../files/json-object.js";
|
|
18
19
|
/** claude's tool names → the canonical pi names the readers already know. Glob/Grep are kept
|
|
19
20
|
* lower-cased; MultiEdit collapses onto edit (pi has one edit tool). Anything else is
|
|
20
21
|
* lower-cased so a future tool still reads as a name rather than as `undefined`. */
|
|
@@ -37,20 +38,10 @@ function str(value) {
|
|
|
37
38
|
}
|
|
38
39
|
/** claude's usage block → pi's canonical usage block, with `cost.total` 0 because the run's
|
|
39
40
|
* authoritative cost rides the final `run_cost` event (a split-block message repeats its usage
|
|
40
|
-
* on every event, so summing per event would double-count — the fold takes it once).
|
|
41
|
+
* on every event, so summing per event would double-count — the fold takes it once). The
|
|
42
|
+
* wire-field reads stay here; the block's assembly is the shared canonicalUsageBlock. */
|
|
41
43
|
function canonicalUsage(usage) {
|
|
42
|
-
|
|
43
|
-
const cacheRead = nonNegativeNumber(usage?.cache_read_input_tokens, 0);
|
|
44
|
-
const cacheWrite = nonNegativeNumber(usage?.cache_creation_input_tokens, 0);
|
|
45
|
-
const output = nonNegativeNumber(usage?.output_tokens, 0);
|
|
46
|
-
return {
|
|
47
|
-
input,
|
|
48
|
-
cacheRead,
|
|
49
|
-
cacheWrite,
|
|
50
|
-
output,
|
|
51
|
-
totalTokens: input + cacheRead + cacheWrite + output,
|
|
52
|
-
cost: { total: 0 },
|
|
53
|
-
};
|
|
44
|
+
return canonicalUsageBlock(nonNegativeNumber(usage?.input_tokens, 0), nonNegativeNumber(usage?.cache_read_input_tokens, 0), nonNegativeNumber(usage?.cache_creation_input_tokens, 0), nonNegativeNumber(usage?.output_tokens, 0), 0);
|
|
54
45
|
}
|
|
55
46
|
/** One claude content block → one pi content block, or undefined for a block pi has no
|
|
56
47
|
* vocabulary for (dropped, like any other unmapped native shape). `tool_use` becomes pi's
|
|
@@ -76,7 +67,7 @@ function canonicalBlock(block) {
|
|
|
76
67
|
return {
|
|
77
68
|
type: "toolCall",
|
|
78
69
|
id: str(block.id) ?? "",
|
|
79
|
-
name: Object.hasOwn(TOOL_NAMES, name) ? TOOL_NAMES[name] : name.toLowerCase(),
|
|
70
|
+
name: Object.hasOwn(TOOL_NAMES, name) ? (TOOL_NAMES[name]) : name.toLowerCase(),
|
|
80
71
|
arguments: args,
|
|
81
72
|
};
|
|
82
73
|
}
|
|
@@ -96,7 +87,7 @@ function classifyResult(result) {
|
|
|
96
87
|
return {};
|
|
97
88
|
if (terminal === "error_max_budget_usd")
|
|
98
89
|
return { kind: "budgetCapped" };
|
|
99
|
-
const text = [str(result.result), str(result.error)]
|
|
90
|
+
const text = joinParts([str(result.result), str(result.error)], " ");
|
|
100
91
|
if (/error_max_budget_usd/i.test(text))
|
|
101
92
|
return { kind: "budgetCapped" };
|
|
102
93
|
const status = nonNegativeNumber(result.api_error_status, undefined);
|
|
@@ -203,7 +194,7 @@ export function claudeTranslator(opts = {}) {
|
|
|
203
194
|
const maxAttempts = nonNegativeNumber(event.max_retries, undefined);
|
|
204
195
|
const status = nonNegativeNumber(event.error_status, undefined);
|
|
205
196
|
const error = str(event.error) ?? "";
|
|
206
|
-
const errorMessage = [status !== undefined ? `status ${status}` : "", error]
|
|
197
|
+
const errorMessage = joinParts([status !== undefined ? `status ${status}` : "", error], ": ");
|
|
207
198
|
return [
|
|
208
199
|
JSON.stringify({
|
|
209
200
|
type: "auto_retry_start",
|
|
@@ -304,7 +295,7 @@ export function claudeTranslator(opts = {}) {
|
|
|
304
295
|
if (info.isUsingOverage === true)
|
|
305
296
|
out.isUsingOverage = true;
|
|
306
297
|
if (status === "rejected" && resetsAt !== undefined) {
|
|
307
|
-
// resetsAt is a unix-seconds stamp; retryAfterSeconds is the wait the
|
|
298
|
+
// resetsAt is a unix-seconds stamp; retryAfterSeconds is the wait the runner honors.
|
|
308
299
|
out.retryAfterSeconds = Math.max(0, Math.round(resetsAt - Date.now() / 1000));
|
|
309
300
|
}
|
|
310
301
|
return [JSON.stringify(out), ...windows];
|
|
@@ -347,7 +338,8 @@ export function claudeTranslator(opts = {}) {
|
|
|
347
338
|
return out;
|
|
348
339
|
}
|
|
349
340
|
case "tool_progress":
|
|
350
|
-
// A 30 s heartbeat of a running tool call: known, and the open call already tracks
|
|
341
|
+
// A 30 s heartbeat of a running tool call: known, and the open call already tracks
|
|
342
|
+
// liveness.
|
|
351
343
|
return [];
|
|
352
344
|
default:
|
|
353
345
|
record(type ?? "unknown");
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** claude's half of the write guard (src/agent/write-guard.ts holds the rule): a PreToolUse hook on
|
|
2
|
+
* the file-writing tools, passed to each run through `--settings`, refuses a write under the team
|
|
3
|
+
* root but outside the run's worktree.
|
|
4
|
+
*
|
|
5
|
+
* Hooks run under `--permission-mode bypassPermissions` too, and a hook's `deny` decision blocks
|
|
6
|
+
* the call and hands its reason to the model (checked live with claude 2.1.295 on 2026-10-23).
|
|
7
|
+
* `--setting-sources project` leaves the operator's own hooks out of a run, while `--settings`
|
|
8
|
+
* still loads this one. The hook gets the run's worktree as an argument rather than reading
|
|
9
|
+
* claude's `cwd`, which follows the Bash tool's `cd` into subdirectories. The entry point is
|
|
10
|
+
* write-guard-hook.ts beside this file. */
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { shellQuote, strayWriteRefusal } from "../write-guard.js";
|
|
13
|
+
/** claude's file-writing tools a team run has: claude-args.ts's `--tools` allows only these two,
|
|
14
|
+
* and `--tools` is a reserved flag, so no passthrough can add another. */
|
|
15
|
+
const WRITE_TOOLS = ["Edit", "Write"];
|
|
16
|
+
/** Seconds claude waits for the hook before running the tool anyway. */
|
|
17
|
+
const HOOK_TIMEOUT_SECONDS = 30;
|
|
18
|
+
/** The `--settings` value for a run in `cwd`: one PreToolUse hook on the file-writing tools that
|
|
19
|
+
* runs the compiled write-guard-hook.js with this process's node, falling back to `node` on PATH
|
|
20
|
+
* when that binary has moved (as the notes shim does), and the run's worktree as its argument. The
|
|
21
|
+
* hook path resolves from this module, so a staged build (.tumwater/build/<sha>) runs its own
|
|
22
|
+
* copy. */
|
|
23
|
+
export function writeGuardSettings(cwd, nodeBin = process.execPath) {
|
|
24
|
+
const hook = fileURLToPath(new URL("./write-guard-hook.js", import.meta.url));
|
|
25
|
+
const command = `n=${shellQuote(nodeBin)}; [ -x "$n" ] || n=node; exec "$n" ${shellQuote(hook)} ${shellQuote(cwd)}`;
|
|
26
|
+
return JSON.stringify({
|
|
27
|
+
hooks: {
|
|
28
|
+
PreToolUse: [
|
|
29
|
+
{ matcher: WRITE_TOOLS.join("|"), hooks: [{ type: "command", command, timeout: HOOK_TIMEOUT_SECONDS }] },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** The refusal for one PreToolUse payload (claude's hook stdin, parsed) from a run in `runDir`, or
|
|
35
|
+
* null to let the call run. A relative path resolves from the payload's `cwd`, where claude's
|
|
36
|
+
* tools would resolve it. */
|
|
37
|
+
export function claudeWriteRefusal(payload, runDir) {
|
|
38
|
+
if (typeof payload !== "object" || payload === null)
|
|
39
|
+
return null;
|
|
40
|
+
const { tool_name: tool, tool_input: input, cwd } = payload;
|
|
41
|
+
if (typeof tool !== "string" || !WRITE_TOOLS.includes(tool))
|
|
42
|
+
return null;
|
|
43
|
+
if (typeof input !== "object" || input === null)
|
|
44
|
+
return null;
|
|
45
|
+
const target = input.file_path;
|
|
46
|
+
if (typeof target !== "string" || target === "")
|
|
47
|
+
return null;
|
|
48
|
+
return strayWriteRefusal(target, runDir, typeof cwd === "string" && cwd !== "" ? cwd : runDir);
|
|
49
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The PreToolUse hook claude runs before each file write in a team run (claude-write-guard.ts
|
|
2
|
+
* installs it through `--settings`): `node write-guard-hook.js <worktree>` with claude's hook
|
|
3
|
+
* payload on stdin. To block the write it prints a `deny` decision with the refusal as its reason:
|
|
4
|
+
* the model reads only the reason, where a refusal by exit 2 would also quote the whole hook
|
|
5
|
+
* command. It prints nothing to let the write run. Any failure of its own lets the write run, so a
|
|
6
|
+
* broken hook cannot stop every edit of a run; the stray-write warning in runner-run.ts still sees
|
|
7
|
+
* a write that slips through. The stdin read and the deny-decision printing are runGuardHook's
|
|
8
|
+
* (src/agent/write-guard.ts), shared with the codex PreToolUse hook; only the refusal computation
|
|
9
|
+
* differs, and it needs the run's checkout, which this process receives as its first argument. */
|
|
10
|
+
import { runGuardHook } from "../write-guard.js";
|
|
11
|
+
import { claudeWriteRefusal } from "./claude-write-guard.js";
|
|
12
|
+
const runDir = process.argv[2];
|
|
13
|
+
runGuardHook((payload) => (runDir ? claudeWriteRefusal(payload, runDir) : null));
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** The codex account state: the codex backend's subscription account (src/agent/
|
|
2
|
+
* subscription-account.ts) — which billing pays for codex runs, and the newest subscription
|
|
3
|
+
* usage-window reading each run reported (plans/codex-backend.md §7 "Spend, free-ness, window,
|
|
4
|
+
* images"). Persisted at `.tumwater/state/codex-account.json`, written only by the
|
|
5
|
+
* orchestrator process, so the sibling parts that throttle or display the subscription window
|
|
6
|
+
* read one answer instead of re-deriving it per surface.
|
|
7
|
+
*
|
|
8
|
+
* Everything here is a thin wrapper over the generic per-backend store
|
|
9
|
+
* (grounds/codex-backend.md "Groundwork: the subscription account is per backend", item 1),
|
|
10
|
+
* specialized to the backend id "codex", plus the codex-only seed (the `codex login status`
|
|
11
|
+
* probe that decides subscription vs API-key billing) that no other backend shares. A Codex
|
|
12
|
+
* run's billing and window readings reach the store through the generic observeAccount call
|
|
13
|
+
* the run finalizers make (run.agent === "codex"); the seed only fills the billing answer in
|
|
14
|
+
* before the first run so the budget gate and the API-key refusal can act on it. The file is a
|
|
15
|
+
* cache, never a source of truth: a run that reports billing overwrites it, and a missing or
|
|
16
|
+
* unreadable file reads as "no subscription". */
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
18
|
+
import { resolvedBackendConfig } from "../../config/config-views.js";
|
|
19
|
+
import { observeAccount, readAccount, writeAccount, } from "../subscription-account.js";
|
|
20
|
+
import { codexBackend } from "./codex-backend.js";
|
|
21
|
+
import { codexEnv } from "./codex-env.js";
|
|
22
|
+
import { codexHome } from "./codex-catalog.js";
|
|
23
|
+
/** Read the persisted codex 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). */
|
|
25
|
+
export function readCodexAccount(root, now = Date.now()) {
|
|
26
|
+
return readAccount(root, "codex", now);
|
|
27
|
+
}
|
|
28
|
+
/** Fold one run's billing and rate-window readings into the codex account file — the store's
|
|
29
|
+
* generic observeAccount for the "codex" backend, so a run's rollout feeding (plans/
|
|
30
|
+
* codex-backend.md "Spend, free-ness, window, images") lands under the codex id. */
|
|
31
|
+
export function observeCodexAccount(root, run, now = Date.now()) {
|
|
32
|
+
return observeAccount(root, "codex", run, now);
|
|
33
|
+
}
|
|
34
|
+
/** Seed the codex account file before the first run (plans/codex-backend.md §7): `codex login
|
|
35
|
+
* status` decides — a ChatGPT login is subscription billing, an API key is API billing.
|
|
36
|
+
* An existing non-api file is left alone, and a probe that cannot run or is not logged in (or
|
|
37
|
+
* prints something else) leaves the file absent (readCodexAccount then reads "no subscription")
|
|
38
|
+
* unless it must clear a sticky api refusal: an "api" answer is re-probed so the operator's
|
|
39
|
+
* remedy (`codex login --device-auth`) clears it next startup, and a re-probe that cannot
|
|
40
|
+
* decide keeps the api file so the refusal does not fail open.
|
|
41
|
+
* Plan and windows arrive with the first run's rollout, so the seed writes no window readings.
|
|
42
|
+
* Returns the account written, or null. A failed write also returns null and never throws, so
|
|
43
|
+
* the first codex run's observation fills the file in. */
|
|
44
|
+
export function seedCodexAccount(root, config, now = Date.now()) {
|
|
45
|
+
const existing = readCodexAccount(root, now);
|
|
46
|
+
// Leave an existing answer alone — a run's observation is fresher than this probe — except
|
|
47
|
+
// an "api" answer: the startup gate refuses on that, so it must never be sticky. Re-probe
|
|
48
|
+
// then, so an API-key login the operator has since fixed to a subscription (the refusal's
|
|
49
|
+
// named remedy, `codex login --device-auth`) clears the refusal on the next startup instead
|
|
50
|
+
// of a stale file refusing forever.
|
|
51
|
+
if (existing !== null && existing.billing !== "api")
|
|
52
|
+
return existing;
|
|
53
|
+
const billing = seedBilling(config);
|
|
54
|
+
if (billing === null) {
|
|
55
|
+
// A re-probe that cannot decide must not fail open: without an existing answer it seeds
|
|
56
|
+
// nothing, but a stale api answer keeps refusing — the persisted api file stays and the
|
|
57
|
+
// gate still reads it as a refusal rather than quietly passing (review objection 2).
|
|
58
|
+
return existing;
|
|
59
|
+
}
|
|
60
|
+
const account = {
|
|
61
|
+
billing,
|
|
62
|
+
observedAt: now,
|
|
63
|
+
windows: {},
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
writeAccount(root, "codex", account);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return account;
|
|
72
|
+
}
|
|
73
|
+
/** Parse `codex login status` stdout into the billing it names: a ChatGPT login is a
|
|
74
|
+
* subscription, an API-key login is API billing, and anything else (not logged in, an unknown
|
|
75
|
+
* string, an IP firewall prompt) is null — the seed then stays silent and the run's rollout
|
|
76
|
+
* decides. Shared by seedBilling below and (from part 9/9) doctor's codex auth check, so the
|
|
77
|
+
* two cannot disagree about what a login means. */
|
|
78
|
+
export function parseCodexLoginStatus(stdout) {
|
|
79
|
+
if (typeof stdout !== "string" || stdout === "")
|
|
80
|
+
return null;
|
|
81
|
+
if (stdout.includes("ChatGPT"))
|
|
82
|
+
return "subscription";
|
|
83
|
+
if (stdout.includes("API key"))
|
|
84
|
+
return "api";
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/** The billing the login probe names, or null when it cannot be decided. */
|
|
88
|
+
function seedBilling(config) {
|
|
89
|
+
const status = probeLoginStatus(config);
|
|
90
|
+
if (status === null)
|
|
91
|
+
return null;
|
|
92
|
+
if (status === "api")
|
|
93
|
+
return "api";
|
|
94
|
+
if (status === "subscription")
|
|
95
|
+
return "subscription";
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/** Run `codex login status` under the same scrubbed environment a real run gets (a team
|
|
99
|
+
* config dir's login is what it reports), bounded by a short deadline. Null when the probe
|
|
100
|
+
* could not run or its stdout was unreadable. */
|
|
101
|
+
function probeLoginStatus(config) {
|
|
102
|
+
const resolved = codexBackend.resolveBin(config);
|
|
103
|
+
const home = codexHome(resolvedBackendConfig(config, "codex"));
|
|
104
|
+
const env = codexEnv(process.env, home !== undefined ? { home } : {});
|
|
105
|
+
try {
|
|
106
|
+
const result = spawnSync(resolved.bin, ["login", "status"], {
|
|
107
|
+
env,
|
|
108
|
+
encoding: "utf8",
|
|
109
|
+
timeout: 10_000,
|
|
110
|
+
killSignal: "SIGKILL",
|
|
111
|
+
});
|
|
112
|
+
if (result.status !== 0)
|
|
113
|
+
return null;
|
|
114
|
+
return parseCodexLoginStatus(typeof result.stdout === "string" ? result.stdout : null);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|