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
|
@@ -4,30 +4,31 @@ import { finiteNumber, stringList } from "../files/json-object.js";
|
|
|
4
4
|
import { removeQuiet } from "../files/files.js";
|
|
5
5
|
import { withStateLock } from "../concurrency/lock.js";
|
|
6
6
|
import { collapseWhitespace, truncate } from "../text/text.js";
|
|
7
|
-
import { pausedPath, pausedRolesLockPath, pausedRolesPath } from "../paths.js";
|
|
7
|
+
import { pausedPath, pausedRolesLockPath, pausedRolesPath, scheduledStopPath } from "../paths.js";
|
|
8
8
|
import { pendingScheduledWakes } from "../operator/operator-requests.js";
|
|
9
|
-
|
|
9
|
+
import { canonicalRoleId } from "../roles/legacy-role-ids.js";
|
|
10
|
+
/** The operator pause markers: the team-wide pause (`tumwater pause`/`resume`) and the
|
|
10
11
|
* per-role pause set (`pause --role <id>`/`resume --role <id>`), their writers and read-side
|
|
11
12
|
* wrappers together so producer (CLI, GUI) and every consumer read one module. The running
|
|
12
|
-
* orchestrator's info file is the other
|
|
13
|
-
/** The
|
|
13
|
+
* orchestrator's info file is the other team-state domain and lives in orchestrator-info.ts. */
|
|
14
|
+
/** The team pause marker read once as the standing pause it represents: non-null while the
|
|
14
15
|
* operator's pause stands, null when absent/unreadable/expired. Snapshot-style callers that
|
|
15
|
-
* need both "is the
|
|
16
|
-
* full marker reads (
|
|
16
|
+
* need both "is the team paused" and its deadline read this once instead of paying two
|
|
17
|
+
* full marker reads (isTeamPaused + pausedUntil each re-read the same file) per poll —
|
|
17
18
|
* the TUI and GUI poll snapshot every second. Never throws (a missing .tumwater/ reads
|
|
18
19
|
* null). Lives here beside the read-half wrappers below: one module, one marker, one read
|
|
19
20
|
* per poll per observer. */
|
|
20
|
-
export function
|
|
21
|
+
export function standingTeamPause(root) {
|
|
21
22
|
return standingMarker(pausedPath(root));
|
|
22
23
|
}
|
|
23
|
-
/** True while the operator has paused the
|
|
24
|
+
/** True while the operator has paused the team (`tumwater pause` wrote its marker). The
|
|
24
25
|
* marker is persistent state, not a one-shot request: presence means paused until `resume`
|
|
25
|
-
* removes it — pausing before startup starts an already-paused
|
|
26
|
+
* removes it — pausing before startup starts an already-paused team. Never throws (a missing
|
|
26
27
|
* .tumwater/ reads false). Lives here because both the scheduler and every observer must
|
|
27
28
|
* evaluate it from disk without importing each other's modules — the same "single
|
|
28
29
|
* definition" rule that put the daily-cost budget in src/budget/budget.ts. */
|
|
29
|
-
export function
|
|
30
|
-
return
|
|
30
|
+
export function isTeamPaused(root) {
|
|
31
|
+
return standingTeamPause(root) !== null;
|
|
31
32
|
}
|
|
32
33
|
/** The reason cap every writer of a pause reason shares (chars): one constant so the CLI
|
|
33
34
|
* path and any later GUI path cannot drift on how long an operator note may be — a writer
|
|
@@ -38,18 +39,18 @@ export const PAUSE_REASON_MAX = 200;
|
|
|
38
39
|
* timed pause: every consumer (scheduler, dashboards, a follow-up `pause`) treats an expired
|
|
39
40
|
* marker as absent, so the deadline releases the pause with no new scheduler code — the
|
|
40
41
|
* orchestrator's poll-diff sees an ordinary unpaused transition. Never throws. The reason is
|
|
41
|
-
* folded and capped here too — the same shape
|
|
42
|
+
* folded and capped here too — the same shape pauseTeam writes — so the marker's reason
|
|
42
43
|
* reads as the operator's one-line why whoever wrote it: a pause standing across the
|
|
43
44
|
* write-side fold (or a hand edit) must not hand the status header's badge a raw newline
|
|
44
45
|
* (BUGS.md 2026-09-30). A non-string reason reads as no reason — src/files/json-files.ts's
|
|
45
46
|
* wrong-shape-reads-as-absent policy — and the type guard is what keeps this function's
|
|
46
47
|
* never-throws contract intact: normalizePauseReason would throw on a number or object,
|
|
47
|
-
* and this read sits on the scheduler's per-cycle
|
|
48
|
+
* and this read sits on the scheduler's per-cycle isTeamPaused poll and every dashboard
|
|
48
49
|
* snapshot.
|
|
49
50
|
*
|
|
50
51
|
* A non-finite `until` (a hand edit's `1e999` parses to Infinity) is the same wrong-shape
|
|
51
52
|
* read: it is dropped, so the marker stands as an indefinite pause rather than a pause that
|
|
52
|
-
* never lapses with its countdown rendered `Infinityd`. The
|
|
53
|
+
* never lapses with its countdown rendered `Infinityd`. The team stays stopped — a corrupt
|
|
53
54
|
* deadline must not resume it — but it no longer claims a deadline no clock can reach. */
|
|
54
55
|
function standingMarker(path) {
|
|
55
56
|
const m = readJsonFile(path);
|
|
@@ -69,21 +70,21 @@ function standingMarker(path) {
|
|
|
69
70
|
delete m.reason;
|
|
70
71
|
return m;
|
|
71
72
|
}
|
|
72
|
-
/** The
|
|
73
|
-
* the
|
|
73
|
+
/** The team marker's standing deadline (ms epoch) while a timed pause holds; undefined when
|
|
74
|
+
* the team is not timed-paused — no marker, an indefinite `pause` (no `until`), or an expired
|
|
74
75
|
* `until` (standingMarker already reads that as unpaused, so the field cannot outlive the
|
|
75
|
-
* pause it describes).
|
|
76
|
-
* badge may claim, since only it covers the whole
|
|
76
|
+
* pause it describes). Team-scoped on purpose: the TEAM marker's deadline is what a header
|
|
77
|
+
* badge may claim, since only it covers the whole team. Lives here beside isTeamPaused —
|
|
77
78
|
* one module for every marker consumer. */
|
|
78
79
|
export function pausedUntil(root) {
|
|
79
|
-
return
|
|
80
|
+
return standingTeamPause(root)?.until;
|
|
80
81
|
}
|
|
81
|
-
/** The standing
|
|
82
|
-
* when the
|
|
82
|
+
/** The standing team pause's operator reason (`tumwater pause --reason <text>`), undefined
|
|
83
|
+
* when the team is not paused or the standing pause carries none — same read (and the same
|
|
83
84
|
* never-throws, expiry-honoring contract) as pausedUntil beside which it lives, so a caller
|
|
84
|
-
* wanting both pays one
|
|
85
|
+
* wanting both pays one standingTeamPause read, not two. */
|
|
85
86
|
export function pausedReason(root) {
|
|
86
|
-
return
|
|
87
|
+
return standingTeamPause(root)?.reason;
|
|
87
88
|
}
|
|
88
89
|
/** Fold an operator pause reason to the one line every pause surface renders (the marker
|
|
89
90
|
* doc's "one-line reason", the status header's badge, the alerts title, the CLI's
|
|
@@ -93,13 +94,13 @@ export function pausedReason(root) {
|
|
|
93
94
|
* that would split an astral character and marks the cut with an ellipsis), so the CLI
|
|
94
95
|
* confirmation, the marker, and every read side quote the same bounded line — a cap applied
|
|
95
96
|
* only at the writer let `pause --reason <over-cap text>` print a note longer than the one
|
|
96
|
-
* persisted. One home beside
|
|
97
|
+
* persisted. One home beside pauseTeam so the CLI path and any later GUI writer cannot
|
|
97
98
|
* drift on the shape the marker stores. */
|
|
98
99
|
export function normalizePauseReason(reason) {
|
|
99
100
|
const oneLine = reason === undefined ? undefined : collapseWhitespace(reason);
|
|
100
101
|
return oneLine ? truncate(oneLine, PAUSE_REASON_MAX) : undefined;
|
|
101
102
|
}
|
|
102
|
-
/** Pause the
|
|
103
|
+
/** Pause the team by writing its marker, the writer half of isTeamPaused's contract; the
|
|
103
104
|
* marker format ({ at: number }, pretty-printed JSON) is the one `tumwater pause` has always
|
|
104
105
|
* written. `reason` is the operator's one-line why (`tumwater pause --reason <text>`):
|
|
105
106
|
* folded to one line and capped at PAUSE_REASON_MAX (both normalizePauseReason) — the
|
|
@@ -109,8 +110,8 @@ export function normalizePauseReason(reason) {
|
|
|
109
110
|
* indefinite pause lands on an already-standing one, so the CLI can report "already paused"
|
|
110
111
|
* and the dashboard's toggle stays idempotent — a no-op never touches the standing reason
|
|
111
112
|
* (a timed `--for` on a standing pause overwrites the deadline instead). Lives here beside
|
|
112
|
-
*
|
|
113
|
-
export function
|
|
113
|
+
* isTeamPaused so the producer (CLI, GUI) and every consumer read the same path. */
|
|
114
|
+
export function pauseTeam(root, untilMs, reason) {
|
|
114
115
|
const marker = pausedPath(root);
|
|
115
116
|
// An already-standing pause is the idempotent no-op it has always been — except under a
|
|
116
117
|
// fresh `--for`, which overwrites the deadline (extend or shorten): the operator asked for
|
|
@@ -126,22 +127,46 @@ export function pauseFleet(root, untilMs, reason) {
|
|
|
126
127
|
writeJsonAtomic(marker, body);
|
|
127
128
|
return true;
|
|
128
129
|
}
|
|
129
|
-
/** Resume the
|
|
130
|
-
* there to lift, mirroring
|
|
130
|
+
/** Resume the team by removing its marker (a no-op if absent); returns whether a marker was
|
|
131
|
+
* there to lift, mirroring pauseTeam's changed-state contract. Uses removeQuiet's
|
|
131
132
|
* never-throws delete: a marker vanishing between the check and the unlink is success, not a
|
|
132
133
|
* failure worth surfacing from a toggle. */
|
|
133
|
-
export function
|
|
134
|
+
export function resumeTeam(root) {
|
|
134
135
|
const marker = pausedPath(root);
|
|
135
136
|
if (!fs.existsSync(marker))
|
|
136
137
|
return false;
|
|
137
138
|
removeQuiet(marker);
|
|
138
139
|
return true;
|
|
139
140
|
}
|
|
141
|
+
/** Schedule a graceful team stop at `untilMs` (epoch ms): `tumwater stop --in`. Overwrites the
|
|
142
|
+
* marker, so the last write wins. */
|
|
143
|
+
export function scheduleStop(root, untilMs) {
|
|
144
|
+
writeJsonAtomic(scheduledStopPath(root), { at: Date.now(), until: untilMs });
|
|
145
|
+
}
|
|
146
|
+
/** The scheduled stop's deadline (epoch ms), or undefined. Never throws: a missing, unreadable,
|
|
147
|
+
* or non-finite marker reads as no stop scheduled. */
|
|
148
|
+
export function scheduledStopAt(root) {
|
|
149
|
+
try {
|
|
150
|
+
const body = readJsonFile(scheduledStopPath(root));
|
|
151
|
+
return typeof body?.until === "number" && Number.isFinite(body.until) ? body.until : undefined;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Drop the scheduled stop; returns whether a marker was there (resumeTeam's contract). */
|
|
158
|
+
export function cancelScheduledStop(root) {
|
|
159
|
+
const marker = scheduledStopPath(root);
|
|
160
|
+
if (!fs.existsSync(marker))
|
|
161
|
+
return false;
|
|
162
|
+
removeQuiet(marker);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
140
165
|
/** The roles the operator has individually paused (`tumwater pause --role <id>`): a role in
|
|
141
166
|
* this set starts no new ticks — in-flight ones finish, every other role keeps ticking, the
|
|
142
|
-
* director included (the operator named it deliberately). Never throws, like
|
|
167
|
+
* director included (the operator named it deliberately). Never throws, like isTeamPaused:
|
|
143
168
|
* a missing or unreadable file reads as no paused roles, so observers (scheduler, dashboards)
|
|
144
|
-
* can poll it every cycle without a guard. Lives here beside
|
|
169
|
+
* can poll it every cycle without a guard. Lives here beside pauseTeam for the same
|
|
145
170
|
* single-definition reason: producer (CLI, GUI) and every consumer read one module. */
|
|
146
171
|
export function pausedRoles(root) {
|
|
147
172
|
return readPausedRoles(pausedRolesPath(root)).roles;
|
|
@@ -176,7 +201,7 @@ function readPausedRoles(path) {
|
|
|
176
201
|
const until = finiteNumber(state.until, undefined);
|
|
177
202
|
if (until !== undefined && until <= Date.now())
|
|
178
203
|
return { roles: [] };
|
|
179
|
-
return { roles: stringList(state.roles), until };
|
|
204
|
+
return { roles: stringList(state.roles).map((r) => canonicalRoleId(r)), until };
|
|
180
205
|
}
|
|
181
206
|
/** The paused-roles marker is structured state several processes rewrite read-modify-write
|
|
182
207
|
* (the CLI's pause/resume --role and the dashboard's per-row toggle are separate processes),
|
|
@@ -204,7 +229,7 @@ function withPausedRolesLock(root, fn) {
|
|
|
204
229
|
* command that named a different role (BUGS.md 2026-09-30). undefined when the file is
|
|
205
230
|
* missing, unreadable, or carries no `until` — or a non-numeric, non-finite, or already-
|
|
206
231
|
* expired one, the same wrong-shape-reads-as-absent discipline standingMarker applies to the
|
|
207
|
-
*
|
|
232
|
+
* team marker. Dropping a non-finite value here matters beyond the read: a rewrite carries
|
|
208
233
|
* this value into the next marker body, where JSON.stringify turns Infinity into null — which
|
|
209
234
|
* the expiry rule then reads as a past deadline and resumes every role. */
|
|
210
235
|
function standingRolesUntil(path) {
|
|
@@ -223,7 +248,7 @@ function writePausedRoles(path, roles, until) {
|
|
|
223
248
|
writeJsonAtomic(path, until === undefined ? { roles, at: Date.now() } : { roles, at: Date.now(), until });
|
|
224
249
|
}
|
|
225
250
|
/** Pause one role by adding it to the paused-roles marker; returns whether this call changed
|
|
226
|
-
* state (false when the role was already paused — idempotent like
|
|
251
|
+
* state (false when the role was already paused — idempotent like pauseTeam, so the CLI and
|
|
227
252
|
* a dashboard toggle can report "already paused"). Custom-loop ids are stored verbatim: the
|
|
228
253
|
* marker must survive config edits, which is why callers resolve built-in ids without
|
|
229
254
|
* touching tumwater.json (namedRole in src/operator/operator-commands.ts). */
|
|
@@ -252,8 +277,8 @@ export function pauseRole(root, role, untilMs) {
|
|
|
252
277
|
});
|
|
253
278
|
}
|
|
254
279
|
/** Resume one role by removing it from the paused-roles marker; returns whether it was there
|
|
255
|
-
* to lift (resume's changed-state contract, mirroring
|
|
256
|
-
* the file outright, so a fully-resumed
|
|
280
|
+
* to lift (resume's changed-state contract, mirroring resumeTeam). The last removal deletes
|
|
281
|
+
* the file outright, so a fully-resumed team leaves no marker behind for observers to read. */
|
|
257
282
|
export function resumeRole(root, role) {
|
|
258
283
|
return withPausedRolesLock(root, () => {
|
|
259
284
|
const current = pausedRoles(root);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
/** Local date/time formatting and calendar-day arithmetic — the zero-padded clock and
|
|
2
3
|
* calendar parts, local-midnight day math, and the report-window header every display and
|
|
3
4
|
* report surface (transcript run separators, the status table's last-tick cell, daily-budget
|
|
@@ -14,12 +15,18 @@ import { EVENTS_LOG_BASENAME } from "../paths.js";
|
|
|
14
15
|
export function pad2(n) {
|
|
15
16
|
return String(n).padStart(2, "0");
|
|
16
17
|
}
|
|
18
|
+
/** The local wall-clock `HH:MM` of an instant, the hours/minutes-only shape the burn-rate
|
|
19
|
+
* forecast, the window badge, and the role view's fallback line share. */
|
|
20
|
+
export function clockHm(ms) {
|
|
21
|
+
const d = new Date(ms);
|
|
22
|
+
return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
23
|
+
}
|
|
17
24
|
/** Whole seconds between two epoch-ms instants, rounded to the nearest second: `secondsSince`
|
|
18
25
|
* clamps at zero (an age never goes negative when the clock skews), `secondsUntil` clamps at
|
|
19
26
|
* zero the same way for the future direction (a countdown never reads negative once its
|
|
20
27
|
* deadline has passed). The one home of the clamp + rounding behind every elapsed age and
|
|
21
28
|
* countdown — status-render.ts's lastTickCell and nextRunCell, the queued-prompt age and
|
|
22
|
-
* deliver-in suffix, the
|
|
29
|
+
* deliver-in suffix, the team pause's auto-resume countdown, and status-model.ts's sleeping
|
|
23
30
|
* phase label (which re-floors at 1s) all render through. The GUI page's browser
|
|
24
31
|
* script keeps its own one-line twins (gui-client.ts's ageSec and its inline countdown
|
|
25
32
|
* rounding — a separate runtime that cannot import TypeScript), pinned by its marked regions'
|
|
@@ -60,6 +67,15 @@ export function formatTime(d) {
|
|
|
60
67
|
export function dayKey(ms) {
|
|
61
68
|
return formatDate(new Date(ms));
|
|
62
69
|
}
|
|
70
|
+
/** Whole days from one `YYYY-MM-DD` key to another (negative when `toKey` is earlier). Parsed with
|
|
71
|
+
* Date.UTC so a DST shift cannot move the count by one. */
|
|
72
|
+
export function daysBetween(fromKey, toKey) {
|
|
73
|
+
const utc = (key) => {
|
|
74
|
+
const [y = 0, m = 1, d = 1] = key.split("-").map(Number);
|
|
75
|
+
return Date.UTC(y, m - 1, d);
|
|
76
|
+
};
|
|
77
|
+
return Math.round((utc(toKey) - utc(fromKey)) / 86_400_000);
|
|
78
|
+
}
|
|
63
79
|
/** The `MM-DD` half of a `YYYY-MM-DD` day key (dayKey/formatDate): the redundant year
|
|
64
80
|
* dropped for a line whose date lies inside the current window. Shared by the failure
|
|
65
81
|
* digest's cluster ranges and transition stamps (failure-render.ts) and the usage report's
|
|
@@ -98,7 +114,7 @@ export function dayWindow(days, now) {
|
|
|
98
114
|
}
|
|
99
115
|
/** Compact whole-second duration: `45s`, `12m`, `3h`, or `2d` — the phrasing every relative
|
|
100
116
|
* time renders through (ago labels, sleeping-remaining countdowns, `prompt --list`'s queued
|
|
101
|
-
* age, the
|
|
117
|
+
* age, the team pause's auto-resume countdown), so the s/m/h/d bucketing and rounding cannot
|
|
102
118
|
* drift per consumer. A day or more reads days, not an hour count (`pause --for 90d`'s
|
|
103
119
|
* countdown reads `90d`, not `2160h`). Lives here, beside the other time phrasing, so core
|
|
104
120
|
* modules (which may not import src/ui) share the same bucketing too. */
|
|
@@ -119,6 +135,30 @@ export function humanSeconds(s) {
|
|
|
119
135
|
}
|
|
120
136
|
return `${Math.round(s / 86400)}d`;
|
|
121
137
|
}
|
|
138
|
+
/** Humanized duration from `now` until a future epoch-ms instant — the "how long until it
|
|
139
|
+
* happens" phrase (a scheduled wake, a timed pause's auto-resume, a hold's re-open):
|
|
140
|
+
* humanSeconds over secondsUntil in one call, collapsing the ten sites that spelled the
|
|
141
|
+
* two-step composition out, so the s/m/h/d bucketing and the past-deadline zero-clamp for
|
|
142
|
+
* the future direction have one home. Reads `0s` once the instant has passed (secondsUntil
|
|
143
|
+
* clamps at zero), matching every countdown's existing behavior. */
|
|
144
|
+
export function humanUntil(atMs, now) {
|
|
145
|
+
return humanSeconds(secondsUntil(atMs, now));
|
|
146
|
+
}
|
|
147
|
+
/** The shared "when may this loop next act" phrase — the `next run` cell (status-render.ts's
|
|
148
|
+
* nextRunCell) and the `Next tick` line (role-render.ts's scheduleLine) render their schedule
|
|
149
|
+
* through it, so the two surfaces cannot drift on the backoff prefix or the humanSeconds
|
|
150
|
+
* bucketing. `remainSeconds` is the seconds until the next run (non-positive = due); `backoff`
|
|
151
|
+
* adds the `backoff ` prefix the wake action clears; `dueWord` names the due case and `withIn`
|
|
152
|
+
* selects the prose `in <duration>` form (the role view) over the cell's bare `<duration>`.
|
|
153
|
+
* Callers keep their own surrounding logic on either side — the status cell's in-flight `-`
|
|
154
|
+
* and its yield-scaled `×N` suffix, the role view's `not scheduled yet`. */
|
|
155
|
+
export function nextRunPhrase(remainSeconds, backoff, dueWord, withIn) {
|
|
156
|
+
if (remainSeconds <= 0)
|
|
157
|
+
return dueWord;
|
|
158
|
+
const label = humanSeconds(remainSeconds);
|
|
159
|
+
const base = withIn ? `in ${label}` : label;
|
|
160
|
+
return backoff ? `sleeping ${base}` : base;
|
|
161
|
+
}
|
|
122
162
|
/** `N days`, singular at 1 — the window label both report headers render, so a one-day window
|
|
123
163
|
* reads the same on either surface. Rendered through text/phrases.ts's plural, like every other
|
|
124
164
|
* count-plus-noun phrase in the harness. */
|
package/dist/src/text/format.js
CHANGED
|
@@ -11,6 +11,15 @@ import { textOr } from "./text.js";
|
|
|
11
11
|
* home of the token display format shared by the status table's gen/peak-ctx columns and the
|
|
12
12
|
* commit trailer's ctx field — pinned here so those surfaces cannot drift even though they
|
|
13
13
|
* live in different modules. */
|
|
14
|
+
/** Append a section note to a running prompt, separated from what precedes it by a blank line.
|
|
15
|
+
* The tick and authoring-tick prompt assemblers (tick-prompt.ts, loop-tick.ts) build a prompt by
|
|
16
|
+
* stacking nameless sections — assignment, held-entry, rejected-review, conflict-discard,
|
|
17
|
+
* conflict-handback, revision and cut-off notes — each appended the same way. This is the single
|
|
18
|
+
* append so the `\n\n` section separator stays in one place. The note is appended unconditionally,
|
|
19
|
+
* exactly as the call sites appended the literal, so behavior is unchanged for any note. */
|
|
20
|
+
export function appendPromptNote(prompt, note) {
|
|
21
|
+
return `${prompt}\n\n${note}`;
|
|
22
|
+
}
|
|
14
23
|
export function compactTokens(n) {
|
|
15
24
|
if (n >= 1_000_000)
|
|
16
25
|
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
@@ -48,7 +57,7 @@ export function usdCap(n) {
|
|
|
48
57
|
}
|
|
49
58
|
/** A disk size in GB for display: one decimal and the unit (`12.3 GB`) — the single home of
|
|
50
59
|
* the disk-size format shared by the event feed's disk lines (event-format.ts), the status
|
|
51
|
-
* header's disk badge (badges.ts's `diskBadge`), the
|
|
60
|
+
* header's disk badge (badges.ts's `diskBadge`), the team's low-disk alert (team-alerts.ts),
|
|
52
61
|
* the doctor's disk check detail (doctor-checks.ts), and `tumwater reclaim`'s freed-size line
|
|
53
62
|
* (operator-commands.ts), so the decimal width and unit cannot drift per consumer. A caller
|
|
54
63
|
* reading the value from an untrusted event field applies event-format.ts's `gigabytes`
|
|
@@ -22,7 +22,7 @@ export function markdownTable(header, rows, align = []) {
|
|
|
22
22
|
* the `map((item, i) => `${i + 1}. ${item}`).join("\n")` step, shared by the review prompt's
|
|
23
23
|
* prior objections, its stage-fix findings, and the rejected/revision notes
|
|
24
24
|
* (prompt/gate-prompts.ts) and `tumwater questions`' open-question list
|
|
25
|
-
* (
|
|
25
|
+
* (backlog/question-answer.ts), so the numbering shape cannot drift between the surfaces that
|
|
26
26
|
* read a numbered list. `empty` is returned verbatim when `items` is empty (the reasons
|
|
27
27
|
* sites pass `(no reasons recorded)`; the others keep the default empty string). */
|
|
28
28
|
export function numberedList(items, empty = "") {
|
package/dist/src/text/phrases.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { finiteNumber, isJsonObject, stringList } from "../files/json-object.js";
|
|
3
4
|
import { squash } from "./text.js";
|
|
4
5
|
import { shortSha, usd } from "./format.js";
|
|
5
|
-
/** The single home of each human-facing PHRASE the
|
|
6
|
+
/** The single home of each human-facing PHRASE the team renders — the wording fragments (a pause
|
|
6
7
|
* reason's suffix, a rate-limit hold's "for 60s", a backend hold's kind, a budget transition's "$x
|
|
7
8
|
* of $y", main's red-tip naming, a tool call's one-line label, the plural helper) shared by the
|
|
8
9
|
* event feed (event-format.ts), the failure digest (src/failure/failure-render.ts and
|
|
@@ -14,11 +15,11 @@ import { shortSha, usd } from "./format.js";
|
|
|
14
15
|
const NO_REASONS_GIVEN = "no reasons given";
|
|
15
16
|
/** The reason an over-long prompt or config field carries: the text rides into a tick's
|
|
16
17
|
* prefill, so a longer value bloats every tick's context. The single home of that tail,
|
|
17
|
-
* shared by init's seed check (init/init.ts) and config-validation's
|
|
18
|
+
* shared by init's seed check (init/init.ts) and config-validation's customRoles task and
|
|
18
19
|
* roles-instructions caps (config/config-validation.ts), so the three cannot drift on the
|
|
19
20
|
* wording. inbox-submit's promptLengthProblem renders a role-scoped variant ("it rides into
|
|
20
21
|
* the <role> tick's prefill") and keeps its own wording. */
|
|
21
|
-
export const PREFILL_REASON = "it rides into every
|
|
22
|
+
export const PREFILL_REASON = "it rides into every run's prefill";
|
|
22
23
|
/** The reason a review rejection leads with — its first listed reason, or `no reasons given`
|
|
23
24
|
* when the list is empty or its first entry is not a string. One home so the fallback cannot
|
|
24
25
|
* drift across the four surfaces that show just that first reason: the review gate's returned
|
|
@@ -33,7 +34,7 @@ export function firstReason(reasons) {
|
|
|
33
34
|
/** A count and its noun as one phrase (`plural(3, "tick")` → `3 ticks`) — the single home of
|
|
34
35
|
* the singular/plural selection the CLI's once summary (cli/cli-run.ts), the day window's day label
|
|
35
36
|
* (datetime.ts), the failure digest's loss-cause lines (src/failure/failure-render.ts), and the
|
|
36
|
-
*
|
|
37
|
+
* team alerts' banner titles (ui/team-alerts.ts, whose local copy this replaces), and the
|
|
37
38
|
* build_stale lines' "N commit(s) ahead" (event-format.ts, src/failure/failure-state-change.ts —
|
|
38
39
|
* the singular/plural wording their tests pin, once "N commit(s)" with the old literal, now "N
|
|
39
40
|
* commits"/"1 commit"), and the stage self-check and dropped-attachment notes
|
|
@@ -48,7 +49,7 @@ export function plural(n, one, many = `${one}s`) {
|
|
|
48
49
|
* → `is`, `agree(2, "cause", "causes")` → `causes` — the companion to plural for the slots
|
|
49
50
|
* plural's count-plus-noun shape cannot fill: a subject or noun whose count is printed elsewhere in
|
|
50
51
|
* the sentence (a subject phrase such as listRoles's role list, a count named earlier) still needs
|
|
51
|
-
* the right number form. The
|
|
52
|
+
* the right number form. The team alerts' banner titles (ui/team-alerts.ts), the resume
|
|
52
53
|
* confirmation's still-paused note (operator/operator-commands.ts), the dropped-attachment note
|
|
53
54
|
* (inbox-attachments.ts), the failure digest's loss-cause and cluster remainders
|
|
54
55
|
* (failure/failure-render.ts), the doctor's oh-my-pi and model notes
|
|
@@ -59,8 +60,8 @@ export function agree(n, one, many) {
|
|
|
59
60
|
return n === 1 ? one : many;
|
|
60
61
|
}
|
|
61
62
|
/** The spine of every phrase naming main's tip as red — `main <short-sha> is red` — built here
|
|
62
|
-
* once so the
|
|
63
|
-
* (review.ts), the
|
|
63
|
+
* once so the team-wide red-main warning (main-red.ts), the review gate's attribution detail
|
|
64
|
+
* (review.ts), the merge check's attribution error (merge-check-failures.ts), and the redeploy
|
|
64
65
|
* hold's block reason (src/redeploy/redeployer.ts) cannot drift over how main's red is worded. Each
|
|
65
66
|
* caller appends its own consequence — the gate's "— not this change's failure", the hold's "
|
|
66
67
|
* — holding the restart until main is green". */
|
|
@@ -68,8 +69,8 @@ export function mainRedPhrase(sha) {
|
|
|
68
69
|
return `main ${shortSha(sha)} is red`;
|
|
69
70
|
}
|
|
70
71
|
/** The main-red attribution detail — main's redness is not this change's failure — the review
|
|
71
|
-
* pre-check's reject detail (review-precheck.ts) and the
|
|
72
|
-
* (
|
|
72
|
+
* pre-check's reject detail (review-precheck.ts) and the merge check's lastError
|
|
73
|
+
* (merge-check-failures.ts) both build as mainRedPhrase plus the consequence suffix. One
|
|
73
74
|
* home beside mainRedPhrase so the two attribution surfaces cannot drift over the wording
|
|
74
75
|
* their tests pin. The other mainRedPhrase callers keep their own consequences: main-red.ts
|
|
75
76
|
* names the failing script and its action, src/redeploy/redeployer.ts holds the restart. */
|
|
@@ -78,7 +79,7 @@ export function mainRedNotMine(sha) {
|
|
|
78
79
|
}
|
|
79
80
|
/** The ` — "<reason>"` suffix the operator's pause reason (`pause --reason <text>`) rides on —
|
|
80
81
|
* one home so the CLI's confirmation line (operator/operator-commands.ts), the status header's
|
|
81
|
-
* pause badge (ui/badges.ts), and the paused alert's title (ui/
|
|
82
|
+
* pause badge (ui/badges.ts), and the paused alert's title (ui/team-alerts.ts) cannot drift on the
|
|
82
83
|
* quoting. No reason, no suffix: every caller's reasonless phrasing keeps today's byte-exact form.
|
|
83
84
|
* (The GUI keeps its own JS copies in gui-client.ts: a separate runtime that cannot import
|
|
84
85
|
* TypeScript.) */
|
|
@@ -87,48 +88,48 @@ export function pauseReasonSuffix(reason) {
|
|
|
87
88
|
}
|
|
88
89
|
/** The harness warning a loop crossing the error-streak threshold raises — `3 consecutive
|
|
89
90
|
* tick failures: <reason>` — shared by the tick-side crossing (tick-finalize.ts, an error or
|
|
90
|
-
* recovery failure) and the
|
|
91
|
-
* (
|
|
91
|
+
* recovery failure) and the merge-side crossing a review rejection resolves
|
|
92
|
+
* (merge-slot.ts), the two sites that build the message, so the operator-facing wording
|
|
92
93
|
* their tests pin cannot drift between them. */
|
|
93
94
|
export function consecutiveFailuresWarning(count, reason) {
|
|
94
|
-
return `${count}
|
|
95
|
+
return `${count} runs failed in a row: ${reason}`;
|
|
95
96
|
}
|
|
96
|
-
/** The base line for an operator
|
|
97
|
+
/** The base line for an operator team pause — `team paused — role loops stop starting new
|
|
97
98
|
* ticks` — built here so the event feed (event-format.ts, which appends its own `(director
|
|
98
|
-
* keeps running)` consequence) and the failure digest's
|
|
99
|
+
* keeps running)` consequence) and the failure digest's Team state changes lines
|
|
99
100
|
* (src/failure/failure-state-change.ts, which keeps the short form) cannot drift on the base
|
|
100
101
|
* wording. `tumwater pause`'s confirmation (operator/operator-commands.ts) interleaves its
|
|
101
102
|
* `--for`/`--reason` spans before the dash, so it composes its own line and keeps that wording
|
|
102
103
|
* inline. */
|
|
103
|
-
export function
|
|
104
|
-
return "
|
|
104
|
+
export function teamPausePhrase() {
|
|
105
|
+
return "team paused — roles start no new runs";
|
|
105
106
|
}
|
|
106
|
-
/** The base line for lifting the
|
|
107
|
-
* shared by the event feed (event-format.ts), the failure digest's
|
|
107
|
+
/** The base line for lifting the team pause — `team resumed — roles run again` —
|
|
108
|
+
* shared by the event feed (event-format.ts), the failure digest's Team state changes lines
|
|
108
109
|
* (src/failure/failure-state-change.ts), and `tumwater resume`'s confirmation
|
|
109
110
|
* (operator/operator-commands.ts, which appends its `--for` and still-paused-role spans), so the
|
|
110
111
|
* three cannot drift. */
|
|
111
|
-
export function
|
|
112
|
-
return "
|
|
112
|
+
export function teamResumePhrase() {
|
|
113
|
+
return "team resumed — roles run again";
|
|
113
114
|
}
|
|
114
|
-
/** The base line for a per-role pause — `role <id> paused — it
|
|
115
|
-
* shared by the event feed (event-format.ts, which appends `(the rest of the
|
|
116
|
-
* and the failure digest's
|
|
115
|
+
/** The base line for a per-role pause — `role <id> paused — it starts no new runs` —
|
|
116
|
+
* shared by the event feed (event-format.ts, which appends `(the rest of the team keeps running)`)
|
|
117
|
+
* and the failure digest's Team state changes lines (src/failure/failure-state-change.ts, which
|
|
117
118
|
* deliberately keeps the short form), so the two cannot drift on the base wording. The role is
|
|
118
119
|
* coerced, so the digest's byte-sliced `field()` output and the feed's raw field both render. */
|
|
119
120
|
export function rolePausePhrase(role) {
|
|
120
|
-
return `role ${String(role)} paused — it
|
|
121
|
+
return `role ${String(role)} paused — it starts no new runs`;
|
|
121
122
|
}
|
|
122
|
-
/** The base line for a per-role resume — `role <id> resumed — it
|
|
123
|
-
* event feed (event-format.ts) and the failure digest's
|
|
123
|
+
/** The base line for a per-role resume — `role <id> resumed — it runs again` — shared by the
|
|
124
|
+
* event feed (event-format.ts) and the failure digest's Team state changes lines
|
|
124
125
|
* (src/failure/failure-state-change.ts), so the two cannot drift. The role is coerced, like
|
|
125
126
|
* rolePausePhrase. */
|
|
126
127
|
export function roleResumePhrase(role) {
|
|
127
|
-
return `role ${String(role)} resumed — it
|
|
128
|
+
return `role ${String(role)} resumed — it runs again`;
|
|
128
129
|
}
|
|
129
130
|
/** The line for an orchestrator shutdown — the event feed (event-format.ts) and the failure
|
|
130
|
-
* digest's
|
|
131
|
-
export const ORCHESTRATOR_STOPPED = "
|
|
131
|
+
* digest's Team state changes lines (src/failure/failure-state-change.ts) both render it. */
|
|
132
|
+
export const ORCHESTRATOR_STOPPED = "tumwater stopped";
|
|
132
133
|
/** A HarnessEvent's loosely typed `roles` list as a `A, B, C` phrase — the single home of the
|
|
133
134
|
* array coercion and join, shared by the event feed (event-format.ts: counters_reset's scope,
|
|
134
135
|
* budget_handback's handed-back list, the rate_limit_hold line) and the failure digest's state-
|
|
@@ -145,7 +146,7 @@ export function rolesPhrase(roles, fallback, max, format = String) {
|
|
|
145
146
|
return list.length > 0 ? list.join(", ") : fallback;
|
|
146
147
|
}
|
|
147
148
|
/** The `$<spent> of $<cap>` fragment every budget-transition event renders — the one home of that
|
|
148
|
-
* phrasing, shared by the event feed (event-format.ts) and the failure digest's
|
|
149
|
+
* phrasing, shared by the event feed (event-format.ts) and the failure digest's Team state changes
|
|
149
150
|
* lines (src/failure/failure-state-change.ts), so a budget transition reads the same on both
|
|
150
151
|
* surfaces. Both fields arrive loosely typed on HarnessEvent, so each is coerced through
|
|
151
152
|
* finiteNumber (a non-number or non-finite value reads as 0, exactly as event-read.ts's eventUsage
|
|
@@ -164,7 +165,7 @@ export function shortSpanPhrase(ms) {
|
|
|
164
165
|
return seconds < 120 ? `${seconds}s` : `${Math.round(ms / 60_000)}m`;
|
|
165
166
|
}
|
|
166
167
|
/** The `for <duration>[ (relapse N)]` fragment the rate_limit_hold event renders — the one home
|
|
167
|
-
* of that phrasing, shared by the event feed (event-format.ts) and the failure digest's
|
|
168
|
+
* of that phrasing, shared by the event feed (event-format.ts) and the failure digest's Team state
|
|
168
169
|
* changes lines (src/failure/failure-state-change.ts), like budgetPhrase. The duration is
|
|
169
170
|
* shortSpanPhrase (seconds under two minutes, so the one-minute base hold reads `60s`); the relapse
|
|
170
171
|
* count is named only when the storm resumed right after an earlier hold, the one fact that says
|
|
@@ -175,8 +176,8 @@ export function holdPhrase(holdMs, escalation) {
|
|
|
175
176
|
const relapse = Number(escalation ?? 0);
|
|
176
177
|
return `for ${span}${relapse > 0 ? ` (relapse ${relapse})` : ""}`;
|
|
177
178
|
}
|
|
178
|
-
/** The human phrase for a
|
|
179
|
-
* shared by the event feed (event-format.ts) and the failure digest's
|
|
179
|
+
/** The human phrase for a team hold's backend-failure kind — the one home of that phrasing,
|
|
180
|
+
* shared by the event feed (event-format.ts) and the failure digest's Team state changes
|
|
180
181
|
* lines (src/failure/failure-state-change.ts), like holdPhrase above. A "rate-limit" hold (or a
|
|
181
182
|
* hold with no readable kind — a torn line) never renders through this: those keep the 429
|
|
182
183
|
* wording, which is the shape every historical event already has. */
|
package/dist/src/text/suggest.js
CHANGED
|
@@ -54,8 +54,9 @@ export function didYouMean(suggestion) {
|
|
|
54
54
|
/** suggestClosest and didYouMean composed: the " — did you mean `x`?" suffix for a mistyped
|
|
55
55
|
* `input` against `candidates`, the empty string when nothing is close enough to suggest. The
|
|
56
56
|
* unknown-X error sites (roles.ts's unknownRoleMessage, config-write.ts's
|
|
57
|
-
* unknownConfigKeyError and its role-field error, config-field-checks.ts's checkKnownKeys
|
|
58
|
-
* and
|
|
57
|
+
* unknownConfigKeyError and its role-field error, config-field-checks.ts's checkKnownKeys,
|
|
58
|
+
* checkKnownRoleId and its thinking-level problem, config-validation.ts's backend problem,
|
|
59
|
+
* gui/gui-args.ts's rejectBadRole, init-templates.ts's
|
|
59
60
|
* unknownTemplateError) render the hint
|
|
60
61
|
* through this one composition so the two-step pairing cannot drift; callers still print the
|
|
61
62
|
* full valid list themselves — a suggestion only annotates it. (cli.ts's unknown-command and
|
package/dist/src/text/text.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* integer parsing. (Number, money, and hash formats — compactTokens, shortSha, usd, usdCap —
|
|
5
5
|
* live in format.ts; local date/time formatting and calendar-day arithmetic live in
|
|
6
6
|
* datetime.ts; terminal-column geometry — the wcwidth table and the displayWidth/
|
|
7
|
-
* padToWidth/clipToWidth clippers — lives in text-width.ts; the
|
|
7
|
+
* padToWidth/clipToWidth clippers — lives in text-width.ts; the team's shared wording
|
|
8
8
|
* fragments — the red-main phrase, the pause-reason suffix, the hold, budget, and backend
|
|
9
9
|
* phrasings, tool-call labels — live in text/phrases.ts; the did-you-mean suggestion layer —
|
|
10
10
|
* suggestClosest, didYouMean, typoSuffix — lives in text/suggest.ts; the Markdown table
|
|
@@ -191,7 +191,7 @@ export function parseNonNegativeInt(raw) {
|
|
|
191
191
|
* with `tail` naming where the text rides once accepted. The one home of that phrasing, used
|
|
192
192
|
* by init's seed check (init.ts, "the initial prompt … every tick's prefill"), inbox-submit's
|
|
193
193
|
* promptLengthProblem ("the prompt … the <role> tick's prefill"), and config-validation's
|
|
194
|
-
*
|
|
194
|
+
* customRoles task and roles instructions caps (both "every tick's prefill"), so the four
|
|
195
195
|
* too-long messages cannot drift apart in shape. Callers keep their own subject, cap, and
|
|
196
196
|
* tail; this owns only the sentence around them. */
|
|
197
197
|
export function tooLongMessage(subject, n, max, tail) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
|
|
2
3
|
import { qaCoveragePath } from "../paths.js";
|
|
3
4
|
import { isJsonObject } from "../files/json-object.js";
|
|
@@ -101,7 +102,7 @@ export function renderCoverageBlock(coverage, now = Date.now()) {
|
|
|
101
102
|
return ` ${name} — ${age} ago, ${status}`;
|
|
102
103
|
});
|
|
103
104
|
return [
|
|
104
|
-
"Flow coverage (from this
|
|
105
|
+
"Flow coverage (from this team's own record; least recently exercised first):",
|
|
105
106
|
...lines,
|
|
106
107
|
].join("\n");
|
|
107
108
|
}
|