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,11 +1,11 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
import { loadConfigSafe } from "../config/config.js";
|
|
3
|
-
import {
|
|
2
|
+
import { withPrimaryWorktreeLockAsync } from "../concurrency/lock.js";
|
|
4
3
|
import { currentBranch, headOf, isMergedInto, targetBranch } from "../git/git.js";
|
|
5
|
-
import {
|
|
6
|
-
import { stampedSubject } from "../git/commit-message.js";
|
|
7
|
-
import { primaryWorktreeLockPath } from "../paths.js";
|
|
4
|
+
import { GitError, commitAuthorArgs, committerIdentArgs, gitTry, runGit } from "../git/git-run.js";
|
|
5
|
+
import { stampedRole, stampedSubject } from "../git/commit-message.js";
|
|
8
6
|
import { shortSha } from "../text/format.js";
|
|
7
|
+
import { logEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
8
|
+
import { runMergedSha } from "../run/run-detail-data.js";
|
|
9
9
|
/** `tumwater revert <sha>`: undo one merged change on main, committing the inverse as
|
|
10
10
|
* `tumwater(operator): Revert <short sha>` on the primary checkout. Mirrors
|
|
11
11
|
* `commitOperatorBacklogEdit`'s lock-and-branch discipline (src/backlog/backlog-commit.ts): the
|
|
@@ -13,6 +13,118 @@ import { shortSha } from "../text/format.js";
|
|
|
13
13
|
* fast-forward, and it refuses — writing nothing — when the primary is not on the base branch.
|
|
14
14
|
* Fail-closed: an unknown ref, a ref not on the base branch, or a revert that conflicts writes
|
|
15
15
|
* no commit and leaves the checkout as it was. */
|
|
16
|
+
/** The shared git steps of a revert: `git revert --no-commit <sha>` in `wt`, then commit just the
|
|
17
|
+
* inverse as `message`. `wt` must have a clean tracked tree (so `--abort` after a conflict has
|
|
18
|
+
* nothing to discard). Never throws; a conflict or a failed commit aborts the revert and comes
|
|
19
|
+
* back as `{ ok: false, error }` with `wt` as it was. */
|
|
20
|
+
export async function revertCommitIn(wt, sha, message) {
|
|
21
|
+
if ((await gitTry(wt, "revert", "--no-commit", sha)) === null) {
|
|
22
|
+
// The tree was clean when the revert started, so --abort restores exactly that state.
|
|
23
|
+
await gitTry(wt, "revert", "--abort");
|
|
24
|
+
return { ok: false, error: `reverting ${shortSha(sha)} conflicted; nothing was committed and the checkout is unchanged` };
|
|
25
|
+
}
|
|
26
|
+
// `--only` with the revert's own staged paths commits just the inverse, so it cannot pick up any
|
|
27
|
+
// unrelated change of another path staged after the clean check. `--no-renames` keeps a rename's
|
|
28
|
+
// two halves as separate path lines: the default name-only collapses a detected rename to its
|
|
29
|
+
// destination, which would commit only half and leave the other half staged with a dirty tree.
|
|
30
|
+
const pathsSpec = ((await gitTry(wt, "diff", "--cached", "--name-only", "--no-renames")) ?? "")
|
|
31
|
+
.split("\n")
|
|
32
|
+
.filter((p) => p.length > 0);
|
|
33
|
+
if ((await gitTry(wt, ...(await committerIdentArgs(wt)), "commit", ...commitAuthorArgs(), "--only", "-m", message, "--", ...pathsSpec)) === null) {
|
|
34
|
+
await gitTry(wt, "revert", "--abort");
|
|
35
|
+
return { ok: false, error: `the revert of ${shortSha(sha)} did not commit; nothing was committed` };
|
|
36
|
+
}
|
|
37
|
+
return { ok: true, sha: await headOf(wt, "HEAD") };
|
|
38
|
+
}
|
|
39
|
+
/** The read-only refusals shared by a revert and its preview: the primary checkout must be on the
|
|
40
|
+
* base branch, `ref` must resolve to a commit, and that commit must be merged into the branch.
|
|
41
|
+
* The dashboard's `handleRun` also calls it, to decide whether a run's commit is on the base
|
|
42
|
+
* branch. */
|
|
43
|
+
export async function resolveRevertTarget(root, ref) {
|
|
44
|
+
const primary = await currentBranch(root);
|
|
45
|
+
const base = targetBranch(loadConfigSafe(root).config?.baseBranch, primary);
|
|
46
|
+
if (primary === null || base !== primary)
|
|
47
|
+
return { ok: false, error: `the primary checkout is not on ${base} — run from a checkout of it` };
|
|
48
|
+
const full = await gitTry(root, "rev-parse", "--verify", ref + "^{commit}");
|
|
49
|
+
if (full === null)
|
|
50
|
+
return { ok: false, error: `no such commit: ${ref}` };
|
|
51
|
+
if (!(await isMergedInto(root, full, primary)))
|
|
52
|
+
return { ok: false, error: `${shortSha(full)} is not on ${primary}` };
|
|
53
|
+
return { ok: true, full, primary };
|
|
54
|
+
}
|
|
55
|
+
/** The refusal for a commit without exactly one parent (a root or a merge), or null. Shared by
|
|
56
|
+
* the revert and its preview: `git revert` of a root deletes the whole tree, of a merge needs
|
|
57
|
+
* `-m`. */
|
|
58
|
+
async function parentRefusal(root, full) {
|
|
59
|
+
const parents = (await runGit(root, ["rev-list", "--parents", "-n", "1", full])).split(" ").length - 1;
|
|
60
|
+
if (parents === 1)
|
|
61
|
+
return null;
|
|
62
|
+
return `${shortSha(full)} has ${parents} parents; tumwater revert undoes a single-parent commit only, so nothing was written`;
|
|
63
|
+
}
|
|
64
|
+
/** The commit a run's merge put on main, or the CLI's refusal: the run did not merge, or merged
|
|
65
|
+
* but the event log no longer names its commit. Shared by `tumwater revert <role> <n>` and the
|
|
66
|
+
* TUI History trail's Ctrl+V so the two cannot drift. */
|
|
67
|
+
export function runRevertSha(role, run, detail) {
|
|
68
|
+
if (detail.result !== "merged")
|
|
69
|
+
return { ok: false, error: `${role} run #${run} did not merge (result: ${detail.result ?? "unknown"}); there is nothing to revert` };
|
|
70
|
+
const sha = runMergedSha(detail);
|
|
71
|
+
if (sha === null)
|
|
72
|
+
return { ok: false, error: `${role} run #${run} merged but the event log no longer names its commit; pass the sha from git log instead` };
|
|
73
|
+
return { ok: true, sha };
|
|
74
|
+
}
|
|
75
|
+
/** `tumwater revert <sha> --dry-run`: what a revert of `ref` would undo and whether it applies
|
|
76
|
+
* cleanly, from git's object store alone — a `git merge-tree` three-way merge (base = the commit,
|
|
77
|
+
* ours = HEAD, theirs = its parent, the merge `git revert` performs). Takes no lock and never
|
|
78
|
+
* touches the index, the worktree or a ref. Never throws. */
|
|
79
|
+
export async function previewRevert(root, ref) {
|
|
80
|
+
try {
|
|
81
|
+
const target = await resolveRevertTarget(root, ref);
|
|
82
|
+
if (!target.ok)
|
|
83
|
+
return target;
|
|
84
|
+
const { full, primary } = target;
|
|
85
|
+
const refusal = await parentRefusal(root, full);
|
|
86
|
+
if (refusal !== null)
|
|
87
|
+
return { ok: false, error: refusal };
|
|
88
|
+
const subject = await runGit(root, ["log", "-1", "--format=%s", full]);
|
|
89
|
+
const paths = (await runGit(root, ["diff", "--name-only", "--no-renames", full + "^", full]))
|
|
90
|
+
.split("\n")
|
|
91
|
+
.filter((p) => p.length > 0);
|
|
92
|
+
const head = await headOf(root, "HEAD");
|
|
93
|
+
let applies = true;
|
|
94
|
+
try {
|
|
95
|
+
await runGit(root, ["merge-tree", "--write-tree", "--merge-base=" + full, "HEAD", full + "^"]);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
if (!(err instanceof GitError))
|
|
99
|
+
throw err;
|
|
100
|
+
if (err.code !== 1)
|
|
101
|
+
return { ok: false, error: `could not preview the revert: ${err.message}` };
|
|
102
|
+
applies = false;
|
|
103
|
+
}
|
|
104
|
+
const dirty = (await runGit(root, ["status", "--porcelain", "--untracked-files=no"])) !== "";
|
|
105
|
+
return { ok: true, sha: full, subject, paths, head, primary, applies, dirty };
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return { ok: false, error: "the revert preview did not complete (git failed); nothing was written" };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** The role a revert of a commit with this subject undoes: the stamped role when it is a working
|
|
112
|
+
* role, else null — an unstamped (hand-made) commit, the operator's or the harness's own. */
|
|
113
|
+
function revertedRole(subject) {
|
|
114
|
+
const role = stampedRole(subject);
|
|
115
|
+
if (role === undefined || role === "operator" || role === "tumwater")
|
|
116
|
+
return null; // vocabulary-ok: commit stamp strings
|
|
117
|
+
return role;
|
|
118
|
+
}
|
|
119
|
+
/** What to tell the operator after reverting a commit with this subject: the role that made it may
|
|
120
|
+
* redo the work. Null when no role made it — an unstamped (hand-made) commit, the operator's or
|
|
121
|
+
* the harness's own. */
|
|
122
|
+
export function revertFollowUp(subject) {
|
|
123
|
+
const role = revertedRole(subject);
|
|
124
|
+
if (role === null)
|
|
125
|
+
return null;
|
|
126
|
+
return `the ${role} role may redo the work, so cancel or edit its PLANS.md/BUGS.md entry if it should not`;
|
|
127
|
+
}
|
|
16
128
|
/** Revert one already-merged commit (`ref` resolved to a commit) and commit the inverse on the
|
|
17
129
|
* primary checkout under the primary-worktree lock. Never throws; every refusal and every git
|
|
18
130
|
* failure comes back as `{ ok: false, error }` after restoring the checkout. `head` is captured
|
|
@@ -24,18 +136,17 @@ export async function revertMergedCommit(root, ref) {
|
|
|
24
136
|
// call, and that write takes the same lock synchronously — a lock already held across our
|
|
25
137
|
// awaits would block its event loop until the lock times out.
|
|
26
138
|
await new Promise((resolve) => setImmediate(resolve));
|
|
27
|
-
return await
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
return { ok: false, error:
|
|
139
|
+
return await withPrimaryWorktreeLockAsync(root, async () => {
|
|
140
|
+
const target = await resolveRevertTarget(root, ref);
|
|
141
|
+
if (!target.ok)
|
|
142
|
+
return target;
|
|
143
|
+
const { full } = target;
|
|
144
|
+
const refusal = await parentRefusal(root, full);
|
|
145
|
+
if (refusal !== null)
|
|
146
|
+
return { ok: false, error: refusal };
|
|
35
147
|
// `head` is captured before the revert so a future caller can compare before/after.
|
|
36
148
|
const head = await headOf(root, "HEAD");
|
|
37
|
-
|
|
38
|
-
return { ok: false, error: `${shortSha(full)} is not on ${primary}` };
|
|
149
|
+
const subject = await runGit(root, ["log", "-1", "--format=%s", full]);
|
|
39
150
|
// Fail-closed on any staged or unstaged change to a tracked path: we refuse to revert a
|
|
40
151
|
// checkout that has one (untracked files are tolerated — the lock dir we just created is
|
|
41
152
|
// one). This keeps the inverse commit free of unrelated content and, just as important,
|
|
@@ -44,25 +155,22 @@ export async function revertMergedCommit(root, ref) {
|
|
|
44
155
|
// pre-existing work can never be lost.
|
|
45
156
|
if ((await gitTry(root, "status", "--porcelain", "--untracked-files=no")) !== "")
|
|
46
157
|
return { ok: false, error: "the worktree has staged or unstaged changes; commit or stash them first, nothing was committed" };
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return { ok: false, error: `the revert of ${shortSha(full)} did not commit; nothing was committed` };
|
|
64
|
-
}
|
|
65
|
-
return { ok: true, headSha: await headOf(root, "HEAD"), beforeSha: head };
|
|
158
|
+
const reverted = await revertCommitIn(root, full, stampedSubject("operator", "Revert " + shortSha(full))); // vocabulary-ok: the tumwater(operator): commit prefix is a machine string
|
|
159
|
+
if (!reverted.ok)
|
|
160
|
+
return reverted;
|
|
161
|
+
// Log the operator's deliberate revert while the primary lock is held, so the inverse
|
|
162
|
+
// commit and its trail land together. Best-effort: a failed event write must never turn a
|
|
163
|
+
// successful revert into a refusal.
|
|
164
|
+
const role = revertedRole(subject);
|
|
165
|
+
logEventBestEffort(root, {
|
|
166
|
+
source: TUMWATER_SOURCE,
|
|
167
|
+
type: "operator_revert",
|
|
168
|
+
commit: full,
|
|
169
|
+
revert: reverted.sha,
|
|
170
|
+
subject,
|
|
171
|
+
...(role === null ? {} : { runner: role }),
|
|
172
|
+
});
|
|
173
|
+
return { ok: true, headSha: await headOf(root, "HEAD"), beforeSha: head, subject };
|
|
66
174
|
});
|
|
67
175
|
}
|
|
68
176
|
catch {
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/** The orchestrator's launch pass: the half of the per-poll runner sweep that admits due
|
|
3
|
-
* roles to their
|
|
2
|
+
* roles to their runs. The decision half (isEligible, the pause/backoff/deferral gates,
|
|
4
3
|
* once-mode settling) stays in runOrchestrator (src/orchestrator/orchestrator.ts) — it reads
|
|
5
4
|
* the poll's gate verdicts and the backlog state; this module takes its output, the
|
|
6
5
|
* `reasons` map of due runners, and turns each into a reserved, semaphore-gated, in-flight
|
|
7
|
-
*
|
|
8
|
-
* reservation/parking bookkeeping,
|
|
6
|
+
* run: the probe admission for the fallback breaker, the wake event, the
|
|
7
|
+
* reservation/parking bookkeeping, runTimedRoleRun's wiring (slot acquisition, the start
|
|
9
8
|
* gate, breaker evidence), and the in-flight-bucket bookkeeping the drain and the p75
|
|
10
9
|
* duration sample read. Split out of orchestrator.ts — whose poll loop otherwise reads as
|
|
11
10
|
* one decision after another — so the launch mechanics read as one step of that loop. The
|
|
12
11
|
* shared in-flight state (buckets, permit holders, duration samples, gateStates) is passed
|
|
13
12
|
* in and mutated in place, exactly as the inline version did: the drain's `finally` and the
|
|
14
13
|
* redeployer read it between polls. */
|
|
15
|
-
import { abandonFallbackProbeAt,
|
|
14
|
+
import { abandonFallbackProbeAt, recordFallbackRunAt, startFallbackProbeAt, } from "../budget/fallback-breaker.js";
|
|
16
15
|
import { seamHoldKey } from "../config/config-views.js";
|
|
17
16
|
import { logEventBestEffort, warnEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
18
17
|
import { errorMessage } from "../text/text.js";
|
|
@@ -21,20 +20,20 @@ import { modelPairName } from "../budget/budget.js";
|
|
|
21
20
|
import { withProviderLease } from "../concurrency/provider-lease.js";
|
|
22
21
|
import { seamConcurrencyKey } from "../config/config-views.js";
|
|
23
22
|
import { DIRECTOR_ROLE, roleTier } from "../roles/roles.js";
|
|
24
|
-
import {
|
|
25
|
-
/** How many recent work-bearing
|
|
26
|
-
* HANDOFF_MERGE_WINDOW_MS input): the drain window tracks recent
|
|
23
|
+
import { runTimedRoleRun } from "../run/run-timing.js";
|
|
24
|
+
/** How many recent work-bearing run durations the p75 sample keeps (drainInFlightWork's
|
|
25
|
+
* HANDOFF_MERGE_WINDOW_MS input): the drain window tracks recent run pace, not all-time
|
|
27
26
|
* pace, so the sample is a ring buffer. */
|
|
28
|
-
export const
|
|
29
|
-
/** The admission wait threshold below which a role
|
|
30
|
-
*
|
|
27
|
+
export const ROLE_RUN_DURATION_SAMPLES = 50;
|
|
28
|
+
/** The admission wait threshold below which a role run's semaphore wait logs no event — a
|
|
29
|
+
* run that starts at once stays silent. Mirrors SLOT_WAIT_EVENT_MS in src/git/worktree-pool.ts:
|
|
31
30
|
* the wait that signals maxConcurrentPerProvider (or maxConcurrent) caps the team. */
|
|
32
31
|
export const PROVIDER_WAIT_EVENT_MS = 30_000;
|
|
33
|
-
/** Launch one due runner's
|
|
34
|
-
*
|
|
32
|
+
/** Launch one due runner's run: reserve it, park it in the semaphore, and start it under
|
|
33
|
+
* runTimedRoleRun with the breaker-evidence wiring. Returns the task promise already added
|
|
35
34
|
* to its in-flight bucket (its `finally` self-removes and folds the duration sample). */
|
|
36
|
-
export function
|
|
37
|
-
const { root, reasons, signal, gateStates, breakerPolicy, breakerNow, probePair, probeRoles, budgetActive, semaphore, piDefault, rolePermitHolders, roleInFlight, directorInFlight,
|
|
35
|
+
export function launchDueRuns(ctx) {
|
|
36
|
+
const { root, reasons, signal, gateStates, breakerPolicy, breakerNow, probePair, probeRoles, budgetActive, windowMovedRoles, semaphore, piDefault, rolePermitHolders, roleInFlight, directorInFlight, roleRunDurationsMs, startHeld, } = ctx;
|
|
38
37
|
let admittedProbe = false;
|
|
39
38
|
for (const runner of fairOrder([...reasons.keys()])) {
|
|
40
39
|
if (signal.aborted)
|
|
@@ -58,10 +57,10 @@ export function launchDueTicks(ctx) {
|
|
|
58
57
|
logEventBestEffort(root, { source: runner.role, type: "wake", reason });
|
|
59
58
|
}
|
|
60
59
|
runner.state.running = true; // Reserve before the semaphore wait so we don't double-schedule.
|
|
61
|
-
// The director never queues behind
|
|
60
|
+
// The director never queues behind roles: a user prompt starts immediately,
|
|
62
61
|
// even when maxConcurrent slots are busy. Parked waiters keep fairOrder's tier order
|
|
63
|
-
// across polls too: a work-role arrival jumps ahead of maintenance
|
|
64
|
-
// in an earlier poll (in-flight
|
|
62
|
+
// across polls too: a work-role arrival jumps ahead of maintenance runs that queued
|
|
63
|
+
// in an earlier poll (in-flight runs always finish).
|
|
65
64
|
const usesSlot = runner.role !== DIRECTOR_ROLE;
|
|
66
65
|
// Mark the parked waiter while it waits: it holds no permit yet, so the dashboards
|
|
67
66
|
// render `awaiting slot` (an inactive state) and the active rows keep tracking
|
|
@@ -73,36 +72,44 @@ export function launchDueTicks(ctx) {
|
|
|
73
72
|
const parkKey = runner.role === DIRECTOR_ROLE ? null : runner.runConfig(Date.now());
|
|
74
73
|
runner.state.parkedSince = usesSlot ? Date.now() : undefined;
|
|
75
74
|
runner.state.parkedOn = usesSlot && parkKey ? seamConcurrencyKey(parkKey, piDefault) : undefined;
|
|
76
|
-
// The
|
|
77
|
-
// work): the drain-window sample is taken only for a work-bearing
|
|
78
|
-
// on its own (
|
|
75
|
+
// The run's own duration (null when it never started, was cut off, or ended without
|
|
76
|
+
// work): the drain-window sample is taken only for a work-bearing run that finished
|
|
77
|
+
// on its own (runTimedRoleRun; BUGS.md 2026-09-30).
|
|
79
78
|
let durationMs = null;
|
|
80
|
-
// Whether runner.
|
|
81
|
-
// turned the
|
|
79
|
+
// Whether runner.run() was ever called — false when the start gate (or a shutdown)
|
|
80
|
+
// turned the run away at its permit.
|
|
82
81
|
let started = false;
|
|
83
|
-
// The
|
|
82
|
+
// The run's keyed semaphore lease (plans/provider-concurrency.md §4): acquired on the
|
|
84
83
|
// authoring seam's provider key and released in place of the bare team release, so the
|
|
85
|
-
// cap is real for role
|
|
84
|
+
// cap is real for role runs from part 2/5 on. The director never queues, so its lease is
|
|
86
85
|
// never created here.
|
|
87
86
|
let lease = null;
|
|
88
87
|
// The director's forced provider lease (plans/provider-concurrency.md §4): never queues and
|
|
89
|
-
// never counts toward maxConcurrent, but DOES count toward its provider key while its
|
|
88
|
+
// never counts toward maxConcurrent, but DOES count toward its provider key while its run
|
|
90
89
|
// runs — so a full provider admits no further grants until the director releases.
|
|
91
90
|
let directorLease = null;
|
|
92
91
|
const task = (async () => {
|
|
93
|
-
durationMs = await
|
|
92
|
+
durationMs = await runTimedRoleRun(signal, usesSlot
|
|
94
93
|
? async () => {
|
|
95
94
|
// The key is the authoring seam's concurrency key, computed here at admission
|
|
96
95
|
// (runConfig applies any budget or model-failure fallback already in force).
|
|
97
96
|
const cfg = runner.role === DIRECTOR_ROLE ? null : runner.runConfig(Date.now());
|
|
98
97
|
const key = cfg ? seamConcurrencyKey(cfg, piDefault) : undefined;
|
|
99
98
|
const admittedAt = Date.now();
|
|
99
|
+
// Which cap would hold the run, read once at park time (not revised if the caps
|
|
100
|
+
// change mid-wait): the run's own provider cap when full, else the team total.
|
|
101
|
+
const pool = semaphore.snapshot();
|
|
102
|
+
const keyed = key !== undefined ? pool.byKey[key] : undefined;
|
|
103
|
+
const providerFull = keyed?.limit !== undefined && keyed.inUse >= keyed.limit;
|
|
104
|
+
const heldBy = providerFull
|
|
105
|
+
? { cap: "provider", limit: keyed.limit }
|
|
106
|
+
: { cap: "team", limit: pool.limit };
|
|
100
107
|
lease = await semaphore.acquireLease(roleTier(runner.role), key);
|
|
101
108
|
// A wait of PROVIDER_WAIT_EVENT_MS or more for the provider/team slot is
|
|
102
109
|
// provider contention worth surfacing: name it so `tumwater doctor`'s provider
|
|
103
110
|
// contention row can sum it and the operator can size maxConcurrentPerProvider
|
|
104
111
|
// (or maxConcurrent) from real waits. Best-effort like the launch pass's other
|
|
105
|
-
// events, so an unwritable log never blocks a start; a
|
|
112
|
+
// events, so an unwritable log never blocks a start; a run admitted at once
|
|
106
113
|
// stays silent.
|
|
107
114
|
if (Date.now() - admittedAt >= (ctx.providerWaitMs ?? PROVIDER_WAIT_EVENT_MS)) {
|
|
108
115
|
logEventBestEffort(ctx.root, {
|
|
@@ -111,18 +118,19 @@ export function launchDueTicks(ctx) {
|
|
|
111
118
|
role: runner.role,
|
|
112
119
|
waitedMs: Date.now() - admittedAt,
|
|
113
120
|
...(key !== undefined ? { key } : {}),
|
|
121
|
+
...heldBy,
|
|
114
122
|
});
|
|
115
123
|
}
|
|
116
|
-
// Permit granted: the
|
|
117
|
-
// release below. A
|
|
124
|
+
// Permit granted: the run is now an active, permit-holding state until the
|
|
125
|
+
// release below. A run the start gate turns away releases within the same
|
|
118
126
|
// microtask chain, so no poll ever counts it as a permit holder.
|
|
119
127
|
runner.state.parkedSince = undefined;
|
|
120
128
|
runner.state.parkedOn = undefined;
|
|
121
129
|
rolePermitHolders.add(runner);
|
|
122
130
|
}
|
|
123
131
|
: async () => {
|
|
124
|
-
// The director wraps its
|
|
125
|
-
// queuing behind
|
|
132
|
+
// The director wraps its run in a forced lease on its provider key, never
|
|
133
|
+
// queuing behind roles: a user prompt starts at once even on a full provider,
|
|
126
134
|
// and the key's in-use rises by one — overshooting by one — so no further grant on
|
|
127
135
|
// that key happens until its release drains it back under the cap.
|
|
128
136
|
const cfg = runner.runConfig(Date.now());
|
|
@@ -136,55 +144,55 @@ export function launchDueTicks(ctx) {
|
|
|
136
144
|
directorLease.release();
|
|
137
145
|
}, async () => {
|
|
138
146
|
started = true;
|
|
139
|
-
// A role
|
|
147
|
+
// A role run that starts while the budget holds uses the fallback pair its tier
|
|
140
148
|
// resolved to (the gate's per-tier view was installed in its config), or — when a
|
|
141
149
|
// model-fallback episode is active — on the tier's model-fallback pair, which can
|
|
142
150
|
// differ from the budget pair. Its outcome is the evidence for the pair it ACTUALLY
|
|
143
151
|
// ran on, so read it from runConfig (config + episode), never from configForRole
|
|
144
|
-
// alone: a per-pair breaker fed a fallback
|
|
152
|
+
// alone: a per-pair breaker fed a fallback run under the primary's name would
|
|
145
153
|
// misprice a healthy pair and miss a dead one (PLANS.md "Model failure fallback,
|
|
146
154
|
// part 1/2").
|
|
147
155
|
const now = Date.now();
|
|
148
156
|
const cfg = runner.role === DIRECTOR_ROLE ? null : runner.runConfig(now);
|
|
149
|
-
// The breaker is keyed by the seam that ran (seamHoldKey), like the
|
|
157
|
+
// The breaker is keyed by the seam that ran (seamHoldKey), like the run pair and
|
|
150
158
|
// the team-hold stamps (PLANS.md "Mixed claude + pi fleets, part 2/4"): a claude
|
|
151
|
-
// fallback
|
|
152
|
-
const ranPair = budgetActive && cfg?.model !== undefined
|
|
159
|
+
// fallback run demotes the `claude/<model>` breaker entry, never `?/…`.
|
|
160
|
+
const ranPair = (budgetActive || windowMovedRoles.has(runner.role)) && cfg?.model !== undefined
|
|
153
161
|
? modelPairName({ provider: seamHoldKey(cfg), model: cfg.model })
|
|
154
162
|
: null;
|
|
155
163
|
const ranOn = ranPair !== null ? gateStates.budget.breakers[ranPair] : undefined;
|
|
156
|
-
// The
|
|
157
|
-
// every run it makes re-keys it to the provider that run actually uses
|
|
158
|
-
// (plans/provider-concurrency.md §5) — a budget or model-failure switch that moves
|
|
159
|
-
//
|
|
164
|
+
// The run's lease rides in the provider-lease store for the length of the run, so
|
|
165
|
+
// every agent run it makes re-keys it to the provider that agent run actually uses
|
|
166
|
+
// (plans/provider-concurrency.md §5) — a budget or model-failure switch that moves an
|
|
167
|
+
// agent run to another provider mid-run moves the lease with it. The director's forced
|
|
160
168
|
// lease re-keys the same way, and its rekey never waits.
|
|
161
|
-
const outcome = await withProviderLease(runner.role === DIRECTOR_ROLE ? directorLease : lease, semaphore, () => runner.
|
|
169
|
+
const outcome = await withProviderLease(runner.role === DIRECTOR_ROLE ? directorLease : lease, semaphore, () => runner.run(), { piDefault });
|
|
162
170
|
if (ranPair !== null && ranOn !== undefined) {
|
|
163
171
|
const at = breakerNow();
|
|
164
172
|
const evidence = outcome.recoveredLeftover ? "skipped" : outcome.result;
|
|
165
|
-
gateStates.budget.breakers =
|
|
173
|
+
gateStates.budget.breakers = recordFallbackRunAt(gateStates.budget.breakers, ranPair, ranOn, evidence, probe, at, breakerPolicy);
|
|
166
174
|
}
|
|
167
175
|
else if (probe && probePair !== null) {
|
|
168
|
-
// A probe whose
|
|
176
|
+
// A probe whose run started on no engaged pair — the budget gate reopened
|
|
169
177
|
// between admission and start (the cap raised, or midnight reset spend) — folds
|
|
170
178
|
// no evidence, so its claim would hang on the pair's entry forever: probing
|
|
171
179
|
// blocks both fallbackProbeDuePair and startFallbackProbeAt, and nothing but a
|
|
172
180
|
// rekey clears it, so the demoted fallback could never earn another probe. Hand
|
|
173
|
-
// the claim back exactly as the !started path below does for a
|
|
181
|
+
// the claim back exactly as the !started path below does for a run turned away
|
|
174
182
|
// at its permit.
|
|
175
183
|
gateStates.budget.breakers = abandonFallbackProbeAt(gateStates.budget.breakers, probePair);
|
|
176
184
|
}
|
|
177
185
|
return outcome;
|
|
178
186
|
}, Date.now,
|
|
179
|
-
// The restart start gate, plus the failure hold for role
|
|
187
|
+
// The restart start gate, plus the failure hold for role runs (the director is
|
|
180
188
|
// exempt, as at scheduling): a waiter granted its permit mid-storm must not start
|
|
181
189
|
// into it.
|
|
182
190
|
() => startHeld(runner.role));
|
|
183
|
-
// A reservation whose
|
|
191
|
+
// A reservation whose run never started hands itself back, so the role re-schedules
|
|
184
192
|
// once the hold lifts (or on the new build) instead of sitting `running` forever with
|
|
185
193
|
// nothing in flight. Memory only, on purpose: nothing started, so no state write and
|
|
186
194
|
// no run_start/run_end — the persisted state still reads exactly as the last real
|
|
187
|
-
//
|
|
195
|
+
// run left it, and the next generation schedules the role from that.
|
|
188
196
|
if (!started)
|
|
189
197
|
runner.state.running = false;
|
|
190
198
|
// A probe turned away the same way answered nothing: hand its claim back (see
|
|
@@ -194,7 +202,7 @@ export function launchDueTicks(ctx) {
|
|
|
194
202
|
})();
|
|
195
203
|
const bucket = runner.role === DIRECTOR_ROLE ? directorInFlight : roleInFlight;
|
|
196
204
|
bucket.add(task);
|
|
197
|
-
// A
|
|
205
|
+
// A RoleRunner.run folds every failure it handles into an `error` outcome, so this task
|
|
198
206
|
// should resolve. A throw outside that fold (a finalize-time file write, a runner bug)
|
|
199
207
|
// rejects it instead; the drain awaits the bucket with allSettled, but chaining cleanup
|
|
200
208
|
// off a bare `void task.finally(...)` would leave the derived promise's rejection
|
|
@@ -210,9 +218,9 @@ export function launchDueTicks(ctx) {
|
|
|
210
218
|
.finally(() => {
|
|
211
219
|
bucket.delete(task);
|
|
212
220
|
if (bucket === roleInFlight && durationMs !== null) {
|
|
213
|
-
|
|
214
|
-
if (
|
|
215
|
-
|
|
221
|
+
roleRunDurationsMs.push(durationMs);
|
|
222
|
+
if (roleRunDurationsMs.length > ROLE_RUN_DURATION_SAMPLES)
|
|
223
|
+
roleRunDurationsMs.shift();
|
|
216
224
|
}
|
|
217
225
|
});
|
|
218
226
|
}
|