tumwater 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +55 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +6 -4
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +404 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +281 -55
- package/dist/src/backlog/backlog-cli-view.js +255 -0
- package/dist/src/backlog/backlog-cli.js +91 -335
- package/dist/src/backlog/backlog-commit.js +20 -13
- package/dist/src/backlog/backlog-eligibility.js +193 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +264 -0
- package/dist/src/backlog/backlog-release.js +134 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-guardrails.js +136 -0
- package/dist/src/baseline/main-red.js +153 -36
- package/dist/src/baseline/main-watch.js +232 -0
- package/dist/src/brief/brief-edit.js +12 -12
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget-fallback-view.js +288 -0
- package/dist/src/budget/budget-wiring.js +158 -0
- package/dist/src/budget/budget.js +31 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +65 -3
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +181 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +183 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +291 -598
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-review-commands.js +113 -0
- package/dist/src/cli/cli-role-command.js +525 -0
- package/dist/src/cli/cli-start-summary.js +167 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
- package/dist/src/cli/completion.js +169 -22
- package/dist/src/cli/config-commands.js +58 -26
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +258 -82
- package/dist/src/cli/log-commands.js +164 -85
- package/dist/src/cli/question-commands.js +211 -10
- package/dist/src/cli.js +173 -45
- package/dist/src/collections.js +56 -13
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +16 -1
- package/dist/src/config/config-fallbacks.js +94 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +169 -222
- package/dist/src/config/config-views.js +136 -54
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +164 -265
- package/dist/src/config/config.js +53 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
- package/dist/src/doctor/doctor-backlog-plans.js +398 -0
- package/dist/src/doctor/doctor-backlog.js +249 -512
- package/dist/src/doctor/doctor-check-names.js +85 -0
- package/dist/src/doctor/doctor-checks.js +61 -593
- package/dist/src/doctor/doctor-claude-checks.js +142 -0
- package/dist/src/doctor/doctor-codex-checks.js +276 -0
- package/dist/src/doctor/doctor-contention-checks.js +506 -0
- package/dist/src/doctor/doctor-fallback-checks.js +130 -0
- package/dist/src/doctor/doctor-holds.js +80 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +216 -537
- package/dist/src/doctor/doctor-notify.js +9 -5
- package/dist/src/doctor/doctor-opencode-checks.js +164 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-pi-checks.js +62 -0
- package/dist/src/doctor/doctor-project-checks.js +397 -0
- package/dist/src/doctor/doctor.js +126 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +21 -12
- package/dist/src/events/event-format.js +185 -89
- package/dist/src/events/event-labels.js +11 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +52 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +80 -26
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +65 -60
- package/dist/src/failure/failure-render.js +29 -21
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +35 -35
- package/dist/src/files/file-queue.js +17 -0
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +88 -225
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +101 -143
- package/dist/src/gates/maintenance-quota.js +37 -30
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +89 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +54 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +43 -19
- package/dist/src/gui/gui-backlog-endpoints.js +156 -0
- package/dist/src/gui/gui-command.js +26 -13
- package/dist/src/gui/gui-config-commands.js +168 -80
- package/dist/src/gui/gui-endpoint-commands.js +5 -4
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +235 -13
- package/dist/src/gui/gui-prompt-commands.js +26 -20
- package/dist/src/gui/gui-report-endpoints.js +97 -0
- package/dist/src/gui/gui-server.js +26 -5
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +155 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +190 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +28 -17
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +58 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +45 -19
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +214 -0
- package/dist/src/merge/merge-queue-render.js +48 -11
- package/dist/src/merge/merge-queue.js +52 -15
- package/dist/src/merge/merge-requeue.js +103 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +27 -11
- package/dist/src/merge/merge-to-main.js +77 -26
- package/dist/src/merge/merge-top.js +83 -0
- package/dist/src/merge/merge-verify.js +75 -18
- package/dist/src/notes-cli.js +35 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +352 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
- package/dist/src/orchestrator/orchestrator.js +129 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +245 -0
- package/dist/src/orchestrator/scheduling-skip.js +195 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +126 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +26 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +150 -217
- package/dist/src/prompt/notes-backlog.js +184 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +131 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +188 -124
- package/dist/src/report/report-fold.js +130 -54
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +136 -65
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +23 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/diff-rules.js +53 -0
- package/dist/src/review/known-flakes.js +112 -1
- package/dist/src/review/post-merge-review-accept.js +51 -0
- package/dist/src/review/post-merge-review-render.js +112 -0
- package/dist/src/review/post-merge-review.js +239 -0
- package/dist/src/review/protected-edits.js +24 -10
- package/dist/src/review/quarantine-render.js +65 -0
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +180 -401
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +127 -12
- package/dist/src/roles/role-guidance.js +65 -20
- package/dist/src/roles/role-labels.js +7 -5
- package/dist/src/roles/role-render.js +108 -56
- package/dist/src/roles/role-view.js +95 -56
- package/dist/src/roles/roles.js +33 -33
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/ci-parity.js +121 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/run/qa-coverage.js +125 -0
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +175 -0
- package/dist/src/run/run-detail-render.js +91 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +178 -0
- package/dist/src/run/stage-checks-shared.js +65 -0
- package/dist/src/run/stage-name-checks.js +204 -0
- package/dist/src/run/stage-path-checks.js +231 -0
- package/dist/src/run/stage-prose-checks.js +261 -0
- package/dist/src/runner/leftover.js +216 -0
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +191 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +109 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +103 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claim-entries.js +49 -0
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +49 -17
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +43 -28
- package/dist/src/status/status-roles.js +50 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +7 -8
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +65 -18
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +60 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +78 -13
- package/dist/src/ui/badges.js +99 -38
- package/dist/src/ui/gui/gui-client-backlog.js +314 -0
- package/dist/src/ui/gui/gui-client-boot.js +89 -61
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
- package/dist/src/ui/gui/gui-client-drawer.js +155 -289
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +437 -68
- package/dist/src/ui/gui/gui-client-labels.js +6 -3
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +60 -52
- package/dist/src/ui/gui/gui-client-pending.js +405 -24
- package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
- package/dist/src/ui/gui/gui-client-report.js +105 -430
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +163 -5
- package/dist/src/ui/gui/gui-client-team.js +36 -465
- package/dist/src/ui/gui/gui-client.js +38 -26
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +10 -7
- package/dist/src/ui/gui/gui-styles.js +38 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +364 -49
- package/dist/src/ui/status-render.js +109 -82
- package/dist/src/ui/team-alerts.js +728 -62
- package/dist/src/ui/tone.js +12 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +53 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +4 -12
- package/dist/src/ui/tui/tui-editor-backends.js +127 -0
- package/dist/src/ui/tui/tui-editor-brief.js +14 -36
- package/dist/src/ui/tui/tui-editor-budget.js +36 -39
- package/dist/src/ui/tui/tui-editor-check.js +29 -0
- package/dist/src/ui/tui/tui-editor-checks.js +28 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
- package/dist/src/ui/tui/tui-editor-config.js +99 -0
- package/dist/src/ui/tui/tui-editor-entry.js +39 -64
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +46 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
- package/dist/src/ui/tui/tui-editor-interval.js +29 -16
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +9 -29
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
- package/dist/src/ui/tui/tui-editor-retire.js +5 -13
- package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +101 -34
- package/dist/src/ui/tui/tui-editor-slots.js +24 -0
- package/dist/src/ui/tui/tui-editors.js +44 -2
- package/dist/src/ui/tui/tui-frame.js +68 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +33 -9
- package/dist/src/ui/tui/tui-pane-activity.js +35 -19
- package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +168 -8
- package/dist/src/ui/tui/tui-pane-history.js +446 -82
- package/dist/src/ui/tui/tui-pane-pending.js +539 -15
- package/dist/src/ui/tui/tui-pane-queued.js +78 -57
- package/dist/src/ui/tui/tui-pane-report.js +54 -16
- package/dist/src/ui/tui/tui-pane-session.js +34 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +91 -28
- package/dist/src/ui/tui/tui-panes.js +153 -34
- package/dist/src/ui/tui/tui.js +18 -15
- package/dist/src/verdict/fix-claim.js +129 -33
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +77 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/leftover.js +0 -130
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/qa-coverage.js +0 -108
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
package/dist/src/pi/pi-bin.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import { envPath, findOnPath } from "../files/files.js";
|
|
5
|
-
/** Which
|
|
2
|
+
import { resolvedBackendConfig } from "../config/config-views.js";
|
|
3
|
+
import { envPath, findOnPath, isExecutableFile } from "../files/files.js";
|
|
4
|
+
/** Which backend binary the harness spawns and where that value came from — the per-backend
|
|
6
5
|
* resolution `TUMWATER_<ID>_BIN` → `backends.<id>.bin` (pi's legacy `agentBin` alias) → the
|
|
7
6
|
* backends' defaults (plans/agent-backends.md "Config"; plans/portability.md §5/7), the
|
|
8
7
|
* executability rule the preflight askers share, and the missing-binary wording they quote.
|
|
9
8
|
* The spawn (src/agent/run-agent.ts) resolves through each backend's resolveBin; the startup
|
|
10
|
-
* gate (gates/startup-gate.ts) walks every enabled seam's
|
|
11
|
-
* executability rule, and doctor (doctor/doctor-checks.ts) asks them for every
|
|
12
|
-
* (
|
|
9
|
+
* gate (gates/startup-gate.ts) walks every enabled seam's backend through the same resolution and
|
|
10
|
+
* executability rule, and doctor (doctor/doctor-checks.ts) asks them for every backend in use
|
|
11
|
+
* (backendsInUse, the startup gate's rule). Each surface quotes this module's wording, so the
|
|
13
12
|
* executability verdict and the pi text cannot drift between them. Split out of
|
|
14
|
-
* gates/readiness.ts, which keeps the repo preconditions' shared wording: the
|
|
13
|
+
* gates/readiness.ts, which keeps the repo preconditions' shared wording: the backend binary is
|
|
15
14
|
* the backends' own concern, so its resolution lives beside the spawn instead of in the gate
|
|
16
15
|
* layer. */
|
|
17
16
|
const PI_MISSING_MESSAGE = "pi not found on PATH — install it (https://github.com/badlogic/pi-mono) or add its bin directory to your PATH";
|
|
@@ -21,7 +20,7 @@ const PI_MISSING_MESSAGE = "pi not found on PATH — install it (https://github.
|
|
|
21
20
|
* default; claude's adapter passes TUMWATER_CLAUDE_BIN), and `configKey` is the tumwater.json
|
|
22
21
|
* key the "config" source names (pi's legacy `agentBin`, or `backends.<id>.bin`), so a claude
|
|
23
22
|
* failure names its own var and key rather than pi's. */
|
|
24
|
-
export function
|
|
23
|
+
export function backendBinSourceLabel(source, binEnv = "TUMWATER_PI_BIN", configKey = "agentBin") {
|
|
25
24
|
switch (source) {
|
|
26
25
|
case "env":
|
|
27
26
|
return binEnv;
|
|
@@ -31,7 +30,7 @@ export function agentBinSourceLabel(source, binEnv = "TUMWATER_PI_BIN", configKe
|
|
|
31
30
|
return "the PATH default";
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
|
-
/** The missing-binary message for a resolved
|
|
33
|
+
/** The missing-binary message for a resolved backend binary. The default source keeps
|
|
35
34
|
* PI_MISSING_MESSAGE byte-identical (pinned by test/cli.test.ts and test/doctor.test.ts);
|
|
36
35
|
* a configured source gains the resolved value and where it came from, so a wrong
|
|
37
36
|
* `agentBin` never reads as "pi is not installed" — and keeps the install hint, since a
|
|
@@ -39,68 +38,60 @@ export function agentBinSourceLabel(source, binEnv = "TUMWATER_PI_BIN", configKe
|
|
|
39
38
|
export function piMissingMessage(resolved) {
|
|
40
39
|
if (resolved.source === "default")
|
|
41
40
|
return PI_MISSING_MESSAGE;
|
|
42
|
-
const from =
|
|
41
|
+
const from = backendBinSourceLabel(resolved.source);
|
|
43
42
|
return `pi not found — resolved "${resolved.bin}" from ${from} is not an executable — install it (https://github.com/badlogic/pi-mono) or point ${from} at a working pi binary`;
|
|
44
43
|
}
|
|
45
|
-
/** The missing-binary message for any
|
|
46
|
-
* over every enabled seam's
|
|
47
|
-
* untouched; any other
|
|
44
|
+
/** The missing-binary message for any backend's resolved binary, used by the startup gate's walk
|
|
45
|
+
* over every enabled seam's backend. pi delegates to piMissingMessage so its pinned wording is
|
|
46
|
+
* untouched; any other backend names itself, the resolved value, and where it came from, so a
|
|
48
47
|
* wrong `backends.<id>.bin` or `TUMWATER_<ID>_BIN` never reads as a missing pi. */
|
|
49
|
-
export function
|
|
50
|
-
if (
|
|
48
|
+
export function backendMissingMessage(resolved, backend) {
|
|
49
|
+
if (backend === "pi")
|
|
51
50
|
return piMissingMessage(resolved);
|
|
52
51
|
if (resolved.source === "default") {
|
|
53
|
-
return `${
|
|
52
|
+
return `${backend} not found on PATH — install it or add its bin directory to your PATH`;
|
|
54
53
|
}
|
|
55
|
-
const from =
|
|
56
|
-
return `${
|
|
54
|
+
const from = backendBinSourceLabelFor(backend, resolved.source);
|
|
55
|
+
return `${backend} not found — resolved "${resolved.bin}" from ${from} is not an executable — install it or point ${from} at a working ${backend} binary`;
|
|
57
56
|
}
|
|
58
|
-
/** The environment variable that overrides one
|
|
59
|
-
* uppercased — the convention
|
|
60
|
-
function
|
|
61
|
-
return `TUMWATER_${
|
|
57
|
+
/** The environment variable that overrides one backend's binary: `TUMWATER_<ID>_BIN` with the id
|
|
58
|
+
* uppercased — the convention resolveBackendBin and backendMissingMessage share. */
|
|
59
|
+
function backendBinEnv(backend) {
|
|
60
|
+
return `TUMWATER_${backend.toUpperCase()}_BIN`;
|
|
62
61
|
}
|
|
63
|
-
/** The tumwater.json key that names one
|
|
64
|
-
* per-
|
|
62
|
+
/** The tumwater.json key that names one backend's binary: pi's legacy `agentBin`, or the
|
|
63
|
+
* per-backend `backends.<id>.bin`. Shared by the missing-binary and spawn-error wording so a
|
|
65
64
|
* non-pi failure never names pi's key. */
|
|
66
|
-
export function
|
|
67
|
-
return
|
|
65
|
+
export function backendBinConfigKey(backend) {
|
|
66
|
+
return backend === "pi" ? "agentBin" : `backends.${backend}.bin`;
|
|
68
67
|
}
|
|
69
|
-
/** The source label for one
|
|
70
|
-
* the
|
|
71
|
-
* wording; any other
|
|
72
|
-
* for the convention
|
|
73
|
-
export function
|
|
74
|
-
return
|
|
75
|
-
?
|
|
76
|
-
:
|
|
68
|
+
/** The source label for one backend's resolved binary, deriving the env var and config key from
|
|
69
|
+
* the backend id when the caller has no backend defaults in hand. pi keeps its pinned legacy
|
|
70
|
+
* wording; any other backend names its own `TUMWATER_<ID>_BIN` and `backends.<id>.bin`. One home
|
|
71
|
+
* for the convention backendMissingMessage and doctor's ok detail both read. */
|
|
72
|
+
export function backendBinSourceLabelFor(backend, source) {
|
|
73
|
+
return backend === "pi"
|
|
74
|
+
? backendBinSourceLabel(source)
|
|
75
|
+
: backendBinSourceLabel(source, backendBinEnv(backend), backendBinConfigKey(backend));
|
|
77
76
|
}
|
|
78
|
-
/** Is the resolved
|
|
77
|
+
/** Is the resolved backend binary executable right now? Returns the usable path, or null when
|
|
79
78
|
* the startup gate should refuse to boot and doctor should fail its check. This is the whole
|
|
80
79
|
* executability rule in one place so the two askers cannot drift: a path-shaped value (already
|
|
81
|
-
* normalized against the process cwd by
|
|
80
|
+
* normalized against the process cwd by resolveBackendBin) is tested directly with
|
|
82
81
|
* accessSync(X_OK) and required to be a regular file — a directory passes X_OK, but can never
|
|
83
82
|
* be spawned, so it is rejected here with exactly the file requirement findOnPath's bare-name
|
|
84
83
|
* branch applies (a bin pointed at a directory would otherwise pass the preflight and die
|
|
85
84
|
* mid-run at the spawn instead of failing the gate with the "not an executable" message). A
|
|
86
85
|
* bare name resolves through PATH. `pathEnv` is injectable so tests need no PATH mutation —
|
|
87
86
|
* the gate and doctor both default it to the process PATH. */
|
|
88
|
-
export function
|
|
87
|
+
export function findBackendBinary(resolved, pathEnv = envPath()) {
|
|
89
88
|
if (resolved.bin.includes("/")) {
|
|
90
|
-
|
|
91
|
-
fs.accessSync(resolved.bin, fs.constants.X_OK); // Directories pass X_OK; require a file.
|
|
92
|
-
if (fs.statSync(resolved.bin).isFile())
|
|
93
|
-
return resolved.bin;
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
89
|
+
return isExecutableFile(resolved.bin) ? resolved.bin : null;
|
|
99
90
|
}
|
|
100
91
|
return findOnPath(resolved.bin, pathEnv);
|
|
101
92
|
}
|
|
102
|
-
/** Normalize a path-shaped
|
|
103
|
-
* resolution time: the spawn runs with each
|
|
93
|
+
/** Normalize a path-shaped backend binary against the harness process's cwd NOW, at
|
|
94
|
+
* resolution time: the spawn runs with each run's worktree as cwd, so a relative value
|
|
104
95
|
* left as given would name a different file there than the run preflight and doctor (which
|
|
105
96
|
* evaluate against the process cwd) had already accepted. An absolute path comes back
|
|
106
97
|
* normalized; a bare name — no separator — is left for PATH resolution exactly as before.
|
|
@@ -111,10 +102,10 @@ function absBin(value) {
|
|
|
111
102
|
}
|
|
112
103
|
/** The precedence every backend shares, parameterized by its own env override and default
|
|
113
104
|
* binary name: `<binEnv>` env → the configured entry's bin → `defaultBin`. An empty or
|
|
114
|
-
* whitespace value falls through to the next source, so `TUMWATER_PI_BIN= tumwater
|
|
105
|
+
* whitespace value falls through to the next source, so `TUMWATER_PI_BIN= tumwater start` cannot
|
|
115
106
|
* wedge the team on a typo'd export. This is precedence only — resolvability is checked at the
|
|
116
|
-
* preflight sites (
|
|
117
|
-
* through
|
|
107
|
+
* preflight sites (cmdStart and doctor), never here. Private: the backends and the gate resolve
|
|
108
|
+
* through resolveBackendBin, its one caller. */
|
|
118
109
|
function resolveBinFrom(binEnv, defaultBin, configured) {
|
|
119
110
|
const env = process.env[binEnv]?.trim();
|
|
120
111
|
if (env)
|
|
@@ -124,21 +115,21 @@ function resolveBinFrom(binEnv, defaultBin, configured) {
|
|
|
124
115
|
return { bin: absBin(cfg), source: "config" };
|
|
125
116
|
return { bin: defaultBin, source: "default" };
|
|
126
117
|
}
|
|
127
|
-
/** Resolve which binary one
|
|
128
|
-
* `backends.<id>.bin` (config-views.ts
|
|
129
|
-
* names pi) → the
|
|
118
|
+
/** Resolve which binary one backend spawns: `TUMWATER_<ID>_BIN` → the effective
|
|
119
|
+
* `backends.<id>.bin` (config-views.ts resolvedBackendConfig, so pi's legacy `agentBin` still
|
|
120
|
+
* names pi) → the backend's default. `backend` defaults to pi, the historical backend; the
|
|
130
121
|
* backends pass their own id, binEnv and defaultBin, while the startup gate derives the
|
|
131
|
-
* reserved
|
|
132
|
-
export function
|
|
133
|
-
const binEnv = defaults.binEnv ??
|
|
134
|
-
const defaultBin = defaults.defaultBin ??
|
|
135
|
-
return resolveBinFrom(binEnv, defaultBin,
|
|
122
|
+
* reserved backends' conventional env name and default binary when it walks every enabled seam. */
|
|
123
|
+
export function resolveBackendBin(config, backend = "pi", defaults = {}) {
|
|
124
|
+
const binEnv = defaults.binEnv ?? backendBinEnv(backend);
|
|
125
|
+
const defaultBin = defaults.defaultBin ?? backend;
|
|
126
|
+
return resolveBinFrom(binEnv, defaultBin, resolvedBackendConfig(config, backend).bin);
|
|
136
127
|
}
|
|
137
128
|
/** The shared body of a backend's `resolveBin` adapter: resolve this backend's binary through
|
|
138
|
-
*
|
|
129
|
+
* resolveBackendBin above using the backend's own id, binEnv and defaultBin — never pi's, so a
|
|
139
130
|
* claude failure names TUMWATER_CLAUDE_BIN/the claude default. The two registered backends
|
|
140
131
|
* (pi-backend.ts, claude-backend.ts) each expose a one-line `resolveBin` delegating here, so
|
|
141
132
|
* the call convention lives in one place and cannot drift between them. */
|
|
142
|
-
export function
|
|
143
|
-
return
|
|
133
|
+
export function resolveBinForBackend(config, backend) {
|
|
134
|
+
return resolveBackendBin(config, backend.id, { binEnv: backend.binEnv, defaultBin: backend.defaultBin });
|
|
144
135
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
|
|
2
|
+
import { piListedModelsPath } from "../paths.js";
|
|
3
|
+
/** The whitespace-split rows of the table `pi --list-models` prints, header first. Null unless
|
|
4
|
+
* the first non-blank line is the `provider model …` header, so other text (a "No models
|
|
5
|
+
* matching" reply) is never taken for an empty table. */
|
|
6
|
+
export function parsePiModelTable(stdout) {
|
|
7
|
+
const rows = stdout.split("\n").map((line) => line.trim().split(/\s+/)).filter((t) => t[0] !== "");
|
|
8
|
+
const header = rows.shift();
|
|
9
|
+
if (header?.[0] !== "provider" || header[1] !== "model")
|
|
10
|
+
return null;
|
|
11
|
+
return { header, rows };
|
|
12
|
+
}
|
|
13
|
+
/** Read the `images` column of the table `pi --list-models` prints. Null unless the first
|
|
14
|
+
* non-blank line is the `provider model …` header and it has an `images` column. A row's
|
|
15
|
+
* `yes`/`no` maps to a boolean; any other value is skipped. */
|
|
16
|
+
export function parsePiModelImages(stdout) {
|
|
17
|
+
const table = parsePiModelTable(stdout);
|
|
18
|
+
if (table === null)
|
|
19
|
+
return null;
|
|
20
|
+
const { header, rows } = table;
|
|
21
|
+
const column = header.indexOf("images");
|
|
22
|
+
if (column < 0)
|
|
23
|
+
return null;
|
|
24
|
+
const images = {};
|
|
25
|
+
for (const row of rows) {
|
|
26
|
+
const [provider, model] = row;
|
|
27
|
+
if (model === undefined)
|
|
28
|
+
continue;
|
|
29
|
+
if (row[column] === "yes")
|
|
30
|
+
images[`${provider}/${model}`] = true;
|
|
31
|
+
else if (row[column] === "no")
|
|
32
|
+
images[`${provider}/${model}`] = false;
|
|
33
|
+
}
|
|
34
|
+
return images;
|
|
35
|
+
}
|
|
36
|
+
/** The saved listing, or null when the file is missing, torn, or has no `images` object. */
|
|
37
|
+
export function readPiListedModels(root) {
|
|
38
|
+
const saved = readJsonFile(piListedModelsPath(root));
|
|
39
|
+
const images = saved?.images;
|
|
40
|
+
if (typeof images !== "object" || images === null || Array.isArray(images))
|
|
41
|
+
return null;
|
|
42
|
+
return { checkedAt: typeof saved?.checkedAt === "number" ? saved.checkedAt : 0, images };
|
|
43
|
+
}
|
|
44
|
+
export function writePiListedModels(root, images, now = Date.now()) {
|
|
45
|
+
writeJsonAtomic(piListedModelsPath(root), { checkedAt: now, images });
|
|
46
|
+
}
|
|
47
|
+
/** Whether pi lists `provider/model` as reading images; null when unlisted or an argument is
|
|
48
|
+
* missing. No TTL: every doctor run that lists the models rewrites the file. */
|
|
49
|
+
export function piListedImageInput(listed, provider, model) {
|
|
50
|
+
if (!listed || provider === undefined || model === undefined)
|
|
51
|
+
return null;
|
|
52
|
+
return listed.images[`${provider}/${model}`] ?? null;
|
|
53
|
+
}
|
package/dist/src/pi/pi-models.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { enabledRoleIds } from "../config/config.js";
|
|
4
|
-
import { configForRole, resolverConfig, reviewConfig } from "../config/config-views.js";
|
|
3
|
+
import { enabledRoleIds, loadConfigSafe } from "../config/config.js";
|
|
4
|
+
import { configForRole, resolverConfig, resolvedBackendConfig, reviewConfig } from "../config/config-views.js";
|
|
5
5
|
import { fallbackPair } from "../config/config-fallbacks.js";
|
|
6
6
|
import { seamAgent } from "../config/model-selector.js";
|
|
7
7
|
import { cachedByStat } from "../files/stat-cache.js";
|
|
@@ -11,6 +11,7 @@ import { expandHomeTilde } from "../files/files.js";
|
|
|
11
11
|
import { declaredWindow } from "../pi-extension/context-window.js";
|
|
12
12
|
import { piSettingsPath, readPiDefaultModel } from "./pi-settings.js";
|
|
13
13
|
import { opencodeModelFree, opencodeModelKey, readOpencodeCache } from "../agent/opencode/opencode-catalog.js";
|
|
14
|
+
import { codexHome, readCodexCatalog } from "../agent/codex/codex-catalog.js";
|
|
14
15
|
/** pi's model definitions — the custom providers and models they serve, with each model's
|
|
15
16
|
* declared cost. This is where a local (free) team differs from an API one: unpriced or
|
|
16
17
|
* zero-cost models never accumulate spend against the daily budget cap, so the badge can
|
|
@@ -103,8 +104,8 @@ export function readPiProviders(modelsPath) {
|
|
|
103
104
|
* A missing cache, an unknown model, or an unusable `context` reads null, as an unknown pi
|
|
104
105
|
* model does. */
|
|
105
106
|
export function seamContextWindow(seam, projectRoot, modelsPath = piModelsPath()) {
|
|
106
|
-
const
|
|
107
|
-
switch (
|
|
107
|
+
const backend = seamAgent(seam);
|
|
108
|
+
switch (backend) {
|
|
108
109
|
case "pi":
|
|
109
110
|
let { provider, model } = seam;
|
|
110
111
|
if (!provider || !model) {
|
|
@@ -121,6 +122,21 @@ export function seamContextWindow(seam, projectRoot, modelsPath = piModelsPath()
|
|
|
121
122
|
if (seam.model === undefined)
|
|
122
123
|
return null;
|
|
123
124
|
return declaredWindow(readOpencodeCache(projectRoot).models[opencodeModelKey(seam.model)]?.context);
|
|
125
|
+
case "codex":
|
|
126
|
+
// The model catalog (codex-catalog.ts) holds the window part 8/9 parsed from
|
|
127
|
+
// `context_window × effective_context_window_percent / 100`, read from the team's codex
|
|
128
|
+
// home (backends.codex.configDir) at this project root — the same home the runs use. An
|
|
129
|
+
// unknown model, an absent home or a missing catalog reads null (declaredWindow's safe
|
|
130
|
+
// answer for an unknown window).
|
|
131
|
+
if (seam.model === undefined)
|
|
132
|
+
return null;
|
|
133
|
+
{
|
|
134
|
+
const { config } = loadConfigSafe(projectRoot);
|
|
135
|
+
const home = codexHome(resolvedBackendConfig(config ?? {}, "codex"));
|
|
136
|
+
if (home === undefined)
|
|
137
|
+
return null;
|
|
138
|
+
return declaredWindow(readCodexCatalog(home).get(seam.model)?.contextWindow);
|
|
139
|
+
}
|
|
124
140
|
default:
|
|
125
141
|
return null;
|
|
126
142
|
}
|
|
@@ -193,19 +209,36 @@ function accountUsesOverage(account) {
|
|
|
193
209
|
* badge never reads n/a while spend it cannot see could still reach the cap. The one central
|
|
194
210
|
* rule: the budget gate, the fallback view, and the status free-ness read all judge seams
|
|
195
211
|
* through it (plans/agent-backends.md "Free-ness" and "Every seam, checked"). The opencode
|
|
196
|
-
* `catalog` is threaded the way the
|
|
197
|
-
* read", so an opencode seam reads NOT free, the safe direction for a budget gate.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// pi
|
|
204
|
-
|
|
212
|
+
* `catalog` is threaded the way the subscription `accounts` lookup is: an absent catalog means
|
|
213
|
+
* "no cache to read", so an opencode seam reads NOT free, the safe direction for a budget gate.
|
|
214
|
+
* `accounts` names the per-backend subscription account (part 2a/9): today only a claude seam
|
|
215
|
+
* asks, and it reads the same account it always did via `accounts((backend) => …)` returning
|
|
216
|
+
* claude's account for "claude" and null otherwise. */
|
|
217
|
+
export function seamFree(providers, seam, accounts = () => null, catalog = null) {
|
|
218
|
+
const backend = seamAgent(seam);
|
|
219
|
+
// One free-ness rule per backend: pi's pair price, the subscription account a backend bills
|
|
220
|
+
// (claude today), opencode's catalog, and nothing for any other backend (its spend could
|
|
221
|
+
// still reach the cap, so the badge must not read n/a). The opencode arm reads the
|
|
222
|
+
// gateway-qualified id (part 6b/7) and changes the pi and subscription arms nothing.
|
|
223
|
+
switch (backend) {
|
|
205
224
|
case "pi":
|
|
206
225
|
return pairFree(providers, seam.provider, seam.model);
|
|
207
|
-
case "claude":
|
|
226
|
+
case "claude": {
|
|
227
|
+
// The generic subscription arm: read the seam's own backend's account (today always
|
|
228
|
+
// "claude") so a future subscription backend is judged by its own lookup, not a captured
|
|
229
|
+
// one. A subscription that is spending into overage is NOT free.
|
|
230
|
+
const account = accounts(seamAgent(seam));
|
|
208
231
|
return account?.billing === "subscription" && !accountUsesOverage(account);
|
|
232
|
+
}
|
|
233
|
+
case "codex": {
|
|
234
|
+
// The same subscription arm for codex (part 8/9): read the seam's own backend's account
|
|
235
|
+
// ("codex") so a codex seam is free while it bills a ChatGPT subscription and is not
|
|
236
|
+
// spending into overage. API-key billing — which this build refuses at the startup gate —
|
|
237
|
+
// and an absent account read NOT free, so the badge never reads n/a while spend it cannot
|
|
238
|
+
// see could still reach the cap.
|
|
239
|
+
const account = accounts(seamAgent(seam));
|
|
240
|
+
return account?.billing === "subscription" && !accountUsesOverage(account);
|
|
241
|
+
}
|
|
209
242
|
case "opencode":
|
|
210
243
|
// A null catalog — no cache to read — and an unknown or billed model must all read NOT
|
|
211
244
|
// free (opencodeModelFree's safe direction): the badge never reads n/a while spend it
|
|
@@ -222,31 +255,31 @@ export function seamFree(providers, seam, account = null, catalog = null) {
|
|
|
222
255
|
/** Whether a fallback-config pair may be called free: a `claude/<id>` pair is free while the
|
|
223
256
|
* account bills a subscription and is not using overage, a pi provider/model is free when
|
|
224
257
|
* models.json prices it at zero, and an opencode pair is free when the catalog prices it at
|
|
225
|
-
* zero (part 6b/7). Any other
|
|
258
|
+
* zero (part 6b/7). Any other backend namespace, or an unresolvable pair, is refused — the badge
|
|
226
259
|
* must not read n/a while a backend whose billing it cannot see runs. Exported so the budget
|
|
227
260
|
* gate (src/gates/budget-gates.ts), the status pause set (src/status/status-data.ts) and the
|
|
228
261
|
* free-ness view judge fallback pairs with the same predicate — neither may engage a backend
|
|
229
|
-
* their own badge would not call free. `catalog` is the opencode cache, threaded like
|
|
230
|
-
* absent, an opencode pair reads NOT free. */
|
|
231
|
-
export function piPairFree(providers,
|
|
232
|
-
return seamFree(providers, { provider, model },
|
|
262
|
+
* their own badge would not call free. `catalog` is the opencode cache, threaded like the
|
|
263
|
+
* `accounts` lookup; absent, an opencode pair reads NOT free. */
|
|
264
|
+
export function piPairFree(providers, pair, accounts = () => null, catalog = null) {
|
|
265
|
+
return seamFree(providers, { agent: pair.backend, provider: pair.provider, model: pair.model }, accounts, catalog);
|
|
233
266
|
}
|
|
234
267
|
/** The free fallback pair the budget gate would engage for the team, or null when none is
|
|
235
268
|
* usable: the price-aware resolution of the default tier (fallbackPair over
|
|
236
269
|
* resolveTierFallbacks' borrow order), so a map whose `default` entry is absent or priced
|
|
237
270
|
* still names the free pair the tier borrows. Null for a missing, unresolvable, or priced
|
|
238
271
|
* pair — the gate's refusal, and the pair the badge/doctor must not advertise. */
|
|
239
|
-
export function fallbackModelPair(config, modelsPath = piModelsPath(),
|
|
272
|
+
export function fallbackModelPair(config, modelsPath = piModelsPath(), accounts = () => null, catalog = null) {
|
|
240
273
|
const providers = readPiProviders(modelsPath);
|
|
241
|
-
return fallbackPair(config, (p) => piPairFree(providers, p
|
|
274
|
+
return fallbackPair(config, (p) => piPairFree(providers, p, accounts, catalog));
|
|
242
275
|
}
|
|
243
276
|
/** True when the configured fallback model (plans/fallback-model.md) is one the budget gate may
|
|
244
277
|
* actually engage: configured at all, resolvable to a provider/model pair, and priced at zero in
|
|
245
278
|
* pi's definitions. False for every other case — no fallback, a half-named pair that would fall
|
|
246
|
-
* through to pi's own default, an unknown id, or a priced one — and the gate then pauses
|
|
247
|
-
*
|
|
248
|
-
export function fallbackModelFree(config, modelsPath = piModelsPath(),
|
|
249
|
-
return fallbackModelPair(config, modelsPath,
|
|
279
|
+
* through to pi's own default, an unknown id, or a priced one — and the gate then pauses
|
|
280
|
+
* roles exactly as it did before this feature. */
|
|
281
|
+
export function fallbackModelFree(config, modelsPath = piModelsPath(), accounts = () => null, catalog = null) {
|
|
282
|
+
return fallbackModelPair(config, modelsPath, accounts, catalog) !== null;
|
|
250
283
|
}
|
|
251
284
|
/** True when every model the team could use is free: each enabled role's effective
|
|
252
285
|
* provider/model (configForRole — the director included, it is a catalog role, resolved at
|
|
@@ -257,10 +290,10 @@ export function fallbackModelFree(config, modelsPath = piModelsPath(), account =
|
|
|
257
290
|
* free when models.json prices it at zero, and everything else counts as NOT free — the badge
|
|
258
291
|
* must never read n/a while spend it is tracking could still reach the cap. With no enabled
|
|
259
292
|
* roles and review off there are no pairs at all, so the team cannot spend and the answer is
|
|
260
|
-
* true. `
|
|
261
|
-
* subscription", and `catalog` the opencode models cache (absent, an
|
|
262
|
-
* free). */
|
|
263
|
-
export function teamModelsFree(config, modelsPath = piModelsPath(),
|
|
293
|
+
* true. `accounts` names each seam's own backend's subscription account (claude today),
|
|
294
|
+
* absent meaning "no subscription", and `catalog` the opencode models cache (absent, an
|
|
295
|
+
* opencode seam reads not free). */
|
|
296
|
+
export function teamModelsFree(config, modelsPath = piModelsPath(), accounts = () => null, catalog = null) {
|
|
264
297
|
const pairs = [];
|
|
265
298
|
for (const role of enabledRoleIds(config)) {
|
|
266
299
|
const rc = configForRole(config, role);
|
|
@@ -281,5 +314,5 @@ export function teamModelsFree(config, modelsPath = piModelsPath(), account = nu
|
|
|
281
314
|
if (pairs.length === 0)
|
|
282
315
|
return true;
|
|
283
316
|
const providers = readPiProviders(modelsPath);
|
|
284
|
-
return pairs.every((seam) => seamFree(providers, seam,
|
|
317
|
+
return pairs.every((seam) => seamFree(providers, seam, accounts, catalog));
|
|
285
318
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Whether a run's cost is real money that charges the daily cap (plans/agent-backends.md
|
|
2
2
|
* part 6a): an API-billed run, or a subscription run that saw overage. A plain subscription
|
|
3
3
|
* run's cost is notional — API-equivalent spend that never moves the cap. The one predicate
|
|
4
|
-
* both the
|
|
4
|
+
* both the run-usage fold and the merge accumulator split on, so the two cannot drift. */
|
|
5
5
|
export function runCostIsReal(run) {
|
|
6
6
|
return run.billing !== "subscription" || run.usedOverage === true;
|
|
7
7
|
}
|
|
@@ -3,7 +3,7 @@ import { cachedByStat } from "../files/stat-cache.js";
|
|
|
3
3
|
import { isJsonObject } from "../files/json-object.js";
|
|
4
4
|
import { readJsonFile } from "../files/json-files.js";
|
|
5
5
|
/** pi's own saved-default model, read from pi's `settings.json` beside the models catalog —
|
|
6
|
-
* the name a
|
|
6
|
+
* the name a role row shows when `tumwater.json` resolves no model and pi starts on its own
|
|
7
7
|
* default. This mirrors step 3 of pi 1.2.0's start-up model choice, "saved default from
|
|
8
8
|
* settings" (`findInitialModel` in pi's `dist/core/model-resolver.js`), for a run tumwater
|
|
9
9
|
* starts without `--provider`/`--model`. The reader does NOT model pi's last step: when the
|
package/dist/src/pi/pi-stream.js
CHANGED
|
@@ -101,14 +101,14 @@ export class PiStreamParser {
|
|
|
101
101
|
outputTokens = 0;
|
|
102
102
|
/** Prompt tokens this run sent, summed across assistant turns (usage.input + cacheRead +
|
|
103
103
|
* cacheWrite). The counterpart to outputTokens: an operator compares the two to see how
|
|
104
|
-
* much of a
|
|
104
|
+
* much of a run's spend is re-sent context versus generation. */
|
|
105
105
|
promptTokens = 0;
|
|
106
106
|
/** The cache-read share of promptTokens (usage.cacheRead summed across turns) — the part
|
|
107
107
|
* the provider served from its prompt cache, billed at the cache-read rate. */
|
|
108
108
|
cacheReadTokens = 0;
|
|
109
109
|
/** Prompt tokens sent up to AND INCLUDING the first assistant turn that carried an edit or
|
|
110
110
|
* write tool call. Before any such turn every turn accumulates; once one is seen the
|
|
111
|
-
* counter freezes, so it measures the context a
|
|
111
|
+
* counter freezes, so it measures the context a run burns before its first change. */
|
|
112
112
|
preEditPromptTokens = 0;
|
|
113
113
|
/** 1-based index of the first assistant turn carrying an edit or write tool call;
|
|
114
114
|
* undefined when the run never edited. */
|
|
@@ -121,7 +121,7 @@ export class PiStreamParser {
|
|
|
121
121
|
errorMessage;
|
|
122
122
|
/** True when any event reports the provider rejecting the context as too large
|
|
123
123
|
* (LM Studio's "Context size has been exceeded"; oMLX rejects over max_context_window).
|
|
124
|
-
* Diagnostic: every
|
|
124
|
+
* Diagnostic: every run starts a fresh session, so nothing needs dropping — but the
|
|
125
125
|
* error names the real cause. */
|
|
126
126
|
contextExceeded = false;
|
|
127
127
|
/** True when any event reports the model server killing an idle predict stream
|
|
@@ -131,7 +131,7 @@ export class PiStreamParser {
|
|
|
131
131
|
transientServerTimeout = false;
|
|
132
132
|
/** True when any event reports the provider rejecting the request with HTTP 429 (rate
|
|
133
133
|
* limiting). Transient by definition: the session is healthy and a later attempt succeeds —
|
|
134
|
-
* the
|
|
134
|
+
* the runner's transient retry covers it, waiting out retryAfterSeconds when present. */
|
|
135
135
|
transientRateLimit = false;
|
|
136
136
|
/** True when any event reports a provider-wide failure that is not rate limiting — the
|
|
137
137
|
* connection down, a 5xx, the model failing to load (TRANSIENT_BACKEND, in pi-failure.ts).
|
|
@@ -187,7 +187,7 @@ export class PiStreamParser {
|
|
|
187
187
|
/** True once any assistant message carried real content — a tool call or non-empty text.
|
|
188
188
|
* A failed run whose every assistant turn is contentless (the provider's error turn has
|
|
189
189
|
* `content: []` and stopReason "error") produced no work of its own, so a dirty worktree
|
|
190
|
-
* belongs to a prior interrupted
|
|
190
|
+
* belongs to a prior interrupted run rather than this run (BUGS.md 2026-10-06). Distinct
|
|
191
191
|
* from finalMessageContentless, which describes only the LAST message. */
|
|
192
192
|
producedAssistantContent = false;
|
|
193
193
|
/** True when pi auto-compacted the session during (or at the end of) the run. */
|
|
@@ -482,7 +482,7 @@ export class PiStreamParser {
|
|
|
482
482
|
this.declaredNothingToDo = true;
|
|
483
483
|
// The refusal comes only from an anchored `TUMWATER_REFUSED: <reason>` line: a bare
|
|
484
484
|
// sentinel and a mid-sentence mention are not refusals, and a negating reason ("none",
|
|
485
|
-
// "(n/a …)") clears one — BUGS.md 2026-09-23, four
|
|
485
|
+
// "(n/a …)") clears one — BUGS.md 2026-09-23, four runs ended ordinary replies with
|
|
486
486
|
// `TUMWATER_REFUSED: none` and the harness destroyed their tested work. The last anchored
|
|
487
487
|
// line wins (the run's final declaration is its verdict); a message WITHOUT a line leaves
|
|
488
488
|
// an earlier declaration standing, so a closing remark cannot erase it.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** The run's watchdog clocks, split out of pi.ts's runPi (2026-10-04, organize): the
|
|
1
|
+
/** The run's watchdog clocks, split out of pi.ts's runPi (2026-10-04, organize): the run
|
|
2
2
|
* deadline and the quiet watchdog (quiet-kill plus the stalled-tool-call warning) — the
|
|
3
3
|
* timing policy of one pi run, which changes for different reasons than the spawn, stream,
|
|
4
4
|
* and result-distillation wiring that drives it (src/agent/run-agent.ts keeps those). The
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* window, the zero-byte grace, the warned-call set) and exposes the flags the run's close
|
|
7
7
|
* handler reads back; run-agent.ts feeds the parser live through the deps accessors, so no
|
|
8
8
|
* notification path is needed when events land. */
|
|
9
|
-
import { bufferedCommandStallMs,
|
|
10
|
-
|
|
11
|
-
/** Start one pi run's two watchdog clocks: the tick deadline (kills the process tree when it
|
|
9
|
+
import { bufferedCommandStallMs, callBuffersOutput } from "./command-shape.js";
|
|
10
|
+
/** Start one pi run's two watchdog clocks: the run deadline (kills the process tree when it
|
|
12
11
|
* fires, recording whether the run was still progressing) and the quiet/stall interval
|
|
13
12
|
* (quiet-kills a run that stopped making progress and warns on stalled tool calls). Returns
|
|
14
13
|
* the live flags the run's close handler reads and the note/stop calls its data handlers and
|
|
@@ -24,14 +23,14 @@ export function startPiWatchdogs(deps) {
|
|
|
24
23
|
// diff's review can finish instead of timing out mid-verification and being re-read from
|
|
25
24
|
// scratch on the next attempt (BUGS.md 2026-10-07). "Still making progress" means a
|
|
26
25
|
// progress event landed within `progressWindowMs` of the deadline — the quiet watchdog's
|
|
27
|
-
// own recency window, but never more than half the
|
|
26
|
+
// own recency window, but never more than half the run budget, so a quiet window
|
|
28
27
|
// configured longer than the budget cannot let a run that stopped progressing past the
|
|
29
28
|
// base deadline. The window does not depend on the quiet watchdog being enabled: a
|
|
30
29
|
// progressing gate run earns its extension even when quietTimeoutSeconds is 0. One
|
|
31
30
|
// checkpoint timer snapshots the progress count `progressWindowMs` before the deadline;
|
|
32
31
|
// only progress since that snapshot counts, so a run that progressed early and then
|
|
33
32
|
// stalled keeps today's bound.
|
|
34
|
-
const progressWindowMs = deps.quietMs > 0 ? Math.min(deps.quietMs, deps.
|
|
33
|
+
const progressWindowMs = deps.quietMs > 0 ? Math.min(deps.quietMs, deps.runTimeoutMs / 2) : deps.runTimeoutMs / 2;
|
|
35
34
|
let checkpointProgress = 0;
|
|
36
35
|
// Arm (or re-arm) the checkpoint `progressWindowMs` before the upcoming deadline: the
|
|
37
36
|
// progress count at that instant is the bar a run must beat to count as progressing. One
|
|
@@ -45,35 +44,35 @@ export function startPiWatchdogs(deps) {
|
|
|
45
44
|
checkpointProgress = deps.progressCount();
|
|
46
45
|
}, Math.max(0, deadlineMs - progressWindowMs));
|
|
47
46
|
};
|
|
48
|
-
armCheckpoint(deps.
|
|
47
|
+
armCheckpoint(deps.runTimeoutMs);
|
|
49
48
|
let extended = false;
|
|
50
|
-
let timeoutBudgetMs = deps.
|
|
49
|
+
let timeoutBudgetMs = deps.runTimeoutMs;
|
|
51
50
|
let timeout;
|
|
52
51
|
const fireDeadline = () => {
|
|
53
52
|
if (deps.extendOnProgress && !extended && deps.progressCount() > checkpointProgress) {
|
|
54
53
|
extended = true;
|
|
55
|
-
timeoutBudgetMs = deps.
|
|
56
|
-
armCheckpoint(deps.
|
|
57
|
-
timeout = setTimeout(fireDeadline, deps.
|
|
54
|
+
timeoutBudgetMs = deps.runTimeoutMs * 2;
|
|
55
|
+
armCheckpoint(deps.runTimeoutMs); // the extended deadline gets its own progress window
|
|
56
|
+
timeout = setTimeout(fireDeadline, deps.runTimeoutMs); // one more slice; the next firing is final
|
|
58
57
|
return;
|
|
59
58
|
}
|
|
60
59
|
timedOut = true;
|
|
61
60
|
// A deadline that fires on a run still making progress killed a slow run, not a hung
|
|
62
61
|
// one: "recent progress" is progress since the checkpoint `progressWindowMs` before this
|
|
63
62
|
// deadline — the same window the extension test uses, so the kill's classification
|
|
64
|
-
// cannot contradict the extension decision when the quiet window is longer than the
|
|
63
|
+
// cannot contradict the extension decision when the quiet window is longer than the run
|
|
65
64
|
// budget (BUGS.md 2026-09-29, 2026-10-09). A run with no progress event at all — never
|
|
66
65
|
// emitted a byte, or bytes without one structured event — keeps today's discard path: it
|
|
67
66
|
// has not demonstrably begun.
|
|
68
67
|
timedOutProgressing = deps.progressCount() > 0 && deps.progressCount() > checkpointProgress;
|
|
69
68
|
deps.kill();
|
|
70
69
|
};
|
|
71
|
-
timeout = setTimeout(fireDeadline, deps.
|
|
70
|
+
timeout = setTimeout(fireDeadline, deps.runTimeoutMs);
|
|
72
71
|
// Quiet watchdog: a healthy run makes PROGRESS continuously even when slow — messages
|
|
73
72
|
// start and end, turns and tool calls complete. Prolonged lack of progress means a hung
|
|
74
73
|
// tool (an interactive command waiting for input) or a zombie stream (a dead generation
|
|
75
74
|
// whose connection drips content-free keepalive updates for hours) that would otherwise
|
|
76
|
-
// burn the whole
|
|
75
|
+
// burn the whole run timeout. Raw output bytes deliberately do NOT reset the clock:
|
|
77
76
|
// keepalives are bytes without progress. Checked on an interval against the wall clock,
|
|
78
77
|
// so it also fires promptly after a machine sleep rather than pausing with a suspended
|
|
79
78
|
// timer. The stall warning below rides the same interval: it needs no kill of its own,
|
|
@@ -87,14 +86,14 @@ export function startPiWatchdogs(deps) {
|
|
|
87
86
|
// first bytes entirely — fork/exec starved by the same load, or bytes already written
|
|
88
87
|
// but not yet drained (a firing timer phase precedes the poll phase that delivers
|
|
89
88
|
// stdout, and a suspended machine resumes the same way). The 2026-09-23 fix therefore
|
|
90
|
-
// gave a byte-silent run no quiet kill at all, delegating its bound to the
|
|
89
|
+
// gave a byte-silent run no quiet kill at all, delegating its bound to the run timeout
|
|
91
90
|
// — which holds only while runTimeoutSeconds is near the quiet window's scale (BUGS.md
|
|
92
91
|
// 2026-09-29: the live 54000 s config left a wedged model connection 15 hours in a
|
|
93
92
|
// concurrency slot). A zero-byte run now gets a finite bound of its own, independent of
|
|
94
|
-
// the
|
|
93
|
+
// the run timeout: max(two quiet windows, 30 min). Startup latency under healthy load
|
|
95
94
|
// stays far below it, so the runs the 2026-09-23 fix protected are still protected; a
|
|
96
95
|
// run that is genuinely wedged before its first byte (dead connection, unscheduled
|
|
97
|
-
// fork/exec) is reaped at half an hour instead of at the
|
|
96
|
+
// fork/exec) is reaped at half an hour instead of at the run timeout. The false
|
|
98
97
|
// positive it can cost — a machine so loaded the child has not been scheduled in 30
|
|
99
98
|
// minutes — is a quiet kill, which resumes the session and the worktree's edits like
|
|
100
99
|
// an interruption: a restart, not discarded work. Once bytes have flowed the run has
|
|
@@ -114,7 +113,7 @@ export function startPiWatchdogs(deps) {
|
|
|
114
113
|
continue;
|
|
115
114
|
// A command whose stdout is piped or redirected holds its bytes away from
|
|
116
115
|
// pi until it exits, so "no output" there is the prescribed shape, not
|
|
117
|
-
// evidence of a hang (BUGS.md 2026-09-28: the
|
|
116
|
+
// evidence of a hang (BUGS.md 2026-09-28: the run prompt tells every role
|
|
118
117
|
// to pipe its verification through `tail`, and the resulting false alarms
|
|
119
118
|
// were the digest's top warning cluster, drowning real hangs). Silence is
|
|
120
119
|
// not the only way such a command hangs, though: a leaked grandchild
|
|
@@ -129,7 +128,7 @@ export function startPiWatchdogs(deps) {
|
|
|
129
128
|
// ANY of its calls — every one is judged by the buffered wall-clock threshold
|
|
130
129
|
// (plans/agent-backends.md "Watchdogs and progress"); pi keeps its per-command
|
|
131
130
|
// classification, where only a piped or redirected command is buffered.
|
|
132
|
-
if (
|
|
131
|
+
if (callBuffersOutput(deps.agent, call.command || call.label)) {
|
|
133
132
|
const openMs = Date.now() - call.lastActivityAt;
|
|
134
133
|
const bufferedStallMs = bufferedCommandStallMs(deps.stallMs);
|
|
135
134
|
if (openMs >= bufferedStallMs) {
|