tumwater 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +27 -13
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +6 -27
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +8 -0
- package/dist/src/agent/codex/codex-args.js +5 -5
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +26 -24
- package/dist/src/agent/opencode/opencode-catalog.js +85 -36
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +23 -27
- package/dist/src/agent/subscription-account.js +2 -83
- package/dist/src/backlog/backlog-archive.js +3 -4
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +56 -122
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +3 -2
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/baseline/main-guardrails.js +2 -2
- package/dist/src/baseline/main-watch.js +84 -50
- package/dist/src/brief/brief.js +26 -13
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/budget.js +4 -5
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-report.js +10 -0
- package/dist/src/build/build-check-scoped.js +40 -18
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/build/build-stage.js +6 -4
- package/dist/src/change/change-data.js +1 -4
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-completion-command.js +75 -0
- package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
- package/dist/src/cli/cli-doctor-command.js +103 -0
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +36 -14
- package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
- package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
- package/dist/src/cli/cli-query-commands.js +2 -2
- package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-role-command.js +2 -41
- package/dist/src/cli/cli-start-summary.js +4 -3
- package/dist/src/cli/cli-start-supervisor.js +190 -0
- package/dist/src/cli/cli-start.js +94 -244
- package/dist/src/cli/completion.js +85 -49
- package/dist/src/cli/follow-view.js +20 -2
- package/dist/src/cli/help.js +6 -13
- package/dist/src/cli.js +70 -176
- package/dist/src/collections.js +39 -27
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +23 -1
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +66 -40
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +163 -102
- package/dist/src/config/config-schema.js +10 -12
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +8 -72
- package/dist/src/config/config-validation.js +4 -39
- package/dist/src/config/config-views.js +26 -50
- package/dist/src/config/config-write.js +128 -102
- package/dist/src/config/config.js +5 -14
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +53 -120
- package/dist/src/doctor/doctor-backlog.js +16 -14
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +8 -8
- package/dist/src/doctor/doctor-claude-checks.js +70 -36
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +57 -117
- package/dist/src/doctor/doctor-fallback-checks.js +48 -26
- package/dist/src/doctor/doctor-holds.js +8 -47
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +122 -21
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +24 -11
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +44 -64
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +3 -5
- package/dist/src/events/{notify.js → event-notify.js} +6 -6
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +8 -0
- package/dist/src/failure/failure-cluster.js +9 -18
- package/dist/src/failure/failure-data.js +12 -8
- package/dist/src/failure/failure-render.js +46 -36
- package/dist/src/failure/failure-state-change.js +54 -120
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +32 -74
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +9 -8
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git-diff.js +6 -2
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-args.js +12 -0
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-config-commands.js +2 -64
- package/dist/src/gui/gui-endpoint-commands.js +1 -1
- package/dist/src/gui/gui-endpoint-common.js +19 -1
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +43 -48
- package/dist/src/gui/gui-prompt-commands.js +25 -25
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/gui/gui-server.js +1 -2
- package/dist/src/gui/gui-team-commands.js +2 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +54 -36
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-check-failures.js +12 -9
- package/dist/src/merge/merge-core.js +7 -32
- package/dist/src/merge/merge-drain.js +11 -13
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +11 -14
- package/dist/src/merge/merge-queue-render.js +3 -3
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-stack.js +17 -18
- package/dist/src/merge/merge-to-main.js +13 -58
- package/dist/src/merge/merge-verify.js +12 -21
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +80 -47
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +44 -69
- package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +18 -27
- package/dist/src/orchestrator/scheduling-skip.js +10 -14
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +10 -9
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +28 -1
- package/dist/src/pi/pi-listed-models.js +13 -0
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +41 -8
- package/dist/src/pi/pi-watchdogs.js +43 -15
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +51 -26
- package/dist/src/process/process.js +20 -12
- package/dist/src/process/run-marker.js +33 -15
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/gate-prompts.js +1 -44
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/redeploy/redeployer.js +21 -1
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/diff-rules.js +35 -21
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +48 -41
- package/dist/src/review/review-precheck.js +14 -4
- package/dist/src/review/review.js +48 -170
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/legacy-role-migration.js +6 -3
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +7 -46
- package/dist/src/roles/role-view.js +10 -33
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/ci-parity.js +19 -8
- package/dist/src/run/run-apply.js +12 -15
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +141 -90
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +128 -50
- package/dist/src/run/stage-check.js +60 -50
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/{leftover.js → runner-leftover.js} +9 -26
- package/dist/src/runner/runner-outcome.js +7 -2
- package/dist/src/runner/runner-pi.js +9 -22
- package/dist/src/runner/runner-recovery.js +5 -13
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
- package/dist/src/runner/runner-run.js +103 -50
- package/dist/src/runner/runner-state-load.js +18 -28
- package/dist/src/runner/runner-state.js +0 -14
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/backoff.js +8 -3
- package/dist/src/scheduling/claim-entries.js +2 -1
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-budget.js +4 -43
- package/dist/src/status/status-data.js +35 -39
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -18
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +105 -70
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
- package/dist/src/team/team-polls.js +53 -201
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/datetime.js +16 -7
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +29 -15
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +6 -5
- package/dist/src/ui/alert-feeds.js +46 -111
- package/dist/src/ui/badges.js +17 -88
- package/dist/src/ui/gui/gui-client-boot.js +2 -8
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-feed.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-operator.js +7 -5
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +12 -6
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +107 -44
- package/dist/src/ui/run-progress-model.js +20 -4
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +11 -30
- package/dist/src/ui/status-render.js +13 -24
- package/dist/src/ui/team-alert-backlog.js +14 -204
- package/dist/src/ui/team-alert-shared.js +6 -29
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +8 -15
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-backlog.js +2 -2
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +5 -10
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +167 -98
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +5 -4
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -230
- package/dist/src/ui/tui/tui-pane-history.js +67 -74
- package/dist/src/ui/tui/tui-pane-pending.js +288 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +15 -10
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +38 -33
- package/dist/src/ui/tui/tui.js +4 -7
- package/dist/src/verdict/fix-claim.js +107 -56
- package/dist/src/verdict/refusal.js +1 -1
- package/dist/src/verdict/reply-contract.js +3 -4
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/review/review-followup.js +0 -87
- package/dist/src/review/review-run.js +0 -395
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/cli/{brief-command.js → cli-brief-command.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
|
@@ -8,13 +8,12 @@
|
|
|
8
8
|
import { truncateExample } from "./failure-cluster.js";
|
|
9
9
|
import { cutSplitsSurrogatePair, isNonBlankString } from "../text/text.js";
|
|
10
10
|
import { finiteNumber, isJsonObject, stringList } from "../files/json-object.js";
|
|
11
|
-
import { orchestratorStopPhrase, backendKindPhrase, budgetPhrase, teamPausePhrase, teamResumePhrase, holdPhrase, plural, rolePausePhrase, roleResumePhrase, rolesPhrase,
|
|
11
|
+
import { orchestratorStopPhrase, backendKindPhrase, budgetPhrase, teamPausePhrase, teamResumePhrase, holdPhrase, plural, rolePausePhrase, roleResumePhrase, rolesPhrase, rendersAsBackendFailure, PAUSE_EXPIRED_SUFFIX } from "../text/phrases.js";
|
|
12
12
|
import { shortSha } from "../text/format.js";
|
|
13
13
|
import { deferredReasonText, untilClockSuffix, gigabytes, windowSuffix, windowResetPhrase, backendLabel } from "../events/event-format.js";
|
|
14
|
-
import { claudeUtilizationPct } from "../agent/claude/claude-account.js";
|
|
15
14
|
/** The transition events the digest replays: the decisions the harness made about itself (the
|
|
16
15
|
* cap/team gates and the team hold, the error-streak breaker's pause, the role's own daily
|
|
17
|
-
* cap,
|
|
16
|
+
* cap, live-config edits, self-hosted redeploys, need-based deferrals,
|
|
18
17
|
* orchestrator lifecycle, and the gate holds/lifts that stop and restart work — low disk,
|
|
19
18
|
* quiet hours, the maintenance allowance and the subscription-window reserve). Everything the
|
|
20
19
|
* set omits is deliberately excluded and named in STATE_CHANGE_EXCLUDED_TYPES; the two sets
|
|
@@ -25,48 +24,55 @@ import { claudeUtilizationPct } from "../agent/claude/claude-account.js";
|
|
|
25
24
|
* selfcheck role's load-bearing rule asks it to
|
|
26
25
|
* judge — whether the harness's RESPONSE to a failure was wrong (plans/telemetry-role.md) —
|
|
27
26
|
* so they sit beside the outcomes rather than being dropped. */
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
27
|
+
const STATE_CHANGE_FAMILY = {
|
|
28
|
+
// Budget and window reopening.
|
|
29
|
+
budget_paused: budgetStateChange,
|
|
30
|
+
budget_fallback: budgetStateChange,
|
|
31
|
+
budget_resumed: budgetStateChange,
|
|
32
|
+
// Pauses and resumes of teams, roles and caps.
|
|
33
|
+
team_paused: pauseStateChange,
|
|
34
|
+
team_resumed: pauseStateChange,
|
|
35
|
+
role_paused: pauseStateChange,
|
|
36
|
+
role_resumed: pauseStateChange,
|
|
37
|
+
role_streak_paused: pauseStateChange,
|
|
38
|
+
role_cap_paused: pauseStateChange,
|
|
39
|
+
role_cap_resumed: pauseStateChange,
|
|
40
|
+
// Model and fallback wiring.
|
|
41
|
+
model_changed: modelStateChange,
|
|
42
|
+
fallback_switched: modelStateChange,
|
|
43
|
+
budget_handback: modelStateChange,
|
|
44
|
+
// Provider hold and resume.
|
|
45
|
+
rate_limit_hold: providerHoldStateChange,
|
|
46
|
+
rate_limit_resumed: providerHoldStateChange,
|
|
47
|
+
// Live-config edits.
|
|
48
|
+
max_concurrent_changed: liveConfigStateChange,
|
|
49
|
+
retention_changed: liveConfigStateChange,
|
|
50
|
+
config_changed: liveConfigStateChange,
|
|
51
|
+
// Self-hosted redeploys.
|
|
52
|
+
build_stale: updateStateChange,
|
|
53
|
+
restart_pending: updateStateChange,
|
|
54
|
+
restart: updateStateChange,
|
|
55
|
+
restart_refused: updateStateChange,
|
|
56
|
+
restart_blocked: updateStateChange,
|
|
57
|
+
// Need-based deferrals and gate holds/lifts.
|
|
58
|
+
run_deferred: waitStateChange,
|
|
59
|
+
disk_low: waitStateChange,
|
|
60
|
+
disk_ok: waitStateChange,
|
|
61
|
+
quiet_hours_started: waitStateChange,
|
|
62
|
+
quiet_hours_ended: waitStateChange,
|
|
63
|
+
maintenance_quota_hold: waitStateChange,
|
|
64
|
+
maintenance_quota_resumed: waitStateChange,
|
|
65
|
+
subscription_window_limited: waitStateChange,
|
|
66
|
+
subscription_window_reset: waitStateChange,
|
|
67
|
+
// Orchestrator lifecycle.
|
|
68
|
+
orchestrator_start: lifecycleStateChange,
|
|
69
|
+
orchestrator_stop: lifecycleStateChange,
|
|
70
|
+
supervisor_exit: lifecycleStateChange,
|
|
71
|
+
bootstrap_complete: lifecycleStateChange,
|
|
72
|
+
};
|
|
73
|
+
/** The event types the digest replays, derived from STATE_CHANGE_FAMILY's keys so the filter
|
|
74
|
+
* set can never drift from the dispatch. */
|
|
75
|
+
export const STATE_CHANGE_TYPES = new Set(Object.keys(STATE_CHANGE_FAMILY));
|
|
70
76
|
/** The event types the digest deliberately does not replay, one per EVENT_TYPE_LABELS key that
|
|
71
77
|
* is not a STATE_CHANGE_TYPES member. Together with STATE_CHANGE_TYPES it partitions
|
|
72
78
|
* EVENT_TYPE_LABELS' keys, so a new event type must be classified in one set or the other — the
|
|
@@ -120,13 +126,13 @@ export const STATE_CHANGE_EXCLUDED_TYPES = new Set([
|
|
|
120
126
|
"check_wait",
|
|
121
127
|
// Observations and actions.
|
|
122
128
|
"disk_reclaim",
|
|
123
|
-
"subscription_window_warning",
|
|
124
129
|
"image_blind",
|
|
125
130
|
// State-adjacent but not self-decisions.
|
|
126
131
|
"state_migrated",
|
|
127
132
|
"restart_forced",
|
|
128
133
|
"backend_binary",
|
|
129
134
|
"opencode_free_model_billed",
|
|
135
|
+
"image_reroute",
|
|
130
136
|
]);
|
|
131
137
|
/** The Team state changes section's caps: newest N transitions, each line's payload capped at
|
|
132
138
|
* STATE_CHANGE_MAX (a marked word-boundary cut per truncateExample), each free field within it
|
|
@@ -171,16 +177,10 @@ function field(v) {
|
|
|
171
177
|
}
|
|
172
178
|
/** The backend a fallback-ish decision line names, as "provider/model", defaulting each half
|
|
173
179
|
* to pi's own default when the event omits it and slicing both through field. Shared by the
|
|
174
|
-
* budget_fallback
|
|
180
|
+
* budget_fallback line so the default and its slice rule stay in one place. */
|
|
175
181
|
function providerModel(ev) {
|
|
176
182
|
return `${field(ev.provider ?? "pi default")}/${field(ev.model ?? "pi default")}`;
|
|
177
183
|
}
|
|
178
|
-
/** A utilization fraction as its whole-number percent, read through finiteNumber so a torn or
|
|
179
|
-
* hand-edited subscription event renders 0% rather than NaN — the same corrupt-field rule the
|
|
180
|
-
* feed's own subscription lines apply. */
|
|
181
|
-
function pct(v) {
|
|
182
|
-
return claudeUtilizationPct(finiteNumber(v, 0));
|
|
183
|
-
}
|
|
184
184
|
/** A per-family builder of one state-change line, one per event family, so the budget, pause,
|
|
185
185
|
* model-wiring, provider-hold, live-config, update, wait and lifecycle wordings are each found
|
|
186
186
|
* by name. Each is a total function over any HarnessEvent — its `default` returns the bare type
|
|
@@ -250,18 +250,6 @@ function pauseStateChange(ev) {
|
|
|
250
250
|
function modelStateChange(ev) {
|
|
251
251
|
let text;
|
|
252
252
|
switch (ev.type) {
|
|
253
|
-
case "model_fallback_started":
|
|
254
|
-
// The role tripped off its primary onto its fallback after provider-class failures.
|
|
255
|
-
text = `model fallback — ${providerModel(ev)} engaged (${field(ev.reason ?? "backend failure")})`;
|
|
256
|
-
break;
|
|
257
|
-
case "model_fallback_ended":
|
|
258
|
-
// The return counterpart: names the primary the role resumed on and how long the
|
|
259
|
-
// off-model episode ran.
|
|
260
|
-
text = `model fallback ended — back on ${providerModel(ev)} after ${shortSpanPhrase(finiteNumber(ev.durationMs, 0))}`;
|
|
261
|
-
break;
|
|
262
|
-
case "provider_concurrency_changed":
|
|
263
|
-
text = `${field(ev.provider)} concurrency ${field(ev.from)} → ${field(ev.to)}`;
|
|
264
|
-
break;
|
|
265
253
|
case "model_changed":
|
|
266
254
|
// A model_cleared (or a roles.<id>.model-only) live edit arrives with `to: null`;
|
|
267
255
|
// name pi's default the way the feed does so the digest never renders a bare
|
|
@@ -401,9 +389,6 @@ function waitStateChange(ev) {
|
|
|
401
389
|
case "maintenance_quota_resumed":
|
|
402
390
|
text = `maintenance allowance reopened — ${field(ev.maint)}/${field(ev.allowance)} in 24h`;
|
|
403
391
|
break;
|
|
404
|
-
case "subscription_window_reserved":
|
|
405
|
-
text = `waiting: ${backendLabel(ev)} window ${field(ev.windowType)} at ${pct(ev.utilization)}% past its ${pct(ev.reserve)}% reserve`;
|
|
406
|
-
break;
|
|
407
392
|
case "subscription_window_limited":
|
|
408
393
|
text = `waiting: ${backendLabel(ev)} window ${field(ev.windowType)} rejected${windowResetPhrase(ev)}`;
|
|
409
394
|
break;
|
|
@@ -442,56 +427,5 @@ function lifecycleStateChange(ev) {
|
|
|
442
427
|
* read as complete or stand mid-word where a key or value should be; the render adds the
|
|
443
428
|
* timestamp and a roleCell-sliced role, so no unbounded field reaches the page. */
|
|
444
429
|
export function describeStateChange(ev) {
|
|
445
|
-
|
|
446
|
-
case "budget_paused":
|
|
447
|
-
case "budget_fallback":
|
|
448
|
-
case "budget_resumed":
|
|
449
|
-
return truncateExample(budgetStateChange(ev), STATE_CHANGE_MAX);
|
|
450
|
-
case "team_paused":
|
|
451
|
-
case "team_resumed":
|
|
452
|
-
case "role_paused":
|
|
453
|
-
case "role_resumed":
|
|
454
|
-
case "role_streak_paused":
|
|
455
|
-
case "role_cap_paused":
|
|
456
|
-
case "role_cap_resumed":
|
|
457
|
-
return truncateExample(pauseStateChange(ev), STATE_CHANGE_MAX);
|
|
458
|
-
case "model_fallback_started":
|
|
459
|
-
case "model_fallback_ended":
|
|
460
|
-
case "provider_concurrency_changed":
|
|
461
|
-
case "model_changed":
|
|
462
|
-
case "fallback_switched":
|
|
463
|
-
case "budget_handback":
|
|
464
|
-
return truncateExample(modelStateChange(ev), STATE_CHANGE_MAX);
|
|
465
|
-
case "rate_limit_hold":
|
|
466
|
-
case "rate_limit_resumed":
|
|
467
|
-
return truncateExample(providerHoldStateChange(ev), STATE_CHANGE_MAX);
|
|
468
|
-
case "max_concurrent_changed":
|
|
469
|
-
case "retention_changed":
|
|
470
|
-
case "config_changed":
|
|
471
|
-
return truncateExample(liveConfigStateChange(ev), STATE_CHANGE_MAX);
|
|
472
|
-
case "build_stale":
|
|
473
|
-
case "restart_pending":
|
|
474
|
-
case "restart":
|
|
475
|
-
case "restart_refused":
|
|
476
|
-
case "restart_blocked":
|
|
477
|
-
return truncateExample(updateStateChange(ev), STATE_CHANGE_MAX);
|
|
478
|
-
case "run_deferred":
|
|
479
|
-
case "disk_low":
|
|
480
|
-
case "disk_ok":
|
|
481
|
-
case "quiet_hours_started":
|
|
482
|
-
case "quiet_hours_ended":
|
|
483
|
-
case "maintenance_quota_hold":
|
|
484
|
-
case "maintenance_quota_resumed":
|
|
485
|
-
case "subscription_window_reserved":
|
|
486
|
-
case "subscription_window_limited":
|
|
487
|
-
case "subscription_window_reset":
|
|
488
|
-
return truncateExample(waitStateChange(ev), STATE_CHANGE_MAX);
|
|
489
|
-
case "orchestrator_start":
|
|
490
|
-
case "orchestrator_stop":
|
|
491
|
-
case "supervisor_exit":
|
|
492
|
-
case "bootstrap_complete":
|
|
493
|
-
return truncateExample(lifecycleStateChange(ev), STATE_CHANGE_MAX);
|
|
494
|
-
default:
|
|
495
|
-
return truncateExample(ev.type, STATE_CHANGE_MAX);
|
|
496
|
-
}
|
|
430
|
+
return truncateExample(STATE_CHANGE_FAMILY[ev.type]?.(ev) ?? ev.type, STATE_CHANGE_MAX);
|
|
497
431
|
}
|
|
@@ -87,92 +87,125 @@ function resolveQueuedRuns(runEvents, allEvents) {
|
|
|
87
87
|
}
|
|
88
88
|
/** Classify one run's loss cause into the key/kind/example triple the ranking folds, or null
|
|
89
89
|
* when the run prices no loss. The branch order is the contract: refused, then cut-off, then
|
|
90
|
-
* no_change, then clustered, then the three queued outcomes
|
|
91
|
-
*
|
|
92
|
-
* indexes from `indexes` so
|
|
90
|
+
* no_change, then clustered, then the three queued outcomes — each rule owned by its own
|
|
91
|
+
* named helper below, so the `??` chain reads left to right in the same order. Reads the
|
|
92
|
+
* per-runner join indexes from `indexes` so each helper names a rule by a single lookup. */
|
|
93
93
|
function lossCauseFor(ev, cls, resolvedOutcome, indexes) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
94
|
+
return (refusedLoss(ev) ??
|
|
95
|
+
cutoffLoss(ev, cls) ??
|
|
96
|
+
noChangeLoss(ev, cls) ??
|
|
97
|
+
clusteredLoss(ev) ??
|
|
98
|
+
reviewRejectedLoss(ev, resolvedOutcome, indexes) ??
|
|
99
|
+
reviewErrorLoss(ev, resolvedOutcome, indexes) ??
|
|
100
|
+
mergeFailureLoss(ev, resolvedOutcome, indexes));
|
|
101
|
+
}
|
|
102
|
+
/** The refused rule: a run whose verdict refused prices its own per-role cause, not no_change. */
|
|
103
|
+
function refusedLoss(ev) {
|
|
104
|
+
if (ev.result !== "refused")
|
|
105
|
+
return null;
|
|
106
|
+
// A refusal is a deliberate objection that blocks its entry, not quiet idleness — the
|
|
107
|
+
// reason live in the event + lastSummary (src/verdict/refusal.ts). Rank it as its own
|
|
108
|
+
// per-role cause so the digest surfaces the objection instead of pricing the run
|
|
109
|
+
// exactly like a silently idle one; the branch sits BEFORE the no_change one because a
|
|
110
|
+
// refused run is also no_change-class in the outcome table. The example rides the
|
|
111
|
+
// refusal's summary the way a review-rejected cause rides its reason.
|
|
112
|
+
return {
|
|
113
|
+
key: `refused\u0000${eventRole(ev)}`,
|
|
114
|
+
kind: "refused",
|
|
115
|
+
example: typeof ev.summary === "string" ? truncateExample(ev.summary) : "",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** The cut-off rule: a no_change run truncated at its context ceiling prices its own cause. */
|
|
119
|
+
function cutoffLoss(ev, cls) {
|
|
120
|
+
if (cls !== "no_change" || ev.cutOff !== true)
|
|
121
|
+
return null;
|
|
122
|
+
// A run truncated at its model's context ceiling ends no_change with cutOff: true
|
|
123
|
+
// (diagnoseNoChange in src/run/run-verdict.ts), and finalizeRun rides that flag onto
|
|
124
|
+
// the run_end. Rank it as its own per-role cause so the digest separates the churn of
|
|
125
|
+
// a context ceiling — a cost of a long session, which the harness resumes — from
|
|
126
|
+
// deliberate idleness; the branch sits BEFORE the no_change one because a cut-off run
|
|
127
|
+
// is also no_change-class in the outcome table. No example, like the no_change cause.
|
|
128
|
+
return { key: `cutoff\u0000${eventRole(ev)}`, kind: "cutoff", example: "" };
|
|
129
|
+
}
|
|
130
|
+
/** The no_change rule: a silent no_change run prices its own per-role cause. */
|
|
131
|
+
function noChangeLoss(ev, cls) {
|
|
132
|
+
if (cls !== "no_change")
|
|
133
|
+
return null;
|
|
134
|
+
return { key: `no_change\u0000${eventRole(ev)}`, kind: "no_change", example: "" };
|
|
135
|
+
}
|
|
136
|
+
/** The clustered rule: a clustered result with no error text prices under the pooled cluster. */
|
|
137
|
+
function clusteredLoss(ev) {
|
|
138
|
+
if (!CLUSTERED_RESULTS.has(String(ev.result)))
|
|
139
|
+
return null;
|
|
140
|
+
// A clustered result with no error text still owns its time: an abort's run_end never
|
|
141
|
+
// carries one (planRunStart clears s.lastError, run-finalize logs error: undefined),
|
|
142
|
+
// and a malformed error run can lose it too, so both take the same placeholder cause the
|
|
143
|
+
// error-clusters section already itemizes rather than dropping the span itemized nowhere.
|
|
144
|
+
const pooled = pooledClusterCause(ev.error);
|
|
145
|
+
return { key: pooled.key, kind: "error-cluster", example: pooled.example };
|
|
146
|
+
}
|
|
147
|
+
/** The review-rejected rule: a queued run whose merge was rejected prices its reason. */
|
|
148
|
+
function reviewRejectedLoss(ev, resolvedOutcome, indexes) {
|
|
149
|
+
if (ev.result !== "queued" || resolvedOutcome?.result !== "rejected")
|
|
150
|
+
return null;
|
|
151
|
+
// Match the merge ATTEMPT, not the pin's sha: every verification rebases its pin onto main
|
|
152
|
+
// before the gate (syncPinToMain), so `review_rejected.head` is the rebased commit while
|
|
153
|
+
// `merge_queued` keeps the authored one — exact-sha equality names no reason for a rebased
|
|
154
|
+
// rejection (BUGS.md 2026-10-10). The rejection for this attempt is the one inside the
|
|
155
|
+
// claimed pin and its outcome, the window no other attempt can occupy.
|
|
156
|
+
const rej = newestAttemptEvent(indexes.rejectedByRunner, eventRole(ev), resolvedOutcome);
|
|
157
|
+
const reasons = stringList(rej?.reasons);
|
|
158
|
+
return {
|
|
159
|
+
key: `review-rejected\u0000${eventRole(ev)}`,
|
|
160
|
+
kind: "review-rejected",
|
|
161
|
+
example: reasons[0] !== undefined ? truncateExample(reasons[0]) : "",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/** The review_error rule: a queued run whose review process failed prices under its cluster. */
|
|
165
|
+
function reviewErrorLoss(ev, resolvedOutcome, indexes) {
|
|
166
|
+
if (ev.result !== "queued" || resolvedOutcome?.result !== "review_error")
|
|
167
|
+
return null;
|
|
168
|
+
// The review process itself failed (a dead backend, a timeout, no parseable verdict):
|
|
169
|
+
// the pin was kept and re-queued, and the matching `review_failed` event carries the
|
|
170
|
+
// message the review-failure section already clusters. Price the authoring span under
|
|
171
|
+
// that cluster's key — a review timeout pools into the shared `timed out after <dur>`
|
|
172
|
+
// cause via poolTimeoutKey — and fall back to the shared placeholder when no event is
|
|
173
|
+
// visible, so the span is itemized rather than hidden. The match keys on the merge
|
|
174
|
+
// ATTEMPT, not the pin's sha: every verification rebases its pin onto main before the gate
|
|
175
|
+
// (syncPinToMain), so `review_failed.head` is the rebased commit while `merge_queued`
|
|
176
|
+
// keeps the authored one — exact-sha equality missed every rebased verification (BUGS.md
|
|
177
|
+
// 2026-10-10). The review failure for this attempt is the one that falls inside the
|
|
178
|
+
// claimed pin and its outcome, the window no other attempt can occupy.
|
|
179
|
+
const failed = newestAttemptEvent(indexes.reviewFailedByRunner, eventRole(ev), resolvedOutcome);
|
|
180
|
+
const pooled = pooledClusterCause(failed?.message);
|
|
181
|
+
return { key: pooled.key, kind: "error-cluster", example: pooled.example };
|
|
182
|
+
}
|
|
183
|
+
/** The merge-failure rule: a queued run whose merge conflicted or was blocked prices its own
|
|
184
|
+
* cause, with the deadline-killed merge-block special case overriding key/example. */
|
|
185
|
+
function mergeFailureLoss(ev, resolvedOutcome, indexes) {
|
|
186
|
+
if (ev.result !== "queued" ||
|
|
187
|
+
(resolvedOutcome?.result !== "merge_conflict" && resolvedOutcome?.result !== "merge_blocked"))
|
|
188
|
+
return null;
|
|
189
|
+
// The merge failed to merge (a conflict, or a blocked fast-forward/rebase): this
|
|
190
|
+
// authoring span burned. The `merge_failed` event carries no message — writeMergeOutcome
|
|
191
|
+
// logs only result/commit/durationMs/usage — so the cause is named by its own result and
|
|
192
|
+
// takes the shared no-text placeholder, the same fallback a message-less aborted run_end
|
|
193
|
+
// uses, rather than dropping a span the error-class cell already priced (BUGS.md
|
|
194
|
+
// 2026-10-10).
|
|
195
|
+
const key = `${resolvedOutcome.result}\u0000${eventRole(ev)}`;
|
|
196
|
+
const kind = resolvedOutcome.result === "merge_conflict" ? "merge-conflict" : "merge-blocked";
|
|
197
|
+
const example = errorTextOrPlaceholder(undefined);
|
|
198
|
+
// A merge-scope check the harness killed at its own deadline is the team's load, not a
|
|
199
|
+
// verdict on the change: split it into its own cause so the ranking names it.
|
|
200
|
+
const killed = newestAttemptEvent(indexes.deadlineKilledByRunner, eventRole(ev), resolvedOutcome);
|
|
201
|
+
if (kind === "merge-blocked" && killed) {
|
|
202
|
+
return {
|
|
203
|
+
key: `merge_blocked_deadline\u0000${eventRole(ev)}`,
|
|
204
|
+
kind,
|
|
205
|
+
example: `${String(killed.scope)} build check killed at its ${nonNegativeNumber(killed.timeoutMs, 0) / 1000}s deadline (tumwater's own timeout, not a test failure)`,
|
|
206
|
+
};
|
|
174
207
|
}
|
|
175
|
-
return
|
|
208
|
+
return { key, kind, example };
|
|
176
209
|
}
|
|
177
210
|
/** The pooled cluster key and truncated example a failure-message source's span prices under:
|
|
178
211
|
* the shared placeholder when the source carries no message, the pooled render
|
package/dist/src/files/files.js
CHANGED
|
@@ -117,6 +117,25 @@ export function readTextOrNull(file) {
|
|
|
117
117
|
return null; // Missing or unreadable — no data.
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
+
/** Read a text file whole, but only when it is a regular file: a symlink (or directory,
|
|
121
|
+
* gitlink, FIFO) is never followed, so a hostile entry that points out of the worktree cannot
|
|
122
|
+
* pull an arbitrary host file's text into whatever reads it (the same lstat policy INV-G3 and
|
|
123
|
+
* INV-G4 pin for the merge gates). A symlink holds no text of its own and its target could
|
|
124
|
+
* name a sensitive host path, so it reads as absent. Null when the file is missing/unreadable,
|
|
125
|
+
* or not a regular file. This is the reader for the two prompt-input files whose text is
|
|
126
|
+
* injected into every model run — PRINCIPLES.md and a role's notes — and their operator
|
|
127
|
+
* readers, so the prompt and the surfaces reporting on those files cannot disagree;
|
|
128
|
+
* readTextOrNull stays for paths where following a symlink is acceptable or impossible. */
|
|
129
|
+
export function readRegularTextOrNull(file) {
|
|
130
|
+
try {
|
|
131
|
+
if (!fs.lstatSync(file).isFile())
|
|
132
|
+
return null; // not a regular file: never followed
|
|
133
|
+
return fs.readFileSync(file, "utf8");
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null; // Missing or unreadable — no data.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
120
139
|
/** Read a text file whole, or null only when it does not exist (ENOENT) — for the paths that
|
|
121
140
|
* must never mistake an unreadable file for an absent one. A permission or I/O failure (EACCES,
|
|
122
141
|
* EISDIR, EMFILE) is rethrown so the caller fails loudly instead of treating the file as gone:
|
|
@@ -351,6 +370,17 @@ export function removeQuiet(file) {
|
|
|
351
370
|
// Already gone (or unremovable) — cleanup must not throw.
|
|
352
371
|
}
|
|
353
372
|
}
|
|
373
|
+
/** Delete `file` and return whether it was there to delete — the toggle-lift idiom shared by
|
|
374
|
+
* resumeTeam and cancelScheduledStop (the team's marker-lift functions): true when a marker
|
|
375
|
+
* was present and removed, false when it was already gone. Relies on removeQuiet's
|
|
376
|
+
* never-throws delete, so a marker vanishing between an existsSync check and the unlink reads
|
|
377
|
+
* as success rather than a failure to surface from a toggle. */
|
|
378
|
+
export function removeIfPresent(file) {
|
|
379
|
+
if (!fs.existsSync(file))
|
|
380
|
+
return false;
|
|
381
|
+
removeQuiet(file);
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
354
384
|
/** Delete a directory tree, retrying the transient filesystem races (`ENOTEMPTY`/`EBUSY`/
|
|
355
385
|
* `EPERM`) that recursive `rmSync` otherwise turns into a hard failure. Node defaults
|
|
356
386
|
* `maxRetries` to 0, and on macOS an entry appearing between `rmSync`'s walk and its `rmdir`
|
|
@@ -16,8 +16,8 @@ export function isJsonObject(value) {
|
|
|
16
16
|
* when it is an array and [] otherwise — the one home of the "an event/record field is the
|
|
17
17
|
* list or it is nothing" read every formatter applies (event-format.ts's review_rejected
|
|
18
18
|
* reasons, dep_install packages, config_changed keys; src/failure/failure-state-change.ts's
|
|
19
|
-
* config_changed keys; time-spend.ts's review-rejection reasons; team-state.ts's
|
|
20
|
-
* list). A torn or hand-edited line
|
|
19
|
+
* config_changed keys; failure-time-spend.ts's review-rejection reasons; team-state.ts's
|
|
20
|
+
* paused-roles list). A torn or hand-edited line
|
|
21
21
|
* never throws and never renders a placeholder: the field reads as absent, and the caller's
|
|
22
22
|
* empty-list branch fires. Foreign entries inside an array are dropped for the same reason —
|
|
23
23
|
* letting them through made `join` render "[object Object]" and handed
|
|
@@ -36,9 +36,9 @@ export function stringList(value) {
|
|
|
36
36
|
* build_stale, slot_wait, restart, and permit_reserve renderers (aheadCommits, waitedMs,
|
|
37
37
|
* abortedRuns, reserve → 0); phrases.ts's budgetPhrase (spentUsd, capUsd → 0); redeploy.ts's
|
|
38
38
|
* autoRestartRecord (lastAt → null); build/build-info.ts's readBuildInfo (builtAt → 0);
|
|
39
|
-
* failure/failure-state-change.ts's
|
|
40
|
-
*
|
|
41
|
-
* team/orchestrator-info.ts's isFiniteNumber (v → undefined); team/team-state.ts's
|
|
39
|
+
* failure/failure-state-change.ts's build_stale renderer (aheadCommits → 0);
|
|
40
|
+
* gates/maintenance-quota.ts's foldLine and seed (ev.ts → null);
|
|
41
|
+
* team/team-orchestrator-info.ts's isFiniteNumber (v → undefined); team/team-state.ts's
|
|
42
42
|
* standingMarker, pausedRoles, and standingRolesUntil (until → undefined);
|
|
43
43
|
* operator/operator-requests.ts's consumeWakeRequest (notBeforeMs → undefined);
|
|
44
44
|
* git/slots-state.ts's readSlotRecord (pinnedAt, lastReleasedAt → null); git/worktree-use.ts's
|
|
@@ -57,9 +57,9 @@ export function finiteNumber(value, fallback) {
|
|
|
57
57
|
* runner/runner-state-load.ts's healNumber (every numeric RunnerState field → 0),
|
|
58
58
|
* run/run-usage.ts's counter (each persisted usage count → 0), pi/pi-stream.ts's usageNumber
|
|
59
59
|
* (one wire usage number → 0), ui/progress-data.ts's message_end read (totalTokens → the
|
|
60
|
-
* previous context, output and its peak floor → 0), time-spend.ts's runDurationMs (an
|
|
61
|
-
* durationMs → null, the "no own duration" marker) and its merge_blocked_deadline
|
|
62
|
-
* (the deadline-killed check's timeoutMs → 0, so a torn deadline prices as 0s, never NaN);
|
|
60
|
+
* previous context, output and its peak floor → 0), failure-time-spend.ts's runDurationMs (an
|
|
61
|
+
* event's durationMs → null, the "no own duration" marker) and its merge_blocked_deadline
|
|
62
|
+
* example (the deadline-killed check's timeoutMs → 0, so a torn deadline prices as 0s, never NaN);
|
|
63
63
|
* pi-extension/context-budget.ts's
|
|
64
64
|
* contextNote (usedTokens, usedWindow → null), pi-extension/context-shake.ts's percentOf
|
|
65
65
|
* (tokens, contextWindow → null), merge/merge-queue.ts's isMergeEntry (run, enqueuedAt,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readRegularTextOrNull, sameStatIdentity, statOrNull } from "./files.js";
|
|
2
2
|
/** Safety cap so a stat-keyed cache can never grow unbounded (e.g. many short-lived roots in
|
|
3
3
|
* tests). Evicting only costs one re-read per file on the next call. */
|
|
4
4
|
const MAX_STAT_CACHED = 64;
|
|
@@ -32,15 +32,19 @@ export function cachedByStat(cache, key, file, load, clone) {
|
|
|
32
32
|
return clone(value);
|
|
33
33
|
}
|
|
34
34
|
/** One stat-cached read of a text file, or null when it is missing or unreadable — the shared
|
|
35
|
-
* load half over `cachedByStat` for the readers that parse a markdown/text file:
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
35
|
+
* load half over `cachedByStat` for the readers that parse a markdown/text file: it reads the
|
|
36
|
+
* file through readRegularTextOrNull, so a symlink (or directory/gitlink) is never followed —
|
|
37
|
+
* a hostile entry dropped at a backlog file path cannot pull an arbitrary host file's text into
|
|
38
|
+
* the run prompts and surfaces these readers feed (the same lstat policy INV-G3/G4 pin for the
|
|
39
|
+
* prompt readers and merge gates) — and a null (no data) propagates through so a render path
|
|
40
|
+
* never throws on the file's state. `parse` runs only on a cache miss and receives the file's
|
|
41
|
+
* text (never null); `clone` hands each caller its own copy. The callers: backlog.ts's
|
|
42
|
+
* sectionEntries and sectionCompletionDates, backlog-structure.ts's strandedPlanEntriesFor and
|
|
43
|
+
* actionableEntryRangesFor, backlog-index-data.ts's actionableEntryRangesFor, and
|
|
44
|
+
* backlog-index.ts's renderBacklogIndexBlock. */
|
|
41
45
|
export function cachedTextByStat(cache, key, file, parse, clone) {
|
|
42
46
|
return cachedByStat(cache, key, file, () => {
|
|
43
|
-
const md =
|
|
47
|
+
const md = readRegularTextOrNull(file); // Missing, unreadable, or not a regular file — no data.
|
|
44
48
|
return md === null ? null : parse(md);
|
|
45
49
|
}, clone);
|
|
46
50
|
}
|