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
@@ -0,0 +1,98 @@
1
+ import { runCommitSha } from "./run-detail-data.js";
2
+ import { formatEvent } from "../events/event-format.js";
3
+ import { eventTypeLabel } from "../events/event-labels.js";
4
+ import { roleLabel } from "../roles/role-labels.js";
5
+ import { shortSpanPhrase } from "../text/phrases.js";
6
+ import { shortSha } from "../text/format.js";
7
+ import { piLogPath } from "../paths.js";
8
+ import { readRunTranscript } from "../ui/transcript-run.js";
9
+ import { gitTry } from "../git/git-run.js";
10
+ /** The `git show --stat` summary or full `--patch` of a run's pinned commit. The sha is the
11
+ * authored commit the run pinned: a merge that rebased gives main a different sha with the
12
+ * same changes, so this describes what the run changed. */
13
+ async function runCommitShow(root, sha, what) {
14
+ if (sha === null)
15
+ return "(no commit: this run sent nothing to merge)";
16
+ const out = await gitTry(root, "show", what, "--format=", sha);
17
+ if (out === null)
18
+ return `(commit ${shortSha(sha)} is not in this repository)`;
19
+ return out.trimEnd();
20
+ }
21
+ /** The pinned commit's stat text for a run, shared by `run --stat` and GET /api/run so both
22
+ * apply one rule for the sha and the "(no commit …)" / "(commit … is not in this repository)"
23
+ * texts. */
24
+ export function runDetailStat(root, d) {
25
+ return runCommitShow(root, runCommitSha(d), "--stat");
26
+ }
27
+ /** The pinned commit's full patch for a run, for `history --patch`. Like runDetailStat it
28
+ * shows the commit the run pinned (runCommitSha), not the `merged as` sha, so the stat and
29
+ * the patch describe the same commit; the placeholder texts are the stat's. GET /api/run is its
30
+ * second caller (the dashboard's "Show patch" button). */
31
+ export function runDetailPatch(root, d) {
32
+ return runCommitShow(root, runCommitSha(d), "--patch");
33
+ }
34
+ /** The human view of a RunDetail: one summary line — result (or the in-flight/unpaired
35
+ * marker), duration, usage, and the model the run started on when its `run_start` carried one
36
+ * (after the usage, before the pinned-commit sha), plus the pinned commit sha when the block's
37
+ * `merge_queued`/`merge_done` event carries one — then each event through formatEvent, the
38
+ * shared renderer `logs` and the
39
+ * TUI use, so the trail reads exactly like the feed it came from. A queued run whose merge
40
+ * has finished shows that verdict in the header and ends with the merge's outcome line; a
41
+ * leftover-recovery run's verdict is the failed attempt it re-queued (resolveQueuedResult),
42
+ * logged before the run began, so that outcome line leads the trail instead — the trail stays
43
+ * in time order. */
44
+ export function renderRunDetail(d) {
45
+ const summary = [];
46
+ if (d.endTs === null)
47
+ summary.push(d.startTs === null ? "unpaired" : "in flight");
48
+ else
49
+ summary.push(d.result ? eventTypeLabel(d.result) : "?");
50
+ if (d.durationMs !== null)
51
+ summary.push(shortSpanPhrase(d.durationMs));
52
+ if (d.usage !== "")
53
+ summary.push(d.usage);
54
+ if (d.model !== null)
55
+ summary.push(d.model);
56
+ const sha = runCommitSha(d);
57
+ let header = `${roleLabel(d.role)} run #${d.run} — ${summary.join(" · ")}`;
58
+ if (sha !== null)
59
+ header += ` · commit ${shortSha(sha)}`;
60
+ if (d.mergedSha !== null && d.mergedSha !== sha)
61
+ header += ` · merged as ${shortSha(d.mergedSha)}`;
62
+ if (d.reverted !== null) {
63
+ header += ` · reverted ${shortSha(d.reverted.sha)}`;
64
+ if (d.reverted.kind === "main") {
65
+ if (d.reverted.failingTests[0] !== undefined)
66
+ header += ` (failing: ${d.reverted.failingTests[0]})`;
67
+ }
68
+ else {
69
+ header += ` (by operator)`; // vocabulary-ok: the deliberate-revert writer this header names
70
+ }
71
+ }
72
+ const trail = d.events.map(formatEvent);
73
+ if (d.merge !== null) {
74
+ const first = d.events[0];
75
+ if (first !== undefined && d.merge.ts < first.ts)
76
+ trail.unshift(formatEvent(d.merge));
77
+ else
78
+ trail.push(formatEvent(d.merge));
79
+ }
80
+ return [header, ...trail].join("\n");
81
+ }
82
+ /** The transcript block of `--transcript`: the rendered lines of the agent runs that started in the
83
+ * run's window, or `found: []` plus the `note` the text view prints in their place. The
84
+ * dashboard's `/api/run?transcript` serves the same text. */
85
+ export function runTranscriptLines(root, role, runNumber, detail, opts = {}) {
86
+ if (detail.startTs === null) {
87
+ return { found: [], note: `(no transcript: run ${runNumber}'s start rotated out of the event log)` };
88
+ }
89
+ return {
90
+ found: readRunTranscript(piLogPath(root, role), detail.startTs, detail.endTs, {
91
+ includePrompts: opts.includePrompts,
92
+ grep: opts.grep?.toLowerCase(),
93
+ }),
94
+ note: opts.grep !== undefined
95
+ ? `(no transcript entries matching "${opts.grep}" in ${role} run ${runNumber})`
96
+ : `(no transcript for ${role} run ${runNumber}: rotated out of .tumwater/log/${role}.pi.jsonl, or the run started no agent)`,
97
+ };
98
+ }
@@ -0,0 +1,168 @@
1
+ /** Command half of `tumwater history <role> <n>` and its `--last` / `--stat` / `--patch` /
2
+ * `--transcript` forms: the read-only command body that resolves which run to open, validates
3
+ * it, and prints the rendered trail (or the not-found line). The payload is collected by
4
+ * run-detail-data.ts (runCommitSha / runMergedSha beside it) and rendered by
5
+ * run-detail-render.ts, so the CLI, the TUI's History drill-down, and the GUI's /api/run serve
6
+ * the same text. Read-only over the event log, stdout only. */
7
+ import { fail, say, sayJson } from "../cli/cli-output.js";
8
+ import { parseCountFlag } from "../cli/cli-args.js";
9
+ import { knownRoleIdsCached } from "../config/config.js";
10
+ import { HISTORY_MAX_RUNS, HISTORY_RESULT_FLAG_NAMES, historyClass, readRunRows, runRowsOnModels } from "../history/history-data.js";
11
+ import { parseHistoryModelList, parseHistoryResultList } from "../history/history.js";
12
+ import { readRunDetail } from "./run-detail-data.js";
13
+ import { runDetailPatch, runDetailStat, renderRunDetail, runTranscriptLines } from "./run-detail-render.js";
14
+ import { unknownRoleMessage } from "../roles/roles.js";
15
+ import { canonicalRoleId } from "../roles/legacy-role-ids.js";
16
+ /** The run command's synopsis, word for word what cli.ts's dispatcher gate and cmdRunDetail's own
17
+ * arity and unknown-role guards fail with — one string so the three sites cannot drift apart
18
+ * when the command's shape changes. */
19
+ export const RUN_DETAIL_USAGE = "tumwater history <role> [<n>] [--last [--result <class>[,...]] [--model <selector>[,...]]] [--stat] [--patch] [--transcript [--prompt] [--grep <text>]] [--json]";
20
+ /** The shared not-found frame both wordings complete — what the scan looked for, the role,
21
+ * and that the scanned window (not the world) is what came up empty. Exactly two consumers,
22
+ * the not-found wordings below (the numbered form's exported message and the --last form's
23
+ * exported sibling), so the trailing hedge cannot drift between them. */
24
+ function runNotFoundPhrase(role, what) {
25
+ return `no ${what} for ${role} in the scanned window (the retained log may have rotated past it)`;
26
+ }
27
+ /** The not-found wording a missed run lookup owes its surface — `tumwater history <role> <n>`'s
28
+ * stdout line and the GUI /api/run endpoint's 404 JSON error, one template so the CLI's prose
29
+ * and the browser's error card cannot drift when the phrasing moves. Names the shape a user
30
+ * can act on: the run number, the role, and that the scanned window (not the world) is what
31
+ * came up empty. */
32
+ export function runNotFoundMessage(role, runNumber) {
33
+ return runNotFoundPhrase(role, `run #${runNumber}`);
34
+ }
35
+ /** The not-found wording for `tumwater history <role> --last`, sibling of runNotFoundMessage —
36
+ * the same surfaces but naming what the last-form scan actually asked for: the newest
37
+ * completed run, not a number the user supplied. Two consumers: cmdRunDetail's stdout line and
38
+ * the GUI /api/run?last=1 404 error, so the CLI and the browser cannot word the miss apart. */
39
+ export function runNotFoundLastMessage(role, classes = null, models = null) {
40
+ const flagNames = classes?.map((c) => HISTORY_RESULT_FLAG_NAMES[c]).join(",");
41
+ const what = `completed run${classes ? ` with result "${flagNames}"` : ""}${models ? ` on model "${models.join(",")}"` : ""}`;
42
+ return runNotFoundPhrase(role ?? "any role", what);
43
+ }
44
+ /** The newest completed run's row for a role (`null` for every role), or null when the scanned
45
+ * window holds none: the run `tumwater history <role> --last` opens, and bare `history --last`
46
+ * with no role. With neither filter it is readRunRows' newest row; with `classes` it is the
47
+ * newest row in those outcome classes, and with `models` the newest that started on one of those
48
+ * selectors. Also GET /api/run?last=1 with no role. */
49
+ export function newestRunRow(root, role, classes, models) {
50
+ if (!classes && !models)
51
+ return readRunRows(root, 1, role)[0] ?? null;
52
+ const rows = models ? runRowsOnModels(readRunRows(root, HISTORY_MAX_RUNS, role), models) : readRunRows(root, HISTORY_MAX_RUNS, role);
53
+ return rows.find((r) => !classes || classes.includes(historyClass(r.result))) ?? null;
54
+ }
55
+ /** The newest completed run's number for a role, or null when the scanned window holds none:
56
+ * the run `history <role> <n>` resolves for `--last`; newestRunRow's number. */
57
+ function newestRunNumber(root, role, classes, models) {
58
+ return newestRunRow(root, role, classes, models)?.run ?? null;
59
+ }
60
+ /** `tumwater history <role> [<n>] [--last [--result <class>[,...]]] [--stat] [--patch]
61
+ * [--transcript [--prompt] [--grep <text>]] [--json]`: print one completed
62
+ * run's full event trail. Read-only: stdout only, no state file created — a run the
63
+ * scan cannot find prints a not-found line and exits 0 (history's empty-output convention:
64
+ * an absent record is an answer, not a failure), and under --json it prints the JSON document
65
+ * `null` instead — the report --json precedent
66
+ * that every exit-0 output is parseable, never prose. `tumwater history <role> --last` resolves
67
+ * the role's newest completed run in the scanned window and prints its trail exactly as the
68
+ * numbered form would — the summary header and event rendering are shared, only the number's
69
+ * provenance differs (readRunRows' newest-first scan, not the user's argument). --last and
70
+ * a numeric <n> are rivals: exactly one positional under --last, exactly two without. With
71
+ * --last, `--result <class>[,...]` picks the newest run in the listed outcome classes instead
72
+ * (the listing's classes, scanning up to HISTORY_MAX_RUNS runs); it needs --last. `--model
73
+ * <selector>[,...]` with --last keeps only runs that started on a listed model (the listing's
74
+ * exact match) and composes with --result; it needs --last too. With --last and no role, the
75
+ * newest run of any role is opened (newestRunRow with a null role) and renders as its
76
+ * role's form. */
77
+ export async function cmdRunDetail(root, positionals, json, { last = false, stat = false, patch = false, transcript = false, prompt = false, grep = null, result = null, model = null } = {}) {
78
+ // Positional arity by form: cli.ts has already peeled the flags off and gated them (only
79
+ // --json and --last are admitted), so anything left over that does not fit the chosen form
80
+ // is a mistake. The role is checked first, so a typo is named whatever the count; then
81
+ // --last takes exactly one positional (<role>) and the numeric path exactly two (<role> <n>).
82
+ const bare = last && positionals.length === 0;
83
+ let role = canonicalRoleId(positionals[0] ?? "");
84
+ // The role is validated like every role-targeting command's --role (knownRoleIdsCached: the
85
+ // built-ins plus user-defined roles, read through the never-throwing cached loader so a
86
+ // transiently broken tumwater.json cannot take a read-only view down), with the shared
87
+ // unknown-role wording plus the usage the positional command owes.
88
+ if (positionals.length >= 1) {
89
+ const ids = knownRoleIdsCached(root);
90
+ if (!ids.includes(role))
91
+ fail(`${unknownRoleMessage(role, ids)} (usage: ${RUN_DETAIL_USAGE})`);
92
+ }
93
+ const arity = last ? 1 : 2;
94
+ if (positionals.length !== arity && !bare) {
95
+ if (positionals.length === 1) {
96
+ fail(`history ${role} needs a run number or --last — \`tumwater history --role ${role}\` lists its runs (usage: ${RUN_DETAIL_USAGE})`);
97
+ }
98
+ if (positionals.length === 2 && last) {
99
+ fail(`history --last takes no run number (got "${positionals[1]}") (usage: ${RUN_DETAIL_USAGE})`);
100
+ }
101
+ fail(`usage: ${RUN_DETAIL_USAGE}`);
102
+ }
103
+ if (prompt && !transcript)
104
+ fail(`history --prompt needs --transcript (usage: ${RUN_DETAIL_USAGE})`);
105
+ if (grep !== null && !transcript)
106
+ fail(`history --grep needs --transcript on a one-run trail (usage: ${RUN_DETAIL_USAGE})`);
107
+ if (result !== null && !last)
108
+ fail(`history --result needs --last on a one-run trail (usage: ${RUN_DETAIL_USAGE})`);
109
+ if (model !== null && !last)
110
+ fail(`history --model needs --last on a one-run trail (usage: ${RUN_DETAIL_USAGE})`);
111
+ const classes = result !== null ? parseHistoryResultList(result) : null;
112
+ const models = model !== null ? parseHistoryModelList(model) : null;
113
+ // The run number: either the user's positive integer (the numbered path, through the
114
+ // shared count parser — a non-positive or non-numeric n fails naming the shape; there is
115
+ // no cap — the number selects, it does not size the scan) or the newest completed run's
116
+ // number, read from readRunRows' newest-first scan for the role. An empty scan is not
117
+ // found by construction, so the not-found path below takes it with the last-form wording.
118
+ let runNumber;
119
+ if (bare) {
120
+ // `history --last` with no role: the newest completed run of any role; its role is the row's.
121
+ const row = newestRunRow(root, null, classes, models);
122
+ if (row !== null)
123
+ role = row.role;
124
+ runNumber = row?.run ?? null;
125
+ }
126
+ else {
127
+ runNumber = last ? newestRunNumber(root, role, classes, models) : parseCountFlag("history <n>", positionals[1]);
128
+ }
129
+ const detail = runNumber === null ? null : readRunDetail(root, role, runNumber);
130
+ if (runNumber === null) {
131
+ // The --last scan came up empty: not found by construction, the last-form wording.
132
+ if (json)
133
+ sayJson(null);
134
+ else
135
+ say(runNotFoundLastMessage(bare ? null : role, classes, models));
136
+ return;
137
+ }
138
+ if (detail === null) {
139
+ // Not found stays exit 0 in both renderings, but --json still owes a parseable document in
140
+ // every exit-0 case (the report --json precedent, the invariant this command's own doc
141
+ // comment claims): `null` is the JSON answer for a single-record lookup that missed — the
142
+ // prose line would end a jq pipe mid-team-incident.
143
+ if (json)
144
+ sayJson(null);
145
+ else
146
+ say(runNotFoundMessage(role, runNumber));
147
+ return;
148
+ }
149
+ // --json swaps the renderer for the collector's own payload, exactly as history --json: a
150
+ // JSON document in every exit-0 case, never prose.
151
+ const footer = {};
152
+ if (stat)
153
+ footer.stat = await runDetailStat(root, detail);
154
+ if (patch)
155
+ footer.patch = await runDetailPatch(root, detail);
156
+ const transcriptLines = transcript
157
+ ? runTranscriptLines(root, role, runNumber, detail, { includePrompts: prompt, grep: grep ?? undefined })
158
+ : null;
159
+ if (json) {
160
+ sayJson({ ...detail, ...footer, ...(transcriptLines === null ? {} : { transcript: transcriptLines.found }) });
161
+ }
162
+ else {
163
+ const transcriptText = transcriptLines === null
164
+ ? []
165
+ : [transcriptLines.found.length > 0 ? transcriptLines.found.join("\n") : transcriptLines.note];
166
+ say([renderRunDetail(detail), ...Object.values(footer), ...transcriptText].join("\n\n"));
167
+ }
168
+ }
@@ -1,11 +1,11 @@
1
- import { hasOutstandingWork, saveLoopState } from "../loop/loop-state.js";
1
+ import { hasOutstandingWork, saveRunnerStateBestEffort } from "../runner/runner-state.js";
2
2
  import { branchHead } from "../git/git.js";
3
3
  import { logEventBestEffort, usageFragment, warnEventBestEffort } from "../events/events.js";
4
- import { observeClaudeAccount } from "../agent/claude/claude-account.js";
5
- import { ERROR_STREAK_WARN, QUIET_KILL_RESUME_LIMIT, applyTickOutcome } from "./tick-apply.js";
6
- import { restoreMidTickWake } from "../scheduling/backoff.js";
4
+ import { observeAccount } from "../agent/subscription-account.js";
5
+ import { ERROR_STREAK_WARN, QUIET_KILL_RESUME_LIMIT, applyRunOutcome } from "./run-apply.js";
6
+ import { restoreMidRunWake } from "../scheduling/backoff.js";
7
7
  import { consecutiveFailuresWarning } from "../text/phrases.js";
8
- /** The tick results that end an instance's claim: the tick found nothing to do, refused the
8
+ /** The run results that end an instance's claim: the run found nothing to do, refused the
9
9
  * entry, was aborted by the user, or was skipped — none of them leaves work in flight. */
10
10
  const CLAIM_RELEASING = new Set([
11
11
  "no_change",
@@ -13,43 +13,43 @@ const CLAIM_RELEASING = new Set([
13
13
  "user_aborted",
14
14
  "skipped",
15
15
  ]);
16
- /** Fold a finished tick's outcome into the loop's state and the event feed — everything that
17
- * happens AFTER the pi run, extracted from LoopRunner.tick (src/loop/loop.ts) so the runner keeps
18
- * only tick lifecycle (worktree, sessions, abort) and this module owns the per-result
16
+ /** Fold a finished run's outcome into the role's state and the event feed — everything that
17
+ * happens AFTER the pi run, extracted from RoleRunner.run (src/runner/runner.ts) so the runner
18
+ * keeps only run lifecycle (worktree, sessions, abort) and this module owns the per-result
19
19
  * bookkeeping policy. Order matters here and is pinned by behavior, not by a test seam: the
20
- * outcome's folds are applied to state before applyTickOutcome schedules the next run, the
20
+ * outcome's folds are applied to state before applyRunOutcome schedules the next run, the
21
21
  * main-head read happens while `running` is still true (a poll that runs in that window must not
22
- * double-wake the loop), and the warnings read the streaks exactly once per episode, after the
22
+ * double-wake the role), and the warnings read the streaks exactly once per episode, after the
23
23
  * schedule. */
24
- export async function finalizeTick(deps) {
25
- const { root, role, mainBranch, config, state: s, usage, tick } = deps;
24
+ export async function finalizeRun(deps) {
25
+ const { root, role, mainBranch, config, state: s, usage, runNumber } = deps;
26
26
  let { outcome } = deps;
27
- // A re-queued leftover's merge failure is not the tick's own result, so it rides the
28
- // outcome separately: applyTickOutcome feeds it into the error streak, and the warning
29
- // below names it — even though runTick cleared `lastError` so it never latched onto
27
+ // A re-queued leftover's merge failure is not the run's own result, so it rides the
28
+ // outcome separately: applyRunOutcome feeds it into the error streak, and the warning
29
+ // below names it — even though runOnce cleared `lastError` so it never latched onto
30
30
  // `run_end` (BUGS.md 2026-09-21).
31
31
  if (deps.recoveryFailure !== undefined)
32
32
  outcome.recoveryFailure = deps.recoveryFailure;
33
- // A main_red tick's cause is written for that result (src/baseline/main-red.ts), not a leftover
33
+ // A main_red run's cause is written for that result (src/baseline/main-red.ts), not a leftover
34
34
  // merge failure: fold it onto the state here so `run_end` logs it like every other
35
35
  // failure's and the digest can itemize what the Outcome table's main_red cells mean
36
- // (BUGS.md 2026-09-28). runTick cleared `lastError` at its start, so nothing foreign
36
+ // (BUGS.md 2026-09-28). runOnce cleared `lastError` at its start, so nothing foreign
37
37
  // can precede this.
38
38
  if (outcome.error !== undefined)
39
39
  s.lastError = outcome.error;
40
- // Read main's current head while this tick is still reserved (running=true): the
41
- // applyTickOutcome below clears running, and a poll that runs between that clear and a later
42
- // head update would see a stale lastMainHead and wake the loop again on the very move
43
- // that triggered this tick — a duplicate wake event plus an extra tick for one world change.
40
+ // Read main's current head while this run is still reserved (running=true): the
41
+ // applyRunOutcome below clears running, and a poll that runs between that clear and a later
42
+ // head update would see a stale lastMainHead and wake the role again on the very move
43
+ // that triggered this run — a duplicate wake event plus an extra run for one world change.
44
44
  // branchHead reads the ref files first (microsecond-scale, like the orchestrator's per-poll
45
45
  // watch) and spawns `git rev-parse` only when they cannot resolve it; a null result keeps
46
46
  // the previous value rather than waking on "main moved" to nowhere.
47
47
  s.lastMainHead = (await branchHead(root, mainBranch)) ?? s.lastMainHead;
48
- // Record the outcome on state and schedule the next run (see src/tick/tick-apply.ts for the
48
+ // Record the outcome on state and schedule the next run (see src/run/run-apply.ts for the
49
49
  // per-result policy: prompt retry, backoff, bounded cut-off resumes).
50
- applyTickOutcome(s, config, role, outcome);
51
- // A tick that ends without producing or keeping work releases this instance's backlog claim
52
- // (plans/parallel-work-instances.md "Claims", part 4/7): the next tick may take a different
50
+ applyRunOutcome(s, config, role, outcome);
51
+ // A run that ends without producing or keeping work releases this instance's backlog claim
52
+ // (plans/parallel-work-instances.md "Claims", part 4/7): the next run may take a different
53
53
  // entry. Every other result — queued, changed, rejected with a revision, an error, an abort
54
54
  // that resumes — keeps the claim so the same instance retries it; an outstanding revision or
55
55
  // a pending resume also holds it, because the entry is still this instance's task.
@@ -60,79 +60,91 @@ export async function finalizeTick(deps) {
60
60
  s.claim = undefined;
61
61
  logEventBestEffort(root, { source: role, type: "claim", action: "released", title, reason: outcome.result });
62
62
  }
63
- // A wake consumed while this tick ran stamped the shared state in place, but the outcome
63
+ // A wake consumed while this run ran stamped the shared state in place, but the outcome
64
64
  // schedule above overwrites it (lastRunEndedAt past wokenAt, nextRunAt a fresh gap or
65
65
  // backoff out), so a plain `tumwater wake --role` with an empty queue would silently wait
66
66
  // out the whole interval (BUGS.md 2026-09-25). Re-apply it here — exactly like a wake
67
- // arriving one poll after the tick ended.
68
- restoreMidTickWake(s);
69
- saveLoopState(root, s);
70
- // One warning per error episode (BUGS.md 2026-09-15: every loop failing identically
71
- // looked like a quiet team). applyTickOutcome increments the streak on error-class
72
- // outcomes, preserves it across a plain `queued` tick (the tick's merge resolves it),
67
+ // arriving one poll after the run ended.
68
+ restoreMidRunWake(s);
69
+ // A failed save warns and the run still ends: the in-memory state already holds the fold and
70
+ // the runner's next save writes it, while a throw here would skip run_end and the launch
71
+ // task's breaker fold.
72
+ saveRunnerStateBestEffort(root, s);
73
+ // One warning per error episode (BUGS.md 2026-09-15: every role failing identically
74
+ // looked like a quiet team). applyRunOutcome increments the streak on error-class
75
+ // outcomes, preserves it across a plain `queued` run (the run's merge resolves it),
73
76
  // and resets it on any other completed result — so warn only when THIS outcome incremented
74
77
  // the streak up to the threshold: a preserved streak already sitting at the bar must not
75
- // re-warn on the queued tick's end. A rejection's crossing warns from the merge side
78
+ // re-warn on the queued run's end. A rejection's crossing warns from the merge side
76
79
  // instead (writeMergeOutcome, src/merge/merge-slot.ts), which is where rejections resolve.
77
80
  if ((outcome.result === "error" || outcome.recoveryFailure !== undefined) &&
78
81
  (s.consecutiveErrors ?? 0) === ERROR_STREAK_WARN) {
79
82
  warnEventBestEffort(root, role, consecutiveFailuresWarning(s.consecutiveErrors ?? 0, s.lastError ?? outcome.recoveryFailure ?? "unknown error"));
80
83
  }
81
- // One warning per quiet-kill episode (BUGS.md 2026-09-18): a loop burning an hour per
82
- // tick with no output must not look like a sleeping loop. applyTickOutcome grows the
84
+ // One warning per quiet-kill episode (BUGS.md 2026-09-18): a role burning an hour per
85
+ // run with no output must not look like a sleeping role. applyRunOutcome grows the
83
86
  // streak on each kill and resets it on any other result, so the crossing fires once;
84
87
  // the give-up (fresh session + backoff) follows on the next kill.
85
88
  if ((s.quietKillStreak ?? 0) === QUIET_KILL_RESUME_LIMIT) {
86
89
  warnEventBestEffort(root, role, `${s.quietKillStreak} consecutive stalls (no progress): ${s.lastError ?? "unknown hang"}`);
87
90
  }
88
- // Per-tick usage: the event feed is where operators see
89
- // spend — this tick's tokens and cost ride on run_end so a budget pause or a money-burning
90
- // no_change/error/rejected/refused tick shows what it spent without diffing status-table
91
- // snapshots. Both fields are per-tick windows (reset at tick start, folded in over every
92
- // pi run of the tick); they ride on HarnessEvent's index signature like other payloads and
93
- // are omitted when zero so skipped ticks render byte-identical to a pre-feature line.
94
- // The tick's wall-clock span rides on run_end too: the
95
- // failure digest prices a tick by its own durationMs first (src/failure/failure-data.ts
96
- // tickDurationMs), so the field is ALWAYS present — a tick that ends within the same
97
- // Date.now() millisecond it started (an instant early-abort error tick) or crosses a
91
+ // Per-run usage: the event feed is where operators see
92
+ // spend — this run's tokens and cost ride on run_end so a budget pause or a money-burning
93
+ // no_change/error/rejected/refused run shows what it spent without diffing status-table
94
+ // snapshots. Both fields are per-run windows (reset at run start, folded in over every
95
+ // pi run of the run); they ride on HarnessEvent's index signature like other payloads and
96
+ // are omitted when zero so skipped runs render byte-identical to a pre-feature line.
97
+ // The run's wall-clock span rides on run_end too: the
98
+ // failure digest prices a run by its own durationMs first (src/failure/failure-data.ts
99
+ // runDurationMs), so the field is ALWAYS present — a run that ends within the same
100
+ // Date.now() millisecond it started (an instant early-abort error run) or crosses a
98
101
  // backward clock step still attests its 0 ms, where the old omit-when-zero convention
99
102
  // dropped the field and broke the "every run_end carries a span" contract (BUGS.md
100
103
  // 2026-09-29). Unlike the usage fields, run_end's renderer never prints the span, so an
101
104
  // always-present 0 changes no rendered line.
102
- const tickDurationMs = Math.max(0, Date.now() - deps.tickStartedAt);
103
- // The account state (plans/agent-backends.md part 6a): fold this tick's billing answer and
104
- // rate-window readings into .tumwater/state/claude-account.json for the sibling parts that
105
- // throttle or display the subscription window. Best-effort — the file is a cache, and an
106
- // unwritable state dir must not fail a tick whose real work is already done.
105
+ const runDurationMs = Math.max(0, Date.now() - deps.runStartedAt);
106
+ // The account state (plans/agent-backends.md part 6a): fold this run's billing answer and
107
+ // rate-window readings into the reporting backend's .tumwater/state/<backend>-account.json
108
+ // for the sibling parts that throttle or display the subscription window. Best-effort — the
109
+ // file is a cache, and an unwritable state dir must not fail a run whose real work is
110
+ // already done. A pi run never reports either, so in practice claude owns the file until a
111
+ // second subscription backend lands (plans/codex-backend.md).
107
112
  if (usage.billing !== undefined || usage.rateWindows !== undefined) {
108
113
  try {
109
- observeClaudeAccount(root, { billing: usage.billing, rateWindows: usage.rateWindows });
114
+ observeAccount(root, usage.agent ?? "pi", {
115
+ billing: usage.billing,
116
+ rateWindows: usage.rateWindows,
117
+ });
110
118
  }
111
119
  catch {
112
- // The next reporting run rewrites it; nothing this tick depends on is lost.
120
+ // The next reporting run rewrites it; nothing this run depends on is lost.
113
121
  }
114
122
  }
115
- // The per-provider split of this tick's usage (plans: per-provider spend in the day
123
+ // The per-provider split of this run's usage (plans: per-provider spend in the day
116
124
  // report): a Record<provider, {costUsd, notionalCostUsd}> for the report's `Cost by
117
- // provider` line. Omitted when empty like the prompt-token fields, so a tick whose runs
125
+ // provider` line. Omitted when empty like the prompt-token fields, so a run whose runs
118
126
  // carried no provider renders byte-identically to today.
119
127
  const providerCost = usage.serializeProviderCost();
120
128
  logEventBestEffort(root, {
121
129
  source: role,
122
130
  type: "run_end",
123
- run: tick,
131
+ run: runNumber,
124
132
  result: outcome.result,
125
133
  summary: outcome.summary,
126
134
  error: s.lastError,
127
- durationMs: tickDurationMs,
135
+ durationMs: runDurationMs,
128
136
  ...usageFragment(s.generatedTokens, usage.costUsd, usage.notionalCostUsd),
129
137
  ...(Object.keys(providerCost).length > 0 ? { providerCost } : {}),
130
- // The billing answer the tick's runs reported, so the report can tell notional spend from
138
+ // The billing answer the run's runs reported, so the report can tell notional spend from
131
139
  // real spend without re-deriving it from the file (plans/agent-backends.md part 6a).
132
140
  ...(usage.billing !== undefined ? { billing: usage.billing } : {}),
133
- // Per-tick prompt-token telemetry: how many prompt
134
- // tokens the tick sent, how many were cache reads, and how many went out before its first
135
- // edit. Omitted when zero like usageFragment's fields, so a tick that sent no prompt — and
141
+ // The backend that reported the billing/rate windows (plans/codex-backend.md part 3d/9):
142
+ // carried under the same condition the account file is written (observeAccount above), so
143
+ // the report folds a run's notional spend to the subscription backend whose runs made it.
144
+ ...(usage.billing !== undefined || usage.rateWindows !== undefined ? { agent: usage.agent } : {}),
145
+ // Per-run prompt-token telemetry: how many prompt
146
+ // tokens the run sent, how many were cache reads, and how many went out before its first
147
+ // edit. Omitted when zero like usageFragment's fields, so a run that sent no prompt — and
136
148
  // every run_end written by an older build — renders byte-identically.
137
149
  ...(usage.promptTokens > 0 ? { promptTokens: usage.promptTokens } : {}),
138
150
  ...(usage.cacheReadTokens > 0 ? { cacheReadTokens: usage.cacheReadTokens } : {}),
@@ -1,14 +1,13 @@
1
- // vocabulary: plain
2
- /** What a finished tick or merge is (the TickResult/TickOutcome vocabulary), shared by the
1
+ /** What a finished run or merge is (the RunResult/RunOutcome vocabulary), shared by the
3
2
  * runner that produces it, the dashboards that render it, and the state machine that applies
4
3
  * it. The application policy — which outcomes count which streaks, when a session resumes,
5
- * how the next run is scheduled — lives next door in tick-apply.ts; the clock arithmetic it
6
- * drives in backoff.ts. This module holds only the shared types. Split out of loop-state.ts —
7
- * the persisted state file's load/save — and later of the types.ts grab-bag, as LoopState and
4
+ * how the next run is scheduled — lives next door in run-apply.ts; the clock arithmetic it
5
+ * drives in backoff.ts. This module holds only the shared types. Split out of runner-state.ts —
6
+ * the persisted state file's load/save — and later of the types.ts grab-bag, as RunnerState and
8
7
  * HarnessEvent were before it. */
9
8
  export {};
10
9
  /** Record the review gate's verdict on a HEAD into `lastReview` — the field the author's next
11
- * tick prompt injects (tick-prompt.ts's buildRejectedReviewNote) and the same-head strike
10
+ * run prompt injects (run-prompt.ts's buildRejectedReviewNote) and the same-head strike
12
11
  * counter reads (review.ts's unreviewFailures). Mutates `s` in place; the timestamp is
13
12
  * stamped here so every call site shares one Date.now() read and the shape stays in one place
14
- * (loop-state.ts's lastReview). */
13
+ * (runner-state.ts's lastReview). */