tumwater 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +55 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +6 -4
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +404 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +281 -55
- package/dist/src/backlog/backlog-cli-view.js +255 -0
- package/dist/src/backlog/backlog-cli.js +91 -335
- package/dist/src/backlog/backlog-commit.js +20 -13
- package/dist/src/backlog/backlog-eligibility.js +193 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +264 -0
- package/dist/src/backlog/backlog-release.js +134 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-guardrails.js +136 -0
- package/dist/src/baseline/main-red.js +153 -36
- package/dist/src/baseline/main-watch.js +232 -0
- package/dist/src/brief/brief-edit.js +12 -12
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget-fallback-view.js +288 -0
- package/dist/src/budget/budget-wiring.js +158 -0
- package/dist/src/budget/budget.js +31 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +65 -3
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +181 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +183 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +291 -598
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-review-commands.js +113 -0
- package/dist/src/cli/cli-role-command.js +525 -0
- package/dist/src/cli/cli-start-summary.js +167 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
- package/dist/src/cli/completion.js +169 -22
- package/dist/src/cli/config-commands.js +58 -26
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +258 -82
- package/dist/src/cli/log-commands.js +164 -85
- package/dist/src/cli/question-commands.js +211 -10
- package/dist/src/cli.js +173 -45
- package/dist/src/collections.js +56 -13
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +16 -1
- package/dist/src/config/config-fallbacks.js +94 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +169 -222
- package/dist/src/config/config-views.js +136 -54
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +164 -265
- package/dist/src/config/config.js +53 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
- package/dist/src/doctor/doctor-backlog-plans.js +398 -0
- package/dist/src/doctor/doctor-backlog.js +249 -512
- package/dist/src/doctor/doctor-check-names.js +85 -0
- package/dist/src/doctor/doctor-checks.js +61 -593
- package/dist/src/doctor/doctor-claude-checks.js +142 -0
- package/dist/src/doctor/doctor-codex-checks.js +276 -0
- package/dist/src/doctor/doctor-contention-checks.js +506 -0
- package/dist/src/doctor/doctor-fallback-checks.js +130 -0
- package/dist/src/doctor/doctor-holds.js +80 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +216 -537
- package/dist/src/doctor/doctor-notify.js +9 -5
- package/dist/src/doctor/doctor-opencode-checks.js +164 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-pi-checks.js +62 -0
- package/dist/src/doctor/doctor-project-checks.js +397 -0
- package/dist/src/doctor/doctor.js +126 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +21 -12
- package/dist/src/events/event-format.js +185 -89
- package/dist/src/events/event-labels.js +11 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +52 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +80 -26
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +65 -60
- package/dist/src/failure/failure-render.js +29 -21
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +35 -35
- package/dist/src/files/file-queue.js +17 -0
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +88 -225
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +101 -143
- package/dist/src/gates/maintenance-quota.js +37 -30
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +89 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +54 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +43 -19
- package/dist/src/gui/gui-backlog-endpoints.js +156 -0
- package/dist/src/gui/gui-command.js +26 -13
- package/dist/src/gui/gui-config-commands.js +168 -80
- package/dist/src/gui/gui-endpoint-commands.js +5 -4
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +235 -13
- package/dist/src/gui/gui-prompt-commands.js +26 -20
- package/dist/src/gui/gui-report-endpoints.js +97 -0
- package/dist/src/gui/gui-server.js +26 -5
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +155 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +190 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +28 -17
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +58 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +45 -19
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +214 -0
- package/dist/src/merge/merge-queue-render.js +48 -11
- package/dist/src/merge/merge-queue.js +52 -15
- package/dist/src/merge/merge-requeue.js +103 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +27 -11
- package/dist/src/merge/merge-to-main.js +77 -26
- package/dist/src/merge/merge-top.js +83 -0
- package/dist/src/merge/merge-verify.js +75 -18
- package/dist/src/notes-cli.js +35 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +352 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
- package/dist/src/orchestrator/orchestrator.js +129 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +245 -0
- package/dist/src/orchestrator/scheduling-skip.js +195 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +126 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +26 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +150 -217
- package/dist/src/prompt/notes-backlog.js +184 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +131 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +188 -124
- package/dist/src/report/report-fold.js +130 -54
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +136 -65
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +23 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/diff-rules.js +53 -0
- package/dist/src/review/known-flakes.js +112 -1
- package/dist/src/review/post-merge-review-accept.js +51 -0
- package/dist/src/review/post-merge-review-render.js +112 -0
- package/dist/src/review/post-merge-review.js +239 -0
- package/dist/src/review/protected-edits.js +24 -10
- package/dist/src/review/quarantine-render.js +65 -0
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +180 -401
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +127 -12
- package/dist/src/roles/role-guidance.js +65 -20
- package/dist/src/roles/role-labels.js +7 -5
- package/dist/src/roles/role-render.js +108 -56
- package/dist/src/roles/role-view.js +95 -56
- package/dist/src/roles/roles.js +33 -33
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/ci-parity.js +121 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/run/qa-coverage.js +125 -0
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +175 -0
- package/dist/src/run/run-detail-render.js +91 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +178 -0
- package/dist/src/run/stage-checks-shared.js +65 -0
- package/dist/src/run/stage-name-checks.js +204 -0
- package/dist/src/run/stage-path-checks.js +231 -0
- package/dist/src/run/stage-prose-checks.js +261 -0
- package/dist/src/runner/leftover.js +216 -0
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +191 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +109 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +103 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claim-entries.js +49 -0
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +49 -17
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +43 -28
- package/dist/src/status/status-roles.js +50 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +7 -8
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +65 -18
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +60 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +78 -13
- package/dist/src/ui/badges.js +99 -38
- package/dist/src/ui/gui/gui-client-backlog.js +314 -0
- package/dist/src/ui/gui/gui-client-boot.js +89 -61
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
- package/dist/src/ui/gui/gui-client-drawer.js +155 -289
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +437 -68
- package/dist/src/ui/gui/gui-client-labels.js +6 -3
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +60 -52
- package/dist/src/ui/gui/gui-client-pending.js +405 -24
- package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
- package/dist/src/ui/gui/gui-client-report.js +105 -430
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +163 -5
- package/dist/src/ui/gui/gui-client-team.js +36 -465
- package/dist/src/ui/gui/gui-client.js +38 -26
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +10 -7
- package/dist/src/ui/gui/gui-styles.js +38 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +364 -49
- package/dist/src/ui/status-render.js +109 -82
- package/dist/src/ui/team-alerts.js +728 -62
- package/dist/src/ui/tone.js +12 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +53 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +4 -12
- package/dist/src/ui/tui/tui-editor-backends.js +127 -0
- package/dist/src/ui/tui/tui-editor-brief.js +14 -36
- package/dist/src/ui/tui/tui-editor-budget.js +36 -39
- package/dist/src/ui/tui/tui-editor-check.js +29 -0
- package/dist/src/ui/tui/tui-editor-checks.js +28 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
- package/dist/src/ui/tui/tui-editor-config.js +99 -0
- package/dist/src/ui/tui/tui-editor-entry.js +39 -64
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +46 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
- package/dist/src/ui/tui/tui-editor-interval.js +29 -16
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +9 -29
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
- package/dist/src/ui/tui/tui-editor-retire.js +5 -13
- package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +101 -34
- package/dist/src/ui/tui/tui-editor-slots.js +24 -0
- package/dist/src/ui/tui/tui-editors.js +44 -2
- package/dist/src/ui/tui/tui-frame.js +68 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +33 -9
- package/dist/src/ui/tui/tui-pane-activity.js +35 -19
- package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +168 -8
- package/dist/src/ui/tui/tui-pane-history.js +446 -82
- package/dist/src/ui/tui/tui-pane-pending.js +539 -15
- package/dist/src/ui/tui/tui-pane-queued.js +78 -57
- package/dist/src/ui/tui/tui-pane-report.js +54 -16
- package/dist/src/ui/tui/tui-pane-session.js +34 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +91 -28
- package/dist/src/ui/tui/tui-panes.js +153 -34
- package/dist/src/ui/tui/tui.js +18 -15
- package/dist/src/verdict/fix-claim.js +129 -33
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +77 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/leftover.js +0 -130
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/qa-coverage.js +0 -108
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
/** Building the claude command line for one tick (plans/agent-backends.md "The claude
|
|
1
|
+
/** Building the claude command line for one run (plans/agent-backends.md "The claude
|
|
3
2
|
* invocation") — pure argv construction plus the prompt-on-stdin pairing, with no subprocess,
|
|
4
3
|
* backend registration, or session ledger. Part 3c supplies the session decision (which
|
|
5
4
|
* `--session-id` or `--resume` to pass); this module turns those inputs into the invocation's
|
|
@@ -17,13 +16,14 @@
|
|
|
17
16
|
import fs from "node:fs";
|
|
18
17
|
import path from "node:path";
|
|
19
18
|
import { appendPassthroughArgs } from "../agent-args.js";
|
|
19
|
+
import { writeGuardSettings } from "./claude-write-guard.js";
|
|
20
20
|
/** Flags the harness passes itself. A passthrough arg equal to one of these is appended AFTER
|
|
21
21
|
* the harness's own copy, and claude's argument parser is last-wins — so it would silently
|
|
22
22
|
* override the isolation flags, the session choice, or the model the harness resolved. Each is
|
|
23
23
|
* rejected by claudeArgs with the offending arg named. This is exactly the design's reserved
|
|
24
|
-
* set (plans/agent-backends.md "Config"); config validation (config-validation.ts)
|
|
25
|
-
* set for `backends.claude.args`, so a value that passes validation is never
|
|
26
|
-
* 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. */
|
|
27
27
|
export const HARNESS_CLAUDE_FLAGS = new Set([
|
|
28
28
|
"-p",
|
|
29
29
|
"--print",
|
|
@@ -39,6 +39,7 @@ export const HARNESS_CLAUDE_FLAGS = new Set([
|
|
|
39
39
|
"--permission-mode",
|
|
40
40
|
"--tools",
|
|
41
41
|
"--setting-sources",
|
|
42
|
+
"--settings",
|
|
42
43
|
"--strict-mcp-config",
|
|
43
44
|
"--mcp-config",
|
|
44
45
|
"--disable-slash-commands",
|
|
@@ -89,7 +90,9 @@ export function claudeArgs(opts) {
|
|
|
89
90
|
args.push("--session-id", opts.sessionId);
|
|
90
91
|
else
|
|
91
92
|
args.push("--resume", opts.resumeSessionId);
|
|
92
|
-
args.push("--permission-mode", "bypassPermissions", "--tools", "Bash,Read,Edit,Write,Glob,Grep", "--setting-sources", "project",
|
|
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");
|
|
93
96
|
args.push("--disable-slash-commands", "--exclude-dynamic-system-prompt-sections");
|
|
94
97
|
if (shouldAppendAgents(opts.cwd))
|
|
95
98
|
args.push("--append-system-prompt-file", "AGENTS.md");
|
|
@@ -17,11 +17,11 @@ import fs from "node:fs";
|
|
|
17
17
|
import os from "node:os";
|
|
18
18
|
import path from "node:path";
|
|
19
19
|
import { randomUUID } from "node:crypto";
|
|
20
|
-
import {
|
|
20
|
+
import { resolvedBackendConfig } from "../../config/config-views.js";
|
|
21
21
|
import { MODEL_TIERS } from "../../config/config-schema.js";
|
|
22
22
|
import { routeModel } from "../../config/model-selector.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { backendRunEnv } from "../../process/run-marker.js";
|
|
24
|
+
import { resolveBinForBackend } from "../../pi/pi-bin.js";
|
|
25
25
|
import { expandHomeTilde } from "../../files/files.js";
|
|
26
26
|
import { claudeArgs } from "./claude-args.js";
|
|
27
27
|
import { claudeEnv, claudeThirdPartyProvider } from "./claude-env.js";
|
|
@@ -58,7 +58,7 @@ export function claudeTranscriptPath(cwd, id, env = process.env, homeDir = os.ho
|
|
|
58
58
|
/** The CLI version every gated claude model needs (plans/agent-backends.md finding 9): on
|
|
59
59
|
* 2.1.216 Opus 5.5 is refused outright and Haiku 5.5 is mispriced about 250×, and team runs
|
|
60
60
|
* set DISABLE_AUTOUPDATER so they never update themselves. The gated models are the three
|
|
61
|
-
* tier defaults, derived from
|
|
61
|
+
* tier defaults, derived from BACKEND_DEFAULT_MODELS through the same routing the config uses
|
|
62
62
|
* rather than listed again, so this table cannot drift from the models a seam resolves. */
|
|
63
63
|
const CLAUDE_MIN_VERSION = "2.1.280";
|
|
64
64
|
const CLAUDE_GATED_MODELS = new Set(MODEL_TIERS.map((tier) => routeModel(undefined, undefined, "claude", tier).model).filter((model) => model !== undefined));
|
|
@@ -114,7 +114,7 @@ export const claudeBackend = {
|
|
|
114
114
|
binEnv: "TUMWATER_CLAUDE_BIN",
|
|
115
115
|
minVersion: claudeMinVersion,
|
|
116
116
|
resolveBin(config) {
|
|
117
|
-
return
|
|
117
|
+
return resolveBinForBackend(config, this);
|
|
118
118
|
},
|
|
119
119
|
invocation(run) {
|
|
120
120
|
// Part 4b/7's config routing fills a claude tier default before the seam, so a nameless
|
|
@@ -124,7 +124,7 @@ export const claudeBackend = {
|
|
|
124
124
|
}
|
|
125
125
|
// The configured team config dir (plans/notes-command.md "Keeping funes out of team runs"):
|
|
126
126
|
// the run's env, its resume lookup, and the account seed all follow it; unset, nothing changes.
|
|
127
|
-
const claudeCfg =
|
|
127
|
+
const claudeCfg = resolvedBackendConfig(run.config, "claude");
|
|
128
128
|
const configDir = claudeCfg.configDir;
|
|
129
129
|
const expandedConfigDir = configDir !== undefined ? claudeConfigDir(process.env, os.homedir(), configDir) : undefined;
|
|
130
130
|
const { sessionId, resumeSessionId } = resolveClaudeSession(run, configDir);
|
|
@@ -134,7 +134,7 @@ export const claudeBackend = {
|
|
|
134
134
|
thinking: run.config.thinking,
|
|
135
135
|
...(resumeSessionId !== undefined ? { resumeSessionId } : { sessionId }),
|
|
136
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).
|
|
137
|
+
// builder rejects any entry that repeats a harness-managed flag). resolvedBackendConfig
|
|
138
138
|
// owns the view so a caller cannot mutate the loaded config through the returned array.
|
|
139
139
|
args: claudeCfg.args,
|
|
140
140
|
prompt: run.prompt,
|
|
@@ -142,12 +142,12 @@ export const claudeBackend = {
|
|
|
142
142
|
// adapter forwards it; a subscription run or the director passes none (part 7a/7).
|
|
143
143
|
...(run.maxBudgetUsd !== undefined ? { maxBudgetUsd: run.maxBudgetUsd } : {}),
|
|
144
144
|
});
|
|
145
|
-
//
|
|
146
|
-
// claudeEnv layers the backend's own scrub-and-set on top. The
|
|
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
147
|
// the marker the same way pi's adapter carries it (withNotesPath); from part 2/4 the run
|
|
148
148
|
// saves its note through the `tumwater-notes` command on PATH, so no MCP server reaches the
|
|
149
149
|
// run and `--strict-mcp-config` (always in claude-args.ts) keeps it that way.
|
|
150
|
-
const base =
|
|
150
|
+
const base = backendRunEnv(run);
|
|
151
151
|
const env = claudeEnv(base, {
|
|
152
152
|
quietTimeoutSeconds: run.config.quietTimeoutSeconds,
|
|
153
153
|
...(expandedConfigDir !== undefined ? { configDir: expandedConfigDir } : {}),
|
|
@@ -1,7 +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
|
-
*
|
|
4
|
+
* notes on first (src/process/run-marker.ts's `withNotesPath`), then hands the result here.
|
|
5
5
|
*
|
|
6
6
|
* Inherited Claude Code session variables break auth: spawned from inside a Claude Code
|
|
7
7
|
* session, a child that keeps `CLAUDECODE`, `CLAUDE_CODE_*`, `CLAUDE_AGENT_SDK_*` or the host's
|
|
@@ -14,6 +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 { joinParts } from "../../text/text.js";
|
|
18
|
+
import { secondsUntil } from "../../text/datetime.js";
|
|
17
19
|
import { canonicalUsageBlock, isJsonObject, objectOrEmpty, nonNegativeNumber, parseJsonObject } from "../../files/json-object.js";
|
|
18
20
|
/** claude's tool names → the canonical pi names the readers already know. Glob/Grep are kept
|
|
19
21
|
* lower-cased; MultiEdit collapses onto edit (pi has one edit tool). Anything else is
|
|
@@ -86,7 +88,7 @@ function classifyResult(result) {
|
|
|
86
88
|
return {};
|
|
87
89
|
if (terminal === "error_max_budget_usd")
|
|
88
90
|
return { kind: "budgetCapped" };
|
|
89
|
-
const text = [str(result.result), str(result.error)]
|
|
91
|
+
const text = joinParts([str(result.result), str(result.error)], " ");
|
|
90
92
|
if (/error_max_budget_usd/i.test(text))
|
|
91
93
|
return { kind: "budgetCapped" };
|
|
92
94
|
const status = nonNegativeNumber(result.api_error_status, undefined);
|
|
@@ -193,7 +195,7 @@ export function claudeTranslator(opts = {}) {
|
|
|
193
195
|
const maxAttempts = nonNegativeNumber(event.max_retries, undefined);
|
|
194
196
|
const status = nonNegativeNumber(event.error_status, undefined);
|
|
195
197
|
const error = str(event.error) ?? "";
|
|
196
|
-
const errorMessage = [status !== undefined ? `status ${status}` : "", error]
|
|
198
|
+
const errorMessage = joinParts([status !== undefined ? `status ${status}` : "", error], ": ");
|
|
197
199
|
return [
|
|
198
200
|
JSON.stringify({
|
|
199
201
|
type: "auto_retry_start",
|
|
@@ -294,8 +296,8 @@ export function claudeTranslator(opts = {}) {
|
|
|
294
296
|
if (info.isUsingOverage === true)
|
|
295
297
|
out.isUsingOverage = true;
|
|
296
298
|
if (status === "rejected" && resetsAt !== undefined) {
|
|
297
|
-
// resetsAt is a unix-seconds stamp;
|
|
298
|
-
out.retryAfterSeconds =
|
|
299
|
+
// resetsAt is a unix-seconds stamp; secondsUntil folds it (×1000) to whole seconds.
|
|
300
|
+
out.retryAfterSeconds = secondsUntil(resetsAt * 1000, Date.now());
|
|
299
301
|
}
|
|
300
302
|
return [JSON.stringify(out), ...windows];
|
|
301
303
|
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** The Codex run's reserved flags and config keys plus the effort mapping — the pure,
|
|
2
|
+
* build-agnostic half of the codex invocation (plans/codex-backend.md "Config and routing" §2
|
|
3
|
+
* and "The invocation" §3). Part 4/9 owns these constants so config validation and the traits
|
|
4
|
+
* layer can screen a `backends.codex.args` passthrough before the adapter registered; part 5/9
|
|
5
|
+
* adds the `codexArgs` argv builder beside them, and 6/9 the stream translator. Pure — imports
|
|
6
|
+
* no runtime registry, config or subprocess — so config-validation.ts can import it without
|
|
7
|
+
* dragging the adapter in.
|
|
8
|
+
*
|
|
9
|
+
* Everything here is proved against Codex 0.163.0: every flag exists on that build, and every
|
|
10
|
+
* `-c` key passes the `--strict-config` device (design finding 20). */
|
|
11
|
+
/** Flags the harness invocation sets itself (§3): the output and model flags, the sandbox and
|
|
12
|
+
* approval switches, the isolation switches, and the where-and-what-it-runs switches. A
|
|
13
|
+
* passthrough arg equal to one of these is appended AFTER the harness's own copy and Codex's
|
|
14
|
+
* parser is last-wins, so it would silently override the model, the isolation or the run
|
|
15
|
+
* location the harness resolved. Each is rejected with the offending arg named. This is the
|
|
16
|
+
* design's reserved set (§2 "Reserved passthrough flags"); config validation
|
|
17
|
+
* (config-validation-agents.ts) mirrors it for `backends.codex.args`, so a value that passes
|
|
18
|
+
* validation is never accepted there and then thrown in the builder.
|
|
19
|
+
*
|
|
20
|
+
* Operators keep `-c`, `--enable` and `--disable` for their own settings — those keys are
|
|
21
|
+
* screened separately (HARNESS_CODEX_CONFIG_KEYS). */
|
|
22
|
+
import { appendPassthroughArgs } from "../agent-args.js";
|
|
23
|
+
export const HARNESS_CODEX_FLAGS = new Set([
|
|
24
|
+
// Output and model: tumwater forces JSON output, the resolved model, and a normal color for
|
|
25
|
+
// the transcript log.
|
|
26
|
+
"--json",
|
|
27
|
+
"-m",
|
|
28
|
+
"--model",
|
|
29
|
+
"-o",
|
|
30
|
+
"--output-last-message",
|
|
31
|
+
"--output-schema",
|
|
32
|
+
"--color",
|
|
33
|
+
// Sandbox and approvals: tumwater runs every job with bypass approval and no sandbox
|
|
34
|
+
// (decision 2), so an operator cannot re-arm an approval prompt or a sandbox mid-run.
|
|
35
|
+
"-s",
|
|
36
|
+
"--sandbox",
|
|
37
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
38
|
+
"--approve-for-me",
|
|
39
|
+
"--dangerously-bypass-hook-trust",
|
|
40
|
+
// Isolation: tumwater ignores the user's config, rules and persistent state so one team run
|
|
41
|
+
// cannot read a personal hook, skill or trust entry.
|
|
42
|
+
"--ignore-user-config",
|
|
43
|
+
"--ignore-rules",
|
|
44
|
+
"--ephemeral",
|
|
45
|
+
// Where and what it runs: tumwater pins the cwd and the worktree and refuses `--oss` /
|
|
46
|
+
// `--local-provider` so a run can never target something other than the resolved backend.
|
|
47
|
+
"-C",
|
|
48
|
+
"--cd",
|
|
49
|
+
"--worktree",
|
|
50
|
+
"--add-dir",
|
|
51
|
+
"--skip-git-repo-check",
|
|
52
|
+
"-p",
|
|
53
|
+
"--profile",
|
|
54
|
+
"--oss",
|
|
55
|
+
"--local-provider",
|
|
56
|
+
]);
|
|
57
|
+
/** The keys tumwater sets on every run via `-c <key>=…` (§3), so an operator's `-c` passthrough
|
|
58
|
+
* whose key is one of these would silently override the model, the effort, the sandbox mode, or
|
|
59
|
+
* the telemetry/state settings the harness already passed. A `-c` or `--config` passthrough whose
|
|
60
|
+
* key names one of these is refused; any other key is the operator's own and passes. */
|
|
61
|
+
export const HARNESS_CODEX_CONFIG_KEYS = new Set([
|
|
62
|
+
"model",
|
|
63
|
+
"model_reasoning_effort",
|
|
64
|
+
"sandbox_mode",
|
|
65
|
+
"project_doc_fallback_filenames",
|
|
66
|
+
"web_search",
|
|
67
|
+
"agents.enabled",
|
|
68
|
+
"check_for_update_on_startup",
|
|
69
|
+
"analytics.enabled",
|
|
70
|
+
"feedback.enabled",
|
|
71
|
+
"history.persistence",
|
|
72
|
+
]);
|
|
73
|
+
/** Config-key prefixes an operator's `-c`/`--config` passthrough may not set: the write guard's
|
|
74
|
+
* own `hooks.*` keys (part 3/9). Codex is last-wins, so a `-c hooks.PreToolUse=…` or
|
|
75
|
+
* `-c hooks.state=…` passthrough would otherwise replace the guard or its trust after the
|
|
76
|
+
* harness's. Written beside HARNESS_CODEX_CONFIG_KEYS so both it and config validation screen
|
|
77
|
+
* against one predicate. */
|
|
78
|
+
const HARNESS_CODEX_CONFIG_KEY_PREFIXES = ["hooks"];
|
|
79
|
+
/** Whether `key` is a config key tumwater sets or reserves, so an operator's `-c`/`--config`
|
|
80
|
+
* passthrough naming it would silently override the harness. True for every
|
|
81
|
+
* HARNESS_CODEX_CONFIG_KEYS key and for the `hooks` key or any `hooks.<…>` subkey, which the
|
|
82
|
+
* write guard owns. */
|
|
83
|
+
export function isHarnessCodexConfigKey(key) {
|
|
84
|
+
if (HARNESS_CODEX_CONFIG_KEYS.has(key))
|
|
85
|
+
return true;
|
|
86
|
+
return HARNESS_CODEX_CONFIG_KEY_PREFIXES.some((p) => key === p || key.startsWith(`${p}.`));
|
|
87
|
+
}
|
|
88
|
+
/** Thinking → `model_reasoning_effort`. `off` and `minimal` clamp to `low` (Codex has no lower
|
|
89
|
+
* setting; doctor notes the clamp), `low` through `max` map 1:1. `ultra` is not a value Codex
|
|
90
|
+
* can receive — tumwater never sends it (it delegates to sub-agents). This is claude's
|
|
91
|
+
* EFFORT_BY_THINKING rule moved to codex, where the level the model's catalog lists also gates
|
|
92
|
+
* the result (part 5/9's `codexEffort`). */
|
|
93
|
+
export const EFFORT_BY_THINKING = {
|
|
94
|
+
off: "low",
|
|
95
|
+
minimal: "low",
|
|
96
|
+
low: "low",
|
|
97
|
+
medium: "medium",
|
|
98
|
+
high: "high",
|
|
99
|
+
xhigh: "xhigh",
|
|
100
|
+
max: "max",
|
|
101
|
+
};
|
|
102
|
+
/** The effort values tumwater will ever send as `model_reasoning_effort`: the set
|
|
103
|
+
* EFFORT_BY_THINKING can produce. `ultra` (which some models add for sub-agent delegation) is
|
|
104
|
+
* deliberately not here —
|
|
105
|
+
* tumwater never sends it, so `codexArgs` rejects it rather than let a whole agent hierarchy
|
|
106
|
+
* spawn on a model that lists it. */
|
|
107
|
+
const KNOWN_EFFORTS = new Set(Object.values(EFFORT_BY_THINKING));
|
|
108
|
+
/** Map a thinking level to the Codex `model_reasoning_effort` it means, gated by the model's own
|
|
109
|
+
* catalog. `levels` is the model's `supported_reasoning_levels` (design finding 14), which a
|
|
110
|
+
* stream-minted catalog (part 8/9) supplies; until then the caller passes `undefined`, and the
|
|
111
|
+
* result is returned unclamped. An unknown thinking level returns `undefined` (no effort flag) —
|
|
112
|
+
* the level simply has no Codex effort, unlike a thinking level that maps but the model does not
|
|
113
|
+
* support, which is also `undefined`. */
|
|
114
|
+
export function codexEffort(thinking, levels) {
|
|
115
|
+
if (!Object.hasOwn(EFFORT_BY_THINKING, thinking))
|
|
116
|
+
return undefined;
|
|
117
|
+
const result = EFFORT_BY_THINKING[thinking];
|
|
118
|
+
if (levels !== undefined && !levels.includes(result))
|
|
119
|
+
return undefined;
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
/** Build one run's codex argv and stdin. The prompt is never an argv element (codex reads `-`
|
|
123
|
+
* input from stdin). Throws on an unknown effort, or a passthrough arg that repeats a harness
|
|
124
|
+
* flag, since each would otherwise silently change what the harness asked for. */
|
|
125
|
+
export function codexArgs(opts) {
|
|
126
|
+
if (opts.effort !== undefined && !KNOWN_EFFORTS.has(opts.effort)) {
|
|
127
|
+
throw new Error(`codexArgs: unknown effort ${JSON.stringify(opts.effort)} (known: ${[...KNOWN_EFFORTS].join(", ")})`);
|
|
128
|
+
}
|
|
129
|
+
// Isolation flags, then the `--disable` list (design finding 7), then the `-c` keys (finding
|
|
130
|
+
// 20), then the model and effort, then the operator's passthrough, then the closing `-`.
|
|
131
|
+
const head = [
|
|
132
|
+
"--json",
|
|
133
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
134
|
+
"--ignore-user-config",
|
|
135
|
+
"--ignore-rules",
|
|
136
|
+
"--disable",
|
|
137
|
+
"plugins",
|
|
138
|
+
"--disable",
|
|
139
|
+
"apps",
|
|
140
|
+
"--disable",
|
|
141
|
+
"goals",
|
|
142
|
+
"--disable",
|
|
143
|
+
"image_generation",
|
|
144
|
+
"--disable",
|
|
145
|
+
"browser_use",
|
|
146
|
+
"--disable",
|
|
147
|
+
"computer_use",
|
|
148
|
+
"--disable",
|
|
149
|
+
"tool_suggest",
|
|
150
|
+
"--disable",
|
|
151
|
+
"skill_search",
|
|
152
|
+
"-c",
|
|
153
|
+
"agents.enabled=false",
|
|
154
|
+
"-c",
|
|
155
|
+
'web_search="disabled"',
|
|
156
|
+
"-c",
|
|
157
|
+
"check_for_update_on_startup=false",
|
|
158
|
+
"-c",
|
|
159
|
+
"analytics.enabled=false",
|
|
160
|
+
"-c",
|
|
161
|
+
"feedback.enabled=false",
|
|
162
|
+
"-c",
|
|
163
|
+
'history.persistence="none"',
|
|
164
|
+
"-c",
|
|
165
|
+
'project_doc_fallback_filenames=["CLAUDE.md"]',
|
|
166
|
+
// The write guard's hook and trust ride the same `-c` layer, after the harness's own
|
|
167
|
+
// keys and before `-m` (plans/codex-write-guard.md "Runs (part 6)"); an untrusted hook
|
|
168
|
+
// would be skipped silently, so a backend with no trust passes nothing here.
|
|
169
|
+
...(opts.guardArgs ?? []),
|
|
170
|
+
"-m",
|
|
171
|
+
opts.model,
|
|
172
|
+
];
|
|
173
|
+
if (opts.effort !== undefined)
|
|
174
|
+
head.push("-c", `model_reasoning_effort=${opts.effort}`);
|
|
175
|
+
const args = opts.resumeId === undefined
|
|
176
|
+
? ["exec", ...head]
|
|
177
|
+
: ["exec", "resume", opts.resumeId, ...head];
|
|
178
|
+
appendPassthroughArgs(args, opts.args, HARNESS_CODEX_FLAGS, "codexArgs");
|
|
179
|
+
args.push("-");
|
|
180
|
+
return { args, stdin: opts.prompt };
|
|
181
|
+
}
|