tumwater 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +42 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +4 -3
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +246 -55
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +85 -274
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +175 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +139 -32
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +9 -11
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget.js +30 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +1 -2
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +118 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +150 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +308 -590
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
- package/dist/src/cli/completion.js +149 -22
- package/dist/src/cli/config-commands.js +41 -18
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +189 -73
- package/dist/src/cli/log-commands.js +148 -82
- package/dist/src/cli/question-commands.js +205 -10
- package/dist/src/cli.js +106 -43
- package/dist/src/collections.js +26 -10
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +13 -1
- package/dist/src/config/config-fallbacks.js +93 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +141 -199
- package/dist/src/config/config-views.js +127 -53
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +131 -264
- package/dist/src/config/config.js +48 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +100 -496
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +52 -579
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +82 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +236 -402
- package/dist/src/doctor/doctor-notify.js +3 -4
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor.js +121 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +7 -11
- package/dist/src/events/event-format.js +181 -86
- package/dist/src/events/event-labels.js +12 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +39 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +60 -24
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +56 -56
- package/dist/src/failure/failure-render.js +31 -23
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +99 -35
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +177 -84
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +108 -58
- package/dist/src/gates/maintenance-quota.js +10 -13
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +77 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +36 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +42 -18
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +8 -13
- package/dist/src/gui/gui-config-commands.js +94 -72
- package/dist/src/gui/gui-endpoint-commands.js +4 -3
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +108 -13
- package/dist/src/gui/gui-prompt-commands.js +11 -14
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +11 -4
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +193 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +25 -9
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +81 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +150 -11
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/merge/merge-queue-render.js +49 -11
- package/dist/src/merge/merge-queue.js +31 -15
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +33 -11
- package/dist/src/merge/merge-to-main.js +86 -27
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +12 -10
- package/dist/src/notes-cli.js +30 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
- package/dist/src/orchestrator/orchestrator.js +128 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +116 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +18 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +121 -211
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +129 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +161 -77
- package/dist/src/report/report-fold.js +129 -52
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +133 -64
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +100 -1
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +4 -5
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +155 -360
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +69 -12
- package/dist/src/roles/role-guidance.js +55 -20
- package/dist/src/roles/role-labels.js +1 -2
- package/dist/src/roles/role-render.js +67 -50
- package/dist/src/roles/role-view.js +59 -47
- package/dist/src/roles/roles.js +31 -31
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +9 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/{loop → runner}/leftover.js +80 -25
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +43 -16
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +28 -28
- package/dist/src/status/status-roles.js +39 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +5 -6
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +63 -17
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +57 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +67 -13
- package/dist/src/ui/badges.js +65 -37
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +32 -26
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +126 -285
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +434 -67
- package/dist/src/ui/gui/gui-client-labels.js +1 -2
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +55 -51
- package/dist/src/ui/gui/gui-client-pending.js +227 -23
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +87 -427
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +56 -4
- package/dist/src/ui/gui/gui-client-team.js +25 -465
- package/dist/src/ui/gui/gui-client.js +27 -25
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +9 -7
- package/dist/src/ui/gui/gui-styles.js +34 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +136 -49
- package/dist/src/ui/status-render.js +101 -82
- package/dist/src/ui/team-alerts.js +248 -57
- package/dist/src/ui/tone.js +5 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +51 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +3 -5
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +10 -12
- package/dist/src/ui/tui/tui-editor-budget.js +35 -31
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-entry.js +36 -47
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +11 -9
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +3 -5
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
- package/dist/src/ui/tui/tui-editor-retire.js +4 -6
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +60 -34
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +35 -2
- package/dist/src/ui/tui/tui-frame.js +56 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +24 -9
- package/dist/src/ui/tui/tui-pane-activity.js +14 -11
- package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +58 -8
- package/dist/src/ui/tui/tui-pane-history.js +402 -82
- package/dist/src/ui/tui/tui-pane-pending.js +308 -13
- package/dist/src/ui/tui/tui-pane-queued.js +71 -55
- package/dist/src/ui/tui/tui-pane-report.js +46 -14
- package/dist/src/ui/tui/tui-pane-session.js +25 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +80 -23
- package/dist/src/ui/tui/tui-panes.js +100 -30
- package/dist/src/ui/tui/tui.js +14 -14
- package/dist/src/verdict/fix-claim.js +82 -32
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +42 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -11,10 +11,10 @@ import { canonicalRoleId } from "../roles/legacy-role-ids.js";
|
|
|
11
11
|
* here would show `dedupe` while the role is still `dry`. */
|
|
12
12
|
/** An old event type name → today's name. */
|
|
13
13
|
const LEGACY_EVENT_TYPES = {
|
|
14
|
-
tick_start: "run_start", // vocabulary-ok: legacy event name read from old logs
|
|
15
|
-
tick_end: "run_end", // vocabulary-ok: legacy event name read from old logs
|
|
16
|
-
tick_deferred: "run_deferred", // vocabulary-ok: legacy event name read from old logs
|
|
17
|
-
tick_aborted: "run_aborted", // vocabulary-ok: legacy event name read from old logs
|
|
14
|
+
tick_start: "run_start", // vocabulary-ok: legacy event name read from old logs // retired-name-ok: legacy name
|
|
15
|
+
tick_end: "run_end", // vocabulary-ok: legacy event name read from old logs // retired-name-ok: legacy name
|
|
16
|
+
tick_deferred: "run_deferred", // vocabulary-ok: legacy event name read from old logs // retired-name-ok: legacy name
|
|
17
|
+
tick_aborted: "run_aborted", // vocabulary-ok: legacy event name read from old logs // retired-name-ok: legacy name
|
|
18
18
|
// retired-name-ok:start old event names
|
|
19
19
|
fleet_paused: "team_paused", // vocabulary-ok: legacy event name read from old logs
|
|
20
20
|
fleet_resumed: "team_resumed", // vocabulary-ok: legacy event name read from old logs
|
|
@@ -24,23 +24,29 @@ const LEGACY_EVENT_TYPES = {
|
|
|
24
24
|
landed: "merge_done", // vocabulary-ok: legacy event name read from old logs
|
|
25
25
|
land_failed: "merge_failed", // vocabulary-ok: legacy event name read from old logs
|
|
26
26
|
// retired-name-ok:end
|
|
27
|
+
// retired-name-ok:start old subscription-window event names
|
|
28
|
+
claude_window_reserved: "subscription_window_reserved", // vocabulary-ok: legacy event name read from old logs
|
|
29
|
+
claude_window_warning: "subscription_window_warning", // vocabulary-ok: legacy event name read from old logs
|
|
30
|
+
claude_window_limited: "subscription_window_limited", // vocabulary-ok: legacy event name read from old logs
|
|
31
|
+
claude_window_reset: "subscription_window_reset", // vocabulary-ok: legacy event name read from old logs
|
|
32
|
+
// retired-name-ok:end
|
|
27
33
|
};
|
|
28
34
|
/** The ordered field rewrites; later parts of the series append here, and they run in order. */
|
|
29
35
|
const LEGACY_EVENT_REWRITES = [
|
|
30
36
|
{
|
|
31
37
|
// The run number rode `tick` on run_start, run_end, prompt_taken, image_blind, image_reroute.
|
|
32
|
-
name: "tick-to-run",
|
|
38
|
+
name: "tick-to-run", // retired-name-ok: legacy name
|
|
33
39
|
apply(ev) {
|
|
34
40
|
const e = ev;
|
|
35
|
-
if ("tick" in e && !("run" in e)) { // vocabulary-ok: legacy field name read from old logs
|
|
36
|
-
e.run = e.tick; // vocabulary-ok: legacy field name read from old logs
|
|
37
|
-
delete e.tick; // vocabulary-ok: legacy field name read from old logs
|
|
41
|
+
if ("tick" in e && !("run" in e)) { // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field
|
|
42
|
+
e.run = e.tick; // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field
|
|
43
|
+
delete e.tick; // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field
|
|
38
44
|
}
|
|
39
45
|
},
|
|
40
46
|
},
|
|
41
47
|
{
|
|
42
48
|
// build_check.scope and conflict_handback.reason said "landing" for what is now "merge".
|
|
43
|
-
name: "
|
|
49
|
+
name: "merge-scope-value",
|
|
44
50
|
apply(ev) {
|
|
45
51
|
const e = ev;
|
|
46
52
|
if (e.type === "build_check" && e.scope === "landing")
|
|
@@ -57,14 +63,14 @@ const LEGACY_EVENT_REWRITES = [
|
|
|
57
63
|
if (e.type !== "slot_wait")
|
|
58
64
|
return;
|
|
59
65
|
if (e.purpose === "tick")
|
|
60
|
-
e.purpose = "run"; // vocabulary-ok: legacy event value read from old logs
|
|
66
|
+
e.purpose = "run"; // vocabulary-ok: legacy event value read from old logs // retired-name-ok: legacy name
|
|
61
67
|
if (e.purpose === "vet")
|
|
62
68
|
e.purpose = "verify"; // vocabulary-ok: legacy event value read from old logs
|
|
63
69
|
},
|
|
64
70
|
},
|
|
65
71
|
{
|
|
66
72
|
// The restart event's drain fields became finish fields.
|
|
67
|
-
name: "restart-
|
|
73
|
+
name: "restart-finish-fields",
|
|
68
74
|
apply(ev) {
|
|
69
75
|
const e = ev;
|
|
70
76
|
if (e.type !== "restart")
|
|
@@ -88,16 +94,16 @@ const LEGACY_EVENT_REWRITES = [
|
|
|
88
94
|
// call site that still passes `source: "harness"` (the warn helpers' callers) maps too.
|
|
89
95
|
// Kept permanently though no TypeScript type accepts `loop` any more: old logs and a
|
|
90
96
|
// still-running old supervisor keep writing it, so the read path and logEvent need this.
|
|
91
|
-
name: "
|
|
97
|
+
name: "writer-id-to-source",
|
|
92
98
|
apply(ev) {
|
|
93
99
|
const e = ev;
|
|
94
|
-
if (typeof e.loop === "string") { // vocabulary-ok: legacy field name read from old logs
|
|
100
|
+
if (typeof e.loop === "string") { // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field, read permanently
|
|
95
101
|
if (e.type === "claim" && e.source === "staged" && !("origin" in e)) {
|
|
96
102
|
e.origin = e.source;
|
|
97
103
|
delete e.source;
|
|
98
104
|
}
|
|
99
|
-
e.source = e.loop; // vocabulary-ok: legacy field name read from old logs
|
|
100
|
-
delete e.loop; // vocabulary-ok: legacy field name read from old logs
|
|
105
|
+
e.source = e.loop; // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field, read permanently
|
|
106
|
+
delete e.loop; // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field, read permanently
|
|
101
107
|
}
|
|
102
108
|
if (e.source === "harness")
|
|
103
109
|
e.source = TUMWATER_SOURCE; // vocabulary-ok: legacy event source read from old logs
|
|
@@ -115,6 +121,24 @@ const LEGACY_EVENT_REWRITES = [
|
|
|
115
121
|
e.role = canonicalRoleId(e.role);
|
|
116
122
|
},
|
|
117
123
|
},
|
|
124
|
+
{
|
|
125
|
+
// The four subscription-window events now name their backend (plans/codex-backend.md part
|
|
126
|
+
// 2b/9). The old claude_window_* names map (above) to subscription_window_* with no
|
|
127
|
+
// backend, so a line written before the rename gets `backend: "claude"` here; a current
|
|
128
|
+
// line written with its own backend is left alone. Kept permanently: a still-running old
|
|
129
|
+
// supervisor and old rotated logs keep writing the old names without a backend.
|
|
130
|
+
name: "subscription-window-backend",
|
|
131
|
+
apply(ev) {
|
|
132
|
+
const e = ev;
|
|
133
|
+
if (e.type === "subscription_window_reserved" ||
|
|
134
|
+
e.type === "subscription_window_warning" ||
|
|
135
|
+
e.type === "subscription_window_limited" ||
|
|
136
|
+
e.type === "subscription_window_reset") {
|
|
137
|
+
if (typeof e.backend !== "string")
|
|
138
|
+
e.backend = "claude";
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
},
|
|
118
142
|
];
|
|
119
143
|
/** Today's name for an old event type name; any other string comes back unchanged. */
|
|
120
144
|
export function canonicalEventType(name) {
|
|
@@ -6,7 +6,7 @@ import { readTailTextWithEnd } from "../files/tail.js";
|
|
|
6
6
|
import { normalizeLegacyEvent } from "./event-legacy.js";
|
|
7
7
|
/** The READ side of the event feed: everything that consumes events.jsonl after the fact —
|
|
8
8
|
* parseEventLine's skip-without-failing rule, the per-event query conventions (eventDayKey,
|
|
9
|
-
* eventRole, eventUsage,
|
|
9
|
+
* eventRole, eventUsage, runStartMap/runSpanMs) the reports and history share, and the
|
|
10
10
|
* stat-cached backwards tail scan readEvents is built on. The WRITE side — logEvent/warnEvent,
|
|
11
11
|
* the append path with its torn-tail repair, rotation, and the subscriber list — and the
|
|
12
12
|
* HarnessEvent shape itself live beside it in events.ts; the split keeps the append path's
|
|
@@ -31,7 +31,7 @@ export function parseEventLine(line) {
|
|
|
31
31
|
* numeric timestamp. The one day-bucketing rule the windowed reader (event-window.ts) and the
|
|
32
32
|
* usage/failure reports all apply, so they agree on what counts as one day. A `ts` that is
|
|
33
33
|
* present but not finite is as unusable as an absent one and reads null too (the same
|
|
34
|
-
* finiteness check
|
|
34
|
+
* finiteness check eventRun/eventUsage/runStartMap/runSpanMs apply): bare
|
|
35
35
|
* `typeof Infinity === "number"` let a JSON `1e999` through to dayKey, whose
|
|
36
36
|
* `new Date(Infinity)` rendered the bucket key "NaN-NaN-NaN" and filed the event under a day
|
|
37
37
|
* no window can name. */
|
|
@@ -39,19 +39,19 @@ export function eventDayKey(ev) {
|
|
|
39
39
|
const ts = finiteNumber(ev.ts, null);
|
|
40
40
|
return ts === null ? null : dayKey(ts);
|
|
41
41
|
}
|
|
42
|
-
/** The
|
|
42
|
+
/** The role id an event is filed under, or "?" when absent or empty — the guard the usage
|
|
43
43
|
* report and the failure digest both apply when grouping by role. */
|
|
44
44
|
export function eventRole(ev) {
|
|
45
45
|
return typeof ev.source === "string" && ev.source !== "" ? ev.source : "?";
|
|
46
46
|
}
|
|
47
|
-
/** The
|
|
48
|
-
* display surface applies to the loosened `
|
|
49
|
-
* filter, so a torn or hand-edited line can carry no
|
|
50
|
-
* in the feed) or a non-numeric one (String(Number(…)) printed "#NaN" in history).
|
|
51
|
-
* 1-based — the
|
|
52
|
-
* a real
|
|
47
|
+
/** The run number an event names, or 0 when it is absent or non-finite. The one guard every
|
|
48
|
+
* display surface applies to the loosened `run` field: an event log is read without a shape
|
|
49
|
+
* filter, so a torn or hand-edited line can carry no run (String(undefined) printed "#undefined"
|
|
50
|
+
* in the feed) or a non-numeric one (String(Number(…)) printed "#NaN" in history). Runs are
|
|
51
|
+
* 1-based — the role increments state.runs before logging its first run — so 0 is visibly not
|
|
52
|
+
* a real run, the same corrupt-value-reads-as-0 rule gigabytes and the failure digest's
|
|
53
53
|
* aheadCommits apply, kept finite so a JSON consumer never sees NaN's null stand-in. */
|
|
54
|
-
export function
|
|
54
|
+
export function eventRun(ev) {
|
|
55
55
|
return finiteNumber(ev.run, 0);
|
|
56
56
|
}
|
|
57
57
|
/** The usage numbers an event records (`run_end`'s own run, `merge_done`'s merge slot):
|
|
@@ -68,15 +68,15 @@ export function eventUsage(ev) {
|
|
|
68
68
|
const notionalCostUsd = finiteNumber(ev.notionalCostUsd, 0);
|
|
69
69
|
return { tokens, costUsd, notionalCostUsd };
|
|
70
70
|
}
|
|
71
|
-
/** Pair the run_start events of `events` by
|
|
71
|
+
/** Pair the run_start events of `events` by run, keyed `${role}#${run}` → the start event
|
|
72
72
|
* itself — the one home of that pairing and its key format, shared by the timestamps
|
|
73
|
-
* (
|
|
74
|
-
* (
|
|
73
|
+
* (runStartMap), the models (runStartModelMap), and the run drill-down's duration
|
|
74
|
+
* (run-detail-data.ts uses runSpanMs' key). A run_start whose `ts` is not a finite number is
|
|
75
75
|
* as unusable as an absent one and adds no entry either (the same "a torn or hand-edited line
|
|
76
|
-
* carries no usable timestamp" policy
|
|
76
|
+
* carries no usable timestamp" policy eventRun/eventUsage apply to their fields), so every
|
|
77
77
|
* entry is a real start the projections below can trust. Private: external consumers read one
|
|
78
78
|
* of the projections, not this raw pairing. */
|
|
79
|
-
function
|
|
79
|
+
function runStartEvents(events) {
|
|
80
80
|
const starts = new Map();
|
|
81
81
|
for (const e of events) {
|
|
82
82
|
const ts = finiteNumber(e.ts, null);
|
|
@@ -85,54 +85,54 @@ function runStartMap(events) {
|
|
|
85
85
|
}
|
|
86
86
|
return starts;
|
|
87
87
|
}
|
|
88
|
-
/** The run_start pairing's start epoch ms: `${
|
|
89
|
-
* of
|
|
90
|
-
* (history-data.ts), the failure digest's time-and-spend fold (time-spend.ts), and the
|
|
91
|
-
* drill-down's duration (
|
|
92
|
-
* notions of a
|
|
93
|
-
* the
|
|
88
|
+
/** The run_start pairing's start epoch ms: `${role}#${run}` → start epoch ms, the projection
|
|
89
|
+
* of runStartEvents onto each event's `ts` — shared by `tumwater history`'s row spans
|
|
90
|
+
* (history-data.ts), the failure digest's time-and-spend fold (time-spend.ts), and the run
|
|
91
|
+
* drill-down's duration (run-detail-data.ts), so those consumers cannot drift into different
|
|
92
|
+
* notions of a run's span. A run_end whose start is not in `events` (log rotation cut it, or
|
|
93
|
+
* the run was skipped before any start logged) has no entry; callers decide what an unpaired
|
|
94
94
|
* end costs. Lives in the read module, not in a ui module, so core collectors can share it
|
|
95
95
|
* without a core→ui import. */
|
|
96
|
-
export function
|
|
96
|
+
export function runStartMap(events) {
|
|
97
97
|
const starts = new Map();
|
|
98
|
-
for (const [key, e] of
|
|
98
|
+
for (const [key, e] of runStartEvents(events))
|
|
99
99
|
starts.set(key, finiteNumber(e.ts, 0));
|
|
100
100
|
return starts;
|
|
101
101
|
}
|
|
102
|
-
/** The run_start pairing's model selectors: `${
|
|
102
|
+
/** The run_start pairing's model selectors: `${role}#${run}` → the run's model when its
|
|
103
103
|
* run_start carries a non-empty string `model` (the agent-qualified selector string — a
|
|
104
104
|
* per-role override, a fallback, or the primary — that the status rows carry; see
|
|
105
|
-
* config-views.ts's
|
|
105
|
+
* config-views.ts's backendQualifiedSelector). A run_start with no `model`, or an empty or
|
|
106
106
|
* non-string one, adds no entry, so `tumwater history`'s model cell renders empty for a start
|
|
107
107
|
* that named none — exactly the empty-usage convention applied to cells. The finite-`ts`
|
|
108
|
-
* guard rides
|
|
109
|
-
export function
|
|
108
|
+
* guard rides runStartEvents, the one home of the pairing. */
|
|
109
|
+
export function runStartModelMap(events) {
|
|
110
110
|
const models = new Map();
|
|
111
|
-
for (const [key, e] of
|
|
111
|
+
for (const [key, e] of runStartEvents(events)) {
|
|
112
112
|
if (typeof e.model === "string" && e.model !== "")
|
|
113
113
|
models.set(key, e.model);
|
|
114
114
|
}
|
|
115
115
|
return models;
|
|
116
116
|
}
|
|
117
|
-
/** The model of the run_end event `ev` against `models` (
|
|
118
|
-
* `${
|
|
119
|
-
* "no model is null" rule are spelled once — the model twin of
|
|
117
|
+
/** The model of the run_end event `ev` against `models` (runStartModelMap's pairing): the
|
|
118
|
+
* `${role}#${run}` lookup, in one place beside the map it indexes, so the key format and the
|
|
119
|
+
* "no model is null" rule are spelled once — the model twin of runSpanMs. null when the
|
|
120
120
|
* start is missing (rotation cut it) or carried no model; callers decide what an empty model
|
|
121
121
|
* costs — history renders it as an empty cell. */
|
|
122
|
-
export function
|
|
122
|
+
export function runStartModel(ev, models) {
|
|
123
123
|
return models.get(`${ev.source}#${ev.run}`) ?? null;
|
|
124
124
|
}
|
|
125
|
-
/** The start→end span of the `run_end` event `ev` against `starts` (
|
|
126
|
-
* the `${
|
|
125
|
+
/** The start→end span of the `run_end` event `ev` against `starts` (runStartMap's pairing):
|
|
126
|
+
* the `${role}#${run}` lookup and the clamp to non-negative, in one place beside the map
|
|
127
127
|
* they index, so the key format and the span rule are spelled once. null when the start is
|
|
128
128
|
* missing (rotation cut it) or when `ev.ts` is not a finite number (a torn or hand-edited end
|
|
129
129
|
* line can carry no usable timestamp); callers decide what an unpaired end costs — the failure
|
|
130
|
-
* digest prices it as 0, history renders it as "—",
|
|
131
|
-
* guard is the same finiteness check
|
|
132
|
-
* `Math.max(0, undefined - start)` was NaN, and the failure digest's `
|
|
133
|
-
* (`
|
|
130
|
+
* digest prices it as 0, history renders it as "—", run detail omits the duration. The end's
|
|
131
|
+
* guard is the same finiteness check eventRun/eventUsage apply to their fields: without it,
|
|
132
|
+
* `Math.max(0, undefined - start)` was NaN, and the failure digest's `runDurationMs`
|
|
133
|
+
* (`runSpanMs(ev, starts) ?? 0`) folded that NaN straight into its agent-hour totals —
|
|
134
134
|
* `NaN ?? 0` is still NaN. */
|
|
135
|
-
export function
|
|
135
|
+
export function runSpanMs(ev, starts) {
|
|
136
136
|
const startTs = starts.get(`${ev.source}#${ev.run}`);
|
|
137
137
|
const endTs = finiteNumber(ev.ts, null);
|
|
138
138
|
return startTs === undefined || endTs === null ? null : Math.max(0, endTs - startTs);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The one ordering the prose `tumwater logs --count` tally and the TUI Activity pane's
|
|
2
|
+
* tally line share: count each present event type, most-frequent first with ties in
|
|
3
|
+
* type-name order, one `[type, count]` pair per present type. Hoisting the sort here keeps
|
|
4
|
+
* `sayTally` and the pane from drifting on which types win or how ties break. The dashboard
|
|
5
|
+
* feed's tally chips keep their own copy because they run in a browser where harness modules
|
|
6
|
+
* cannot be imported (the `reportRoleOrder` exception
|
|
7
|
+
* src/ui/gui/gui-client-report-chart.ts documents). */
|
|
8
|
+
import { increment, sortByValueDesc } from "../collections.js";
|
|
9
|
+
/** Tally the distinct types present, most-frequent first with ties in type-name order.
|
|
10
|
+
* `[]` in, `[]` out. */
|
|
11
|
+
export function eventTypeTally(events) {
|
|
12
|
+
const counts = new Map();
|
|
13
|
+
for (const e of events)
|
|
14
|
+
increment(counts, e.type);
|
|
15
|
+
return sortByValueDesc([...counts.entries()]);
|
|
16
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { dayAt, dayKey, humanSeconds } from "../text/datetime.js";
|
|
1
|
+
import { dayAt, dayKey, humanSeconds, DAY_MS } from "../text/datetime.js";
|
|
2
2
|
import { eventDayKey, parseEventLine } from "./event-read.js";
|
|
3
|
+
import { EVENTS_KEEP } from "./events.js";
|
|
3
4
|
import { eventsArchivePath, eventsLogPath } from "../paths.js";
|
|
4
5
|
import { statOrNull } from "../files/files.js";
|
|
5
6
|
import { readTailTextWithEnd } from "../files/tail.js";
|
|
@@ -17,16 +18,16 @@ export const REPORT_MAX_DAYS = 90;
|
|
|
17
18
|
* signal, and the log rotates at 16 MB (EVENTS_MAX_BYTES in events.ts) anyway, so a huge window
|
|
18
19
|
* mostly reads rotated-away nothing. Lives here so the CLI's flag validation and its window
|
|
19
20
|
* read share one bound. */
|
|
20
|
-
export const LOGS_SINCE_MAX_MS = 7 *
|
|
21
|
+
export const LOGS_SINCE_MAX_MS = 7 * DAY_MS;
|
|
21
22
|
/** The `tumwater report --since <duration>` window's cap: 7 days. The same bound as the logs
|
|
22
23
|
* view above, for the same rationale — a longer window only re-reads more log without adding
|
|
23
24
|
* signal, and the log rotates at 16 MB (EVENTS_MAX_BYTES in events.ts) anyway. Lives here so
|
|
24
25
|
* the CLI's flag validation and the collector's window read share one bound. */
|
|
25
|
-
export const REPORT_SINCE_MAX_MS = 7 *
|
|
26
|
+
export const REPORT_SINCE_MAX_MS = 7 * DAY_MS;
|
|
26
27
|
/** Why a trailing-window length is refused, as the rejection text naming the out-of-range
|
|
27
28
|
* value (`sinceMs must be between 1 and 7d (got 604800000)`). The single home of the report
|
|
28
29
|
* window's bound wording, shared by the data-layer guard (assertReportSinceMs) and the
|
|
29
|
-
* dashboard's ?sinceMs= 400 (gui-endpoints.ts) so the two cannot drift apart; the `--since`
|
|
30
|
+
* dashboard's ?sinceMs= 400 (gui-report-endpoints.ts) so the two cannot drift apart; the `--since`
|
|
30
31
|
* CLI flag uses parseSinceFlag's own distinct wording. */
|
|
31
32
|
export function reportSinceMsg(sinceMs) {
|
|
32
33
|
return `sinceMs must be between 1 and ${humanSeconds(REPORT_SINCE_MAX_MS / 1000)} (got ${sinceMs})`;
|
|
@@ -198,32 +199,42 @@ function scanEventsFile(file, fromKey) {
|
|
|
198
199
|
const end = trailing !== "" && parseEventLine(trailing) !== null ? coveredEnd + Buffer.byteLength(trailing) : coveredEnd;
|
|
199
200
|
return { events, coversFullWindow, end, logState: classifyLogFile(text, st) };
|
|
200
201
|
}
|
|
201
|
-
/** Events whose local day is on or after `fromKey`, read from the live log and
|
|
202
|
-
* log alone does not cover the window
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
202
|
+
/** Events whose local day is on or after `fromKey`, read from the live log and, when the
|
|
203
|
+
* live log alone does not cover the window, its archived generations (events.jsonl.1 …
|
|
204
|
+
* `.EVENTS_KEEP`, each written by rotation — the newest archive `.1` first, then `.2` …
|
|
205
|
+
* `.5` only while the window is not yet covered, per G4). Each file is scanned with bounded
|
|
206
|
+
* I/O (scanEventsFile) and every archive's in-window events are strictly older than the live
|
|
207
|
+
* file's, so prepending them oldest-archive-first keeps the oldest-first ordering every
|
|
208
|
+
* consumer assumes; the concatenation needs no dedup because rotation moves whole files, so
|
|
209
|
+
* the files share no lines. `coversFullWindow` is true when any read file's oldest retained
|
|
210
|
+
* event predates the window, which keeps the rotation note honest in both directions: it
|
|
211
|
+
* vanishes once an archive completes the window and still appears when even the newest
|
|
212
|
+
* archive starts inside it. A window the live file provably covers never touches an archive,
|
|
210
213
|
* and a missing or empty archive (the normal case) reads as nothing, so the no-rotation
|
|
211
214
|
* behavior is unchanged. */
|
|
212
215
|
export function readWindowEvents(root, fromKey) {
|
|
213
216
|
const live = scanEventsFile(eventsLogPath(root), fromKey);
|
|
214
217
|
if (live.coversFullWindow)
|
|
215
218
|
return { events: live.events, coversFullWindow: true, liveEnd: live.end, logState: live.logState };
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
219
|
+
const archived = [];
|
|
220
|
+
let covered = live.coversFullWindow;
|
|
221
|
+
for (let gen = 1; gen <= EVENTS_KEEP && !covered; gen++) {
|
|
222
|
+
const archive = scanEventsFile(eventsArchivePath(root, gen), fromKey);
|
|
223
|
+
archived.push(archive);
|
|
224
|
+
covered = archive.coversFullWindow;
|
|
225
|
+
}
|
|
226
|
+
const events = [];
|
|
227
|
+
for (let i = archived.length - 1; i >= 0; i--)
|
|
228
|
+
events.push(...archived[i].events);
|
|
229
|
+
events.push(...live.events);
|
|
230
|
+
let logState = live.logState;
|
|
231
|
+
for (const a of archived)
|
|
232
|
+
logState = combineLogState(logState, a.logState);
|
|
233
|
+
return { events, coversFullWindow: covered, liveEnd: live.end, logState };
|
|
223
234
|
}
|
|
224
235
|
/** The event feed's read outcome for a one-shot caller that needs no window — `doctor`'s
|
|
225
236
|
* event-log pre-flight. Reads the live log and its archive whole and classifies each; the
|
|
226
|
-
* combine rule matches readWindowEvents, so the doctor check and the
|
|
237
|
+
* combine rule matches readWindowEvents, so the doctor check and the selfcheck digest agree on
|
|
227
238
|
* what "unreadable" means. */
|
|
228
239
|
export function eventLogState(root) {
|
|
229
240
|
return combineLogState(scanFileReadState(eventsLogPath(root)), scanFileReadState(eventsArchivePath(root)));
|
|
@@ -239,7 +250,7 @@ function scanFileReadState(file) {
|
|
|
239
250
|
return classifyLogFile(text, st);
|
|
240
251
|
}
|
|
241
252
|
/** The duration-shaped windowed read whose consumer needs a SECOND view of the same window
|
|
242
|
-
* with a wider cutoff (history's
|
|
253
|
+
* with a wider cutoff (history's run-row join: a queued run spanning the rows' cutoff logged
|
|
243
254
|
* its merge_queued pin before that cutoff, so its merge verdict must be joined from evidence
|
|
244
255
|
* up to `joinBackslackMs` older). One backwards scan serves both: the wide read's day key never
|
|
245
256
|
* sits after the narrow one's, so every `ts >= cutoff` event it holds is exactly the narrow
|
|
@@ -309,8 +320,9 @@ export function readEventsSince(root, sinceMs) {
|
|
|
309
320
|
/** The "recent events of a kind" read every doctor wait/failure check shares: events since the
|
|
310
321
|
* trailing `sinceMs` window, kept only when they clear `now − sinceMs` (the caller's seam for the
|
|
311
322
|
* window's far edge) and pass `keep`. One home of the read-and-recheck shape the slot/provider
|
|
312
|
-
* wait checks (doctor-checks.ts), the notify-failure check, and the queued-merge
|
|
313
|
-
* spelled beside readEventsSince's cutoff — the kind + recency rule cannot drift
|
|
323
|
+
* wait checks (doctor-contention-checks.ts), the notify-failure check, and the queued-merge
|
|
324
|
+
* wait check each spelled beside readEventsSince's cutoff — the kind + recency rule cannot drift
|
|
325
|
+
* per caller. `keep`
|
|
314
326
|
* defaults to every event, so the ts-only reader passes nothing; `now` is explicit so every call in
|
|
315
327
|
* one window derives from the same instant (readEventsSince's own cutoff still uses the real
|
|
316
328
|
* clock, so this recheck is what a test seam can pin). `ts` is already number-typed by
|
|
@@ -318,3 +330,27 @@ export function readEventsSince(root, sinceMs) {
|
|
|
318
330
|
export function eventsSince(root, sinceMs, now, keep = () => true) {
|
|
319
331
|
return readEventsSince(root, sinceMs).events.filter((ev) => keep(ev) && ev.ts >= now - sinceMs);
|
|
320
332
|
}
|
|
333
|
+
/** The `--since` / `?sinceMs=` trailing window's bounds and identity slice, shared by the two
|
|
334
|
+
* trailing-window collectors (`collectReportSince` in report-data.ts and
|
|
335
|
+
* `collectFailureReportSince` in failure-data.ts): an open-ended window (cutoff = now − sinceMs,
|
|
336
|
+
* end = now) when `untilMs` is absent, else the bounded [now − sinceMs, now − untilMs). Both
|
|
337
|
+
* collectors budget events with the same open/closed comparisons against `cutoff`/`end` (and
|
|
338
|
+
* `now` for the open-ended case), and both stamp the same fromIso/toIso identity fields into
|
|
339
|
+
* their JSON, so the timing math and the ISO stamping live here rather than drifting per
|
|
340
|
+
* collector. `slice` is exactly the identity object each embeds: sinceMs and fromIso always,
|
|
341
|
+
* plus untilMs and toIso only when the window is bounded. */
|
|
342
|
+
export function sinceWindowTiming(sinceMs, untilMs) {
|
|
343
|
+
const now = Date.now();
|
|
344
|
+
const cutoff = now - sinceMs;
|
|
345
|
+
const end = untilMs === undefined ? now : now - untilMs;
|
|
346
|
+
return {
|
|
347
|
+
now,
|
|
348
|
+
cutoff,
|
|
349
|
+
end,
|
|
350
|
+
slice: {
|
|
351
|
+
sinceMs,
|
|
352
|
+
fromIso: new Date(cutoff).toISOString(),
|
|
353
|
+
...(untilMs === undefined ? {} : { untilMs, toIso: new Date(end).toISOString() }),
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
@@ -14,17 +14,22 @@ export function subscribeEvents(listener) {
|
|
|
14
14
|
* harness event log is small per event, so a fixed cap keeps logEvent config-free. Exported
|
|
15
15
|
* because the report headers name the threshold — a fact only this module owns. */
|
|
16
16
|
export const EVENTS_MAX_BYTES = 16 * 1024 * 1024;
|
|
17
|
+
/** How many rotated event-log generations rotateIfLarge retains (plans/fast-merge.md
|
|
18
|
+
* "Guardrails" G4): at most 96 MB with the live file, about six weeks at the series' daily
|
|
19
|
+
* rate, so a by-hand audit of the team still sees months. A constant, not a config key, like
|
|
20
|
+
* EVENTS_MAX_BYTES. */
|
|
21
|
+
export const EVENTS_KEEP = 5;
|
|
17
22
|
/** The rotation phrase the report headers render — `eventsRotationLabel()` rather than a
|
|
18
|
-
* hardcoded "rotated at 16 MB" copy, so changing EVENTS_MAX_BYTES updates the
|
|
19
|
-
* instead of leaving every report claiming a
|
|
23
|
+
* hardcoded "rotated at 16 MB" copy, so changing EVENTS_MAX_BYTES or EVENTS_KEEP updates the
|
|
24
|
+
* header with it instead of leaving every report claiming a rotation the log no longer does. */
|
|
20
25
|
export function eventsRotationLabel() {
|
|
21
|
-
return `rotated at ${EVENTS_MAX_BYTES / (1024 * 1024)} MB`;
|
|
26
|
+
return `rotated at ${EVENTS_MAX_BYTES / (1024 * 1024)} MB, ${EVENTS_KEEP} kept`;
|
|
22
27
|
}
|
|
23
28
|
;
|
|
24
29
|
/** The `source` of events tumwater writes about itself rather than on a role's behalf. */
|
|
25
30
|
export const TUMWATER_SOURCE = "tumwater";
|
|
26
31
|
/** The usage fields an event carries, as a fragment with both omitted when zero — the
|
|
27
|
-
* omit-when-zero convention the two usage-emitters apply (
|
|
32
|
+
* omit-when-zero convention the two usage-emitters apply (run-finalize.ts's run_end,
|
|
28
33
|
* merge-slot.ts's merge_done/merge_failed): a zero-token, zero-cost run (a free model, an abort
|
|
29
34
|
* before any tokens) renders without usage, so event-format's usage fragment and
|
|
30
35
|
* `tumwater history` show nothing rather than "$0.00". Spreading the fragment keeps the
|
|
@@ -51,7 +56,7 @@ export function logEvent(root, event) {
|
|
|
51
56
|
const full = normalizeLegacyEvent({ ts: Date.now(), ...event });
|
|
52
57
|
const file = eventsLogPath(root);
|
|
53
58
|
ensureParentDir(file);
|
|
54
|
-
rotateIfLarge(file, EVENTS_MAX_BYTES);
|
|
59
|
+
rotateIfLarge(file, EVENTS_MAX_BYTES, EVENTS_KEEP);
|
|
55
60
|
appendEventLine(file, JSON.stringify(full) + "\n");
|
|
56
61
|
for (const listener of listeners)
|
|
57
62
|
listener(full);
|
|
@@ -72,7 +77,7 @@ export function logEventBestEffort(root, event) {
|
|
|
72
77
|
logEvent(root, event);
|
|
73
78
|
}
|
|
74
79
|
catch (err) {
|
|
75
|
-
// A terminal display boundary like cli-output's say()/fail(): the
|
|
80
|
+
// A terminal display boundary like cli-output's say()/fail(): the role id, event type, and
|
|
76
81
|
// error text can carry model- or subprocess-supplied bytes, and stderr is a terminal. Strip
|
|
77
82
|
// the control characters the same way so a fallback line cannot drive the operator's shell.
|
|
78
83
|
const detail = `${event.source}: could not log ${event.type} event: ${errorMessage(err)}`;
|
|
@@ -80,9 +85,9 @@ export function logEventBestEffort(root, event) {
|
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
/** warnEvent's twin of logEventBestEffort: the "something is off but the loop continues"
|
|
83
|
-
* warning must not be the thing that ends the
|
|
88
|
+
* warning must not be the thing that ends the role it warns about. The fallback keeps the
|
|
84
89
|
* warning's own text on stderr — when the feed is the failure, this line is the only place the
|
|
85
|
-
* caller's message (a spawn failure, a rejected
|
|
90
|
+
* caller's message (a spawn failure, a rejected run) survives to be read. The single home of
|
|
86
91
|
* the try-warn → message-bearing-stderr-fallback shape, so the notify hook and the launch pass
|
|
87
92
|
* do not each carry a copy of it. */
|
|
88
93
|
export function warnEventBestEffort(root, source, message) {
|
|
@@ -90,17 +95,29 @@ export function warnEventBestEffort(root, source, message) {
|
|
|
90
95
|
warnEvent(root, source, message);
|
|
91
96
|
}
|
|
92
97
|
catch (err) {
|
|
93
|
-
// The warning's own text is very often model-supplied (a stalled tool call's label, a
|
|
94
|
-
// lastError) and the
|
|
98
|
+
// The warning's own text is very often model-supplied (a stalled tool call's label, a run's
|
|
99
|
+
// lastError) and the role id can be a custom one, so this fallback is a terminal boundary:
|
|
95
100
|
// sanitize exactly as say()/fail() do before the line reaches the operator's terminal.
|
|
96
101
|
const detail = `${source}: ${message} (warning log unwritable: ${errorMessage(err)})`;
|
|
97
102
|
process.stderr.write(`tumwater: ${stripTerminalControls(detail)}\n`);
|
|
98
103
|
}
|
|
99
104
|
}
|
|
105
|
+
/** Run a best-effort bookkeeping step: when `step` throws, warn "could not <what>: <error>" under
|
|
106
|
+
* `source` and return false, so the caller keeps going (or returns early on false). */
|
|
107
|
+
export function tryOrWarn(root, source, what, step) {
|
|
108
|
+
try {
|
|
109
|
+
step();
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
warnEventBestEffort(root, source, `could not ${what}: ${errorMessage(err)}`);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
100
117
|
/** The `{ emit, warn }` pair for a caller on a catch-less path (the orchestrator's poll loop
|
|
101
118
|
* and the live config reload it drives): both route through logEventBestEffort and
|
|
102
119
|
* warnEventBestEffort, so the "an unwritable feed must not end the team" policy has one home
|
|
103
|
-
* and the harness-
|
|
120
|
+
* and the harness-level warning shorthand cannot drift from warnEvent's event shape. */
|
|
104
121
|
export function bestEffortEventLogger(root) {
|
|
105
122
|
return {
|
|
106
123
|
emit: (event) => logEventBestEffort(root, event),
|
|
@@ -140,11 +157,11 @@ function appendEventLine(file, line) {
|
|
|
140
157
|
}
|
|
141
158
|
}
|
|
142
159
|
// The READ side of this feed — parseEventLine, the per-event query conventions
|
|
143
|
-
// (eventDayKey/eventRole/eventUsage,
|
|
160
|
+
// (eventDayKey/eventRole/eventUsage, runStartMap/runSpanMs), and the stat-cached tail
|
|
144
161
|
// reader (readEvents/readEventsTailWithEnd) — lives in event-read.ts, so the append path's
|
|
145
162
|
// fs machinery and stat-memo cache stay apart from the read path's parse-and-scan machinery
|
|
146
163
|
// and its own stat-keyed cache. Human-facing formatting of events lives in event-format.ts;
|
|
147
164
|
// the windowed reader in event-window.ts; both beside this module because each is used by
|
|
148
165
|
// both display surfaces (src/ui/) and core consumers (the `run` banner's live stream,
|
|
149
|
-
// cli/cli-
|
|
166
|
+
// cli/cli-start.ts), and src/ui/ is imported only by itself and the CLI command layer that
|
|
150
167
|
// drives it (cli.ts and the src/ command bodies — DEVELOPMENT.md Layout).
|
|
@@ -14,7 +14,7 @@ import { childEnv } from "../process/process.js";
|
|
|
14
14
|
* role-cap-gates.ts and disk-gate.ts, src/baseline/main-red.ts, src/team/team-polls.ts,
|
|
15
15
|
* merge-slot.ts, merge-questions.ts,
|
|
16
16
|
* src/redeploy/redeployer.ts). `supervisor_exit` is logged by the supervisor after the
|
|
17
|
-
* orchestrator is gone, so src/cli/cli-
|
|
17
|
+
* orchestrator is gone, so src/cli/cli-start.ts pages it directly with notifyTeamDown; events
|
|
18
18
|
* appended by
|
|
19
19
|
* operator CLI commands go straight to the file and are out of scope by design.
|
|
20
20
|
*
|
|
@@ -24,14 +24,14 @@ import { childEnv } from "../process/process.js";
|
|
|
24
24
|
* (the exact line `tumwater logs` renders, via formatEvent). */
|
|
25
25
|
export const NOTIFY_EVENT_TYPES = [
|
|
26
26
|
"budget_warning", // daily spend crossed 80% of the cap, gate still open — time to raise it or fix the fallback before the page below
|
|
27
|
-
"role_cap_warning", // one role's own daily spend crossed 80% of its cap, still
|
|
28
|
-
"budget_paused", // spend cap hit with no usable free fallback —
|
|
29
|
-
"
|
|
30
|
-
"
|
|
27
|
+
"role_cap_warning", // one role's own daily spend crossed 80% of its cap, still running — time to raise it before the page below
|
|
28
|
+
"budget_paused", // spend cap hit with no usable free fallback — roles are stopped
|
|
29
|
+
"subscription_window_warning", // the subscription window first reported allowed_warning — subscription seams still run; the heads-up before the hold below
|
|
30
|
+
"subscription_window_limited", // a rejected subscription window holds every subscription seam until it resets — the subscription team's binding stop
|
|
31
31
|
"role_streak_paused", // the error-streak circuit breaker auto-paused a role
|
|
32
|
-
"role_cap_paused", // the role's own daily cost cap is reached — it stops starting
|
|
32
|
+
"role_cap_paused", // the role's own daily cost cap is reached — it stops starting runs
|
|
33
33
|
"disk_low", // free space on the worktrees volume dropped below diskHoldGB — new work is held
|
|
34
|
-
"main_red_hold", // main's own check is red — every code
|
|
34
|
+
"main_red_hold", // main's own check is red — every code role is held until a fix lands
|
|
35
35
|
"merge_failed", // the merge slot finished without merging a change
|
|
36
36
|
"restart_blocked", // the self-redeploy for main is blocked and latched — the team is stuck behind it
|
|
37
37
|
"question_posted", // a merge posted a new QUESTIONS.md ## Open entry — only a human can answer it
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The cluster-key rules shared by everything that groups failure messages into causes: the
|
|
2
2
|
* failure digest's collection (failure-data.ts) and the error-storm reducer
|
|
3
3
|
* (src/team/error-storm.ts) both count by these keys, so the normalization, the two
|
|
4
|
-
*
|
|
4
|
+
* run-timeout shapes and their pooling, and the grouping engine live here rather than
|
|
5
5
|
* inside either consumer. Pure string
|
|
6
6
|
* and grouping logic — no event reads, no clock. */
|
|
7
7
|
import { rankByCount } from "../collections.js";
|
|
@@ -76,34 +76,34 @@ export function normalizeClusterKey(message) {
|
|
|
76
76
|
.replace(/(?<!exited\s)\b\d+\b/g, "<n>");
|
|
77
77
|
return normalized.trim().slice(0, EXAMPLE_MAX);
|
|
78
78
|
}
|
|
79
|
-
/** normalizeClusterKey's rendering of the two
|
|
79
|
+
/** normalizeClusterKey's rendering of the two run-timeout errors src/agent/run-agent.ts emits —
|
|
80
80
|
* the plain kill and the still-making-progress variant (session and worktree edits preserved
|
|
81
81
|
* for resume, BUGS.md 2026-09-29). Both are one cause pointing at one knob (runTimeoutSeconds),
|
|
82
82
|
* so every consumer that counts by cluster key pools them under the plain one. */
|
|
83
|
-
export const
|
|
84
|
-
const
|
|
83
|
+
export const RUN_TIMEOUT_KEY = "timed out after <dur>";
|
|
84
|
+
const RUN_TIMEOUT_PROGRESSING_KEY = "timed out after <dur> while still making progress — session and worktree edits preserved for resume";
|
|
85
85
|
// The review run's rewording of the same cause (BUGS.md 2026-09-30): the reviewer never
|
|
86
86
|
// resumes, so its timeout names what actually happens — the commit is kept, the next
|
|
87
87
|
// attempt re-reviews from scratch. Same cause, same knob (review.timeoutSeconds), one row.
|
|
88
|
-
const
|
|
88
|
+
const RUN_TIMEOUT_PROGRESSING_REVIEW_KEY = "timed out after <dur> while still making progress — the commit is kept; the next attempt reviews it from scratch";
|
|
89
89
|
// The review run's wording when a resumable session remains:
|
|
90
90
|
// the next attempt continues this review instead of re-reviewing. Continuing the timed-out
|
|
91
91
|
// session is a different cause from a from-scratch re-review, so this wording is its own
|
|
92
|
-
// cluster rather than joining the pooled plain
|
|
93
|
-
// (like
|
|
94
|
-
export const
|
|
92
|
+
// cluster rather than joining the pooled plain run-timeout row. Exported for its unit tests
|
|
93
|
+
// (like RUN_TIMEOUT_KEY), so a display or digest test can pin the mapping by name.
|
|
94
|
+
export const RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY = "timed out after <dur> while still making progress — the commit is kept; the next attempt continues this review";
|
|
95
95
|
/** The known progressing-timeout wordings and the key each pools under. The two review
|
|
96
96
|
* wordings name one cause on one knob (review.timeoutSeconds): a still-making-progress kill
|
|
97
|
-
* pools into the plain
|
|
97
|
+
* pools into the plain run-timeout row (a mixed team of plain and progressing kills is one
|
|
98
98
|
* cause's agent-hours, not two half-size rows the top-N cut can drop — the same pooling the
|
|
99
99
|
* error-storm reducer applies, src/team/error-storm.ts), and a resumed review's wording maps
|
|
100
100
|
* to itself, because continuing the timed-out session is a different cause from a from-scratch
|
|
101
101
|
* re-review. Enumerated so a future wording edit lands in
|
|
102
102
|
* this table rather than silently changing which row its timeouts count under. */
|
|
103
103
|
const TIMEOUT_POOL = new Map([
|
|
104
|
-
[
|
|
105
|
-
[
|
|
106
|
-
[
|
|
104
|
+
[RUN_TIMEOUT_PROGRESSING_KEY, RUN_TIMEOUT_KEY],
|
|
105
|
+
[RUN_TIMEOUT_PROGRESSING_REVIEW_KEY, RUN_TIMEOUT_KEY],
|
|
106
|
+
[RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY, RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY],
|
|
107
107
|
]);
|
|
108
108
|
/** The cluster key a normalized error clusters under: the pooled wordings above map to their
|
|
109
109
|
* row, and every other cause stands as normalizeClusterKey rendered it. */
|