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
@@ -1,50 +1,20 @@
1
- import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
2
- import { isJsonObject, nonNegativeNumber } from "../files/json-object.js";
1
+ import { APPROVED_PATCH_IDS_MAX, freshRunnerState, SKIP_REASONS } from "./runner-state.js";
3
2
  import path from "node:path";
4
- import { readdirSync } from "node:fs";
3
+ import { dirEntryNames } from "../files/files.js";
4
+ import { readJsonFile } from "../files/json-files.js";
5
+ import { isJsonObject, nonNegativeNumber, validEntries } from "../files/json-object.js";
5
6
  import { cachedByStat } from "../files/stat-cache.js";
6
7
  import { statePath, STATE_DIR } from "../paths.js";
7
- import { baseRoleOf } from "../roles/loop-ids.js";
8
+ import { baseRoleOf } from "../roles/runner-ids.js";
8
9
  import { allRoleIds } from "../roles/roles.js";
9
- /** The loop's persisted state file — one JSON object per role under .tumwater/ — and the
10
- * observation-window counter reset: fresh-state defaults, the tolerant load, the atomic
11
- * save, and zeroCounters. The scheduling POLICY that mutates this state from tick outcomes
12
- * and operator wakes — the outcome application and resume limits in tick-apply.ts, the
13
- * clock arithmetic (backoff ladders, wakes, yield ring) in backoff.ts; the generic file
14
- * helpers are src/files/files.ts and the JSON convention
15
- * itself is src/files/json-files.ts. */
16
- /** How many times a progressing-timeout review may be resumed before the next attempt
17
- * reviews the change from scratch (LoopState.reviewResume). The budget bounds a reviewer that
18
- * keeps timing out mid-progress, so a wedged review converges to a fresh run instead of
19
- * resuming the same session forever. */
20
- export const REVIEW_RESUME_LIMIT = 2;
21
- const SKIP_REASONS = [
22
- "no-free-entry",
23
- "claim-hold",
24
- "deferred-backlog",
25
- "deferred-no-work",
26
- "deferred-plans-full",
27
- ];
28
- /** A new LoopState for one role, before its first tick. */
29
- export function freshLoopState(role) {
30
- return {
31
- role,
32
- runs: 0,
33
- commits: 0,
34
- nextRunAt: 0,
35
- backoffSeconds: 0,
36
- lastMainHead: "",
37
- generatedTokens: 0,
38
- peakContextTokens: 0,
39
- totalCostUsd: 0,
40
- dayStamp: "",
41
- dayCostUsd: 0,
42
- };
43
- }
10
+ /** The read side of the runner's persisted state: the tolerant load of one role's file (heals
11
+ * every field a hand edit or an older build could leave unusable, and renames the keys an older
12
+ * build wrote) and the live-claim scan over every role's file. The type, the fresh-state
13
+ * defaults, the atomic save and zeroCounters stay in runner-state.ts. */
44
14
  /** A persisted numeric field, healed to a finite non-negative number (the fallback when it is
45
- * absent, foreign, NaN/±Infinity, or negative). Every numeric field on LoopState — top-level or
15
+ * absent, foreign, NaN/±Infinity, or negative). Every numeric field on RunnerState — top-level or
46
16
  * inside one of the nested records below — is a count, a duration, an epoch-ms stamp, or an
47
- * amount, so zero is the only sensible repair; the `tick-usage.ts` and `budget.ts` writers floor
17
+ * amount, so zero is the only sensible repair; the `run-usage.ts` and `budget.ts` writers floor
48
18
  * the same three quantities they own. A guard count (mergeConflicts, mergeCheckFailures,
49
19
  * unreviewFailures) heals to 0 like the rest: a corrupt strike tally re-accumulates from the
50
20
  * next attempt, the same "the field is unusable, start the count over" repair the top-level heal
@@ -55,19 +25,12 @@ function healNumber(value, fallback) {
55
25
  /** Heal the per-pair daily spend map: a non-object reads as `{}`, entries that are not
56
26
  * non-negative finite numbers drop, and a `__proto__` key stays an own property. */
57
27
  function healPairCosts(value) {
58
- const out = {};
59
- if (typeof value !== "object" || value === null || Array.isArray(value))
60
- return out;
61
- for (const [k, v] of Object.entries(value)) {
62
- if (typeof v === "number" && Number.isFinite(v) && v >= 0)
63
- Object.defineProperty(out, k, { value: v, enumerable: true, writable: true, configurable: true });
64
- }
65
- return out;
28
+ return validEntries(value, (v) => nonNegativeNumber(v, -1) >= 0);
66
29
  }
67
30
  /** Heal one OPTIONAL numeric field of a persisted record in place, preserving the field's
68
31
  * absent shape: an absent field stays absent (an older state file with no such field reads
69
32
  * unchanged), while a present field heals by healNumber's rule. Every optional numeric field
70
- * loadLoopState repairs — the eight top-level stamps/streaks and conflictHandback.round —
33
+ * loadRunnerState repairs — the eight top-level stamps/streaks and conflictHandback.round —
71
34
  * goes through this one guard, so the "absent stays absent" repair cannot be dropped from
72
35
  * one field while another keeps it. */
73
36
  function healOptionalNumber(rec, key) {
@@ -77,7 +40,7 @@ function healOptionalNumber(rec, key) {
77
40
  }
78
41
  /** Heal one persisted nested record: absent stays absent; a present non-object (a string,
79
42
  * number, NaN, null, or array) is dropped to undefined — every consumer of these records reads
80
- * them with property access or mutation (merge-core sets `lastReview.exhausted`, tick-apply
43
+ * them with property access or mutation (merge-core sets `lastReview.exhausted`, run-apply
81
44
  * adds to `mergeConflicts.count`), and property access on a torn scalar reads undefined where
82
45
  * the mutation throws "Cannot create property … on string" in a strict ES module; a present
83
46
  * object keeps its shape with each numeric field repaired by `heal`. */
@@ -87,13 +50,13 @@ function healRecord(value, heal) {
87
50
  heal(value);
88
51
  return value;
89
52
  }
90
- /** Keys an older build wrote, mapped to their current names. loadLoopState copies the old key's
53
+ /** Keys an older build wrote, mapped to their current names. loadRunnerState copies the old key's
91
54
  * value to the new key when the new key is absent and always drops the old key, so a file holding
92
55
  * both keeps the new value and the next save writes only current keys. */
93
56
  const LEGACY_STATE_KEYS = {
94
- ticks: "runs", // vocabulary-ok: legacy state key read from old files
95
- lastTickStartedAt: "lastRunStartedAt", // vocabulary-ok: legacy state key read from old files
96
- lastTickEndedAt: "lastRunEndedAt", // vocabulary-ok: legacy state key read from old files
57
+ ticks: "runs", // retired-name-ok: legacy name
58
+ lastTickStartedAt: "lastRunStartedAt", // retired-name-ok: legacy name
59
+ lastTickEndedAt: "lastRunEndedAt", // retired-name-ok: legacy name
97
60
  // retired-name-ok:start old state keys
98
61
  landingReviewError: "mergeReviewError", // vocabulary-ok: legacy state key read from old files
99
62
  landingCheckFailures: "mergeCheckFailures", // vocabulary-ok: legacy state key read from old files
@@ -110,7 +73,7 @@ function migrateLegacyStateKeys(saved) {
110
73
  delete saved[oldKey];
111
74
  }
112
75
  }
113
- /** Load the loop's persisted state; never throws — a missing or unreadable file yields a
76
+ /** Load the runner's persisted state; never throws — a missing or unreadable file yields a
114
77
  * fresh state, fields absent from an older file fall back to defaults, and a numeric field
115
78
  * (top-level or nested) that is present but not a finite non-negative number is healed to 0,
116
79
  * a nested record that is present but not a plain object is dropped, and lastReview.reasons is
@@ -119,11 +82,19 @@ function migrateLegacyStateKeys(saved) {
119
82
  * keeps a hand-edited string, null, or negative out of the schedulers and the dashboards'
120
83
  * arithmetic instead of relying on each reader to guard it. Optional fields that are absent
121
84
  * stay absent, so an older state file's shape is preserved. */
122
- export function loadLoopState(root, role) {
85
+ export function loadRunnerState(root, role) {
123
86
  const saved = readJsonFile(statePath(root, role));
124
87
  if (saved !== undefined && saved !== null && isJsonObject(saved))
125
88
  migrateLegacyStateKeys(saved);
126
- const s = { ...freshLoopState(role), ...(saved ?? {}) };
89
+ const s = { ...freshRunnerState(role), ...(saved ?? {}) };
90
+ // The file name is the identity: a state file written by an older build (or moved by
91
+ // migrateLegacyRoleIds) can carry a stale `role` inside, and saveRunnerState writes
92
+ // statePath(root, state.role) — so letting the saved value win would save the file back
93
+ // under a retired id and leak a frozen copy for every later start to reload (the frozen-copy
94
+ // split behind BUGS.md 2026-10-23). Force the role the caller asked for, whatever the file
95
+ // says, so the state saves (and loads) under the name it was asked for.
96
+ // (Retired-name-ok: legacy role id read from an old state file.)
97
+ s.role = role;
127
98
  s.runs = healNumber(s.runs, 0);
128
99
  s.commits = healNumber(s.commits, 0);
129
100
  s.nextRunAt = healNumber(s.nextRunAt, 0);
@@ -142,10 +113,21 @@ export function loadLoopState(root, role) {
142
113
  healOptionalNumber(s, "quietKillStreak");
143
114
  healOptionalNumber(s, "consecutiveErrors");
144
115
  healOptionalNumber(s, "noChangeStreak");
116
+ // The breaker's excused-streak ack is a count like the others — a corrupt value (a negative,
117
+ // NaN, or foreign type) would otherwise silently defeat or mistime the breaker's bar on the
118
+ // next poll, so it heals to 0 (a fresh ack) the same way the error streak itself does.
119
+ healOptionalNumber(s, "streakAcked");
145
120
  healOptionalNumber(s, "unreviewFailures");
146
121
  healOptionalNumber(s, "parkedSince");
147
122
  if (s.skipReason !== undefined && !SKIP_REASONS.includes(s.skipReason))
148
123
  delete s.skipReason;
124
+ // The one-id field an older state file carries folds into the list.
125
+ const legacy = saved?.lastApprovedPatchId;
126
+ delete s.lastApprovedPatchId;
127
+ if (s.approvedPatchIds !== undefined || typeof legacy === "string") {
128
+ const ids = [...(Array.isArray(s.approvedPatchIds) ? s.approvedPatchIds : []), ...(typeof legacy === "string" ? [legacy] : [])];
129
+ s.approvedPatchIds = ids.filter((id) => typeof id === "string").slice(-APPROVED_PATCH_IDS_MAX);
130
+ }
149
131
  if (s.reviewResume !== undefined) {
150
132
  const r = healRecord(s.reviewResume, (v) => {
151
133
  v.attempts = healNumber(v.attempts, 0);
@@ -165,7 +147,7 @@ export function loadLoopState(root, role) {
165
147
  v.at = healNumber(v.at, 0);
166
148
  // The one nested non-numeric slot whose foreign type throws rather than reads odd: the
167
149
  // rejected-review notes build a numbered list from `reasons` (numberedList's `.map`), so
168
- // a hand-edited scalar or a missing field would crash the next tick's prompt. Rebuild it
150
+ // a hand-edited scalar or a missing field would crash the next run's prompt. Rebuild it
169
151
  // to its string entries — the same "the field is unusable, start it over" repair the
170
152
  // numeric fields get.
171
153
  v.reasons = Array.isArray(v.reasons)
@@ -186,6 +168,15 @@ export function loadLoopState(root, role) {
186
168
  ? v.reasonCounts.map((c) => healNumber(c, 0))
187
169
  : undefined;
188
170
  });
171
+ // A torn retry record reads as none: leftover recovery is the backstop for the pin.
172
+ if (s.mainRedRetry !== undefined &&
173
+ !(isJsonObject(s.mainRedRetry) &&
174
+ typeof s.mainRedRetry.head === "string" &&
175
+ isJsonObject(s.mainRedRetry.entry) &&
176
+ typeof s.mainRedRetry.entry.role === "string" &&
177
+ typeof s.mainRedRetry.entry.summary === "string" &&
178
+ (s.mainRedRetry.waitingSince === undefined || typeof s.mainRedRetry.waitingSince === "number")))
179
+ s.mainRedRetry = undefined;
189
180
  if (s.mergeConflicts !== undefined)
190
181
  s.mergeConflicts = healRecord(s.mergeConflicts, (v) => {
191
182
  v.count = healNumber(v.count, 0);
@@ -217,9 +208,9 @@ export function loadLoopState(root, role) {
217
208
  v.probeAt = healNumber(v.probeAt, 0);
218
209
  v.cooldownMs = healNumber(v.cooldownMs, 0);
219
210
  });
220
- // The image-reroute marker is a one-shot nested record consumed by the next tick; a torn
211
+ // The image-reroute marker is a one-shot nested record consumed by the next run; a torn
221
212
  // scalar is dropped (its consumers read/mutate fields), and the tier is repaired to a
222
- // known tier name so a corrupt tier cannot route a rerouted tick to a phantom seam.
213
+ // known tier name so a corrupt tier cannot route a rerouted run to a phantom seam.
223
214
  if (s.imageReroute !== undefined) {
224
215
  const healed = healRecord(s.imageReroute, () => { });
225
216
  s.imageReroute =
@@ -229,51 +220,11 @@ export function loadLoopState(root, role) {
229
220
  }
230
221
  return s;
231
222
  }
232
- /** Persist the loop's state atomically via writeJsonAtomic, so a crash mid-write cannot
233
- * leave a torn file behind. Its per-pid tmp name matters here: two processes can write one
234
- * role's state concurrently — the orchestrator saves at tick end and around its review gate
235
- * while `tumwater reset-counters` rewrites the same file from the CLI process — and per-pid
236
- * names give each writer its own tmp with a clean last-writer-wins. */
237
- export function saveLoopState(root, state) {
238
- writeJsonAtomic(statePath(root, state.role), state);
239
- }
240
- /** Zero the accumulated counters (runs, commits, tokens, cost) so a fresh observation
241
- * window can begin. Pure: returns a new state and preserves everything else — scheduling
242
- * fields (nextRunAt, backoffSeconds), wake tracking (lastMainHead), and the last-result
243
- * fields. peakContextTokens is zeroed too: under per-tick semantics it holds the loop's
244
- * last completed tick's peak, so a fresh window must clear it or sleeping loops keep
245
- * showing their old value until they next tick. The daily cost budget window (dayStamp/
246
- * dayCostUsd) is deliberately NOT zeroed: the budget is a safety valve, not an observation
247
- * window — zeroing today's spend would let the cap be bypassed by running reset-counters.
248
- */
249
- export function zeroCounters(s) {
250
- return { ...s, runs: 0, commits: 0, generatedTokens: 0, peakContextTokens: 0, totalCostUsd: 0 };
251
- }
252
- /** The skip reason a loop's persisted state yields when it does not run this round: a pending
253
- * resume wait outranks backoff, anything else is idle. The shared tail of the two skip
254
- * classifiers — once-round.ts's settleSkipped (which adds the disabled arm) and cli/cli-run.ts's
255
- * once-summary (which adds the team-pause arm) — so the two surfaces cannot drift on which
256
- * reason wins when several conditions hold at once. */
257
- export function stateSkipReason(s) {
258
- if (s.resumePending)
259
- return "resume pending";
260
- if (s.backoffSeconds > 0)
261
- return "backoff"; // vocabulary-ok: skip-reason code is a state value until the state rename
262
- return "idle";
263
- }
264
- /** True when the loop still owes follow-up work for its current task after a tick: a rejected
265
- * change it must revise (state.revision) or a session it must resume (state.resumePending).
266
- * Both keep the instance bound to the same task, so the claim-release rule
267
- * (tick/tick-finalize.ts), the conflict-handback rule (tick/tick-apply.ts), and claims.ts's
268
- * idle test all read this one predicate instead of repeating the revision/resume conjunction. */
269
- export function hasOutstandingWork(s) {
270
- return s.revision !== undefined || s.resumePending === true;
271
- }
272
223
  /** Per-role cache of the persisted live claim, keyed by the state file's stat — one stat per
273
224
  * role per call in steady state, a read only when the file changed (cachedByStat's contract),
274
225
  * so a team poll's allLiveClaims does no read I/O once every claim is known. */
275
226
  const liveClaimCache = new Map();
276
- /** Every loop id whose persisted claim must be considered — the built-in catalog roles
227
+ /** Every runner id whose persisted claim must be considered — the built-in catalog roles
277
228
  * (allRoleIds) plus the parallel-work instance ids (feature-2, bugfix-3, …) that have a state
278
229
  * file. Instances are not in the built-in catalog, so they are enumerated from the state
279
230
  * directory rather than the config: a claim lives in its role's state file, and reading the
@@ -282,17 +233,13 @@ const liveClaimCache = new Map();
282
233
  * base). Any state file whose id resolves away from itself under baseRoleOf is an instance
283
234
  * id; the named non-role files in that directory (orchestrator.json, claude-account.json, …)
284
235
  * resolve to themselves and so are skipped, as are the base roles allRoleIds already names. */
285
- function liveClaimLoopIds(root) {
236
+ function liveClaimRunnerIds(root) {
286
237
  const ids = new Set(allRoleIds());
287
- try {
288
- for (const name of readdirSync(path.join(root, STATE_DIR, "state"))) {
289
- const id = name.replace(/\.json$/, "");
290
- if (baseRoleOf(id) !== id)
291
- ids.add(id);
292
- }
293
- }
294
- catch {
295
- // no state directory yet — the catalog roles (fresh, claim-less) suffice
238
+ // No state directory yet reads empty — the catalog roles (fresh, claim-less) suffice.
239
+ for (const name of dirEntryNames(path.join(root, STATE_DIR, "state"))) {
240
+ const id = name.replace(/\.json$/, "");
241
+ if (baseRoleOf(id) !== id)
242
+ ids.add(id);
296
243
  }
297
244
  return [...ids];
298
245
  }
@@ -308,10 +255,10 @@ function liveClaimLoopIds(root) {
308
255
  * under `feature-2`; callers fold it to its base. */
309
256
  export function allLiveClaims(root) {
310
257
  const out = new Map();
311
- for (const id of liveClaimLoopIds(root)) {
258
+ for (const id of liveClaimRunnerIds(root)) {
312
259
  const file = statePath(root, id);
313
260
  const claim = cachedByStat(liveClaimCache, file, file, () => {
314
- const s = loadLoopState(root, id);
261
+ const s = loadRunnerState(root, id);
315
262
  return s.claim ? { role: id, key: s.claim.key } : null;
316
263
  }, (v) => ({ ...v }));
317
264
  if (claim)
@@ -0,0 +1,102 @@
1
+ import { writeJsonAtomic } from "../files/json-files.js";
2
+ import { statePath } from "../paths.js";
3
+ import { tryOrWarn } from "../events/events.js";
4
+ /** The runner's persisted state file — one JSON object per role under .tumwater/ — and the
5
+ * observation-window counter reset: fresh-state defaults, the atomic save, and zeroCounters
6
+ * (the tolerant load and the live-claim scan are runner-state-load.ts). The scheduling POLICY
7
+ * that mutates this state from run outcomes and operator wakes — the outcome application and
8
+ * resume limits in run-apply.ts, the clock arithmetic (backoff ladders, wakes, yield ring) in
9
+ * backoff.ts; the generic file helpers are src/files/files.ts and the JSON convention itself is
10
+ * src/files/json-files.ts. */
11
+ /** How many times a progressing-timeout review may be resumed before the next attempt
12
+ * reviews the change from scratch (RunnerState.reviewResume). The budget bounds a reviewer that
13
+ * keeps timing out mid-progress, so a wedged review converges to a fresh run instead of
14
+ * resuming the same session forever. */
15
+ export const REVIEW_RESUME_LIMIT = 2;
16
+ /** Approved patch-ids a runner remembers (RunnerState.approvedPatchIds). */
17
+ export const APPROVED_PATCH_IDS_MAX = 8;
18
+ export const SKIP_REASONS = [
19
+ "no-free-entry",
20
+ "claim-hold",
21
+ "deferred-backlog",
22
+ "deferred-no-work",
23
+ "deferred-plans-full",
24
+ "instances-full",
25
+ "maintenance-due-cap",
26
+ "yield-gap",
27
+ ];
28
+ /** A new RunnerState for one role, before its first run. */
29
+ export function freshRunnerState(role) {
30
+ return {
31
+ role,
32
+ runs: 0,
33
+ commits: 0,
34
+ nextRunAt: 0,
35
+ backoffSeconds: 0,
36
+ lastMainHead: "",
37
+ generatedTokens: 0,
38
+ peakContextTokens: 0,
39
+ totalCostUsd: 0,
40
+ dayStamp: "",
41
+ dayCostUsd: 0,
42
+ };
43
+ }
44
+ /** Persist the runner's state atomically via writeJsonAtomic, so a crash mid-write cannot
45
+ * leave a torn file behind. Its per-pid tmp name matters here: two processes can write one
46
+ * role's state concurrently — the orchestrator saves at run end and around its review gate
47
+ * while `tumwater reset-counters` rewrites the same file from the CLI process — and per-pid
48
+ * names give each writer its own tmp with a clean last-writer-wins. */
49
+ export function saveRunnerState(root, state) {
50
+ writeJsonAtomic(statePath(root, state.role), state);
51
+ }
52
+ /** Save for callers that cannot afford a throw — the orchestrator's poll loop, which has no
53
+ * catch, and a run's end, where a throw would skip run_end and the launch task's breaker fold:
54
+ * a failed write warns under the role's id and returns. The in-memory state stays ahead of the
55
+ * file until the runner's next successful save. */
56
+ export function saveRunnerStateBestEffort(root, state) {
57
+ tryOrWarn(root, state.role, "save the role's state", () => saveRunnerState(root, state));
58
+ }
59
+ /** Zero the accumulated counters (runs, commits, tokens, cost) so a fresh observation
60
+ * window can begin. Pure: returns a new state and preserves everything else — scheduling
61
+ * fields (nextRunAt, backoffSeconds), wake tracking (lastMainHead), and the last-result
62
+ * fields. peakContextTokens is zeroed too: under per-run semantics it holds the runner's
63
+ * last completed run's peak, so a fresh window must clear it or sleeping runners keep
64
+ * showing their old value until they next run. The daily cost budget window (dayStamp/
65
+ * dayCostUsd) is deliberately NOT zeroed: the budget is a safety valve, not an observation
66
+ * window — zeroing today's spend would let the cap be bypassed by running reset-counters.
67
+ */
68
+ export function zeroCounters(s) {
69
+ return { ...s, runs: 0, commits: 0, generatedTokens: 0, peakContextTokens: 0, totalCostUsd: 0 };
70
+ }
71
+ /** The skip reason a runner's persisted state yields when it does not run this round: a pending
72
+ * resume wait outranks backoff, anything else is idle. The shared tail of the two skip
73
+ * classifiers — once-round.ts's settleSkipped (which adds the disabled arm) and cli/cli-start.ts's
74
+ * once-summary (which adds the team-pause arm) — so the two surfaces cannot drift on which
75
+ * reason wins when several conditions hold at once. */
76
+ export function stateSkipReason(s) {
77
+ if (s.resumePending)
78
+ return "resume pending";
79
+ if (s.backoffSeconds > 0)
80
+ return "backoff"; // vocabulary-ok: skip-reason code is a state value until the state rename
81
+ return "idle";
82
+ }
83
+ /** True when the runner still owes follow-up work for its current task after a run: a rejected
84
+ * change it must revise (state.revision) or a session it must resume (state.resumePending).
85
+ * Both keep the instance bound to the same task, so the claim-release rule
86
+ * (run/run-finalize.ts), the conflict-handback rule (run/run-apply.ts), and claims.ts's
87
+ * idle test all read this one predicate instead of repeating the revision/resume conjunction. */
88
+ export function hasOutstandingWork(s) {
89
+ return s.revision !== undefined || s.resumePending === true;
90
+ }
91
+ /** True when the runner got an explicit operator wake after its last completed run
92
+ * (state.wokenAt newer than lastRunEndedAt), so the scheduler ships its min-interval gap and
93
+ * the need-based deferral stands the runner down. The single home of the "freshly woken since
94
+ * the last run end" test, previously repeated inline by the due-check, plan deferral, and
95
+ * need-based deferral (scheduling.ts's isEligible, planDeferral, and deferRunReason) and by
96
+ * the orchestrator's fresh-demand and plans-full stand-down tests (scheduling-skip.ts's
97
+ * per-runner demand and scheduling-admit.ts's stand-down guard), so those surfaces cannot drift
98
+ * on what counts
99
+ * as a fresh wake. */
100
+ export function freshlyWoken(s) {
101
+ return s.wokenAt !== undefined && s.wokenAt > (s.lastRunEndedAt ?? 0);
102
+ }