tumwater 0.4.0 → 0.4.2
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 +4 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +5 -2
- package/dist/src/agent/backend-traits.js +21 -0
- package/dist/src/agent/claude/claude-backend.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +181 -182
- package/dist/src/agent/claude/{write-guard-hook.js → claude-write-guard-hook.js} +1 -1
- package/dist/src/agent/claude/claude-write-guard.js +5 -4
- package/dist/src/agent/codex/codex-backend.js +1 -1
- package/dist/src/agent/codex/codex-rollout.js +48 -45
- package/dist/src/agent/codex/codex-stream.js +269 -215
- package/dist/src/agent/codex/{write-guard-hook.js → codex-write-guard-hook.js} +2 -1
- package/dist/src/agent/codex/codex-write-guard.js +3 -3
- package/dist/src/agent/image-input.js +18 -87
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +2 -2
- package/dist/src/agent/opencode/opencode-config.js +2 -2
- package/dist/src/agent/opencode/opencode-stream.js +101 -92
- package/dist/src/agent/run-agent.js +76 -382
- package/dist/src/agent/run-settle.js +25 -15
- package/dist/src/agent/run-spawn.js +226 -0
- package/dist/src/agent/run-stream.js +185 -0
- package/dist/src/agent/session-ledger.js +16 -0
- package/dist/src/agent/write-guard.js +5 -3
- package/dist/src/backlog/backlog-archive.js +86 -170
- package/dist/src/backlog/backlog-cli-view.js +89 -70
- package/dist/src/backlog/backlog-eligibility.js +51 -59
- package/dist/src/backlog/backlog-file-bugs.js +133 -0
- package/dist/src/backlog/backlog-index-data.js +159 -0
- package/dist/src/backlog/backlog-index.js +7 -5
- package/dist/src/backlog/backlog-md.js +39 -28
- package/dist/src/backlog/backlog-release.js +21 -36
- package/dist/src/backlog/backlog-render.js +1 -1
- package/dist/src/backlog/backlog-structure.js +14 -145
- package/dist/src/backlog/backlog-write.js +31 -18
- package/dist/src/backlog/backlog.js +14 -4
- package/dist/src/backlog/question-answer.js +33 -15
- package/dist/src/baseline/main-baseline.js +71 -26
- package/dist/src/baseline/main-guardrails.js +76 -15
- package/dist/src/baseline/main-red.js +45 -94
- package/dist/src/baseline/main-watch.js +9 -3
- package/dist/src/brief/brief.js +40 -0
- package/dist/src/budget/budget-fallback-view.js +50 -8
- package/dist/src/budget/budget-wiring.js +15 -11
- package/dist/src/budget/budget.js +3 -2
- package/dist/src/build/build-check-events.js +4 -2
- package/dist/src/build/build-check-report.js +2 -1
- package/dist/src/build/build-check-scoped.js +70 -41
- package/dist/src/change/change-data.js +4 -2
- package/dist/src/cli/cli-args.js +9 -0
- package/dist/src/cli/cli-command-args.js +199 -98
- package/dist/src/cli/cli-flag-specs.js +6 -4
- package/dist/src/cli/cli-marker-commands.js +2 -2
- package/dist/src/cli/cli-query-commands.js +41 -6
- package/dist/src/cli/cli-review-commands.js +2 -1
- package/dist/src/cli/cli-role-command.js +46 -92
- package/dist/src/cli/cli-start.js +123 -47
- package/dist/src/cli/follow-view.js +3 -1
- package/dist/src/cli/help.js +19 -12
- package/dist/src/cli/log-commands.js +128 -107
- package/dist/src/cli/question-commands.js +82 -63
- package/dist/src/cli.js +396 -343
- package/dist/src/collections.js +46 -9
- package/dist/src/config/config-fallbacks.js +0 -4
- package/dist/src/config/config-live.js +179 -147
- package/dist/src/config/config-request.js +176 -51
- package/dist/src/config/config-validation-roles.js +271 -0
- package/dist/src/config/config-validation.js +10 -205
- package/dist/src/config/config-views.js +2 -7
- package/dist/src/config/config-write.js +205 -175
- package/dist/src/config/config.js +2 -1
- package/dist/src/doctor/doctor-backlog-bugs.js +24 -122
- package/dist/src/doctor/doctor-backlog-plans.js +24 -25
- package/dist/src/doctor/doctor-backlog.js +34 -42
- package/dist/src/doctor/doctor-check-names.js +4 -2
- package/dist/src/doctor/doctor-checks.js +16 -4
- package/dist/src/doctor/doctor-claude-checks.js +2 -2
- package/dist/src/doctor/doctor-codex-checks.js +9 -20
- package/dist/src/doctor/doctor-contention-checks.js +52 -19
- package/dist/src/doctor/doctor-holds.js +22 -20
- package/dist/src/doctor/doctor-model-checks.js +8 -184
- package/dist/src/doctor/doctor-notify.js +2 -5
- package/dist/src/doctor/doctor-opencode-checks.js +90 -41
- package/dist/src/doctor/doctor-principles.js +26 -0
- package/dist/src/doctor/doctor-project-checks.js +69 -19
- package/dist/src/doctor/doctor-role-notes.js +33 -0
- package/dist/src/doctor/doctor.js +13 -6
- package/dist/src/doctor/probe.js +1 -1
- package/dist/src/events/event-format.js +307 -162
- package/dist/src/events/event-labels.js +0 -1
- package/dist/src/events/event-legacy.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +15 -2
- package/dist/src/events/notify.js +1 -1
- package/dist/src/failure/failure-cluster.js +4 -3
- package/dist/src/failure/failure-data.js +126 -94
- package/dist/src/failure/failure-render.js +58 -17
- package/dist/src/failure/failure-state-change.js +308 -27
- package/dist/src/failure/time-spend.js +139 -106
- package/dist/src/files/files.js +7 -0
- package/dist/src/files/json-object.js +15 -2
- package/dist/src/gates/budget-gates.js +90 -68
- package/dist/src/gates/gate-polls.js +92 -61
- package/dist/src/gates/role-cap-gates.js +1 -1
- package/dist/src/gates/startup-gate.js +28 -28
- package/dist/src/gates/streak-gate.js +1 -1
- package/dist/src/git/git.js +1 -1
- package/dist/src/git/slot-salvage.js +9 -9
- package/dist/src/git/slots-state.js +12 -3
- package/dist/src/git/worktree-pool-startup.js +127 -0
- package/dist/src/git/worktree-pool.js +1 -120
- package/dist/src/gui/gui-admin-endpoints.js +3 -4
- package/dist/src/gui/gui-args.js +1 -1
- package/dist/src/gui/gui-backlog-commands.js +20 -11
- package/dist/src/gui/gui-backlog-endpoints.js +2 -8
- package/dist/src/gui/gui-config-commands.js +51 -76
- package/dist/src/gui/gui-endpoint-commands.js +4 -2
- package/dist/src/gui/gui-endpoint-common.js +4 -3
- package/dist/src/gui/gui-history-endpoints.js +36 -27
- package/dist/src/gui/gui-operator-commands.js +43 -17
- package/dist/src/gui/gui-prompt-commands.js +27 -21
- package/dist/src/gui/gui-quarantine-commands.js +30 -0
- package/dist/src/gui/gui-report-endpoints.js +13 -9
- package/dist/src/gui/gui-server.js +5 -3
- package/dist/src/gui/gui-team-commands.js +1 -1
- package/dist/src/history/history-data.js +7 -1
- package/dist/src/history/history-render.js +15 -10
- package/dist/src/history/history.js +103 -68
- package/dist/src/inbox/inbox-submit.js +1 -5
- package/dist/src/inbox/pending-prompt.js +3 -2
- package/dist/src/inbox/prompt-commands.js +194 -161
- package/dist/src/init/init.js +104 -25
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +1 -1
- package/dist/src/merge/merge-git.js +5 -5
- package/dist/src/merge/{merge-post-review.js → merge-post-review-drain.js} +1 -1
- package/dist/src/merge/merge-queue.js +2 -1
- package/dist/src/merge/merge-to-main.js +1 -1
- package/dist/src/merge/merge-verify.js +3 -2
- package/dist/src/operator/operator-commands.js +84 -52
- package/dist/src/operator/operator-intent.js +26 -3
- package/dist/src/operator/retire.js +2 -1
- package/dist/src/operator/revert.js +18 -12
- package/dist/src/orchestrator/orchestrator-poll.js +2 -2
- package/dist/src/orchestrator/orchestrator.js +1 -1
- package/dist/src/orchestrator/retention.js +6 -5
- package/dist/src/paths.js +10 -10
- package/dist/src/pi/pi-bin.js +2 -2
- package/dist/src/pi/pi-models.js +9 -0
- package/dist/src/pi/pi-stream.js +63 -41
- package/dist/src/pi/pi-watchdogs.js +84 -49
- package/dist/src/pi-extension/context-shake.js +55 -31
- package/dist/src/pi-extension/full-output.js +2 -3
- package/dist/src/process/process.js +24 -1
- package/dist/src/process/run-marker.js +2 -2
- package/dist/src/process/supervisor.js +24 -5
- package/dist/src/prompt/gate-notes.js +41 -16
- package/dist/src/prompt/notes-backlog.js +22 -29
- package/dist/src/prompt/principles.js +11 -0
- package/dist/src/prompt/prompt-followup.js +4 -12
- package/dist/src/prompt/prompt.js +15 -12
- package/dist/src/redeploy/redeploy-probes.js +11 -1
- package/dist/src/redeploy/redeployer.js +54 -7
- package/dist/src/redeploy/self-reload.js +7 -2
- package/dist/src/report/report-data.js +224 -320
- package/dist/src/report/report-fold.js +23 -231
- package/dist/src/report/report-main-red.js +70 -3
- package/dist/src/report/report-render.js +181 -48
- package/dist/src/report/report-since-data.js +218 -0
- package/dist/src/report/report-usage-fold.js +288 -0
- package/dist/src/report/report.js +2 -1
- package/dist/src/review/known-flakes.js +22 -3
- package/dist/src/review/post-merge-review-accept.js +3 -2
- package/dist/src/review/post-merge-review.js +16 -14
- package/dist/src/review/quarantine-render.js +29 -11
- package/dist/src/review/review-precheck.js +2 -1
- package/dist/src/roles/role-catalog.js +30 -29
- package/dist/src/roles/role-guidance.js +26 -43
- package/dist/src/roles/role-render.js +19 -0
- package/dist/src/roles/role-view.js +19 -5
- package/dist/src/roles/roles.js +2 -22
- package/dist/src/run/ci-parity.js +5 -6
- package/dist/src/run/feedback-filings.js +10 -1
- package/dist/src/run/qa-coverage.js +4 -4
- package/dist/src/run/run-apply.js +14 -2
- package/dist/src/run/run-detail-data.js +50 -10
- package/dist/src/run/run-detail-render.js +32 -1
- package/dist/src/run/run-detail.js +44 -22
- package/dist/src/run/run-dismiss.js +43 -0
- package/dist/src/run/run-finalize.js +75 -46
- package/dist/src/run/run-prompt.js +271 -187
- package/dist/src/run/run-resume.js +32 -31
- package/dist/src/run/run-stage.js +116 -64
- package/dist/src/run/run-timing.js +18 -4
- package/dist/src/run/run-verdict.js +119 -83
- package/dist/src/run/stage-backlog-checks.js +4 -31
- package/dist/src/run/stage-check.js +4 -12
- package/dist/src/run/stage-checks-shared.js +15 -0
- package/dist/src/run/stage-name-checks.js +3 -13
- package/dist/src/run/stage-path-checks.js +26 -29
- package/dist/src/runner/leftover.js +13 -4
- package/dist/src/runner/revision.js +2 -2
- package/dist/src/runner/runner-fallback.js +1 -1
- package/dist/src/runner/runner-outcome.js +0 -5
- package/dist/src/runner/runner-pi.js +72 -49
- package/dist/src/runner/runner-run.js +19 -102
- package/dist/src/runner/runner-state-load.js +0 -10
- package/dist/src/runner/runner.js +1 -32
- package/dist/src/scheduling/claims.js +4 -11
- package/dist/src/status/status-data.js +140 -91
- package/dist/src/status/status-polls.js +41 -5
- package/dist/src/status/status-role-notes.js +53 -0
- package/dist/src/status/status-roles.js +10 -37
- package/dist/src/text/datetime.js +8 -1
- package/dist/src/text/format.js +13 -1
- package/dist/src/text/phrases.js +16 -2
- package/dist/src/text/text.js +12 -0
- package/dist/src/ui/alert-feeds.js +115 -0
- package/dist/src/ui/badges.js +3 -4
- package/dist/src/ui/gui/gui-client-boot.js +78 -57
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -3
- package/dist/src/ui/gui/gui-client-drawer.js +18 -2
- package/dist/src/ui/gui/gui-client-feed.js +3 -8
- package/dist/src/ui/gui/gui-client-history.js +16 -16
- package/dist/src/ui/gui/gui-client-model.js +9 -9
- package/dist/src/ui/gui/gui-client-operator.js +6 -10
- package/dist/src/ui/gui/gui-client-pending.js +4 -4
- package/dist/src/ui/gui/gui-client-report-chart.js +111 -10
- package/dist/src/ui/gui/gui-client-report.js +70 -11
- package/dist/src/ui/gui/gui-client-roles.js +52 -12
- package/dist/src/ui/gui/gui-client-settings.js +31 -26
- package/dist/src/ui/gui/gui-client-team.js +28 -2
- package/dist/src/ui/gui/gui-client.js +33 -4
- package/dist/src/ui/gui/gui-icons.js +7 -1
- package/dist/src/ui/progress-data.js +2 -2
- package/dist/src/ui/run-progress-model.js +2 -2
- package/dist/src/ui/status-model.js +26 -16
- package/dist/src/ui/status-payload.js +149 -326
- package/dist/src/ui/status-render.js +131 -80
- package/dist/src/ui/team-alert-backlog.js +236 -0
- package/dist/src/ui/team-alert-shared.js +92 -0
- package/dist/src/ui/team-alert-status.js +326 -0
- package/dist/src/ui/team-alert-tail.js +238 -0
- package/dist/src/ui/team-alerts.js +16 -984
- package/dist/src/ui/transcript-tail.js +137 -94
- package/dist/src/ui/tui/tui-backlog.js +1 -1
- package/dist/src/ui/tui/tui-editor-answer.js +54 -44
- package/dist/src/ui/tui/tui-editor-attach.js +74 -63
- package/dist/src/ui/tui/tui-editor-backends.js +62 -56
- package/dist/src/ui/tui/tui-editor-brief.js +64 -54
- package/dist/src/ui/tui/tui-editor-budget.js +106 -89
- package/dist/src/ui/tui/tui-editor-config.js +2 -4
- package/dist/src/ui/tui/tui-editor-ctx.js +12 -1
- package/dist/src/ui/tui/tui-editor-entry.js +171 -153
- package/dist/src/ui/tui/tui-editor-instances.js +2 -1
- package/dist/src/ui/tui/tui-editor-instructions.js +50 -107
- package/dist/src/ui/tui/tui-editor-interval.js +39 -112
- package/dist/src/ui/tui/tui-editor-notes.js +28 -84
- package/dist/src/ui/tui/tui-editor-pause.js +210 -183
- package/dist/src/ui/tui/tui-editor-prompt.js +46 -35
- package/dist/src/ui/tui/tui-editor-queued.js +74 -64
- package/dist/src/ui/tui/tui-editor-quiet.js +122 -108
- package/dist/src/ui/tui/tui-editor-release.js +85 -0
- package/dist/src/ui/tui/tui-editor-retire.js +57 -46
- package/dist/src/ui/tui/tui-editor-role-enabled.js +90 -80
- package/dist/src/ui/tui/tui-editor-role-file.js +87 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +50 -42
- package/dist/src/ui/tui/tui-editor-search.js +70 -58
- package/dist/src/ui/tui/tui-editor-session.js +3 -0
- package/dist/src/ui/tui/tui-editors.js +4 -1
- package/dist/src/ui/tui/tui-frame.js +7 -3
- package/dist/src/ui/tui/tui-history.js +12 -10
- package/dist/src/ui/tui/tui-keys.js +6 -1
- package/dist/src/ui/tui/tui-pane-activity.js +62 -50
- package/dist/src/ui/tui/tui-pane-backlog.js +4 -4
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +45 -26
- package/dist/src/ui/tui/tui-pane-history.js +296 -264
- package/dist/src/ui/tui/tui-pane-report.js +74 -57
- package/dist/src/ui/tui/tui-pane-session.js +6 -2
- package/dist/src/ui/tui/tui-pane-transcript.js +158 -131
- package/dist/src/ui/tui/tui-pane.js +201 -174
- package/dist/src/ui/tui/tui-panes.js +211 -148
- package/dist/src/ui/tui/tui.js +4 -4
- package/dist/src/verdict/fix-claim.js +18 -6
- package/dist/src/verdict/refusal.js +32 -7
- package/package.json +1 -1
- package/dist/src/doctor/image-probe.js +0 -257
- /package/dist/src/gui/{http-body.js → gui-http-body.js} +0 -0
- /package/dist/src/merge/{backlog-conflicts.js → merge-backlog-conflicts.js} +0 -0
|
@@ -57,24 +57,15 @@ export function newestRunRow(root, role, classes, models) {
|
|
|
57
57
|
function newestRunNumber(root, role, classes, models) {
|
|
58
58
|
return newestRunRow(root, role, classes, models)?.run ?? null;
|
|
59
59
|
}
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* the
|
|
68
|
-
|
|
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 } = {}) {
|
|
60
|
+
/** The invocation half of cmdRunDetail: rule out the arity and flag mistakes before any run is
|
|
61
|
+
* resolved, so the shared unknown-role wording and the usage string are the whole validation
|
|
62
|
+
* surface, found by this one name. Succeeds only for a well-formed call; returns the parsed
|
|
63
|
+
* bare/role/classes/models for the resolver. The role is checked first, so a typo is named
|
|
64
|
+
* whatever the count (knownRoleIdsCached: the built-ins plus user-defined roles, read through
|
|
65
|
+
* the never-throwing cached loader so a transiently broken tumwater.json cannot take a
|
|
66
|
+
* read-only view down). The arity, unknown-role and flag-dependency failures all `fail` with
|
|
67
|
+
* the usage. */
|
|
68
|
+
function parseRunDetailInvocation(root, positionals, { last, transcript, prompt, grep, result, model }) {
|
|
78
69
|
// Positional arity by form: cli.ts has already peeled the flags off and gated them (only
|
|
79
70
|
// --json and --last are admitted), so anything left over that does not fit the chosen form
|
|
80
71
|
// is a mistake. The role is checked first, so a typo is named whatever the count; then
|
|
@@ -110,29 +101,60 @@ export async function cmdRunDetail(root, positionals, json, { last = false, stat
|
|
|
110
101
|
fail(`history --model needs --last on a one-run trail (usage: ${RUN_DETAIL_USAGE})`);
|
|
111
102
|
const classes = result !== null ? parseHistoryResultList(result) : null;
|
|
112
103
|
const models = model !== null ? parseHistoryModelList(model) : null;
|
|
104
|
+
return { bare, role, classes, models };
|
|
105
|
+
}
|
|
106
|
+
/** The resolution half of cmdRunDetail: pick the run to open and read its detail. Bare `--last`
|
|
107
|
+
* takes the newest run of any role (its role from the row); `--last` with a role the newest run
|
|
108
|
+
* by number; the numbered path the user's positive integer. An empty or unreadable scan is null
|
|
109
|
+
* (the detail stays null, and the not-found branch of cmdRunDetail words it), so the resolver
|
|
110
|
+
* never reports a miss itself. */
|
|
111
|
+
function resolveRunDetailTarget(root, parsed, positionals, last) {
|
|
112
|
+
let role = parsed.role;
|
|
113
113
|
// The run number: either the user's positive integer (the numbered path, through the
|
|
114
114
|
// shared count parser — a non-positive or non-numeric n fails naming the shape; there is
|
|
115
115
|
// no cap — the number selects, it does not size the scan) or the newest completed run's
|
|
116
116
|
// number, read from readRunRows' newest-first scan for the role. An empty scan is not
|
|
117
117
|
// found by construction, so the not-found path below takes it with the last-form wording.
|
|
118
118
|
let runNumber;
|
|
119
|
-
if (bare) {
|
|
119
|
+
if (parsed.bare) {
|
|
120
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);
|
|
121
|
+
const row = newestRunRow(root, null, parsed.classes, parsed.models);
|
|
122
122
|
if (row !== null)
|
|
123
123
|
role = row.role;
|
|
124
124
|
runNumber = row?.run ?? null;
|
|
125
125
|
}
|
|
126
126
|
else {
|
|
127
|
-
runNumber = last ? newestRunNumber(root, role, classes, models) : parseCountFlag("history <n>", positionals[1]);
|
|
127
|
+
runNumber = last ? newestRunNumber(root, role, parsed.classes, parsed.models) : parseCountFlag("history <n>", positionals[1]);
|
|
128
128
|
}
|
|
129
129
|
const detail = runNumber === null ? null : readRunDetail(root, role, runNumber);
|
|
130
|
+
return { role, runNumber, detail };
|
|
131
|
+
}
|
|
132
|
+
/** `tumwater history <role> [<n>] [--last [--result <class>[,...]]] [--stat] [--patch]
|
|
133
|
+
* [--transcript [--prompt] [--grep <text>]] [--json]`: print one completed
|
|
134
|
+
* run's full event trail. Read-only: stdout only, no state file created — a run the
|
|
135
|
+
* scan cannot find prints a not-found line and exits 0 (history's empty-output convention:
|
|
136
|
+
* an absent record is an answer, not a failure), and under --json it prints the JSON document
|
|
137
|
+
* `null` instead — the report --json precedent
|
|
138
|
+
* that every exit-0 output is parseable, never prose. `tumwater history <role> --last` resolves
|
|
139
|
+
* the role's newest completed run in the scanned window and prints its trail exactly as the
|
|
140
|
+
* numbered form would — the summary header and event rendering are shared, only the number's
|
|
141
|
+
* provenance differs (readRunRows' newest-first scan, not the user's argument). --last and
|
|
142
|
+
* a numeric <n> are rivals: exactly one positional under --last, exactly two without. With
|
|
143
|
+
* --last, `--result <class>[,...]` picks the newest run in the listed outcome classes instead
|
|
144
|
+
* (the listing's classes, scanning up to HISTORY_MAX_RUNS runs); it needs --last. `--model
|
|
145
|
+
* <selector>[,...]` with --last keeps only runs that started on a listed model (the listing's
|
|
146
|
+
* exact match) and composes with --result; it needs --last too. With --last and no role, the
|
|
147
|
+
* newest run of any role is opened (newestRunRow with a null role) and renders as its
|
|
148
|
+
* role's form. */
|
|
149
|
+
export async function cmdRunDetail(root, positionals, json, { last = false, stat = false, patch = false, transcript = false, prompt = false, grep = null, result = null, model = null } = {}) {
|
|
150
|
+
const parsed = parseRunDetailInvocation(root, positionals, { last, transcript, prompt, grep, result, model });
|
|
151
|
+
const { role, runNumber, detail } = resolveRunDetailTarget(root, parsed, positionals, last);
|
|
130
152
|
if (runNumber === null) {
|
|
131
153
|
// The --last scan came up empty: not found by construction, the last-form wording.
|
|
132
154
|
if (json)
|
|
133
155
|
sayJson(null);
|
|
134
156
|
else
|
|
135
|
-
say(runNotFoundLastMessage(bare ? null : role, classes, models));
|
|
157
|
+
say(runNotFoundLastMessage(parsed.bare ? null : role, parsed.classes, parsed.models));
|
|
136
158
|
return;
|
|
137
159
|
}
|
|
138
160
|
if (detail === null) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** The outcome-recording a run writes at its dismissal — the qa-flow ledger entry and the
|
|
2
|
+
* feedback filing — shared by the two dismissal paths so the run that changed the tree
|
|
3
|
+
* (run-stage.ts) and the no-change run (run-verdict.ts) file their flow and filing
|
|
4
|
+
* identically. Before the split each path carried its own copy: the same try/catch, the same
|
|
5
|
+
* `not recorded` warn wording, and the same issue_filed event, so a wording or guard drift
|
|
6
|
+
* in one path could not show up in the other. Each recorder is bookkeeping that never fails
|
|
7
|
+
* the run — a failed write warns and the run's outcome stands — and the caller keeps its own
|
|
8
|
+
* guard (the cut-off runs record nothing, the changed path records even when the merge is
|
|
9
|
+
* queued). The ci-parity recorder is deliberately NOT shared: only the no-change path writes
|
|
10
|
+
* it, because a run that changed the tree has not verified main (run-stage.ts's comment). */
|
|
11
|
+
import { logEventBestEffort } from "../events/events.js";
|
|
12
|
+
import { errorMessage } from "../text/text.js";
|
|
13
|
+
import { recordFiled } from "./feedback-filings.js";
|
|
14
|
+
import { recordFlow } from "./qa-coverage.js";
|
|
15
|
+
/** Record a dismissed run's qa flow (qa-coverage.ts's recordFlow) — a failed write warns
|
|
16
|
+
* with the same wording on both dismissal paths. `summary` is the run's summary only when
|
|
17
|
+
* the flow result is a bug (the recordFlow shape carries one just for the bug case, as each
|
|
18
|
+
* path's caller spelled the ternary inline); a passed flow records none. */
|
|
19
|
+
export function recordDismissedFlow(ctx, root, flow, result, summary) {
|
|
20
|
+
try {
|
|
21
|
+
recordFlow(root, flow, result, result === "bug" ? summary : undefined);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
ctx.warn(`qa flow "${flow}" not recorded: ${errorMessage(err)}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Record a dismissed run's feedback filing (feedback-filings.ts's recordFiled) and log the
|
|
28
|
+
* issue_filed event so the filing reaches the operator's activity feed — a failed ledger
|
|
29
|
+
* write warns with the same wording on both dismissal paths. */
|
|
30
|
+
export function recordDismissedFiling(ctx, root, filed) {
|
|
31
|
+
logEventBestEffort(root, {
|
|
32
|
+
source: ctx.role,
|
|
33
|
+
type: "issue_filed",
|
|
34
|
+
issue: filed.issue,
|
|
35
|
+
title: filed.title,
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
recordFiled(root, filed.issue, filed.title);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
ctx.warn(`feedback filing #${filed.issue} not recorded: ${errorMessage(err)}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -2,7 +2,7 @@ import { hasOutstandingWork, saveRunnerStateBestEffort } from "../runner/runner-
|
|
|
2
2
|
import { branchHead } from "../git/git.js";
|
|
3
3
|
import { logEventBestEffort, usageFragment, warnEventBestEffort } from "../events/events.js";
|
|
4
4
|
import { observeAccount } from "../agent/subscription-account.js";
|
|
5
|
-
import { ERROR_STREAK_WARN, QUIET_KILL_RESUME_LIMIT, applyRunOutcome } from "./run-apply.js";
|
|
5
|
+
import { CUT_OFF_RESUME_LIMIT, ERROR_STREAK_WARN, QUIET_KILL_RESUME_LIMIT, applyRunOutcome } from "./run-apply.js";
|
|
6
6
|
import { restoreMidRunWake } from "../scheduling/backoff.js";
|
|
7
7
|
import { consecutiveFailuresWarning } from "../text/phrases.js";
|
|
8
8
|
/** The run results that end an instance's claim: the run found nothing to do, refused the
|
|
@@ -48,28 +48,46 @@ export async function finalizeRun(deps) {
|
|
|
48
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
50
|
applyRunOutcome(s, config, role, outcome);
|
|
51
|
-
|
|
52
|
-
// (plans/parallel-work-instances.md "Claims", part 4/7): the next run may take a different
|
|
53
|
-
// entry. Every other result — queued, changed, rejected with a revision, an error, an abort
|
|
54
|
-
// that resumes — keeps the claim so the same instance retries it; an outstanding revision or
|
|
55
|
-
// a pending resume also holds it, because the entry is still this instance's task.
|
|
56
|
-
if (s.claim &&
|
|
57
|
-
CLAIM_RELEASING.has(outcome.result) &&
|
|
58
|
-
!hasOutstandingWork(s)) {
|
|
59
|
-
const title = s.claim.title;
|
|
60
|
-
s.claim = undefined;
|
|
61
|
-
logEventBestEffort(root, { source: role, type: "claim", action: "released", title, reason: outcome.result });
|
|
62
|
-
}
|
|
63
|
-
// A wake consumed while this run ran stamped the shared state in place, but the outcome
|
|
64
|
-
// schedule above overwrites it (lastRunEndedAt past wokenAt, nextRunAt a fresh gap or
|
|
65
|
-
// backoff out), so a plain `tumwater wake --role` with an empty queue would silently wait
|
|
66
|
-
// out the whole interval (BUGS.md 2026-09-25). Re-apply it here — exactly like a wake
|
|
67
|
-
// arriving one poll after the run ended.
|
|
51
|
+
releaseFinishedClaim(root, role, s, outcome);
|
|
68
52
|
restoreMidRunWake(s);
|
|
69
53
|
// A failed save warns and the run still ends: the in-memory state already holds the fold and
|
|
70
54
|
// the runner's next save writes it, while a throw here would skip run_end and the launch
|
|
71
55
|
// task's breaker fold.
|
|
72
56
|
saveRunnerStateBestEffort(root, s);
|
|
57
|
+
warnOnStreakCrossings(root, role, s, outcome);
|
|
58
|
+
// The run's wall-clock span rides on run_end too: the
|
|
59
|
+
// failure digest prices a run by its own durationMs first (src/failure/failure-data.ts
|
|
60
|
+
// runDurationMs), so the field is ALWAYS present — a run that ends within the same
|
|
61
|
+
// Date.now() millisecond it started (an instant early-abort error run) or crosses a
|
|
62
|
+
// backward clock step still attests its 0 ms, where the old omit-when-zero convention
|
|
63
|
+
// dropped the field and broke the "every run_end carries a span" contract (BUGS.md
|
|
64
|
+
// 2026-09-29). Unlike the usage fields, run_end's renderer never prints the span, so an
|
|
65
|
+
// always-present 0 changes no rendered line.
|
|
66
|
+
const runDurationMs = Math.max(0, Date.now() - deps.runStartedAt);
|
|
67
|
+
observeRunAccount(root, usage);
|
|
68
|
+
logRunEnd(root, role, runNumber, s, outcome, usage, runDurationMs);
|
|
69
|
+
return outcome;
|
|
70
|
+
}
|
|
71
|
+
/** A run that ends without producing or keeping work releases this instance's backlog claim
|
|
72
|
+
* (plans/parallel-work-instances.md "Claims", part 4/7): the next run may take a different
|
|
73
|
+
* entry. Every other result — queued, changed, rejected with a revision, an error, an abort
|
|
74
|
+
* that resumes — keeps the claim so the same instance retries it; an outstanding revision or
|
|
75
|
+
* a pending resume also holds it, because the entry is still this instance's task. */
|
|
76
|
+
function releaseFinishedClaim(root, role, state, outcome) {
|
|
77
|
+
if (state.claim &&
|
|
78
|
+
CLAIM_RELEASING.has(outcome.result) &&
|
|
79
|
+
!hasOutstandingWork(state)) {
|
|
80
|
+
const title = state.claim.title;
|
|
81
|
+
state.claim = undefined;
|
|
82
|
+
logEventBestEffort(root, { source: role, type: "claim", action: "released", title, reason: outcome.result });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** One warning per error episode (BUGS.md 2026-09-15: every role failing identically
|
|
86
|
+
* looked like a quiet team), quiet-kill episode (BUGS.md 2026-09-18: a role burning an hour
|
|
87
|
+
* per run with no output must not look like a sleeping role) and cut-off episode (mirroring
|
|
88
|
+
* quiet-kill: a role cycling cut-off → resume burns an hour-plus per cycle with no other
|
|
89
|
+
* operator signal) — each fired exactly once at the streak's crossing, after the schedule. */
|
|
90
|
+
function warnOnStreakCrossings(root, role, state, outcome) {
|
|
73
91
|
// One warning per error episode (BUGS.md 2026-09-15: every role failing identically
|
|
74
92
|
// looked like a quiet team). applyRunOutcome increments the streak on error-class
|
|
75
93
|
// outcomes, preserves it across a plain `queued` run (the run's merge resolves it),
|
|
@@ -78,37 +96,34 @@ export async function finalizeRun(deps) {
|
|
|
78
96
|
// re-warn on the queued run's end. A rejection's crossing warns from the merge side
|
|
79
97
|
// instead (writeMergeOutcome, src/merge/merge-slot.ts), which is where rejections resolve.
|
|
80
98
|
if ((outcome.result === "error" || outcome.recoveryFailure !== undefined) &&
|
|
81
|
-
(
|
|
82
|
-
warnEventBestEffort(root, role, consecutiveFailuresWarning(
|
|
99
|
+
(state.consecutiveErrors ?? 0) === ERROR_STREAK_WARN) {
|
|
100
|
+
warnEventBestEffort(root, role, consecutiveFailuresWarning(state.consecutiveErrors ?? 0, state.lastError ?? outcome.recoveryFailure ?? "unknown error"));
|
|
83
101
|
}
|
|
84
102
|
// One warning per quiet-kill episode (BUGS.md 2026-09-18): a role burning an hour per
|
|
85
103
|
// run with no output must not look like a sleeping role. applyRunOutcome grows the
|
|
86
104
|
// streak on each kill and resets it on any other result, so the crossing fires once;
|
|
87
105
|
// the give-up (fresh session + backoff) follows on the next kill.
|
|
88
|
-
if ((
|
|
89
|
-
warnEventBestEffort(root, role, `${
|
|
106
|
+
if ((state.quietKillStreak ?? 0) === QUIET_KILL_RESUME_LIMIT) {
|
|
107
|
+
warnEventBestEffort(root, role, `${state.quietKillStreak} consecutive stalls (no progress): ${state.lastError ?? "unknown hang"}`);
|
|
90
108
|
}
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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).
|
|
109
|
+
// One warning per cut-off episode, mirroring quiet-kill above: a role cycling cut-off →
|
|
110
|
+
// resume burns an hour-plus per cycle (run-apply.ts CUT_OFF_RESUME_LIMIT) with no other
|
|
111
|
+
// operator signal, so the crossing must not look like a benign idle role. The `outcome.cutOff`
|
|
112
|
+
// guard makes it fire only on the run that increments the streak to the limit — every
|
|
113
|
+
// non-cut-off run resets the streak to 0 and a later cut-off sits above the limit — so
|
|
114
|
+
// exactly one warning per episode. No role guard, matching quiet-kill (a cut-off director
|
|
115
|
+
// prompt is re-queued but its streak still counts).
|
|
116
|
+
if (outcome.cutOff === true && (state.cutOffStreak ?? 0) === CUT_OFF_RESUME_LIMIT) {
|
|
117
|
+
warnEventBestEffort(root, role, `${state.cutOffStreak} consecutive runs cut off at the context ceiling; give the role a smaller task or a longer context window`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** The account state (plans/agent-backends.md part 6a): fold this run's billing answer and
|
|
121
|
+
* rate-window readings into the reporting backend's .tumwater/state/<backend>-account.json
|
|
122
|
+
* for the sibling parts that throttle or display the subscription window. Best-effort — the
|
|
123
|
+
* file is a cache, and an unwritable state dir must not fail a run whose real work is
|
|
124
|
+
* already done. A pi run never reports either, so in practice claude owns the file until a
|
|
125
|
+
* second subscription backend lands (plans/codex-backend.md). */
|
|
126
|
+
function observeRunAccount(root, usage) {
|
|
112
127
|
if (usage.billing !== undefined || usage.rateWindows !== undefined) {
|
|
113
128
|
try {
|
|
114
129
|
observeAccount(root, usage.agent ?? "pi", {
|
|
@@ -120,6 +135,12 @@ export async function finalizeRun(deps) {
|
|
|
120
135
|
// The next reporting run rewrites it; nothing this run depends on is lost.
|
|
121
136
|
}
|
|
122
137
|
}
|
|
138
|
+
}
|
|
139
|
+
/** Assemble and log the run_end event: the per-run usage fields (tokens and cost), the run's
|
|
140
|
+
* wall-clock span, and every conditional payload spread. Omitted-when-zero fields ride the
|
|
141
|
+
* event feed only when nonzero so skipped runs render byte-identically to a pre-feature line;
|
|
142
|
+
* `durationMs` is ALWAYS present to keep the "every run_end carries a span" contract. */
|
|
143
|
+
function logRunEnd(root, role, runNumber, state, outcome, usage, runDurationMs) {
|
|
123
144
|
// The per-provider split of this run's usage (plans: per-provider spend in the day
|
|
124
145
|
// report): a Record<provider, {costUsd, notionalCostUsd}> for the report's `Cost by
|
|
125
146
|
// provider` line. Omitted when empty like the prompt-token fields, so a run whose runs
|
|
@@ -131,10 +152,14 @@ export async function finalizeRun(deps) {
|
|
|
131
152
|
run: runNumber,
|
|
132
153
|
result: outcome.result,
|
|
133
154
|
summary: outcome.summary,
|
|
134
|
-
error:
|
|
155
|
+
error: state.lastError,
|
|
135
156
|
durationMs: runDurationMs,
|
|
136
157
|
...(outcome.heldOnBranch ? { heldOnBranch: true } : {}),
|
|
137
|
-
|
|
158
|
+
// The context-ceiling truncation flag: set when the run burned its whole window and
|
|
159
|
+
// the harness resumes its pinned session (slotPinnedForResume). Omitted when false, so
|
|
160
|
+
// a normal run — and every run_end written by an older build — renders byte-identically.
|
|
161
|
+
...(outcome.cutOff === true ? { cutOff: true } : {}),
|
|
162
|
+
...usageFragment(state.generatedTokens, usage.costUsd, usage.notionalCostUsd),
|
|
138
163
|
...(Object.keys(providerCost).length > 0 ? { providerCost } : {}),
|
|
139
164
|
// The billing answer the run's runs reported, so the report can tell notional spend from
|
|
140
165
|
// real spend without re-deriving it from the file (plans/agent-backends.md part 6a).
|
|
@@ -150,6 +175,10 @@ export async function finalizeRun(deps) {
|
|
|
150
175
|
...(usage.promptTokens > 0 ? { promptTokens: usage.promptTokens } : {}),
|
|
151
176
|
...(usage.cacheReadTokens > 0 ? { cacheReadTokens: usage.cacheReadTokens } : {}),
|
|
152
177
|
...(usage.preEditPromptTokens > 0 ? { preEditPromptTokens: usage.preEditPromptTokens } : {}),
|
|
178
|
+
// The run's peak single-request context: how hot the run's largest model call ran
|
|
179
|
+
// against the model's declared window (runner-state.ts's peakContextTokens, reset at
|
|
180
|
+
// run start). Omitted when zero like the prompt-token fields, so a run that reported
|
|
181
|
+
// no context — and every run_end written by an older build — renders byte-identically.
|
|
182
|
+
...(state.peakContextTokens > 0 ? { peakContextTokens: state.peakContextTokens } : {}),
|
|
153
183
|
});
|
|
154
|
-
return outcome;
|
|
155
184
|
}
|