tumwater 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +25 -12
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +1 -8
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +9 -0
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +50 -27
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +1 -1
- package/dist/src/agent/subscription-account.js +3 -3
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +58 -102
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +1 -1
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-scoped.js +40 -16
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +34 -12
- package/dist/src/cli/cli-query-commands.js +1 -1
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-start.js +91 -58
- package/dist/src/cli/completion.js +82 -46
- package/dist/src/cli/config-commands.js +82 -71
- package/dist/src/cli/help.js +4 -6
- package/dist/src/cli/log-commands.js +43 -23
- package/dist/src/cli/notify-command.js +2 -2
- package/dist/src/cli/question-commands.js +86 -60
- package/dist/src/cli.js +181 -135
- package/dist/src/collections.js +31 -16
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +22 -0
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +5 -20
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +153 -102
- package/dist/src/config/config-schema.js +9 -11
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +4 -52
- package/dist/src/config/config-validation.js +4 -38
- package/dist/src/config/config-views.js +9 -34
- package/dist/src/config/config-write.js +6 -23
- package/dist/src/config/config.js +4 -5
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +24 -24
- package/dist/src/doctor/doctor-backlog.js +7 -5
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +5 -5
- package/dist/src/doctor/doctor-claude-checks.js +44 -28
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +44 -112
- package/dist/src/doctor/doctor-fallback-checks.js +2 -2
- package/dist/src/doctor/doctor-holds.js +6 -45
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +120 -20
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +3 -3
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +30 -54
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +1 -3
- package/dist/src/events/{notify.js → event-notify.js} +2 -2
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/failure/failure-cluster.js +6 -6
- package/dist/src/failure/failure-data.js +8 -4
- package/dist/src/failure/failure-render.js +44 -34
- package/dist/src/failure/failure-state-change.js +4 -34
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +27 -68
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +7 -7
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +22 -23
- package/dist/src/gui/gui-prompt-commands.js +23 -23
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +51 -33
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +8 -11
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +10 -13
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-verify.js +8 -18
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +76 -44
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +27 -32
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +1 -1
- package/dist/src/orchestrator/scheduling-skip.js +1 -1
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +8 -7
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +1 -1
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +32 -6
- package/dist/src/pi/pi-watchdogs.js +12 -5
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +49 -5
- package/dist/src/process/process.js +8 -8
- package/dist/src/process/run-marker.js +31 -13
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +1 -1
- package/dist/src/review/review-precheck.js +11 -1
- package/dist/src/review/review.js +40 -153
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +1 -14
- package/dist/src/roles/role-view.js +9 -21
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/run-apply.js +12 -6
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +135 -84
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +119 -50
- package/dist/src/run/stage-check.js +49 -32
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/runner-outcome.js +6 -1
- package/dist/src/runner/runner-pi.js +3 -10
- package/dist/src/runner/runner-recovery.js +1 -1
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -1
- package/dist/src/runner/runner-run.js +97 -35
- package/dist/src/runner/runner-state-load.js +12 -9
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-data.js +2 -2
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -17
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +98 -65
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +2 -15
- package/dist/src/team/team-polls.js +28 -126
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +20 -15
- package/dist/src/text/text.js +3 -3
- package/dist/src/ui/alert-feeds.js +49 -73
- package/dist/src/ui/badges.js +3 -13
- package/dist/src/ui/gui/gui-client-boot.js +2 -2
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +7 -17
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +4 -4
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +53 -40
- package/dist/src/ui/run-progress-model.js +9 -2
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +3 -7
- package/dist/src/ui/status-render.js +5 -9
- package/dist/src/ui/team-alert-backlog.js +6 -6
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +7 -8
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +1 -7
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +159 -92
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +3 -3
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -228
- package/dist/src/ui/tui/tui-pane-history.js +61 -70
- package/dist/src/ui/tui/tui-pane-pending.js +284 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +11 -7
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +29 -32
- package/dist/src/ui/tui/tui.js +3 -4
- package/dist/src/verdict/fix-claim.js +76 -54
- package/dist/src/verdict/refusal.js +1 -1
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
- /package/dist/src/runner/{leftover.js → runner-leftover.js} +0 -0
|
@@ -39,56 +39,99 @@ export function emptyGrepCriteria(body) {
|
|
|
39
39
|
* shell syntax (an unquoted `|;&<>$()` or backtick) or leaves a quote open. Nothing runs through
|
|
40
40
|
* a shell, so `*` stays literal. */
|
|
41
41
|
export function grepArgv(cmd) {
|
|
42
|
-
const words =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
42
|
+
const words = splitShellWords(cmd);
|
|
43
|
+
return words === undefined ? undefined : grepInvocation(words);
|
|
44
|
+
}
|
|
45
|
+
/** Take one character at `i` inside a single-quoted run (`s.quote === "'"`): a `'` closes the
|
|
46
|
+
* quote, anything else stays literal. Returns the index after the character. Inside single
|
|
47
|
+
* quotes everything is literal. */
|
|
48
|
+
function takeSingleQuoted(cmd, i, s) {
|
|
49
|
+
const c = cmd[i];
|
|
50
|
+
if (c === "'")
|
|
51
|
+
s.quote = null;
|
|
52
|
+
else
|
|
53
|
+
s.cur += c;
|
|
54
|
+
return i + 1;
|
|
55
|
+
}
|
|
56
|
+
/** Take one character at `i` inside a double-quoted run (`s.quote === '"'`): a `\\`-escaped
|
|
57
|
+
* `"` or `\\` appends the escaped character and consumes two characters, a `"` closes the
|
|
58
|
+
* quote, anything else stays literal. Returns the index after the consumed character(s). Double
|
|
59
|
+
* quotes honour only `\"` and `\\`. */
|
|
60
|
+
function takeDoubleQuoted(cmd, i, s) {
|
|
61
|
+
const c = cmd[i];
|
|
62
|
+
const next = cmd[i + 1];
|
|
63
|
+
if (c === "\\" && (next === '"' || next === "\\")) {
|
|
64
|
+
s.cur += next;
|
|
65
|
+
return i + 2;
|
|
66
|
+
}
|
|
67
|
+
if (c === '"')
|
|
68
|
+
s.quote = null;
|
|
69
|
+
else
|
|
70
|
+
s.cur += c;
|
|
71
|
+
return i + 1;
|
|
72
|
+
}
|
|
73
|
+
/** Take one character at `i` outside any quote, or return `undefined` when it is refused shell
|
|
74
|
+
* syntax: an unquoted `'`/`"` opens the quote and marks a word, an unquoted `\\` appends the
|
|
75
|
+
* next character (if any) and marks a word, an unquoted `|;&<>$()` or backtick refuses the
|
|
76
|
+
* command, whitespace ends a word (pushing `cur` when one is open) and resets the buffer, and
|
|
77
|
+
* anything else appends and marks a word. Returns the index after the consumed character(s), or
|
|
78
|
+
* `undefined` for the shell refusal. */
|
|
79
|
+
function takeUnquoted(cmd, i, s) {
|
|
80
|
+
const c = cmd[i];
|
|
81
|
+
if (c === "'" || c === '"') {
|
|
82
|
+
s.quote = c;
|
|
83
|
+
s.inWord = true;
|
|
84
|
+
return i + 1;
|
|
85
|
+
}
|
|
86
|
+
if (c === "\\") {
|
|
87
|
+
if (i + 1 < cmd.length) {
|
|
88
|
+
s.cur += cmd[i + 1];
|
|
89
|
+
s.inWord = true;
|
|
90
|
+
return i + 2;
|
|
82
91
|
}
|
|
92
|
+
s.inWord = true;
|
|
93
|
+
return i + 1;
|
|
94
|
+
}
|
|
95
|
+
if (/[|;&<>$()`]/.test(c))
|
|
96
|
+
return undefined;
|
|
97
|
+
if (/\s/.test(c)) {
|
|
98
|
+
if (s.inWord)
|
|
99
|
+
s.words.push(s.cur);
|
|
100
|
+
s.cur = "";
|
|
101
|
+
s.inWord = false;
|
|
102
|
+
return i + 1;
|
|
103
|
+
}
|
|
104
|
+
s.cur += c;
|
|
105
|
+
s.inWord = true;
|
|
106
|
+
return i + 1;
|
|
107
|
+
}
|
|
108
|
+
/** Walk the whole command, dispatching each character to the quote-aware helper, and reject an
|
|
109
|
+
* unterminated quote. Returns the word list. */
|
|
110
|
+
function splitShellWords(cmd) {
|
|
111
|
+
const s = { words: [], cur: "", inWord: false, quote: null };
|
|
112
|
+
let i = 0;
|
|
113
|
+
while (i < cmd.length) {
|
|
114
|
+
if (s.quote === "'")
|
|
115
|
+
i = takeSingleQuoted(cmd, i, s);
|
|
116
|
+
else if (s.quote === '"')
|
|
117
|
+
i = takeDoubleQuoted(cmd, i, s);
|
|
83
118
|
else {
|
|
84
|
-
|
|
85
|
-
|
|
119
|
+
const next = takeUnquoted(cmd, i, s);
|
|
120
|
+
if (next === undefined)
|
|
121
|
+
return undefined;
|
|
122
|
+
i = next;
|
|
86
123
|
}
|
|
87
124
|
}
|
|
88
|
-
if (quote !== null)
|
|
125
|
+
if (s.quote !== null)
|
|
89
126
|
return undefined;
|
|
90
|
-
if (inWord)
|
|
91
|
-
words.push(cur);
|
|
127
|
+
if (s.inWord)
|
|
128
|
+
s.words.push(s.cur);
|
|
129
|
+
return s.words;
|
|
130
|
+
}
|
|
131
|
+
/** The shape pick: what invocation does this word list name? `git grep …` gives file `git` and
|
|
132
|
+
* the args after `git`; `grep …` gives file `grep` and the args after `grep`; anything else is
|
|
133
|
+
* not a grep we run. */
|
|
134
|
+
function grepInvocation(words) {
|
|
92
135
|
if (words[0] === "git" && words[1] === "grep")
|
|
93
136
|
return { file: "git", args: words.slice(1) };
|
|
94
137
|
if (words[0] === "grep")
|
|
@@ -46,7 +46,7 @@ export const ERROR_STREAK_WARN = 3;
|
|
|
46
46
|
export function errorStreakAtWarn(s) {
|
|
47
47
|
return (s.consecutiveErrors ?? 0) >= ERROR_STREAK_WARN;
|
|
48
48
|
}
|
|
49
|
-
/** Consecutive failed runs after which the error-streak circuit breaker (src/gates/streak-
|
|
49
|
+
/** Consecutive failed runs after which the error-streak circuit breaker (src/gates/streak-gates.ts)
|
|
50
50
|
* auto-pauses the role through the per-role pause marker — the same act-on-it escalation the
|
|
51
51
|
* budget gate and the pause gates perform, applied to a role failing on its own cause. Ten,
|
|
52
52
|
* not the warn bar: at 3 the failure is diagnosed but possibly transient, and by run 4 the
|
|
@@ -68,6 +68,13 @@ export const CUT_OFF_RESUME_LIMIT = 3;
|
|
|
68
68
|
* consumed 44% of the team over two days). Mirrors CUT_OFF_RESUME_LIMIT; the streak itself
|
|
69
69
|
* keeps counting (RunnerState.quietKillStreak) so one warning per episode can be raised. */
|
|
70
70
|
export const QUIET_KILL_RESUME_LIMIT = 3;
|
|
71
|
+
/** Bump a streak counter to `current + 1` when `hit`, and reset it to 0 otherwise — the one
|
|
72
|
+
* home of the cut-off, quiet-kill and no-change streaks' shared shape in applyRunOutcome, so
|
|
73
|
+
* the three count identically. (The consecutive-error streak does NOT share it: it resumes
|
|
74
|
+
* from a `queued`-ended run rather than resetting, so it keeps its own longer arm.) */
|
|
75
|
+
function bumpOrResetStreak(current, hit) {
|
|
76
|
+
return hit ? (current ?? 0) + 1 : 0;
|
|
77
|
+
}
|
|
71
78
|
/** Record a finished run on the role's state and schedule its next run from the outcome.
|
|
72
79
|
* Split out of RoleRunner.run() (runner.ts) so the scheduling policy — which outcomes retry
|
|
73
80
|
* promptly, which back off and on which ladder, how cut-off resumes are bounded — sits with
|
|
@@ -78,11 +85,10 @@ export const QUIET_KILL_RESUME_LIMIT = 3;
|
|
|
78
85
|
export function applyRunOutcome(s, cfg, role, outcome) {
|
|
79
86
|
s.running = false;
|
|
80
87
|
s.parkedSince = undefined;
|
|
81
|
-
s.parkedOn = undefined;
|
|
82
88
|
// The cut-off streak counts EVERY consecutive run truncated at the context ceiling, past the
|
|
83
89
|
// resume limit too: the next fresh run's prompt names how many attempts the window has eaten
|
|
84
90
|
// (buildCutOffNote), so the count must not freeze at the limit. Any other outcome resets it.
|
|
85
|
-
s.cutOffStreak =
|
|
91
|
+
s.cutOffStreak = bumpOrResetStreak(s.cutOffStreak, outcome.cutOff);
|
|
86
92
|
// The error streak counts consecutive failed runs; any other result resets it
|
|
87
93
|
// (BUGS.md 2026-09-15: 44 identical failures raised no alarm). A run whose leftover
|
|
88
94
|
// recovery re-queued a pin its last merge failed counts too, even though the run
|
|
@@ -106,7 +112,7 @@ export function applyRunOutcome(s, cfg, role, outcome) {
|
|
|
106
112
|
// The quiet-kill streak counts consecutive watchdog kills; any other result resets it.
|
|
107
113
|
// run-finalize.ts warns once when it crosses QUIET_KILL_RESUME_LIMIT, and the branch below uses
|
|
108
114
|
// it to bound how many times a starved session is resumed (BUGS.md 2026-09-18).
|
|
109
|
-
s.quietKillStreak = outcome.result === "quiet_killed"
|
|
115
|
+
s.quietKillStreak = bumpOrResetStreak(s.quietKillStreak, outcome.result === "quiet_killed");
|
|
110
116
|
// The no-change streak counts consecutive completed runs that ended `no_change` (the
|
|
111
117
|
// resting state cell's ` · no change ×N` suffix — status-model.ts rolePhase). Any other
|
|
112
118
|
// completed result resets it, so a role that merges between no-ops never shows a stale
|
|
@@ -115,7 +121,7 @@ export function applyRunOutcome(s, cfg, role, outcome) {
|
|
|
115
121
|
// `quiet_killed`/`user_aborted` all reset it and only a `no_change` run grows it.
|
|
116
122
|
// Observational only — never warns, unlike the error/quiet-kill streaks: a legitimately
|
|
117
123
|
// idle maintenance role with an empty backlog can hold a long streak and that is expected.
|
|
118
|
-
s.noChangeStreak = outcome.result === "no_change"
|
|
124
|
+
s.noChangeStreak = bumpOrResetStreak(s.noChangeStreak, outcome.result === "no_change");
|
|
119
125
|
// The yield ring records the result beside the streaks: a merge or a counted empty
|
|
120
126
|
// stretches the role's min-run gap (yieldMultiplier, read by isEligible), an error-class
|
|
121
127
|
// result is skipped entirely — no evidence either way (yield-scaled clocks, PLANS.md).
|
|
@@ -329,7 +335,7 @@ export function applyMergeOutcome(s, result, change) {
|
|
|
329
335
|
// leave the streak unchanged and keep the fast minimum-interval retry the queued run
|
|
330
336
|
// scheduled. This merge outcome is the accumulation point (the authoring run ends
|
|
331
337
|
// `queued`, which no longer resets the streak, see applyRunOutcome) — the same field the
|
|
332
|
-
// warn, the breaker (src/gates/streak-
|
|
338
|
+
// warn, the breaker (src/gates/streak-gates.ts), and the error-storm observation
|
|
333
339
|
// (src/team/team-polls.ts) already read. No new machinery, no new constants: the warn
|
|
334
340
|
// bar and the breaker bar are ERROR_STREAK_WARN and ERROR_STREAK_BREAKER themselves.
|
|
335
341
|
if (s.revision === undefined) {
|
|
@@ -46,45 +46,59 @@ export function readRunDetail(root, role, runNumber) {
|
|
|
46
46
|
// same bound.
|
|
47
47
|
const all = readEvents(root, HISTORY_SCAN_MAX_EVENTS);
|
|
48
48
|
const events = all.filter((e) => e.source === role);
|
|
49
|
-
|
|
50
|
-
let startIdx = -1;
|
|
51
|
-
for (let i = events.length - 1; i >= 0; i--) {
|
|
52
|
-
const e = events[i];
|
|
53
|
-
if (e.type === "run_start" && e.run === runNumber) {
|
|
54
|
-
startIdx = i;
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
49
|
+
const startIdx = newestRunStartIndex(events, runNumber);
|
|
58
50
|
if (startIdx < 0) {
|
|
59
51
|
// The start is gone (rotation) or the run never ran. A surviving run_end for the same
|
|
60
52
|
// number still answers — unpaired, undated at the front — else there is no such run here.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return detailFrom(root, role, runNumber, all, events, [e], e, null);
|
|
65
|
-
}
|
|
53
|
+
const loneEnd = newestRunEnd(events, runNumber);
|
|
54
|
+
if (loneEnd !== null)
|
|
55
|
+
return detailFrom(root, role, runNumber, all, events, [loneEnd], loneEnd, null);
|
|
66
56
|
return null;
|
|
67
57
|
}
|
|
68
58
|
const start = events[startIdx];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
59
|
+
const { end, nextStart } = runBlockBounds(events, startIdx, runNumber);
|
|
60
|
+
const block = collectRunBlock(events, startIdx, end, nextStart);
|
|
61
|
+
return detailFrom(root, role, runNumber, all, events, block, end, start);
|
|
62
|
+
}
|
|
63
|
+
/** The newest run_start for this role+run — the block's lower bound — or -1 when none (the
|
|
64
|
+
* start rotated out or the run never ran). A backward scan: a counter reset can repeat a run
|
|
65
|
+
* number, and the operator asking by number means the most recent one. */
|
|
66
|
+
function newestRunStartIndex(events, runNumber) {
|
|
67
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
68
|
+
const e = events[i];
|
|
69
|
+
if (e.type === "run_start" && e.run === runNumber)
|
|
70
|
+
return i;
|
|
71
|
+
}
|
|
72
|
+
return -1;
|
|
73
|
+
}
|
|
74
|
+
/** The newest surviving run_end for this role+run, or null. Read only on the no-start path:
|
|
75
|
+
* the start is gone (rotation) or the run never ran, but a surviving run_end still answers —
|
|
76
|
+
* unpaired, undated at the front — else there is no such run here. */
|
|
77
|
+
function newestRunEnd(events, runNumber) {
|
|
78
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
79
|
+
const e = events[i];
|
|
80
|
+
if (e.type === "run_end" && e.run === runNumber)
|
|
81
|
+
return e;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
/** The block's upper bound for the run opened by `startIdx`: the first run_end for this run
|
|
86
|
+
* after it ends the block; else a later run_start (this run's end lost to rotation) closes it
|
|
87
|
+
* exclusive — the events after belong to the next run, not this one. Neither afterwards reads
|
|
88
|
+
* as in flight: everything since the start belongs to the open run. */
|
|
89
|
+
function runBlockBounds(events, startIdx, runNumber) {
|
|
75
90
|
for (let i = startIdx + 1; i < events.length; i++) {
|
|
76
91
|
const e = events[i];
|
|
77
|
-
if (e.type === "run_end" && e.run === runNumber)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (e.type === "run_start") {
|
|
82
|
-
nextStart = e;
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
92
|
+
if (e.type === "run_end" && e.run === runNumber)
|
|
93
|
+
return { end: e, nextStart: null };
|
|
94
|
+
if (e.type === "run_start")
|
|
95
|
+
return { end: null, nextStart: e };
|
|
85
96
|
}
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
return { end: null, nextStart: null };
|
|
98
|
+
}
|
|
99
|
+
/** The block itself: the start event plus everything this role logged up to the bound —
|
|
100
|
+
* inclusive at a paired end, exclusive at a next run's start. */
|
|
101
|
+
function collectRunBlock(events, startIdx, end, nextStart) {
|
|
88
102
|
const block = [];
|
|
89
103
|
for (let i = startIdx; i < events.length; i++) {
|
|
90
104
|
const e = events[i];
|
|
@@ -92,19 +106,12 @@ export function readRunDetail(root, role, runNumber) {
|
|
|
92
106
|
break;
|
|
93
107
|
block.push(e);
|
|
94
108
|
}
|
|
95
|
-
return
|
|
109
|
+
return block;
|
|
96
110
|
}
|
|
97
|
-
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
function
|
|
101
|
-
const usageNums = end === null ? { tokens: 0, costUsd: 0 } : eventUsage(end);
|
|
102
|
-
// The run_end's prompt-token total and its cache-read subset, corrupt-field-guarded reads
|
|
103
|
-
// (nonNegativeNumber, the same guard peakContextTokens applies) with the cache subset
|
|
104
|
-
// clamped to the total so the rendered share never exceeds 100%; 0 on an in-flight run.
|
|
105
|
-
const promptTokens = end === null ? 0 : nonNegativeNumber(end.promptTokens, 0);
|
|
106
|
-
const cacheReadTokens = end === null ? 0 : Math.min(nonNegativeNumber(end.cacheReadTokens, 0), promptTokens);
|
|
107
|
-
const preEditPromptTokens = end === null ? 0 : Math.min(nonNegativeNumber(end.preEditPromptTokens, 0), promptTokens);
|
|
111
|
+
/** The verdict a run reports: the `run_end`'s outcome text, resolved through its merge for a
|
|
112
|
+
* run that ended `queued` (its result becomes the merge's result and `merge` the merge_done/
|
|
113
|
+
* merge_failed event carrying it) — the same fresh claim cursor walk the history rows use. */
|
|
114
|
+
function runResultOf(end, scoped) {
|
|
108
115
|
let result = end === null ? null : outcomeText(end);
|
|
109
116
|
let merge = null;
|
|
110
117
|
if (end !== null && result === "queued") {
|
|
@@ -117,42 +124,88 @@ function detailFrom(root, role, runNumber, all, scoped, block, end, start) {
|
|
|
117
124
|
merge = resolved.outcome;
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
return { result, merge };
|
|
128
|
+
}
|
|
129
|
+
/** The token and cost totals from the run_end; both zero on an in-flight run. */
|
|
130
|
+
function runUsageOf(end) {
|
|
131
|
+
if (end === null)
|
|
132
|
+
return { tokens: 0, costUsd: 0 };
|
|
133
|
+
const { tokens, costUsd } = eventUsage(end);
|
|
134
|
+
return { tokens, costUsd };
|
|
135
|
+
}
|
|
136
|
+
/** The run's start/end timestamps and its span: `startTs`/`endTs` null when rotation cut that
|
|
137
|
+
* side, `durationMs` null whenever the pair is incomplete (no fabricated span). */
|
|
138
|
+
function runTimesOf(start, end, scoped) {
|
|
139
|
+
return {
|
|
124
140
|
startTs: start === null ? null : start.ts,
|
|
125
|
-
model: typeof start?.model === "string" && start.model.length > 0 ? start.model : null,
|
|
126
141
|
endTs: end === null ? null : end.ts,
|
|
127
142
|
durationMs: end === null ? null : runSpanMs(end, runStartMap(scoped)),
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** The model the run's `run_start` named, or null when it carried none or sits outside the scan. */
|
|
146
|
+
function runModelOf(start) {
|
|
147
|
+
return typeof start?.model === "string" && start.model.length > 0 ? start.model : null;
|
|
148
|
+
}
|
|
149
|
+
/** The run's usage as its header line; empty when the run carries neither total. */
|
|
150
|
+
function runUsageTextOf(end) {
|
|
151
|
+
return end === null ? "" : usageText(end);
|
|
152
|
+
}
|
|
153
|
+
/** The run_end's prompt-token total and its cache-read and pre-edit subsets, corrupt-field-
|
|
154
|
+
* guarded reads (nonNegativeNumber) with the subsets clamped to the total so the rendered share
|
|
155
|
+
* never exceeds 100%; all three keys absent on a prompt-free or in-flight run. */
|
|
156
|
+
function runPromptFields(end) {
|
|
157
|
+
const promptTokens = end === null ? 0 : nonNegativeNumber(end.promptTokens, 0);
|
|
158
|
+
const cacheReadTokens = end === null ? 0 : Math.min(nonNegativeNumber(end.cacheReadTokens, 0), promptTokens);
|
|
159
|
+
const preEditPromptTokens = end === null ? 0 : Math.min(nonNegativeNumber(end.preEditPromptTokens, 0), promptTokens);
|
|
160
|
+
return {
|
|
140
161
|
...(promptTokens > 0 ? { promptTokens } : {}),
|
|
141
162
|
...(cacheReadTokens > 0 ? { cacheReadTokens } : {}),
|
|
142
163
|
...(preEditPromptTokens > 0 ? { preEditPromptTokens } : {}),
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** The run_end's ceiling-truncation flag, absent when the run did not cut off. */
|
|
167
|
+
function runCutOffField(end) {
|
|
168
|
+
return end !== null && end.cutOff === true ? { cutOff: true } : {};
|
|
169
|
+
}
|
|
170
|
+
/** The run's peak context, a corrupt-field-guarded read of the run_end; 0 when in flight. */
|
|
171
|
+
function runPeakContextOf(end) {
|
|
172
|
+
return end === null ? 0 : nonNegativeNumber(end.peakContextTokens, 0);
|
|
173
|
+
}
|
|
174
|
+
/** The end-of-run → on-main landing latency (the landing `merged` event's ts minus the run
|
|
175
|
+
* end's), absent without both an end and a landing event — or a landing before the end, or a
|
|
176
|
+
* negative difference. */
|
|
177
|
+
function runMergeLatencyField(end, mergedEvent) {
|
|
178
|
+
return end !== null && mergedEvent !== null && mergedEvent.ts - end.ts >= 0
|
|
179
|
+
? { mergeAfterRunMs: mergedEvent.ts - end.ts }
|
|
180
|
+
: {};
|
|
181
|
+
}
|
|
182
|
+
/** Fill the payload around a collected block: each concern resolves in its own named helper
|
|
183
|
+
* (runResultOf, runUsageOf, runTimesOf, runModelOf, runUsageTextOf, runPromptFields,
|
|
184
|
+
* runCutOffField, runPeakContextOf, runMergeLatencyField), and this assembles their results.
|
|
185
|
+
* Events stay oldest-first; usage/duration pair through the shared event-read helpers so this
|
|
186
|
+
* view and the history rows cannot drift on what a span is. */
|
|
187
|
+
function detailFrom(root, role, runNumber, all, scoped, block, end, start) {
|
|
188
|
+
const { result, merge } = runResultOf(end, scoped);
|
|
189
|
+
const mergedEvent = mergedEventOf(result, merge, scoped, block);
|
|
190
|
+
const detail = {
|
|
191
|
+
role,
|
|
192
|
+
run: runNumber,
|
|
193
|
+
...runTimesOf(start, end, scoped),
|
|
194
|
+
model: runModelOf(start),
|
|
195
|
+
...runUsageOf(end),
|
|
196
|
+
usage: runUsageTextOf(end),
|
|
197
|
+
result,
|
|
198
|
+
// The window comes from the shared seamContextWindowForRole — same seam the role view and
|
|
199
|
+
// status table print — with loadConfigSafe falling back to defaults, so a broken
|
|
200
|
+
// tumwater.json yields a null window rather than failing this read-only view.
|
|
201
|
+
peakContextTokens: runPeakContextOf(end),
|
|
202
|
+
...runPromptFields(end),
|
|
203
|
+
...runCutOffField(end),
|
|
146
204
|
contextWindow: seamContextWindowForRole(loadConfigSafe(root).config ?? defaultConfig(), role, root),
|
|
147
205
|
events: block,
|
|
148
206
|
merge,
|
|
149
207
|
mergedSha: mergedShaOf(result, merge, scoped, block),
|
|
150
|
-
|
|
151
|
-
// end's), spread so a run without both an end and a landing event — or a landing before its
|
|
152
|
-
// end, or a negative difference — keeps today's exact payload (no key).
|
|
153
|
-
...(end !== null && mergedEvent !== null && mergedEvent.ts - end.ts >= 0
|
|
154
|
-
? { mergeAfterRunMs: mergedEvent.ts - end.ts }
|
|
155
|
-
: {}),
|
|
208
|
+
...runMergeLatencyField(end, mergedEvent),
|
|
156
209
|
reverted: null,
|
|
157
210
|
};
|
|
158
211
|
detail.reverted = revertedOf(detail, all);
|
|
@@ -31,30 +31,41 @@ export function runDetailStat(root, d) {
|
|
|
31
31
|
export function runDetailPatch(root, d) {
|
|
32
32
|
return runCommitShow(root, runCommitSha(d), "--patch");
|
|
33
33
|
}
|
|
34
|
-
/** The
|
|
35
|
-
* marker
|
|
36
|
-
|
|
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 = [];
|
|
34
|
+
/** The run's result cell, always present: the outcome label when the run finished, the
|
|
35
|
+
* in-flight/unpaired marker when it has not. Owns the first summary cell. */
|
|
36
|
+
function runResultCell(d) {
|
|
46
37
|
if (d.endTs === null)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
return d.startTs === null ? "unpaired" : "in flight";
|
|
39
|
+
return d.result ? eventTypeLabel(d.result) : "?";
|
|
40
|
+
}
|
|
41
|
+
/** The run's cut-off cell, the ceiling-truncation marker straight after the result cell. It
|
|
42
|
+
* only rides a completed end (cutOff is set from the run_end), so it can never follow in
|
|
43
|
+
* flight/unpaired. */
|
|
44
|
+
function runCutOffCell(d) {
|
|
50
45
|
// The ceiling-truncation marker, straight after the result cell. It only rides a
|
|
51
46
|
// completed end (cutOff is set from the run_end), so it can never follow in flight/unpaired.
|
|
52
|
-
if (d.cutOff
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
if (d.cutOff !== true)
|
|
48
|
+
return null;
|
|
49
|
+
return "cut off at the context ceiling";
|
|
50
|
+
}
|
|
51
|
+
/** The run's duration cell. */
|
|
52
|
+
function runDurationCell(d) {
|
|
53
|
+
if (d.durationMs === null)
|
|
54
|
+
return null;
|
|
55
|
+
return shortSpanPhrase(d.durationMs);
|
|
56
|
+
}
|
|
57
|
+
/** The run's usage cell. */
|
|
58
|
+
function runUsageCell(d) {
|
|
59
|
+
if (d.usage === "")
|
|
60
|
+
return null;
|
|
61
|
+
return d.usage;
|
|
62
|
+
}
|
|
63
|
+
/** The run's prompt cell: the prompt-token total and the share the backend served from cache,
|
|
64
|
+
* the input side a cost-only header hides. The `${n}% cached` wording and rounding are the
|
|
65
|
+
* usage report's `Prompt tokens by role` share; a run with prompt tokens but no cache reads
|
|
66
|
+
* shows no parenthetical, and a run that reported neither keeps today's header byte-identical
|
|
67
|
+
* (the omit-when-zero convention). */
|
|
68
|
+
function runPromptCell(d) {
|
|
58
69
|
// The run's prompt-token total and the share the backend served from cache, the input
|
|
59
70
|
// side a cost-only header hides. The `${n}% cached` wording and rounding are the usage
|
|
60
71
|
// report's `Prompt tokens by role` share; a run with prompt tokens but no cache reads
|
|
@@ -62,37 +73,71 @@ export function renderRunDetail(d) {
|
|
|
62
73
|
// byte-identical (the omit-when-zero convention).
|
|
63
74
|
const prompt = d.promptTokens ?? 0;
|
|
64
75
|
const cached = Math.min(d.cacheReadTokens ?? 0, prompt);
|
|
65
|
-
if (prompt
|
|
66
|
-
|
|
76
|
+
if (prompt <= 0)
|
|
77
|
+
return null;
|
|
78
|
+
return cached > 0 ? `prompt ${compactTokens(prompt)} tok (${Math.round((100 * cached) / prompt)}% cached)` : `prompt ${compactTokens(prompt)} tok`;
|
|
79
|
+
}
|
|
80
|
+
/** The run's pre-edit prompt-token share, the `… before first edit` signal a cost-only header
|
|
81
|
+
* hides: how much of the prompt budget went out before the run made its first edit. Clamped
|
|
82
|
+
* to the total (run-detail-data.ts), written after the prompt cell and before the
|
|
83
|
+
* peak-context cell; a run that reported no pre-edit reading — and an in-flight run — keeps
|
|
84
|
+
* today's header byte-identical. */
|
|
85
|
+
function runPreEditCell(d) {
|
|
67
86
|
// The run's pre-edit prompt-token share, the `… before first edit` signal a cost-only
|
|
68
87
|
// header hides: how much of the prompt budget went out before the run made its first
|
|
69
88
|
// edit. Clamped to the total (run-detail-data.ts), written after the prompt cell and
|
|
70
89
|
// before the peak-context cell; a run that reported no pre-edit reading — and an
|
|
71
90
|
// in-flight run — keeps today's header byte-identical.
|
|
91
|
+
const prompt = d.promptTokens ?? 0;
|
|
72
92
|
const preEdit = Math.min(d.preEditPromptTokens ?? 0, prompt);
|
|
73
|
-
if (preEdit
|
|
74
|
-
|
|
93
|
+
if (preEdit <= 0)
|
|
94
|
+
return null;
|
|
95
|
+
return `pre-edit ${compactTokens(preEdit)} tok`;
|
|
96
|
+
}
|
|
97
|
+
/** The run's peak-context cell against the seam's declared window, the one header every surface
|
|
98
|
+
* renders. Omitted when the run reported no peak (byte-identical to today's header). */
|
|
99
|
+
function runPeakContextCell(d) {
|
|
75
100
|
// The run's peak context against the seam's declared window, the one header every surface
|
|
76
101
|
// renders. Omitted when the run reported no peak (byte-identical to today's header).
|
|
77
|
-
if ((d.peakContextTokens ?? 0)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
102
|
+
if ((d.peakContextTokens ?? 0) <= 0)
|
|
103
|
+
return null;
|
|
104
|
+
return `peak ctx ${contextShareLabel(d.peakContextTokens ?? 0, d.contextWindow)}`;
|
|
105
|
+
}
|
|
106
|
+
/** The run's model cell, the model the run started on. */
|
|
107
|
+
function runModelCell(d) {
|
|
108
|
+
if (d.model === null)
|
|
109
|
+
return null;
|
|
110
|
+
return d.model;
|
|
111
|
+
}
|
|
112
|
+
/** The summary cells in today's push order — result, cut-off, duration, usage, prompt,
|
|
113
|
+
* pre-edit, peak-context, model — each present only when its guard omits nothing. */
|
|
114
|
+
function runSummaryCells(d) {
|
|
115
|
+
return [runResultCell(d), runCutOffCell(d), runDurationCell(d), runUsageCell(d), runPromptCell(d), runPreEditCell(d), runPeakContextCell(d), runModelCell(d)].filter((cell) => cell !== null);
|
|
116
|
+
}
|
|
117
|
+
/** The header's ` · `-separated suffix cells after the summary: the pinned commit, `merged
|
|
118
|
+
* as`, end-of-run landing latency, and the deliberate-revert verdict. */
|
|
119
|
+
function runHeaderSuffixCells(d) {
|
|
81
120
|
const sha = runCommitSha(d);
|
|
82
|
-
|
|
121
|
+
const cells = [];
|
|
83
122
|
if (sha !== null)
|
|
84
|
-
|
|
123
|
+
cells.push(`commit ${shortSha(sha)}`);
|
|
85
124
|
if (d.mergedSha !== null && d.mergedSha !== sha)
|
|
86
|
-
|
|
125
|
+
cells.push(`merged as ${shortSha(d.mergedSha)}`);
|
|
87
126
|
// The end-of-run → on-main landing latency, the per-change twin of the day report's `Merge
|
|
88
127
|
// wait median`. shortSpanPhrase is the duration cell's renderer, so both spans read alike; a
|
|
89
128
|
// run without the field (rejected/failed merge, in flight, landing rotated out, no-op
|
|
90
129
|
// fast-forward) keeps today's header byte-identical.
|
|
91
130
|
if (d.mergeAfterRunMs !== undefined)
|
|
92
|
-
|
|
131
|
+
cells.push(`merged ${shortSpanPhrase(d.mergeAfterRunMs)} after the run`);
|
|
93
132
|
if (d.reverted !== null) {
|
|
94
|
-
|
|
133
|
+
cells.push(`reverted ${shortSha(d.reverted.sha)} (by operator)`); // vocabulary-ok: the deliberate-revert writer this header names
|
|
95
134
|
}
|
|
135
|
+
return cells;
|
|
136
|
+
}
|
|
137
|
+
/** The trail lines: the run's events through the shared formatEvent, with the merge outcome
|
|
138
|
+
* line placed before the first event when it predates it (a leftover-recovery run) and after
|
|
139
|
+
* the last otherwise, keeping the trail in time order. */
|
|
140
|
+
function runTrailLines(d) {
|
|
96
141
|
const trail = d.events.map(formatEvent);
|
|
97
142
|
if (d.merge !== null) {
|
|
98
143
|
const first = d.events[0];
|
|
@@ -101,7 +146,19 @@ export function renderRunDetail(d) {
|
|
|
101
146
|
else
|
|
102
147
|
trail.push(formatEvent(d.merge));
|
|
103
148
|
}
|
|
104
|
-
return
|
|
149
|
+
return trail;
|
|
150
|
+
}
|
|
151
|
+
/** The human view of a RunDetail: one summary line assembled from the per-cell and
|
|
152
|
+
* per-trail-step helpers — the result (or in-flight/unpaired marker), cut-off, duration,
|
|
153
|
+
* usage, prompt, pre-edit, peak-context and model cells, then the committed sha, `merged as`,
|
|
154
|
+
* merge-latency and reverted suffix cells — and each event through formatEvent. A queued run
|
|
155
|
+
* whose merge has finished shows that verdict in the header and ends with the merge's outcome
|
|
156
|
+
* line; a leftover-recovery run's verdict is the failed attempt it re-queued
|
|
157
|
+
* (resolveQueuedResult), logged before the run began, so that outcome line leads the trail
|
|
158
|
+
* instead — the trail stays in time order. */
|
|
159
|
+
export function renderRunDetail(d) {
|
|
160
|
+
const header = [`${roleLabel(d.role)} run #${d.run} — ${runSummaryCells(d).join(" · ")}`, ...runHeaderSuffixCells(d)].join(" · ");
|
|
161
|
+
return [header, ...runTrailLines(d)].join("\n");
|
|
105
162
|
}
|
|
106
163
|
/** The transcript block of `--transcript`: the rendered lines of the agent runs that started in the
|
|
107
164
|
* run's window, or `found: []` plus the `note` the text view prints in their place. The
|