tumwater 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +27 -13
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +6 -27
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +8 -0
- package/dist/src/agent/codex/codex-args.js +5 -5
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +26 -24
- package/dist/src/agent/opencode/opencode-catalog.js +85 -36
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +23 -27
- package/dist/src/agent/subscription-account.js +2 -83
- package/dist/src/backlog/backlog-archive.js +3 -4
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +56 -122
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +3 -2
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/baseline/main-guardrails.js +2 -2
- package/dist/src/baseline/main-watch.js +84 -50
- package/dist/src/brief/brief.js +26 -13
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/budget.js +4 -5
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-report.js +10 -0
- package/dist/src/build/build-check-scoped.js +40 -18
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/build/build-stage.js +6 -4
- package/dist/src/change/change-data.js +1 -4
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-completion-command.js +75 -0
- package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
- package/dist/src/cli/cli-doctor-command.js +103 -0
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +36 -14
- package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
- package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
- package/dist/src/cli/cli-query-commands.js +2 -2
- package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-role-command.js +2 -41
- package/dist/src/cli/cli-start-summary.js +4 -3
- package/dist/src/cli/cli-start-supervisor.js +190 -0
- package/dist/src/cli/cli-start.js +94 -244
- package/dist/src/cli/completion.js +85 -49
- package/dist/src/cli/follow-view.js +20 -2
- package/dist/src/cli/help.js +6 -13
- package/dist/src/cli.js +70 -176
- package/dist/src/collections.js +39 -27
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +23 -1
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +66 -40
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +163 -102
- package/dist/src/config/config-schema.js +10 -12
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +8 -72
- package/dist/src/config/config-validation.js +4 -39
- package/dist/src/config/config-views.js +26 -50
- package/dist/src/config/config-write.js +128 -102
- package/dist/src/config/config.js +5 -14
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +53 -120
- package/dist/src/doctor/doctor-backlog.js +16 -14
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +8 -8
- package/dist/src/doctor/doctor-claude-checks.js +70 -36
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +57 -117
- package/dist/src/doctor/doctor-fallback-checks.js +48 -26
- package/dist/src/doctor/doctor-holds.js +8 -47
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +122 -21
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +24 -11
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +44 -64
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +3 -5
- package/dist/src/events/{notify.js → event-notify.js} +6 -6
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +8 -0
- package/dist/src/failure/failure-cluster.js +9 -18
- package/dist/src/failure/failure-data.js +12 -8
- package/dist/src/failure/failure-render.js +46 -36
- package/dist/src/failure/failure-state-change.js +54 -120
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +32 -74
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +9 -8
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git-diff.js +6 -2
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-args.js +12 -0
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-config-commands.js +2 -64
- package/dist/src/gui/gui-endpoint-commands.js +1 -1
- package/dist/src/gui/gui-endpoint-common.js +19 -1
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +43 -48
- package/dist/src/gui/gui-prompt-commands.js +25 -25
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/gui/gui-server.js +1 -2
- package/dist/src/gui/gui-team-commands.js +2 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +54 -36
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-check-failures.js +12 -9
- package/dist/src/merge/merge-core.js +7 -32
- package/dist/src/merge/merge-drain.js +11 -13
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +11 -14
- package/dist/src/merge/merge-queue-render.js +3 -3
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-stack.js +17 -18
- package/dist/src/merge/merge-to-main.js +13 -58
- package/dist/src/merge/merge-verify.js +12 -21
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +80 -47
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +44 -69
- package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +18 -27
- package/dist/src/orchestrator/scheduling-skip.js +10 -14
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +10 -9
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +28 -1
- package/dist/src/pi/pi-listed-models.js +13 -0
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +41 -8
- package/dist/src/pi/pi-watchdogs.js +43 -15
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +51 -26
- package/dist/src/process/process.js +20 -12
- package/dist/src/process/run-marker.js +33 -15
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/gate-prompts.js +1 -44
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/redeploy/redeployer.js +21 -1
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/diff-rules.js +35 -21
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +48 -41
- package/dist/src/review/review-precheck.js +14 -4
- package/dist/src/review/review.js +48 -170
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/legacy-role-migration.js +6 -3
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +7 -46
- package/dist/src/roles/role-view.js +10 -33
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/ci-parity.js +19 -8
- package/dist/src/run/run-apply.js +12 -15
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +141 -90
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +128 -50
- package/dist/src/run/stage-check.js +60 -50
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/{leftover.js → runner-leftover.js} +9 -26
- package/dist/src/runner/runner-outcome.js +7 -2
- package/dist/src/runner/runner-pi.js +9 -22
- package/dist/src/runner/runner-recovery.js +5 -13
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
- package/dist/src/runner/runner-run.js +103 -50
- package/dist/src/runner/runner-state-load.js +18 -28
- package/dist/src/runner/runner-state.js +0 -14
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/backoff.js +8 -3
- package/dist/src/scheduling/claim-entries.js +2 -1
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-budget.js +4 -43
- package/dist/src/status/status-data.js +35 -39
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -18
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +105 -70
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
- package/dist/src/team/team-polls.js +53 -201
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/datetime.js +16 -7
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +29 -15
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +6 -5
- package/dist/src/ui/alert-feeds.js +46 -111
- package/dist/src/ui/badges.js +17 -88
- package/dist/src/ui/gui/gui-client-boot.js +2 -8
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-feed.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-operator.js +7 -5
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +12 -6
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +107 -44
- package/dist/src/ui/run-progress-model.js +20 -4
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +11 -30
- package/dist/src/ui/status-render.js +13 -24
- package/dist/src/ui/team-alert-backlog.js +14 -204
- package/dist/src/ui/team-alert-shared.js +6 -29
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +8 -15
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-backlog.js +2 -2
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +5 -10
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +167 -98
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +5 -4
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -230
- package/dist/src/ui/tui/tui-pane-history.js +67 -74
- package/dist/src/ui/tui/tui-pane-pending.js +288 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +15 -10
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +38 -33
- package/dist/src/ui/tui/tui.js +4 -7
- package/dist/src/verdict/fix-claim.js +107 -56
- package/dist/src/verdict/refusal.js +1 -1
- package/dist/src/verdict/reply-contract.js +3 -4
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/review/review-followup.js +0 -87
- package/dist/src/review/review-run.js +0 -395
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/cli/{brief-command.js → cli-brief-command.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { claudeUtilizationPct } from "../agent/claude/claude-account.js";
|
|
2
1
|
import { eventRole, eventRun, eventUsage } from "./event-read.js";
|
|
3
2
|
import { eventTypeLabel } from "./event-labels.js";
|
|
4
3
|
import { roleLabel } from "../roles/role-labels.js";
|
|
@@ -50,6 +49,14 @@ function elapsed(ms) {
|
|
|
50
49
|
return "";
|
|
51
50
|
return ` (in ${shortSpanPhrase(n)})`;
|
|
52
51
|
}
|
|
52
|
+
/** The `waited <duration>` head of the three wait-family events (slot_wait, provider_wait,
|
|
53
|
+
* check_wait) — each rendered its own `waited ${shortSpanPhrase(finiteNumber(e.waitedMs, 0))}`
|
|
54
|
+
* inline, so the corrupt-field rule and the s/m bucketing cannot drift between them. Each
|
|
55
|
+
* caller keeps its own `to start`/`for a run slot`/`for a check slot` tail and its own
|
|
56
|
+
* parenthetical after it. */
|
|
57
|
+
function waitedSpan(waitedMs) {
|
|
58
|
+
return `waited ${shortSpanPhrase(finiteNumber(waitedMs, 0))}`;
|
|
59
|
+
}
|
|
53
60
|
/** A disk event's GB field as "12.3 GB" text. A torn or hand-edited event whose value is
|
|
54
61
|
* missing or non-numeric reads as "0.0 GB", never "NaN" — the same corrupt-field rule
|
|
55
62
|
* budgetPhrase applies to spentUsd/capUsd, since HarnessEvent's index signature hands every
|
|
@@ -111,9 +118,16 @@ function providerScope(e) {
|
|
|
111
118
|
export function backendLabel(e) {
|
|
112
119
|
return typeof e.backend === "string" && e.backend !== "" ? e.backend : "claude";
|
|
113
120
|
}
|
|
121
|
+
/** A subscription-window event's window, as "<backend> subscription window <type>" — the head
|
|
122
|
+
* every `subscription_window_*` line names (windowType is a torn/older event's only nullable
|
|
123
|
+
* half, so `reset` and the shaped lines differ only in what follows this prefix). One home so
|
|
124
|
+
* the four lines cannot drift on the backend's label or the window-type word. */
|
|
125
|
+
function subscriptionWindowPhrase(e) {
|
|
126
|
+
return `${backendLabel(e)} subscription window ${textOr(e.windowType)}`;
|
|
127
|
+
}
|
|
114
128
|
/** The provider/model pair a fallback-ish line names, as "provider/model", defaulting each
|
|
115
129
|
* half when the event omits it (a bare pi run carries neither). Shared by the budget_fallback
|
|
116
|
-
*
|
|
130
|
+
* line so the pair's shape and defaults cannot drift. */
|
|
117
131
|
function providerModelPair(e, providerDefault, modelDefault) {
|
|
118
132
|
return `${textOr(e.provider, providerDefault)}/${textOr(e.model, modelDefault)}`;
|
|
119
133
|
}
|
|
@@ -177,7 +191,7 @@ export function eventFeedItem(e) {
|
|
|
177
191
|
}
|
|
178
192
|
/** The feed's `--grep`/Search match rule: case-insensitive substring over the raw event type
|
|
179
193
|
* prefixed to the rendered line — exactly the haystack the operator would otherwise read. One
|
|
180
|
-
* home shared by the CLI's `tumwater logs --grep` (cli/log-commands.ts) and the dashboard's
|
|
194
|
+
* home shared by the CLI's `tumwater logs --grep` (cli/cli-log-commands.ts) and the dashboard's
|
|
181
195
|
* /api/logs search, so the two surfaces cannot disagree about what a pattern matches. */
|
|
182
196
|
export function eventMatchesGrep(e, patternLower) {
|
|
183
197
|
return `${e.type} ${formatEvent(e)}`.toLowerCase().includes(patternLower);
|
|
@@ -213,9 +227,11 @@ function runEventMessage(e) {
|
|
|
213
227
|
? "resuming the run cut off at the context ceiling (compacted pi session, same workspace)"
|
|
214
228
|
: e.cause === "handoff"
|
|
215
229
|
? "handing the interrupted run off to a fresh session on another agent (same workspace, uncommitted edits kept)"
|
|
216
|
-
: e.cause === "
|
|
217
|
-
? "handing the
|
|
218
|
-
:
|
|
230
|
+
: e.cause === "image-reroute"
|
|
231
|
+
? "handing the blind-read run off to a fresh session on an image-capable model (same workspace, uncommitted edits kept)"
|
|
232
|
+
: e.cause === "salvaged"
|
|
233
|
+
? "handing the work an earlier run left unsaved to a fresh session"
|
|
234
|
+
: "resuming the run a shutdown interrupted (same agent session and workspace)") + restored);
|
|
219
235
|
}
|
|
220
236
|
case "wake":
|
|
221
237
|
return `woke (${textOr(e.reason)})`;
|
|
@@ -304,22 +320,19 @@ function mergeEventMessage(e) {
|
|
|
304
320
|
default: return textOr(e.type);
|
|
305
321
|
}
|
|
306
322
|
}
|
|
307
|
-
/** " after merge" when the review is a post-merge review
|
|
323
|
+
/** " after merge" when the review is a post-merge review, else "". */
|
|
308
324
|
function afterMergeSuffix(e) {
|
|
309
325
|
return e.afterMerge === true ? " after merge" : "";
|
|
310
326
|
}
|
|
311
327
|
function reviewEventMessage(e) {
|
|
312
328
|
switch (e.type) {
|
|
313
329
|
case "review_start": {
|
|
314
|
-
//
|
|
315
|
-
// 3/3). The verb names that, because a bare "reviewing" line reads as a from-scratch run;
|
|
316
|
-
// the revision and model suffixes are unchanged and independent of the flag.
|
|
317
|
-
// afterMerge is the tier flag reviewMergedCommit writes for a review.afterMerge role; the
|
|
330
|
+
// afterMerge is the tier flag reviewMergedCommit writes for a post-merge review; the
|
|
318
331
|
// lineage pins review_start/review_verdict/review_rejected to the same discriminator.
|
|
319
332
|
const tier = e.afterMerge === true ? " after merge" : " before merge";
|
|
320
333
|
const revision = e.revision !== undefined ? ` (revision ${textOr(e.revision)})` : "";
|
|
321
334
|
const model = e.model !== undefined ? ` on ${textOr(e.model)}` : "";
|
|
322
|
-
return
|
|
335
|
+
return `reviewing ${shortSha(e.head)}${revision}${tier}${model}`;
|
|
323
336
|
}
|
|
324
337
|
case "review_verdict":
|
|
325
338
|
return `review approved ${shortSha(e.head)}${afterMergeSuffix(e)}${e.reason ? ` — ${textOr(e.reason)}` : ""}${elapsed(e.durationMs)}`;
|
|
@@ -430,13 +443,6 @@ function budgetEventMessage(e) {
|
|
|
430
443
|
});
|
|
431
444
|
return `fallback: ${parts.join("; ")}`;
|
|
432
445
|
}
|
|
433
|
-
case "model_fallback_started":
|
|
434
|
-
// Routine-with-explanation, like rate_limit_hold: the switch IS the harness handling the
|
|
435
|
-
// failure. Names the pair the role now runs on and why the primary was abandoned.
|
|
436
|
-
return `model fallback — ${providerModelPair(e, "pi's default", "pi's default")} engaged (primary failing: ${textOr(e.reason, "backend failure")})`;
|
|
437
|
-
case "model_fallback_ended":
|
|
438
|
-
// The return counterpart: names the pair runs resume on and how long the episode ran.
|
|
439
|
-
return `model fallback ended — back on ${providerModelPair(e, "pi's default", "pi's default")} after ${minutes(e.durationMs)}m`;
|
|
440
446
|
case "opencode_free_model_billed":
|
|
441
447
|
// The promotion ended (plans/opencode-backend.md §7, part 6c/7): a run billed what the
|
|
442
448
|
// catalog called free, so the team now prices that model as paid from here on. No warning
|
|
@@ -531,19 +537,6 @@ function resourceEventMessage(e) {
|
|
|
531
537
|
return `backend recovered (${backendKindPhrase(e.kind)})${providerAt(e)} — roles on ${providerScope(e)} run again`;
|
|
532
538
|
return `provider limit lifted${providerAt(e)} — roles on ${providerScope(e)} run again`;
|
|
533
539
|
}
|
|
534
|
-
case "subscription_window_warning": {
|
|
535
|
-
// Routine-with-explanation, like rate_limit_hold — no warning prefix: the account is
|
|
536
|
-
// telling us the window is filling, not failing. It names the window and when it resets.
|
|
537
|
-
const used = finiteNumber(e.utilization, null);
|
|
538
|
-
const fill = used === null ? "" : ` (at ${claudeUtilizationPct(used)}% used)`;
|
|
539
|
-
return `${backendLabel(e)} subscription window ${textOr(e.windowType)} near its limit${fill}${windowResetPhrase(e)} — subscription ${backendLabel(e)} model jobs keep running`;
|
|
540
|
-
}
|
|
541
|
-
case "subscription_window_reserved": {
|
|
542
|
-
const used = finiteNumber(e.utilization, null);
|
|
543
|
-
const line = finiteNumber(e.reserve, null);
|
|
544
|
-
const pct = (f) => (f === null ? "?" : `${claudeUtilizationPct(f)}%`);
|
|
545
|
-
return `${backendLabel(e)} subscription window ${textOr(e.windowType)} at ${pct(used)}, past its ${pct(line)} reserve${windowResetPhrase(e)} — the team leaves it to you (director keeps running)`;
|
|
546
|
-
}
|
|
547
540
|
case "subscription_window_limited":
|
|
548
541
|
// Routine-with-explanation, like rate_limit_hold — no warning prefix: waiting is the
|
|
549
542
|
// harness handling the account's own throttle. Names the window, the reset, and which
|
|
@@ -554,9 +547,9 @@ function resourceEventMessage(e) {
|
|
|
554
547
|
{
|
|
555
548
|
const moved = fallbackTierEntries(isJsonObject(e.tiers) ? e.tiers : undefined);
|
|
556
549
|
if (moved.length > 0)
|
|
557
|
-
return `${
|
|
550
|
+
return `${subscriptionWindowPhrase(e)} rejected — ${backendLabel(e)} model jobs move to ${moved.join(", ")}${windowResetPhrase(e)} (director keeps waiting)`;
|
|
558
551
|
}
|
|
559
|
-
return `waiting: ${
|
|
552
|
+
return `waiting: ${subscriptionWindowPhrase(e)} rejected — ${backendLabel(e)} model jobs wait${windowResetPhrase(e)} (director keeps running)`;
|
|
560
553
|
case "subscription_window_reset":
|
|
561
554
|
return `${backendLabel(e)} subscription window reset — ${backendLabel(e)} model jobs run again`;
|
|
562
555
|
default: return textOr(e.type);
|
|
@@ -568,10 +561,6 @@ function configEventMessage(e) {
|
|
|
568
561
|
// Routine state change, like counters_reset — no warning prefix.
|
|
569
562
|
return `maxConcurrent changed: ${textOr(e.from)} → ${textOr(e.to)}`;
|
|
570
563
|
}
|
|
571
|
-
case "provider_concurrency_changed": {
|
|
572
|
-
// Routine state change, its maxConcurrent sibling's per-provider form — no warning prefix.
|
|
573
|
-
return `concurrency for ${textOr(e.provider)} changed: ${textOr(e.from)} → ${textOr(e.to)}`;
|
|
574
|
-
}
|
|
575
564
|
case "retention_changed": {
|
|
576
565
|
// Routine state change, like its maxConcurrent sibling — no warning prefix.
|
|
577
566
|
return `sessionRetentionDays changed: ${textOr(e.from)} → ${textOr(e.to)}`;
|
|
@@ -604,9 +593,9 @@ function configEventMessage(e) {
|
|
|
604
593
|
function imageRestartEventMessage(e) {
|
|
605
594
|
switch (e.type) {
|
|
606
595
|
case "image_blind": {
|
|
607
|
-
// Routine-with-explanation
|
|
608
|
-
//
|
|
609
|
-
//
|
|
596
|
+
// Routine-with-explanation — no warning prefix: the run was stopped (or a gate run
|
|
597
|
+
// noted) because the model cannot see the image. Names the file, the model, and
|
|
598
|
+
// whether the run failed on it or a gate run only logged it.
|
|
610
599
|
const action = e.action === "logged"
|
|
611
600
|
? " (gate run logged)"
|
|
612
601
|
: e.action === "fail"
|
|
@@ -614,6 +603,13 @@ function imageRestartEventMessage(e) {
|
|
|
614
603
|
: "";
|
|
615
604
|
return `blind image read: ${textOr(e.path)} on ${textOr(e.model, "the model")}${action}`;
|
|
616
605
|
}
|
|
606
|
+
case "image_reroute":
|
|
607
|
+
// The run's work moves to a model that can read the image (plans/image-input.md part
|
|
608
|
+
// 4/5). Routine-with-explanation — no warning prefix: names the hop the way the
|
|
609
|
+
// handoff reads, and whether the traits-of-attachment preflight avoided a failed run.
|
|
610
|
+
return e.preflight === true
|
|
611
|
+
? `image attachment routed to ${textOr(e.to, "the target")} (${textOr(e.tier, "a peer tier")}) to read its image — no failed run`
|
|
612
|
+
: `image rerouted to ${textOr(e.to, "the target")} (${textOr(e.tier, "a peer tier")}) — next run goes there after a blind read on ${textOr(e.from, "the prior model")}`;
|
|
617
613
|
case "build_stale":
|
|
618
614
|
// Self-hosting teams only (src/redeploy/redeploy.ts): the code main describes is not the
|
|
619
615
|
// code running. Not a warning prefix — a stale build is a state, and auto-restart resolves
|
|
@@ -666,25 +662,13 @@ function checkWaitEventMessage(e) {
|
|
|
666
662
|
// working as designed. Names the wait, the pool it waited on, and how many slots are
|
|
667
663
|
// pinned, so the operator can raise worktreeSlots with the numbers in hand.
|
|
668
664
|
const purpose = e.purpose === "verify" ? "check" : "run";
|
|
669
|
-
return
|
|
665
|
+
return `${waitedSpan(e.waitedMs)} to start (${textOr(e.role)} ${purpose}; ${textOr(e.slots)} workspaces, ${textOr(e.pinned)} pinned)`;
|
|
670
666
|
}
|
|
671
667
|
case "provider_wait": {
|
|
672
|
-
// Routine-with-explanation, like slot_wait: waiting for
|
|
673
|
-
// working as designed, so no warning prefix. Names the wait
|
|
674
|
-
//
|
|
675
|
-
|
|
676
|
-
// (a team-cap wait names no key); one without `cap` (logged before it existed) renders
|
|
677
|
-
// the key-or-run-slot line alone.
|
|
678
|
-
const span = shortSpanPhrase(finiteNumber(e.waitedMs, 0));
|
|
679
|
-
const limit = finiteNumber(e.limit, Number.NaN);
|
|
680
|
-
const hasKey = typeof e.key === "string" && e.key.length > 0;
|
|
681
|
-
if (e.cap === "provider" && hasKey) {
|
|
682
|
-
return `waited ${span} for a ${e.key} slot (${textOr(e.role)}${Number.isFinite(limit) ? `; maxConcurrentPerProvider.${e.key} is ${limit}` : ""})`;
|
|
683
|
-
}
|
|
684
|
-
if (e.cap === "team") {
|
|
685
|
-
return `waited ${span} for a run slot (${textOr(e.role)}${Number.isFinite(limit) ? `; maxConcurrent is ${limit}` : ""})`;
|
|
686
|
-
}
|
|
687
|
-
return `waited ${span} for a ${hasKey ? `${e.key} slot` : "run slot"} (${textOr(e.role)})`;
|
|
668
|
+
// Routine-with-explanation, like slot_wait: waiting for the team slot is the cap
|
|
669
|
+
// working as designed, so no warning prefix. Names the wait, so the operator can size
|
|
670
|
+
// maxConcurrent with the number in hand.
|
|
671
|
+
return `${waitedSpan(e.waitedMs)} for a run slot (${textOr(e.role)})`;
|
|
688
672
|
}
|
|
689
673
|
case "check_wait":
|
|
690
674
|
// Routine-with-explanation, like slot_wait: waiting for a check permit is the cap working
|
|
@@ -694,7 +678,7 @@ function checkWaitEventMessage(e) {
|
|
|
694
678
|
const heldBy = stringList(e.heldBy);
|
|
695
679
|
const limit = finiteNumber(e.limit, null);
|
|
696
680
|
const behind = heldBy.length > 0 ? `; held by ${heldBy.join(", ")}${limit === null ? "" : ` of maxConcurrentChecks ${limit}`}` : "";
|
|
697
|
-
return
|
|
681
|
+
return `${waitedSpan(e.waitedMs)} for a check slot (${textOr(e.role)} ${textOr(e.scope)} check${behind})`;
|
|
698
682
|
}
|
|
699
683
|
default: return textOr(e.type);
|
|
700
684
|
}
|
|
@@ -754,8 +738,6 @@ export function eventMessage(e) {
|
|
|
754
738
|
case "budget_resumed":
|
|
755
739
|
case "budget_handback":
|
|
756
740
|
case "fallback_switched":
|
|
757
|
-
case "model_fallback_started":
|
|
758
|
-
case "model_fallback_ended":
|
|
759
741
|
case "opencode_free_model_billed": return budgetEventMessage(e);
|
|
760
742
|
case "team_paused":
|
|
761
743
|
case "team_resumed":
|
|
@@ -775,12 +757,9 @@ export function eventMessage(e) {
|
|
|
775
757
|
case "quiet_hours_ended":
|
|
776
758
|
case "rate_limit_hold":
|
|
777
759
|
case "rate_limit_resumed":
|
|
778
|
-
case "subscription_window_warning":
|
|
779
|
-
case "subscription_window_reserved":
|
|
780
760
|
case "subscription_window_limited":
|
|
781
761
|
case "subscription_window_reset": return resourceEventMessage(e);
|
|
782
762
|
case "max_concurrent_changed":
|
|
783
|
-
case "provider_concurrency_changed":
|
|
784
763
|
case "retention_changed":
|
|
785
764
|
case "config_changed":
|
|
786
765
|
case "model_changed":
|
|
@@ -789,6 +768,7 @@ export function eventMessage(e) {
|
|
|
789
768
|
case "orchestrator_start":
|
|
790
769
|
case "orchestrator_stop": return configEventMessage(e);
|
|
791
770
|
case "image_blind":
|
|
771
|
+
case "image_reroute":
|
|
792
772
|
case "build_stale":
|
|
793
773
|
case "restart_pending":
|
|
794
774
|
case "restart":
|
|
@@ -70,20 +70,16 @@ export const EVENT_TYPE_LABELS = {
|
|
|
70
70
|
check_wait: "waited for a check slot",
|
|
71
71
|
rate_limit_hold: "waiting: provider limit",
|
|
72
72
|
rate_limit_resumed: "provider limit lifted",
|
|
73
|
-
subscription_window_reserved: "usage window reserved",
|
|
74
|
-
subscription_window_warning: "usage window warning",
|
|
75
73
|
subscription_window_limited: "waiting: usage window",
|
|
76
74
|
subscription_window_reset: "usage window reset",
|
|
77
75
|
max_concurrent_changed: "concurrency changed",
|
|
78
|
-
provider_concurrency_changed: "provider concurrency changed",
|
|
79
76
|
retention_changed: "retention changed",
|
|
80
77
|
config_changed: "settings changed",
|
|
81
78
|
model_changed: "model changed",
|
|
82
79
|
backend_binary: "backend checked",
|
|
83
|
-
model_fallback_started: "fallback model started",
|
|
84
|
-
model_fallback_ended: "fallback model ended",
|
|
85
80
|
opencode_free_model_billed: "free model billed",
|
|
86
81
|
image_blind: "image not seen",
|
|
82
|
+
image_reroute: "image sent to another model",
|
|
87
83
|
build_stale: "update available",
|
|
88
84
|
restart_pending: "update waits for idle",
|
|
89
85
|
restart: "restarted onto update",
|
|
@@ -25,8 +25,6 @@ const LEGACY_EVENT_TYPES = {
|
|
|
25
25
|
land_failed: "merge_failed", // vocabulary-ok: legacy event name read from old logs
|
|
26
26
|
// retired-name-ok:end
|
|
27
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
28
|
claude_window_limited: "subscription_window_limited", // vocabulary-ok: legacy event name read from old logs
|
|
31
29
|
claude_window_reset: "subscription_window_reset", // vocabulary-ok: legacy event name read from old logs
|
|
32
30
|
// retired-name-ok:end
|
|
@@ -34,7 +32,7 @@ const LEGACY_EVENT_TYPES = {
|
|
|
34
32
|
/** The ordered field rewrites; later parts of the series append here, and they run in order. */
|
|
35
33
|
const LEGACY_EVENT_REWRITES = [
|
|
36
34
|
{
|
|
37
|
-
// The run number rode `tick` on run_start, run_end, prompt_taken, image_blind.
|
|
35
|
+
// The run number rode `tick` on run_start, run_end, prompt_taken, image_blind, image_reroute.
|
|
38
36
|
name: "tick-to-run", // retired-name-ok: legacy name
|
|
39
37
|
apply(ev) {
|
|
40
38
|
const e = ev;
|
|
@@ -110,8 +108,8 @@ const LEGACY_EVENT_REWRITES = [
|
|
|
110
108
|
},
|
|
111
109
|
},
|
|
112
110
|
{
|
|
113
|
-
// A retired role id on an old line reads as the role's current id. The table
|
|
114
|
-
//
|
|
111
|
+
// A retired role id on an old line reads as the role's current id. The table grows a row
|
|
112
|
+
// per retired role as future switch parts rename; a row already present applies here.
|
|
115
113
|
name: "legacy-role-ids",
|
|
116
114
|
apply(ev) {
|
|
117
115
|
const e = ev;
|
|
@@ -10,12 +10,12 @@ import { childEnv } from "../process/process.js";
|
|
|
10
10
|
*
|
|
11
11
|
* Scope, stated plainly: the notifier subscribes via subscribeEvents (src/events/events.ts), which
|
|
12
12
|
* only sees events THIS process logs. Every allowlisted type but `supervisor_exit` is logged by
|
|
13
|
-
* the orchestrator's own process (budget-gates.ts, gate-polls.ts → streak-
|
|
14
|
-
* role-cap-gates.ts and disk-
|
|
13
|
+
* the orchestrator's own process (budget-gates.ts, gate-polls.ts → streak-gates.ts,
|
|
14
|
+
* role-cap-gates.ts and disk-gates.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-start.ts pages it directly with notifyTeamDown;
|
|
18
|
-
* appended by
|
|
17
|
+
* orchestrator is gone, so src/cli/cli-start-supervisor.ts pages it directly with notifyTeamDown;
|
|
18
|
+
* events appended by
|
|
19
19
|
* operator CLI commands go straight to the file and are out of scope by design.
|
|
20
20
|
*
|
|
21
21
|
* One knob, an opinionated fixed allowlist, no per-event selection — a command that can post
|
|
@@ -45,7 +45,7 @@ export const NOTIFY_EVENT_TYPES = [
|
|
|
45
45
|
export const NOTIFY_MIN_GAP_MS = 60_000;
|
|
46
46
|
/** The child environment the notify hook runs with: the three TUMWATER_EVENT_* variables over
|
|
47
47
|
* the harness's own environment. One home for the variable names, shared by the notifier's
|
|
48
|
-
* spawn and the `tumwater notify --test` diagnostic (src/cli/notify-command.ts), so the two
|
|
48
|
+
* spawn and the `tumwater notify --test` diagnostic (src/cli/cli-notify-command.ts), so the two
|
|
49
49
|
* cannot drift on what a hook sees. */
|
|
50
50
|
export function notifyCommandEnv(type, source, message) {
|
|
51
51
|
return childEnv({
|
|
@@ -76,7 +76,7 @@ export function notifyExitWarning(type, code, signal) {
|
|
|
76
76
|
* not found", 126 its "found but not executable" — most often the configured `notify` command
|
|
77
77
|
* is a typo or an uninstalled/non-executable binary, so a bare "code 127" hides the fix the
|
|
78
78
|
* gloss names. Shared by the notifier's warning (notifyExitWarning above) and `tumwater
|
|
79
|
-
* notify --test`'s diagnostic (src/cli/notify-command.ts) so both surfaces name the same fix
|
|
79
|
+
* notify --test`'s diagnostic (src/cli/cli-notify-command.ts) so both surfaces name the same fix
|
|
80
80
|
* for a hook that cannot launch. Any other code gets no gloss. */
|
|
81
81
|
export function notifyExitGloss(code) {
|
|
82
82
|
return code === 127 ? " (command not found)" : code === 126 ? " (found but not executable)" : "";
|
|
@@ -87,7 +87,7 @@ function runStartEvents(events) {
|
|
|
87
87
|
}
|
|
88
88
|
/** The run_start pairing's start epoch ms: `${role}#${run}` → start epoch ms, the projection
|
|
89
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
|
|
90
|
+
* (history-data.ts), the failure digest's time-and-spend fold (failure-time-spend.ts), and the run
|
|
91
91
|
* drill-down's duration (run-detail-data.ts), so those consumers cannot drift into different
|
|
92
92
|
* notions of a run's span. A run_end whose start is not in `events` (log rotation cut it, or
|
|
93
93
|
* the run was skipped before any start logged) has no entry; callers decide what an unpaired
|
|
@@ -321,7 +321,7 @@ export function readEventsSinceJoined(root, sinceMs, joinBackslackMs) {
|
|
|
321
321
|
(window.events[firstNarrowIdx]?.ts ?? Number.POSITIVE_INFINITY) <= cutoff;
|
|
322
322
|
return { cutoff, events, join: { cutoff: joinCutoff, events: joinEvents }, covered };
|
|
323
323
|
}
|
|
324
|
-
/** The duration-shaped windowed read behind `logs --since` (log-commands.ts): the cutoff is
|
|
324
|
+
/** The duration-shaped windowed read behind `logs --since` (cli-log-commands.ts): the cutoff is
|
|
325
325
|
* now − sinceMs, the rotation-spanning read is keyed on the cutoff's local calendar day
|
|
326
326
|
* (dayKey — the shared dayKey helper eventDayKey buckets events with, so the read's day keys
|
|
327
327
|
* cannot disagree with the ts filter), the day-keyed read may include earlier hours of that
|
|
@@ -97,6 +97,14 @@ export function logEventBestEffort(root, event) {
|
|
|
97
97
|
process.stderr.write(`tumwater: ${stripTerminalControls(detail)}\n`);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
+
/** Log one `image_blind` event (plans/image-input.md "Detecting a blind read"): an agent read an
|
|
101
|
+
* image its model cannot see — the authoring run was killed (action "fail") or a gate run merely
|
|
102
|
+
* recorded the read (action "logged"). The runner and both review paths used to build this exact
|
|
103
|
+
* event object themselves (source + type + run + path + model + action); it lives here so the
|
|
104
|
+
* payload has one home and cannot drift between the three callers. */
|
|
105
|
+
export function logBlindRead(root, source, run, path, model, action) {
|
|
106
|
+
logEventBestEffort(root, { source, type: "image_blind", run, path, model, action });
|
|
107
|
+
}
|
|
100
108
|
/** warnEvent's twin of logEventBestEffort: the "something is off but the loop continues"
|
|
101
109
|
* warning must not be the thing that ends the role it warns about. The fallback keeps the
|
|
102
110
|
* warning's own text on stderr — when the feed is the failure, this line is the only place the
|
|
@@ -1,6 +1,6 @@
|
|
|
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
|
-
* (src/team/error-storm.ts) both count by these keys, so the normalization, the two
|
|
3
|
+
* (src/team/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. */
|
|
@@ -12,13 +12,13 @@ import { cutSplitsSurrogatePair } from "../text/text.js";
|
|
|
12
12
|
export const EXAMPLE_MAX = 120;
|
|
13
13
|
/** The example and cluster key every message-less failure takes: errorTextOrPlaceholder
|
|
14
14
|
* returns it for the digest's error clusters (failure-data.ts) and its loss ranking
|
|
15
|
-
* (time-spend.ts), so a broken `run_end` that skipped its error text reads the same on both
|
|
15
|
+
* (failure-time-spend.ts), so a broken `run_end` that skipped its error text reads the same on both
|
|
16
16
|
* surfaces. */
|
|
17
17
|
const NO_ERROR_TEXT = "(no error text recorded)";
|
|
18
18
|
/** The error text a failure event carries, or NO_ERROR_TEXT when it carries none — the field
|
|
19
19
|
* is a non-empty string or absent. The single home of the placeholder rule, shared by the
|
|
20
20
|
* digest's error clusters (failure-data.ts) and the loss ranking's clustered and review_error
|
|
21
|
-
* arms (time-spend.ts), so a message-less failure reads the same on every surface. */
|
|
21
|
+
* arms (failure-time-spend.ts), so a message-less failure reads the same on every surface. */
|
|
22
22
|
export function errorTextOrPlaceholder(v) {
|
|
23
23
|
return typeof v === "string" && v !== "" ? v : NO_ERROR_TEXT;
|
|
24
24
|
}
|
|
@@ -49,7 +49,7 @@ export function truncateExample(message, max = EXAMPLE_MAX) {
|
|
|
49
49
|
* array in ascending localeCompare order, so equal inputs always render identically no matter
|
|
50
50
|
* the Set's insertion order. One home for the rule the failure digest's clusters
|
|
51
51
|
* (clusterMessages) and loss causes (failure-data.ts) and the error-storm warning
|
|
52
|
-
* (src/team/error-storm.ts) all share — an inline `.sort()` without the comparator would
|
|
52
|
+
* (src/team/team-error-storm.ts) all share — an inline `.sort()` without the comparator would
|
|
53
53
|
* silently pick code-unit order instead and let the two rules drift. */
|
|
54
54
|
export function sortedRoles(roles) {
|
|
55
55
|
return [...roles].sort((a, b) => a.localeCompare(b));
|
|
@@ -76,7 +76,7 @@ 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 run-timeout errors src/agent/run
|
|
79
|
+
/** normalizeClusterKey's rendering of the two run-timeout errors src/agent/agent-run.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. */
|
|
@@ -86,24 +86,15 @@ const RUN_TIMEOUT_PROGRESSING_KEY = "timed out after <dur> while still making pr
|
|
|
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
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
|
-
|
|
90
|
-
|
|
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 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
|
-
/** The known progressing-timeout wordings and the key each pools under. The two review
|
|
96
|
-
* wordings name one cause on one knob (review.timeoutSeconds): a still-making-progress kill
|
|
89
|
+
/** The known progressing-timeout wordings and the key each pools under. The review
|
|
90
|
+
* wording names one cause on one knob (review.timeoutSeconds): a still-making-progress kill
|
|
97
91
|
* pools into the plain run-timeout row (a mixed team of plain and progressing kills is one
|
|
98
92
|
* cause's agent-hours, not two half-size rows the top-N cut can drop — the same pooling the
|
|
99
|
-
* error-storm reducer applies, src/team/error-storm.ts)
|
|
100
|
-
*
|
|
101
|
-
* re-review. Enumerated so a future wording edit lands in
|
|
102
|
-
* this table rather than silently changing which row its timeouts count under. */
|
|
93
|
+
* error-storm reducer applies, src/team/team-error-storm.ts). Enumerated so a future wording
|
|
94
|
+
* edit lands in this table rather than silently changing which row its timeouts count under. */
|
|
103
95
|
const TIMEOUT_POOL = new Map([
|
|
104
96
|
[RUN_TIMEOUT_PROGRESSING_KEY, RUN_TIMEOUT_KEY],
|
|
105
97
|
[RUN_TIMEOUT_PROGRESSING_REVIEW_KEY, RUN_TIMEOUT_KEY],
|
|
106
|
-
[RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY, RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY],
|
|
107
98
|
]);
|
|
108
99
|
/** The cluster key a normalized error clusters under: the pooled wordings above map to their
|
|
109
100
|
* row, and every other cause stands as normalizeClusterKey rendered it. */
|
|
@@ -2,11 +2,11 @@ import { assertReportSinceMs, eventWindowCovers, readWindowEvents, sinceWindowTi
|
|
|
2
2
|
import { eventDayKey, eventRole } from "../events/event-read.js";
|
|
3
3
|
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
4
4
|
import { scopeBases } from "../report/report-fold.js";
|
|
5
|
-
import { timeAndSpend } from "./time-spend.js";
|
|
6
|
-
import { dayAt, dayKey, dayWindow, formatDate } from "../text/datetime.js";
|
|
5
|
+
import { timeAndSpend } from "./failure-time-spend.js";
|
|
6
|
+
import { dayAt, dayKey, dayWindow, DAY_MS, formatDate } from "../text/datetime.js";
|
|
7
7
|
import { describeStateChange, STATE_CHANGE_TOP, STATE_CHANGE_TYPES } from "./failure-state-change.js";
|
|
8
8
|
import { clusterMessages, errorTextOrPlaceholder, truncateExample } from "./failure-cluster.js";
|
|
9
|
-
import { addTo, getOrCreate, memoSetLru, rankByCount } from "../collections.js";
|
|
9
|
+
import { addTo, distinctOrdered, getOrCreate, memoSetLru, rankByCount } from "../collections.js";
|
|
10
10
|
import { firstReason } from "../text/phrases.js";
|
|
11
11
|
import { sameStatIdentity, statIdentityOrZero, statOrNull } from "../files/files.js";
|
|
12
12
|
import { nonNegativeNumber } from "../files/json-object.js";
|
|
@@ -100,9 +100,13 @@ function mergeFailureSection(events) {
|
|
|
100
100
|
}
|
|
101
101
|
return section(clusterMessages(messages, MERGE_FAILURE_TOP), total);
|
|
102
102
|
}
|
|
103
|
+
/** A role with no counted outcomes — the zero baseline roleStats starts a role on (a fresh
|
|
104
|
+
* copy, since roleStats mutates it) and deltaRows hands a role missing from either window.
|
|
105
|
+
* One home for the zero shape so a future RoleStats field is added once. */
|
|
106
|
+
const ZERO_ROLE_STATS = { runs: 0, errors: 0, quietKills: 0, rejections: 0 };
|
|
103
107
|
function roleStats(events) {
|
|
104
108
|
const byRole = new Map();
|
|
105
|
-
const statsFor = (role) => getOrCreate(byRole, role, () => ({
|
|
109
|
+
const statsFor = (role) => getOrCreate(byRole, role, () => ({ ...ZERO_ROLE_STATS }));
|
|
106
110
|
for (const ev of events) {
|
|
107
111
|
// Runs are run_end events; a rejection is now recorded by the merge slot, AFTER the
|
|
108
112
|
// authoring run has already ended `queued` (plans/merge-queue.md 3/5). review_rejected is
|
|
@@ -196,7 +200,7 @@ export function collectFailureReportSince(root, sinceMs, role, untilMs) {
|
|
|
196
200
|
prior.push(ev);
|
|
197
201
|
}
|
|
198
202
|
return assembleFailureReport(current, prior, raw.events, eventWindowCovers(raw, priorFrom), raw.logState, {
|
|
199
|
-
days: Math.ceil(length /
|
|
203
|
+
days: Math.ceil(length / DAY_MS),
|
|
200
204
|
from: dayKey(cutoff),
|
|
201
205
|
to: dayKey(end),
|
|
202
206
|
...slice,
|
|
@@ -294,11 +298,11 @@ function outcomeRows(runEvents) {
|
|
|
294
298
|
function deltaRows(current, prior) {
|
|
295
299
|
const curStats = roleStats(current);
|
|
296
300
|
const prevStats = roleStats(prior);
|
|
297
|
-
const roles =
|
|
301
|
+
const roles = distinctOrdered([...curStats.keys(), ...prevStats.keys()]);
|
|
298
302
|
return roles
|
|
299
303
|
.map((role) => {
|
|
300
|
-
const c = curStats.get(role) ??
|
|
301
|
-
const p = prevStats.get(role) ??
|
|
304
|
+
const c = curStats.get(role) ?? ZERO_ROLE_STATS;
|
|
305
|
+
const p = prevStats.get(role) ?? ZERO_ROLE_STATS;
|
|
302
306
|
return {
|
|
303
307
|
role,
|
|
304
308
|
prevRuns: p.runs,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agree, plural } from "../text/phrases.js";
|
|
2
2
|
import { shortSha, usd } from "../text/format.js";
|
|
3
|
-
import { boundedWindowLine, clockHm, dayKey, dayLabel, monthDay, reportWindow, sinceWindowLine } from "../text/datetime.js";
|
|
3
|
+
import { HOUR_MS, boundedWindowLine, clockHm, dayKey, dayLabel, 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
6
|
import { eventTypeLabel } from "../events/event-labels.js";
|
|
@@ -99,7 +99,7 @@ function spendCell(cell) {
|
|
|
99
99
|
* from shortSpanPhrase's minutes-or-seconds (an event-feed phrase), because a summed 8-hour
|
|
100
100
|
* timeout episode must not read as "480m". */
|
|
101
101
|
function hoursPhrase(ms) {
|
|
102
|
-
return `${(ms /
|
|
102
|
+
return `${(ms / HOUR_MS).toFixed(1)} h`;
|
|
103
103
|
}
|
|
104
104
|
/** Render the digest as bounded Markdown. Byte bound: for a given team the tables grow only with
|
|
105
105
|
* the number of configured roles (fixed by config), the RESULT_ORDER vocabulary (fixed by
|
|
@@ -209,42 +209,52 @@ function pushTimeAndSpend(lines, data) {
|
|
|
209
209
|
lines.push("## Time and spend by outcome");
|
|
210
210
|
if (data.timeSpend.length === 0) {
|
|
211
211
|
lines.push("_no finished runs in the window_");
|
|
212
|
+
return;
|
|
212
213
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
? `context cut-off on ${roleCell(c.roles[0] ?? "?")}`
|
|
237
|
-
: `${c.example} (${roleList(c.roles)})`;
|
|
238
|
-
lines.push(`- ${hoursPhrase(c.ms)} · ${usd(c.costUsd)} — ${plural(c.runs, "run")}: ${cause}`);
|
|
239
|
-
}
|
|
240
|
-
// Mark the top-5 cut the way the cluster sections mark theirs: a capped ranking that
|
|
241
|
-
// stays silent reads as a complete itemization of the window's loss (BUGS.md 2026-10-01).
|
|
242
|
-
if (data.lossCausesHidden > 0) {
|
|
243
|
-
const noun = agree(data.lossCausesHidden, "cause", "causes");
|
|
244
|
-
lines.push(`_+${data.lossCausesHidden} more loss ${noun} by time not listed_`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
214
|
+
pushTimeSpendRows(lines, data);
|
|
215
|
+
pushLossCauses(lines, data);
|
|
216
|
+
}
|
|
217
|
+
/** The role × class table of hours and dollars. */
|
|
218
|
+
function pushTimeSpendRows(lines, data) {
|
|
219
|
+
lines.push(...markdownTable(["role", "merged", "no change", "error or blocked"], data.timeSpend.map((row) => [
|
|
220
|
+
roleCell(row.role),
|
|
221
|
+
spendCell(row.classes.merged),
|
|
222
|
+
spendCell(row.classes.no_change),
|
|
223
|
+
spendCell(row.classes.error),
|
|
224
|
+
])));
|
|
225
|
+
}
|
|
226
|
+
/** The top loss causes by time, capped at the LOSS_TOP cut; a refused cause's text (and the
|
|
227
|
+
* empty ranking's placeholder) lives in `lossCauseText`/`pushLossCauses` here. */
|
|
228
|
+
function pushLossCauses(lines, data) {
|
|
229
|
+
lines.push("");
|
|
230
|
+
lines.push("**Top loss causes by time:**");
|
|
231
|
+
if (data.lossCauses.length === 0) {
|
|
232
|
+
lines.push("_no loss to rank_");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
for (const c of data.lossCauses) {
|
|
236
|
+
lines.push(`- ${hoursPhrase(c.ms)} · ${usd(c.costUsd)} — ${plural(c.runs, "run")}: ${lossCauseText(c)}`);
|
|
247
237
|
}
|
|
238
|
+
// Mark the top-5 cut the way the cluster sections mark theirs: a capped ranking that
|
|
239
|
+
// stays silent reads as a complete itemization of the window's loss (BUGS.md 2026-10-01).
|
|
240
|
+
if (data.lossCausesHidden > 0) {
|
|
241
|
+
const noun = agree(data.lossCausesHidden, "cause", "causes");
|
|
242
|
+
lines.push(`_+${data.lossCausesHidden} more loss ${noun} by time not listed_`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/** One loss cause's bullet text. */
|
|
246
|
+
function lossCauseText(c) {
|
|
247
|
+
return c.kind === "no_change"
|
|
248
|
+
? `no change on ${roleCell(c.roles[0] ?? "?")}`
|
|
249
|
+
: c.kind === "review-rejected"
|
|
250
|
+
? `review-rejected authoring on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
251
|
+
: c.kind === "refused"
|
|
252
|
+
? `refused on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
253
|
+
: c.kind === "merge-conflict" || c.kind === "merge-blocked"
|
|
254
|
+
? `${c.kind} authoring on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
255
|
+
: c.kind === "cutoff"
|
|
256
|
+
? `context cut-off on ${roleCell(c.roles[0] ?? "?")}`
|
|
257
|
+
: `${c.example} (${roleList(c.roles)})`;
|
|
248
258
|
}
|
|
249
259
|
/** Prompt-token telemetry: the per-role median prompt
|
|
250
260
|
* send and how much of the prompt budget goes out before a run's first edit, plus each
|