tumwater 0.4.1 → 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 +3 -5
- 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} +35 -13
- package/dist/src/agent/{run-settle.js → agent-settle.js} +70 -37
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +24 -24
- package/dist/src/agent/{run-stream.js → agent-stream.js} +6 -14
- package/dist/src/agent/backend-traits.js +16 -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 +131 -78
- 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 +120 -69
- package/dist/src/agent/codex/codex-stream.js +72 -39
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +21 -33
- 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 +52 -29
- 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 +51 -29
- package/dist/src/agent/session-ledger.js +16 -0
- package/dist/src/agent/subscription-account.js +3 -3
- package/dist/src/backlog/backlog-archive.js +64 -165
- 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 +84 -136
- package/dist/src/backlog/backlog-file-bugs.js +133 -0
- package/dist/src/backlog/backlog-index-data.js +2 -2
- package/dist/src/backlog/backlog-md.js +42 -31
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +30 -14
- package/dist/src/backlog/backlog-release.js +15 -23
- package/dist/src/backlog/backlog-structure.js +128 -62
- package/dist/src/backlog/backlog-write.js +1 -1
- package/dist/src/backlog/backlog.js +5 -4
- package/dist/src/baseline/main-baseline.js +178 -104
- package/dist/src/baseline/main-guardrails.js +54 -17
- package/dist/src/baseline/main-watch.js +1 -1
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +24 -65
- package/dist/src/budget/budget.js +2 -1
- 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 +103 -50
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/change/change-data.js +4 -2
- package/dist/src/cli/cli-command-args.js +275 -137
- package/dist/src/cli/cli-flag-specs.js +43 -26
- 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-review-commands.js +2 -1
- package/dist/src/cli/cli-role-command.js +27 -50
- package/dist/src/cli/cli-start.js +150 -67
- package/dist/src/cli/completion.js +82 -46
- package/dist/src/cli/config-commands.js +82 -71
- package/dist/src/cli/follow-view.js +3 -1
- package/dist/src/cli/help.js +6 -9
- package/dist/src/cli/log-commands.js +46 -24
- package/dist/src/cli/notify-command.js +2 -2
- package/dist/src/cli/question-commands.js +86 -60
- package/dist/src/cli.js +183 -141
- package/dist/src/collections.js +52 -23
- 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 +79 -103
- 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 +161 -153
- 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 +4 -34
- package/dist/src/config/config-write.js +22 -46
- package/dist/src/config/config.js +4 -5
- package/dist/src/doctor/doctor-backlog-bugs.js +24 -121
- package/dist/src/doctor/doctor-backlog-plans.js +27 -27
- package/dist/src/doctor/doctor-backlog.js +29 -41
- package/dist/src/doctor/doctor-check-names.js +4 -7
- package/dist/src/doctor/doctor-checks.js +5 -5
- package/dist/src/doctor/doctor-claude-checks.js +46 -30
- 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 +19 -56
- 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 +181 -273
- package/dist/src/doctor/doctor-notify.js +4 -7
- package/dist/src/doctor/doctor-opencode-checks.js +90 -52
- 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/{probe.js → doctor-probe.js} +1 -1
- package/dist/src/doctor/doctor-project-checks.js +69 -20
- package/dist/src/doctor/doctor-role-notes.js +7 -25
- package/dist/src/doctor/doctor.js +12 -15
- package/dist/src/events/event-format.js +19 -53
- package/dist/src/events/event-labels.js +0 -5
- package/dist/src/events/event-legacy.js +0 -2
- package/dist/src/events/{notify.js → event-notify.js} +2 -2
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +15 -2
- 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 +177 -92
- 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 +21 -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} +34 -34
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/slot-salvage.js +5 -4
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +5 -6
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +62 -48
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-endpoint-commands.js +1 -1
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +22 -35
- package/dist/src/gui/gui-prompt-commands.js +48 -42
- package/dist/src/gui/gui-report-endpoints.js +3 -2
- package/dist/src/gui/gui-server.js +1 -2
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +138 -85
- package/dist/src/inbox/inbox-submit.js +1 -5
- package/dist/src/inbox/prompt-commands.js +82 -60
- package/dist/src/init/init.js +156 -55
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-core.js +2 -2
- 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 +11 -14
- 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 +143 -90
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/operator/{retire.js → operator-retire.js} +2 -1
- 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/retention.js +6 -5
- 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 -13
- 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 +29 -3
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +87 -51
- 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 +31 -8
- package/dist/src/process/run-marker.js +31 -13
- package/dist/src/process/supervisor.js +57 -29
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +11 -11
- package/dist/src/prompt/notes-backlog.js +73 -57
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt.js +92 -43
- package/dist/src/report/report-data.js +61 -260
- package/dist/src/report/report-fold.js +34 -302
- package/dist/src/report/report-main-red.js +45 -42
- package/dist/src/report/report-render.js +37 -183
- package/dist/src/report/report-since-data.js +219 -0
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +288 -0
- package/dist/src/report/report.js +48 -23
- package/dist/src/review/known-flakes.js +3 -3
- package/dist/src/review/post-merge-review-accept.js +3 -2
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +10 -10
- package/dist/src/review/quarantine-render.js +2 -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 +28 -32
- package/dist/src/roles/role-guidance.js +25 -46
- package/dist/src/roles/role-render.js +1 -14
- package/dist/src/roles/role-view.js +10 -23
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/ci-parity.js +5 -6
- package/dist/src/run/qa-coverage.js +4 -4
- package/dist/src/run/run-apply.js +12 -6
- package/dist/src/run/run-detail-data.js +137 -68
- package/dist/src/run/run-detail-render.js +97 -34
- package/dist/src/run/run-detail.js +106 -53
- package/dist/src/run/run-dismiss.js +43 -0
- package/dist/src/run/run-prompt.js +150 -93
- package/dist/src/run/run-resume.js +85 -37
- package/dist/src/run/run-stage.js +139 -105
- package/dist/src/run/run-timing.js +26 -6
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +71 -67
- package/dist/src/run/stage-backlog-checks.js +2 -29
- package/dist/src/run/stage-check.js +52 -43
- 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 +2 -2
- 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 +23 -38
- package/dist/src/runner/runner-state-load.js +2 -9
- package/dist/src/runner/runner.js +21 -36
- package/dist/src/scheduling/claims.js +3 -14
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-data.js +7 -11
- package/dist/src/status/status-role-notes.js +20 -16
- package/dist/src/status/status-roles.js +11 -52
- 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/datetime.js +8 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +20 -15
- package/dist/src/text/text.js +15 -3
- package/dist/src/ui/alert-feeds.js +52 -281
- package/dist/src/ui/badges.js +3 -14
- package/dist/src/ui/gui/gui-client-boot.js +21 -6
- 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 +2 -2
- 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 +12 -10
- package/dist/src/ui/gui/gui-client-report.js +1 -2
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -107
- package/dist/src/ui/gui/gui-client-team.js +30 -4
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/gui/gui-icons.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 +4 -26
- package/dist/src/ui/status-payload.js +4 -8
- package/dist/src/ui/status-render.js +6 -10
- package/dist/src/ui/team-alert-backlog.js +29 -60
- package/dist/src/ui/team-alert-shared.js +23 -10
- package/dist/src/ui/team-alert-status.js +17 -173
- package/dist/src/ui/team-alert-tail.js +19 -228
- package/dist/src/ui/team-alerts.js +12 -24
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/transcript-tail.js +137 -94
- package/dist/src/ui/tui/tui-editor-answer.js +66 -54
- package/dist/src/ui/tui/tui-editor-attach.js +77 -65
- package/dist/src/ui/tui/tui-editor-backends.js +65 -58
- package/dist/src/ui/tui/tui-editor-brief.js +64 -57
- package/dist/src/ui/tui/tui-editor-budget.js +109 -96
- package/dist/src/ui/tui/tui-editor-config.js +81 -71
- package/dist/src/ui/tui/tui-editor-entry.js +167 -157
- package/dist/src/ui/tui/tui-editor-instances.js +2 -1
- package/dist/src/ui/tui/tui-editor-pause.js +208 -188
- package/dist/src/ui/tui/tui-editor-prompt.js +50 -44
- package/dist/src/ui/tui/tui-editor-queued.js +74 -67
- package/dist/src/ui/tui/tui-editor-quiet.js +124 -110
- package/dist/src/ui/tui/tui-editor-release.js +53 -44
- package/dist/src/ui/tui/tui-editor-retire.js +60 -49
- package/dist/src/ui/tui/tui-editor-role-enabled.js +93 -82
- package/dist/src/ui/tui/tui-editor-role-file.js +61 -49
- package/dist/src/ui/tui/tui-editor-role-key.js +52 -44
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +73 -60
- 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 +2 -2
- package/dist/src/ui/tui/tui-pane-activity.js +65 -53
- package/dist/src/ui/tui/tui-pane-backlog.js +78 -40
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +254 -251
- package/dist/src/ui/tui/tui-pane-history.js +81 -94
- 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 +76 -58
- package/dist/src/ui/tui/tui-pane-session.js +14 -9
- package/dist/src/ui/tui/tui-pane-transcript.js +152 -141
- package/dist/src/ui/tui/tui-pane.js +205 -175
- package/dist/src/ui/tui/tui-panes.js +31 -35
- package/dist/src/ui/tui/tui.js +4 -5
- package/dist/src/verdict/fix-claim.js +87 -53
- package/dist/src/verdict/refusal.js +31 -7
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/doctor/image-probe.js +0 -257
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/team-alert-backlog-feed.js +0 -181
- package/dist/src/ui/team-alert-merge.js +0 -183
- 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/gates/{disk-gate.js → disk-gates.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
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ and is also outside `npm test`.
|
|
|
42
42
|
## Status
|
|
43
43
|
|
|
44
44
|
<!-- tumwater:status:start -->
|
|
45
|
-
**v0.4.
|
|
45
|
+
**v0.4.3**: working tumwater. The director and 15 of the 17 roles are enabled by default; `selfcheck`,
|
|
46
46
|
which files tumwater bugs from tumwater's own event log, and `feedback`, which files them as GitHub
|
|
47
47
|
issues on znation/tumwater, are opt-in (`roles.selfcheck.enabled`, `roles.feedback.enabled`).
|
|
48
48
|
|
|
@@ -77,8 +77,7 @@ so pair it with `--token <secret>`.
|
|
|
77
77
|
`tumwater prompt --attach <image>` gives a role an image to read at its next run. When a role's
|
|
78
78
|
model cannot read images, the command refuses the attachment up front (naming the fix) unless
|
|
79
79
|
the role reroutes a blind read to a vision model, in which case it says where the message will
|
|
80
|
-
run; `tumwater
|
|
81
|
-
force) and the dashboards and `tumwater role <id>` say which roles read images and where a blind
|
|
80
|
+
run; the dashboards and `tumwater role <id>` say which roles read images and where a blind
|
|
82
81
|
run reroutes.
|
|
83
82
|
|
|
84
83
|
Every command, flag, and TUI key is in [docs/cli.md](docs/cli.md); `tumwater help` prints the
|
|
@@ -109,8 +108,7 @@ object with both, so one config can mix a Claude strong tier with pi providers:
|
|
|
109
108
|
|
|
110
109
|
The default tier runs on pi through Hugging Face, the strong model jobs (the director, the plan
|
|
111
110
|
role, the reviewer, and the conflict resolver) run on the claude CLI's included usage, and the fallback runs on pi through
|
|
112
|
-
oMLX — all concurrently, under
|
|
113
|
-
`tumwater status` shows how many runs each key has going out of its cap). An OpenCode tier is
|
|
111
|
+
oMLX — all concurrently, under the team's `maxConcurrent` cap. An OpenCode tier is
|
|
114
112
|
`{ "backend": "opencode", "model": "deepseek-v4.1-flash-free" }`, so its free tier can sit beside pi
|
|
115
113
|
and claude in one config; a codex tier is `{ "backend": "codex", "model": "gpt-6.1-sol" }`, so a ChatGPT
|
|
116
114
|
subscription can cover the team's default tier beside pi and claude. A Claude model that should stay on pi (billed per token) is the plain
|
package/dist/build-info.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/** The backend seam (plans/agent-backends.md "The seam"): what one non-interactive agent
|
|
2
2
|
* run needs from its backend — how to build the command, how to translate its native stdout
|
|
3
|
-
* into tumwater's canonical event stream, and how to classify its exit. src/agent/
|
|
4
|
-
* builds and spawns the run (the marker, parser, bin resolution and spawn); src/agent/run
|
|
3
|
+
* into tumwater's canonical event stream, and how to classify its exit. src/agent/agent-spawn.ts
|
|
4
|
+
* builds and spawns the run (the marker, parser, bin resolution and spawn); src/agent/agent-run.ts
|
|
5
5
|
* keeps the coordinator — the backend-neutral watchdogs, the abort wiring and the settlement-state
|
|
6
|
-
* assembly — while src/agent/
|
|
7
|
-
* split and the stderr window) and the settle/flush; the
|
|
6
|
+
* assembly — while src/agent/agent-stream.ts owns the streaming pipeline (the native-to-canonical
|
|
7
|
+
* stdout split and the stderr window) and the settle/flush; the
|
|
8
8
|
* post-run settlement — the group-kill and marker sweeps on exit, the spawn-error result and
|
|
9
9
|
* the close-time reconcile/classify that builds the `PiRunResult` — lives in
|
|
10
|
-
* src/agent/
|
|
10
|
+
* src/agent/agent-settle.ts. A backend registered here supplies the rest. src/agent/agent-run.ts
|
|
11
11
|
* registers the pi and claude adapters here.
|
|
12
12
|
*
|
|
13
|
-
* The registry is keyed by id and fed by explicit registration (src/agent/run
|
|
13
|
+
* The registry is keyed by id and fed by explicit registration (src/agent/agent-run.ts
|
|
14
14
|
* registers both adapters at load), so a test can register a fake backend without importing a
|
|
15
15
|
* real one. */
|
|
16
16
|
import { hasResumableSession } from "./session-ledger.js";
|
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
* matcher, and the exit classification. This file keeps the coordinator — setup, the watchdog
|
|
7
7
|
* wiring, abort, the child's stream-attachment and the settlement-state assembly — and
|
|
8
8
|
* delegates the build-and-spawn stage (the marker,
|
|
9
|
-
* parser, bin resolution, `prepare`, invocation and spawn) to src/agent/
|
|
9
|
+
* parser, bin resolution, `prepare`, invocation and spawn) to src/agent/agent-spawn.ts, the
|
|
10
10
|
* streaming pipeline (native-to-canonical stdout split, the stderr window, the stream-minted
|
|
11
|
-
* session record and the settle/flush) to src/agent/
|
|
11
|
+
* session record and the settle/flush) to src/agent/agent-stream.ts, and the post-run settlement
|
|
12
12
|
* (spawn-error result, exit orphan sweep, close-time reconcile and
|
|
13
|
-
* classify) to src/agent/
|
|
13
|
+
* classify) to src/agent/agent-settle.ts. `runPi` (src/pi/pi.ts) is the thin alias the existing
|
|
14
14
|
* call sites use, so a caller and its fakes are untouched by this split. */
|
|
15
15
|
import fs from "node:fs";
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
import path from "node:path";
|
|
18
|
-
import { ensureDir } from "../files/files.js";
|
|
18
|
+
import { ensureDir, removeTree } from "../files/files.js";
|
|
19
19
|
import { runOnAbort, terminateChild } from "../process/process.js";
|
|
20
20
|
import { startPiWatchdogs } from "../pi/pi-watchdogs.js";
|
|
21
21
|
import { DEFAULT_BACKEND, backendById, registerAgentBackend } from "./agent-backend.js";
|
|
@@ -24,12 +24,12 @@ import { piBackend } from "../pi/pi-backend.js";
|
|
|
24
24
|
import { claudeBackend } from "./claude/claude-backend.js";
|
|
25
25
|
import { codexBackend } from "./codex/codex-backend.js";
|
|
26
26
|
import { opencodeBackend } from "./opencode/opencode-backend.js";
|
|
27
|
-
import { setupFailureResult, settleClose, settleError, settleExit } from "./
|
|
28
|
-
import { openAppendLog, spawnPiRun } from "./
|
|
29
|
-
import { createAgentStream } from "./
|
|
27
|
+
import { setupFailureResult, settleClose, settleError, settleExit } from "./agent-settle.js";
|
|
28
|
+
import { openAppendLog, spawnPiRun } from "./agent-spawn.js";
|
|
29
|
+
import { createAgentStream } from "./agent-stream.js";
|
|
30
30
|
// The streaming pipeline's over-long-native-line cap stays reachable from here, the historical
|
|
31
|
-
// home the test suite still imports it from, even though the value now lives in
|
|
32
|
-
export { MAX_NATIVE_LINE_CHARS } from "./
|
|
31
|
+
// home the test suite still imports it from, even though the value now lives in agent-stream.ts.
|
|
32
|
+
export { MAX_NATIVE_LINE_CHARS } from "./agent-stream.js";
|
|
33
33
|
// The seam's adapters. Registering here — where the lookup happens — keeps the registration
|
|
34
34
|
// explicit and lets a test register a fake under its own id.
|
|
35
35
|
registerAgentBackend(piBackend);
|
|
@@ -78,7 +78,7 @@ export function runAgent(opts) {
|
|
|
78
78
|
// The whole build-and-spawn stage — the marker write, parser construction, run marker,
|
|
79
79
|
// backend-bin resolution, notes shim, run spec, the backend's optional `prepare`, the
|
|
80
80
|
// session-ledger record, translator/decoders, and the child spawn — now lives in
|
|
81
|
-
// spawnPiRun (src/agent/
|
|
81
|
+
// spawnPiRun (src/agent/agent-spawn.ts) and returns the SpawnedRun bundle. A synchronous
|
|
82
82
|
// throw anywhere in it (a broken marker write, parser construction, backend.resolveBin,
|
|
83
83
|
// claude's invocation rejecting a run with no model) propagates out of the await and is
|
|
84
84
|
// rejected by the catch below, matching what a synchronous executor's throw used to get
|
|
@@ -133,7 +133,7 @@ export function runAgent(opts) {
|
|
|
133
133
|
// The streaming pipeline — native-to-canonical stdout split with the over-long-line
|
|
134
134
|
// discard, the stderr window and its fallback-clone warning matcher, the stream-minted
|
|
135
135
|
// session's second ledger record, and the settle/flush that resolves the run — now lives
|
|
136
|
-
// in src/agent/
|
|
136
|
+
// in src/agent/agent-stream.ts behind the AgentStream interface. agent-run only wires the
|
|
137
137
|
// child's `data` events to it and binds its getters into the settlement state below.
|
|
138
138
|
const stream = createAgentStream({
|
|
139
139
|
opts,
|
|
@@ -148,7 +148,7 @@ export function runAgent(opts) {
|
|
|
148
148
|
});
|
|
149
149
|
child.stdout?.on("data", (chunk) => stream.feedStdout(chunk));
|
|
150
150
|
child.stderr?.on("data", (chunk) => stream.feedStderr(chunk));
|
|
151
|
-
// The run state the settlement-half handlers (
|
|
151
|
+
// The run state the settlement-half handlers (agent-settle.ts) read when their event
|
|
152
152
|
// fires. The getters read through to the run's still-mutating feed state, so a handler
|
|
153
153
|
// sees the current value rather than a snapshot taken when its listener was registered.
|
|
154
154
|
const state = {
|
|
@@ -208,7 +208,29 @@ export function runAgent(opts) {
|
|
|
208
208
|
catch (err) {
|
|
209
209
|
// A synchronous throw in the run's build-and-spawn stage (claude's invocation rejects a
|
|
210
210
|
// run with no resolved model) rejects the run, preserving the contract a synchronous
|
|
211
|
-
// executor's throw used to get from the Promise constructor.
|
|
211
|
+
// executor's throw used to get from the Promise constructor. No child was spawned, so no
|
|
212
|
+
// `error`/`close` ever fires and the settle path that removes the private TMPDIR
|
|
213
|
+
// (agent-stream.ts's finish) never runs — remove it here, best-effort like finish does, so
|
|
214
|
+
// a broken run does not leave a `tumwater-run-*` temp dir behind.
|
|
215
|
+
try {
|
|
216
|
+
removeTree(runTmp);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
/* left for the OS temp sweep */
|
|
220
|
+
}
|
|
221
|
+
// The two append-log streams opened before the build-and-spawn stage (the raw and
|
|
222
|
+
// backend-native transcripts) are never handed to a stream-settlement path on this throw
|
|
223
|
+
// — there is no child and no finish(), so nothing else would ever end them — end them
|
|
224
|
+
// here best-effort (the same cleanup the outer setup-failure catch makes) or every
|
|
225
|
+
// rejected run leaks two open transcript descriptors that accumulate in the
|
|
226
|
+
// long-lived orchestrator process.
|
|
227
|
+
try {
|
|
228
|
+
rawLog?.stream.end();
|
|
229
|
+
nativeLog?.stream.end();
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
/* best-effort */
|
|
233
|
+
}
|
|
212
234
|
reject(err instanceof Error ? err : new Error(`runAgent: ${String(err)}`));
|
|
213
235
|
}
|
|
214
236
|
});
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
/** The settlement half of a backend agent run (the split of src/agent/run
|
|
1
|
+
/** The settlement half of a backend agent run (the split of src/agent/agent-run.ts along the
|
|
2
2
|
* same seam the runner-run → runner-outcome split used): everything that happens after the
|
|
3
3
|
* run's live feed — the spawn-`error` result, the `exit` orphan sweep, and the `close`-time
|
|
4
|
-
* flush/reconcile/classify that builds the `PiRunResult`. run
|
|
5
|
-
* (setup, watchdogs, the feed), delegates its build-and-spawn stage to src/agent/
|
|
6
|
-
* its streaming pipeline to src/agent/
|
|
4
|
+
* flush/reconcile/classify that builds the `PiRunResult`. agent-run.ts keeps the lifecycle
|
|
5
|
+
* (setup, watchdogs, the feed), delegates its build-and-spawn stage to src/agent/agent-spawn.ts,
|
|
6
|
+
* its streaming pipeline to src/agent/agent-stream.ts and its `error`/`exit`/`close` listeners
|
|
7
7
|
* here, so the streaming half stays readable.
|
|
8
8
|
*
|
|
9
|
-
* Each handler takes the run's live state (`RunSettleState`, defined in run
|
|
9
|
+
* Each handler takes the run's live state (`RunSettleState`, defined in agent-run.ts and
|
|
10
10
|
* imported here only as a type) and reads what it closes over at the moment its event fires —
|
|
11
11
|
* the live flags it needs are getters on that state. There is no runtime import cycle:
|
|
12
|
-
* run
|
|
12
|
+
* agent-run imports these functions at runtime, and this module imports only agent-run's type.
|
|
13
13
|
*/
|
|
14
14
|
import { backendBinConfigKey, backendBinSourceLabel } from "../pi/pi-bin.js";
|
|
15
15
|
import { terminateChild } from "../process/process.js";
|
|
@@ -144,58 +144,91 @@ export function settleExit(state) {
|
|
|
144
144
|
})
|
|
145
145
|
.catch(() => { });
|
|
146
146
|
}
|
|
147
|
-
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
/** Settle the run's `lastError` text in priority order: a failed blind image read names both
|
|
148
|
+
* the file and the model (plans/image-input.md "Failing the run"), then the abort, quiet-kill
|
|
149
|
+
* and timeout causes the watchdogs and signal path set, then the parser's own event-derived
|
|
150
|
+
* error, else the raw stderr tail. Undefined for a run that simply ended with a message. */
|
|
151
|
+
function settleErrorMessage(state, blindRead, failed, stderr, code) {
|
|
152
|
+
if (blindRead !== undefined) {
|
|
153
|
+
return `read ${blindRead}, but ${state.config.model ?? "the model"} cannot read images`;
|
|
154
|
+
}
|
|
155
|
+
if (state.aborted())
|
|
156
|
+
return "stopped by tumwater shutdown";
|
|
157
|
+
if (state.wd.quietKilled) {
|
|
158
|
+
return `killed as hung: no agent progress for over ${state.config.quietTimeoutSeconds}s`;
|
|
159
|
+
}
|
|
160
|
+
if (state.wd.timedOut) {
|
|
161
|
+
return state.wd.timedOutProgressing
|
|
162
|
+
? `timed out after ${state.wd.timeoutBudgetMs / 1000}s while still making progress — session and worktree edits preserved for resume`
|
|
163
|
+
: `timed out after ${state.wd.timeoutBudgetMs / 1000}s`;
|
|
164
|
+
}
|
|
165
|
+
return (state.parser.errorMessage ??
|
|
166
|
+
(failed ? stderr.trim().slice(-500) || `the agent exited ${code}` : undefined));
|
|
167
|
+
}
|
|
168
|
+
/** Flush the tail the decoders and translator held back at stream end: the decoder's last
|
|
169
|
+
* bytes and unterminated final line, then a batching backend's unclosed final assistant
|
|
170
|
+
* message (which a killed run never closes with a later event). */
|
|
171
|
+
function flushRunTail(state) {
|
|
152
172
|
// Flush any bytes the decoder held back at stream end and any unterminated final line so the
|
|
153
173
|
// tail is not lost, then let the translator flush its own pending canonical line(s) — a
|
|
154
174
|
// batching backend's unclosed last assistant message, which a killed run never closes with a
|
|
155
175
|
// later event.
|
|
156
176
|
state.feedCanonical(state.decoder.end(), true);
|
|
157
|
-
for (const canonical of translator.finalize?.() ?? [])
|
|
158
|
-
state.parser.feed(canonical + "\n", onLine);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
177
|
+
for (const canonical of state.translator.finalize?.() ?? [])
|
|
178
|
+
state.parser.feed(canonical + "\n", state.onLine);
|
|
179
|
+
}
|
|
180
|
+
/** Reconcile the backend's post-run work (opencode's authoritative session export), only when
|
|
181
|
+
* the run is not already settled (a failed spawn settles in the 'error' handler before
|
|
182
|
+
* 'close') and was not killed — a killed run's session is half-done and no export should spend
|
|
183
|
+
* its 10 s on it. Its lines are written and parsed like streamed ones, so they reach the raw
|
|
184
|
+
* log and the parser's authoritative fold (run_cost/run_usage). */
|
|
185
|
+
function reconcileBackendAfterRun(state, killed) {
|
|
162
186
|
// The backend's post-run reconciliation (opencode's authoritative session export), only when
|
|
163
187
|
// the run is not already settled (a failed spawn settles in the 'error' handler before
|
|
164
188
|
// 'close') and was not killed — a killed run's session is half-done and no export should
|
|
165
189
|
// spend its 10 s on it. Its lines are written and parsed like streamed ones, so they reach
|
|
166
190
|
// the raw log and the parser's authoritative fold (run_cost/run_usage).
|
|
167
191
|
if (!state.settled() && !killed) {
|
|
168
|
-
for (const canonical of backend.afterRun?.(runSpec, invocation, translator) ?? []) {
|
|
169
|
-
state.parser.feed(canonical + "\n", onLine);
|
|
192
|
+
for (const canonical of state.backend.afterRun?.(state.runSpec, state.invocation, state.translator) ?? []) {
|
|
193
|
+
state.parser.feed(canonical + "\n", state.onLine);
|
|
170
194
|
}
|
|
171
195
|
}
|
|
172
|
-
|
|
196
|
+
}
|
|
197
|
+
/** Whether the exit reads as failed: killed, a parser error stop, or a non-zero code with no
|
|
198
|
+
* final text. */
|
|
199
|
+
function closeFailed(state, code, killed) {
|
|
200
|
+
return (killed ||
|
|
173
201
|
state.parser.stopReason === "error" ||
|
|
174
|
-
(code !== 0 && !state.parser.finalText.trim());
|
|
202
|
+
(code !== 0 && !state.parser.finalText.trim()));
|
|
203
|
+
}
|
|
204
|
+
/** Whether the exit is a permanent config error — a non-killed exit with no transient pi crash
|
|
205
|
+
* and no backend kind, whose parser flag or backend classification say config error. */
|
|
206
|
+
function closeConfigError(state, cls, killed) {
|
|
207
|
+
return (!killed &&
|
|
208
|
+
cls.transientPiCrash !== true &&
|
|
209
|
+
cls.backendKind === undefined &&
|
|
210
|
+
(state.parser.configError || cls.configError === true));
|
|
211
|
+
}
|
|
212
|
+
/** The run's `close` event: the process died and all its streams closed. Flush what the
|
|
213
|
+
* decoders held back, reconcile the backend's post-run work, classify the exit cause, and
|
|
214
|
+
* settle the run's result. */
|
|
215
|
+
export function settleClose(state, code) {
|
|
216
|
+
const { backend } = state;
|
|
217
|
+
flushRunTail(state);
|
|
218
|
+
// A blind image read on an authoring run deliberately killed it, so its exit is a kill like
|
|
219
|
+
// the watchdogs' — the half-done work is discarded.
|
|
220
|
+
const killed = state.wasKilled();
|
|
221
|
+
reconcileBackendAfterRun(state, killed);
|
|
222
|
+
const failed = closeFailed(state, code, killed);
|
|
175
223
|
// The backend classifies the exit's stderr (pi: the transient JSON-parse crash, a
|
|
176
224
|
// provider-wide backend failure, a permanent config error). The runner merges that with the
|
|
177
225
|
// parser's own event-derived flags — one exit has one cause.
|
|
178
226
|
const stderr = state.stderr();
|
|
179
227
|
const cls = backend.classifyExit({ code, stderr, killed });
|
|
180
228
|
const stderrBackend = cls.backendKind;
|
|
181
|
-
const configError =
|
|
182
|
-
cls.transientPiCrash !== true &&
|
|
183
|
-
stderrBackend === undefined &&
|
|
184
|
-
(state.parser.configError || cls.configError === true);
|
|
185
|
-
// The blind-read failure names both the file and the model (plans/image-input.md
|
|
186
|
-
// "Failing the run"): take it over any generic exit text so lastError is actionable.
|
|
229
|
+
const configError = closeConfigError(state, cls, killed);
|
|
187
230
|
const blindRead = state.blindRead();
|
|
188
|
-
const errorMessage = blindRead
|
|
189
|
-
? `read ${blindRead}, but ${state.config.model ?? "the model"} cannot read images`
|
|
190
|
-
: state.aborted()
|
|
191
|
-
? "stopped by tumwater shutdown"
|
|
192
|
-
: state.wd.quietKilled
|
|
193
|
-
? `killed as hung: no agent progress for over ${state.config.quietTimeoutSeconds}s`
|
|
194
|
-
: state.wd.timedOut
|
|
195
|
-
? state.wd.timedOutProgressing
|
|
196
|
-
? `timed out after ${state.wd.timeoutBudgetMs / 1000}s while still making progress — session and worktree edits preserved for resume`
|
|
197
|
-
: `timed out after ${state.wd.timeoutBudgetMs / 1000}s`
|
|
198
|
-
: (state.parser.errorMessage ?? (failed ? stderr.trim().slice(-500) || `the agent exited ${code}` : undefined));
|
|
231
|
+
const errorMessage = settleErrorMessage(state, blindRead, failed, stderr, code);
|
|
199
232
|
state.finish(resultFromParser(state, {
|
|
200
233
|
ok: !failed,
|
|
201
234
|
transientPiCrash: cls.transientPiCrash === true,
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* parser, the run marker, backend-bin resolution, the notes shim, the run spec, the backend's
|
|
4
4
|
* optional `prepare`, the session-ledger record, the native-to-canonical translator, the two
|
|
5
5
|
* incremental decoders, and the child spawn. It returns the `SpawnedRun` bundle the coordinator
|
|
6
|
-
* (src/agent/run
|
|
7
|
-
* createAgentStream (src/agent/
|
|
8
|
-
* Part 1/2 of the runAgent split: the streaming pipeline (part 2/2) lives in
|
|
9
|
-
* This module imports only run
|
|
6
|
+
* (src/agent/agent-run.ts) binds its watchdogs and settlement wiring to, and passes to
|
|
7
|
+
* createAgentStream (src/agent/agent-stream.ts).
|
|
8
|
+
* Part 1/2 of the runAgent split: the streaming pipeline (part 2/2) lives in agent-stream.ts.
|
|
9
|
+
* This module imports only agent-run's type surface, so
|
|
10
10
|
* there is no runtime import cycle. */
|
|
11
11
|
import { spawn } from "node:child_process";
|
|
12
12
|
import fs from "node:fs";
|
|
@@ -15,7 +15,7 @@ import { ensureParentDir, rotateIfLarge } from "../files/files.js";
|
|
|
15
15
|
import { terminateChild } from "../process/process.js";
|
|
16
16
|
import { makeRunMarker } from "../process/run-marker.js";
|
|
17
17
|
import { PiStreamParser } from "../pi/pi-stream.js";
|
|
18
|
-
import { recordSession } from "./session-ledger.js";
|
|
18
|
+
import { recordSession, sessionRecordFor } from "./session-ledger.js";
|
|
19
19
|
import { errorMessage } from "../text/text.js";
|
|
20
20
|
import { warnEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
21
21
|
import { seamImageInput } from "./image-input.js";
|
|
@@ -44,15 +44,7 @@ function buildAndSpawn(backend, opts, pre) {
|
|
|
44
44
|
// session of its own kind instead of whatever file is newest in the dir. The id is the backend's
|
|
45
45
|
// own when it mints one ahead of the run (claude's UUID, or the resumed record's id); pi mints
|
|
46
46
|
// none and keeps the session name.
|
|
47
|
-
recordSession(opts.sessionDir,
|
|
48
|
-
agent: backend.id,
|
|
49
|
-
id: preSpawnSessionId,
|
|
50
|
-
kind: opts.kind,
|
|
51
|
-
name: opts.sessionName,
|
|
52
|
-
cwd: opts.cwd,
|
|
53
|
-
...(opts.config.model !== undefined ? { model: opts.config.model } : {}),
|
|
54
|
-
at: Date.now(),
|
|
55
|
-
});
|
|
47
|
+
recordSession(opts.sessionDir, sessionRecordFor(backend.id, preSpawnSessionId, opts));
|
|
56
48
|
const translator = backend.translator();
|
|
57
49
|
// Decode stdout incrementally instead of per chunk: a raw Buffer.toString("utf8") replaces any
|
|
58
50
|
// multi-byte character whose bytes straddle two 'data' events with U+FFFD, corrupting that
|
|
@@ -108,6 +100,15 @@ function buildAndSpawn(backend, opts, pre) {
|
|
|
108
100
|
blindRead: () => blind.read,
|
|
109
101
|
};
|
|
110
102
|
}
|
|
103
|
+
/** Warn on a run's own root that a backend's optional `prepare` step failed, then let the run
|
|
104
|
+
* go on without it — the shared tail of the synchronous-throw and async-rejection prepare guards
|
|
105
|
+
* below, so both warn the same way. `warnRoot` is undefined when the session dir has no team
|
|
106
|
+
* root; the warning is then skipped because there is no tumwater event log to write to. */
|
|
107
|
+
function warnPrepareFailed(warnRoot, error) {
|
|
108
|
+
if (warnRoot !== undefined) {
|
|
109
|
+
warnEventBestEffort(warnRoot, TUMWATER_SOURCE, `agent prepare failed, run continues: ${errorMessage(error)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
111
112
|
/** Build and spawn one non-interactive agent run, from the marker write through the child spawn,
|
|
112
113
|
* and return the bundle the run's stream pipeline, watchdogs and settlement wiring consume. The
|
|
113
114
|
* caller's setup (opened logs, the run's private TMPDIR) is passed in; a synchronous throw here
|
|
@@ -146,8 +147,7 @@ export function spawnPiRun(backend, logs, runTmp, opts) {
|
|
|
146
147
|
}
|
|
147
148
|
const parser = new PiStreamParser(opts.onToolCallStart, {
|
|
148
149
|
// The seam's effective image input (src/agent/image-input.ts): a text-only seam makes every
|
|
149
|
-
// image read blind even when pi adds no note —
|
|
150
|
-
// the parser checks against its own note rule.
|
|
150
|
+
// image read blind even when pi adds no note — the parser checks against its own note rule.
|
|
151
151
|
imageInput: seamImageInput(opts.config, readPiProviders(piModelsPath())),
|
|
152
152
|
onBlindRead: (path) => {
|
|
153
153
|
// Kill an authoring run at once, before the agent goes on working from a picture it never
|
|
@@ -160,8 +160,12 @@ export function spawnPiRun(backend, logs, runTmp, opts) {
|
|
|
160
160
|
},
|
|
161
161
|
});
|
|
162
162
|
// The run's cross-group attribution mark, minted before the spawn so the environment can carry
|
|
163
|
-
// it and the exit sweep can name it.
|
|
164
|
-
|
|
163
|
+
// it and the exit sweep can name it. When the session sits under a team root, the mark is
|
|
164
|
+
// stamped with that root's token so a dead-marked orphan can be attributed to this checkout
|
|
165
|
+
// and never reaped as another team's (BUGS.md, "Reap the doctor's orphans, part 1/3"); a
|
|
166
|
+
// session with no team root (a lab script, a smoke run, a prompt-lab replay) stays rootless.
|
|
167
|
+
const markerRoot = nearestTumwaterRoot(opts.sessionDir);
|
|
168
|
+
const runMarker = makeRunMarker(process.pid, markerRoot);
|
|
165
169
|
const resolved = backend.resolveBin(opts.config);
|
|
166
170
|
// Install the notes shim under the team root's `.tumwater/bin` so every backend's run finds
|
|
167
171
|
// `tumwater-notes` first on PATH (notes-shim.ts). A failure to write it logs one warning and
|
|
@@ -211,9 +215,7 @@ export function spawnPiRun(backend, logs, runTmp, opts) {
|
|
|
211
215
|
prepared = backend.prepare?.(runSpec);
|
|
212
216
|
}
|
|
213
217
|
catch (error) {
|
|
214
|
-
|
|
215
|
-
warnEventBestEffort(warnRoot, TUMWATER_SOURCE, `agent prepare failed, run continues: ${errorMessage(error)}`);
|
|
216
|
-
}
|
|
218
|
+
warnPrepareFailed(warnRoot, error);
|
|
217
219
|
}
|
|
218
220
|
if (prepared === undefined) {
|
|
219
221
|
// No `prepare` step: build and spawn synchronously and hand the bundle straight back, so
|
|
@@ -226,9 +228,7 @@ export function spawnPiRun(backend, logs, runTmp, opts) {
|
|
|
226
228
|
await prepared;
|
|
227
229
|
}
|
|
228
230
|
catch (error) {
|
|
229
|
-
|
|
230
|
-
warnEventBestEffort(warnRoot, TUMWATER_SOURCE, `agent prepare failed, run continues: ${errorMessage(error)}`);
|
|
231
|
-
}
|
|
231
|
+
warnPrepareFailed(warnRoot, error);
|
|
232
232
|
}
|
|
233
233
|
return buildAndSpawn(backend, opts, { runSpec, parser, runMarker, resolved, blind, runTmp });
|
|
234
234
|
})();
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* canonical stdout line splitting with its over-long-line discard, the stderr window and its
|
|
4
4
|
* fallback-clone warning matcher, the stream-minted session's second ledger record, and the
|
|
5
5
|
* settle/flush that resolves the run's promise. It is the part of the runAgent split (part
|
|
6
|
-
* 2/2) that the coordinator (src/agent/run
|
|
7
|
-
* interface: run
|
|
6
|
+
* 2/2) that the coordinator (src/agent/agent-run.ts) delegates to behind the `AgentStream`
|
|
7
|
+
* interface: agent-run wires the child's `data` events to `feedStdout`/`feedStderr`, binds the
|
|
8
8
|
* settlement state's stream getters, and keeps the watchdogs, abort wiring and the
|
|
9
|
-
* `error`/`exit`/`close` listeners itself. This module imports only run
|
|
10
|
-
* and
|
|
9
|
+
* `error`/`exit`/`close` listeners itself. This module imports only agent-run's, agent-spawn's
|
|
10
|
+
* and agent-settle's type surfaces, so there is no runtime import cycle. */
|
|
11
11
|
import { removeTree } from "../files/files.js";
|
|
12
|
-
import { recordSession } from "./session-ledger.js";
|
|
12
|
+
import { recordSession, sessionRecordFor } from "./session-ledger.js";
|
|
13
13
|
/** How many trailing stderr characters are carried into the next chunk's warning match, so a
|
|
14
14
|
* warning split across two `data` events is still seen. The warnings are short lines, so a few
|
|
15
15
|
* hundred characters always cover a straddle. */
|
|
@@ -60,15 +60,7 @@ export function createAgentStream(input) {
|
|
|
60
60
|
const sid = translator.sessionId?.();
|
|
61
61
|
if (sid !== undefined && sid !== preSpawnSessionId && !streamMintedSession) {
|
|
62
62
|
streamMintedSession = true;
|
|
63
|
-
recordSession(opts.sessionDir,
|
|
64
|
-
agent: backend.id,
|
|
65
|
-
id: sid,
|
|
66
|
-
kind: opts.kind,
|
|
67
|
-
name: opts.sessionName,
|
|
68
|
-
cwd: opts.cwd,
|
|
69
|
-
...(opts.config.model !== undefined ? { model: opts.config.model } : {}),
|
|
70
|
-
at: Date.now(),
|
|
71
|
-
});
|
|
63
|
+
recordSession(opts.sessionDir, sessionRecordFor(backend.id, sid, opts));
|
|
72
64
|
}
|
|
73
65
|
};
|
|
74
66
|
// Split native stdout into complete lines, translate each, and feed the canonical lines to
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* read (plans/opencode-backend.md "Groundwork: the claude-only branches become backend
|
|
4
4
|
* traits"). Pure — imports nothing from the runtime registry (agent-backend.ts) — so those
|
|
5
5
|
* layers can read a backend's traits without pulling the registry in. That is the same reason
|
|
6
|
-
* `BACKEND_DEFAULT_MODELS` lives in src/config/model-selector.ts.
|
|
6
|
+
* `BACKEND_DEFAULT_MODELS` lives in src/config/config-model-selector.ts.
|
|
7
7
|
*
|
|
8
|
-
* An id with no entry reads as pi, because src/agent/run
|
|
8
|
+
* An id with no entry reads as pi, because src/agent/agent-run.ts routes an unregistered id to
|
|
9
9
|
* the pi backend.
|
|
10
10
|
*/
|
|
11
11
|
import { HARNESS_CLAUDE_FLAGS } from "./claude/claude-args.js";
|
|
@@ -95,3 +95,17 @@ export function backendDefaultLabel(backend) {
|
|
|
95
95
|
* for one cannot go missing from the other (opencode classifies the same failure from message
|
|
96
96
|
* text, not a status code, so it is not in the set). */
|
|
97
97
|
export const SERVER_TRANSIENT_STATUS = new Set([500, 502, 503, 504, 529]);
|
|
98
|
+
/** Classify a failure message's prose into the transient-backend failure it names, when the
|
|
99
|
+
* wire carries no structured signal: a dropped connection or a timeout, read from the message
|
|
100
|
+
* text by claude's and opencode's stream translators, which both fall back to prose for the
|
|
101
|
+
* statuses their native events leave to the message (claude's classifyResult and opencode's
|
|
102
|
+
* classifyError end with this same connection/then-timeout tail). The single home of the
|
|
103
|
+
* connection/timeout vocabulary, so the two backends' tail regexes cannot drift. undefined
|
|
104
|
+
* when the text names neither. */
|
|
105
|
+
export function transientProseKind(text) {
|
|
106
|
+
if (/connection|econn|refused|reset|terminated/i.test(text))
|
|
107
|
+
return { kind: "transientBackend", backendKind: "connection" };
|
|
108
|
+
if (/timed out|timeout/i.test(text))
|
|
109
|
+
return { kind: "transientBackend", backendKind: "timeout" };
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
@@ -17,7 +17,7 @@ import { spawnSync } from "node:child_process";
|
|
|
17
17
|
import os from "node:os";
|
|
18
18
|
import { resolvedBackendConfig } from "../../config/config-views.js";
|
|
19
19
|
import { parseJsonObject } from "../../files/json-object.js";
|
|
20
|
-
import { observeAccount, readAccount, subscriptionWindowRejected,
|
|
20
|
+
import { observeAccount, readAccount, subscriptionWindowRejected, windowEntries, windowLabel, windowReserved, windowUsedUp, writeAccount, } from "../subscription-account.js";
|
|
21
21
|
import { claudeBackend, claudeConfigDir } from "./claude-backend.js";
|
|
22
22
|
import { claudeEnv, claudeThirdPartyProvider } from "./claude-env.js";
|
|
23
23
|
/** Read the persisted claude account state, or null when it is absent or unreadable. A file
|
|
@@ -57,13 +57,6 @@ export { subscriptionWindowRejected, windowReserved };
|
|
|
57
57
|
export function claudeWindowLabel(type) {
|
|
58
58
|
return windowLabel(type);
|
|
59
59
|
}
|
|
60
|
-
/** The fraction of a subscription usage window consumed as a whole-number percent —
|
|
61
|
-
* subscription-account.ts's utilizationPct, under its historical claude name. */
|
|
62
|
-
export function claudeUtilizationPct(utilization) {
|
|
63
|
-
return utilizationPct(utilization);
|
|
64
|
-
}
|
|
65
|
-
/** The account's windows as a list, most constrained first — subscription-account.ts's
|
|
66
|
-
* windowEntries, under its historical claude name. */
|
|
67
60
|
export function claudeWindowEntries(account) {
|
|
68
61
|
return windowEntries(account);
|
|
69
62
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The claude adapter for the agent seam (plans/agent-backends.md "The seam", "Sessions",
|
|
2
2
|
* "The claude invocation"). It shapes one run's argv/stdin/env, translates claude's
|
|
3
3
|
* `stream-json` into the canonical stream, and classifies the exit's stderr — everything
|
|
4
|
-
* backend-specific. src/agent/
|
|
4
|
+
* backend-specific. src/agent/agent-spawn.ts spawns the run and records this run in the session
|
|
5
5
|
* ledger; claude mints its own session id (a fresh UUID, or the resumed record's id), which
|
|
6
6
|
* `invocation` returns so the ledger can carry it.
|
|
7
7
|
*
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* missing transcript, a different cwd, or another kind's newer session all review fresh rather
|
|
12
12
|
* than resuming the wrong conversation.
|
|
13
13
|
*
|
|
14
|
-
* Registration lives in src/agent/run
|
|
14
|
+
* Registration lives in src/agent/agent-run.ts beside the pi adapter, so a test can register a
|
|
15
15
|
* fake under its own id; a test selects claude by setting `config.agent = "claude"` on a run. */
|
|
16
16
|
import fs from "node:fs";
|
|
17
17
|
import os from "node:os";
|
|
@@ -19,7 +19,7 @@ import path from "node:path";
|
|
|
19
19
|
import { randomUUID } from "node:crypto";
|
|
20
20
|
import { resolvedBackendConfig } from "../../config/config-views.js";
|
|
21
21
|
import { MODEL_TIERS } from "../../config/config-schema.js";
|
|
22
|
-
import { routeModel } from "../../config/model-selector.js";
|
|
22
|
+
import { routeModel } from "../../config/config-model-selector.js";
|
|
23
23
|
import { backendRunEnv } from "../../process/run-marker.js";
|
|
24
24
|
import { resolveBinForBackend } from "../../pi/pi-bin.js";
|
|
25
25
|
import { expandHomeTilde } from "../../files/files.js";
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* the foreground. `BASH_MAX_TIMEOUT_MS` is derived from `quietTimeoutSeconds` so claude's own
|
|
18
18
|
* Bash timeout fires before tumwater's quiet kill and the model sees a tool error rather than a
|
|
19
19
|
* dead run. */
|
|
20
|
+
import { scrubEnv } from "../env-scrub.js";
|
|
20
21
|
/** The cloud-provider switches that make a claude run bill as API rather than a claude.ai
|
|
21
22
|
* subscription: Bedrock, Vertex, and Azure Foundry. They carry operator intent, so the run
|
|
22
23
|
* environment keeps them (the allowlist below); claude-account's seedBilling and this module's
|
|
@@ -54,35 +55,30 @@ const MIN_BASH_MAX_TIMEOUT_MS = 1_000;
|
|
|
54
55
|
/** Build the claude process environment from `base`. Never mutates `base`. */
|
|
55
56
|
export function claudeEnv(base, opts) {
|
|
56
57
|
const hadClaudeCode = base.CLAUDECODE !== undefined;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// own config and transcripts under it, so a funes hook on ~/.claude/projects never indexes
|
|
84
|
-
// team runs. Unset leaves any inherited CLAUDE_CONFIG_DIR (or claude's default) untouched.
|
|
85
|
-
if (opts.configDir)
|
|
86
|
-
env.CLAUDE_CONFIG_DIR = opts.configDir;
|
|
87
|
-
return env;
|
|
58
|
+
return scrubEnv(base, {
|
|
59
|
+
keep: (key) => (key !== "CLAUDECODE" && key !== "CLAUDE_PID" && key !== "CLAUDE_EFFORT") &&
|
|
60
|
+
!(key.startsWith("CLAUDE_CODE_") && !CLAUDE_CODE_ALLOWLIST.has(key)) &&
|
|
61
|
+
!key.startsWith("CLAUDE_AGENT_SDK_") &&
|
|
62
|
+
// The host session's proxy, not the operator's: only when CLAUDECODE says we are inside one.
|
|
63
|
+
!(key === "ANTHROPIC_BASE_URL" && hadClaudeCode),
|
|
64
|
+
then: (env) => {
|
|
65
|
+
env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = "1";
|
|
66
|
+
env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS = "1";
|
|
67
|
+
env.BASH_DEFAULT_TIMEOUT_MS = String(BASH_DEFAULT_TIMEOUT_MS);
|
|
68
|
+
if (opts.quietTimeoutSeconds > 0) {
|
|
69
|
+
// 60 s under the quiet kill, so claude's tool error arrives before the run is
|
|
70
|
+
// declared hung; floored so a short quiet window never derives a non-positive cap.
|
|
71
|
+
const derived = opts.quietTimeoutSeconds * 1000 - 60_000;
|
|
72
|
+
env.BASH_MAX_TIMEOUT_MS = String(Math.max(derived, MIN_BASH_MAX_TIMEOUT_MS));
|
|
73
|
+
}
|
|
74
|
+
env.DISABLE_AUTOUPDATER = "1";
|
|
75
|
+
env.DISABLE_TELEMETRY = "1";
|
|
76
|
+
env.DISABLE_ERROR_REPORTING = "1";
|
|
77
|
+
// A team config dir (plans/notes-command.md "Keeping funes out of team runs"): force claude's
|
|
78
|
+
// own config and transcripts under it, so a funes hook on ~/.claude/projects never indexes
|
|
79
|
+
// team runs. Unset leaves any inherited CLAUDE_CONFIG_DIR (or claude's default) untouched.
|
|
80
|
+
if (opts.configDir)
|
|
81
|
+
env.CLAUDE_CONFIG_DIR = opts.configDir;
|
|
82
|
+
},
|
|
83
|
+
});
|
|
88
84
|
}
|