tumwater 0.2.0 → 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 (446) 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 +42 -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 +4 -3
  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 +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 +246 -55
  41. package/dist/src/backlog/backlog-cli-view.js +280 -0
  42. package/dist/src/backlog/backlog-cli.js +85 -274
  43. package/dist/src/backlog/backlog-commit.js +4 -5
  44. package/dist/src/backlog/backlog-eligibility.js +175 -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 +254 -0
  48. package/dist/src/backlog/backlog-release.js +117 -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-culprit.js +94 -0
  56. package/dist/src/baseline/main-guardrails.js +209 -0
  57. package/dist/src/baseline/main-red.js +139 -32
  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 +9 -11
  61. package/dist/src/brief/brief.js +31 -51
  62. package/dist/src/budget/budget.js +30 -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 +1 -2
  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 +118 -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 +150 -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 +308 -590
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-role-command.js +420 -0
  84. package/dist/src/cli/cli-start-summary.js +160 -0
  85. package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
  86. package/dist/src/cli/completion.js +149 -22
  87. package/dist/src/cli/config-commands.js +41 -18
  88. package/dist/src/cli/follow-view.js +75 -0
  89. package/dist/src/cli/help.js +189 -73
  90. package/dist/src/cli/log-commands.js +148 -82
  91. package/dist/src/cli/question-commands.js +205 -10
  92. package/dist/src/cli.js +106 -43
  93. package/dist/src/collections.js +26 -10
  94. package/dist/src/concurrency/check-permit.js +20 -3
  95. package/dist/src/concurrency/lock.js +17 -13
  96. package/dist/src/concurrency/provider-lease.js +44 -8
  97. package/dist/src/concurrency/semaphore.js +102 -26
  98. package/dist/src/config/config-aliases.js +0 -1
  99. package/dist/src/config/config-editable-keys.js +13 -1
  100. package/dist/src/config/config-fallbacks.js +93 -56
  101. package/dist/src/config/config-field-checks.js +40 -10
  102. package/dist/src/config/config-keys.js +30 -8
  103. package/dist/src/config/config-live.js +63 -24
  104. package/dist/src/config/config-request.js +137 -0
  105. package/dist/src/config/config-schema.js +31 -24
  106. package/dist/src/config/config-selector-checks.js +123 -0
  107. package/dist/src/config/config-validation-agents.js +80 -0
  108. package/dist/src/config/config-validation-model.js +116 -0
  109. package/dist/src/config/config-validation.js +141 -199
  110. package/dist/src/config/config-views.js +127 -53
  111. package/dist/src/config/config-write-core.js +135 -0
  112. package/dist/src/config/config-write.js +131 -264
  113. package/dist/src/config/config.js +48 -38
  114. package/dist/src/config/model-selector.js +34 -34
  115. package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
  116. package/dist/src/doctor/doctor-backlog-plans.js +263 -0
  117. package/dist/src/doctor/doctor-backlog.js +100 -496
  118. package/dist/src/doctor/doctor-check-names.js +81 -0
  119. package/dist/src/doctor/doctor-checks.js +52 -579
  120. package/dist/src/doctor/doctor-claude-checks.js +145 -0
  121. package/dist/src/doctor/doctor-codex-checks.js +279 -0
  122. package/dist/src/doctor/doctor-contention-checks.js +380 -0
  123. package/dist/src/doctor/doctor-holds.js +82 -66
  124. package/dist/src/doctor/doctor-install.js +2 -1
  125. package/dist/src/doctor/doctor-model-checks.js +236 -402
  126. package/dist/src/doctor/doctor-notify.js +3 -4
  127. package/dist/src/doctor/doctor-opencode-checks.js +162 -0
  128. package/dist/src/doctor/doctor-orphans.js +182 -63
  129. package/dist/src/doctor/doctor-project-checks.js +303 -0
  130. package/dist/src/doctor/doctor.js +121 -77
  131. package/dist/src/doctor/image-probe.js +21 -8
  132. package/dist/src/doctor/outcome.js +3 -2
  133. package/dist/src/doctor/probe.js +7 -11
  134. package/dist/src/events/event-format.js +181 -86
  135. package/dist/src/events/event-labels.js +12 -5
  136. package/dist/src/events/event-legacy.js +39 -15
  137. package/dist/src/events/event-read.js +39 -39
  138. package/dist/src/events/event-tally.js +16 -0
  139. package/dist/src/events/event-window.js +60 -24
  140. package/dist/src/events/events.js +30 -13
  141. package/dist/src/events/notify.js +7 -7
  142. package/dist/src/failure/failure-cluster.js +12 -12
  143. package/dist/src/failure/failure-data.js +56 -56
  144. package/dist/src/failure/failure-render.js +31 -23
  145. package/dist/src/failure/failure-state-change.js +17 -12
  146. package/dist/src/failure/time-spend.js +99 -35
  147. package/dist/src/files/files.js +111 -35
  148. package/dist/src/files/json-files.js +3 -3
  149. package/dist/src/files/json-object.js +28 -7
  150. package/dist/src/files/stat-cache.js +2 -2
  151. package/dist/src/files/tail.js +1 -1
  152. package/dist/src/gates/bootstrap-gates.js +4 -5
  153. package/dist/src/gates/budget-gates.js +177 -84
  154. package/dist/src/gates/disk-gate.js +1 -1
  155. package/dist/src/gates/gate-polls.js +108 -58
  156. package/dist/src/gates/maintenance-quota.js +10 -13
  157. package/dist/src/gates/pause-gates.js +28 -10
  158. package/dist/src/gates/readiness.js +7 -3
  159. package/dist/src/gates/role-cap-gates.js +8 -8
  160. package/dist/src/gates/startup-gate.js +94 -32
  161. package/dist/src/gates/streak-gate.js +54 -30
  162. package/dist/src/git/commit-message.js +77 -46
  163. package/dist/src/git/git-diff.js +16 -10
  164. package/dist/src/git/git-run.js +79 -38
  165. package/dist/src/git/git.js +36 -34
  166. package/dist/src/git/slot-salvage.js +253 -0
  167. package/dist/src/git/slots-state.js +6 -6
  168. package/dist/src/git/worktree-pool.js +132 -38
  169. package/dist/src/git/worktree-use.js +4 -11
  170. package/dist/src/git/worktree.js +94 -37
  171. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  172. package/dist/src/gui/gui-args.js +79 -8
  173. package/dist/src/gui/gui-backlog-commands.js +42 -18
  174. package/dist/src/gui/gui-backlog-endpoints.js +155 -0
  175. package/dist/src/gui/gui-command.js +8 -13
  176. package/dist/src/gui/gui-config-commands.js +94 -72
  177. package/dist/src/gui/gui-endpoint-commands.js +4 -3
  178. package/dist/src/gui/gui-endpoint-common.js +4 -4
  179. package/dist/src/gui/gui-endpoints.js +11 -380
  180. package/dist/src/gui/gui-history-endpoints.js +301 -0
  181. package/dist/src/gui/gui-operator-commands.js +108 -13
  182. package/dist/src/gui/gui-prompt-commands.js +11 -14
  183. package/dist/src/gui/gui-report-endpoints.js +90 -0
  184. package/dist/src/gui/gui-server.js +11 -4
  185. package/dist/src/gui/gui-team-commands.js +7 -7
  186. package/dist/src/gui/http-body.js +1 -1
  187. package/dist/src/history/history-data.js +122 -234
  188. package/dist/src/history/history-queued.js +147 -0
  189. package/dist/src/history/history-render.js +42 -22
  190. package/dist/src/history/history.js +193 -49
  191. package/dist/src/inbox/inbox-attachments.js +29 -30
  192. package/dist/src/inbox/inbox-cancel.js +10 -11
  193. package/dist/src/inbox/inbox-edit.js +9 -9
  194. package/dist/src/inbox/inbox-submit.js +18 -18
  195. package/dist/src/inbox/inbox-top.js +25 -9
  196. package/dist/src/inbox/inbox.js +42 -43
  197. package/dist/src/inbox/pending-prompt.js +26 -26
  198. package/dist/src/inbox/prompt-commands.js +62 -49
  199. package/dist/src/inbox/prompt-not-before.js +2 -2
  200. package/dist/src/init/init-templates.js +1 -2
  201. package/dist/src/init/init.js +28 -23
  202. package/dist/src/merge/backlog-conflicts.js +215 -35
  203. package/dist/src/merge/merge-batch.js +14 -8
  204. package/dist/src/merge/merge-cancel.js +53 -0
  205. package/dist/src/merge/merge-check-failures.js +44 -22
  206. package/dist/src/merge/merge-core.js +81 -32
  207. package/dist/src/merge/merge-diff.js +5 -4
  208. package/dist/src/merge/merge-drain.js +150 -11
  209. package/dist/src/merge/merge-git.js +177 -38
  210. package/dist/src/merge/merge-pipeline.js +15 -3
  211. package/dist/src/merge/merge-post-review.js +90 -0
  212. package/dist/src/merge/merge-queue-render.js +49 -11
  213. package/dist/src/merge/merge-queue.js +31 -15
  214. package/dist/src/merge/merge-requeue.js +102 -0
  215. package/dist/src/merge/merge-slot.js +61 -16
  216. package/dist/src/merge/merge-stack.js +33 -11
  217. package/dist/src/merge/merge-to-main.js +86 -27
  218. package/dist/src/merge/merge-top.js +69 -0
  219. package/dist/src/merge/merge-verify.js +12 -10
  220. package/dist/src/notes-cli.js +30 -15
  221. package/dist/src/operator/operator-commands.js +192 -81
  222. package/dist/src/operator/operator-intent.js +69 -42
  223. package/dist/src/operator/operator-requests.js +35 -40
  224. package/dist/src/operator/retire.js +5 -6
  225. package/dist/src/operator/revert.js +142 -34
  226. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  227. package/dist/src/orchestrator/orchestrator-poll.js +320 -0
  228. package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
  229. package/dist/src/orchestrator/orchestrator.js +128 -369
  230. package/dist/src/orchestrator/retention.js +42 -8
  231. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  232. package/dist/src/orchestrator/scheduling-scope.js +201 -0
  233. package/dist/src/orchestrator/scheduling-skip.js +185 -0
  234. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  235. package/dist/src/orchestrator/startup-migrations.js +11 -7
  236. package/dist/src/paths.js +116 -49
  237. package/dist/src/pi/command-shape.js +12 -1
  238. package/dist/src/pi/pi-args.js +6 -4
  239. package/dist/src/pi/pi-backend.js +6 -6
  240. package/dist/src/pi/pi-bin.js +54 -63
  241. package/dist/src/pi/pi-listed-models.js +53 -0
  242. package/dist/src/pi/pi-models.js +63 -30
  243. package/dist/src/pi/pi-run-result.js +1 -1
  244. package/dist/src/pi/pi-settings.js +1 -1
  245. package/dist/src/pi/pi-stream.js +6 -6
  246. package/dist/src/pi/pi-watchdogs.js +18 -19
  247. package/dist/src/pi-extension/bounded-output.js +5 -3
  248. package/dist/src/pi-extension/context-budget.js +2 -2
  249. package/dist/src/pi-extension/context-shake.js +1 -1
  250. package/dist/src/pi-extension/context-window.js +0 -1
  251. package/dist/src/pi-extension/full-output.js +3 -10
  252. package/dist/src/pi-extension/strip-thinking.js +0 -1
  253. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  254. package/dist/src/pi-extension/write-guard.js +37 -0
  255. package/dist/src/process/launch-services.js +1 -2
  256. package/dist/src/process/node-bin.js +16 -0
  257. package/dist/src/process/process-group.js +8 -18
  258. package/dist/src/process/process.js +41 -2
  259. package/dist/src/process/run-marker.js +18 -6
  260. package/dist/src/process/supervisor.js +22 -21
  261. package/dist/src/prompt/dialect.js +17 -2
  262. package/dist/src/prompt/gate-notes.js +203 -0
  263. package/dist/src/prompt/gate-prompts.js +121 -211
  264. package/dist/src/prompt/notes-backlog.js +181 -0
  265. package/dist/src/prompt/principles.js +1 -2
  266. package/dist/src/prompt/prompt-followup.js +19 -14
  267. package/dist/src/prompt/prompt.js +129 -204
  268. package/dist/src/redeploy/redeploy-policy.js +24 -19
  269. package/dist/src/redeploy/redeploy-probes.js +1 -1
  270. package/dist/src/redeploy/redeploy.js +2 -3
  271. package/dist/src/redeploy/redeployer.js +20 -20
  272. package/dist/src/redeploy/self-reload.js +2 -1
  273. package/dist/src/report/report-data.js +161 -77
  274. package/dist/src/report/report-fold.js +129 -52
  275. package/dist/src/report/report-main-red.js +55 -0
  276. package/dist/src/report/report-render.js +133 -64
  277. package/dist/src/report/report-types.js +1 -0
  278. package/dist/src/report/report.js +26 -9
  279. package/dist/src/request-timeouts.js +2 -2
  280. package/dist/src/review/known-flakes.js +100 -1
  281. package/dist/src/review/post-merge-review.js +106 -0
  282. package/dist/src/review/protected-edits.js +4 -5
  283. package/dist/src/review/review-followup.js +8 -8
  284. package/dist/src/review/review-precheck.js +66 -23
  285. package/dist/src/review/review-run.js +395 -0
  286. package/dist/src/review/review-verdict.js +2 -2
  287. package/dist/src/review/review.js +155 -360
  288. package/dist/src/review/secret-leaks.js +36 -16
  289. package/dist/src/review/suite-rerun.js +0 -1
  290. package/dist/src/roles/legacy-role-migration.js +80 -7
  291. package/dist/src/roles/role-catalog.js +69 -12
  292. package/dist/src/roles/role-guidance.js +55 -20
  293. package/dist/src/roles/role-labels.js +1 -2
  294. package/dist/src/roles/role-render.js +67 -50
  295. package/dist/src/roles/role-view.js +59 -47
  296. package/dist/src/roles/roles.js +31 -31
  297. package/dist/src/roles/runner-ids.js +97 -0
  298. package/dist/src/run/acceptance-greps.js +119 -0
  299. package/dist/src/run/feedback-filings.js +51 -0
  300. package/dist/src/{tick → run}/qa-coverage.js +9 -27
  301. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  302. package/dist/src/run/run-detail-data.js +179 -0
  303. package/dist/src/run/run-detail-render.js +98 -0
  304. package/dist/src/run/run-detail.js +168 -0
  305. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
  306. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  307. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
  308. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  309. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
  310. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  311. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  312. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
  313. package/dist/src/run/selfcheck-digest.js +40 -0
  314. package/dist/src/run/stage-backlog-checks.js +249 -0
  315. package/dist/src/run/stage-check.js +172 -0
  316. package/dist/src/run/stage-path-checks.js +730 -0
  317. package/dist/src/{loop → runner}/leftover.js +80 -25
  318. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  319. package/dist/src/runner/revision.js +202 -0
  320. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  321. package/dist/src/runner/runner-outcome.js +107 -0
  322. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  323. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
  324. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
  325. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  326. package/dist/src/runner/runner-state.js +102 -0
  327. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  328. package/dist/src/scheduling/backoff.js +38 -38
  329. package/dist/src/scheduling/claims.js +35 -22
  330. package/dist/src/scheduling/once-round.js +22 -22
  331. package/dist/src/scheduling/quiet-hours.js +5 -6
  332. package/dist/src/scheduling/scheduling.js +74 -64
  333. package/dist/src/scheduling/work-merged-cache.js +2 -2
  334. package/dist/src/status/status-budget.js +55 -47
  335. package/dist/src/status/status-data.js +43 -16
  336. package/dist/src/status/status-inbox.js +2 -2
  337. package/dist/src/status/status-install.js +0 -1
  338. package/dist/src/status/status-polls.js +28 -28
  339. package/dist/src/status/status-roles.js +39 -13
  340. package/dist/src/team/error-storm.js +8 -8
  341. package/dist/src/team/failure-spread.js +2 -2
  342. package/dist/src/team/orchestrator-info.js +15 -9
  343. package/dist/src/team/reclaim.js +5 -6
  344. package/dist/src/team/team-hold.js +7 -7
  345. package/dist/src/team/team-polls.js +91 -50
  346. package/dist/src/team/team-state.js +8 -2
  347. package/dist/src/text/datetime.js +63 -17
  348. package/dist/src/text/format.js +48 -11
  349. package/dist/src/text/markdown.js +8 -5
  350. package/dist/src/text/phrases.js +57 -23
  351. package/dist/src/text/suggest.js +1 -1
  352. package/dist/src/text/text.js +67 -13
  353. package/dist/src/ui/badges.js +65 -37
  354. package/dist/src/ui/gui/gui-client-backlog.js +292 -0
  355. package/dist/src/ui/gui/gui-client-boot.js +32 -26
  356. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  357. package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
  358. package/dist/src/ui/gui/gui-client-drawer.js +126 -285
  359. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  360. package/dist/src/ui/gui/gui-client-history.js +434 -67
  361. package/dist/src/ui/gui/gui-client-labels.js +1 -2
  362. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  363. package/dist/src/ui/gui/gui-client-model.js +27 -36
  364. package/dist/src/ui/gui/gui-client-operator.js +55 -51
  365. package/dist/src/ui/gui/gui-client-pending.js +227 -23
  366. package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
  367. package/dist/src/ui/gui/gui-client-report.js +87 -427
  368. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  369. package/dist/src/ui/gui/gui-client-settings.js +56 -4
  370. package/dist/src/ui/gui/gui-client-team.js +25 -465
  371. package/dist/src/ui/gui/gui-client.js +27 -25
  372. package/dist/src/ui/gui/gui-icons.js +0 -2
  373. package/dist/src/ui/gui/gui-page.js +9 -7
  374. package/dist/src/ui/gui/gui-styles.js +34 -34
  375. package/dist/src/ui/progress-data.js +9 -11
  376. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
  377. package/dist/src/ui/status-model.js +133 -125
  378. package/dist/src/ui/status-payload.js +136 -49
  379. package/dist/src/ui/status-render.js +101 -82
  380. package/dist/src/ui/team-alerts.js +248 -57
  381. package/dist/src/ui/tone.js +5 -6
  382. package/dist/src/ui/transcript-run.js +58 -0
  383. package/dist/src/ui/transcript-tail.js +28 -20
  384. package/dist/src/ui/transcript.js +63 -23
  385. package/dist/src/ui/tui/tui-app.js +0 -1
  386. package/dist/src/ui/tui/tui-backlog.js +51 -14
  387. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  388. package/dist/src/ui/tui/tui-editor-attach.js +3 -5
  389. package/dist/src/ui/tui/tui-editor-backends.js +132 -0
  390. package/dist/src/ui/tui/tui-editor-brief.js +10 -12
  391. package/dist/src/ui/tui/tui-editor-budget.js +35 -31
  392. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  393. package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
  394. package/dist/src/ui/tui/tui-editor-config.js +86 -0
  395. package/dist/src/ui/tui/tui-editor-entry.js +36 -47
  396. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  397. package/dist/src/ui/tui/tui-editor-instances.js +37 -0
  398. package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
  399. package/dist/src/ui/tui/tui-editor-interval.js +11 -9
  400. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  401. package/dist/src/ui/tui/tui-editor-notes.js +3 -5
  402. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  403. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  404. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  405. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  406. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  407. package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
  408. package/dist/src/ui/tui/tui-editor-retire.js +4 -6
  409. package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
  410. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  411. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  412. package/dist/src/ui/tui/tui-editor-session.js +60 -34
  413. package/dist/src/ui/tui/tui-editor-slots.js +23 -0
  414. package/dist/src/ui/tui/tui-editors.js +35 -2
  415. package/dist/src/ui/tui/tui-frame.js +56 -11
  416. package/dist/src/ui/tui/tui-history.js +8 -9
  417. package/dist/src/ui/tui/tui-input.js +13 -11
  418. package/dist/src/ui/tui/tui-keymap.js +0 -1
  419. package/dist/src/ui/tui/tui-keys.js +24 -9
  420. package/dist/src/ui/tui/tui-pane-activity.js +14 -11
  421. package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
  422. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  423. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  424. package/dist/src/ui/tui/tui-pane-health.js +58 -8
  425. package/dist/src/ui/tui/tui-pane-history.js +402 -82
  426. package/dist/src/ui/tui/tui-pane-pending.js +308 -13
  427. package/dist/src/ui/tui/tui-pane-queued.js +71 -55
  428. package/dist/src/ui/tui/tui-pane-report.js +46 -14
  429. package/dist/src/ui/tui/tui-pane-session.js +25 -6
  430. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  431. package/dist/src/ui/tui/tui-pane.js +80 -23
  432. package/dist/src/ui/tui/tui-panes.js +100 -30
  433. package/dist/src/ui/tui/tui.js +14 -14
  434. package/dist/src/verdict/fix-claim.js +82 -32
  435. package/dist/src/verdict/no-change.js +4 -4
  436. package/dist/src/verdict/refusal.js +40 -16
  437. package/dist/src/verdict/reply-contract.js +42 -31
  438. package/dist/src/version.js +0 -1
  439. package/package.json +2 -1
  440. package/dist/src/loop/revision.js +0 -70
  441. package/dist/src/roles/loop-ids.js +0 -77
  442. package/dist/src/tick/stage-check.js +0 -419
  443. package/dist/src/tick/telemetry-digest.js +0 -25
  444. package/dist/src/tick/tick-detail-data.js +0 -108
  445. package/dist/src/tick/tick-detail.js +0 -163
  446. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -11,10 +11,10 @@ import { canonicalRoleId } from "../roles/legacy-role-ids.js";
11
11
  * here would show `dedupe` while the role is still `dry`. */
12
12
  /** An old event type name → today's name. */
13
13
  const LEGACY_EVENT_TYPES = {
14
- tick_start: "run_start", // vocabulary-ok: legacy event name read from old logs
15
- tick_end: "run_end", // vocabulary-ok: legacy event name read from old logs
16
- tick_deferred: "run_deferred", // vocabulary-ok: legacy event name read from old logs
17
- tick_aborted: "run_aborted", // vocabulary-ok: legacy event name read from old logs
14
+ tick_start: "run_start", // vocabulary-ok: legacy event name read from old logs // retired-name-ok: legacy name
15
+ tick_end: "run_end", // vocabulary-ok: legacy event name read from old logs // retired-name-ok: legacy name
16
+ tick_deferred: "run_deferred", // vocabulary-ok: legacy event name read from old logs // retired-name-ok: legacy name
17
+ tick_aborted: "run_aborted", // vocabulary-ok: legacy event name read from old logs // retired-name-ok: legacy name
18
18
  // retired-name-ok:start old event names
19
19
  fleet_paused: "team_paused", // vocabulary-ok: legacy event name read from old logs
20
20
  fleet_resumed: "team_resumed", // vocabulary-ok: legacy event name read from old logs
@@ -24,23 +24,29 @@ const LEGACY_EVENT_TYPES = {
24
24
  landed: "merge_done", // vocabulary-ok: legacy event name read from old logs
25
25
  land_failed: "merge_failed", // vocabulary-ok: legacy event name read from old logs
26
26
  // retired-name-ok:end
27
+ // retired-name-ok:start old subscription-window event names
28
+ claude_window_reserved: "subscription_window_reserved", // vocabulary-ok: legacy event name read from old logs
29
+ claude_window_warning: "subscription_window_warning", // vocabulary-ok: legacy event name read from old logs
30
+ claude_window_limited: "subscription_window_limited", // vocabulary-ok: legacy event name read from old logs
31
+ claude_window_reset: "subscription_window_reset", // vocabulary-ok: legacy event name read from old logs
32
+ // retired-name-ok:end
27
33
  };
28
34
  /** The ordered field rewrites; later parts of the series append here, and they run in order. */
29
35
  const LEGACY_EVENT_REWRITES = [
30
36
  {
31
37
  // The run number rode `tick` on run_start, run_end, prompt_taken, image_blind, image_reroute.
32
- name: "tick-to-run",
38
+ name: "tick-to-run", // retired-name-ok: legacy name
33
39
  apply(ev) {
34
40
  const e = ev;
35
- if ("tick" in e && !("run" in e)) { // vocabulary-ok: legacy field name read from old logs
36
- e.run = e.tick; // vocabulary-ok: legacy field name read from old logs
37
- delete e.tick; // vocabulary-ok: legacy field name read from old logs
41
+ if ("tick" in e && !("run" in e)) { // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field
42
+ e.run = e.tick; // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field
43
+ delete e.tick; // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field
38
44
  }
39
45
  },
40
46
  },
41
47
  {
42
48
  // build_check.scope and conflict_handback.reason said "landing" for what is now "merge".
43
- name: "landing-to-merge",
49
+ name: "merge-scope-value",
44
50
  apply(ev) {
45
51
  const e = ev;
46
52
  if (e.type === "build_check" && e.scope === "landing")
@@ -57,14 +63,14 @@ const LEGACY_EVENT_REWRITES = [
57
63
  if (e.type !== "slot_wait")
58
64
  return;
59
65
  if (e.purpose === "tick")
60
- e.purpose = "run"; // vocabulary-ok: legacy event value read from old logs
66
+ e.purpose = "run"; // vocabulary-ok: legacy event value read from old logs // retired-name-ok: legacy name
61
67
  if (e.purpose === "vet")
62
68
  e.purpose = "verify"; // vocabulary-ok: legacy event value read from old logs
63
69
  },
64
70
  },
65
71
  {
66
72
  // The restart event's drain fields became finish fields.
67
- name: "restart-drain-to-finish",
73
+ name: "restart-finish-fields",
68
74
  apply(ev) {
69
75
  const e = ev;
70
76
  if (e.type !== "restart")
@@ -88,16 +94,16 @@ const LEGACY_EVENT_REWRITES = [
88
94
  // call site that still passes `source: "harness"` (the warn helpers' callers) maps too.
89
95
  // Kept permanently though no TypeScript type accepts `loop` any more: old logs and a
90
96
  // still-running old supervisor keep writing it, so the read path and logEvent need this.
91
- name: "loop-to-source",
97
+ name: "writer-id-to-source",
92
98
  apply(ev) {
93
99
  const e = ev;
94
- if (typeof e.loop === "string") { // vocabulary-ok: legacy field name read from old logs
100
+ if (typeof e.loop === "string") { // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field, read permanently
95
101
  if (e.type === "claim" && e.source === "staged" && !("origin" in e)) {
96
102
  e.origin = e.source;
97
103
  delete e.source;
98
104
  }
99
- e.source = e.loop; // vocabulary-ok: legacy field name read from old logs
100
- delete e.loop; // vocabulary-ok: legacy field name read from old logs
105
+ e.source = e.loop; // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field, read permanently
106
+ delete e.loop; // vocabulary-ok: legacy field name read from old logs // retired-name-ok: legacy field, read permanently
101
107
  }
102
108
  if (e.source === "harness")
103
109
  e.source = TUMWATER_SOURCE; // vocabulary-ok: legacy event source read from old logs
@@ -115,6 +121,24 @@ const LEGACY_EVENT_REWRITES = [
115
121
  e.role = canonicalRoleId(e.role);
116
122
  },
117
123
  },
124
+ {
125
+ // The four subscription-window events now name their backend (plans/codex-backend.md part
126
+ // 2b/9). The old claude_window_* names map (above) to subscription_window_* with no
127
+ // backend, so a line written before the rename gets `backend: "claude"` here; a current
128
+ // line written with its own backend is left alone. Kept permanently: a still-running old
129
+ // supervisor and old rotated logs keep writing the old names without a backend.
130
+ name: "subscription-window-backend",
131
+ apply(ev) {
132
+ const e = ev;
133
+ if (e.type === "subscription_window_reserved" ||
134
+ e.type === "subscription_window_warning" ||
135
+ e.type === "subscription_window_limited" ||
136
+ e.type === "subscription_window_reset") {
137
+ if (typeof e.backend !== "string")
138
+ e.backend = "claude";
139
+ }
140
+ },
141
+ },
118
142
  ];
119
143
  /** Today's name for an old event type name; any other string comes back unchanged. */
120
144
  export function canonicalEventType(name) {
@@ -6,7 +6,7 @@ import { readTailTextWithEnd } from "../files/tail.js";
6
6
  import { normalizeLegacyEvent } from "./event-legacy.js";
7
7
  /** The READ side of the event feed: everything that consumes events.jsonl after the fact —
8
8
  * parseEventLine's skip-without-failing rule, the per-event query conventions (eventDayKey,
9
- * eventRole, eventUsage, tickStartMap/tickSpanMs) the reports and history share, and the
9
+ * eventRole, eventUsage, runStartMap/runSpanMs) the reports and history share, and the
10
10
  * stat-cached backwards tail scan readEvents is built on. The WRITE side — logEvent/warnEvent,
11
11
  * the append path with its torn-tail repair, rotation, and the subscriber list — and the
12
12
  * HarnessEvent shape itself live beside it in events.ts; the split keeps the append path's
@@ -31,7 +31,7 @@ export function parseEventLine(line) {
31
31
  * numeric timestamp. The one day-bucketing rule the windowed reader (event-window.ts) and the
32
32
  * usage/failure reports all apply, so they agree on what counts as one day. A `ts` that is
33
33
  * present but not finite is as unusable as an absent one and reads null too (the same
34
- * finiteness check eventTick/eventUsage/tickStartMap/tickSpanMs apply): bare
34
+ * finiteness check eventRun/eventUsage/runStartMap/runSpanMs apply): bare
35
35
  * `typeof Infinity === "number"` let a JSON `1e999` through to dayKey, whose
36
36
  * `new Date(Infinity)` rendered the bucket key "NaN-NaN-NaN" and filed the event under a day
37
37
  * no window can name. */
@@ -39,19 +39,19 @@ export function eventDayKey(ev) {
39
39
  const ts = finiteNumber(ev.ts, null);
40
40
  return ts === null ? null : dayKey(ts);
41
41
  }
42
- /** The loop id an event is filed under, or "?" when absent or empty — the guard the usage
42
+ /** The role id an event is filed under, or "?" when absent or empty — the guard the usage
43
43
  * report and the failure digest both apply when grouping by role. */
44
44
  export function eventRole(ev) {
45
45
  return typeof ev.source === "string" && ev.source !== "" ? ev.source : "?";
46
46
  }
47
- /** The tick number an event names, or 0 when it is absent or non-finite. The one guard every
48
- * display surface applies to the loosened `tick` field: an event log is read without a shape
49
- * filter, so a torn or hand-edited line can carry no tick (String(undefined) printed "#undefined"
50
- * in the feed) or a non-numeric one (String(Number(…)) printed "#NaN" in history). Ticks are
51
- * 1-based — the loop increments state.runs before logging its first tick — so 0 is visibly not
52
- * a real tick, the same corrupt-value-reads-as-0 rule gigabytes and the failure digest's
47
+ /** The run number an event names, or 0 when it is absent or non-finite. The one guard every
48
+ * display surface applies to the loosened `run` field: an event log is read without a shape
49
+ * filter, so a torn or hand-edited line can carry no run (String(undefined) printed "#undefined"
50
+ * in the feed) or a non-numeric one (String(Number(…)) printed "#NaN" in history). Runs are
51
+ * 1-based — the role increments state.runs before logging its first run — so 0 is visibly not
52
+ * a real run, the same corrupt-value-reads-as-0 rule gigabytes and the failure digest's
53
53
  * aheadCommits apply, kept finite so a JSON consumer never sees NaN's null stand-in. */
54
- export function eventTick(ev) {
54
+ export function eventRun(ev) {
55
55
  return finiteNumber(ev.run, 0);
56
56
  }
57
57
  /** The usage numbers an event records (`run_end`'s own run, `merge_done`'s merge slot):
@@ -68,15 +68,15 @@ export function eventUsage(ev) {
68
68
  const notionalCostUsd = finiteNumber(ev.notionalCostUsd, 0);
69
69
  return { tokens, costUsd, notionalCostUsd };
70
70
  }
71
- /** Pair the run_start events of `events` by tick, keyed `${loop}#${tick}` → the start event
71
+ /** Pair the run_start events of `events` by run, keyed `${role}#${run}` → the start event
72
72
  * itself — the one home of that pairing and its key format, shared by the timestamps
73
- * (tickStartMap), the models (tickStartModelMap), and the tick drill-down's duration
74
- * (tick-detail-data.ts uses tickSpanMs' key). A run_start whose `ts` is not a finite number is
73
+ * (runStartMap), the models (runStartModelMap), and the run drill-down's duration
74
+ * (run-detail-data.ts uses runSpanMs' key). A run_start whose `ts` is not a finite number is
75
75
  * as unusable as an absent one and adds no entry either (the same "a torn or hand-edited line
76
- * carries no usable timestamp" policy eventTick/eventUsage apply to their fields), so every
76
+ * carries no usable timestamp" policy eventRun/eventUsage apply to their fields), so every
77
77
  * entry is a real start the projections below can trust. Private: external consumers read one
78
78
  * of the projections, not this raw pairing. */
79
- function runStartMap(events) {
79
+ function runStartEvents(events) {
80
80
  const starts = new Map();
81
81
  for (const e of events) {
82
82
  const ts = finiteNumber(e.ts, null);
@@ -85,54 +85,54 @@ function runStartMap(events) {
85
85
  }
86
86
  return starts;
87
87
  }
88
- /** The run_start pairing's start epoch ms: `${loop}#${tick}` → start epoch ms, the projection
89
- * of runStartMap onto each event's `ts` — shared by `tumwater history`'s row spans
90
- * (history-data.ts), the failure digest's time-and-spend fold (time-spend.ts), and the tick
91
- * drill-down's duration (tick-detail-data.ts), so those consumers cannot drift into different
92
- * notions of a tick's span. A run_end whose start is not in `events` (log rotation cut it, or
93
- * the tick was skipped before any start logged) has no entry; callers decide what an unpaired
88
+ /** The run_start pairing's start epoch ms: `${role}#${run}` → start epoch ms, the projection
89
+ * of runStartEvents onto each event's `ts` — shared by `tumwater history`'s row spans
90
+ * (history-data.ts), the failure digest's time-and-spend fold (time-spend.ts), and the run
91
+ * drill-down's duration (run-detail-data.ts), so those consumers cannot drift into different
92
+ * notions of a run's span. A run_end whose start is not in `events` (log rotation cut it, or
93
+ * the run was skipped before any start logged) has no entry; callers decide what an unpaired
94
94
  * end costs. Lives in the read module, not in a ui module, so core collectors can share it
95
95
  * without a core→ui import. */
96
- export function tickStartMap(events) {
96
+ export function runStartMap(events) {
97
97
  const starts = new Map();
98
- for (const [key, e] of runStartMap(events))
98
+ for (const [key, e] of runStartEvents(events))
99
99
  starts.set(key, finiteNumber(e.ts, 0));
100
100
  return starts;
101
101
  }
102
- /** The run_start pairing's model selectors: `${loop}#${tick}` → the run's model when its
102
+ /** The run_start pairing's model selectors: `${role}#${run}` → the run's model when its
103
103
  * run_start carries a non-empty string `model` (the agent-qualified selector string — a
104
104
  * per-role override, a fallback, or the primary — that the status rows carry; see
105
- * config-views.ts's agentQualifiedSelector). A run_start with no `model`, or an empty or
105
+ * config-views.ts's backendQualifiedSelector). A run_start with no `model`, or an empty or
106
106
  * non-string one, adds no entry, so `tumwater history`'s model cell renders empty for a start
107
107
  * that named none — exactly the empty-usage convention applied to cells. The finite-`ts`
108
- * guard rides runStartMap, the one home of the pairing. */
109
- export function tickStartModelMap(events) {
108
+ * guard rides runStartEvents, the one home of the pairing. */
109
+ export function runStartModelMap(events) {
110
110
  const models = new Map();
111
- for (const [key, e] of runStartMap(events)) {
111
+ for (const [key, e] of runStartEvents(events)) {
112
112
  if (typeof e.model === "string" && e.model !== "")
113
113
  models.set(key, e.model);
114
114
  }
115
115
  return models;
116
116
  }
117
- /** The model of the run_end event `ev` against `models` (tickStartModelMap's pairing): the
118
- * `${loop}#${tick}` lookup, in one place beside the map it indexes, so the key format and the
119
- * "no model is null" rule are spelled once — the model twin of tickSpanMs. null when the
117
+ /** The model of the run_end event `ev` against `models` (runStartModelMap's pairing): the
118
+ * `${role}#${run}` lookup, in one place beside the map it indexes, so the key format and the
119
+ * "no model is null" rule are spelled once — the model twin of runSpanMs. null when the
120
120
  * start is missing (rotation cut it) or carried no model; callers decide what an empty model
121
121
  * costs — history renders it as an empty cell. */
122
- export function tickStartModel(ev, models) {
122
+ export function runStartModel(ev, models) {
123
123
  return models.get(`${ev.source}#${ev.run}`) ?? null;
124
124
  }
125
- /** The start→end span of the `run_end` event `ev` against `starts` (tickStartMap's pairing):
126
- * the `${loop}#${tick}` lookup and the clamp to non-negative, in one place beside the map
125
+ /** The start→end span of the `run_end` event `ev` against `starts` (runStartMap's pairing):
126
+ * the `${role}#${run}` lookup and the clamp to non-negative, in one place beside the map
127
127
  * they index, so the key format and the span rule are spelled once. null when the start is
128
128
  * missing (rotation cut it) or when `ev.ts` is not a finite number (a torn or hand-edited end
129
129
  * line can carry no usable timestamp); callers decide what an unpaired end costs — the failure
130
- * digest prices it as 0, history renders it as "—", tick detail omits the duration. The end's
131
- * guard is the same finiteness check eventTick/eventUsage apply to their fields: without it,
132
- * `Math.max(0, undefined - start)` was NaN, and the failure digest's `tickDurationMs`
133
- * (`tickSpanMs(ev, starts) ?? 0`) folded that NaN straight into its agent-hour totals —
130
+ * digest prices it as 0, history renders it as "—", run detail omits the duration. The end's
131
+ * guard is the same finiteness check eventRun/eventUsage apply to their fields: without it,
132
+ * `Math.max(0, undefined - start)` was NaN, and the failure digest's `runDurationMs`
133
+ * (`runSpanMs(ev, starts) ?? 0`) folded that NaN straight into its agent-hour totals —
134
134
  * `NaN ?? 0` is still NaN. */
135
- export function tickSpanMs(ev, starts) {
135
+ export function runSpanMs(ev, starts) {
136
136
  const startTs = starts.get(`${ev.source}#${ev.run}`);
137
137
  const endTs = finiteNumber(ev.ts, null);
138
138
  return startTs === undefined || endTs === null ? null : Math.max(0, endTs - startTs);
@@ -0,0 +1,16 @@
1
+ /** The one ordering the prose `tumwater logs --count` tally and the TUI Activity pane's
2
+ * tally line share: count each present event type, most-frequent first with ties in
3
+ * type-name order, one `[type, count]` pair per present type. Hoisting the sort here keeps
4
+ * `sayTally` and the pane from drifting on which types win or how ties break. The dashboard
5
+ * feed's tally chips keep their own copy because they run in a browser where harness modules
6
+ * cannot be imported (the `reportRoleOrder` exception
7
+ * src/ui/gui/gui-client-report-chart.ts documents). */
8
+ import { increment, sortByValueDesc } from "../collections.js";
9
+ /** Tally the distinct types present, most-frequent first with ties in type-name order.
10
+ * `[]` in, `[]` out. */
11
+ export function eventTypeTally(events) {
12
+ const counts = new Map();
13
+ for (const e of events)
14
+ increment(counts, e.type);
15
+ return sortByValueDesc([...counts.entries()]);
16
+ }
@@ -1,5 +1,6 @@
1
- import { dayAt, dayKey, humanSeconds } from "../text/datetime.js";
1
+ import { dayAt, dayKey, humanSeconds, DAY_MS } from "../text/datetime.js";
2
2
  import { eventDayKey, parseEventLine } from "./event-read.js";
3
+ import { EVENTS_KEEP } from "./events.js";
3
4
  import { eventsArchivePath, eventsLogPath } from "../paths.js";
4
5
  import { statOrNull } from "../files/files.js";
5
6
  import { readTailTextWithEnd } from "../files/tail.js";
@@ -17,16 +18,16 @@ export const REPORT_MAX_DAYS = 90;
17
18
  * signal, and the log rotates at 16 MB (EVENTS_MAX_BYTES in events.ts) anyway, so a huge window
18
19
  * mostly reads rotated-away nothing. Lives here so the CLI's flag validation and its window
19
20
  * read share one bound. */
20
- export const LOGS_SINCE_MAX_MS = 7 * 24 * 60 * 60 * 1000;
21
+ export const LOGS_SINCE_MAX_MS = 7 * DAY_MS;
21
22
  /** The `tumwater report --since <duration>` window's cap: 7 days. The same bound as the logs
22
23
  * view above, for the same rationale — a longer window only re-reads more log without adding
23
24
  * signal, and the log rotates at 16 MB (EVENTS_MAX_BYTES in events.ts) anyway. Lives here so
24
25
  * the CLI's flag validation and the collector's window read share one bound. */
25
- export const REPORT_SINCE_MAX_MS = 7 * 24 * 60 * 60 * 1000;
26
+ export const REPORT_SINCE_MAX_MS = 7 * DAY_MS;
26
27
  /** Why a trailing-window length is refused, as the rejection text naming the out-of-range
27
28
  * value (`sinceMs must be between 1 and 7d (got 604800000)`). The single home of the report
28
29
  * window's bound wording, shared by the data-layer guard (assertReportSinceMs) and the
29
- * dashboard's ?sinceMs= 400 (gui-endpoints.ts) so the two cannot drift apart; the `--since`
30
+ * dashboard's ?sinceMs= 400 (gui-report-endpoints.ts) so the two cannot drift apart; the `--since`
30
31
  * CLI flag uses parseSinceFlag's own distinct wording. */
31
32
  export function reportSinceMsg(sinceMs) {
32
33
  return `sinceMs must be between 1 and ${humanSeconds(REPORT_SINCE_MAX_MS / 1000)} (got ${sinceMs})`;
@@ -198,32 +199,42 @@ function scanEventsFile(file, fromKey) {
198
199
  const end = trailing !== "" && parseEventLine(trailing) !== null ? coveredEnd + Buffer.byteLength(trailing) : coveredEnd;
199
200
  return { events, coversFullWindow, end, logState: classifyLogFile(text, st) };
200
201
  }
201
- /** Events whose local day is on or after `fromKey`, read from the live log and — when the live
202
- * log alone does not cover the window — its one archived generation (events.jsonl.1, written by
203
- * rotation). Each file is scanned with bounded I/O (scanEventsFile) and the archive's in-window
204
- * events are strictly older than the live file's, so prepending them keeps the oldest-first
205
- * ordering every consumer assumes; the concatenation needs no dedup because rotation moves the
206
- * whole file, so the two files share no lines. `coversFullWindow` is true when either file's
207
- * oldest retained event predates the window, which keeps the rotation note honest in both
208
- * directions: it vanishes once the archive completes the window and still appears when even the
209
- * archive starts inside it. A window the live file provably covers never touches the archive,
202
+ /** Events whose local day is on or after `fromKey`, read from the live log and, when the
203
+ * live log alone does not cover the window, its archived generations (events.jsonl.1 …
204
+ * `.EVENTS_KEEP`, each written by rotation — the newest archive `.1` first, then `.2` …
205
+ * `.5` only while the window is not yet covered, per G4). Each file is scanned with bounded
206
+ * I/O (scanEventsFile) and every archive's in-window events are strictly older than the live
207
+ * file's, so prepending them oldest-archive-first keeps the oldest-first ordering every
208
+ * consumer assumes; the concatenation needs no dedup because rotation moves whole files, so
209
+ * the files share no lines. `coversFullWindow` is true when any read file's oldest retained
210
+ * event predates the window, which keeps the rotation note honest in both directions: it
211
+ * vanishes once an archive completes the window and still appears when even the newest
212
+ * archive starts inside it. A window the live file provably covers never touches an archive,
210
213
  * and a missing or empty archive (the normal case) reads as nothing, so the no-rotation
211
214
  * behavior is unchanged. */
212
215
  export function readWindowEvents(root, fromKey) {
213
216
  const live = scanEventsFile(eventsLogPath(root), fromKey);
214
217
  if (live.coversFullWindow)
215
218
  return { events: live.events, coversFullWindow: true, liveEnd: live.end, logState: live.logState };
216
- const archive = scanEventsFile(eventsArchivePath(root), fromKey);
217
- return {
218
- events: [...archive.events, ...live.events],
219
- coversFullWindow: archive.coversFullWindow || live.coversFullWindow,
220
- liveEnd: live.end,
221
- logState: combineLogState(live.logState, archive.logState),
222
- };
219
+ const archived = [];
220
+ let covered = live.coversFullWindow;
221
+ for (let gen = 1; gen <= EVENTS_KEEP && !covered; gen++) {
222
+ const archive = scanEventsFile(eventsArchivePath(root, gen), fromKey);
223
+ archived.push(archive);
224
+ covered = archive.coversFullWindow;
225
+ }
226
+ const events = [];
227
+ for (let i = archived.length - 1; i >= 0; i--)
228
+ events.push(...archived[i].events);
229
+ events.push(...live.events);
230
+ let logState = live.logState;
231
+ for (const a of archived)
232
+ logState = combineLogState(logState, a.logState);
233
+ return { events, coversFullWindow: covered, liveEnd: live.end, logState };
223
234
  }
224
235
  /** The event feed's read outcome for a one-shot caller that needs no window — `doctor`'s
225
236
  * event-log pre-flight. Reads the live log and its archive whole and classifies each; the
226
- * combine rule matches readWindowEvents, so the doctor check and the telemetry digest agree on
237
+ * combine rule matches readWindowEvents, so the doctor check and the selfcheck digest agree on
227
238
  * what "unreadable" means. */
228
239
  export function eventLogState(root) {
229
240
  return combineLogState(scanFileReadState(eventsLogPath(root)), scanFileReadState(eventsArchivePath(root)));
@@ -239,7 +250,7 @@ function scanFileReadState(file) {
239
250
  return classifyLogFile(text, st);
240
251
  }
241
252
  /** The duration-shaped windowed read whose consumer needs a SECOND view of the same window
242
- * with a wider cutoff (history's tick-row join: a queued tick spanning the rows' cutoff logged
253
+ * with a wider cutoff (history's run-row join: a queued run spanning the rows' cutoff logged
243
254
  * its merge_queued pin before that cutoff, so its merge verdict must be joined from evidence
244
255
  * up to `joinBackslackMs` older). One backwards scan serves both: the wide read's day key never
245
256
  * sits after the narrow one's, so every `ts >= cutoff` event it holds is exactly the narrow
@@ -309,8 +320,9 @@ export function readEventsSince(root, sinceMs) {
309
320
  /** The "recent events of a kind" read every doctor wait/failure check shares: events since the
310
321
  * trailing `sinceMs` window, kept only when they clear `now − sinceMs` (the caller's seam for the
311
322
  * window's far edge) and pass `keep`. One home of the read-and-recheck shape the slot/provider
312
- * wait checks (doctor-checks.ts), the notify-failure check, and the queued-merge wait check each
313
- * spelled beside readEventsSince's cutoff — the kind + recency rule cannot drift per caller. `keep`
323
+ * wait checks (doctor-contention-checks.ts), the notify-failure check, and the queued-merge
324
+ * wait check each spelled beside readEventsSince's cutoff — the kind + recency rule cannot drift
325
+ * per caller. `keep`
314
326
  * defaults to every event, so the ts-only reader passes nothing; `now` is explicit so every call in
315
327
  * one window derives from the same instant (readEventsSince's own cutoff still uses the real
316
328
  * clock, so this recheck is what a test seam can pin). `ts` is already number-typed by
@@ -318,3 +330,27 @@ export function readEventsSince(root, sinceMs) {
318
330
  export function eventsSince(root, sinceMs, now, keep = () => true) {
319
331
  return readEventsSince(root, sinceMs).events.filter((ev) => keep(ev) && ev.ts >= now - sinceMs);
320
332
  }
333
+ /** The `--since` / `?sinceMs=` trailing window's bounds and identity slice, shared by the two
334
+ * trailing-window collectors (`collectReportSince` in report-data.ts and
335
+ * `collectFailureReportSince` in failure-data.ts): an open-ended window (cutoff = now − sinceMs,
336
+ * end = now) when `untilMs` is absent, else the bounded [now − sinceMs, now − untilMs). Both
337
+ * collectors budget events with the same open/closed comparisons against `cutoff`/`end` (and
338
+ * `now` for the open-ended case), and both stamp the same fromIso/toIso identity fields into
339
+ * their JSON, so the timing math and the ISO stamping live here rather than drifting per
340
+ * collector. `slice` is exactly the identity object each embeds: sinceMs and fromIso always,
341
+ * plus untilMs and toIso only when the window is bounded. */
342
+ export function sinceWindowTiming(sinceMs, untilMs) {
343
+ const now = Date.now();
344
+ const cutoff = now - sinceMs;
345
+ const end = untilMs === undefined ? now : now - untilMs;
346
+ return {
347
+ now,
348
+ cutoff,
349
+ end,
350
+ slice: {
351
+ sinceMs,
352
+ fromIso: new Date(cutoff).toISOString(),
353
+ ...(untilMs === undefined ? {} : { untilMs, toIso: new Date(end).toISOString() }),
354
+ },
355
+ };
356
+ }
@@ -14,17 +14,22 @@ export function subscribeEvents(listener) {
14
14
  * harness event log is small per event, so a fixed cap keeps logEvent config-free. Exported
15
15
  * because the report headers name the threshold — a fact only this module owns. */
16
16
  export const EVENTS_MAX_BYTES = 16 * 1024 * 1024;
17
+ /** How many rotated event-log generations rotateIfLarge retains (plans/fast-merge.md
18
+ * "Guardrails" G4): at most 96 MB with the live file, about six weeks at the series' daily
19
+ * rate, so a by-hand audit of the team still sees months. A constant, not a config key, like
20
+ * EVENTS_MAX_BYTES. */
21
+ export const EVENTS_KEEP = 5;
17
22
  /** The rotation phrase the report headers render — `eventsRotationLabel()` rather than a
18
- * hardcoded "rotated at 16 MB" copy, so changing EVENTS_MAX_BYTES updates the header with it
19
- * instead of leaving every report claiming a size the log no longer rotates at. */
23
+ * hardcoded "rotated at 16 MB" copy, so changing EVENTS_MAX_BYTES or EVENTS_KEEP updates the
24
+ * header with it instead of leaving every report claiming a rotation the log no longer does. */
20
25
  export function eventsRotationLabel() {
21
- return `rotated at ${EVENTS_MAX_BYTES / (1024 * 1024)} MB`;
26
+ return `rotated at ${EVENTS_MAX_BYTES / (1024 * 1024)} MB, ${EVENTS_KEEP} kept`;
22
27
  }
23
28
  ;
24
29
  /** The `source` of events tumwater writes about itself rather than on a role's behalf. */
25
30
  export const TUMWATER_SOURCE = "tumwater";
26
31
  /** The usage fields an event carries, as a fragment with both omitted when zero — the
27
- * omit-when-zero convention the two usage-emitters apply (tick-finalize.ts's run_end,
32
+ * omit-when-zero convention the two usage-emitters apply (run-finalize.ts's run_end,
28
33
  * merge-slot.ts's merge_done/merge_failed): a zero-token, zero-cost run (a free model, an abort
29
34
  * before any tokens) renders without usage, so event-format's usage fragment and
30
35
  * `tumwater history` show nothing rather than "$0.00". Spreading the fragment keeps the
@@ -51,7 +56,7 @@ export function logEvent(root, event) {
51
56
  const full = normalizeLegacyEvent({ ts: Date.now(), ...event });
52
57
  const file = eventsLogPath(root);
53
58
  ensureParentDir(file);
54
- rotateIfLarge(file, EVENTS_MAX_BYTES);
59
+ rotateIfLarge(file, EVENTS_MAX_BYTES, EVENTS_KEEP);
55
60
  appendEventLine(file, JSON.stringify(full) + "\n");
56
61
  for (const listener of listeners)
57
62
  listener(full);
@@ -72,7 +77,7 @@ export function logEventBestEffort(root, event) {
72
77
  logEvent(root, event);
73
78
  }
74
79
  catch (err) {
75
- // A terminal display boundary like cli-output's say()/fail(): the loop id, event type, and
80
+ // A terminal display boundary like cli-output's say()/fail(): the role id, event type, and
76
81
  // error text can carry model- or subprocess-supplied bytes, and stderr is a terminal. Strip
77
82
  // the control characters the same way so a fallback line cannot drive the operator's shell.
78
83
  const detail = `${event.source}: could not log ${event.type} event: ${errorMessage(err)}`;
@@ -80,9 +85,9 @@ export function logEventBestEffort(root, event) {
80
85
  }
81
86
  }
82
87
  /** warnEvent's twin of logEventBestEffort: the "something is off but the loop continues"
83
- * warning must not be the thing that ends the loop it warns about. The fallback keeps the
88
+ * warning must not be the thing that ends the role it warns about. The fallback keeps the
84
89
  * warning's own text on stderr — when the feed is the failure, this line is the only place the
85
- * caller's message (a spawn failure, a rejected tick) survives to be read. The single home of
90
+ * caller's message (a spawn failure, a rejected run) survives to be read. The single home of
86
91
  * the try-warn → message-bearing-stderr-fallback shape, so the notify hook and the launch pass
87
92
  * do not each carry a copy of it. */
88
93
  export function warnEventBestEffort(root, source, message) {
@@ -90,17 +95,29 @@ export function warnEventBestEffort(root, source, message) {
90
95
  warnEvent(root, source, message);
91
96
  }
92
97
  catch (err) {
93
- // The warning's own text is very often model-supplied (a stalled tool call's label, a tick's
94
- // lastError) and the loop id can be a custom one, so this fallback is a terminal boundary:
98
+ // The warning's own text is very often model-supplied (a stalled tool call's label, a run's
99
+ // lastError) and the role id can be a custom one, so this fallback is a terminal boundary:
95
100
  // sanitize exactly as say()/fail() do before the line reaches the operator's terminal.
96
101
  const detail = `${source}: ${message} (warning log unwritable: ${errorMessage(err)})`;
97
102
  process.stderr.write(`tumwater: ${stripTerminalControls(detail)}\n`);
98
103
  }
99
104
  }
105
+ /** Run a best-effort bookkeeping step: when `step` throws, warn "could not <what>: <error>" under
106
+ * `source` and return false, so the caller keeps going (or returns early on false). */
107
+ export function tryOrWarn(root, source, what, step) {
108
+ try {
109
+ step();
110
+ return true;
111
+ }
112
+ catch (err) {
113
+ warnEventBestEffort(root, source, `could not ${what}: ${errorMessage(err)}`);
114
+ return false;
115
+ }
116
+ }
100
117
  /** The `{ emit, warn }` pair for a caller on a catch-less path (the orchestrator's poll loop
101
118
  * and the live config reload it drives): both route through logEventBestEffort and
102
119
  * warnEventBestEffort, so the "an unwritable feed must not end the team" policy has one home
103
- * and the harness-loop warning shorthand cannot drift from warnEvent's event shape. */
120
+ * and the harness-level warning shorthand cannot drift from warnEvent's event shape. */
104
121
  export function bestEffortEventLogger(root) {
105
122
  return {
106
123
  emit: (event) => logEventBestEffort(root, event),
@@ -140,11 +157,11 @@ function appendEventLine(file, line) {
140
157
  }
141
158
  }
142
159
  // The READ side of this feed — parseEventLine, the per-event query conventions
143
- // (eventDayKey/eventRole/eventUsage, tickStartMap/tickSpanMs), and the stat-cached tail
160
+ // (eventDayKey/eventRole/eventUsage, runStartMap/runSpanMs), and the stat-cached tail
144
161
  // reader (readEvents/readEventsTailWithEnd) — lives in event-read.ts, so the append path's
145
162
  // fs machinery and stat-memo cache stay apart from the read path's parse-and-scan machinery
146
163
  // and its own stat-keyed cache. Human-facing formatting of events lives in event-format.ts;
147
164
  // the windowed reader in event-window.ts; both beside this module because each is used by
148
165
  // both display surfaces (src/ui/) and core consumers (the `run` banner's live stream,
149
- // cli/cli-run.ts), and src/ui/ is imported only by itself and the CLI command layer that
166
+ // cli/cli-start.ts), and src/ui/ is imported only by itself and the CLI command layer that
150
167
  // drives it (cli.ts and the src/ command bodies — DEVELOPMENT.md Layout).
@@ -14,7 +14,7 @@ import { childEnv } from "../process/process.js";
14
14
  * role-cap-gates.ts and disk-gate.ts, src/baseline/main-red.ts, src/team/team-polls.ts,
15
15
  * merge-slot.ts, merge-questions.ts,
16
16
  * src/redeploy/redeployer.ts). `supervisor_exit` is logged by the supervisor after the
17
- * orchestrator is gone, so src/cli/cli-run.ts pages it directly with notifyTeamDown; events
17
+ * orchestrator is gone, so src/cli/cli-start.ts pages it directly with notifyTeamDown; events
18
18
  * appended by
19
19
  * operator CLI commands go straight to the file and are out of scope by design.
20
20
  *
@@ -24,14 +24,14 @@ import { childEnv } from "../process/process.js";
24
24
  * (the exact line `tumwater logs` renders, via formatEvent). */
25
25
  export const NOTIFY_EVENT_TYPES = [
26
26
  "budget_warning", // daily spend crossed 80% of the cap, gate still open — time to raise it or fix the fallback before the page below
27
- "role_cap_warning", // one role's own daily spend crossed 80% of its cap, still ticking — time to raise it before the page below
28
- "budget_paused", // spend cap hit with no usable free fallback — role loops are stopped
29
- "claude_window_warning", // the claude.ai subscription window first reported allowed_warning — subscription claude seams still tick; the heads-up before the hold below
30
- "claude_window_limited", // a rejected subscription window holds every subscription claude seam until it resets — the subscription team's binding stop
27
+ "role_cap_warning", // one role's own daily spend crossed 80% of its cap, still running — time to raise it before the page below
28
+ "budget_paused", // spend cap hit with no usable free fallback — roles are stopped
29
+ "subscription_window_warning", // the subscription window first reported allowed_warning — subscription seams still run; the heads-up before the hold below
30
+ "subscription_window_limited", // a rejected subscription window holds every subscription seam until it resets — the subscription team's binding stop
31
31
  "role_streak_paused", // the error-streak circuit breaker auto-paused a role
32
- "role_cap_paused", // the role's own daily cost cap is reached — it stops starting ticks
32
+ "role_cap_paused", // the role's own daily cost cap is reached — it stops starting runs
33
33
  "disk_low", // free space on the worktrees volume dropped below diskHoldGB — new work is held
34
- "main_red_hold", // main's own check is red — every code loop is held until a fix lands
34
+ "main_red_hold", // main's own check is red — every code role is held until a fix lands
35
35
  "merge_failed", // the merge slot finished without merging a change
36
36
  "restart_blocked", // the self-redeploy for main is blocked and latched — the team is stuck behind it
37
37
  "question_posted", // a merge posted a new QUESTIONS.md ## Open entry — only a human can answer it
@@ -1,7 +1,7 @@
1
1
  /** The cluster-key rules shared by everything that groups failure messages into causes: the
2
2
  * failure digest's collection (failure-data.ts) and the error-storm reducer
3
3
  * (src/team/error-storm.ts) both count by these keys, so the normalization, the two
4
- * tick-timeout shapes and their pooling, and the grouping engine live here rather than
4
+ * run-timeout shapes and their pooling, and the grouping engine live here rather than
5
5
  * inside either consumer. Pure string
6
6
  * and grouping logic — no event reads, no clock. */
7
7
  import { rankByCount } from "../collections.js";
@@ -76,34 +76,34 @@ export function normalizeClusterKey(message) {
76
76
  .replace(/(?<!exited\s)\b\d+\b/g, "<n>");
77
77
  return normalized.trim().slice(0, EXAMPLE_MAX);
78
78
  }
79
- /** normalizeClusterKey's rendering of the two tick-timeout errors src/agent/run-agent.ts emits —
79
+ /** normalizeClusterKey's rendering of the two run-timeout errors src/agent/run-agent.ts emits —
80
80
  * the plain kill and the still-making-progress variant (session and worktree edits preserved
81
81
  * for resume, BUGS.md 2026-09-29). Both are one cause pointing at one knob (runTimeoutSeconds),
82
82
  * so every consumer that counts by cluster key pools them under the plain one. */
83
- export const TICK_TIMEOUT_KEY = "timed out after <dur>";
84
- const TICK_TIMEOUT_PROGRESSING_KEY = "timed out after <dur> while still making progress — session and worktree edits preserved for resume";
83
+ export const RUN_TIMEOUT_KEY = "timed out after <dur>";
84
+ const RUN_TIMEOUT_PROGRESSING_KEY = "timed out after <dur> while still making progress — session and worktree edits preserved for resume";
85
85
  // The review run's rewording of the same cause (BUGS.md 2026-09-30): the reviewer never
86
86
  // resumes, so its timeout names what actually happens — the commit is kept, the next
87
87
  // attempt re-reviews from scratch. Same cause, same knob (review.timeoutSeconds), one row.
88
- const TICK_TIMEOUT_PROGRESSING_REVIEW_KEY = "timed out after <dur> while still making progress — the commit is kept; the next attempt reviews it from scratch";
88
+ const RUN_TIMEOUT_PROGRESSING_REVIEW_KEY = "timed out after <dur> while still making progress — the commit is kept; the next attempt reviews it from scratch";
89
89
  // The review run's wording when a resumable session remains:
90
90
  // the next attempt continues this review instead of re-reviewing. Continuing the timed-out
91
91
  // session is a different cause from a from-scratch re-review, so this wording is its own
92
- // cluster rather than joining the pooled plain tick-timeout row. Exported for its unit tests
93
- // (like TICK_TIMEOUT_KEY), so a display or digest test can pin the mapping by name.
94
- export const TICK_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY = "timed out after <dur> while still making progress — the commit is kept; the next attempt continues this review";
92
+ // cluster rather than joining the pooled plain run-timeout row. Exported for its unit tests
93
+ // (like RUN_TIMEOUT_KEY), so a display or digest test can pin the mapping by name.
94
+ export const RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY = "timed out after <dur> while still making progress — the commit is kept; the next attempt continues this review";
95
95
  /** The known progressing-timeout wordings and the key each pools under. The two review
96
96
  * wordings name one cause on one knob (review.timeoutSeconds): a still-making-progress kill
97
- * pools into the plain tick-timeout row (a mixed team of plain and progressing kills is one
97
+ * pools into the plain run-timeout row (a mixed team of plain and progressing kills is one
98
98
  * cause's agent-hours, not two half-size rows the top-N cut can drop — the same pooling the
99
99
  * error-storm reducer applies, src/team/error-storm.ts), and a resumed review's wording maps
100
100
  * to itself, because continuing the timed-out session is a different cause from a from-scratch
101
101
  * re-review. Enumerated so a future wording edit lands in
102
102
  * this table rather than silently changing which row its timeouts count under. */
103
103
  const TIMEOUT_POOL = new Map([
104
- [TICK_TIMEOUT_PROGRESSING_KEY, TICK_TIMEOUT_KEY],
105
- [TICK_TIMEOUT_PROGRESSING_REVIEW_KEY, TICK_TIMEOUT_KEY],
106
- [TICK_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY, TICK_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY],
104
+ [RUN_TIMEOUT_PROGRESSING_KEY, RUN_TIMEOUT_KEY],
105
+ [RUN_TIMEOUT_PROGRESSING_REVIEW_KEY, RUN_TIMEOUT_KEY],
106
+ [RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY, RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY],
107
107
  ]);
108
108
  /** The cluster key a normalized error clusters under: the pooled wordings above map to their
109
109
  * row, and every other cause stands as normalizeClusterKey rendered it. */