tumwater 0.1.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (482) hide show
  1. package/README.md +77 -28
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +23 -0
  4. package/dist/src/agent/agent-backend.js +9 -7
  5. package/dist/src/agent/backend-traits.js +77 -0
  6. package/dist/src/agent/claude/claude-account.js +81 -161
  7. package/dist/src/agent/claude/claude-args.js +19 -19
  8. package/dist/src/agent/claude/claude-backend.js +58 -48
  9. package/dist/src/agent/claude/claude-env.js +7 -3
  10. package/dist/src/agent/claude/claude-stream.js +11 -19
  11. package/dist/src/agent/claude/claude-write-guard.js +49 -0
  12. package/dist/src/agent/claude/write-guard-hook.js +13 -0
  13. package/dist/src/agent/codex/codex-account.js +119 -0
  14. package/dist/src/agent/codex/codex-args.js +181 -0
  15. package/dist/src/agent/codex/codex-backend.js +279 -0
  16. package/dist/src/agent/codex/codex-catalog.js +97 -0
  17. package/dist/src/agent/codex/codex-env.js +18 -0
  18. package/dist/src/agent/codex/codex-hook-trust.js +246 -0
  19. package/dist/src/agent/codex/codex-patch-paths.js +36 -0
  20. package/dist/src/agent/codex/codex-rollout.js +179 -0
  21. package/dist/src/agent/codex/codex-stream.js +402 -0
  22. package/dist/src/agent/codex/codex-write-guard.js +86 -0
  23. package/dist/src/agent/codex/write-guard-hook.js +12 -0
  24. package/dist/src/agent/image-input.js +112 -0
  25. package/dist/src/agent/notes-shim.js +40 -0
  26. package/dist/src/agent/opencode/opencode-args.js +60 -0
  27. package/dist/src/agent/opencode/opencode-backend.js +258 -0
  28. package/dist/src/agent/opencode/opencode-catalog.js +210 -0
  29. package/dist/src/agent/opencode/opencode-config.js +193 -0
  30. package/dist/src/agent/opencode/opencode-env.js +35 -0
  31. package/dist/src/agent/opencode/opencode-session-id.js +36 -0
  32. package/dist/src/agent/opencode/opencode-stream.js +278 -0
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +16 -33
  35. package/dist/src/agent/run-agent.js +475 -381
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +142 -16
  38. package/dist/src/agent/subscription-account.js +220 -0
  39. package/dist/src/agent/write-guard.js +148 -0
  40. package/dist/src/backlog/backlog-archive.js +487 -0
  41. package/dist/src/backlog/backlog-cli-view.js +280 -0
  42. package/dist/src/backlog/backlog-cli.js +384 -0
  43. package/dist/src/backlog/backlog-commit.js +4 -5
  44. package/dist/src/backlog/backlog-eligibility.js +354 -132
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +132 -22
  47. package/dist/src/backlog/backlog-parts.js +254 -0
  48. package/dist/src/backlog/backlog-release.js +117 -0
  49. package/dist/src/backlog/backlog-render.js +89 -11
  50. package/dist/src/backlog/backlog-structure.js +271 -125
  51. package/dist/src/backlog/backlog-write.js +121 -471
  52. package/dist/src/backlog/backlog.js +129 -17
  53. package/dist/src/backlog/question-answer.js +345 -0
  54. package/dist/src/baseline/main-baseline.js +157 -101
  55. package/dist/src/baseline/main-culprit.js +94 -0
  56. package/dist/src/baseline/main-guardrails.js +209 -0
  57. package/dist/src/baseline/main-red.js +164 -55
  58. package/dist/src/baseline/main-revert.js +133 -0
  59. package/dist/src/baseline/main-watch.js +184 -0
  60. package/dist/src/brief/brief-edit.js +41 -0
  61. package/dist/src/{brief.js → brief/brief.js} +38 -33
  62. package/dist/src/budget/budget.js +55 -28
  63. package/dist/src/budget/fallback-breaker.js +31 -31
  64. package/dist/src/build/build-check-detect.js +25 -16
  65. package/dist/src/build/build-check-events.js +87 -22
  66. package/dist/src/build/build-check-report.js +66 -27
  67. package/dist/src/build/build-check-scoped.js +64 -68
  68. package/dist/src/build/build-check.js +17 -15
  69. package/dist/src/build/build-info.js +3 -3
  70. package/dist/src/build/build-stage.js +31 -35
  71. package/dist/src/build/dep-install.js +21 -21
  72. package/dist/src/build/host-sleep.js +1 -1
  73. package/dist/src/change/change-data.js +131 -37
  74. package/dist/src/change/change-render.js +56 -14
  75. package/dist/src/cli/brief-command.js +9 -8
  76. package/dist/src/cli/cli-args.js +133 -28
  77. package/dist/src/cli/cli-command-args.js +82 -21
  78. package/dist/src/cli/cli-editor.js +15 -2
  79. package/dist/src/cli/cli-flag-specs.js +180 -50
  80. package/dist/src/cli/cli-init.js +51 -0
  81. package/dist/src/cli/cli-marker-commands.js +14 -11
  82. package/dist/src/cli/cli-query-commands.js +325 -224
  83. package/dist/src/cli/cli-repo-gate.js +12 -2
  84. package/dist/src/cli/cli-role-command.js +420 -0
  85. package/dist/src/cli/cli-start-summary.js +160 -0
  86. package/dist/src/cli/cli-start.js +453 -0
  87. package/dist/src/cli/completion.js +202 -17
  88. package/dist/src/cli/config-commands.js +51 -25
  89. package/dist/src/cli/follow-view.js +75 -0
  90. package/dist/src/cli/help.js +413 -157
  91. package/dist/src/cli/log-commands.js +234 -62
  92. package/dist/src/cli/notify-command.js +33 -18
  93. package/dist/src/cli/question-commands.js +215 -231
  94. package/dist/src/cli.js +154 -49
  95. package/dist/src/collections.js +84 -18
  96. package/dist/src/concurrency/check-permit.js +35 -19
  97. package/dist/src/concurrency/lock.js +22 -17
  98. package/dist/src/concurrency/provider-lease.js +105 -0
  99. package/dist/src/concurrency/semaphore.js +344 -46
  100. package/dist/src/config/config-aliases.js +105 -0
  101. package/dist/src/config/config-editable-keys.js +16 -4
  102. package/dist/src/config/config-fallbacks.js +416 -0
  103. package/dist/src/config/config-field-checks.js +58 -19
  104. package/dist/src/config/config-keys.js +182 -0
  105. package/dist/src/config/config-live.js +113 -22
  106. package/dist/src/config/config-request.js +137 -0
  107. package/dist/src/config/config-schema.js +58 -39
  108. package/dist/src/config/config-selector-checks.js +123 -0
  109. package/dist/src/config/config-validation-agents.js +80 -0
  110. package/dist/src/config/config-validation-model.js +116 -0
  111. package/dist/src/config/config-validation.js +201 -175
  112. package/dist/src/config/config-views.js +202 -346
  113. package/dist/src/config/config-write-core.js +135 -0
  114. package/dist/src/config/config-write.js +219 -391
  115. package/dist/src/config/config.js +102 -81
  116. package/dist/src/config/model-selector.js +44 -18
  117. package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
  118. package/dist/src/doctor/doctor-backlog-plans.js +263 -0
  119. package/dist/src/doctor/doctor-backlog.js +191 -161
  120. package/dist/src/doctor/doctor-check-names.js +81 -0
  121. package/dist/src/doctor/doctor-checks.js +229 -305
  122. package/dist/src/doctor/doctor-claude-checks.js +145 -0
  123. package/dist/src/doctor/doctor-codex-checks.js +279 -0
  124. package/dist/src/doctor/doctor-contention-checks.js +380 -0
  125. package/dist/src/doctor/doctor-holds.js +181 -104
  126. package/dist/src/doctor/doctor-install.js +31 -0
  127. package/dist/src/doctor/doctor-launch-services.js +2 -2
  128. package/dist/src/doctor/doctor-model-checks.js +496 -270
  129. package/dist/src/doctor/doctor-notify.js +5 -7
  130. package/dist/src/doctor/doctor-opencode-checks.js +162 -0
  131. package/dist/src/doctor/doctor-orphans.js +189 -69
  132. package/dist/src/doctor/doctor-project-checks.js +303 -0
  133. package/dist/src/doctor/doctor-render.js +36 -9
  134. package/dist/src/doctor/doctor.js +145 -50
  135. package/dist/src/doctor/image-probe.js +257 -0
  136. package/dist/src/doctor/outcome.js +3 -2
  137. package/dist/src/doctor/probe.js +60 -0
  138. package/dist/src/events/event-format.js +363 -155
  139. package/dist/src/events/event-labels.js +123 -0
  140. package/dist/src/events/event-legacy.js +164 -0
  141. package/dist/src/events/event-read.js +74 -38
  142. package/dist/src/events/event-tally.js +16 -0
  143. package/dist/src/events/event-window.js +83 -27
  144. package/dist/src/events/events.js +47 -26
  145. package/dist/src/events/notify.js +85 -35
  146. package/dist/src/failure/failure-cluster.js +23 -20
  147. package/dist/src/failure/failure-data.js +140 -92
  148. package/dist/src/failure/failure-render.js +97 -78
  149. package/dist/src/failure/failure-state-change.js +45 -33
  150. package/dist/src/failure/time-spend.js +166 -82
  151. package/dist/src/files/file-queue.js +16 -4
  152. package/dist/src/files/files.js +129 -41
  153. package/dist/src/files/json-files.js +3 -3
  154. package/dist/src/files/json-object.js +45 -9
  155. package/dist/src/files/stat-cache.js +2 -2
  156. package/dist/src/files/tail.js +2 -2
  157. package/dist/src/gates/bootstrap-gates.js +8 -8
  158. package/dist/src/gates/budget-gates.js +361 -52
  159. package/dist/src/gates/disk-gate.js +18 -18
  160. package/dist/src/gates/gate-polls.js +201 -103
  161. package/dist/src/gates/maintenance-quota.js +34 -37
  162. package/dist/src/gates/pause-gates.js +33 -15
  163. package/dist/src/gates/readiness.js +8 -3
  164. package/dist/src/gates/role-cap-gates.js +23 -28
  165. package/dist/src/gates/startup-gate.js +154 -48
  166. package/dist/src/gates/streak-gate.js +67 -41
  167. package/dist/src/git/commit-message.js +80 -48
  168. package/dist/src/git/git-diff.js +29 -23
  169. package/dist/src/git/git-run.js +87 -46
  170. package/dist/src/git/git.js +114 -92
  171. package/dist/src/git/slot-salvage.js +253 -0
  172. package/dist/src/git/slots-state.js +18 -9
  173. package/dist/src/git/worktree-pool.js +150 -49
  174. package/dist/src/git/worktree-use.js +14 -20
  175. package/dist/src/git/worktree.js +108 -51
  176. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  177. package/dist/src/gui/gui-args.js +88 -21
  178. package/dist/src/gui/gui-backlog-commands.js +297 -0
  179. package/dist/src/gui/gui-backlog-endpoints.js +155 -0
  180. package/dist/src/gui/gui-command.js +9 -14
  181. package/dist/src/gui/gui-config-commands.js +462 -0
  182. package/dist/src/gui/gui-endpoint-commands.js +17 -716
  183. package/dist/src/gui/gui-endpoint-common.js +49 -0
  184. package/dist/src/gui/gui-endpoints.js +13 -331
  185. package/dist/src/gui/gui-history-endpoints.js +301 -0
  186. package/dist/src/gui/gui-operator-commands.js +240 -0
  187. package/dist/src/gui/gui-prompt-commands.js +231 -0
  188. package/dist/src/gui/gui-report-endpoints.js +90 -0
  189. package/dist/src/gui/gui-server.js +33 -9
  190. package/dist/src/gui/gui-team-commands.js +164 -0
  191. package/dist/src/gui/http-body.js +17 -1
  192. package/dist/src/history/history-data.js +166 -203
  193. package/dist/src/history/history-queued.js +147 -0
  194. package/dist/src/history/history-render.js +96 -0
  195. package/dist/src/history/history.js +206 -103
  196. package/dist/src/inbox/inbox-attachments.js +94 -39
  197. package/dist/src/inbox/inbox-cancel.js +16 -15
  198. package/dist/src/inbox/inbox-edit.js +10 -10
  199. package/dist/src/inbox/inbox-submit.js +54 -14
  200. package/dist/src/inbox/inbox-top.js +99 -0
  201. package/dist/src/inbox/inbox.js +49 -44
  202. package/dist/src/inbox/pending-prompt.js +27 -27
  203. package/dist/src/inbox/prompt-commands.js +154 -100
  204. package/dist/src/inbox/prompt-not-before.js +2 -2
  205. package/dist/src/init/init-templates.js +15 -14
  206. package/dist/src/init/init.js +35 -29
  207. package/dist/src/merge/backlog-conflicts.js +335 -0
  208. package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +100 -93
  209. package/dist/src/merge/merge-cancel.js +53 -0
  210. package/dist/src/merge/merge-check-failures.js +209 -0
  211. package/dist/src/{landing/landing-core.js → merge/merge-core.js} +183 -110
  212. package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +6 -5
  213. package/dist/src/merge/merge-drain.js +351 -0
  214. package/dist/src/merge/merge-git.js +403 -0
  215. package/dist/src/merge/merge-pipeline.js +111 -0
  216. package/dist/src/merge/merge-post-review.js +90 -0
  217. package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
  218. package/dist/src/merge/merge-queue-render.js +113 -0
  219. package/dist/src/merge/merge-queue.js +224 -0
  220. package/dist/src/merge/merge-requeue.js +102 -0
  221. package/dist/src/merge/merge-slot.js +385 -0
  222. package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +97 -71
  223. package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +151 -82
  224. package/dist/src/merge/merge-top.js +69 -0
  225. package/dist/src/merge/merge-verify.js +214 -0
  226. package/dist/src/notes-cli.js +112 -0
  227. package/dist/src/operator/operator-commands.js +268 -89
  228. package/dist/src/operator/operator-intent.js +130 -82
  229. package/dist/src/operator/operator-requests.js +82 -87
  230. package/dist/src/operator/retire.js +49 -31
  231. package/dist/src/operator/revert.js +179 -0
  232. package/dist/src/orchestrator/orchestrator-launch.js +116 -48
  233. package/dist/src/orchestrator/orchestrator-poll.js +320 -0
  234. package/dist/src/orchestrator/orchestrator-scheduling.js +78 -348
  235. package/dist/src/orchestrator/orchestrator.js +173 -361
  236. package/dist/src/orchestrator/retention.js +66 -15
  237. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  238. package/dist/src/orchestrator/scheduling-scope.js +201 -0
  239. package/dist/src/orchestrator/scheduling-skip.js +185 -0
  240. package/dist/src/orchestrator/startup-migration-steps.js +52 -0
  241. package/dist/src/orchestrator/startup-migrations.js +44 -0
  242. package/dist/src/paths.js +228 -75
  243. package/dist/src/pi/command-shape.js +12 -1
  244. package/dist/src/pi/pi-args.js +30 -5
  245. package/dist/src/pi/pi-backend.js +19 -27
  246. package/dist/src/pi/pi-bin.js +64 -67
  247. package/dist/src/pi/pi-event-line.js +17 -1
  248. package/dist/src/pi/pi-failure.js +87 -0
  249. package/dist/src/pi/pi-listed-models.js +53 -0
  250. package/dist/src/pi/pi-models.js +163 -42
  251. package/dist/src/pi/pi-run-result.js +1 -1
  252. package/dist/src/pi/pi-settings.js +86 -0
  253. package/dist/src/pi/pi-stream.js +137 -113
  254. package/dist/src/pi/pi-watchdogs.js +20 -20
  255. package/dist/src/pi-extension/bounded-output.js +25 -8
  256. package/dist/src/pi-extension/context-budget.js +5 -3
  257. package/dist/src/pi-extension/context-shake.js +54 -21
  258. package/dist/src/pi-extension/context-window.js +36 -0
  259. package/dist/src/pi-extension/full-output.js +5 -12
  260. package/dist/src/pi-extension/strip-thinking.js +79 -0
  261. package/dist/src/pi-extension/trim-system-prompt.js +37 -0
  262. package/dist/src/pi-extension/write-guard.js +37 -0
  263. package/dist/src/process/launch-services.js +11 -11
  264. package/dist/src/process/node-bin.js +16 -0
  265. package/dist/src/process/process-group.js +8 -18
  266. package/dist/src/process/process-table.js +1 -1
  267. package/dist/src/process/process.js +48 -9
  268. package/dist/src/process/run-marker.js +42 -7
  269. package/dist/src/process/supervisor.js +47 -35
  270. package/dist/src/prompt/dialect.js +46 -11
  271. package/dist/src/prompt/gate-notes.js +203 -0
  272. package/dist/src/prompt/gate-prompts.js +201 -188
  273. package/dist/src/prompt/notes-backlog.js +181 -0
  274. package/dist/src/prompt/principles.js +1 -1
  275. package/dist/src/prompt/prompt-followup.js +43 -29
  276. package/dist/src/prompt/prompt.js +255 -172
  277. package/dist/src/redeploy/redeploy-policy.js +29 -24
  278. package/dist/src/redeploy/redeploy-probes.js +1 -1
  279. package/dist/src/redeploy/redeploy.js +7 -6
  280. package/dist/src/redeploy/redeployer.js +78 -70
  281. package/dist/src/redeploy/self-reload.js +3 -2
  282. package/dist/src/report/report-data.js +374 -285
  283. package/dist/src/report/report-fold.js +430 -0
  284. package/dist/src/report/report-main-red.js +55 -0
  285. package/dist/src/report/report-render.js +321 -61
  286. package/dist/src/report/report-types.js +1 -0
  287. package/dist/src/report/report.js +26 -9
  288. package/dist/src/request-timeouts.js +2 -2
  289. package/dist/src/review/known-flakes.js +133 -14
  290. package/dist/src/review/post-merge-review.js +106 -0
  291. package/dist/src/review/protected-edits.js +7 -7
  292. package/dist/src/review/review-followup.js +21 -9
  293. package/dist/src/review/review-precheck.js +77 -33
  294. package/dist/src/review/review-run.js +395 -0
  295. package/dist/src/review/review-verdict.js +16 -2
  296. package/dist/src/review/review.js +172 -356
  297. package/dist/src/review/secret-leaks.js +147 -0
  298. package/dist/src/review/suite-rerun.js +3 -3
  299. package/dist/src/roles/legacy-role-ids.js +21 -0
  300. package/dist/src/roles/legacy-role-migration.js +135 -0
  301. package/dist/src/roles/role-catalog.js +172 -137
  302. package/dist/src/roles/role-guidance.js +112 -44
  303. package/dist/src/roles/role-labels.js +9 -0
  304. package/dist/src/roles/role-render.js +221 -28
  305. package/dist/src/roles/role-view.js +244 -33
  306. package/dist/src/roles/roles.js +37 -35
  307. package/dist/src/roles/runner-ids.js +97 -0
  308. package/dist/src/run/acceptance-greps.js +119 -0
  309. package/dist/src/run/feedback-filings.js +51 -0
  310. package/dist/src/{tick → run}/qa-coverage.js +10 -27
  311. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +166 -136
  312. package/dist/src/run/run-detail-data.js +179 -0
  313. package/dist/src/run/run-detail-render.js +98 -0
  314. package/dist/src/run/run-detail.js +168 -0
  315. package/dist/src/run/run-finalize.js +154 -0
  316. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -6
  317. package/dist/src/run/run-prompt.js +304 -0
  318. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +80 -35
  319. package/dist/src/run/run-stage.js +295 -0
  320. package/dist/src/run/run-timing.js +198 -0
  321. package/dist/src/run/run-usage.js +180 -0
  322. package/dist/src/run/run-verdict.js +301 -0
  323. package/dist/src/run/selfcheck-digest.js +40 -0
  324. package/dist/src/run/stage-backlog-checks.js +249 -0
  325. package/dist/src/run/stage-check.js +172 -0
  326. package/dist/src/run/stage-path-checks.js +730 -0
  327. package/dist/src/runner/leftover.js +185 -0
  328. package/dist/src/{loop → runner}/model-fallback.js +13 -12
  329. package/dist/src/runner/revision.js +202 -0
  330. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +10 -10
  331. package/dist/src/runner/runner-outcome.js +107 -0
  332. package/dist/src/runner/runner-pi.js +433 -0
  333. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +34 -28
  334. package/dist/src/runner/runner-run.js +625 -0
  335. package/dist/src/runner/runner-state-load.js +268 -0
  336. package/dist/src/runner/runner-state.js +102 -0
  337. package/dist/src/runner/runner.js +350 -0
  338. package/dist/src/scheduling/backoff.js +64 -54
  339. package/dist/src/scheduling/claims.js +46 -26
  340. package/dist/src/scheduling/once-round.js +30 -30
  341. package/dist/src/scheduling/quiet-hours.js +26 -34
  342. package/dist/src/scheduling/scheduling.js +87 -74
  343. package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
  344. package/dist/src/status/status-budget.js +123 -0
  345. package/dist/src/status/status-data.js +106 -191
  346. package/dist/src/status/status-inbox.js +47 -0
  347. package/dist/src/status/status-install.js +26 -0
  348. package/dist/src/status/status-polls.js +70 -59
  349. package/dist/src/status/status-roles.js +131 -0
  350. package/dist/src/{fleet → team}/error-storm.js +16 -16
  351. package/dist/src/{fleet → team}/failure-spread.js +10 -10
  352. package/dist/src/{fleet → team}/orchestrator-info.js +97 -25
  353. package/dist/src/{fleet → team}/reclaim.js +9 -9
  354. package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +60 -26
  355. package/dist/src/team/team-polls.js +299 -0
  356. package/dist/src/{fleet/fleet-state.js → team/team-state.js} +47 -40
  357. package/dist/src/text/datetime.js +102 -16
  358. package/dist/src/text/format.js +49 -3
  359. package/dist/src/text/markdown.js +8 -5
  360. package/dist/src/text/phrases.js +89 -54
  361. package/dist/src/text/suggest.js +3 -2
  362. package/dist/src/text/text.js +68 -14
  363. package/dist/src/ui/badges.js +165 -93
  364. package/dist/src/ui/gui/gui-client-backlog.js +292 -0
  365. package/dist/src/ui/gui/gui-client-boot.js +221 -42
  366. package/dist/src/ui/gui/gui-client-composer.js +105 -27
  367. package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
  368. package/dist/src/ui/gui/gui-client-drawer.js +226 -130
  369. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  370. package/dist/src/ui/gui/gui-client-history.js +492 -68
  371. package/dist/src/ui/gui/gui-client-labels.js +23 -0
  372. package/dist/src/ui/gui/gui-client-markdown.js +1 -1
  373. package/dist/src/ui/gui/gui-client-model.js +84 -76
  374. package/dist/src/ui/gui/gui-client-operator.js +112 -78
  375. package/dist/src/ui/gui/gui-client-pending.js +248 -41
  376. package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
  377. package/dist/src/ui/gui/gui-client-report.js +122 -179
  378. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +118 -76
  379. package/dist/src/ui/gui/gui-client-settings.js +180 -20
  380. package/dist/src/ui/gui/gui-client-team.js +252 -0
  381. package/dist/src/ui/gui/gui-client.js +48 -40
  382. package/dist/src/ui/gui/gui-icons.js +0 -2
  383. package/dist/src/ui/gui/gui-page.js +25 -24
  384. package/dist/src/ui/gui/gui-styles.js +46 -38
  385. package/dist/src/ui/progress-data.js +11 -11
  386. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +40 -40
  387. package/dist/src/ui/status-model.js +276 -194
  388. package/dist/src/ui/status-payload.js +227 -62
  389. package/dist/src/ui/status-render.js +196 -108
  390. package/dist/src/ui/team-alerts.js +532 -0
  391. package/dist/src/ui/tone.js +11 -11
  392. package/dist/src/ui/transcript-run.js +58 -0
  393. package/dist/src/ui/transcript-tail.js +28 -19
  394. package/dist/src/ui/transcript.js +85 -38
  395. package/dist/src/ui/tui/tui-backlog.js +132 -21
  396. package/dist/src/ui/tui/tui-editor-answer.js +93 -0
  397. package/dist/src/ui/tui/tui-editor-attach.js +105 -0
  398. package/dist/src/ui/tui/tui-editor-backends.js +132 -0
  399. package/dist/src/ui/tui/tui-editor-brief.js +105 -0
  400. package/dist/src/ui/tui/tui-editor-budget.js +159 -0
  401. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  402. package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
  403. package/dist/src/ui/tui/tui-editor-config.js +86 -0
  404. package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
  405. package/dist/src/ui/tui/tui-editor-entry.js +219 -0
  406. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  407. package/dist/src/ui/tui/tui-editor-instances.js +37 -0
  408. package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
  409. package/dist/src/ui/tui/tui-editor-interval.js +124 -0
  410. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  411. package/dist/src/ui/tui/tui-editor-notes.js +112 -0
  412. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  413. package/dist/src/ui/tui/tui-editor-pause.js +255 -0
  414. package/dist/src/ui/tui/tui-editor-prompt.js +90 -0
  415. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  416. package/dist/src/ui/tui/tui-editor-queued.js +94 -0
  417. package/dist/src/ui/tui/tui-editor-quiet.js +170 -0
  418. package/dist/src/ui/tui/tui-editor-retire.js +81 -0
  419. package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
  420. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  421. package/dist/src/ui/tui/tui-editor-search.js +85 -0
  422. package/dist/src/ui/tui/tui-editor-session.js +123 -0
  423. package/dist/src/ui/tui/tui-editor-slots.js +23 -0
  424. package/dist/src/ui/tui/tui-editors.js +185 -0
  425. package/dist/src/ui/tui/tui-frame.js +107 -58
  426. package/dist/src/ui/tui/tui-history.js +16 -12
  427. package/dist/src/ui/tui/tui-input.js +56 -60
  428. package/dist/src/ui/tui/tui-keys.js +106 -1825
  429. package/dist/src/ui/tui/tui-pane-activity.js +102 -0
  430. package/dist/src/ui/tui/tui-pane-backlog.js +269 -0
  431. package/dist/src/ui/tui/tui-pane-brief.js +29 -0
  432. package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
  433. package/dist/src/ui/tui/tui-pane-health.js +174 -0
  434. package/dist/src/ui/tui/tui-pane-history.js +636 -0
  435. package/dist/src/ui/tui/tui-pane-pending.js +369 -0
  436. package/dist/src/ui/tui/tui-pane-queued.js +159 -0
  437. package/dist/src/ui/tui/tui-pane-report.js +154 -0
  438. package/dist/src/ui/tui/tui-pane-session.js +120 -0
  439. package/dist/src/ui/tui/tui-pane-transcript.js +224 -0
  440. package/dist/src/ui/tui/tui-pane.js +125 -44
  441. package/dist/src/ui/tui/tui-panes.js +364 -0
  442. package/dist/src/ui/tui/tui.js +52 -34
  443. package/dist/src/verdict/fix-claim.js +135 -62
  444. package/dist/src/verdict/no-change.js +19 -0
  445. package/dist/src/verdict/refusal.js +80 -24
  446. package/dist/src/verdict/reply-contract.js +51 -28
  447. package/dist/src/version.js +2 -2
  448. package/package.json +5 -2
  449. package/dist/src/agent/mcp/role-notes-server.js +0 -160
  450. package/dist/src/brief-edit.js +0 -42
  451. package/dist/src/cli/cli-run.js +0 -469
  452. package/dist/src/fleet/fleet-polls.js +0 -207
  453. package/dist/src/landing/backlog-conflicts.js +0 -116
  454. package/dist/src/landing/landing-check-failures.js +0 -141
  455. package/dist/src/landing/landing-drain.js +0 -204
  456. package/dist/src/landing/landing-git.js +0 -190
  457. package/dist/src/landing/landing-pipeline.js +0 -96
  458. package/dist/src/landing/landing-queue-render.js +0 -56
  459. package/dist/src/landing/landing-queue.js +0 -149
  460. package/dist/src/landing/landing-slot.js +0 -256
  461. package/dist/src/landing/landing-vetting.js +0 -196
  462. package/dist/src/loop/leftover.js +0 -129
  463. package/dist/src/loop/loop-pi.js +0 -370
  464. package/dist/src/loop/loop-state.js +0 -204
  465. package/dist/src/loop/loop-tick.js +0 -545
  466. package/dist/src/loop/loop.js +0 -315
  467. package/dist/src/loop/revision.js +0 -61
  468. package/dist/src/pi-extension/role-notes.js +0 -40
  469. package/dist/src/roles/loop-ids.js +0 -60
  470. package/dist/src/tick/stage-check.js +0 -302
  471. package/dist/src/tick/telemetry-digest.js +0 -24
  472. package/dist/src/tick/tick-detail-data.js +0 -107
  473. package/dist/src/tick/tick-detail.js +0 -150
  474. package/dist/src/tick/tick-finalize.js +0 -135
  475. package/dist/src/tick/tick-prompt.js +0 -201
  476. package/dist/src/tick/tick-stage.js +0 -223
  477. package/dist/src/tick/tick-timing.js +0 -195
  478. package/dist/src/tick/tick-usage.js +0 -142
  479. package/dist/src/tick/tick-verdict.js +0 -201
  480. package/dist/src/ui/fleet-alerts.js +0 -174
  481. package/dist/src/ui/gui/gui-client-fleet.js +0 -576
  482. package/dist/src/ui/gui/gui-client-sound.js +0 -52
@@ -0,0 +1,181 @@
1
+ /** The Codex run's reserved flags and config keys plus the effort mapping — the pure,
2
+ * build-agnostic half of the codex invocation (plans/codex-backend.md "Config and routing" §2
3
+ * and "The invocation" §3). Part 4/9 owns these constants so config validation and the traits
4
+ * layer can screen a `backends.codex.args` passthrough before the adapter registered; part 5/9
5
+ * adds the `codexArgs` argv builder beside them, and 6/9 the stream translator. Pure — imports
6
+ * no runtime registry, config or subprocess — so config-validation.ts can import it without
7
+ * dragging the adapter in.
8
+ *
9
+ * Everything here is proved against Codex 0.163.0: every flag exists on that build, and every
10
+ * `-c` key passes the `--strict-config` device (design finding 20). */
11
+ /** Flags the harness invocation sets itself (§3): the output and model flags, the sandbox and
12
+ * approval switches, the isolation switches, and the where-and-what-it-runs switches. A
13
+ * passthrough arg equal to one of these is appended AFTER the harness's own copy and Codex's
14
+ * parser is last-wins, so it would silently override the model, the isolation or the run
15
+ * location the harness resolved. Each is rejected with the offending arg named. This is the
16
+ * design's reserved set (§2 "Reserved passthrough flags"); config validation
17
+ * (config-validation-agents.ts) mirrors it for `backends.codex.args`, so a value that passes
18
+ * validation is never accepted there and then thrown in the builder.
19
+ *
20
+ * Operators keep `-c`, `--enable` and `--disable` for their own settings — those keys are
21
+ * screened separately (HARNESS_CODEX_CONFIG_KEYS). */
22
+ import { appendPassthroughArgs } from "../agent-args.js";
23
+ export const HARNESS_CODEX_FLAGS = new Set([
24
+ // Output and model: tumwater forces JSON output, the resolved model, and a normal color for
25
+ // the transcript log.
26
+ "--json",
27
+ "-m",
28
+ "--model",
29
+ "-o",
30
+ "--output-last-message",
31
+ "--output-schema",
32
+ "--color",
33
+ // Sandbox and approvals: tumwater runs every job with bypass approval and no sandbox
34
+ // (decision 2), so an operator cannot re-arm an approval prompt or a sandbox mid-run.
35
+ "-s",
36
+ "--sandbox",
37
+ "--dangerously-bypass-approvals-and-sandbox",
38
+ "--approve-for-me",
39
+ "--dangerously-bypass-hook-trust",
40
+ // Isolation: tumwater ignores the user's config, rules and persistent state so one team run
41
+ // cannot read a personal hook, skill or trust entry.
42
+ "--ignore-user-config",
43
+ "--ignore-rules",
44
+ "--ephemeral",
45
+ // Where and what it runs: tumwater pins the cwd and the worktree and refuses `--oss` /
46
+ // `--local-provider` so a run can never target something other than the resolved backend.
47
+ "-C",
48
+ "--cd",
49
+ "--worktree",
50
+ "--add-dir",
51
+ "--skip-git-repo-check",
52
+ "-p",
53
+ "--profile",
54
+ "--oss",
55
+ "--local-provider",
56
+ ]);
57
+ /** The keys tumwater sets on every run via `-c <key>=…` (§3), so an operator's `-c` passthrough
58
+ * whose key is one of these would silently override the model, the effort, the sandbox mode, or
59
+ * the telemetry/state settings the harness already passed. A `-c` or `--config` passthrough whose
60
+ * key names one of these is refused; any other key is the operator's own and passes. */
61
+ export const HARNESS_CODEX_CONFIG_KEYS = new Set([
62
+ "model",
63
+ "model_reasoning_effort",
64
+ "sandbox_mode",
65
+ "project_doc_fallback_filenames",
66
+ "web_search",
67
+ "agents.enabled",
68
+ "check_for_update_on_startup",
69
+ "analytics.enabled",
70
+ "feedback.enabled",
71
+ "history.persistence",
72
+ ]);
73
+ /** Config-key prefixes an operator's `-c`/`--config` passthrough may not set: the write guard's
74
+ * own `hooks.*` keys (part 3/9). Codex is last-wins, so a `-c hooks.PreToolUse=…` or
75
+ * `-c hooks.state=…` passthrough would otherwise replace the guard or its trust after the
76
+ * harness's. Written beside HARNESS_CODEX_CONFIG_KEYS so both it and config validation screen
77
+ * against one predicate. */
78
+ const HARNESS_CODEX_CONFIG_KEY_PREFIXES = ["hooks"];
79
+ /** Whether `key` is a config key tumwater sets or reserves, so an operator's `-c`/`--config`
80
+ * passthrough naming it would silently override the harness. True for every
81
+ * HARNESS_CODEX_CONFIG_KEYS key and for the `hooks` key or any `hooks.<…>` subkey, which the
82
+ * write guard owns. */
83
+ export function isHarnessCodexConfigKey(key) {
84
+ if (HARNESS_CODEX_CONFIG_KEYS.has(key))
85
+ return true;
86
+ return HARNESS_CODEX_CONFIG_KEY_PREFIXES.some((p) => key === p || key.startsWith(`${p}.`));
87
+ }
88
+ /** Thinking → `model_reasoning_effort`. `off` and `minimal` clamp to `low` (Codex has no lower
89
+ * setting; doctor notes the clamp), `low` through `max` map 1:1. `ultra` is not a value Codex
90
+ * can receive — tumwater never sends it (it delegates to sub-agents). This is claude's
91
+ * EFFORT_BY_THINKING rule moved to codex, where the level the model's catalog lists also gates
92
+ * the result (part 5/9's `codexEffort`). */
93
+ export const EFFORT_BY_THINKING = {
94
+ off: "low",
95
+ minimal: "low",
96
+ low: "low",
97
+ medium: "medium",
98
+ high: "high",
99
+ xhigh: "xhigh",
100
+ max: "max",
101
+ };
102
+ /** The effort values tumwater will ever send as `model_reasoning_effort`: the set
103
+ * EFFORT_BY_THINKING can produce. `ultra` (which some models add for sub-agent delegation) is
104
+ * deliberately not here —
105
+ * tumwater never sends it, so `codexArgs` rejects it rather than let a whole agent hierarchy
106
+ * spawn on a model that lists it. */
107
+ const KNOWN_EFFORTS = new Set(Object.values(EFFORT_BY_THINKING));
108
+ /** Map a thinking level to the Codex `model_reasoning_effort` it means, gated by the model's own
109
+ * catalog. `levels` is the model's `supported_reasoning_levels` (design finding 14), which a
110
+ * stream-minted catalog (part 8/9) supplies; until then the caller passes `undefined`, and the
111
+ * result is returned unclamped. An unknown thinking level returns `undefined` (no effort flag) —
112
+ * the level simply has no Codex effort, unlike a thinking level that maps but the model does not
113
+ * support, which is also `undefined`. */
114
+ export function codexEffort(thinking, levels) {
115
+ if (!Object.hasOwn(EFFORT_BY_THINKING, thinking))
116
+ return undefined;
117
+ const result = EFFORT_BY_THINKING[thinking];
118
+ if (levels !== undefined && !levels.includes(result))
119
+ return undefined;
120
+ return result;
121
+ }
122
+ /** Build one run's codex argv and stdin. The prompt is never an argv element (codex reads `-`
123
+ * input from stdin). Throws on an unknown effort, or a passthrough arg that repeats a harness
124
+ * flag, since each would otherwise silently change what the harness asked for. */
125
+ export function codexArgs(opts) {
126
+ if (opts.effort !== undefined && !KNOWN_EFFORTS.has(opts.effort)) {
127
+ throw new Error(`codexArgs: unknown effort ${JSON.stringify(opts.effort)} (known: ${[...KNOWN_EFFORTS].join(", ")})`);
128
+ }
129
+ // Isolation flags, then the `--disable` list (design finding 7), then the `-c` keys (finding
130
+ // 20), then the model and effort, then the operator's passthrough, then the closing `-`.
131
+ const head = [
132
+ "--json",
133
+ "--dangerously-bypass-approvals-and-sandbox",
134
+ "--ignore-user-config",
135
+ "--ignore-rules",
136
+ "--disable",
137
+ "plugins",
138
+ "--disable",
139
+ "apps",
140
+ "--disable",
141
+ "goals",
142
+ "--disable",
143
+ "image_generation",
144
+ "--disable",
145
+ "browser_use",
146
+ "--disable",
147
+ "computer_use",
148
+ "--disable",
149
+ "tool_suggest",
150
+ "--disable",
151
+ "skill_search",
152
+ "-c",
153
+ "agents.enabled=false",
154
+ "-c",
155
+ 'web_search="disabled"',
156
+ "-c",
157
+ "check_for_update_on_startup=false",
158
+ "-c",
159
+ "analytics.enabled=false",
160
+ "-c",
161
+ "feedback.enabled=false",
162
+ "-c",
163
+ 'history.persistence="none"',
164
+ "-c",
165
+ 'project_doc_fallback_filenames=["CLAUDE.md"]',
166
+ // The write guard's hook and trust ride the same `-c` layer, after the harness's own
167
+ // keys and before `-m` (plans/codex-write-guard.md "Runs (part 6)"); an untrusted hook
168
+ // would be skipped silently, so a backend with no trust passes nothing here.
169
+ ...(opts.guardArgs ?? []),
170
+ "-m",
171
+ opts.model,
172
+ ];
173
+ if (opts.effort !== undefined)
174
+ head.push("-c", `model_reasoning_effort=${opts.effort}`);
175
+ const args = opts.resumeId === undefined
176
+ ? ["exec", ...head]
177
+ : ["exec", "resume", opts.resumeId, ...head];
178
+ appendPassthroughArgs(args, opts.args, HARNESS_CODEX_FLAGS, "codexArgs");
179
+ args.push("-");
180
+ return { args, stdin: opts.prompt };
181
+ }
@@ -0,0 +1,279 @@
1
+ /** The codex adapter for the agent seam (plans/codex-backend.md §4, §5, §6): it shapes one
2
+ * run's argv/stdin/env, translates codex's `exec --json` stream, reads the authoritative
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
5
+ * ledger; codex mints its own session id (a thread id, or the resumed record's id) in the
6
+ * stream's `thread.started`, which the runner's stream-mint pass appends to the ledger and
7
+ * `invocation` returns so a later `exec resume` can reach it.
8
+ *
9
+ * Sessions resume by id, out of the team's `CODEX_HOME`. A continue takes the ledger's newest
10
+ * record of this run's kind and resumes only when it is codex's, has this cwd, carries a UUID
11
+ * id, and `findRollout` finds its rollout under the run's home — anything else mints a fresh
12
+ * thread and the existing `handoff` path takes over (design §4). Together with the `resumable`
13
+ * hook, that is every resume point's one rule.
14
+ *
15
+ * Registration lives in src/agent/run-agent.ts beside the pi, claude and opencode adapters; a
16
+ * test selects codex by setting `config.agent = "codex"` on a run. A run carrying a note gets
17
+ * the notes path in its environment (the `tumwater-notes` command on PATH reads it), as the
18
+ * pi and claude adapters do. */
19
+ import fs from "node:fs";
20
+ import { loadConfigSafe } from "../../config/config.js";
21
+ import { resolvedBackendConfig } from "../../config/config-views.js";
22
+ import { backendBaseEnv, withNotesPath } from "../../process/run-marker.js";
23
+ import { resolveBinForBackend } from "../../pi/pi-bin.js";
24
+ import { teamRootOf, nearestTumwaterRoot } from "../../paths.js";
25
+ import { warnEventBestEffort, TUMWATER_SOURCE } from "../../events/events.js";
26
+ import { errorMessage } from "../../text/text.js";
27
+ import { codexHome, readCodexCatalog } from "./codex-catalog.js";
28
+ import { resumableSession } from "../session-ledger.js";
29
+ import { codexArgs, codexEffort } from "./codex-args.js";
30
+ import { codexEnv } from "./codex-env.js";
31
+ import { codexHookTrust } from "./codex-hook-trust.js";
32
+ import { codexHookArg, codexHookTrustArg, codexWriteGuardDefinition } from "./codex-write-guard.js";
33
+ import { findRollout, rolloutLines } from "./codex-rollout.js";
34
+ import { codexTranslator } from "./codex-stream.js";
35
+ /** A codex thread id (a UUID format, e.g. `01a15c9d-6762-7931-814e-a3e6e64c1b51`). The
36
+ * stream mints it in `thread.started`; the pre-spawn ledger record carries the session name
37
+ * instead, so this regex is what tells a resume that a record holds a real, stream-minted id
38
+ * worth resuming (design §4). */
39
+ const THREAD_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
40
+ /** The oldest codex CLI the team targets, the sibling of `OPENCODE_MIN_VERSION`
41
+ * (src/agent/opencode/opencode-backend.ts) and `CLAUDE_MIN_VERSION`
42
+ * (src/agent/claude/claude-backend.ts): doctor's `codex version` row fails an installed CLI
43
+ * below this floor (plans/codex-backend.md "Hand steps", H1). */
44
+ export const CODEX_MIN_VERSION = "0.163.0";
45
+ /** The wall-clock bound `prepare` gives each trust lookup, mirroring lookupCodexHookTrust's
46
+ * own 15 s default (plans/codex-write-guard.md "Trust (parts 4–5)"). Settable so a test can
47
+ * shorten the bound (to 500 ms) without waiting out the real 15 s; a hand run keeps the
48
+ * default. */
49
+ let codexTrustLookupTimeoutMs = 15_000;
50
+ /** The test seam for the trust lookup bound: a test sets it to 500 ms and returns it to the
51
+ * 15 s default in a finally, so a hanging app-server case stays fast without a config knob. */
52
+ export function setCodexTrustLookupTimeoutMs(ms) {
53
+ codexTrustLookupTimeoutMs = ms;
54
+ }
55
+ /** The write-guard trust `prepare` has resolved, keyed by home and hook definition, so one
56
+ * process looks each up at most once and a run's invocation never spawns `app-server` again. */
57
+ const writeGuardTrust = new Map();
58
+ /** One in-flight resolution per (home, definition) key, so concurrent runs share a single
59
+ * `app-server` instead of starting one each (plans/codex-write-guard.md "Trust (parts 4–5)"). */
60
+ const writeGuardInFlight = new Map();
61
+ /** The (home, definition) keys whose `off` warning has already been logged, so a failed lookup
62
+ * warns once per process instead of once per run. */
63
+ const writeGuardWarned = new Set();
64
+ /** Warn once per process per (home, definition) that the write guard is off. The warning rides
65
+ * the best-effort event feed on the run's root, skipped when there is no root (a hand run's
66
+ * prepared look-up must not mint a `.tumwater`). A `trust` result logs nothing. */
67
+ function warnGuardOffOnce(key, root, state) {
68
+ if (state.kind !== "off")
69
+ return;
70
+ if (writeGuardWarned.has(key))
71
+ return;
72
+ writeGuardWarned.add(key);
73
+ if (root !== undefined) {
74
+ warnEventBestEffort(root, TUMWATER_SOURCE, `codex write guard off: ${state.reason}`);
75
+ }
76
+ }
77
+ /** The (home, definition) cache key shared by the single-flight and the resolved-trust map. */
78
+ function writeGuardKey(home, definition) {
79
+ return `${home ?? ""}|${JSON.stringify(definition)}`;
80
+ }
81
+ /** The session this run uses: for a continue, the ledger's newest record of this kind when it
82
+ * is codex's, has this cwd, carries a UUID thread id, and `findRollout` finds its rollout
83
+ * under the run's home; otherwise the run is fresh (no pre-minted id — the stream names it).
84
+ * Returns the id and whether this is a resume, so `invocation` can name it and build `exec
85
+ * resume`. */
86
+ function resolveCodexSession(run, home) {
87
+ if (run.continueSession && home !== undefined) {
88
+ const record = resumableSession(run.sessionDir, {
89
+ kind: run.kind,
90
+ agent: "codex",
91
+ cwd: run.cwd,
92
+ });
93
+ if (record !== null && THREAD_ID_RE.test(record.id) && findRollout(home, record.id) !== null) {
94
+ return { sessionId: record.id, resumed: true };
95
+ }
96
+ }
97
+ return { resumed: false };
98
+ }
99
+ /** Classify codex's stderr: the seam's stderr half for a CLI that died before emitting a
100
+ * `turn.failed` (the stream's own failures are classified by the translator). A run the
101
+ * harness killed has its own cause and is never classified here (design finding 12). The
102
+ * codex CLI surfaces login/model/usage failures as stream JSON, so a bare nonzero exit with
103
+ * no stream carries no signal this part has a verdict for — it stays unclassified and the
104
+ * parser's own event-derived flags stand. */
105
+ function classifyCodexExit(info) {
106
+ if (info.killed)
107
+ return {};
108
+ return {};
109
+ }
110
+ /** The agent-backend seam's codex adapter. */
111
+ export const codexBackend = {
112
+ id: "codex",
113
+ label: "Codex",
114
+ defaultBin: "codex",
115
+ binEnv: "TUMWATER_CODEX_BIN",
116
+ resolveBin(config) {
117
+ return resolveBinForBackend(config, this);
118
+ },
119
+ invocation(run) {
120
+ // The notes path rides beside the run marker, as the pi and claude adapters' envs do
121
+ // (withNotesPath); from part 2/4 the run saves its note through the `tumwater-notes`
122
+ // command on PATH, so codex configures no MCP server for notes.
123
+ const base = withNotesPath(backendBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
124
+ // A team home of its own (`CODEX_HOME`) keeps team sessions and trust entries out of the
125
+ // operator's `~/.codex` (design §4, finding 10); unset leaves the operator's home.
126
+ const home = codexHome(resolvedBackendConfig(run.config, "codex"));
127
+ const env = codexEnv(base, { home });
128
+ const { sessionId, resumed } = resolveCodexSession(run, home);
129
+ const thinking = run.config.thinking;
130
+ // The effort (parts 5/9 and 8/9): ride the thinking level through the decoder, then gate
131
+ // it on the model's cataloged effort levels — a level the model does not list is never
132
+ // sent (codexEffort's `levels` half). No home (no catalog) leaves the value unclamped, as
133
+ // before part 8/9.
134
+ const catalog = home === undefined ? new Map() : readCodexCatalog(home);
135
+ const effort = thinking === undefined ? undefined : codexEffort(thinking, catalog.get(run.config.model ?? "")?.levels);
136
+ // The write guard's trust (part 6/9): `prepare` resolved it under this run's env and
137
+ // home and stored it keyed by (home, definition), so `invocation` reads it synchronously.
138
+ // With trust it rides the hook and its trust entry as two `-c` values (after the
139
+ // harness's own keys, before `-m`); with `off` or no state it passes neither, since an
140
+ // untrusted hook would be skipped silently anyway. tumwater never passes
141
+ // `--dangerously-bypass-hook-trust` (findings 1 and 4).
142
+ const definition = codexWriteGuardDefinition();
143
+ const trust = writeGuardTrust.get(writeGuardKey(home, definition));
144
+ const guardArgs = trust?.kind === "trust"
145
+ ? ["-c", codexHookArg(definition), "-c", codexHookTrustArg(trust.trust)]
146
+ : undefined;
147
+ const { args, stdin } = codexArgs({
148
+ model: run.config.model ?? "",
149
+ ...(effort !== undefined ? { effort } : {}),
150
+ ...(resumed && sessionId !== undefined ? { resumeId: sessionId } : {}),
151
+ ...(guardArgs !== undefined ? { guardArgs } : {}),
152
+ args: resolvedBackendConfig(run.config, "codex").args,
153
+ prompt: run.prompt,
154
+ });
155
+ // A resumed run returns its id so the runner's pre-spawn ledger record carries it; a fresh
156
+ // run has none until the stream names it (the runner's stream-mint pass appends it).
157
+ return { args, stdin, env, ...(resumed && sessionId !== undefined ? { sessionId } : {}) };
158
+ },
159
+ async prepare(run) {
160
+ // Resolve the write guard's trust under the same env and home invocation will use (the
161
+ // run's CODEX_HOME, plans/codex-write-guard.md "Trust (parts 4–5)"), so the trust an
162
+ // invocation later reads was looked up under the run's own config. It never throws a run
163
+ // away: a failed lookup records `off` (and warns once) and the run continues without the
164
+ // guard's trust.
165
+ const base = withNotesPath(backendBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
166
+ const home = codexHome(resolvedBackendConfig(run.config, "codex"));
167
+ const env = codexEnv(base, { home });
168
+ const root = nearestTumwaterRoot(run.sessionDir);
169
+ const definition = codexWriteGuardDefinition();
170
+ const key = writeGuardKey(home, definition);
171
+ // Single-flight per (home, definition): a run whose key is being resolved (or already
172
+ // resolved) shares the one in-flight lookup, so concurrent runs start one `app-server`.
173
+ const settled = writeGuardTrust.get(key);
174
+ if (settled !== undefined) {
175
+ warnGuardOffOnce(key, root, settled);
176
+ return;
177
+ }
178
+ const inFlight = writeGuardInFlight.get(key);
179
+ if (inFlight !== undefined) {
180
+ await inFlight;
181
+ const after = writeGuardTrust.get(key);
182
+ if (after !== undefined)
183
+ warnGuardOffOnce(key, root, after);
184
+ return;
185
+ }
186
+ const promise = (async () => {
187
+ try {
188
+ const trust = await codexHookTrust(root, {
189
+ bin: run.agentBin,
190
+ env,
191
+ cwd: run.cwd,
192
+ definition,
193
+ timeoutMs: codexTrustLookupTimeoutMs,
194
+ });
195
+ writeGuardTrust.set(key, { kind: "trust", trust });
196
+ }
197
+ catch (error) {
198
+ writeGuardTrust.set(key, { kind: "off", reason: errorMessage(error) });
199
+ }
200
+ })();
201
+ writeGuardInFlight.set(key, promise);
202
+ await promise;
203
+ writeGuardInFlight.delete(key);
204
+ const state = writeGuardTrust.get(key);
205
+ if (state !== undefined)
206
+ warnGuardOffOnce(key, root, state);
207
+ },
208
+ translator() {
209
+ return codexTranslator();
210
+ },
211
+ imageInput(model) {
212
+ // Answer from the model catalog (part 8/9): a model reads images when its catalog entry's
213
+ // `input_modalities` say so; a model missing from the catalog — or a run with no home to
214
+ // read — answers false, the safe direction, so a blind image read is never silently
215
+ // assumed readable. The home is the team's `backends.codex.configDir`, found by walking
216
+ // up from the process's working directory to its `.tumwater` root and loading the config
217
+ // there, the same root the runs' home and account live under (like opencode's cache).
218
+ if (model === undefined)
219
+ return false;
220
+ const root = teamRootOf(process.cwd());
221
+ const { config } = loadConfigSafe(root);
222
+ const home = codexHome(resolvedBackendConfig(config ?? {}, "codex"));
223
+ if (home === undefined)
224
+ return false;
225
+ return readCodexCatalog(home).get(model)?.images === true;
226
+ },
227
+ resumable(dir, opts) {
228
+ const record = resumableSession(dir, { kind: opts.kind, agent: "codex", cwd: opts.cwd });
229
+ if (record === null || !THREAD_ID_RE.test(record.id))
230
+ return false;
231
+ const root = teamRootOf(dir);
232
+ const { config } = loadConfigSafe(root);
233
+ const home = codexHome(resolvedBackendConfig(config ?? {}, "codex"));
234
+ if (home === undefined)
235
+ return false;
236
+ return findRollout(home, record.id) !== null;
237
+ },
238
+ afterRun(run, invocation, translator) {
239
+ const ct = translator;
240
+ const home = codexHome(resolvedBackendConfig(run.config, "codex"));
241
+ const id = invocation.sessionId ?? ct.sessionId?.();
242
+ const fresh = invocation.sessionId === undefined;
243
+ // The rollout is authoritative, so its loss is worth one warning: a fresh run keeps the
244
+ // stream's own usage as a fallback, a resumed run records none (design §5, finding 4).
245
+ const fallback = () => {
246
+ ct.flagUsagePartial();
247
+ if (!fresh)
248
+ return [];
249
+ const usage = ct.turnCompletedUsage();
250
+ return usage !== undefined ? [JSON.stringify({ type: "run_usage", ...usage })] : [];
251
+ };
252
+ if (home === undefined || id === undefined)
253
+ return fallback();
254
+ const rollout = findRollout(home, id);
255
+ if (rollout === null)
256
+ return fallback();
257
+ let text;
258
+ try {
259
+ text = fs.readFileSync(rollout, "utf8");
260
+ }
261
+ catch {
262
+ return fallback();
263
+ }
264
+ // The stream never exposes the turn id — only the thread id that names the rollout file
265
+ // (real captures' `task_started.turn_id` is a different UUID), so the reader selects the
266
+ // last block, this run's, from the file the thread id located.
267
+ const { lines, switchedTo } = rolloutLines(text, {
268
+ requestedModel: run.config.model ?? "",
269
+ });
270
+ if (lines.length === 0)
271
+ return fallback();
272
+ // Codex can switch models on a usage limit by itself (finding 24): warn once and record
273
+ // the model that actually ran in the rollout's reading.
274
+ if (switchedTo !== undefined)
275
+ ct.flagModelSwitched(run.config.model ?? "", switchedTo);
276
+ return lines;
277
+ },
278
+ classifyExit: classifyCodexExit,
279
+ };
@@ -0,0 +1,97 @@
1
+ /** The Codex model catalog (plans/codex-backend.md design finding 14 — "Spend, free-ness,
2
+ * window, images" part 8/9): tumwater reads a Codex model's effort levels, default effort,
3
+ * context window and image input from `$CODEX_HOME/models_cache.json`, the file the CLI itself
4
+ * refreshes and `codex debug models` prints. Before part 8/9 the adapter assumed every Codex
5
+ * model had every effort and read every image; the catalog refines both, and seats the context
6
+ * window `seamContextWindow` reports for a codex seam. There are no prices in the catalog — the
7
+ * whole file knows nothing of cost — so free-ness never reads here; the codex account
8
+ * (codex-account.ts) owns billing.
9
+ *
10
+ * A missing, unreadable or malformed file reads as an empty catalog, the same "no data" policy
11
+ * pi's models.json reader uses: the adapter then answers blind (no effort gating, no images,
12
+ * no window), which is the safe direction for the window and image checks. The parse itself is
13
+ * pure and build-agnostic — no registry, config or subprocess — so the adapter, pi's
14
+ * seamContextWindow and the doctor can read one answer without dragging a runtime in. */
15
+ import os from "node:os";
16
+ import path from "node:path";
17
+ import { cachedByStat } from "../../files/stat-cache.js";
18
+ import { finiteNumber, isJsonObject } from "../../files/json-object.js";
19
+ import { readJsonFile } from "../../files/json-files.js";
20
+ import { expandHomeTilde } from "../../files/files.js";
21
+ /** The team's Codex home for a run: `backends.codex.configDir`, a leading `~` expanded, else
22
+ * the CLI's default `~/.codex` — a run with no configured home keeps the operator's
23
+ * `CODEX_HOME`, which is exactly Codex's own default home, so the catalog and the sessions
24
+ * live there too and a known home always exists. Pure. Shared by the adapter, pi's
25
+ * seamContextWindow and the codex-account seed, so all three read one home. */
26
+ export function codexHome(config) {
27
+ const dir = config.configDir;
28
+ if (dir === undefined)
29
+ return path.join(os.homedir(), ".codex");
30
+ return expandHomeTilde(dir, os.homedir());
31
+ }
32
+ /** Where a home's model catalog lives: `$CODEX_HOME/models_cache.json`. */
33
+ export function codexModelsPath(home) {
34
+ return path.join(home, "models_cache.json");
35
+ }
36
+ /** Per-catalog cache of the parsed entries, keyed by the models_cache path: the status
37
+ * surfaces read the window every poll while the file changes only when the CLI refreshes it —
38
+ * and it grows with the model catalog — so an unchanged file costs one stat per poll instead
39
+ * of a re-read plus a JSON.parse of the whole file. Any write invalidates via
40
+ * dev/ino/mtime/size (stat-cache.cachedByStat, the same freshness check pi's providers use). */
41
+ const catalogCache = new Map();
42
+ /** The parsed model catalog from `$CODEX_HOME/models_cache.json`, keyed by model slug, or an
43
+ * empty map when the file is missing, unreadable, malformed or not shaped like what the CLI
44
+ * writes. A model's entry is kept only when it names a usable slug and context_window; a
45
+ * missing `effective_context_window_percent` reads as no window (the model is dropped), since
46
+ * the window is the whole point of the entry. The returned map is a copy — callers may treat
47
+ * their result as their own. */
48
+ export function readCodexCatalog(home) {
49
+ const modelsPath = codexModelsPath(home);
50
+ return (cachedByStat(catalogCache, modelsPath, modelsPath, () => {
51
+ const out = new Map();
52
+ // readJsonFile is the shared "missing or torn reads as no data" policy for a JSON
53
+ // object file; the nested `models` array gets the same isJsonObject/Array guards.
54
+ const doc = readJsonFile(modelsPath);
55
+ if (doc === null)
56
+ return out;
57
+ const models = doc.models;
58
+ if (!Array.isArray(models))
59
+ return out;
60
+ for (const model of models) {
61
+ const entry = parseModelEntry(model);
62
+ if (entry !== undefined)
63
+ out.set(entry.slug, entry.entry);
64
+ }
65
+ return out;
66
+ }, (models) => new Map([...models].map(([slug, entry]) => [slug, { ...entry, levels: [...entry.levels] }]))) ?? new Map());
67
+ }
68
+ /** One catalog model entry as the map needs it: the slug to key by plus the parsed entry. The
69
+ * entry is kept only when the slug and context window are usable; any other missing field
70
+ * (levels, modalities, default level) just reads as absent on the entry. */
71
+ function parseModelEntry(model) {
72
+ if (!isJsonObject(model) || typeof model.slug !== "string")
73
+ return undefined;
74
+ const context = finiteNumber(model.context_window, Number.NaN);
75
+ const percent = finiteNumber(model.effective_context_window_percent, Number.NaN);
76
+ if (!Number.isFinite(context) || !Number.isFinite(percent))
77
+ return undefined;
78
+ const levels = [];
79
+ if (Array.isArray(model.supported_reasoning_levels)) {
80
+ for (const level of model.supported_reasoning_levels) {
81
+ if (isJsonObject(level) && typeof level.effort === "string")
82
+ levels.push(level.effort);
83
+ }
84
+ }
85
+ const modalities = model.input_modalities;
86
+ return {
87
+ slug: model.slug,
88
+ entry: {
89
+ levels,
90
+ ...(typeof model.default_reasoning_level === "string"
91
+ ? { defaultLevel: model.default_reasoning_level }
92
+ : {}),
93
+ contextWindow: Math.round((context * percent) / 100),
94
+ images: Array.isArray(modalities) && modalities.includes("image"),
95
+ },
96
+ };
97
+ }
@@ -0,0 +1,18 @@
1
+ /** Build the codex process environment from `base`. Never mutates `base`. */
2
+ export function codexEnv(base, opts = {}) {
3
+ const env = {};
4
+ for (const [key, value] of Object.entries(base)) {
5
+ if (key === "CLAUDECODE")
6
+ continue;
7
+ if (key.startsWith("CODEX_"))
8
+ continue;
9
+ if (key.startsWith("CLAUDE_CODE_"))
10
+ continue;
11
+ if (key === "OPENAI_API_KEY" || key === "CODEX_API_KEY")
12
+ continue;
13
+ env[key] = value;
14
+ }
15
+ if (opts.home)
16
+ env.CODEX_HOME = opts.home;
17
+ return env;
18
+ }