tumwater 0.3.0 → 0.4.1

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 (315) hide show
  1. package/README.md +1 -1
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-backend.js +5 -2
  4. package/dist/src/agent/backend-traits.js +20 -0
  5. package/dist/src/agent/claude/claude-backend.js +3 -3
  6. package/dist/src/agent/claude/claude-stream.js +170 -166
  7. package/dist/src/agent/claude/{write-guard-hook.js → claude-write-guard-hook.js} +1 -1
  8. package/dist/src/agent/claude/claude-write-guard.js +5 -4
  9. package/dist/src/agent/codex/codex-backend.js +4 -4
  10. package/dist/src/agent/codex/codex-rollout.js +10 -21
  11. package/dist/src/agent/codex/codex-stream.js +202 -179
  12. package/dist/src/agent/codex/{write-guard-hook.js → codex-write-guard-hook.js} +2 -1
  13. package/dist/src/agent/codex/codex-write-guard.js +3 -3
  14. package/dist/src/agent/notes-shim.js +2 -2
  15. package/dist/src/agent/opencode/opencode-backend.js +4 -4
  16. package/dist/src/agent/opencode/opencode-config.js +2 -2
  17. package/dist/src/agent/opencode/opencode-stream.js +97 -88
  18. package/dist/src/agent/run-agent.js +66 -381
  19. package/dist/src/agent/run-settle.js +3 -2
  20. package/dist/src/agent/run-spawn.js +235 -0
  21. package/dist/src/agent/run-stream.js +193 -0
  22. package/dist/src/agent/write-guard.js +5 -3
  23. package/dist/src/backlog/backlog-archive.js +88 -36
  24. package/dist/src/backlog/backlog-cli-view.js +92 -98
  25. package/dist/src/backlog/backlog-cli.js +9 -64
  26. package/dist/src/backlog/backlog-commit.js +17 -9
  27. package/dist/src/backlog/backlog-eligibility.js +45 -27
  28. package/dist/src/backlog/backlog-index-data.js +159 -0
  29. package/dist/src/backlog/backlog-index.js +7 -5
  30. package/dist/src/backlog/backlog-md.js +1 -1
  31. package/dist/src/backlog/backlog-parts.js +23 -13
  32. package/dist/src/backlog/backlog-release.js +43 -33
  33. package/dist/src/backlog/backlog-render.js +1 -1
  34. package/dist/src/backlog/backlog-structure.js +0 -144
  35. package/dist/src/backlog/backlog-write.js +30 -17
  36. package/dist/src/backlog/backlog.js +9 -0
  37. package/dist/src/backlog/question-answer.js +3 -1
  38. package/dist/src/baseline/main-baseline.js +50 -15
  39. package/dist/src/baseline/main-guardrails.js +29 -78
  40. package/dist/src/baseline/main-red.js +64 -103
  41. package/dist/src/baseline/main-watch.js +89 -35
  42. package/dist/src/brief/brief-edit.js +3 -1
  43. package/dist/src/brief/brief.js +40 -0
  44. package/dist/src/budget/budget-fallback-view.js +330 -0
  45. package/dist/src/budget/budget-wiring.js +167 -0
  46. package/dist/src/budget/budget.js +3 -2
  47. package/dist/src/build/build-check-events.js +4 -2
  48. package/dist/src/build/build-check-report.js +2 -1
  49. package/dist/src/build/dep-install.js +64 -1
  50. package/dist/src/cli/cli-args.js +72 -0
  51. package/dist/src/cli/cli-flag-specs.js +38 -0
  52. package/dist/src/cli/cli-marker-commands.js +2 -2
  53. package/dist/src/cli/cli-query-commands.js +51 -41
  54. package/dist/src/cli/cli-review-commands.js +113 -0
  55. package/dist/src/cli/cli-role-command.js +140 -58
  56. package/dist/src/cli/cli-start-summary.js +10 -3
  57. package/dist/src/cli/cli-start.js +92 -38
  58. package/dist/src/cli/completion.js +21 -1
  59. package/dist/src/cli/config-commands.js +17 -8
  60. package/dist/src/cli/help.js +84 -16
  61. package/dist/src/cli/log-commands.js +137 -105
  62. package/dist/src/cli/question-commands.js +89 -64
  63. package/dist/src/cli.js +408 -286
  64. package/dist/src/collections.js +56 -6
  65. package/dist/src/config/config-editable-keys.js +3 -0
  66. package/dist/src/config/config-fallbacks.js +3 -2
  67. package/dist/src/config/config-request.js +176 -51
  68. package/dist/src/config/config-validation-roles.js +271 -0
  69. package/dist/src/config/config-validation.js +51 -241
  70. package/dist/src/config/config-views.js +10 -2
  71. package/dist/src/config/config-write.js +209 -140
  72. package/dist/src/config/config.js +9 -3
  73. package/dist/src/doctor/doctor-backlog-bugs.js +96 -53
  74. package/dist/src/doctor/doctor-backlog-plans.js +222 -88
  75. package/dist/src/doctor/doctor-backlog.js +171 -32
  76. package/dist/src/doctor/doctor-check-names.js +7 -1
  77. package/dist/src/doctor/doctor-checks.js +26 -19
  78. package/dist/src/doctor/doctor-claude-checks.js +6 -9
  79. package/dist/src/doctor/doctor-codex-checks.js +15 -29
  80. package/dist/src/doctor/doctor-contention-checks.js +293 -134
  81. package/dist/src/doctor/doctor-fallback-checks.js +130 -0
  82. package/dist/src/doctor/doctor-holds.js +13 -15
  83. package/dist/src/doctor/doctor-model-checks.js +45 -200
  84. package/dist/src/doctor/doctor-notify.js +7 -2
  85. package/dist/src/doctor/doctor-opencode-checks.js +23 -9
  86. package/dist/src/doctor/doctor-pi-checks.js +62 -0
  87. package/dist/src/doctor/doctor-principles.js +26 -0
  88. package/dist/src/doctor/doctor-project-checks.js +108 -13
  89. package/dist/src/doctor/doctor-role-notes.js +51 -0
  90. package/dist/src/doctor/doctor.js +17 -5
  91. package/dist/src/doctor/probe.js +14 -1
  92. package/dist/src/events/event-format.js +313 -157
  93. package/dist/src/events/event-labels.js +0 -1
  94. package/dist/src/events/event-read.js +13 -0
  95. package/dist/src/events/event-window.js +20 -2
  96. package/dist/src/events/notify.js +1 -1
  97. package/dist/src/failure/failure-cluster.js +4 -3
  98. package/dist/src/failure/failure-data.js +127 -90
  99. package/dist/src/failure/failure-render.js +62 -21
  100. package/dist/src/failure/failure-state-change.js +174 -8
  101. package/dist/src/failure/time-spend.js +136 -167
  102. package/dist/src/files/file-queue.js +17 -0
  103. package/dist/src/files/files.js +7 -0
  104. package/dist/src/gates/budget-gates.js +116 -324
  105. package/dist/src/gates/gate-polls.js +123 -184
  106. package/dist/src/gates/maintenance-quota.js +27 -17
  107. package/dist/src/gates/role-cap-gates.js +1 -1
  108. package/dist/src/gates/streak-gate.js +1 -1
  109. package/dist/src/git/commit-message.js +12 -0
  110. package/dist/src/git/git.js +18 -0
  111. package/dist/src/git/slot-salvage.js +4 -5
  112. package/dist/src/git/slots-state.js +12 -3
  113. package/dist/src/git/worktree-pool-startup.js +127 -0
  114. package/dist/src/git/worktree-pool.js +1 -120
  115. package/dist/src/gui/gui-admin-endpoints.js +1 -1
  116. package/dist/src/gui/gui-args.js +1 -1
  117. package/dist/src/gui/gui-backlog-commands.js +21 -12
  118. package/dist/src/gui/gui-backlog-endpoints.js +6 -5
  119. package/dist/src/gui/gui-command.js +19 -1
  120. package/dist/src/gui/gui-config-commands.js +126 -85
  121. package/dist/src/gui/gui-endpoint-commands.js +5 -3
  122. package/dist/src/gui/gui-endpoint-common.js +4 -3
  123. package/dist/src/gui/gui-history-endpoints.js +36 -27
  124. package/dist/src/gui/gui-operator-commands.js +175 -10
  125. package/dist/src/gui/gui-prompt-commands.js +16 -7
  126. package/dist/src/gui/gui-quarantine-commands.js +30 -0
  127. package/dist/src/gui/gui-report-endpoints.js +14 -4
  128. package/dist/src/gui/gui-server.js +20 -3
  129. package/dist/src/gui/gui-team-commands.js +1 -1
  130. package/dist/src/history/history-data.js +7 -1
  131. package/dist/src/history/history-queued.js +8 -0
  132. package/dist/src/history/history-render.js +15 -10
  133. package/dist/src/history/history.js +3 -6
  134. package/dist/src/inbox/inbox-top.js +4 -9
  135. package/dist/src/inbox/pending-prompt.js +3 -2
  136. package/dist/src/inbox/prompt-commands.js +189 -161
  137. package/dist/src/init/init.js +43 -17
  138. package/dist/src/merge/merge-core.js +2 -25
  139. package/dist/src/merge/merge-drain.js +23 -136
  140. package/dist/src/merge/merge-git.js +5 -5
  141. package/dist/src/merge/merge-pipeline.js +1 -1
  142. package/dist/src/merge/merge-post-review-drain.js +214 -0
  143. package/dist/src/merge/merge-queue-render.js +11 -12
  144. package/dist/src/merge/merge-queue.js +24 -2
  145. package/dist/src/merge/merge-requeue.js +1 -0
  146. package/dist/src/merge/merge-stack.js +1 -7
  147. package/dist/src/merge/merge-to-main.js +2 -10
  148. package/dist/src/merge/merge-top.js +33 -19
  149. package/dist/src/merge/merge-verify.js +65 -9
  150. package/dist/src/notes-cli.js +8 -3
  151. package/dist/src/operator/operator-commands.js +16 -5
  152. package/dist/src/operator/operator-intent.js +26 -3
  153. package/dist/src/operator/revert.js +18 -12
  154. package/dist/src/orchestrator/orchestrator-poll.js +49 -17
  155. package/dist/src/orchestrator/orchestrator-scheduling.js +4 -2
  156. package/dist/src/orchestrator/orchestrator.js +6 -5
  157. package/dist/src/orchestrator/scheduling-scope.js +51 -7
  158. package/dist/src/orchestrator/scheduling-skip.js +15 -5
  159. package/dist/src/paths.js +26 -10
  160. package/dist/src/pi/pi-backend.js +2 -2
  161. package/dist/src/pi/pi-bin.js +2 -2
  162. package/dist/src/pi/pi-models.js +9 -0
  163. package/dist/src/pi/pi-stream.js +63 -41
  164. package/dist/src/pi/pi-watchdogs.js +65 -49
  165. package/dist/src/pi-extension/full-output.js +2 -3
  166. package/dist/src/process/process.js +1 -1
  167. package/dist/src/process/run-marker.js +10 -2
  168. package/dist/src/process/supervisor.js +7 -4
  169. package/dist/src/prompt/gate-notes.js +35 -10
  170. package/dist/src/prompt/gate-prompts.js +30 -7
  171. package/dist/src/prompt/notes-backlog.js +24 -15
  172. package/dist/src/prompt/principles.js +11 -0
  173. package/dist/src/prompt/prompt.js +13 -7
  174. package/dist/src/redeploy/redeploy-probes.js +11 -1
  175. package/dist/src/redeploy/redeployer.js +54 -7
  176. package/dist/src/redeploy/self-reload.js +7 -2
  177. package/dist/src/report/report-data.js +322 -223
  178. package/dist/src/report/report-fold.js +257 -191
  179. package/dist/src/report/report-main-red.js +64 -0
  180. package/dist/src/report/report-render.js +133 -25
  181. package/dist/src/report/report.js +2 -5
  182. package/dist/src/review/diff-rules.js +53 -0
  183. package/dist/src/review/known-flakes.js +32 -1
  184. package/dist/src/review/post-merge-review-accept.js +51 -0
  185. package/dist/src/review/post-merge-review-render.js +112 -0
  186. package/dist/src/review/post-merge-review.js +159 -24
  187. package/dist/src/review/protected-edits.js +22 -7
  188. package/dist/src/review/quarantine-render.js +82 -0
  189. package/dist/src/review/review-precheck.js +2 -1
  190. package/dist/src/review/review.js +33 -49
  191. package/dist/src/roles/role-catalog.js +73 -2
  192. package/dist/src/roles/role-guidance.js +18 -0
  193. package/dist/src/roles/role-labels.js +6 -3
  194. package/dist/src/roles/role-render.js +62 -8
  195. package/dist/src/roles/role-view.js +54 -12
  196. package/dist/src/roles/roles.js +3 -23
  197. package/dist/src/run/ci-parity.js +121 -0
  198. package/dist/src/run/feedback-filings.js +10 -1
  199. package/dist/src/run/qa-coverage.js +50 -15
  200. package/dist/src/run/run-apply.js +14 -2
  201. package/dist/src/run/run-detail-data.js +36 -16
  202. package/dist/src/run/run-detail-render.js +27 -9
  203. package/dist/src/run/run-finalize.js +76 -46
  204. package/dist/src/run/run-prompt.js +231 -177
  205. package/dist/src/run/run-stage.js +168 -70
  206. package/dist/src/run/run-verdict.js +127 -13
  207. package/dist/src/run/stage-backlog-checks.js +2 -2
  208. package/dist/src/run/stage-check.js +17 -11
  209. package/dist/src/run/stage-checks-shared.js +80 -0
  210. package/dist/src/run/stage-name-checks.js +194 -0
  211. package/dist/src/run/stage-path-checks.js +35 -537
  212. package/dist/src/run/stage-prose-checks.js +261 -0
  213. package/dist/src/runner/leftover.js +61 -21
  214. package/dist/src/runner/revision.js +14 -25
  215. package/dist/src/runner/runner-fallback.js +1 -1
  216. package/dist/src/runner/runner-outcome.js +4 -2
  217. package/dist/src/runner/runner-pi.js +72 -49
  218. package/dist/src/runner/runner-recovery.js +18 -3
  219. package/dist/src/runner/runner-run.js +36 -29
  220. package/dist/src/runner/runner-state.js +4 -3
  221. package/dist/src/scheduling/claim-entries.js +49 -0
  222. package/dist/src/scheduling/claims.js +1 -1
  223. package/dist/src/status/status-data.js +144 -86
  224. package/dist/src/status/status-polls.js +56 -5
  225. package/dist/src/status/status-role-notes.js +49 -0
  226. package/dist/src/status/status-roles.js +19 -2
  227. package/dist/src/team/reclaim.js +2 -2
  228. package/dist/src/text/datetime.js +3 -2
  229. package/dist/src/text/format.js +13 -1
  230. package/dist/src/text/phrases.js +22 -5
  231. package/dist/src/text/text.js +11 -0
  232. package/dist/src/ui/alert-feeds.js +320 -0
  233. package/dist/src/ui/badges.js +35 -2
  234. package/dist/src/ui/gui/gui-client-backlog.js +25 -3
  235. package/dist/src/ui/gui/gui-client-boot.js +102 -74
  236. package/dist/src/ui/gui/gui-client-drawer-config.js +64 -17
  237. package/dist/src/ui/gui/gui-client-drawer.js +46 -5
  238. package/dist/src/ui/gui/gui-client-feed.js +4 -9
  239. package/dist/src/ui/gui/gui-client-history.js +90 -88
  240. package/dist/src/ui/gui/gui-client-labels.js +5 -1
  241. package/dist/src/ui/gui/gui-client-model.js +9 -9
  242. package/dist/src/ui/gui/gui-client-operator.js +11 -11
  243. package/dist/src/ui/gui/gui-client-pending.js +189 -12
  244. package/dist/src/ui/gui/gui-client-report-chart.js +137 -10
  245. package/dist/src/ui/gui/gui-client-report.js +81 -6
  246. package/dist/src/ui/gui/gui-client-roles.js +52 -12
  247. package/dist/src/ui/gui/gui-client-settings.js +138 -4
  248. package/dist/src/ui/gui/gui-client-team.js +13 -2
  249. package/dist/src/ui/gui/gui-client.js +44 -5
  250. package/dist/src/ui/gui/gui-page.js +1 -0
  251. package/dist/src/ui/gui/gui-styles.js +4 -0
  252. package/dist/src/ui/progress-data.js +2 -2
  253. package/dist/src/ui/run-progress-model.js +4 -3
  254. package/dist/src/ui/status-model.js +24 -14
  255. package/dist/src/ui/status-payload.js +168 -117
  256. package/dist/src/ui/status-render.js +140 -81
  257. package/dist/src/ui/team-alert-backlog-feed.js +181 -0
  258. package/dist/src/ui/team-alert-backlog.js +267 -0
  259. package/dist/src/ui/team-alert-merge.js +183 -0
  260. package/dist/src/ui/team-alert-shared.js +79 -0
  261. package/dist/src/ui/team-alert-status.js +321 -0
  262. package/dist/src/ui/team-alert-tail.js +251 -0
  263. package/dist/src/ui/team-alerts.js +31 -513
  264. package/dist/src/ui/tone.js +7 -0
  265. package/dist/src/ui/tui/tui-backlog.js +8 -6
  266. package/dist/src/ui/tui/tui-editor-attach.js +1 -7
  267. package/dist/src/ui/tui/tui-editor-backends.js +1 -6
  268. package/dist/src/ui/tui/tui-editor-brief.js +4 -24
  269. package/dist/src/ui/tui/tui-editor-budget.js +10 -12
  270. package/dist/src/ui/tui/tui-editor-check.js +29 -0
  271. package/dist/src/ui/tui/tui-editor-checks.js +28 -0
  272. package/dist/src/ui/tui/tui-editor-concurrency.js +1 -0
  273. package/dist/src/ui/tui/tui-editor-config.js +19 -6
  274. package/dist/src/ui/tui/tui-editor-ctx.js +12 -1
  275. package/dist/src/ui/tui/tui-editor-entry.js +3 -17
  276. package/dist/src/ui/tui/tui-editor-instances.js +9 -0
  277. package/dist/src/ui/tui/tui-editor-instructions.js +51 -127
  278. package/dist/src/ui/tui/tui-editor-interval.js +51 -113
  279. package/dist/src/ui/tui/tui-editor-notes.js +29 -103
  280. package/dist/src/ui/tui/tui-editor-pause.js +2 -1
  281. package/dist/src/ui/tui/tui-editor-prompt.js +2 -1
  282. package/dist/src/ui/tui/tui-editor-quiet.js +4 -13
  283. package/dist/src/ui/tui/tui-editor-release.js +77 -0
  284. package/dist/src/ui/tui/tui-editor-retire.js +1 -7
  285. package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
  286. package/dist/src/ui/tui/tui-editor-role-file.js +75 -0
  287. package/dist/src/ui/tui/tui-editor-role-key.js +3 -7
  288. package/dist/src/ui/tui/tui-editor-session.js +44 -0
  289. package/dist/src/ui/tui/tui-editor-slots.js +1 -0
  290. package/dist/src/ui/tui/tui-editors.js +13 -1
  291. package/dist/src/ui/tui/tui-frame.js +23 -7
  292. package/dist/src/ui/tui/tui-history.js +12 -10
  293. package/dist/src/ui/tui/tui-keys.js +14 -0
  294. package/dist/src/ui/tui/tui-pane-activity.js +21 -8
  295. package/dist/src/ui/tui/tui-pane-backlog.js +36 -9
  296. package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
  297. package/dist/src/ui/tui/tui-pane-health.js +153 -3
  298. package/dist/src/ui/tui/tui-pane-history.js +308 -228
  299. package/dist/src/ui/tui/tui-pane-pending.js +285 -56
  300. package/dist/src/ui/tui/tui-pane-queued.js +7 -2
  301. package/dist/src/ui/tui/tui-pane-report.js +9 -3
  302. package/dist/src/ui/tui/tui-pane-session.js +12 -0
  303. package/dist/src/ui/tui/tui-pane-transcript.js +12 -8
  304. package/dist/src/ui/tui/tui-pane.js +16 -10
  305. package/dist/src/ui/tui/tui-panes.js +247 -134
  306. package/dist/src/ui/tui/tui.js +8 -5
  307. package/dist/src/verdict/fix-claim.js +47 -1
  308. package/dist/src/verdict/refusal.js +2 -1
  309. package/dist/src/verdict/reply-contract.js +35 -0
  310. package/package.json +1 -1
  311. package/dist/src/baseline/main-culprit.js +0 -94
  312. package/dist/src/baseline/main-revert.js +0 -133
  313. package/dist/src/merge/merge-post-review.js +0 -90
  314. /package/dist/src/gui/{http-body.js → gui-http-body.js} +0 -0
  315. /package/dist/src/merge/{backlog-conflicts.js → merge-backlog-conflicts.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.3.0**: working tumwater. The director and 14 of the 16 roles are enabled by default; `selfcheck`,
45
+ **v0.4.1**: 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
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha": "d36bf79c535e10fe228f058ecc1ca839f3af61b1",
3
- "builtAt": 1792789212458,
2
+ "sha": "defe3e5dd243a9eb3c844751752328d88f5ad674",
3
+ "builtAt": 1792989016668,
4
4
  "root": "/home/runner/work/tumwater/tumwater"
5
5
  }
@@ -1,7 +1,10 @@
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-agent.ts
4
- * owns everything backend-neutral (spawn, marker, watchdogs, abort, stderr capture); the
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
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
5
8
  * post-run settlement — the group-kill and marker sweeps on exit, the spawn-error result and
6
9
  * the close-time reconcile/classify that builds the `PiRunResult` — lives in
7
10
  * src/agent/run-settle.ts. A backend registered here supplies the rest. src/agent/run-agent.ts
@@ -75,3 +75,23 @@ export function modelLabel(backend, provider, model) {
75
75
  export function backendDisplayName(id) {
76
76
  return id.charAt(0).toUpperCase() + id.slice(1);
77
77
  }
78
+ /** A seam's own-default label when neither a provider nor a model resolves:
79
+ * `<backend> default` — pi's historical `pi default` for the pi backend (and an unknown id,
80
+ * which reads as pi), `claude default` for claude, and so on. The one home of the label
81
+ * `role-render.ts`'s Model line and the status rows share, so the two surfaces cannot
82
+ * disagree about what a seam without a model is named. */
83
+ export function backendDefaultLabel(backend) {
84
+ // Only claude, opencode and codex carry their own `<backend> default`; pi, an absent
85
+ // backend and any unregistered id (which reads as pi) all carry pi's historical `pi
86
+ // default`, matching streamsToolOutput's reads-as-pi window.
87
+ if (backend === "claude" || backend === "opencode" || backend === "codex")
88
+ return `${backend} default`;
89
+ return "pi default";
90
+ }
91
+ /** The HTTP status codes the native claude and codex stream translators read as a transient
92
+ * backend (server) failure — the `transientBackend` classification with `backendKind:
93
+ * "server"`. 529 is Anthropic's "overloaded" spelling, which codex's `turn.failed` error
94
+ * carries too. One home for the set the two adapters' classifiers share, so a status added
95
+ * for one cannot go missing from the other (opencode classifies the same failure from message
96
+ * text, not a status code, so it is not in the set). */
97
+ export const SERVER_TRANSIENT_STATUS = new Set([500, 502, 503, 504, 529]);
@@ -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-agent.ts owns the spawn and records this run in the session
4
+ * backend-specific. src/agent/run-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
  *
@@ -20,7 +20,7 @@ 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
22
  import { routeModel } from "../../config/model-selector.js";
23
- import { backendBaseEnv, withNotesPath } from "../../process/run-marker.js";
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";
26
26
  import { claudeArgs } from "./claude-args.js";
@@ -147,7 +147,7 @@ export const claudeBackend = {
147
147
  // the marker the same way pi's adapter carries it (withNotesPath); from part 2/4 the run
148
148
  // saves its note through the `tumwater-notes` command on PATH, so no MCP server reaches the
149
149
  // run and `--strict-mcp-config` (always in claude-args.ts) keeps it that way.
150
- const base = withNotesPath(backendBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
150
+ const base = backendRunEnv(run);
151
151
  const env = claudeEnv(base, {
152
152
  quietTimeoutSeconds: run.config.quietTimeoutSeconds,
153
153
  ...(expandedConfigDir !== undefined ? { configDir: expandedConfigDir } : {}),
@@ -11,11 +11,13 @@
11
11
  * Pure translation: no spawn, no backend registration, no config. The factory keeps per-run
12
12
  * state (the pending split-block assistant message, the unmapped native types seen, whether a
13
13
  * `run_cost` arrived), so call it once per run. Unmapped native types are recorded once each
14
- * on the seam's optional `warnings()` — src/agent/run-agent.ts forwards each as an
14
+ * on the seam's optional `warnings()` — src/agent/run-stream.ts forwards each as an
15
15
  * `agent_unknown_event` warning after the run. `cost_unknown` is recorded the same way when a
16
16
  * run produced no authoritative `run_cost`. */
17
17
  import { joinParts } from "../../text/text.js";
18
+ import { secondsUntil } from "../../text/datetime.js";
18
19
  import { canonicalUsageBlock, isJsonObject, objectOrEmpty, nonNegativeNumber, parseJsonObject } from "../../files/json-object.js";
20
+ import { SERVER_TRANSIENT_STATUS } from "../backend-traits.js";
19
21
  /** claude's tool names → the canonical pi names the readers already know. Glob/Grep are kept
20
22
  * lower-cased; MultiEdit collapses onto edit (pi has one edit tool). Anything else is
21
23
  * lower-cased so a future tool still reads as a name rather than as `undefined`. */
@@ -28,9 +30,6 @@ const TOOL_NAMES = {
28
30
  Glob: "glob",
29
31
  Grep: "grep",
30
32
  };
31
- /** claude's HTTP status classes that mean a transient backend failure. 529 is Anthropic's
32
- * "overloaded" spelling. */
33
- const SERVER_STATUS = new Set([500, 502, 503, 504, 529]);
34
33
  /** claude's HTTP status classes that mean a permanent configuration error. */
35
34
  const CONFIG_STATUS = new Set([400, 401, 403, 404]);
36
35
  function str(value) {
@@ -93,7 +92,7 @@ function classifyResult(result) {
93
92
  const status = nonNegativeNumber(result.api_error_status, undefined);
94
93
  if (status === 429)
95
94
  return { kind: "transientRateLimit" };
96
- if (status !== undefined && SERVER_STATUS.has(status))
95
+ if (status !== undefined && SERVER_TRANSIENT_STATUS.has(status))
97
96
  return { kind: "transientBackend", backendKind: "server" };
98
97
  // The context-overflow text wins over a configuration status: claude reports an oversized
99
98
  // prompt as HTTP 400 ("prompt is too long"), whose status alone would read as configError.
@@ -169,6 +168,167 @@ export function claudeTranslator(opts = {}) {
169
168
  seen.add(kind);
170
169
  warnings.add(kind);
171
170
  };
171
+ const handleSystem = (event) => {
172
+ const subtype = str(event.subtype);
173
+ if (subtype === "init") {
174
+ const out = [];
175
+ const cwd = str(event.cwd);
176
+ const id = str(event.session_id);
177
+ out.push(JSON.stringify({ type: "session", ...(cwd !== undefined ? { cwd } : {}), ...(id !== undefined ? { id } : {}) }));
178
+ out.push(JSON.stringify({ type: "agent_start" }));
179
+ const billing = billingOf(str(event.apiKeySource), opts.thirdPartyProvider === true);
180
+ if (billing)
181
+ out.push(JSON.stringify({ type: "billing", billing }));
182
+ return out;
183
+ }
184
+ if (subtype === "api_retry") {
185
+ const attempt = nonNegativeNumber(event.attempt, undefined);
186
+ const maxAttempts = nonNegativeNumber(event.max_retries, undefined);
187
+ const status = nonNegativeNumber(event.error_status, undefined);
188
+ const error = str(event.error) ?? "";
189
+ const errorMessage = joinParts([status !== undefined ? `status ${status}` : "", error], ": ");
190
+ return [
191
+ JSON.stringify({
192
+ type: "auto_retry_start",
193
+ ...(attempt !== undefined ? { attempt } : {}),
194
+ ...(maxAttempts !== undefined ? { maxAttempts } : {}),
195
+ errorMessage: errorMessage || "api retry",
196
+ }),
197
+ ];
198
+ }
199
+ if (subtype === "compact_boundary")
200
+ return [JSON.stringify({ type: "compaction_start" })];
201
+ // A thinking-token estimate during extended thinking: known, carries nothing canonical.
202
+ if (subtype === "thinking_tokens")
203
+ return [];
204
+ record(`system:${subtype ?? "unknown"}`);
205
+ return [];
206
+ };
207
+ const handleAssistant = (event) => {
208
+ sawActivity = true;
209
+ const message = objectOrEmpty(event.message);
210
+ const id = str(message.id) ?? `anon-${anonymous++}`;
211
+ // A new id flushes the open message; a continuing one flushes nothing. Either way the
212
+ // fresh or open pending then folds this event's content, usage, and stop reason the
213
+ // same way — that shared fold is why both branches live on one path.
214
+ let out = [];
215
+ if (pending?.id !== id) {
216
+ out = flush();
217
+ pending = { id, content: [], usage: canonicalUsage(isJsonObject(message.usage) ? message.usage : undefined) };
218
+ }
219
+ const blocks = Array.isArray(message.content) ? message.content : [];
220
+ for (const raw of blocks) {
221
+ if (!isJsonObject(raw))
222
+ continue;
223
+ const block = canonicalBlock(raw);
224
+ if (!block)
225
+ continue;
226
+ pending.content.push(block);
227
+ if (block.type === "toolCall")
228
+ out.push(startToolCall(block));
229
+ }
230
+ // Usage repeats on every event of the same message; the last read wins and is counted
231
+ // once because the fold emits one message_end per id.
232
+ pending.usage = canonicalUsage(isJsonObject(message.usage) ? message.usage : undefined);
233
+ const stopReason = str(message.stop_reason);
234
+ if (stopReason !== undefined)
235
+ pending.stopReason = stopReason;
236
+ return out;
237
+ };
238
+ const handleUser = (event) => {
239
+ const out = flush();
240
+ const message = objectOrEmpty(event.message);
241
+ const blocks = Array.isArray(message.content) ? message.content : [];
242
+ for (const raw of blocks) {
243
+ if (!isJsonObject(raw) || raw.type !== "tool_result")
244
+ continue;
245
+ out.push(JSON.stringify({
246
+ type: "tool_execution_end",
247
+ toolCallId: str(raw.tool_use_id) ?? "",
248
+ isError: raw.is_error === true,
249
+ }));
250
+ }
251
+ return out;
252
+ };
253
+ const handleRateLimitEvent = (event) => {
254
+ const info = objectOrEmpty(event.rate_limit_info);
255
+ const rateLimitType = str(info.rateLimitType);
256
+ const status = str(info.status);
257
+ // claude 2.1.295 reports every window's utilization and reset under `unifiedWindows`,
258
+ // keyed by window type, and only the binding window's status at the top level.
259
+ const windows = [];
260
+ for (const [type, raw] of Object.entries(objectOrEmpty(info.unifiedWindows))) {
261
+ if (type === rateLimitType || !isJsonObject(raw))
262
+ continue;
263
+ const reading = { type: "rate_limit", rateLimitType: type };
264
+ const windowResets = nonNegativeNumber(raw.resetsAt, undefined);
265
+ if (windowResets !== undefined)
266
+ reading.resetsAt = windowResets;
267
+ const windowUtil = nonNegativeNumber(raw.utilization, undefined);
268
+ if (windowUtil !== undefined)
269
+ reading.utilization = windowUtil;
270
+ windows.push(JSON.stringify(reading));
271
+ }
272
+ const own = rateLimitType === undefined ? undefined : objectOrEmpty(objectOrEmpty(info.unifiedWindows)[rateLimitType]);
273
+ const resetsAt = nonNegativeNumber(info.resetsAt, undefined) ?? nonNegativeNumber(own?.resetsAt, undefined);
274
+ const out = { type: "rate_limit" };
275
+ if (rateLimitType !== undefined)
276
+ out.rateLimitType = rateLimitType;
277
+ if (status !== undefined)
278
+ out.status = status;
279
+ if (resetsAt !== undefined)
280
+ out.resetsAt = resetsAt;
281
+ const utilization = nonNegativeNumber(info.utilization, undefined) ?? nonNegativeNumber(own?.utilization, undefined);
282
+ if (utilization !== undefined)
283
+ out.utilization = utilization;
284
+ const overageStatus = str(info.overageStatus);
285
+ if (overageStatus !== undefined)
286
+ out.overageStatus = overageStatus;
287
+ if (info.isUsingOverage === true)
288
+ out.isUsingOverage = true;
289
+ if (status === "rejected" && resetsAt !== undefined) {
290
+ // resetsAt is a unix-seconds stamp; secondsUntil folds it (×1000) to whole seconds.
291
+ out.retryAfterSeconds = secondsUntil(resetsAt * 1000, Date.now());
292
+ }
293
+ return [JSON.stringify(out), ...windows];
294
+ };
295
+ const handleResult = (event) => {
296
+ sawActivity = true;
297
+ const out = flush();
298
+ // The assistant events' usage is the message-start snapshot, so the result's total
299
+ // is the run's real output-token count (thinking included).
300
+ const outputTokens = nonNegativeNumber(objectOrEmpty(event.usage).output_tokens, undefined);
301
+ if (outputTokens !== undefined)
302
+ out.push(JSON.stringify({ type: "run_usage", outputTokens }));
303
+ const total = nonNegativeNumber(event.total_cost_usd, undefined);
304
+ if (total !== undefined) {
305
+ sawRunCost = true;
306
+ out.push(JSON.stringify({ type: "run_cost", total }));
307
+ }
308
+ else {
309
+ warnings.add("cost_unknown");
310
+ }
311
+ if (event.is_error === true) {
312
+ const { kind, backendKind } = classifyResult(event);
313
+ const message = {
314
+ role: "assistant",
315
+ content: [],
316
+ stopReason: "error",
317
+ };
318
+ const errorMessage = str(event.result) ?? str(event.error);
319
+ if (errorMessage !== undefined)
320
+ message.errorMessage = errorMessage;
321
+ // tumwaterKind/backendKind ride the canonical EVENT (not its message): the parser
322
+ // reads event.tumwaterKind and trusts it before its text regexes.
323
+ out.push(JSON.stringify({
324
+ type: "message_end",
325
+ message,
326
+ ...(kind !== undefined ? { tumwaterKind: kind } : {}),
327
+ ...(backendKind !== undefined ? { backendKind } : {}),
328
+ }));
329
+ }
330
+ return out;
331
+ };
172
332
  const translate = (line) => {
173
333
  const parsed = parseJsonObject(line);
174
334
  if (!parsed)
@@ -176,167 +336,11 @@ export function claudeTranslator(opts = {}) {
176
336
  const event = parsed;
177
337
  const type = str(event.type);
178
338
  switch (type) {
179
- case "system": {
180
- const subtype = str(event.subtype);
181
- if (subtype === "init") {
182
- const out = [];
183
- const cwd = str(event.cwd);
184
- const id = str(event.session_id);
185
- out.push(JSON.stringify({ type: "session", ...(cwd !== undefined ? { cwd } : {}), ...(id !== undefined ? { id } : {}) }));
186
- out.push(JSON.stringify({ type: "agent_start" }));
187
- const billing = billingOf(str(event.apiKeySource), opts.thirdPartyProvider === true);
188
- if (billing)
189
- out.push(JSON.stringify({ type: "billing", billing }));
190
- return out;
191
- }
192
- if (subtype === "api_retry") {
193
- const attempt = nonNegativeNumber(event.attempt, undefined);
194
- const maxAttempts = nonNegativeNumber(event.max_retries, undefined);
195
- const status = nonNegativeNumber(event.error_status, undefined);
196
- const error = str(event.error) ?? "";
197
- const errorMessage = joinParts([status !== undefined ? `status ${status}` : "", error], ": ");
198
- return [
199
- JSON.stringify({
200
- type: "auto_retry_start",
201
- ...(attempt !== undefined ? { attempt } : {}),
202
- ...(maxAttempts !== undefined ? { maxAttempts } : {}),
203
- errorMessage: errorMessage || "api retry",
204
- }),
205
- ];
206
- }
207
- if (subtype === "compact_boundary")
208
- return [JSON.stringify({ type: "compaction_start" })];
209
- // A thinking-token estimate during extended thinking: known, carries nothing canonical.
210
- if (subtype === "thinking_tokens")
211
- return [];
212
- record(`system:${subtype ?? "unknown"}`);
213
- return [];
214
- }
215
- case "assistant": {
216
- sawActivity = true;
217
- const message = objectOrEmpty(event.message);
218
- const id = str(message.id) ?? `anon-${anonymous++}`;
219
- // A new id flushes the open message; a continuing one flushes nothing. Either way the
220
- // fresh or open pending then folds this event's content, usage, and stop reason the
221
- // same way — that shared fold is why both branches live on one path.
222
- let out = [];
223
- if (pending?.id !== id) {
224
- out = flush();
225
- pending = { id, content: [], usage: canonicalUsage(isJsonObject(message.usage) ? message.usage : undefined) };
226
- }
227
- const blocks = Array.isArray(message.content) ? message.content : [];
228
- for (const raw of blocks) {
229
- if (!isJsonObject(raw))
230
- continue;
231
- const block = canonicalBlock(raw);
232
- if (!block)
233
- continue;
234
- pending.content.push(block);
235
- if (block.type === "toolCall")
236
- out.push(startToolCall(block));
237
- }
238
- // Usage repeats on every event of the same message; the last read wins and is counted
239
- // once because the fold emits one message_end per id.
240
- pending.usage = canonicalUsage(isJsonObject(message.usage) ? message.usage : undefined);
241
- const stopReason = str(message.stop_reason);
242
- if (stopReason !== undefined)
243
- pending.stopReason = stopReason;
244
- return out;
245
- }
246
- case "user": {
247
- const out = flush();
248
- const message = objectOrEmpty(event.message);
249
- const blocks = Array.isArray(message.content) ? message.content : [];
250
- for (const raw of blocks) {
251
- if (!isJsonObject(raw) || raw.type !== "tool_result")
252
- continue;
253
- out.push(JSON.stringify({
254
- type: "tool_execution_end",
255
- toolCallId: str(raw.tool_use_id) ?? "",
256
- isError: raw.is_error === true,
257
- }));
258
- }
259
- return out;
260
- }
261
- case "rate_limit_event": {
262
- const info = objectOrEmpty(event.rate_limit_info);
263
- const rateLimitType = str(info.rateLimitType);
264
- const status = str(info.status);
265
- // claude 2.1.295 reports every window's utilization and reset under `unifiedWindows`,
266
- // keyed by window type, and only the binding window's status at the top level.
267
- const windows = [];
268
- for (const [type, raw] of Object.entries(objectOrEmpty(info.unifiedWindows))) {
269
- if (type === rateLimitType || !isJsonObject(raw))
270
- continue;
271
- const reading = { type: "rate_limit", rateLimitType: type };
272
- const windowResets = nonNegativeNumber(raw.resetsAt, undefined);
273
- if (windowResets !== undefined)
274
- reading.resetsAt = windowResets;
275
- const windowUtil = nonNegativeNumber(raw.utilization, undefined);
276
- if (windowUtil !== undefined)
277
- reading.utilization = windowUtil;
278
- windows.push(JSON.stringify(reading));
279
- }
280
- const own = rateLimitType === undefined ? undefined : objectOrEmpty(objectOrEmpty(info.unifiedWindows)[rateLimitType]);
281
- const resetsAt = nonNegativeNumber(info.resetsAt, undefined) ?? nonNegativeNumber(own?.resetsAt, undefined);
282
- const out = { type: "rate_limit" };
283
- if (rateLimitType !== undefined)
284
- out.rateLimitType = rateLimitType;
285
- if (status !== undefined)
286
- out.status = status;
287
- if (resetsAt !== undefined)
288
- out.resetsAt = resetsAt;
289
- const utilization = nonNegativeNumber(info.utilization, undefined) ?? nonNegativeNumber(own?.utilization, undefined);
290
- if (utilization !== undefined)
291
- out.utilization = utilization;
292
- const overageStatus = str(info.overageStatus);
293
- if (overageStatus !== undefined)
294
- out.overageStatus = overageStatus;
295
- if (info.isUsingOverage === true)
296
- out.isUsingOverage = true;
297
- if (status === "rejected" && resetsAt !== undefined) {
298
- // resetsAt is a unix-seconds stamp; retryAfterSeconds is the wait the runner honors.
299
- out.retryAfterSeconds = Math.max(0, Math.round(resetsAt - Date.now() / 1000));
300
- }
301
- return [JSON.stringify(out), ...windows];
302
- }
303
- case "result": {
304
- sawActivity = true;
305
- const out = flush();
306
- // The assistant events' usage is the message-start snapshot, so the result's total
307
- // is the run's real output-token count (thinking included).
308
- const outputTokens = nonNegativeNumber(objectOrEmpty(event.usage).output_tokens, undefined);
309
- if (outputTokens !== undefined)
310
- out.push(JSON.stringify({ type: "run_usage", outputTokens }));
311
- const total = nonNegativeNumber(event.total_cost_usd, undefined);
312
- if (total !== undefined) {
313
- sawRunCost = true;
314
- out.push(JSON.stringify({ type: "run_cost", total }));
315
- }
316
- else {
317
- warnings.add("cost_unknown");
318
- }
319
- if (event.is_error === true) {
320
- const { kind, backendKind } = classifyResult(event);
321
- const message = {
322
- role: "assistant",
323
- content: [],
324
- stopReason: "error",
325
- };
326
- const errorMessage = str(event.result) ?? str(event.error);
327
- if (errorMessage !== undefined)
328
- message.errorMessage = errorMessage;
329
- // tumwaterKind/backendKind ride the canonical EVENT (not its message): the parser
330
- // reads event.tumwaterKind and trusts it before its text regexes.
331
- out.push(JSON.stringify({
332
- type: "message_end",
333
- message,
334
- ...(kind !== undefined ? { tumwaterKind: kind } : {}),
335
- ...(backendKind !== undefined ? { backendKind } : {}),
336
- }));
337
- }
338
- return out;
339
- }
339
+ case "system": return handleSystem(event);
340
+ case "assistant": return handleAssistant(event);
341
+ case "user": return handleUser(event);
342
+ case "rate_limit_event": return handleRateLimitEvent(event);
343
+ case "result": return handleResult(event);
340
344
  case "tool_progress":
341
345
  // A 30 s heartbeat of a running tool call: known, and the open call already tracks
342
346
  // liveness.
@@ -1,5 +1,5 @@
1
1
  /** The PreToolUse hook claude runs before each file write in a team run (claude-write-guard.ts
2
- * installs it through `--settings`): `node write-guard-hook.js <worktree>` with claude's hook
2
+ * installs it through `--settings`): `node claude-write-guard-hook.js <worktree>` with the hook
3
3
  * payload on stdin. To block the write it prints a `deny` decision with the refusal as its reason:
4
4
  * the model reads only the reason, where a refusal by exit 2 would also quote the whole hook
5
5
  * command. It prints nothing to let the write run. Any failure of its own lets the write run, so a
@@ -7,7 +7,7 @@
7
7
  * `--setting-sources project` leaves the operator's own hooks out of a run, while `--settings`
8
8
  * still loads this one. The hook gets the run's worktree as an argument rather than reading
9
9
  * claude's `cwd`, which follows the Bash tool's `cd` into subdirectories. The entry point is
10
- * write-guard-hook.ts beside this file. */
10
+ * claude-write-guard-hook.ts beside this file. */
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { shellQuote, strayWriteRefusal } from "../write-guard.js";
13
13
  /** claude's file-writing tools a team run has: claude-args.ts's `--tools` allows only these two,
@@ -16,12 +16,13 @@ const WRITE_TOOLS = ["Edit", "Write"];
16
16
  /** Seconds claude waits for the hook before running the tool anyway. */
17
17
  const HOOK_TIMEOUT_SECONDS = 30;
18
18
  /** The `--settings` value for a run in `cwd`: one PreToolUse hook on the file-writing tools that
19
- * runs the compiled write-guard-hook.js with this process's node, falling back to `node` on PATH
20
- * when that binary has moved (as the notes shim does), and the run's worktree as its argument. The
19
+ * runs the compiled claude-write-guard-hook.js with this process's node, falling back to
20
+ * `node` on PATH when that binary has moved (as the notes shim does), and the run's worktree
21
+ * as its argument. The
21
22
  * hook path resolves from this module, so a staged build (.tumwater/build/<sha>) runs its own
22
23
  * copy. */
23
24
  export function writeGuardSettings(cwd, nodeBin = process.execPath) {
24
- const hook = fileURLToPath(new URL("./write-guard-hook.js", import.meta.url));
25
+ const hook = fileURLToPath(new URL("./claude-write-guard-hook.js", import.meta.url));
25
26
  const command = `n=${shellQuote(nodeBin)}; [ -x "$n" ] || n=node; exec "$n" ${shellQuote(hook)} ${shellQuote(cwd)}`;
26
27
  return JSON.stringify({
27
28
  hooks: {
@@ -1,7 +1,7 @@
1
1
  /** The codex adapter for the agent seam (plans/codex-backend.md §4, §5, §6): it shapes one
2
2
  * run's argv/stdin/env, translates codex's `exec --json` stream, reads the authoritative
3
3
  * rollout after the process exits, and classifies the exit's stderr — everything
4
- * backend-specific. src/agent/run-agent.ts owns the spawn and records this run in the session
4
+ * backend-specific. src/agent/run-spawn.ts spawns the run and records this run in the session
5
5
  * ledger; codex mints its own session id (a thread id, or the resumed record's id) in the
6
6
  * stream's `thread.started`, which the runner's stream-mint pass appends to the ledger and
7
7
  * `invocation` returns so a later `exec resume` can reach it.
@@ -19,7 +19,7 @@
19
19
  import fs from "node:fs";
20
20
  import { loadConfigSafe } from "../../config/config.js";
21
21
  import { resolvedBackendConfig } from "../../config/config-views.js";
22
- import { backendBaseEnv, withNotesPath } from "../../process/run-marker.js";
22
+ import { backendRunEnv } from "../../process/run-marker.js";
23
23
  import { resolveBinForBackend } from "../../pi/pi-bin.js";
24
24
  import { teamRootOf, nearestTumwaterRoot } from "../../paths.js";
25
25
  import { warnEventBestEffort, TUMWATER_SOURCE } from "../../events/events.js";
@@ -120,7 +120,7 @@ export const codexBackend = {
120
120
  // The notes path rides beside the run marker, as the pi and claude adapters' envs do
121
121
  // (withNotesPath); from part 2/4 the run saves its note through the `tumwater-notes`
122
122
  // command on PATH, so codex configures no MCP server for notes.
123
- const base = withNotesPath(backendBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
123
+ const base = backendRunEnv(run);
124
124
  // A team home of its own (`CODEX_HOME`) keeps team sessions and trust entries out of the
125
125
  // operator's `~/.codex` (design §4, finding 10); unset leaves the operator's home.
126
126
  const home = codexHome(resolvedBackendConfig(run.config, "codex"));
@@ -162,7 +162,7 @@ export const codexBackend = {
162
162
  // invocation later reads was looked up under the run's own config. It never throws a run
163
163
  // away: a failed lookup records `off` (and warns once) and the run continues without the
164
164
  // guard's trust.
165
- const base = withNotesPath(backendBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
165
+ const base = backendRunEnv(run);
166
166
  const home = codexHome(resolvedBackendConfig(run.config, "codex"));
167
167
  const env = codexEnv(base, { home });
168
168
  const root = nearestTumwaterRoot(run.sessionDir);
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import fs from "node:fs";
14
14
  import path from "node:path";
15
+ import { walkFiles } from "../../files/files.js";
15
16
  import { isJsonObject, nonNegativeNumber, objectOrEmpty, parseJsonObject } from "../../files/json-object.js";
16
17
  import { windowTypeName } from "./codex-stream.js";
17
18
  function str(value) {
@@ -155,25 +156,13 @@ export function findRollout(home, id) {
155
156
  if (!fs.existsSync(sessionsRoot))
156
157
  return null;
157
158
  const suffix = `-${id}.jsonl`;
158
- const stack = [sessionsRoot];
159
- while (stack.length > 0) {
160
- const dir = stack.pop();
161
- let entries;
162
- try {
163
- entries = fs.readdirSync(dir, { withFileTypes: true });
164
- }
165
- catch {
166
- continue; // an unreadable dir is simply no match, never a failure
167
- }
168
- for (const entry of entries) {
169
- const full = path.join(dir, entry.name);
170
- if (entry.isDirectory()) {
171
- stack.push(full);
172
- }
173
- else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(suffix)) {
174
- return full;
175
- }
176
- }
177
- }
178
- return null;
159
+ let found = null;
160
+ // The walk-and-swallow recursion is files.ts's walkFiles (an unreadable dir is no match);
161
+ // the id match alone decides, so only the one id's rollout can win. The sessions tree is one
162
+ // directory per active day, so walking it whole to the first match stays small.
163
+ walkFiles(sessionsRoot, (file, entry) => {
164
+ if (found === null && entry.name.startsWith("rollout-") && entry.name.endsWith(suffix))
165
+ found = file;
166
+ });
167
+ return found;
179
168
  }