tumwater 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +42 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +4 -3
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +246 -55
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +85 -274
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +175 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +139 -32
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +9 -11
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget.js +30 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +1 -2
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +118 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +150 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +308 -590
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
- package/dist/src/cli/completion.js +149 -22
- package/dist/src/cli/config-commands.js +41 -18
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +189 -73
- package/dist/src/cli/log-commands.js +148 -82
- package/dist/src/cli/question-commands.js +205 -10
- package/dist/src/cli.js +106 -43
- package/dist/src/collections.js +26 -10
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +13 -1
- package/dist/src/config/config-fallbacks.js +93 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +141 -199
- package/dist/src/config/config-views.js +127 -53
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +131 -264
- package/dist/src/config/config.js +48 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +100 -496
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +52 -579
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +82 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +236 -402
- package/dist/src/doctor/doctor-notify.js +3 -4
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor.js +121 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +7 -11
- package/dist/src/events/event-format.js +181 -86
- package/dist/src/events/event-labels.js +12 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +39 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +60 -24
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +56 -56
- package/dist/src/failure/failure-render.js +31 -23
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +99 -35
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +177 -84
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +108 -58
- package/dist/src/gates/maintenance-quota.js +10 -13
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +77 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +36 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +42 -18
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +8 -13
- package/dist/src/gui/gui-config-commands.js +94 -72
- package/dist/src/gui/gui-endpoint-commands.js +4 -3
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +108 -13
- package/dist/src/gui/gui-prompt-commands.js +11 -14
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +11 -4
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +193 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +25 -9
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +81 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +150 -11
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/merge/merge-queue-render.js +49 -11
- package/dist/src/merge/merge-queue.js +31 -15
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +33 -11
- package/dist/src/merge/merge-to-main.js +86 -27
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +12 -10
- package/dist/src/notes-cli.js +30 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
- package/dist/src/orchestrator/orchestrator.js +128 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +116 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +18 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +121 -211
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +129 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +161 -77
- package/dist/src/report/report-fold.js +129 -52
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +133 -64
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +100 -1
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +4 -5
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +155 -360
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +69 -12
- package/dist/src/roles/role-guidance.js +55 -20
- package/dist/src/roles/role-labels.js +1 -2
- package/dist/src/roles/role-render.js +67 -50
- package/dist/src/roles/role-view.js +59 -47
- package/dist/src/roles/roles.js +31 -31
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +9 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/{loop → runner}/leftover.js +80 -25
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +43 -16
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +28 -28
- package/dist/src/status/status-roles.js +39 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +5 -6
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +63 -17
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +57 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +67 -13
- package/dist/src/ui/badges.js +65 -37
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +32 -26
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +126 -285
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +434 -67
- package/dist/src/ui/gui/gui-client-labels.js +1 -2
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +55 -51
- package/dist/src/ui/gui/gui-client-pending.js +227 -23
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +87 -427
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +56 -4
- package/dist/src/ui/gui/gui-client-team.js +25 -465
- package/dist/src/ui/gui/gui-client.js +27 -25
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +9 -7
- package/dist/src/ui/gui/gui-styles.js +34 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +136 -49
- package/dist/src/ui/status-render.js +101 -82
- package/dist/src/ui/team-alerts.js +248 -57
- package/dist/src/ui/tone.js +5 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +51 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +3 -5
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +10 -12
- package/dist/src/ui/tui/tui-editor-budget.js +35 -31
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-entry.js +36 -47
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +11 -9
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +3 -5
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
- package/dist/src/ui/tui/tui-editor-retire.js +4 -6
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +60 -34
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +35 -2
- package/dist/src/ui/tui/tui-frame.js +56 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +24 -9
- package/dist/src/ui/tui/tui-pane-activity.js +14 -11
- package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +58 -8
- package/dist/src/ui/tui/tui-pane-history.js +402 -82
- package/dist/src/ui/tui/tui-pane-pending.js +308 -13
- package/dist/src/ui/tui/tui-pane-queued.js +71 -55
- package/dist/src/ui/tui/tui-pane-report.js +46 -14
- package/dist/src/ui/tui/tui-pane-session.js +25 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +80 -23
- package/dist/src/ui/tui/tui-panes.js +100 -30
- package/dist/src/ui/tui/tui.js +14 -14
- package/dist/src/verdict/fix-claim.js +82 -32
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +42 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { joinParts } from "../text/text.js";
|
|
2
2
|
import { agree, shortSpanPhrase } from "../text/phrases.js";
|
|
3
3
|
import { compactTokens, usd } from "../text/format.js";
|
|
4
|
-
import { clockHm, formatTimestamp,
|
|
4
|
+
import { clockHm, formatTimestamp, humanAgo, nextRunPhrase, secondsUntil } from "../text/datetime.js";
|
|
5
5
|
import { claudeWindowLabel } from "../agent/claude/claude-account.js";
|
|
6
|
+
import { windowLabel } from "../agent/subscription-account.js";
|
|
7
|
+
import { backendDisplayName } from "../agent/backend-traits.js";
|
|
6
8
|
import { yieldMultiplierSuffix } from "../scheduling/backoff.js";
|
|
7
9
|
import { quietWindowEnd } from "../scheduling/quiet-hours.js";
|
|
8
|
-
|
|
9
|
-
/** The terminal's view of one loop — what `tumwater role <id>` prints, rendered as Markdown
|
|
10
|
+
/** The terminal's view of one role — what `tumwater role <id>` prints, rendered as Markdown
|
|
10
11
|
* from the payload role-view.ts's rolePayload collects (the same document `--json` prints).
|
|
11
12
|
* Like backlog-render.ts, the renderer owns no collection of its own: the CLI's --json and
|
|
12
13
|
* human branches share one collection through sayJsonOrRender's thunk, so the two surfaces
|
|
@@ -15,7 +16,7 @@ import { isAgentId } from "../config/model-selector.js";
|
|
|
15
16
|
* prose-and-shell markdown the operator may copy verbatim — with the fence grown past any
|
|
16
17
|
* backtick run the text itself contains, so a prompt that quotes a fence still round-trips.
|
|
17
18
|
* Unset things render as explicit `_(none)_` lines rather than disappearing, so an
|
|
18
|
-
* un-overridden
|
|
19
|
+
* un-overridden role reads as fully defaulted, not as a truncated report. */
|
|
19
20
|
/** A code fence one longer than any backtick run in the fenced text, so the block always
|
|
20
21
|
* closes where the text itself would have ended it. */
|
|
21
22
|
function fenceFor(text) {
|
|
@@ -31,22 +32,22 @@ function fencedOrNone(label, text, noneNote = "none") {
|
|
|
31
32
|
const fence = fenceFor(text);
|
|
32
33
|
return [`## ${label}`, "", fence, text.replace(/\n+$/, ""), fence, ""];
|
|
33
34
|
}
|
|
34
|
-
/** The
|
|
35
|
-
* lines — the read-only inspection the TUI's
|
|
36
|
-
* so the terminal and the CLI show one
|
|
35
|
+
/** The Notes and Next run prompt blocks and the "more waits" disclosure, as Markdown
|
|
36
|
+
* lines — the read-only inspection the TUI's notes view and `tumwater role <id>` share,
|
|
37
|
+
* so the terminal and the CLI show one role's notes and assembled prompt identically.
|
|
37
38
|
* `renderRoleMarkdown` splices these at the same position, so the extracted function and the
|
|
38
39
|
* full document cannot drift. */
|
|
39
40
|
export function inspectMarkdownLines(p) {
|
|
40
41
|
const running = chainStepLine(p);
|
|
41
42
|
return [...(running ? [running, ""] : []), ...inspectBodyLines(p)];
|
|
42
43
|
}
|
|
43
|
-
/** The
|
|
44
|
+
/** The notes, prompt and disclosure blocks alone — what renderRoleMarkdown splices after
|
|
44
45
|
* its own Team lines, which already carry the fallback-step bullet. */
|
|
45
46
|
function inspectBodyLines(p) {
|
|
46
47
|
const lines = [];
|
|
47
48
|
lines.push(...fencedOrNone("Notes", p.notes, "none yet — the role's own earlier runs write them, and the director has none"));
|
|
48
49
|
lines.push(...fencedOrNone("Next run's prompt", p.nextPrompt, "nothing to do this run"));
|
|
49
|
-
//
|
|
50
|
+
// run-prompt.ts dequeues exactly one queued prompt per run, so with more than one
|
|
50
51
|
// waiting the block above shows only the oldest — say so, so the count line and the
|
|
51
52
|
// block never read as a contradiction (the 2026-10-04 BUGS.md entry under ## Fixed).
|
|
52
53
|
if (p.inboxCount > 1 && p.nextPrompt !== null && p.nextPrompt.trim() !== "") {
|
|
@@ -59,11 +60,11 @@ function inspectBodyLines(p) {
|
|
|
59
60
|
* `model` alone when only the model is (the join simply drops unset halves) — shared by the
|
|
60
61
|
* main line and the budget-fallback line, so the two render the same wiring the same way. */
|
|
61
62
|
function modelPair(provider, model) {
|
|
62
|
-
return [provider, model]
|
|
63
|
+
return joinParts([provider, model], "/");
|
|
63
64
|
}
|
|
64
65
|
/** The backend's own-default label when no model resolves: `<agent> default` — pi's
|
|
65
66
|
* historical `pi default` for the pi backend, `claude default` for claude (part 4f/7). */
|
|
66
|
-
function
|
|
67
|
+
function backendDefault(p) {
|
|
67
68
|
return `${p.backend !== "pi" ? p.backend : "pi"} default`;
|
|
68
69
|
}
|
|
69
70
|
/** The provider/model wiring as one readable token: `provider/model` when both resolve,
|
|
@@ -75,11 +76,11 @@ function modelLine(p) {
|
|
|
75
76
|
// level (piDefault is formatted with the effective thinking winning), so the separate
|
|
76
77
|
// ` (thinking: …)` suffix is dropped for this case only.
|
|
77
78
|
if (p.piDefault)
|
|
78
|
-
return `${
|
|
79
|
+
return `${backendDefault(p)} (${p.piDefault})`;
|
|
79
80
|
const pair = modelPair(p.provider, p.model);
|
|
80
81
|
const thinking = p.thinking ? ` (thinking: ${p.thinking})` : "";
|
|
81
82
|
if (!pair)
|
|
82
|
-
return `${
|
|
83
|
+
return `${backendDefault(p)}${thinking}`;
|
|
83
84
|
const agent = p.backend !== "pi" ? `${p.backend}/` : "";
|
|
84
85
|
return `${agent}${pair}${thinking}`;
|
|
85
86
|
}
|
|
@@ -89,9 +90,8 @@ function modelLine(p) {
|
|
|
89
90
|
* backend-default label. One home for the Model fallback and Budget fallback lines, so a
|
|
90
91
|
* claude fallback reads `claude/claude-sonnet-5-5`, not a bare id (PLANS.md "Mixed claude +
|
|
91
92
|
* pi fleets, part 3/4" gap 7). */
|
|
92
|
-
function fallbackPairLabel(fb,
|
|
93
|
-
const agentBackend =
|
|
94
|
-
(fb.provider !== undefined && isAgentId(fb.provider) ? fb.provider : undefined);
|
|
93
|
+
function fallbackPairLabel(fb, backendDefault) {
|
|
94
|
+
const agentBackend = fb.backend !== undefined && fb.backend !== "pi" ? fb.backend : undefined;
|
|
95
95
|
if (fb.model !== undefined) {
|
|
96
96
|
if (agentBackend !== undefined)
|
|
97
97
|
return `${agentBackend}/${fb.model}`;
|
|
@@ -99,12 +99,27 @@ function fallbackPairLabel(fb, agentDefault) {
|
|
|
99
99
|
}
|
|
100
100
|
if (fb.provider !== undefined)
|
|
101
101
|
return fb.provider;
|
|
102
|
-
return
|
|
102
|
+
return backendDefault;
|
|
103
103
|
}
|
|
104
|
-
/**
|
|
105
|
-
|
|
104
|
+
/** The backend-and-window label the role view shows for a non-claude backend's window:
|
|
105
|
+
* `backendDisplayName(billingBackend)` beside `windowLabel(windowType)` — "Codex 7d" for a
|
|
106
|
+
* codex subscription (plans/codex-backend.md part 3c/9), so a reader knows whose window it
|
|
107
|
+
* is. Claude keeps today's bare window type ("5h") so its role view stays byte-identical. */
|
|
108
|
+
function billingWindowLabel(billingBackend, windowType) {
|
|
109
|
+
const type = windowType !== undefined ? windowLabel(windowType) : "";
|
|
110
|
+
const backend = billingBackend !== undefined && billingBackend !== "claude" ? backendDisplayName(billingBackend) : "";
|
|
111
|
+
return [backend, type].filter(Boolean).join(" ");
|
|
112
|
+
}
|
|
113
|
+
/** Why a tier moved down its fallback chain, as the role view words it. The window phrase
|
|
114
|
+
* names the backend in play ("Codex 7d window used up") when a non-claude backend moved the
|
|
115
|
+
* role; claude keeps today's wording byte-identical, and an unknown backend falls back to
|
|
116
|
+
* today's hardcoded "Claude". */
|
|
117
|
+
function chainReasonPhrase(reason, billingBackend, windowType) {
|
|
106
118
|
switch (reason) {
|
|
107
119
|
case "window":
|
|
120
|
+
if (billingBackend !== undefined && billingBackend !== "claude") {
|
|
121
|
+
return `${billingWindowLabel(billingBackend, windowType)} window used up`;
|
|
122
|
+
}
|
|
108
123
|
return `${windowType !== undefined ? claudeWindowLabel(windowType) : "Claude"} window used up`;
|
|
109
124
|
case "reserve":
|
|
110
125
|
return "window reserve reached";
|
|
@@ -125,10 +140,10 @@ function chainStepLine(p) {
|
|
|
125
140
|
if (!c)
|
|
126
141
|
return null;
|
|
127
142
|
const resets = c.returnsAt !== undefined ? `, resets ${clockHm(c.returnsAt)}` : "";
|
|
128
|
-
return `- Running on fallback ${c.step} of ${c.of} (${chainReasonPhrase(c.reason, p.billingWindow)}${resets})`;
|
|
143
|
+
return `- Running on fallback ${c.step} of ${c.of} (${chainReasonPhrase(c.reason, p.billingBackend, p.billingWindow)}${resets})`;
|
|
129
144
|
}
|
|
130
|
-
/** One recent
|
|
131
|
-
* <summary>` — so `tumwater role <id>` and the TUI
|
|
145
|
+
/** One recent run outcome as a Markdown bullet line — `<time> · <result> · <span> —
|
|
146
|
+
* <summary>` — so `tumwater role <id>` and the TUI notes view read a role's discrete
|
|
132
147
|
* outcomes at a glance. `time` is the same local `YYYY-MM-DD HH:MM:SS` stamp the model-
|
|
133
148
|
* fallback `since` prints (formatTimestamp); `span` is the same short-duration phrasing
|
|
134
149
|
* shortSpanPhrase renders for holdPhrase and the event feed's countdowns; the summary is
|
|
@@ -142,29 +157,29 @@ function recentOutcomeLine(o) {
|
|
|
142
157
|
line += ` — ${o.summary}`;
|
|
143
158
|
return line;
|
|
144
159
|
}
|
|
145
|
-
/** One quiet-hours bullet: the window, whose it is, and whether it holds the
|
|
160
|
+
/** One quiet-hours bullet: the window, whose it is, and whether it holds the role now — with
|
|
146
161
|
* the same `quiet until <end>` text the status table uses. */
|
|
147
162
|
function quietHoursLine(w) {
|
|
148
163
|
const whose = w.scope === "role" ? "own window" : "team window";
|
|
149
164
|
const state = w.inWindow ? `holding now — quiet until ${quietWindowEnd(w.window)}` : "not holding now";
|
|
150
165
|
return `- Quiet hours: ${w.window} (${whose}; ${state})`;
|
|
151
166
|
}
|
|
152
|
-
/** The
|
|
167
|
+
/** The role's schedule as Markdown bullet lines — `Next run`, `Last run`, then one
|
|
153
168
|
* `Quiet hours` line per governing window (none when the payload carries no windows) — in the
|
|
154
169
|
* same words and wording family as the team surface's `next run` cell (status-render.ts's
|
|
155
|
-
* nextRunCell), so the role view and the status table cannot drift on when a
|
|
156
|
-
* a never-scheduled
|
|
170
|
+
* nextRunCell), so the role view and the status table cannot drift on when a role acts next:
|
|
171
|
+
* a never-scheduled role (nextRunAt 0) reads `not scheduled yet`; a due role reads `due now`;
|
|
157
172
|
* a future one reads `in <Ns>` through humanSeconds, prefixed `backoff ` while backoffSeconds
|
|
158
173
|
* > 0 (the wake row-action clears exactly that) — the shared nextRunPhrase, so the role view
|
|
159
174
|
* and the status `next run` cell cannot drift on the backoff prefix or the humanSeconds
|
|
160
|
-
* bucketing. `Last
|
|
161
|
-
* status `last
|
|
175
|
+
* bucketing. `Last run` reuses formatTimestamp and the same `humanSeconds … ago` phrasing the
|
|
176
|
+
* status `last run` cell ages with, or `never` for a role that has not run. `now` is
|
|
162
177
|
* injected (default Date.now()) so the renderer stays a pure function of the payload and the
|
|
163
178
|
* test can pin a fixed clock. */
|
|
164
179
|
function scheduleLine(p, now) {
|
|
165
180
|
// The yield multiplier rides the line as `×N` exactly like nextRunCell/fmtNextRun: a quiet
|
|
166
|
-
// yield-scaled
|
|
167
|
-
// gates on the multiplier too), so the Schedule must not understate when the
|
|
181
|
+
// yield-scaled role's effective gap is longer than nextRunAt's countdown shows (isEligible
|
|
182
|
+
// gates on the multiplier too), so the Schedule must not understate when the role acts next.
|
|
168
183
|
const suffix = yieldMultiplierSuffix(p.yieldMultiplier);
|
|
169
184
|
let next;
|
|
170
185
|
if (p.nextRunAt <= 0) {
|
|
@@ -175,11 +190,11 @@ function scheduleLine(p, now) {
|
|
|
175
190
|
next = nextRunPhrase(remain, p.backoffSeconds > 0, "due now", true) + suffix;
|
|
176
191
|
}
|
|
177
192
|
const last = p.lastRunEndedAt !== undefined
|
|
178
|
-
? `${formatTimestamp(p.lastRunEndedAt)} (${
|
|
193
|
+
? `${formatTimestamp(p.lastRunEndedAt)} (${humanAgo(p.lastRunEndedAt, now)})`
|
|
179
194
|
: "never";
|
|
180
195
|
return [`- Next run: ${next}`, `- Last run: ${last}`, ...(p.quietHours ?? []).map(quietHoursLine)];
|
|
181
196
|
}
|
|
182
|
-
/** Render one
|
|
197
|
+
/** Render one role's inspection payload as Markdown. `now` is injected (default Date.now())
|
|
183
198
|
* so the schedule lines stay a pure function of the payload — the test pins a fixed clock. */
|
|
184
199
|
export function renderRoleMarkdown(p, now = Date.now()) {
|
|
185
200
|
const lines = [`# tumwater role: ${p.id}`, ""];
|
|
@@ -194,15 +209,15 @@ export function renderRoleMarkdown(p, now = Date.now()) {
|
|
|
194
209
|
// unresolvable pair, a model with no declared window).
|
|
195
210
|
lines.push(`- Context window: ${p.contextWindow === null ? "unknown" : `${compactTokens(p.contextWindow)} tokens`}`);
|
|
196
211
|
// Per-provider concurrency cap (plans/provider-concurrency.md §8): name the key whose cap
|
|
197
|
-
// bounds the role's concurrent
|
|
212
|
+
// bounds the role's concurrent runs, plus the cap itself. Present only while
|
|
198
213
|
// `maxConcurrentPerProvider` maps this seam's key — a cap-less default team has no
|
|
199
214
|
// resolvable key entry, so its role view renders no new line and stays byte-identical.
|
|
200
215
|
if (p.providerKey !== undefined && p.providerCap !== undefined) {
|
|
201
216
|
lines.push(`- Provider: ${p.providerKey} (cap ${p.providerCap})`);
|
|
202
217
|
}
|
|
203
|
-
// Image input (plans/image-input.md "Surfaces", part 5/5): whether this
|
|
204
|
-
// reads images, and — when it cannot — where a
|
|
205
|
-
// `yes`/`no`: a text-only
|
|
218
|
+
// Image input (plans/image-input.md "Surfaces", part 5/5): whether this role's own model
|
|
219
|
+
// reads images, and — when it cannot — where a run that reads an image reroutes. Not just
|
|
220
|
+
// `yes`/`no`: a text-only role that falls back names the target, so `tumwater role <id>`
|
|
206
221
|
// explains the team alert without running doctor. Unknown (null) renders `unknown` rather
|
|
207
222
|
// than guessing.
|
|
208
223
|
const images = p.imageInput === true
|
|
@@ -216,11 +231,11 @@ export function renderRoleMarkdown(p, now = Date.now()) {
|
|
|
216
231
|
// Model failure fallback, part 2/2: name the off-model pair, when the episode began, and
|
|
217
232
|
// the provider-class failure that tripped it, so `tumwater role <id>` explains the state.
|
|
218
233
|
if (p.modelFallback) {
|
|
219
|
-
const pair = fallbackPairLabel(p.modelFallback,
|
|
234
|
+
const pair = fallbackPairLabel(p.modelFallback, backendDefault(p));
|
|
220
235
|
lines.push(`- Model fallback: ${pair} — on fallback since ${formatTimestamp(p.modelFallback.since)} (primary failing: ${p.modelFallback.reason})`);
|
|
221
236
|
}
|
|
222
237
|
if (p.fallback) {
|
|
223
|
-
const pair = fallbackPairLabel(p.fallback,
|
|
238
|
+
const pair = fallbackPairLabel(p.fallback, backendDefault(p));
|
|
224
239
|
lines.push(`- Over-cap model: ${pair} (${p.fallbackFree ? "free" : "priced"})`);
|
|
225
240
|
}
|
|
226
241
|
const running = chainStepLine(p);
|
|
@@ -229,17 +244,19 @@ export function renderRoleMarkdown(p, now = Date.now()) {
|
|
|
229
244
|
// The claude seam's billing (plans/agent-backends.md §7/§8, PLANS.md part 6d/7): absent on
|
|
230
245
|
// a pi seam, so its role view is byte-identical to before.
|
|
231
246
|
if (p.billing) {
|
|
232
|
-
const window = p.billing === "subscription" && p.billingWindow
|
|
247
|
+
const window = p.billing === "subscription" && p.billingWindow
|
|
248
|
+
? ` (${billingWindowLabel(p.billingBackend, p.billingWindow)} window)`
|
|
249
|
+
: "";
|
|
233
250
|
lines.push(`- Billing: ${p.billing === "api" ? "API" : p.billing}${window}`);
|
|
234
251
|
}
|
|
235
252
|
lines.push(`- Min run interval: ${p.minRunIntervalSeconds}s`);
|
|
236
253
|
if (p.instances !== undefined)
|
|
237
254
|
lines.push(`- Instances: ${p.instances}`);
|
|
238
255
|
lines.push(`- Inbox: ${p.inboxCount}`);
|
|
239
|
-
// The
|
|
240
|
-
// same run_end events recentOutcomes reads): how much the
|
|
256
|
+
// The role's own spend over its retained event window (role-view.ts's usage, summed from the
|
|
257
|
+
// same run_end events recentOutcomes reads): how much the role burned on tokens and cost,
|
|
241
258
|
// with a notional suffix only when a claude-subscription seam burned non-zero notional spend
|
|
242
|
-
// (plans/agent-backends.md part 6a). Absent for a never-costly
|
|
259
|
+
// (plans/agent-backends.md part 6a). Absent for a never-costly role, so old `tumwater role`
|
|
243
260
|
// goldens are byte-identical.
|
|
244
261
|
if (p.usage) {
|
|
245
262
|
let spend = `- Spend: ${usd(p.usage.costUsd)} · ${compactTokens(p.usage.tokens)} tokens`;
|
|
@@ -248,7 +265,7 @@ export function renderRoleMarkdown(p, now = Date.now()) {
|
|
|
248
265
|
lines.push(spend);
|
|
249
266
|
}
|
|
250
267
|
// The per-role daily cap (role-view.ts's dailyCap): today's spend against the cap and whether
|
|
251
|
-
// it holds the
|
|
268
|
+
// it holds the role now. Absent without a cap, so old goldens are unchanged.
|
|
252
269
|
if (p.dailyCap) {
|
|
253
270
|
const dc = p.dailyCap;
|
|
254
271
|
let cap = `- Daily cap: ${usd(dc.spentUsd)} of ${usd(dc.capUsd)} spent today (${dc.over ? "holding now — no new runs until local midnight" : "not holding"})`;
|
|
@@ -256,14 +273,14 @@ export function renderRoleMarkdown(p, now = Date.now()) {
|
|
|
256
273
|
cap += ` · shared by ${dc.roleIds.join(", ")}`;
|
|
257
274
|
lines.push(cap);
|
|
258
275
|
}
|
|
259
|
-
// The
|
|
260
|
-
// the same persisted state): when it may
|
|
261
|
-
// place the team's `next run`/`last
|
|
276
|
+
// The role's schedule (role-view.ts's nextRunAt/backoffSeconds/lastRunEndedAt, read from
|
|
277
|
+
// the same persisted state): when it may run again, and when it last did — the one
|
|
278
|
+
// place the team's `next run`/`last run` vocabulary was absent. Rendered from the
|
|
262
279
|
// injected `now`, so the lines stay a pure function of the payload.
|
|
263
280
|
lines.push(...scheduleLine(p, now));
|
|
264
|
-
// The
|
|
265
|
-
// one line per outcome so `tumwater role <id>` shows what the
|
|
266
|
-
// what it is. Absent (never
|
|
281
|
+
// The role's recent run outcomes (role-view.ts's recentOutcomes, read from the event log):
|
|
282
|
+
// one line per outcome so `tumwater role <id>` shows what the role has been DOING, not just
|
|
283
|
+
// what it is. Absent (never run) renders nothing new, so old goldens are byte-identical.
|
|
267
284
|
if (p.recentOutcomes && p.recentOutcomes.length > 0) {
|
|
268
285
|
lines.push("", "## Recent runs", "");
|
|
269
286
|
for (const o of p.recentOutcomes)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* what the loop IS (catalog entry or custom, enabled, paused, tier), what it RUNS ON
|
|
1
|
+
/** One role's read-only inspection payload — the collector behind `tumwater role <id>`:
|
|
2
|
+
* what the role IS (catalog entry or custom, enabled, paused, tier), what it RUNS ON
|
|
4
3
|
* (resolved provider/model, the budget fallback pair), what it CARRIES (the instructions
|
|
5
|
-
* override and the role's own find text verbatim), and what its NEXT
|
|
4
|
+
* override and the role's own find text verbatim), and what its NEXT run's prompt will
|
|
6
5
|
* read like, assembled through the preview seam so nothing queued is consumed. Split out
|
|
7
6
|
* of cli.ts like backlogPayload and status/status-data.ts's snapshot so the collector is testable
|
|
8
7
|
* without the CLI and one renderer consumes exactly this shape. Every read degrades like
|
|
@@ -13,21 +12,23 @@ import { defaultConfig, enabledRoleIds, isCustomRole, knownRoleIds, loadConfigSa
|
|
|
13
12
|
import { DIRECTOR_ROLE, customRole, roleById, roleTier, unknownRoleMessage } from "./roles.js";
|
|
14
13
|
import { queuedRolePromptCount } from "../inbox/inbox.js";
|
|
15
14
|
import { pausedRoles } from "../team/team-state.js";
|
|
16
|
-
import {
|
|
17
|
-
import { baseRoleOf,
|
|
15
|
+
import { loadRunnerState } from "../runner/runner-state-load.js";
|
|
16
|
+
import { baseRoleOf, runnerIds, runnerPaused } from "./runner-ids.js";
|
|
18
17
|
import { roleCapVerdicts } from "../gates/role-cap-gates.js";
|
|
19
18
|
import { yieldMultiplierFor } from "../scheduling/backoff.js";
|
|
20
19
|
import { quietHoursStatus } from "../scheduling/quiet-hours.js";
|
|
21
|
-
import {
|
|
20
|
+
import { assembleRunPrompt } from "../run/run-prompt.js";
|
|
22
21
|
import { configForRole, resolvedModelFields, reviewRunConfig, roleSeamTier, seamConcurrencyKey } from "../config/config-views.js";
|
|
23
22
|
import { fallbackPair, modelFallbackView } from "../config/config-fallbacks.js";
|
|
24
23
|
import { fallbackModelPair, piModelsPath, seamContextWindow } from "../pi/pi-models.js";
|
|
25
24
|
import { readOpencodeCache } from "../agent/opencode/opencode-catalog.js";
|
|
26
25
|
import { formatModelSelector } from "../config/model-selector.js";
|
|
27
26
|
import { roleImageInfo } from "../agent/image-input.js";
|
|
27
|
+
import { readPiListedModels } from "../pi/pi-listed-models.js";
|
|
28
28
|
import { readImageProbeCache } from "../doctor/image-probe.js";
|
|
29
29
|
import { piSettingsPath, readPiDefaultModel } from "../pi/pi-settings.js";
|
|
30
|
-
import {
|
|
30
|
+
import { readClaudeAccount } from "../agent/claude/claude-account.js";
|
|
31
|
+
import { readAccount, windowEntries } from "../agent/subscription-account.js";
|
|
31
32
|
import { CONFIG_BASENAME, configPath, roleNotesPath } from "../paths.js";
|
|
32
33
|
import { readTextOrNull } from "../files/files.js";
|
|
33
34
|
import { eventRole, eventUsage, readEvents } from "../events/event-read.js";
|
|
@@ -49,7 +50,7 @@ export function rawRoleField(root, roleId, field) {
|
|
|
49
50
|
return undefined;
|
|
50
51
|
return obj[field];
|
|
51
52
|
}
|
|
52
|
-
/** Collect one
|
|
53
|
+
/** Collect one role's inspection payload. `modelsPath` overrides pi's model definitions location
|
|
53
54
|
* — a test seam, like status/status-data.ts's snapshot — consulted only when a fallback pair is
|
|
54
55
|
* configured (the freeness verdict needs the definitions; nothing else does). */
|
|
55
56
|
export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now = new Date()) {
|
|
@@ -61,30 +62,37 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
61
62
|
const cfg = config ?? defaultConfig();
|
|
62
63
|
const catalog = roleById(role);
|
|
63
64
|
const customEntry = cfg.customRoles.find((c) => c.name === role);
|
|
64
|
-
// The director is inspectable like any
|
|
65
|
+
// The director is inspectable like any role but has no catalog entry: a pseudo-Role whose
|
|
65
66
|
// find text is empty (rolePayload nulls it below — the director's queued prompts ARE its
|
|
66
67
|
// work), so the unknown-role throw only fires for ids nothing can answer.
|
|
67
68
|
const resolved = catalog ??
|
|
68
69
|
(customEntry ? customRole(customEntry.name, customEntry.task)
|
|
69
70
|
: role === DIRECTOR_ROLE ? { id: DIRECTOR_ROLE, title: DIRECTOR_ROLE, find: "" }
|
|
70
71
|
: undefined);
|
|
71
|
-
|
|
72
|
+
// roleById maps any `feature-N` to its base whatever the config runs, so an instance id
|
|
73
|
+
// counts only when knownRoleIds (the ids the config runs) holds it.
|
|
74
|
+
if (!resolved || !knownRoleIds(cfg).includes(role)) {
|
|
72
75
|
// The one unknown-role answer (unknownRoleMessage): the CLI's main catch turns the throw
|
|
73
76
|
// into `tumwater: <message>` with exit 1, so the collector keeps the wording and the CLI
|
|
74
77
|
// stays a thin peel-parse-print layer.
|
|
75
78
|
throw new Error(unknownRoleMessage(role, knownRoleIds(cfg)));
|
|
76
79
|
}
|
|
77
80
|
const effective = configForRole(cfg, role);
|
|
78
|
-
// The
|
|
79
|
-
// the review gate's when the reviewer runs
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
// The subscription backend whose billing is in play — the role's OWN seam when it routes
|
|
82
|
+
// to a non-pi backend, else the review gate's backend when the reviewer runs a non-pi
|
|
83
|
+
// backend (today's claude precedence generalized off the literal "claude", part 3c/9;
|
|
84
|
+
// PLANS.md "Mixed claude + pi fleets, part 3/4" gap 7): a pi author on a claude/codex
|
|
85
|
+
// reviewer still shows where its changes' spend lands. Read that backend's account; the
|
|
86
|
+
// account file is team-global, so either seam reads the same answer. Its subscription
|
|
87
|
+
// window's primary type rides along for the renderer's label.
|
|
88
|
+
const roleBackend = effective.agent !== undefined && effective.agent !== "pi" ? effective.agent : undefined;
|
|
89
|
+
const reviewAgent = reviewRunConfig(cfg).agent;
|
|
90
|
+
const reviewBackend = cfg.review.enabled && reviewAgent !== undefined && reviewAgent !== "pi" ? reviewAgent : undefined;
|
|
91
|
+
const billingBackend = roleBackend ?? reviewBackend;
|
|
92
|
+
const account = billingBackend !== undefined ? readAccount(root, billingBackend) : null;
|
|
85
93
|
const info = readOrchestratorInfo(root);
|
|
86
94
|
const chainStep = (orchestratorAlive(root, info) && info?.chain?.[roleSeamTier(cfg, role)]) || null;
|
|
87
|
-
const billingWindow = account?.billing === "subscription" ?
|
|
95
|
+
const billingWindow = account?.billing === "subscription" ? windowEntries(account)[0]?.type : undefined;
|
|
88
96
|
// pi's own saved default for a pi seam that resolves no model, read once beside the models
|
|
89
97
|
// catalog this collector already takes as a seam (piSettingsPath). The config's `thinking`
|
|
90
98
|
// wins over the default's because tumwater passes it to pi as `--thinking`, mirroring the
|
|
@@ -117,28 +125,34 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
117
125
|
// gate does) keeps them on one answer. OpenCode's `catalog` is threaded the way claude's
|
|
118
126
|
// `account` is: absent, an opencode pair reads NOT free.
|
|
119
127
|
const opencodeCatalog = readOpencodeCache(root);
|
|
120
|
-
|
|
128
|
+
// The claude account answer (6a/7), read the way the budget gate reads it — always, so the
|
|
129
|
+
// fallback price verdict sees the subscription even when claude is not this role's own seam
|
|
130
|
+
// (a pi role may still fall back to claude). The lookup returns it only for "claude" (part
|
|
131
|
+
// 2a/9) — no caller asks for another backend yet.
|
|
132
|
+
const claudeFallbackAccount = readClaudeAccount(root);
|
|
133
|
+
const accounts = (b) => (b === "claude" ? claudeFallbackAccount : null);
|
|
134
|
+
const freeFallback = declaredFallback ? fallbackModelPair(cfg, modelsPath, accounts, opencodeCatalog) : null;
|
|
121
135
|
const fallback = freeFallback ?? declaredFallback;
|
|
122
|
-
// One state read serves both the model-fallback episode and the next-
|
|
123
|
-
const state =
|
|
136
|
+
// One state read serves both the model-fallback episode and the next-run preview below.
|
|
137
|
+
const state = loadRunnerState(root, role);
|
|
124
138
|
const episode = state.modelFallback;
|
|
125
|
-
// The
|
|
139
|
+
// The role's recent run outcomes, read from the event log (readEvents) so the command
|
|
126
140
|
// answers with the team stopped and never touches live state. readEvents returns the log's
|
|
127
141
|
// newest events in file (chronological) order, so filtering to this role's run_end events
|
|
128
142
|
// and taking the last 8 yields the newest 8, reversed to newest-first. Durations and
|
|
129
143
|
// summaries ride along only when the event carried them (a torn or empty line drops them),
|
|
130
|
-
// and an empty result is omitted so a never-
|
|
131
|
-
// One event-log read serves both per-
|
|
144
|
+
// and an empty result is omitted so a never-run role carries no `recentOutcomes` at all.
|
|
145
|
+
// One event-log read serves both per-role facts — the recent outcomes and the spend sum — so
|
|
132
146
|
// the two cannot drift on what they cover: filtered to this role's run_end events once, then
|
|
133
147
|
// the outcome projection keeps the newest valid 8 while the usage fold sums the whole window.
|
|
134
148
|
const events = readEvents(root);
|
|
135
|
-
const
|
|
149
|
+
const runEnds = events.filter((ev) => ev.type === "run_end" && eventRole(ev) === role);
|
|
136
150
|
// The event log is read without a shape filter, so a torn or hand-edited run_end line can
|
|
137
151
|
// carry a non-numeric ts/durationMs or an empty result — the same foreign shape every other
|
|
138
152
|
// consumer guards with finiteNumber. A line with no usable ts or result is as unusable as
|
|
139
|
-
// an absent one (the same "a torn line carries no usable timestamp" rule
|
|
153
|
+
// an absent one (the same "a torn line carries no usable timestamp" rule eventRun/runStartMap
|
|
140
154
|
// apply), so it contributes no outcome; a non-numeric durationMs is dropped like a missing one.
|
|
141
|
-
const recentOutcomes =
|
|
155
|
+
const recentOutcomes = runEnds
|
|
142
156
|
.filter((ev) => {
|
|
143
157
|
return finiteNumber(ev.ts, null) !== null && typeof ev.result === "string" && ev.result !== "";
|
|
144
158
|
})
|
|
@@ -150,13 +164,13 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
150
164
|
...(typeof ev.summary === "string" && ev.summary !== "" ? { summary: ev.summary } : {}),
|
|
151
165
|
}))
|
|
152
166
|
.reverse();
|
|
153
|
-
// The
|
|
167
|
+
// The role's own spend over the same retained window, summed per-run_end through the shared
|
|
154
168
|
// eventUsage guard (a torn or hand-edited run_end line contributes nothing: a non-numeric
|
|
155
169
|
// field reads as 0, and a line without a usable ts is as unusable as an absent one, matching
|
|
156
|
-
// recentOutcomes's drop rule so a torn
|
|
157
|
-
// sums are 0, keeping a never-costly
|
|
170
|
+
// recentOutcomes's drop rule so a torn run never silently spends). Omitted when all three
|
|
171
|
+
// sums are 0, keeping a never-costly role's JSON unchanged (the report's zero-means-absent
|
|
158
172
|
// convention).
|
|
159
|
-
const usage =
|
|
173
|
+
const usage = runEnds.reduce((acc, ev) => {
|
|
160
174
|
if (finiteNumber(ev.ts, null) === null)
|
|
161
175
|
return acc;
|
|
162
176
|
const u = eventUsage(ev);
|
|
@@ -193,15 +207,15 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
193
207
|
const capUsd = cfg.maxDailyCostUsdPerRole?.[base];
|
|
194
208
|
let dailyCap;
|
|
195
209
|
if (role !== DIRECTOR_ROLE && typeof capUsd === "number" && Number.isFinite(capUsd) && capUsd > 0) {
|
|
196
|
-
const ids =
|
|
210
|
+
const ids = runnerIds(cfg).filter((id) => baseRoleOf(id) === base);
|
|
197
211
|
if (!ids.includes(role))
|
|
198
212
|
ids.push(role);
|
|
199
|
-
const runners = ids.map((id) => ({ role: id, state: id === role ? state :
|
|
213
|
+
const runners = ids.map((id) => ({ role: id, state: id === role ? state : loadRunnerState(root, id) }));
|
|
200
214
|
const verdict = roleCapVerdicts(runners, cfg.maxDailyCostUsdPerRole, now.getTime()).get(base);
|
|
201
215
|
if (verdict)
|
|
202
216
|
dailyCap = { capUsd, spentUsd: verdict.spentUsd, over: verdict.over, roleIds: verdict.roleIds };
|
|
203
217
|
}
|
|
204
|
-
// The role's
|
|
218
|
+
// The role's notes, read for display. The same degradation as every other read here: a
|
|
205
219
|
// missing, empty, or unreadable file is simply "no note yet". The director has none.
|
|
206
220
|
let note = null;
|
|
207
221
|
if (role !== DIRECTOR_ROLE) {
|
|
@@ -227,7 +241,7 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
227
241
|
title: resolved.title,
|
|
228
242
|
custom: isCustomRole(cfg, role),
|
|
229
243
|
enabled: enabledRoleIds(cfg).includes(role),
|
|
230
|
-
paused:
|
|
244
|
+
paused: runnerPaused(new Set(pausedRoles(root)), role),
|
|
231
245
|
tier: roleTier(role),
|
|
232
246
|
modelTier: roleSeamTier(cfg, role),
|
|
233
247
|
...resolvedModelFields(effective.provider, effective.model, effective.thinking),
|
|
@@ -245,10 +259,10 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
245
259
|
fallback,
|
|
246
260
|
fallbackFree: freeFallback !== null,
|
|
247
261
|
modelFallback: modelFallbackView(episode, cfg, role),
|
|
248
|
-
...(account !== null ? { billing: account.billing } : {}),
|
|
262
|
+
...(account !== null ? { billing: account.billing, billingBackend } : {}),
|
|
249
263
|
...(billingWindow !== undefined ? { billingWindow } : {}),
|
|
250
264
|
chainStep,
|
|
251
|
-
...roleImageInfo(cfg, role, modelsPath, readImageProbeCache(root)),
|
|
265
|
+
...roleImageInfo(cfg, role, modelsPath, readImageProbeCache(root), readPiListedModels(root)),
|
|
252
266
|
minRunIntervalSeconds: effective.minRunIntervalSeconds,
|
|
253
267
|
...(minRunIntervalOverride !== undefined ? { minRunIntervalOverride } : {}),
|
|
254
268
|
...(providerOverride !== undefined ? { providerOverride } : {}),
|
|
@@ -261,7 +275,7 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
261
275
|
...(cfg.roles[base]?.instances !== undefined ? { instances: cfg.roles[base].instances } : {}),
|
|
262
276
|
// The roles.<base>.instructions override verbatim — resolved through the base role like
|
|
263
277
|
// the cap and quiet hours, so an instance id shows (and can edit) the override its runs
|
|
264
|
-
// carry (
|
|
278
|
+
// carry (run-prompt reads config.roles[base]); null when unset.
|
|
265
279
|
instructions: cfg.roles[baseRoleOf(role)]?.instructions ?? null,
|
|
266
280
|
find: role === DIRECTOR_ROLE ? null : resolved.find,
|
|
267
281
|
notes: note,
|
|
@@ -271,20 +285,18 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
271
285
|
// declared window) — the same seam `- Next run:` previews. Independent of the fallback.
|
|
272
286
|
contextWindow,
|
|
273
287
|
// The preview seam: the queued prompt (if any) is read, not consumed, so an inspection
|
|
274
|
-
// never costs the
|
|
275
|
-
nextPrompt:
|
|
288
|
+
// never costs the role its queued work — the property the run-prompt test pins.
|
|
289
|
+
nextPrompt: assembleRunPrompt({
|
|
276
290
|
root,
|
|
277
291
|
config: cfg,
|
|
278
292
|
role,
|
|
279
293
|
state,
|
|
280
294
|
preview: true,
|
|
281
|
-
// The compact prompt every run gets (loop-tick.ts), so the preview is the real prompt.
|
|
282
|
-
compact: true,
|
|
283
295
|
})?.prompt ?? null,
|
|
284
|
-
// The
|
|
285
|
-
// already loaded for the model-fallback episode and the next-
|
|
286
|
-
// read path exists and the collector stays one
|
|
287
|
-
// is omitted (JSON unchanged) until the
|
|
296
|
+
// The role's schedule — surfaced read-only from the same persisted state rolePayload
|
|
297
|
+
// already loaded for the model-fallback episode and the next-run preview, so no new
|
|
298
|
+
// read path exists and the collector stays one loadRunnerState per role. `lastRunEndedAt`
|
|
299
|
+
// is omitted (JSON unchanged) until the role has actually run.
|
|
288
300
|
nextRunAt: state.nextRunAt,
|
|
289
301
|
backoffSeconds: state.backoffSeconds,
|
|
290
302
|
yieldMultiplier: yieldMultiplierFor(state),
|