tumwater 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -28
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +23 -0
- package/dist/src/agent/agent-backend.js +9 -7
- package/dist/src/agent/backend-traits.js +77 -0
- package/dist/src/agent/claude/claude-account.js +81 -161
- package/dist/src/agent/claude/claude-args.js +19 -19
- package/dist/src/agent/claude/claude-backend.js +58 -48
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +11 -19
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +112 -0
- package/dist/src/agent/notes-shim.js +40 -0
- package/dist/src/agent/opencode/opencode-args.js +60 -0
- package/dist/src/agent/opencode/opencode-backend.js +258 -0
- package/dist/src/agent/opencode/opencode-catalog.js +210 -0
- package/dist/src/agent/opencode/opencode-config.js +193 -0
- package/dist/src/agent/opencode/opencode-env.js +35 -0
- package/dist/src/agent/opencode/opencode-session-id.js +36 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +16 -33
- package/dist/src/agent/run-agent.js +475 -381
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +142 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +487 -0
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +384 -0
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +354 -132
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +132 -22
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +89 -11
- package/dist/src/backlog/backlog-structure.js +271 -125
- package/dist/src/backlog/backlog-write.js +121 -471
- package/dist/src/backlog/backlog.js +129 -17
- package/dist/src/backlog/question-answer.js +345 -0
- package/dist/src/baseline/main-baseline.js +157 -101
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +164 -55
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +41 -0
- package/dist/src/{brief.js → brief/brief.js} +38 -33
- package/dist/src/budget/budget.js +55 -28
- package/dist/src/budget/fallback-breaker.js +31 -31
- package/dist/src/build/build-check-detect.js +25 -16
- package/dist/src/build/build-check-events.js +87 -22
- package/dist/src/build/build-check-report.js +66 -27
- package/dist/src/build/build-check-scoped.js +64 -68
- package/dist/src/build/build-check.js +17 -15
- package/dist/src/build/build-info.js +3 -3
- package/dist/src/build/build-stage.js +31 -35
- package/dist/src/build/dep-install.js +21 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +131 -37
- package/dist/src/change/change-render.js +56 -14
- package/dist/src/cli/brief-command.js +9 -8
- package/dist/src/cli/cli-args.js +133 -28
- package/dist/src/cli/cli-command-args.js +82 -21
- package/dist/src/cli/cli-editor.js +15 -2
- package/dist/src/cli/cli-flag-specs.js +180 -50
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +14 -11
- package/dist/src/cli/cli-query-commands.js +325 -224
- package/dist/src/cli/cli-repo-gate.js +12 -2
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/cli-start.js +453 -0
- package/dist/src/cli/completion.js +202 -17
- package/dist/src/cli/config-commands.js +51 -25
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +413 -157
- package/dist/src/cli/log-commands.js +234 -62
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +215 -231
- package/dist/src/cli.js +154 -49
- package/dist/src/collections.js +84 -18
- package/dist/src/concurrency/check-permit.js +35 -19
- package/dist/src/concurrency/lock.js +22 -17
- package/dist/src/concurrency/provider-lease.js +105 -0
- package/dist/src/concurrency/semaphore.js +344 -46
- package/dist/src/config/config-aliases.js +105 -0
- package/dist/src/config/config-editable-keys.js +16 -4
- package/dist/src/config/config-fallbacks.js +416 -0
- package/dist/src/config/config-field-checks.js +58 -19
- package/dist/src/config/config-keys.js +182 -0
- package/dist/src/config/config-live.js +113 -22
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +58 -39
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +201 -175
- package/dist/src/config/config-views.js +202 -346
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +219 -391
- package/dist/src/config/config.js +102 -81
- package/dist/src/config/model-selector.js +44 -18
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +191 -161
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +229 -305
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +181 -104
- package/dist/src/doctor/doctor-install.js +31 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +496 -270
- package/dist/src/doctor/doctor-notify.js +5 -7
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +189 -69
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +145 -50
- package/dist/src/doctor/image-probe.js +257 -0
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +60 -0
- package/dist/src/events/event-format.js +363 -155
- package/dist/src/events/event-labels.js +123 -0
- package/dist/src/events/event-legacy.js +164 -0
- package/dist/src/events/event-read.js +74 -38
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +83 -27
- package/dist/src/events/events.js +47 -26
- package/dist/src/events/notify.js +85 -35
- package/dist/src/failure/failure-cluster.js +23 -20
- package/dist/src/failure/failure-data.js +140 -92
- package/dist/src/failure/failure-render.js +97 -78
- package/dist/src/failure/failure-state-change.js +45 -33
- package/dist/src/failure/time-spend.js +166 -82
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +129 -41
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +45 -9
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +2 -2
- package/dist/src/gates/bootstrap-gates.js +8 -8
- package/dist/src/gates/budget-gates.js +361 -52
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +201 -103
- package/dist/src/gates/maintenance-quota.js +34 -37
- package/dist/src/gates/pause-gates.js +33 -15
- package/dist/src/gates/readiness.js +8 -3
- package/dist/src/gates/role-cap-gates.js +23 -28
- package/dist/src/gates/startup-gate.js +154 -48
- package/dist/src/gates/streak-gate.js +67 -41
- package/dist/src/git/commit-message.js +80 -48
- package/dist/src/git/git-diff.js +29 -23
- package/dist/src/git/git-run.js +87 -46
- package/dist/src/git/git.js +114 -92
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +18 -9
- package/dist/src/git/worktree-pool.js +150 -49
- package/dist/src/git/worktree-use.js +14 -20
- package/dist/src/git/worktree.js +108 -51
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +88 -21
- package/dist/src/gui/gui-backlog-commands.js +297 -0
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +9 -14
- package/dist/src/gui/gui-config-commands.js +462 -0
- package/dist/src/gui/gui-endpoint-commands.js +17 -716
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +13 -331
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +240 -0
- package/dist/src/gui/gui-prompt-commands.js +231 -0
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +33 -9
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +166 -203
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +96 -0
- package/dist/src/history/history.js +206 -103
- package/dist/src/inbox/inbox-attachments.js +94 -39
- package/dist/src/inbox/inbox-cancel.js +16 -15
- package/dist/src/inbox/inbox-edit.js +10 -10
- package/dist/src/inbox/inbox-submit.js +54 -14
- package/dist/src/inbox/inbox-top.js +99 -0
- package/dist/src/inbox/inbox.js +49 -44
- package/dist/src/inbox/pending-prompt.js +27 -27
- package/dist/src/inbox/prompt-commands.js +154 -100
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +15 -14
- package/dist/src/init/init.js +35 -29
- package/dist/src/merge/backlog-conflicts.js +335 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +100 -93
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +209 -0
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +183 -110
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +6 -5
- package/dist/src/merge/merge-drain.js +351 -0
- package/dist/src/merge/merge-git.js +403 -0
- package/dist/src/merge/merge-pipeline.js +111 -0
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +113 -0
- package/dist/src/merge/merge-queue.js +224 -0
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +385 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +97 -71
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +151 -82
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +214 -0
- package/dist/src/notes-cli.js +112 -0
- package/dist/src/operator/operator-commands.js +268 -89
- package/dist/src/operator/operator-intent.js +130 -82
- package/dist/src/operator/operator-requests.js +82 -87
- package/dist/src/operator/retire.js +49 -31
- package/dist/src/operator/revert.js +179 -0
- package/dist/src/orchestrator/orchestrator-launch.js +116 -48
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +78 -348
- package/dist/src/orchestrator/orchestrator.js +173 -361
- package/dist/src/orchestrator/retention.js +66 -15
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +52 -0
- package/dist/src/orchestrator/startup-migrations.js +44 -0
- package/dist/src/paths.js +228 -75
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +30 -5
- package/dist/src/pi/pi-backend.js +19 -27
- package/dist/src/pi/pi-bin.js +64 -67
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +163 -42
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +137 -113
- package/dist/src/pi/pi-watchdogs.js +20 -20
- package/dist/src/pi-extension/bounded-output.js +25 -8
- package/dist/src/pi-extension/context-budget.js +5 -3
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +36 -0
- package/dist/src/pi-extension/full-output.js +5 -12
- package/dist/src/pi-extension/strip-thinking.js +79 -0
- package/dist/src/pi-extension/trim-system-prompt.js +37 -0
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +11 -11
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +48 -9
- package/dist/src/process/run-marker.js +42 -7
- package/dist/src/process/supervisor.js +47 -35
- package/dist/src/prompt/dialect.js +46 -11
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +201 -188
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -1
- package/dist/src/prompt/prompt-followup.js +43 -29
- package/dist/src/prompt/prompt.js +255 -172
- package/dist/src/redeploy/redeploy-policy.js +29 -24
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +7 -6
- package/dist/src/redeploy/redeployer.js +78 -70
- package/dist/src/redeploy/self-reload.js +3 -2
- package/dist/src/report/report-data.js +374 -285
- package/dist/src/report/report-fold.js +430 -0
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +321 -61
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +133 -14
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +7 -7
- package/dist/src/review/review-followup.js +21 -9
- package/dist/src/review/review-precheck.js +77 -33
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +16 -2
- package/dist/src/review/review.js +172 -356
- package/dist/src/review/secret-leaks.js +147 -0
- package/dist/src/review/suite-rerun.js +3 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +135 -0
- package/dist/src/roles/role-catalog.js +172 -137
- package/dist/src/roles/role-guidance.js +112 -44
- package/dist/src/roles/role-labels.js +9 -0
- package/dist/src/roles/role-render.js +221 -28
- package/dist/src/roles/role-view.js +244 -33
- package/dist/src/roles/roles.js +37 -35
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +10 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +166 -136
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/run/run-finalize.js +154 -0
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -6
- package/dist/src/run/run-prompt.js +304 -0
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +80 -35
- package/dist/src/run/run-stage.js +295 -0
- package/dist/src/run/run-timing.js +198 -0
- package/dist/src/run/run-usage.js +180 -0
- package/dist/src/run/run-verdict.js +301 -0
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/runner/leftover.js +185 -0
- package/dist/src/{loop → runner}/model-fallback.js +13 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +10 -10
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/runner/runner-pi.js +433 -0
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +34 -28
- package/dist/src/runner/runner-run.js +625 -0
- package/dist/src/runner/runner-state-load.js +268 -0
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/runner/runner.js +350 -0
- package/dist/src/scheduling/backoff.js +64 -54
- package/dist/src/scheduling/claims.js +46 -26
- package/dist/src/scheduling/once-round.js +30 -30
- package/dist/src/scheduling/quiet-hours.js +26 -34
- package/dist/src/scheduling/scheduling.js +87 -74
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +123 -0
- package/dist/src/status/status-data.js +106 -191
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +26 -0
- package/dist/src/status/status-polls.js +70 -59
- package/dist/src/status/status-roles.js +131 -0
- package/dist/src/{fleet → team}/error-storm.js +16 -16
- package/dist/src/{fleet → team}/failure-spread.js +10 -10
- package/dist/src/{fleet → team}/orchestrator-info.js +97 -25
- package/dist/src/{fleet → team}/reclaim.js +9 -9
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +60 -26
- package/dist/src/team/team-polls.js +299 -0
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +47 -40
- package/dist/src/text/datetime.js +102 -16
- package/dist/src/text/format.js +49 -3
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +89 -54
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +68 -14
- package/dist/src/ui/badges.js +165 -93
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +221 -42
- package/dist/src/ui/gui/gui-client-composer.js +105 -27
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +226 -130
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +492 -68
- package/dist/src/ui/gui/gui-client-labels.js +23 -0
- package/dist/src/ui/gui/gui-client-markdown.js +1 -1
- package/dist/src/ui/gui/gui-client-model.js +84 -76
- package/dist/src/ui/gui/gui-client-operator.js +112 -78
- package/dist/src/ui/gui/gui-client-pending.js +248 -41
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +122 -179
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +118 -76
- package/dist/src/ui/gui/gui-client-settings.js +180 -20
- package/dist/src/ui/gui/gui-client-team.js +252 -0
- package/dist/src/ui/gui/gui-client.js +48 -40
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +25 -24
- package/dist/src/ui/gui/gui-styles.js +46 -38
- package/dist/src/ui/progress-data.js +11 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +40 -40
- package/dist/src/ui/status-model.js +276 -194
- package/dist/src/ui/status-payload.js +227 -62
- package/dist/src/ui/status-render.js +196 -108
- package/dist/src/ui/team-alerts.js +532 -0
- package/dist/src/ui/tone.js +11 -11
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -19
- package/dist/src/ui/transcript.js +85 -38
- package/dist/src/ui/tui/tui-backlog.js +132 -21
- package/dist/src/ui/tui/tui-editor-answer.js +93 -0
- package/dist/src/ui/tui/tui-editor-attach.js +105 -0
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +105 -0
- package/dist/src/ui/tui/tui-editor-budget.js +159 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +219 -0
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +124 -0
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +112 -0
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +255 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +90 -0
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +94 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +170 -0
- package/dist/src/ui/tui/tui-editor-retire.js +81 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +85 -0
- package/dist/src/ui/tui/tui-editor-session.js +123 -0
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +185 -0
- package/dist/src/ui/tui/tui-frame.js +107 -58
- package/dist/src/ui/tui/tui-history.js +16 -12
- package/dist/src/ui/tui/tui-input.js +56 -60
- package/dist/src/ui/tui/tui-keys.js +106 -1825
- package/dist/src/ui/tui/tui-pane-activity.js +102 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +269 -0
- package/dist/src/ui/tui/tui-pane-brief.js +29 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +174 -0
- package/dist/src/ui/tui/tui-pane-history.js +636 -0
- package/dist/src/ui/tui/tui-pane-pending.js +369 -0
- package/dist/src/ui/tui/tui-pane-queued.js +159 -0
- package/dist/src/ui/tui/tui-pane-report.js +154 -0
- package/dist/src/ui/tui/tui-pane-session.js +120 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +224 -0
- package/dist/src/ui/tui/tui-pane.js +125 -44
- package/dist/src/ui/tui/tui-panes.js +364 -0
- package/dist/src/ui/tui/tui.js +52 -34
- package/dist/src/verdict/fix-claim.js +135 -62
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +80 -24
- package/dist/src/verdict/reply-contract.js +51 -28
- package/dist/src/version.js +2 -2
- package/package.json +5 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/brief-edit.js +0 -42
- package/dist/src/cli/cli-run.js +0 -469
- package/dist/src/fleet/fleet-polls.js +0 -207
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-check-failures.js +0 -141
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-git.js +0 -190
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -56
- package/dist/src/landing/landing-queue.js +0 -149
- package/dist/src/landing/landing-slot.js +0 -256
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/loop/leftover.js +0 -129
- package/dist/src/loop/loop-pi.js +0 -370
- package/dist/src/loop/loop-state.js +0 -204
- package/dist/src/loop/loop-tick.js +0 -545
- package/dist/src/loop/loop.js +0 -315
- package/dist/src/loop/revision.js +0 -61
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/roles/loop-ids.js +0 -60
- package/dist/src/tick/stage-check.js +0 -302
- package/dist/src/tick/telemetry-digest.js +0 -24
- package/dist/src/tick/tick-detail-data.js +0 -107
- package/dist/src/tick/tick-detail.js +0 -150
- package/dist/src/tick/tick-finalize.js +0 -135
- package/dist/src/tick/tick-prompt.js +0 -201
- package/dist/src/tick/tick-stage.js +0 -223
- package/dist/src/tick/tick-timing.js +0 -195
- package/dist/src/tick/tick-usage.js +0 -142
- package/dist/src/tick/tick-verdict.js +0 -201
- package/dist/src/ui/fleet-alerts.js +0 -174
- package/dist/src/ui/gui/gui-client-fleet.js +0 -576
- package/dist/src/ui/gui/gui-client-sound.js +0 -52
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/** The codex adapter for the agent seam (plans/codex-backend.md §4, §5, §6): it shapes one
|
|
2
|
+
* run's argv/stdin/env, translates codex's `exec --json` stream, reads the authoritative
|
|
3
|
+
* rollout after the process exits, and classifies the exit's stderr — everything
|
|
4
|
+
* backend-specific. src/agent/run-agent.ts owns the spawn and records this run in the session
|
|
5
|
+
* ledger; codex mints its own session id (a thread id, or the resumed record's id) in the
|
|
6
|
+
* stream's `thread.started`, which the runner's stream-mint pass appends to the ledger and
|
|
7
|
+
* `invocation` returns so a later `exec resume` can reach it.
|
|
8
|
+
*
|
|
9
|
+
* Sessions resume by id, out of the team's `CODEX_HOME`. A continue takes the ledger's newest
|
|
10
|
+
* record of this run's kind and resumes only when it is codex's, has this cwd, carries a UUID
|
|
11
|
+
* id, and `findRollout` finds its rollout under the run's home — anything else mints a fresh
|
|
12
|
+
* thread and the existing `handoff` path takes over (design §4). Together with the `resumable`
|
|
13
|
+
* hook, that is every resume point's one rule.
|
|
14
|
+
*
|
|
15
|
+
* Registration lives in src/agent/run-agent.ts beside the pi, claude and opencode adapters; a
|
|
16
|
+
* test selects codex by setting `config.agent = "codex"` on a run. A run carrying a note gets
|
|
17
|
+
* the notes path in its environment (the `tumwater-notes` command on PATH reads it), as the
|
|
18
|
+
* pi and claude adapters do. */
|
|
19
|
+
import fs from "node:fs";
|
|
20
|
+
import { loadConfigSafe } from "../../config/config.js";
|
|
21
|
+
import { resolvedBackendConfig } from "../../config/config-views.js";
|
|
22
|
+
import { backendBaseEnv, withNotesPath } from "../../process/run-marker.js";
|
|
23
|
+
import { resolveBinForBackend } from "../../pi/pi-bin.js";
|
|
24
|
+
import { teamRootOf, nearestTumwaterRoot } from "../../paths.js";
|
|
25
|
+
import { warnEventBestEffort, TUMWATER_SOURCE } from "../../events/events.js";
|
|
26
|
+
import { errorMessage } from "../../text/text.js";
|
|
27
|
+
import { codexHome, readCodexCatalog } from "./codex-catalog.js";
|
|
28
|
+
import { resumableSession } from "../session-ledger.js";
|
|
29
|
+
import { codexArgs, codexEffort } from "./codex-args.js";
|
|
30
|
+
import { codexEnv } from "./codex-env.js";
|
|
31
|
+
import { codexHookTrust } from "./codex-hook-trust.js";
|
|
32
|
+
import { codexHookArg, codexHookTrustArg, codexWriteGuardDefinition } from "./codex-write-guard.js";
|
|
33
|
+
import { findRollout, rolloutLines } from "./codex-rollout.js";
|
|
34
|
+
import { codexTranslator } from "./codex-stream.js";
|
|
35
|
+
/** A codex thread id (a UUID format, e.g. `01a15c9d-6762-7931-814e-a3e6e64c1b51`). The
|
|
36
|
+
* stream mints it in `thread.started`; the pre-spawn ledger record carries the session name
|
|
37
|
+
* instead, so this regex is what tells a resume that a record holds a real, stream-minted id
|
|
38
|
+
* worth resuming (design §4). */
|
|
39
|
+
const THREAD_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
40
|
+
/** The oldest codex CLI the team targets, the sibling of `OPENCODE_MIN_VERSION`
|
|
41
|
+
* (src/agent/opencode/opencode-backend.ts) and `CLAUDE_MIN_VERSION`
|
|
42
|
+
* (src/agent/claude/claude-backend.ts): doctor's `codex version` row fails an installed CLI
|
|
43
|
+
* below this floor (plans/codex-backend.md "Hand steps", H1). */
|
|
44
|
+
export const CODEX_MIN_VERSION = "0.163.0";
|
|
45
|
+
/** The wall-clock bound `prepare` gives each trust lookup, mirroring lookupCodexHookTrust's
|
|
46
|
+
* own 15 s default (plans/codex-write-guard.md "Trust (parts 4–5)"). Settable so a test can
|
|
47
|
+
* shorten the bound (to 500 ms) without waiting out the real 15 s; a hand run keeps the
|
|
48
|
+
* default. */
|
|
49
|
+
let codexTrustLookupTimeoutMs = 15_000;
|
|
50
|
+
/** The test seam for the trust lookup bound: a test sets it to 500 ms and returns it to the
|
|
51
|
+
* 15 s default in a finally, so a hanging app-server case stays fast without a config knob. */
|
|
52
|
+
export function setCodexTrustLookupTimeoutMs(ms) {
|
|
53
|
+
codexTrustLookupTimeoutMs = ms;
|
|
54
|
+
}
|
|
55
|
+
/** The write-guard trust `prepare` has resolved, keyed by home and hook definition, so one
|
|
56
|
+
* process looks each up at most once and a run's invocation never spawns `app-server` again. */
|
|
57
|
+
const writeGuardTrust = new Map();
|
|
58
|
+
/** One in-flight resolution per (home, definition) key, so concurrent runs share a single
|
|
59
|
+
* `app-server` instead of starting one each (plans/codex-write-guard.md "Trust (parts 4–5)"). */
|
|
60
|
+
const writeGuardInFlight = new Map();
|
|
61
|
+
/** The (home, definition) keys whose `off` warning has already been logged, so a failed lookup
|
|
62
|
+
* warns once per process instead of once per run. */
|
|
63
|
+
const writeGuardWarned = new Set();
|
|
64
|
+
/** Warn once per process per (home, definition) that the write guard is off. The warning rides
|
|
65
|
+
* the best-effort event feed on the run's root, skipped when there is no root (a hand run's
|
|
66
|
+
* prepared look-up must not mint a `.tumwater`). A `trust` result logs nothing. */
|
|
67
|
+
function warnGuardOffOnce(key, root, state) {
|
|
68
|
+
if (state.kind !== "off")
|
|
69
|
+
return;
|
|
70
|
+
if (writeGuardWarned.has(key))
|
|
71
|
+
return;
|
|
72
|
+
writeGuardWarned.add(key);
|
|
73
|
+
if (root !== undefined) {
|
|
74
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `codex write guard off: ${state.reason}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** The (home, definition) cache key shared by the single-flight and the resolved-trust map. */
|
|
78
|
+
function writeGuardKey(home, definition) {
|
|
79
|
+
return `${home ?? ""}|${JSON.stringify(definition)}`;
|
|
80
|
+
}
|
|
81
|
+
/** The session this run uses: for a continue, the ledger's newest record of this kind when it
|
|
82
|
+
* is codex's, has this cwd, carries a UUID thread id, and `findRollout` finds its rollout
|
|
83
|
+
* under the run's home; otherwise the run is fresh (no pre-minted id — the stream names it).
|
|
84
|
+
* Returns the id and whether this is a resume, so `invocation` can name it and build `exec
|
|
85
|
+
* resume`. */
|
|
86
|
+
function resolveCodexSession(run, home) {
|
|
87
|
+
if (run.continueSession && home !== undefined) {
|
|
88
|
+
const record = resumableSession(run.sessionDir, {
|
|
89
|
+
kind: run.kind,
|
|
90
|
+
agent: "codex",
|
|
91
|
+
cwd: run.cwd,
|
|
92
|
+
});
|
|
93
|
+
if (record !== null && THREAD_ID_RE.test(record.id) && findRollout(home, record.id) !== null) {
|
|
94
|
+
return { sessionId: record.id, resumed: true };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { resumed: false };
|
|
98
|
+
}
|
|
99
|
+
/** Classify codex's stderr: the seam's stderr half for a CLI that died before emitting a
|
|
100
|
+
* `turn.failed` (the stream's own failures are classified by the translator). A run the
|
|
101
|
+
* harness killed has its own cause and is never classified here (design finding 12). The
|
|
102
|
+
* codex CLI surfaces login/model/usage failures as stream JSON, so a bare nonzero exit with
|
|
103
|
+
* no stream carries no signal this part has a verdict for — it stays unclassified and the
|
|
104
|
+
* parser's own event-derived flags stand. */
|
|
105
|
+
function classifyCodexExit(info) {
|
|
106
|
+
if (info.killed)
|
|
107
|
+
return {};
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
/** The agent-backend seam's codex adapter. */
|
|
111
|
+
export const codexBackend = {
|
|
112
|
+
id: "codex",
|
|
113
|
+
label: "Codex",
|
|
114
|
+
defaultBin: "codex",
|
|
115
|
+
binEnv: "TUMWATER_CODEX_BIN",
|
|
116
|
+
resolveBin(config) {
|
|
117
|
+
return resolveBinForBackend(config, this);
|
|
118
|
+
},
|
|
119
|
+
invocation(run) {
|
|
120
|
+
// The notes path rides beside the run marker, as the pi and claude adapters' envs do
|
|
121
|
+
// (withNotesPath); from part 2/4 the run saves its note through the `tumwater-notes`
|
|
122
|
+
// command on PATH, so codex configures no MCP server for notes.
|
|
123
|
+
const base = withNotesPath(backendBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
|
|
124
|
+
// A team home of its own (`CODEX_HOME`) keeps team sessions and trust entries out of the
|
|
125
|
+
// operator's `~/.codex` (design §4, finding 10); unset leaves the operator's home.
|
|
126
|
+
const home = codexHome(resolvedBackendConfig(run.config, "codex"));
|
|
127
|
+
const env = codexEnv(base, { home });
|
|
128
|
+
const { sessionId, resumed } = resolveCodexSession(run, home);
|
|
129
|
+
const thinking = run.config.thinking;
|
|
130
|
+
// The effort (parts 5/9 and 8/9): ride the thinking level through the decoder, then gate
|
|
131
|
+
// it on the model's cataloged effort levels — a level the model does not list is never
|
|
132
|
+
// sent (codexEffort's `levels` half). No home (no catalog) leaves the value unclamped, as
|
|
133
|
+
// before part 8/9.
|
|
134
|
+
const catalog = home === undefined ? new Map() : readCodexCatalog(home);
|
|
135
|
+
const effort = thinking === undefined ? undefined : codexEffort(thinking, catalog.get(run.config.model ?? "")?.levels);
|
|
136
|
+
// The write guard's trust (part 6/9): `prepare` resolved it under this run's env and
|
|
137
|
+
// home and stored it keyed by (home, definition), so `invocation` reads it synchronously.
|
|
138
|
+
// With trust it rides the hook and its trust entry as two `-c` values (after the
|
|
139
|
+
// harness's own keys, before `-m`); with `off` or no state it passes neither, since an
|
|
140
|
+
// untrusted hook would be skipped silently anyway. tumwater never passes
|
|
141
|
+
// `--dangerously-bypass-hook-trust` (findings 1 and 4).
|
|
142
|
+
const definition = codexWriteGuardDefinition();
|
|
143
|
+
const trust = writeGuardTrust.get(writeGuardKey(home, definition));
|
|
144
|
+
const guardArgs = trust?.kind === "trust"
|
|
145
|
+
? ["-c", codexHookArg(definition), "-c", codexHookTrustArg(trust.trust)]
|
|
146
|
+
: undefined;
|
|
147
|
+
const { args, stdin } = codexArgs({
|
|
148
|
+
model: run.config.model ?? "",
|
|
149
|
+
...(effort !== undefined ? { effort } : {}),
|
|
150
|
+
...(resumed && sessionId !== undefined ? { resumeId: sessionId } : {}),
|
|
151
|
+
...(guardArgs !== undefined ? { guardArgs } : {}),
|
|
152
|
+
args: resolvedBackendConfig(run.config, "codex").args,
|
|
153
|
+
prompt: run.prompt,
|
|
154
|
+
});
|
|
155
|
+
// A resumed run returns its id so the runner's pre-spawn ledger record carries it; a fresh
|
|
156
|
+
// run has none until the stream names it (the runner's stream-mint pass appends it).
|
|
157
|
+
return { args, stdin, env, ...(resumed && sessionId !== undefined ? { sessionId } : {}) };
|
|
158
|
+
},
|
|
159
|
+
async prepare(run) {
|
|
160
|
+
// Resolve the write guard's trust under the same env and home invocation will use (the
|
|
161
|
+
// run's CODEX_HOME, plans/codex-write-guard.md "Trust (parts 4–5)"), so the trust an
|
|
162
|
+
// invocation later reads was looked up under the run's own config. It never throws a run
|
|
163
|
+
// away: a failed lookup records `off` (and warns once) and the run continues without the
|
|
164
|
+
// guard's trust.
|
|
165
|
+
const base = withNotesPath(backendBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
|
|
166
|
+
const home = codexHome(resolvedBackendConfig(run.config, "codex"));
|
|
167
|
+
const env = codexEnv(base, { home });
|
|
168
|
+
const root = nearestTumwaterRoot(run.sessionDir);
|
|
169
|
+
const definition = codexWriteGuardDefinition();
|
|
170
|
+
const key = writeGuardKey(home, definition);
|
|
171
|
+
// Single-flight per (home, definition): a run whose key is being resolved (or already
|
|
172
|
+
// resolved) shares the one in-flight lookup, so concurrent runs start one `app-server`.
|
|
173
|
+
const settled = writeGuardTrust.get(key);
|
|
174
|
+
if (settled !== undefined) {
|
|
175
|
+
warnGuardOffOnce(key, root, settled);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const inFlight = writeGuardInFlight.get(key);
|
|
179
|
+
if (inFlight !== undefined) {
|
|
180
|
+
await inFlight;
|
|
181
|
+
const after = writeGuardTrust.get(key);
|
|
182
|
+
if (after !== undefined)
|
|
183
|
+
warnGuardOffOnce(key, root, after);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const promise = (async () => {
|
|
187
|
+
try {
|
|
188
|
+
const trust = await codexHookTrust(root, {
|
|
189
|
+
bin: run.agentBin,
|
|
190
|
+
env,
|
|
191
|
+
cwd: run.cwd,
|
|
192
|
+
definition,
|
|
193
|
+
timeoutMs: codexTrustLookupTimeoutMs,
|
|
194
|
+
});
|
|
195
|
+
writeGuardTrust.set(key, { kind: "trust", trust });
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
writeGuardTrust.set(key, { kind: "off", reason: errorMessage(error) });
|
|
199
|
+
}
|
|
200
|
+
})();
|
|
201
|
+
writeGuardInFlight.set(key, promise);
|
|
202
|
+
await promise;
|
|
203
|
+
writeGuardInFlight.delete(key);
|
|
204
|
+
const state = writeGuardTrust.get(key);
|
|
205
|
+
if (state !== undefined)
|
|
206
|
+
warnGuardOffOnce(key, root, state);
|
|
207
|
+
},
|
|
208
|
+
translator() {
|
|
209
|
+
return codexTranslator();
|
|
210
|
+
},
|
|
211
|
+
imageInput(model) {
|
|
212
|
+
// Answer from the model catalog (part 8/9): a model reads images when its catalog entry's
|
|
213
|
+
// `input_modalities` say so; a model missing from the catalog — or a run with no home to
|
|
214
|
+
// read — answers false, the safe direction, so a blind image read is never silently
|
|
215
|
+
// assumed readable. The home is the team's `backends.codex.configDir`, found by walking
|
|
216
|
+
// up from the process's working directory to its `.tumwater` root and loading the config
|
|
217
|
+
// there, the same root the runs' home and account live under (like opencode's cache).
|
|
218
|
+
if (model === undefined)
|
|
219
|
+
return false;
|
|
220
|
+
const root = teamRootOf(process.cwd());
|
|
221
|
+
const { config } = loadConfigSafe(root);
|
|
222
|
+
const home = codexHome(resolvedBackendConfig(config ?? {}, "codex"));
|
|
223
|
+
if (home === undefined)
|
|
224
|
+
return false;
|
|
225
|
+
return readCodexCatalog(home).get(model)?.images === true;
|
|
226
|
+
},
|
|
227
|
+
resumable(dir, opts) {
|
|
228
|
+
const record = resumableSession(dir, { kind: opts.kind, agent: "codex", cwd: opts.cwd });
|
|
229
|
+
if (record === null || !THREAD_ID_RE.test(record.id))
|
|
230
|
+
return false;
|
|
231
|
+
const root = teamRootOf(dir);
|
|
232
|
+
const { config } = loadConfigSafe(root);
|
|
233
|
+
const home = codexHome(resolvedBackendConfig(config ?? {}, "codex"));
|
|
234
|
+
if (home === undefined)
|
|
235
|
+
return false;
|
|
236
|
+
return findRollout(home, record.id) !== null;
|
|
237
|
+
},
|
|
238
|
+
afterRun(run, invocation, translator) {
|
|
239
|
+
const ct = translator;
|
|
240
|
+
const home = codexHome(resolvedBackendConfig(run.config, "codex"));
|
|
241
|
+
const id = invocation.sessionId ?? ct.sessionId?.();
|
|
242
|
+
const fresh = invocation.sessionId === undefined;
|
|
243
|
+
// The rollout is authoritative, so its loss is worth one warning: a fresh run keeps the
|
|
244
|
+
// stream's own usage as a fallback, a resumed run records none (design §5, finding 4).
|
|
245
|
+
const fallback = () => {
|
|
246
|
+
ct.flagUsagePartial();
|
|
247
|
+
if (!fresh)
|
|
248
|
+
return [];
|
|
249
|
+
const usage = ct.turnCompletedUsage();
|
|
250
|
+
return usage !== undefined ? [JSON.stringify({ type: "run_usage", ...usage })] : [];
|
|
251
|
+
};
|
|
252
|
+
if (home === undefined || id === undefined)
|
|
253
|
+
return fallback();
|
|
254
|
+
const rollout = findRollout(home, id);
|
|
255
|
+
if (rollout === null)
|
|
256
|
+
return fallback();
|
|
257
|
+
let text;
|
|
258
|
+
try {
|
|
259
|
+
text = fs.readFileSync(rollout, "utf8");
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
return fallback();
|
|
263
|
+
}
|
|
264
|
+
// The stream never exposes the turn id — only the thread id that names the rollout file
|
|
265
|
+
// (real captures' `task_started.turn_id` is a different UUID), so the reader selects the
|
|
266
|
+
// last block, this run's, from the file the thread id located.
|
|
267
|
+
const { lines, switchedTo } = rolloutLines(text, {
|
|
268
|
+
requestedModel: run.config.model ?? "",
|
|
269
|
+
});
|
|
270
|
+
if (lines.length === 0)
|
|
271
|
+
return fallback();
|
|
272
|
+
// Codex can switch models on a usage limit by itself (finding 24): warn once and record
|
|
273
|
+
// the model that actually ran in the rollout's reading.
|
|
274
|
+
if (switchedTo !== undefined)
|
|
275
|
+
ct.flagModelSwitched(run.config.model ?? "", switchedTo);
|
|
276
|
+
return lines;
|
|
277
|
+
},
|
|
278
|
+
classifyExit: classifyCodexExit,
|
|
279
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** The Codex model catalog (plans/codex-backend.md design finding 14 — "Spend, free-ness,
|
|
2
|
+
* window, images" part 8/9): tumwater reads a Codex model's effort levels, default effort,
|
|
3
|
+
* context window and image input from `$CODEX_HOME/models_cache.json`, the file the CLI itself
|
|
4
|
+
* refreshes and `codex debug models` prints. Before part 8/9 the adapter assumed every Codex
|
|
5
|
+
* model had every effort and read every image; the catalog refines both, and seats the context
|
|
6
|
+
* window `seamContextWindow` reports for a codex seam. There are no prices in the catalog — the
|
|
7
|
+
* whole file knows nothing of cost — so free-ness never reads here; the codex account
|
|
8
|
+
* (codex-account.ts) owns billing.
|
|
9
|
+
*
|
|
10
|
+
* A missing, unreadable or malformed file reads as an empty catalog, the same "no data" policy
|
|
11
|
+
* pi's models.json reader uses: the adapter then answers blind (no effort gating, no images,
|
|
12
|
+
* no window), which is the safe direction for the window and image checks. The parse itself is
|
|
13
|
+
* pure and build-agnostic — no registry, config or subprocess — so the adapter, pi's
|
|
14
|
+
* seamContextWindow and the doctor can read one answer without dragging a runtime in. */
|
|
15
|
+
import os from "node:os";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { cachedByStat } from "../../files/stat-cache.js";
|
|
18
|
+
import { finiteNumber, isJsonObject } from "../../files/json-object.js";
|
|
19
|
+
import { readJsonFile } from "../../files/json-files.js";
|
|
20
|
+
import { expandHomeTilde } from "../../files/files.js";
|
|
21
|
+
/** The team's Codex home for a run: `backends.codex.configDir`, a leading `~` expanded, else
|
|
22
|
+
* the CLI's default `~/.codex` — a run with no configured home keeps the operator's
|
|
23
|
+
* `CODEX_HOME`, which is exactly Codex's own default home, so the catalog and the sessions
|
|
24
|
+
* live there too and a known home always exists. Pure. Shared by the adapter, pi's
|
|
25
|
+
* seamContextWindow and the codex-account seed, so all three read one home. */
|
|
26
|
+
export function codexHome(config) {
|
|
27
|
+
const dir = config.configDir;
|
|
28
|
+
if (dir === undefined)
|
|
29
|
+
return path.join(os.homedir(), ".codex");
|
|
30
|
+
return expandHomeTilde(dir, os.homedir());
|
|
31
|
+
}
|
|
32
|
+
/** Where a home's model catalog lives: `$CODEX_HOME/models_cache.json`. */
|
|
33
|
+
export function codexModelsPath(home) {
|
|
34
|
+
return path.join(home, "models_cache.json");
|
|
35
|
+
}
|
|
36
|
+
/** Per-catalog cache of the parsed entries, keyed by the models_cache path: the status
|
|
37
|
+
* surfaces read the window every poll while the file changes only when the CLI refreshes it —
|
|
38
|
+
* and it grows with the model catalog — so an unchanged file costs one stat per poll instead
|
|
39
|
+
* of a re-read plus a JSON.parse of the whole file. Any write invalidates via
|
|
40
|
+
* dev/ino/mtime/size (stat-cache.cachedByStat, the same freshness check pi's providers use). */
|
|
41
|
+
const catalogCache = new Map();
|
|
42
|
+
/** The parsed model catalog from `$CODEX_HOME/models_cache.json`, keyed by model slug, or an
|
|
43
|
+
* empty map when the file is missing, unreadable, malformed or not shaped like what the CLI
|
|
44
|
+
* writes. A model's entry is kept only when it names a usable slug and context_window; a
|
|
45
|
+
* missing `effective_context_window_percent` reads as no window (the model is dropped), since
|
|
46
|
+
* the window is the whole point of the entry. The returned map is a copy — callers may treat
|
|
47
|
+
* their result as their own. */
|
|
48
|
+
export function readCodexCatalog(home) {
|
|
49
|
+
const modelsPath = codexModelsPath(home);
|
|
50
|
+
return (cachedByStat(catalogCache, modelsPath, modelsPath, () => {
|
|
51
|
+
const out = new Map();
|
|
52
|
+
// readJsonFile is the shared "missing or torn reads as no data" policy for a JSON
|
|
53
|
+
// object file; the nested `models` array gets the same isJsonObject/Array guards.
|
|
54
|
+
const doc = readJsonFile(modelsPath);
|
|
55
|
+
if (doc === null)
|
|
56
|
+
return out;
|
|
57
|
+
const models = doc.models;
|
|
58
|
+
if (!Array.isArray(models))
|
|
59
|
+
return out;
|
|
60
|
+
for (const model of models) {
|
|
61
|
+
const entry = parseModelEntry(model);
|
|
62
|
+
if (entry !== undefined)
|
|
63
|
+
out.set(entry.slug, entry.entry);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}, (models) => new Map([...models].map(([slug, entry]) => [slug, { ...entry, levels: [...entry.levels] }]))) ?? new Map());
|
|
67
|
+
}
|
|
68
|
+
/** One catalog model entry as the map needs it: the slug to key by plus the parsed entry. The
|
|
69
|
+
* entry is kept only when the slug and context window are usable; any other missing field
|
|
70
|
+
* (levels, modalities, default level) just reads as absent on the entry. */
|
|
71
|
+
function parseModelEntry(model) {
|
|
72
|
+
if (!isJsonObject(model) || typeof model.slug !== "string")
|
|
73
|
+
return undefined;
|
|
74
|
+
const context = finiteNumber(model.context_window, Number.NaN);
|
|
75
|
+
const percent = finiteNumber(model.effective_context_window_percent, Number.NaN);
|
|
76
|
+
if (!Number.isFinite(context) || !Number.isFinite(percent))
|
|
77
|
+
return undefined;
|
|
78
|
+
const levels = [];
|
|
79
|
+
if (Array.isArray(model.supported_reasoning_levels)) {
|
|
80
|
+
for (const level of model.supported_reasoning_levels) {
|
|
81
|
+
if (isJsonObject(level) && typeof level.effort === "string")
|
|
82
|
+
levels.push(level.effort);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const modalities = model.input_modalities;
|
|
86
|
+
return {
|
|
87
|
+
slug: model.slug,
|
|
88
|
+
entry: {
|
|
89
|
+
levels,
|
|
90
|
+
...(typeof model.default_reasoning_level === "string"
|
|
91
|
+
? { defaultLevel: model.default_reasoning_level }
|
|
92
|
+
: {}),
|
|
93
|
+
contextWindow: Math.round((context * percent) / 100),
|
|
94
|
+
images: Array.isArray(modalities) && modalities.includes("image"),
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Build the codex process environment from `base`. Never mutates `base`. */
|
|
2
|
+
export function codexEnv(base, opts = {}) {
|
|
3
|
+
const env = {};
|
|
4
|
+
for (const [key, value] of Object.entries(base)) {
|
|
5
|
+
if (key === "CLAUDECODE")
|
|
6
|
+
continue;
|
|
7
|
+
if (key.startsWith("CODEX_"))
|
|
8
|
+
continue;
|
|
9
|
+
if (key.startsWith("CLAUDE_CODE_"))
|
|
10
|
+
continue;
|
|
11
|
+
if (key === "OPENAI_API_KEY" || key === "CODEX_API_KEY")
|
|
12
|
+
continue;
|
|
13
|
+
env[key] = value;
|
|
14
|
+
}
|
|
15
|
+
if (opts.home)
|
|
16
|
+
env.CODEX_HOME = opts.home;
|
|
17
|
+
return env;
|
|
18
|
+
}
|