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
|
@@ -3,6 +3,7 @@ import { shortSha, usd } from "../text/format.js";
|
|
|
3
3
|
import { dayKey, dayLabel, formatTime, monthDay, reportWindow, sinceWindowLine } from "../text/datetime.js";
|
|
4
4
|
import { durationLabel } from "../cli/cli-args.js";
|
|
5
5
|
import { eventsRotationLabel } from "../events/events.js";
|
|
6
|
+
import { eventTypeLabel } from "../events/event-labels.js";
|
|
6
7
|
import { markdownTable } from "../text/markdown.js";
|
|
7
8
|
/** A cluster's role list shows at most this many names before a "+N more" remainder. */
|
|
8
9
|
const ROLES_SHOWN = 4;
|
|
@@ -28,7 +29,7 @@ const RESULT_ORDER = {
|
|
|
28
29
|
};
|
|
29
30
|
/** The digest table's columns: every TickResult that at least one role logged in the window, ranked
|
|
30
31
|
* by RESULT_ORDER (ties alphabetical), so the table's shape follows the window's evidence instead
|
|
31
|
-
* of the whole fixed vocabulary — a
|
|
32
|
+
* of the whole fixed vocabulary — a team with no errors gets no error column. */
|
|
32
33
|
function columns(outcomes) {
|
|
33
34
|
const present = new Set();
|
|
34
35
|
for (const row of outcomes) {
|
|
@@ -37,20 +38,20 @@ function columns(outcomes) {
|
|
|
37
38
|
}
|
|
38
39
|
return [...present].sort((a, b) => RESULT_ORDER[a] - RESULT_ORDER[b] || a.localeCompare(b));
|
|
39
40
|
}
|
|
40
|
-
/** A delta cell: `prev → cur`, with "—" on a side where the role had no
|
|
41
|
+
/** A delta cell: `prev → cur`, with "—" on a side where the role had no runs (the "new role"
|
|
41
42
|
* case for the preceding window), so an absence is never rendered as an infinite increase.
|
|
42
43
|
* The displayed halves may be raw counts (stringified here) or pre-rendered rate strings —
|
|
43
44
|
* the tick-count halves exist only to decide the "—", not to be printed. */
|
|
44
|
-
function deltaCell(
|
|
45
|
-
return `${
|
|
45
|
+
function deltaCell(prevRuns, prev, runs, cur) {
|
|
46
|
+
return `${prevRuns === 0 ? "—" : prev} → ${runs === 0 ? "—" : cur}`;
|
|
46
47
|
}
|
|
47
48
|
/** The rejections column's cell. It cannot reuse deltaCell: a rejection is logged by the
|
|
48
|
-
*
|
|
49
|
+
* merge slot, so its authoring run_end can fall in the other window — a role with a
|
|
49
50
|
* rejection must show it even when its tick count is 0. Presence is judged on the rejection
|
|
50
|
-
* count itself (the role is "absent" from the column only with neither
|
|
51
|
-
function rejectionCell(
|
|
52
|
-
const prev =
|
|
53
|
-
const cur =
|
|
51
|
+
* count itself (the role is "absent" from the column only with neither runs nor rejections). */
|
|
52
|
+
function rejectionCell(prevRuns, prevRejections, runs, rejections) {
|
|
53
|
+
const prev = prevRuns === 0 && prevRejections === 0 ? "—" : String(prevRejections);
|
|
54
|
+
const cur = runs === 0 && rejections === 0 ? "—" : String(rejections);
|
|
54
55
|
return `${prev} → ${cur}`;
|
|
55
56
|
}
|
|
56
57
|
/** A ratio as a whole percent — but whole-percent rounding must not erase a real
|
|
@@ -62,29 +63,35 @@ function rejectionCell(prevTicks, prevRejections, ticks, rejections) {
|
|
|
62
63
|
function percent(part, whole) {
|
|
63
64
|
const pct = (100 * part) / whole;
|
|
64
65
|
const rounded = Math.round(pct);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return
|
|
72
|
-
}
|
|
66
|
+
// A nonzero ratio at the 0/100 edges must not round away to "0%"/"100%": both arms keep
|
|
67
|
+
// one decimal the same way, so they share one helper — the two calls differ only in the
|
|
68
|
+
// edge they guard ("0.0"/"<0.1%" vs "100.0"/">99.9%").
|
|
69
|
+
if (rounded === 0 && part > 0)
|
|
70
|
+
return edgePercent(pct, "0.0", "<0.1%");
|
|
71
|
+
if (rounded === 100 && part < whole)
|
|
72
|
+
return edgePercent(pct, "100.0", ">99.9%");
|
|
73
73
|
return `${rounded}%`;
|
|
74
74
|
}
|
|
75
|
-
/**
|
|
75
|
+
/** A near-edge one-decimal percent: `${n.d}%`, or the `edge` literal when even one decimal
|
|
76
|
+
* rounds back to the edge (`"0.0"` → "<0.1%", `"100.0"` → ">99.9%"). Shared by percent's two
|
|
77
|
+
* boundary windows so the formula cannot drift between them. */
|
|
78
|
+
function edgePercent(pct, edgeWhole, edge) {
|
|
79
|
+
const oneDecimal = pct.toFixed(1);
|
|
80
|
+
return oneDecimal === edgeWhole ? edge : `${oneDecimal}%`;
|
|
81
|
+
}
|
|
82
|
+
/** An error rate as a whole percent, or "—" when the role ran no runs (an absence is not
|
|
76
83
|
* a 0% rate, for the same reason deltaCell says "—"). */
|
|
77
|
-
function rate(errors,
|
|
78
|
-
if (
|
|
84
|
+
function rate(errors, runs) {
|
|
85
|
+
if (runs === 0)
|
|
79
86
|
return "—";
|
|
80
|
-
return percent(errors,
|
|
87
|
+
return percent(errors, runs);
|
|
81
88
|
}
|
|
82
89
|
/** One time-and-spend cell: `x.x h · $y.yy`, or "—" when the role ended no tick on that
|
|
83
90
|
* class — an absence is not a zero cost, for the same reason the Deltas cells say "—".
|
|
84
91
|
* Hours carry one decimal: the digest prices agent-hours, and whole-hour rounding would
|
|
85
92
|
* erase the difference between ten 200-ms errors and one 30-minute timeout. */
|
|
86
93
|
function spendCell(cell) {
|
|
87
|
-
if (cell.
|
|
94
|
+
if (cell.runs === 0)
|
|
88
95
|
return "—";
|
|
89
96
|
return `${hoursPhrase(cell.ms)} · ${usd(cell.costUsd)}`;
|
|
90
97
|
}
|
|
@@ -94,19 +101,20 @@ function spendCell(cell) {
|
|
|
94
101
|
function hoursPhrase(ms) {
|
|
95
102
|
return `${(ms / 3_600_000).toFixed(1)} h`;
|
|
96
103
|
}
|
|
97
|
-
/** Render the digest as bounded Markdown. Byte bound: for a given
|
|
104
|
+
/** Render the digest as bounded Markdown. Byte bound: for a given team the tables grow only with
|
|
98
105
|
* the number of configured roles (fixed by config), the RESULT_ORDER vocabulary (fixed by
|
|
99
106
|
* src/tick/tick-outcome.ts), and the time-and-spend section's LOSS_TOP loss-cause lines (fixed by
|
|
100
|
-
* the cut),
|
|
107
|
+
* the cut), the merge-failure section's MERGE_FAILURE_TOP clusters (src/failure/failure-data.ts),
|
|
108
|
+
* and every free string is capped — cluster examples at 120 chars (plus a `… (+N
|
|
101
109
|
* chars)` cut marker when truncated, so a marked cut can never read as a complete message —
|
|
102
|
-
* BUGS.md 2026-09-30),
|
|
110
|
+
* BUGS.md 2026-09-30), merged summaries at 100 under the same rule, a cluster's role list at 4
|
|
103
111
|
* names plus a remainder count, and any loop id sliced to 32 chars (config validation already
|
|
104
112
|
* refuses longer custom-loop ids, so the slice is a guard rather than the real bound). Cluster
|
|
105
|
-
* counts are capped at top-N, and the
|
|
113
|
+
* counts are capped at top-N, and the Team state changes section is capped at STATE_CHANGE_TOP
|
|
106
114
|
* lines with each payload at STATE_CHANGE_MAX and each free field at STATE_CHANGE_FIELD_MAX; the
|
|
107
|
-
*
|
|
115
|
+
* merged list at MERGED_TOP lines, each capped section closing with one remainder line when its cut
|
|
108
116
|
* dropped anything. Nothing here grows with how bad the window was: measured 6,017 bytes at the
|
|
109
|
-
* CLI's default 14 days on the live
|
|
117
|
+
* CLI's default 14 days on the live team, and the worst-case byte-bound fixture in
|
|
110
118
|
* test/failure-render.test.ts covers transition events too. Pure function of FailureReportData: no
|
|
111
119
|
* I/O, no clock reads. */
|
|
112
120
|
export function renderFailureMarkdown(data) {
|
|
@@ -117,8 +125,8 @@ export function renderFailureMarkdown(data) {
|
|
|
117
125
|
// instant span (the wording renderSinceReportMarkdown uses); the day shape keeps its
|
|
118
126
|
// day-window line. The empty branch follows the same split so neither claims the other's unit.
|
|
119
127
|
lines.push(data.sinceMs !== undefined
|
|
120
|
-
? `${sinceWindowLine(durationLabel(data.sinceMs), data.fromIso)} · ${data.
|
|
121
|
-
: `${reportWindow(data.from, data.to, data.days, eventsRotationLabel())} · ${data.
|
|
128
|
+
? `${sinceWindowLine(durationLabel(data.sinceMs), data.fromIso)} · ${data.runs} runs`
|
|
129
|
+
: `${reportWindow(data.from, data.to, data.days, eventsRotationLabel())} · ${data.runs} runs`);
|
|
122
130
|
if (data.role !== undefined)
|
|
123
131
|
lines.push(`role: ${data.role}`);
|
|
124
132
|
if (data.logState === "unreadable")
|
|
@@ -132,15 +140,15 @@ export function renderFailureMarkdown(data) {
|
|
|
132
140
|
}
|
|
133
141
|
else if (data.partial)
|
|
134
142
|
lines.push(`partial: retained log starts ${data.oldestEventDate ?? "?"}`);
|
|
135
|
-
// The causal frame before the counts: what the
|
|
143
|
+
// The causal frame before the counts: what the team DID in the window, so the reader can ask
|
|
136
144
|
// whether the harness's response was right. Omitted entirely when the window held none. When
|
|
137
145
|
// the newest-N cap cut the window's transitions, the remainder is named — a quiet window (no
|
|
138
146
|
// line) and a truncated one must not look the same.
|
|
139
147
|
if (data.stateChanges.length > 0) {
|
|
140
148
|
lines.push("");
|
|
141
149
|
// A scoped digest retitles the section: its transitions are the named role's, so the
|
|
142
|
-
//
|
|
143
|
-
lines.push(data.role !== undefined ? "## State changes" : "##
|
|
150
|
+
// team-wide "Team" claim would overreach.
|
|
151
|
+
lines.push(data.role !== undefined ? "## State changes" : "## Team state changes");
|
|
144
152
|
for (const s of data.stateChanges) {
|
|
145
153
|
lines.push(`- ${stateChangeStamp(s.ts)} ${roleCell(s.role)} — ${s.description}`);
|
|
146
154
|
}
|
|
@@ -152,18 +160,18 @@ export function renderFailureMarkdown(data) {
|
|
|
152
160
|
lines.push("");
|
|
153
161
|
lines.push("## Outcome by role");
|
|
154
162
|
if (data.outcomes.length === 0) {
|
|
155
|
-
lines.push("_no
|
|
163
|
+
lines.push("_no finished runs in the window_");
|
|
156
164
|
}
|
|
157
165
|
else {
|
|
158
166
|
// markdownTable derives the separator from the header, so the two rows cannot disagree on
|
|
159
167
|
// their cell count (the BUGS.md 2026-09-21 plain-text collapse). `---:` right-aligns the
|
|
160
168
|
// counts.
|
|
161
|
-
lines.push(...markdownTable(["role", ...cols], data.outcomes.map((row) => [
|
|
169
|
+
lines.push(...markdownTable(["role", ...cols.map(eventTypeLabel)], data.outcomes.map((row) => [
|
|
162
170
|
roleCell(row.role),
|
|
163
171
|
...cols.map((c) => String(row.counts[c] ?? 0)),
|
|
164
172
|
]), ["left", ...cols.map(() => "right")]));
|
|
165
173
|
}
|
|
166
|
-
// The same
|
|
174
|
+
// The same runs priced by what they cost, so a 30-minute timeout outranks ten 200-ms
|
|
167
175
|
// errors that the Outcome table above weighs equally. Two parts: the role × class table of
|
|
168
176
|
// hours and dollars, then the top loss causes by time (an error cluster, a role's
|
|
169
177
|
// no_change total — the quiet loss no cluster names, a review-rejected authoring span, or a
|
|
@@ -172,12 +180,12 @@ export function renderFailureMarkdown(data) {
|
|
|
172
180
|
lines.push("");
|
|
173
181
|
lines.push("## Time and spend by outcome");
|
|
174
182
|
if (data.timeSpend.length === 0) {
|
|
175
|
-
lines.push("_no
|
|
183
|
+
lines.push("_no finished runs in the window_");
|
|
176
184
|
}
|
|
177
185
|
else {
|
|
178
|
-
lines.push(...markdownTable(["role", "
|
|
186
|
+
lines.push(...markdownTable(["role", "merged", "no change", "error or blocked"], data.timeSpend.map((row) => [
|
|
179
187
|
roleCell(row.role),
|
|
180
|
-
spendCell(row.classes.
|
|
188
|
+
spendCell(row.classes.merged),
|
|
181
189
|
spendCell(row.classes.no_change),
|
|
182
190
|
spendCell(row.classes.error),
|
|
183
191
|
])));
|
|
@@ -189,13 +197,15 @@ export function renderFailureMarkdown(data) {
|
|
|
189
197
|
else {
|
|
190
198
|
for (const c of data.lossCauses) {
|
|
191
199
|
const cause = c.kind === "no_change"
|
|
192
|
-
? `
|
|
200
|
+
? `no change on ${roleCell(c.roles[0] ?? "?")}`
|
|
193
201
|
: c.kind === "review-rejected"
|
|
194
202
|
? `review-rejected authoring on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
195
|
-
: c.kind === "
|
|
196
|
-
?
|
|
197
|
-
:
|
|
198
|
-
|
|
203
|
+
: c.kind === "refused"
|
|
204
|
+
? `refused on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
205
|
+
: c.kind === "merge-conflict" || c.kind === "merge-blocked"
|
|
206
|
+
? `${c.kind} authoring on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
207
|
+
: `${c.example} (${roleList(c.roles)})`;
|
|
208
|
+
lines.push(`- ${hoursPhrase(c.ms)} · ${usd(c.costUsd)} — ${plural(c.runs, "run")}: ${cause}`);
|
|
199
209
|
}
|
|
200
210
|
// Mark the top-5 cut the way the cluster sections mark theirs: a capped ranking that
|
|
201
211
|
// stays silent reads as a complete itemization of the window's loss (BUGS.md 2026-10-01).
|
|
@@ -207,49 +217,50 @@ export function renderFailureMarkdown(data) {
|
|
|
207
217
|
}
|
|
208
218
|
// Prompt-token telemetry: the per-role median prompt
|
|
209
219
|
// send and how much of the prompt budget goes out before a tick's first edit. Omitted
|
|
210
|
-
// entirely when no
|
|
220
|
+
// entirely when no run_end in the window carries the fields, so old logs and their golden
|
|
211
221
|
// digests render unchanged.
|
|
212
222
|
if (data.promptStats.length > 0) {
|
|
213
223
|
lines.push("");
|
|
214
224
|
lines.push("## Prompt tokens by role");
|
|
215
225
|
for (const row of data.promptStats) {
|
|
216
226
|
const share = percent(row.preEditShare, 1);
|
|
217
|
-
lines.push(`- ${roleCell(row.role)}: median ${row.medianPromptTokens} prompt tokens/
|
|
227
|
+
lines.push(`- ${roleCell(row.role)}: median ${row.medianPromptTokens} prompt tokens/run · ${share} before first edit`);
|
|
218
228
|
}
|
|
219
229
|
}
|
|
220
230
|
lines.push("");
|
|
221
231
|
lines.push(`## Deltas vs the preceding ${data.sinceMs !== undefined ? durationLabel(data.sinceMs) : dayLabel(data.days)}`);
|
|
222
232
|
if (data.deltas.length === 0) {
|
|
223
|
-
lines.push("_no
|
|
233
|
+
lines.push("_no runs in either window_");
|
|
224
234
|
}
|
|
225
235
|
else {
|
|
226
|
-
lines.push(...markdownTable(["role", "
|
|
236
|
+
lines.push(...markdownTable(["role", "runs", "error rate", "stalls", "rejections"], data.deltas.map((d) => [
|
|
227
237
|
roleCell(d.role),
|
|
228
|
-
deltaCell(d.
|
|
229
|
-
deltaCell(d.
|
|
230
|
-
deltaCell(d.
|
|
231
|
-
rejectionCell(d.
|
|
238
|
+
deltaCell(d.prevRuns, d.prevRuns, d.runs, d.runs),
|
|
239
|
+
deltaCell(d.prevRuns, rate(d.prevErrors, d.prevRuns), d.runs, rate(d.errors, d.runs)),
|
|
240
|
+
deltaCell(d.prevRuns, d.prevQuietKills, d.runs, d.quietKills),
|
|
241
|
+
rejectionCell(d.prevRuns, d.prevRejections, d.runs, d.rejections),
|
|
232
242
|
])));
|
|
233
243
|
}
|
|
234
244
|
// The title restates the section's contract: its total now covers the Outcome table's error
|
|
235
245
|
// column AND its main_red cells (the baseline gate's own cause rides the tick, BUGS.md
|
|
236
246
|
// 2026-09-28), so an operator cross-checking counts against the tables above is not misled
|
|
237
247
|
// by a title that reads as the error column alone.
|
|
238
|
-
renderClusters(lines, "Top error clusters (red-main causes included)", data.errors, "
|
|
248
|
+
renderClusters(lines, "Top error clusters (red-main causes included)", data.errors, "run errors");
|
|
239
249
|
renderClusters(lines, "Top warning clusters", data.warnings, "warnings");
|
|
240
250
|
renderClusters(lines, "Top review failure clusters", data.reviewFailures, "review failures");
|
|
241
251
|
renderClusters(lines, "Top rejection clusters", data.rejections, "rejections");
|
|
252
|
+
renderClusters(lines, "Merge failures", data.mergeFailures, "merge failures");
|
|
242
253
|
lines.push("");
|
|
243
|
-
lines.push("##
|
|
244
|
-
if (data.
|
|
254
|
+
lines.push("## Merged in the window");
|
|
255
|
+
if (data.merged.length === 0) {
|
|
245
256
|
lines.push("_nothing merged in the window_");
|
|
246
257
|
}
|
|
247
258
|
else {
|
|
248
|
-
for (const c of data.
|
|
259
|
+
for (const c of data.merged)
|
|
249
260
|
lines.push(`- ${shortSha(c.commit)} — ${c.summary}`);
|
|
250
261
|
// Like the transitions' remainder line: a cut list says so, so a busy window's newest ten
|
|
251
262
|
// are not mistaken for everything that merged.
|
|
252
|
-
const hidden = data.
|
|
263
|
+
const hidden = data.mergedTotal - data.merged.length;
|
|
253
264
|
if (hidden > 0)
|
|
254
265
|
lines.push(`+${hidden} older merges not listed`);
|
|
255
266
|
}
|
|
@@ -279,7 +290,7 @@ function renderClusters(lines, title, section, noun) {
|
|
|
279
290
|
lines.push(`_+${section.hiddenClusters} more ${clusters} holding ${remaining} ${noun1}_`);
|
|
280
291
|
}
|
|
281
292
|
}
|
|
282
|
-
/** The cluster's roles, capped so a
|
|
293
|
+
/** The cluster's roles, capped so a team-wide cluster cannot blow the byte bound; the count of
|
|
283
294
|
* the omitted remainder is kept so the cluster's reach stays legible. */
|
|
284
295
|
function roleList(roles) {
|
|
285
296
|
const shown = roles.slice(0, ROLES_SHOWN).map(roleCell).join(", ");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
/** Wording half of the failure digest's Team state changes section: which transition event types
|
|
2
3
|
* the digest replays and the bounded one-liner describing each. Split out of failure-data.ts (the
|
|
3
4
|
* same seam build/build-check-events.ts split from build/build-check.ts) so the per-event-type
|
|
4
5
|
* wording — which changes whenever the harness grows a new self-decision — stays apart from the
|
|
@@ -8,11 +9,11 @@
|
|
|
8
9
|
import { truncateExample } from "./failure-cluster.js";
|
|
9
10
|
import { cutSplitsSurrogatePair } from "../text/text.js";
|
|
10
11
|
import { finiteNumber, stringList } from "../files/json-object.js";
|
|
11
|
-
import { ORCHESTRATOR_STOPPED, backendKindPhrase, budgetPhrase,
|
|
12
|
+
import { ORCHESTRATOR_STOPPED, backendKindPhrase, budgetPhrase, teamPausePhrase, teamResumePhrase, holdPhrase, plural, rolePausePhrase, roleResumePhrase, rolesPhrase, shortSpanPhrase } from "../text/phrases.js";
|
|
12
13
|
import { shortSha } from "../text/format.js";
|
|
13
14
|
import { deferredReasonText } from "../events/event-format.js";
|
|
14
15
|
/** The transition events the digest replays: the decisions the harness made about itself (the
|
|
15
|
-
* cap/
|
|
16
|
+
* cap/team gates and the team hold, the error-streak breaker's pause, the role's own daily
|
|
16
17
|
* cap, model fallbacks, live-config edits, self-hosted redeploys, need-based deferrals,
|
|
17
18
|
* orchestrator lifecycle). They are the evidence the telemetry role's load-bearing
|
|
18
19
|
* rule asks it to judge — whether the harness's RESPONSE to a failure was wrong
|
|
@@ -21,8 +22,8 @@ export const STATE_CHANGE_TYPES = new Set([
|
|
|
21
22
|
"budget_paused",
|
|
22
23
|
"budget_fallback",
|
|
23
24
|
"budget_resumed",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
25
|
+
"team_paused",
|
|
26
|
+
"team_resumed",
|
|
26
27
|
"role_paused",
|
|
27
28
|
"role_resumed",
|
|
28
29
|
"role_streak_paused",
|
|
@@ -40,12 +41,12 @@ export const STATE_CHANGE_TYPES = new Set([
|
|
|
40
41
|
"restart",
|
|
41
42
|
"restart_refused",
|
|
42
43
|
"restart_blocked",
|
|
43
|
-
"
|
|
44
|
+
"run_deferred",
|
|
44
45
|
"orchestrator_start",
|
|
45
46
|
"orchestrator_stop",
|
|
46
47
|
"supervisor_exit",
|
|
47
48
|
]);
|
|
48
|
-
/** The
|
|
49
|
+
/** The Team state changes section's caps: newest N transitions, each line's payload capped at
|
|
49
50
|
* STATE_CHANGE_MAX (a marked word-boundary cut per truncateExample), each free field within it
|
|
50
51
|
* at STATE_CHANGE_FIELD_MAX. Together with the fixed timestamp/role cells these make the
|
|
51
52
|
* section's bytes a constant-plus-marker, so the digest's ~6 KB bound holds no matter how many
|
|
@@ -65,7 +66,13 @@ function field(v) {
|
|
|
65
66
|
cut -= 1; // Never emit a lone high surrogate.
|
|
66
67
|
return s.slice(0, cut);
|
|
67
68
|
}
|
|
68
|
-
/**
|
|
69
|
+
/** The backend a fallback-ish decision line names, as "provider/model", defaulting each half
|
|
70
|
+
* to pi's own default when the event omits it and slicing both through field. Shared by the
|
|
71
|
+
* budget_fallback and model_fallback lines so the default and its slice rule stay in one place. */
|
|
72
|
+
function providerModel(ev) {
|
|
73
|
+
return `${field(ev.provider ?? "pi default")}/${field(ev.model ?? "pi default")}`;
|
|
74
|
+
}
|
|
75
|
+
/** A compact, bounded one-liner for one harness decision event, for the Team state changes
|
|
69
76
|
* section. Every free string is sliced (field) and the whole line is capped again at
|
|
70
77
|
* STATE_CHANGE_MAX so the digest's byte bound holds for any event shape — the cap cut falls on
|
|
71
78
|
* a word boundary and carries truncateExample's `… (+N chars)` marker, so a cut line can never
|
|
@@ -78,7 +85,7 @@ export function describeStateChange(ev) {
|
|
|
78
85
|
// A breaker demotion (BUGS.md 2026-09-20) leads with its cause instead of the spend: the
|
|
79
86
|
// spend line is the budget_fallback just before it, and the line cap would cut the pair.
|
|
80
87
|
if (ev.fallbackDemoted) {
|
|
81
|
-
text = `budget paused — ${field(ev.failures ?? "?")}
|
|
88
|
+
text = `budget paused — ${field(ev.failures ?? "?")} runs failed on over-cap model ${field(ev.fallbackDemoted)}`;
|
|
82
89
|
break;
|
|
83
90
|
}
|
|
84
91
|
const refused = ev.fallbackRejected
|
|
@@ -88,16 +95,16 @@ export function describeStateChange(ev) {
|
|
|
88
95
|
break;
|
|
89
96
|
}
|
|
90
97
|
case "budget_fallback":
|
|
91
|
-
text = `
|
|
98
|
+
text = `over-cap model — ${budgetPhrase(ev.spentUsd, ev.capUsd)} daily cost reached; roles continue on ${providerModel(ev)} (cost n/a)`;
|
|
92
99
|
break;
|
|
93
100
|
case "budget_resumed":
|
|
94
101
|
text = `budget resumed (${budgetPhrase(ev.spentUsd, ev.capUsd)} today)`;
|
|
95
102
|
break;
|
|
96
|
-
case "
|
|
97
|
-
text =
|
|
103
|
+
case "team_paused":
|
|
104
|
+
text = teamPausePhrase();
|
|
98
105
|
break;
|
|
99
|
-
case "
|
|
100
|
-
text =
|
|
106
|
+
case "team_resumed":
|
|
107
|
+
text = teamResumePhrase();
|
|
101
108
|
break;
|
|
102
109
|
case "role_paused":
|
|
103
110
|
text = rolePausePhrase(field(ev.role ?? "?"));
|
|
@@ -108,10 +115,10 @@ export function describeStateChange(ev) {
|
|
|
108
115
|
case "role_streak_paused":
|
|
109
116
|
// The error-streak breaker auto-paused the role; the digest names the depth so the
|
|
110
117
|
// operator can connect the pause to the failing ticks the clusters above show.
|
|
111
|
-
text = `role ${field(ev.role ?? "?")} paused — ${field(ev.streak ?? "?")}
|
|
118
|
+
text = `role ${field(ev.role ?? "?")} paused — ${field(ev.streak ?? "?")} runs failed in a row`;
|
|
112
119
|
break;
|
|
113
120
|
case "role_cap_paused":
|
|
114
|
-
// The role reached its own daily spend cap and stopped starting
|
|
121
|
+
// The role reached its own daily spend cap and stopped starting runs.
|
|
115
122
|
text = `role ${field(ev.role ?? "?")} paused — ${budgetPhrase(ev.spentUsd, ev.capUsd)} of its daily cap spent`;
|
|
116
123
|
break;
|
|
117
124
|
case "role_cap_resumed":
|
|
@@ -119,12 +126,12 @@ export function describeStateChange(ev) {
|
|
|
119
126
|
break;
|
|
120
127
|
case "model_fallback_started":
|
|
121
128
|
// The role tripped off its primary onto its fallback after provider-class failures.
|
|
122
|
-
text = `model fallback — ${
|
|
129
|
+
text = `model fallback — ${providerModel(ev)} engaged (${field(ev.reason ?? "backend failure")})`;
|
|
123
130
|
break;
|
|
124
131
|
case "model_fallback_ended":
|
|
125
132
|
// The return counterpart: names the primary the role resumed on and how long the
|
|
126
133
|
// off-model episode ran.
|
|
127
|
-
text = `model fallback ended — back on ${
|
|
134
|
+
text = `model fallback ended — back on ${providerModel(ev)} after ${shortSpanPhrase(finiteNumber(ev.durationMs, 0))}`;
|
|
128
135
|
break;
|
|
129
136
|
case "rate_limit_hold": {
|
|
130
137
|
// Same split as the event feed's rendering (event-format.ts): a rate-limit hold keeps
|
|
@@ -142,8 +149,8 @@ export function describeStateChange(ev) {
|
|
|
142
149
|
// carries the ended hold's kind, so a backend hold's lift names itself.
|
|
143
150
|
text =
|
|
144
151
|
ev.kind && ev.kind !== "rate-limit"
|
|
145
|
-
? `backend hold lifted (${backendKindPhrase(ev.kind)}) —
|
|
146
|
-
: "429 hold lifted —
|
|
152
|
+
? `backend hold lifted (${backendKindPhrase(ev.kind)}) — roles run again`
|
|
153
|
+
: "429 hold lifted — roles run again";
|
|
147
154
|
break;
|
|
148
155
|
case "max_concurrent_changed":
|
|
149
156
|
text = `maxConcurrent ${field(ev.from)} → ${field(ev.to)}`;
|
|
@@ -171,13 +178,13 @@ export function describeStateChange(ev) {
|
|
|
171
178
|
break;
|
|
172
179
|
}
|
|
173
180
|
case "build_stale":
|
|
174
|
-
text = `
|
|
181
|
+
text = `update available (${plural(finiteNumber(ev.aheadCommits, 0), "commit")} behind) — restarts when idle`;
|
|
175
182
|
break;
|
|
176
183
|
case "restart_pending":
|
|
177
|
-
text = `restart pending — main ${shortSha(ev.head)} green
|
|
184
|
+
text = `restart pending — main ${shortSha(ev.head)} green`;
|
|
178
185
|
break;
|
|
179
186
|
case "restart":
|
|
180
|
-
text = `restarting onto
|
|
187
|
+
text = `restarting onto update ${shortSha(ev.to)}`;
|
|
181
188
|
break;
|
|
182
189
|
case "restart_refused":
|
|
183
190
|
text = `restart onto ${shortSha(ev.to)} refused: ${field(ev.reason)}`;
|
|
@@ -185,17 +192,17 @@ export function describeStateChange(ev) {
|
|
|
185
192
|
case "restart_blocked":
|
|
186
193
|
text = `restart blocked — ${field(ev.reason)}; staying on build ${shortSha(ev.from)}`;
|
|
187
194
|
break;
|
|
188
|
-
case "
|
|
195
|
+
case "run_deferred":
|
|
189
196
|
text = `deferred — ${deferredReasonText(ev)}`;
|
|
190
197
|
break;
|
|
191
198
|
case "orchestrator_start":
|
|
192
|
-
text = `
|
|
199
|
+
text = `tumwater started (pid ${field(ev.pid)}${ev.build ? `, build ${shortSha(ev.build)}` : ""})`;
|
|
193
200
|
break;
|
|
194
201
|
case "orchestrator_stop":
|
|
195
202
|
text = ORCHESTRATOR_STOPPED;
|
|
196
203
|
break;
|
|
197
204
|
case "supervisor_exit":
|
|
198
|
-
text = `
|
|
205
|
+
text = `tumwater down — restart ${field(ev.generation)} ${ev.signal ? `killed by ${field(ev.signal)}` : `exited ${field(ev.code)}`}${ev.reason ? `: ${field(ev.reason)}` : ""}`;
|
|
199
206
|
break;
|
|
200
207
|
default:
|
|
201
208
|
text = ev.type;
|