tumwater 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +27 -13
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +6 -27
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +8 -0
- package/dist/src/agent/codex/codex-args.js +5 -5
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +26 -24
- package/dist/src/agent/opencode/opencode-catalog.js +85 -36
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +23 -27
- package/dist/src/agent/subscription-account.js +2 -83
- package/dist/src/backlog/backlog-archive.js +3 -4
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +56 -122
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +3 -2
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/baseline/main-guardrails.js +2 -2
- package/dist/src/baseline/main-watch.js +84 -50
- package/dist/src/brief/brief.js +26 -13
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/budget.js +4 -5
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-report.js +10 -0
- package/dist/src/build/build-check-scoped.js +40 -18
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/build/build-stage.js +6 -4
- package/dist/src/change/change-data.js +1 -4
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-completion-command.js +75 -0
- package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
- package/dist/src/cli/cli-doctor-command.js +103 -0
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +36 -14
- package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
- package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
- package/dist/src/cli/cli-query-commands.js +2 -2
- package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-role-command.js +2 -41
- package/dist/src/cli/cli-start-summary.js +4 -3
- package/dist/src/cli/cli-start-supervisor.js +190 -0
- package/dist/src/cli/cli-start.js +94 -244
- package/dist/src/cli/completion.js +85 -49
- package/dist/src/cli/follow-view.js +20 -2
- package/dist/src/cli/help.js +6 -13
- package/dist/src/cli.js +70 -176
- package/dist/src/collections.js +39 -27
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +23 -1
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +66 -40
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +163 -102
- package/dist/src/config/config-schema.js +10 -12
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +8 -72
- package/dist/src/config/config-validation.js +4 -39
- package/dist/src/config/config-views.js +26 -50
- package/dist/src/config/config-write.js +128 -102
- package/dist/src/config/config.js +5 -14
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +53 -120
- package/dist/src/doctor/doctor-backlog.js +16 -14
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +8 -8
- package/dist/src/doctor/doctor-claude-checks.js +70 -36
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +57 -117
- package/dist/src/doctor/doctor-fallback-checks.js +48 -26
- package/dist/src/doctor/doctor-holds.js +8 -47
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +122 -21
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +24 -11
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +44 -64
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +3 -5
- package/dist/src/events/{notify.js → event-notify.js} +6 -6
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +8 -0
- package/dist/src/failure/failure-cluster.js +9 -18
- package/dist/src/failure/failure-data.js +12 -8
- package/dist/src/failure/failure-render.js +46 -36
- package/dist/src/failure/failure-state-change.js +54 -120
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +32 -74
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +9 -8
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git-diff.js +6 -2
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-args.js +12 -0
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-config-commands.js +2 -64
- package/dist/src/gui/gui-endpoint-commands.js +1 -1
- package/dist/src/gui/gui-endpoint-common.js +19 -1
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +43 -48
- package/dist/src/gui/gui-prompt-commands.js +25 -25
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/gui/gui-server.js +1 -2
- package/dist/src/gui/gui-team-commands.js +2 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +54 -36
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-check-failures.js +12 -9
- package/dist/src/merge/merge-core.js +7 -32
- package/dist/src/merge/merge-drain.js +11 -13
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +11 -14
- package/dist/src/merge/merge-queue-render.js +3 -3
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-stack.js +17 -18
- package/dist/src/merge/merge-to-main.js +13 -58
- package/dist/src/merge/merge-verify.js +12 -21
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +80 -47
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +44 -69
- package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +18 -27
- package/dist/src/orchestrator/scheduling-skip.js +10 -14
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +10 -9
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +28 -1
- package/dist/src/pi/pi-listed-models.js +13 -0
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +41 -8
- package/dist/src/pi/pi-watchdogs.js +43 -15
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +51 -26
- package/dist/src/process/process.js +20 -12
- package/dist/src/process/run-marker.js +33 -15
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/gate-prompts.js +1 -44
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/redeploy/redeployer.js +21 -1
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/diff-rules.js +35 -21
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +48 -41
- package/dist/src/review/review-precheck.js +14 -4
- package/dist/src/review/review.js +48 -170
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/legacy-role-migration.js +6 -3
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +7 -46
- package/dist/src/roles/role-view.js +10 -33
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/ci-parity.js +19 -8
- package/dist/src/run/run-apply.js +12 -15
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +141 -90
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +128 -50
- package/dist/src/run/stage-check.js +60 -50
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/{leftover.js → runner-leftover.js} +9 -26
- package/dist/src/runner/runner-outcome.js +7 -2
- package/dist/src/runner/runner-pi.js +9 -22
- package/dist/src/runner/runner-recovery.js +5 -13
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
- package/dist/src/runner/runner-run.js +103 -50
- package/dist/src/runner/runner-state-load.js +18 -28
- package/dist/src/runner/runner-state.js +0 -14
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/backoff.js +8 -3
- package/dist/src/scheduling/claim-entries.js +2 -1
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-budget.js +4 -43
- package/dist/src/status/status-data.js +35 -39
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -18
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +105 -70
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
- package/dist/src/team/team-polls.js +53 -201
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/datetime.js +16 -7
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +29 -15
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +6 -5
- package/dist/src/ui/alert-feeds.js +46 -111
- package/dist/src/ui/badges.js +17 -88
- package/dist/src/ui/gui/gui-client-boot.js +2 -8
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-feed.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-operator.js +7 -5
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +12 -6
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +107 -44
- package/dist/src/ui/run-progress-model.js +20 -4
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +11 -30
- package/dist/src/ui/status-render.js +13 -24
- package/dist/src/ui/team-alert-backlog.js +14 -204
- package/dist/src/ui/team-alert-shared.js +6 -29
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +8 -15
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-backlog.js +2 -2
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +5 -10
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +167 -98
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +5 -4
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -230
- package/dist/src/ui/tui/tui-pane-history.js +67 -74
- package/dist/src/ui/tui/tui-pane-pending.js +288 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +15 -10
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +38 -33
- package/dist/src/ui/tui/tui.js +4 -7
- package/dist/src/verdict/fix-claim.js +107 -56
- package/dist/src/verdict/refusal.js +1 -1
- package/dist/src/verdict/reply-contract.js +3 -4
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/review/review-followup.js +0 -87
- package/dist/src/review/review-run.js +0 -395
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/cli/{brief-command.js → cli-brief-command.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
|
@@ -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;
|
|
@@ -3,7 +3,7 @@ import { changedFiles } from "../git/git-diff.js";
|
|
|
3
3
|
import { buildCommitMessage, commitTrailer, extractCommitBody, extractSummary, summaryOverCap, fallbackSummary, formatCommitBody, appendSelfCheckNotes, stampedSubject, } from "../git/commit-message.js";
|
|
4
4
|
import { logEventBestEffort } from "../events/events.js";
|
|
5
5
|
import { enqueueMerge, entryRevisionFields, queuedMerges } from "../merge/merge-queue.js";
|
|
6
|
-
import { hasUnmergedPin } from "../runner/leftover.js";
|
|
6
|
+
import { hasUnmergedPin } from "../runner/runner-leftover.js";
|
|
7
7
|
import { plural } from "../text/phrases.js";
|
|
8
8
|
import { squash } from "../text/text.js";
|
|
9
9
|
import { recordDismissedFiling, recordDismissedFlow } from "./run-dismiss.js";
|
|
@@ -56,75 +56,114 @@ function revisionFields(ctx) {
|
|
|
56
56
|
: undefined;
|
|
57
57
|
return { revisionRound, priorReview };
|
|
58
58
|
}
|
|
59
|
+
/** The bounded follow-up turn both recovery paths share: fold the follow-up's commits past the
|
|
60
|
+
* head it started from, and end the run aborted (returning the outcome to surface) when the
|
|
61
|
+
* follow-up aborted, else hand back the follow-up run. */
|
|
62
|
+
async function requestSummaryFollowUp(ctx, over) {
|
|
63
|
+
const base = await headOf(ctx.wt, "HEAD");
|
|
64
|
+
const followUp = await ctx.requestSummary(ctx.wt, over);
|
|
65
|
+
await foldAgentCommitsSince(ctx, ctx.wt, base);
|
|
66
|
+
if (followUp?.aborted)
|
|
67
|
+
return { aborted: await ctx.finishAbortedRun() };
|
|
68
|
+
return { followUp };
|
|
69
|
+
}
|
|
70
|
+
/** Recover a summary when the reply left none: take the shared follow-up turn, refill `pending`
|
|
71
|
+
* from it, fall back to the changed paths when it still yields none, and warn about the recovery.
|
|
72
|
+
* Returns an outcome only when the follow-up aborted, else `undefined`. */
|
|
73
|
+
async function recoverMissingSummary(ctx, pending) {
|
|
74
|
+
const result = await requestSummaryFollowUp(ctx);
|
|
75
|
+
if ("aborted" in result)
|
|
76
|
+
return result.aborted;
|
|
77
|
+
const followUp = result.followUp;
|
|
78
|
+
if (followUp) {
|
|
79
|
+
pending.summary = extractSummary(followUp.finalText);
|
|
80
|
+
pending.body = pending.body ?? extractCommitBody(followUp.finalText);
|
|
81
|
+
}
|
|
82
|
+
if (pending.summary === null) {
|
|
83
|
+
pending.summary = fallbackSummary(await changedFiles(ctx.wt), ctx.role, ctx.state.runs);
|
|
84
|
+
pending.summaryDerived = true;
|
|
85
|
+
}
|
|
86
|
+
ctx.warn(missingSummaryWarning(followUp, pending.summary));
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
/** The missing-SUMMARY warning's four-way wording: "recovered it with a follow-up turn", or the
|
|
90
|
+
* derived-subject variants naming the follow-up's failure or absence. */
|
|
91
|
+
function missingSummaryWarning(followUp, summary) {
|
|
92
|
+
return (`reply had no SUMMARY line — ` +
|
|
93
|
+
(followUp && extractSummary(followUp.finalText) !== null
|
|
94
|
+
? "recovered it with a follow-up turn"
|
|
95
|
+
: followUp && !followUp.ok
|
|
96
|
+
? `the follow-up run failed (${followUp.errorMessage || "no error message"}); ` +
|
|
97
|
+
`subject derived from the changed files: "${summary}"`
|
|
98
|
+
: followUp
|
|
99
|
+
? `follow-up gave none; subject derived from the changed files: "${summary}"`
|
|
100
|
+
: `no follow-up session was available; subject derived from the changed files: "${summary}"`));
|
|
101
|
+
}
|
|
102
|
+
/** Shorten a SUMMARY the cap would cut mid-word: take the shared follow-up turn asking for it
|
|
103
|
+
* restated short, keep the restated subject when it fits, and warn. Returns an outcome only when
|
|
104
|
+
* the follow-up aborted, else `undefined`. */
|
|
105
|
+
async function shortenOverCapSummary(ctx, pending) {
|
|
106
|
+
const over = summaryOverCap(ctx.finalText);
|
|
107
|
+
if (over === null)
|
|
108
|
+
return undefined;
|
|
109
|
+
// A SUMMARY the cap would cut mid-word gets one follow-up turn asking for it restated short.
|
|
110
|
+
const result = await requestSummaryFollowUp(ctx, over);
|
|
111
|
+
if ("aborted" in result)
|
|
112
|
+
return result.aborted;
|
|
113
|
+
const followUp = result.followUp;
|
|
114
|
+
const short = followUp && summaryOverCap(followUp.finalText) === null ? extractSummary(followUp.finalText) : null;
|
|
115
|
+
if (short !== null) {
|
|
116
|
+
pending.summary = short;
|
|
117
|
+
pending.body = pending.body ?? extractCommitBody(followUp.finalText);
|
|
118
|
+
}
|
|
119
|
+
ctx.warn(short !== null
|
|
120
|
+
? `SUMMARY was ${over} characters — shortened with a follow-up turn`
|
|
121
|
+
: `SUMMARY was ${over} characters — follow-up gave no shorter one; subject truncated`);
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
59
124
|
/** Derive the commit's subject and body from the reply's SUMMARY block, running one bounded
|
|
60
125
|
* follow-up turn when the reply left none or a SUMMARY the cap would cut mid-word. Returns the
|
|
61
126
|
* aborted-run outcome when a follow-up aborts, otherwise the description. */
|
|
62
127
|
async function resolveCommitDescription(ctx) {
|
|
63
|
-
const s = ctx.state;
|
|
64
128
|
// The commit subject and body come from the reply's closing block. A run that changed files
|
|
65
129
|
// without one — a cut-off final message, or plain non-compliance — gets one bounded follow-up
|
|
66
130
|
// turn in its own session to produce it (the session still holds everything the run did);
|
|
67
131
|
// only if that too yields nothing is the subject derived from the changed paths.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
await
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
body = body ?? extractCommitBody(followUp.finalText);
|
|
81
|
-
}
|
|
82
|
-
if (summary === null) {
|
|
83
|
-
summary = fallbackSummary(await changedFiles(ctx.wt), ctx.role, s.runs);
|
|
84
|
-
summaryDerived = true;
|
|
85
|
-
}
|
|
86
|
-
ctx.warn(`reply had no SUMMARY line — ` +
|
|
87
|
-
(followUp && extractSummary(followUp.finalText) !== null
|
|
88
|
-
? "recovered it with a follow-up turn"
|
|
89
|
-
: followUp && !followUp.ok
|
|
90
|
-
? `the follow-up run failed (${followUp.errorMessage || "no error message"}); ` +
|
|
91
|
-
`subject derived from the changed files: "${summary}"`
|
|
92
|
-
: followUp
|
|
93
|
-
? `follow-up gave none; subject derived from the changed files: "${summary}"`
|
|
94
|
-
: `no follow-up session was available; subject derived from the changed files: "${summary}"`));
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
// A SUMMARY the cap would cut mid-word gets one follow-up turn asking for it restated short.
|
|
98
|
-
const over = summaryOverCap(ctx.finalText);
|
|
99
|
-
if (over !== null) {
|
|
100
|
-
const base = await headOf(ctx.wt, "HEAD");
|
|
101
|
-
const followUp = await ctx.requestSummary(ctx.wt, over);
|
|
102
|
-
await foldAgentCommitsSince(ctx, ctx.wt, base);
|
|
103
|
-
if (followUp?.aborted)
|
|
104
|
-
return ctx.finishAbortedRun();
|
|
105
|
-
const short = followUp && summaryOverCap(followUp.finalText) === null ? extractSummary(followUp.finalText) : null;
|
|
106
|
-
if (short !== null) {
|
|
107
|
-
summary = short;
|
|
108
|
-
body = body ?? extractCommitBody(followUp.finalText);
|
|
109
|
-
}
|
|
110
|
-
ctx.warn(short !== null
|
|
111
|
-
? `SUMMARY was ${over} characters — shortened with a follow-up turn`
|
|
112
|
-
: `SUMMARY was ${over} characters — follow-up gave no shorter one; subject truncated`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return { summary, body, summaryDerived };
|
|
132
|
+
const pending = {
|
|
133
|
+
summary: extractSummary(ctx.finalText),
|
|
134
|
+
body: extractCommitBody(ctx.finalText),
|
|
135
|
+
// True when no reply named the change, so the subject below is derived from its paths.
|
|
136
|
+
summaryDerived: false,
|
|
137
|
+
};
|
|
138
|
+
const outcome = pending.summary === null
|
|
139
|
+
? await recoverMissingSummary(ctx, pending)
|
|
140
|
+
: await shortenOverCapSummary(ctx, pending);
|
|
141
|
+
if (outcome)
|
|
142
|
+
return outcome;
|
|
143
|
+
return { summary: pending.summary, body: pending.body, summaryDerived: pending.summaryDerived };
|
|
116
144
|
}
|
|
117
|
-
/** Run the gate's
|
|
145
|
+
/** Run the gate's remaining checks before the commit, giving the author's session
|
|
118
146
|
* one bounded fix-up turn, and end the run `no_change` when the fix-up reverts the whole change
|
|
119
147
|
* (leaving nothing to commit). Returns an outcome only when the run must end early; otherwise
|
|
120
148
|
* mutates `desc.summary`/`desc.body` with any fix-up recovery, exactly as the inline block did. */
|
|
121
149
|
async function runStageSelfCheck(ctx, desc) {
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
|
|
150
|
+
// The gate's remaining checks (a protected edit, a leaked credential) run here, before the
|
|
151
|
+
// commit, while the authoring session can still be continued: a finding that reaches review
|
|
152
|
+
// costs a queue slot, a verification, and a rejection that discards the work. One bounded
|
|
153
|
+
// follow-up turn on the author's own session gets to fix them, and the change commits and
|
|
154
|
+
// queues either way.
|
|
155
|
+
const moved = await stagedBacklogMoves(ctx);
|
|
156
|
+
const movedFindings = recordStagedClaims(ctx, moved);
|
|
157
|
+
const findings = await ctx.stageCheck(ctx.wt);
|
|
158
|
+
findings.push(...movedFindings);
|
|
159
|
+
if (findings.length === 0)
|
|
160
|
+
return undefined;
|
|
161
|
+
return applyStageFixUp(ctx, desc, findings);
|
|
162
|
+
}
|
|
163
|
+
/** Decide whether this is a claim-staging run and, if so, read the backlog entries its diff moved
|
|
164
|
+
* out of their actionable section (plans/parallel-work-instances.md "Claims", part 4/7). Returns
|
|
165
|
+
* `[]` for every non-claim-staging run and on any unreadable file or git hiccup. */
|
|
166
|
+
async function stagedBacklogMoves(ctx) {
|
|
128
167
|
// Claims made at staging (plans/parallel-work-instances.md "Claims", part 4/7): an
|
|
129
168
|
// unassigned run of a MULTI-instance base whose diff moved a backlog entry out of its
|
|
130
169
|
// actionable section records the claim so a sibling is not assigned it while this change
|
|
@@ -139,6 +178,13 @@ async function runStageSelfCheck(ctx, desc) {
|
|
|
139
178
|
configuredInstances(ctx.config, ctx.role) > 1
|
|
140
179
|
? await stagedMovedEntries(ctx.wt, ctx.mainBranch, ctx.role).catch(() => [])
|
|
141
180
|
: [];
|
|
181
|
+
return moved;
|
|
182
|
+
}
|
|
183
|
+
/** Record the run's staged claim for the first claimable moved entry (when it has none yet) and
|
|
184
|
+
* return the moved-entry findings in the order the inline body pushed them: `assignedMoved`
|
|
185
|
+
* first, then `heldMoved`. */
|
|
186
|
+
function recordStagedClaims(ctx, moved) {
|
|
187
|
+
const s = ctx.state;
|
|
142
188
|
const heldBySiblings = new Set(s.claim === undefined ? (s.heldElsewhere ?? []).map(entryKey) : []);
|
|
143
189
|
const heldMoved = heldMovedFinding(heldBySiblings, moved);
|
|
144
190
|
const claimable = moved.filter((m) => !heldBySiblings.has(m.key));
|
|
@@ -157,45 +203,50 @@ async function runStageSelfCheck(ctx, desc) {
|
|
|
157
203
|
});
|
|
158
204
|
}
|
|
159
205
|
const assignedMoved = s.claim ? assignedMovedFinding(s.claim.key, moved) : undefined;
|
|
160
|
-
const findings =
|
|
206
|
+
const findings = [];
|
|
161
207
|
if (assignedMoved)
|
|
162
208
|
findings.push(assignedMoved);
|
|
163
209
|
if (heldMoved)
|
|
164
210
|
findings.push(heldMoved);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
211
|
+
return findings;
|
|
212
|
+
}
|
|
213
|
+
/** Run the author's one bounded fix-up turn over the stage findings, fold its commits, recover
|
|
214
|
+
* `desc.summary`/`desc.body` from the reply, and end the run `no_change` when the follow-up left
|
|
215
|
+
* the worktree clean; otherwise stage-check again and log the outcome warning. Returns an outcome
|
|
216
|
+
* only when it ends the run early, else `undefined`. */
|
|
217
|
+
async function applyStageFixUp(ctx, desc, findings) {
|
|
218
|
+
const before = findings.length;
|
|
219
|
+
const fixBase = await headOf(ctx.wt, "HEAD");
|
|
220
|
+
const fixUp = await ctx.requestStageFix(ctx.wt, findings, {
|
|
221
|
+
summary: desc.summaryDerived ? null : desc.summary,
|
|
222
|
+
body: desc.body ? formatCommitBody(desc.body) : "",
|
|
223
|
+
});
|
|
224
|
+
await foldAgentCommitsSince(ctx, ctx.wt, fixBase);
|
|
225
|
+
if (fixUp) {
|
|
226
|
+
if (fixUp.aborted)
|
|
174
227
|
return ctx.finishAbortedRun();
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return { result: "no_change" };
|
|
193
|
-
}
|
|
194
|
-
const remaining = await ctx.stageCheck(ctx.wt);
|
|
195
|
-
logSelfCheckOutcome(ctx, remaining.length === 0
|
|
196
|
-
? `stage self-check: ${plural(before, "finding")} — fixed by the follow-up turn`
|
|
197
|
-
: `stage self-check: ${plural(before, "finding")}; ${remaining.length} still open, queued for the gate`, findings, remaining);
|
|
228
|
+
// The fix-up reply answers the findings, not the change, so it never replaces the
|
|
229
|
+
// change's description (BUGS.md 2026-10-21): its SUMMARY is taken only over a subject
|
|
230
|
+
// derived from the paths, and its RISK/VERIFIED are appended as self-check notes.
|
|
231
|
+
const fixSummary = desc.summaryDerived ? extractSummary(fixUp.finalText) : null;
|
|
232
|
+
if (fixSummary !== null)
|
|
233
|
+
desc.summary = fixSummary;
|
|
234
|
+
desc.body = appendSelfCheckNotes(desc.body, extractCommitBody(fixUp.finalText));
|
|
235
|
+
}
|
|
236
|
+
// A fix-up turn may resolve its findings by reverting the whole change. With nothing left
|
|
237
|
+
// in the worktree there is nothing to commit or queue: end the run no_change here instead
|
|
238
|
+
// of handing a clean tree to commitAll. The worktree is left clean and no pin is taken.
|
|
239
|
+
// isDirty, not changedFiles: a `git status` that fails must throw, not read as clean — on
|
|
240
|
+
// 2026-10-21 a spawn failure here ended bugfix-2's 17-minute run no_change and released
|
|
241
|
+
// its edits unpinned; thrown, the run ends unsettled and its next run gets them back.
|
|
242
|
+
if (!(await isDirty(ctx.wt))) {
|
|
243
|
+
logSelfCheckOutcome(ctx, "stage self-check: the follow-up turn left no change; nothing to land", findings);
|
|
244
|
+
return { result: "no_change" };
|
|
198
245
|
}
|
|
246
|
+
const remaining = await ctx.stageCheck(ctx.wt);
|
|
247
|
+
logSelfCheckOutcome(ctx, remaining.length === 0
|
|
248
|
+
? `stage self-check: ${plural(before, "finding")} — fixed by the follow-up turn`
|
|
249
|
+
: `stage self-check: ${plural(before, "finding")}; ${remaining.length} still open, queued for the gate`, findings, remaining);
|
|
199
250
|
return undefined;
|
|
200
251
|
}
|
|
201
252
|
/** Compute the run's friction signal over this run's pre-commit authoring runs: minutes of wall
|
|
@@ -89,16 +89,22 @@ export async function runTimedRoleRun(signal, acquire, release, run, now = Date.
|
|
|
89
89
|
/** Sleep up to ms, but wake immediately when `signal` aborts — so shutdown (SIGTERM →
|
|
90
90
|
* abort) is prompt instead of waiting out the current poll cycle. The listener is removed
|
|
91
91
|
* on either exit path so long-running orchestrators don't accumulate one per poll, and an
|
|
92
|
-
* already-aborted signal returns synchronously through runOnAbort.
|
|
92
|
+
* already-aborted signal returns synchronously through runOnAbort. Without a signal it is a
|
|
93
|
+
* plain setTimeout sleep. The review gate's retry wait (src/review/review.ts, whose signal is
|
|
94
|
+
* optional) shares this helper rather than keeping a second copy. Exported as a unit-test
|
|
93
95
|
* seam, like runTimedRoleRun above. */
|
|
94
96
|
export function sleepInterruptible(ms, signal) {
|
|
95
97
|
return new Promise((resolve) => {
|
|
98
|
+
if (signal === undefined) {
|
|
99
|
+
setTimeout(resolve, ms);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
96
102
|
const onAbort = () => {
|
|
97
103
|
clearTimeout(timer);
|
|
98
104
|
resolve();
|
|
99
105
|
};
|
|
100
106
|
const timer = setTimeout(() => {
|
|
101
|
-
signal
|
|
107
|
+
signal?.removeEventListener("abort", onAbort);
|
|
102
108
|
resolve();
|
|
103
109
|
}, ms);
|
|
104
110
|
runOnAbort(signal, onAbort);
|
|
@@ -108,9 +108,24 @@ export class RunUsage {
|
|
|
108
108
|
* through foldMergeUsage: those never advance the run's pre-edit counter, which measures
|
|
109
109
|
* the authoring search. */
|
|
110
110
|
fold(s, run, authoring = true, provider, pair) {
|
|
111
|
+
this.foldLifetimeTotals(s, run);
|
|
112
|
+
this.foldCost(s, run, provider, pair);
|
|
113
|
+
this.foldAccount(run);
|
|
114
|
+
this.foldPromptTokens(run, authoring);
|
|
115
|
+
this.turns += run.turns;
|
|
116
|
+
this.foldEpisodes(run, provider);
|
|
117
|
+
}
|
|
118
|
+
/** Grow the role's lifetime totals on `s` from this run: its generated tokens and the real
|
|
119
|
+
* spend both accumulate through the healed counter, and the peak context is the new max. */
|
|
120
|
+
foldLifetimeTotals(s, run) {
|
|
111
121
|
s.generatedTokens = counter(s.generatedTokens) + run.outputTokens;
|
|
112
122
|
s.peakContextTokens = Math.max(counter(s.peakContextTokens), run.peakContextTokens);
|
|
113
123
|
s.totalCostUsd = counter(s.totalCostUsd) + run.costUsd;
|
|
124
|
+
}
|
|
125
|
+
/** Split this run's cost real from notional: a subscription run's cost is notional and never
|
|
126
|
+
* charges the daily cap; an API run's — or a run that ever saw overage — is real money.
|
|
127
|
+
* totalCostUsd still counts both: it is lifetime usage, not a budget figure. */
|
|
128
|
+
foldCost(s, run, provider, pair) {
|
|
114
129
|
// The billing split (plans/agent-backends.md §7): a subscription run's cost is notional
|
|
115
130
|
// and never charges the daily cap; an API run's — or a run that ever saw overage — is real
|
|
116
131
|
// money. totalCostUsd still counts both: it is lifetime usage, not a budget figure.
|
|
@@ -126,12 +141,21 @@ export class RunUsage {
|
|
|
126
141
|
this.notionalCostUsd += run.costUsd;
|
|
127
142
|
this.bumpProviderCost(provider, run.costUsd, 'notionalCostUsd');
|
|
128
143
|
}
|
|
144
|
+
}
|
|
145
|
+
/** Carry the newest account readings this run reported: the billing state, the subscription
|
|
146
|
+
* rate windows, and the agent they came from (whose name is only meaningful beside them). */
|
|
147
|
+
foldAccount(run) {
|
|
129
148
|
if (run.billing !== undefined)
|
|
130
149
|
this.billing = run.billing;
|
|
131
150
|
if (run.rateWindows !== undefined)
|
|
132
151
|
this.rateWindows = run.rateWindows;
|
|
133
152
|
if (run.billing !== undefined || run.rateWindows !== undefined)
|
|
134
153
|
this.agent = run.agent;
|
|
154
|
+
}
|
|
155
|
+
/** Advance the token counters: the prompt and cache-read shares count every run, while the
|
|
156
|
+
* pre-edit prefix freezes at the first edit — authoring-only, and skipped for a merge/review
|
|
157
|
+
* run. */
|
|
158
|
+
foldPromptTokens(run, authoring) {
|
|
135
159
|
this.promptTokens += run.promptTokens;
|
|
136
160
|
this.cacheReadTokens += run.cacheReadTokens;
|
|
137
161
|
// The pre-edit prefix freezes at the run's first edit: every authoring run while no edit
|
|
@@ -143,7 +167,13 @@ export class RunUsage {
|
|
|
143
167
|
if (run.firstEditTurn !== undefined)
|
|
144
168
|
this.editSeen = true;
|
|
145
169
|
}
|
|
146
|
-
|
|
170
|
+
}
|
|
171
|
+
/** Stamp the team-wide hold's failure observations for a run that ENDED on them: pi exits on
|
|
172
|
+
* a 429 or backend error, so "now" is when the provider refused. A run that merely logged one
|
|
173
|
+
* inside pi's own auto-retry and then finished would stamp a false observation, possibly hours
|
|
174
|
+
* late. The backend kind travels with its stamp (src/pi/pi-failure.ts classified the text at
|
|
175
|
+
* parse time); a 429 never ends up here — pi-stream checks rate-limit first. */
|
|
176
|
+
foldEpisodes(run, provider) {
|
|
147
177
|
// The team-wide hold's rate-limit input (lastRateLimit above), from the same
|
|
148
178
|
// once-per-run choke point. Only a run that ENDED on the 429 counts: pi exits on the error,
|
|
149
179
|
// so "now" is when the provider refused — a run that merely logged one inside pi's own
|