tumwater 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +42 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +4 -3
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +246 -55
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +85 -274
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +175 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +139 -32
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +9 -11
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget.js +30 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +1 -2
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +118 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +150 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +308 -590
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
- package/dist/src/cli/completion.js +149 -22
- package/dist/src/cli/config-commands.js +41 -18
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +189 -73
- package/dist/src/cli/log-commands.js +148 -82
- package/dist/src/cli/question-commands.js +205 -10
- package/dist/src/cli.js +106 -43
- package/dist/src/collections.js +26 -10
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +13 -1
- package/dist/src/config/config-fallbacks.js +93 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +141 -199
- package/dist/src/config/config-views.js +127 -53
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +131 -264
- package/dist/src/config/config.js +48 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +100 -496
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +52 -579
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +82 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +236 -402
- package/dist/src/doctor/doctor-notify.js +3 -4
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor.js +121 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +7 -11
- package/dist/src/events/event-format.js +181 -86
- package/dist/src/events/event-labels.js +12 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +39 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +60 -24
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +56 -56
- package/dist/src/failure/failure-render.js +31 -23
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +99 -35
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +177 -84
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +108 -58
- package/dist/src/gates/maintenance-quota.js +10 -13
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +77 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +36 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +42 -18
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +8 -13
- package/dist/src/gui/gui-config-commands.js +94 -72
- package/dist/src/gui/gui-endpoint-commands.js +4 -3
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +108 -13
- package/dist/src/gui/gui-prompt-commands.js +11 -14
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +11 -4
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +193 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +25 -9
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +81 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +150 -11
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/merge/merge-queue-render.js +49 -11
- package/dist/src/merge/merge-queue.js +31 -15
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +33 -11
- package/dist/src/merge/merge-to-main.js +86 -27
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +12 -10
- package/dist/src/notes-cli.js +30 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
- package/dist/src/orchestrator/orchestrator.js +128 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +116 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +18 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +121 -211
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +129 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +161 -77
- package/dist/src/report/report-fold.js +129 -52
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +133 -64
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +100 -1
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +4 -5
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +155 -360
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +69 -12
- package/dist/src/roles/role-guidance.js +55 -20
- package/dist/src/roles/role-labels.js +1 -2
- package/dist/src/roles/role-render.js +67 -50
- package/dist/src/roles/role-view.js +59 -47
- package/dist/src/roles/roles.js +31 -31
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +9 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/{loop → runner}/leftover.js +80 -25
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +43 -16
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +28 -28
- package/dist/src/status/status-roles.js +39 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +5 -6
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +63 -17
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +57 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +67 -13
- package/dist/src/ui/badges.js +65 -37
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +32 -26
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +126 -285
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +434 -67
- package/dist/src/ui/gui/gui-client-labels.js +1 -2
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +55 -51
- package/dist/src/ui/gui/gui-client-pending.js +227 -23
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +87 -427
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +56 -4
- package/dist/src/ui/gui/gui-client-team.js +25 -465
- package/dist/src/ui/gui/gui-client.js +27 -25
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +9 -7
- package/dist/src/ui/gui/gui-styles.js +34 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +136 -49
- package/dist/src/ui/status-render.js +101 -82
- package/dist/src/ui/team-alerts.js +248 -57
- package/dist/src/ui/tone.js +5 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +51 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +3 -5
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +10 -12
- package/dist/src/ui/tui/tui-editor-budget.js +35 -31
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-entry.js +36 -47
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +11 -9
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +3 -5
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
- package/dist/src/ui/tui/tui-editor-retire.js +4 -6
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +60 -34
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +35 -2
- package/dist/src/ui/tui/tui-frame.js +56 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +24 -9
- package/dist/src/ui/tui/tui-pane-activity.js +14 -11
- package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +58 -8
- package/dist/src/ui/tui/tui-pane-history.js +402 -82
- package/dist/src/ui/tui/tui-pane-pending.js +308 -13
- package/dist/src/ui/tui/tui-pane-queued.js +71 -55
- package/dist/src/ui/tui/tui-pane-report.js +46 -14
- package/dist/src/ui/tui/tui-pane-session.js +25 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +80 -23
- package/dist/src/ui/tui/tui-panes.js +100 -30
- package/dist/src/ui/tui/tui.js +14 -14
- package/dist/src/verdict/fix-claim.js +82 -32
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +42 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import { childEnv } from "../process/process.js";
|
|
3
|
+
import { boundedProbe } from "./probe.js";
|
|
4
|
+
import { claudeBackend, claudeConfigDir } from "../agent/claude/claude-backend.js";
|
|
5
|
+
import { claudeEnv } from "../agent/claude/claude-env.js";
|
|
6
|
+
import { claudeAuthUsesSubscription, parseClaudeAuthStatus } from "../agent/claude/claude-account.js";
|
|
7
|
+
import { resolvedBackendConfig } from "../config/config-views.js";
|
|
8
|
+
import { envPath } from "../files/files.js";
|
|
9
|
+
import { belowNodeFloor } from "../version.js";
|
|
10
|
+
import { failOutcome, okOutcome, warnOutcome } from "./outcome.js";
|
|
11
|
+
import { AUTH_CHECK_MAX_OUTPUT_BYTES, AUTH_CHECK_TIMEOUT_MS, claudeSeamsInUse, loadConfigForCheck, seamedModels, } from "./doctor-model-checks.js";
|
|
12
|
+
/** The doctor's claude credential and version readiness checks, split out of
|
|
13
|
+
* doctor-model-checks.ts (whose shared seam/model framework and the pi, tier-models and
|
|
14
|
+
* image-input checks stay there): checkClaudeAuth, checkAnthroApiKey and checkClaudeVersion.
|
|
15
|
+
* These are the doctor's only checks that spawn the `claude` binary and read its auth — a
|
|
16
|
+
* distinct concern from the filesystem/git checks, kept together so a change to how doctor
|
|
17
|
+
* spawns or scrubs claude does not touch the other model checks. The shared report contract
|
|
18
|
+
* (CheckOutcome) and the seam/model framework (loadConfigForCheck, claudeSeamsInUse,
|
|
19
|
+
* seamPairs, the AUTH_CHECK_* budgets) come from doctor-model-checks.ts and outcome.ts,
|
|
20
|
+
* exactly as the other doctor-* modules share them. */
|
|
21
|
+
/** One bounded probe of the resolved `claude` binary under the scrubbed run environment a real
|
|
22
|
+
* run gets: resolve the binary through the claude adapter that would spawn it and run `args`
|
|
23
|
+
* with a short deadline. Null when the probe could not run, the child errored, or the deadline
|
|
24
|
+
* passed. The one home of the resolveBin + claudeEnv pairing the auth and version checks share,
|
|
25
|
+
* so neither probes a different binary or environment than the other. */
|
|
26
|
+
function claudeProbe(config, args, pathEnv, timeoutMs) {
|
|
27
|
+
const resolved = claudeBackend.resolveBin(config);
|
|
28
|
+
// The configured team config dir must reach the probe's env the way a real run's does, or
|
|
29
|
+
// the auth/version rows would look in claude's default dir (usually ~/.claude) while a run
|
|
30
|
+
// with backends.claude.configDir writes and reads elsewhere — the very mismatch doctor exists
|
|
31
|
+
// to surface. Mirror backends.run's expansion: only claude accepts configDir, and unset leaves
|
|
32
|
+
// today's env byte-identical.
|
|
33
|
+
const configDir = resolvedBackendConfig(config, "claude").configDir;
|
|
34
|
+
const expandedConfigDir = configDir !== undefined ? claudeConfigDir(process.env, os.homedir(), configDir) : undefined;
|
|
35
|
+
const env = claudeEnv(childEnv({ PATH: pathEnv }), {
|
|
36
|
+
quietTimeoutSeconds: 0,
|
|
37
|
+
...(expandedConfigDir !== undefined ? { configDir: expandedConfigDir } : {}),
|
|
38
|
+
});
|
|
39
|
+
return boundedProbe(resolved.bin, args, env, timeoutMs, AUTH_CHECK_MAX_OUTPUT_BYTES);
|
|
40
|
+
}
|
|
41
|
+
/** Run `claude auth status` under the same scrubbed environment a real run gets (claudeEnv),
|
|
42
|
+
* or null when the probe could not run or its reply was unreadable. */
|
|
43
|
+
async function claudeAuthProbe(config, pathEnv, timeoutMs) {
|
|
44
|
+
const result = await claudeProbe(config, ["auth", "status"], pathEnv, timeoutMs);
|
|
45
|
+
if (result?.code !== 0)
|
|
46
|
+
return null;
|
|
47
|
+
return parseClaudeAuthStatus(result.stdout);
|
|
48
|
+
}
|
|
49
|
+
/** claude credential readiness (plans/agent-backends.md "Every seam, checked"): a claude seam
|
|
50
|
+
* whose login is gone fails every run before it can work, so doctor runs `claude auth status`
|
|
51
|
+
* under the scrubbed run environment and fails when it reports `loggedIn: false`. An ok detail
|
|
52
|
+
* names the billing the reply implies — the subscription type, or an API key — so an operator
|
|
53
|
+
* telling subscription spend from API spend does not have to run the command themselves. A
|
|
54
|
+
* probe that could not run or a reply this cannot read warns rather than guesses. Stands down
|
|
55
|
+
* (ok) when no seam routes to claude, so a pi-only team spawns nothing. */
|
|
56
|
+
export async function checkClaudeAuth(root, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
|
|
57
|
+
const config = loadConfigForCheck(root);
|
|
58
|
+
if ("level" in config)
|
|
59
|
+
return config;
|
|
60
|
+
if (!claudeSeamsInUse(config))
|
|
61
|
+
return okOutcome("no model jobs run on claude");
|
|
62
|
+
const auth = await claudeAuthProbe(config, pathEnv, timeoutMs);
|
|
63
|
+
if (auth === null)
|
|
64
|
+
return warnOutcome("could not check — `claude auth status` did not run or its output was unreadable");
|
|
65
|
+
if (!auth.loggedIn)
|
|
66
|
+
return failOutcome("claude reports logged in: false — run `claude auth login`");
|
|
67
|
+
const subscription = claudeAuthUsesSubscription(auth);
|
|
68
|
+
const billing = subscription
|
|
69
|
+
? `subscription${auth.subscriptionType !== undefined ? ` (${auth.subscriptionType})` : ""}`
|
|
70
|
+
: "API key";
|
|
71
|
+
return okOutcome(`logged in — billing: ${billing}`);
|
|
72
|
+
}
|
|
73
|
+
/** The subscription-vs-API billing trap (plans/provider-concurrency.md §9 gap 6): an operator
|
|
74
|
+
* who exports `ANTHROPIC_API_KEY` for pi's `anthropic` provider will silently move every claude
|
|
75
|
+
* seam to API billing — claude prefers the key over a claude.ai login — and subscription seams
|
|
76
|
+
* stop counting as free. Doctor warns when the key is set, some seam routes to claude, and
|
|
77
|
+
* `claude auth status` reports a claude.ai login (the subscription the key would override). It
|
|
78
|
+
* stays silent (ok) when no seam runs on claude, when the key is unset, or when claude reports
|
|
79
|
+
* an API-key login (then there is no subscription to lose). `env` is injectable so the check
|
|
80
|
+
* stays hermetic; a probe that cannot run warns rather than guesses like checkClaudeAuth. */
|
|
81
|
+
export async function checkAnthroApiKey(root, env = process.env, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
|
|
82
|
+
const config = loadConfigForCheck(root);
|
|
83
|
+
if ("level" in config)
|
|
84
|
+
return config;
|
|
85
|
+
if (!claudeSeamsInUse(config))
|
|
86
|
+
return okOutcome("no model jobs run on claude");
|
|
87
|
+
const apiKey = env.ANTHROPIC_API_KEY;
|
|
88
|
+
if (apiKey === undefined || apiKey === "")
|
|
89
|
+
return okOutcome("no ANTHROPIC_API_KEY set");
|
|
90
|
+
const auth = await claudeAuthProbe(config, pathEnv, timeoutMs);
|
|
91
|
+
if (auth === null)
|
|
92
|
+
return warnOutcome("could not check — `claude auth status` did not run or its output was unreadable");
|
|
93
|
+
if (!auth.loggedIn || !claudeAuthUsesSubscription(auth))
|
|
94
|
+
return okOutcome("ANTHROPIC_API_KEY is set, but claude reports no claude.ai login");
|
|
95
|
+
return warnOutcome("ANTHROPIC_API_KEY is set while claude reports a claude.ai login — model jobs on claude will bill the API key, not the subscription; unset it or route every anthropic model job on pi explicitly");
|
|
96
|
+
}
|
|
97
|
+
/** The dotted version in `claude --version`'s output, or undefined when it names none. */
|
|
98
|
+
function parseClaudeCliVersion(output) {
|
|
99
|
+
return /(\d+\.\d+\.\d+)/.exec(output)?.[1];
|
|
100
|
+
}
|
|
101
|
+
/** claude CLI-version readiness (plans/agent-backends.md finding 9): an installed CLI below a
|
|
102
|
+
* configured model's minimum fails or misprices that model, and team runs disable the
|
|
103
|
+
* auto-updater, so the version an operator has is the version agent runs use. Fails below the
|
|
104
|
+
* highest floor among the claude-routed models, naming which model needs it; an ok detail
|
|
105
|
+
* prints the installed version and the floor it satisfied (or no floor when none applies).
|
|
106
|
+
* The `off`/`minimal` thinking clamp is noted here too, since this check already has the
|
|
107
|
+
* claude seams in hand. Stands down when no seam routes to claude. */
|
|
108
|
+
export async function checkClaudeVersion(root, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
|
|
109
|
+
const config = loadConfigForCheck(root);
|
|
110
|
+
if ("level" in config)
|
|
111
|
+
return config;
|
|
112
|
+
const seams = seamedModels(config, "claude");
|
|
113
|
+
if (seams.length === 0)
|
|
114
|
+
return okOutcome("no model jobs run on claude");
|
|
115
|
+
// The floor each configured model needs; a model the adapter does not gate contributes none.
|
|
116
|
+
const floors = new Map();
|
|
117
|
+
for (const seam of seams) {
|
|
118
|
+
const min = claudeBackend.minVersion?.(seam.model);
|
|
119
|
+
if (min !== undefined)
|
|
120
|
+
floors.set(seam.model, min);
|
|
121
|
+
}
|
|
122
|
+
const result = await claudeProbe(config, ["--version"], pathEnv, timeoutMs);
|
|
123
|
+
const found = result !== null && result.code === 0 ? parseClaudeCliVersion(result.stdout) : undefined;
|
|
124
|
+
if (found === undefined)
|
|
125
|
+
return warnOutcome("could not check — `claude --version` did not run or its output was unreadable");
|
|
126
|
+
const clamped = seams.some((seam) => seam.thinking === "off" || seam.thinking === "minimal");
|
|
127
|
+
const clampNote = clamped
|
|
128
|
+
? "; `off`/`minimal` thinking clamps to `--effort low` (claude has no lower setting)"
|
|
129
|
+
: "";
|
|
130
|
+
if (floors.size === 0)
|
|
131
|
+
return okOutcome(`claude ${found}${clampNote}`);
|
|
132
|
+
// The highest floor decides: whichever model needs the newest CLI. Below it, fail and name
|
|
133
|
+
// that model; at or above, print the version against the floor.
|
|
134
|
+
let required = "";
|
|
135
|
+
let neededBy = "";
|
|
136
|
+
for (const [model, min] of floors) {
|
|
137
|
+
if (required === "" || belowNodeFloor(required, `>=${min}`)) {
|
|
138
|
+
required = min;
|
|
139
|
+
neededBy = model;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (belowNodeFloor(found, `>=${required}`))
|
|
143
|
+
return failOutcome(`claude ${found} is older than ${required} required by ${neededBy} — run \`claude update\``);
|
|
144
|
+
return okOutcome(`claude ${found} (>= ${required} for ${neededBy})${clampNote}`);
|
|
145
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { childEnv } from "../process/process.js";
|
|
4
|
+
import { boundedProbe } from "./probe.js";
|
|
5
|
+
import { codexBackend, CODEX_MIN_VERSION } from "../agent/codex/codex-backend.js";
|
|
6
|
+
import { codexEffort, EFFORT_BY_THINKING } from "../agent/codex/codex-args.js";
|
|
7
|
+
import { codexEnv } from "../agent/codex/codex-env.js";
|
|
8
|
+
import { codexHome, readCodexCatalog } from "../agent/codex/codex-catalog.js";
|
|
9
|
+
import { codexHookTrust } from "../agent/codex/codex-hook-trust.js";
|
|
10
|
+
import { parseCodexLoginStatus } from "../agent/codex/codex-account.js";
|
|
11
|
+
import { codexWriteGuardDefinition } from "../agent/codex/codex-write-guard.js";
|
|
12
|
+
import { resolvedBackendConfig } from "../config/config-views.js";
|
|
13
|
+
import { envPath, isDirectory, readTextOrNull, statOrNull, treeBytes } from "../files/files.js";
|
|
14
|
+
import { formatBytes, tokenWindowLabel } from "../text/format.js";
|
|
15
|
+
import { errorMessage } from "../text/text.js";
|
|
16
|
+
import { failOutcome, okOutcome, warnOutcome } from "./outcome.js";
|
|
17
|
+
import { AUTH_CHECK_MAX_OUTPUT_BYTES, AUTH_CHECK_TIMEOUT_MS, codexSeamsInUse, loadConfigForCheck, minVersionOutcome, seamedModels, } from "./doctor-model-checks.js";
|
|
18
|
+
/** The doctor's codex rows (part 9a/9, plans/codex-backend.md §4, §7, "Hand steps"): one
|
|
19
|
+
* exported check per row — install/version, login, home, models, account, leaks, sessions
|
|
20
|
+
* size and the write guard. These are the doctor's only checks that spawn the `codex` CLI and
|
|
21
|
+
* read the team's
|
|
22
|
+
* `CODEX_HOME` state (catalog, skills, hooks, sessions, rollouts) — a distinct concern from the
|
|
23
|
+
* filesystem/git checks, kept together so a change to how doctor reads codex does not touch the
|
|
24
|
+
* other model checks. Each returns null (omitting its row) when no seam routes to codex, so a
|
|
25
|
+
* pi-only team shows no codex row and spawns nothing and reads nothing. The shared
|
|
26
|
+
* report contract (CheckOutcome) and the seam/model framework (loadConfigForCheck,
|
|
27
|
+
* codexSeamsInUse, seamPairs, the AUTH_CHECK_* budgets) come from doctor-model-checks.ts and
|
|
28
|
+
* outcome.ts, as the other doctor-* modules share them. */
|
|
29
|
+
/** The config and codex home a codex readiness row reads, or the warn outcome a broken
|
|
30
|
+
* `tumwater.json` earns, or null when no seam routes to codex (the row's stand-down). All
|
|
31
|
+
* eight rows repeat the same three-line guard and the same home lookup
|
|
32
|
+
* (`codexHome(resolvedBackendConfig(config, "codex"))`); this folds them so a change to how
|
|
33
|
+
* doctor reads codex's home touches one spot instead of seven. `home` is always the team's
|
|
34
|
+
* codex home, whether or not the row needs it. */
|
|
35
|
+
function codexCheckBase(root) {
|
|
36
|
+
const config = loadConfigForCheck(root);
|
|
37
|
+
if ("level" in config)
|
|
38
|
+
return config;
|
|
39
|
+
if (!codexSeamsInUse(config))
|
|
40
|
+
return null;
|
|
41
|
+
return { config, home: codexHome(resolvedBackendConfig(config, "codex")) };
|
|
42
|
+
}
|
|
43
|
+
/** One bounded probe of the resolved `codex` binary under the scrubbed run environment a real
|
|
44
|
+
* run gets: resolve the binary through the codex adapter that would spawn it, point it at the
|
|
45
|
+
* run's home (`CODEX_HOME`), and run `args` with a short deadline. Null when the probe could
|
|
46
|
+
* not run, the child errored, or the deadline passed. The one home of the resolveBin + codexEnv
|
|
47
|
+
* pairing the version and login rows share, so neither probes a different binary or environment
|
|
48
|
+
* than the other. */
|
|
49
|
+
function codexProbe(config, args, pathEnv, timeoutMs) {
|
|
50
|
+
const resolved = codexBackend.resolveBin(config);
|
|
51
|
+
const home = codexHome(resolvedBackendConfig(config, "codex"));
|
|
52
|
+
const env = codexEnv(childEnv({ PATH: pathEnv }), { home });
|
|
53
|
+
return boundedProbe(resolved.bin, args, env, timeoutMs, AUTH_CHECK_MAX_OUTPUT_BYTES);
|
|
54
|
+
}
|
|
55
|
+
/** The dotted version in `codex --version`'s output, or undefined when it names none. */
|
|
56
|
+
function parseCodexCliVersion(output) {
|
|
57
|
+
return /(\d+\.\d+\.\d+)/.exec(output)?.[1];
|
|
58
|
+
}
|
|
59
|
+
/** codex CLI-version readiness (plans/codex-backend.md "Hand steps", H1): the team targets
|
|
60
|
+
* `CODEX_MIN_VERSION` or later, the version H1 verified live. An installed CLI below that fails
|
|
61
|
+
* naming the floor and the upgrade; at or above it reads the version. Stands down when no seam
|
|
62
|
+
* routes to codex. */
|
|
63
|
+
export async function checkCodexVersion(root, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
|
|
64
|
+
const base = codexCheckBase(root);
|
|
65
|
+
if (base === null || "level" in base)
|
|
66
|
+
return base;
|
|
67
|
+
const result = await codexProbe(base.config, ["--version"], pathEnv, timeoutMs);
|
|
68
|
+
const found = result?.code === 0 ? parseCodexCliVersion(result.stdout) : undefined;
|
|
69
|
+
return minVersionOutcome("codex", found, CODEX_MIN_VERSION, "upgrade the codex CLI");
|
|
70
|
+
}
|
|
71
|
+
/** codex login readiness (plans/codex-backend.md §4, §7): a ChatGPT login is subscription
|
|
72
|
+
* billing and passes; an API-key login is refused (decision 5 — Codex reports no cost, so an
|
|
73
|
+
* API key would spend money the cap cannot see) with the startup gate's message; a logged-out
|
|
74
|
+
* home fails naming the login command under the run's `CODEX_HOME`. A probe that could not run
|
|
75
|
+
* or a reply this cannot read (neither a ChatGPT login, an API key, nor an explicit
|
|
76
|
+
* not-logged-in) warns rather than guesses. Stands down when no seam routes to codex. */
|
|
77
|
+
export async function checkCodexAuth(root, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
|
|
78
|
+
const base = codexCheckBase(root);
|
|
79
|
+
if (base === null || "level" in base)
|
|
80
|
+
return base;
|
|
81
|
+
const { home } = base;
|
|
82
|
+
const result = await codexProbe(base.config, ["login", "status"], pathEnv, timeoutMs);
|
|
83
|
+
if (result?.code !== 0)
|
|
84
|
+
return warnOutcome("could not check — `codex login status` did not run or its output was unreadable");
|
|
85
|
+
const status = parseCodexLoginStatus(result.stdout);
|
|
86
|
+
if (status === "subscription")
|
|
87
|
+
return okOutcome("logged in using ChatGPT — billing: subscription");
|
|
88
|
+
if (status === "api")
|
|
89
|
+
return failOutcome(`Codex is logged in with an API key; tumwater runs Codex on a ChatGPT subscription only — run \`CODEX_HOME=${home} codex login --device-auth\``);
|
|
90
|
+
if (/Not logged in/i.test(result.stdout ?? ""))
|
|
91
|
+
return failOutcome(`codex is not logged in — run \`CODEX_HOME=${home} codex login --device-auth\``);
|
|
92
|
+
return warnOutcome("could not check — `codex login status` reply was unreadable");
|
|
93
|
+
}
|
|
94
|
+
/** codex home readiness (plans/codex-backend.md §4): unset `backends.codex.configDir` warns
|
|
95
|
+
* that team runs share the operator's `~/.codex` — the trust entries, team sessions in the
|
|
96
|
+
* `codex resume` picker, any skills and a global `AGENTS.md`. Set, it reads the team home.
|
|
97
|
+
* Stands down when no seam routes to codex. */
|
|
98
|
+
export async function checkCodexHome(root) {
|
|
99
|
+
const base = codexCheckBase(root);
|
|
100
|
+
if (base === null || "level" in base)
|
|
101
|
+
return base;
|
|
102
|
+
const configDir = resolvedBackendConfig(base.config, "codex").configDir;
|
|
103
|
+
if (configDir === undefined)
|
|
104
|
+
return warnOutcome("backends.codex.configDir is unset — team runs share the user's ~/.codex (trust entries, team sessions in the resume picker, skills, a global AGENTS.md); set backends.codex.configDir to a home of the team's own");
|
|
105
|
+
return okOutcome(`team codex home: ${codexHome({ configDir })}`);
|
|
106
|
+
}
|
|
107
|
+
/** codex model readiness (plans/codex-backend.md §7): every codex-routed model must be in the
|
|
108
|
+
* home's catalog (`models_cache.json`) — a missing model warns that its thinking levels and
|
|
109
|
+
* window are unknown, and a thinking level the catalog does not list warns that it is dropped.
|
|
110
|
+
* The cataloged models are reported with their context window. Stands down when no seam routes
|
|
111
|
+
* to codex. */
|
|
112
|
+
export async function checkCodexModels(root) {
|
|
113
|
+
const base = codexCheckBase(root);
|
|
114
|
+
if (base === null || "level" in base)
|
|
115
|
+
return base;
|
|
116
|
+
const { config, home } = base;
|
|
117
|
+
const catalog = readCodexCatalog(home);
|
|
118
|
+
const models = seamedModels(config, "codex");
|
|
119
|
+
const warns = [];
|
|
120
|
+
const notes = [];
|
|
121
|
+
for (const pair of models) {
|
|
122
|
+
const entry = catalog.get(pair.model);
|
|
123
|
+
if (entry === undefined) {
|
|
124
|
+
warns.push(`${pair.model} is not in the codex catalog (models_cache.json) — thinking levels and the window are unknown`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const window = tokenWindowLabel(entry.contextWindow);
|
|
128
|
+
notes.push(`${pair.model} — ${window}`);
|
|
129
|
+
// A level is dropped when the run path would send no effort for it: `codexEffort` maps the
|
|
130
|
+
// thinking through EFFORT_BY_THINKING (off/minimal clamp to low) and yields undefined when
|
|
131
|
+
// the model's catalog does not list that effort (part 5/9's codexEffort). A thinking level
|
|
132
|
+
// with no mapping at all (e.g. ultra — never sent) is not a catalog gap, so it is not warned.
|
|
133
|
+
if (pair.thinking !== undefined &&
|
|
134
|
+
pair.thinking !== "" &&
|
|
135
|
+
Object.hasOwn(EFFORT_BY_THINKING, pair.thinking) &&
|
|
136
|
+
codexEffort(pair.thinking, entry.levels) === undefined) {
|
|
137
|
+
warns.push(`${pair.model} has no ${pair.thinking} thinking level in the catalog — that level is dropped`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (warns.length > 0) {
|
|
141
|
+
const extra = notes.length > 0 ? `; ${notes.join("; ")}` : "";
|
|
142
|
+
return warnOutcome(`${warns.join("; ")}${extra}`);
|
|
143
|
+
}
|
|
144
|
+
return okOutcome(notes.length > 0 ? `codex models ready — ${notes.join("; ")}` : "no codex-routed models declared");
|
|
145
|
+
}
|
|
146
|
+
/** A `has_credits` reading from the home's rollout captures, or null when none names it.
|
|
147
|
+
* `has_credits` travels in each run's `token_count.rate_limits.credits` (design §7), so the
|
|
148
|
+
* account row reads the captures a finished run left rather than a live probe — the same
|
|
149
|
+
* source the runtime account is fed from. The newest capture (by file mtime) wins,
|
|
150
|
+
* approximating the account's current state — readdir order is filesystem order, not
|
|
151
|
+
* chronological, so the captures are sorted by mtime first. A missing reading is the safe
|
|
152
|
+
* direction (nothing to warn about). */
|
|
153
|
+
function codexSessionCredits(home) {
|
|
154
|
+
const sessions = path.join(home, "sessions");
|
|
155
|
+
if (!isDirectory(sessions))
|
|
156
|
+
return null;
|
|
157
|
+
const rollouts = [];
|
|
158
|
+
const walk = (dir) => {
|
|
159
|
+
let entries;
|
|
160
|
+
try {
|
|
161
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
const p = path.join(dir, entry.name);
|
|
168
|
+
if (entry.isDirectory())
|
|
169
|
+
walk(p);
|
|
170
|
+
else if (entry.isFile() && /^rollout-.*\.jsonl$/.test(entry.name)) {
|
|
171
|
+
const st = statOrNull(p);
|
|
172
|
+
if (st !== null)
|
|
173
|
+
rollouts.push({ file: p, mtimeMs: st.mtimeMs });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
walk(sessions);
|
|
178
|
+
rollouts.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
179
|
+
for (const r of rollouts) {
|
|
180
|
+
const text = readTextOrNull(r.file);
|
|
181
|
+
if (text === null)
|
|
182
|
+
continue;
|
|
183
|
+
const m = /"has_credits"\s*:\s*(true|false)/.exec(text);
|
|
184
|
+
if (m !== null)
|
|
185
|
+
return m[1] === "true";
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
/** codex account readiness (plans/codex-backend.md §7 "Credits"): a Plus account can buy
|
|
190
|
+
* credits that run past the windows, and tumwater never spends them on purpose but cannot see
|
|
191
|
+
* the amount, so doctor warns when the runs' captures report `has_credits: true` — the run that
|
|
192
|
+
* crosses a limit may draw on them. Otherwise it reads ok with the safe direction. Stands down
|
|
193
|
+
* when no seam routes to codex. */
|
|
194
|
+
export async function checkCodexAccount(root) {
|
|
195
|
+
const base = codexCheckBase(root);
|
|
196
|
+
if (base === null || "level" in base)
|
|
197
|
+
return base;
|
|
198
|
+
const { home } = base;
|
|
199
|
+
const hasCredits = codexSessionCredits(home);
|
|
200
|
+
if (hasCredits === true)
|
|
201
|
+
return warnOutcome("the codex account can buy credits (has_credits: true) — a run that crosses a limit may spend credits tumwater cannot count; clear the credit balance or accept the blind spend");
|
|
202
|
+
return okOutcome(hasCredits === false ? "no credits — runs stop at the subscription window" : "no credit reading yet — runs stop at the subscription window");
|
|
203
|
+
}
|
|
204
|
+
/** codex home leaks (plans/codex-backend.md §4): the operator's home `AGENTS.md`, any
|
|
205
|
+
* `skills/` entry beyond `.system`, any `hooks/` entry, and the target repo's
|
|
206
|
+
* `.codex/config.toml` MCP servers each still load into team runs, so each is named as one
|
|
207
|
+
* warning. Stands down when no seam routes to codex. */
|
|
208
|
+
export async function checkCodexLeaks(root) {
|
|
209
|
+
const base = codexCheckBase(root);
|
|
210
|
+
if (base === null || "level" in base)
|
|
211
|
+
return base;
|
|
212
|
+
const { home } = base;
|
|
213
|
+
const warns = [];
|
|
214
|
+
const agentsMd = path.join(home, "AGENTS.md");
|
|
215
|
+
if (statOrNull(agentsMd)?.isFile() === true)
|
|
216
|
+
warns.push(`${agentsMd} still loads into team runs`);
|
|
217
|
+
const skills = path.join(home, "skills");
|
|
218
|
+
if (isDirectory(skills)) {
|
|
219
|
+
for (const entry of fs.readdirSync(skills)) {
|
|
220
|
+
if (entry === ".system")
|
|
221
|
+
continue;
|
|
222
|
+
warns.push(`${path.join(skills, entry)} — a skill beyond .system still loads into team runs`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const hooks = path.join(home, "hooks");
|
|
226
|
+
if (isDirectory(hooks)) {
|
|
227
|
+
for (const entry of fs.readdirSync(hooks))
|
|
228
|
+
warns.push(`${path.join(hooks, entry)} — a hook still loads into team runs`);
|
|
229
|
+
}
|
|
230
|
+
// The target repo's own `.codex/config.toml` MCP servers (design §4): a repo-local config
|
|
231
|
+
// with an `[mcp_servers.NAME]` section loads those servers into every team run here.
|
|
232
|
+
const repoConfig = path.join(root, ".codex", "config.toml");
|
|
233
|
+
const text = readTextOrNull(repoConfig);
|
|
234
|
+
if (text !== null) {
|
|
235
|
+
const servers = [...text.matchAll(/^\s*\[mcp_servers\.([^\]]+)\]\s*$/gm)].map((m) => m[1]);
|
|
236
|
+
for (const server of servers)
|
|
237
|
+
warns.push(`${repoConfig} configures MCP server ${server}, which loads into team runs`);
|
|
238
|
+
}
|
|
239
|
+
if (warns.length === 0)
|
|
240
|
+
return okOutcome("no codex home leaks");
|
|
241
|
+
return warnOutcome(warns.join("; "));
|
|
242
|
+
}
|
|
243
|
+
/** The size of the home's `sessions/` (plans/codex-backend.md §4): the retention knob prunes
|
|
244
|
+
* expired sessions, and doctor reports how much the collection holds so the operator can size
|
|
245
|
+
* the knob. Stands down when no seam routes to codex. */
|
|
246
|
+
export async function checkCodexSessions(root) {
|
|
247
|
+
const base = codexCheckBase(root);
|
|
248
|
+
if (base === null || "level" in base)
|
|
249
|
+
return base;
|
|
250
|
+
const { home } = base;
|
|
251
|
+
const sessions = path.join(home, "sessions");
|
|
252
|
+
const size = isDirectory(sessions) ? treeBytes(sessions) : null;
|
|
253
|
+
return okOutcome(size !== null ? `codex sessions: ${formatBytes(size)}` : "codex sessions: no sessions dir");
|
|
254
|
+
}
|
|
255
|
+
/** The bound on the codex write-guard row's trust lookup, matching the group's other calls. */
|
|
256
|
+
const CODEX_HOOK_TRUST_TIMEOUT_MS = 10_000;
|
|
257
|
+
/** codex write-guard readiness (plans/codex-write-guard.md "Visibility (parts 8–9)"): whether
|
|
258
|
+
* codex runs the trusted write-guard hook under this team's home, so the operator sees whether
|
|
259
|
+
* codex edits are guarded and why not when they are not. Asks codex's own `hooks/list` through
|
|
260
|
+
* codexHookTrust for the current build's definition, bounded like the group's other Codex calls;
|
|
261
|
+
* the cache file it writes is the one writable surface Doctor keeps. A trust reading gives the
|
|
262
|
+
* pass row naming the hash's first 12 hex; a failed lookup warns with its reason — codex then
|
|
263
|
+
* runs edits without the worktree check. Stands down when no seam routes to codex. */
|
|
264
|
+
export async function checkCodexWriteGuard(root, pathEnv = envPath(), timeoutMs = CODEX_HOOK_TRUST_TIMEOUT_MS) {
|
|
265
|
+
const base = codexCheckBase(root);
|
|
266
|
+
if (base === null || "level" in base)
|
|
267
|
+
return base;
|
|
268
|
+
const resolved = codexBackend.resolveBin(base.config);
|
|
269
|
+
const { home } = base;
|
|
270
|
+
const env = codexEnv(childEnv({ PATH: pathEnv }), { home });
|
|
271
|
+
const definition = codexWriteGuardDefinition();
|
|
272
|
+
try {
|
|
273
|
+
const trust = await codexHookTrust(root, { bin: resolved.bin, env, cwd: root, definition, timeoutMs });
|
|
274
|
+
return okOutcome(`trusted (sha256:${trust.hash.replace(/^sha256:/, "").slice(0, 12)})`);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
return warnOutcome(`off: ${errorMessage(error)} — codex runs edit without the worktree check`);
|
|
278
|
+
}
|
|
279
|
+
}
|