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,246 @@
1
+ /** How tumwater learns the write guard's trust from codex itself (plans/codex-write-guard.md
2
+ * "Trust (parts 4–5)", findings 2–3): codex runs a session-flag PreToolUse hook only when its
3
+ * `trusted_hash` matches the hash codex computed for the hook's definition, and that hash is
4
+ * opaque — no simple digest of any command or JSON spelling matched (finding 3). So this module
5
+ * asks codex's own `hooks/list` RPC for the hook's `key` and `currentHash`, and caches the
6
+ * answer per build and codex version so a run's argv never spawns `app-server` again. The
7
+ * answer is never hardcoded: the key (`/<session-flags>/config.toml:pre_tool_use:0:0` on
8
+ * 0.163.0) and the hash both come from this lookup.
9
+ *
10
+ * The lookup spawns `app-server` in its own process group under the run's env and a bounded
11
+ * deadline, kills the group whether it succeeds, times out, or fails, and makes no model
12
+ * request and needs no login (an empty `CODEX_HOME` is enough — finding 3). Part 5/9 wires it
13
+ * into a run before the codex adapter's synchronous invocation. */
14
+ import { spawn } from "node:child_process";
15
+ import { codexHookArg } from "./codex-write-guard.js";
16
+ import { readJsonFile, writeJsonAtomic } from "../../files/json-files.js";
17
+ import { releaseChildHandles, signalTree } from "../../process/process.js";
18
+ import { codexHookTrustPath } from "../../paths.js";
19
+ import { PACKAGE_JSON, packageVersion } from "../../version.js";
20
+ /** The JSON-RPC request/notification sequence one lookup sends over `app-server`'s stdio: the
21
+ * `initialize` handshake (carrying tumwater's own version), the `initialized` notification,
22
+ * then `hooks/list` for the one cwd. The reply to `id: 2` carries the hooks. */
23
+ function hookListMessages(cwd) {
24
+ const version = packageVersion(PACKAGE_JSON).version;
25
+ return [
26
+ {
27
+ jsonrpc: "2.0",
28
+ id: 1,
29
+ method: "initialize",
30
+ params: { clientInfo: { name: "tumwater", version: version ?? "unknown" } },
31
+ },
32
+ { jsonrpc: "2.0", method: "initialized" },
33
+ { jsonrpc: "2.0", id: 2, method: "hooks/list", params: { cwds: [cwd] } },
34
+ ];
35
+ }
36
+ /** The first hook in `hooks/list`'s `result.data[*].hooks` with `source: "sessionFlags"`,
37
+ * `eventName: "preToolUse"` and `command` exactly the guard definition's (finding 3), or null
38
+ * when none matches. The key and hash are returned as codex reports them. */
39
+ function findTrust(result, definition) {
40
+ if (typeof result !== "object" || result === null)
41
+ return null;
42
+ const data = result.data;
43
+ if (!Array.isArray(data))
44
+ return null;
45
+ for (const group of data) {
46
+ if (typeof group !== "object" || group === null)
47
+ continue;
48
+ const hooks = group.hooks;
49
+ if (!Array.isArray(hooks))
50
+ continue;
51
+ for (const hook of hooks) {
52
+ if (typeof hook !== "object" || hook === null)
53
+ continue;
54
+ const h = hook;
55
+ if (h.source !== "sessionFlags" || h.eventName !== "preToolUse")
56
+ continue;
57
+ if (h.command !== definition.command)
58
+ continue;
59
+ if (typeof h.key === "string" && typeof h.currentHash === "string") {
60
+ return { key: h.key, hash: h.currentHash };
61
+ }
62
+ }
63
+ }
64
+ return null;
65
+ }
66
+ /** A short name for a JSON-RPC `error` value, for the rejection message. */
67
+ function describeError(error) {
68
+ if (typeof error === "string")
69
+ return error;
70
+ if (typeof error === "object" && error !== null) {
71
+ const message = error.message;
72
+ if (typeof message === "string")
73
+ return message;
74
+ }
75
+ return JSON.stringify(error);
76
+ }
77
+ /** Ask codex for the guard hook's trust. Spawns `<bin> -c <codexHookArg(definition)>
78
+ * app-server` in `cwd` under `env`, in its own process group, sends the JSON-RPC sequence
79
+ * above over stdio, and resolves with the trust from the first matching `hooks/list` hook.
80
+ *
81
+ * It rejects on a timeout (`timeoutMs`), on the child exiting before the reply, on a JSON-RPC
82
+ * `error`, and on codex listing no session-flag PreToolUse hook with this command. The child's
83
+ * process group is SIGKILLed whether the lookup settles by success, failure, or timeout, so no
84
+ * `app-server` outlives the call. */
85
+ export function lookupCodexHookTrust(opts) {
86
+ const timeoutMs = opts.timeoutMs ?? 15_000;
87
+ return new Promise((resolve, reject) => {
88
+ const child = spawn(opts.bin, ["-c", codexHookArg(opts.definition), "app-server"], {
89
+ cwd: opts.cwd,
90
+ env: opts.env,
91
+ detached: true,
92
+ stdio: ["pipe", "pipe", "pipe"],
93
+ });
94
+ let settled = false;
95
+ let buffer = "";
96
+ const finish = (err, trust) => {
97
+ if (settled)
98
+ return;
99
+ settled = true;
100
+ clearTimeout(timer);
101
+ signalTree(child, "SIGKILL");
102
+ releaseChildHandles(child);
103
+ if (err !== null)
104
+ reject(err);
105
+ else
106
+ resolve(trust);
107
+ };
108
+ const timer = setTimeout(() => {
109
+ finish(new Error(`codex hook trust lookup timed out after ${timeoutMs} ms`));
110
+ }, timeoutMs);
111
+ timer.unref?.();
112
+ const onLine = (line) => {
113
+ let msg;
114
+ try {
115
+ msg = JSON.parse(line);
116
+ }
117
+ catch {
118
+ return; // A non-JSON-RPC line (a notice, a prompt) is not our reply.
119
+ }
120
+ if (typeof msg !== "object" || msg === null)
121
+ return;
122
+ const m = msg;
123
+ if (m.error !== undefined) {
124
+ finish(new Error(`codex hooks/list errored: ${describeError(m.error)}`));
125
+ return;
126
+ }
127
+ if (m.id !== 2)
128
+ return;
129
+ const trust = findTrust(m.result, opts.definition);
130
+ if (trust !== null)
131
+ finish(null, trust);
132
+ else
133
+ finish(new Error("codex listed no session-flag PreToolUse hook with this command"));
134
+ };
135
+ child.stdout?.setEncoding("utf8");
136
+ child.stdout?.on("data", (chunk) => {
137
+ buffer += chunk;
138
+ let nl = buffer.indexOf("\n");
139
+ while (nl >= 0) {
140
+ const line = buffer.slice(0, nl);
141
+ buffer = buffer.slice(nl + 1);
142
+ if (line.trim() !== "")
143
+ onLine(line);
144
+ nl = buffer.indexOf("\n");
145
+ }
146
+ });
147
+ child.on("error", (err) => finish(err instanceof Error ? err : new Error(String(err))));
148
+ child.on("close", (code) => {
149
+ if (!settled) {
150
+ finish(new Error(`codex app-server exited (code ${code ?? "signal"}) before answering hooks/list`));
151
+ }
152
+ });
153
+ for (const message of hookListMessages(opts.cwd)) {
154
+ try {
155
+ child.stdin?.write(JSON.stringify(message) + "\n");
156
+ }
157
+ catch {
158
+ // The pipe is gone (a failed spawn); the error handler settles the lookup.
159
+ }
160
+ }
161
+ });
162
+ }
163
+ /** The trimmed output of `<bin> --version`, or "" when the probe fails or prints nothing. It
164
+ * keys the trust cache, so a codex upgrade re-looks-up the guard's trust for the new build.
165
+ * Async (spawn, not spawnSync): a hung binary's probe must not block the event loop — every
166
+ * run's `prepare` awaits this, and a blocking probe would freeze the whole agent while waiting
167
+ * out its deadline. The child's group is killed whether it succeeds or times out. */
168
+ function codexVersion(bin, env, cwd, timeoutMs) {
169
+ return new Promise((resolve) => {
170
+ const child = spawn(bin, ["--version"], {
171
+ env,
172
+ cwd,
173
+ stdio: ["ignore", "pipe", "ignore"],
174
+ });
175
+ let out = "";
176
+ let done = false;
177
+ const finish = (value) => {
178
+ if (done)
179
+ return;
180
+ done = true;
181
+ clearTimeout(timer);
182
+ signalTree(child, "SIGKILL");
183
+ releaseChildHandles(child);
184
+ resolve(value);
185
+ };
186
+ const timer = setTimeout(() => finish(""), timeoutMs);
187
+ timer.unref?.();
188
+ child.stdout?.setEncoding("utf8");
189
+ child.stdout?.on("data", (chunk) => {
190
+ out += chunk;
191
+ });
192
+ child.on("error", () => finish(""));
193
+ child.on("close", () => finish(out.trim()));
194
+ });
195
+ }
196
+ /** Whether a cached value is a usable `{key, hash}` entry. */
197
+ function isTrust(value) {
198
+ return (typeof value === "object" &&
199
+ value !== null &&
200
+ typeof value.key === "string" &&
201
+ typeof value.hash === "string");
202
+ }
203
+ /** Resolve the guard hook's trust, cached per codex version and hook definition in
204
+ * `.tumwater/state/codex-hook-trust.json` so a run's argv never spawns `app-server` again.
205
+ * Returns the trust, rejecting on a failed lookup.
206
+ *
207
+ * The cache key is the trimmed `<bin> --version` output plus `JSON.stringify(definition)`, so
208
+ * a changed codex version, a changed hook command, or a changed `timeout` (which changes the
209
+ * hash, finding 3) each re-look-up; each entry is `{key, hash, checkedAt}`. A hit resolves
210
+ * without spawning `app-server`; a miss looks up and writes the entry atomically.
211
+ *
212
+ * `root` undefined (a hand run with no `.tumwater` root) looks up and caches nothing and never
213
+ * creates a `.tumwater` directory — a hand run once minted a phantom `$TMPDIR/.tumwater` and
214
+ * reddened every suite on the host (plans/codex-write-guard.md "Visibility"). */
215
+ export async function codexHookTrust(root, opts) {
216
+ const timeoutMs = opts.timeoutMs ?? 15_000;
217
+ if (root === undefined)
218
+ return lookupCodexHookTrust(opts);
219
+ const file = codexHookTrustPath(root);
220
+ // The version probe and the app-server lookup share one deadline (timeoutMs): a failed
221
+ // lookup — a hung binary, an app-server that never answers — never delays a run past the
222
+ // bound, and the probe never blocks the event loop (it is async, not spawnSync). The probe
223
+ // is a quick identity read, so its own slice is capped at a third of the deadline — a slow
224
+ // probe never starves the app-server lookup of its budget, and the sum of the two always
225
+ // stays within the one deadline.
226
+ const deadline = Date.now() + timeoutMs;
227
+ const version = await codexVersion(opts.bin, opts.env, opts.cwd, Math.floor(timeoutMs / 3));
228
+ const cacheKey = `${version}${JSON.stringify(opts.definition)}`;
229
+ const cache = readJsonFile(file);
230
+ const hit = cache?.[cacheKey];
231
+ if (isTrust(hit))
232
+ return { key: hit.key, hash: hit.hash };
233
+ const trust = await lookupCodexHookTrust({
234
+ ...opts,
235
+ timeoutMs: Math.max(1, deadline - Date.now()),
236
+ });
237
+ const next = { ...(cache ?? {}) };
238
+ next[cacheKey] = { key: trust.key, hash: trust.hash, checkedAt: Date.now() };
239
+ try {
240
+ writeJsonAtomic(file, next);
241
+ }
242
+ catch {
243
+ // A cache write failure must never fail the lookup whose result it is only persisting.
244
+ }
245
+ return trust;
246
+ }
@@ -0,0 +1,36 @@
1
+ /** List the paths an `apply_patch` text writes, from the patch text alone (plans/
2
+ * codex-write-guard.md "The hook (parts 1–2)"; PLANS.md "Write guard on Codex, part 1/9").
3
+ * Pure: no filesystem, no imports beyond node built-ins, so a later part can call it from the
4
+ * hook without dragging in a registry or subprocess.
5
+ *
6
+ * Codex's PreToolUse payload carries the patch text, not a path, and a patch is applied even
7
+ * when it is truncated or its `*** Begin Patch` / `*** End Patch` markers are missing — so this
8
+ * scans every line, marker or not, and trusts only lines that begin at column 0 with a header.
9
+ * A hunk line (`+`, `-`, a space, `@@`) never counts as a header even when its text reads like
10
+ * one, because a hunk prefix shifts `*** ` off column 0. Paths are kept as written; the caller
11
+ * resolves relative ones against the run's directory. */
12
+ /** Headers that name a path the patch writes, in the order a line may spell them. */
13
+ const HEADERS = [
14
+ "*** Add File: ",
15
+ "*** Delete File: ",
16
+ "*** Update File: ",
17
+ "*** Move to: ",
18
+ ];
19
+ /** The paths an `apply_patch` text writes, in first-appearance order, without duplicates. */
20
+ export function patchWritePaths(patch) {
21
+ const seen = new Set();
22
+ const paths = [];
23
+ for (const line of patch.split("\n")) {
24
+ for (const header of HEADERS) {
25
+ if (line.startsWith(header)) {
26
+ const path = line.slice(header.length).replace(/[\s\r]+$/, "");
27
+ if (path !== "" && !seen.has(path)) {
28
+ seen.add(path);
29
+ paths.push(path);
30
+ }
31
+ break;
32
+ }
33
+ }
34
+ }
35
+ return paths;
36
+ }
@@ -0,0 +1,179 @@
1
+ /** The codex adapter's rollout reader (plans/codex-backend.md "The rollout read that
2
+ * completes it"). `codex exec --json`'s stream reports usage only once, at the end, counting
3
+ * the WHOLE thread (finding 4) — a resume would otherwise charge its earlier run to the follow-
4
+ * up. The authoritative per-run figures live in `$CODEX_HOME/sessions/…/rollout-*-<id>.jsonl`,
5
+ * Codex's own JSONL record of a session, where each run's `task_started` .. `task_complete`
6
+ * block carries its own `token_usage_record`, `token_count` (the rate limits) and
7
+ * `turn_context` (the model that actually ran). This module reads just that block: what the
8
+ * stream lacks — this run's own usage, the billing and the two usage windows.
9
+ *
10
+ * Pure over text (plus one filesystem walk for `findRollout`): no spawn, no billing math, no
11
+ * session state. Part 7/9's adapter wires the lines into the canonical stream in `afterRun`.
12
+ */
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import { isJsonObject, nonNegativeNumber, objectOrEmpty, parseJsonObject } from "../../files/json-object.js";
16
+ import { windowTypeName } from "./codex-stream.js";
17
+ function str(value) {
18
+ return typeof value === "string" ? value : undefined;
19
+ }
20
+ function finite(value) {
21
+ return nonNegativeNumber(value, undefined);
22
+ }
23
+ /** The rate-limit status of one window: `rejected` when the account hit it outright,
24
+ * `allowed_warning` when it is at or past 90% full, else `allowed` (design §5, decision 7). */
25
+ function windowStatus(windowType, rateLimitReachedType, usedPercent) {
26
+ if (rateLimitReachedType === windowType)
27
+ return "rejected";
28
+ if (usedPercent !== undefined && usedPercent >= 100)
29
+ return "rejected";
30
+ if (usedPercent !== undefined && usedPercent >= 90)
31
+ return "allowed_warning";
32
+ return "allowed";
33
+ }
34
+ /** Read the records of ONE turn out of a rollout's text. A rollout is one file per thread that
35
+ * appends a new `task_started` block per run, so the adapter's `afterRun` read (which knows
36
+ * only the thread id that names the file, never a turn id) reads the LAST block — everything
37
+ * after the last `task_started`. `sinceTurnStart` (optional) selects a specific turn's block
38
+ * instead, which pins per-turn isolation: the thread total hides a resume's own figures
39
+ * (finding 4). `requestedModel` is the `-m` tumwater passed; a turn whose `turn_context.model`
40
+ * differs reports the switch. */
41
+ export function rolloutLines(text, opts) {
42
+ const { sinceTurnStart, requestedModel } = opts;
43
+ let currentTurn;
44
+ let sawTurn = false;
45
+ let lastUsage;
46
+ let lastRateLimits;
47
+ let model;
48
+ let planType;
49
+ for (const line of text.split("\n")) {
50
+ if (!line.trim())
51
+ continue;
52
+ const parsed = parseJsonObject(line);
53
+ if (!parsed)
54
+ continue; // a torn or foreign line belongs to no turn
55
+ const payload = objectOrEmpty(parsed.payload);
56
+ // `task_started`, `task_complete` and `token_count` come wrapped in an `event_msg` whose
57
+ // `payload.type` discriminates; `turn_context` and `token_usage_record` name themselves in
58
+ // the outer `type`. Read either.
59
+ const ptype = str(parsed.type) === "event_msg" ? str(payload.type) : str(parsed.type);
60
+ if (ptype === "task_started") {
61
+ currentTurn = str(payload.turn_id);
62
+ // Each new task_started begins a fresh turn (a resume appends a second run to the same
63
+ // file), so the collectors restart at the block we are reading. With no turn id (the
64
+ // adapter's read) every task_started restarts them and the last block wins; with one,
65
+ // only the requested turn's does.
66
+ if (sinceTurnStart === undefined || currentTurn === sinceTurnStart) {
67
+ sawTurn = true;
68
+ lastUsage = undefined;
69
+ lastRateLimits = undefined;
70
+ model = undefined;
71
+ planType = undefined;
72
+ }
73
+ continue;
74
+ }
75
+ if (sinceTurnStart !== undefined && currentTurn !== sinceTurnStart)
76
+ continue;
77
+ switch (ptype) {
78
+ case "turn_context":
79
+ if (model === undefined)
80
+ model = str(payload.model);
81
+ break;
82
+ case "token_usage_record": {
83
+ const turnUsage = objectOrEmpty(payload.turn_token_usage);
84
+ if (isJsonObject(turnUsage) && (turnUsage.input_tokens !== undefined || turnUsage.output_tokens !== undefined)) {
85
+ lastUsage = turnUsage;
86
+ }
87
+ break;
88
+ }
89
+ case "token_count": {
90
+ const rateLimits = objectOrEmpty(payload.rate_limits);
91
+ lastRateLimits = rateLimits;
92
+ const plan = str(rateLimits.plan_type);
93
+ if (plan !== undefined)
94
+ planType = plan;
95
+ break;
96
+ }
97
+ default:
98
+ break;
99
+ }
100
+ }
101
+ // No matching task_started, or the turn's usage record is missing or malformed (a truncated
102
+ // or torn file): there is no reading. (currentTurn will have moved on to a later run's
103
+ // task_started in a resume-shared file; sawTurn — not currentTurn — is the real signal.)
104
+ if (!sawTurn || !isJsonObject(lastUsage)) {
105
+ return { lines: [] };
106
+ }
107
+ const lines = [];
108
+ const input = finite(lastUsage.input_tokens) ?? 0;
109
+ const cacheRead = finite(lastUsage.cached_input_tokens) ?? 0;
110
+ lines.push(JSON.stringify({
111
+ type: "run_usage",
112
+ input: Math.max(0, input - cacheRead),
113
+ output: finite(lastUsage.output_tokens) ?? 0,
114
+ cacheRead,
115
+ cacheWrite: finite(lastUsage.cache_write_input_tokens) ?? 0,
116
+ }));
117
+ if (planType !== undefined) {
118
+ // A reading with a plan_type bills on the subscription (finding 5; API-key login is
119
+ // refused, decision 5).
120
+ lines.push(JSON.stringify({ type: "billing", billing: "subscription" }));
121
+ }
122
+ // Two windows when present: the primary (5 h) and the secondary (weekly). A killed run's
123
+ // rollout has a token_usage_record but no token_count, so it yields run_usage with no
124
+ // rate_limit (finding 23).
125
+ if (lastRateLimits !== undefined) {
126
+ const reached = str(lastRateLimits.rate_limit_reached_type);
127
+ for (const key of ["primary", "secondary"]) {
128
+ const windowRaw = lastRateLimits[key];
129
+ if (!isJsonObject(windowRaw))
130
+ continue;
131
+ const minutes = finite(windowRaw.window_minutes);
132
+ if (minutes === undefined)
133
+ continue;
134
+ const typeName = windowTypeName(minutes);
135
+ const usedPercent = finite(windowRaw.used_percent);
136
+ lines.push(JSON.stringify({
137
+ type: "rate_limit",
138
+ rateLimitType: typeName,
139
+ status: windowStatus(typeName, reached, usedPercent),
140
+ ...(usedPercent !== undefined ? { utilization: usedPercent / 100 } : {}),
141
+ ...(finite(windowRaw.resets_at) !== undefined ? { resetsAt: finite(windowRaw.resets_at) } : {}),
142
+ }));
143
+ }
144
+ }
145
+ const switchedTo = model !== undefined && requestedModel !== undefined && model !== requestedModel ? model : undefined;
146
+ return { lines, ...(switchedTo !== undefined ? { switchedTo } : {}) };
147
+ }
148
+ /** Locate the rollout file of `$CODEX_HOME` that names the session `id`: the first
149
+ * `sessions/…/rollout-*-<id>.jsonl` under `home`, or null. It takes no start date and does no
150
+ * date math — the id match alone decides (finding 5: Codex's rollout name carries local time,
151
+ * so a UTC-derived bound can miss the file; the sessions tree is one directory per active day,
152
+ * so the walk stays small). */
153
+ export function findRollout(home, id) {
154
+ const sessionsRoot = path.join(home, "sessions");
155
+ if (!fs.existsSync(sessionsRoot))
156
+ return null;
157
+ const suffix = `-${id}.jsonl`;
158
+ const stack = [sessionsRoot];
159
+ while (stack.length > 0) {
160
+ const dir = stack.pop();
161
+ let entries;
162
+ try {
163
+ entries = fs.readdirSync(dir, { withFileTypes: true });
164
+ }
165
+ catch {
166
+ continue; // an unreadable dir is simply no match, never a failure
167
+ }
168
+ for (const entry of entries) {
169
+ const full = path.join(dir, entry.name);
170
+ if (entry.isDirectory()) {
171
+ stack.push(full);
172
+ }
173
+ else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(suffix)) {
174
+ return full;
175
+ }
176
+ }
177
+ }
178
+ return null;
179
+ }