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,15 +1,15 @@
1
1
  import { runPi } from "../pi/pi.js";
2
- import { agentSessionResumable } from "../agent/agent-backend.js";
2
+ import { backendSessionResumable } from "../agent/agent-backend.js";
3
3
  import { HOLD_BASE_MS } from "../team/team-hold.js";
4
4
  import { backendKindPhrase } from "../text/phrases.js";
5
- import { configForRole, resolvedAgentConfig, seamHoldKey, seamPairName } from "../config/config-views.js";
5
+ import { configForRole, resolvedBackendConfig, seamHoldKey, seamPairName } from "../config/config-views.js";
6
6
  import { ensureProviderLease } from "../concurrency/provider-lease.js";
7
7
  import { buildSummaryRequestPrompt } from "../prompt/prompt-followup.js";
8
8
  import { readClaudeAccount, subscriptionWindowRejected } from "../agent/claude/claude-account.js";
9
9
  import { observeOpencodeRun, opencodeModelKey } from "../agent/opencode/opencode-catalog.js";
10
10
  import { dailyCost, teamDailyCost, pairDailyCost } from "../budget/budget.js";
11
- import { loadLoopState } from "./loop-state.js";
12
- import { baseRoleOf, loopIds } from "../roles/loop-ids.js";
11
+ import { loadRunnerState } from "./runner-state-load.js";
12
+ import { baseRoleOf, runnerIds } from "../roles/runner-ids.js";
13
13
  import { buildStageFixPrompt } from "../prompt/gate-prompts.js";
14
14
  import { logEventBestEffort } from "../events/events.js";
15
15
  import { nativeLogPath, piLogPath, roleNotesPath, sessionDir } from "../paths.js";
@@ -17,15 +17,15 @@ import { piModelsPath } from "../pi/pi-models.js";
17
17
  import { DIRECTOR_ROLE } from "../roles/roles.js";
18
18
  import { cappedRequestTimeouts } from "../request-timeouts.js";
19
19
  /** Upper bound on how long the transient retry waits out a provider's Retry-After hint
20
- * before re-attempting a rate-limited run. Honoring the hint is the point; capping it is
21
- * what keeps one generous hint from consuming the tick's own run budget (the retry gets a
22
- * full fresh run budget, so a wait larger than the cap would spend the tick waiting, not
20
+ * before re-attempting a rate-limited agent run. Honoring the hint is the point; capping it is
21
+ * what keeps one generous hint from consuming the run's own agent run budget (the retry gets a
22
+ * full fresh agent run budget, so a wait larger than the cap would spend the run waiting, not
23
23
  * working). */
24
24
  const RATE_LIMIT_RETRY_AFTER_CAP_S = 120;
25
25
  /** What a hint-less 429 waits before its one retry. With no Retry-After from the provider, the
26
26
  * team's own constants state the refill physics: team/team-hold.ts's base hold is one minute,
27
27
  * "the shortest pause that lets the bucket refill". Retrying sooner re-enters the same exhausted
28
- * per-minute bucket the first request just emptied and burns the tick's only retry on a
28
+ * per-minute bucket the first request just emptied and burns the run's only retry on a
29
29
  * near-certain second 429. A present hint always wins instead; the cap above still bounds
30
30
  * whatever the wait ends up being. */
31
31
  const RATE_LIMIT_NO_HINT_RETRY_S = Math.round(HOLD_BASE_MS / 1000);
@@ -38,10 +38,10 @@ function sleepFor(ms) {
38
38
  * base role's own remaining cap when `maxDailyCostUsdPerRole` names it. The cap map and its
39
39
  * spend are keyed by BASE role, exactly as the scheduler's role-cap gate reads them
40
40
  * (src/gates/role-cap-gates.ts: `baseRoleOf` groups `feature`/`feature-2` and sums the
41
- * group's spend), so instance loop ids share their base's cap instead of silently falling
41
+ * group's spend), so instance runner ids share their base's cap instead of silently falling
42
42
  * through to the team budget. Returns undefined — no cap — for the director, a non-claude
43
43
  * seam, an account that is not API-billed, a disabled cap (0), or no remaining budget at all
44
- * (a non-positive cap is dropped by the argv builder). Spend is read from the persisted loop
44
+ * (a non-positive cap is dropped by the argv builder). Spend is read from the persisted runner
45
45
  * states: each role's daily window is what the budget gate and the dashboards sum, and this
46
46
  * runs before the authoring run records its own cost. A pair with a step cap of its own (a
47
47
  * priced fallback step, `fallbackStepCaps`) is also held to that step's remaining budget. */
@@ -51,7 +51,7 @@ function authoringBudgetCap(root, role, cfg, agent, pair) {
51
51
  if (readClaudeAccount(root)?.billing !== "api")
52
52
  return undefined;
53
53
  const now = Date.now();
54
- const states = loopIds(cfg).map((r) => loadLoopState(root, r));
54
+ const states = runnerIds(cfg).map((r) => loadRunnerState(root, r));
55
55
  let cap = cfg.maxDailyCostUsd - teamDailyCost(states, now);
56
56
  const base = baseRoleOf(role);
57
57
  const perRole = cfg.maxDailyCostUsdPerRole?.[base];
@@ -59,11 +59,11 @@ function authoringBudgetCap(root, role, cfg, agent, pair) {
59
59
  const members = states.filter((s) => baseRoleOf(s.role) === base);
60
60
  const spent = members.length > 0
61
61
  ? members.reduce((sum, s) => sum + dailyCost(s, now), 0)
62
- : dailyCost(loadLoopState(root, role), now);
62
+ : dailyCost(loadRunnerState(root, role), now);
63
63
  cap = Math.min(cap, perRole - spent);
64
64
  }
65
65
  // A fallback step with its own cap (applyFallbackModel's fallbackStepCaps, set only on the
66
- // derived view) lowers the run to what is left of the step's budget for the day.
66
+ // derived view) lowers the agent run to what is left of the step's budget for the day.
67
67
  const stepCap = cfg.fallbackStepCaps?.[pair];
68
68
  if (stepCap !== undefined)
69
69
  cap = Math.min(cap, stepCap - pairDailyCost(states, pair, now));
@@ -71,28 +71,28 @@ function authoringBudgetCap(root, role, cfg, agent, pair) {
71
71
  cap = Math.round(cap * 1e6) / 1e6;
72
72
  return cap > 0 ? cap : undefined;
73
73
  }
74
- /** The pi-invocation plumbing of one role loop, extracted from LoopRunner (src/loop/loop.ts):
75
- * the shared per-loop wiring for every run (role config, session dir, raw log, abort
76
- * signal), the one bounded transient-failure retry all runs share, the merge slot's
77
- * run (which watches only the shutdown signal), and the tick's SUMMARY follow-up turn.
78
- * It owns HOW a loop talks to pi; the LoopRunner tick state machine owns WHAT runs happen
79
- * and folds every run's spend back through the host's foldUsage (authoring) or
74
+ /** The pi-invocation plumbing of one role's runner, extracted from RoleRunner
75
+ * (src/runner/runner.ts): the shared per-runner wiring for every agent run (role config, session
76
+ * dir, raw log, abort signal), the one bounded transient-failure retry all agent runs share, the
77
+ * merge slot's agent run (which watches only the shutdown signal), and the run's SUMMARY follow-up
78
+ * turn. It owns HOW a runner talks to pi; the RoleRunner state machine owns WHAT agent runs happen
79
+ * and folds every agent run's spend back through the host's foldUsage (authoring) or
80
80
  * foldMergeUsage (merge/review). */
81
- export class LoopPi {
81
+ export class RunnerPi {
82
82
  host;
83
83
  constructor(host) {
84
84
  this.host = host;
85
85
  }
86
- /** The shared per-loop wiring for every pi run this tick makes in worktree `wt`: the
87
- * role-resolved config, this loop's session dir and raw log, and the per-tick abort signal.
86
+ /** The shared per-runner wiring for every agent run this run makes in worktree `wt`: the
87
+ * role-resolved config, this runner's session dir and raw log, and the per-run abort signal.
88
88
  * One place for those derivations — a new PiRunOptions field touches only here instead of
89
89
  * drifting between the author run and the SUMMARY follow-up. Callers override what differs
90
90
  * (the follow-up's capped timeouts, the merge's shutdown-only signal). */
91
- loopPiOpts(wt, prompt, sessionName, kind, resume = false, config, notesPath) {
91
+ runnerPiOpts(wt, prompt, sessionName, kind, resume = false, config, notesPath) {
92
92
  // A caller-supplied config (the merge conflict resolver's strong-tier one,
93
- // plans/model-tiers.md part 4/8) replaces the role's own for that one run; the role's
93
+ // plans/model-tiers.md part 4/8) replaces the role's own for that one agent run; the role's
94
94
  // config is the default for every other caller. Resolved once here so the native-log path
95
- // names the same agent the run actually uses.
95
+ // names the agent that run actually uses.
96
96
  const resolved = config ?? configForRole(this.host.config(), this.host.role);
97
97
  const maxBudgetUsd = kind === "author"
98
98
  ? authoringBudgetCap(this.host.root, this.host.role, this.host.config(), resolved.agent ?? "pi", seamPairName(resolved))
@@ -101,7 +101,7 @@ export class LoopPi {
101
101
  cwd: wt,
102
102
  prompt,
103
103
  ...(maxBudgetUsd !== undefined ? { maxBudgetUsd } : {}),
104
- // Every run names its kind explicitly; the marker it writes is the dashboards' demux
104
+ // Every agent run names its kind explicitly; the marker it writes is the dashboards' demux
105
105
  // key, so parts 2/5 and 4/5 may put author and gate runs in the same worktree.
106
106
  kind,
107
107
  config: resolved,
@@ -112,60 +112,61 @@ export class LoopPi {
112
112
  nativeLogFile: nativeLogPath(this.host.root, this.host.role, resolved.agent ?? "pi"),
113
113
  signal: this.host.runSignal(),
114
114
  // Only runAuthoringPi and its SUMMARY/stage-fix follow-ups pass this; a merge,
115
- // review, or conflict-resolution run has no notebook, and neither does the director.
115
+ // review, or conflict-resolution run has no notes, and neither does the director.
116
116
  notesPath,
117
117
  // A tool call silent for the configured stall threshold names itself in the event feed
118
118
  // while the quiet watchdog still counts down (BUGS.md 2026-09-13 sibling): before this,
119
119
  // a hung command was invisible until the kill. The dashboards derive their own flag from
120
120
  // the raw log, so only the harness event needs wiring here.
121
121
  onToolCallStalled: (message) => this.host.warn(message),
122
+ onLeftoversSwept: (message) => this.host.warn(message),
122
123
  // The translator's unmapped-native-type and cost_unknown warnings reach the event feed
123
- // as their own warning events, once per run.
124
+ // as their own warning events, once per agent run.
124
125
  onAgentWarning: (warning) => this.host.warn(`agent_unknown_event: ${warning}`),
125
- // A run that read an image its model cannot see (plans/image-input.md "Detecting a
126
+ // An agent run that read an image its model cannot see (plans/image-input.md "Detecting a
126
127
  // blind read"): the runner killed the authoring run (action "fail") or a gate run
127
- // merely recorded it ("logged") — each logged here with this loop's identity so the
128
- // feed names who, on which tick, read which file on which blind model.
128
+ // merely recorded it ("logged") — each logged here with this runner's identity so the
129
+ // feed names who, on which run, read which file on which blind model.
129
130
  onBlindRead: (path, action) => logEventBestEffort(this.host.root, {
130
131
  source: this.host.role,
131
132
  type: "image_blind",
132
- run: this.host.tickNumber(),
133
+ run: this.host.runNumber(),
133
134
  path,
134
135
  model: resolved.model,
135
136
  action,
136
137
  }),
137
138
  };
138
139
  }
139
- /** Run the orchestrator's merge slot's pi (merge queue 3/5): the shared per-loop wiring
140
+ /** Run the orchestrator's merge slot's pi (merge queue 3/5): the shared per-runner wiring
140
141
  * of runRolePi — role config, session dir, raw log, transient-failure retry — with usage
141
- * folded into the authoring loop's counters, but watching ONLY the harness shutdown signal
142
+ * folded into the authoring runner's counters, but watching ONLY the harness shutdown signal
142
143
  * (which may be undefined — PiRunOptions.signal is optional): a merge is outside any
143
- * tick, so `tumwater abort --role` (which targets a tick's per-tick abort controller) must
144
- * never reach it, and the stale per-tick controller of a finished tick must not abort it
144
+ * run, so `tumwater abort --role` (which targets a run's per-run abort controller) must
145
+ * never reach it, and the stale per-run controller of a finished run must not abort it
145
146
  * either. Session naming is the caller's (the reviewer composes its own from
146
- * ReviewContext.tick; merge-to-main.ts keeps its conflict-resolver naming through
147
+ * ReviewContext.runNumber; merge-to-main.ts keeps its conflict-resolver naming through
147
148
  * MergerContext.runPi).
148
149
  */
149
150
  async runMergePi(wt, prompt, sessionName, config) {
150
151
  return this.runWithTransientRetry({
151
- ...this.loopPiOpts(wt, prompt, sessionName, "gate", false, config),
152
+ ...this.runnerPiOpts(wt, prompt, sessionName, "gate", false, config),
152
153
  signal: this.host.signal,
153
154
  }, { authoring: false });
154
155
  }
155
156
  /** The merge gate's reviewer runs (review.ts's review run and verdict follow-up): the
156
157
  * same shared transient-retry wiring — a 429 in the gate waits its hint out and retries
157
158
  * once, exactly like an authoring run (BUGS.md 2026-10-01) — with one folding difference:
158
- * the FINAL run's usage is folded by the gate's caller (merge-core's foldUsage of
159
+ * the FINAL agent run's usage is folded by the gate's caller (merge-core's foldUsage of
159
160
  * gate.run / gate.followUpRun), so only a RETRIED first attempt is folded here. That fold
160
161
  * is not optional: it stamps the team-wide rate-limit hold's input before the wait, and
161
162
  * it is the only record the failed attempt's spend would ever get.
162
- * Takes full PiRunOptions because the gate's two runs differ in everything but the loop
163
+ * Takes full PiRunOptions because the gate's two agent runs differ in everything but the runner
163
164
  * they charge (config, session dir and name, label, tool-call callbacks, signal).
164
165
  */
165
166
  async runGatePi(opts) {
166
167
  return this.runWithTransientRetry(opts, { foldFinal: false, authoring: false });
167
168
  }
168
- /** The one bounded transient-failure retry shared by EVERY pi run this loop makes
169
+ /** The one bounded transient-failure retry shared by EVERY pi run this runner makes
169
170
  * (runRolePi, runMergePi, and runGatePi): two transient failures of the world (not of
170
171
  * the session) earn exactly one retry that continues the same session — the model server
171
172
  * timing out an idle predict stream, the provider severing that stream outright (undici's
@@ -174,22 +175,23 @@ export class LoopPi {
174
175
  * kind), pi itself crashing on a torn server chunk (a JSON.parse failure on its stderr),
175
176
  * and the provider rate-limiting the request with HTTP 429 (where the provider's
176
177
  * Retry-After hint, when sent, is waited out first — capped, so one provider's generosity
177
- * cannot eat the tick's own run budget). A harness-killed or quiet-killed run never takes
178
- * the transient-retry path: its session is intact but resuming it would just re-hit
179
- * whatever hung, burning another full quiet timeout. A run whose `--resume` target the
178
+ * cannot eat the run's own agent run budget). A harness-killed or quiet-killed agent run never
179
+ * takes the transient-retry path: its session is intact but resuming it would just re-hit
180
+ * whatever hung, burning another full quiet timeout. An agent run whose `--resume` target the
180
181
  * backend no longer holds (`PiRunResult.resumeMissing`) is not retried here: continuing is
181
- * impossible and this layer has no fresh tick prompt to hand a new session, so the failed
182
- * run is returned and its kept worktree resumes as a `handoff` on the next tick
183
- * (src/tick/tick-resume.ts).
182
+ * impossible and this layer has no fresh run prompt to hand a new session, so the failed
183
+ * agent run is returned and its kept worktree resumes as a `handoff` on the next run
184
+ * (src/run/run-resume.ts).
184
185
  * Extracted verbatim from runRolePi so the rule lives in one place
185
- * (574a14c's loopPiOpts move was the wiring half of the same single-source-of-truth).
186
+ * (574a14c's runnerPiOpts move was the wiring half of the same single-source-of-truth).
186
187
  */
187
- /** Part 6c/7: demote a catalog-free opencode model the first time a run bills it. Observed
188
- * here — the fold chokepoint every run of the loop passes through — because it is the one
189
- * place that has the run's resolved model (opts.config) and its cost (the run) together, and
190
- * it holds the loop's root and role for the cache and the event feed. pi and claude runs
191
- * have no opencode catalog entry and no-op. Best-effort like observeClaudeAccount: an
192
- * unwritable cache or event feed must not fail the run that observed it. */
188
+ /** Part 6c/7: demote a catalog-free opencode model the first time an agent run bills it. Observed
189
+ * here — the fold chokepoint every agent run of the runner passes through — because it is the one
190
+ * place that has the agent run's resolved model (opts.config) and its cost (the agent run)
191
+ * together, and it holds the runner's root and role for the cache and the event feed. pi and
192
+ * claude agent runs have no opencode catalog entry and no-op. Best-effort like
193
+ * observeClaudeAccount: an unwritable cache or event feed must not fail the agent run that
194
+ * observed it. */
193
195
  observeFreeModelCost(run, config) {
194
196
  if (config.agent !== "opencode")
195
197
  return;
@@ -197,32 +199,35 @@ export class LoopPi {
197
199
  observeOpencodeRun(this.host.root, { model: opencodeModelKey(config.model ?? ""), costUsd: run.costUsd }, { source: this.host.role });
198
200
  }
199
201
  catch {
200
- // The next priced run demotes it; nothing this run depends on is lost.
202
+ // The next priced run demotes it; nothing this agent run depends on is lost.
201
203
  }
202
204
  }
203
205
  async runWithTransientRetry(opts,
204
- /** runGatePi only: skip folding the FINAL returned run (the caller folds it), while a
206
+ /** runGatePi only: skip folding the FINAL returned agent run (the caller folds it), while a
205
207
  * retried first attempt still folds — its spend and its rate-limit hold stamp must not
206
208
  * vanish just because the gate routes its own folding. Default folds everything, the
207
209
  * authoring and merge behavior. */
208
210
  { foldFinal = true, authoring = true } = {}) {
209
- // The fold every attempt takes: an authoring run charges the loop's tick counters
211
+ // The fold every attempt takes: an authoring run charges the runner's run counters
210
212
  // (foldUsage); a merge, reviewer, or conflict-resolution run charges the same cost and
211
- // prompt-token totals but never the tick's pre-edit prefix (foldMergeUsage).
212
- // The seam this run actually ran on, as the team hold keys it (seamHoldKey): the fold
213
+ // prompt-token totals but never the run's pre-edit prefix (foldMergeUsage).
214
+ // The seam this agent run actually ran on, as the team hold keys it (seamHoldKey): the fold
213
215
  // stamps the rate-limit and backend-failure observations with it, so a storm on a claude
214
216
  // reviewer holds `claude` and a pi author on a different provider stays open. opts.config
215
- // is always the run's own resolved seam (reviewRunConfig for the reviewer, the role's
217
+ // is always the agent run's own resolved seam (reviewRunConfig for the reviewer, the role's
216
218
  // config or fallback pair for the author), so this names the backend that failed.
217
219
  const provider = seamHoldKey(opts.config);
218
- // The run's model pair name, the key of the per-step daily spend (LoopState.dayCostByPair).
220
+ // The agent run's model pair name, the key of the per-step daily spend
221
+ // (RunnerState.dayCostByPair).
219
222
  const pair = seamPairName(opts.config);
223
+ // Names the seam in per-run provider warnings; omitted when pi picks its own default model.
224
+ const on = opts.config.model !== undefined ? ` on ${pair}` : "";
220
225
  const fold = (run) => authoring ? this.host.foldUsage(run, provider, pair) : this.host.foldMergeUsage(run, provider, pair);
221
- // The run re-keys the calling tick's provider lease to the provider THIS run uses, if it is
222
- // not already there (plans/provider-concurrency.md §5): a budget handback or a model-failure
223
- // switch that moved this run onto another seam moves the tick's lease with it, so a
224
- // claude-authored tick whose gate run lands on omlx is counted there. No-op when the tick
225
- // carries no lease (a bare LoopPi unit test, `tumwater tick`).
226
+ // The agent run re-keys the calling run's provider lease to the provider THIS agent run uses,
227
+ // if it is not already there (plans/provider-concurrency.md §5): a budget handback or a
228
+ // model-failure switch that moved this agent run onto another seam moves the run's lease with
229
+ // it, so a claude-authored run whose gate agent run lands on omlx is counted there. No-op when
230
+ // the run carries no lease (a bare RunnerPi unit test, `tumwater history`).
226
231
  await ensureProviderLease(opts.config);
227
232
  const pi = await runPi(opts);
228
233
  this.warnFallbackClone(pi);
@@ -237,33 +242,33 @@ export class LoopPi {
237
242
  !pi.ok &&
238
243
  // A subscription claude usage-window rejection is not the 429 the retry's minute-scale
239
244
  // wait beats: the window resets hours out, so a within-run retry is guaranteed to fail
240
- // after burning up to RATE_LIMIT_RETRY_AFTER_CAP_S. The tick ends and the kept session
245
+ // after burning up to RATE_LIMIT_RETRY_AFTER_CAP_S. The run ends and the kept session
241
246
  // resumes once the team hold lifts (plans/agent-backends.md §8, part 6e).
242
247
  !subscriptionWindowRejected(pi)) {
243
248
  this.host.warn(pi.transientPiCrash
244
- ? `pi crashed on malformed JSON (${pi.errorMessage ?? "no detail"}) — resuming the session once`
249
+ ? `pi crashed on malformed JSON (${pi.errorMessage ?? "no detail"})${on} — resuming the session once`
245
250
  : pi.transientRateLimit
246
- ? `provider rate-limited the request (429${pi.retryAfterSeconds ? `, retry after ${pi.retryAfterSeconds}s` : `, no hint — waiting ${RATE_LIMIT_NO_HINT_RETRY_S}s`}) — retrying the agent run once`
251
+ ? `provider rate-limited the request (429${pi.retryAfterSeconds ? `, retry after ${pi.retryAfterSeconds}s` : `, no hint — waiting ${RATE_LIMIT_NO_HINT_RETRY_S}s`})${on} — retrying the agent run once`
247
252
  : pi.backendKind === "stream-severed"
248
- ? "provider severed the in-flight stream (terminated) — retrying the agent run once"
253
+ ? `provider severed the in-flight stream (terminated)${on} — retrying the agent run once`
249
254
  : pi.backendKind === "timeout"
250
- ? "provider request timed out after accepting the connection — retrying the agent run once"
251
- : "model server timed out an idle predict stream (e.g. machine sleep) — retrying the agent run once");
255
+ ? `provider request timed out after accepting the connection${on} — retrying the agent run once`
256
+ : `model server timed out an idle predict stream (e.g. machine sleep)${on} — retrying the agent run once`);
252
257
  // The failed attempt folds NOW, before the wait and the retry: a 429 it ended on is the
253
- // team-wide rate-limit hold's input (LoopRunner.lastRateLimit, stamped at fold time), and
258
+ // team-wide rate-limit hold's input (RoleRunner.lastRateLimit, stamped at fold time), and
254
259
  // folding after a retry that ran on for an hour would report the storm an hour late.
255
260
  fold(pi);
256
261
  // The pause is the rate-limit branch's, plus the timeout kind's: a server timeout or
257
262
  // pi crash is a failure of the local path, retried at once, while a 429 must wait its
258
263
  // per-minute bucket out. A hint-less 429 defaults to that minute-scale refill pause
259
264
  // instead of 0 — an immediate retry lands in the same exhausted bucket and burns the
260
- // tick's only retry (BUGS.md 2026-09-25). A present hint wins; the cap bounds either.
265
+ // run's only retry (BUGS.md 2026-09-25). A present hint wins; the cap bounds either.
261
266
  // A hint of 0 (or any non-positive value) is no usable hint — "retry now" is exactly
262
267
  // the burned-retry bug a hint-less 429 causes — so it falls back to the refill pause
263
268
  // just like a missing hint, keeping the warning text and the actual wait in agreement.
264
269
  // The timeout kind (BUGS.md 2026-09-30) takes that same minute-scale pause: the
265
270
  // provider accepted the request and failed to answer it, an overload shape — an
266
- // immediate re-request lands at the back of the same queue and burns the tick's only
271
+ // immediate re-request lands at the back of the same queue and burns the run's only
267
272
  // retry, so the retry waits the refill pause out first.
268
273
  const hintS = pi.retryAfterSeconds && pi.retryAfterSeconds > 0 ? pi.retryAfterSeconds : RATE_LIMIT_NO_HINT_RETRY_S;
269
274
  const waitS = pi.transientRateLimit
@@ -274,40 +279,41 @@ export class LoopPi {
274
279
  if (waitS > 0)
275
280
  await (this.host.sleep?.(waitS * 1000) ?? sleepFor(waitS * 1000));
276
281
  // Within-run continuity only: resume the session the first attempt created, so its
277
- // partial progress is not re-done. The next tick still starts fresh.
282
+ // partial progress is not re-done. The next run still starts fresh.
278
283
  const retry = await runPi({ ...opts, continueSession: true });
279
284
  this.warnFallbackClone(retry);
280
285
  this.observeFreeModelCost(retry, opts.config);
281
286
  if (foldFinal)
282
287
  fold(retry);
283
- this.warnBackendKindFloor(retry);
288
+ this.warnBackendKindFloor(retry, on);
284
289
  return retry;
285
290
  }
286
- this.warnBackendKindFloor(pi);
291
+ this.warnBackendKindFloor(pi, on);
287
292
  if (foldFinal)
288
293
  fold(pi);
289
294
  return pi;
290
295
  }
291
- /** The backend-kind floor (BUGS.md 2026-09-29): a failed run that ended on a
296
+ /** The backend-kind floor (BUGS.md 2026-09-29): a failed agent run that ended on a
292
297
  * provider-backend failure (connection down, 5xx, model load) still warns — parity with
293
298
  * the 429 branch above, so any episode is visible from the feed alone instead of living
294
- * only in per-tick error events an operator must aggregate by hand. The team-wide hold
299
+ * only in per-run error events an operator must aggregate by hand. The team-wide hold
295
300
  * (src/team/team-hold.ts) and the storm alarms judge the spread; this line is the per-run
296
- * floor under them, not a retry: a dead backend is not something an immediate re-run beats.
297
- * Applied to whichever run is returned (BUGS.md 2026-10-09): the retry's returned run is a
298
- * second, independent run the retry predicate was never applied to, so a retry that dies
299
- * on an uncovered kind must warn exactly as a first attempt does. A run the retry predicate
300
- * DID admit on a backend kind (stream-severed/timeout) is excluded, so the warning's "does
301
- * not cover this kind" stays true and a retry failing on a covered kind emits no such line
302
- * (BUGS.md 2026-10-09 review). */
303
- warnBackendKindFloor(run) {
301
+ * floor under them, not a retry: a dead backend is not something an immediate agent re-run beats.
302
+ * Applied to whichever agent run is returned (BUGS.md 2026-10-09): the retry's returned agent run
303
+ * is a second, independent agent run the retry predicate was never applied to, so a retry that
304
+ * dies on an uncovered kind must warn exactly as a first attempt does. An agent run the retry
305
+ * predicate DID admit on a backend kind (stream-severed/timeout) is excluded, so the warning's
306
+ * "does not cover this kind" stays true and a retry failing on a covered kind emits no such line
307
+ * (BUGS.md 2026-10-09 review). The line names the seam's provider/model pair (`on`,
308
+ * empty with no model). */
309
+ warnBackendKindFloor(run, on) {
304
310
  if (!run.ok &&
305
311
  !run.aborted &&
306
312
  !run.timedOut &&
307
313
  !run.quietKilled &&
308
314
  run.transientBackend &&
309
315
  !this.retryCoversBackendKind(run)) {
310
- this.host.warn(`provider backend failure (${backendKindPhrase(run.backendKind)}) — the transient retry does not cover this kind; the team-wide wait watches for a storm`);
316
+ this.host.warn(`provider backend failure (${backendKindPhrase(run.backendKind)})${on} — the transient retry does not cover this kind; the team-wide wait watches for a storm`);
311
317
  }
312
318
  }
313
319
  /** Whether the one bounded transient retry admits `run` on its backend-kind alone: the
@@ -318,48 +324,49 @@ export class LoopPi {
318
324
  retryCoversBackendKind(run) {
319
325
  return run.transientBackend && (run.backendKind === "stream-severed" || run.backendKind === "timeout");
320
326
  }
321
- /** One warning per run whose model id pi could not resolve exactly (BUGS.md 2026-10-06):
322
- * pi cloned the provider's default model, so the run is priced and windowed as that default
327
+ /** One warning per agent run whose model id pi could not resolve exactly (BUGS.md 2026-10-06):
328
+ * pi cloned the provider's default model, so the agent run is priced and windowed as that default
323
329
  * while the operator's config says otherwise — unconditionally visible from the feed alone.
324
- * A no-op for the ordinary run that printed no such stderr line. */
330
+ * A no-op for the ordinary agent run that printed no such stderr line. */
325
331
  warnFallbackClone(run) {
326
332
  if (!run.fallbackClone)
327
333
  return;
328
334
  this.host.warn(`pi could not resolve the model id exactly (${run.fallbackClone}) — the run uses the provider default's price and context window; add an exact entry to ${piModelsPath()}`);
329
335
  }
330
- /** Run pi for this loop's authoring run in worktree `wt`: the shared per-loop wiring and
331
- * the transient-failure retry, with usage folded into the tick's counters. It is the ONE
332
- * entry point that carries the role notebook (PLANS.md "Role notebook": TUMWATER_NOTES_PATH,
333
- * read by the `tumwater-notes` command): every other run (runRolePi's merge/conflict path,
336
+ /** Run pi for this runner's authoring run in worktree `wt`: the shared per-runner wiring and
337
+ * the transient-failure retry, with usage folded into the run's counters. It is the ONE
338
+ * entry point that carries the role notes (PLANS.md "Role notebook": TUMWATER_NOTES_PATH,
339
+ * read by the `tumwater-notes` command): every other agent run (runRolePi's merge/conflict path,
334
340
  * runMergePi, runGatePi) leaves it unset, and the director is excluded because its work is
335
341
  * the operator's prompt, not a recurring search.
336
342
  *
337
- * Every run starts a FRESH pi session: context never accumulates across ticks, so ticks start
343
+ * Every agent run starts a FRESH pi session: context never accumulates across runs, so runs start
338
344
  * cheap (small prefill), never inherit a near-full window, and durable knowledge lives where
339
345
  * the prompt makes pi read it — README/PLANS/BUGS, the code itself, and the role's own note.
340
346
  * `resume` continues the role's most recent session instead — used only when picking up
341
- * a tick that a harness shutdown interrupted. */
347
+ * a run that a harness shutdown interrupted. */
342
348
  async runAuthoringPi(wt, prompt, sessionName, resume = false, config) {
343
- return this.runWithTransientRetry(this.loopPiOpts(wt, prompt, sessionName, "author", resume, config, this.authorNotesPath()));
349
+ return this.runWithTransientRetry(this.runnerPiOpts(wt, prompt, sessionName, "author", resume, config, this.authorNotesPath()));
344
350
  }
345
- /** The notebook path an authoring run carries: the role's note file, or undefined for the
346
- * director, which has no notebook. The SUMMARY/stage-fix follow-ups share the authoring
347
- * tick's session and must carry the same notes wiring — every invocation reads the path
351
+ /** The notes path an authoring run carries: the role's note file, or undefined for the
352
+ * director, which has no notes. The SUMMARY/stage-fix follow-ups share the authoring
353
+ * run's session and must carry the same notes wiring — every invocation reads the path
348
354
  * from TUMWATER_NOTES_PATH in the environment, so the follow-up re-passes the same path. */
349
355
  authorNotesPath() {
350
356
  return this.host.role === DIRECTOR_ROLE
351
357
  ? undefined
352
358
  : roleNotesPath(this.host.root, this.host.role);
353
359
  }
354
- /** The shared authoring/conflict run wiring WITHOUT the role notebook. The merge pipeline's
360
+ /** The shared authoring/conflict agent run wiring WITHOUT the role notes. The merge pipeline's
355
361
  * conflict resolver reaches pi through this (plans/merge-queue.md), and a conflict resolution
356
362
  * is not the role's recurring search: it must not carry the notes tool or rewrite the note.
357
- * Authoring ticks run through runAuthoringPi above, which is the only notebook-carrying path. */
363
+ * An authoring agent run goes through runAuthoringPi above, which is the only notes-carrying
364
+ * path. */
358
365
  async runRolePi(wt, prompt, sessionName, resume = false, config) {
359
- // The refusal-note merge (loop.ts's merge) reaches pi here, in the ROLE's own worktree,
360
- // so its runs demux as author — exactly the verdict the pre-part-1/5 cwd test gave them.
366
+ // The refusal-note merge (runner.ts's merge) reaches pi here, in the ROLE's own worktree,
367
+ // so its agent runs demux as author — exactly the verdict the pre-part-1/5 cwd test gave them.
361
368
  // The shared-worktree conflict resolver goes through runMergePi and demuxes as gate.
362
- return this.runWithTransientRetry(this.loopPiOpts(wt, prompt, sessionName, "author", resume, config));
369
+ return this.runWithTransientRetry(this.runnerPiOpts(wt, prompt, sessionName, "author", resume, config));
363
370
  }
364
371
  /** Hard caps on the SUMMARY follow-up turn: it should take one short reply on a warm session,
365
372
  * so it never gets the authoring run's hours-long budget. */
@@ -370,55 +377,57 @@ export class LoopPi {
370
377
  * are short replies to a session that already holds the work. */
371
378
  static STAGE_FIX_TIMEOUT_S = 600;
372
379
  /** The shared body of the two `--continue` follow-up turns (requestSummary and
373
- * requestStageFix): guard on a resumable session, resolve the tick's effective config, and
374
- * run one capped turn on the loop's shared transient-retry wiring. Both follow-ups must keep
380
+ * requestStageFix): guard on a resumable session, resolve the run's effective config, and
381
+ * run one capped turn on the runner's shared transient-retry wiring. Both follow-ups must keep
375
382
  * the same session guard and capped-config discipline, so the two read one assembly here;
376
383
  * `suffix` names the turn in its session name and `timeoutS` is its hard cap, while both
377
384
  * share the quiet watchdog value (SUMMARY_REQUEST_QUIET_S). Null when there is no session to
378
- * continue; the run is returned even when it failed so the caller can honor a shutdown abort.
385
+ * continue; the agent run is returned even when it failed so the caller can honor a shutdown
386
+ * abort.
379
387
  *
380
- * The follow-up continues this tick's own session, so it must run on the same pair that
381
- * session ran on: the caller passes the tick's effective config (the fallback pair while a
388
+ * The follow-up continues this run's own session, so it must run on the same pair that
389
+ * session ran on: the caller passes the run's effective config (the fallback pair while a
382
390
  * model-fallback episode is active). A caller without one rides the role's own config. The
383
- * shared per-loop wiring (loopPiOpts) takes the follow-up's hard caps overriding the
384
- * authoring run's budget: one short reply on a warm session. The run takes the loop's SHARED
385
- * transient-failure retry (BUGS.md 2026-10-01): a 429 on the follow-up turn waits its
391
+ * shared per-runner wiring (runnerPiOpts) takes the follow-up's hard caps overriding the
392
+ * authoring run's budget: one short reply on a warm session. The agent run takes the runner's
393
+ * SHARED transient-failure retry (BUGS.md 2026-10-01): a 429 on the follow-up turn waits its
386
394
  * Retry-After hint out and retries once on the same session, exactly like the authoring run,
387
- * the merge slot's run, and the gate's reviewer runs it mirrors. The caps bound BOTH
395
+ * the merge slot's agent run, and the gate's reviewer agent runs it mirrors. The caps bound BOTH
388
396
  * attempts (the retry inherits this capped config), so a flaky provider cannot stretch the
389
397
  * follow-up past its budget by more than one capped turn. The wiring folds every attempt
390
- * (the failed 429 before the wait), and the returned final run is folded here by the wiring
398
+ * (the failed 429 before the wait), and the returned final agent run is folded here by the wiring
391
399
  * itself — no second fold at the call site. */
392
400
  async requestFollowUp(wt, prompt, suffix, timeoutS, config) {
393
401
  const cfg = config ?? configForRole(this.host.config(), this.host.role);
394
402
  const dir = sessionDir(this.host.root, this.host.role);
395
- // The follow-up continues THIS tick's author session. The backend's own resumable rule
403
+ // The follow-up continues THIS run's author session. The backend's own resumable rule
396
404
  // keeps it honest: pi would reach the newest file, claude its recorded id, so a conflict
397
- // resolver's gate run in the same dir cannot be mistaken for this tick's session. The
405
+ // resolver's gate run in the same dir cannot be mistaken for this run's session. The
398
406
  // claude config dir rides along so a claude team's resume looks under its own dir.
399
- if (!agentSessionResumable(dir, { kind: "author", agent: cfg.agent ?? "pi" }, resolvedAgentConfig(cfg, "claude").configDir)) {
407
+ if (!backendSessionResumable(dir, { kind: "author", agent: cfg.agent ?? "pi" }, resolvedBackendConfig(cfg, "claude").configDir)) {
400
408
  return null;
401
409
  }
402
410
  return this.runWithTransientRetry({
403
- ...this.loopPiOpts(wt, prompt, `tumwater-${this.host.role}-${this.host.tickNumber()}-${suffix}`, "author", true, cfg, this.authorNotesPath()),
411
+ ...this.runnerPiOpts(wt, prompt, `tumwater-${this.host.role}-${this.host.runNumber()}-${suffix}`, "author", true, cfg, this.authorNotesPath()),
404
412
  config: {
405
413
  ...cfg,
406
- ...cappedRequestTimeouts(cfg, timeoutS, LoopPi.SUMMARY_REQUEST_QUIET_S),
414
+ ...cappedRequestTimeouts(cfg, timeoutS, RunnerPi.SUMMARY_REQUEST_QUIET_S),
407
415
  },
408
416
  });
409
417
  }
410
- /** Ask the tick's own pi session (--continue) for the missing SUMMARY block: one tightly
411
- * bounded turn, folded into the tick's usage like every other run. Null when there is no
418
+ /** Ask the run's own pi session (--continue) for the missing SUMMARY block: one tightly
419
+ * bounded turn, folded into the run's usage like every other agent run. Null when there is no
412
420
  * session to continue (pi never wrote one) — the caller then derives a subject itself. The
413
- * run is returned even when it failed so the caller can honor a shutdown abort. */
421
+ * agent run is returned even when it failed so the caller can honor a shutdown abort. */
414
422
  async requestSummary(wt, config, overLength) {
415
- return this.requestFollowUp(wt, buildSummaryRequestPrompt(overLength), "summary", LoopPi.SUMMARY_REQUEST_TIMEOUT_S, config);
423
+ return this.requestFollowUp(wt, buildSummaryRequestPrompt(overLength), "summary", RunnerPi.SUMMARY_REQUEST_TIMEOUT_S, config);
416
424
  }
417
- /** Ask the tick's own pi session (--continue) to fix the pre-queue self-check's findings:
418
- * one bounded turn, folded into the tick's usage like every other run. Null when there is no
419
- * session to continue — the caller then warns and lets the gate judge. The run is returned
425
+ /** Ask the run's own pi session (--continue) to fix the pre-queue self-check's findings:
426
+ * one bounded turn, folded into the run's usage like every other agent run. `change` is the
427
+ * change's description so far, shown so the turn does not restate it. Null when there is no
428
+ * session to continue — the caller then warns and lets the gate judge. The agent run is returned
420
429
  * even when it failed so the caller can honor a shutdown abort. */
421
- async requestStageFix(wt, findings, config) {
422
- return this.requestFollowUp(wt, buildStageFixPrompt(findings), "stagefix", LoopPi.STAGE_FIX_TIMEOUT_S, config);
430
+ async requestStageFix(wt, findings, change, config) {
431
+ return this.requestFollowUp(wt, buildStageFixPrompt(findings, change), "stagefix", RunnerPi.STAGE_FIX_TIMEOUT_S, config);
423
432
  }
424
433
  }