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
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
import { eventRole, eventTick, eventUsage } from "./event-read.js";
|
|
2
|
-
import {
|
|
3
|
+
import { eventTypeLabel } from "./event-labels.js";
|
|
4
|
+
import { roleLabel } from "../roles/role-labels.js";
|
|
5
|
+
import { TUMWATER_SOURCE } from "./events.js";
|
|
6
|
+
import { ORCHESTRATOR_STOPPED, backendKindPhrase, budgetPhrase, firstReason, teamPausePhrase, teamResumePhrase, holdPhrase, plural, rolePausePhrase, roleResumePhrase, rolesPhrase, shortSpanPhrase } from "../text/phrases.js";
|
|
3
7
|
import { compactTokens, formatGB, shortSha, usd } from "../text/format.js";
|
|
4
8
|
import { padToWidth } from "../text/text-width.js";
|
|
5
|
-
import { textOr } from "../text/text.js";
|
|
9
|
+
import { squash, textOr, truncate } from "../text/text.js";
|
|
6
10
|
import { formatTimestamp, formatTime } from "../text/datetime.js";
|
|
7
11
|
import { finiteNumber, isJsonObject, stringList } from "../files/json-object.js";
|
|
8
12
|
import { fallbackTierEntries } from "../budget/budget.js";
|
|
9
|
-
/** The `<N> tok · $<spent>` usage fragment every event that records a run's cost shares (
|
|
10
|
-
*
|
|
11
|
-
* shares), rendered through the shared token/money formats (compactTokens, usd) in one
|
|
12
|
-
* Either part is omitted when zero or absent, so skipped ticks and zero-usage
|
|
13
|
+
/** The `<N> tok · $<spent>` usage fragment every event that records a run's cost shares (run_end,
|
|
14
|
+
* merge_done): the usage numbers arrive via eventUsage (the loose-typing coercion every usage
|
|
15
|
+
* consumer shares), rendered through the shared token/money formats (compactTokens, usd) in one
|
|
16
|
+
* place. Either part is omitted when zero or absent, so skipped ticks and zero-usage merges render
|
|
13
17
|
* byte-identical to a pre-feature line — no leading or trailing separator. "·" is the separator
|
|
14
18
|
* the budget badge already uses. Sentence contexts prepend their own " · " via usagePhrase;
|
|
15
19
|
* table-cell contexts (history's usage column) use the bare text. */
|
|
@@ -57,8 +61,15 @@ function windowResetPhrase(e) {
|
|
|
57
61
|
const at = finiteNumber(e.resetsAt, null);
|
|
58
62
|
return at === null ? "" : `, resets ${formatTime(new Date(at))}`;
|
|
59
63
|
}
|
|
64
|
+
/** The parenthesized ` (<window>)` a quiet-hours event names when it carries one, or "" when
|
|
65
|
+
* the field is absent or blank (pollQuietHoursGate may send no window, so a routine hold must
|
|
66
|
+
* not fabricate a pair). Shared by the started and ended lines so the two cannot drift on the
|
|
67
|
+
* window's presence or format. */
|
|
68
|
+
function windowSuffix(e) {
|
|
69
|
+
return typeof e.window === "string" && e.window.trim() ? ` (${textOr(e.window)})` : "";
|
|
70
|
+
}
|
|
60
71
|
/** The backend a hold event names, rendered as " at <provider>" when one is configured
|
|
61
|
-
* (
|
|
72
|
+
* (pollTeamHold omits it for pi's default) and "" otherwise, so a multi-provider team can
|
|
62
73
|
* tell WHICH backend the storm was at. Shared by the hold and resumed lines. */
|
|
63
74
|
function providerAt(e) {
|
|
64
75
|
return typeof e.provider === "string" ? ` at ${e.provider}` : "";
|
|
@@ -69,7 +80,13 @@ function providerAt(e) {
|
|
|
69
80
|
function providerScope(e) {
|
|
70
81
|
return typeof e.provider === "string" ? "that provider" : "every provider";
|
|
71
82
|
}
|
|
72
|
-
/**
|
|
83
|
+
/** The provider/model pair a fallback-ish line names, as "provider/model", defaulting each
|
|
84
|
+
* half when the event omits it (a bare pi run carries neither). Shared by the budget_fallback
|
|
85
|
+
* and model_fallback lines so the pair's shape and defaults cannot drift between them. */
|
|
86
|
+
function providerModelPair(e, providerDefault, modelDefault) {
|
|
87
|
+
return `${textOr(e.provider, providerDefault)}/${textOr(e.model, modelDefault)}`;
|
|
88
|
+
}
|
|
89
|
+
/** An event's outcome string — a tick's `result`, or a merge's or build check's `status`,
|
|
73
90
|
* whichever the event carries; undefined when neither does. The activity feeds' tone and
|
|
74
91
|
* filter key (tone.ts's eventKind takes it beside the event type), also shipped as the
|
|
75
92
|
* status payload's per-event `result` field, so the two surfaces cannot disagree on what a
|
|
@@ -86,13 +103,16 @@ export function eventResult(e) {
|
|
|
86
103
|
export function outcomeText(e) {
|
|
87
104
|
return eventResult(e) ?? "?";
|
|
88
105
|
}
|
|
89
|
-
/** Why a `
|
|
90
|
-
* reason ("backlog"
|
|
91
|
-
* digest (describeStateChange) so the two cannot disagree on the deferral's cause.
|
|
106
|
+
/** Why a `run_deferred` event fired, as the clause that can follow "deferred — ": scheduling's
|
|
107
|
+
* reason ("backlog", "no-work" or "plans-full"). Shared by the event feed (eventMessage) and
|
|
108
|
+
* the failure digest (describeStateChange) so the two cannot disagree on the deferral's cause.
|
|
109
|
+
* A missing or
|
|
92
110
|
* unknown value — a torn line, or an event written before the field existed — reads as the
|
|
93
111
|
* no-work spelling, the only clause the pre-field builds could log. */
|
|
94
112
|
export function deferredReasonText(e) {
|
|
95
|
-
|
|
113
|
+
if (e.reason === "plans-full")
|
|
114
|
+
return "plan backlog at its target";
|
|
115
|
+
return e.reason === "backlog" ? "feature/bugfix backlog open" : "nothing merged since the last run";
|
|
96
116
|
}
|
|
97
117
|
/** Human one-liner for an event, shared by `logs`, `run` output, and the TUI activity pane:
|
|
98
118
|
* the local `YYYY-MM-DD HH:MM:SS` stamp (formatTimestamp — the same zero-padded, locale-
|
|
@@ -109,11 +129,12 @@ export function formatEvent(e) {
|
|
|
109
129
|
// sat right of every ASCII row's (the same unit mistake the aligned tables shed). The loop
|
|
110
130
|
// itself goes through eventRole, so a torn or hand-edited event with no loop shows "?" like
|
|
111
131
|
// the usage report and failure digest, never String(undefined)'s literal "undefined".
|
|
112
|
-
const
|
|
132
|
+
const source = eventRole(e);
|
|
133
|
+
const loop = padToWidth(source === TUMWATER_SOURCE ? "tumwater" : roleLabel(source), 10);
|
|
113
134
|
return `${time} ${loop} ${eventMessage(e)}`;
|
|
114
135
|
}
|
|
115
136
|
/** One event as the dashboard's activity feed consumes it: `result` is a tick's outcome, a
|
|
116
|
-
*
|
|
137
|
+
* merge's result, or a build check's status — whichever string the event carries. The event
|
|
117
138
|
* layer owns the event shape's projection, so both the status poll (ui/status-payload.ts) and
|
|
118
139
|
* the slice endpoint (gui/gui-endpoints.ts's handleLogs) read the same fields, and a feed row's
|
|
119
140
|
* shape cannot drift between them. */
|
|
@@ -121,7 +142,7 @@ export function eventFeedItem(e) {
|
|
|
121
142
|
const result = eventResult(e);
|
|
122
143
|
// eventRole renders a missing/empty loop as "?" like the reports, so a torn or hand-edited
|
|
123
144
|
// event never shows a literal "undefined" loop chip in the dashboard's activity feed.
|
|
124
|
-
return { ts: e.ts,
|
|
145
|
+
return { ts: e.ts, source: eventRole(e), type: e.type, ...(result === undefined ? {} : { result }), message: eventMessage(e) };
|
|
125
146
|
}
|
|
126
147
|
/** The feed's `--grep`/Search match rule: case-insensitive substring over the raw event type
|
|
127
148
|
* prefixed to the rendered line — exactly the haystack the operator would otherwise read. One
|
|
@@ -135,64 +156,79 @@ export function eventMatchesGrep(e, patternLower) {
|
|
|
135
156
|
* ships this beside the event's type and loop instead of making the page re-parse a line. */
|
|
136
157
|
export function eventMessage(e) {
|
|
137
158
|
switch (e.type) {
|
|
138
|
-
case "
|
|
139
|
-
return `
|
|
140
|
-
case "
|
|
159
|
+
case "run_start":
|
|
160
|
+
return `run #${eventTick(e)} started${e.model !== undefined ? ` on ${textOr(e.model)}` : ""}`;
|
|
161
|
+
case "run_end": {
|
|
141
162
|
// The payload that explains the outcome: summary for changed/refused/rejected/
|
|
142
163
|
// review_error ticks, error (lastError) for error and merge-failed ones. Showing it
|
|
143
164
|
// for every result that carries one keeps the event feed self-explanatory — a bare
|
|
144
165
|
// "tick #N refused" would force operators to open the transcript for the reason.
|
|
145
166
|
const extra = e.summary ? ` — ${textOr(e.summary)}` : e.error ? ` — ${textOr(e.error)}` : "";
|
|
146
167
|
// Per-tick usage: where the day's spend went.
|
|
147
|
-
return `
|
|
168
|
+
return `run #${eventTick(e)} ${eventTypeLabel(outcomeText(e))}${extra}${usagePhrase(e)}`;
|
|
148
169
|
}
|
|
149
170
|
case "merged":
|
|
150
171
|
return `merged ${shortSha(e.commit)} to main — ${textOr(e.summary)}`;
|
|
151
|
-
case "
|
|
152
|
-
// Merge queue 3/5: routine state change (the tick committed and the
|
|
153
|
-
// ahead) — no warning prefix. The
|
|
154
|
-
return `
|
|
155
|
-
case "
|
|
156
|
-
// The
|
|
157
|
-
// spend (reviewer + conflict resolution), omitted when zero so review-exempt
|
|
158
|
-
// render bare — the same phrase
|
|
159
|
-
return `
|
|
160
|
-
case "
|
|
172
|
+
case "merge_queued":
|
|
173
|
+
// Merge queue 3/5: routine state change (the tick committed and the merge slot is
|
|
174
|
+
// ahead) — no warning prefix. The merge's own events follow it.
|
|
175
|
+
return `waiting to merge ${shortSha(e.commit)} — ${textOr(e.summary)}`;
|
|
176
|
+
case "merge_done":
|
|
177
|
+
// The merge slot finished with the change on main; the usage is the merge's own
|
|
178
|
+
// spend (reviewer + conflict resolution), omitted when zero so review-exempt merges
|
|
179
|
+
// render bare — the same phrase run_end carries for its run.
|
|
180
|
+
return `merge of ${shortSha(e.commit)} done${elapsed(e.durationMs)}${usagePhrase(e)}`;
|
|
181
|
+
case "merge_failed":
|
|
161
182
|
// Routine failure detail: the review events themselves (review_rejected/review_failed)
|
|
162
183
|
// or the merged/build_check lines carry the reason in the feed.
|
|
163
|
-
return `
|
|
184
|
+
return `merge of ${shortSha(e.commit)} failed (${eventTypeLabel(outcomeText(e))})${elapsed(e.durationMs)}${typeof e.summary === "string" && e.summary.trim() ? ` — ${truncate(e.summary, 80)}` : ""}`;
|
|
164
185
|
case "question_posted":
|
|
165
186
|
// Routine operation (a loop asked the user something), not a warning.
|
|
166
|
-
|
|
187
|
+
// The position is the `<n>` `questions answer` takes, so the notify page is actionable.
|
|
188
|
+
return `question posted: ${textOr(e.question)}${typeof e.position === "number" && Number.isInteger(e.position) && e.position >= 1 ? ` — question ${e.position} in \`tumwater questions\`; answer it with \`tumwater questions answer ${e.position} "<decision>"\`` : ""}`;
|
|
189
|
+
case "entry_refused":
|
|
190
|
+
// Routine operation (a refusal note merged), not a warning — like question_posted.
|
|
191
|
+
const files = Array.isArray(e.files) ? e.files.join(", ") : "";
|
|
192
|
+
const titles = stringList(e.entries);
|
|
193
|
+
if (titles.length === 0)
|
|
194
|
+
return `entry refused: ${textOr(e.reason)} (${files})`;
|
|
195
|
+
const more = titles.length > 1 ? ` (and ${titles.length - 1} more)` : "";
|
|
196
|
+
return `entry refused: "${truncate(titles[0], 80)}"${more}: ${textOr(e.reason)} (${files})`;
|
|
167
197
|
case "wake":
|
|
168
198
|
return `woke (${textOr(e.reason)})`;
|
|
169
|
-
case "
|
|
199
|
+
case "run_deferred":
|
|
170
200
|
// Routine state change (need-based prioritization), like counters_reset — no warning
|
|
171
201
|
// prefix. One per deferral episode; the tick's own events cover the episode's end.
|
|
172
202
|
return `deferred — ${deferredReasonText(e)}`;
|
|
173
203
|
case "orchestrator_start":
|
|
174
|
-
return `
|
|
204
|
+
return `tumwater started (pid ${textOr(e.pid)}${e.build ? `, version ${shortSha(e.build)}` : ""})`;
|
|
175
205
|
case "orchestrator_stop":
|
|
176
206
|
return ORCHESTRATOR_STOPPED;
|
|
177
207
|
case "prompt_enqueued":
|
|
178
|
-
return `
|
|
208
|
+
return `message added to the inbox: ${textOr(e.preview)}`;
|
|
179
209
|
case "prompt_cancelled":
|
|
180
210
|
// Routine operation (the user removed a queued prompt), not a warning.
|
|
181
|
-
return `
|
|
211
|
+
return `message removed from the inbox: ${textOr(e.preview)}`;
|
|
182
212
|
case "prompt_edited":
|
|
183
213
|
// Routine operation (the user corrected a queued prompt), not a warning.
|
|
184
|
-
return `
|
|
214
|
+
return `inbox message edited: ${textOr(e.preview)}`;
|
|
215
|
+
case "prompt_topped":
|
|
216
|
+
// Routine operation (the user reordered a queued prompt), not a warning.
|
|
217
|
+
return `inbox message moved to the front: ${textOr(e.preview)}`;
|
|
218
|
+
case "prompt_taken":
|
|
219
|
+
// Routine: a tick dequeued the operator's request to run it.
|
|
220
|
+
return `inbox message taken by run #${eventTick(e)}: ${textOr(e.preview)}`;
|
|
185
221
|
case "counters_reset": {
|
|
186
222
|
// One role → the event is filed under that loop; several → one harness-level event
|
|
187
223
|
// listing them.
|
|
188
224
|
const roles = rolesPhrase(e.roles, "");
|
|
189
225
|
const scope = roles ? ` for ${roles}` : "";
|
|
190
|
-
return `counters reset${scope} (
|
|
226
|
+
return `counters reset${scope} (runs, merges, tokens, cost)`;
|
|
191
227
|
}
|
|
192
|
-
case "
|
|
228
|
+
case "run_aborted":
|
|
193
229
|
// Routine state change (the user stopped one loop's tick), like counters_reset — no
|
|
194
|
-
// warning prefix. The resulting
|
|
195
|
-
return `
|
|
230
|
+
// warning prefix. The resulting run_end line carries the user_aborted outcome.
|
|
231
|
+
return `run stopped by you`;
|
|
196
232
|
case "review_start": {
|
|
197
233
|
// A resumed review continues a timed-out reviewer's session (PLANS.md, review-retry part
|
|
198
234
|
// 3/3). The verb names that, because a bare "reviewing" line reads as a from-scratch run;
|
|
@@ -209,7 +245,7 @@ export function eventMessage(e) {
|
|
|
209
245
|
// one-liner read as the complete verdict — the full list rides in state.lastReview
|
|
210
246
|
// into the author's next tick prompt, and this line is the operator's pointer to it.
|
|
211
247
|
const more = reasons.length > 1
|
|
212
|
-
? ` (+${reasons.length - 1} more — the author's next
|
|
248
|
+
? ` (+${reasons.length - 1} more — the author's next run carries every reason)`
|
|
213
249
|
: "";
|
|
214
250
|
return `review rejected ${shortSha(e.head)} — ${firstReason(reasons)}${more}${elapsed(e.durationMs)}`;
|
|
215
251
|
}
|
|
@@ -220,7 +256,7 @@ export function eventMessage(e) {
|
|
|
220
256
|
// conflicted with it, dropped by its author, or exhausted its rounds.
|
|
221
257
|
return `revision ${textOr(e.round)} ${textOr(e.action)} ${shortSha(e.sha)}`;
|
|
222
258
|
case "conflict_handback": {
|
|
223
|
-
// A change whose
|
|
259
|
+
// A change whose merges kept conflicting was handed back to its author with the markers
|
|
224
260
|
// in place (PLANS.md "Robust conflict landing, part 2/2"): queued, applied, or failed.
|
|
225
261
|
const files = stringList(e.conflicted);
|
|
226
262
|
const where = files.length > 0 ? `: ${files.join(", ")}` : "";
|
|
@@ -230,44 +266,46 @@ export function eventMessage(e) {
|
|
|
230
266
|
case "build_check":
|
|
231
267
|
// The deterministic check's cost, per run: scope names which gate paid (the pre-merge review
|
|
232
268
|
// gate, the red-main baseline check of main itself, or the merge lock's post-rebase re-check
|
|
233
|
-
// of the tree about to
|
|
269
|
+
// of the tree about to merge). script carries the npm script name for an npm check but the
|
|
234
270
|
// FULL configured command otherwise (checkScriptName in build/build-check.ts), so no "npm"
|
|
235
271
|
// prefix is asserted here — the bare name reads correctly for both kinds ("npm test" would
|
|
236
272
|
// misrender a cargo or make-based project's check).
|
|
237
|
-
return `
|
|
273
|
+
return `check (${textOr(e.scope)}): ${textOr(e.script)} ${eventTypeLabel(outcomeText(e))}${elapsed(e.durationMs)}`;
|
|
238
274
|
case "dep_install": {
|
|
239
|
-
// The root checkout's install catching up with a
|
|
275
|
+
// The root checkout's install catching up with a merged lockfile change — routine on
|
|
240
276
|
// success; a failure also rides its own warning, which says what happens meanwhile.
|
|
241
277
|
const pkgs = stringList(e.packages).join(", ");
|
|
242
|
-
return `root install (${pkgs}) ${outcomeText(e)}${e.error ? ` — ${textOr(e.error)}` : ""}${elapsed(e.durationMs)}`;
|
|
278
|
+
return `root install (${pkgs}) ${eventTypeLabel(outcomeText(e))}${e.error ? ` — ${textOr(e.error)}` : ""}${elapsed(e.durationMs)}`;
|
|
243
279
|
}
|
|
244
280
|
case "budget_warning":
|
|
245
281
|
// The early page beside budget_paused: the cap is not reached yet, so say so — the
|
|
246
|
-
// operator still has room to raise it or fix the fallback before the
|
|
247
|
-
return `budget warning — ${budgetPhrase(e.spentUsd, e.capUsd)} of the daily cap spent;
|
|
282
|
+
// operator still has room to raise it or fix the fallback before the team stops.
|
|
283
|
+
return `budget warning — ${budgetPhrase(e.spentUsd, e.capUsd)} of the daily cap spent; runs still start`;
|
|
248
284
|
case "budget_paused": {
|
|
249
285
|
// Routine state change, like counters_reset — no warning prefix. A configured fallback
|
|
250
|
-
// the gate refused is named here: it is the whole reason the
|
|
286
|
+
// the gate refused is named here: it is the whole reason the team stopped instead of
|
|
251
287
|
// switching over, and an operator reading the feed must be able to act on it. So is a
|
|
252
288
|
// free fallback the breaker demoted because its ticks kept failing (BUGS.md 2026-09-20):
|
|
253
|
-
// the backend, not the price, is what to fix, and the
|
|
289
|
+
// the backend, not the price, is what to fix, and the team retries it on its own.
|
|
290
|
+
// Like role_cap_paused, the line names the lift paths: a raised or removed cap, or the
|
|
291
|
+
// local day rolling over (the demoted branch's cool-down retry is the automatic third).
|
|
254
292
|
const refused = e.fallbackRejected
|
|
255
|
-
? ` (
|
|
293
|
+
? ` (over-cap model ${textOr(e.fallbackRejected)} is not a cost n/a model in pi's models.json)`
|
|
256
294
|
: e.fallbackDemoted
|
|
257
|
-
? ` (
|
|
295
|
+
? ` (over-cap model ${textOr(e.fallbackDemoted)} is not serving — ${textOr(e.failures)} runs failed in a row on it; one trial run retries it after a cool-down)`
|
|
258
296
|
: "";
|
|
259
|
-
return `budget paused — ${budgetPhrase(e.spentUsd, e.capUsd)} daily cost reached${refused}`;
|
|
297
|
+
return `budget paused — ${budgetPhrase(e.spentUsd, e.capUsd)} daily cost reached${refused}; roles start no new runs until maxDailyCostUsd is raised or removed in tumwater.json or the local day rolls over`;
|
|
260
298
|
}
|
|
261
299
|
case "budget_fallback": {
|
|
262
|
-
// The cap is spent but the
|
|
300
|
+
// The cap is spent but the team keeps working: name the free model it switched to, the
|
|
263
301
|
// one fact that distinguishes this from a pause. When the event carries the per-tier
|
|
264
302
|
// map (tiers, part 7b/8 — only emitted when the tiers resolve to two or more distinct
|
|
265
303
|
// pairs), list each tier's pair and let the borrowed ones' `(from …)` suffixes show a
|
|
266
304
|
// strong-tier borrow at a glance; a tier-free event keeps today's single-pair text.
|
|
267
305
|
const tierEntries = fallbackTierEntries(isJsonObject(e.tiers) ? e.tiers : undefined);
|
|
268
306
|
if (tierEntries.length >= 2)
|
|
269
|
-
return `
|
|
270
|
-
return `
|
|
307
|
+
return `over-cap model — ${budgetPhrase(e.spentUsd, e.capUsd)} daily cost reached; roles continue on ${tierEntries.join(", ")} (cost n/a)`;
|
|
308
|
+
return `over-cap model — ${budgetPhrase(e.spentUsd, e.capUsd)} daily cost reached; roles continue on ${providerModelPair(e, "pi's default provider", "pi's default model")} (cost n/a)`;
|
|
271
309
|
}
|
|
272
310
|
case "budget_resumed":
|
|
273
311
|
return `budget resumed (${budgetPhrase(e.spentUsd, e.capUsd)} today)`;
|
|
@@ -276,52 +314,78 @@ export function eventMessage(e) {
|
|
|
276
314
|
// reopened. Their ticks end `aborted` and resume promptly on the primary, so an operator
|
|
277
315
|
// reading the feed can connect the aborted ticks to this one line.
|
|
278
316
|
const roles = rolesPhrase(e.roles, "?");
|
|
317
|
+
if (e.reason === "window")
|
|
318
|
+
return `claude subscription window reset: handed ${roles} back to claude`;
|
|
279
319
|
return `budget reopened: handed ${roles} back to the primary`;
|
|
280
320
|
}
|
|
281
|
-
case "
|
|
321
|
+
case "fallback_switched": {
|
|
322
|
+
// One line for the poll's changes: `<tier> → <pair> (<reason>)`, the primary as "primary".
|
|
323
|
+
const tiers = isJsonObject(e.tiers) ? e.tiers : {};
|
|
324
|
+
const parts = Object.entries(tiers).map(([tier, v]) => {
|
|
325
|
+
const t = isJsonObject(v) ? v : {};
|
|
326
|
+
return `${tier} → ${typeof t.to === "string" ? t.to : "primary"} (${textOr(t.reason)})`;
|
|
327
|
+
});
|
|
328
|
+
return `fallback: ${parts.join("; ")}`;
|
|
329
|
+
}
|
|
330
|
+
case "team_paused":
|
|
282
331
|
// Routine state change, like counters_reset — no warning prefix.
|
|
283
|
-
return `${
|
|
284
|
-
case "
|
|
285
|
-
return
|
|
332
|
+
return `${teamPausePhrase()} (director keeps running)`;
|
|
333
|
+
case "state_migrated":
|
|
334
|
+
return `state migrated — ${textOr(e.detail)}`;
|
|
335
|
+
case "team_resumed":
|
|
336
|
+
return teamResumePhrase();
|
|
286
337
|
case "role_paused":
|
|
287
|
-
// Routine state change, like
|
|
288
|
-
return `${rolePausePhrase(textOr(e.role))} (the rest of the
|
|
338
|
+
// Routine state change, like team_paused — no warning prefix.
|
|
339
|
+
return `${rolePausePhrase(textOr(e.role))} (the rest of the team keeps running)`;
|
|
289
340
|
case "role_resumed":
|
|
290
341
|
return roleResumePhrase(textOr(e.role));
|
|
291
342
|
case "role_streak_paused":
|
|
292
343
|
// Routine-with-explanation, like rate_limit_hold — no warning prefix: the pause IS the
|
|
293
344
|
// harness handling the failure. Names the streak depth and the lift command, so the
|
|
294
|
-
// operator knows why the loop stopped and what to do about it
|
|
295
|
-
|
|
345
|
+
// operator knows why the loop stopped and what to do about it; names the last error
|
|
346
|
+
// when one is recorded.
|
|
347
|
+
return `role ${textOr(e.role)} paused — ${textOr(e.streak)} runs failed in a row${typeof e.lastError === "string" && e.lastError.trim() ? ` — last error: ${squash(e.lastError, 120)}` : ""}; fix the cause and resume it (tumwater resume --role <id>)`;
|
|
296
348
|
case "role_cap_warning":
|
|
297
349
|
// The per-role early page beside role_cap_paused: the cap is not reached yet, so say so —
|
|
298
350
|
// the operator still has room to raise it before the loop stops.
|
|
299
|
-
return `role ${textOr(e.role)} at ${budgetPhrase(e.spentUsd, e.capUsd)} of its daily cap — it keeps
|
|
351
|
+
return `role ${textOr(e.role)} at ${budgetPhrase(e.spentUsd, e.capUsd)} of its daily cap — it keeps running; raise or remove the cap in tumwater.json before it pauses`;
|
|
300
352
|
case "role_cap_paused":
|
|
301
353
|
// Routine state change, like budget_paused — no warning prefix: the pause IS the harness
|
|
302
354
|
// holding the role's own spend line. Names the role, its spend vs its cap, and the two
|
|
303
355
|
// lift paths, so the operator knows why the loop stopped and what to do about it.
|
|
304
|
-
return `role ${textOr(e.role)} paused — ${budgetPhrase(e.spentUsd, e.capUsd)} of its daily cap spent; it starts no new
|
|
356
|
+
return `role ${textOr(e.role)} paused — ${budgetPhrase(e.spentUsd, e.capUsd)} of its daily cap spent; it starts no new runs until the cap is raised or removed in tumwater.json or the local day rolls over`;
|
|
305
357
|
case "role_cap_resumed":
|
|
306
|
-
return `role ${textOr(e.role)} resumed — it is under its daily cap again and
|
|
358
|
+
return `role ${textOr(e.role)} resumed — it is under its daily cap again and runs again`;
|
|
359
|
+
case "main_red_hold":
|
|
360
|
+
// The message already names the SHA, script, and consequence; the problem tone comes
|
|
361
|
+
// from PROBLEM_EVENTS.
|
|
362
|
+
return textOr(e.message);
|
|
307
363
|
case "maintenance_quota_hold":
|
|
308
364
|
// Routine state change, like role_cap_paused — no warning prefix: the hold IS the harness
|
|
309
|
-
// keeping maintenance
|
|
365
|
+
// keeping maintenance merges in proportion to work. Names the window's counts so the
|
|
310
366
|
// operator can see why the hygiene loops stopped and what would re-open them.
|
|
311
|
-
return `maintenance
|
|
367
|
+
return `waiting: maintenance allowance used — ${textOr(e.maint)} maintenance merges against an allowance of ${textOr(e.allowance)} in the last 24h (${textOr(e.work)} work); the maintenance roles wait until the window rolls over`;
|
|
312
368
|
case "maintenance_quota_resumed":
|
|
313
|
-
return `maintenance
|
|
369
|
+
return `maintenance allowance reopened — ${textOr(e.maint)} maintenance merges against an allowance of ${textOr(e.allowance)} in the last 24h; the maintenance roles run again`;
|
|
314
370
|
case "bootstrap_complete":
|
|
315
|
-
// Routine state change, like counters_reset — no warning prefix: the
|
|
371
|
+
// Routine state change, like counters_reset — no warning prefix: the team just finished
|
|
316
372
|
// its bootstrap and the maintenance loops are admitted.
|
|
317
|
-
return `bootstrap complete — ${textOr(e.plansDone)}/${textOr(e.untilPlansDone)} plans done; the maintenance
|
|
373
|
+
return `bootstrap complete — ${textOr(e.plansDone)}/${textOr(e.untilPlansDone)} plans done; the maintenance roles now run`;
|
|
318
374
|
case "disk_low":
|
|
319
|
-
// Routine state change, like
|
|
375
|
+
// Routine state change, like team_paused — no warning prefix: the hold IS the harness
|
|
320
376
|
// handling the low disk, and naming the free space and the floor tells the operator
|
|
321
377
|
// whether to clear space or lower diskHoldGB in tumwater.json.
|
|
322
|
-
return `
|
|
378
|
+
return `waiting: low disk — ${gigabytes(e.freeGB)} free on the workspaces volume (floor ${gigabytes(e.holdGB)}); new work waits until space recovers`;
|
|
379
|
+
case "quiet_hours_started": {
|
|
380
|
+
// Routine hold, like disk_low — no warning prefix: the window is configured, and the line
|
|
381
|
+
// says what stops and what keeps running. pollQuietHoursGate may send no window.
|
|
382
|
+
return `waiting: quiet hours${windowSuffix(e)} — roles start no new runs until the window ends; the director still runs`;
|
|
383
|
+
}
|
|
384
|
+
case "quiet_hours_ended": {
|
|
385
|
+
return `quiet hours ended${windowSuffix(e)} — roles start runs again`;
|
|
386
|
+
}
|
|
323
387
|
case "disk_ok":
|
|
324
|
-
return `disk recovered — ${gigabytes(e.freeGB)} free on the
|
|
388
|
+
return `disk recovered — ${gigabytes(e.freeGB)} free on the workspaces volume; new work starts again`;
|
|
325
389
|
case "disk_reclaim": {
|
|
326
390
|
// Routine maintenance, no warning prefix: the pass freed space so the hold need not
|
|
327
391
|
// engage. Naming the mode, the worktrees, and the delta tells the operator what and why.
|
|
@@ -330,38 +394,56 @@ export function eventMessage(e) {
|
|
|
330
394
|
return `disk reclaim (${textOr(e.mode, "pressure")}) — freed ${gigabytes(e.freedGB)} from ${from}; ${gigabytes(e.freeGB)} free now`;
|
|
331
395
|
}
|
|
332
396
|
case "rate_limit_hold": {
|
|
333
|
-
// Routine state change, like
|
|
334
|
-
// handling the storm. Names who saw the failure and when the
|
|
397
|
+
// Routine state change, like team_paused — no warning prefix: the hold IS the harness
|
|
398
|
+
// handling the storm. Names who saw the failure and when the team re-opens on its own.
|
|
335
399
|
// The rate-limit kind keeps the wording every historical event has; a backend-failure
|
|
336
400
|
// kind names itself instead, since "429" would be a lie about a connection error.
|
|
337
401
|
const roles = rolesPhrase(e.roles, "several roles");
|
|
338
402
|
if (e.kind && e.kind !== "rate-limit")
|
|
339
|
-
return `backend
|
|
340
|
-
return `
|
|
403
|
+
return `waiting: backend failing (${backendKindPhrase(e.kind)})${providerAt(e)} — ${roles} hit backend failures; roles on ${providerScope(e)} start no new runs ${holdPhrase(e.holdMs, e.escalation)} (director keeps running)`;
|
|
404
|
+
return `waiting: provider limit${providerAt(e)} — ${roles} rate-limited by the provider; roles on ${providerScope(e)} start no new runs ${holdPhrase(e.holdMs, e.escalation)} (director keeps running)`;
|
|
341
405
|
}
|
|
342
406
|
case "rate_limit_resumed": {
|
|
343
|
-
// The ended hold's kind rides the resumed event (
|
|
407
|
+
// The ended hold's kind rides the resumed event (pollTeamHold logs it), so the lift
|
|
344
408
|
// names what actually ended — the same split as the hold line above: "429 hold lifted"
|
|
345
409
|
// after a connection-error hold would be a lie about a connection error.
|
|
346
410
|
if (e.kind && e.kind !== "rate-limit")
|
|
347
|
-
return `backend
|
|
348
|
-
return `
|
|
411
|
+
return `backend recovered (${backendKindPhrase(e.kind)})${providerAt(e)} — roles on ${providerScope(e)} run again`;
|
|
412
|
+
return `provider limit lifted${providerAt(e)} — roles on ${providerScope(e)} run again`;
|
|
349
413
|
}
|
|
350
414
|
case "claude_window_warning":
|
|
351
415
|
// Routine-with-explanation, like rate_limit_hold — no warning prefix: the account is
|
|
352
416
|
// telling us the window is filling, not failing. It names the window and when it resets.
|
|
353
|
-
return `claude subscription window ${textOr(e.windowType)} near its limit${windowResetPhrase(e)} — subscription claude
|
|
417
|
+
return `claude subscription window ${textOr(e.windowType)} near its limit${windowResetPhrase(e)} — subscription claude model jobs keep running`;
|
|
418
|
+
case "claude_window_reserved": {
|
|
419
|
+
const used = finiteNumber(e.utilization, null);
|
|
420
|
+
const line = finiteNumber(e.reserve, null);
|
|
421
|
+
const pct = (f) => (f === null ? "?" : `${Math.round(f * 100)}%`);
|
|
422
|
+
return `claude subscription window ${textOr(e.windowType)} at ${pct(used)}, past its ${pct(line)} reserve${windowResetPhrase(e)} — the team leaves it to you (director keeps running)`;
|
|
423
|
+
}
|
|
354
424
|
case "claude_window_limited":
|
|
355
425
|
// Routine-with-explanation, like rate_limit_hold — no warning prefix: waiting is the
|
|
356
426
|
// harness handling the account's own throttle. Names the window, the reset, and which
|
|
357
427
|
// seams stop, so the operator knows why claude held and what keeps running.
|
|
358
|
-
|
|
428
|
+
// With a fallback the work moves rather than waits: `tiers` names where each moved tier's
|
|
429
|
+
// work went (the budget poll's window move), the director and any tier with no fallback
|
|
430
|
+
// still wait.
|
|
431
|
+
{
|
|
432
|
+
const moved = fallbackTierEntries(isJsonObject(e.tiers) ? e.tiers : undefined);
|
|
433
|
+
if (moved.length > 0)
|
|
434
|
+
return `claude subscription window ${textOr(e.windowType)} rejected — claude model jobs move to ${moved.join(", ")}${windowResetPhrase(e)} (director keeps waiting)`;
|
|
435
|
+
}
|
|
436
|
+
return `waiting: claude subscription window ${textOr(e.windowType)} rejected — claude model jobs wait${windowResetPhrase(e)} (director keeps running)`;
|
|
359
437
|
case "claude_window_reset":
|
|
360
|
-
return `claude subscription window reset — claude
|
|
438
|
+
return `claude subscription window reset — claude model jobs run again`;
|
|
361
439
|
case "max_concurrent_changed": {
|
|
362
440
|
// Routine state change, like counters_reset — no warning prefix.
|
|
363
441
|
return `maxConcurrent changed: ${textOr(e.from)} → ${textOr(e.to)}`;
|
|
364
442
|
}
|
|
443
|
+
case "provider_concurrency_changed": {
|
|
444
|
+
// Routine state change, its maxConcurrent sibling's per-provider form — no warning prefix.
|
|
445
|
+
return `concurrency for ${textOr(e.provider)} changed: ${textOr(e.from)} → ${textOr(e.to)}`;
|
|
446
|
+
}
|
|
365
447
|
case "retention_changed": {
|
|
366
448
|
// Routine state change, like its maxConcurrent sibling — no warning prefix.
|
|
367
449
|
return `sessionRetentionDays changed: ${textOr(e.from)} → ${textOr(e.to)}`;
|
|
@@ -376,53 +458,95 @@ export function eventMessage(e) {
|
|
|
376
458
|
// The live-edit sibling of config_changed: names the new selector, the one fact the key
|
|
377
459
|
// list cannot show. The per-role diffs stay structured on the event.
|
|
378
460
|
return `model changed — now ${textOr(e.to, "pi's default")}`;
|
|
461
|
+
case "backend_binary":
|
|
462
|
+
// The binary check for a newly routed backend: found is the routine verdict; a missing
|
|
463
|
+
// binary is the warning.
|
|
464
|
+
return e.found === false
|
|
465
|
+
? `warning: newly routed ${textOr(e.agent)} backend — its binary was not found`
|
|
466
|
+
: `newly routed ${textOr(e.agent)} backend checked — binary found`;
|
|
379
467
|
case "model_fallback_started":
|
|
380
468
|
// Routine-with-explanation, like rate_limit_hold: the switch IS the harness handling the
|
|
381
469
|
// failure. Names the pair the role now runs on and why the primary was abandoned.
|
|
382
|
-
return `model fallback — ${
|
|
470
|
+
return `model fallback — ${providerModelPair(e, "pi's default", "pi's default")} engaged (primary failing: ${textOr(e.reason, "backend failure")})`;
|
|
383
471
|
case "model_fallback_ended":
|
|
384
472
|
// The return counterpart: names the pair ticks resume on and how long the episode ran.
|
|
385
|
-
return `model fallback ended — back on ${
|
|
473
|
+
return `model fallback ended — back on ${providerModelPair(e, "pi's default", "pi's default")} after ${minutes(e.durationMs)}m`;
|
|
474
|
+
case "opencode_free_model_billed":
|
|
475
|
+
// The promotion ended (plans/opencode-backend.md §7, part 6c/7): a run billed what the
|
|
476
|
+
// catalog called free, so the team now prices that model as paid from here on. No warning
|
|
477
|
+
// prefix — the demotion is the harness handling the spend — but it names the model and the
|
|
478
|
+
// cost so the operator sees why a badge stopped saying "cost n/a".
|
|
479
|
+
return `opencode ${textOr(e.model, "model")} billed ${usd(finiteNumber(e.costUsd, 0))} — demoted from free to priced`;
|
|
480
|
+
case "image_blind": {
|
|
481
|
+
// Routine-with-explanation like the model-fallback events — no warning prefix: the
|
|
482
|
+
// run was stopped (or a gate run noted) because the model cannot see the image. Names
|
|
483
|
+
// the file, the model, and whether the tick failed on it or a gate run only logged it.
|
|
484
|
+
const action = e.action === "logged"
|
|
485
|
+
? " (gate run logged)"
|
|
486
|
+
: e.action === "fail"
|
|
487
|
+
? " — run failed"
|
|
488
|
+
: "";
|
|
489
|
+
return `blind image read: ${textOr(e.path)} on ${textOr(e.model, "the model")}${action}`;
|
|
490
|
+
}
|
|
491
|
+
case "image_reroute":
|
|
492
|
+
// The tick's work moves to a model that can read the image (plans/image-input.md part
|
|
493
|
+
// 4/5). Routine-with-explanation — no warning prefix: names the hop the way the
|
|
494
|
+
// handoff reads, and whether the traits-of-attachment preflight avoided a failed tick.
|
|
495
|
+
return e.preflight === true
|
|
496
|
+
? `image attachment routed to ${textOr(e.to, "the target")} (${textOr(e.tier, "a peer tier")}) to read its image — no failed run`
|
|
497
|
+
: `image rerouted to ${textOr(e.to, "the target")} (${textOr(e.tier, "a peer tier")}) — next run goes there after a blind read on ${textOr(e.from, "the prior model")}`;
|
|
386
498
|
case "build_stale":
|
|
387
|
-
// Self-hosting
|
|
499
|
+
// Self-hosting teams only (src/redeploy/redeploy.ts): the code main describes is not the
|
|
388
500
|
// code running. Not a warning prefix — a stale build is a state, and auto-restart resolves
|
|
389
501
|
// it.
|
|
390
|
-
return `
|
|
502
|
+
return `update available (${plural(finiteNumber(e.aheadCommits, 0), "commit")} behind) — restarts when idle`;
|
|
391
503
|
case "restart_pending":
|
|
392
|
-
return `restart pending — main ${shortSha(e.head)} is green;
|
|
504
|
+
return `restart pending — main ${shortSha(e.head)} is green; finishing current runs (no new runs start)`;
|
|
393
505
|
case "restart": {
|
|
394
|
-
const aborted = finiteNumber(e.
|
|
395
|
-
return `restarting onto
|
|
506
|
+
const aborted = finiteNumber(e.abortedRuns, 0);
|
|
507
|
+
return `restarting onto update ${shortSha(e.to)} (finished current work in ${minutes(e.finishedMs)}m${aborted > 0 ? `, ${aborted} run(s) resume after the restart` : ""})`;
|
|
396
508
|
}
|
|
397
509
|
case "restart_refused":
|
|
398
510
|
// The restart is refused, not failed: the running build stays and the gate is re-asked
|
|
399
511
|
// every poll, so the line names both builds and what the operator must repair.
|
|
400
|
-
return `restart onto
|
|
512
|
+
return `restart onto update ${shortSha(e.to)} refused — the new version could not start here: ${textOr(e.reason)}; staying on version ${shortSha(e.from)} until it can`;
|
|
401
513
|
case "restart_blocked":
|
|
402
514
|
// The restart is blocked, not refused: a verdict latched this head, so the line names
|
|
403
515
|
// what failed and that only main moving can end it.
|
|
404
|
-
return `restart blocked for main ${shortSha(e.to)} — ${textOr(e.reason)}; staying on
|
|
516
|
+
return `restart blocked for main ${shortSha(e.to)} — ${textOr(e.reason)}; staying on version ${shortSha(e.from)} until main moves`;
|
|
517
|
+
case "restart_forced":
|
|
518
|
+
// The operator's own override: names the running build, the one fact the type code lacks.
|
|
519
|
+
return `restart forced by you — the update skips its cooldown (running version ${shortSha(e.build)})`;
|
|
405
520
|
case "supervisor_exit": {
|
|
406
|
-
// The
|
|
521
|
+
// The team is DOWN and nothing will bring it back: the one line that must say so, since
|
|
407
522
|
// the dead generation's own stderr reached only the supervisor's terminal.
|
|
408
523
|
const how = e.signal ? `was killed by ${textOr(e.signal)}` : `exited ${textOr(e.code)}`;
|
|
409
|
-
return `
|
|
524
|
+
return `tumwater is down — restart ${textOr(e.generation)} ${how}${e.reason ? `: ${textOr(e.reason)}` : ""}; start it again with \`tumwater start\``;
|
|
410
525
|
}
|
|
411
526
|
case "resume":
|
|
412
527
|
// Two causes share the resume machinery; the line names the real one so an operator
|
|
413
528
|
// reading the feed can tell a restart from a loop fighting the context ceiling, and a
|
|
414
529
|
// cross-agent handoff from either — the handoff keeps the worktree but starts fresh.
|
|
415
530
|
return e.cause === "cut-off"
|
|
416
|
-
? "resuming the run cut off at the context ceiling (compacted pi session, same
|
|
531
|
+
? "resuming the run cut off at the context ceiling (compacted pi session, same workspace)"
|
|
417
532
|
: e.cause === "handoff"
|
|
418
|
-
? "handing the interrupted
|
|
419
|
-
:
|
|
533
|
+
? "handing the interrupted run off to a fresh session on another agent (same workspace, uncommitted edits kept)"
|
|
534
|
+
: e.cause === "image-reroute"
|
|
535
|
+
? "handing the blind-read run off to a fresh session on an image-capable model (same workspace, uncommitted edits kept)"
|
|
536
|
+
: "resuming the run a shutdown interrupted (same agent session and workspace)";
|
|
420
537
|
case "slot_wait": {
|
|
421
538
|
// Routine-with-explanation, like rate_limit_hold — no warning prefix: waiting is the pool
|
|
422
539
|
// working as designed. Names the wait, the pool it waited on, and how many slots are
|
|
423
540
|
// pinned, so the operator can raise worktreeSlots with the numbers in hand.
|
|
424
|
-
const purpose = e.purpose === "
|
|
425
|
-
return `waited ${shortSpanPhrase(finiteNumber(e.waitedMs, 0))}
|
|
541
|
+
const purpose = e.purpose === "verify" ? "check" : "run";
|
|
542
|
+
return `waited ${shortSpanPhrase(finiteNumber(e.waitedMs, 0))} to start (${textOr(e.role)} ${purpose}; ${textOr(e.slots)} workspaces, ${textOr(e.pinned)} pinned)`;
|
|
543
|
+
}
|
|
544
|
+
case "provider_wait": {
|
|
545
|
+
// Routine-with-explanation, like slot_wait: waiting for a provider/team slot is the cap
|
|
546
|
+
// working as designed, so no warning prefix. Names the wait and the key (or the run cap
|
|
547
|
+
// when the seam has none), so the operator can raise maxConcurrentPerProvider with the
|
|
548
|
+
// numbers in hand.
|
|
549
|
+
return `waited ${shortSpanPhrase(finiteNumber(e.waitedMs, 0))} for a ${typeof e.key === "string" && e.key.length > 0 ? `${e.key} slot` : "run slot"} (${textOr(e.role)})`;
|
|
426
550
|
}
|
|
427
551
|
case "claim":
|
|
428
552
|
// Parallel work instances (plans/parallel-work-instances.md "Observability", part 6/7):
|
|
@@ -434,7 +558,7 @@ export function eventMessage(e) {
|
|
|
434
558
|
// Priority headroom (part 7/7): a reserve appearing or vanishing is the scheduler working
|
|
435
559
|
// as designed; it names the permits withheld for the work tier so an operator can see why
|
|
436
560
|
// maintenance concurrency dipped.
|
|
437
|
-
return `${finiteNumber(e.reserve, 0)} permit(s) reserved for work
|
|
561
|
+
return `${finiteNumber(e.reserve, 0)} permit(s) reserved for work roles that have work to take`;
|
|
438
562
|
case "warning":
|
|
439
563
|
return `warning: ${textOr(e.message)}`;
|
|
440
564
|
default:
|