tumwater 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
/** Words for the two code families the dashboards show: event types (the activity chips) and run
|
|
3
|
+
* result codes (the result cells). Pure, no I/O, so core modules and the UI may both import it. */
|
|
4
|
+
/** The words for each event type. The `Record` type makes the compiler demand a label for every
|
|
5
|
+
* type in the union, so a new event type cannot ship without one; renaming a type renames its key
|
|
6
|
+
* here and leaves the label unchanged. A label is a lowercase phrase of a few words, never the
|
|
7
|
+
* code itself. */
|
|
8
|
+
export const EVENT_TYPE_LABELS = {
|
|
9
|
+
run_start: "run started",
|
|
10
|
+
run_end: "run finished",
|
|
11
|
+
merge_queued: "waiting to merge",
|
|
12
|
+
merge_done: "merge finished",
|
|
13
|
+
merge_failed: "merge failed",
|
|
14
|
+
merged: "merged",
|
|
15
|
+
question_posted: "question posted",
|
|
16
|
+
entry_refused: "plan refused",
|
|
17
|
+
wake: "woke up",
|
|
18
|
+
run_deferred: "run deferred",
|
|
19
|
+
orchestrator_start: "tumwater started",
|
|
20
|
+
orchestrator_stop: "tumwater stopped",
|
|
21
|
+
prompt_enqueued: "message queued",
|
|
22
|
+
prompt_cancelled: "message cancelled",
|
|
23
|
+
prompt_edited: "message edited",
|
|
24
|
+
prompt_topped: "message moved to front",
|
|
25
|
+
prompt_taken: "message picked up",
|
|
26
|
+
counters_reset: "counters reset",
|
|
27
|
+
run_aborted: "run stopped by you",
|
|
28
|
+
resume: "run resumed",
|
|
29
|
+
review_start: "review started",
|
|
30
|
+
review_verdict: "review approved",
|
|
31
|
+
review_rejected: "review rejected",
|
|
32
|
+
review_failed: "review failed",
|
|
33
|
+
revision: "revision round",
|
|
34
|
+
conflict_handback: "sent back to resolve",
|
|
35
|
+
build_check: "check ran",
|
|
36
|
+
dep_install: "dependencies installed",
|
|
37
|
+
budget_warning: "budget warning",
|
|
38
|
+
budget_paused: "budget paused",
|
|
39
|
+
budget_fallback: "over-cap model",
|
|
40
|
+
budget_resumed: "budget resumed",
|
|
41
|
+
budget_handback: "returned to main model",
|
|
42
|
+
fallback_switched: "fallback switched",
|
|
43
|
+
team_paused: "team paused",
|
|
44
|
+
team_resumed: "team resumed",
|
|
45
|
+
state_migrated: "state migrated",
|
|
46
|
+
role_paused: "role paused",
|
|
47
|
+
role_resumed: "role resumed",
|
|
48
|
+
role_streak_paused: "repeated-failure pause",
|
|
49
|
+
role_cap_warning: "role budget warning",
|
|
50
|
+
role_cap_paused: "role budget paused",
|
|
51
|
+
role_cap_resumed: "role budget resumed",
|
|
52
|
+
main_red_hold: "main's check is failing",
|
|
53
|
+
maintenance_quota_hold: "waiting: maintenance allowance used",
|
|
54
|
+
maintenance_quota_resumed: "maintenance allowance reopened",
|
|
55
|
+
bootstrap_complete: "bootstrap complete",
|
|
56
|
+
quiet_hours_started: "quiet hours started",
|
|
57
|
+
quiet_hours_ended: "quiet hours ended",
|
|
58
|
+
disk_low: "waiting: low disk",
|
|
59
|
+
disk_ok: "disk space restored",
|
|
60
|
+
disk_reclaim: "disk space reclaimed",
|
|
61
|
+
claim: "entry claimed",
|
|
62
|
+
permit_reserve: "permits reserved",
|
|
63
|
+
slot_wait: "waited to start",
|
|
64
|
+
provider_wait: "waited for a provider slot",
|
|
65
|
+
rate_limit_hold: "waiting: provider limit",
|
|
66
|
+
rate_limit_resumed: "provider limit lifted",
|
|
67
|
+
claude_window_reserved: "claude usage window reserved",
|
|
68
|
+
claude_window_warning: "claude usage window warning",
|
|
69
|
+
claude_window_limited: "waiting: claude usage window",
|
|
70
|
+
claude_window_reset: "claude usage window reset",
|
|
71
|
+
max_concurrent_changed: "concurrency changed",
|
|
72
|
+
provider_concurrency_changed: "provider concurrency changed",
|
|
73
|
+
retention_changed: "retention changed",
|
|
74
|
+
config_changed: "settings changed",
|
|
75
|
+
model_changed: "model changed",
|
|
76
|
+
backend_binary: "backend checked",
|
|
77
|
+
model_fallback_started: "fallback model started",
|
|
78
|
+
model_fallback_ended: "fallback model ended",
|
|
79
|
+
opencode_free_model_billed: "free model billed",
|
|
80
|
+
image_blind: "image not seen",
|
|
81
|
+
image_reroute: "image sent to another model",
|
|
82
|
+
build_stale: "update available",
|
|
83
|
+
restart_pending: "update waits for idle",
|
|
84
|
+
restart: "restarted onto update",
|
|
85
|
+
restart_refused: "update refused",
|
|
86
|
+
restart_blocked: "update blocked",
|
|
87
|
+
restart_forced: "update forced",
|
|
88
|
+
supervisor_exit: "tumwater exited",
|
|
89
|
+
warning: "warning",
|
|
90
|
+
};
|
|
91
|
+
/** The words for each run result code, shown in the result cells. */
|
|
92
|
+
export const RUN_RESULT_LABELS = {
|
|
93
|
+
changed: "merged",
|
|
94
|
+
queued: "waiting to merge",
|
|
95
|
+
no_change: "no change",
|
|
96
|
+
skipped: "skipped",
|
|
97
|
+
refused: "refused",
|
|
98
|
+
rejected: "rejected in review",
|
|
99
|
+
review_error: "review failed",
|
|
100
|
+
merge_conflict: "merge conflict",
|
|
101
|
+
merge_blocked: "merge blocked",
|
|
102
|
+
error: "error",
|
|
103
|
+
aborted: "interrupted",
|
|
104
|
+
quiet_killed: "stalled, resumed",
|
|
105
|
+
user_aborted: "stopped by you",
|
|
106
|
+
main_red: "main's check is failing",
|
|
107
|
+
};
|
|
108
|
+
/** The words for an event type or run result code: its own-property label, else the code with
|
|
109
|
+
* underscores as spaces. The two code families are disjoint, so one lookup serves both. */
|
|
110
|
+
export function eventTypeLabel(code) {
|
|
111
|
+
if (Object.hasOwn(EVENT_TYPE_LABELS, code))
|
|
112
|
+
return EVENT_TYPE_LABELS[code];
|
|
113
|
+
if (Object.hasOwn(RUN_RESULT_LABELS, code))
|
|
114
|
+
return RUN_RESULT_LABELS[code];
|
|
115
|
+
return code.replace(/_/g, " ");
|
|
116
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { TUMWATER_SOURCE } from "./events.js";
|
|
2
|
+
import { canonicalRoleId } from "../roles/legacy-role-ids.js";
|
|
3
|
+
/** Old event lines read under today's names. `.tumwater/log/events.jsonl` (and its rotated
|
|
4
|
+
* `events.jsonl.1`) outlive the build that wrote them, and a long-lived supervisor on an older
|
|
5
|
+
* build keeps writing old names, so every reader normalizes on read: `normalizeLegacyEvent` is
|
|
6
|
+
* applied by `parseEventLine` (which every file reader goes through) and by `logEvent` (so
|
|
7
|
+
* in-process subscribers see exactly what readers see). Its rewrites are permanent.
|
|
8
|
+
*
|
|
9
|
+
* Role ids (`dry`, `telemetry`) are deliberately NOT mapped here. The role-id rename aliases an
|
|
10
|
+
* event's `source` id through its own legacy-id table when it switches each id, so mapping them
|
|
11
|
+
* here would show `dedupe` while the role is still `dry`. */
|
|
12
|
+
/** An old event type name → today's name. */
|
|
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
|
|
18
|
+
// retired-name-ok:start old event names
|
|
19
|
+
fleet_paused: "team_paused", // vocabulary-ok: legacy event name read from old logs
|
|
20
|
+
fleet_resumed: "team_resumed", // vocabulary-ok: legacy event name read from old logs
|
|
21
|
+
// retired-name-ok:end
|
|
22
|
+
// retired-name-ok:start old event names
|
|
23
|
+
land_queued: "merge_queued", // vocabulary-ok: legacy event name read from old logs
|
|
24
|
+
landed: "merge_done", // vocabulary-ok: legacy event name read from old logs
|
|
25
|
+
land_failed: "merge_failed", // vocabulary-ok: legacy event name read from old logs
|
|
26
|
+
// retired-name-ok:end
|
|
27
|
+
};
|
|
28
|
+
/** The ordered field rewrites; later parts of the series append here, and they run in order. */
|
|
29
|
+
const LEGACY_EVENT_REWRITES = [
|
|
30
|
+
{
|
|
31
|
+
// The run number rode `tick` on run_start, run_end, prompt_taken, image_blind, image_reroute.
|
|
32
|
+
name: "tick-to-run",
|
|
33
|
+
apply(ev) {
|
|
34
|
+
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
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
// build_check.scope and conflict_handback.reason said "landing" for what is now "merge".
|
|
43
|
+
name: "landing-to-merge",
|
|
44
|
+
apply(ev) {
|
|
45
|
+
const e = ev;
|
|
46
|
+
if (e.type === "build_check" && e.scope === "landing")
|
|
47
|
+
e.scope = "merge"; // vocabulary-ok: legacy event value read from old logs
|
|
48
|
+
if (e.type === "conflict_handback" && e.reason === "landing")
|
|
49
|
+
e.reason = "merge"; // vocabulary-ok: legacy event value read from old logs
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
// slot_wait.purpose said "tick"/"vet" for what is now "run"/"verify".
|
|
54
|
+
name: "slot-purpose",
|
|
55
|
+
apply(ev) {
|
|
56
|
+
const e = ev;
|
|
57
|
+
if (e.type !== "slot_wait")
|
|
58
|
+
return;
|
|
59
|
+
if (e.purpose === "tick")
|
|
60
|
+
e.purpose = "run"; // vocabulary-ok: legacy event value read from old logs
|
|
61
|
+
if (e.purpose === "vet")
|
|
62
|
+
e.purpose = "verify"; // vocabulary-ok: legacy event value read from old logs
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
// The restart event's drain fields became finish fields.
|
|
67
|
+
name: "restart-drain-to-finish",
|
|
68
|
+
apply(ev) {
|
|
69
|
+
const e = ev;
|
|
70
|
+
if (e.type !== "restart")
|
|
71
|
+
return;
|
|
72
|
+
const renames = [
|
|
73
|
+
["drainedMs", "finishedMs"], // vocabulary-ok: legacy field name read from old logs
|
|
74
|
+
["abortedTicks", "abortedRuns"], // vocabulary-ok: legacy field name read from old logs
|
|
75
|
+
["drainWindowMs", "finishWindowMs"], // vocabulary-ok: legacy field name read from old logs
|
|
76
|
+
];
|
|
77
|
+
for (const [from, to] of renames) {
|
|
78
|
+
if (from in e && !(to in e)) {
|
|
79
|
+
e[to] = e[from];
|
|
80
|
+
delete e[from];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
// The writer's id rode `loop`, with "harness" for tumwater's own events. A staged-claim
|
|
87
|
+
// `claim` line also carried a payload `source: "staged"`, which moves to `origin` first. A
|
|
88
|
+
// call site that still passes `source: "harness"` (the warn helpers' callers) maps too.
|
|
89
|
+
// Kept permanently though no TypeScript type accepts `loop` any more: old logs and a
|
|
90
|
+
// still-running old supervisor keep writing it, so the read path and logEvent need this.
|
|
91
|
+
name: "loop-to-source",
|
|
92
|
+
apply(ev) {
|
|
93
|
+
const e = ev;
|
|
94
|
+
if (typeof e.loop === "string") { // vocabulary-ok: legacy field name read from old logs
|
|
95
|
+
if (e.type === "claim" && e.source === "staged" && !("origin" in e)) {
|
|
96
|
+
e.origin = e.source;
|
|
97
|
+
delete e.source;
|
|
98
|
+
}
|
|
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
|
|
101
|
+
}
|
|
102
|
+
if (e.source === "harness")
|
|
103
|
+
e.source = TUMWATER_SOURCE; // vocabulary-ok: legacy event source read from old logs
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
// A retired role id on an old line reads as the role's current id. The table is empty
|
|
108
|
+
// until a switch part adds a row, so this changes nothing yet.
|
|
109
|
+
name: "legacy-role-ids",
|
|
110
|
+
apply(ev) {
|
|
111
|
+
const e = ev;
|
|
112
|
+
if (typeof e.source === "string")
|
|
113
|
+
e.source = canonicalRoleId(e.source);
|
|
114
|
+
if (typeof e.role === "string")
|
|
115
|
+
e.role = canonicalRoleId(e.role);
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
/** Today's name for an old event type name; any other string comes back unchanged. */
|
|
120
|
+
export function canonicalEventType(name) {
|
|
121
|
+
return Object.prototype.hasOwnProperty.call(LEGACY_EVENT_TYPES, name) ? LEGACY_EVENT_TYPES[name] : name;
|
|
122
|
+
}
|
|
123
|
+
/** Rewrite a freshly parsed event line in place to today's names and return it. Never throws; a
|
|
124
|
+
* current line comes back untouched. */
|
|
125
|
+
export function normalizeLegacyEvent(ev) {
|
|
126
|
+
if (typeof ev.type === "string") {
|
|
127
|
+
const next = canonicalEventType(ev.type);
|
|
128
|
+
if (next !== ev.type)
|
|
129
|
+
ev.type = next;
|
|
130
|
+
}
|
|
131
|
+
for (const rewrite of LEGACY_EVENT_REWRITES) {
|
|
132
|
+
try {
|
|
133
|
+
rewrite.apply(ev);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// A malformed field is left as written: normalizing must never lose the line.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return ev;
|
|
140
|
+
}
|
|
@@ -3,6 +3,7 @@ import { finiteNumber, parseJsonObject } from "../files/json-object.js";
|
|
|
3
3
|
import { eventsLogPath } from "../paths.js";
|
|
4
4
|
import { cachedByStat } from "../files/stat-cache.js";
|
|
5
5
|
import { readTailTextWithEnd } from "../files/tail.js";
|
|
6
|
+
import { normalizeLegacyEvent } from "./event-legacy.js";
|
|
6
7
|
/** The READ side of the event feed: everything that consumes events.jsonl after the fact —
|
|
7
8
|
* parseEventLine's skip-without-failing rule, the per-event query conventions (eventDayKey,
|
|
8
9
|
* eventRole, eventUsage, tickStartMap/tickSpanMs) the reports and history share, and the
|
|
@@ -20,10 +21,11 @@ import { readTailTextWithEnd } from "../files/tail.js";
|
|
|
20
21
|
* object either: the log is one JSON object per line, so anything else is corrupt (or foreign)
|
|
21
22
|
* and reads as no data — the same object check readJsonFile applies to the harness's state
|
|
22
23
|
* files. Without it, `readEvents` pushes the truthy non-object into the feed and `formatEvent`
|
|
23
|
-
* renders it as an `Invalid Date undefined` line.
|
|
24
|
+
* renders it as an `Invalid Date undefined` line. An old line comes back under today's names
|
|
25
|
+
* (normalizeLegacyEvent, event-legacy.ts). */
|
|
24
26
|
export function parseEventLine(line) {
|
|
25
27
|
const parsed = parseJsonObject(line);
|
|
26
|
-
return parsed ? parsed : null;
|
|
28
|
+
return parsed ? normalizeLegacyEvent(parsed) : null;
|
|
27
29
|
}
|
|
28
30
|
/** The local calendar-day key ("YYYY-MM-DD") of an event's `ts`, or null when it carries no
|
|
29
31
|
* numeric timestamp. The one day-bucketing rule the windowed reader (event-window.ts) and the
|
|
@@ -40,19 +42,19 @@ export function eventDayKey(ev) {
|
|
|
40
42
|
/** The loop id an event is filed under, or "?" when absent or empty — the guard the usage
|
|
41
43
|
* report and the failure digest both apply when grouping by role. */
|
|
42
44
|
export function eventRole(ev) {
|
|
43
|
-
return typeof ev.
|
|
45
|
+
return typeof ev.source === "string" && ev.source !== "" ? ev.source : "?";
|
|
44
46
|
}
|
|
45
47
|
/** The tick number an event names, or 0 when it is absent or non-finite. The one guard every
|
|
46
48
|
* display surface applies to the loosened `tick` field: an event log is read without a shape
|
|
47
49
|
* filter, so a torn or hand-edited line can carry no tick (String(undefined) printed "#undefined"
|
|
48
50
|
* in the feed) or a non-numeric one (String(Number(…)) printed "#NaN" in history). Ticks are
|
|
49
|
-
* 1-based — the loop increments state.
|
|
51
|
+
* 1-based — the loop increments state.runs before logging its first tick — so 0 is visibly not
|
|
50
52
|
* a real tick, the same corrupt-value-reads-as-0 rule gigabytes and the failure digest's
|
|
51
53
|
* aheadCommits apply, kept finite so a JSON consumer never sees NaN's null stand-in. */
|
|
52
54
|
export function eventTick(ev) {
|
|
53
|
-
return finiteNumber(ev.
|
|
55
|
+
return finiteNumber(ev.run, 0);
|
|
54
56
|
}
|
|
55
|
-
/** The usage numbers an event records (`
|
|
57
|
+
/** The usage numbers an event records (`run_end`'s own run, `merge_done`'s merge slot):
|
|
56
58
|
* `tokens` and `costUsd` arrive `unknown` through the event's index signature, so they are
|
|
57
59
|
* coerced to numbers here, with an absent, non-numeric, or non-finite value reading as 0 —
|
|
58
60
|
* the one convention every usage consumer applies (the event feed's usage fragment,
|
|
@@ -66,27 +68,61 @@ export function eventUsage(ev) {
|
|
|
66
68
|
const notionalCostUsd = finiteNumber(ev.notionalCostUsd, 0);
|
|
67
69
|
return { tokens, costUsd, notionalCostUsd };
|
|
68
70
|
}
|
|
69
|
-
/** Pair the
|
|
70
|
-
* the one home of that pairing and its key format, shared by
|
|
71
|
+
/** Pair the run_start events of `events` by tick, keyed `${loop}#${tick}` → the start event
|
|
72
|
+
* itself — the one home of that pairing and its key format, shared by the timestamps
|
|
73
|
+
* (tickStartMap), the models (tickStartModelMap), and the tick drill-down's duration
|
|
74
|
+
* (tick-detail-data.ts uses tickSpanMs' key). A run_start whose `ts` is not a finite number is
|
|
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 eventTick/eventUsage apply to their fields), so every
|
|
77
|
+
* entry is a real start the projections below can trust. Private: external consumers read one
|
|
78
|
+
* of the projections, not this raw pairing. */
|
|
79
|
+
function runStartMap(events) {
|
|
80
|
+
const starts = new Map();
|
|
81
|
+
for (const e of events) {
|
|
82
|
+
const ts = finiteNumber(e.ts, null);
|
|
83
|
+
if (e.type === "run_start" && ts !== null)
|
|
84
|
+
starts.set(`${e.source}#${e.run}`, e);
|
|
85
|
+
}
|
|
86
|
+
return starts;
|
|
87
|
+
}
|
|
88
|
+
/** The run_start pairing's start epoch ms: `${loop}#${tick}` → start epoch ms, the projection
|
|
89
|
+
* of runStartMap onto each event's `ts` — shared by `tumwater history`'s row spans
|
|
71
90
|
* (history-data.ts), the failure digest's time-and-spend fold (time-spend.ts), and the tick
|
|
72
91
|
* drill-down's duration (tick-detail-data.ts), so those consumers cannot drift into different
|
|
73
|
-
* notions of a tick's span. A
|
|
92
|
+
* notions of a tick's span. A run_end whose start is not in `events` (log rotation cut it, or
|
|
74
93
|
* the tick was skipped before any start logged) has no entry; callers decide what an unpaired
|
|
75
|
-
* end costs.
|
|
76
|
-
*
|
|
77
|
-
* policy eventTick/eventUsage apply to their fields), so every value in the map is real epoch
|
|
78
|
-
* ms and the span below cannot subtract from `undefined`. Lives in the read module, not in a
|
|
79
|
-
* ui module, so core collectors can share it without a core→ui import. */
|
|
94
|
+
* end costs. Lives in the read module, not in a ui module, so core collectors can share it
|
|
95
|
+
* without a core→ui import. */
|
|
80
96
|
export function tickStartMap(events) {
|
|
81
97
|
const starts = new Map();
|
|
82
|
-
for (const e of events)
|
|
83
|
-
|
|
84
|
-
if (e.type === "tick_start" && ts !== null)
|
|
85
|
-
starts.set(`${e.loop}#${e.tick}`, ts);
|
|
86
|
-
}
|
|
98
|
+
for (const [key, e] of runStartMap(events))
|
|
99
|
+
starts.set(key, finiteNumber(e.ts, 0));
|
|
87
100
|
return starts;
|
|
88
101
|
}
|
|
89
|
-
/** The
|
|
102
|
+
/** The run_start pairing's model selectors: `${loop}#${tick}` → the run's model when its
|
|
103
|
+
* run_start carries a non-empty string `model` (the agent-qualified selector string — a
|
|
104
|
+
* per-role override, a fallback, or the primary — that the status rows carry; see
|
|
105
|
+
* config-views.ts's agentQualifiedSelector). A run_start with no `model`, or an empty or
|
|
106
|
+
* non-string one, adds no entry, so `tumwater history`'s model cell renders empty for a start
|
|
107
|
+
* that named none — exactly the empty-usage convention applied to cells. The finite-`ts`
|
|
108
|
+
* guard rides runStartMap, the one home of the pairing. */
|
|
109
|
+
export function tickStartModelMap(events) {
|
|
110
|
+
const models = new Map();
|
|
111
|
+
for (const [key, e] of runStartMap(events)) {
|
|
112
|
+
if (typeof e.model === "string" && e.model !== "")
|
|
113
|
+
models.set(key, e.model);
|
|
114
|
+
}
|
|
115
|
+
return models;
|
|
116
|
+
}
|
|
117
|
+
/** The model of the run_end event `ev` against `models` (tickStartModelMap's pairing): the
|
|
118
|
+
* `${loop}#${tick}` 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 tickSpanMs. null when the
|
|
120
|
+
* start is missing (rotation cut it) or carried no model; callers decide what an empty model
|
|
121
|
+
* costs — history renders it as an empty cell. */
|
|
122
|
+
export function tickStartModel(ev, models) {
|
|
123
|
+
return models.get(`${ev.source}#${ev.run}`) ?? null;
|
|
124
|
+
}
|
|
125
|
+
/** The start→end span of the `run_end` event `ev` against `starts` (tickStartMap's pairing):
|
|
90
126
|
* the `${loop}#${tick}` lookup and the clamp to non-negative, in one place beside the map
|
|
91
127
|
* they index, so the key format and the span rule are spelled once. null when the start is
|
|
92
128
|
* missing (rotation cut it) or when `ev.ts` is not a finite number (a torn or hand-edited end
|
|
@@ -97,12 +133,12 @@ export function tickStartMap(events) {
|
|
|
97
133
|
* (`tickSpanMs(ev, starts) ?? 0`) folded that NaN straight into its agent-hour totals —
|
|
98
134
|
* `NaN ?? 0` is still NaN. */
|
|
99
135
|
export function tickSpanMs(ev, starts) {
|
|
100
|
-
const startTs = starts.get(`${ev.
|
|
136
|
+
const startTs = starts.get(`${ev.source}#${ev.run}`);
|
|
101
137
|
const endTs = finiteNumber(ev.ts, null);
|
|
102
138
|
return startTs === undefined || endTs === null ? null : Math.max(0, endTs - startTs);
|
|
103
139
|
}
|
|
104
140
|
/** The event-tail size readEvents falls back to when a caller omits `limit` — a working
|
|
105
|
-
* handful of a busy
|
|
141
|
+
* handful of a busy team's recent activity, small enough that the per-poll tail read stays
|
|
106
142
|
* cheap. Exported so a caller that starts a scan at the default (status-polls.ts's
|
|
107
143
|
* mainCheckForPoll ladder) grows from the same value instead of re-spelling the literal. */
|
|
108
144
|
export const DEFAULT_EVENT_TAIL = 200;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dayKey, humanSeconds } from "../text/datetime.js";
|
|
1
|
+
import { dayAt, dayKey, humanSeconds } from "../text/datetime.js";
|
|
2
2
|
import { eventDayKey, parseEventLine } from "./event-read.js";
|
|
3
3
|
import { eventsArchivePath, eventsLogPath } from "../paths.js";
|
|
4
4
|
import { statOrNull } from "../files/files.js";
|
|
@@ -23,16 +23,24 @@ export const LOGS_SINCE_MAX_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
23
23
|
* signal, and the log rotates at 16 MB (EVENTS_MAX_BYTES in events.ts) anyway. Lives here so
|
|
24
24
|
* the CLI's flag validation and the collector's window read share one bound. */
|
|
25
25
|
export const REPORT_SINCE_MAX_MS = 7 * 24 * 60 * 60 * 1000;
|
|
26
|
+
/** Why a trailing-window length is refused, as the rejection text naming the out-of-range
|
|
27
|
+
* value (`sinceMs must be between 1 and 7d (got 604800000)`). The single home of the report
|
|
28
|
+
* 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
|
+
* CLI flag uses parseSinceFlag's own distinct wording. */
|
|
31
|
+
export function reportSinceMsg(sinceMs) {
|
|
32
|
+
return `sinceMs must be between 1 and ${humanSeconds(REPORT_SINCE_MAX_MS / 1000)} (got ${sinceMs})`;
|
|
33
|
+
}
|
|
26
34
|
/** Reject a trailing-window length outside the report bound before a collector reads the log.
|
|
27
35
|
* The CLI's parseSinceFlag already rejects one, but both trailing-window collectors are also
|
|
28
36
|
* called directly (tests, future callers), so the data layer enforces the bound too. A
|
|
29
37
|
* non-finite length is refused here as well: NaN slips past both comparisons (`NaN <= 0` and
|
|
30
38
|
* `NaN > MAX` are each false) and would reach the window arithmetic, so the guard tests
|
|
31
|
-
* finiteness first — the same rule the persisted-state readers apply.
|
|
32
|
-
*
|
|
39
|
+
* finiteness first — the same rule the persisted-state readers apply. The message comes from
|
|
40
|
+
* reportSinceMsg so the data layer and the dashboard share the bound's wording. */
|
|
33
41
|
export function assertReportSinceMs(sinceMs) {
|
|
34
42
|
if (!Number.isFinite(sinceMs) || sinceMs <= 0 || sinceMs > REPORT_SINCE_MAX_MS)
|
|
35
|
-
throw new Error(
|
|
43
|
+
throw new Error(reportSinceMsg(sinceMs));
|
|
36
44
|
}
|
|
37
45
|
/** The one-line note every windowed render prints when its read cannot prove the window was
|
|
38
46
|
* fully covered (coversFullWindow false or eventWindowCovers false): the log's oldest retained
|
|
@@ -139,7 +147,7 @@ export function eventWindowCovers(w, cutoff) {
|
|
|
139
147
|
* as the oldest complete line in hand predates the window — cost scales with the window's size,
|
|
140
148
|
* not the file's. `coversFullWindow` records whether this file reaches back before the window
|
|
141
149
|
* (see EventWindow's field doc), so the caller can tell "retention cut the window" from "the
|
|
142
|
-
*
|
|
150
|
+
* team was idle": both leave the oldest returned event later than the window start, but only
|
|
143
151
|
* the former means data was lost. */
|
|
144
152
|
function scanEventsFile(file, fromKey) {
|
|
145
153
|
const st = statOrNull(file);
|
|
@@ -232,7 +240,7 @@ function scanFileReadState(file) {
|
|
|
232
240
|
}
|
|
233
241
|
/** The duration-shaped windowed read whose consumer needs a SECOND view of the same window
|
|
234
242
|
* with a wider cutoff (history's tick-row join: a queued tick spanning the rows' cutoff logged
|
|
235
|
-
* its
|
|
243
|
+
* its merge_queued pin before that cutoff, so its merge verdict must be joined from evidence
|
|
236
244
|
* up to `joinBackslackMs` older). One backwards scan serves both: the wide read's day key never
|
|
237
245
|
* sits after the narrow one's, so every `ts >= cutoff` event it holds is exactly the narrow
|
|
238
246
|
* read's event list — the narrow scan reads a prefix of the wide scan's bytes and stops no
|
|
@@ -260,7 +268,7 @@ export function readEventsSinceJoined(root, sinceMs, joinBackslackMs) {
|
|
|
260
268
|
// exactly ts < that midnight — so the checks below are numeric and the event list's
|
|
261
269
|
// oldest-first order makes them one bounded pass instead of a day-key formatting per event.
|
|
262
270
|
const cut = new Date(cutoff);
|
|
263
|
-
const narrowDayStartMs =
|
|
271
|
+
const narrowDayStartMs = dayAt(0, cut).getTime();
|
|
264
272
|
// One pass up the oldest-first list: the oldest numerically-timestamped event (any line
|
|
265
273
|
// older than the narrow day proves the narrow scan would have early-stopped on it) and
|
|
266
274
|
// the first event inside the narrow day (the narrow window's oldest retained event, for
|
|
@@ -298,3 +306,15 @@ export function readEventsSince(root, sinceMs) {
|
|
|
298
306
|
const events = window.events.filter((e) => typeof e.ts === "number" && e.ts >= cutoff);
|
|
299
307
|
return { cutoff, events, covered: eventWindowCovers(window, cutoff) };
|
|
300
308
|
}
|
|
309
|
+
/** The "recent events of a kind" read every doctor wait/failure check shares: events since the
|
|
310
|
+
* trailing `sinceMs` window, kept only when they clear `now − sinceMs` (the caller's seam for the
|
|
311
|
+
* 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 wait check each
|
|
313
|
+
* spelled beside readEventsSince's cutoff — the kind + recency rule cannot drift per caller. `keep`
|
|
314
|
+
* defaults to every event, so the ts-only reader passes nothing; `now` is explicit so every call in
|
|
315
|
+
* one window derives from the same instant (readEventsSince's own cutoff still uses the real
|
|
316
|
+
* clock, so this recheck is what a test seam can pin). `ts` is already number-typed by
|
|
317
|
+
* readEventsSince's filter, so the recheck never compares against a NaN. */
|
|
318
|
+
export function eventsSince(root, sinceMs, now, keep = () => true) {
|
|
319
|
+
return readEventsSince(root, sinceMs).events.filter((ev) => keep(ev) && ev.ts >= now - sinceMs);
|
|
320
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { eventsLogPath } from "../paths.js";
|
|
3
3
|
import { ensureParentDir, rotateIfLarge, sameStatIdentity, statOrNull, terminateTornTail } from "../files/files.js";
|
|
4
|
+
import { normalizeLegacyEvent } from "./event-legacy.js";
|
|
4
5
|
import { errorMessage, stripTerminalControls } from "../text/text.js";
|
|
5
6
|
const listeners = new Set();
|
|
6
7
|
/** Get notified of every event logged in this process (e.g. to narrate `tumwater run`).
|
|
@@ -19,9 +20,12 @@ export const EVENTS_MAX_BYTES = 16 * 1024 * 1024;
|
|
|
19
20
|
export function eventsRotationLabel() {
|
|
20
21
|
return `rotated at ${EVENTS_MAX_BYTES / (1024 * 1024)} MB`;
|
|
21
22
|
}
|
|
23
|
+
;
|
|
24
|
+
/** The `source` of events tumwater writes about itself rather than on a role's behalf. */
|
|
25
|
+
export const TUMWATER_SOURCE = "tumwater";
|
|
22
26
|
/** The usage fields an event carries, as a fragment with both omitted when zero — the
|
|
23
|
-
* omit-when-zero convention the two usage-emitters apply (tick-finalize.ts's
|
|
24
|
-
*
|
|
27
|
+
* omit-when-zero convention the two usage-emitters apply (tick-finalize.ts's run_end,
|
|
28
|
+
* merge-slot.ts's merge_done/merge_failed): a zero-token, zero-cost run (a free model, an abort
|
|
25
29
|
* before any tokens) renders without usage, so event-format's usage fragment and
|
|
26
30
|
* `tumwater history` show nothing rather than "$0.00". Spreading the fragment keeps the
|
|
27
31
|
* fields absent rather than 0; readers (event-read.ts's eventUsage and the index-signature
|
|
@@ -32,7 +36,7 @@ export function usageFragment(tokens, costUsd, notionalCostUsd = 0) {
|
|
|
32
36
|
...(tokens > 0 ? { tokens } : {}),
|
|
33
37
|
...(costUsd > 0 ? { costUsd } : {}),
|
|
34
38
|
// Subscription spend rides on the same fragment (plans/agent-backends.md part 6a): it is
|
|
35
|
-
// omitted when zero like the other fields, so an API-only
|
|
39
|
+
// omitted when zero like the other fields, so an API-only team's lines are unchanged.
|
|
36
40
|
...(notionalCostUsd > 0 ? { notionalCostUsd } : {}),
|
|
37
41
|
};
|
|
38
42
|
}
|
|
@@ -44,7 +48,7 @@ export function usageFragment(tokens, costUsd, notionalCostUsd = 0) {
|
|
|
44
48
|
* so it runs only when the file's shape differs from what this process's last append left —
|
|
45
49
|
* a steady stream of events costs one stat, not an fd cycle, per event. */
|
|
46
50
|
export function logEvent(root, event) {
|
|
47
|
-
const full = { ts: Date.now(), ...event };
|
|
51
|
+
const full = normalizeLegacyEvent({ ts: Date.now(), ...event });
|
|
48
52
|
const file = eventsLogPath(root);
|
|
49
53
|
ensureParentDir(file);
|
|
50
54
|
rotateIfLarge(file, EVENTS_MAX_BYTES);
|
|
@@ -54,10 +58,10 @@ export function logEvent(root, event) {
|
|
|
54
58
|
return full;
|
|
55
59
|
}
|
|
56
60
|
/** Log a warning event: the harness's "something is off but the loop continues" signal. The
|
|
57
|
-
* single home of the `{
|
|
61
|
+
* single home of the `{ source, type: "warning", message }` shape every warn site constructs —
|
|
58
62
|
* without it, each caller restates the event object and a field can drift between them. */
|
|
59
|
-
export function warnEvent(root,
|
|
60
|
-
return logEvent(root, {
|
|
63
|
+
export function warnEvent(root, source, message) {
|
|
64
|
+
return logEvent(root, { source, type: "warning", message });
|
|
61
65
|
}
|
|
62
66
|
/** logEvent for a caller on a path that cannot handle a throw — the orchestrator's
|
|
63
67
|
* catch-less poll loop and the gate family it runs every couple of seconds. An unwritable
|
|
@@ -71,7 +75,7 @@ export function logEventBestEffort(root, event) {
|
|
|
71
75
|
// A terminal display boundary like cli-output's say()/fail(): the loop id, event type, and
|
|
72
76
|
// error text can carry model- or subprocess-supplied bytes, and stderr is a terminal. Strip
|
|
73
77
|
// the control characters the same way so a fallback line cannot drive the operator's shell.
|
|
74
|
-
const detail = `${event.
|
|
78
|
+
const detail = `${event.source}: could not log ${event.type} event: ${errorMessage(err)}`;
|
|
75
79
|
process.stderr.write(`tumwater: ${stripTerminalControls(detail)}\n`);
|
|
76
80
|
}
|
|
77
81
|
}
|
|
@@ -81,26 +85,26 @@ export function logEventBestEffort(root, event) {
|
|
|
81
85
|
* caller's message (a spawn failure, a rejected tick) survives to be read. The single home of
|
|
82
86
|
* the try-warn → message-bearing-stderr-fallback shape, so the notify hook and the launch pass
|
|
83
87
|
* do not each carry a copy of it. */
|
|
84
|
-
export function warnEventBestEffort(root,
|
|
88
|
+
export function warnEventBestEffort(root, source, message) {
|
|
85
89
|
try {
|
|
86
|
-
warnEvent(root,
|
|
90
|
+
warnEvent(root, source, message);
|
|
87
91
|
}
|
|
88
92
|
catch (err) {
|
|
89
93
|
// The warning's own text is very often model-supplied (a stalled tool call's label, a tick's
|
|
90
94
|
// lastError) and the loop id can be a custom one, so this fallback is a terminal boundary:
|
|
91
95
|
// sanitize exactly as say()/fail() do before the line reaches the operator's terminal.
|
|
92
|
-
const detail = `${
|
|
96
|
+
const detail = `${source}: ${message} (warning log unwritable: ${errorMessage(err)})`;
|
|
93
97
|
process.stderr.write(`tumwater: ${stripTerminalControls(detail)}\n`);
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
100
|
/** The `{ emit, warn }` pair for a caller on a catch-less path (the orchestrator's poll loop
|
|
97
101
|
* and the live config reload it drives): both route through logEventBestEffort and
|
|
98
|
-
* warnEventBestEffort, so the "an unwritable feed must not end the
|
|
102
|
+
* warnEventBestEffort, so the "an unwritable feed must not end the team" policy has one home
|
|
99
103
|
* and the harness-loop warning shorthand cannot drift from warnEvent's event shape. */
|
|
100
104
|
export function bestEffortEventLogger(root) {
|
|
101
105
|
return {
|
|
102
106
|
emit: (event) => logEventBestEffort(root, event),
|
|
103
|
-
warn: (message) => warnEventBestEffort(root,
|
|
107
|
+
warn: (message) => warnEventBestEffort(root, TUMWATER_SOURCE, message),
|
|
104
108
|
};
|
|
105
109
|
}
|
|
106
110
|
/** The shape this process's last event append left a log in, per log path: while the file
|
|
@@ -127,7 +131,7 @@ function appendEventLine(file, line) {
|
|
|
127
131
|
// The append writes UTF-8 BYTES, so the growth is Buffer.byteLength, not the string's
|
|
128
132
|
// code-unit length: a multi-byte event line (an em dash in an agent's message is enough)
|
|
129
133
|
// would otherwise leave the memo short of the file's real size, every later stat would
|
|
130
|
-
// miss it, and the memo would silently never hit on the
|
|
134
|
+
// miss it, and the memo would silently never hit on the team's actual traffic.
|
|
131
135
|
size: st.size + (terminated ? 1 : 0) + Buffer.byteLength(line, "utf8"),
|
|
132
136
|
});
|
|
133
137
|
}
|