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,383 +1,48 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/** The poll loop's per-runner scheduling pass (src/orchestrator/orchestrator.ts): decide, for each
|
|
3
|
-
* runner, whether this poll admits a
|
|
2
|
+
* runner, whether this poll admits a run and why — the pause/quiet/cost-cap/failure-hold
|
|
4
3
|
* gates, the merge-queue interlock, need-based deferral, and once-mode settlement —
|
|
5
|
-
* producing the reasons map orchestrator-launch.ts's
|
|
4
|
+
* producing the reasons map orchestrator-launch.ts's launchDueRuns starts runs from.
|
|
6
5
|
* Split out of runOrchestrator's poll body so the WHY-due policy reads on its own screen;
|
|
7
|
-
*
|
|
6
|
+
* orchestrator-poll.ts keeps the poll's other passes (config reload, markers, gates, redeploy,
|
|
8
7
|
* merge drain) and the launch call itself. */
|
|
9
8
|
import { increment } from "../collections.js";
|
|
10
|
-
import { reviewRunConfig, seamHoldKey } from "../config/config-views.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { briefStatusIncomplete } from "../brief/brief.js";
|
|
17
|
-
import { queuedMergeRoles } from "../merge/merge-queue.js";
|
|
18
|
-
import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
|
|
9
|
+
import { reviewRunConfig, roleSeamTier, seamHoldKey } from "../config/config-views.js";
|
|
10
|
+
import { openBugs } from "../backlog/backlog.js";
|
|
11
|
+
import { fallbackProbeDuePair } from "../budget/fallback-breaker.js";
|
|
12
|
+
import { modelPairName } from "../budget/budget.js";
|
|
13
|
+
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
14
|
+
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
19
15
|
import { eligibleEntries } from "../backlog/backlog-eligibility.js";
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
function noteSkip(root, runner, reason) {
|
|
25
|
-
if (runner.state.skipReason === reason)
|
|
26
|
-
return;
|
|
27
|
-
runner.state.skipReason = reason;
|
|
28
|
-
saveLoopState(root, runner.state);
|
|
29
|
-
}
|
|
16
|
+
import { planBacklogFull } from "../scheduling/claims.js";
|
|
17
|
+
import { admitRunner } from "./scheduling-admit.js";
|
|
18
|
+
import { skipRunner } from "./scheduling-skip.js";
|
|
19
|
+
import { buildSchedulingScope, noteSkip, } from "./scheduling-scope.js";
|
|
30
20
|
/** Decide which runners are due this poll. Mutates `deferredDue` (and once-mode settlement)
|
|
31
|
-
* as its only side effects; returns the reason per admitted runner.
|
|
21
|
+
* as its only side effects; returns the reason per admitted runner. The once-per-poll
|
|
22
|
+
* precompute lives on the scope scheduling-scope.ts's buildSchedulingScope builds (the
|
|
23
|
+
* merge-queue snapshot, director pin, per-base claim groups, gate predicates and admission
|
|
24
|
+
* state); this per-runner loop reads them all through it. */
|
|
32
25
|
export async function pollRunnerReasons(ctx) {
|
|
33
|
-
const
|
|
34
|
-
const reasons =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// check below only asks whether the runner's role has a queued entry. The queue changes
|
|
42
|
-
// only when a merge completes or a tick enqueues — both asynchronous events this pass
|
|
43
|
-
// observes on its next poll — so one snapshot is as fresh as per-runner reads, and a
|
|
44
|
-
// merge that completes mid-pass just keeps its role blocked one extra poll (conservative).
|
|
45
|
-
const queuedRoles = queuedMergeRoles(root);
|
|
46
|
-
// Parallel work instances (plans/parallel-work-instances.md "Claims", part 4/7): a base role
|
|
47
|
-
// with more than one runner assigns each due instance a distinct eligible entry, and releases
|
|
48
|
-
// the claims whose entry left, turned ineligible, was disabled or went stale. Computed once
|
|
49
|
-
// per poll and only for a multi-runner base, so a single-runner team is unchanged. The
|
|
50
|
-
// release runs before assignment so a freed key counts as free below.
|
|
51
|
-
const claimGroups = new Map();
|
|
52
|
-
const baseCounts = new Map();
|
|
53
|
-
for (const runner of runners) {
|
|
54
|
-
const base = baseRoleOf(runner.role);
|
|
55
|
-
increment(baseCounts, base);
|
|
56
|
-
}
|
|
57
|
-
for (const [base, count] of baseCounts) {
|
|
58
|
-
if (count <= 1)
|
|
59
|
-
continue;
|
|
60
|
-
const groupRunners = runners.filter((r) => baseRoleOf(r.role) === base);
|
|
61
|
-
const eligible = eligibleEntries(root, base);
|
|
62
|
-
const eligibleKeys = new Set(eligible.map((e) => e.key));
|
|
63
|
-
const listed = listedKeys(root, base);
|
|
64
|
-
// A group whose runner is mid-entry with no recorded claim (it took the entry while it was
|
|
65
|
-
// the only runner, before a live reload raised `instances`) cannot be told WHICH entry that
|
|
66
|
-
// is from state alone. Assigning a free entry to a sibling could then hand it the same
|
|
67
|
-
// work: hold the whole group's assignment until that runner settles (merges, drops or goes
|
|
68
|
-
// idle), at which point its own staging may have recorded a claim. A busy runner that DOES
|
|
69
|
-
// hold a claim is fine — `held` excludes its key and siblings take other entries.
|
|
70
|
-
let hold = false;
|
|
71
|
-
for (const r of groupRunners) {
|
|
72
|
-
const busy = r.state.running ||
|
|
73
|
-
queuedRoles.has(r.role) ||
|
|
74
|
-
hasOutstandingWork(r.state);
|
|
75
|
-
if (r.state.claim === undefined && busy)
|
|
76
|
-
hold = true;
|
|
77
|
-
const released = claimReleaseReason(r, {
|
|
78
|
-
listedKeys: listed,
|
|
79
|
-
eligibleKeys,
|
|
80
|
-
now,
|
|
81
|
-
hasQueuedMerge: queuedRoles.has(r.role),
|
|
82
|
-
// Per instance, not per base role: a lowered `instances` leaves the surplus runner in
|
|
83
|
-
// place with `loopEnabled` false, and its idle claim must be released so a sibling can
|
|
84
|
-
// take the entry instead of the key staying held until the 24h stale sweep.
|
|
85
|
-
enabled: loopEnabled(r.config, r.role),
|
|
86
|
-
// Only this instance paused (not its whole role; instance 1 under `<role>-1`): a sibling
|
|
87
|
-
// can take the entry. With the base role paused no sibling runs, so the claim stays.
|
|
88
|
-
paused: loopPaused(pausedRolesSet, r.role) && !pausedRolesSet.has(base),
|
|
89
|
-
});
|
|
90
|
-
if (released === null)
|
|
91
|
-
continue;
|
|
92
|
-
const title = r.state.claim?.title ?? "";
|
|
93
|
-
r.state.claim = undefined;
|
|
94
|
-
// Persist the release: an idle extra instance gets no tick to save its state, so the dead
|
|
95
|
-
// claim would stay on disk for status/doctor to show and for a restart to release again.
|
|
96
|
-
saveLoopState(root, r.state);
|
|
97
|
-
const reason = released;
|
|
98
|
-
logEventBestEffort(root, { source: r.role, type: "claim", action: "released", title, reason });
|
|
99
|
-
if (reason === "stale") {
|
|
100
|
-
warnEventBestEffort(root, r.role, `released "${title}", which a stopped instance was working on (idle ${Math.round(CLAIM_IDLE_MAX_MS / 3_600_000)}h)`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
claimGroups.set(base, {
|
|
104
|
-
eligible,
|
|
105
|
-
eligibleKeys,
|
|
106
|
-
held: heldKeys(groupRunners, eligibleKeys, listed),
|
|
107
|
-
hold,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
// The team gates' block predicate (the director exempt — an explicit human prompt
|
|
111
|
-
// outranks any autonomous gate, quiet hours included): the once-mode settle below and
|
|
112
|
-
// the start gate must agree on exactly when a team gate holds, so the compound lives
|
|
113
|
-
// here once. Quiet hours fold in beside the operator pause and the budget gate — the
|
|
114
|
-
// schedule is not probe-worthy the way a paused budget is, so no probeDue exception.
|
|
115
|
-
// The per-role windows (quietHoursPerRole) fold in beside the team one as a fourth
|
|
116
|
-
// disjunct — a role held by either window (or both, held once) starts no new ticks;
|
|
117
|
-
// the director stays exempt by the same role check that covers the other disjuncts.
|
|
118
|
-
// The budget hold, per role (part 5c/8): a tier that resolved to pause holds its own roles,
|
|
119
|
-
// and — with review on — an unresolvable strong tier holds every role (nothing could merge).
|
|
120
|
-
// The probe of a demoted pair pierces the hold for EXACTLY the probed pair's tier's roles:
|
|
121
|
-
// the earlier draft's pair-blind pierce lifted the pause team-wide on any due probe.
|
|
122
|
-
// Quiet hours fold in beside the operator pause — the schedule is not probe-worthy the way a
|
|
123
|
-
// paused budget is.
|
|
124
|
-
const budgetHolds = (role) => (reviewStrongPaused || budgetPausedRoles.has(role)) && !probeRoles.has(role);
|
|
125
|
-
const operatorPauseBlocks = (role) => (userPaused || quietNow || roleQuietHeld.has(role) || budgetHolds(role)) &&
|
|
126
|
-
role !== DIRECTOR_ROLE;
|
|
127
|
-
// Fresh operator demand: a queued prompt or a wake newer than the loop's last tick end. The
|
|
128
|
-
// same override the need-based deferral honors (an explicit "try again now" is not idle
|
|
129
|
-
// maintenance); shared by the bootstrap and maintenance-allowance holds so the two cannot
|
|
130
|
-
// drift on what counts as demand.
|
|
131
|
-
const hasDemand = (runner) => inboxSize(root, runner.role) > 0 ||
|
|
132
|
-
(runner.state.wokenAt !== undefined && runner.state.wokenAt > (runner.state.lastRunEndedAt ?? 0));
|
|
133
|
-
// The new-project bootstrap hold (part 2/2): held unless the loop has a fresh operator wake
|
|
134
|
-
// or a queued prompt.
|
|
135
|
-
const bootstrapHeldNow = (runner) => bootstrapHeld.has(runner.role) && !hasDemand(runner);
|
|
136
|
-
// The maintenance allowance's team-level hold (part 1b/4): `used` has reached `allowance`,
|
|
137
|
-
// so no maintenance loop starts unless it carries demand (`tumwater wake <maintenance-role>`
|
|
138
|
-
// runs exactly one tick). The per-poll admission headroom is spent separately, after each
|
|
139
|
-
// loop's due check, where the pass knows what actually ticks; the verdict alone cannot.
|
|
140
|
-
const maintenanceQuotaOverLine = (runner) => commitTier(baseRoleOf(runner.role)) === "maintenance" &&
|
|
141
|
-
maintenanceQuota.used >= maintenanceQuota.allowance &&
|
|
142
|
-
!hasDemand(runner);
|
|
143
|
-
// The one-poll admission budget: each due maintenance loop this pass admits spends one of
|
|
144
|
-
// the remaining `allowance - used` slots, reset every pass, so two due maintenance loops
|
|
145
|
-
// cannot start in the same poll and overshoot the allowance.
|
|
146
|
-
let maintenanceQuotaAdmitted = 0;
|
|
147
|
-
// The stand-down's eligible lists per base role, read at most once per poll (see the check
|
|
148
|
-
// below).
|
|
149
|
-
const eligibleMemo = new Map();
|
|
150
|
-
for (const runner of runners) {
|
|
151
|
-
// Once mode: a paused role runs no tick this round and must be reported as skipped,
|
|
152
|
-
// so it settles here — before the gates that would otherwise skip it silently (a
|
|
153
|
-
// once round has to end even when a pause marker is left over; a later resume within
|
|
154
|
-
// the same round cannot un-settle it, which is the at-most-one-tick contract).
|
|
155
|
-
if (once.active &&
|
|
156
|
-
!once.isSettled(runner) &&
|
|
157
|
-
!runner.state.running &&
|
|
158
|
-
(loopPaused(pausedRolesSet, runner.role) ||
|
|
159
|
-
capPaused.has(runner.role) ||
|
|
160
|
-
maintenanceQuotaOverLine(runner) ||
|
|
161
|
-
bootstrapHeldNow(runner) ||
|
|
162
|
-
operatorPauseBlocks(runner.role))) {
|
|
163
|
-
once.settle(runner.role, "paused");
|
|
164
|
-
}
|
|
165
|
-
// Once mode's at-most-one-tick contract (src/scheduling/once-round.ts): a role that already ran
|
|
166
|
-
// its tick — or was settled with a skip reason — runs nothing further this round, even
|
|
167
|
-
// when its backoff has expired or the clock override would admit it again. Without this
|
|
168
|
-
// gate the one-tick guarantee held only for deferrable built-ins (their deferral
|
|
169
|
-
// settles them); a custom loop or work role with a short backoff re-qualified every
|
|
170
|
-
// poll and the round never ended.
|
|
171
|
-
if (once.active && !runner.state.running && once.isSettled(runner))
|
|
172
|
-
continue;
|
|
173
|
-
if (holdForRestart || diskHeld)
|
|
174
|
-
continue; // a restart or a low disk is pending: nothing new starts, on any loop
|
|
175
|
-
// The per-role pause gates BEFORE the team check and exempts nothing — the director
|
|
176
|
-
// included (the operator named that one loop deliberately).
|
|
177
|
-
if (loopPaused(pausedRolesSet, runner.role))
|
|
26
|
+
const scope = await buildSchedulingScope(ctx);
|
|
27
|
+
const { root, writing, reasons } = scope;
|
|
28
|
+
for (const runner of scope.ordered) {
|
|
29
|
+
// The WHY-due policy: every gate that decides this runner is due or skipped, and the
|
|
30
|
+
// once/deferral bookkeeping that travels with them, lives in scheduling-skip.ts's
|
|
31
|
+
// skipRunner. What follows is only the claim-admission tail (part 3/3, scheduling-admit.ts).
|
|
32
|
+
const verdict = await skipRunner(scope, runner);
|
|
33
|
+
if (verdict.skip)
|
|
178
34
|
continue;
|
|
179
|
-
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
if (
|
|
185
|
-
continue;
|
|
186
|
-
// The maintenance allowance (plans/work-ratio.md, part 1b/4): no new maintenance tick while
|
|
187
|
-
// the rolling 24 h window is over its line. A fresh operator wake or a queued prompt admits
|
|
188
|
-
// one anyway (maintenanceQuotaOverLine), the same demand override the bootstrap hold honors.
|
|
189
|
-
if (maintenanceQuotaOverLine(runner))
|
|
190
|
-
continue;
|
|
191
|
-
// The new-project bootstrap (part 2/2): no new maintenance ticks until the plan target is
|
|
192
|
-
// met; a fresh wake or queued prompt admits one anyway (bootstrapHeldNow).
|
|
193
|
-
if (bootstrapHeldNow(runner))
|
|
194
|
-
continue;
|
|
195
|
-
if (operatorPauseBlocks(runner.role))
|
|
196
|
-
continue; // no new role ticks while either gate holds
|
|
197
|
-
// The director honors only the subscription-window hold: the per-run 429 retry that backs
|
|
198
|
-
// its other exemptions does not answer a window rejection, so its prompt would fail and
|
|
199
|
-
// re-queue every poll until the repeated-failure pause stops it.
|
|
200
|
-
if (runner.role === DIRECTOR_ROLE && windowHeldProviders.has(roleProviders.get(runner.role)))
|
|
201
|
-
continue;
|
|
202
|
-
if (runner.role !== DIRECTOR_ROLE &&
|
|
203
|
-
(reviewHeld || heldProviders.has(roleProviders.get(runner.role))))
|
|
204
|
-
continue; // nor while ITS provider's failure storm holds — or the reviewer's (nothing could merge)
|
|
205
|
-
// The loop's OWN queue decides inbox due-ness: the director counts its historical
|
|
206
|
-
// inbox, every other loop its per-role queue (tumwater prompt --role <id>) — so a
|
|
207
|
-
// queued prompt makes its loop due by itself, no wake marker needed (isEligible).
|
|
208
|
-
const { run, reason } = isEligible(runner, now, mainHead, inboxSize(root, runner.role), {
|
|
209
|
-
once: once.active,
|
|
210
|
-
bootstrapActive,
|
|
211
|
-
descriptionIncomplete,
|
|
212
|
-
});
|
|
213
|
-
if (!run) {
|
|
214
|
-
// Not due this poll: any deferral episode has ended (or never started). No event —
|
|
215
|
-
// the tick's own events cover it.
|
|
216
|
-
if (deferredDue.get(runner.role))
|
|
217
|
-
deferredDue.set(runner.role, false);
|
|
218
|
-
// Once mode: idle and not due — settleSkipped records the skip reason (the caller's
|
|
219
|
-
// summary reports it).
|
|
220
|
-
if (once.active && !runner.state.running)
|
|
221
|
-
once.settleSkipped(runner);
|
|
35
|
+
const reason = verdict.reason;
|
|
36
|
+
// Post-gate admission (part 3/3, scheduling-admit.ts): the stand-down turn-down and the
|
|
37
|
+
// parallel-work-instance claim assignment, after every skip gate has admitted this runner.
|
|
38
|
+
// Returns "skipped" having recorded its skip reason and settled its once answer; "admitted"
|
|
39
|
+
// runs the binder: the final noteSkip, the reasons entry and the writing increment.
|
|
40
|
+
if (admitRunner(scope, runner, reason) === "admitted") {
|
|
222
41
|
noteSkip(root, runner, undefined);
|
|
223
|
-
|
|
42
|
+
reasons.set(runner, reason);
|
|
43
|
+
if (scope.claimGroups.has(baseRoleOf(runner.role)) && !runner.state.running)
|
|
44
|
+
increment(writing, baseRoleOf(runner.role));
|
|
224
45
|
}
|
|
225
|
-
// Merge queue 3/5 interlock (invariant 3): a role with a QUEUED or IN-FLIGHT merge never
|
|
226
|
-
// starts a tick — the entry stays in the queue until its merge completes, so one check
|
|
227
|
-
// covers both. Uniform over every role, director included: its prompt is not finished until it
|
|
228
|
-
// merges. Placed before the need-based deferral block on purpose — no episode bookkeeping is
|
|
229
|
-
// needed (the tick that queued the merge ran, which closed any deferral episode; its `queued`
|
|
230
|
-
// can leave a prior no_change in lastResult — loop-state.ts records only completed results
|
|
231
|
-
// — but the merge's outcome replaces it before the interlock lets the role back into this
|
|
232
|
-
// pass), and skipping here saves that branch's workMergedSince git-range query for the skipped
|
|
233
|
-
// role.
|
|
234
|
-
if (queuedRoles.has(runner.role)) {
|
|
235
|
-
noteSkip(root, runner, undefined);
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
// Need-based deferral: a due maintenance tick (scheduled or main-moved wake) whose last
|
|
239
|
-
// tick did nothing stays deferred while the feature/bugfix backlog is open or no new
|
|
240
|
-
// work has merged to react to — nextRunAt is left untouched, so it re-checks every poll
|
|
241
|
-
// until the backlog drains and qualifying work merges. Resume wakes precede this check in
|
|
242
|
-
// isEligible, the director's "inbox" reason skips it, work roles are not in
|
|
243
|
-
// DEFERRABLE_ROLES (bugfix is the exception while its backlog is empty — deferTick), and
|
|
244
|
-
// a fresh operator wake overrides it (deferTick's woken check — an explicit "try again
|
|
245
|
-
// now" is a demand, not idle maintenance). Sitting before reasons.set also keeps a
|
|
246
|
-
// deferred role out of the wake-event pass below.
|
|
247
|
-
if (reason === "scheduled" || reason === "main moved") {
|
|
248
|
-
const s = runner.state;
|
|
249
|
-
// The git range is only consulted when the other conditions already hold — a
|
|
250
|
-
// never-ticked role or a tick with pending business runs without paying for it, and an
|
|
251
|
-
// open backlog defers regardless of what merged. bugfix in search mode is the
|
|
252
|
-
// exception: its deferral keys on the work-merged verdict alone, so the backlog-open
|
|
253
|
-
// shortcut must not stand in for the query.
|
|
254
|
-
const searchBugfix = baseRoleOf(runner.role) === BUGFIX_ROLE && !openBugsNow;
|
|
255
|
-
const merged = (!workBacklogOpen || searchBugfix) && s.lastMainHead !== ""
|
|
256
|
-
? await workMergedSince.since(s.lastMainHead, mainHead)
|
|
257
|
-
: true;
|
|
258
|
-
const deferral = deferTickReason(s, runner.role, merged, workBacklogOpen, openBugsNow, now, planBacklogFull);
|
|
259
|
-
const deferredNow = deferral !== null;
|
|
260
|
-
if (deferredNow !== (deferredDue.get(runner.role) ?? false)) {
|
|
261
|
-
if (deferral)
|
|
262
|
-
logEventBestEffort(root, { source: runner.role, type: "run_deferred", reason: deferral });
|
|
263
|
-
deferredDue.set(runner.role, deferredNow);
|
|
264
|
-
}
|
|
265
|
-
if (deferredNow) {
|
|
266
|
-
// Once mode: a deferred maintenance tick decided not to run — its once-round
|
|
267
|
-
// answer, so it settles instead of holding the round open for DEFER_MAX_MS.
|
|
268
|
-
if (once.active)
|
|
269
|
-
once.settle(runner.role, "deferred");
|
|
270
|
-
noteSkip(root, runner, deferral === "backlog"
|
|
271
|
-
? "deferred-backlog"
|
|
272
|
-
: deferral === "plans-full"
|
|
273
|
-
? "deferred-plans-full"
|
|
274
|
-
: "deferred-no-work");
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
else if (deferredDue.get(runner.role)) {
|
|
279
|
-
deferredDue.set(runner.role, false); // an inbox/resume run ends the episode
|
|
280
|
-
}
|
|
281
|
-
// The allowance's in-flight admission (plans/work-ratio.md, part 1b/4): `used` counts the
|
|
282
|
-
// maintenance loops already in flight, so a due maintenance tick admitted here spends one
|
|
283
|
-
// of the remaining `allowance - used` slots. This must sit after the due check — the pure
|
|
284
|
-
// verdict cannot know which loops will be eligible this poll, so the reservation lives in
|
|
285
|
-
// the pass that does; without it two due maintenance loops start in the same poll. Demand
|
|
286
|
-
// spends no slot: it is an explicit operator request, not idle maintenance.
|
|
287
|
-
if (commitTier(baseRoleOf(runner.role)) === "maintenance" && !hasDemand(runner)) {
|
|
288
|
-
if (maintenanceQuotaAdmitted >= maintenanceQuota.allowance - maintenanceQuota.used) {
|
|
289
|
-
noteSkip(root, runner, undefined);
|
|
290
|
-
continue;
|
|
291
|
-
}
|
|
292
|
-
maintenanceQuotaAdmitted++;
|
|
293
|
-
}
|
|
294
|
-
// Parallel work instances (part 4/7): a multi-runner base role's claim assignment, after
|
|
295
|
-
// every gate has admitted this runner. A runner that already holds a claim proceeds — its
|
|
296
|
-
// revision, resume or retry is the same entry. An unclaimed runner with outstanding work
|
|
297
|
-
// (a revision) is the mid-entry runner the group hold waits on: it proceeds unassigned —
|
|
298
|
-
// holding it too would deadlock the whole group, since only its tick can settle the hold.
|
|
299
|
-
// Otherwise, when the tick is not an inbox/resume run, it takes the first free entry; an
|
|
300
|
-
// extra instance with none free is skipped without touching its backoff, and the primary
|
|
301
|
-
// runs unassigned only while some eligible entry exists (all of them held by siblings).
|
|
302
|
-
// Stand-down: the feature charter says "If PLANS.md is empty or everything is done, there is
|
|
303
|
-
// nothing to do" and the bugfix charter says "when every open entry carries [a Refused note],
|
|
304
|
-
// there is nothing to do", so a feature runner (primary included) with no eligible plan at all,
|
|
305
|
-
// or a bugfix runner whose open bugs are all held (no open bugs at all is search mode, which
|
|
306
|
-
// still runs), skips its scheduled and main-moved ticks instead of paying a model run that
|
|
307
|
-
// can only end in
|
|
308
|
-
// nothing-to-do. Startup, inbox and resume ticks, a fresh operator wake, and a runner with a
|
|
309
|
-
// claim or outstanding work still run. nextRunAt and backoff stay put, so the first poll
|
|
310
|
-
// after a plan turns eligible ticks it.
|
|
311
|
-
const standDownBase = baseRoleOf(runner.role);
|
|
312
|
-
if ((standDownBase === "feature" || (standDownBase === BUGFIX_ROLE && openBugsNow)) &&
|
|
313
|
-
(reason === "scheduled" || reason === "main moved") &&
|
|
314
|
-
!(runner.state.wokenAt !== undefined && runner.state.wokenAt > (runner.state.lastRunEndedAt ?? 0)) &&
|
|
315
|
-
runner.state.claim === undefined &&
|
|
316
|
-
!hasOutstandingWork(runner.state)) {
|
|
317
|
-
let eligible = eligibleMemo.get(standDownBase);
|
|
318
|
-
if (eligible === undefined) {
|
|
319
|
-
eligible = claimGroups.get(standDownBase)?.eligible ?? eligibleEntries(root, standDownBase);
|
|
320
|
-
eligibleMemo.set(standDownBase, eligible);
|
|
321
|
-
}
|
|
322
|
-
if (eligible.length === 0) {
|
|
323
|
-
noteSkip(root, runner, "no-free-entry");
|
|
324
|
-
runner.state.heldElsewhere = undefined;
|
|
325
|
-
if (once.active)
|
|
326
|
-
once.settle(runner.role, "no free entry");
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
const claimGroup = claimGroups.get(baseRoleOf(runner.role));
|
|
331
|
-
if (claimGroup) {
|
|
332
|
-
if (runner.state.claim) {
|
|
333
|
-
// keeps its claim
|
|
334
|
-
}
|
|
335
|
-
else if (hasOutstandingWork(runner.state)) {
|
|
336
|
-
// finishes its own unrecorded entry
|
|
337
|
-
}
|
|
338
|
-
else if (reason !== "inbox" && reason !== "resume") {
|
|
339
|
-
// The group holds an unclaimed mid-entry sibling: assign nothing this poll, or a
|
|
340
|
-
// sibling could be handed that same entry (the reported `feature-2`/`bugfix-2`
|
|
341
|
-
// duplication). It resolves the moment the sibling settles.
|
|
342
|
-
if (claimGroup.hold) {
|
|
343
|
-
noteSkip(root, runner, "claim-hold");
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
const free = claimGroup.eligible.filter((e) => !claimGroup.held.has(e.key));
|
|
347
|
-
const pick = assignNext(free);
|
|
348
|
-
// An unassigned runner is told which entries its siblings hold (cleared when none).
|
|
349
|
-
const heldTitles = claimGroup.eligible.filter((e) => claimGroup.held.has(e.key)).map((e) => e.title);
|
|
350
|
-
runner.state.heldElsewhere = !pick && heldTitles.length > 0 ? heldTitles : undefined;
|
|
351
|
-
if (pick) {
|
|
352
|
-
runner.state.claim = {
|
|
353
|
-
file: baseRoleOf(runner.role) === BUGFIX_ROLE ? "BUGS.md" : "PLANS.md",
|
|
354
|
-
key: pick.key,
|
|
355
|
-
title: pick.title,
|
|
356
|
-
at: now,
|
|
357
|
-
source: "assigned",
|
|
358
|
-
};
|
|
359
|
-
claimGroup.held.add(pick.key);
|
|
360
|
-
logEventBestEffort(root, {
|
|
361
|
-
source: runner.role,
|
|
362
|
-
type: "claim",
|
|
363
|
-
action: "assigned",
|
|
364
|
-
key: pick.key,
|
|
365
|
-
title: pick.title,
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
else if (instanceIndex(runner.role) >= 2) {
|
|
369
|
-
noteSkip(root, runner, "no-free-entry");
|
|
370
|
-
if (once.active)
|
|
371
|
-
once.settle(runner.role, "no free entry");
|
|
372
|
-
continue; // an idle extra instance: no tick, no backoff climb
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
runner.state.heldElsewhere = undefined; // no claim group (e.g. instances lowered to 1)
|
|
378
|
-
}
|
|
379
|
-
noteSkip(root, runner, undefined);
|
|
380
|
-
reasons.set(runner, reason);
|
|
381
46
|
}
|
|
382
47
|
return reasons;
|
|
383
48
|
}
|
|
@@ -387,3 +52,37 @@ export async function pollRunnerReasons(ctx) {
|
|
|
387
52
|
export function reviewHoldKey(roleConfig) {
|
|
388
53
|
return seamHoldKey(reviewRunConfig(roleConfig));
|
|
389
54
|
}
|
|
55
|
+
/** Backlog-aware deferral: while PLANS.md `## Planned` or BUGS.md `## Open` on main
|
|
56
|
+
* holds an entry feature or bugfix may take (eligibleEntries), idle maintenance runs
|
|
57
|
+
* stay deferred — queued feature/bugfix work outranks them regardless of what merged.
|
|
58
|
+
* Stat-cached reads (backlog.ts): one stat per file per poll while the files are unchanged.
|
|
59
|
+
* bugfix is not a maintenance role, but while BUGS.md `## Open` is empty it defers like
|
|
60
|
+
* one (deferRun) — openBugsNow is that emptiness, read once here and reused for both
|
|
61
|
+
* predicates. Also the demoted fallback's half-open window (part 5c/8): the pair whose probe
|
|
62
|
+
* is due, and the roles whose model tier WOULD run on that pair absent the demotion (the
|
|
63
|
+
* price-based resolution — the serving resolution has already dropped the pair, which is
|
|
64
|
+
* exactly why it is being probed). Only those roles pierce the budget hold, and the launch
|
|
65
|
+
* pass admits exactly one of them as the probe — a probe blind to the pair's tier would lift
|
|
66
|
+
* the pause for every role, and a probe with no eligible runner must not hijack the poll's
|
|
67
|
+
* other launches. The review-on strong pause (budgetGate): with the review gate on, an
|
|
68
|
+
* unresolvable strong tier means nothing could merge, so EVERY role is held — the per-tier
|
|
69
|
+
* pause set alone would let small/default roles author changes no reviewer could judge. The
|
|
70
|
+
* probe of the strong pair pierces this hold for exactly the probed tier's roles too. */
|
|
71
|
+
export function pollDeferralInputs(root, runners, liveConfig, gateStates, gates, breakerNowMs) {
|
|
72
|
+
const openBugsNow = openBugs(root).length > 0;
|
|
73
|
+
const workBacklogOpen = eligibleEntries(root, "feature").length > 0 || eligibleEntries(root, "bugfix").length > 0;
|
|
74
|
+
const plansFull = planBacklogFull(root, liveConfig);
|
|
75
|
+
const probePair = fallbackProbeDuePair(gateStates.budget.breakers, breakerNowMs);
|
|
76
|
+
const probeRoles = new Set();
|
|
77
|
+
if (probePair !== null) {
|
|
78
|
+
for (const r of runners) {
|
|
79
|
+
if (r.role === DIRECTOR_ROLE)
|
|
80
|
+
continue;
|
|
81
|
+
const p = gates.priceResolved[roleSeamTier(liveConfig, r.role)].pair;
|
|
82
|
+
if (p !== null && modelPairName(p) === probePair)
|
|
83
|
+
probeRoles.add(r.role);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const reviewStrongPaused = gates.budgetActive && liveConfig.review.enabled && gates.servingResolved.strong.pair === null;
|
|
87
|
+
return { openBugsNow, workBacklogOpen, plansFull, probePair, probeRoles, reviewStrongPaused };
|
|
88
|
+
}
|