tumwater 0.4.2 → 0.4.4
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 +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +27 -13
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +6 -27
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +8 -0
- package/dist/src/agent/codex/codex-args.js +5 -5
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +26 -24
- package/dist/src/agent/opencode/opencode-catalog.js +85 -36
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +23 -27
- package/dist/src/agent/subscription-account.js +2 -83
- package/dist/src/backlog/backlog-archive.js +3 -4
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +56 -122
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +3 -2
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/baseline/main-guardrails.js +2 -2
- package/dist/src/baseline/main-watch.js +84 -50
- package/dist/src/brief/brief.js +26 -13
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/budget.js +4 -5
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-report.js +10 -0
- package/dist/src/build/build-check-scoped.js +40 -18
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/build/build-stage.js +6 -4
- package/dist/src/change/change-data.js +1 -4
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-completion-command.js +75 -0
- package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
- package/dist/src/cli/cli-doctor-command.js +103 -0
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +36 -14
- package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
- package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
- package/dist/src/cli/cli-query-commands.js +2 -2
- package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-role-command.js +2 -41
- package/dist/src/cli/cli-start-summary.js +4 -3
- package/dist/src/cli/cli-start-supervisor.js +190 -0
- package/dist/src/cli/cli-start.js +94 -244
- package/dist/src/cli/completion.js +85 -49
- package/dist/src/cli/follow-view.js +20 -2
- package/dist/src/cli/help.js +6 -13
- package/dist/src/cli.js +70 -176
- package/dist/src/collections.js +39 -27
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +23 -1
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +66 -40
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +163 -102
- package/dist/src/config/config-schema.js +10 -12
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +8 -72
- package/dist/src/config/config-validation.js +4 -39
- package/dist/src/config/config-views.js +26 -50
- package/dist/src/config/config-write.js +128 -102
- package/dist/src/config/config.js +5 -14
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +53 -120
- package/dist/src/doctor/doctor-backlog.js +16 -14
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +8 -8
- package/dist/src/doctor/doctor-claude-checks.js +70 -36
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +57 -117
- package/dist/src/doctor/doctor-fallback-checks.js +48 -26
- package/dist/src/doctor/doctor-holds.js +8 -47
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +122 -21
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +24 -11
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +44 -64
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +3 -5
- package/dist/src/events/{notify.js → event-notify.js} +6 -6
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +8 -0
- package/dist/src/failure/failure-cluster.js +9 -18
- package/dist/src/failure/failure-data.js +12 -8
- package/dist/src/failure/failure-render.js +46 -36
- package/dist/src/failure/failure-state-change.js +54 -120
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +32 -74
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +9 -8
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git-diff.js +6 -2
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-args.js +12 -0
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-config-commands.js +2 -64
- package/dist/src/gui/gui-endpoint-commands.js +1 -1
- package/dist/src/gui/gui-endpoint-common.js +19 -1
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +43 -48
- package/dist/src/gui/gui-prompt-commands.js +25 -25
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/gui/gui-server.js +1 -2
- package/dist/src/gui/gui-team-commands.js +2 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +54 -36
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-check-failures.js +12 -9
- package/dist/src/merge/merge-core.js +7 -32
- package/dist/src/merge/merge-drain.js +11 -13
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +11 -14
- package/dist/src/merge/merge-queue-render.js +3 -3
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-stack.js +17 -18
- package/dist/src/merge/merge-to-main.js +13 -58
- package/dist/src/merge/merge-verify.js +12 -21
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +80 -47
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +44 -69
- package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +18 -27
- package/dist/src/orchestrator/scheduling-skip.js +10 -14
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +10 -9
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +28 -1
- package/dist/src/pi/pi-listed-models.js +13 -0
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +41 -8
- package/dist/src/pi/pi-watchdogs.js +43 -15
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +51 -26
- package/dist/src/process/process.js +20 -12
- package/dist/src/process/run-marker.js +33 -15
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/gate-prompts.js +1 -44
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/redeploy/redeployer.js +21 -1
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/diff-rules.js +35 -21
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +48 -41
- package/dist/src/review/review-precheck.js +14 -4
- package/dist/src/review/review.js +48 -170
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/legacy-role-migration.js +6 -3
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +7 -46
- package/dist/src/roles/role-view.js +10 -33
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/ci-parity.js +19 -8
- package/dist/src/run/run-apply.js +12 -15
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +141 -90
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +128 -50
- package/dist/src/run/stage-check.js +60 -50
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/{leftover.js → runner-leftover.js} +9 -26
- package/dist/src/runner/runner-outcome.js +7 -2
- package/dist/src/runner/runner-pi.js +9 -22
- package/dist/src/runner/runner-recovery.js +5 -13
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
- package/dist/src/runner/runner-run.js +103 -50
- package/dist/src/runner/runner-state-load.js +18 -28
- package/dist/src/runner/runner-state.js +0 -14
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/backoff.js +8 -3
- package/dist/src/scheduling/claim-entries.js +2 -1
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-budget.js +4 -43
- package/dist/src/status/status-data.js +35 -39
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -18
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +105 -70
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
- package/dist/src/team/team-polls.js +53 -201
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/datetime.js +16 -7
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +29 -15
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +6 -5
- package/dist/src/ui/alert-feeds.js +46 -111
- package/dist/src/ui/badges.js +17 -88
- package/dist/src/ui/gui/gui-client-boot.js +2 -8
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-feed.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-operator.js +7 -5
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +12 -6
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +107 -44
- package/dist/src/ui/run-progress-model.js +20 -4
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +11 -30
- package/dist/src/ui/status-render.js +13 -24
- package/dist/src/ui/team-alert-backlog.js +14 -204
- package/dist/src/ui/team-alert-shared.js +6 -29
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +8 -15
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-backlog.js +2 -2
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +5 -10
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +167 -98
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +5 -4
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -230
- package/dist/src/ui/tui/tui-pane-history.js +67 -74
- package/dist/src/ui/tui/tui-pane-pending.js +288 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +15 -10
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +38 -33
- package/dist/src/ui/tui/tui.js +4 -7
- package/dist/src/verdict/fix-claim.js +107 -56
- package/dist/src/verdict/refusal.js +1 -1
- package/dist/src/verdict/reply-contract.js +3 -4
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/review/review-followup.js +0 -87
- package/dist/src/review/review-run.js +0 -395
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/cli/{brief-command.js → cli-brief-command.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
|
@@ -3,8 +3,9 @@ import { fail, say, sayJson } from "../cli/cli-output.js";
|
|
|
3
3
|
import { failOverDurationCap, flagValue, parseCountFlag, parseDurationFlag, parseRoleFlag } from "../cli/cli-args.js";
|
|
4
4
|
import { errorMessage, splitCommaList } from "../text/text.js";
|
|
5
5
|
import { PAUSE_FOR_OVERFLOW_HINT, REASON_VALUE_ERROR, ROLE_VALUE_ERROR } from "../cli/cli-flag-specs.js";
|
|
6
|
-
import { artifactPhrase, hasRetirableLeftovers, previewRetire, retireRole } from "./retire.js";
|
|
7
|
-
import { previewRevert, revertFollowUp, revertMergedCommit, runRevertSha } from "./revert.js";
|
|
6
|
+
import { artifactPhrase, hasRetirableLeftovers, previewRetire, retireRole } from "./operator-retire.js";
|
|
7
|
+
import { previewRevert, revertFollowUp, revertMergedCommit, runRevertSha } from "./operator-revert.js";
|
|
8
|
+
import { distinctOrdered } from "../collections.js";
|
|
8
9
|
import { runNotFoundMessage } from "../run/run-detail.js";
|
|
9
10
|
import { readRunDetail } from "../run/run-detail-data.js";
|
|
10
11
|
import { readRunRows } from "../history/history-data.js";
|
|
@@ -15,9 +16,9 @@ import { shortSha } from "../text/format.js";
|
|
|
15
16
|
import { INSTANCE_ROLES } from "../roles/runner-ids.js";
|
|
16
17
|
import { allRoleIds, unknownRoleMessage } from "../roles/roles.js";
|
|
17
18
|
import { normalizePauseReason, pauseTeam, pauseRole, pausedRoles, resumeTeam, resumeRole, } from "../team/team-state.js";
|
|
18
|
-
import { readOrchestratorInfo } from "../team/orchestrator-info.js";
|
|
19
|
+
import { readOrchestratorInfo } from "../team/team-orchestrator-info.js";
|
|
19
20
|
import { pidAlive } from "../process/process.js";
|
|
20
|
-
import { inspectReclaimCandidates } from "../team/reclaim.js";
|
|
21
|
+
import { inspectReclaimCandidates } from "../team/team-reclaim.js";
|
|
21
22
|
import { markerApplyNote, NO_HARNESS_ERROR, PAUSE_FOR_MAX_MS, cancelScheduledStopMessage, cancelWakes, listWakes, renderWakeList, requestAbort, requestAbortAll, requestResetCounters, requestRestart, requestScheduledStop, requestWake, rolePauseMessage, roleResumeMessage, runManualReclaim, timedPauseBits, } from "./operator-intent.js";
|
|
22
23
|
/** The CLI layer of the operator commands: the `reset-counters`, `wake`, `reclaim`, `abort`,
|
|
23
24
|
* `pause`, `resume`, `stop`, and `retire` commands, split out of cli.ts so the command bodies
|
|
@@ -27,7 +28,7 @@ import { markerApplyNote, NO_HARNESS_ERROR, PAUSE_FOR_MAX_MS, cancelScheduledSto
|
|
|
27
28
|
* the `prompt` command, which drives the durable per-role queues rather than the marker
|
|
28
29
|
* protocol, lives beside them in src/inbox/prompt-commands.ts, and the `config` command,
|
|
29
30
|
* which reads and writes tumwater.json directly rather than touching team state, in
|
|
30
|
-
* src/cli/config-commands.ts. Every command here is deliberately usable without a live
|
|
31
|
+
* src/cli/cli-config-commands.ts. Every command here is deliberately usable without a live
|
|
31
32
|
* harness except `abort` and `stop`, which have nothing to reach when no team is up. */
|
|
32
33
|
/** The `--role <id>` value when given, resolved WITHOUT tumwater.json when it names a
|
|
33
34
|
* built-in catalog role: the team itself tolerates a broken config (the live reload keeps
|
|
@@ -67,7 +68,7 @@ function namedRoleList(root, args) {
|
|
|
67
68
|
const pieces = rolePieces(args);
|
|
68
69
|
if (pieces === null)
|
|
69
70
|
return null;
|
|
70
|
-
return
|
|
71
|
+
return distinctOrdered(pieces.map((piece) => namedRole(root, ["--role", piece])));
|
|
71
72
|
}
|
|
72
73
|
/** namedRole for the commands that write or lift the per-role pause marker, over a comma list
|
|
73
74
|
* (`--role clean,dedupe`, like `history --role`): `feature-1` (`bugfix-1`) is the pause key of
|
|
@@ -80,7 +81,7 @@ function pauseTargetRoles(root, args) {
|
|
|
80
81
|
if (pieces === null)
|
|
81
82
|
return null;
|
|
82
83
|
const roles = pieces.map((piece) => [...INSTANCE_ROLES].some((base) => piece === `${base}-1`) ? piece : namedRole(root, ["--role", piece]));
|
|
83
|
-
return
|
|
84
|
+
return distinctOrdered(roles);
|
|
84
85
|
}
|
|
85
86
|
/** The `--role <id>` value a marker command requires: namedRole's resolution plus the
|
|
86
87
|
* command-specific "requires --role" failure. retire needs a target (unlike
|
|
@@ -222,17 +223,9 @@ export async function cmdRestart(root, _args) {
|
|
|
222
223
|
fail(result.error);
|
|
223
224
|
say(result.message);
|
|
224
225
|
}
|
|
225
|
-
/**
|
|
226
|
-
*
|
|
227
|
-
|
|
228
|
-
* keeps running; without one, stop every role from starting NEW runs while in-flight
|
|
229
|
-
* ones finish and the director keeps running (its prompts outrank operator gates, like
|
|
230
|
-
* under the budget cap). The markers
|
|
231
|
-
* are persistent state, not one-shot requests: presence means paused until `resume` removes
|
|
232
|
-
* it — so pausing before startup starts an already-paused team. Unlike abort, no live
|
|
233
|
-
* harness is required; when none runs, say where the pause takes effect instead of failing.
|
|
234
|
-
* Idempotent: a second pause reports the existing marker as-is. */
|
|
235
|
-
export async function cmdPause(root, args = [], now = Date.now()) {
|
|
226
|
+
/** Parse and validate the `--for`/`--reason` flags, failing fast on a bad value before any
|
|
227
|
+
* marker is written: "how is `--reason` validated?" is answered here. */
|
|
228
|
+
function pauseFlags(args) {
|
|
236
229
|
// `--for <duration>` (the timed pause): the ms-epoch deadline pauseTeam/pauseRole write
|
|
237
230
|
// into the marker and every consumer honors — the command's own gate (runMarkerCommand's
|
|
238
231
|
// rejectUnknownArgs) has already restricted the flag to this command and validated its
|
|
@@ -255,28 +248,45 @@ export async function cmdPause(root, args = [], now = Date.now()) {
|
|
|
255
248
|
// capped-flag idiom the --since windows in cli.ts use).
|
|
256
249
|
if (forMs !== undefined)
|
|
257
250
|
failOverDurationCap("pause --for", forMs, PAUSE_FOR_MAX_MS, PAUSE_FOR_OVERFLOW_HINT);
|
|
251
|
+
return { forMs, reason };
|
|
252
|
+
}
|
|
253
|
+
/** The timed-pause deadline shape from `--for` and the single `now` instant the deadline and
|
|
254
|
+
* the confirmation both read, injected by tests so the wording they pin cannot drift with
|
|
255
|
+
* the wall clock — the same single-clock discipline timedPauseBits's own `now` parameter
|
|
256
|
+
* follows: "what deadline does `--for` resolve to?" is answered here. */
|
|
257
|
+
function pauseTiming(forMs, now) {
|
|
258
258
|
// `now` is the one instant the deadline and the confirmation both read, injected by tests
|
|
259
259
|
// so the wording they pin cannot drift with the wall clock — the same single-clock
|
|
260
260
|
// discipline timedPauseBits's own `now` parameter follows.
|
|
261
261
|
const timed = forMs === undefined ? undefined : { ms: forMs, untilMs: now + forMs };
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
262
|
+
return { timed, untilMs: timed?.untilMs };
|
|
263
|
+
}
|
|
264
|
+
/** The per-role pause branch: rejects a reason aimed at a role pause (a per-role reason would
|
|
265
|
+
* need a per-role map for no observed need), then pauses each named role through
|
|
266
|
+
* `rolePauseMessage`/`pauseRole`. */
|
|
267
|
+
function pauseRoleGroup(root, roles, forMs, now, reason) {
|
|
268
|
+
const { timed, untilMs } = pauseTiming(forMs, now);
|
|
269
|
+
// The plan keeps per-role pauses anonymous (a per-role reason would need a per-role map
|
|
270
|
+
// for no observed need), so a reason aimed at a role pause fails fast here — accepting
|
|
271
|
+
// the flag and silently dropping the note would tell the operator the reason stands
|
|
272
|
+
// when no marker carries it. Before any marker is written, beside the other fail-fasts.
|
|
273
|
+
if (reason !== undefined)
|
|
274
|
+
fail("pause --reason states why the whole team is paused — a per-role pause carries no reason (drop --role, or run bare `tumwater pause --reason <text>`)");
|
|
275
|
+
// Per-role branch: pauseRole in src/team/team-state.ts is the single writer of the role
|
|
276
|
+
// marker (the TUI's Ctrl+P and the dashboard's /api/pause-role toggle call it too), so
|
|
277
|
+
// the CLI, TUI, and GUI cannot drift on format or idempotence; a false return means the
|
|
278
|
+
// role was already in the set, which rolePauseMessage words — unless a `--for` stands,
|
|
279
|
+
// which overwrites the deadline and reports the fresh confirmation.
|
|
280
|
+
for (const role of roles)
|
|
281
|
+
say(rolePauseMessage(root, role, pauseRole(root, role, untilMs), timed, now));
|
|
282
|
+
}
|
|
283
|
+
/** The team pause branch: writes the pause marker through pauseTeam (the single writer, the
|
|
284
|
+
* GUI's /api/pause toggle calls it too), reports "already paused" on a false return, and
|
|
285
|
+
* prints the confirmation template. The reason rides the fresh pause write (or a `--for`
|
|
286
|
+
* overwrite): the same last-write-wins rule as the deadline, so an "already paused" no-op
|
|
287
|
+
* keeps the standing note as-is. */
|
|
288
|
+
function pauseTeamGroup(root, forMs, now, reason) {
|
|
289
|
+
const { timed, untilMs } = pauseTiming(forMs, now);
|
|
280
290
|
// pauseTeam in src/team/team-state.ts is the single writer of the pause marker — the GUI's
|
|
281
291
|
// /api/pause toggle calls it too, so the CLI and the dashboard cannot drift on format
|
|
282
292
|
// or idempotence; a false return means the marker was already there (a `--for` never
|
|
@@ -292,6 +302,24 @@ export async function cmdPause(root, args = [], now = Date.now()) {
|
|
|
292
302
|
const why = pauseReasonSuffix(reason);
|
|
293
303
|
say(`team paused${forPhrase}${why} — roles start no new runs${when} (current runs finish; the director keeps running your messages)${note}${tail}`);
|
|
294
304
|
}
|
|
305
|
+
/** `tumwater pause [--role <id>[,<id>...]] [--for <duration>]`: with roles (a comma list pauses
|
|
306
|
+
* each), stop THAT role from
|
|
307
|
+
* starting new runs — in-flight ones finish, every other role (the director included)
|
|
308
|
+
* keeps running; without one, stop every role from starting NEW runs while in-flight
|
|
309
|
+
* ones finish and the director keeps running (its prompts outrank operator gates, like
|
|
310
|
+
* under the budget cap). The markers
|
|
311
|
+
* are persistent state, not one-shot requests: presence means paused until `resume` removes
|
|
312
|
+
* it — so pausing before startup starts an already-paused team. Unlike abort, no live
|
|
313
|
+
* harness is required; when none runs, say where the pause takes effect instead of failing.
|
|
314
|
+
* Idempotent: a second pause reports the existing marker as-is. */
|
|
315
|
+
export async function cmdPause(root, args = [], now = Date.now()) {
|
|
316
|
+
const { forMs, reason } = pauseFlags(args);
|
|
317
|
+
const roles = pauseTargetRoles(root, args);
|
|
318
|
+
if (roles)
|
|
319
|
+
pauseRoleGroup(root, roles, forMs, now, reason);
|
|
320
|
+
else
|
|
321
|
+
pauseTeamGroup(root, forMs, now, reason);
|
|
322
|
+
}
|
|
295
323
|
/** Deliver the stop signal to the recorded orchestrator pid: "signalled" when SIGTERM was
|
|
296
324
|
* delivered, "gone" when the process died between the caller's liveness check and the
|
|
297
325
|
* signal (ESRCH) — stop's goal is already met in that case, so the caller reports it as
|
|
@@ -404,10 +432,21 @@ async function retireTargetRole(root, args) {
|
|
|
404
432
|
return role;
|
|
405
433
|
fail(unknownRoleMessage(role, ids));
|
|
406
434
|
}
|
|
435
|
+
/** Render one line per artifact an inventory holds and one per skipped artifact — the same
|
|
436
|
+
* wording both the `--dry-run` preview and the committed run go through, so the two cannot
|
|
437
|
+
* drift on the removed / `nothing to remove` phrasing. `verb` distinguishes the preview's
|
|
438
|
+
* `would remove` from the committed `removed`. The `nothing to remove for role` no-ops line
|
|
439
|
+
* stays in the caller (only the committed run carries it). */
|
|
440
|
+
function sayRemoveInventory(removed, skipped, verb, role) {
|
|
441
|
+
for (const name of removed)
|
|
442
|
+
say(`${verb} ${artifactPhrase(name, role)}`);
|
|
443
|
+
for (const name of skipped)
|
|
444
|
+
say(`nothing to remove: ${artifactPhrase(name, role)}`);
|
|
445
|
+
}
|
|
407
446
|
/** `tumwater retire --role <id> [--force] [--json]`: remove a disabled role's leftover worktree,
|
|
408
447
|
* branch, merge ref, and paused-state marker. The collection, safety rails, and removal live in
|
|
409
|
-
* src/operator/retire.ts; this is the render layer — one line per removed
|
|
410
|
-
* that make a second run idempotent, or the `--json` payload. */
|
|
448
|
+
* src/operator/operator-retire.ts; this is the render layer — one line per removed
|
|
449
|
+
* artifact, the skip lines that make a second run idempotent, or the `--json` payload. */
|
|
411
450
|
export async function cmdRetire(root, args) {
|
|
412
451
|
// The config exists only to validate the id against built-ins plus user-defined roles, with
|
|
413
452
|
// the leftover-branch gate for ids no config names; a missing --role fails before it is needed.
|
|
@@ -425,10 +464,7 @@ export async function cmdRetire(root, args) {
|
|
|
425
464
|
});
|
|
426
465
|
return;
|
|
427
466
|
}
|
|
428
|
-
|
|
429
|
-
say(`would remove ${artifactPhrase(name, role)}`);
|
|
430
|
-
for (const name of preview.skipped)
|
|
431
|
-
say(`nothing to remove: ${artifactPhrase(name, role)}`);
|
|
467
|
+
sayRemoveInventory(preview.wouldRemove, preview.skipped, "would remove", role);
|
|
432
468
|
for (const reason of preview.objections)
|
|
433
469
|
say(force ? `--force overrides: ${reason}` : `would refuse: ${reason}`);
|
|
434
470
|
say("dry run — nothing removed");
|
|
@@ -439,10 +475,7 @@ export async function cmdRetire(root, args) {
|
|
|
439
475
|
sayJson({ role, removed: result.removed, skipped: result.skipped });
|
|
440
476
|
return;
|
|
441
477
|
}
|
|
442
|
-
|
|
443
|
-
say(`removed ${artifactPhrase(name, role)}`);
|
|
444
|
-
for (const name of result.skipped)
|
|
445
|
-
say(`nothing to remove: ${artifactPhrase(name, role)}`);
|
|
478
|
+
sayRemoveInventory(result.removed, result.skipped, "removed", role);
|
|
446
479
|
if (result.removed.length === 0 && result.skipped.length === 0)
|
|
447
480
|
say(`nothing to remove for role ${role}`);
|
|
448
481
|
}
|
|
@@ -540,7 +573,7 @@ function revertRunSha(root, positionals, last) {
|
|
|
540
573
|
/** `tumwater revert <sha>|<role> <n>|<role> --last`: undo one merged change on main,
|
|
541
574
|
* committing the inverse as `tumwater(operator): Revert <short sha>` under the primary-worktree
|
|
542
575
|
* lock. The lock, branch guard, and fail-closed ref/conflict handling live in
|
|
543
|
-
* src/operator/revert.ts; this renderer names the new main head and names the role
|
|
576
|
+
* src/operator/operator-revert.ts; this renderer names the new main head and names the role
|
|
544
577
|
* that made the reverted commit, when one did, as the one that may redo the work.
|
|
545
578
|
* `--json` prints the preview or the revert as one JSON object instead (refusals still fail on
|
|
546
579
|
* stderr). */
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { cancelScheduledStop, isTeamPaused, scheduleStop } from "../team/team-state.js";
|
|
4
|
-
import { orchestratorAlive, readOrchestratorInfo } from "../team/orchestrator-info.js";
|
|
4
|
+
import { orchestratorAlive, readOrchestratorInfo } from "../team/team-orchestrator-info.js";
|
|
5
5
|
import { durationLabel } from "../cli/cli-args.js";
|
|
6
6
|
import { formatDate, formatTime, humanUntil, DAY_MS } from "../text/datetime.js";
|
|
7
7
|
import { submitRolePrompt } from "../inbox/inbox-submit.js";
|
|
8
|
-
import { questionAsker } from "../backlog/question-answer.js";
|
|
8
|
+
import { questionAsker } from "../backlog/backlog-question-answer.js";
|
|
9
9
|
import { knownRoleIdsCached, loadConfigCached } from "../config/config.js";
|
|
10
10
|
import { baseRoleOf, runnerIds } from "../roles/runner-ids.js";
|
|
11
11
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
@@ -14,7 +14,7 @@ import { loadRunnerState } from "../runner/runner-state-load.js";
|
|
|
14
14
|
import { clearBackoff } from "../scheduling/backoff.js";
|
|
15
15
|
import { writeJsonAtomic } from "../files/json-files.js";
|
|
16
16
|
import { ensureDir } from "../files/files.js";
|
|
17
|
-
import { reclaimPass } from "../team/reclaim.js";
|
|
17
|
+
import { reclaimPass } from "../team/team-reclaim.js";
|
|
18
18
|
import { formatGB } from "../text/format.js";
|
|
19
19
|
import { plural } from "../text/phrases.js";
|
|
20
20
|
import { numberedList } from "../text/markdown.js";
|
|
@@ -17,8 +17,7 @@ import { logEventBestEffort, warnEventBestEffort, TUMWATER_SOURCE } from "../eve
|
|
|
17
17
|
import { errorMessage } from "../text/text.js";
|
|
18
18
|
import { fairOrder } from "../scheduling/scheduling.js";
|
|
19
19
|
import { modelPairName } from "../budget/budget.js";
|
|
20
|
-
import {
|
|
21
|
-
import { seamConcurrencyKey } from "../config/config-views.js";
|
|
20
|
+
import { withRunPermit } from "../concurrency/run-permit.js";
|
|
22
21
|
import { DIRECTOR_ROLE, roleTier } from "../roles/roles.js";
|
|
23
22
|
import { runTimedRoleRun } from "../run/run-timing.js";
|
|
24
23
|
/** How many recent work-bearing run durations the p75 sample keeps (drainInFlightWork's
|
|
@@ -27,13 +26,13 @@ import { runTimedRoleRun } from "../run/run-timing.js";
|
|
|
27
26
|
export const ROLE_RUN_DURATION_SAMPLES = 50;
|
|
28
27
|
/** The admission wait threshold below which a role run's semaphore wait logs no event — a
|
|
29
28
|
* run that starts at once stays silent. Mirrors SLOT_WAIT_EVENT_MS in src/git/worktree-pool.ts:
|
|
30
|
-
* the wait that signals
|
|
29
|
+
* the wait that signals maxConcurrent caps the team. */
|
|
31
30
|
export const PROVIDER_WAIT_EVENT_MS = 30_000;
|
|
32
31
|
/** Launch one due runner's run: reserve it, park it in the semaphore, and start it under
|
|
33
32
|
* runTimedRoleRun with the breaker-evidence wiring. Returns the task promise already added
|
|
34
33
|
* to its in-flight bucket (its `finally` self-removes and folds the duration sample). */
|
|
35
34
|
export function launchDueRuns(ctx) {
|
|
36
|
-
const { root, reasons, signal, gateStates, breakerPolicy, breakerNow, probePair, probeRoles, budgetActive,
|
|
35
|
+
const { root, reasons, signal, gateStates, breakerPolicy, breakerNow, probePair, probeRoles, budgetActive, semaphore, rolePermitHolders, roleInFlight, directorInFlight, roleRunDurationsMs, startHeld, } = ctx;
|
|
37
36
|
let admittedProbe = false;
|
|
38
37
|
for (const runner of fairOrder([...reasons.keys()])) {
|
|
39
38
|
if (signal.aborted)
|
|
@@ -65,13 +64,8 @@ export function launchDueRuns(ctx) {
|
|
|
65
64
|
// Mark the parked waiter while it waits: it holds no permit yet, so the dashboards
|
|
66
65
|
// render `awaiting slot` (an inactive state) and the active rows keep tracking
|
|
67
66
|
// maxConcurrent (BUGS.md 2026-09-24). Cleared the moment the permit is granted. The
|
|
68
|
-
// director never queues, so it is never a parked waiter.
|
|
69
|
-
// `parkedSince` (provider-concurrency part 5/5): the key the admission lease will
|
|
70
|
-
// acquire, computed from the same runConfig admission uses, so a waiter named by a
|
|
71
|
-
// full key renders `awaiting <key> slot` on the dashboards.
|
|
72
|
-
const parkKey = runner.role === DIRECTOR_ROLE ? null : runner.runConfig(Date.now());
|
|
67
|
+
// director never queues, so it is never a parked waiter.
|
|
73
68
|
runner.state.parkedSince = usesSlot ? Date.now() : undefined;
|
|
74
|
-
runner.state.parkedOn = usesSlot && parkKey ? seamConcurrencyKey(parkKey, piDefault) : undefined;
|
|
75
69
|
// The run's own duration (null when it never started, was cut off, or ended without
|
|
76
70
|
// work): the drain-window sample is taken only for a work-bearing run that finished
|
|
77
71
|
// on its own (runTimedRoleRun; BUGS.md 2026-09-30).
|
|
@@ -79,36 +73,21 @@ export function launchDueRuns(ctx) {
|
|
|
79
73
|
// Whether runner.run() was ever called — false when the start gate (or a shutdown)
|
|
80
74
|
// turned the run away at its permit.
|
|
81
75
|
let started = false;
|
|
82
|
-
// The run's
|
|
83
|
-
//
|
|
84
|
-
// cap is real for role runs from part 2/5 on. The director never queues, so its lease is
|
|
85
|
-
// never created here.
|
|
76
|
+
// The run's team semaphore lease, acquired at admission and released in place of the bare
|
|
77
|
+
// team release. The director never queues, so its lease is never created here.
|
|
86
78
|
let lease = null;
|
|
87
|
-
// The director's forced provider lease (plans/provider-concurrency.md §4): never queues and
|
|
88
|
-
// never counts toward maxConcurrent, but DOES count toward its provider key while its run
|
|
89
|
-
// runs — so a full provider admits no further grants until the director releases.
|
|
90
|
-
let directorLease = null;
|
|
91
79
|
const task = (async () => {
|
|
92
80
|
durationMs = await runTimedRoleRun(signal, usesSlot
|
|
93
81
|
? async () => {
|
|
94
|
-
// The
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
const key = cfg ? seamConcurrencyKey(cfg, piDefault) : undefined;
|
|
82
|
+
// The run is admitted by the one team total (maxConcurrent) alone — there is no
|
|
83
|
+
// per-provider key or cap any more. Which cap holds the run is always the team
|
|
84
|
+
// total.
|
|
98
85
|
const admittedAt = Date.now();
|
|
99
|
-
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const heldBy = providerFull
|
|
105
|
-
? { cap: "provider", limit: keyed.limit }
|
|
106
|
-
: { cap: "team", limit: pool.limit };
|
|
107
|
-
lease = await semaphore.acquireLease(roleTier(runner.role), key);
|
|
108
|
-
// A wait of PROVIDER_WAIT_EVENT_MS or more for the provider/team slot is
|
|
109
|
-
// provider contention worth surfacing: name it so `tumwater doctor`'s provider
|
|
110
|
-
// contention row can sum it and the operator can size maxConcurrentPerProvider
|
|
111
|
-
// (or maxConcurrent) from real waits. Best-effort like the launch pass's other
|
|
86
|
+
lease = await semaphore.acquireLease(roleTier(runner.role));
|
|
87
|
+
// A wait of PROVIDER_WAIT_EVENT_MS or more for the team slot is
|
|
88
|
+
// contention worth surfacing: name it so `tumwater doctor`'s provider
|
|
89
|
+
// contention row can sum it and the operator can size maxConcurrent from real
|
|
90
|
+
// waits. Best-effort like the launch pass's other
|
|
112
91
|
// events, so an unwritable log never blocks a start; a run admitted at once
|
|
113
92
|
// stays silent.
|
|
114
93
|
if (Date.now() - admittedAt >= (ctx.providerWaitMs ?? PROVIDER_WAIT_EVENT_MS)) {
|
|
@@ -117,56 +96,45 @@ export function launchDueRuns(ctx) {
|
|
|
117
96
|
type: "provider_wait",
|
|
118
97
|
role: runner.role,
|
|
119
98
|
waitedMs: Date.now() - admittedAt,
|
|
120
|
-
...(key !== undefined ? { key } : {}),
|
|
121
|
-
...heldBy,
|
|
122
99
|
});
|
|
123
100
|
}
|
|
124
101
|
// Permit granted: the run is now an active, permit-holding state until the
|
|
125
102
|
// release below. A run the start gate turns away releases within the same
|
|
126
103
|
// microtask chain, so no poll ever counts it as a permit holder.
|
|
127
104
|
runner.state.parkedSince = undefined;
|
|
128
|
-
runner.state.parkedOn = undefined;
|
|
129
105
|
rolePermitHolders.add(runner);
|
|
130
106
|
}
|
|
131
107
|
: async () => {
|
|
132
|
-
// The director
|
|
133
|
-
//
|
|
134
|
-
// and the key's in-use rises by one — overshooting by one — so no further grant on
|
|
135
|
-
// that key happens until its release drains it back under the cap.
|
|
136
|
-
const cfg = runner.runConfig(Date.now());
|
|
137
|
-
directorLease = semaphore.forceLease(cfg ? seamConcurrencyKey(cfg, piDefault) : undefined);
|
|
108
|
+
// The director never queues behind roles: a user prompt starts at once, taking
|
|
109
|
+
// no semaphore permit (never counting toward maxConcurrent).
|
|
138
110
|
}, usesSlot
|
|
139
111
|
? () => {
|
|
140
112
|
rolePermitHolders.delete(runner);
|
|
141
113
|
lease.release();
|
|
142
114
|
}
|
|
143
|
-
: () => {
|
|
144
|
-
directorLease.release();
|
|
145
|
-
}, async () => {
|
|
115
|
+
: () => { }, async () => {
|
|
146
116
|
started = true;
|
|
147
117
|
// A role run that starts while the budget holds uses the fallback pair its tier
|
|
148
|
-
// resolved to (the gate's per-tier view was installed in its config)
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
const now = Date.now();
|
|
156
|
-
const cfg = runner.role === DIRECTOR_ROLE ? null : runner.runConfig(now);
|
|
118
|
+
// resolved to (the gate's per-tier view was installed in its config). Its outcome
|
|
119
|
+
// is the evidence for the pair it ACTUALLY ran on, so read it from the runner's
|
|
120
|
+
// resolved config (runConfig), which names the budget-fallback pair when a cap
|
|
121
|
+
// episode is active: a per-pair breaker fed a fallback run under the primary's
|
|
122
|
+
// name would misprice a healthy pair and miss a dead one (PLANS.md "Mixed claude +
|
|
123
|
+
// pi fleets, part 2/4").
|
|
124
|
+
const cfg = runner.role === DIRECTOR_ROLE ? null : runner.runConfig();
|
|
157
125
|
// The breaker is keyed by the seam that ran (seamHoldKey), like the run pair and
|
|
158
126
|
// the team-hold stamps (PLANS.md "Mixed claude + pi fleets, part 2/4"): a claude
|
|
159
127
|
// fallback run demotes the `claude/<model>` breaker entry, never `?/…`.
|
|
160
|
-
const ranPair =
|
|
128
|
+
const ranPair = budgetActive && cfg?.model !== undefined
|
|
161
129
|
? modelPairName({ provider: seamHoldKey(cfg), model: cfg.model })
|
|
162
130
|
: null;
|
|
163
131
|
const ranOn = ranPair !== null ? gateStates.budget.breakers[ranPair] : undefined;
|
|
164
|
-
// The run's lease rides in the
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
132
|
+
// The run's lease rides in the run-permit store for the length of the run, so an
|
|
133
|
+
// idle wait inside it (a failed review's in-place retry pause) can hand the team
|
|
134
|
+
// permit back and re-take it before the next attempt. The director holds no lease.
|
|
135
|
+
const outcome = usesSlot
|
|
136
|
+
? await withRunPermit(lease, () => runner.run())
|
|
137
|
+
: await runner.run();
|
|
170
138
|
if (ranPair !== null && ranOn !== undefined) {
|
|
171
139
|
const at = breakerNow();
|
|
172
140
|
const evidence = outcome.recoveredLeftover ? "skipped" : outcome.result;
|