tumwater 0.2.0 → 0.4.0
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 +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +55 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +6 -4
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +404 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +281 -55
- package/dist/src/backlog/backlog-cli-view.js +255 -0
- package/dist/src/backlog/backlog-cli.js +91 -335
- package/dist/src/backlog/backlog-commit.js +20 -13
- package/dist/src/backlog/backlog-eligibility.js +193 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +264 -0
- package/dist/src/backlog/backlog-release.js +134 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-guardrails.js +136 -0
- package/dist/src/baseline/main-red.js +153 -36
- package/dist/src/baseline/main-watch.js +232 -0
- package/dist/src/brief/brief-edit.js +12 -12
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget-fallback-view.js +288 -0
- package/dist/src/budget/budget-wiring.js +158 -0
- package/dist/src/budget/budget.js +31 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +65 -3
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +181 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +183 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +291 -598
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-review-commands.js +113 -0
- package/dist/src/cli/cli-role-command.js +525 -0
- package/dist/src/cli/cli-start-summary.js +167 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
- package/dist/src/cli/completion.js +169 -22
- package/dist/src/cli/config-commands.js +58 -26
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +258 -82
- package/dist/src/cli/log-commands.js +164 -85
- package/dist/src/cli/question-commands.js +211 -10
- package/dist/src/cli.js +173 -45
- package/dist/src/collections.js +56 -13
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +16 -1
- package/dist/src/config/config-fallbacks.js +94 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +169 -222
- package/dist/src/config/config-views.js +136 -54
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +164 -265
- package/dist/src/config/config.js +53 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
- package/dist/src/doctor/doctor-backlog-plans.js +398 -0
- package/dist/src/doctor/doctor-backlog.js +249 -512
- package/dist/src/doctor/doctor-check-names.js +85 -0
- package/dist/src/doctor/doctor-checks.js +61 -593
- package/dist/src/doctor/doctor-claude-checks.js +142 -0
- package/dist/src/doctor/doctor-codex-checks.js +276 -0
- package/dist/src/doctor/doctor-contention-checks.js +506 -0
- package/dist/src/doctor/doctor-fallback-checks.js +130 -0
- package/dist/src/doctor/doctor-holds.js +80 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +216 -537
- package/dist/src/doctor/doctor-notify.js +9 -5
- package/dist/src/doctor/doctor-opencode-checks.js +164 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-pi-checks.js +62 -0
- package/dist/src/doctor/doctor-project-checks.js +397 -0
- package/dist/src/doctor/doctor.js +126 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +21 -12
- package/dist/src/events/event-format.js +185 -89
- package/dist/src/events/event-labels.js +11 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +52 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +80 -26
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +65 -60
- package/dist/src/failure/failure-render.js +29 -21
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +35 -35
- package/dist/src/files/file-queue.js +17 -0
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +88 -225
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +101 -143
- package/dist/src/gates/maintenance-quota.js +37 -30
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +89 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +54 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +43 -19
- package/dist/src/gui/gui-backlog-endpoints.js +156 -0
- package/dist/src/gui/gui-command.js +26 -13
- package/dist/src/gui/gui-config-commands.js +168 -80
- package/dist/src/gui/gui-endpoint-commands.js +5 -4
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +235 -13
- package/dist/src/gui/gui-prompt-commands.js +26 -20
- package/dist/src/gui/gui-report-endpoints.js +97 -0
- package/dist/src/gui/gui-server.js +26 -5
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +155 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +190 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +28 -17
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +58 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +45 -19
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +214 -0
- package/dist/src/merge/merge-queue-render.js +48 -11
- package/dist/src/merge/merge-queue.js +52 -15
- package/dist/src/merge/merge-requeue.js +103 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +27 -11
- package/dist/src/merge/merge-to-main.js +77 -26
- package/dist/src/merge/merge-top.js +83 -0
- package/dist/src/merge/merge-verify.js +75 -18
- package/dist/src/notes-cli.js +35 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +352 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
- package/dist/src/orchestrator/orchestrator.js +129 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +245 -0
- package/dist/src/orchestrator/scheduling-skip.js +195 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +126 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +26 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +150 -217
- package/dist/src/prompt/notes-backlog.js +184 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +131 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +188 -124
- package/dist/src/report/report-fold.js +130 -54
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +136 -65
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +23 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/diff-rules.js +53 -0
- package/dist/src/review/known-flakes.js +112 -1
- package/dist/src/review/post-merge-review-accept.js +51 -0
- package/dist/src/review/post-merge-review-render.js +112 -0
- package/dist/src/review/post-merge-review.js +239 -0
- package/dist/src/review/protected-edits.js +24 -10
- package/dist/src/review/quarantine-render.js +65 -0
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +180 -401
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +127 -12
- package/dist/src/roles/role-guidance.js +65 -20
- package/dist/src/roles/role-labels.js +7 -5
- package/dist/src/roles/role-render.js +108 -56
- package/dist/src/roles/role-view.js +95 -56
- package/dist/src/roles/roles.js +33 -33
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/ci-parity.js +121 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/run/qa-coverage.js +125 -0
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +175 -0
- package/dist/src/run/run-detail-render.js +91 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +178 -0
- package/dist/src/run/stage-checks-shared.js +65 -0
- package/dist/src/run/stage-name-checks.js +204 -0
- package/dist/src/run/stage-path-checks.js +231 -0
- package/dist/src/run/stage-prose-checks.js +261 -0
- package/dist/src/runner/leftover.js +216 -0
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +191 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +109 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +103 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claim-entries.js +49 -0
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +49 -17
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +43 -28
- package/dist/src/status/status-roles.js +50 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +7 -8
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +65 -18
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +60 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +78 -13
- package/dist/src/ui/badges.js +99 -38
- package/dist/src/ui/gui/gui-client-backlog.js +314 -0
- package/dist/src/ui/gui/gui-client-boot.js +89 -61
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
- package/dist/src/ui/gui/gui-client-drawer.js +155 -289
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +437 -68
- package/dist/src/ui/gui/gui-client-labels.js +6 -3
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +60 -52
- package/dist/src/ui/gui/gui-client-pending.js +405 -24
- package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
- package/dist/src/ui/gui/gui-client-report.js +105 -430
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +163 -5
- package/dist/src/ui/gui/gui-client-team.js +36 -465
- package/dist/src/ui/gui/gui-client.js +38 -26
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +10 -7
- package/dist/src/ui/gui/gui-styles.js +38 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +364 -49
- package/dist/src/ui/status-render.js +109 -82
- package/dist/src/ui/team-alerts.js +728 -62
- package/dist/src/ui/tone.js +12 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +53 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +4 -12
- package/dist/src/ui/tui/tui-editor-backends.js +127 -0
- package/dist/src/ui/tui/tui-editor-brief.js +14 -36
- package/dist/src/ui/tui/tui-editor-budget.js +36 -39
- package/dist/src/ui/tui/tui-editor-check.js +29 -0
- package/dist/src/ui/tui/tui-editor-checks.js +28 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
- package/dist/src/ui/tui/tui-editor-config.js +99 -0
- package/dist/src/ui/tui/tui-editor-entry.js +39 -64
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +46 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
- package/dist/src/ui/tui/tui-editor-interval.js +29 -16
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +9 -29
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
- package/dist/src/ui/tui/tui-editor-retire.js +5 -13
- package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +101 -34
- package/dist/src/ui/tui/tui-editor-slots.js +24 -0
- package/dist/src/ui/tui/tui-editors.js +44 -2
- package/dist/src/ui/tui/tui-frame.js +68 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +33 -9
- package/dist/src/ui/tui/tui-pane-activity.js +35 -19
- package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +168 -8
- package/dist/src/ui/tui/tui-pane-history.js +446 -82
- package/dist/src/ui/tui/tui-pane-pending.js +539 -15
- package/dist/src/ui/tui/tui-pane-queued.js +78 -57
- package/dist/src/ui/tui/tui-pane-report.js +54 -16
- package/dist/src/ui/tui/tui-pane-session.js +34 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +91 -28
- package/dist/src/ui/tui/tui-panes.js +153 -34
- package/dist/src/ui/tui/tui.js +18 -15
- package/dist/src/verdict/fix-claim.js +129 -33
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +77 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/leftover.js +0 -130
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/qa-coverage.js +0 -108
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundled pi extension: bounds oversized tool results so a single read or command cannot
|
|
3
|
-
* flood the
|
|
3
|
+
* flood the run's context window (PLANS.md "Bound tool output head+tail with a tumwater
|
|
4
4
|
* pi extension"). Loaded on every pi run via `-e <this file>` from src/pi/pi-args.ts.
|
|
5
5
|
*
|
|
6
6
|
* All bounding logic lives in pure, filesystem-free exported functions — `boundText`,
|
|
@@ -12,14 +12,16 @@
|
|
|
12
12
|
* harness root and writes the file.
|
|
13
13
|
*
|
|
14
14
|
* Limits are constants on purpose (opinionated defaults over configuration). The read
|
|
15
|
-
* limit (~12k chars ≈ 300 lines)
|
|
15
|
+
* limit (~12k chars ≈ 300 lines) backs the prompt's contextBudgetRule, which asks for ranged
|
|
16
|
+
* reads past ~150 lines, the share a 64k window's scaled limit holds; the bash limit
|
|
16
17
|
* is looser because test logs and build output legitimately need more tail. Both were tuned on
|
|
17
18
|
* the 128k local fallback, so a model that declares a smaller window gets them scaled down in
|
|
18
19
|
* proportion (`outputLimits`): a 12k-char result is twice the share of a 64k window.
|
|
19
20
|
*/
|
|
20
21
|
import { windowScale } from "./context-window.js";
|
|
21
22
|
import { writeFullOutput } from "./full-output.js";
|
|
22
|
-
/** Char budget for a `read` tool result (~300 lines
|
|
23
|
+
/** Char budget for a `read` tool result (~300 lines; contextBudgetRule asks for ranges past
|
|
24
|
+
* ~150). */
|
|
23
25
|
export const READ_LIMIT_CHARS = 12_000;
|
|
24
26
|
/** Char budget for a `bash` tool result — looser, so test/build tails survive. */
|
|
25
27
|
export const BASH_LIMIT_CHARS = 16_000;
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* the note is the stop-reading backstop for a run whose remaining results were not reclaimable. (On
|
|
13
13
|
* a window under 128k context-shake crosses earlier, under pi's compaction line — see its
|
|
14
14
|
* `shakeLimits` — while these notes keep their percentages.) The
|
|
15
|
-
* failure mode the numbers exist for is the fallback's local model (Qwen3.8-27B): its longest
|
|
16
|
-
* peaked at 96–110k tokens (coverage, steward,
|
|
15
|
+
* failure mode the numbers exist for is the fallback's local model (Qwen3.8-27B): its longest runs
|
|
16
|
+
* peaked at 96–110k tokens (coverage, steward, selfcheck, plan; 2026-09-29..10-01) while the
|
|
17
17
|
* prompt's reading budget could only say "your window is finite". On a large-window model the
|
|
18
18
|
* thresholds are rarely reached, so the extension costs nothing there.
|
|
19
19
|
*
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* old tool results with recoverable references and keep the recent window intact. pi 1.0.0 can
|
|
12
12
|
* do the same from an extension — a `turn_end` handler appends persisted `context_edit` entries
|
|
13
13
|
* (pi docs, extensions.md "Events and concurrency"; session-format.md "ContextEditEntry"). On a
|
|
14
|
-
* large-window primary (1M) the 70% line sits far above the largest team
|
|
14
|
+
* large-window primary (1M) the 70% line sits far above the largest team run, so the
|
|
15
15
|
* extension plans nothing and costs nothing there.
|
|
16
16
|
*
|
|
17
17
|
* The selection logic is a pure, exported function (`shakePlan`) so every rule is unit-testable
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import fs from "node:fs";
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { toolOutputDir } from "../paths.js";
|
|
17
|
-
import { walkUp, writeTextAtomic } from "../files/files.js";
|
|
17
|
+
import { isDirectory, walkUp, writeTextAtomic } from "../files/files.js";
|
|
18
18
|
/** Walk up from `startDir` looking for a `.tumwater/` directory — the harness root. Worktrees
|
|
19
19
|
* under it (`.tumwater/worktrees/<role>`, merger, gate alike) reach it three levels up
|
|
20
20
|
* (`../../..`, the repo checkout that owns `.tumwater/`); a harness root carries it directly.
|
|
@@ -22,14 +22,7 @@ import { walkUp, writeTextAtomic } from "../files/files.js";
|
|
|
22
22
|
* terminates without walking to the filesystem root; returns null when no ancestor within
|
|
23
23
|
* that bound has one, so such a run writes nothing. Exported for tests. */
|
|
24
24
|
export function findTumwaterRoot(startDir = process.cwd()) {
|
|
25
|
-
return walkUp(startDir, 63, (dir) =>
|
|
26
|
-
try {
|
|
27
|
-
return fs.statSync(path.join(dir, ".tumwater")).isDirectory() ? dir : null;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return null; // Not here — keep walking up.
|
|
31
|
-
}
|
|
32
|
-
});
|
|
25
|
+
return walkUp(startDir, 63, (dir) => (isDirectory(path.join(dir, ".tumwater")) ? dir : null));
|
|
33
26
|
}
|
|
34
27
|
/** Persist a full tool output under the harness's gitignored `.tumwater/` area and return
|
|
35
28
|
* its path, or null when there is no harness root (or the write fails). Files are named
|
|
@@ -41,7 +34,7 @@ export function findTumwaterRoot(startDir = process.cwd()) {
|
|
|
41
34
|
* orchestrator's retention passes prune this directory with the team's
|
|
42
35
|
* sessionRetentionDays window (paths.ts's toolOutputDir, the single home of this path that
|
|
43
36
|
* this module shares instead of re-joining it), so a pointer to a full output stays readable
|
|
44
|
-
* while its
|
|
37
|
+
* while its run is recent and never accumulates forever. */
|
|
45
38
|
export function writeFullOutput(text, toolCallId, startDir = process.cwd()) {
|
|
46
39
|
const root = findTumwaterRoot(startDir);
|
|
47
40
|
if (!root)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/**
|
|
3
2
|
* Bundled pi extension: send pi's system prompt without its `docs` section, on every model.
|
|
4
3
|
* Loaded on every pi run via `-e <this file>` from src/pi/pi-args.ts.
|
|
@@ -6,15 +5,15 @@
|
|
|
6
5
|
* Why: pi 1.2.0's default system prompt carries a `<docs>` section — the install paths of pi's own
|
|
7
6
|
* README, docs and examples, to read "only when the user asks about pi itself". A tumwater run
|
|
8
7
|
* never does, and the section is ~1,400 characters (~350 tokens) that every request re-sends,
|
|
9
|
-
* alongside tumwater's own run prompt, which is
|
|
10
|
-
*
|
|
8
|
+
* alongside tumwater's own run prompt, which is compact on every model too (prompt.ts). Until
|
|
9
|
+
* 2026-10-18 both applied only at a declared window of at most 128k; they
|
|
11
10
|
* now apply everywhere to cut token spend (a 2026-10-18 audit of the team's 1M-window runs). pi
|
|
12
11
|
* offers no option to leave the section out short of replacing the whole prompt, so the
|
|
13
12
|
* `before_agent_start` handler returns the rendered prompt with that one section cut — the
|
|
14
13
|
* preamble, tool list, tool rules and working directory are pi's own text.
|
|
15
14
|
*
|
|
16
15
|
* The cut is a pure exported function so it is unit-testable without pi; the default export is
|
|
17
|
-
* the thin adapter over pi's `before_agent_start` event
|
|
16
|
+
* the thin adapter over pi's `before_agent_start` event.
|
|
18
17
|
*/
|
|
19
18
|
/** pi's docs section as rendered, with the blank lines that separate it from the section before
|
|
20
19
|
* it. pi wraps each named section in a tag of the same name. */
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled pi extension: refuse an `edit` or `write` that would land under the team root but
|
|
3
|
+
* outside the run's own worktree, and name the path inside the worktree to use instead. Loaded on
|
|
4
|
+
* every pi run via `-e <this file>` from src/pi/pi-args.ts.
|
|
5
|
+
*
|
|
6
|
+
* The rule lives in src/agent/write-guard.ts, shared with claude's hook. This file is the thin
|
|
7
|
+
* adapter over pi's `tool_call` event, which blocks a call when a handler returns
|
|
8
|
+
* `{ block: true, reason }` (pi docs, extensions.md "Events and concurrency"); the model gets the
|
|
9
|
+
* reason as the tool's error. A nested call from a codemode script goes through the same event.
|
|
10
|
+
* pi blocks the tool when a handler throws, so an unexpected failure here lets the write through
|
|
11
|
+
* rather than stopping every edit of the run: runner-run.ts's stray-write warning still sees it.
|
|
12
|
+
*/
|
|
13
|
+
import { strayWriteRefusal } from "../agent/write-guard.js";
|
|
14
|
+
/** The file-writing tools pi ships, by the name a `tool_call` event carries. Both take `path`. */
|
|
15
|
+
const WRITE_TOOLS = new Set(["edit", "write"]);
|
|
16
|
+
/** The refusal for one `tool_call` event, or undefined to let it run. Exported for tests. */
|
|
17
|
+
export function guardToolCall(event, cwd) {
|
|
18
|
+
if (typeof event.toolName !== "string" || !WRITE_TOOLS.has(event.toolName))
|
|
19
|
+
return undefined;
|
|
20
|
+
const input = event.input;
|
|
21
|
+
if (typeof input !== "object" || input === null)
|
|
22
|
+
return undefined;
|
|
23
|
+
const target = input.path;
|
|
24
|
+
if (typeof target !== "string" || target === "")
|
|
25
|
+
return undefined;
|
|
26
|
+
try {
|
|
27
|
+
const reason = strayWriteRefusal(target, cwd);
|
|
28
|
+
return reason === null ? undefined : { block: true, reason };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** The pi extension entry point. */
|
|
35
|
+
export default function writeGuard(pi) {
|
|
36
|
+
pi.on("tool_call", (event, ctx) => guardToolCall(event, typeof ctx?.cwd === "string" ? ctx.cwd : process.cwd()));
|
|
37
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/** launchservicesd's Mach-port budget on macOS: the running team's warning, driven by a watch
|
|
3
2
|
* stepped from the orchestrator's poll. The daemon keeps a port for every process that ever
|
|
4
3
|
* checked in with LaunchServices and never releases it, and the kernel kills it near
|
|
@@ -47,7 +46,7 @@ export function nextLaunchServicesWarning(lastWarned, ports) {
|
|
|
47
46
|
* is already leaking says so right away — and logs a harness warning per
|
|
48
47
|
* nextLaunchServicesWarning. The sample runs in the background: a poll never waits on `top`.
|
|
49
48
|
* In memory only: a restarted team warns again on its first sample if the count is still high,
|
|
50
|
-
* the right default for a condition only a reboot clears. cli/cli-
|
|
49
|
+
* the right default for a condition only a reboot clears. cli/cli-start.ts builds one for a daemon
|
|
51
50
|
* `tumwater run` only — a `--once` round and in-process tests run without it. */
|
|
52
51
|
export class LaunchServicesWatch {
|
|
53
52
|
root;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Choosing which node binary a supervised child or self-reexec spawns, shared by the
|
|
2
|
+
* supervisor (spawnSupervised, src/process/supervisor.ts) and the dashboard reload
|
|
3
|
+
* (reexecSelf, src/redeploy/self-reload.ts). Both embed `process.execPath`, which is the
|
|
4
|
+
* running process's resolved, versioned node path — a Homebrew upgrade under the running team
|
|
5
|
+
* deletes the old keg while the team stays up, so `execPath` names a file that no longer
|
|
6
|
+
* exists (BUGS.md 2026-10-21). The fix is the same trick build-stage's `compileStaged` uses
|
|
7
|
+
* for tsc (BUGS-FIXED.md, the 2026-09-29 `tsc exited ENOENT` cluster): PATH resolves a live
|
|
8
|
+
* node at spawn time, so fall back to the bare `node` when the recorded binary is gone.
|
|
9
|
+
* Nothing spawns until a child is created, so this cheap existence check is the whole fix. */
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
/** The node binary to pass `spawn` for a harness child: the running process's own `execPath`
|
|
12
|
+
* while it still exists, else the bare `node` resolved through PATH. An explicit path makes
|
|
13
|
+
* the deleted-keg branch testable without touching this process. */
|
|
14
|
+
export function liveNodeBin(execPath = process.execPath) {
|
|
15
|
+
return fs.existsSync(execPath) ? execPath : "node";
|
|
16
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { releaseChildHandles, signalTree, withoutLaunchServicesCheckIn } from "./process.js";
|
|
2
|
+
import { captureCappedOutput, releaseChildHandles, signalTree, withoutLaunchServicesCheckIn } from "./process.js";
|
|
3
3
|
import { errCode } from "../errno.js";
|
|
4
4
|
/** The detached process-group runner: runScriptGroup starts a command in its own process
|
|
5
5
|
* group — exactly like pi — and settles exactly once, bounding a timed-out tree at its
|
|
@@ -122,22 +122,12 @@ export function runScriptGroup(cmd, args, opts) {
|
|
|
122
122
|
env: withoutLaunchServicesCheckIn(process.env),
|
|
123
123
|
});
|
|
124
124
|
let settled = false;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
stdoutSize += chunk.length;
|
|
132
|
-
stdout += chunk;
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
child.stderr?.on("data", (chunk) => {
|
|
136
|
-
if (stderrSize < opts.maxBuffer) {
|
|
137
|
-
stderrSize += chunk.length;
|
|
138
|
-
stderr += chunk;
|
|
139
|
-
}
|
|
140
|
-
});
|
|
125
|
+
// Captured output is capped at maxBuffer per stream (further chunks are dropped —
|
|
126
|
+
// classification reads the tail, so a chatty script can neither wedge the check nor balloon
|
|
127
|
+
// memory). The overflow is deliberately ignored here: truncated output still classifies.
|
|
128
|
+
// `captured` is read at settle time (its fields accumulate as data arrives), never
|
|
129
|
+
// destructured up front.
|
|
130
|
+
const captured = captureCappedOutput(child, opts.maxBuffer);
|
|
141
131
|
const deadline = armGroupDeadline(child, {
|
|
142
132
|
startedAt: spawnedAt,
|
|
143
133
|
timeoutMs: opts.timeoutMs,
|
|
@@ -158,7 +148,7 @@ export function runScriptGroup(cmd, args, opts) {
|
|
|
158
148
|
const run = { spawnedAt, settledAt: Date.now(), timeoutMs: opts.timeoutMs };
|
|
159
149
|
if (timedOut)
|
|
160
150
|
run.deadlineLateMs = deadline.lateMs();
|
|
161
|
-
resolve({ ...result, timedOut, stdout, stderr, run });
|
|
151
|
+
resolve({ ...result, timedOut, stdout: captured.stdout, stderr: captured.stderr, run });
|
|
162
152
|
}
|
|
163
153
|
child.on("error", (err) => {
|
|
164
154
|
// Before the deadline this is the spawn failing (npm missing from PATH). After it, it can
|
|
@@ -24,7 +24,7 @@ export function execFileAsync(file, args, options = {}) {
|
|
|
24
24
|
* added after the abort event never fires, so `addEventListener` alone would leave the caller
|
|
25
25
|
* waiting on a shutdown that has come and gone. A missing signal (no shutdown wiring)
|
|
26
26
|
* registers nothing. Shared by spawnSupervised (supervisor.ts), runPi's child spawn
|
|
27
|
-
* (src/agent/run-agent.ts), sleepInterruptible (
|
|
27
|
+
* (src/agent/run-agent.ts), sleepInterruptible (run-timing.ts), and abortOnShutdown
|
|
28
28
|
* (merge-pipeline.ts).
|
|
29
29
|
*
|
|
30
30
|
* Returns a disposer that detaches `fn`. A `{ once: true }` listener detaches itself when the
|
|
@@ -69,7 +69,7 @@ export function pidAlive(pid) {
|
|
|
69
69
|
* escalating to SIGKILL after 10 s if any of it is still alive. The child is spawned detached
|
|
70
70
|
* (its own process group leader — runPi does this for pi), so a negative PID reaches every
|
|
71
71
|
* tool-call grandchild — which a single-PID kill leaves orphaned to launchd — while the
|
|
72
|
-
* harness's own group is never in the blast radius. Shared by the
|
|
72
|
+
* harness's own group is never in the blast radius. Shared by the run-timeout,
|
|
73
73
|
* quiet-watchdog, and harness-shutdown kills, and by the sweep every run gets once pi exits
|
|
74
74
|
* (runPi's 'exit' handler), when the group holds only what pi's tool calls backgrounded.
|
|
75
75
|
*
|
|
@@ -121,6 +121,45 @@ export function releaseChildHandles(child) {
|
|
|
121
121
|
child.stderr?.destroy();
|
|
122
122
|
child.unref();
|
|
123
123
|
}
|
|
124
|
+
/** Collect a detached child's stdout/stderr, keeping each stream while it stays within
|
|
125
|
+
* `maxBuffer` BYTES and reporting the stream that passes the ceiling via `onOverflow` (called at
|
|
126
|
+
* most once per stream, on the first overflowing chunk). The shared acquisition the three
|
|
127
|
+
* bounded detached-child probes (execGitBounded, runScriptGroup, boundedProbe) all hand-declared
|
|
128
|
+
* before: git fails loudly and kills the child on overflow, the script runner keeps a truncated
|
|
129
|
+
* tail for classification, the doctor probe aborts and reads "could not check" — the policy stays
|
|
130
|
+
* in the caller, the byte counting here. The chunk arrives as
|
|
131
|
+
* a Buffer from a raw stdio pipe, or as a String once the caller setEncoding'd the stream; both
|
|
132
|
+
* are counted by BYTE length (Buffer.byteLength / Buffer.length), exactly what execFile's
|
|
133
|
+
* maxBuffer ceiling limits. Call BEFORE the child can emit: receivers are attached here. */
|
|
134
|
+
export function captureCappedOutput(child, maxBuffer, onOverflow) {
|
|
135
|
+
const captured = { stdout: "", stderr: "", stdoutOverflow: false, stderrOverflow: false };
|
|
136
|
+
const capture = (stream) => {
|
|
137
|
+
let bytes = 0;
|
|
138
|
+
child[stream]?.on("data", (chunk) => {
|
|
139
|
+
bytes += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
|
|
140
|
+
if (bytes <= maxBuffer) {
|
|
141
|
+
captured[stream] += chunk;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Past the ceiling: drop this and every later chunk, and call onOverflow once per stream
|
|
145
|
+
// (further overflowing chunks are ignored).
|
|
146
|
+
if (stream === "stdout") {
|
|
147
|
+
if (captured.stdoutOverflow)
|
|
148
|
+
return;
|
|
149
|
+
captured.stdoutOverflow = true;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
if (captured.stderrOverflow)
|
|
153
|
+
return;
|
|
154
|
+
captured.stderrOverflow = true;
|
|
155
|
+
}
|
|
156
|
+
onOverflow?.(stream);
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
capture("stdout");
|
|
160
|
+
capture("stderr");
|
|
161
|
+
return captured;
|
|
162
|
+
}
|
|
124
163
|
// ── Child environments ───────────────────────────────────────────────────────────────────
|
|
125
164
|
/** The child-environment policy every spawn that hands a child the harness's environment shares:
|
|
126
165
|
* start from `process.env` and apply `overrides` on top. The explicit spawn sites
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import crypto from "node:crypto";
|
|
11
11
|
import fs from "node:fs";
|
|
12
12
|
import path from "node:path";
|
|
13
|
+
import { plural } from "../text/phrases.js";
|
|
13
14
|
import { execFileAsync, withoutLaunchServicesCheckIn } from "./process.js";
|
|
14
15
|
/** Every lookup normally finishes in well under a second; a wedged one must not hang
|
|
15
16
|
* its caller — the same ceiling process-table.ts's probe lookups carry. */
|
|
@@ -45,7 +46,7 @@ export function runMarkerEnv(base, marker) {
|
|
|
45
46
|
* given, is prepended to PATH — run-agent passes the notes shim dir (notesShimDir) so every
|
|
46
47
|
* run finds the `tumwater-notes` command first (plans/notes-command.md "Reaching it from a
|
|
47
48
|
* run"). */
|
|
48
|
-
export function
|
|
49
|
+
export function backendBaseEnv(marker, base = process.env, binDir) {
|
|
49
50
|
const env = runMarkerEnv(withoutLaunchServicesCheckIn(base), marker);
|
|
50
51
|
if (binDir) {
|
|
51
52
|
const prev = env.PATH;
|
|
@@ -53,20 +54,28 @@ export function agentBaseEnv(marker, base = process.env, binDir) {
|
|
|
53
54
|
}
|
|
54
55
|
return env;
|
|
55
56
|
}
|
|
56
|
-
/** The environment variable that names a run's
|
|
57
|
+
/** The environment variable that names a run's notes file, exported to authoring runs as
|
|
57
58
|
* the path the bundled `tumwater-notes` command reads and writes (notes-cli.ts). */
|
|
58
59
|
const NOTES_PATH_VAR = "TUMWATER_NOTES_PATH";
|
|
59
|
-
/** `base` plus this run's
|
|
60
|
+
/** `base` plus this run's notes as NOTES_PATH_VAR when `notesPath` is set — the agent
|
|
60
61
|
* adapter's run spec carries one only for authoring runs, so every other run leaves the
|
|
61
62
|
* variable untouched (and an inherited value survives). Kept out of runMarkerEnv so the two
|
|
62
|
-
* environment concerns — cross-group attribution and the
|
|
63
|
-
* beside
|
|
63
|
+
* environment concerns — cross-group attribution and the notes — stay separate; shared here
|
|
64
|
+
* beside backendBaseEnv because all three adapters (pi, claude, opencode) build their run
|
|
64
65
|
* environment from it. */
|
|
65
66
|
export function withNotesPath(base, notesPath) {
|
|
66
67
|
if (!notesPath)
|
|
67
68
|
return base;
|
|
68
69
|
return { ...base, [NOTES_PATH_VAR]: notesPath };
|
|
69
70
|
}
|
|
71
|
+
/** The full environment every agent backend's run starts from: the composition every adapter
|
|
72
|
+
* (pi, claude, codex, opencode) once spelled `withNotesPath(backendBaseEnv(run.runMarker,
|
|
73
|
+
* undefined, run.binDir), run.notesPath)`; the adapter layers its own settings on top, so
|
|
74
|
+
* the shared base lives here rather than being re-composed in each. Structurally typed on
|
|
75
|
+
* the three `AgentRunSpec` fields it reads so run-marker (process/) need not import agent/. */
|
|
76
|
+
export function backendRunEnv(run) {
|
|
77
|
+
return withNotesPath(backendBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
|
|
78
|
+
}
|
|
70
79
|
/** True when a `TUMWATER_RUN=` value names `marker` as one of its comma-separated runs. */
|
|
71
80
|
function markerValueCarries(value, marker) {
|
|
72
81
|
return value.split(",").includes(marker);
|
|
@@ -171,13 +180,24 @@ async function findMarkedPids(marker) {
|
|
|
171
180
|
}
|
|
172
181
|
return pidsMarkedInPs(await psEnvironTable(), marker);
|
|
173
182
|
}
|
|
183
|
+
/** The warning a run logs when its end-of-run sweep had to signal `count` processes it left
|
|
184
|
+
* running, or undefined when there is nothing to report. A run tumwater killed (abort, deadline,
|
|
185
|
+
* quiet watchdog, blind image read) is excluded: its process group got SIGTERM moments before
|
|
186
|
+
* the sweep scans, so members still exiting would be counted and every killed run would raise
|
|
187
|
+
* a false leak. */
|
|
188
|
+
export function leftoversSweptMessage(kind, count, killed) {
|
|
189
|
+
if (count === 0 || killed)
|
|
190
|
+
return undefined;
|
|
191
|
+
const verb = count === 1 ? "was" : "were";
|
|
192
|
+
return `run sweep: ${plural(count, "process", "processes")} the ${kind} run started ${verb} still running after pi exited; sent SIGTERM`;
|
|
193
|
+
}
|
|
174
194
|
/** SIGTERM every same-user process whose environment names `marker`, escalating to SIGKILL
|
|
175
195
|
* after 10 s for any that survive — the run's cross-group leftovers: what a tool call
|
|
176
196
|
* backgrounded into its own detached group, which the process-group sweep cannot reach
|
|
177
197
|
* (BUGS.md 2026-09-30). Marker membership is per-run unique, so the blast radius is exactly
|
|
178
198
|
* this run's tree even while sibling runs are live. Never throws: an unreadable table (no
|
|
179
199
|
* ps, a wedged /proc) degrades to no sweep — the group sweep still ran — never a failed
|
|
180
|
-
*
|
|
200
|
+
* run. Fire-and-forget by design: the run resolves on pi's output, not on the sweep. */
|
|
181
201
|
export async function sweepRunMarker(marker) {
|
|
182
202
|
let victims;
|
|
183
203
|
try {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
import { spawn } from "node:child_process";
|
|
3
2
|
import { TUMWATER_SOURCE } from "../events/events.js";
|
|
4
3
|
import { RESTART_EXIT_CODE } from "../redeploy/redeploy-policy.js";
|
|
5
4
|
import { childEnv, runOnAbort } from "./process.js";
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { liveNodeBin } from "./node-bin.js";
|
|
6
|
+
/** The respawn loop behind `tumwater start`. The command runs as two processes: this supervisor
|
|
7
|
+
* (the one the operator started, holding the terminal) and a child that runs the orchestrator.
|
|
8
8
|
* When the child rebuilt dist/ onto a newer main (src/redeploy/redeployer.ts) it exits
|
|
9
9
|
* RESTART_EXIT_CODE and the supervisor respawns it — the same script path, now holding the new
|
|
10
10
|
* code — so a self-hosting team picks up its own changes without anyone at the keyboard. Any
|
|
@@ -12,7 +12,7 @@ import { childEnv, runOnAbort } from "./process.js";
|
|
|
12
12
|
* is down with nobody at the keyboard either, so that exit is recorded first (onTeamDown). Ctrl+C
|
|
13
13
|
* reaches both processes from the terminal (same foreground group), so the supervisor forwards only
|
|
14
14
|
* SIGTERM, which a plain `kill` delivers to it alone. */
|
|
15
|
-
/** Marks the child: `tumwater
|
|
15
|
+
/** Marks the child: `tumwater start` with this set runs the orchestrator instead of supervising. */
|
|
16
16
|
export const SUPERVISED_ENV = "TUMWATER_SUPERVISED";
|
|
17
17
|
/** Names the supervisor's pid in a supervised child's environment, so the child's parent-death
|
|
18
18
|
* watch compares against the pid the spawner handed down instead of its own first read. A
|
|
@@ -23,9 +23,9 @@ export const SUPERVISOR_PID_ENV = "TUMWATER_SUPERVISOR_PID";
|
|
|
23
23
|
/** Marks a `run --for` window's start (epoch ms), set once by the supervisor and handed to every
|
|
24
24
|
* orchestrator generation, so a self-redeploy continues the window instead of opening a new one. */
|
|
25
25
|
export const RUN_WINDOW_START_ENV = "TUMWATER_RUN_WINDOW_START";
|
|
26
|
-
/** The exit code of a `run --once` round that finished and found failed
|
|
27
|
-
* onceFailures): an asked-for ending, so the supervisor
|
|
28
|
-
* team-down hook. */
|
|
26
|
+
/** The exit code of a `run --once` round that finished and found failed runs
|
|
27
|
+
* (onceFailures in src/cli/cli-start-summary.ts): an asked-for ending, so the supervisor
|
|
28
|
+
* passes it through without paging the team-down hook. */
|
|
29
29
|
export const ONCE_FAILED_EXIT_CODE = 3;
|
|
30
30
|
/** How often the supervised generation checks that its supervisor still lives (ms). */
|
|
31
31
|
const PARENT_POLL_MS = 5_000;
|
|
@@ -41,7 +41,7 @@ function intervalPoll(intervalMs) {
|
|
|
41
41
|
/** Parent-death watch for the supervised generation. The supervisor forwards only SIGTERM —
|
|
42
42
|
* SIGKILL (the OOM killer, `kill -9`) cannot be trapped or forwarded, and an uncaught supervisor
|
|
43
43
|
* crash forwards nothing either — so a supervisor that dies outright leaves the generation
|
|
44
|
-
* reparented (on macOS to launchd) and
|
|
44
|
+
* reparented (on macOS to launchd) and running the team unattended, with nothing left to sweep
|
|
45
45
|
* it. The watch polls the parent pid on an interval: once it changes, the supervisor is gone
|
|
46
46
|
* without having stopped the team, and `onGone` runs the same graceful stop a forwarded SIGTERM
|
|
47
47
|
* would have run — a killed supervisor takes its team down with it. The timer is unref'd so it
|
|
@@ -96,7 +96,7 @@ function exitCodeOf(exit) {
|
|
|
96
96
|
}
|
|
97
97
|
/** Run child generations until one exits with something other than a restart request, and
|
|
98
98
|
* return the code the supervisor should exit with. Pure apart from the injected effects. */
|
|
99
|
-
export async function
|
|
99
|
+
export async function superviseStart(opts, signal) {
|
|
100
100
|
const now = opts.now ?? Date.now;
|
|
101
101
|
let generation = 1;
|
|
102
102
|
const recentRespawns = [];
|
|
@@ -114,7 +114,7 @@ export async function superviseRun(opts, signal) {
|
|
|
114
114
|
if (exit.code !== RESTART_EXIT_CODE || opts.stopping()) {
|
|
115
115
|
// A clean exit (a child stopped by its own SIGTERM logs orchestrator_stop) and anything
|
|
116
116
|
// while the operator is stopping are asked-for endings, and so is a once round that
|
|
117
|
-
// finished with failed
|
|
117
|
+
// finished with failed runs; everything else is the team dying.
|
|
118
118
|
const code = exitCodeOf(exit);
|
|
119
119
|
if (code !== 0 && code !== ONCE_FAILED_EXIT_CODE && !opts.stopping())
|
|
120
120
|
await teamDown({ generation, exit, crashLoop: false });
|
|
@@ -163,7 +163,7 @@ export function teamDownEvent(down, diagnosis) {
|
|
|
163
163
|
}
|
|
164
164
|
/** The production child spawner: the same node binary and cli.js script as this process with
|
|
165
165
|
* `run` as the command — plus any flags the operator's original invocation carried (e.g.
|
|
166
|
-
* `tumwater
|
|
166
|
+
* `tumwater start --branch x`: the child IS the orchestrator generation, so it must target the
|
|
167
167
|
* same branch) — stdio shared (the child's event stream is what the operator sees), and
|
|
168
168
|
* SUPERVISED_ENV set so it runs the orchestrator instead of supervising again, plus
|
|
169
169
|
* SUPERVISOR_PID_ENV naming this supervisor so the child's own parent-death watch fires even if
|
|
@@ -171,7 +171,7 @@ export function teamDownEvent(down, diagnosis) {
|
|
|
171
171
|
* RUN_WINDOW_START_ENV for a `--for` window. `stdio` defaults to `"inherit"` (the child's
|
|
172
172
|
* event stream is what a plain `run` operator sees); `run --tui` passes silent stdio instead,
|
|
173
173
|
* because the observer TUI owns the terminal and the child must not paint over its render. */
|
|
174
|
-
export function
|
|
174
|
+
export function spawnStartChild(signal, extraArgs = [], env = {}, stdio = "inherit") {
|
|
175
175
|
// Spawn `run`, never `start`: the supervisor runs the build it started on, but each child
|
|
176
176
|
// loads dist/src/cli.js, which a redeploy replaces with main's newest build. Every build from
|
|
177
177
|
// the `start` rename on accepts `run`, and older builds accept only `run`, so `run` works
|
|
@@ -183,21 +183,22 @@ export function spawnRunChild(signal, extraArgs = [], env = {}, stdio = "inherit
|
|
|
183
183
|
...env,
|
|
184
184
|
}, stdio);
|
|
185
185
|
}
|
|
186
|
-
/** The production dashboard spawner for `run --gui`: a `tumwater gui` child on its defaults,
|
|
187
|
-
* stdio shared so a terminal Ctrl+C reaches
|
|
188
|
-
* unless `--tui` is hosted, when the caller
|
|
189
|
-
* `tumwater gui at http://…` banner cannot paint over the
|
|
186
|
+
/** The production dashboard spawner for `run --gui`: a `tumwater gui` child (on its defaults, or
|
|
187
|
+
* on `--port <n>` when `extraArgs` carries the port), stdio shared so a terminal Ctrl+C reaches
|
|
188
|
+
* it exactly as it reaches the orchestrator child — unless `--tui` is hosted, when the caller
|
|
189
|
+
* passes silent stdio so the dashboard's `tumwater gui at http://…` banner cannot paint over the
|
|
190
|
+
* observer TUI's full-screen render.
|
|
190
191
|
* `env` carries the caller's mark — in production DASHBOARD_CHILD_ENV naming this supervisor's
|
|
191
192
|
* pid — so the child's own reload watch (src/redeploy/self-reload.ts) both exits
|
|
192
193
|
* RESTART_EXIT_CODE for a fresh generation on a redeploy and exits on its own if this supervisor
|
|
193
194
|
* dies outright (SIGKILL, a crash), freeing the dashboard port. Deliberately not SUPERVISED_ENV:
|
|
194
|
-
* the child must run `gui`, not supervise a team. Resolves like
|
|
195
|
+
* the child must run `gui`, not supervise a team. Resolves like spawnStartChild — a failed spawn
|
|
195
196
|
* is `{ code: 1, signal: null }` — so an EMFILE/ENOMEM here cannot throw an unhandled 'error'. */
|
|
196
|
-
export function spawnGuiChild(signal, env = {}, stdio = "inherit") {
|
|
197
|
-
return spawnSupervised(["gui"], signal, env, stdio);
|
|
197
|
+
export function spawnGuiChild(signal, env = {}, stdio = "inherit", extraArgs = []) {
|
|
198
|
+
return spawnSupervised(["gui", ...extraArgs], signal, env, stdio);
|
|
198
199
|
}
|
|
199
200
|
/** Spawn one supervised child (`commandArgs` after the cli.js script path) and resolve on its
|
|
200
|
-
* exit, with the abort signal wiring and spawn-failure handling shared by
|
|
201
|
+
* exit, with the abort signal wiring and spawn-failure handling shared by spawnStartChild and
|
|
201
202
|
* spawnGuiChild: the signal's abort sends SIGTERM, and a spawn that never starts resolves as a
|
|
202
203
|
* failed exit instead of emitting an unhandled 'error'. */
|
|
203
204
|
function spawnSupervised(commandArgs, signal, env, stdio = "inherit") {
|
|
@@ -207,7 +208,7 @@ function spawnSupervised(commandArgs, signal, env, stdio = "inherit") {
|
|
|
207
208
|
resolve({ code: 1, signal: null });
|
|
208
209
|
return;
|
|
209
210
|
}
|
|
210
|
-
const child = spawn(
|
|
211
|
+
const child = spawn(liveNodeBin(), [script, ...commandArgs], {
|
|
211
212
|
stdio,
|
|
212
213
|
env: childEnv(env),
|
|
213
214
|
});
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/**
|
|
3
2
|
* The only backend-specific prompt text (plans/agent-backends.md "What the pi extensions did,
|
|
4
3
|
* per backend"). Everything else in the prompts already speaks in shell commands and "tool
|
|
5
|
-
* calls"; what differs between pi and claude is the name of the
|
|
4
|
+
* calls"; what differs between pi and claude is the name of the notes tool and how each
|
|
6
5
|
* backend's oversized tool output reaches the model, so those live behind a `PromptDialect`
|
|
7
6
|
* that prompt.ts and prompt-followup.ts take as an optional argument.
|
|
8
7
|
*
|
|
@@ -41,6 +40,21 @@ export const OPENCODE_DIALECT = {
|
|
|
41
40
|
"Run every shell command in the foreground: never set `background`, because a background command is killed when your run ends.",
|
|
42
41
|
],
|
|
43
42
|
};
|
|
43
|
+
/** codex's dialect (plans/codex-backend.md §6): it has no read tool (so the reading-budget
|
|
44
|
+
* rule names only `sed -n`/`head`/`tail`), its oversized tool output comes truncated around a
|
|
45
|
+
* `…N tokens truncated…` marker (finding 18), and two backend rules tell the model how a
|
|
46
|
+
* longer command works: codex returns a session id to poll with `write_stdin`, and ends the
|
|
47
|
+
* command when the turn ends (finding 19); and all file edits go through `apply_patch` (finding
|
|
48
|
+
* 6), because the write guard checks only that tool (plans/codex-write-guard.md). */
|
|
49
|
+
export const CODEX_DIALECT = {
|
|
50
|
+
readRange: "`sed -n 'A,Bp'` (or `head` / `tail`)",
|
|
51
|
+
oversizedOutput: `Oversized command output comes back as its head and tail around a \`…N tokens truncated…\`
|
|
52
|
+
marker — narrow the command (grep, \`sed -n\`, \`head\`/\`tail\`) instead of re-running it whole.`,
|
|
53
|
+
backendRules: [
|
|
54
|
+
"A command that runs longer than 30 seconds returns a session id while it keeps running. Keep calling `write_stdin` with that session id until it reports an exit code, and never end your turn while a command is still running: Codex kills it when you finish, and its result is lost.",
|
|
55
|
+
"Edit and create files with the `apply_patch` tool, not shell redirection (`>`, `>>`, `tee`) or `sed -i`.",
|
|
56
|
+
],
|
|
57
|
+
};
|
|
44
58
|
/** The dialect table keyed by backend id: every backend but claude speaks pi's dialect (and
|
|
45
59
|
* an unknown id falls back to pi because run-agent.ts routes an unregistered id to the pi
|
|
46
60
|
* backend). Adding a backend means one row here, not another branch. */
|
|
@@ -48,6 +62,7 @@ const DIALECTS = {
|
|
|
48
62
|
pi: PI_DIALECT,
|
|
49
63
|
claude: CLAUDE_DIALECT,
|
|
50
64
|
opencode: OPENCODE_DIALECT,
|
|
65
|
+
codex: CODEX_DIALECT,
|
|
51
66
|
};
|
|
52
67
|
/** The dialect for an agent id, read from the per-backend table. */
|
|
53
68
|
export function dialectFor(agent) {
|