tumwater 0.2.0 → 0.4.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 (464) 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 +55 -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 +6 -4
  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 +404 -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 +281 -55
  41. package/dist/src/backlog/backlog-cli-view.js +255 -0
  42. package/dist/src/backlog/backlog-cli.js +91 -335
  43. package/dist/src/backlog/backlog-commit.js +20 -13
  44. package/dist/src/backlog/backlog-eligibility.js +193 -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 +264 -0
  48. package/dist/src/backlog/backlog-release.js +134 -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-guardrails.js +136 -0
  56. package/dist/src/baseline/main-red.js +153 -36
  57. package/dist/src/baseline/main-watch.js +232 -0
  58. package/dist/src/brief/brief-edit.js +12 -12
  59. package/dist/src/brief/brief.js +31 -51
  60. package/dist/src/budget/budget-fallback-view.js +288 -0
  61. package/dist/src/budget/budget-wiring.js +158 -0
  62. package/dist/src/budget/budget.js +31 -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 +65 -3
  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 +181 -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 +183 -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 +291 -598
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-review-commands.js +113 -0
  84. package/dist/src/cli/cli-role-command.js +525 -0
  85. package/dist/src/cli/cli-start-summary.js +167 -0
  86. package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
  87. package/dist/src/cli/completion.js +169 -22
  88. package/dist/src/cli/config-commands.js +58 -26
  89. package/dist/src/cli/follow-view.js +75 -0
  90. package/dist/src/cli/help.js +258 -82
  91. package/dist/src/cli/log-commands.js +164 -85
  92. package/dist/src/cli/question-commands.js +211 -10
  93. package/dist/src/cli.js +173 -45
  94. package/dist/src/collections.js +56 -13
  95. package/dist/src/concurrency/check-permit.js +20 -3
  96. package/dist/src/concurrency/lock.js +17 -13
  97. package/dist/src/concurrency/provider-lease.js +44 -8
  98. package/dist/src/concurrency/semaphore.js +102 -26
  99. package/dist/src/config/config-aliases.js +0 -1
  100. package/dist/src/config/config-editable-keys.js +16 -1
  101. package/dist/src/config/config-fallbacks.js +94 -56
  102. package/dist/src/config/config-field-checks.js +40 -10
  103. package/dist/src/config/config-keys.js +30 -8
  104. package/dist/src/config/config-live.js +63 -24
  105. package/dist/src/config/config-request.js +137 -0
  106. package/dist/src/config/config-schema.js +31 -24
  107. package/dist/src/config/config-selector-checks.js +123 -0
  108. package/dist/src/config/config-validation-agents.js +80 -0
  109. package/dist/src/config/config-validation-model.js +116 -0
  110. package/dist/src/config/config-validation.js +169 -222
  111. package/dist/src/config/config-views.js +136 -54
  112. package/dist/src/config/config-write-core.js +135 -0
  113. package/dist/src/config/config-write.js +164 -265
  114. package/dist/src/config/config.js +53 -38
  115. package/dist/src/config/model-selector.js +34 -34
  116. package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
  117. package/dist/src/doctor/doctor-backlog-plans.js +398 -0
  118. package/dist/src/doctor/doctor-backlog.js +249 -512
  119. package/dist/src/doctor/doctor-check-names.js +85 -0
  120. package/dist/src/doctor/doctor-checks.js +61 -593
  121. package/dist/src/doctor/doctor-claude-checks.js +142 -0
  122. package/dist/src/doctor/doctor-codex-checks.js +276 -0
  123. package/dist/src/doctor/doctor-contention-checks.js +506 -0
  124. package/dist/src/doctor/doctor-fallback-checks.js +130 -0
  125. package/dist/src/doctor/doctor-holds.js +80 -66
  126. package/dist/src/doctor/doctor-install.js +2 -1
  127. package/dist/src/doctor/doctor-model-checks.js +216 -537
  128. package/dist/src/doctor/doctor-notify.js +9 -5
  129. package/dist/src/doctor/doctor-opencode-checks.js +164 -0
  130. package/dist/src/doctor/doctor-orphans.js +182 -63
  131. package/dist/src/doctor/doctor-pi-checks.js +62 -0
  132. package/dist/src/doctor/doctor-project-checks.js +397 -0
  133. package/dist/src/doctor/doctor.js +126 -77
  134. package/dist/src/doctor/image-probe.js +21 -8
  135. package/dist/src/doctor/outcome.js +3 -2
  136. package/dist/src/doctor/probe.js +21 -12
  137. package/dist/src/events/event-format.js +185 -89
  138. package/dist/src/events/event-labels.js +11 -5
  139. package/dist/src/events/event-legacy.js +39 -15
  140. package/dist/src/events/event-read.js +52 -39
  141. package/dist/src/events/event-tally.js +16 -0
  142. package/dist/src/events/event-window.js +80 -26
  143. package/dist/src/events/events.js +30 -13
  144. package/dist/src/events/notify.js +7 -7
  145. package/dist/src/failure/failure-cluster.js +12 -12
  146. package/dist/src/failure/failure-data.js +65 -60
  147. package/dist/src/failure/failure-render.js +29 -21
  148. package/dist/src/failure/failure-state-change.js +17 -12
  149. package/dist/src/failure/time-spend.js +35 -35
  150. package/dist/src/files/file-queue.js +17 -0
  151. package/dist/src/files/files.js +111 -35
  152. package/dist/src/files/json-files.js +3 -3
  153. package/dist/src/files/json-object.js +28 -7
  154. package/dist/src/files/stat-cache.js +2 -2
  155. package/dist/src/files/tail.js +1 -1
  156. package/dist/src/gates/bootstrap-gates.js +4 -5
  157. package/dist/src/gates/budget-gates.js +88 -225
  158. package/dist/src/gates/disk-gate.js +1 -1
  159. package/dist/src/gates/gate-polls.js +101 -143
  160. package/dist/src/gates/maintenance-quota.js +37 -30
  161. package/dist/src/gates/pause-gates.js +28 -10
  162. package/dist/src/gates/readiness.js +7 -3
  163. package/dist/src/gates/role-cap-gates.js +8 -8
  164. package/dist/src/gates/startup-gate.js +94 -32
  165. package/dist/src/gates/streak-gate.js +54 -30
  166. package/dist/src/git/commit-message.js +89 -46
  167. package/dist/src/git/git-diff.js +16 -10
  168. package/dist/src/git/git-run.js +79 -38
  169. package/dist/src/git/git.js +54 -34
  170. package/dist/src/git/slot-salvage.js +253 -0
  171. package/dist/src/git/slots-state.js +6 -6
  172. package/dist/src/git/worktree-pool.js +132 -38
  173. package/dist/src/git/worktree-use.js +4 -11
  174. package/dist/src/git/worktree.js +94 -37
  175. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  176. package/dist/src/gui/gui-args.js +79 -8
  177. package/dist/src/gui/gui-backlog-commands.js +43 -19
  178. package/dist/src/gui/gui-backlog-endpoints.js +156 -0
  179. package/dist/src/gui/gui-command.js +26 -13
  180. package/dist/src/gui/gui-config-commands.js +168 -80
  181. package/dist/src/gui/gui-endpoint-commands.js +5 -4
  182. package/dist/src/gui/gui-endpoint-common.js +4 -4
  183. package/dist/src/gui/gui-endpoints.js +11 -380
  184. package/dist/src/gui/gui-history-endpoints.js +301 -0
  185. package/dist/src/gui/gui-operator-commands.js +235 -13
  186. package/dist/src/gui/gui-prompt-commands.js +26 -20
  187. package/dist/src/gui/gui-report-endpoints.js +97 -0
  188. package/dist/src/gui/gui-server.js +26 -5
  189. package/dist/src/gui/gui-team-commands.js +7 -7
  190. package/dist/src/gui/http-body.js +1 -1
  191. package/dist/src/history/history-data.js +122 -234
  192. package/dist/src/history/history-queued.js +155 -0
  193. package/dist/src/history/history-render.js +42 -22
  194. package/dist/src/history/history.js +190 -49
  195. package/dist/src/inbox/inbox-attachments.js +29 -30
  196. package/dist/src/inbox/inbox-cancel.js +10 -11
  197. package/dist/src/inbox/inbox-edit.js +9 -9
  198. package/dist/src/inbox/inbox-submit.js +18 -18
  199. package/dist/src/inbox/inbox-top.js +28 -17
  200. package/dist/src/inbox/inbox.js +42 -43
  201. package/dist/src/inbox/pending-prompt.js +26 -26
  202. package/dist/src/inbox/prompt-commands.js +62 -49
  203. package/dist/src/inbox/prompt-not-before.js +2 -2
  204. package/dist/src/init/init-templates.js +1 -2
  205. package/dist/src/init/init.js +28 -23
  206. package/dist/src/merge/backlog-conflicts.js +215 -35
  207. package/dist/src/merge/merge-batch.js +14 -8
  208. package/dist/src/merge/merge-cancel.js +53 -0
  209. package/dist/src/merge/merge-check-failures.js +44 -22
  210. package/dist/src/merge/merge-core.js +58 -32
  211. package/dist/src/merge/merge-diff.js +5 -4
  212. package/dist/src/merge/merge-drain.js +45 -19
  213. package/dist/src/merge/merge-git.js +177 -38
  214. package/dist/src/merge/merge-pipeline.js +15 -3
  215. package/dist/src/merge/merge-post-review.js +214 -0
  216. package/dist/src/merge/merge-queue-render.js +48 -11
  217. package/dist/src/merge/merge-queue.js +52 -15
  218. package/dist/src/merge/merge-requeue.js +103 -0
  219. package/dist/src/merge/merge-slot.js +61 -16
  220. package/dist/src/merge/merge-stack.js +27 -11
  221. package/dist/src/merge/merge-to-main.js +77 -26
  222. package/dist/src/merge/merge-top.js +83 -0
  223. package/dist/src/merge/merge-verify.js +75 -18
  224. package/dist/src/notes-cli.js +35 -15
  225. package/dist/src/operator/operator-commands.js +192 -81
  226. package/dist/src/operator/operator-intent.js +69 -42
  227. package/dist/src/operator/operator-requests.js +35 -40
  228. package/dist/src/operator/retire.js +5 -6
  229. package/dist/src/operator/revert.js +142 -34
  230. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  231. package/dist/src/orchestrator/orchestrator-poll.js +352 -0
  232. package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
  233. package/dist/src/orchestrator/orchestrator.js +129 -369
  234. package/dist/src/orchestrator/retention.js +42 -8
  235. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  236. package/dist/src/orchestrator/scheduling-scope.js +245 -0
  237. package/dist/src/orchestrator/scheduling-skip.js +195 -0
  238. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  239. package/dist/src/orchestrator/startup-migrations.js +11 -7
  240. package/dist/src/paths.js +126 -49
  241. package/dist/src/pi/command-shape.js +12 -1
  242. package/dist/src/pi/pi-args.js +6 -4
  243. package/dist/src/pi/pi-backend.js +6 -6
  244. package/dist/src/pi/pi-bin.js +54 -63
  245. package/dist/src/pi/pi-listed-models.js +53 -0
  246. package/dist/src/pi/pi-models.js +63 -30
  247. package/dist/src/pi/pi-run-result.js +1 -1
  248. package/dist/src/pi/pi-settings.js +1 -1
  249. package/dist/src/pi/pi-stream.js +6 -6
  250. package/dist/src/pi/pi-watchdogs.js +18 -19
  251. package/dist/src/pi-extension/bounded-output.js +5 -3
  252. package/dist/src/pi-extension/context-budget.js +2 -2
  253. package/dist/src/pi-extension/context-shake.js +1 -1
  254. package/dist/src/pi-extension/context-window.js +0 -1
  255. package/dist/src/pi-extension/full-output.js +3 -10
  256. package/dist/src/pi-extension/strip-thinking.js +0 -1
  257. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  258. package/dist/src/pi-extension/write-guard.js +37 -0
  259. package/dist/src/process/launch-services.js +1 -2
  260. package/dist/src/process/node-bin.js +16 -0
  261. package/dist/src/process/process-group.js +8 -18
  262. package/dist/src/process/process.js +41 -2
  263. package/dist/src/process/run-marker.js +26 -6
  264. package/dist/src/process/supervisor.js +22 -21
  265. package/dist/src/prompt/dialect.js +17 -2
  266. package/dist/src/prompt/gate-notes.js +203 -0
  267. package/dist/src/prompt/gate-prompts.js +150 -217
  268. package/dist/src/prompt/notes-backlog.js +184 -0
  269. package/dist/src/prompt/principles.js +1 -2
  270. package/dist/src/prompt/prompt-followup.js +19 -14
  271. package/dist/src/prompt/prompt.js +131 -204
  272. package/dist/src/redeploy/redeploy-policy.js +24 -19
  273. package/dist/src/redeploy/redeploy-probes.js +1 -1
  274. package/dist/src/redeploy/redeploy.js +2 -3
  275. package/dist/src/redeploy/redeployer.js +20 -20
  276. package/dist/src/redeploy/self-reload.js +2 -1
  277. package/dist/src/report/report-data.js +188 -124
  278. package/dist/src/report/report-fold.js +130 -54
  279. package/dist/src/report/report-main-red.js +55 -0
  280. package/dist/src/report/report-render.js +136 -65
  281. package/dist/src/report/report-types.js +1 -0
  282. package/dist/src/report/report.js +23 -9
  283. package/dist/src/request-timeouts.js +2 -2
  284. package/dist/src/review/diff-rules.js +53 -0
  285. package/dist/src/review/known-flakes.js +112 -1
  286. package/dist/src/review/post-merge-review-accept.js +51 -0
  287. package/dist/src/review/post-merge-review-render.js +112 -0
  288. package/dist/src/review/post-merge-review.js +239 -0
  289. package/dist/src/review/protected-edits.js +24 -10
  290. package/dist/src/review/quarantine-render.js +65 -0
  291. package/dist/src/review/review-followup.js +8 -8
  292. package/dist/src/review/review-precheck.js +66 -23
  293. package/dist/src/review/review-run.js +395 -0
  294. package/dist/src/review/review-verdict.js +2 -2
  295. package/dist/src/review/review.js +180 -401
  296. package/dist/src/review/secret-leaks.js +36 -16
  297. package/dist/src/review/suite-rerun.js +0 -1
  298. package/dist/src/roles/legacy-role-migration.js +80 -7
  299. package/dist/src/roles/role-catalog.js +127 -12
  300. package/dist/src/roles/role-guidance.js +65 -20
  301. package/dist/src/roles/role-labels.js +7 -5
  302. package/dist/src/roles/role-render.js +108 -56
  303. package/dist/src/roles/role-view.js +95 -56
  304. package/dist/src/roles/roles.js +33 -33
  305. package/dist/src/roles/runner-ids.js +97 -0
  306. package/dist/src/run/acceptance-greps.js +119 -0
  307. package/dist/src/run/ci-parity.js +121 -0
  308. package/dist/src/run/feedback-filings.js +51 -0
  309. package/dist/src/run/qa-coverage.js +125 -0
  310. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  311. package/dist/src/run/run-detail-data.js +175 -0
  312. package/dist/src/run/run-detail-render.js +91 -0
  313. package/dist/src/run/run-detail.js +168 -0
  314. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
  315. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  316. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
  317. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  318. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
  319. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  320. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  321. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
  322. package/dist/src/run/selfcheck-digest.js +40 -0
  323. package/dist/src/run/stage-backlog-checks.js +249 -0
  324. package/dist/src/run/stage-check.js +178 -0
  325. package/dist/src/run/stage-checks-shared.js +65 -0
  326. package/dist/src/run/stage-name-checks.js +204 -0
  327. package/dist/src/run/stage-path-checks.js +231 -0
  328. package/dist/src/run/stage-prose-checks.js +261 -0
  329. package/dist/src/runner/leftover.js +216 -0
  330. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  331. package/dist/src/runner/revision.js +191 -0
  332. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  333. package/dist/src/runner/runner-outcome.js +109 -0
  334. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  335. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
  336. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
  337. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  338. package/dist/src/runner/runner-state.js +103 -0
  339. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  340. package/dist/src/scheduling/backoff.js +38 -38
  341. package/dist/src/scheduling/claim-entries.js +49 -0
  342. package/dist/src/scheduling/claims.js +35 -22
  343. package/dist/src/scheduling/once-round.js +22 -22
  344. package/dist/src/scheduling/quiet-hours.js +5 -6
  345. package/dist/src/scheduling/scheduling.js +74 -64
  346. package/dist/src/scheduling/work-merged-cache.js +2 -2
  347. package/dist/src/status/status-budget.js +55 -47
  348. package/dist/src/status/status-data.js +49 -17
  349. package/dist/src/status/status-inbox.js +2 -2
  350. package/dist/src/status/status-install.js +0 -1
  351. package/dist/src/status/status-polls.js +43 -28
  352. package/dist/src/status/status-roles.js +50 -13
  353. package/dist/src/team/error-storm.js +8 -8
  354. package/dist/src/team/failure-spread.js +2 -2
  355. package/dist/src/team/orchestrator-info.js +15 -9
  356. package/dist/src/team/reclaim.js +7 -8
  357. package/dist/src/team/team-hold.js +7 -7
  358. package/dist/src/team/team-polls.js +91 -50
  359. package/dist/src/team/team-state.js +8 -2
  360. package/dist/src/text/datetime.js +65 -18
  361. package/dist/src/text/format.js +48 -11
  362. package/dist/src/text/markdown.js +8 -5
  363. package/dist/src/text/phrases.js +60 -23
  364. package/dist/src/text/suggest.js +1 -1
  365. package/dist/src/text/text.js +78 -13
  366. package/dist/src/ui/badges.js +99 -38
  367. package/dist/src/ui/gui/gui-client-backlog.js +314 -0
  368. package/dist/src/ui/gui/gui-client-boot.js +89 -61
  369. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  370. package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
  371. package/dist/src/ui/gui/gui-client-drawer.js +155 -289
  372. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  373. package/dist/src/ui/gui/gui-client-history.js +437 -68
  374. package/dist/src/ui/gui/gui-client-labels.js +6 -3
  375. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  376. package/dist/src/ui/gui/gui-client-model.js +27 -36
  377. package/dist/src/ui/gui/gui-client-operator.js +60 -52
  378. package/dist/src/ui/gui/gui-client-pending.js +405 -24
  379. package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
  380. package/dist/src/ui/gui/gui-client-report.js +105 -430
  381. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  382. package/dist/src/ui/gui/gui-client-settings.js +163 -5
  383. package/dist/src/ui/gui/gui-client-team.js +36 -465
  384. package/dist/src/ui/gui/gui-client.js +38 -26
  385. package/dist/src/ui/gui/gui-icons.js +0 -2
  386. package/dist/src/ui/gui/gui-page.js +10 -7
  387. package/dist/src/ui/gui/gui-styles.js +38 -34
  388. package/dist/src/ui/progress-data.js +9 -11
  389. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
  390. package/dist/src/ui/status-model.js +133 -125
  391. package/dist/src/ui/status-payload.js +364 -49
  392. package/dist/src/ui/status-render.js +109 -82
  393. package/dist/src/ui/team-alerts.js +728 -62
  394. package/dist/src/ui/tone.js +12 -6
  395. package/dist/src/ui/transcript-run.js +58 -0
  396. package/dist/src/ui/transcript-tail.js +28 -20
  397. package/dist/src/ui/transcript.js +63 -23
  398. package/dist/src/ui/tui/tui-app.js +0 -1
  399. package/dist/src/ui/tui/tui-backlog.js +53 -14
  400. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  401. package/dist/src/ui/tui/tui-editor-attach.js +4 -12
  402. package/dist/src/ui/tui/tui-editor-backends.js +127 -0
  403. package/dist/src/ui/tui/tui-editor-brief.js +14 -36
  404. package/dist/src/ui/tui/tui-editor-budget.js +36 -39
  405. package/dist/src/ui/tui/tui-editor-check.js +29 -0
  406. package/dist/src/ui/tui/tui-editor-checks.js +28 -0
  407. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  408. package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
  409. package/dist/src/ui/tui/tui-editor-config.js +99 -0
  410. package/dist/src/ui/tui/tui-editor-entry.js +39 -64
  411. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  412. package/dist/src/ui/tui/tui-editor-instances.js +46 -0
  413. package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
  414. package/dist/src/ui/tui/tui-editor-interval.js +29 -16
  415. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  416. package/dist/src/ui/tui/tui-editor-notes.js +9 -29
  417. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  418. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  419. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  420. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  421. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  422. package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
  423. package/dist/src/ui/tui/tui-editor-retire.js +5 -13
  424. package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
  425. package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
  426. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  427. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  428. package/dist/src/ui/tui/tui-editor-session.js +101 -34
  429. package/dist/src/ui/tui/tui-editor-slots.js +24 -0
  430. package/dist/src/ui/tui/tui-editors.js +44 -2
  431. package/dist/src/ui/tui/tui-frame.js +68 -11
  432. package/dist/src/ui/tui/tui-history.js +8 -9
  433. package/dist/src/ui/tui/tui-input.js +13 -11
  434. package/dist/src/ui/tui/tui-keymap.js +0 -1
  435. package/dist/src/ui/tui/tui-keys.js +33 -9
  436. package/dist/src/ui/tui/tui-pane-activity.js +35 -19
  437. package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
  438. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  439. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  440. package/dist/src/ui/tui/tui-pane-health.js +168 -8
  441. package/dist/src/ui/tui/tui-pane-history.js +446 -82
  442. package/dist/src/ui/tui/tui-pane-pending.js +539 -15
  443. package/dist/src/ui/tui/tui-pane-queued.js +78 -57
  444. package/dist/src/ui/tui/tui-pane-report.js +54 -16
  445. package/dist/src/ui/tui/tui-pane-session.js +34 -6
  446. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  447. package/dist/src/ui/tui/tui-pane.js +91 -28
  448. package/dist/src/ui/tui/tui-panes.js +153 -34
  449. package/dist/src/ui/tui/tui.js +18 -15
  450. package/dist/src/verdict/fix-claim.js +129 -33
  451. package/dist/src/verdict/no-change.js +4 -4
  452. package/dist/src/verdict/refusal.js +40 -16
  453. package/dist/src/verdict/reply-contract.js +77 -31
  454. package/dist/src/version.js +0 -1
  455. package/package.json +2 -1
  456. package/dist/src/loop/leftover.js +0 -130
  457. package/dist/src/loop/revision.js +0 -70
  458. package/dist/src/roles/loop-ids.js +0 -77
  459. package/dist/src/tick/qa-coverage.js +0 -108
  460. package/dist/src/tick/stage-check.js +0 -419
  461. package/dist/src/tick/telemetry-digest.js +0 -25
  462. package/dist/src/tick/tick-detail-data.js +0 -108
  463. package/dist/src/tick/tick-detail.js +0 -163
  464. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,10 +1,12 @@
1
- // vocabulary: plain
2
1
  /** How a role's id shows up in words: an instance id as `feature #2`. Pure, no I/O, so core
3
2
  * modules and the UI may both import it. */
4
- import { baseRoleOf, instanceIndex } from "./loop-ids.js";
5
- /** An instance id as `feature #2`; any other id (a bare role, `director`, a custom role,
6
- * `feature-1`) unchanged. */
3
+ import { baseRoleOf, instanceIndex } from "./runner-ids.js";
4
+ /** The shared display labels that differ from a role's id (the shared table every surface that
5
+ * shows a role label reads through roleLabel, and the dashboard client's hand-kept copy). ci
6
+ * renders as "CI"; every other id — a bare role, a custom role, an instance — renders as its id. */
7
+ export const ROLE_LABELS = { ci: "CI" };
7
8
  export function roleLabel(runnerId) {
8
9
  const base = baseRoleOf(runnerId);
9
- return base === runnerId ? runnerId : `${base} #${instanceIndex(runnerId)}`;
10
+ const label = Object.prototype.hasOwnProperty.call(ROLE_LABELS, base) ? ROLE_LABELS[base] : base;
11
+ return base === runnerId ? label : `${label} #${instanceIndex(runnerId)}`;
10
12
  }
@@ -1,12 +1,13 @@
1
- // vocabulary: plain
1
+ import { joinParts } from "../text/text.js";
2
2
  import { agree, shortSpanPhrase } from "../text/phrases.js";
3
3
  import { compactTokens, usd } from "../text/format.js";
4
- import { clockHm, formatTimestamp, humanSeconds, nextRunPhrase, secondsSince, secondsUntil } from "../text/datetime.js";
4
+ import { clockHm, formatAge, formatTimestamp, humanAgo, nextRunPhrase, secondsUntil } from "../text/datetime.js";
5
5
  import { claudeWindowLabel } from "../agent/claude/claude-account.js";
6
+ import { windowLabel } from "../agent/subscription-account.js";
7
+ import { backendDefaultLabel, backendDisplayName } from "../agent/backend-traits.js";
6
8
  import { yieldMultiplierSuffix } from "../scheduling/backoff.js";
7
9
  import { quietWindowEnd } from "../scheduling/quiet-hours.js";
8
- import { isAgentId } from "../config/model-selector.js";
9
- /** The terminal's view of one loop — what `tumwater role <id>` prints, rendered as Markdown
10
+ /** The terminal's view of one role — what `tumwater role <id>` prints, rendered as Markdown
10
11
  * from the payload role-view.ts's rolePayload collects (the same document `--json` prints).
11
12
  * Like backlog-render.ts, the renderer owns no collection of its own: the CLI's --json and
12
13
  * human branches share one collection through sayJsonOrRender's thunk, so the two surfaces
@@ -15,7 +16,7 @@ import { isAgentId } from "../config/model-selector.js";
15
16
  * prose-and-shell markdown the operator may copy verbatim — with the fence grown past any
16
17
  * backtick run the text itself contains, so a prompt that quotes a fence still round-trips.
17
18
  * Unset things render as explicit `_(none)_` lines rather than disappearing, so an
18
- * un-overridden loop reads as fully defaulted, not as a truncated report. */
19
+ * un-overridden role reads as fully defaulted, not as a truncated report. */
19
20
  /** A code fence one longer than any backtick run in the fenced text, so the block always
20
21
  * closes where the text itself would have ended it. */
21
22
  function fenceFor(text) {
@@ -31,22 +32,50 @@ function fencedOrNone(label, text, noneNote = "none") {
31
32
  const fence = fenceFor(text);
32
33
  return [`## ${label}`, "", fence, text.replace(/\n+$/, ""), fence, ""];
33
34
  }
34
- /** The Notebook and Next tick prompt blocks and the "more waits" disclosure, as Markdown
35
- * lines — the read-only inspection the TUI's notebook view and `tumwater role <id>` share,
36
- * so the terminal and the CLI show one loop's notebook and assembled prompt identically.
35
+ /** The Notes and Next run prompt blocks and the "more waits" disclosure, as Markdown
36
+ * lines — the read-only inspection the TUI's notes view and `tumwater role <id>` share,
37
+ * so the terminal and the CLI show one role's notes and assembled prompt identically.
37
38
  * `renderRoleMarkdown` splices these at the same position, so the extracted function and the
38
39
  * full document cannot drift. */
39
- export function inspectMarkdownLines(p) {
40
+ export function inspectMarkdownLines(p, now = Date.now()) {
40
41
  const running = chainStepLine(p);
41
- return [...(running ? [running, ""] : []), ...inspectBodyLines(p)];
42
+ return [...(running ? [running, ""] : []), ...inspectBodyLines(p, now)];
42
43
  }
43
- /** The notebook, prompt and disclosure blocks alone — what renderRoleMarkdown splices after
44
+ /** The notes, prompt and disclosure blocks alone — what renderRoleMarkdown splices after
44
45
  * its own Team lines, which already carry the fallback-step bullet. */
45
- function inspectBodyLines(p) {
46
+ function inspectBodyLines(p, now) {
46
47
  const lines = [];
47
48
  lines.push(...fencedOrNone("Notes", p.notes, "none yet — the role's own earlier runs write them, and the director has none"));
49
+ // The operator twin of the `<role-notes>` prompt block's save age: name when the note was
50
+ // last saved, so a note from five minutes ago and one from five days ago read differently.
51
+ // Present only when there is a note (noteSavedAt is null with none), so a note-less role's
52
+ // render is byte-identical to today's.
53
+ if (p.noteSavedAt !== null) {
54
+ lines.push(`_Last saved ${formatTimestamp(p.noteSavedAt)} (${humanAgo(p.noteSavedAt, now)}) — a snapshot of the past; verify its claims against the code._`);
55
+ }
56
+ // The qa observer's flow-coverage ledger (role-view.ts's flowCoverage, qa-coverage.ts's
57
+ // coverageRows): each flow's last exercise and result, in the same order and wording family
58
+ // as the prompt block (never-exercised first, then stalest-first), without the prompt's
59
+ // intro sentence — the operator twin of the ledger that is otherwise the observer's only
60
+ // memory. Present only for qa, so every other role's render stays byte-identical.
61
+ if (p.flowCoverage) {
62
+ lines.push("## Flow coverage", "");
63
+ for (const row of p.flowCoverage) {
64
+ if (row.lastRunAt === null) {
65
+ lines.push(`- ${row.flow}: never exercised`);
66
+ }
67
+ else {
68
+ const age = formatAge(now - row.lastRunAt);
69
+ const status = row.result === "bug"
70
+ ? `bug filed (BUGS.md: "${row.summary ?? "see BUGS.md"}")`
71
+ : "passed";
72
+ lines.push(`- ${row.flow}: ${age} ago, ${status}`);
73
+ }
74
+ }
75
+ lines.push("");
76
+ }
48
77
  lines.push(...fencedOrNone("Next run's prompt", p.nextPrompt, "nothing to do this run"));
49
- // tick-prompt.ts dequeues exactly one queued prompt per tick, so with more than one
78
+ // run-prompt.ts dequeues exactly one queued prompt per run, so with more than one
50
79
  // waiting the block above shows only the oldest — say so, so the count line and the
51
80
  // block never read as a contradiction (the 2026-10-04 BUGS.md entry under ## Fixed).
52
81
  if (p.inboxCount > 1 && p.nextPrompt !== null && p.nextPrompt.trim() !== "") {
@@ -59,12 +88,13 @@ function inspectBodyLines(p) {
59
88
  * `model` alone when only the model is (the join simply drops unset halves) — shared by the
60
89
  * main line and the budget-fallback line, so the two render the same wiring the same way. */
61
90
  function modelPair(provider, model) {
62
- return [provider, model].filter(Boolean).join("/");
91
+ return joinParts([provider, model], "/");
63
92
  }
64
93
  /** The backend's own-default label when no model resolves: `<agent> default` — pi's
65
- * historical `pi default` for the pi backend, `claude default` for claude (part 4f/7). */
66
- function agentDefault(p) {
67
- return `${p.backend !== "pi" ? p.backend : "pi"} default`;
94
+ * historical `pi default` for the pi backend, `claude default` for claude (part 4f/7),
95
+ * shared with the status rows through `backendDefaultLabel`. */
96
+ function backendDefault(p) {
97
+ return backendDefaultLabel(p.backend);
68
98
  }
69
99
  /** The provider/model wiring as one readable token: `provider/model` when both resolve,
70
100
  * `model` alone when only the model is named, and the backend-default note when neither is.
@@ -75,11 +105,11 @@ function modelLine(p) {
75
105
  // level (piDefault is formatted with the effective thinking winning), so the separate
76
106
  // ` (thinking: …)` suffix is dropped for this case only.
77
107
  if (p.piDefault)
78
- return `${agentDefault(p)} (${p.piDefault})`;
108
+ return `${backendDefault(p)} (${p.piDefault})`;
79
109
  const pair = modelPair(p.provider, p.model);
80
110
  const thinking = p.thinking ? ` (thinking: ${p.thinking})` : "";
81
111
  if (!pair)
82
- return `${agentDefault(p)}${thinking}`;
112
+ return `${backendDefault(p)}${thinking}`;
83
113
  const agent = p.backend !== "pi" ? `${p.backend}/` : "";
84
114
  return `${agent}${pair}${thinking}`;
85
115
  }
@@ -89,9 +119,8 @@ function modelLine(p) {
89
119
  * backend-default label. One home for the Model fallback and Budget fallback lines, so a
90
120
  * claude fallback reads `claude/claude-sonnet-5-5`, not a bare id (PLANS.md "Mixed claude +
91
121
  * pi fleets, part 3/4" gap 7). */
92
- function fallbackPairLabel(fb, agentDefault) {
93
- const agentBackend = (fb.backend !== undefined && fb.backend !== "pi" ? fb.backend : undefined) ??
94
- (fb.provider !== undefined && isAgentId(fb.provider) ? fb.provider : undefined);
122
+ function fallbackPairLabel(fb, backendDefault) {
123
+ const agentBackend = fb.backend !== undefined && fb.backend !== "pi" ? fb.backend : undefined;
95
124
  if (fb.model !== undefined) {
96
125
  if (agentBackend !== undefined)
97
126
  return `${agentBackend}/${fb.model}`;
@@ -99,12 +128,27 @@ function fallbackPairLabel(fb, agentDefault) {
99
128
  }
100
129
  if (fb.provider !== undefined)
101
130
  return fb.provider;
102
- return agentDefault;
131
+ return backendDefault;
103
132
  }
104
- /** Why a tier moved down its fallback chain, as the role view words it. */
105
- function chainReasonPhrase(reason, windowType) {
133
+ /** The backend-and-window label the role view shows for a non-claude backend's window:
134
+ * `backendDisplayName(billingBackend)` beside `windowLabel(windowType)` — "Codex 7d" for a
135
+ * codex subscription (plans/codex-backend.md part 3c/9), so a reader knows whose window it
136
+ * is. Claude keeps today's bare window type ("5h") so its role view stays byte-identical. */
137
+ function billingWindowLabel(billingBackend, windowType) {
138
+ const type = windowType !== undefined ? windowLabel(windowType) : "";
139
+ const backend = billingBackend !== undefined && billingBackend !== "claude" ? backendDisplayName(billingBackend) : "";
140
+ return [backend, type].filter(Boolean).join(" ");
141
+ }
142
+ /** Why a tier moved down its fallback chain, as the role view words it. The window phrase
143
+ * names the backend in play ("Codex 7d window used up") when a non-claude backend moved the
144
+ * role; claude keeps today's wording byte-identical, and an unknown backend falls back to
145
+ * today's hardcoded "Claude". */
146
+ function chainReasonPhrase(reason, billingBackend, windowType) {
106
147
  switch (reason) {
107
148
  case "window":
149
+ if (billingBackend !== undefined && billingBackend !== "claude") {
150
+ return `${billingWindowLabel(billingBackend, windowType)} window used up`;
151
+ }
108
152
  return `${windowType !== undefined ? claudeWindowLabel(windowType) : "Claude"} window used up`;
109
153
  case "reserve":
110
154
  return "window reserve reached";
@@ -125,10 +169,10 @@ function chainStepLine(p) {
125
169
  if (!c)
126
170
  return null;
127
171
  const resets = c.returnsAt !== undefined ? `, resets ${clockHm(c.returnsAt)}` : "";
128
- return `- Running on fallback ${c.step} of ${c.of} (${chainReasonPhrase(c.reason, p.billingWindow)}${resets})`;
172
+ return `- Running on fallback ${c.step} of ${c.of} (${chainReasonPhrase(c.reason, p.billingBackend, p.billingWindow)}${resets})`;
129
173
  }
130
- /** One recent tick outcome as a Markdown bullet line — `<time> · <result> · <span> —
131
- * <summary>` — so `tumwater role <id>` and the TUI notebook view read a loop's discrete
174
+ /** One recent run outcome as a Markdown bullet line — `<time> · <result> · <span> —
175
+ * <summary>` — so `tumwater role <id>` and the TUI notes view read a role's discrete
132
176
  * outcomes at a glance. `time` is the same local `YYYY-MM-DD HH:MM:SS` stamp the model-
133
177
  * fallback `since` prints (formatTimestamp); `span` is the same short-duration phrasing
134
178
  * shortSpanPhrase renders for holdPhrase and the event feed's countdowns; the summary is
@@ -142,29 +186,29 @@ function recentOutcomeLine(o) {
142
186
  line += ` — ${o.summary}`;
143
187
  return line;
144
188
  }
145
- /** One quiet-hours bullet: the window, whose it is, and whether it holds the loop now — with
189
+ /** One quiet-hours bullet: the window, whose it is, and whether it holds the role now — with
146
190
  * the same `quiet until <end>` text the status table uses. */
147
191
  function quietHoursLine(w) {
148
192
  const whose = w.scope === "role" ? "own window" : "team window";
149
193
  const state = w.inWindow ? `holding now — quiet until ${quietWindowEnd(w.window)}` : "not holding now";
150
194
  return `- Quiet hours: ${w.window} (${whose}; ${state})`;
151
195
  }
152
- /** The loop's schedule as Markdown bullet lines — `Next tick`, `Last tick`, then one
196
+ /** The role's schedule as Markdown bullet lines — `Next run`, `Last run`, then one
153
197
  * `Quiet hours` line per governing window (none when the payload carries no windows) — in the
154
198
  * same words and wording family as the team surface's `next run` cell (status-render.ts's
155
- * nextRunCell), so the role view and the status table cannot drift on when a loop acts next:
156
- * a never-scheduled loop (nextRunAt 0) reads `not scheduled yet`; a due loop reads `due now`;
199
+ * nextRunCell), so the role view and the status table cannot drift on when a role acts next:
200
+ * a never-scheduled role (nextRunAt 0) reads `not scheduled yet`; a due role reads `due now`;
157
201
  * a future one reads `in <Ns>` through humanSeconds, prefixed `backoff ` while backoffSeconds
158
202
  * > 0 (the wake row-action clears exactly that) — the shared nextRunPhrase, so the role view
159
203
  * and the status `next run` cell cannot drift on the backoff prefix or the humanSeconds
160
- * bucketing. `Last tick` reuses formatTimestamp and the same `humanSeconds … ago` phrasing the
161
- * status `last tick` cell ages with, or `never` for a loop that has not ticked. `now` is
204
+ * bucketing. `Last run` reuses formatTimestamp and the same `humanSeconds … ago` phrasing the
205
+ * status `last run` cell ages with, or `never` for a role that has not run. `now` is
162
206
  * injected (default Date.now()) so the renderer stays a pure function of the payload and the
163
207
  * test can pin a fixed clock. */
164
208
  function scheduleLine(p, now) {
165
209
  // The yield multiplier rides the line as `×N` exactly like nextRunCell/fmtNextRun: a quiet
166
- // yield-scaled loop's effective gap is longer than nextRunAt's countdown shows (isEligible
167
- // gates on the multiplier too), so the Schedule must not understate when the loop acts next.
210
+ // yield-scaled role's effective gap is longer than nextRunAt's countdown shows (isEligible
211
+ // gates on the multiplier too), so the Schedule must not understate when the role acts next.
168
212
  const suffix = yieldMultiplierSuffix(p.yieldMultiplier);
169
213
  let next;
170
214
  if (p.nextRunAt <= 0) {
@@ -175,11 +219,11 @@ function scheduleLine(p, now) {
175
219
  next = nextRunPhrase(remain, p.backoffSeconds > 0, "due now", true) + suffix;
176
220
  }
177
221
  const last = p.lastRunEndedAt !== undefined
178
- ? `${formatTimestamp(p.lastRunEndedAt)} (${humanSeconds(secondsSince(p.lastRunEndedAt, now))} ago)`
222
+ ? `${formatTimestamp(p.lastRunEndedAt)} (${humanAgo(p.lastRunEndedAt, now)})`
179
223
  : "never";
180
224
  return [`- Next run: ${next}`, `- Last run: ${last}`, ...(p.quietHours ?? []).map(quietHoursLine)];
181
225
  }
182
- /** Render one loop's inspection payload as Markdown. `now` is injected (default Date.now())
226
+ /** Render one role's inspection payload as Markdown. `now` is injected (default Date.now())
183
227
  * so the schedule lines stay a pure function of the payload — the test pins a fixed clock. */
184
228
  export function renderRoleMarkdown(p, now = Date.now()) {
185
229
  const lines = [`# tumwater role: ${p.id}`, ""];
@@ -194,15 +238,15 @@ export function renderRoleMarkdown(p, now = Date.now()) {
194
238
  // unresolvable pair, a model with no declared window).
195
239
  lines.push(`- Context window: ${p.contextWindow === null ? "unknown" : `${compactTokens(p.contextWindow)} tokens`}`);
196
240
  // Per-provider concurrency cap (plans/provider-concurrency.md §8): name the key whose cap
197
- // bounds the role's concurrent ticks, plus the cap itself. Present only while
241
+ // bounds the role's concurrent runs, plus the cap itself. Present only while
198
242
  // `maxConcurrentPerProvider` maps this seam's key — a cap-less default team has no
199
243
  // resolvable key entry, so its role view renders no new line and stays byte-identical.
200
244
  if (p.providerKey !== undefined && p.providerCap !== undefined) {
201
245
  lines.push(`- Provider: ${p.providerKey} (cap ${p.providerCap})`);
202
246
  }
203
- // Image input (plans/image-input.md "Surfaces", part 5/5): whether this loop's own model
204
- // reads images, and — when it cannot — where a tick that reads an image reroutes. Not just
205
- // `yes`/`no`: a text-only loop that falls back names the target, so `tumwater role <id>`
247
+ // Image input (plans/image-input.md "Surfaces", part 5/5): whether this role's own model
248
+ // reads images, and — when it cannot — where a run that reads an image reroutes. Not just
249
+ // `yes`/`no`: a text-only role that falls back names the target, so `tumwater role <id>`
206
250
  // explains the team alert without running doctor. Unknown (null) renders `unknown` rather
207
251
  // than guessing.
208
252
  const images = p.imageInput === true
@@ -213,14 +257,20 @@ export function renderRoleMarkdown(p, now = Date.now()) {
213
257
  : "no"
214
258
  : "unknown";
215
259
  lines.push(`- Images: ${images}`);
260
+ // Whether the review gate reviews this role's commits after they merge (role-view.ts's
261
+ // reviewedAfterMerge). Present only while review.enabled is on and review.afterMerge lists
262
+ // the role's base id — a pre-merge-reviewed role or a gate-off config omits the line.
263
+ if (p.reviewedAfterMerge) {
264
+ lines.push(`- Review: after merge (a rejected change stays on main with the reviewer's recorded reasons)`);
265
+ }
216
266
  // Model failure fallback, part 2/2: name the off-model pair, when the episode began, and
217
267
  // the provider-class failure that tripped it, so `tumwater role <id>` explains the state.
218
268
  if (p.modelFallback) {
219
- const pair = fallbackPairLabel(p.modelFallback, agentDefault(p));
269
+ const pair = fallbackPairLabel(p.modelFallback, backendDefault(p));
220
270
  lines.push(`- Model fallback: ${pair} — on fallback since ${formatTimestamp(p.modelFallback.since)} (primary failing: ${p.modelFallback.reason})`);
221
271
  }
222
272
  if (p.fallback) {
223
- const pair = fallbackPairLabel(p.fallback, agentDefault(p));
273
+ const pair = fallbackPairLabel(p.fallback, backendDefault(p));
224
274
  lines.push(`- Over-cap model: ${pair} (${p.fallbackFree ? "free" : "priced"})`);
225
275
  }
226
276
  const running = chainStepLine(p);
@@ -229,17 +279,19 @@ export function renderRoleMarkdown(p, now = Date.now()) {
229
279
  // The claude seam's billing (plans/agent-backends.md §7/§8, PLANS.md part 6d/7): absent on
230
280
  // a pi seam, so its role view is byte-identical to before.
231
281
  if (p.billing) {
232
- const window = p.billing === "subscription" && p.billingWindow ? ` (${claudeWindowLabel(p.billingWindow)} window)` : "";
282
+ const window = p.billing === "subscription" && p.billingWindow
283
+ ? ` (${billingWindowLabel(p.billingBackend, p.billingWindow)} window)`
284
+ : "";
233
285
  lines.push(`- Billing: ${p.billing === "api" ? "API" : p.billing}${window}`);
234
286
  }
235
287
  lines.push(`- Min run interval: ${p.minRunIntervalSeconds}s`);
236
288
  if (p.instances !== undefined)
237
289
  lines.push(`- Instances: ${p.instances}`);
238
290
  lines.push(`- Inbox: ${p.inboxCount}`);
239
- // The loop's own spend over its retained event window (role-view.ts's usage, summed from the
240
- // same run_end events recentOutcomes reads): how much the loop burned on tokens and cost,
291
+ // The role's own spend over its retained event window (role-view.ts's usage, summed from the
292
+ // same run_end events recentOutcomes reads): how much the role burned on tokens and cost,
241
293
  // with a notional suffix only when a claude-subscription seam burned non-zero notional spend
242
- // (plans/agent-backends.md part 6a). Absent for a never-costly loop, so old `tumwater role`
294
+ // (plans/agent-backends.md part 6a). Absent for a never-costly role, so old `tumwater role`
243
295
  // goldens are byte-identical.
244
296
  if (p.usage) {
245
297
  let spend = `- Spend: ${usd(p.usage.costUsd)} · ${compactTokens(p.usage.tokens)} tokens`;
@@ -248,7 +300,7 @@ export function renderRoleMarkdown(p, now = Date.now()) {
248
300
  lines.push(spend);
249
301
  }
250
302
  // The per-role daily cap (role-view.ts's dailyCap): today's spend against the cap and whether
251
- // it holds the loop now. Absent without a cap, so old goldens are unchanged.
303
+ // it holds the role now. Absent without a cap, so old goldens are unchanged.
252
304
  if (p.dailyCap) {
253
305
  const dc = p.dailyCap;
254
306
  let cap = `- Daily cap: ${usd(dc.spentUsd)} of ${usd(dc.capUsd)} spent today (${dc.over ? "holding now — no new runs until local midnight" : "not holding"})`;
@@ -256,14 +308,14 @@ export function renderRoleMarkdown(p, now = Date.now()) {
256
308
  cap += ` · shared by ${dc.roleIds.join(", ")}`;
257
309
  lines.push(cap);
258
310
  }
259
- // The loop's schedule (role-view.ts's nextRunAt/backoffSeconds/lastRunEndedAt, read from
260
- // the same persisted state): when it may tick again, and when it last did — the one
261
- // place the team's `next run`/`last tick` vocabulary was absent. Rendered from the
311
+ // The role's schedule (role-view.ts's nextRunAt/backoffSeconds/lastRunEndedAt, read from
312
+ // the same persisted state): when it may run again, and when it last did — the one
313
+ // place the team's `next run`/`last run` vocabulary was absent. Rendered from the
262
314
  // injected `now`, so the lines stay a pure function of the payload.
263
315
  lines.push(...scheduleLine(p, now));
264
- // The loop's recent tick outcomes (role-view.ts's recentOutcomes, read from the event log):
265
- // one line per outcome so `tumwater role <id>` shows what the loop has been DOING, not just
266
- // what it is. Absent (never ticked) renders nothing new, so old goldens are byte-identical.
316
+ // The role's recent run outcomes (role-view.ts's recentOutcomes, read from the event log):
317
+ // one line per outcome so `tumwater role <id>` shows what the role has been DOING, not just
318
+ // what it is. Absent (never run) renders nothing new, so old goldens are byte-identical.
267
319
  if (p.recentOutcomes && p.recentOutcomes.length > 0) {
268
320
  lines.push("", "## Recent runs", "");
269
321
  for (const o of p.recentOutcomes)
@@ -272,6 +324,6 @@ export function renderRoleMarkdown(p, now = Date.now()) {
272
324
  lines.push("");
273
325
  lines.push(...fencedOrNone("Instructions override", p.instructions));
274
326
  lines.push(...fencedOrNone("Find text", p.find, "none — the director is driven by its inbox, not by instructions"));
275
- lines.push(...inspectBodyLines(p));
327
+ lines.push(...inspectBodyLines(p, now));
276
328
  return lines.join("\n").trimEnd();
277
329
  }