tumwater 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.md +6 -6
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-backend.js +6 -6
  4. package/dist/src/agent/{run-agent.js → agent-run.js} +27 -13
  5. package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
  6. package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
  7. package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
  8. package/dist/src/agent/backend-traits.js +2 -2
  9. package/dist/src/agent/claude/claude-account.js +6 -27
  10. package/dist/src/agent/claude/claude-backend.js +3 -3
  11. package/dist/src/agent/claude/claude-env.js +27 -31
  12. package/dist/src/agent/claude/claude-stream.js +111 -54
  13. package/dist/src/agent/codex/codex-account.js +8 -0
  14. package/dist/src/agent/codex/codex-args.js +5 -5
  15. package/dist/src/agent/codex/codex-backend.js +2 -2
  16. package/dist/src/agent/codex/codex-env.js +28 -15
  17. package/dist/src/agent/codex/codex-hook-trust.js +39 -20
  18. package/dist/src/agent/codex/codex-rollout.js +93 -56
  19. package/dist/src/agent/codex/codex-stream.js +1 -1
  20. package/dist/src/agent/env-scrub.js +18 -0
  21. package/dist/src/agent/image-input.js +68 -11
  22. package/dist/src/agent/notes-shim.js +2 -2
  23. package/dist/src/agent/opencode/opencode-backend.js +26 -24
  24. package/dist/src/agent/opencode/opencode-catalog.js +85 -36
  25. package/dist/src/agent/opencode/opencode-config.js +32 -14
  26. package/dist/src/agent/opencode/opencode-env.js +12 -13
  27. package/dist/src/agent/opencode/opencode-stream.js +47 -25
  28. package/dist/src/agent/session-ledger.js +23 -27
  29. package/dist/src/agent/subscription-account.js +2 -83
  30. package/dist/src/backlog/backlog-archive.js +3 -4
  31. package/dist/src/backlog/backlog-cli-view.js +81 -41
  32. package/dist/src/backlog/backlog-cli.js +57 -26
  33. package/dist/src/backlog/backlog-eligibility.js +56 -122
  34. package/dist/src/backlog/backlog-md.js +5 -5
  35. package/dist/src/backlog/backlog-parts.js +65 -27
  36. package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
  37. package/dist/src/backlog/backlog-release.js +12 -12
  38. package/dist/src/backlog/backlog-structure.js +114 -61
  39. package/dist/src/backlog/backlog.js +3 -2
  40. package/dist/src/baseline/main-baseline.js +148 -84
  41. package/dist/src/baseline/main-guardrails.js +2 -2
  42. package/dist/src/baseline/main-watch.js +84 -50
  43. package/dist/src/brief/brief.js +26 -13
  44. package/dist/src/budget/budget-fallback-view.js +100 -243
  45. package/dist/src/budget/budget-wiring.js +29 -65
  46. package/dist/src/budget/budget.js +4 -5
  47. package/dist/src/budget/fallback-breaker.js +2 -2
  48. package/dist/src/build/build-check-detect.js +43 -14
  49. package/dist/src/build/build-check-events.js +2 -2
  50. package/dist/src/build/build-check-report.js +10 -0
  51. package/dist/src/build/build-check-scoped.js +40 -18
  52. package/dist/src/build/build-check.js +13 -5
  53. package/dist/src/build/build-stage.js +6 -4
  54. package/dist/src/change/change-data.js +1 -4
  55. package/dist/src/cli/cli-command-args.js +89 -52
  56. package/dist/src/cli/cli-completion-command.js +75 -0
  57. package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
  58. package/dist/src/cli/cli-doctor-command.js +103 -0
  59. package/dist/src/cli/cli-flag-specs.js +42 -22
  60. package/dist/src/cli/cli-init.js +36 -14
  61. package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
  62. package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
  63. package/dist/src/cli/cli-query-commands.js +2 -2
  64. package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
  65. package/dist/src/cli/cli-repo-gate.js +2 -2
  66. package/dist/src/cli/cli-role-command.js +2 -41
  67. package/dist/src/cli/cli-start-summary.js +4 -3
  68. package/dist/src/cli/cli-start-supervisor.js +190 -0
  69. package/dist/src/cli/cli-start.js +94 -244
  70. package/dist/src/cli/completion.js +85 -49
  71. package/dist/src/cli/follow-view.js +20 -2
  72. package/dist/src/cli/help.js +6 -13
  73. package/dist/src/cli.js +70 -176
  74. package/dist/src/collections.js +39 -27
  75. package/dist/src/concurrency/check-slots.js +70 -0
  76. package/dist/src/concurrency/lock.js +23 -1
  77. package/dist/src/concurrency/run-permit.js +49 -0
  78. package/dist/src/concurrency/semaphore.js +46 -250
  79. package/dist/src/config/config-editable-keys.js +4 -7
  80. package/dist/src/config/config-fallbacks.js +81 -101
  81. package/dist/src/config/config-field-checks.js +66 -37
  82. package/dist/src/config/config-keys.js +66 -40
  83. package/dist/src/config/config-live.js +4 -28
  84. package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
  85. package/dist/src/config/config-request.js +163 -102
  86. package/dist/src/config/config-schema.js +10 -12
  87. package/dist/src/config/config-selector-checks.js +1 -1
  88. package/dist/src/config/config-validation-agents.js +2 -2
  89. package/dist/src/config/config-validation-roles.js +8 -72
  90. package/dist/src/config/config-validation.js +4 -39
  91. package/dist/src/config/config-views.js +26 -50
  92. package/dist/src/config/config-write.js +128 -102
  93. package/dist/src/config/config.js +5 -14
  94. package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
  95. package/dist/src/doctor/doctor-backlog-plans.js +53 -120
  96. package/dist/src/doctor/doctor-backlog.js +16 -14
  97. package/dist/src/doctor/doctor-check-names.js +3 -6
  98. package/dist/src/doctor/doctor-checks.js +8 -8
  99. package/dist/src/doctor/doctor-claude-checks.js +70 -36
  100. package/dist/src/doctor/doctor-codex-checks.js +6 -6
  101. package/dist/src/doctor/doctor-contention-checks.js +57 -117
  102. package/dist/src/doctor/doctor-fallback-checks.js +48 -26
  103. package/dist/src/doctor/doctor-holds.js +8 -47
  104. package/dist/src/doctor/doctor-install.js +1 -1
  105. package/dist/src/doctor/doctor-launch-services.js +1 -1
  106. package/dist/src/doctor/doctor-model-checks.js +178 -94
  107. package/dist/src/doctor/doctor-notify.js +2 -2
  108. package/dist/src/doctor/doctor-opencode-checks.js +5 -4
  109. package/dist/src/doctor/doctor-orphans.js +122 -21
  110. package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
  111. package/dist/src/doctor/doctor-pi-checks.js +2 -2
  112. package/dist/src/doctor/doctor-principles.js +3 -3
  113. package/dist/src/doctor/doctor-project-checks.js +24 -11
  114. package/dist/src/doctor/doctor-role-notes.js +1 -1
  115. package/dist/src/doctor/doctor.js +8 -11
  116. package/dist/src/events/event-format.js +44 -64
  117. package/dist/src/events/event-labels.js +1 -5
  118. package/dist/src/events/event-legacy.js +3 -5
  119. package/dist/src/events/{notify.js → event-notify.js} +6 -6
  120. package/dist/src/events/event-read.js +1 -1
  121. package/dist/src/events/event-window.js +1 -1
  122. package/dist/src/events/events.js +8 -0
  123. package/dist/src/failure/failure-cluster.js +9 -18
  124. package/dist/src/failure/failure-data.js +12 -8
  125. package/dist/src/failure/failure-render.js +46 -36
  126. package/dist/src/failure/failure-state-change.js +54 -120
  127. package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
  128. package/dist/src/files/files.js +30 -0
  129. package/dist/src/files/json-object.js +8 -8
  130. package/dist/src/files/stat-cache.js +12 -8
  131. package/dist/src/gates/budget-gates.js +85 -84
  132. package/dist/src/gates/gate-polls.js +32 -74
  133. package/dist/src/gates/pause-gates.js +16 -2
  134. package/dist/src/gates/readiness.js +1 -1
  135. package/dist/src/gates/role-cap-gates.js +44 -35
  136. package/dist/src/gates/{startup-gate.js → startup-gates.js} +9 -8
  137. package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
  138. package/dist/src/git/git-diff.js +6 -2
  139. package/dist/src/git/git.js +75 -45
  140. package/dist/src/git/worktree-pool.js +3 -6
  141. package/dist/src/gui/gui-admin-endpoints.js +3 -3
  142. package/dist/src/gui/gui-args.js +12 -0
  143. package/dist/src/gui/gui-backlog-commands.js +4 -4
  144. package/dist/src/gui/gui-backlog-endpoints.js +61 -41
  145. package/dist/src/gui/gui-command.js +43 -24
  146. package/dist/src/gui/gui-config-commands.js +2 -64
  147. package/dist/src/gui/gui-endpoint-commands.js +1 -1
  148. package/dist/src/gui/gui-endpoint-common.js +19 -1
  149. package/dist/src/gui/gui-history-endpoints.js +100 -52
  150. package/dist/src/gui/gui-operator-commands.js +43 -48
  151. package/dist/src/gui/gui-prompt-commands.js +25 -25
  152. package/dist/src/gui/gui-report-endpoints.js +1 -1
  153. package/dist/src/gui/gui-server.js +1 -2
  154. package/dist/src/gui/gui-team-commands.js +2 -1
  155. package/dist/src/history/history-data.js +71 -44
  156. package/dist/src/history/history-queued.js +49 -27
  157. package/dist/src/history/history.js +54 -36
  158. package/dist/src/inbox/prompt-commands.js +59 -42
  159. package/dist/src/init/init.js +88 -40
  160. package/dist/src/merge/merge-backlog-conflicts.js +1 -1
  161. package/dist/src/merge/merge-check-failures.js +12 -9
  162. package/dist/src/merge/merge-core.js +7 -32
  163. package/dist/src/merge/merge-drain.js +11 -13
  164. package/dist/src/merge/merge-pipeline.js +6 -7
  165. package/dist/src/merge/merge-post-review-drain.js +11 -14
  166. package/dist/src/merge/merge-queue-render.js +3 -3
  167. package/dist/src/merge/merge-requeue.js +1 -1
  168. package/dist/src/merge/merge-slot.js +0 -8
  169. package/dist/src/merge/merge-stack.js +17 -18
  170. package/dist/src/merge/merge-to-main.js +13 -58
  171. package/dist/src/merge/merge-verify.js +12 -21
  172. package/dist/src/notes-cli.js +52 -42
  173. package/dist/src/operator/operator-commands.js +80 -47
  174. package/dist/src/operator/operator-intent.js +3 -3
  175. package/dist/src/orchestrator/orchestrator-launch.js +31 -63
  176. package/dist/src/orchestrator/orchestrator-poll.js +44 -69
  177. package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
  178. package/dist/src/orchestrator/orchestrator.js +8 -21
  179. package/dist/src/orchestrator/scheduling-admit.js +101 -68
  180. package/dist/src/orchestrator/scheduling-scope.js +18 -27
  181. package/dist/src/orchestrator/scheduling-skip.js +10 -14
  182. package/dist/src/orchestrator/startup-migrations.js +1 -1
  183. package/dist/src/paths.js +10 -9
  184. package/dist/src/pi/pi-args.js +5 -2
  185. package/dist/src/pi/pi-backend.js +2 -2
  186. package/dist/src/pi/pi-bin.js +3 -3
  187. package/dist/src/pi/pi-event-line.js +28 -1
  188. package/dist/src/pi/pi-listed-models.js +13 -0
  189. package/dist/src/pi/pi-models.js +43 -29
  190. package/dist/src/pi/pi-settings.js +43 -20
  191. package/dist/src/pi/pi-stream.js +41 -8
  192. package/dist/src/pi/pi-watchdogs.js +43 -15
  193. package/dist/src/pi/pi.js +2 -2
  194. package/dist/src/pi-extension/context-shake.js +32 -20
  195. package/dist/src/pi-extension/kill-guard.js +69 -0
  196. package/dist/src/process/process-group.js +51 -26
  197. package/dist/src/process/process.js +20 -12
  198. package/dist/src/process/run-marker.js +33 -15
  199. package/dist/src/process/supervisor.js +40 -28
  200. package/dist/src/prompt/dialect.js +1 -1
  201. package/dist/src/prompt/gate-notes.js +2 -2
  202. package/dist/src/prompt/gate-prompts.js +1 -44
  203. package/dist/src/prompt/notes-backlog.js +66 -37
  204. package/dist/src/prompt/principles.js +6 -4
  205. package/dist/src/prompt/prompt-followup.js +12 -4
  206. package/dist/src/prompt/prompt.js +88 -38
  207. package/dist/src/redeploy/redeployer.js +21 -1
  208. package/dist/src/report/report-data.js +53 -37
  209. package/dist/src/report/report-fold.js +16 -9
  210. package/dist/src/report/report-render.js +21 -194
  211. package/dist/src/report/report-since-data.js +4 -3
  212. package/dist/src/report/report-since-render.js +173 -0
  213. package/dist/src/report/report-usage-fold.js +8 -8
  214. package/dist/src/report/report.js +46 -22
  215. package/dist/src/review/diff-rules.js +35 -21
  216. package/dist/src/review/post-merge-review-render.js +9 -3
  217. package/dist/src/review/post-merge-review.js +48 -41
  218. package/dist/src/review/review-precheck.js +14 -4
  219. package/dist/src/review/review.js +48 -170
  220. package/dist/src/review/secret-leaks.js +26 -16
  221. package/dist/src/roles/legacy-role-migration.js +6 -3
  222. package/dist/src/roles/role-catalog.js +11 -3
  223. package/dist/src/roles/role-guidance.js +16 -12
  224. package/dist/src/roles/role-render.js +7 -46
  225. package/dist/src/roles/role-view.js +10 -33
  226. package/dist/src/run/acceptance-greps.js +88 -45
  227. package/dist/src/run/ci-parity.js +19 -8
  228. package/dist/src/run/run-apply.js +12 -15
  229. package/dist/src/run/run-detail-data.js +121 -68
  230. package/dist/src/run/run-detail-render.js +92 -35
  231. package/dist/src/run/run-detail.js +72 -41
  232. package/dist/src/run/run-prompt.js +63 -50
  233. package/dist/src/run/run-resume.js +76 -29
  234. package/dist/src/run/run-stage.js +141 -90
  235. package/dist/src/run/run-timing.js +8 -2
  236. package/dist/src/run/run-usage.js +31 -1
  237. package/dist/src/run/run-verdict.js +128 -50
  238. package/dist/src/run/stage-check.js +60 -50
  239. package/dist/src/run/stage-checks-shared.js +28 -0
  240. package/dist/src/run/stage-name-checks.js +111 -92
  241. package/dist/src/run/stage-path-checks.js +69 -42
  242. package/dist/src/run/stage-prose-checks.js +55 -40
  243. package/dist/src/runner/{leftover.js → runner-leftover.js} +9 -26
  244. package/dist/src/runner/runner-outcome.js +7 -2
  245. package/dist/src/runner/runner-pi.js +9 -22
  246. package/dist/src/runner/runner-recovery.js +5 -13
  247. package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
  248. package/dist/src/runner/runner-run.js +103 -50
  249. package/dist/src/runner/runner-state-load.js +18 -28
  250. package/dist/src/runner/runner-state.js +0 -14
  251. package/dist/src/runner/runner.js +41 -25
  252. package/dist/src/scheduling/backoff.js +8 -3
  253. package/dist/src/scheduling/claim-entries.js +2 -1
  254. package/dist/src/scheduling/claims.js +0 -4
  255. package/dist/src/scheduling/scheduling.js +53 -66
  256. package/dist/src/status/status-budget.js +4 -43
  257. package/dist/src/status/status-data.js +35 -39
  258. package/dist/src/status/status-role-notes.js +2 -2
  259. package/dist/src/status/status-roles.js +9 -18
  260. package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
  261. package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
  262. package/dist/src/team/team-hold.js +105 -70
  263. package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
  264. package/dist/src/team/team-polls.js +53 -201
  265. package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
  266. package/dist/src/team/team-state.js +4 -13
  267. package/dist/src/text/datetime.js +16 -7
  268. package/dist/src/text/markdown.js +1 -1
  269. package/dist/src/text/phrases.js +29 -15
  270. package/dist/src/text/suggest.js +1 -1
  271. package/dist/src/text/text.js +6 -5
  272. package/dist/src/ui/alert-feeds.js +46 -111
  273. package/dist/src/ui/badges.js +17 -88
  274. package/dist/src/ui/gui/gui-client-boot.js +2 -8
  275. package/dist/src/ui/gui/gui-client-composer.js +1 -1
  276. package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
  277. package/dist/src/ui/gui/gui-client-drawer.js +1 -1
  278. package/dist/src/ui/gui/gui-client-feed.js +1 -1
  279. package/dist/src/ui/gui/gui-client-history.js +28 -14
  280. package/dist/src/ui/gui/gui-client-model.js +0 -12
  281. package/dist/src/ui/gui/gui-client-operator.js +7 -5
  282. package/dist/src/ui/gui/gui-client-pending.js +7 -6
  283. package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
  284. package/dist/src/ui/gui/gui-client-report.js +1 -1
  285. package/dist/src/ui/gui/gui-client-roles.js +0 -1
  286. package/dist/src/ui/gui/gui-client-settings.js +43 -84
  287. package/dist/src/ui/gui/gui-client-team.js +12 -6
  288. package/dist/src/ui/gui/gui-client.js +7 -1
  289. package/dist/src/ui/progress-data.js +107 -44
  290. package/dist/src/ui/run-progress-model.js +20 -4
  291. package/dist/src/ui/status-model.js +2 -24
  292. package/dist/src/ui/status-payload.js +11 -30
  293. package/dist/src/ui/status-render.js +13 -24
  294. package/dist/src/ui/team-alert-backlog.js +14 -204
  295. package/dist/src/ui/team-alert-shared.js +6 -29
  296. package/dist/src/ui/team-alert-status.js +9 -170
  297. package/dist/src/ui/team-alert-tail.js +19 -215
  298. package/dist/src/ui/team-alerts.js +8 -15
  299. package/dist/src/ui/transcript-run.js +27 -16
  300. package/dist/src/ui/tui/tui-backlog.js +2 -2
  301. package/dist/src/ui/tui/tui-editor-answer.js +12 -10
  302. package/dist/src/ui/tui/tui-editor-attach.js +4 -3
  303. package/dist/src/ui/tui/tui-editor-backends.js +4 -3
  304. package/dist/src/ui/tui/tui-editor-brief.js +6 -9
  305. package/dist/src/ui/tui/tui-editor-budget.js +4 -3
  306. package/dist/src/ui/tui/tui-editor-config.js +80 -68
  307. package/dist/src/ui/tui/tui-editor-entry.js +12 -20
  308. package/dist/src/ui/tui/tui-editor-pause.js +51 -57
  309. package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
  310. package/dist/src/ui/tui/tui-editor-queued.js +11 -14
  311. package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
  312. package/dist/src/ui/tui/tui-editor-release.js +4 -3
  313. package/dist/src/ui/tui/tui-editor-retire.js +7 -7
  314. package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
  315. package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
  316. package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
  317. package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
  318. package/dist/src/ui/tui/tui-editor-search.js +4 -3
  319. package/dist/src/ui/tui/tui-editor-session.js +17 -4
  320. package/dist/src/ui/tui/tui-editors.js +5 -10
  321. package/dist/src/ui/tui/tui-frame.js +182 -134
  322. package/dist/src/ui/tui/tui-guard.js +20 -0
  323. package/dist/src/ui/tui/tui-input.js +167 -98
  324. package/dist/src/ui/tui/tui-keymap.js +51 -29
  325. package/dist/src/ui/tui/tui-keys.js +1 -1
  326. package/dist/src/ui/tui/tui-pane-activity.js +5 -4
  327. package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
  328. package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
  329. package/dist/src/ui/tui/tui-pane-health.js +252 -230
  330. package/dist/src/ui/tui/tui-pane-history.js +67 -74
  331. package/dist/src/ui/tui/tui-pane-pending.js +288 -247
  332. package/dist/src/ui/tui/tui-pane-queued.js +25 -27
  333. package/dist/src/ui/tui/tui-pane-report.js +2 -1
  334. package/dist/src/ui/tui/tui-pane-session.js +15 -10
  335. package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
  336. package/dist/src/ui/tui/tui-pane.js +14 -11
  337. package/dist/src/ui/tui/tui-panes.js +38 -33
  338. package/dist/src/ui/tui/tui.js +4 -7
  339. package/dist/src/verdict/fix-claim.js +107 -56
  340. package/dist/src/verdict/refusal.js +1 -1
  341. package/dist/src/verdict/reply-contract.js +3 -4
  342. package/package.json +5 -5
  343. package/dist/src/concurrency/provider-lease.js +0 -105
  344. package/dist/src/review/review-followup.js +0 -87
  345. package/dist/src/review/review-run.js +0 -395
  346. package/dist/src/runner/model-fallback.js +0 -76
  347. package/dist/src/runner/runner-fallback.js +0 -66
  348. package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
  349. package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
  350. /package/dist/src/cli/{brief-command.js → cli-brief-command.js} +0 -0
  351. /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
  352. /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
  353. /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
  354. /package/dist/src/operator/{revert.js → operator-revert.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.2**: working tumwater. The director and 15 of the 17 roles are enabled by default; `selfcheck`,
45
+ **v0.4.4**: 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
 
@@ -75,9 +75,10 @@ and `tumwater pause`, `resume`, `wake`, and `stop` control the roles. `tumwater
75
75
  so pair it with `--token <secret>`.
76
76
 
77
77
  `tumwater prompt --attach <image>` gives a role an image to read at its next run. When a role's
78
- model cannot read images, the command refuses the attachment up front (naming the fix); the
79
- dashboards and `tumwater role <id>` say which roles read images. Nothing reroutes a blind read
80
- to another tier — a run that reads an image its model cannot see fails with `lastError`.
78
+ model cannot read images, the command refuses the attachment up front (naming the fix) unless
79
+ the role reroutes a blind read to a vision model, in which case it says where the message will
80
+ run; the dashboards and `tumwater role <id>` say which roles read images and where a blind
81
+ run reroutes.
81
82
 
82
83
  Every command, flag, and TUI key is in [docs/cli.md](docs/cli.md); `tumwater help` prints the
83
84
  same list, and `tumwater completion bash|zsh|fish` prints shell completion. Settings live in
@@ -107,8 +108,7 @@ object with both, so one config can mix a Claude strong tier with pi providers:
107
108
 
108
109
  The default tier runs on pi through Hugging Face, the strong model jobs (the director, the plan
109
110
  role, the reviewer, and the conflict resolver) run on the claude CLI's included usage, and the fallback runs on pi through
110
- oMLX — all concurrently, under each provider's cap (set it with `maxConcurrentPerProvider`;
111
- `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
112
112
  `{ "backend": "opencode", "model": "deepseek-v4.1-flash-free" }`, so its free tier can sit beside pi
113
113
  and claude in one config; a codex tier is `{ "backend": "codex", "model": "gpt-6.1-sol" }`, so a ChatGPT
114
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha": "115515a5f5134c37c2bf8fa9dfb0edf620dd94b6",
3
- "builtAt": 1793081614993,
2
+ "sha": "1f6978ed5d8061b12afc907b017f0164426f49ee",
3
+ "builtAt": 1793345933702,
4
4
  "root": "/home/runner/work/tumwater/tumwater"
5
5
  }
@@ -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/run-spawn.ts
4
- * builds and spawns the run (the marker, parser, bin resolution and spawn); src/agent/run-agent.ts
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/run-stream.ts owns the streaming pipeline (native-to-canonical stdout
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/run-settle.ts. A backend registered here supplies the rest. src/agent/run-agent.ts
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-agent.ts
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,11 +6,11 @@
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/run-spawn.ts, the
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/run-stream.ts, and the post-run settlement
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/run-settle.ts. `runPi` (src/pi/pi.ts) is the thin alias the existing
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";
@@ -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 "./run-settle.js";
28
- import { openAppendLog, spawnPiRun } from "./run-spawn.js";
29
- import { createAgentStream } from "./run-stream.js";
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 run-stream.ts.
32
- export { MAX_NATIVE_LINE_CHARS } from "./run-stream.js";
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/run-spawn.ts) and returns the SpawnedRun bundle. A synchronous
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
@@ -107,6 +107,7 @@ export function runAgent(opts) {
107
107
  const wd = startPiWatchdogs({
108
108
  progressCount: () => parser.progressCount,
109
109
  openToolCalls: () => parser.openToolCalls,
110
+ pendingReply: () => parser.pendingReply,
110
111
  runTimeoutMs: opts.config.runTimeoutSeconds * 1000,
111
112
  quietMs: opts.config.quietTimeoutSeconds * 1000,
112
113
  stallMs: Math.max(0, opts.config.toolCallStallSeconds) * 1000,
@@ -120,7 +121,7 @@ export function runAgent(opts) {
120
121
  // deadline stays right for them.
121
122
  extendOnProgress: opts.kind === "gate",
122
123
  kill: () => terminateChild(child),
123
- onToolCallStalled: opts.onToolCallStalled,
124
+ onStallWarning: opts.onStallWarning,
124
125
  });
125
126
  let aborted = false;
126
127
  // A blind image read on an authoring run deliberately kills it, like the watchdogs do.
@@ -133,7 +134,7 @@ export function runAgent(opts) {
133
134
  // The streaming pipeline — native-to-canonical stdout split with the over-long-line
134
135
  // discard, the stderr window and its fallback-clone warning matcher, the stream-minted
135
136
  // session's second ledger record, and the settle/flush that resolves the run — now lives
136
- // in src/agent/run-stream.ts behind the AgentStream interface. run-agent only wires the
137
+ // in src/agent/agent-stream.ts behind the AgentStream interface. agent-run only wires the
137
138
  // child's `data` events to it and binds its getters into the settlement state below.
138
139
  const stream = createAgentStream({
139
140
  opts,
@@ -148,7 +149,7 @@ export function runAgent(opts) {
148
149
  });
149
150
  child.stdout?.on("data", (chunk) => stream.feedStdout(chunk));
150
151
  child.stderr?.on("data", (chunk) => stream.feedStderr(chunk));
151
- // The run state the settlement-half handlers (run-settle.ts) read when their event
152
+ // The run state the settlement-half handlers (agent-settle.ts) read when their event
152
153
  // fires. The getters read through to the run's still-mutating feed state, so a handler
153
154
  // sees the current value rather than a snapshot taken when its listener was registered.
154
155
  const state = {
@@ -210,7 +211,7 @@ export function runAgent(opts) {
210
211
  // run with no resolved model) rejects the run, preserving the contract a synchronous
211
212
  // executor's throw used to get from the Promise constructor. No child was spawned, so no
212
213
  // `error`/`close` ever fires and the settle path that removes the private TMPDIR
213
- // (run-stream.ts's finish) never runs — remove it here, best-effort like finish does, so
214
+ // (agent-stream.ts's finish) never runs — remove it here, best-effort like finish does, so
214
215
  // a broken run does not leave a `tumwater-run-*` temp dir behind.
215
216
  try {
216
217
  removeTree(runTmp);
@@ -218,6 +219,19 @@ export function runAgent(opts) {
218
219
  catch {
219
220
  /* left for the OS temp sweep */
220
221
  }
222
+ // The two append-log streams opened before the build-and-spawn stage (the raw and
223
+ // backend-native transcripts) are never handed to a stream-settlement path on this throw
224
+ // — there is no child and no finish(), so nothing else would ever end them — end them
225
+ // here best-effort (the same cleanup the outer setup-failure catch makes) or every
226
+ // rejected run leaks two open transcript descriptors that accumulate in the
227
+ // long-lived orchestrator process.
228
+ try {
229
+ rawLog?.stream.end();
230
+ nativeLog?.stream.end();
231
+ }
232
+ catch {
233
+ /* best-effort */
234
+ }
221
235
  reject(err instanceof Error ? err : new Error(`runAgent: ${String(err)}`));
222
236
  }
223
237
  });
@@ -1,15 +1,15 @@
1
- /** The settlement half of a backend agent run (the split of src/agent/run-agent.ts along the
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-agent.ts keeps the lifecycle
5
- * (setup, watchdogs, the feed), delegates its build-and-spawn stage to src/agent/run-spawn.ts,
6
- * its streaming pipeline to src/agent/run-stream.ts and its `error`/`exit`/`close` listeners
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-agent.ts and
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-agent imports these functions at runtime, and this module imports only run-agent's type.
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";
@@ -165,44 +165,68 @@ function settleErrorMessage(state, blindRead, failed, stderr, code) {
165
165
  return (state.parser.errorMessage ??
166
166
  (failed ? stderr.trim().slice(-500) || `the agent exited ${code}` : undefined));
167
167
  }
168
- /** The run's `close` event: the process died and all its streams closed. Flush what the
169
- * decoders held back, reconcile the backend's post-run work, classify the exit cause, and
170
- * settle the run's result. */
171
- export function settleClose(state, code) {
172
- const { backend, runSpec, invocation, translator, onLine } = state;
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) {
173
172
  // Flush any bytes the decoder held back at stream end and any unterminated final line so the
174
173
  // tail is not lost, then let the translator flush its own pending canonical line(s) — a
175
174
  // batching backend's unclosed last assistant message, which a killed run never closes with a
176
175
  // later event.
177
176
  state.feedCanonical(state.decoder.end(), true);
178
- for (const canonical of translator.finalize?.() ?? [])
179
- state.parser.feed(canonical + "\n", onLine);
180
- // A blind image read on an authoring run deliberately killed it, so its exit is a kill like
181
- // the watchdogs' — the half-done work is discarded.
182
- const killed = state.wasKilled();
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) {
183
186
  // The backend's post-run reconciliation (opencode's authoritative session export), only when
184
187
  // the run is not already settled (a failed spawn settles in the 'error' handler before
185
188
  // 'close') and was not killed — a killed run's session is half-done and no export should
186
189
  // spend its 10 s on it. Its lines are written and parsed like streamed ones, so they reach
187
190
  // the raw log and the parser's authoritative fold (run_cost/run_usage).
188
191
  if (!state.settled() && !killed) {
189
- for (const canonical of backend.afterRun?.(runSpec, invocation, translator) ?? []) {
190
- 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);
191
194
  }
192
195
  }
193
- const failed = killed ||
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 ||
194
201
  state.parser.stopReason === "error" ||
195
- (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);
196
223
  // The backend classifies the exit's stderr (pi: the transient JSON-parse crash, a
197
224
  // provider-wide backend failure, a permanent config error). The runner merges that with the
198
225
  // parser's own event-derived flags — one exit has one cause.
199
226
  const stderr = state.stderr();
200
227
  const cls = backend.classifyExit({ code, stderr, killed });
201
228
  const stderrBackend = cls.backendKind;
202
- const configError = !killed &&
203
- cls.transientPiCrash !== true &&
204
- stderrBackend === undefined &&
205
- (state.parser.configError || cls.configError === true);
229
+ const configError = closeConfigError(state, cls, killed);
206
230
  const blindRead = state.blindRead();
207
231
  const errorMessage = settleErrorMessage(state, blindRead, failed, stderr, code);
208
232
  state.finish(resultFromParser(state, {
@@ -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-agent.ts) binds its watchdogs and settlement wiring to, and passes to
7
- * createAgentStream (src/agent/run-stream.ts).
8
- * Part 1/2 of the runAgent split: the streaming pipeline (part 2/2) lives in run-stream.ts.
9
- * This module imports only run-agent's type surface, so
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";
@@ -100,6 +100,15 @@ function buildAndSpawn(backend, opts, pre) {
100
100
  blindRead: () => blind.read,
101
101
  };
102
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
+ }
103
112
  /** Build and spawn one non-interactive agent run, from the marker write through the child spawn,
104
113
  * and return the bundle the run's stream pipeline, watchdogs and settlement wiring consume. The
105
114
  * caller's setup (opened logs, the run's private TMPDIR) is passed in; a synchronous throw here
@@ -151,8 +160,12 @@ export function spawnPiRun(backend, logs, runTmp, opts) {
151
160
  },
152
161
  });
153
162
  // The run's cross-group attribution mark, minted before the spawn so the environment can carry
154
- // it and the exit sweep can name it.
155
- const runMarker = makeRunMarker();
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);
156
169
  const resolved = backend.resolveBin(opts.config);
157
170
  // Install the notes shim under the team root's `.tumwater/bin` so every backend's run finds
158
171
  // `tumwater-notes` first on PATH (notes-shim.ts). A failure to write it logs one warning and
@@ -202,9 +215,7 @@ export function spawnPiRun(backend, logs, runTmp, opts) {
202
215
  prepared = backend.prepare?.(runSpec);
203
216
  }
204
217
  catch (error) {
205
- if (warnRoot !== undefined) {
206
- warnEventBestEffort(warnRoot, TUMWATER_SOURCE, `agent prepare failed, run continues: ${errorMessage(error)}`);
207
- }
218
+ warnPrepareFailed(warnRoot, error);
208
219
  }
209
220
  if (prepared === undefined) {
210
221
  // No `prepare` step: build and spawn synchronously and hand the bundle straight back, so
@@ -217,9 +228,7 @@ export function spawnPiRun(backend, logs, runTmp, opts) {
217
228
  await prepared;
218
229
  }
219
230
  catch (error) {
220
- if (warnRoot !== undefined) {
221
- warnEventBestEffort(warnRoot, TUMWATER_SOURCE, `agent prepare failed, run continues: ${errorMessage(error)}`);
222
- }
231
+ warnPrepareFailed(warnRoot, error);
223
232
  }
224
233
  return buildAndSpawn(backend, opts, { runSpec, parser, runMarker, resolved, blind, runTmp });
225
234
  })();
@@ -3,11 +3,11 @@
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-agent.ts) delegates to behind the `AgentStream`
7
- * interface: run-agent wires the child's `data` events to `feedStdout`/`feedStderr`, binds the
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-agent's, run-spawn's
10
- * and run-settle's type surfaces, so there is no runtime import cycle. */
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
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
@@ -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-agent.ts routes an unregistered id to
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";
@@ -2,8 +2,8 @@
2
2
  * subscription-account.ts) — which billing pays for claude runs, and the newest subscription
3
3
  * usage-window reading each run reported (plans/agent-backends.md §7 "Billing" and §8 "The
4
4
  * subscription usage window"). Persisted at `.tumwater/state/claude-account.json`, written only
5
- * by the orchestrator process, so the sibling parts that throttle or display the subscription
6
- * window (6b/7–6e/7) read one answer instead of re-deriving it per surface.
5
+ * by the orchestrator process, so the sibling parts read one answer instead of re-deriving it
6
+ * per surface.
7
7
  *
8
8
  * Everything here is a thin wrapper over the generic per-backend store (found
9
9
  * grounds/codex-backend.md "Groundwork: the subscription account is per backend", item 1),
@@ -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, utilizationPct, windowEntries, windowLabel, windowReserved, windowUsedUp, writeAccount, } from "../subscription-account.js";
20
+ import { observeAccount, readAccount, subscriptionWindowRejected, 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
@@ -43,30 +43,9 @@ export function writeClaudeAccount(root, account) {
43
43
  export function observeClaudeAccount(root, run, now = Date.now()) {
44
44
  return observeAccount(root, "claude", run, now);
45
45
  }
46
- /** The subscription account's used-up usage window, or null — see subscription-account.ts's
47
- * windowUsedUp for the rule. */
48
- export function claudeWindowUsedUp(account, now, includeOverage = true, reserve) {
49
- return windowUsedUp(account, now, includeOverage, reserve);
50
- }
51
- /** Re-export of the per-backend helpers under their historical claude names for callers that
52
- * predate the per-backend store (budget gates, team holds, the doctor, the role view, the
53
- * status poll). */
54
- export { subscriptionWindowRejected, windowReserved };
55
- /** The window type as a compact label — subscription-account.ts's windowLabel, under its
56
- * historical claude name. */
57
- export function claudeWindowLabel(type) {
58
- return windowLabel(type);
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
- export function claudeWindowEntries(account) {
68
- return windowEntries(account);
69
- }
46
+ /** Re-export of the per-backend rejection signal under its historical claude name for callers
47
+ * that predate the per-backend store (the runner's run-verdict and hold wiring). */
48
+ export { subscriptionWindowRejected };
70
49
  /** Seed the claude account file before the first run (plans/agent-backends.md §7):
71
50
  * `ANTHROPIC_API_KEY` set means claude prefers the key, so billing is API; otherwise
72
51
  * `claude auth status` decides — a claude.ai OAuth login is a subscription, any other login is
@@ -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/run-spawn.ts spawns the run and records this run in the session
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-agent.ts beside the pi adapter, so a test can register a
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
- const env = {};
58
- for (const [key, value] of Object.entries(base)) {
59
- if (key === "CLAUDECODE" || key === "CLAUDE_PID" || key === "CLAUDE_EFFORT")
60
- continue;
61
- if (key.startsWith("CLAUDE_CODE_") && !CLAUDE_CODE_ALLOWLIST.has(key))
62
- continue;
63
- if (key.startsWith("CLAUDE_AGENT_SDK_"))
64
- continue;
65
- // The host session's proxy, not the operator's: only when CLAUDECODE says we are inside one.
66
- if (key === "ANTHROPIC_BASE_URL" && hadClaudeCode)
67
- continue;
68
- env[key] = value;
69
- }
70
- env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = "1";
71
- env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS = "1";
72
- env.BASH_DEFAULT_TIMEOUT_MS = String(BASH_DEFAULT_TIMEOUT_MS);
73
- if (opts.quietTimeoutSeconds > 0) {
74
- // 60 s under the quiet kill, so claude's tool error arrives before the run is declared hung;
75
- // floored so a quiet window under 60 s never derives a non-positive cap.
76
- const derived = opts.quietTimeoutSeconds * 1000 - 60_000;
77
- env.BASH_MAX_TIMEOUT_MS = String(Math.max(derived, MIN_BASH_MAX_TIMEOUT_MS));
78
- }
79
- env.DISABLE_AUTOUPDATER = "1";
80
- env.DISABLE_TELEMETRY = "1";
81
- env.DISABLE_ERROR_REPORTING = "1";
82
- // A team config dir (plans/notes-command.md "Keeping funes out of team runs"): force claude's
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
  }