tumwater 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +42 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +4 -3
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +246 -55
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +85 -274
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +175 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +139 -32
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +9 -11
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget.js +30 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +1 -2
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +118 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +150 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +308 -590
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
- package/dist/src/cli/completion.js +149 -22
- package/dist/src/cli/config-commands.js +41 -18
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +189 -73
- package/dist/src/cli/log-commands.js +148 -82
- package/dist/src/cli/question-commands.js +205 -10
- package/dist/src/cli.js +106 -43
- package/dist/src/collections.js +26 -10
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +13 -1
- package/dist/src/config/config-fallbacks.js +93 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +141 -199
- package/dist/src/config/config-views.js +127 -53
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +131 -264
- package/dist/src/config/config.js +48 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +100 -496
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +52 -579
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +82 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +236 -402
- package/dist/src/doctor/doctor-notify.js +3 -4
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor.js +121 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +7 -11
- package/dist/src/events/event-format.js +181 -86
- package/dist/src/events/event-labels.js +12 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +39 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +60 -24
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +56 -56
- package/dist/src/failure/failure-render.js +31 -23
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +99 -35
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +177 -84
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +108 -58
- package/dist/src/gates/maintenance-quota.js +10 -13
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +77 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +36 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +42 -18
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +8 -13
- package/dist/src/gui/gui-config-commands.js +94 -72
- package/dist/src/gui/gui-endpoint-commands.js +4 -3
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +108 -13
- package/dist/src/gui/gui-prompt-commands.js +11 -14
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +11 -4
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +193 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +25 -9
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +81 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +150 -11
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/merge/merge-queue-render.js +49 -11
- package/dist/src/merge/merge-queue.js +31 -15
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +33 -11
- package/dist/src/merge/merge-to-main.js +86 -27
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +12 -10
- package/dist/src/notes-cli.js +30 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
- package/dist/src/orchestrator/orchestrator.js +128 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +116 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +18 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +121 -211
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +129 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +161 -77
- package/dist/src/report/report-fold.js +129 -52
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +133 -64
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +100 -1
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +4 -5
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +155 -360
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +69 -12
- package/dist/src/roles/role-guidance.js +55 -20
- package/dist/src/roles/role-labels.js +1 -2
- package/dist/src/roles/role-render.js +67 -50
- package/dist/src/roles/role-view.js +59 -47
- package/dist/src/roles/roles.js +31 -31
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +9 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/{loop → runner}/leftover.js +80 -25
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +43 -16
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +28 -28
- package/dist/src/status/status-roles.js +39 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +5 -6
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +63 -17
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +57 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +67 -13
- package/dist/src/ui/badges.js +65 -37
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +32 -26
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +126 -285
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +434 -67
- package/dist/src/ui/gui/gui-client-labels.js +1 -2
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +55 -51
- package/dist/src/ui/gui/gui-client-pending.js +227 -23
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +87 -427
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +56 -4
- package/dist/src/ui/gui/gui-client-team.js +25 -465
- package/dist/src/ui/gui/gui-client.js +27 -25
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +9 -7
- package/dist/src/ui/gui/gui-styles.js +34 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +136 -49
- package/dist/src/ui/status-render.js +101 -82
- package/dist/src/ui/team-alerts.js +248 -57
- package/dist/src/ui/tone.js +5 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +51 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +3 -5
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +10 -12
- package/dist/src/ui/tui/tui-editor-budget.js +35 -31
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-entry.js +36 -47
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +11 -9
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +3 -5
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
- package/dist/src/ui/tui/tui-editor-retire.js +4 -6
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +60 -34
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +35 -2
- package/dist/src/ui/tui/tui-frame.js +56 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +24 -9
- package/dist/src/ui/tui/tui-pane-activity.js +14 -11
- package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +58 -8
- package/dist/src/ui/tui/tui-pane-history.js +402 -82
- package/dist/src/ui/tui/tui-pane-pending.js +308 -13
- package/dist/src/ui/tui/tui-pane-queued.js +71 -55
- package/dist/src/ui/tui/tui-pane-report.js +46 -14
- package/dist/src/ui/tui/tui-pane-session.js +25 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +80 -23
- package/dist/src/ui/tui/tui-panes.js +100 -30
- package/dist/src/ui/tui/tui.js +14 -14
- package/dist/src/verdict/fix-claim.js +82 -32
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +42 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { runCommitSha } from "./run-detail-data.js";
|
|
2
|
+
import { formatEvent } from "../events/event-format.js";
|
|
3
|
+
import { eventTypeLabel } from "../events/event-labels.js";
|
|
4
|
+
import { roleLabel } from "../roles/role-labels.js";
|
|
5
|
+
import { shortSpanPhrase } from "../text/phrases.js";
|
|
6
|
+
import { shortSha } from "../text/format.js";
|
|
7
|
+
import { piLogPath } from "../paths.js";
|
|
8
|
+
import { readRunTranscript } from "../ui/transcript-run.js";
|
|
9
|
+
import { gitTry } from "../git/git-run.js";
|
|
10
|
+
/** The `git show --stat` summary or full `--patch` of a run's pinned commit. The sha is the
|
|
11
|
+
* authored commit the run pinned: a merge that rebased gives main a different sha with the
|
|
12
|
+
* same changes, so this describes what the run changed. */
|
|
13
|
+
async function runCommitShow(root, sha, what) {
|
|
14
|
+
if (sha === null)
|
|
15
|
+
return "(no commit: this run sent nothing to merge)";
|
|
16
|
+
const out = await gitTry(root, "show", what, "--format=", sha);
|
|
17
|
+
if (out === null)
|
|
18
|
+
return `(commit ${shortSha(sha)} is not in this repository)`;
|
|
19
|
+
return out.trimEnd();
|
|
20
|
+
}
|
|
21
|
+
/** The pinned commit's stat text for a run, shared by `run --stat` and GET /api/run so both
|
|
22
|
+
* apply one rule for the sha and the "(no commit …)" / "(commit … is not in this repository)"
|
|
23
|
+
* texts. */
|
|
24
|
+
export function runDetailStat(root, d) {
|
|
25
|
+
return runCommitShow(root, runCommitSha(d), "--stat");
|
|
26
|
+
}
|
|
27
|
+
/** The pinned commit's full patch for a run, for `history --patch`. Like runDetailStat it
|
|
28
|
+
* shows the commit the run pinned (runCommitSha), not the `merged as` sha, so the stat and
|
|
29
|
+
* the patch describe the same commit; the placeholder texts are the stat's. GET /api/run is its
|
|
30
|
+
* second caller (the dashboard's "Show patch" button). */
|
|
31
|
+
export function runDetailPatch(root, d) {
|
|
32
|
+
return runCommitShow(root, runCommitSha(d), "--patch");
|
|
33
|
+
}
|
|
34
|
+
/** The human view of a RunDetail: one summary line — result (or the in-flight/unpaired
|
|
35
|
+
* marker), duration, usage, and the model the run started on when its `run_start` carried one
|
|
36
|
+
* (after the usage, before the pinned-commit sha), plus the pinned commit sha when the block's
|
|
37
|
+
* `merge_queued`/`merge_done` event carries one — then each event through formatEvent, the
|
|
38
|
+
* shared renderer `logs` and the
|
|
39
|
+
* TUI use, so the trail reads exactly like the feed it came from. A queued run whose merge
|
|
40
|
+
* has finished shows that verdict in the header and ends with the merge's outcome line; a
|
|
41
|
+
* leftover-recovery run's verdict is the failed attempt it re-queued (resolveQueuedResult),
|
|
42
|
+
* logged before the run began, so that outcome line leads the trail instead — the trail stays
|
|
43
|
+
* in time order. */
|
|
44
|
+
export function renderRunDetail(d) {
|
|
45
|
+
const summary = [];
|
|
46
|
+
if (d.endTs === null)
|
|
47
|
+
summary.push(d.startTs === null ? "unpaired" : "in flight");
|
|
48
|
+
else
|
|
49
|
+
summary.push(d.result ? eventTypeLabel(d.result) : "?");
|
|
50
|
+
if (d.durationMs !== null)
|
|
51
|
+
summary.push(shortSpanPhrase(d.durationMs));
|
|
52
|
+
if (d.usage !== "")
|
|
53
|
+
summary.push(d.usage);
|
|
54
|
+
if (d.model !== null)
|
|
55
|
+
summary.push(d.model);
|
|
56
|
+
const sha = runCommitSha(d);
|
|
57
|
+
let header = `${roleLabel(d.role)} run #${d.run} — ${summary.join(" · ")}`;
|
|
58
|
+
if (sha !== null)
|
|
59
|
+
header += ` · commit ${shortSha(sha)}`;
|
|
60
|
+
if (d.mergedSha !== null && d.mergedSha !== sha)
|
|
61
|
+
header += ` · merged as ${shortSha(d.mergedSha)}`;
|
|
62
|
+
if (d.reverted !== null) {
|
|
63
|
+
header += ` · reverted ${shortSha(d.reverted.sha)}`;
|
|
64
|
+
if (d.reverted.kind === "main") {
|
|
65
|
+
if (d.reverted.failingTests[0] !== undefined)
|
|
66
|
+
header += ` (failing: ${d.reverted.failingTests[0]})`;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
header += ` (by operator)`; // vocabulary-ok: the deliberate-revert writer this header names
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const trail = d.events.map(formatEvent);
|
|
73
|
+
if (d.merge !== null) {
|
|
74
|
+
const first = d.events[0];
|
|
75
|
+
if (first !== undefined && d.merge.ts < first.ts)
|
|
76
|
+
trail.unshift(formatEvent(d.merge));
|
|
77
|
+
else
|
|
78
|
+
trail.push(formatEvent(d.merge));
|
|
79
|
+
}
|
|
80
|
+
return [header, ...trail].join("\n");
|
|
81
|
+
}
|
|
82
|
+
/** The transcript block of `--transcript`: the rendered lines of the agent runs that started in the
|
|
83
|
+
* run's window, or `found: []` plus the `note` the text view prints in their place. The
|
|
84
|
+
* dashboard's `/api/run?transcript` serves the same text. */
|
|
85
|
+
export function runTranscriptLines(root, role, runNumber, detail, opts = {}) {
|
|
86
|
+
if (detail.startTs === null) {
|
|
87
|
+
return { found: [], note: `(no transcript: run ${runNumber}'s start rotated out of the event log)` };
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
found: readRunTranscript(piLogPath(root, role), detail.startTs, detail.endTs, {
|
|
91
|
+
includePrompts: opts.includePrompts,
|
|
92
|
+
grep: opts.grep?.toLowerCase(),
|
|
93
|
+
}),
|
|
94
|
+
note: opts.grep !== undefined
|
|
95
|
+
? `(no transcript entries matching "${opts.grep}" in ${role} run ${runNumber})`
|
|
96
|
+
: `(no transcript for ${role} run ${runNumber}: rotated out of .tumwater/log/${role}.pi.jsonl, or the run started no agent)`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/** Command half of `tumwater history <role> <n>` and its `--last` / `--stat` / `--patch` /
|
|
2
|
+
* `--transcript` forms: the read-only command body that resolves which run to open, validates
|
|
3
|
+
* it, and prints the rendered trail (or the not-found line). The payload is collected by
|
|
4
|
+
* run-detail-data.ts (runCommitSha / runMergedSha beside it) and rendered by
|
|
5
|
+
* run-detail-render.ts, so the CLI, the TUI's History drill-down, and the GUI's /api/run serve
|
|
6
|
+
* the same text. Read-only over the event log, stdout only. */
|
|
7
|
+
import { fail, say, sayJson } from "../cli/cli-output.js";
|
|
8
|
+
import { parseCountFlag } from "../cli/cli-args.js";
|
|
9
|
+
import { knownRoleIdsCached } from "../config/config.js";
|
|
10
|
+
import { HISTORY_MAX_RUNS, HISTORY_RESULT_FLAG_NAMES, historyClass, readRunRows, runRowsOnModels } from "../history/history-data.js";
|
|
11
|
+
import { parseHistoryModelList, parseHistoryResultList } from "../history/history.js";
|
|
12
|
+
import { readRunDetail } from "./run-detail-data.js";
|
|
13
|
+
import { runDetailPatch, runDetailStat, renderRunDetail, runTranscriptLines } from "./run-detail-render.js";
|
|
14
|
+
import { unknownRoleMessage } from "../roles/roles.js";
|
|
15
|
+
import { canonicalRoleId } from "../roles/legacy-role-ids.js";
|
|
16
|
+
/** The run command's synopsis, word for word what cli.ts's dispatcher gate and cmdRunDetail's own
|
|
17
|
+
* arity and unknown-role guards fail with — one string so the three sites cannot drift apart
|
|
18
|
+
* when the command's shape changes. */
|
|
19
|
+
export const RUN_DETAIL_USAGE = "tumwater history <role> [<n>] [--last [--result <class>[,...]] [--model <selector>[,...]]] [--stat] [--patch] [--transcript [--prompt] [--grep <text>]] [--json]";
|
|
20
|
+
/** The shared not-found frame both wordings complete — what the scan looked for, the role,
|
|
21
|
+
* and that the scanned window (not the world) is what came up empty. Exactly two consumers,
|
|
22
|
+
* the not-found wordings below (the numbered form's exported message and the --last form's
|
|
23
|
+
* exported sibling), so the trailing hedge cannot drift between them. */
|
|
24
|
+
function runNotFoundPhrase(role, what) {
|
|
25
|
+
return `no ${what} for ${role} in the scanned window (the retained log may have rotated past it)`;
|
|
26
|
+
}
|
|
27
|
+
/** The not-found wording a missed run lookup owes its surface — `tumwater history <role> <n>`'s
|
|
28
|
+
* stdout line and the GUI /api/run endpoint's 404 JSON error, one template so the CLI's prose
|
|
29
|
+
* and the browser's error card cannot drift when the phrasing moves. Names the shape a user
|
|
30
|
+
* can act on: the run number, the role, and that the scanned window (not the world) is what
|
|
31
|
+
* came up empty. */
|
|
32
|
+
export function runNotFoundMessage(role, runNumber) {
|
|
33
|
+
return runNotFoundPhrase(role, `run #${runNumber}`);
|
|
34
|
+
}
|
|
35
|
+
/** The not-found wording for `tumwater history <role> --last`, sibling of runNotFoundMessage —
|
|
36
|
+
* the same surfaces but naming what the last-form scan actually asked for: the newest
|
|
37
|
+
* completed run, not a number the user supplied. Two consumers: cmdRunDetail's stdout line and
|
|
38
|
+
* the GUI /api/run?last=1 404 error, so the CLI and the browser cannot word the miss apart. */
|
|
39
|
+
export function runNotFoundLastMessage(role, classes = null, models = null) {
|
|
40
|
+
const flagNames = classes?.map((c) => HISTORY_RESULT_FLAG_NAMES[c]).join(",");
|
|
41
|
+
const what = `completed run${classes ? ` with result "${flagNames}"` : ""}${models ? ` on model "${models.join(",")}"` : ""}`;
|
|
42
|
+
return runNotFoundPhrase(role ?? "any role", what);
|
|
43
|
+
}
|
|
44
|
+
/** The newest completed run's row for a role (`null` for every role), or null when the scanned
|
|
45
|
+
* window holds none: the run `tumwater history <role> --last` opens, and bare `history --last`
|
|
46
|
+
* with no role. With neither filter it is readRunRows' newest row; with `classes` it is the
|
|
47
|
+
* newest row in those outcome classes, and with `models` the newest that started on one of those
|
|
48
|
+
* selectors. Also GET /api/run?last=1 with no role. */
|
|
49
|
+
export function newestRunRow(root, role, classes, models) {
|
|
50
|
+
if (!classes && !models)
|
|
51
|
+
return readRunRows(root, 1, role)[0] ?? null;
|
|
52
|
+
const rows = models ? runRowsOnModels(readRunRows(root, HISTORY_MAX_RUNS, role), models) : readRunRows(root, HISTORY_MAX_RUNS, role);
|
|
53
|
+
return rows.find((r) => !classes || classes.includes(historyClass(r.result))) ?? null;
|
|
54
|
+
}
|
|
55
|
+
/** The newest completed run's number for a role, or null when the scanned window holds none:
|
|
56
|
+
* the run `history <role> <n>` resolves for `--last`; newestRunRow's number. */
|
|
57
|
+
function newestRunNumber(root, role, classes, models) {
|
|
58
|
+
return newestRunRow(root, role, classes, models)?.run ?? null;
|
|
59
|
+
}
|
|
60
|
+
/** `tumwater history <role> [<n>] [--last [--result <class>[,...]]] [--stat] [--patch]
|
|
61
|
+
* [--transcript [--prompt] [--grep <text>]] [--json]`: print one completed
|
|
62
|
+
* run's full event trail. Read-only: stdout only, no state file created — a run the
|
|
63
|
+
* scan cannot find prints a not-found line and exits 0 (history's empty-output convention:
|
|
64
|
+
* an absent record is an answer, not a failure), and under --json it prints the JSON document
|
|
65
|
+
* `null` instead — the report --json precedent
|
|
66
|
+
* that every exit-0 output is parseable, never prose. `tumwater history <role> --last` resolves
|
|
67
|
+
* the role's newest completed run in the scanned window and prints its trail exactly as the
|
|
68
|
+
* numbered form would — the summary header and event rendering are shared, only the number's
|
|
69
|
+
* provenance differs (readRunRows' newest-first scan, not the user's argument). --last and
|
|
70
|
+
* a numeric <n> are rivals: exactly one positional under --last, exactly two without. With
|
|
71
|
+
* --last, `--result <class>[,...]` picks the newest run in the listed outcome classes instead
|
|
72
|
+
* (the listing's classes, scanning up to HISTORY_MAX_RUNS runs); it needs --last. `--model
|
|
73
|
+
* <selector>[,...]` with --last keeps only runs that started on a listed model (the listing's
|
|
74
|
+
* exact match) and composes with --result; it needs --last too. With --last and no role, the
|
|
75
|
+
* newest run of any role is opened (newestRunRow with a null role) and renders as its
|
|
76
|
+
* role's form. */
|
|
77
|
+
export async function cmdRunDetail(root, positionals, json, { last = false, stat = false, patch = false, transcript = false, prompt = false, grep = null, result = null, model = null } = {}) {
|
|
78
|
+
// Positional arity by form: cli.ts has already peeled the flags off and gated them (only
|
|
79
|
+
// --json and --last are admitted), so anything left over that does not fit the chosen form
|
|
80
|
+
// is a mistake. The role is checked first, so a typo is named whatever the count; then
|
|
81
|
+
// --last takes exactly one positional (<role>) and the numeric path exactly two (<role> <n>).
|
|
82
|
+
const bare = last && positionals.length === 0;
|
|
83
|
+
let role = canonicalRoleId(positionals[0] ?? "");
|
|
84
|
+
// The role is validated like every role-targeting command's --role (knownRoleIdsCached: the
|
|
85
|
+
// built-ins plus user-defined roles, read through the never-throwing cached loader so a
|
|
86
|
+
// transiently broken tumwater.json cannot take a read-only view down), with the shared
|
|
87
|
+
// unknown-role wording plus the usage the positional command owes.
|
|
88
|
+
if (positionals.length >= 1) {
|
|
89
|
+
const ids = knownRoleIdsCached(root);
|
|
90
|
+
if (!ids.includes(role))
|
|
91
|
+
fail(`${unknownRoleMessage(role, ids)} (usage: ${RUN_DETAIL_USAGE})`);
|
|
92
|
+
}
|
|
93
|
+
const arity = last ? 1 : 2;
|
|
94
|
+
if (positionals.length !== arity && !bare) {
|
|
95
|
+
if (positionals.length === 1) {
|
|
96
|
+
fail(`history ${role} needs a run number or --last — \`tumwater history --role ${role}\` lists its runs (usage: ${RUN_DETAIL_USAGE})`);
|
|
97
|
+
}
|
|
98
|
+
if (positionals.length === 2 && last) {
|
|
99
|
+
fail(`history --last takes no run number (got "${positionals[1]}") (usage: ${RUN_DETAIL_USAGE})`);
|
|
100
|
+
}
|
|
101
|
+
fail(`usage: ${RUN_DETAIL_USAGE}`);
|
|
102
|
+
}
|
|
103
|
+
if (prompt && !transcript)
|
|
104
|
+
fail(`history --prompt needs --transcript (usage: ${RUN_DETAIL_USAGE})`);
|
|
105
|
+
if (grep !== null && !transcript)
|
|
106
|
+
fail(`history --grep needs --transcript on a one-run trail (usage: ${RUN_DETAIL_USAGE})`);
|
|
107
|
+
if (result !== null && !last)
|
|
108
|
+
fail(`history --result needs --last on a one-run trail (usage: ${RUN_DETAIL_USAGE})`);
|
|
109
|
+
if (model !== null && !last)
|
|
110
|
+
fail(`history --model needs --last on a one-run trail (usage: ${RUN_DETAIL_USAGE})`);
|
|
111
|
+
const classes = result !== null ? parseHistoryResultList(result) : null;
|
|
112
|
+
const models = model !== null ? parseHistoryModelList(model) : null;
|
|
113
|
+
// The run number: either the user's positive integer (the numbered path, through the
|
|
114
|
+
// shared count parser — a non-positive or non-numeric n fails naming the shape; there is
|
|
115
|
+
// no cap — the number selects, it does not size the scan) or the newest completed run's
|
|
116
|
+
// number, read from readRunRows' newest-first scan for the role. An empty scan is not
|
|
117
|
+
// found by construction, so the not-found path below takes it with the last-form wording.
|
|
118
|
+
let runNumber;
|
|
119
|
+
if (bare) {
|
|
120
|
+
// `history --last` with no role: the newest completed run of any role; its role is the row's.
|
|
121
|
+
const row = newestRunRow(root, null, classes, models);
|
|
122
|
+
if (row !== null)
|
|
123
|
+
role = row.role;
|
|
124
|
+
runNumber = row?.run ?? null;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
runNumber = last ? newestRunNumber(root, role, classes, models) : parseCountFlag("history <n>", positionals[1]);
|
|
128
|
+
}
|
|
129
|
+
const detail = runNumber === null ? null : readRunDetail(root, role, runNumber);
|
|
130
|
+
if (runNumber === null) {
|
|
131
|
+
// The --last scan came up empty: not found by construction, the last-form wording.
|
|
132
|
+
if (json)
|
|
133
|
+
sayJson(null);
|
|
134
|
+
else
|
|
135
|
+
say(runNotFoundLastMessage(bare ? null : role, classes, models));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (detail === null) {
|
|
139
|
+
// Not found stays exit 0 in both renderings, but --json still owes a parseable document in
|
|
140
|
+
// every exit-0 case (the report --json precedent, the invariant this command's own doc
|
|
141
|
+
// comment claims): `null` is the JSON answer for a single-record lookup that missed — the
|
|
142
|
+
// prose line would end a jq pipe mid-team-incident.
|
|
143
|
+
if (json)
|
|
144
|
+
sayJson(null);
|
|
145
|
+
else
|
|
146
|
+
say(runNotFoundMessage(role, runNumber));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// --json swaps the renderer for the collector's own payload, exactly as history --json: a
|
|
150
|
+
// JSON document in every exit-0 case, never prose.
|
|
151
|
+
const footer = {};
|
|
152
|
+
if (stat)
|
|
153
|
+
footer.stat = await runDetailStat(root, detail);
|
|
154
|
+
if (patch)
|
|
155
|
+
footer.patch = await runDetailPatch(root, detail);
|
|
156
|
+
const transcriptLines = transcript
|
|
157
|
+
? runTranscriptLines(root, role, runNumber, detail, { includePrompts: prompt, grep: grep ?? undefined })
|
|
158
|
+
: null;
|
|
159
|
+
if (json) {
|
|
160
|
+
sayJson({ ...detail, ...footer, ...(transcriptLines === null ? {} : { transcript: transcriptLines.found }) });
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const transcriptText = transcriptLines === null
|
|
164
|
+
? []
|
|
165
|
+
: [transcriptLines.found.length > 0 ? transcriptLines.found.join("\n") : transcriptLines.note];
|
|
166
|
+
say([renderRunDetail(detail), ...Object.values(footer), ...transcriptText].join("\n\n"));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { hasOutstandingWork,
|
|
1
|
+
import { hasOutstandingWork, saveRunnerStateBestEffort } from "../runner/runner-state.js";
|
|
2
2
|
import { branchHead } from "../git/git.js";
|
|
3
3
|
import { logEventBestEffort, usageFragment, warnEventBestEffort } from "../events/events.js";
|
|
4
|
-
import {
|
|
5
|
-
import { ERROR_STREAK_WARN, QUIET_KILL_RESUME_LIMIT,
|
|
6
|
-
import {
|
|
4
|
+
import { observeAccount } from "../agent/subscription-account.js";
|
|
5
|
+
import { ERROR_STREAK_WARN, QUIET_KILL_RESUME_LIMIT, applyRunOutcome } from "./run-apply.js";
|
|
6
|
+
import { restoreMidRunWake } from "../scheduling/backoff.js";
|
|
7
7
|
import { consecutiveFailuresWarning } from "../text/phrases.js";
|
|
8
|
-
/** The
|
|
8
|
+
/** The run results that end an instance's claim: the run found nothing to do, refused the
|
|
9
9
|
* entry, was aborted by the user, or was skipped — none of them leaves work in flight. */
|
|
10
10
|
const CLAIM_RELEASING = new Set([
|
|
11
11
|
"no_change",
|
|
@@ -13,43 +13,43 @@ const CLAIM_RELEASING = new Set([
|
|
|
13
13
|
"user_aborted",
|
|
14
14
|
"skipped",
|
|
15
15
|
]);
|
|
16
|
-
/** Fold a finished
|
|
17
|
-
* happens AFTER the pi run, extracted from
|
|
18
|
-
* only
|
|
16
|
+
/** Fold a finished run's outcome into the role's state and the event feed — everything that
|
|
17
|
+
* happens AFTER the pi run, extracted from RoleRunner.run (src/runner/runner.ts) so the runner
|
|
18
|
+
* keeps only run lifecycle (worktree, sessions, abort) and this module owns the per-result
|
|
19
19
|
* bookkeeping policy. Order matters here and is pinned by behavior, not by a test seam: the
|
|
20
|
-
* outcome's folds are applied to state before
|
|
20
|
+
* outcome's folds are applied to state before applyRunOutcome schedules the next run, the
|
|
21
21
|
* main-head read happens while `running` is still true (a poll that runs in that window must not
|
|
22
|
-
* double-wake the
|
|
22
|
+
* double-wake the role), and the warnings read the streaks exactly once per episode, after the
|
|
23
23
|
* schedule. */
|
|
24
|
-
export async function
|
|
25
|
-
const { root, role, mainBranch, config, state: s, usage,
|
|
24
|
+
export async function finalizeRun(deps) {
|
|
25
|
+
const { root, role, mainBranch, config, state: s, usage, runNumber } = deps;
|
|
26
26
|
let { outcome } = deps;
|
|
27
|
-
// A re-queued leftover's merge failure is not the
|
|
28
|
-
// outcome separately:
|
|
29
|
-
// below names it — even though
|
|
27
|
+
// A re-queued leftover's merge failure is not the run's own result, so it rides the
|
|
28
|
+
// outcome separately: applyRunOutcome feeds it into the error streak, and the warning
|
|
29
|
+
// below names it — even though runOnce cleared `lastError` so it never latched onto
|
|
30
30
|
// `run_end` (BUGS.md 2026-09-21).
|
|
31
31
|
if (deps.recoveryFailure !== undefined)
|
|
32
32
|
outcome.recoveryFailure = deps.recoveryFailure;
|
|
33
|
-
// A main_red
|
|
33
|
+
// A main_red run's cause is written for that result (src/baseline/main-red.ts), not a leftover
|
|
34
34
|
// merge failure: fold it onto the state here so `run_end` logs it like every other
|
|
35
35
|
// failure's and the digest can itemize what the Outcome table's main_red cells mean
|
|
36
|
-
// (BUGS.md 2026-09-28).
|
|
36
|
+
// (BUGS.md 2026-09-28). runOnce cleared `lastError` at its start, so nothing foreign
|
|
37
37
|
// can precede this.
|
|
38
38
|
if (outcome.error !== undefined)
|
|
39
39
|
s.lastError = outcome.error;
|
|
40
|
-
// Read main's current head while this
|
|
41
|
-
//
|
|
42
|
-
// head update would see a stale lastMainHead and wake the
|
|
43
|
-
// that triggered this
|
|
40
|
+
// Read main's current head while this run is still reserved (running=true): the
|
|
41
|
+
// applyRunOutcome below clears running, and a poll that runs between that clear and a later
|
|
42
|
+
// head update would see a stale lastMainHead and wake the role again on the very move
|
|
43
|
+
// that triggered this run — a duplicate wake event plus an extra run for one world change.
|
|
44
44
|
// branchHead reads the ref files first (microsecond-scale, like the orchestrator's per-poll
|
|
45
45
|
// watch) and spawns `git rev-parse` only when they cannot resolve it; a null result keeps
|
|
46
46
|
// the previous value rather than waking on "main moved" to nowhere.
|
|
47
47
|
s.lastMainHead = (await branchHead(root, mainBranch)) ?? s.lastMainHead;
|
|
48
|
-
// Record the outcome on state and schedule the next run (see src/
|
|
48
|
+
// Record the outcome on state and schedule the next run (see src/run/run-apply.ts for the
|
|
49
49
|
// per-result policy: prompt retry, backoff, bounded cut-off resumes).
|
|
50
|
-
|
|
51
|
-
// A
|
|
52
|
-
// (plans/parallel-work-instances.md "Claims", part 4/7): the next
|
|
50
|
+
applyRunOutcome(s, config, role, outcome);
|
|
51
|
+
// A run that ends without producing or keeping work releases this instance's backlog claim
|
|
52
|
+
// (plans/parallel-work-instances.md "Claims", part 4/7): the next run may take a different
|
|
53
53
|
// entry. Every other result — queued, changed, rejected with a revision, an error, an abort
|
|
54
54
|
// that resumes — keeps the claim so the same instance retries it; an outstanding revision or
|
|
55
55
|
// a pending resume also holds it, because the entry is still this instance's task.
|
|
@@ -60,79 +60,91 @@ export async function finalizeTick(deps) {
|
|
|
60
60
|
s.claim = undefined;
|
|
61
61
|
logEventBestEffort(root, { source: role, type: "claim", action: "released", title, reason: outcome.result });
|
|
62
62
|
}
|
|
63
|
-
// A wake consumed while this
|
|
63
|
+
// A wake consumed while this run ran stamped the shared state in place, but the outcome
|
|
64
64
|
// schedule above overwrites it (lastRunEndedAt past wokenAt, nextRunAt a fresh gap or
|
|
65
65
|
// backoff out), so a plain `tumwater wake --role` with an empty queue would silently wait
|
|
66
66
|
// out the whole interval (BUGS.md 2026-09-25). Re-apply it here — exactly like a wake
|
|
67
|
-
// arriving one poll after the
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
67
|
+
// arriving one poll after the run ended.
|
|
68
|
+
restoreMidRunWake(s);
|
|
69
|
+
// A failed save warns and the run still ends: the in-memory state already holds the fold and
|
|
70
|
+
// the runner's next save writes it, while a throw here would skip run_end and the launch
|
|
71
|
+
// task's breaker fold.
|
|
72
|
+
saveRunnerStateBestEffort(root, s);
|
|
73
|
+
// One warning per error episode (BUGS.md 2026-09-15: every role failing identically
|
|
74
|
+
// looked like a quiet team). applyRunOutcome increments the streak on error-class
|
|
75
|
+
// outcomes, preserves it across a plain `queued` run (the run's merge resolves it),
|
|
73
76
|
// and resets it on any other completed result — so warn only when THIS outcome incremented
|
|
74
77
|
// the streak up to the threshold: a preserved streak already sitting at the bar must not
|
|
75
|
-
// re-warn on the queued
|
|
78
|
+
// re-warn on the queued run's end. A rejection's crossing warns from the merge side
|
|
76
79
|
// instead (writeMergeOutcome, src/merge/merge-slot.ts), which is where rejections resolve.
|
|
77
80
|
if ((outcome.result === "error" || outcome.recoveryFailure !== undefined) &&
|
|
78
81
|
(s.consecutiveErrors ?? 0) === ERROR_STREAK_WARN) {
|
|
79
82
|
warnEventBestEffort(root, role, consecutiveFailuresWarning(s.consecutiveErrors ?? 0, s.lastError ?? outcome.recoveryFailure ?? "unknown error"));
|
|
80
83
|
}
|
|
81
|
-
// One warning per quiet-kill episode (BUGS.md 2026-09-18): a
|
|
82
|
-
//
|
|
84
|
+
// One warning per quiet-kill episode (BUGS.md 2026-09-18): a role burning an hour per
|
|
85
|
+
// run with no output must not look like a sleeping role. applyRunOutcome grows the
|
|
83
86
|
// streak on each kill and resets it on any other result, so the crossing fires once;
|
|
84
87
|
// the give-up (fresh session + backoff) follows on the next kill.
|
|
85
88
|
if ((s.quietKillStreak ?? 0) === QUIET_KILL_RESUME_LIMIT) {
|
|
86
89
|
warnEventBestEffort(root, role, `${s.quietKillStreak} consecutive stalls (no progress): ${s.lastError ?? "unknown hang"}`);
|
|
87
90
|
}
|
|
88
|
-
// Per-
|
|
89
|
-
// spend — this
|
|
90
|
-
// no_change/error/rejected/refused
|
|
91
|
-
// snapshots. Both fields are per-
|
|
92
|
-
// pi run of the
|
|
93
|
-
// are omitted when zero so skipped
|
|
94
|
-
// The
|
|
95
|
-
// failure digest prices a
|
|
96
|
-
//
|
|
97
|
-
// Date.now() millisecond it started (an instant early-abort error
|
|
91
|
+
// Per-run usage: the event feed is where operators see
|
|
92
|
+
// spend — this run's tokens and cost ride on run_end so a budget pause or a money-burning
|
|
93
|
+
// no_change/error/rejected/refused run shows what it spent without diffing status-table
|
|
94
|
+
// snapshots. Both fields are per-run windows (reset at run start, folded in over every
|
|
95
|
+
// pi run of the run); they ride on HarnessEvent's index signature like other payloads and
|
|
96
|
+
// are omitted when zero so skipped runs render byte-identical to a pre-feature line.
|
|
97
|
+
// The run's wall-clock span rides on run_end too: the
|
|
98
|
+
// failure digest prices a run by its own durationMs first (src/failure/failure-data.ts
|
|
99
|
+
// runDurationMs), so the field is ALWAYS present — a run that ends within the same
|
|
100
|
+
// Date.now() millisecond it started (an instant early-abort error run) or crosses a
|
|
98
101
|
// backward clock step still attests its 0 ms, where the old omit-when-zero convention
|
|
99
102
|
// dropped the field and broke the "every run_end carries a span" contract (BUGS.md
|
|
100
103
|
// 2026-09-29). Unlike the usage fields, run_end's renderer never prints the span, so an
|
|
101
104
|
// always-present 0 changes no rendered line.
|
|
102
|
-
const
|
|
103
|
-
// The account state (plans/agent-backends.md part 6a): fold this
|
|
104
|
-
// rate-window readings into .tumwater/state
|
|
105
|
-
// throttle or display the subscription window. Best-effort — the
|
|
106
|
-
// unwritable state dir must not fail a
|
|
105
|
+
const runDurationMs = Math.max(0, Date.now() - deps.runStartedAt);
|
|
106
|
+
// The account state (plans/agent-backends.md part 6a): fold this run's billing answer and
|
|
107
|
+
// rate-window readings into the reporting backend's .tumwater/state/<backend>-account.json
|
|
108
|
+
// for the sibling parts that throttle or display the subscription window. Best-effort — the
|
|
109
|
+
// file is a cache, and an unwritable state dir must not fail a run whose real work is
|
|
110
|
+
// already done. A pi run never reports either, so in practice claude owns the file until a
|
|
111
|
+
// second subscription backend lands (plans/codex-backend.md).
|
|
107
112
|
if (usage.billing !== undefined || usage.rateWindows !== undefined) {
|
|
108
113
|
try {
|
|
109
|
-
|
|
114
|
+
observeAccount(root, usage.agent ?? "pi", {
|
|
115
|
+
billing: usage.billing,
|
|
116
|
+
rateWindows: usage.rateWindows,
|
|
117
|
+
});
|
|
110
118
|
}
|
|
111
119
|
catch {
|
|
112
|
-
// The next reporting run rewrites it; nothing this
|
|
120
|
+
// The next reporting run rewrites it; nothing this run depends on is lost.
|
|
113
121
|
}
|
|
114
122
|
}
|
|
115
|
-
// The per-provider split of this
|
|
123
|
+
// The per-provider split of this run's usage (plans: per-provider spend in the day
|
|
116
124
|
// report): a Record<provider, {costUsd, notionalCostUsd}> for the report's `Cost by
|
|
117
|
-
// provider` line. Omitted when empty like the prompt-token fields, so a
|
|
125
|
+
// provider` line. Omitted when empty like the prompt-token fields, so a run whose runs
|
|
118
126
|
// carried no provider renders byte-identically to today.
|
|
119
127
|
const providerCost = usage.serializeProviderCost();
|
|
120
128
|
logEventBestEffort(root, {
|
|
121
129
|
source: role,
|
|
122
130
|
type: "run_end",
|
|
123
|
-
run:
|
|
131
|
+
run: runNumber,
|
|
124
132
|
result: outcome.result,
|
|
125
133
|
summary: outcome.summary,
|
|
126
134
|
error: s.lastError,
|
|
127
|
-
durationMs:
|
|
135
|
+
durationMs: runDurationMs,
|
|
128
136
|
...usageFragment(s.generatedTokens, usage.costUsd, usage.notionalCostUsd),
|
|
129
137
|
...(Object.keys(providerCost).length > 0 ? { providerCost } : {}),
|
|
130
|
-
// The billing answer the
|
|
138
|
+
// The billing answer the run's runs reported, so the report can tell notional spend from
|
|
131
139
|
// real spend without re-deriving it from the file (plans/agent-backends.md part 6a).
|
|
132
140
|
...(usage.billing !== undefined ? { billing: usage.billing } : {}),
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
141
|
+
// The backend that reported the billing/rate windows (plans/codex-backend.md part 3d/9):
|
|
142
|
+
// carried under the same condition the account file is written (observeAccount above), so
|
|
143
|
+
// the report folds a run's notional spend to the subscription backend whose runs made it.
|
|
144
|
+
...(usage.billing !== undefined || usage.rateWindows !== undefined ? { agent: usage.agent } : {}),
|
|
145
|
+
// Per-run prompt-token telemetry: how many prompt
|
|
146
|
+
// tokens the run sent, how many were cache reads, and how many went out before its first
|
|
147
|
+
// edit. Omitted when zero like usageFragment's fields, so a run that sent no prompt — and
|
|
136
148
|
// every run_end written by an older build — renders byte-identically.
|
|
137
149
|
...(usage.promptTokens > 0 ? { promptTokens: usage.promptTokens } : {}),
|
|
138
150
|
...(usage.cacheReadTokens > 0 ? { cacheReadTokens: usage.cacheReadTokens } : {}),
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
/** What a finished tick or merge is (the TickResult/TickOutcome vocabulary), shared by the
|
|
1
|
+
/** What a finished run or merge is (the RunResult/RunOutcome vocabulary), shared by the
|
|
3
2
|
* runner that produces it, the dashboards that render it, and the state machine that applies
|
|
4
3
|
* it. The application policy — which outcomes count which streaks, when a session resumes,
|
|
5
|
-
* how the next run is scheduled — lives next door in
|
|
6
|
-
* drives in backoff.ts. This module holds only the shared types. Split out of
|
|
7
|
-
* the persisted state file's load/save — and later of the types.ts grab-bag, as
|
|
4
|
+
* how the next run is scheduled — lives next door in run-apply.ts; the clock arithmetic it
|
|
5
|
+
* drives in backoff.ts. This module holds only the shared types. Split out of runner-state.ts —
|
|
6
|
+
* the persisted state file's load/save — and later of the types.ts grab-bag, as RunnerState and
|
|
8
7
|
* HarnessEvent were before it. */
|
|
9
8
|
export {};
|
|
10
9
|
/** Record the review gate's verdict on a HEAD into `lastReview` — the field the author's next
|
|
11
|
-
*
|
|
10
|
+
* run prompt injects (run-prompt.ts's buildRejectedReviewNote) and the same-head strike
|
|
12
11
|
* counter reads (review.ts's unreviewFailures). Mutates `s` in place; the timestamp is
|
|
13
12
|
* stamped here so every call site shares one Date.now() read and the shape stays in one place
|
|
14
|
-
* (
|
|
13
|
+
* (runner-state.ts's lastReview). */
|