tumwater 0.1.2 → 0.2.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 +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
/** The team ALERT model: everything about the team that needs the operator — or that they
|
|
3
|
+
* should know — phrased once for both observer surfaces: the dashboard's alert banners
|
|
4
|
+
* (shipped in the status payload as `alerts`) and the TUI's attention lines
|
|
5
|
+
* (tui-frame.ts alertLines). Split from status-model.ts, whose per-loop derivation this reads
|
|
6
|
+
* as input; `tone` ranks an
|
|
7
|
+
* alert (red and amber ask for action, indigo asks a question, blue and gray inform), and
|
|
8
|
+
* `actions` are the dashboard's buttons (`act` names a page action — open a loop, a view,
|
|
9
|
+
* the cap editor…). */
|
|
10
|
+
import { stripEntryStamp } from "../backlog/backlog-structure.js";
|
|
11
|
+
import { quietWindowEnd } from "../scheduling/quiet-hours.js";
|
|
12
|
+
import { chainFragments, pauseCountdown } from "./badges.js";
|
|
13
|
+
import { fallbackTierEntries } from "../budget/budget.js";
|
|
14
|
+
import { fallbackDisplayPair } from "../config/config-views.js";
|
|
15
|
+
import { agree, pauseReasonSuffix, plural } from "../text/phrases.js";
|
|
16
|
+
import { STALL_RE, duration } from "./tick-progress-model.js";
|
|
17
|
+
import { formatGB, usd, usdCap } from "../text/format.js";
|
|
18
|
+
import { formatTimestamp, humanUntil } from "../text/datetime.js";
|
|
19
|
+
import { roleLabel } from "../roles/role-labels.js";
|
|
20
|
+
/** The dashboard's "Open <role>" buttons for an alert about a set of loops: one button per
|
|
21
|
+
* loop (capped to the first three so the banner does not overflow), each opening that loop's
|
|
22
|
+
* pane. Shared by the failing, stuck, and no-image-target alerts, so the button shape and its
|
|
23
|
+
* cap cannot drift between them. */
|
|
24
|
+
function openRoleActions(loops) {
|
|
25
|
+
return loops.slice(0, 3).map((l) => ({ label: `Open ${roleLabel(l.role)}`, act: "role", arg: l.role }));
|
|
26
|
+
}
|
|
27
|
+
/** Natural English list of the given names: “a and b” for one or two, “a, b, and c”
|
|
28
|
+
* beyond that. Shared by the loop-row and held-id banners so the wording cannot drift. */
|
|
29
|
+
function andJoin(names) {
|
|
30
|
+
return names.length <= 2 ? names.join(" and ") : `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`;
|
|
31
|
+
}
|
|
32
|
+
function listRoles(loops) {
|
|
33
|
+
return andJoin(loops.map((l) => roleLabel(l.role)));
|
|
34
|
+
}
|
|
35
|
+
/** The `listRoles` rendering for a set of held role ids (status-data.ts's `capPaused` and
|
|
36
|
+
* `maintenanceQuota.held`, the scheduler's enabled-but-held sets rather than rendered loop
|
|
37
|
+
* rows): names every held id through roleLabel even when a stale id matches no live loop, so
|
|
38
|
+
* the role-cap and maintenance banners never drop a held role from their copy. */
|
|
39
|
+
function listRoleIds(ids) {
|
|
40
|
+
return andJoin(ids.map(roleLabel));
|
|
41
|
+
}
|
|
42
|
+
/** The stuck alert's per-loop detail: the stall phrase the phase rendered, plus — when that
|
|
43
|
+
* phrase does not already carry one — the honest silence span from the live tail (stuckMs,
|
|
44
|
+
* ms since the loop's last pi log write, capped to the tick by progressOfTick). A
|
|
45
|
+
* "no agent output for <d>" phrase already carries its duration, so only a tool-call stall gets
|
|
46
|
+
* a " for <duration>" suffix, and only when the caller holds a live tail (stuckMs present). */
|
|
47
|
+
function stuckDetail(l) {
|
|
48
|
+
const cause = (STALL_RE.exec(l.phase)?.[0] ?? "").trim();
|
|
49
|
+
if (cause && !cause.startsWith("no agent output for") && l.stuckMs !== undefined) {
|
|
50
|
+
return `${roleLabel(l.role)}: ${cause} for ${duration(l.stuckMs)}`;
|
|
51
|
+
}
|
|
52
|
+
return `${roleLabel(l.role)}: ${cause}`;
|
|
53
|
+
}
|
|
54
|
+
/** ISO instants in server text (a restart cooldown's deadline) in local time. */
|
|
55
|
+
function localizeInstants(text) {
|
|
56
|
+
return text.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z/g, (iso) => formatTimestamp(Date.parse(iso)));
|
|
57
|
+
}
|
|
58
|
+
/** Everything about the team that needs the operator, most urgent first: a spent budget, a red
|
|
59
|
+
* main, loops failing tick after tick, loops that look stuck, a stale running build, open
|
|
60
|
+
* questions, refused entries, a pause, a stopped team. `now` is passed so a timed pause's
|
|
61
|
+
* countdown agrees with the rest of the frame. */
|
|
62
|
+
export function teamAlerts(snap, questions, loops, now, refused = []) {
|
|
63
|
+
const out = [];
|
|
64
|
+
const b = snap.budget;
|
|
65
|
+
const onFallback = chainFragments(snap.chain);
|
|
66
|
+
if (onFallback) {
|
|
67
|
+
out.push({
|
|
68
|
+
key: "chain",
|
|
69
|
+
tone: "blue",
|
|
70
|
+
title: "Running on fallback",
|
|
71
|
+
detail: `running on fallback: ${onFallback}`,
|
|
72
|
+
actions: [],
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (!b.free && b.capUsd > 0 && b.spentUsd >= b.capUsd) {
|
|
76
|
+
// Tiered fallbacks (part 7b/8): the snapshot's tier map stands exactly when the
|
|
77
|
+
// tiers resolve to two or more distinct pairs, so list them and let the borrowed
|
|
78
|
+
// ones' `(from …)` suffixes mark the borrow; a tier-free snapshot keeps the
|
|
79
|
+
// single-pair sentence.
|
|
80
|
+
const fallbackDetail = b.tiers
|
|
81
|
+
? `${fallbackTierEntries(b.tiers).join(", ")} carry them at no cost until midnight.`
|
|
82
|
+
: `${fallbackDisplayPair(b.fallback, "The fallback")} carries them at no cost until midnight.`;
|
|
83
|
+
out.push(b.fallback
|
|
84
|
+
? {
|
|
85
|
+
key: "fallback",
|
|
86
|
+
tone: "blue",
|
|
87
|
+
title: "Today's budget is spent — roles switched to the over-cap model",
|
|
88
|
+
// The roles whose tier has no usable free pair (gate-polls.ts's budgetPausedRoles)
|
|
89
|
+
// don't move: they sit on `budget paused` rows while the banner says everyone
|
|
90
|
+
// switched. One sentence names both halves of the split so the spent-cap banner
|
|
91
|
+
// doesn't read as if every role kept working.
|
|
92
|
+
detail: snap.budgetPausedRoles.length > 0
|
|
93
|
+
? `${fallbackDetail} ${listRoleIds(snap.budgetPausedRoles)} ${agree(snap.budgetPausedRoles.length, "has", "have")} no free pair and ${agree(snap.budgetPausedRoles.length, "stays", "stay")} paused until local midnight.`
|
|
94
|
+
: fallbackDetail,
|
|
95
|
+
actions: [{ label: "Raise the cap", act: "budget" }],
|
|
96
|
+
}
|
|
97
|
+
: {
|
|
98
|
+
key: "budget",
|
|
99
|
+
tone: "red",
|
|
100
|
+
title: "Today's budget is spent, so the roles are paused",
|
|
101
|
+
detail: `Spent ${usd(b.spentUsd)} of the ${usdCap(b.capUsd)} cap. They resume at midnight, or as soon as you raise the cap.`,
|
|
102
|
+
actions: [{ label: "Raise the cap", act: "budget" }],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// A role over its maxDailyCostUsdPerRole cap (status-model.ts "waiting: daily cap spent") is
|
|
106
|
+
// a per-role hold — one role sits idle while the rest of the team keeps working — so the team
|
|
107
|
+
// banner names it the way the budget, disk, and quiet banners name theirs, instead of leaving
|
|
108
|
+
// an operator to read every row. An amber ask (it needs a raise to resume); clears on its own
|
|
109
|
+
// at local midnight when the spend window rolls over.
|
|
110
|
+
const capPausedLoops = loops.filter((l) => snap.capPaused.includes(l.role));
|
|
111
|
+
if (snap.capPaused.length > 0) {
|
|
112
|
+
out.push({
|
|
113
|
+
key: "role-cap",
|
|
114
|
+
tone: "amber",
|
|
115
|
+
title: `${listRoleIds(snap.capPaused)} ${agree(snap.capPaused.length, "is over its daily cap", "are over their daily caps")}`,
|
|
116
|
+
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")}.`,
|
|
117
|
+
actions: openRoleActions(capPausedLoops),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// The disk floor holding new work (plans/disk-floor.md, part 4/4): the header badge and the
|
|
121
|
+
// loop rows say what is happening, but the banner names the threshold and the way out, so an
|
|
122
|
+
// operator hitting a full volume does not have to read events. An amber ask — the team is
|
|
123
|
+
// stopped and needs room, not merely informed. Only while the hold is on; below the reclaim
|
|
124
|
+
// threshold but not yet held is the badge's quieter business.
|
|
125
|
+
if (snap.disk?.held) {
|
|
126
|
+
out.push({
|
|
127
|
+
key: "disk",
|
|
128
|
+
tone: "amber",
|
|
129
|
+
title: `Waiting: low disk — the team is holding new work at ${formatGB(snap.disk.freeGB)} free`,
|
|
130
|
+
detail: `Free space is below the ${snap.disk.holdGB} GB floor. Build outputs are reclaimed first; new work starts again once space recovers.`,
|
|
131
|
+
actions: [],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// The root checkout's node_modules is behind package-lock.json (a failed post-merge re-sync):
|
|
135
|
+
// checks install into their own trees until it is fixed. Clears on drift, so a hand fix
|
|
136
|
+
// clears it too.
|
|
137
|
+
const drift = snap.rootInstallDrift ?? [];
|
|
138
|
+
if (drift.length) {
|
|
139
|
+
const shown = drift.slice(0, 5).join(", ");
|
|
140
|
+
const names = drift.length > 5 ? `${shown}, +${drift.length - 5} more` : shown;
|
|
141
|
+
out.push({
|
|
142
|
+
key: "install",
|
|
143
|
+
tone: "amber",
|
|
144
|
+
title: "The root install is behind package-lock.json",
|
|
145
|
+
detail: `${plural(drift.length, "direct dependency", "direct dependencies")} not installed as pinned (${names}). Run npm install in the project's root checkout; until then each check installs into its own tree, and \`tumwater doctor\` names the last re-sync failure.`,
|
|
146
|
+
actions: [],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
const red = loops.filter((l) => l.phase === "main's check is failing");
|
|
150
|
+
if (red.length) {
|
|
151
|
+
out.push({
|
|
152
|
+
key: "mainred",
|
|
153
|
+
tone: "red",
|
|
154
|
+
title: `main's check is failing — ${plural(red.length, "role is", "roles are")} blocked`,
|
|
155
|
+
detail: "Code roles stop authoring until main's check passes, and pick up on their own once it does.",
|
|
156
|
+
actions: [{ label: "See failures", act: "view", arg: "failures" }],
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// A loop whose own failure streak is at or past the alarm threshold (onErrorStreak — an
|
|
160
|
+
// error streak at ERROR_STREAK_WARN, or a quiet-kill streak): the red "failing tick after
|
|
161
|
+
// tick" banner. Keyed on the streak, not on the rendered phase, so it stays up while the
|
|
162
|
+
// loop's next tick runs — where loopPhase reads "working"/"merge"/"reviewing" rather
|
|
163
|
+
// than "failing" — and clears only when the streak resets (BUGS.md 2026-10-14). The
|
|
164
|
+
// wording names what happened (a streak of failed or rejected ticks), not a single repeated
|
|
165
|
+
// error, because a rejection streak is three different rejected changes.
|
|
166
|
+
const failing = loops.filter((l) => l.errorStreak);
|
|
167
|
+
if (failing.length) {
|
|
168
|
+
out.push({
|
|
169
|
+
key: "failing",
|
|
170
|
+
tone: "red",
|
|
171
|
+
title: `${listRoles(failing)} ${agree(failing.length, "is", "are")} failing run after run`,
|
|
172
|
+
detail: failing.some((l) => l.lastError)
|
|
173
|
+
? failing.map((l) => `${roleLabel(l.role)}: ${l.lastError || "see its transcript"}`).join(" · ")
|
|
174
|
+
: "The streak clears only on the next healthy run; the transcript names each failure.",
|
|
175
|
+
actions: openRoleActions(failing),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// A stall marker rides only a running label. The director is excluded from `inFlight` (it
|
|
179
|
+
// holds no maxConcurrent permit), so its own running phase is accepted too — otherwise a
|
|
180
|
+
// stalled director would go unalerted (BUGS.md 2026-10-06).
|
|
181
|
+
const stuck = loops.filter((l) => (l.inFlight || l.phase.startsWith("director working")) && STALL_RE.test(l.phase));
|
|
182
|
+
if (stuck.length) {
|
|
183
|
+
out.push({
|
|
184
|
+
key: "stuck",
|
|
185
|
+
tone: "amber",
|
|
186
|
+
title: `${listRoles(stuck)} ${agree(stuck.length, "looks", "look")} stuck`,
|
|
187
|
+
detail: stuck.map(stuckDetail).join(" · "),
|
|
188
|
+
actions: openRoleActions(stuck),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (snap.build?.stale) {
|
|
192
|
+
const behind = plural(snap.build.aheadCommits ?? 0, "commit");
|
|
193
|
+
out.push(snap.build.restartBlocked
|
|
194
|
+
? {
|
|
195
|
+
key: "build",
|
|
196
|
+
tone: "amber",
|
|
197
|
+
title: `Update available (${behind} behind) — restart blocked`,
|
|
198
|
+
detail: localizeInstants(snap.build.restartBlocked),
|
|
199
|
+
actions: [],
|
|
200
|
+
}
|
|
201
|
+
: {
|
|
202
|
+
key: "build",
|
|
203
|
+
tone: "blue",
|
|
204
|
+
title: `Update available (${behind} behind)${snap.build.restartPending ? " — restarts when idle" : ""}`,
|
|
205
|
+
detail: snap.build.restartPending ? "The team restarts onto it once running work finishes." : "Restart tumwater start to pick it up.",
|
|
206
|
+
actions: [],
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (questions.length) {
|
|
210
|
+
out.push({
|
|
211
|
+
key: "questions",
|
|
212
|
+
tone: "indigo",
|
|
213
|
+
title: `${plural(questions.length, "question needs", "questions need")} your answer`,
|
|
214
|
+
detail: `${stripEntryStamp(questions[0])}${questions.length > 1 ? ` — and ${questions.length - 1} more` : ""}`,
|
|
215
|
+
actions: [{ label: "Answer", act: "questions" }],
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// Refused entries hold until a human decides; amber because the operator has to act.
|
|
219
|
+
if (refused.length) {
|
|
220
|
+
out.push({
|
|
221
|
+
key: "refused",
|
|
222
|
+
tone: "amber",
|
|
223
|
+
title: `${plural(refused.length, "refused entry waits", "refused entries wait")} on your decision`,
|
|
224
|
+
detail: `${stripEntryStamp(refused[0])}${refused.length > 1 ? ` — and ${refused.length - 1} more` : ""} · no role takes it until you release it (tumwater plan|bug --release <n>, or the entry's drawer) or cancel it`,
|
|
225
|
+
actions: [{ label: "Open backlog", act: "backlog" }],
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
// Quiet hours (plans: "Quiet hours … part 2/2, observability") informs rather than asks:
|
|
229
|
+
// the team holding to a schedule is not a problem, but an operator watching idle loops
|
|
230
|
+
// inside the window needs the same explanation the pause alert gives a paused team —
|
|
231
|
+
// why nothing ticks, and when it starts again. The window end comes from quiet-hours.ts's
|
|
232
|
+
// quietWindowEnd, the same helper quietBadge uses for the header.
|
|
233
|
+
if (snap.inQuietHours && snap.quietHours) {
|
|
234
|
+
const end = quietWindowEnd(snap.quietHours);
|
|
235
|
+
out.push({
|
|
236
|
+
key: "quiet",
|
|
237
|
+
tone: "blue",
|
|
238
|
+
title: `Quiet hours — roles start no new runs until ${end}`,
|
|
239
|
+
detail: `The schedule (${snap.quietHours} local time, quietHours in tumwater.json) holds them. Runs in progress finish, and the director still runs your prompts.`,
|
|
240
|
+
actions: [],
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
// The maintenance allowance (plans/work-ratio.md, part 1b/4): by design and self-clearing —
|
|
244
|
+
// the scheduler holds maintenance loops at the rolling 24 h allowance until merges free room,
|
|
245
|
+
// then they tick again on their own — so it informs (gray, no actions), the quiet alert's
|
|
246
|
+
// tone, rather than amber like the failed/refused asks. An operator watching idle maintenance
|
|
247
|
+
// roles gets the same explanation the hold's own row carries, team-wide.
|
|
248
|
+
if (snap.maintenanceQuota.held.length > 0) {
|
|
249
|
+
const mq = snap.maintenanceQuota;
|
|
250
|
+
out.push({
|
|
251
|
+
key: "maintenance",
|
|
252
|
+
tone: "gray",
|
|
253
|
+
title: "Maintenance is held at its rolling allowance",
|
|
254
|
+
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.`,
|
|
255
|
+
actions: [],
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
// A standing provider failure hold (src/team/team-hold.ts `standingHolds`, published through
|
|
259
|
+
// the orchestrator info file): a provider storm holds roles on that provider, so an operator
|
|
260
|
+
// watching idle loops with no log open sees why nothing new starts there. Self-clearing by
|
|
261
|
+
// design — the hold re-opens at its own deadline — so it informs (blue, no actions), the
|
|
262
|
+
// quiet and maintenance alerts' tone, rather than asking. The snapshot carries the holds only
|
|
263
|
+
// while a live orchestrator publishes one.
|
|
264
|
+
if (snap.providerHolds && snap.providerHolds.length > 0) {
|
|
265
|
+
const phrased = snap.providerHolds
|
|
266
|
+
.map((h) => `${h.provider ?? "pi's default backend"} (${h.kind}) until it re-opens in ${humanUntil(h.until, now)}`)
|
|
267
|
+
.join("; ");
|
|
268
|
+
out.push({
|
|
269
|
+
key: "provider-hold",
|
|
270
|
+
tone: "blue",
|
|
271
|
+
title: agree(snap.providerHolds.length, "Provider limit is holding new runs", "Provider limits are holding new runs"),
|
|
272
|
+
detail: `${phrased}. Runs in progress finish; roles on a held provider start no new runs until it re-opens.`,
|
|
273
|
+
actions: [],
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (snap.paused) {
|
|
277
|
+
const left = pauseCountdown(snap.pausedUntil, now);
|
|
278
|
+
out.push({
|
|
279
|
+
key: "paused",
|
|
280
|
+
tone: "amber",
|
|
281
|
+
// The operator's pause reason (`pause --reason <text>`) states verbatim, so an
|
|
282
|
+
// operator returning to a paused team knows why without digging through history.
|
|
283
|
+
title: left
|
|
284
|
+
? `The team is paused and resumes in ${left}${pauseReasonSuffix(snap.pauseReason)}`
|
|
285
|
+
: `The team is paused${pauseReasonSuffix(snap.pauseReason)}`,
|
|
286
|
+
detail: "Roles start no new runs. Runs in progress finish, and the director still runs your prompts.",
|
|
287
|
+
actions: [{ label: "Resume now", act: "resume" }],
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (!snap.running) {
|
|
291
|
+
out.push({
|
|
292
|
+
key: "stopped",
|
|
293
|
+
tone: "gray",
|
|
294
|
+
title: "tumwater is not running — start it with `tumwater start`",
|
|
295
|
+
detail: "Start it with tumwater start in the project directory; the dashboards pick it up on their own.",
|
|
296
|
+
actions: [{ label: "Copy the command", act: "copy", arg: "tumwater start" }],
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
// Image input (plans/image-input.md "Surfaces", part 5/5): a loop whose model cannot read
|
|
300
|
+
// images. Blue when every text-only loop has a reroute target (a tick that reads an image
|
|
301
|
+
// stops and reruns — nothing fails); amber when some text-only loop has none (a blind read
|
|
302
|
+
// fails and image submissions are refused); a separate amber when a claimed model failed
|
|
303
|
+
// doctor's live probe (the claim is contradicted, whatever any loop resolved to). No alert
|
|
304
|
+
// when every enabled loop reads images (imageInput true or unknown — an unknown seam is not
|
|
305
|
+
// counted as text-only, mirroring the submit gate and the doctor's `tier models` stance).
|
|
306
|
+
const textOnly = loops.filter((l) => l.imageInput === false);
|
|
307
|
+
const noTarget = textOnly.filter((l) => l.imageReroute == null || l.imageReroute === "");
|
|
308
|
+
if (noTarget.length > 0) {
|
|
309
|
+
out.push({
|
|
310
|
+
key: "images",
|
|
311
|
+
tone: "amber",
|
|
312
|
+
title: `No configured model can read images for ${listRoles(noTarget)}`,
|
|
313
|
+
detail: "A run that reads an image fails, and image attachments to these roles are refused. Declare image input on a vision model in pi's models.json, or point a tier at one.",
|
|
314
|
+
actions: openRoleActions(noTarget),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
else if (textOnly.length > 0) {
|
|
318
|
+
const targets = [...new Set(textOnly.map((l) => l.imageReroute ?? ""))].filter(Boolean);
|
|
319
|
+
out.push({
|
|
320
|
+
key: "images",
|
|
321
|
+
tone: "blue",
|
|
322
|
+
title: `${listRoles(textOnly)} ${agree(textOnly.length, "runs", "run")} on models that can't read images`,
|
|
323
|
+
detail: `A run that reads an image stops and reruns on ${targets.join(", ")}.`,
|
|
324
|
+
actions: [],
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
if (snap.imageProbeFailures.length > 0) {
|
|
328
|
+
out.push({
|
|
329
|
+
key: "images-probe",
|
|
330
|
+
tone: "amber",
|
|
331
|
+
title: `A claimed image-capable model failed doctor's probe`,
|
|
332
|
+
detail: `${snap.imageProbeFailures.join(", ")} declare image input but failed the live probe — run \`tumwater doctor --reprobe\` to re-check.`,
|
|
333
|
+
actions: [{ label: "See failures", act: "view", arg: "failures" }],
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
/** Does this alert ask something of the operator (red, amber, indigo), rather than inform? */
|
|
339
|
+
export function alertNeedsYou(alert) {
|
|
340
|
+
return alert.tone === "red" || alert.tone === "amber" || alert.tone === "indigo";
|
|
341
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readLiveProgress } from "./progress-data.js";
|
|
2
2
|
import { compactTokens } from "../text/format.js";
|
|
3
3
|
/** The in-flight tick's PROGRESS display model (split from status-model.ts, which keeps the
|
|
4
|
-
* loop-phase ladder, the
|
|
4
|
+
* loop-phase ladder, the merge cells, and the row derivations): how a running tick renders
|
|
5
5
|
* its live detail — elapsed, turn, context, current tool, stall — derived from the live log
|
|
6
6
|
* tail (progress-data.ts) and shared by BOTH observer surfaces: the terminal table
|
|
7
7
|
* (status-render.ts's state cell) and the JSON/GUI payload (status-payload.ts), plus the
|
|
@@ -9,7 +9,7 @@ import { compactTokens } from "../text/format.js";
|
|
|
9
9
|
* drift apart. This is the disk-reading half: every helper here reads (or accepts a caller's
|
|
10
10
|
* precomputed) live log tail, while status-model.ts derives labels from the snapshot. */
|
|
11
11
|
/** Seconds/minutes/hours elapsed formatting shared by the progress helpers and the phase
|
|
12
|
-
* ladder's elapsed branches (loopPhase's
|
|
12
|
+
* ladder's elapsed branches (loopPhase's merge and parked branches). The hours bucket
|
|
13
13
|
* rounds to whole minutes off the total, so a rounded-up 59m5Xs carries into the next hour
|
|
14
14
|
* ("2h0m"); rounding the remainder alone would print the impossible "1h60m". */
|
|
15
15
|
export function duration(ms) {
|
|
@@ -24,10 +24,10 @@ export function duration(ms) {
|
|
|
24
24
|
/** The label for a loop's in-flight tick: `<label>` plus how long it has been running
|
|
25
25
|
* ("working 3m", "reviewing 2m") — shared by workingDetail and the review-gate branch of
|
|
26
26
|
* loopPhase so their elapsed formatting cannot drift. A tick with no recorded start time
|
|
27
|
-
* renders as just the bare label. (A
|
|
28
|
-
* loopPhase's
|
|
27
|
+
* renders as just the bare label. (A merge's elapsed is its marker's, never the tick's —
|
|
28
|
+
* loopPhase's merge branch builds that head itself.) */
|
|
29
29
|
export function inFlightLabel(s, label) {
|
|
30
|
-
const elapsed = s.
|
|
30
|
+
const elapsed = s.lastRunStartedAt ? duration(Date.now() - s.lastRunStartedAt) : "";
|
|
31
31
|
return `${label} ${elapsed}`.trim();
|
|
32
32
|
}
|
|
33
33
|
/** One rule for whether a log tail's progress describes THIS tick: the log's newest write
|
|
@@ -47,25 +47,25 @@ export function inFlightLabel(s, label) {
|
|
|
47
47
|
function progressOfTick(s, p) {
|
|
48
48
|
if (!p)
|
|
49
49
|
return null;
|
|
50
|
-
if (s.
|
|
50
|
+
if (s.lastRunStartedAt === undefined)
|
|
51
51
|
return p;
|
|
52
|
-
const elapsed = Math.max(0, Date.now() - s.
|
|
52
|
+
const elapsed = Math.max(0, Date.now() - s.lastRunStartedAt);
|
|
53
53
|
if (p.quietMs > elapsed)
|
|
54
54
|
return null; // Newest write predates this tick: a previous run's tail.
|
|
55
55
|
return { ...p, quietMs: Math.min(p.quietMs, elapsed) };
|
|
56
56
|
}
|
|
57
57
|
/** The stall phrases a phase label can carry — inFlightDetail appends "tool call stalled:
|
|
58
|
-
* <tool>" and "no
|
|
58
|
+
* <tool>" and "no agent output for <duration>" to the head — as a regex SOURCE string plus the
|
|
59
59
|
* compiled matcher, single-homed beside the code that renders those phrases. Every consumer
|
|
60
60
|
* that must recognize a stall inside a rendered phase label goes through these two, so the
|
|
61
|
-
* server-side alert (
|
|
61
|
+
* server-side alert (team-alerts.ts) and the dashboard's browser twin (gui-client-model.ts,
|
|
62
62
|
* interpolating the source into its String.raw script) cannot drift from the wording the cell
|
|
63
63
|
* actually renders. */
|
|
64
|
-
export const STALL_SOURCE = String.raw `tool call stalled[^·]*|no
|
|
65
|
-
/** STALL_SOURCE compiled — the server-side matcher (
|
|
64
|
+
export const STALL_SOURCE = String.raw `tool call stalled[^·]*|no agent output for [^·]*`;
|
|
65
|
+
/** STALL_SOURCE compiled — the server-side matcher (team-alerts.ts's stuck alert). */
|
|
66
66
|
export const STALL_RE = new RegExp(STALL_SOURCE);
|
|
67
67
|
/** The shared parts assembly for an in-flight state cell: `head` (the phase label with its
|
|
68
|
-
* own elapsed — inFlightLabel for a tick, the
|
|
68
|
+
* own elapsed — inFlightLabel for a tick, the merge branch's own for a merge) plus turn,
|
|
69
69
|
* live context, current tool, and the ≥5-min no-output stall flag. `p` is null when there is
|
|
70
70
|
* no progress (no log yet) — falls back to the bare head. */
|
|
71
71
|
export function inFlightDetail(head, p) {
|
|
@@ -84,7 +84,7 @@ export function inFlightDetail(head, p) {
|
|
|
84
84
|
// Silence under five minutes is normal (slow local-model prefills, long tool calls);
|
|
85
85
|
// only flag a stall once at least five minutes have passed without any pi output.
|
|
86
86
|
if (p.quietMs >= 300_000)
|
|
87
|
-
parts.push(`no
|
|
87
|
+
parts.push(`no agent output for ${duration(p.quietMs)}`);
|
|
88
88
|
return parts.join(" · ");
|
|
89
89
|
}
|
|
90
90
|
/** Which pi run kind (progress-data.ts's ProgressRunKind) a loop's in-flight cells should read:
|
|
@@ -103,7 +103,7 @@ export function progressKind(s) {
|
|
|
103
103
|
* role's log itself — the author accumulator by default, the gate one via `kind`. Every
|
|
104
104
|
* tail-reading caller outside this module routes through it; status-model.ts's staged branch
|
|
105
105
|
* is the one exception, keeping its raw readLiveProgress call because it feeds inFlightDetail
|
|
106
|
-
* a freshly read gate tail for the
|
|
106
|
+
* a freshly read gate tail for the merge label, not a per-frame threaded one. */
|
|
107
107
|
export function tickProgress(root, s, live, kind = "author") {
|
|
108
108
|
return progressOfTick(s, live === undefined ? readLiveProgress(root, s.role, kind) : live);
|
|
109
109
|
}
|
package/dist/src/ui/tone.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
import { loopRank } from "./status-model.js";
|
|
2
3
|
/** A loop phase label's (status-model loopPhase) tone, by its rank: live and pipeline work
|
|
3
4
|
* blue, attention red, paused yellow, idle none. */
|
|
@@ -5,8 +6,8 @@ export function phaseTone(phase) {
|
|
|
5
6
|
const rank = loopRank(phase);
|
|
6
7
|
return rank <= 1 ? "blue" : rank === 2 ? "red" : rank === 3 ? "yellow" : undefined;
|
|
7
8
|
}
|
|
8
|
-
/** A tick result's tone (tick-outcome.ts TickResult): a completed
|
|
9
|
-
* queued to
|
|
9
|
+
/** A tick result's tone (tick-outcome.ts TickResult): a completed merge (changed) green,
|
|
10
|
+
* queued to merge cyan, the failures red, the held-back outcomes (refused, rejected, aborted,
|
|
10
11
|
* quiet_killed) yellow. */
|
|
11
12
|
export function resultTone(result) {
|
|
12
13
|
switch (result) {
|
|
@@ -29,17 +30,17 @@ export function resultTone(result) {
|
|
|
29
30
|
return undefined;
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
/** The tick results that make a `
|
|
33
|
+
/** The tick results that make a `run_end` a problem — one home of the list, also fed to the
|
|
33
34
|
* dashboard's browser twin (gui-client-model.ts interpolates it into its script, both for
|
|
34
35
|
* eventKind and for resultWhy's "does this result carry an error" check), so a newly problem
|
|
35
36
|
* result cannot be classified on one surface and not the other. */
|
|
36
37
|
export const PROBLEM_RESULTS = ["refused", "rejected", "review_error", "merge_conflict", "merge_blocked", "error", "aborted", "quiet_killed", "main_red"];
|
|
37
38
|
/** The named event types that are routine bookkeeping — one home of the list, interpolated into
|
|
38
39
|
* gui-client-model.ts's browser eventKind exactly as PROBLEM_RESULTS is. */
|
|
39
|
-
export const ROUTINE_EVENTS = ["
|
|
40
|
+
export const ROUTINE_EVENTS = ["run_start", "wake", "run_deferred", "review_start", "review_verdict", "merge_queued", "merge_done", "resume", "counters_reset", "state_migrated"];
|
|
40
41
|
/** The named event types that are problems — one home of the list, interpolated into
|
|
41
42
|
* gui-client-model.ts's browser eventKind exactly as PROBLEM_RESULTS is. */
|
|
42
|
-
export const PROBLEM_EVENTS = ["
|
|
43
|
+
export const PROBLEM_EVENTS = ["merge_failed", "review_rejected", "review_failed", "restart_blocked", "restart_refused", "budget_warning", "budget_paused", "role_cap_warning", "role_cap_paused", "disk_low", "main_red_hold", "supervisor_exit", "warning"];
|
|
43
44
|
/** The build-check results that read as routine rather than problem in `eventKind` — one home
|
|
44
45
|
* of the list, interpolated into gui-client-model.ts's browser eventKind exactly as
|
|
45
46
|
* PROBLEM_RESULTS is. */
|
|
@@ -51,16 +52,18 @@ const ROUTINE_EVENT_SET = new Set(ROUTINE_EVENTS);
|
|
|
51
52
|
const PROBLEM_EVENT_SET = new Set(PROBLEM_EVENTS);
|
|
52
53
|
const ROUTINE_BUILD_RESULT_SET = new Set(ROUTINE_BUILD_RESULTS);
|
|
53
54
|
/** Which EventKind one event is, checked in precedence order: the two hard-coded news types
|
|
54
|
-
* first (merged, question_posted), then the result-carrying types by their result — a
|
|
55
|
+
* first (merged, question_posted), then the result-carrying types by their result — a run_end
|
|
55
56
|
* is routine unless its result is a known failure, and a build_check is routine only when it
|
|
56
57
|
* passed or was skipped — then the named event sets, with anything unrecognized reading as
|
|
57
58
|
* info (an event this file predates is news until it is classified). */
|
|
58
59
|
export function eventKind(type, result) {
|
|
59
60
|
if (type === "merged")
|
|
60
|
-
return "landing";
|
|
61
|
+
return "landing"; // vocabulary-ok: EventKind value renamed by the code series
|
|
61
62
|
if (type === "question_posted")
|
|
62
63
|
return "attention";
|
|
63
|
-
if (type === "
|
|
64
|
+
if (type === "entry_refused")
|
|
65
|
+
return "attention";
|
|
66
|
+
if (type === "run_end")
|
|
64
67
|
return result !== undefined && PROBLEM_RESULT_SET.has(result) ? "problem" : "routine";
|
|
65
68
|
if (type === "build_check")
|
|
66
69
|
return result !== undefined && ROUTINE_BUILD_RESULT_SET.has(result) ? "routine" : "problem";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
import { parsePiEventLine } from "../pi/pi-event-line.js";
|
|
2
3
|
import { statRoleLog, withTail } from "../files/tail.js";
|
|
3
4
|
import { collapseWhitespace, squash, textOr, truncate } from "../text/text.js";
|
|
@@ -15,6 +16,26 @@ function contentBlocks(content) {
|
|
|
15
16
|
return [];
|
|
16
17
|
return content.map((raw) => (raw ?? {}));
|
|
17
18
|
}
|
|
19
|
+
/** Append a `text` content block's lines to `out` under the per-message line budget, returning
|
|
20
|
+
* the new (shown, overflow-marked) counters. Blank lines render nothing; once the budget is
|
|
21
|
+
* spent the ellipsis marks the overflow a single time and every later line is dropped. */
|
|
22
|
+
function renderTextLines(block, out, textShown, textOverflowMarked) {
|
|
23
|
+
for (const rawLine of textOr(block.text, "").split("\n")) {
|
|
24
|
+
const line = rawLine.trim();
|
|
25
|
+
if (!line)
|
|
26
|
+
continue;
|
|
27
|
+
if (textShown >= TEXT_LINES_PER_MESSAGE) {
|
|
28
|
+
if (!textOverflowMarked) {
|
|
29
|
+
out.push(" …");
|
|
30
|
+
textOverflowMarked = true;
|
|
31
|
+
}
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
out.push(` ${truncate(line, TEXT_LINE_MAX_COLS)}`);
|
|
35
|
+
textShown += 1;
|
|
36
|
+
}
|
|
37
|
+
return { textShown, textOverflowMarked };
|
|
38
|
+
}
|
|
18
39
|
/** Render one completed assistant message into transcript lines: abbreviated thinking,
|
|
19
40
|
* indented text (capped), and labeled tool calls — in the order they appear. */
|
|
20
41
|
function renderAssistantMessage(content) {
|
|
@@ -29,23 +50,9 @@ function renderAssistantMessage(content) {
|
|
|
29
50
|
out.push(`· ${truncate(thinking, THINKING_MAX_CHARS)}`);
|
|
30
51
|
break;
|
|
31
52
|
}
|
|
32
|
-
case "text":
|
|
33
|
-
|
|
34
|
-
const line = rawLine.trim();
|
|
35
|
-
if (!line)
|
|
36
|
-
continue;
|
|
37
|
-
if (textShown >= TEXT_LINES_PER_MESSAGE) {
|
|
38
|
-
if (!textOverflowMarked) {
|
|
39
|
-
out.push(" …");
|
|
40
|
-
textOverflowMarked = true;
|
|
41
|
-
}
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
out.push(` ${truncate(line, TEXT_LINE_MAX_COLS)}`);
|
|
45
|
-
textShown += 1;
|
|
46
|
-
}
|
|
53
|
+
case "text":
|
|
54
|
+
({ textShown, textOverflowMarked } = renderTextLines(block, out, textShown, textOverflowMarked));
|
|
47
55
|
break;
|
|
48
|
-
}
|
|
49
56
|
case "toolCall":
|
|
50
57
|
out.push(`→ ${describeToolCall(textOr(block.name), block.arguments)}`);
|
|
51
58
|
break;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// vocabulary: plain
|
|
2
3
|
/** The ink component tree that draws `tumwater tui`'s frame. Pure drawing: it takes the
|
|
3
4
|
* frame tui.tsx's render step assembles — the same clipped StatusLines the tui-frame.ts
|
|
4
5
|
* builders produce — and lays one line out per row, one styled Text per span. Ink
|