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
|
@@ -57,15 +57,11 @@ 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
|
-
/** The
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
|
|
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 }) {
|
|
60
|
+
/** The positional and role half of parseRunDetailInvocation's validation: the bare/last arity,
|
|
61
|
+
* the canonical role id, the unknown-role guard (knownRoleIdsCached) and the three arity
|
|
62
|
+
* `fail`s — one name so the whole positional surface is found by this helper. Returns whether
|
|
63
|
+
* the call is bare (`--last` with no role) and the resolved role. */
|
|
64
|
+
function runDetailPositionals(root, positionals, last) {
|
|
69
65
|
// Positional arity by form: cli.ts has already peeled the flags off and gated them (only
|
|
70
66
|
// --json and --last are admitted), so anything left over that does not fit the chosen form
|
|
71
67
|
// is a mistake. The role is checked first, so a typo is named whatever the count; then
|
|
@@ -91,6 +87,13 @@ function parseRunDetailInvocation(root, positionals, { last, transcript, prompt,
|
|
|
91
87
|
}
|
|
92
88
|
fail(`usage: ${RUN_DETAIL_USAGE}`);
|
|
93
89
|
}
|
|
90
|
+
return { bare, role };
|
|
91
|
+
}
|
|
92
|
+
/** The flag-dependency half of parseRunDetailInvocation's validation: the four `fail` guards —
|
|
93
|
+
* `--prompt`/`--grep` need `--transcript`, `--result`/`--model` need `--last` — one name so the
|
|
94
|
+
* whole dependency surface is found by this helper. */
|
|
95
|
+
function checkRunDetailFlagDeps(flags) {
|
|
96
|
+
const { last, transcript, prompt, grep, result, model } = flags;
|
|
94
97
|
if (prompt && !transcript)
|
|
95
98
|
fail(`history --prompt needs --transcript (usage: ${RUN_DETAIL_USAGE})`);
|
|
96
99
|
if (grep !== null && !transcript)
|
|
@@ -99,10 +102,67 @@ function parseRunDetailInvocation(root, positionals, { last, transcript, prompt,
|
|
|
99
102
|
fail(`history --result needs --last on a one-run trail (usage: ${RUN_DETAIL_USAGE})`);
|
|
100
103
|
if (model !== null && !last)
|
|
101
104
|
fail(`history --model needs --last on a one-run trail (usage: ${RUN_DETAIL_USAGE})`);
|
|
105
|
+
}
|
|
106
|
+
/** The invocation half of cmdRunDetail: rule out the arity and flag mistakes before any run is
|
|
107
|
+
* resolved, so the shared unknown-role wording and the usage string are the whole validation
|
|
108
|
+
* surface, found by this one name. Succeeds only for a well-formed call; returns the parsed
|
|
109
|
+
* bare/role/classes/models for the resolver. Delegates the positional/role arity to
|
|
110
|
+
* runDetailPositionals and the flag-dependency guards to checkRunDetailFlagDeps, then parses
|
|
111
|
+
* the --result class and --model selector lists. */
|
|
112
|
+
function parseRunDetailInvocation(root, positionals, { last, transcript, prompt, grep, result, model }) {
|
|
113
|
+
const { bare, role } = runDetailPositionals(root, positionals, last);
|
|
114
|
+
checkRunDetailFlagDeps({ last, transcript, prompt, grep, result, model });
|
|
102
115
|
const classes = result !== null ? parseHistoryResultList(result) : null;
|
|
103
116
|
const models = model !== null ? parseHistoryModelList(model) : null;
|
|
104
117
|
return { bare, role, classes, models };
|
|
105
118
|
}
|
|
119
|
+
/** The not-found branch of cmdRunDetail for the --last form: the scan came up empty, not found
|
|
120
|
+
* by construction, the last-form wording — `sayJson(null)` under --json, else
|
|
121
|
+
* runNotFoundLastMessage. */
|
|
122
|
+
function runDetailLastMiss(parsed, role, json) {
|
|
123
|
+
// The --last scan came up empty: not found by construction, the last-form wording.
|
|
124
|
+
if (json)
|
|
125
|
+
sayJson(null);
|
|
126
|
+
else
|
|
127
|
+
say(runNotFoundLastMessage(parsed.bare ? null : role, parsed.classes, parsed.models));
|
|
128
|
+
}
|
|
129
|
+
/** The not-found branch of cmdRunDetail for the numbered form: not found stays exit 0 in both
|
|
130
|
+
* renderings, but --json still owes a parseable document in every exit-0 case (`null`), else
|
|
131
|
+
* the runNotFoundMessage prose line. */
|
|
132
|
+
function runDetailNumberMiss(role, runNumber, json) {
|
|
133
|
+
// Not found stays exit 0 in both renderings, but --json still owes a parseable document in
|
|
134
|
+
// every exit-0 case (the report --json precedent, the invariant this command's own doc
|
|
135
|
+
// comment claims): `null` is the JSON answer for a single-record lookup that missed — the
|
|
136
|
+
// prose line would end a jq pipe mid-team-incident.
|
|
137
|
+
if (json)
|
|
138
|
+
sayJson(null);
|
|
139
|
+
else
|
|
140
|
+
say(runNotFoundMessage(role, runNumber));
|
|
141
|
+
}
|
|
142
|
+
/** The render half of cmdRunDetail: build the --stat/--patch footer and the --transcript lines,
|
|
143
|
+
* then emit the --json payload or the human-rendered trail (renderRunDetail plus the footer and
|
|
144
|
+
* transcript text). */
|
|
145
|
+
async function emitRunDetail(root, role, runNumber, detail, json, opts) {
|
|
146
|
+
// --json swaps the renderer for the collector's own payload, exactly as history --json: a
|
|
147
|
+
// JSON document in every exit-0 case, never prose.
|
|
148
|
+
const footer = {};
|
|
149
|
+
if (opts.stat)
|
|
150
|
+
footer.stat = await runDetailStat(root, detail);
|
|
151
|
+
if (opts.patch)
|
|
152
|
+
footer.patch = await runDetailPatch(root, detail);
|
|
153
|
+
const transcriptLines = opts.transcript
|
|
154
|
+
? runTranscriptLines(root, role, runNumber, detail, { includePrompts: opts.prompt, grep: opts.grep ?? undefined })
|
|
155
|
+
: null;
|
|
156
|
+
if (json) {
|
|
157
|
+
sayJson({ ...detail, ...footer, ...(transcriptLines === null ? {} : { transcript: transcriptLines.found }) });
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
const transcriptText = transcriptLines === null
|
|
161
|
+
? []
|
|
162
|
+
: [transcriptLines.found.length > 0 ? transcriptLines.found.join("\n") : transcriptLines.note];
|
|
163
|
+
say([renderRunDetail(detail), ...Object.values(footer), ...transcriptText].join("\n\n"));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
106
166
|
/** The resolution half of cmdRunDetail: pick the run to open and read its detail. Bare `--last`
|
|
107
167
|
* takes the newest run of any role (its role from the row); `--last` with a role the newest run
|
|
108
168
|
* by number; the numbered path the user's positive integer. An empty or unreadable scan is null
|
|
@@ -150,41 +210,12 @@ export async function cmdRunDetail(root, positionals, json, { last = false, stat
|
|
|
150
210
|
const parsed = parseRunDetailInvocation(root, positionals, { last, transcript, prompt, grep, result, model });
|
|
151
211
|
const { role, runNumber, detail } = resolveRunDetailTarget(root, parsed, positionals, last);
|
|
152
212
|
if (runNumber === null) {
|
|
153
|
-
|
|
154
|
-
if (json)
|
|
155
|
-
sayJson(null);
|
|
156
|
-
else
|
|
157
|
-
say(runNotFoundLastMessage(parsed.bare ? null : role, parsed.classes, parsed.models));
|
|
213
|
+
runDetailLastMiss(parsed, role, json);
|
|
158
214
|
return;
|
|
159
215
|
}
|
|
160
216
|
if (detail === null) {
|
|
161
|
-
|
|
162
|
-
// every exit-0 case (the report --json precedent, the invariant this command's own doc
|
|
163
|
-
// comment claims): `null` is the JSON answer for a single-record lookup that missed — the
|
|
164
|
-
// prose line would end a jq pipe mid-team-incident.
|
|
165
|
-
if (json)
|
|
166
|
-
sayJson(null);
|
|
167
|
-
else
|
|
168
|
-
say(runNotFoundMessage(role, runNumber));
|
|
217
|
+
runDetailNumberMiss(role, runNumber, json);
|
|
169
218
|
return;
|
|
170
219
|
}
|
|
171
|
-
|
|
172
|
-
// JSON document in every exit-0 case, never prose.
|
|
173
|
-
const footer = {};
|
|
174
|
-
if (stat)
|
|
175
|
-
footer.stat = await runDetailStat(root, detail);
|
|
176
|
-
if (patch)
|
|
177
|
-
footer.patch = await runDetailPatch(root, detail);
|
|
178
|
-
const transcriptLines = transcript
|
|
179
|
-
? runTranscriptLines(root, role, runNumber, detail, { includePrompts: prompt, grep: grep ?? undefined })
|
|
180
|
-
: null;
|
|
181
|
-
if (json) {
|
|
182
|
-
sayJson({ ...detail, ...footer, ...(transcriptLines === null ? {} : { transcript: transcriptLines.found }) });
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
const transcriptText = transcriptLines === null
|
|
186
|
-
? []
|
|
187
|
-
: [transcriptLines.found.length > 0 ? transcriptLines.found.join("\n") : transcriptLines.note];
|
|
188
|
-
say([renderRunDetail(detail), ...Object.values(footer), ...transcriptText].join("\n\n"));
|
|
189
|
-
}
|
|
220
|
+
await emitRunDetail(root, role, runNumber, detail, json, { stat, patch, transcript, prompt, grep });
|
|
190
221
|
}
|
|
@@ -23,7 +23,7 @@ import { readFeedbackFilings, renderFilingsBlock } from "./feedback-filings.js";
|
|
|
23
23
|
import { roleNotesPath } from "../paths.js";
|
|
24
24
|
import * as path from "node:path";
|
|
25
25
|
import { pathTokens } from "../text/text.js";
|
|
26
|
-
import { isDirectory, readTextOrNull, statOrNull } from "../files/files.js";
|
|
26
|
+
import { isDirectory, readRegularTextOrNull, readTextOrNull, statOrNull } from "../files/files.js";
|
|
27
27
|
import { dayKey } from "../text/datetime.js";
|
|
28
28
|
import { appendPromptNote, prependPromptNote } from "../text/format.js";
|
|
29
29
|
import { ROLE_NOTES_MAX_BYTES } from "../agent/role-notes-write.js";
|
|
@@ -55,9 +55,11 @@ function missingEntryPaths(root, file, range) {
|
|
|
55
55
|
* brief text reader apply to their hand-editable files. A UTF-8 character is never
|
|
56
56
|
* fewer than one byte, so a note the tool accepted (≤ 4096 bytes) is always ≤ 4096 chars
|
|
57
57
|
* and never trips this bound; only an over-cap file is cut, with truncateWithNote's visible
|
|
58
|
-
* marker naming the loss.
|
|
58
|
+
* marker naming the loss. A symlinked note file reads as absent (readRegularTextOrNull never
|
|
59
|
+
* follows a symlink out of the worktree, INV-G3/G4's lstat policy): a hostile note that is a
|
|
60
|
+
* symlink cannot pull an arbitrary host file's text into this role's prompt. */
|
|
59
61
|
function readRoleNote(root, role) {
|
|
60
|
-
const text =
|
|
62
|
+
const text = readRegularTextOrNull(roleNotesPath(root, role));
|
|
61
63
|
return text === null ? undefined : truncateWithNote(text, ROLE_NOTES_MAX_BYTES, "role notes");
|
|
62
64
|
}
|
|
63
65
|
/** The bugfix run's tally of checks the merge gate excused as flaky in the last day, or
|
|
@@ -199,55 +201,40 @@ function collectRoleRequest(root, role, preview) {
|
|
|
199
201
|
function collectSelfcheckDigest(root, base) {
|
|
200
202
|
return base === "selfcheck" || base === "feedback" ? selfcheckDigest(root) : undefined;
|
|
201
203
|
}
|
|
202
|
-
/**
|
|
203
|
-
* block on a missing or unreadable ledger (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
204
|
+
/** One observer-ledger block: rendered from disk only for its owning role, degrading to no
|
|
205
|
+
* block on a missing or unreadable ledger (a throw) so the run prompt stays byte-unchanged in
|
|
206
|
+
* the common first-run case. The `qa` (flow coverage), `ci` (parity) and `feedback` (filings)
|
|
207
|
+
* ledgers are runtime state a fresh session must not depend on, and the three collect*Block
|
|
208
|
+
* functions each rendered their ledger through this same try/catch guard before the helper. */
|
|
209
|
+
function runtimeLedgerBlock(base, role, render) {
|
|
210
|
+
if (base !== role)
|
|
211
|
+
return undefined;
|
|
212
|
+
try {
|
|
213
|
+
return render();
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return undefined;
|
|
216
217
|
}
|
|
217
|
-
return coverage;
|
|
218
218
|
}
|
|
219
|
-
/** The `
|
|
220
|
-
*
|
|
221
|
-
*
|
|
219
|
+
/** The `qa` observer's flow-coverage ledger, rerendered from disk each run (the flow rotation
|
|
220
|
+
* needs a memory of what it last exercised — every run is a fresh session, and a passing cheap
|
|
221
|
+
* check leaves nothing in the repo), degrading to no block (plans/observer-roles.md 2/2). */
|
|
222
|
+
function collectQaCoverageBlock(root, base) {
|
|
223
|
+
return runtimeLedgerBlock(base, "qa", () => renderCoverageBlock(readQaCoverage(root)));
|
|
224
|
+
}
|
|
225
|
+
/** The `ci` observer's parity block: what the last run covered (tip and lock hash) so a fresh
|
|
226
|
+
* session can decide nothing-to-do without re-running the whole suite (part 3/3). Degrades to
|
|
227
|
+
* no block exactly like qa's. */
|
|
222
228
|
function collectCiParityBlock(root, config, base) {
|
|
223
|
-
|
|
224
|
-
if (base === "ci") {
|
|
225
|
-
try {
|
|
226
|
-
ciParity = parityBlockFor(root, config);
|
|
227
|
-
}
|
|
228
|
-
catch {
|
|
229
|
-
ciParity = undefined;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return ciParity;
|
|
229
|
+
return runtimeLedgerBlock(base, "ci", () => parityBlockFor(root, config));
|
|
233
230
|
}
|
|
234
231
|
/** The `feedback` observer's per-install filing ledger: what this install already filed as
|
|
235
232
|
* GitHub issues on znation/tumwater, so the fresh session's charter dedupes against a
|
|
236
|
-
* "previously filed" block instead of the network alone.
|
|
237
|
-
*
|
|
233
|
+
* "previously filed" block instead of the network alone. Degrades to no block
|
|
234
|
+
* (plans/feedback-role.md 2/2); the render returns undefined for an empty ledger, so the
|
|
235
|
+
* prompt is unchanged in the common first-run case. */
|
|
238
236
|
function collectFeedbackFilingsBlock(root, base) {
|
|
239
|
-
|
|
240
|
-
// first-run case.
|
|
241
|
-
let filings;
|
|
242
|
-
if (base === "feedback") {
|
|
243
|
-
try {
|
|
244
|
-
filings = renderFilingsBlock(readFeedbackFilings(root));
|
|
245
|
-
}
|
|
246
|
-
catch {
|
|
247
|
-
filings = undefined;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return filings;
|
|
237
|
+
return runtimeLedgerBlock(base, "feedback", () => renderFilingsBlock(readFeedbackFilings(root)));
|
|
251
238
|
}
|
|
252
239
|
/** The clean role's deterministic backlog repair: stranded plan headings in the primary
|
|
253
240
|
* checkout's PLANS.md (plans, part 3/4), rendered like the digest and coverage blocks. */
|
|
@@ -258,7 +245,7 @@ function collectBacklogStructureBlock(root, base) {
|
|
|
258
245
|
/** Read the role's own notes plus their saved day and live backlog state (notes-backlog.ts),
|
|
259
246
|
* returning all three in one place. */
|
|
260
247
|
function readPromptNotes(root, role) {
|
|
261
|
-
// The role's notes (
|
|
248
|
+
// The role's own notes (readRoleNote): its earlier runs' note. A missing,
|
|
262
249
|
// empty, or unreadable file yields undefined, so the block is omitted; the write
|
|
263
250
|
// instruction in buildRunPrompt is present either way. The director branch above never
|
|
264
251
|
// reads this path, so it carries no notes.
|
|
@@ -312,15 +299,12 @@ function collectRoleDirectives(root, config, base, check) {
|
|
|
312
299
|
: undefined;
|
|
313
300
|
return { planTargetNote, flakyChecks, noCheck };
|
|
314
301
|
}
|
|
315
|
-
|
|
316
|
-
* in its current order, and return the finished prompt. */
|
|
317
|
-
function appendRunEpilogue(root, role, state, prompt, userPrompt, rejectedReview) {
|
|
302
|
+
function appendAssignmentNotes(prompt, root, role, claim, userPrompt) {
|
|
318
303
|
// A work instance holding a claim is told which entry is its one task (plans/parallel-work-
|
|
319
304
|
// instances.md "Claims", part 4/7) — the harness picked it, so the charter's choosing step
|
|
320
305
|
// is replaced. A run that dequeued a user request runs that request instead and keeps the
|
|
321
306
|
// claim for the next non-request run. The range is looked up by key in the current checkout,
|
|
322
307
|
// so it survives a moved section boundary.
|
|
323
|
-
const claim = role === DIRECTOR_ROLE ? undefined : state.claim;
|
|
324
308
|
if (claim && userPrompt === null) {
|
|
325
309
|
const range = eligibleEntries(root, role).find((e) => e.key === claim.key);
|
|
326
310
|
prompt = appendPromptNote(prompt, buildAssignmentNote(claim, range, range ? missingEntryPaths(root, claim.file, range) : []));
|
|
@@ -328,20 +312,32 @@ function appendRunEpilogue(root, role, state, prompt, userPrompt, rejectedReview
|
|
|
328
312
|
if (merged.length > 0)
|
|
329
313
|
prompt = appendPromptNote(prompt, buildMergedPrerequisitesNote(merged));
|
|
330
314
|
}
|
|
315
|
+
return prompt;
|
|
316
|
+
}
|
|
317
|
+
function appendWaitingPartsNote(prompt, root, claim, userPrompt) {
|
|
331
318
|
if (claim && userPrompt === null && claim.file === "PLANS.md") {
|
|
332
319
|
const waiting = waitingPartRanges(root, claim.title);
|
|
333
320
|
if (waiting.length > 0)
|
|
334
321
|
prompt = appendPromptNote(prompt, buildWaitingPartsNote(waiting));
|
|
335
322
|
}
|
|
323
|
+
return prompt;
|
|
324
|
+
}
|
|
325
|
+
function appendHeldElsewhereNote(prompt, role, claim, userPrompt, state) {
|
|
336
326
|
if (!claim && userPrompt === null && role !== DIRECTOR_ROLE && state.heldElsewhere?.length) {
|
|
337
327
|
prompt = appendPromptNote(prompt, buildHeldEntriesNote(state.heldElsewhere));
|
|
338
328
|
}
|
|
329
|
+
return prompt;
|
|
330
|
+
}
|
|
331
|
+
function prependRejectedReviewNote(prompt, role, rejectedReview) {
|
|
339
332
|
// The director builds its prompt through buildDirectorPrompt, which does not take the rejected
|
|
340
333
|
// note, so the standing rejection leads its prompt here instead (a director-authored change can
|
|
341
334
|
// be rejected too). Kept uniform with the authoring roles: the reasons open the run.
|
|
342
335
|
if (rejectedReview !== undefined && role === DIRECTOR_ROLE) {
|
|
343
336
|
prompt = prependPromptNote(prompt, rejectedReview);
|
|
344
337
|
}
|
|
338
|
+
return prompt;
|
|
339
|
+
}
|
|
340
|
+
function appendRunMemoryNotes(prompt, state) {
|
|
345
341
|
// Likewise a change leftover recovery discarded as unmergeable: named until the role queues
|
|
346
342
|
// its next change (the discarding run itself appends it after recovery — see runOnce).
|
|
347
343
|
const discard = state.conflictDiscard;
|
|
@@ -355,6 +351,23 @@ function appendRunEpilogue(root, role, state, prompt, userPrompt, rejectedReview
|
|
|
355
351
|
}
|
|
356
352
|
return prompt;
|
|
357
353
|
}
|
|
354
|
+
/** Append the shared claim/held/discard/cut-off tail — identical for a director and a role —
|
|
355
|
+
* in its current order, and return the finished prompt. It delegates each note group to a
|
|
356
|
+
* named module-private helper: appendAssignmentNotes (which folds in the merged-prerequisite
|
|
357
|
+
* follow-up), appendWaitingPartsNote, appendHeldElsewhereNote, prependRejectedReviewNote and
|
|
358
|
+
* appendRunMemoryNotes. */
|
|
359
|
+
function appendRunEpilogue(root, role, state, prompt, userPrompt, rejectedReview) {
|
|
360
|
+
// The shared cross-run tail every role prompt carries, ordinally: the claim assignment (with
|
|
361
|
+
// its merged-prerequisite and waiting-part follow-ups), the held-elsewhere note, the
|
|
362
|
+
// director's leading rejection, and the discard and cut-off run memory.
|
|
363
|
+
const claim = role === DIRECTOR_ROLE ? undefined : state.claim;
|
|
364
|
+
prompt = appendAssignmentNotes(prompt, root, role, claim, userPrompt);
|
|
365
|
+
prompt = appendWaitingPartsNote(prompt, root, claim, userPrompt);
|
|
366
|
+
prompt = appendHeldElsewhereNote(prompt, role, claim, userPrompt, state);
|
|
367
|
+
prompt = prependRejectedReviewNote(prompt, role, rejectedReview);
|
|
368
|
+
prompt = appendRunMemoryNotes(prompt, state);
|
|
369
|
+
return prompt;
|
|
370
|
+
}
|
|
358
371
|
/** Assemble the prompt a role's next run operates on — the whole "what should this run see"
|
|
359
372
|
* concern, split out of runner.ts so the runner owns only lifecycle and the prompt owns only
|
|
360
373
|
* content. Returns null when the role has nothing to run this run (a director with an empty
|
|
@@ -29,12 +29,10 @@ export function planWithSalvagedWork(role, plan, restored, note) {
|
|
|
29
29
|
restored,
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
function resolveRunStartDecision(opts) {
|
|
37
|
-
const s = opts.state;
|
|
32
|
+
/** A run-start's first phase: read the last merge's failure and clear it, so a merge that
|
|
33
|
+
* failed non-terminally (kept its pin) still feeds the error streak when this run's recovery
|
|
34
|
+
* re-queues that pin (finishRecoveryRun). */
|
|
35
|
+
function readPriorMergeFailure(s) {
|
|
38
36
|
// The last merge's failure, read before the clear below: a merge that failed
|
|
39
37
|
// non-terminally kept its pin (the merger's own vocabulary — review_error, merge_conflict,
|
|
40
38
|
// merge_blocked — with the detail it wrote into `lastError`), and when this run's recovery
|
|
@@ -43,6 +41,13 @@ function resolveRunStartDecision(opts) {
|
|
|
43
41
|
? (s.lastError ?? `merge failed: ${s.lastResult}`)
|
|
44
42
|
: undefined;
|
|
45
43
|
s.lastError = undefined;
|
|
44
|
+
return priorMergeFailure;
|
|
45
|
+
}
|
|
46
|
+
/** A run-start's second phase: resolve, from the session ledger, which session a continue
|
|
47
|
+
* would reach and whether the backend can resume it, capturing the pending resume cause
|
|
48
|
+
* before the flags are consumed. */
|
|
49
|
+
function resolveResumeTarget(opts) {
|
|
50
|
+
const s = opts.state;
|
|
46
51
|
// A run interrupted by a harness shutdown left its pi session and its worktree's
|
|
47
52
|
// uncommitted edits in place: resume that session instead of starting fresh. The flag
|
|
48
53
|
// is consumed here so a resume that fails falls back to a normal fresh run; another
|
|
@@ -69,7 +74,6 @@ function resolveRunStartDecision(opts) {
|
|
|
69
74
|
const resumable = resumePending && !resumeMissing
|
|
70
75
|
? backendSessionResumable(dir, { kind: "author", agent }, opts.claudeConfigDir)
|
|
71
76
|
: false;
|
|
72
|
-
const resumableSession = resumable;
|
|
73
77
|
// A newest line our backend cannot resume is not ours to continue: another kind's or
|
|
74
78
|
// another agent's session, OR our own author line whose session the backend no longer holds
|
|
75
79
|
// (a `--resume` that answered "No conversation found" mid-run, leaving `resumeMissing`, or a
|
|
@@ -83,48 +87,82 @@ function resolveRunStartDecision(opts) {
|
|
|
83
87
|
// forward.
|
|
84
88
|
// Captured before the flag is consumed below; a stale cause must not leak into a later resume.
|
|
85
89
|
const pendingResumeCause = s.resumeCause;
|
|
90
|
+
return { latest, resumable, pendingResumeCause };
|
|
91
|
+
}
|
|
92
|
+
/** A run-start's third phase: decide whether this run resumes its interrupted session or hands
|
|
93
|
+
* off to a fresh session over the kept worktree. */
|
|
94
|
+
function resumeMode(latest, resumable, budgetCapped, imageReroute, resumeBlocked) {
|
|
95
|
+
const handoff = (latest !== null && !resumable) || budgetCapped || imageReroute;
|
|
96
|
+
// A budget-capped handback never resumes the session whose seam the cap moved off of, even
|
|
97
|
+
// when its transcript is still resumable: it hands off instead. An image reroute likewise.
|
|
98
|
+
const resuming = resumable && !resumeBlocked && !budgetCapped && !imageReroute;
|
|
99
|
+
const handingOff = handoff && !resumeBlocked;
|
|
100
|
+
return { resuming, handingOff };
|
|
101
|
+
}
|
|
102
|
+
/** A run-start's last phase: derive which cause reaches the bridge, naming a quiet-kill, the
|
|
103
|
+
* cut-off streak, the resume's own cause, or a plain restart/handoff. */
|
|
104
|
+
function resumeCauseFor(handingOff, budgetCapped, imageReroute, pendingResumeCause, cutOffStreak) {
|
|
105
|
+
// Why the resume: a named quiet-kill means the last run died on a stalled tool call (the
|
|
106
|
+
// bridge then warns against re-running it unchanged) or on the run deadline while still
|
|
107
|
+
// making progress (the bridge asks for the smallest finish against the same limit); a
|
|
108
|
+
// cut-off streak means the last run ran out of context and pi compacted the session (the
|
|
109
|
+
// bridge asks for the smallest finish); otherwise a shutdown/crash. A handoff names itself,
|
|
110
|
+
// except a budget-capped one, which names the cap that ended the run, and an image reroute,
|
|
111
|
+
// whose bridge names the blind read.
|
|
112
|
+
const resumeCause = handingOff
|
|
113
|
+
? budgetCapped
|
|
114
|
+
? "budget-capped"
|
|
115
|
+
: imageReroute
|
|
116
|
+
? "image-reroute"
|
|
117
|
+
: "handoff"
|
|
118
|
+
: pendingResumeCause ?? (cutOffStreak > 0 ? "cut-off" : "restart");
|
|
119
|
+
return resumeCause;
|
|
120
|
+
}
|
|
121
|
+
/** Resolve how a run starts, consuming the resume flags as a side effect (a flag is consumed
|
|
122
|
+
* here so a resume that fails falls back to a normal fresh run; another shutdown mid-resume
|
|
123
|
+
* sets it again) and reclaiming the interrupted run's re-queued prompt. Owns every read and
|
|
124
|
+
* write of the resume flags, so a new cause or handoff rule is added here. The body composes
|
|
125
|
+
* the four named phases — readPriorMergeFailure, resolveResumeTarget, resumeMode and
|
|
126
|
+
* resumeCauseFor — in order. */
|
|
127
|
+
function resolveRunStartDecision(opts) {
|
|
128
|
+
const s = opts.state;
|
|
129
|
+
const priorMergeFailure = readPriorMergeFailure(s);
|
|
130
|
+
const { latest, resumable, pendingResumeCause } = resolveResumeTarget(opts);
|
|
86
131
|
// A budget-capped API claude run is a handback onto the fallback seam: the kept worktree must
|
|
87
132
|
// survive, but the next run is a FRESH session — the fallback seam may be a different backend,
|
|
88
133
|
// and claude keeps no flags across `--resume` (plans/agent-backends.md §7, part 7a/7).
|
|
89
134
|
const budgetCapped = pendingResumeCause === "budget-capped";
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
135
|
+
// An image-blind run with an image-capable peer tier ends `aborted` with this cause
|
|
136
|
+
// (plans/image-input.md part 4/5): the next run runs a FRESH session on the TARGET seam
|
|
137
|
+
// over the kept worktree — never `--continue` — because the old session carries pi's
|
|
138
|
+
// "image omitted" note and the target may be a different backend.
|
|
139
|
+
const imageReroute = pendingResumeCause === "image-reroute";
|
|
94
140
|
// An interruption during the review gate leaves the author's work fully committed — there
|
|
95
141
|
// is nothing left to finish in its session. Recover (and re-review) the leftover commits
|
|
96
142
|
// via a fresh run instead: continuing the author session would burn a run on finished
|
|
97
143
|
// work, and any uncommitted edits are the reviewer's stray output, discarded by the fresh
|
|
98
144
|
// path's reset below.
|
|
99
145
|
const resumeBlocked = s.phase === "review";
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
146
|
+
const { resuming, handingOff } = resumeMode(latest, resumable, budgetCapped, imageReroute, resumeBlocked);
|
|
147
|
+
s.resumePending = false;
|
|
148
|
+
s.resumeCause = undefined;
|
|
149
|
+
s.resumeMissing = undefined;
|
|
104
150
|
// Reclaim the prompt the interrupted run re-queued for its resume (src/inbox/pending-prompt.ts):
|
|
105
151
|
// the resumed session still owns that request in its context, so this run's bookkeeping
|
|
106
152
|
// operates on the queue's copy. A handoff runs a FRESH session, so it does not reclaim — the
|
|
107
153
|
// fresh run dequeues the re-queued request like any other. Consumed even when this run does
|
|
108
154
|
// not resume, so a stale record never survives into a later resume.
|
|
109
155
|
opts.pending.reclaimForResume(s, resuming);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// making progress (the bridge asks for the smallest finish against the same limit); a
|
|
113
|
-
// cut-off streak means the last run ran out of context and pi compacted the session (the
|
|
114
|
-
// bridge asks for the smallest finish); otherwise a shutdown/crash. A handoff names itself,
|
|
115
|
-
// except a budget-capped one, which names the cap that ended the run.
|
|
116
|
-
const resumeCause = handingOff
|
|
117
|
-
? budgetCapped
|
|
118
|
-
? "budget-capped"
|
|
119
|
-
: "handoff"
|
|
120
|
-
: pendingResumeCause ?? ((s.cutOffStreak ?? 0) > 0 ? "cut-off" : "restart");
|
|
121
|
-
return { priorMergeFailure, resuming, handoff: handingOff, resumeCause };
|
|
156
|
+
const resumeCause = resumeCauseFor(handingOff, budgetCapped, imageReroute, pendingResumeCause, s.cutOffStreak ?? 0);
|
|
157
|
+
return { priorMergeFailure, resuming, handoff: handingOff, resumeCause, imageReroute };
|
|
122
158
|
}
|
|
123
159
|
/** Build the run's prompt from the resolved decision: the resume bridge on a resume, the
|
|
124
|
-
* handoff bridge on a handoff, the assembled prompt otherwise.
|
|
160
|
+
* handoff bridge (with the image-reroute facts) on a handoff, the assembled prompt otherwise.
|
|
161
|
+
* Reads the image-reroute facts before consuming their flag, and returns null only when a
|
|
125
162
|
* handoff/fresh assemble yields nothing. The raw user prompt is read by the caller only when a
|
|
126
163
|
* prompt exists. */
|
|
127
164
|
function buildRunStartPrompt(opts, d) {
|
|
165
|
+
const s = opts.state;
|
|
128
166
|
const agent = opts.agent ?? "pi";
|
|
129
167
|
let prompt;
|
|
130
168
|
if (d.resuming) {
|
|
@@ -132,11 +170,20 @@ function buildRunStartPrompt(opts, d) {
|
|
|
132
170
|
}
|
|
133
171
|
else {
|
|
134
172
|
const assembled = opts.runPrompt(agent);
|
|
173
|
+
// The image-reroute handoff note names the model the blind run was on and the image it
|
|
174
|
+
// read; those facts ride the one-shot marker, which is consumed here. A handoff's fresh
|
|
175
|
+
// session also needs the to-be-run model named for its own side of the note, but the
|
|
176
|
+
// bridge already carries the facts that matter.
|
|
177
|
+
const rerouteFacts = d.imageReroute && s.imageReroute
|
|
178
|
+
? { from: s.imageReroute.from ?? "the prior model", path: s.imageReroute.path ?? "an image" }
|
|
179
|
+
: undefined;
|
|
180
|
+
if (d.imageReroute)
|
|
181
|
+
s.imageReroute = undefined;
|
|
135
182
|
prompt =
|
|
136
183
|
assembled === null
|
|
137
184
|
? null
|
|
138
185
|
: d.handoff
|
|
139
|
-
? buildHandoffPrompt(opts.role, assembled, d.resumeCause)
|
|
186
|
+
? buildHandoffPrompt(opts.role, assembled, d.resumeCause, rerouteFacts)
|
|
140
187
|
: assembled;
|
|
141
188
|
}
|
|
142
189
|
return prompt;
|