tumwater 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +27 -13
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +8 -9
  4. package/dist/src/agent/agent-backend.js +8 -6
  5. package/dist/src/agent/backend-traits.js +55 -25
  6. package/dist/src/agent/claude/claude-account.js +71 -204
  7. package/dist/src/agent/claude/claude-args.js +9 -6
  8. package/dist/src/agent/claude/claude-backend.js +10 -10
  9. package/dist/src/agent/claude/claude-env.js +1 -1
  10. package/dist/src/agent/claude/claude-stream.js +6 -4
  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 +404 -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 +29 -24
  25. package/dist/src/agent/notes-shim.js +11 -12
  26. package/dist/src/agent/opencode/opencode-args.js +0 -1
  27. package/dist/src/agent/opencode/opencode-backend.js +12 -9
  28. package/dist/src/agent/opencode/opencode-catalog.js +55 -29
  29. package/dist/src/agent/opencode/opencode-config.js +19 -14
  30. package/dist/src/agent/opencode/opencode-env.js +0 -1
  31. package/dist/src/agent/opencode/opencode-session-id.js +1 -2
  32. package/dist/src/agent/opencode/opencode-stream.js +1 -1
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +5 -6
  35. package/dist/src/agent/run-agent.js +465 -448
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +86 -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 +281 -55
  41. package/dist/src/backlog/backlog-cli-view.js +255 -0
  42. package/dist/src/backlog/backlog-cli.js +91 -335
  43. package/dist/src/backlog/backlog-commit.js +20 -13
  44. package/dist/src/backlog/backlog-eligibility.js +193 -242
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +54 -6
  47. package/dist/src/backlog/backlog-parts.js +264 -0
  48. package/dist/src/backlog/backlog-release.js +134 -0
  49. package/dist/src/backlog/backlog-render.js +27 -0
  50. package/dist/src/backlog/backlog-structure.js +99 -156
  51. package/dist/src/backlog/backlog-write.js +40 -127
  52. package/dist/src/backlog/backlog.js +21 -6
  53. package/dist/src/backlog/question-answer.js +21 -15
  54. package/dist/src/baseline/main-baseline.js +89 -20
  55. package/dist/src/baseline/main-guardrails.js +136 -0
  56. package/dist/src/baseline/main-red.js +153 -36
  57. package/dist/src/baseline/main-watch.js +232 -0
  58. package/dist/src/brief/brief-edit.js +12 -12
  59. package/dist/src/brief/brief.js +31 -51
  60. package/dist/src/budget/budget-fallback-view.js +288 -0
  61. package/dist/src/budget/budget-wiring.js +158 -0
  62. package/dist/src/budget/budget.js +31 -20
  63. package/dist/src/budget/fallback-breaker.js +26 -27
  64. package/dist/src/build/build-check-detect.js +23 -15
  65. package/dist/src/build/build-check-events.js +25 -6
  66. package/dist/src/build/build-check-report.js +49 -9
  67. package/dist/src/build/build-check-scoped.js +18 -11
  68. package/dist/src/build/build-check.js +2 -1
  69. package/dist/src/build/build-info.js +2 -3
  70. package/dist/src/build/build-stage.js +9 -14
  71. package/dist/src/build/dep-install.js +65 -3
  72. package/dist/src/change/change-data.js +123 -31
  73. package/dist/src/change/change-render.js +46 -5
  74. package/dist/src/cli/brief-command.js +3 -4
  75. package/dist/src/cli/cli-args.js +181 -33
  76. package/dist/src/cli/cli-command-args.js +46 -18
  77. package/dist/src/cli/cli-editor.js +2 -1
  78. package/dist/src/cli/cli-flag-specs.js +183 -45
  79. package/dist/src/cli/cli-init.js +51 -0
  80. package/dist/src/cli/cli-marker-commands.js +4 -4
  81. package/dist/src/cli/cli-query-commands.js +291 -598
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-review-commands.js +113 -0
  84. package/dist/src/cli/cli-role-command.js +525 -0
  85. package/dist/src/cli/cli-start-summary.js +167 -0
  86. package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
  87. package/dist/src/cli/completion.js +169 -22
  88. package/dist/src/cli/config-commands.js +58 -26
  89. package/dist/src/cli/follow-view.js +75 -0
  90. package/dist/src/cli/help.js +258 -82
  91. package/dist/src/cli/log-commands.js +164 -85
  92. package/dist/src/cli/question-commands.js +211 -10
  93. package/dist/src/cli.js +173 -45
  94. package/dist/src/collections.js +56 -13
  95. package/dist/src/concurrency/check-permit.js +20 -3
  96. package/dist/src/concurrency/lock.js +17 -13
  97. package/dist/src/concurrency/provider-lease.js +44 -8
  98. package/dist/src/concurrency/semaphore.js +102 -26
  99. package/dist/src/config/config-aliases.js +0 -1
  100. package/dist/src/config/config-editable-keys.js +16 -1
  101. package/dist/src/config/config-fallbacks.js +94 -56
  102. package/dist/src/config/config-field-checks.js +40 -10
  103. package/dist/src/config/config-keys.js +30 -8
  104. package/dist/src/config/config-live.js +63 -24
  105. package/dist/src/config/config-request.js +137 -0
  106. package/dist/src/config/config-schema.js +31 -24
  107. package/dist/src/config/config-selector-checks.js +123 -0
  108. package/dist/src/config/config-validation-agents.js +80 -0
  109. package/dist/src/config/config-validation-model.js +116 -0
  110. package/dist/src/config/config-validation.js +169 -222
  111. package/dist/src/config/config-views.js +136 -54
  112. package/dist/src/config/config-write-core.js +135 -0
  113. package/dist/src/config/config-write.js +164 -265
  114. package/dist/src/config/config.js +53 -38
  115. package/dist/src/config/model-selector.js +34 -34
  116. package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
  117. package/dist/src/doctor/doctor-backlog-plans.js +398 -0
  118. package/dist/src/doctor/doctor-backlog.js +249 -512
  119. package/dist/src/doctor/doctor-check-names.js +85 -0
  120. package/dist/src/doctor/doctor-checks.js +61 -593
  121. package/dist/src/doctor/doctor-claude-checks.js +142 -0
  122. package/dist/src/doctor/doctor-codex-checks.js +276 -0
  123. package/dist/src/doctor/doctor-contention-checks.js +506 -0
  124. package/dist/src/doctor/doctor-fallback-checks.js +130 -0
  125. package/dist/src/doctor/doctor-holds.js +80 -66
  126. package/dist/src/doctor/doctor-install.js +2 -1
  127. package/dist/src/doctor/doctor-model-checks.js +216 -537
  128. package/dist/src/doctor/doctor-notify.js +9 -5
  129. package/dist/src/doctor/doctor-opencode-checks.js +164 -0
  130. package/dist/src/doctor/doctor-orphans.js +182 -63
  131. package/dist/src/doctor/doctor-pi-checks.js +62 -0
  132. package/dist/src/doctor/doctor-project-checks.js +397 -0
  133. package/dist/src/doctor/doctor.js +126 -77
  134. package/dist/src/doctor/image-probe.js +21 -8
  135. package/dist/src/doctor/outcome.js +3 -2
  136. package/dist/src/doctor/probe.js +21 -12
  137. package/dist/src/events/event-format.js +185 -89
  138. package/dist/src/events/event-labels.js +11 -5
  139. package/dist/src/events/event-legacy.js +39 -15
  140. package/dist/src/events/event-read.js +52 -39
  141. package/dist/src/events/event-tally.js +16 -0
  142. package/dist/src/events/event-window.js +80 -26
  143. package/dist/src/events/events.js +30 -13
  144. package/dist/src/events/notify.js +7 -7
  145. package/dist/src/failure/failure-cluster.js +12 -12
  146. package/dist/src/failure/failure-data.js +65 -60
  147. package/dist/src/failure/failure-render.js +29 -21
  148. package/dist/src/failure/failure-state-change.js +17 -12
  149. package/dist/src/failure/time-spend.js +35 -35
  150. package/dist/src/files/file-queue.js +17 -0
  151. package/dist/src/files/files.js +111 -35
  152. package/dist/src/files/json-files.js +3 -3
  153. package/dist/src/files/json-object.js +28 -7
  154. package/dist/src/files/stat-cache.js +2 -2
  155. package/dist/src/files/tail.js +1 -1
  156. package/dist/src/gates/bootstrap-gates.js +4 -5
  157. package/dist/src/gates/budget-gates.js +88 -225
  158. package/dist/src/gates/disk-gate.js +1 -1
  159. package/dist/src/gates/gate-polls.js +101 -143
  160. package/dist/src/gates/maintenance-quota.js +37 -30
  161. package/dist/src/gates/pause-gates.js +28 -10
  162. package/dist/src/gates/readiness.js +7 -3
  163. package/dist/src/gates/role-cap-gates.js +8 -8
  164. package/dist/src/gates/startup-gate.js +94 -32
  165. package/dist/src/gates/streak-gate.js +54 -30
  166. package/dist/src/git/commit-message.js +89 -46
  167. package/dist/src/git/git-diff.js +16 -10
  168. package/dist/src/git/git-run.js +79 -38
  169. package/dist/src/git/git.js +54 -34
  170. package/dist/src/git/slot-salvage.js +253 -0
  171. package/dist/src/git/slots-state.js +6 -6
  172. package/dist/src/git/worktree-pool.js +132 -38
  173. package/dist/src/git/worktree-use.js +4 -11
  174. package/dist/src/git/worktree.js +94 -37
  175. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  176. package/dist/src/gui/gui-args.js +79 -8
  177. package/dist/src/gui/gui-backlog-commands.js +43 -19
  178. package/dist/src/gui/gui-backlog-endpoints.js +156 -0
  179. package/dist/src/gui/gui-command.js +26 -13
  180. package/dist/src/gui/gui-config-commands.js +168 -80
  181. package/dist/src/gui/gui-endpoint-commands.js +5 -4
  182. package/dist/src/gui/gui-endpoint-common.js +4 -4
  183. package/dist/src/gui/gui-endpoints.js +11 -380
  184. package/dist/src/gui/gui-history-endpoints.js +301 -0
  185. package/dist/src/gui/gui-operator-commands.js +235 -13
  186. package/dist/src/gui/gui-prompt-commands.js +26 -20
  187. package/dist/src/gui/gui-report-endpoints.js +97 -0
  188. package/dist/src/gui/gui-server.js +26 -5
  189. package/dist/src/gui/gui-team-commands.js +7 -7
  190. package/dist/src/gui/http-body.js +1 -1
  191. package/dist/src/history/history-data.js +122 -234
  192. package/dist/src/history/history-queued.js +155 -0
  193. package/dist/src/history/history-render.js +42 -22
  194. package/dist/src/history/history.js +190 -49
  195. package/dist/src/inbox/inbox-attachments.js +29 -30
  196. package/dist/src/inbox/inbox-cancel.js +10 -11
  197. package/dist/src/inbox/inbox-edit.js +9 -9
  198. package/dist/src/inbox/inbox-submit.js +18 -18
  199. package/dist/src/inbox/inbox-top.js +28 -17
  200. package/dist/src/inbox/inbox.js +42 -43
  201. package/dist/src/inbox/pending-prompt.js +26 -26
  202. package/dist/src/inbox/prompt-commands.js +62 -49
  203. package/dist/src/inbox/prompt-not-before.js +2 -2
  204. package/dist/src/init/init-templates.js +1 -2
  205. package/dist/src/init/init.js +28 -23
  206. package/dist/src/merge/backlog-conflicts.js +215 -35
  207. package/dist/src/merge/merge-batch.js +14 -8
  208. package/dist/src/merge/merge-cancel.js +53 -0
  209. package/dist/src/merge/merge-check-failures.js +44 -22
  210. package/dist/src/merge/merge-core.js +58 -32
  211. package/dist/src/merge/merge-diff.js +5 -4
  212. package/dist/src/merge/merge-drain.js +45 -19
  213. package/dist/src/merge/merge-git.js +177 -38
  214. package/dist/src/merge/merge-pipeline.js +15 -3
  215. package/dist/src/merge/merge-post-review.js +214 -0
  216. package/dist/src/merge/merge-queue-render.js +48 -11
  217. package/dist/src/merge/merge-queue.js +52 -15
  218. package/dist/src/merge/merge-requeue.js +103 -0
  219. package/dist/src/merge/merge-slot.js +61 -16
  220. package/dist/src/merge/merge-stack.js +27 -11
  221. package/dist/src/merge/merge-to-main.js +77 -26
  222. package/dist/src/merge/merge-top.js +83 -0
  223. package/dist/src/merge/merge-verify.js +75 -18
  224. package/dist/src/notes-cli.js +35 -15
  225. package/dist/src/operator/operator-commands.js +192 -81
  226. package/dist/src/operator/operator-intent.js +69 -42
  227. package/dist/src/operator/operator-requests.js +35 -40
  228. package/dist/src/operator/retire.js +5 -6
  229. package/dist/src/operator/revert.js +142 -34
  230. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  231. package/dist/src/orchestrator/orchestrator-poll.js +352 -0
  232. package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
  233. package/dist/src/orchestrator/orchestrator.js +129 -369
  234. package/dist/src/orchestrator/retention.js +42 -8
  235. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  236. package/dist/src/orchestrator/scheduling-scope.js +245 -0
  237. package/dist/src/orchestrator/scheduling-skip.js +195 -0
  238. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  239. package/dist/src/orchestrator/startup-migrations.js +11 -7
  240. package/dist/src/paths.js +126 -49
  241. package/dist/src/pi/command-shape.js +12 -1
  242. package/dist/src/pi/pi-args.js +6 -4
  243. package/dist/src/pi/pi-backend.js +6 -6
  244. package/dist/src/pi/pi-bin.js +54 -63
  245. package/dist/src/pi/pi-listed-models.js +53 -0
  246. package/dist/src/pi/pi-models.js +63 -30
  247. package/dist/src/pi/pi-run-result.js +1 -1
  248. package/dist/src/pi/pi-settings.js +1 -1
  249. package/dist/src/pi/pi-stream.js +6 -6
  250. package/dist/src/pi/pi-watchdogs.js +18 -19
  251. package/dist/src/pi-extension/bounded-output.js +5 -3
  252. package/dist/src/pi-extension/context-budget.js +2 -2
  253. package/dist/src/pi-extension/context-shake.js +1 -1
  254. package/dist/src/pi-extension/context-window.js +0 -1
  255. package/dist/src/pi-extension/full-output.js +3 -10
  256. package/dist/src/pi-extension/strip-thinking.js +0 -1
  257. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  258. package/dist/src/pi-extension/write-guard.js +37 -0
  259. package/dist/src/process/launch-services.js +1 -2
  260. package/dist/src/process/node-bin.js +16 -0
  261. package/dist/src/process/process-group.js +8 -18
  262. package/dist/src/process/process.js +41 -2
  263. package/dist/src/process/run-marker.js +26 -6
  264. package/dist/src/process/supervisor.js +22 -21
  265. package/dist/src/prompt/dialect.js +17 -2
  266. package/dist/src/prompt/gate-notes.js +203 -0
  267. package/dist/src/prompt/gate-prompts.js +150 -217
  268. package/dist/src/prompt/notes-backlog.js +184 -0
  269. package/dist/src/prompt/principles.js +1 -2
  270. package/dist/src/prompt/prompt-followup.js +19 -14
  271. package/dist/src/prompt/prompt.js +131 -204
  272. package/dist/src/redeploy/redeploy-policy.js +24 -19
  273. package/dist/src/redeploy/redeploy-probes.js +1 -1
  274. package/dist/src/redeploy/redeploy.js +2 -3
  275. package/dist/src/redeploy/redeployer.js +20 -20
  276. package/dist/src/redeploy/self-reload.js +2 -1
  277. package/dist/src/report/report-data.js +188 -124
  278. package/dist/src/report/report-fold.js +130 -54
  279. package/dist/src/report/report-main-red.js +55 -0
  280. package/dist/src/report/report-render.js +136 -65
  281. package/dist/src/report/report-types.js +1 -0
  282. package/dist/src/report/report.js +23 -9
  283. package/dist/src/request-timeouts.js +2 -2
  284. package/dist/src/review/diff-rules.js +53 -0
  285. package/dist/src/review/known-flakes.js +112 -1
  286. package/dist/src/review/post-merge-review-accept.js +51 -0
  287. package/dist/src/review/post-merge-review-render.js +112 -0
  288. package/dist/src/review/post-merge-review.js +239 -0
  289. package/dist/src/review/protected-edits.js +24 -10
  290. package/dist/src/review/quarantine-render.js +65 -0
  291. package/dist/src/review/review-followup.js +8 -8
  292. package/dist/src/review/review-precheck.js +66 -23
  293. package/dist/src/review/review-run.js +395 -0
  294. package/dist/src/review/review-verdict.js +2 -2
  295. package/dist/src/review/review.js +180 -401
  296. package/dist/src/review/secret-leaks.js +36 -16
  297. package/dist/src/review/suite-rerun.js +0 -1
  298. package/dist/src/roles/legacy-role-migration.js +80 -7
  299. package/dist/src/roles/role-catalog.js +127 -12
  300. package/dist/src/roles/role-guidance.js +65 -20
  301. package/dist/src/roles/role-labels.js +7 -5
  302. package/dist/src/roles/role-render.js +108 -56
  303. package/dist/src/roles/role-view.js +95 -56
  304. package/dist/src/roles/roles.js +33 -33
  305. package/dist/src/roles/runner-ids.js +97 -0
  306. package/dist/src/run/acceptance-greps.js +119 -0
  307. package/dist/src/run/ci-parity.js +121 -0
  308. package/dist/src/run/feedback-filings.js +51 -0
  309. package/dist/src/run/qa-coverage.js +125 -0
  310. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  311. package/dist/src/run/run-detail-data.js +175 -0
  312. package/dist/src/run/run-detail-render.js +91 -0
  313. package/dist/src/run/run-detail.js +168 -0
  314. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
  315. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  316. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
  317. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  318. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
  319. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  320. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  321. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
  322. package/dist/src/run/selfcheck-digest.js +40 -0
  323. package/dist/src/run/stage-backlog-checks.js +249 -0
  324. package/dist/src/run/stage-check.js +178 -0
  325. package/dist/src/run/stage-checks-shared.js +65 -0
  326. package/dist/src/run/stage-name-checks.js +204 -0
  327. package/dist/src/run/stage-path-checks.js +231 -0
  328. package/dist/src/run/stage-prose-checks.js +261 -0
  329. package/dist/src/runner/leftover.js +216 -0
  330. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  331. package/dist/src/runner/revision.js +191 -0
  332. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  333. package/dist/src/runner/runner-outcome.js +109 -0
  334. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  335. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
  336. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
  337. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  338. package/dist/src/runner/runner-state.js +103 -0
  339. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  340. package/dist/src/scheduling/backoff.js +38 -38
  341. package/dist/src/scheduling/claim-entries.js +49 -0
  342. package/dist/src/scheduling/claims.js +35 -22
  343. package/dist/src/scheduling/once-round.js +22 -22
  344. package/dist/src/scheduling/quiet-hours.js +5 -6
  345. package/dist/src/scheduling/scheduling.js +74 -64
  346. package/dist/src/scheduling/work-merged-cache.js +2 -2
  347. package/dist/src/status/status-budget.js +55 -47
  348. package/dist/src/status/status-data.js +49 -17
  349. package/dist/src/status/status-inbox.js +2 -2
  350. package/dist/src/status/status-install.js +0 -1
  351. package/dist/src/status/status-polls.js +43 -28
  352. package/dist/src/status/status-roles.js +50 -13
  353. package/dist/src/team/error-storm.js +8 -8
  354. package/dist/src/team/failure-spread.js +2 -2
  355. package/dist/src/team/orchestrator-info.js +15 -9
  356. package/dist/src/team/reclaim.js +7 -8
  357. package/dist/src/team/team-hold.js +7 -7
  358. package/dist/src/team/team-polls.js +91 -50
  359. package/dist/src/team/team-state.js +8 -2
  360. package/dist/src/text/datetime.js +65 -18
  361. package/dist/src/text/format.js +48 -11
  362. package/dist/src/text/markdown.js +8 -5
  363. package/dist/src/text/phrases.js +60 -23
  364. package/dist/src/text/suggest.js +1 -1
  365. package/dist/src/text/text.js +78 -13
  366. package/dist/src/ui/badges.js +99 -38
  367. package/dist/src/ui/gui/gui-client-backlog.js +314 -0
  368. package/dist/src/ui/gui/gui-client-boot.js +89 -61
  369. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  370. package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
  371. package/dist/src/ui/gui/gui-client-drawer.js +155 -289
  372. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  373. package/dist/src/ui/gui/gui-client-history.js +437 -68
  374. package/dist/src/ui/gui/gui-client-labels.js +6 -3
  375. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  376. package/dist/src/ui/gui/gui-client-model.js +27 -36
  377. package/dist/src/ui/gui/gui-client-operator.js +60 -52
  378. package/dist/src/ui/gui/gui-client-pending.js +405 -24
  379. package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
  380. package/dist/src/ui/gui/gui-client-report.js +105 -430
  381. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  382. package/dist/src/ui/gui/gui-client-settings.js +163 -5
  383. package/dist/src/ui/gui/gui-client-team.js +36 -465
  384. package/dist/src/ui/gui/gui-client.js +38 -26
  385. package/dist/src/ui/gui/gui-icons.js +0 -2
  386. package/dist/src/ui/gui/gui-page.js +10 -7
  387. package/dist/src/ui/gui/gui-styles.js +38 -34
  388. package/dist/src/ui/progress-data.js +9 -11
  389. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
  390. package/dist/src/ui/status-model.js +133 -125
  391. package/dist/src/ui/status-payload.js +364 -49
  392. package/dist/src/ui/status-render.js +109 -82
  393. package/dist/src/ui/team-alerts.js +728 -62
  394. package/dist/src/ui/tone.js +12 -6
  395. package/dist/src/ui/transcript-run.js +58 -0
  396. package/dist/src/ui/transcript-tail.js +28 -20
  397. package/dist/src/ui/transcript.js +63 -23
  398. package/dist/src/ui/tui/tui-app.js +0 -1
  399. package/dist/src/ui/tui/tui-backlog.js +53 -14
  400. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  401. package/dist/src/ui/tui/tui-editor-attach.js +4 -12
  402. package/dist/src/ui/tui/tui-editor-backends.js +127 -0
  403. package/dist/src/ui/tui/tui-editor-brief.js +14 -36
  404. package/dist/src/ui/tui/tui-editor-budget.js +36 -39
  405. package/dist/src/ui/tui/tui-editor-check.js +29 -0
  406. package/dist/src/ui/tui/tui-editor-checks.js +28 -0
  407. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  408. package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
  409. package/dist/src/ui/tui/tui-editor-config.js +99 -0
  410. package/dist/src/ui/tui/tui-editor-entry.js +39 -64
  411. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  412. package/dist/src/ui/tui/tui-editor-instances.js +46 -0
  413. package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
  414. package/dist/src/ui/tui/tui-editor-interval.js +29 -16
  415. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  416. package/dist/src/ui/tui/tui-editor-notes.js +9 -29
  417. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  418. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  419. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  420. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  421. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  422. package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
  423. package/dist/src/ui/tui/tui-editor-retire.js +5 -13
  424. package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
  425. package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
  426. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  427. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  428. package/dist/src/ui/tui/tui-editor-session.js +101 -34
  429. package/dist/src/ui/tui/tui-editor-slots.js +24 -0
  430. package/dist/src/ui/tui/tui-editors.js +44 -2
  431. package/dist/src/ui/tui/tui-frame.js +68 -11
  432. package/dist/src/ui/tui/tui-history.js +8 -9
  433. package/dist/src/ui/tui/tui-input.js +13 -11
  434. package/dist/src/ui/tui/tui-keymap.js +0 -1
  435. package/dist/src/ui/tui/tui-keys.js +33 -9
  436. package/dist/src/ui/tui/tui-pane-activity.js +35 -19
  437. package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
  438. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  439. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  440. package/dist/src/ui/tui/tui-pane-health.js +168 -8
  441. package/dist/src/ui/tui/tui-pane-history.js +446 -82
  442. package/dist/src/ui/tui/tui-pane-pending.js +539 -15
  443. package/dist/src/ui/tui/tui-pane-queued.js +78 -57
  444. package/dist/src/ui/tui/tui-pane-report.js +54 -16
  445. package/dist/src/ui/tui/tui-pane-session.js +34 -6
  446. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  447. package/dist/src/ui/tui/tui-pane.js +91 -28
  448. package/dist/src/ui/tui/tui-panes.js +153 -34
  449. package/dist/src/ui/tui/tui.js +18 -15
  450. package/dist/src/verdict/fix-claim.js +129 -33
  451. package/dist/src/verdict/no-change.js +4 -4
  452. package/dist/src/verdict/refusal.js +40 -16
  453. package/dist/src/verdict/reply-contract.js +77 -31
  454. package/dist/src/version.js +0 -1
  455. package/package.json +2 -1
  456. package/dist/src/loop/leftover.js +0 -130
  457. package/dist/src/loop/revision.js +0 -70
  458. package/dist/src/roles/loop-ids.js +0 -77
  459. package/dist/src/tick/qa-coverage.js +0 -108
  460. package/dist/src/tick/stage-check.js +0 -419
  461. package/dist/src/tick/telemetry-digest.js +0 -25
  462. package/dist/src/tick/tick-detail-data.js +0 -108
  463. package/dist/src/tick/tick-detail.js +0 -163
  464. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,5 +1,4 @@
1
- // vocabulary: plain
2
- /** Building the claude command line for one tick (plans/agent-backends.md "The claude
1
+ /** Building the claude command line for one run (plans/agent-backends.md "The claude
3
2
  * invocation") — pure argv construction plus the prompt-on-stdin pairing, with no subprocess,
4
3
  * backend registration, or session ledger. Part 3c supplies the session decision (which
5
4
  * `--session-id` or `--resume` to pass); this module turns those inputs into the invocation's
@@ -17,13 +16,14 @@
17
16
  import fs from "node:fs";
18
17
  import path from "node:path";
19
18
  import { appendPassthroughArgs } from "../agent-args.js";
19
+ import { writeGuardSettings } from "./claude-write-guard.js";
20
20
  /** Flags the harness passes itself. A passthrough arg equal to one of these is appended AFTER
21
21
  * the harness's own copy, and claude's argument parser is last-wins — so it would silently
22
22
  * override the isolation flags, the session choice, or the model the harness resolved. Each is
23
23
  * rejected by claudeArgs with the offending arg named. This is exactly the design's reserved
24
- * set (plans/agent-backends.md "Config"); config validation (config-validation.ts) mirrors this
25
- * set for `backends.claude.args`, so a value that passes validation is never accepted by
26
- * validation and then thrown here. */
24
+ * set (plans/agent-backends.md "Config"); config validation (config-validation-agents.ts)
25
+ * mirrors this set for `backends.claude.args`, so a value that passes validation is never
26
+ * accepted by validation and then thrown here. */
27
27
  export const HARNESS_CLAUDE_FLAGS = new Set([
28
28
  "-p",
29
29
  "--print",
@@ -39,6 +39,7 @@ export const HARNESS_CLAUDE_FLAGS = new Set([
39
39
  "--permission-mode",
40
40
  "--tools",
41
41
  "--setting-sources",
42
+ "--settings",
42
43
  "--strict-mcp-config",
43
44
  "--mcp-config",
44
45
  "--disable-slash-commands",
@@ -89,7 +90,9 @@ export function claudeArgs(opts) {
89
90
  args.push("--session-id", opts.sessionId);
90
91
  else
91
92
  args.push("--resume", opts.resumeSessionId);
92
- args.push("--permission-mode", "bypassPermissions", "--tools", "Bash,Read,Edit,Write,Glob,Grep", "--setting-sources", "project", "--strict-mcp-config");
93
+ args.push("--permission-mode", "bypassPermissions", "--tools", "Bash,Read,Edit,Write,Glob,Grep", "--setting-sources", "project",
94
+ // The write guard's PreToolUse hook (claude-write-guard.ts).
95
+ "--settings", writeGuardSettings(opts.cwd), "--strict-mcp-config");
93
96
  args.push("--disable-slash-commands", "--exclude-dynamic-system-prompt-sections");
94
97
  if (shouldAppendAgents(opts.cwd))
95
98
  args.push("--append-system-prompt-file", "AGENTS.md");
@@ -17,11 +17,11 @@ import fs from "node:fs";
17
17
  import os from "node:os";
18
18
  import path from "node:path";
19
19
  import { randomUUID } from "node:crypto";
20
- import { resolvedAgentConfig } from "../../config/config-views.js";
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 { agentBaseEnv, withNotesPath } from "../../process/run-marker.js";
24
- import { resolveBackendBin } from "../../pi/pi-bin.js";
23
+ import { backendRunEnv } from "../../process/run-marker.js";
24
+ import { resolveBinForBackend } from "../../pi/pi-bin.js";
25
25
  import { expandHomeTilde } from "../../files/files.js";
26
26
  import { claudeArgs } from "./claude-args.js";
27
27
  import { claudeEnv, claudeThirdPartyProvider } from "./claude-env.js";
@@ -58,7 +58,7 @@ export function claudeTranscriptPath(cwd, id, env = process.env, homeDir = os.ho
58
58
  /** The CLI version every gated claude model needs (plans/agent-backends.md finding 9): on
59
59
  * 2.1.216 Opus 5.5 is refused outright and Haiku 5.5 is mispriced about 250×, and team runs
60
60
  * set DISABLE_AUTOUPDATER so they never update themselves. The gated models are the three
61
- * tier defaults, derived from AGENT_DEFAULT_MODELS through the same routing the config uses
61
+ * tier defaults, derived from BACKEND_DEFAULT_MODELS through the same routing the config uses
62
62
  * rather than listed again, so this table cannot drift from the models a seam resolves. */
63
63
  const CLAUDE_MIN_VERSION = "2.1.280";
64
64
  const CLAUDE_GATED_MODELS = new Set(MODEL_TIERS.map((tier) => routeModel(undefined, undefined, "claude", tier).model).filter((model) => model !== undefined));
@@ -114,7 +114,7 @@ export const claudeBackend = {
114
114
  binEnv: "TUMWATER_CLAUDE_BIN",
115
115
  minVersion: claudeMinVersion,
116
116
  resolveBin(config) {
117
- return resolveBackendBin(config, this);
117
+ return resolveBinForBackend(config, this);
118
118
  },
119
119
  invocation(run) {
120
120
  // Part 4b/7's config routing fills a claude tier default before the seam, so a nameless
@@ -124,7 +124,7 @@ export const claudeBackend = {
124
124
  }
125
125
  // The configured team config dir (plans/notes-command.md "Keeping funes out of team runs"):
126
126
  // the run's env, its resume lookup, and the account seed all follow it; unset, nothing changes.
127
- const claudeCfg = resolvedAgentConfig(run.config, "claude");
127
+ const claudeCfg = resolvedBackendConfig(run.config, "claude");
128
128
  const configDir = claudeCfg.configDir;
129
129
  const expandedConfigDir = configDir !== undefined ? claudeConfigDir(process.env, os.homedir(), configDir) : undefined;
130
130
  const { sessionId, resumeSessionId } = resolveClaudeSession(run, configDir);
@@ -134,7 +134,7 @@ export const claudeBackend = {
134
134
  thinking: run.config.thinking,
135
135
  ...(resumeSessionId !== undefined ? { resumeSessionId } : { sessionId }),
136
136
  // The operator's `backends.claude.args` passthrough, appended after the harness flags (the
137
- // builder rejects any entry that repeats a harness-managed flag). resolvedAgentConfig
137
+ // builder rejects any entry that repeats a harness-managed flag). resolvedBackendConfig
138
138
  // owns the view so a caller cannot mutate the loaded config through the returned array.
139
139
  args: claudeCfg.args,
140
140
  prompt: run.prompt,
@@ -142,12 +142,12 @@ export const claudeBackend = {
142
142
  // adapter forwards it; a subscription run or the director passes none (part 7a/7).
143
143
  ...(run.maxBudgetUsd !== undefined ? { maxBudgetUsd: run.maxBudgetUsd } : {}),
144
144
  });
145
- // agentBaseEnv applies the run marker and the macOS preload (shared with the pi adapter);
146
- // claudeEnv layers the backend's own scrub-and-set on top. The notebook path rides beside
145
+ // backendBaseEnv applies the run marker and the macOS preload (shared with the pi adapter);
146
+ // claudeEnv layers the backend's own scrub-and-set on top. The notes path rides beside
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(agentBaseEnv(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 } : {}),
@@ -1,7 +1,7 @@
1
1
  /** Building the environment for one claude run (plans/agent-backends.md "The claude
2
2
  * invocation", "Findings" 2 and 4) — pure scrub-and-set over a base environment, with no
3
3
  * subprocess. The caller layers the run-marker, the macOS LaunchServices preload, and the
4
- * notebook on first (src/process/run-marker.ts's `withNotesPath`), then hands the result here.
4
+ * notes on first (src/process/run-marker.ts's `withNotesPath`), then hands the result here.
5
5
  *
6
6
  * Inherited Claude Code session variables break auth: spawned from inside a Claude Code
7
7
  * session, a child that keeps `CLAUDECODE`, `CLAUDE_CODE_*`, `CLAUDE_AGENT_SDK_*` or the host's
@@ -14,6 +14,8 @@
14
14
  * on the seam's optional `warnings()` — src/agent/run-agent.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
+ import { joinParts } from "../../text/text.js";
18
+ import { secondsUntil } from "../../text/datetime.js";
17
19
  import { canonicalUsageBlock, isJsonObject, objectOrEmpty, nonNegativeNumber, parseJsonObject } from "../../files/json-object.js";
18
20
  /** claude's tool names → the canonical pi names the readers already know. Glob/Grep are kept
19
21
  * lower-cased; MultiEdit collapses onto edit (pi has one edit tool). Anything else is
@@ -86,7 +88,7 @@ function classifyResult(result) {
86
88
  return {};
87
89
  if (terminal === "error_max_budget_usd")
88
90
  return { kind: "budgetCapped" };
89
- const text = [str(result.result), str(result.error)].filter(Boolean).join(" ");
91
+ const text = joinParts([str(result.result), str(result.error)], " ");
90
92
  if (/error_max_budget_usd/i.test(text))
91
93
  return { kind: "budgetCapped" };
92
94
  const status = nonNegativeNumber(result.api_error_status, undefined);
@@ -193,7 +195,7 @@ export function claudeTranslator(opts = {}) {
193
195
  const maxAttempts = nonNegativeNumber(event.max_retries, undefined);
194
196
  const status = nonNegativeNumber(event.error_status, undefined);
195
197
  const error = str(event.error) ?? "";
196
- const errorMessage = [status !== undefined ? `status ${status}` : "", error].filter(Boolean).join(": ");
198
+ const errorMessage = joinParts([status !== undefined ? `status ${status}` : "", error], ": ");
197
199
  return [
198
200
  JSON.stringify({
199
201
  type: "auto_retry_start",
@@ -294,8 +296,8 @@ export function claudeTranslator(opts = {}) {
294
296
  if (info.isUsingOverage === true)
295
297
  out.isUsingOverage = true;
296
298
  if (status === "rejected" && resetsAt !== undefined) {
297
- // resetsAt is a unix-seconds stamp; retryAfterSeconds is the wait the loop honors.
298
- out.retryAfterSeconds = Math.max(0, Math.round(resetsAt - Date.now() / 1000));
299
+ // resetsAt is a unix-seconds stamp; secondsUntil folds it (×1000) to whole seconds.
300
+ out.retryAfterSeconds = secondsUntil(resetsAt * 1000, Date.now());
299
301
  }
300
302
  return [JSON.stringify(out), ...windows];
301
303
  }
@@ -0,0 +1,49 @@
1
+ /** claude's half of the write guard (src/agent/write-guard.ts holds the rule): a PreToolUse hook on
2
+ * the file-writing tools, passed to each run through `--settings`, refuses a write under the team
3
+ * root but outside the run's worktree.
4
+ *
5
+ * Hooks run under `--permission-mode bypassPermissions` too, and a hook's `deny` decision blocks
6
+ * the call and hands its reason to the model (checked live with claude 2.1.295 on 2026-10-23).
7
+ * `--setting-sources project` leaves the operator's own hooks out of a run, while `--settings`
8
+ * still loads this one. The hook gets the run's worktree as an argument rather than reading
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. */
11
+ import { fileURLToPath } from "node:url";
12
+ import { shellQuote, strayWriteRefusal } from "../write-guard.js";
13
+ /** claude's file-writing tools a team run has: claude-args.ts's `--tools` allows only these two,
14
+ * and `--tools` is a reserved flag, so no passthrough can add another. */
15
+ const WRITE_TOOLS = ["Edit", "Write"];
16
+ /** Seconds claude waits for the hook before running the tool anyway. */
17
+ const HOOK_TIMEOUT_SECONDS = 30;
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
21
+ * hook path resolves from this module, so a staged build (.tumwater/build/<sha>) runs its own
22
+ * copy. */
23
+ export function writeGuardSettings(cwd, nodeBin = process.execPath) {
24
+ const hook = fileURLToPath(new URL("./write-guard-hook.js", import.meta.url));
25
+ const command = `n=${shellQuote(nodeBin)}; [ -x "$n" ] || n=node; exec "$n" ${shellQuote(hook)} ${shellQuote(cwd)}`;
26
+ return JSON.stringify({
27
+ hooks: {
28
+ PreToolUse: [
29
+ { matcher: WRITE_TOOLS.join("|"), hooks: [{ type: "command", command, timeout: HOOK_TIMEOUT_SECONDS }] },
30
+ ],
31
+ },
32
+ });
33
+ }
34
+ /** The refusal for one PreToolUse payload (claude's hook stdin, parsed) from a run in `runDir`, or
35
+ * null to let the call run. A relative path resolves from the payload's `cwd`, where claude's
36
+ * tools would resolve it. */
37
+ export function claudeWriteRefusal(payload, runDir) {
38
+ if (typeof payload !== "object" || payload === null)
39
+ return null;
40
+ const { tool_name: tool, tool_input: input, cwd } = payload;
41
+ if (typeof tool !== "string" || !WRITE_TOOLS.includes(tool))
42
+ return null;
43
+ if (typeof input !== "object" || input === null)
44
+ return null;
45
+ const target = input.file_path;
46
+ if (typeof target !== "string" || target === "")
47
+ return null;
48
+ return strayWriteRefusal(target, runDir, typeof cwd === "string" && cwd !== "" ? cwd : runDir);
49
+ }
@@ -0,0 +1,13 @@
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
3
+ * payload on stdin. To block the write it prints a `deny` decision with the refusal as its reason:
4
+ * the model reads only the reason, where a refusal by exit 2 would also quote the whole hook
5
+ * command. It prints nothing to let the write run. Any failure of its own lets the write run, so a
6
+ * broken hook cannot stop every edit of a run; the stray-write warning in runner-run.ts still sees
7
+ * a write that slips through. The stdin read and the deny-decision printing are runGuardHook's
8
+ * (src/agent/write-guard.ts), shared with the codex PreToolUse hook; only the refusal computation
9
+ * differs, and it needs the run's checkout, which this process receives as its first argument. */
10
+ import { runGuardHook } from "../write-guard.js";
11
+ import { claudeWriteRefusal } from "./claude-write-guard.js";
12
+ const runDir = process.argv[2];
13
+ runGuardHook((payload) => (runDir ? claudeWriteRefusal(payload, runDir) : null));
@@ -0,0 +1,119 @@
1
+ /** The codex account state: the codex backend's subscription account (src/agent/
2
+ * subscription-account.ts) — which billing pays for codex runs, and the newest subscription
3
+ * usage-window reading each run reported (plans/codex-backend.md §7 "Spend, free-ness, window,
4
+ * images"). Persisted at `.tumwater/state/codex-account.json`, written only by the
5
+ * orchestrator process, so the sibling parts that throttle or display the subscription window
6
+ * read one answer instead of re-deriving it per surface.
7
+ *
8
+ * Everything here is a thin wrapper over the generic per-backend store
9
+ * (grounds/codex-backend.md "Groundwork: the subscription account is per backend", item 1),
10
+ * specialized to the backend id "codex", plus the codex-only seed (the `codex login status`
11
+ * probe that decides subscription vs API-key billing) that no other backend shares. A Codex
12
+ * run's billing and window readings reach the store through the generic observeAccount call
13
+ * the run finalizers make (run.agent === "codex"); the seed only fills the billing answer in
14
+ * before the first run so the budget gate and the API-key refusal can act on it. The file is a
15
+ * cache, never a source of truth: a run that reports billing overwrites it, and a missing or
16
+ * unreadable file reads as "no subscription". */
17
+ import { spawnSync } from "node:child_process";
18
+ import { resolvedBackendConfig } from "../../config/config-views.js";
19
+ import { observeAccount, readAccount, writeAccount, } from "../subscription-account.js";
20
+ import { codexBackend } from "./codex-backend.js";
21
+ import { codexEnv } from "./codex-env.js";
22
+ import { codexHome } from "./codex-catalog.js";
23
+ /** Read the persisted codex account state, or null when it is absent or unreadable. A file
24
+ * without a usable top-level `observedAt` reads as absent (the plan's torn-file rule). */
25
+ export function readCodexAccount(root, now = Date.now()) {
26
+ return readAccount(root, "codex", now);
27
+ }
28
+ /** Fold one run's billing and rate-window readings into the codex account file — the store's
29
+ * generic observeAccount for the "codex" backend, so a run's rollout feeding (plans/
30
+ * codex-backend.md "Spend, free-ness, window, images") lands under the codex id. */
31
+ export function observeCodexAccount(root, run, now = Date.now()) {
32
+ return observeAccount(root, "codex", run, now);
33
+ }
34
+ /** Seed the codex account file before the first run (plans/codex-backend.md §7): `codex login
35
+ * status` decides — a ChatGPT login is subscription billing, an API key is API billing.
36
+ * An existing non-api file is left alone, and a probe that cannot run or is not logged in (or
37
+ * prints something else) leaves the file absent (readCodexAccount then reads "no subscription")
38
+ * unless it must clear a sticky api refusal: an "api" answer is re-probed so the operator's
39
+ * remedy (`codex login --device-auth`) clears it next startup, and a re-probe that cannot
40
+ * decide keeps the api file so the refusal does not fail open.
41
+ * Plan and windows arrive with the first run's rollout, so the seed writes no window readings.
42
+ * Returns the account written, or null. A failed write also returns null and never throws, so
43
+ * the first codex run's observation fills the file in. */
44
+ export function seedCodexAccount(root, config, now = Date.now()) {
45
+ const existing = readCodexAccount(root, now);
46
+ // Leave an existing answer alone — a run's observation is fresher than this probe — except
47
+ // an "api" answer: the startup gate refuses on that, so it must never be sticky. Re-probe
48
+ // then, so an API-key login the operator has since fixed to a subscription (the refusal's
49
+ // named remedy, `codex login --device-auth`) clears the refusal on the next startup instead
50
+ // of a stale file refusing forever.
51
+ if (existing !== null && existing.billing !== "api")
52
+ return existing;
53
+ const billing = seedBilling(config);
54
+ if (billing === null) {
55
+ // A re-probe that cannot decide must not fail open: without an existing answer it seeds
56
+ // nothing, but a stale api answer keeps refusing — the persisted api file stays and the
57
+ // gate still reads it as a refusal rather than quietly passing (review objection 2).
58
+ return existing;
59
+ }
60
+ const account = {
61
+ billing,
62
+ observedAt: now,
63
+ windows: {},
64
+ };
65
+ try {
66
+ writeAccount(root, "codex", account);
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ return account;
72
+ }
73
+ /** Parse `codex login status` stdout into the billing it names: a ChatGPT login is a
74
+ * subscription, an API-key login is API billing, and anything else (not logged in, an unknown
75
+ * string, an IP firewall prompt) is null — the seed then stays silent and the run's rollout
76
+ * decides. Shared by seedBilling below and (from part 9/9) doctor's codex auth check, so the
77
+ * two cannot disagree about what a login means. */
78
+ export function parseCodexLoginStatus(stdout) {
79
+ if (typeof stdout !== "string" || stdout === "")
80
+ return null;
81
+ if (stdout.includes("ChatGPT"))
82
+ return "subscription";
83
+ if (stdout.includes("API key"))
84
+ return "api";
85
+ return null;
86
+ }
87
+ /** The billing the login probe names, or null when it cannot be decided. */
88
+ function seedBilling(config) {
89
+ const status = probeLoginStatus(config);
90
+ if (status === null)
91
+ return null;
92
+ if (status === "api")
93
+ return "api";
94
+ if (status === "subscription")
95
+ return "subscription";
96
+ return null;
97
+ }
98
+ /** Run `codex login status` under the same scrubbed environment a real run gets (a team
99
+ * config dir's login is what it reports), bounded by a short deadline. Null when the probe
100
+ * could not run or its stdout was unreadable. */
101
+ function probeLoginStatus(config) {
102
+ const resolved = codexBackend.resolveBin(config);
103
+ const home = codexHome(resolvedBackendConfig(config, "codex"));
104
+ const env = codexEnv(process.env, home !== undefined ? { home } : {});
105
+ try {
106
+ const result = spawnSync(resolved.bin, ["login", "status"], {
107
+ env,
108
+ encoding: "utf8",
109
+ timeout: 10_000,
110
+ killSignal: "SIGKILL",
111
+ });
112
+ if (result.status !== 0)
113
+ return null;
114
+ return parseCodexLoginStatus(typeof result.stdout === "string" ? result.stdout : null);
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
@@ -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
+ }