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
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
/** The status/capacity and role-phase team alerts, in emission order: a spent budget
|
|
2
|
-
* (chain/fallback/budget),
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
import {
|
|
6
|
-
import { openRoleActions, seeFailures, listRoles, listRoleIds, stuckDetail, localizeInstants, } from "./team-alert-shared.js";
|
|
2
|
+
* (chain/fallback/budget), disk, install, and main-red holds, roles failing run after run or
|
|
3
|
+
* stuck. One of the per-domain builders split out of src/ui/team-alerts.ts; the shared types
|
|
4
|
+
* and wording helpers live in team-alert-shared.ts. */
|
|
5
|
+
import { openRoleActions, seeFailures, listRoles, listRoleIds, stuckDetail, } from "./team-alert-shared.js";
|
|
7
6
|
import { chainFragments } from "./badges.js";
|
|
8
7
|
import { fallbackTierEntries } from "../budget/budget.js";
|
|
9
8
|
import { fallbackDisplayPair } from "../config/config-views.js";
|
|
10
9
|
import { agree, plural } from "../text/phrases.js";
|
|
11
|
-
import { BRIEF_TEXT_MAX_CHARS } from "../brief/brief.js";
|
|
12
10
|
import { formatGB, shortSha, usd, usdCap } from "../text/format.js";
|
|
13
11
|
import { joinWithMore, squash } from "../text/text.js";
|
|
14
12
|
import { roleLabel } from "../roles/role-labels.js";
|
|
15
13
|
import { STALL_RE } from "./run-progress-model.js";
|
|
16
|
-
import { ROLE_NOTES_MAX_BYTES } from "../agent/role-notes-write.js";
|
|
17
|
-
import { PRINCIPLES_MAX_CHARS } from "../prompt/principles.js";
|
|
18
14
|
function pushChainAlert(out, snap) {
|
|
19
15
|
const onFallback = chainFragments(snap.chain);
|
|
20
16
|
if (onFallback) {
|
|
@@ -60,23 +56,6 @@ function pushSpentBudgetAlert(out, snap) {
|
|
|
60
56
|
});
|
|
61
57
|
}
|
|
62
58
|
}
|
|
63
|
-
// A role over its maxDailyCostUsdPerRole cap (status-model.ts "waiting: daily cap spent") is
|
|
64
|
-
// a per-role hold — one role sits idle while the rest of the team keeps working — so the team
|
|
65
|
-
// banner names it the way the budget, disk, and quiet banners name theirs, instead of leaving
|
|
66
|
-
// an operator to read every row. An amber ask (it needs a raise to resume); clears on its own
|
|
67
|
-
// at local midnight when the spend window rolls over.
|
|
68
|
-
function pushRoleCapAlert(out, snap, roles) {
|
|
69
|
-
const capPausedRoles = roles.filter((l) => snap.capPaused.includes(l.role));
|
|
70
|
-
if (snap.capPaused.length > 0) {
|
|
71
|
-
out.push({
|
|
72
|
-
key: "role-cap",
|
|
73
|
-
tone: "amber",
|
|
74
|
-
title: `${listRoleIds(snap.capPaused)} ${agree(snap.capPaused.length, "is over its daily cap", "are over their daily caps")}`,
|
|
75
|
-
detail: `The daily cap (maxDailyCostUsdPerRole) holds ${listRoleIds(snap.capPaused)}: no new runs until local midnight. Raise the cap to resume ${agree(snap.capPaused.length, "it", "them")}.`,
|
|
76
|
-
actions: openRoleActions(capPausedRoles),
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
59
|
// The disk floor holding new work (plans/disk-floor.md, part 4/4): the header badge and the
|
|
81
60
|
// role rows say what is happening, but the banner names the threshold and the way out, so an
|
|
82
61
|
// operator hitting a full volume does not have to read events. An amber ask — the team is
|
|
@@ -109,86 +88,6 @@ function pushInstallDriftAlert(out, snap) {
|
|
|
109
88
|
});
|
|
110
89
|
}
|
|
111
90
|
}
|
|
112
|
-
// No deterministic build check is declared (detectBuildCheck returned null): the review
|
|
113
|
-
// gate's build pre-check, the red-main baseline and redeploy's green check have nothing to
|
|
114
|
-
// run, and the header badge renders nothing (mainCheckBadge needs a build_check event).
|
|
115
|
-
// The same state the doctor `check` row warns on, raised as a standing alert on the always-on
|
|
116
|
-
// surfaces. Independent of team state (running or stopped), like the install alert beside it.
|
|
117
|
-
function pushNoCheckAlert(out, snap) {
|
|
118
|
-
if (snap.noDeclaredCheck) {
|
|
119
|
-
out.push({
|
|
120
|
-
key: "no-check",
|
|
121
|
-
tone: "amber",
|
|
122
|
-
title: "No project check is declared — the safety gates are off",
|
|
123
|
-
detail: "The review gate's build pre-check, the red-main baseline and redeploy's green check have nothing to run. The plan role will plan this project's check and the feature role declare it — a test/typecheck/build npm script, or check.command. To skip the wait, declare it yourself; a project with nothing to verify yet stays as it is.",
|
|
124
|
-
actions: [],
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
// A brief candidate exists but none resolves to a usable prompt block (briefBlockMissing):
|
|
129
|
-
// every run reaches its model with no project prompt, so the team burns tokens producing
|
|
130
|
-
// work unrelated to the project. The same marker-less/unreadable/empty-block state the doctor
|
|
131
|
-
// `brief` row warns on, raised as a standing alert on the always-on surfaces (the bare
|
|
132
|
-
// pre-init repo stays silent). Independent of team state, like the no-check alert beside it.
|
|
133
|
-
function pushNoBriefAlert(out, snap) {
|
|
134
|
-
if (snap.noBrief) {
|
|
135
|
-
out.push({
|
|
136
|
-
key: "no-brief",
|
|
137
|
-
tone: "amber",
|
|
138
|
-
title: "The project brief's prompt block is missing or empty — every role runs without its project prompt",
|
|
139
|
-
detail: "A brief file exists but carries no usable tumwater:prompt block (no markers, or no text between them), so each run's prompt reaches the model with no project brief. Restore the block in the file the doctor `brief` row names, or re-run `tumwater init`.",
|
|
140
|
-
actions: [],
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
// A brief block past the read cap (briefBlockOverCap): every run's prompt silently carries
|
|
145
|
-
// only the head plus a truncation marker, so each role plans and codes against a cut brief.
|
|
146
|
-
// The same over-cap state the doctor `brief` row warns on, raised as a standing alert on the
|
|
147
|
-
// always-on surfaces; mutually exclusive with `no-brief` (an over-cap block is non-empty).
|
|
148
|
-
function pushBriefTruncatedAlert(out, snap) {
|
|
149
|
-
if (snap.briefTruncated) {
|
|
150
|
-
out.push({
|
|
151
|
-
key: "brief-truncated",
|
|
152
|
-
tone: "amber",
|
|
153
|
-
title: "The project brief is over its prompt cap — every role runs on a truncated brief",
|
|
154
|
-
detail: "The brief block exceeds the read cap, so each run's prompt carries only its head plus a truncation marker. Shorten the block to at most " + BRIEF_TEXT_MAX_CHARS + " characters in the file the doctor `brief` row names (a hand edit, or the dashboard's Brief card), then save it.",
|
|
155
|
-
actions: [],
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
// A role note past the read bound (collectRoleNotesOverCap's predicate, the doctor `role
|
|
160
|
-
// notes` row's own): `readRoleNote` truncates that role's note on read, so the role's next
|
|
161
|
-
// prompt carries only the note's head plus a truncation marker while the operator sees the
|
|
162
|
-
// file whole — a silently degraded next run on a green-looking status. The standing twin of
|
|
163
|
-
// the doctor `role notes` row's warn, raised as an amber alert on the always-on surfaces.
|
|
164
|
-
// Independent of team state, like the no-brief alert beside it.
|
|
165
|
-
function pushRoleNotesTruncatedAlert(out, snap) {
|
|
166
|
-
const notesOver = snap.roleNotesOverCap ?? [];
|
|
167
|
-
if (notesOver.length > 0) {
|
|
168
|
-
out.push({
|
|
169
|
-
key: "role-notes-truncated",
|
|
170
|
-
tone: "amber",
|
|
171
|
-
title: `${plural(notesOver.length, "role note is", "role notes are")} over the read bound — those roles run on truncated notes`,
|
|
172
|
-
detail: `${joinWithMore(notesOver.map((o) => `${o.role} (${o.chars})`), 3)} exceed the ${ROLE_NOTES_MAX_BYTES}-character read bound, so each of those roles' prompt carries only the note's head plus a truncation marker. Rewrite them under the cap (the run's \`tumwater-notes write\` shim, \`tumwater role <id> --notes-file <path>\`, the dashboard drawer, or the TUI notes editor).`,
|
|
173
|
-
actions: [],
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// PRINCIPLES.md is over its prompt cap (principlesBlockOverCap): every role's <principles>
|
|
178
|
-
// block carries only its head plus a truncation marker, so the team runs on truncated design
|
|
179
|
-
// guidance. The same over-cap state the doctor `principles` row warns on, raised as a standing
|
|
180
|
-
// alert on the always-on surfaces. Independent of team state, like the no-brief alert beside it.
|
|
181
|
-
function pushPrinciplesTruncatedAlert(out, snap) {
|
|
182
|
-
if (snap.principlesTruncated) {
|
|
183
|
-
out.push({
|
|
184
|
-
key: "principles-truncated",
|
|
185
|
-
tone: "amber",
|
|
186
|
-
title: "PRINCIPLES.md is over its prompt cap — every role runs on truncated design guidance",
|
|
187
|
-
detail: "The file exceeds the " + PRINCIPLES_MAX_CHARS + "-character prompt cap, so every run's <principles> block carries only its head plus a truncation marker and only the leading bullets survive. Keep PRINCIPLES.md to its list bullets and move long-form rationale into docs/, which is not injected.",
|
|
188
|
-
actions: [],
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
91
|
function pushMainRedAlert(out, snap, roles) {
|
|
193
92
|
const red = roles.filter((l) => l.phase === "main's check is failing");
|
|
194
93
|
if (red.length) {
|
|
@@ -246,81 +145,21 @@ function pushStuckRoleAlert(out, roles) {
|
|
|
246
145
|
});
|
|
247
146
|
}
|
|
248
147
|
}
|
|
249
|
-
function pushBuildAlert(out, snap) {
|
|
250
|
-
if (snap.build?.stale) {
|
|
251
|
-
const behind = plural(snap.build.aheadCommits ?? 0, "commit");
|
|
252
|
-
// The cooldown scopes the deferral to the redeploy, not the team (PLANS.md 2026-10-23): a
|
|
253
|
-
// restart held by the cooldown leaves the roles running on the stale build, so its alert
|
|
254
|
-
// reads blue "work continues" with the deadline, while a genuinely blocked restart (red main,
|
|
255
|
-
// a failed build, a boot refusal) stays the amber "restart blocked", and the momentary drain
|
|
256
|
-
// of an in-flight restart — the one stale state that actually holds new runs and merges —
|
|
257
|
-
// keeps the plain wording.
|
|
258
|
-
const deferred = snap.build.restartBlocked?.startsWith("cooldown until ");
|
|
259
|
-
out.push(deferred
|
|
260
|
-
? {
|
|
261
|
-
key: "build",
|
|
262
|
-
tone: "blue",
|
|
263
|
-
title: `Update available (${behind} behind) — restarts after cooldown; work continues`,
|
|
264
|
-
detail: `Work continues on the running build; the restart lands when the cooldown lapses or you force it (${localizeInstants(snap.build.restartBlocked ?? "")}).`,
|
|
265
|
-
actions: [],
|
|
266
|
-
}
|
|
267
|
-
: snap.build.restartBlocked
|
|
268
|
-
? {
|
|
269
|
-
key: "build",
|
|
270
|
-
tone: "amber",
|
|
271
|
-
title: `Update available (${behind} behind) — restart blocked`,
|
|
272
|
-
detail: localizeInstants(snap.build.restartBlocked),
|
|
273
|
-
actions: [],
|
|
274
|
-
}
|
|
275
|
-
: {
|
|
276
|
-
key: "build",
|
|
277
|
-
tone: "blue",
|
|
278
|
-
title: `Update available (${behind} behind)${snap.build.restartPending ? " — restarts when idle" : ""}`,
|
|
279
|
-
detail: snap.build.restartPending ? "The team restarts onto it once running work finishes." : "Restart tumwater start to pick it up.",
|
|
280
|
-
actions: [],
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
// A work-instance claim the operator should act on without running `tumwater doctor`
|
|
285
|
-
// (src/scheduling/claim-entries.ts, the doctor's `parallel instances` row): a stopped
|
|
286
|
-
// instance holding an entry that left its role's section, or idle past the scheduler's
|
|
287
|
-
// release window, is an amber ask. One detail line per claim, worded like the doctor row so
|
|
288
|
-
// the two cannot drift; actions open the first three claimed roles. Nothing when empty.
|
|
289
|
-
function pushStaleClaimsAlert(out, staleClaims) {
|
|
290
|
-
if (staleClaims.length > 0) {
|
|
291
|
-
out.push({
|
|
292
|
-
key: "stale-claims",
|
|
293
|
-
tone: "amber",
|
|
294
|
-
title: `${plural(staleClaims.length, "work-instance claim is", "work-instance claims are")} stale`,
|
|
295
|
-
detail: staleClaims.map(staleClaimDetail).join("; "),
|
|
296
|
-
actions: openRoleActions(staleClaims.map((c) => ({ role: c.id }))),
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
148
|
/** The status/capacity and role-phase alerts, in emission order: a spent budget
|
|
301
|
-
* (chain/fallback/budget),
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
* alerts at the position this run used to occupy, so the emitted key order is
|
|
149
|
+
* (chain/fallback/budget), disk, install, and main-red holds, roles failing run after run or
|
|
150
|
+
* stuck. Extracted from teamAlerts (part 1/5 of the teamAlerts split) so the shared module
|
|
151
|
+
* stays small; the input names exactly the fields this run reads. The caller pushes the
|
|
152
|
+
* returned alerts at the position this run used to occupy, so the emitted key order is
|
|
306
153
|
* unchanged. */
|
|
307
154
|
export function buildStatusAlerts(input) {
|
|
308
|
-
const { snap, roles
|
|
155
|
+
const { snap, roles } = input;
|
|
309
156
|
const out = [];
|
|
310
157
|
pushChainAlert(out, snap);
|
|
311
158
|
pushSpentBudgetAlert(out, snap);
|
|
312
|
-
pushRoleCapAlert(out, snap, roles);
|
|
313
159
|
pushDiskAlert(out, snap);
|
|
314
160
|
pushInstallDriftAlert(out, snap);
|
|
315
|
-
pushNoCheckAlert(out, snap);
|
|
316
|
-
pushNoBriefAlert(out, snap);
|
|
317
|
-
pushBriefTruncatedAlert(out, snap);
|
|
318
|
-
pushRoleNotesTruncatedAlert(out, snap);
|
|
319
|
-
pushPrinciplesTruncatedAlert(out, snap);
|
|
320
161
|
pushMainRedAlert(out, snap, roles);
|
|
321
162
|
pushFailingStreakAlert(out, roles);
|
|
322
163
|
pushStuckRoleAlert(out, roles);
|
|
323
|
-
pushBuildAlert(out, snap);
|
|
324
|
-
pushStaleClaimsAlert(out, staleClaims);
|
|
325
164
|
return out;
|
|
326
165
|
}
|
|
@@ -1,173 +1,6 @@
|
|
|
1
|
-
/** The tail team alerts
|
|
2
|
-
*
|
|
3
|
-
|
|
4
|
-
* regressed, a stopped tumwater, and roles that cannot or may not read images. One of the
|
|
5
|
-
* per-domain builders split out of src/ui/team-alerts.ts; the shared types and wording helpers
|
|
6
|
-
* live in team-alert-shared.ts. */
|
|
7
|
-
import { listRoleIds, listRoles, openRoleActions, seeFailures } from "./team-alert-shared.js";
|
|
8
|
-
import { stalePinPhrase } from "../doctor/doctor-contention-checks.js";
|
|
9
|
-
import { quietWindowEnd } from "../scheduling/quiet-hours.js";
|
|
10
|
-
import { pauseCountdown } from "./badges.js";
|
|
11
|
-
import { agree, pauseReasonSuffix, plural } from "../text/phrases.js";
|
|
12
|
-
import { humanUntil } from "../text/datetime.js";
|
|
13
|
-
import { squash } from "../text/text.js";
|
|
14
|
-
function pushQuietAlert(out, snap) {
|
|
15
|
-
// Quiet hours (plans: "Quiet hours … part 2/2, observability") informs rather than asks:
|
|
16
|
-
// the team holding to a schedule is not a problem, but an operator watching idle roles
|
|
17
|
-
// inside the window needs the same explanation the pause alert gives a paused team —
|
|
18
|
-
// why nothing runs, and when it starts again. The window end comes from quiet-hours.ts's
|
|
19
|
-
// quietWindowEnd, the same helper quietBadge uses for the header.
|
|
20
|
-
if (snap.inQuietHours && snap.quietHours) {
|
|
21
|
-
const end = quietWindowEnd(snap.quietHours);
|
|
22
|
-
out.push({
|
|
23
|
-
key: "quiet",
|
|
24
|
-
tone: "blue",
|
|
25
|
-
title: `Quiet hours — roles start no new runs until ${end}`,
|
|
26
|
-
detail: `The schedule (${snap.quietHours} local time, quietHours in tumwater.json) holds them. Runs in progress finish, and the director still runs your prompts.`,
|
|
27
|
-
actions: [],
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function pushMaintenanceAlert(out, snap) {
|
|
32
|
-
// The maintenance allowance (plans/work-ratio.md, part 1b/4): by design and self-clearing —
|
|
33
|
-
// the scheduler holds maintenance roles at the rolling 24 h allowance until merges free room,
|
|
34
|
-
// then they run again on their own — so it informs (gray, no actions), the quiet alert's
|
|
35
|
-
// tone, rather than amber like the failed/refused asks. An operator watching idle maintenance
|
|
36
|
-
// roles gets the same explanation the hold's own row carries, team-wide.
|
|
37
|
-
if (snap.maintenanceQuota.held.length > 0) {
|
|
38
|
-
const mq = snap.maintenanceQuota;
|
|
39
|
-
out.push({
|
|
40
|
-
key: "maintenance",
|
|
41
|
-
tone: "gray",
|
|
42
|
-
title: "Maintenance is held at its rolling allowance",
|
|
43
|
-
detail: `${mq.used} of ${mq.allowance} maintenance allowance used over the last 24 h; ${listRoleIds(mq.held)} ${agree(mq.held.length, "resumes", "resume")} as work merges.`,
|
|
44
|
-
actions: [],
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function pushProviderHoldAlert(out, snap, now) {
|
|
49
|
-
// A standing provider failure hold (src/team/team-hold.ts `standingHolds`, published through
|
|
50
|
-
// the orchestrator info file): a provider storm holds roles on that provider, so an operator
|
|
51
|
-
// watching idle roles with no log open sees why nothing new starts there. Self-clearing by
|
|
52
|
-
// design — the hold re-opens at its own deadline — so it informs (blue, no actions), the
|
|
53
|
-
// quiet and maintenance alerts' tone, rather than asking. The snapshot carries the holds only
|
|
54
|
-
// while a live orchestrator publishes one.
|
|
55
|
-
if (snap.providerHolds && snap.providerHolds.length > 0) {
|
|
56
|
-
const phrased = snap.providerHolds
|
|
57
|
-
.map((h) => `${h.provider ?? "pi's default backend"} (${h.kind}) until it re-opens in ${humanUntil(h.until, now)}`)
|
|
58
|
-
.join("; ");
|
|
59
|
-
out.push({
|
|
60
|
-
key: "provider-hold",
|
|
61
|
-
tone: "blue",
|
|
62
|
-
title: agree(snap.providerHolds.length, "Provider limit is holding new runs", "Provider limits are holding new runs"),
|
|
63
|
-
detail: `${phrased}. Runs in progress finish; roles on a held provider start no new runs until it re-opens.`,
|
|
64
|
-
actions: [],
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function pushProviderCapsAlert(out, providerCaps) {
|
|
69
|
-
// A `maxConcurrentPerProvider` map that cannot do its job — the always-on twin of the doctor's
|
|
70
|
-
// `provider concurrency` row (doctor-contention-checks.ts checkProviderConcurrency): a cap key
|
|
71
|
-
// no seam resolves to, a cap at or above maxConcurrent, or a keyless seam in a capped team.
|
|
72
|
-
// The provider-hold and contention alerts cover a cap that is merely too small; this one
|
|
73
|
-
// names the static misconfiguration itself, which the doctor row is the only other surface
|
|
74
|
-
// that exposes. alert-feeds.ts's providerCapProblems feeds it the row's own warning strings,
|
|
75
|
-
// so the alert and the row cannot drift on the trigger or the wording.
|
|
76
|
-
if (providerCaps.length > 0) {
|
|
77
|
-
out.push({
|
|
78
|
-
key: "provider-caps",
|
|
79
|
-
tone: "amber",
|
|
80
|
-
title: "maxConcurrentPerProvider is misconfigured",
|
|
81
|
-
detail: `${providerCaps.join("; ")} — fix the map in tumwater.json (each key must name a provider the team runs on, and each cap must sit below maxConcurrent); the provider-hold and contention alerts cover a cap that is merely too small.`,
|
|
82
|
-
actions: [],
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function pushPausedAlert(out, snap, now) {
|
|
87
|
-
if (snap.paused) {
|
|
88
|
-
const left = pauseCountdown(snap.pausedUntil, now);
|
|
89
|
-
out.push({
|
|
90
|
-
key: "paused",
|
|
91
|
-
tone: "amber",
|
|
92
|
-
// The operator's pause reason (`pause --reason <text>`) states verbatim, so an
|
|
93
|
-
// operator returning to a paused team knows why without digging through history.
|
|
94
|
-
title: left
|
|
95
|
-
? `The team is paused and resumes in ${left}${pauseReasonSuffix(snap.pauseReason)}`
|
|
96
|
-
: `The team is paused${pauseReasonSuffix(snap.pauseReason)}`,
|
|
97
|
-
detail: "Roles start no new runs. Runs in progress finish, and the director still runs your prompts.",
|
|
98
|
-
actions: [{ label: "Resume now", act: "resume" }],
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function pushContentionAlert(out, contention) {
|
|
103
|
-
// Last day's capacity contention — the always-on twin of the doctor's `workspaces`,
|
|
104
|
-
// `provider contention` and `check contention` rows (doctor-contention-checks.ts). The
|
|
105
|
-
// instantaneous badges show only current waits, and the 24 h total is the tuning signal that
|
|
106
|
-
// says a cap is too small; alert-feeds.ts's capacityContention feeds it from the same
|
|
107
|
-
// `*_wait` events and ten-minute thresholds the doctor sums, so the alert and the doctor
|
|
108
|
-
// cannot drift on the trigger or the knob-raising advice.
|
|
109
|
-
if (contention.length > 0) {
|
|
110
|
-
out.push({
|
|
111
|
-
key: "contention",
|
|
112
|
-
tone: "amber",
|
|
113
|
-
title: plural(contention.length, "capacity bottleneck in the last day", "capacity bottlenecks in the last day"),
|
|
114
|
-
detail: contention.map((c) => `${c.name}: ${squash(c.detail, 200)}`).join(" · "),
|
|
115
|
-
actions: [],
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function pushWorkspacePinsAlert(out, workspacePins) {
|
|
120
|
-
// A stale workspace pin — the always-on twin of the doctor's `worktree pool` row's stuck-pin
|
|
121
|
-
// clause (doctor-contention-checks.ts checkWorktreePool): a slot pinned past a day by a run
|
|
122
|
-
// that never resumed silently shrinks the pool, and no other observer surface names it until
|
|
123
|
-
// an operator runs `tumwater doctor`. alert-feeds.ts feeds it the same staleWorkspacePins
|
|
124
|
-
// predicate the doctor's row uses, so the alert and the row cannot drift on what is stale;
|
|
125
|
-
// the "Open <role>" buttons reach the role drawer like the other role alerts.
|
|
126
|
-
if (workspacePins.length > 0) {
|
|
127
|
-
out.push({
|
|
128
|
-
key: "workspace-pins",
|
|
129
|
-
tone: "amber",
|
|
130
|
-
title: plural(workspacePins.length, "workspace pinned past a day", "workspaces pinned past a day"),
|
|
131
|
-
detail: `${workspacePins
|
|
132
|
-
.map(stalePinPhrase)
|
|
133
|
-
.join("; ")} — a pinned workspace is held out of the pool; resume the role, or retire its leftovers and run \`tumwater doctor\``,
|
|
134
|
-
actions: openRoleActions(workspacePins),
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function pushInstancePoolAlert(out, instancePool) {
|
|
139
|
-
// An enabled multi-instance role sharing a workspace pool smaller than the run cap — the
|
|
140
|
-
// always-on twin of the doctor `parallel instances` row's pool-mismatch clause
|
|
141
|
-
// (doctor-contention-checks.ts smallPoolProblems): instances then contend for the same
|
|
142
|
-
// pooled checkouts, unseen until an operator runs `tumwater doctor`. alert-feeds.ts feeds
|
|
143
|
-
// it the same smallPoolProblems selector the row uses, so the alert and the row cannot
|
|
144
|
-
// drift on the trigger, the role list, or the wording.
|
|
145
|
-
if (instancePool.length > 0) {
|
|
146
|
-
out.push({
|
|
147
|
-
key: "instance-pool",
|
|
148
|
-
tone: "amber",
|
|
149
|
-
title: "Parallel instances share a workspace pool smaller than the run cap",
|
|
150
|
-
detail: `${instancePool.join("; ")} — runs wait for a pooled checkout; raise worktreeSlots to at least maxConcurrent, or lower maxConcurrent.`,
|
|
151
|
-
actions: [],
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
function pushRegressedFixesAlert(out, regressedFixes) {
|
|
156
|
-
// A Fixed record whose quoted test failed again in the trailing day — the always-on twin of
|
|
157
|
-
// the doctor's `regressed fixes` row (doctor-backlog-bugs.ts checkRegressedFixes).
|
|
158
|
-
// alert-feeds.ts's regressedFixesAlert feeds it the same candidate selection, failureMentions
|
|
159
|
-
// match and verbatim sentence, so the alert and the doctor cannot drift on the trigger or the
|
|
160
|
-
// wording; the Failures view shows the naming events.
|
|
161
|
-
if (regressedFixes) {
|
|
162
|
-
out.push({
|
|
163
|
-
key: "regressed-fixes",
|
|
164
|
-
tone: "amber",
|
|
165
|
-
title: plural(regressedFixes.count, "Fixed test has regressed", "Fixed tests have regressed"),
|
|
166
|
-
detail: regressedFixes.detail,
|
|
167
|
-
actions: seeFailures(),
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
1
|
+
/** The tail team alerts: a stopped tumwater. One of the per-domain builders split out of
|
|
2
|
+
* src/ui/team-alerts.ts; the shared types and wording helpers live in team-alert-shared.ts. */
|
|
3
|
+
import { plural } from "../text/phrases.js";
|
|
171
4
|
function pushStoppedAlert(out, snap) {
|
|
172
5
|
if (!snap.running) {
|
|
173
6
|
out.push({
|
|
@@ -179,60 +12,31 @@ function pushStoppedAlert(out, snap) {
|
|
|
179
12
|
});
|
|
180
13
|
}
|
|
181
14
|
}
|
|
182
|
-
function
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
const noTarget = textOnly.filter((l) => l.imageReroute == null || l.imageReroute === "");
|
|
191
|
-
if (noTarget.length > 0) {
|
|
15
|
+
function pushOrphansAlert(out, orphans) {
|
|
16
|
+
// Orphaned worktree processes — the always-on twin of the doctor's `orphans` row
|
|
17
|
+
// (doctor-orphans.ts checkOrphans): a detected orphan holds no lock, writes no event and
|
|
18
|
+
// touches no state file, so nothing else on the always-on surfaces names it until an operator
|
|
19
|
+
// runs `tumwater doctor`, and it then burns CPU for days unnoticed. alert-feeds.ts's
|
|
20
|
+
// getOrphanAlert feeds it the doctor's own orphanFailDetail sentence and count, so the alert
|
|
21
|
+
// and the row cannot drift on the trigger, the cpu, or the remediation.
|
|
22
|
+
if (orphans) {
|
|
192
23
|
out.push({
|
|
193
|
-
key: "
|
|
24
|
+
key: "orphans",
|
|
194
25
|
tone: "amber",
|
|
195
|
-
title:
|
|
196
|
-
detail:
|
|
197
|
-
actions: openRoleActions(noTarget),
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
else if (textOnly.length > 0) {
|
|
201
|
-
const targets = [...new Set(textOnly.map((l) => l.imageReroute ?? ""))].filter(Boolean);
|
|
202
|
-
out.push({
|
|
203
|
-
key: "images",
|
|
204
|
-
tone: "blue",
|
|
205
|
-
title: `${listRoles(textOnly)} ${agree(textOnly.length, "runs", "run")} on models that can't read images`,
|
|
206
|
-
detail: `A run that reads an image stops and reruns on ${targets.join(", ")}.`,
|
|
26
|
+
title: `${plural(orphans.count, "orphaned process", "orphaned processes")} are burning CPU (PPID 1)`,
|
|
27
|
+
detail: orphans.detail,
|
|
207
28
|
actions: [],
|
|
208
29
|
});
|
|
209
30
|
}
|
|
210
31
|
}
|
|
211
|
-
/** The
|
|
212
|
-
* instance-pool, stale-workspace-pin, regressed-fix, stopped and image-input alerts, in
|
|
213
|
-
* emission order: a run of quiet hours
|
|
214
|
-
* holding the team, a
|
|
215
|
-
* maintenance allowance held at its rolling cap, a standing provider failure hold, an operator
|
|
216
|
-
* pause with its countdown, the last day's capacity contention, a stale workspace pin, a
|
|
217
|
-
* fixed test that regressed, a
|
|
218
|
-
* stopped tumwater, and roles that cannot or may not read images. Extracted from teamAlerts
|
|
32
|
+
/** The stopped-tumwater alert, in emission order: a stopped team. Extracted from teamAlerts
|
|
219
33
|
* (part 5/5 of the teamAlerts split) so the shared module stays small; the input names exactly
|
|
220
|
-
* the fields this run reads
|
|
221
|
-
*
|
|
222
|
-
* occupy, so the emitted key order is unchanged. */
|
|
34
|
+
* the fields this run reads. The caller pushes the returned alerts at the position this run
|
|
35
|
+
* used to occupy, so the emitted key order is unchanged. */
|
|
223
36
|
export function buildTailAlerts(input) {
|
|
224
37
|
const out = [];
|
|
225
|
-
const { snap,
|
|
226
|
-
|
|
227
|
-
pushMaintenanceAlert(out, snap);
|
|
228
|
-
pushProviderHoldAlert(out, snap, now);
|
|
229
|
-
pushProviderCapsAlert(out, providerCaps);
|
|
230
|
-
pushPausedAlert(out, snap, now);
|
|
231
|
-
pushContentionAlert(out, contention);
|
|
232
|
-
pushWorkspacePinsAlert(out, workspacePins);
|
|
233
|
-
pushInstancePoolAlert(out, instancePool);
|
|
234
|
-
pushRegressedFixesAlert(out, regressedFixes);
|
|
38
|
+
const { snap, orphans } = input;
|
|
39
|
+
pushOrphansAlert(out, orphans);
|
|
235
40
|
pushStoppedAlert(out, snap);
|
|
236
|
-
pushImagesAlert(out, roles);
|
|
237
41
|
return out;
|
|
238
42
|
}
|
|
@@ -15,22 +15,15 @@ import { buildStatusAlerts } from "./team-alert-status.js";
|
|
|
15
15
|
import { buildBacklogAlerts } from "./team-alert-backlog.js";
|
|
16
16
|
import { buildTailAlerts } from "./team-alert-tail.js";
|
|
17
17
|
/** Everything about the team that needs the operator, most urgent first: a spent budget, a red
|
|
18
|
-
* main, roles failing run after run, roles that look stuck,
|
|
19
|
-
* questions, refused entries,
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* filed under the wrong backlog section, a duplicated
|
|
24
|
-
* backlog section heading, finished history still under a live backlog file's `## Done` or
|
|
25
|
-
* `## Fixed`, a Fixed record with a bad Validation gap trace, a pause, a
|
|
26
|
-
* stopped team.
|
|
27
|
-
* `now` is passed so a timed
|
|
28
|
-
* pause's countdown agrees with the rest of the frame. */
|
|
29
|
-
export function teamAlerts(snap, questions, roles, now, refused = [], planDependency = null, stalledEntries = [], strandedPlans = [], duplicatedHeadings = [], validationGaps = [], contention = [], regressedFixes = null, staleClaims = [], staleQuestions = [], fixClaims = [], workspacePins = [], liveFinished = [], instancePool = [], providerCaps = []) {
|
|
18
|
+
* main, roles failing run after run, roles that look stuck, disk and install drift, open
|
|
19
|
+
* questions, refused entries, the team waiting on capacity slots (workspace, provider or
|
|
20
|
+
* check), and a stopped team.
|
|
21
|
+
*/
|
|
22
|
+
export function teamAlerts(snap, questions, roles, _now, refused = [], orphans = null) {
|
|
30
23
|
const out = [];
|
|
31
|
-
out.push(...buildStatusAlerts({ snap, roles
|
|
32
|
-
out.push(...buildBacklogAlerts({ questions,
|
|
33
|
-
out.push(...buildTailAlerts({ snap,
|
|
24
|
+
out.push(...buildStatusAlerts({ snap, roles }));
|
|
25
|
+
out.push(...buildBacklogAlerts({ questions, refused }));
|
|
26
|
+
out.push(...buildTailAlerts({ snap, orphans }));
|
|
34
27
|
return out;
|
|
35
28
|
}
|
|
36
29
|
/** Does this alert ask something of the operator (red, amber, indigo), rather than inform? */
|
|
@@ -8,6 +8,31 @@ import { parsePiEventLine } from "../pi/pi-event-line.js";
|
|
|
8
8
|
* renderer stamps its separator from). A segment with neither is skipped. */
|
|
9
9
|
const MARKER_ONLY = new Set(["tumwater_run"]);
|
|
10
10
|
const MESSAGE_ONLY = new Set(["message_end"]);
|
|
11
|
+
function segmentsInText(text) {
|
|
12
|
+
const segments = [];
|
|
13
|
+
for (const line of text.split("\n")) {
|
|
14
|
+
if (line === "")
|
|
15
|
+
continue;
|
|
16
|
+
if (line.includes('"tumwater_run"') && parsePiEventLine(line, MARKER_ONLY))
|
|
17
|
+
segments.push([line]);
|
|
18
|
+
else
|
|
19
|
+
segments.at(-1)?.push(line);
|
|
20
|
+
}
|
|
21
|
+
return segments;
|
|
22
|
+
}
|
|
23
|
+
function keepInWindow(segments, fromTs, toTs) {
|
|
24
|
+
const kept = [];
|
|
25
|
+
for (const seg of segments) {
|
|
26
|
+
const start = segmentStart(seg);
|
|
27
|
+
if (start !== null && start >= fromTs && start <= (toTs ?? Infinity))
|
|
28
|
+
kept.push(...seg);
|
|
29
|
+
}
|
|
30
|
+
return kept;
|
|
31
|
+
}
|
|
32
|
+
function applyTranscriptGrep(entries, grep) {
|
|
33
|
+
const keep = createTranscriptFilter(grep);
|
|
34
|
+
return entries.flatMap((e) => keep(e) ?? []);
|
|
35
|
+
}
|
|
11
36
|
function segmentStart(lines) {
|
|
12
37
|
const marker = parsePiEventLine(lines[0], MARKER_ONLY);
|
|
13
38
|
if (typeof marker?.ts === "number")
|
|
@@ -35,24 +60,10 @@ export function readRunTranscript(file, fromTs, toTs, opts = {}) {
|
|
|
35
60
|
catch {
|
|
36
61
|
continue;
|
|
37
62
|
}
|
|
38
|
-
|
|
39
|
-
for (const line of text.split("\n")) {
|
|
40
|
-
if (line === "")
|
|
41
|
-
continue;
|
|
42
|
-
if (line.includes('"tumwater_run"') && parsePiEventLine(line, MARKER_ONLY))
|
|
43
|
-
segments.push([line]);
|
|
44
|
-
else
|
|
45
|
-
segments.at(-1)?.push(line);
|
|
46
|
-
}
|
|
47
|
-
for (const seg of segments) {
|
|
48
|
-
const start = segmentStart(seg);
|
|
49
|
-
if (start !== null && start >= fromTs && start <= (toTs ?? Infinity))
|
|
50
|
-
kept.push(...seg);
|
|
51
|
-
}
|
|
63
|
+
kept.push(...keepInWindow(segmentsInText(text), fromTs, toTs));
|
|
52
64
|
}
|
|
53
65
|
const entries = formatTranscript(kept, opts);
|
|
54
66
|
if (opts.grep === undefined)
|
|
55
67
|
return entries.flat();
|
|
56
|
-
|
|
57
|
-
return entries.flatMap((e) => keep(e) ?? []);
|
|
68
|
+
return applyTranscriptGrep(entries, opts.grep);
|
|
58
69
|
}
|
|
@@ -15,9 +15,9 @@ import { DASHBOARD_ANSWERED_QUESTIONS, DASHBOARD_RECENT_FINISHED } from "../stat
|
|
|
15
15
|
* — the one definition shared by the keypress handlers and the render (src/ui/tui/tui-keys.ts,
|
|
16
16
|
* src/ui/tui/tui-pane.ts), so stale-selection clamping cannot drift. */
|
|
17
17
|
export function labeledBacklogEntries(root) {
|
|
18
|
-
const marks = backlogMarks(root);
|
|
19
|
-
const holds = backlogHolds(root);
|
|
20
18
|
const runners = backlogInProgress(root);
|
|
19
|
+
const marks = backlogMarks(root, runners);
|
|
20
|
+
const holds = backlogHolds(root);
|
|
21
21
|
return [
|
|
22
22
|
...plannedPlanEntries(root).map((e, i) => ({
|
|
23
23
|
label: "plan",
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { questionAsker, tryAnswerQuestion, UNCLOSED_FENCE_MESSAGE, } from "../../backlog/question-answer.js";
|
|
1
|
+
import { questionAsker, tryAnswerQuestion, UNCLOSED_FENCE_MESSAGE, } from "../../backlog/backlog-question-answer.js";
|
|
2
2
|
import { wakeQuestionAsker } from "../../operator/operator-intent.js";
|
|
3
|
+
import { savedPromptDraft } from "./tui-editor-session.js";
|
|
4
|
+
import { guardError } from "./tui-guard.js";
|
|
3
5
|
/** The backlog pane's answer editor (Ctrl+A on a selected open question) on the shared prompt
|
|
4
6
|
* line. It owns its target (`answerFor`), its saved-draft pair, its exit helper, and the
|
|
5
7
|
* tryAnswerQuestion / wake arms that record the decision. Mutually exclusive with the other
|
|
6
8
|
* editors via the session's held registry. */
|
|
7
9
|
export function createAnswerEditor(session) {
|
|
8
10
|
let answerFor = null;
|
|
9
|
-
|
|
11
|
+
const draft = savedPromptDraft(session);
|
|
10
12
|
// Leave the backlog pane's answer editor (Esc, a recorded answer, or Ctrl+T): restore the
|
|
11
13
|
// saved prompt text and reset the recall state, mirroring exitQueuedEdit.
|
|
12
14
|
const exitAnswerMode = () => {
|
|
@@ -14,7 +16,7 @@ export function createAnswerEditor(session) {
|
|
|
14
16
|
return;
|
|
15
17
|
answerFor = null;
|
|
16
18
|
session.held.answer = null;
|
|
17
|
-
|
|
19
|
+
draft.restore();
|
|
18
20
|
};
|
|
19
21
|
// Ctrl+A opens the answer editor on the selected open question (mnemonic: **A**nswer);
|
|
20
22
|
// Enter records the decision through tryAnswerQuestion, the core `tumwater questions
|
|
@@ -23,26 +25,26 @@ export function createAnswerEditor(session) {
|
|
|
23
25
|
const answerOpenEdit = (ctx) => {
|
|
24
26
|
if (!session.anyEditorHeld() &&
|
|
25
27
|
ctx.view === ctx.roleIds.length + 1 && ctx.selectedEntry !== null) {
|
|
26
|
-
|
|
28
|
+
const selected = ctx.selectedEntry;
|
|
29
|
+
if (selected === null)
|
|
30
|
+
return false;
|
|
31
|
+
guardError(session, () => {
|
|
27
32
|
const entries = session.flat();
|
|
28
|
-
const sel = Math.min(
|
|
33
|
+
const sel = Math.min(selected, entries.length - 1);
|
|
29
34
|
const entry = entries[sel];
|
|
30
35
|
if (entry?.label !== "question") {
|
|
31
36
|
session.flashMessage("only an open question can be answered here");
|
|
32
37
|
}
|
|
33
38
|
else {
|
|
34
39
|
const position = sel - entries.filter((e) => e.label === "plan" || e.label === "bug").length + 1;
|
|
35
|
-
|
|
40
|
+
draft.save();
|
|
36
41
|
answerFor = { position, title: entry.title };
|
|
37
42
|
session.held.answer = answerFor;
|
|
38
43
|
session.input = "";
|
|
39
44
|
session.cursor = 0;
|
|
40
45
|
session.flashMessage(`answering question #${position} — Enter to record, Esc to cancel`);
|
|
41
46
|
}
|
|
42
|
-
}
|
|
43
|
-
catch (err) {
|
|
44
|
-
session.flashError(err);
|
|
45
|
-
}
|
|
47
|
+
});
|
|
46
48
|
return true;
|
|
47
49
|
}
|
|
48
50
|
return false;
|