tumwater 0.1.2 → 0.2.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 (361) hide show
  1. package/README.md +74 -108
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +24 -0
  4. package/dist/src/agent/agent-backend.js +2 -2
  5. package/dist/src/agent/backend-traits.js +60 -0
  6. package/dist/src/agent/claude/claude-account.js +61 -8
  7. package/dist/src/agent/claude/claude-args.js +12 -15
  8. package/dist/src/agent/claude/claude-backend.js +51 -41
  9. package/dist/src/agent/claude/claude-env.js +7 -3
  10. package/dist/src/agent/claude/claude-stream.js +7 -16
  11. package/dist/src/agent/image-input.js +107 -0
  12. package/dist/src/agent/notes-shim.js +41 -0
  13. package/dist/src/agent/opencode/opencode-args.js +61 -0
  14. package/dist/src/agent/opencode/opencode-backend.js +255 -0
  15. package/dist/src/agent/opencode/opencode-catalog.js +184 -0
  16. package/dist/src/agent/opencode/opencode-config.js +188 -0
  17. package/dist/src/agent/opencode/opencode-env.js +36 -0
  18. package/dist/src/agent/opencode/opencode-session-id.js +37 -0
  19. package/dist/src/agent/opencode/opencode-stream.js +278 -0
  20. package/dist/src/agent/role-notes-write.js +16 -32
  21. package/dist/src/agent/run-agent.js +95 -18
  22. package/dist/src/agent/session-ledger.js +57 -1
  23. package/dist/src/backlog/backlog-archive.js +296 -0
  24. package/dist/src/backlog/backlog-cli.js +573 -0
  25. package/dist/src/backlog/backlog-commit.js +31 -0
  26. package/dist/src/backlog/backlog-eligibility.js +357 -33
  27. package/dist/src/backlog/backlog-md.js +98 -18
  28. package/dist/src/backlog/backlog-render.js +62 -11
  29. package/dist/src/backlog/backlog-structure.js +261 -58
  30. package/dist/src/backlog/backlog-write.js +197 -319
  31. package/dist/src/backlog/backlog.js +113 -16
  32. package/dist/src/backlog/question-answer.js +339 -0
  33. package/dist/src/baseline/main-baseline.js +75 -88
  34. package/dist/src/baseline/main-red.js +38 -30
  35. package/dist/src/brief/brief-edit.js +43 -0
  36. package/dist/src/{brief.js → brief/brief.js} +66 -10
  37. package/dist/src/budget/budget.js +32 -15
  38. package/dist/src/budget/fallback-breaker.js +8 -7
  39. package/dist/src/build/build-check-detect.js +8 -7
  40. package/dist/src/build/build-check-events.js +68 -22
  41. package/dist/src/build/build-check-report.js +22 -23
  42. package/dist/src/build/build-check-scoped.js +51 -62
  43. package/dist/src/build/build-check.js +15 -14
  44. package/dist/src/build/build-info.js +2 -1
  45. package/dist/src/build/build-stage.js +23 -22
  46. package/dist/src/build/dep-install.js +22 -21
  47. package/dist/src/build/host-sleep.js +1 -1
  48. package/dist/src/change/change-data.js +25 -14
  49. package/dist/src/change/change-render.js +11 -10
  50. package/dist/src/cli/brief-command.js +58 -0
  51. package/dist/src/cli/cli-args.js +25 -5
  52. package/dist/src/cli/cli-command-args.js +46 -14
  53. package/dist/src/cli/cli-editor.js +64 -0
  54. package/dist/src/cli/cli-flag-specs.js +54 -16
  55. package/dist/src/cli/cli-marker-commands.js +13 -10
  56. package/dist/src/cli/cli-output.js +9 -0
  57. package/dist/src/cli/cli-query-commands.js +458 -49
  58. package/dist/src/cli/cli-repo-gate.js +1 -1
  59. package/dist/src/cli/cli-run.js +208 -59
  60. package/dist/src/cli/completion.js +207 -0
  61. package/dist/src/cli/config-commands.js +14 -11
  62. package/dist/src/cli/help.js +305 -110
  63. package/dist/src/cli/log-commands.js +127 -21
  64. package/dist/src/cli/notify-command.js +33 -18
  65. package/dist/src/cli/question-commands.js +29 -224
  66. package/dist/src/cli.js +121 -40
  67. package/dist/src/collections.js +66 -16
  68. package/dist/src/concurrency/check-permit.js +16 -17
  69. package/dist/src/concurrency/lock.js +6 -5
  70. package/dist/src/concurrency/provider-lease.js +69 -0
  71. package/dist/src/concurrency/semaphore.js +258 -36
  72. package/dist/src/config/config-aliases.js +106 -0
  73. package/dist/src/config/config-editable-keys.js +3 -3
  74. package/dist/src/config/config-fallbacks.js +379 -0
  75. package/dist/src/config/config-field-checks.js +19 -10
  76. package/dist/src/config/config-keys.js +160 -0
  77. package/dist/src/config/config-live.js +64 -12
  78. package/dist/src/config/config-schema.js +34 -22
  79. package/dist/src/config/config-validation.js +132 -48
  80. package/dist/src/config/config-views.js +119 -337
  81. package/dist/src/config/config-write.js +138 -177
  82. package/dist/src/config/config.js +60 -49
  83. package/dist/src/config/model-selector.js +28 -2
  84. package/dist/src/doctor/doctor-backlog.js +546 -49
  85. package/dist/src/doctor/doctor-checks.js +534 -56
  86. package/dist/src/doctor/doctor-holds.js +193 -53
  87. package/dist/src/doctor/doctor-install.js +30 -0
  88. package/dist/src/doctor/doctor-launch-services.js +2 -2
  89. package/dist/src/doctor/doctor-model-checks.js +549 -157
  90. package/dist/src/doctor/doctor-notify.js +27 -0
  91. package/dist/src/doctor/doctor-orphans.js +10 -9
  92. package/dist/src/doctor/doctor-render.js +36 -9
  93. package/dist/src/doctor/doctor.js +80 -22
  94. package/dist/src/doctor/image-probe.js +244 -0
  95. package/dist/src/doctor/probe.js +64 -0
  96. package/dist/src/events/event-format.js +226 -102
  97. package/dist/src/events/event-labels.js +116 -0
  98. package/dist/src/events/event-legacy.js +140 -0
  99. package/dist/src/events/event-read.js +58 -22
  100. package/dist/src/events/event-window.js +27 -7
  101. package/dist/src/events/events.js +18 -14
  102. package/dist/src/events/notify.js +137 -38
  103. package/dist/src/failure/failure-cluster.js +12 -9
  104. package/dist/src/failure/failure-data.js +96 -48
  105. package/dist/src/failure/failure-render.js +69 -58
  106. package/dist/src/failure/failure-state-change.js +33 -26
  107. package/dist/src/failure/time-spend.js +79 -59
  108. package/dist/src/files/file-queue.js +16 -4
  109. package/dist/src/files/files.js +18 -6
  110. package/dist/src/files/json-object.js +18 -3
  111. package/dist/src/files/tail.js +1 -1
  112. package/dist/src/gates/bootstrap-gates.js +5 -4
  113. package/dist/src/gates/budget-gates.js +250 -34
  114. package/dist/src/gates/disk-gate.js +18 -18
  115. package/dist/src/gates/gate-polls.js +117 -69
  116. package/dist/src/gates/maintenance-quota.js +29 -29
  117. package/dist/src/gates/pause-gates.js +11 -11
  118. package/dist/src/gates/readiness.js +2 -1
  119. package/dist/src/gates/role-cap-gates.js +16 -21
  120. package/dist/src/gates/startup-gate.js +75 -31
  121. package/dist/src/gates/streak-gate.js +18 -16
  122. package/dist/src/git/commit-message.js +12 -5
  123. package/dist/src/git/git-diff.js +16 -16
  124. package/dist/src/git/git-run.js +8 -8
  125. package/dist/src/git/git.js +86 -55
  126. package/dist/src/git/slots-state.js +14 -5
  127. package/dist/src/git/worktree-pool.js +25 -18
  128. package/dist/src/git/worktree-use.js +15 -14
  129. package/dist/src/git/worktree.js +18 -18
  130. package/dist/src/gui/gui-args.js +10 -14
  131. package/dist/src/gui/gui-backlog-commands.js +273 -0
  132. package/dist/src/gui/gui-command.js +1 -1
  133. package/dist/src/gui/gui-config-commands.js +440 -0
  134. package/dist/src/gui/gui-endpoint-commands.js +16 -669
  135. package/dist/src/gui/gui-endpoint-common.js +49 -0
  136. package/dist/src/gui/gui-endpoints.js +116 -56
  137. package/dist/src/gui/gui-operator-commands.js +145 -0
  138. package/dist/src/gui/gui-prompt-commands.js +234 -0
  139. package/dist/src/gui/gui-server.js +28 -8
  140. package/dist/src/gui/gui-team-commands.js +164 -0
  141. package/dist/src/gui/http-body.js +17 -1
  142. package/dist/src/history/history-data.js +194 -98
  143. package/dist/src/history/history-render.js +76 -0
  144. package/dist/src/history/history.js +37 -58
  145. package/dist/src/inbox/inbox-attachments.js +75 -14
  146. package/dist/src/inbox/inbox-cancel.js +7 -5
  147. package/dist/src/inbox/inbox-edit.js +1 -1
  148. package/dist/src/inbox/inbox-submit.js +44 -4
  149. package/dist/src/inbox/inbox-top.js +83 -0
  150. package/dist/src/inbox/inbox.js +8 -2
  151. package/dist/src/inbox/pending-prompt.js +1 -1
  152. package/dist/src/inbox/prompt-commands.js +170 -80
  153. package/dist/src/init/init-templates.js +16 -14
  154. package/dist/src/init/init.js +12 -11
  155. package/dist/src/loop/leftover.js +28 -27
  156. package/dist/src/loop/loop-fallback.js +2 -2
  157. package/dist/src/loop/loop-pi.js +106 -52
  158. package/dist/src/loop/loop-recovery.js +15 -15
  159. package/dist/src/loop/loop-state.js +139 -13
  160. package/dist/src/loop/loop-tick.js +194 -86
  161. package/dist/src/loop/loop.js +72 -36
  162. package/dist/src/loop/model-fallback.js +3 -2
  163. package/dist/src/loop/revision.js +16 -7
  164. package/dist/src/merge/backlog-conflicts.js +155 -0
  165. package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
  166. package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
  167. package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
  168. package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
  169. package/dist/src/merge/merge-drain.js +212 -0
  170. package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
  171. package/dist/src/merge/merge-pipeline.js +99 -0
  172. package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
  173. package/dist/src/merge/merge-queue-render.js +75 -0
  174. package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
  175. package/dist/src/merge/merge-slot.js +340 -0
  176. package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
  177. package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
  178. package/dist/src/merge/merge-verify.js +212 -0
  179. package/dist/src/notes-cli.js +97 -0
  180. package/dist/src/operator/operator-commands.js +154 -47
  181. package/dist/src/operator/operator-intent.js +87 -49
  182. package/dist/src/operator/operator-requests.js +52 -52
  183. package/dist/src/operator/retire.js +62 -27
  184. package/dist/src/operator/revert.js +71 -0
  185. package/dist/src/orchestrator/orchestrator-launch.js +74 -14
  186. package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
  187. package/dist/src/orchestrator/orchestrator.js +158 -100
  188. package/dist/src/orchestrator/retention.js +29 -12
  189. package/dist/src/orchestrator/startup-migration-steps.js +51 -0
  190. package/dist/src/orchestrator/startup-migrations.js +40 -0
  191. package/dist/src/paths.js +128 -37
  192. package/dist/src/pi/pi-args.js +26 -3
  193. package/dist/src/pi/pi-backend.js +15 -23
  194. package/dist/src/pi/pi-bin.js +18 -12
  195. package/dist/src/pi/pi-event-line.js +17 -1
  196. package/dist/src/pi/pi-failure.js +87 -0
  197. package/dist/src/pi/pi-models.js +126 -38
  198. package/dist/src/pi/pi-run-result.js +1 -1
  199. package/dist/src/pi/pi-settings.js +86 -0
  200. package/dist/src/pi/pi-stream.js +131 -107
  201. package/dist/src/pi/pi-watchdogs.js +47 -43
  202. package/dist/src/pi-extension/bounded-output.js +20 -5
  203. package/dist/src/pi-extension/context-budget.js +3 -1
  204. package/dist/src/pi-extension/context-shake.js +54 -21
  205. package/dist/src/pi-extension/context-window.js +37 -0
  206. package/dist/src/pi-extension/full-output.js +2 -2
  207. package/dist/src/pi-extension/strip-thinking.js +80 -0
  208. package/dist/src/pi-extension/trim-system-prompt.js +38 -0
  209. package/dist/src/process/launch-services.js +11 -10
  210. package/dist/src/process/process-table.js +1 -1
  211. package/dist/src/process/process.js +7 -7
  212. package/dist/src/process/run-marker.js +29 -6
  213. package/dist/src/process/supervisor.js +47 -25
  214. package/dist/src/prompt/dialect.js +30 -10
  215. package/dist/src/prompt/gate-prompts.js +170 -67
  216. package/dist/src/prompt/principles.js +1 -0
  217. package/dist/src/prompt/prompt-followup.js +35 -20
  218. package/dist/src/prompt/prompt.js +249 -91
  219. package/dist/src/redeploy/redeploy-policy.js +10 -10
  220. package/dist/src/redeploy/redeploy.js +6 -4
  221. package/dist/src/redeploy/redeployer.js +63 -55
  222. package/dist/src/redeploy/self-reload.js +1 -1
  223. package/dist/src/report/report-data.js +264 -255
  224. package/dist/src/report/report-fold.js +353 -0
  225. package/dist/src/report/report-render.js +270 -33
  226. package/dist/src/request-timeouts.js +1 -1
  227. package/dist/src/review/known-flakes.js +34 -14
  228. package/dist/src/review/protected-edits.js +33 -0
  229. package/dist/src/review/review-followup.js +16 -4
  230. package/dist/src/review/review-precheck.js +14 -13
  231. package/dist/src/review/review-verdict.js +14 -0
  232. package/dist/src/review/review.js +81 -39
  233. package/dist/src/review/secret-leaks.js +127 -0
  234. package/dist/src/review/suite-rerun.js +4 -3
  235. package/dist/src/roles/legacy-role-ids.js +21 -0
  236. package/dist/src/roles/legacy-role-migration.js +62 -0
  237. package/dist/src/roles/loop-ids.js +19 -2
  238. package/dist/src/roles/role-catalog.js +125 -135
  239. package/dist/src/roles/role-guidance.js +64 -31
  240. package/dist/src/roles/role-labels.js +10 -0
  241. package/dist/src/roles/role-render.js +194 -18
  242. package/dist/src/roles/role-view.js +216 -17
  243. package/dist/src/roles/roles.js +18 -15
  244. package/dist/src/scheduling/backoff.js +29 -19
  245. package/dist/src/scheduling/claims.js +25 -9
  246. package/dist/src/scheduling/once-round.js +11 -11
  247. package/dist/src/scheduling/quiet-hours.js +25 -32
  248. package/dist/src/scheduling/scheduling.js +43 -24
  249. package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
  250. package/dist/src/status/status-budget.js +115 -0
  251. package/dist/src/status/status-data.js +77 -184
  252. package/dist/src/status/status-inbox.js +47 -0
  253. package/dist/src/status/status-install.js +27 -0
  254. package/dist/src/status/status-polls.js +68 -33
  255. package/dist/src/status/status-roles.js +105 -0
  256. package/dist/src/{fleet → team}/error-storm.js +10 -10
  257. package/dist/src/{fleet → team}/failure-spread.js +8 -8
  258. package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
  259. package/dist/src/{fleet → team}/reclaim.js +5 -4
  260. package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
  261. package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
  262. package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
  263. package/dist/src/text/datetime.js +42 -2
  264. package/dist/src/text/format.js +10 -1
  265. package/dist/src/text/markdown.js +1 -1
  266. package/dist/src/text/phrases.js +35 -34
  267. package/dist/src/text/suggest.js +3 -2
  268. package/dist/src/text/text.js +2 -2
  269. package/dist/src/tick/qa-coverage.js +2 -1
  270. package/dist/src/tick/stage-check.js +147 -26
  271. package/dist/src/tick/telemetry-digest.js +10 -9
  272. package/dist/src/tick/tick-apply.js +101 -72
  273. package/dist/src/tick/tick-detail-data.js +32 -18
  274. package/dist/src/tick/tick-detail.js +62 -21
  275. package/dist/src/tick/tick-finalize.js +29 -22
  276. package/dist/src/tick/tick-outcome.js +2 -1
  277. package/dist/src/tick/tick-prompt.js +94 -26
  278. package/dist/src/tick/tick-resume.js +29 -13
  279. package/dist/src/tick/tick-stage.js +91 -47
  280. package/dist/src/tick/tick-timing.js +60 -59
  281. package/dist/src/tick/tick-usage.js +53 -23
  282. package/dist/src/tick/tick-verdict.js +76 -10
  283. package/dist/src/ui/badges.js +118 -67
  284. package/dist/src/ui/gui/gui-client-boot.js +209 -30
  285. package/dist/src/ui/gui/gui-client-composer.js +102 -23
  286. package/dist/src/ui/gui/gui-client-drawer.js +330 -75
  287. package/dist/src/ui/gui/gui-client-history.js +96 -38
  288. package/dist/src/ui/gui/gui-client-labels.js +24 -0
  289. package/dist/src/ui/gui/gui-client-loops.js +94 -52
  290. package/dist/src/ui/gui/gui-client-markdown.js +1 -0
  291. package/dist/src/ui/gui/gui-client-model.js +63 -43
  292. package/dist/src/ui/gui/gui-client-operator.js +129 -39
  293. package/dist/src/ui/gui/gui-client-pending.js +36 -32
  294. package/dist/src/ui/gui/gui-client-report.js +339 -46
  295. package/dist/src/ui/gui/gui-client-settings.js +148 -14
  296. package/dist/src/ui/gui/gui-client-sound.js +2 -1
  297. package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
  298. package/dist/src/ui/gui/gui-client.js +26 -20
  299. package/dist/src/ui/gui/gui-page.js +19 -19
  300. package/dist/src/ui/gui/gui-styles.js +12 -4
  301. package/dist/src/ui/progress-data.js +5 -3
  302. package/dist/src/ui/status-model.js +184 -122
  303. package/dist/src/ui/status-payload.js +119 -32
  304. package/dist/src/ui/status-render.js +122 -52
  305. package/dist/src/ui/team-alerts.js +341 -0
  306. package/dist/src/ui/tick-progress-model.js +14 -14
  307. package/dist/src/ui/tone.js +11 -8
  308. package/dist/src/ui/transcript-tail.js +1 -0
  309. package/dist/src/ui/transcript.js +23 -16
  310. package/dist/src/ui/tui/tui-app.js +1 -0
  311. package/dist/src/ui/tui/tui-backlog.js +85 -11
  312. package/dist/src/ui/tui/tui-editor-answer.js +97 -0
  313. package/dist/src/ui/tui/tui-editor-attach.js +107 -0
  314. package/dist/src/ui/tui/tui-editor-brief.js +107 -0
  315. package/dist/src/ui/tui/tui-editor-budget.js +155 -0
  316. package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
  317. package/dist/src/ui/tui/tui-editor-entry.js +230 -0
  318. package/dist/src/ui/tui/tui-editor-interval.js +122 -0
  319. package/dist/src/ui/tui/tui-editor-notes.js +114 -0
  320. package/dist/src/ui/tui/tui-editor-pause.js +266 -0
  321. package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
  322. package/dist/src/ui/tui/tui-editor-queued.js +98 -0
  323. package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
  324. package/dist/src/ui/tui/tui-editor-retire.js +83 -0
  325. package/dist/src/ui/tui/tui-editor-search.js +73 -0
  326. package/dist/src/ui/tui/tui-editor-session.js +97 -0
  327. package/dist/src/ui/tui/tui-editors.js +152 -0
  328. package/dist/src/ui/tui/tui-frame.js +67 -55
  329. package/dist/src/ui/tui/tui-history.js +12 -7
  330. package/dist/src/ui/tui/tui-input.js +49 -44
  331. package/dist/src/ui/tui/tui-keymap.js +1 -0
  332. package/dist/src/ui/tui/tui-keys.js +91 -1704
  333. package/dist/src/ui/tui/tui-pane-activity.js +99 -0
  334. package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
  335. package/dist/src/ui/tui/tui-pane-brief.js +30 -0
  336. package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
  337. package/dist/src/ui/tui/tui-pane-health.js +124 -0
  338. package/dist/src/ui/tui/tui-pane-history.js +316 -0
  339. package/dist/src/ui/tui/tui-pane-pending.js +74 -0
  340. package/dist/src/ui/tui/tui-pane-queued.js +143 -0
  341. package/dist/src/ui/tui/tui-pane-report.js +122 -0
  342. package/dist/src/ui/tui/tui-pane-session.js +101 -0
  343. package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
  344. package/dist/src/ui/tui/tui-pane.js +66 -28
  345. package/dist/src/ui/tui/tui-panes.js +294 -0
  346. package/dist/src/ui/tui/tui.js +45 -25
  347. package/dist/src/verdict/fix-claim.js +141 -38
  348. package/dist/src/verdict/no-change.js +19 -0
  349. package/dist/src/verdict/refusal.js +92 -8
  350. package/dist/src/verdict/reply-contract.js +17 -5
  351. package/dist/src/version.js +3 -2
  352. package/package.json +4 -2
  353. package/dist/src/agent/mcp/role-notes-server.js +0 -160
  354. package/dist/src/landing/backlog-conflicts.js +0 -116
  355. package/dist/src/landing/landing-drain.js +0 -204
  356. package/dist/src/landing/landing-pipeline.js +0 -96
  357. package/dist/src/landing/landing-queue-render.js +0 -31
  358. package/dist/src/landing/landing-slot.js +0 -246
  359. package/dist/src/landing/landing-vetting.js +0 -196
  360. package/dist/src/pi-extension/role-notes.js +0 -40
  361. package/dist/src/ui/fleet-alerts.js +0 -174
@@ -3,6 +3,7 @@ import { shortSha, usd } from "../text/format.js";
3
3
  import { dayKey, dayLabel, formatTime, monthDay, reportWindow, sinceWindowLine } from "../text/datetime.js";
4
4
  import { durationLabel } from "../cli/cli-args.js";
5
5
  import { eventsRotationLabel } from "../events/events.js";
6
+ import { eventTypeLabel } from "../events/event-labels.js";
6
7
  import { markdownTable } from "../text/markdown.js";
7
8
  /** A cluster's role list shows at most this many names before a "+N more" remainder. */
8
9
  const ROLES_SHOWN = 4;
@@ -28,7 +29,7 @@ const RESULT_ORDER = {
28
29
  };
29
30
  /** The digest table's columns: every TickResult that at least one role logged in the window, ranked
30
31
  * by RESULT_ORDER (ties alphabetical), so the table's shape follows the window's evidence instead
31
- * of the whole fixed vocabulary — a fleet with no errors gets no error column. */
32
+ * of the whole fixed vocabulary — a team with no errors gets no error column. */
32
33
  function columns(outcomes) {
33
34
  const present = new Set();
34
35
  for (const row of outcomes) {
@@ -37,20 +38,20 @@ function columns(outcomes) {
37
38
  }
38
39
  return [...present].sort((a, b) => RESULT_ORDER[a] - RESULT_ORDER[b] || a.localeCompare(b));
39
40
  }
40
- /** A delta cell: `prev → cur`, with "—" on a side where the role had no ticks (the "new role"
41
+ /** A delta cell: `prev → cur`, with "—" on a side where the role had no runs (the "new role"
41
42
  * case for the preceding window), so an absence is never rendered as an infinite increase.
42
43
  * The displayed halves may be raw counts (stringified here) or pre-rendered rate strings —
43
44
  * the tick-count halves exist only to decide the "—", not to be printed. */
44
- function deltaCell(prevTicks, prev, ticks, cur) {
45
- return `${prevTicks === 0 ? "—" : prev} → ${ticks === 0 ? "—" : cur}`;
45
+ function deltaCell(prevRuns, prev, runs, cur) {
46
+ return `${prevRuns === 0 ? "—" : prev} → ${runs === 0 ? "—" : cur}`;
46
47
  }
47
48
  /** The rejections column's cell. It cannot reuse deltaCell: a rejection is logged by the
48
- * landing slot, so its authoring tick_end can fall in the other window — a role with a
49
+ * merge slot, so its authoring run_end can fall in the other window — a role with a
49
50
  * rejection must show it even when its tick count is 0. Presence is judged on the rejection
50
- * count itself (the role is "absent" from the column only with neither ticks nor rejections). */
51
- function rejectionCell(prevTicks, prevRejections, ticks, rejections) {
52
- const prev = prevTicks === 0 && prevRejections === 0 ? "—" : String(prevRejections);
53
- const cur = ticks === 0 && rejections === 0 ? "—" : String(rejections);
51
+ * count itself (the role is "absent" from the column only with neither runs nor rejections). */
52
+ function rejectionCell(prevRuns, prevRejections, runs, rejections) {
53
+ const prev = prevRuns === 0 && prevRejections === 0 ? "—" : String(prevRejections);
54
+ const cur = runs === 0 && rejections === 0 ? "—" : String(rejections);
54
55
  return `${prev} → ${cur}`;
55
56
  }
56
57
  /** A ratio as a whole percent — but whole-percent rounding must not erase a real
@@ -62,29 +63,35 @@ function rejectionCell(prevTicks, prevRejections, ticks, rejections) {
62
63
  function percent(part, whole) {
63
64
  const pct = (100 * part) / whole;
64
65
  const rounded = Math.round(pct);
65
- if (rounded === 0 && part > 0) {
66
- const oneDecimal = pct.toFixed(1);
67
- return oneDecimal === "0.0" ? "<0.1%" : `${oneDecimal}%`;
68
- }
69
- if (rounded === 100 && part < whole) {
70
- const oneDecimal = pct.toFixed(1);
71
- return oneDecimal === "100.0" ? ">99.9%" : `${oneDecimal}%`;
72
- }
66
+ // A nonzero ratio at the 0/100 edges must not round away to "0%"/"100%": both arms keep
67
+ // one decimal the same way, so they share one helper — the two calls differ only in the
68
+ // edge they guard ("0.0"/"<0.1%" vs "100.0"/">99.9%").
69
+ if (rounded === 0 && part > 0)
70
+ return edgePercent(pct, "0.0", "<0.1%");
71
+ if (rounded === 100 && part < whole)
72
+ return edgePercent(pct, "100.0", ">99.9%");
73
73
  return `${rounded}%`;
74
74
  }
75
- /** An error rate as a whole percent, or "—" when the role ran no ticks (an absence is not
75
+ /** A near-edge one-decimal percent: `${n.d}%`, or the `edge` literal when even one decimal
76
+ * rounds back to the edge (`"0.0"` → "<0.1%", `"100.0"` → ">99.9%"). Shared by percent's two
77
+ * boundary windows so the formula cannot drift between them. */
78
+ function edgePercent(pct, edgeWhole, edge) {
79
+ const oneDecimal = pct.toFixed(1);
80
+ return oneDecimal === edgeWhole ? edge : `${oneDecimal}%`;
81
+ }
82
+ /** An error rate as a whole percent, or "—" when the role ran no runs (an absence is not
76
83
  * a 0% rate, for the same reason deltaCell says "—"). */
77
- function rate(errors, ticks) {
78
- if (ticks === 0)
84
+ function rate(errors, runs) {
85
+ if (runs === 0)
79
86
  return "—";
80
- return percent(errors, ticks);
87
+ return percent(errors, runs);
81
88
  }
82
89
  /** One time-and-spend cell: `x.x h · $y.yy`, or "—" when the role ended no tick on that
83
90
  * class — an absence is not a zero cost, for the same reason the Deltas cells say "—".
84
91
  * Hours carry one decimal: the digest prices agent-hours, and whole-hour rounding would
85
92
  * erase the difference between ten 200-ms errors and one 30-minute timeout. */
86
93
  function spendCell(cell) {
87
- if (cell.ticks === 0)
94
+ if (cell.runs === 0)
88
95
  return "—";
89
96
  return `${hoursPhrase(cell.ms)} · ${usd(cell.costUsd)}`;
90
97
  }
@@ -94,19 +101,20 @@ function spendCell(cell) {
94
101
  function hoursPhrase(ms) {
95
102
  return `${(ms / 3_600_000).toFixed(1)} h`;
96
103
  }
97
- /** Render the digest as bounded Markdown. Byte bound: for a given fleet the tables grow only with
104
+ /** Render the digest as bounded Markdown. Byte bound: for a given team the tables grow only with
98
105
  * the number of configured roles (fixed by config), the RESULT_ORDER vocabulary (fixed by
99
106
  * src/tick/tick-outcome.ts), and the time-and-spend section's LOSS_TOP loss-cause lines (fixed by
100
- * the cut), and every free string is capped — cluster examples at 120 chars (plus a `… (+N
107
+ * the cut), the merge-failure section's MERGE_FAILURE_TOP clusters (src/failure/failure-data.ts),
108
+ * and every free string is capped — cluster examples at 120 chars (plus a `… (+N
101
109
  * chars)` cut marker when truncated, so a marked cut can never read as a complete message —
102
- * BUGS.md 2026-09-30), landed summaries at 100 under the same rule, a cluster's role list at 4
110
+ * BUGS.md 2026-09-30), merged summaries at 100 under the same rule, a cluster's role list at 4
103
111
  * names plus a remainder count, and any loop id sliced to 32 chars (config validation already
104
112
  * refuses longer custom-loop ids, so the slice is a guard rather than the real bound). Cluster
105
- * counts are capped at top-N, and the Fleet state changes section is capped at STATE_CHANGE_TOP
113
+ * counts are capped at top-N, and the Team state changes section is capped at STATE_CHANGE_TOP
106
114
  * lines with each payload at STATE_CHANGE_MAX and each free field at STATE_CHANGE_FIELD_MAX; the
107
- * landed list at LANDED_TOP lines, each capped section closing with one remainder line when its cut
115
+ * merged list at MERGED_TOP lines, each capped section closing with one remainder line when its cut
108
116
  * dropped anything. Nothing here grows with how bad the window was: measured 6,017 bytes at the
109
- * CLI's default 14 days on the live fleet, and the worst-case byte-bound fixture in
117
+ * CLI's default 14 days on the live team, and the worst-case byte-bound fixture in
110
118
  * test/failure-render.test.ts covers transition events too. Pure function of FailureReportData: no
111
119
  * I/O, no clock reads. */
112
120
  export function renderFailureMarkdown(data) {
@@ -117,8 +125,8 @@ export function renderFailureMarkdown(data) {
117
125
  // instant span (the wording renderSinceReportMarkdown uses); the day shape keeps its
118
126
  // day-window line. The empty branch follows the same split so neither claims the other's unit.
119
127
  lines.push(data.sinceMs !== undefined
120
- ? `${sinceWindowLine(durationLabel(data.sinceMs), data.fromIso)} · ${data.ticks} ticks`
121
- : `${reportWindow(data.from, data.to, data.days, eventsRotationLabel())} · ${data.ticks} ticks`);
128
+ ? `${sinceWindowLine(durationLabel(data.sinceMs), data.fromIso)} · ${data.runs} runs`
129
+ : `${reportWindow(data.from, data.to, data.days, eventsRotationLabel())} · ${data.runs} runs`);
122
130
  if (data.role !== undefined)
123
131
  lines.push(`role: ${data.role}`);
124
132
  if (data.logState === "unreadable")
@@ -132,15 +140,15 @@ export function renderFailureMarkdown(data) {
132
140
  }
133
141
  else if (data.partial)
134
142
  lines.push(`partial: retained log starts ${data.oldestEventDate ?? "?"}`);
135
- // The causal frame before the counts: what the fleet DID in the window, so the reader can ask
143
+ // The causal frame before the counts: what the team DID in the window, so the reader can ask
136
144
  // whether the harness's response was right. Omitted entirely when the window held none. When
137
145
  // the newest-N cap cut the window's transitions, the remainder is named — a quiet window (no
138
146
  // line) and a truncated one must not look the same.
139
147
  if (data.stateChanges.length > 0) {
140
148
  lines.push("");
141
149
  // A scoped digest retitles the section: its transitions are the named role's, so the
142
- // fleet-wide "Fleet" claim would overreach.
143
- lines.push(data.role !== undefined ? "## State changes" : "## Fleet state changes");
150
+ // team-wide "Team" claim would overreach.
151
+ lines.push(data.role !== undefined ? "## State changes" : "## Team state changes");
144
152
  for (const s of data.stateChanges) {
145
153
  lines.push(`- ${stateChangeStamp(s.ts)} ${roleCell(s.role)} — ${s.description}`);
146
154
  }
@@ -152,18 +160,18 @@ export function renderFailureMarkdown(data) {
152
160
  lines.push("");
153
161
  lines.push("## Outcome by role");
154
162
  if (data.outcomes.length === 0) {
155
- lines.push("_no tick_end events in the window_");
163
+ lines.push("_no finished runs in the window_");
156
164
  }
157
165
  else {
158
166
  // markdownTable derives the separator from the header, so the two rows cannot disagree on
159
167
  // their cell count (the BUGS.md 2026-09-21 plain-text collapse). `---:` right-aligns the
160
168
  // counts.
161
- lines.push(...markdownTable(["role", ...cols], data.outcomes.map((row) => [
169
+ lines.push(...markdownTable(["role", ...cols.map(eventTypeLabel)], data.outcomes.map((row) => [
162
170
  roleCell(row.role),
163
171
  ...cols.map((c) => String(row.counts[c] ?? 0)),
164
172
  ]), ["left", ...cols.map(() => "right")]));
165
173
  }
166
- // The same ticks priced by what they cost, so a 30-minute timeout outranks ten 200-ms
174
+ // The same runs priced by what they cost, so a 30-minute timeout outranks ten 200-ms
167
175
  // errors that the Outcome table above weighs equally. Two parts: the role × class table of
168
176
  // hours and dollars, then the top loss causes by time (an error cluster, a role's
169
177
  // no_change total — the quiet loss no cluster names, a review-rejected authoring span, or a
@@ -172,12 +180,12 @@ export function renderFailureMarkdown(data) {
172
180
  lines.push("");
173
181
  lines.push("## Time and spend by outcome");
174
182
  if (data.timeSpend.length === 0) {
175
- lines.push("_no tick_end events in the window_");
183
+ lines.push("_no finished runs in the window_");
176
184
  }
177
185
  else {
178
- lines.push(...markdownTable(["role", "landed", "no_change", "error-class"], data.timeSpend.map((row) => [
186
+ lines.push(...markdownTable(["role", "merged", "no change", "error or blocked"], data.timeSpend.map((row) => [
179
187
  roleCell(row.role),
180
- spendCell(row.classes.landed),
188
+ spendCell(row.classes.merged),
181
189
  spendCell(row.classes.no_change),
182
190
  spendCell(row.classes.error),
183
191
  ])));
@@ -189,13 +197,15 @@ export function renderFailureMarkdown(data) {
189
197
  else {
190
198
  for (const c of data.lossCauses) {
191
199
  const cause = c.kind === "no_change"
192
- ? `no_change on ${roleCell(c.roles[0] ?? "?")}`
200
+ ? `no change on ${roleCell(c.roles[0] ?? "?")}`
193
201
  : c.kind === "review-rejected"
194
202
  ? `review-rejected authoring on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
195
- : c.kind === "merge-conflict" || c.kind === "merge-blocked"
196
- ? `${c.kind} authoring on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
197
- : `${c.example} (${roleList(c.roles)})`;
198
- lines.push(`- ${hoursPhrase(c.ms)} · ${usd(c.costUsd)} — ${plural(c.ticks, "tick")}: ${cause}`);
203
+ : c.kind === "refused"
204
+ ? `refused on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
205
+ : c.kind === "merge-conflict" || c.kind === "merge-blocked"
206
+ ? `${c.kind} authoring on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
207
+ : `${c.example} (${roleList(c.roles)})`;
208
+ lines.push(`- ${hoursPhrase(c.ms)} · ${usd(c.costUsd)} — ${plural(c.runs, "run")}: ${cause}`);
199
209
  }
200
210
  // Mark the top-5 cut the way the cluster sections mark theirs: a capped ranking that
201
211
  // stays silent reads as a complete itemization of the window's loss (BUGS.md 2026-10-01).
@@ -207,49 +217,50 @@ export function renderFailureMarkdown(data) {
207
217
  }
208
218
  // Prompt-token telemetry: the per-role median prompt
209
219
  // send and how much of the prompt budget goes out before a tick's first edit. Omitted
210
- // entirely when no tick_end in the window carries the fields, so old logs and their golden
220
+ // entirely when no run_end in the window carries the fields, so old logs and their golden
211
221
  // digests render unchanged.
212
222
  if (data.promptStats.length > 0) {
213
223
  lines.push("");
214
224
  lines.push("## Prompt tokens by role");
215
225
  for (const row of data.promptStats) {
216
226
  const share = percent(row.preEditShare, 1);
217
- lines.push(`- ${roleCell(row.role)}: median ${row.medianPromptTokens} prompt tokens/tick · ${share} before first edit`);
227
+ lines.push(`- ${roleCell(row.role)}: median ${row.medianPromptTokens} prompt tokens/run · ${share} before first edit`);
218
228
  }
219
229
  }
220
230
  lines.push("");
221
231
  lines.push(`## Deltas vs the preceding ${data.sinceMs !== undefined ? durationLabel(data.sinceMs) : dayLabel(data.days)}`);
222
232
  if (data.deltas.length === 0) {
223
- lines.push("_no ticks in either window_");
233
+ lines.push("_no runs in either window_");
224
234
  }
225
235
  else {
226
- lines.push(...markdownTable(["role", "ticks", "error rate", "quiet kills", "rejections"], data.deltas.map((d) => [
236
+ lines.push(...markdownTable(["role", "runs", "error rate", "stalls", "rejections"], data.deltas.map((d) => [
227
237
  roleCell(d.role),
228
- deltaCell(d.prevTicks, d.prevTicks, d.ticks, d.ticks),
229
- deltaCell(d.prevTicks, rate(d.prevErrors, d.prevTicks), d.ticks, rate(d.errors, d.ticks)),
230
- deltaCell(d.prevTicks, d.prevQuietKills, d.ticks, d.quietKills),
231
- rejectionCell(d.prevTicks, d.prevRejections, d.ticks, d.rejections),
238
+ deltaCell(d.prevRuns, d.prevRuns, d.runs, d.runs),
239
+ deltaCell(d.prevRuns, rate(d.prevErrors, d.prevRuns), d.runs, rate(d.errors, d.runs)),
240
+ deltaCell(d.prevRuns, d.prevQuietKills, d.runs, d.quietKills),
241
+ rejectionCell(d.prevRuns, d.prevRejections, d.runs, d.rejections),
232
242
  ])));
233
243
  }
234
244
  // The title restates the section's contract: its total now covers the Outcome table's error
235
245
  // column AND its main_red cells (the baseline gate's own cause rides the tick, BUGS.md
236
246
  // 2026-09-28), so an operator cross-checking counts against the tables above is not misled
237
247
  // by a title that reads as the error column alone.
238
- renderClusters(lines, "Top error clusters (red-main causes included)", data.errors, "tick errors");
248
+ renderClusters(lines, "Top error clusters (red-main causes included)", data.errors, "run errors");
239
249
  renderClusters(lines, "Top warning clusters", data.warnings, "warnings");
240
250
  renderClusters(lines, "Top review failure clusters", data.reviewFailures, "review failures");
241
251
  renderClusters(lines, "Top rejection clusters", data.rejections, "rejections");
252
+ renderClusters(lines, "Merge failures", data.mergeFailures, "merge failures");
242
253
  lines.push("");
243
- lines.push("## Landed in the window");
244
- if (data.landed.length === 0) {
254
+ lines.push("## Merged in the window");
255
+ if (data.merged.length === 0) {
245
256
  lines.push("_nothing merged in the window_");
246
257
  }
247
258
  else {
248
- for (const c of data.landed)
259
+ for (const c of data.merged)
249
260
  lines.push(`- ${shortSha(c.commit)} — ${c.summary}`);
250
261
  // Like the transitions' remainder line: a cut list says so, so a busy window's newest ten
251
262
  // are not mistaken for everything that merged.
252
- const hidden = data.landedTotal - data.landed.length;
263
+ const hidden = data.mergedTotal - data.merged.length;
253
264
  if (hidden > 0)
254
265
  lines.push(`+${hidden} older merges not listed`);
255
266
  }
@@ -279,7 +290,7 @@ function renderClusters(lines, title, section, noun) {
279
290
  lines.push(`_+${section.hiddenClusters} more ${clusters} holding ${remaining} ${noun1}_`);
280
291
  }
281
292
  }
282
- /** The cluster's roles, capped so a fleet-wide cluster cannot blow the byte bound; the count of
293
+ /** The cluster's roles, capped so a team-wide cluster cannot blow the byte bound; the count of
283
294
  * the omitted remainder is kept so the cluster's reach stays legible. */
284
295
  function roleList(roles) {
285
296
  const shown = roles.slice(0, ROLES_SHOWN).map(roleCell).join(", ");
@@ -1,4 +1,5 @@
1
- /** Wording half of the failure digest's Fleet state changes section: which transition event types
1
+ // vocabulary: plain
2
+ /** Wording half of the failure digest's Team state changes section: which transition event types
2
3
  * the digest replays and the bounded one-liner describing each. Split out of failure-data.ts (the
3
4
  * same seam build/build-check-events.ts split from build/build-check.ts) so the per-event-type
4
5
  * wording — which changes whenever the harness grows a new self-decision — stays apart from the
@@ -8,11 +9,11 @@
8
9
  import { truncateExample } from "./failure-cluster.js";
9
10
  import { cutSplitsSurrogatePair } from "../text/text.js";
10
11
  import { finiteNumber, stringList } from "../files/json-object.js";
11
- import { ORCHESTRATOR_STOPPED, backendKindPhrase, budgetPhrase, fleetPausePhrase, fleetResumePhrase, holdPhrase, plural, rolePausePhrase, roleResumePhrase, rolesPhrase, shortSpanPhrase } from "../text/phrases.js";
12
+ import { ORCHESTRATOR_STOPPED, backendKindPhrase, budgetPhrase, teamPausePhrase, teamResumePhrase, holdPhrase, plural, rolePausePhrase, roleResumePhrase, rolesPhrase, shortSpanPhrase } from "../text/phrases.js";
12
13
  import { shortSha } from "../text/format.js";
13
14
  import { deferredReasonText } from "../events/event-format.js";
14
15
  /** The transition events the digest replays: the decisions the harness made about itself (the
15
- * cap/fleet gates and the fleet hold, the error-streak breaker's pause, the role's own daily
16
+ * cap/team gates and the team hold, the error-streak breaker's pause, the role's own daily
16
17
  * cap, model fallbacks, live-config edits, self-hosted redeploys, need-based deferrals,
17
18
  * orchestrator lifecycle). They are the evidence the telemetry role's load-bearing
18
19
  * rule asks it to judge — whether the harness's RESPONSE to a failure was wrong
@@ -21,8 +22,8 @@ export const STATE_CHANGE_TYPES = new Set([
21
22
  "budget_paused",
22
23
  "budget_fallback",
23
24
  "budget_resumed",
24
- "fleet_paused",
25
- "fleet_resumed",
25
+ "team_paused",
26
+ "team_resumed",
26
27
  "role_paused",
27
28
  "role_resumed",
28
29
  "role_streak_paused",
@@ -40,12 +41,12 @@ export const STATE_CHANGE_TYPES = new Set([
40
41
  "restart",
41
42
  "restart_refused",
42
43
  "restart_blocked",
43
- "tick_deferred",
44
+ "run_deferred",
44
45
  "orchestrator_start",
45
46
  "orchestrator_stop",
46
47
  "supervisor_exit",
47
48
  ]);
48
- /** The Fleet state changes section's caps: newest N transitions, each line's payload capped at
49
+ /** The Team state changes section's caps: newest N transitions, each line's payload capped at
49
50
  * STATE_CHANGE_MAX (a marked word-boundary cut per truncateExample), each free field within it
50
51
  * at STATE_CHANGE_FIELD_MAX. Together with the fixed timestamp/role cells these make the
51
52
  * section's bytes a constant-plus-marker, so the digest's ~6 KB bound holds no matter how many
@@ -65,7 +66,13 @@ function field(v) {
65
66
  cut -= 1; // Never emit a lone high surrogate.
66
67
  return s.slice(0, cut);
67
68
  }
68
- /** A compact, bounded one-liner for one harness decision event, for the Fleet state changes
69
+ /** The backend a fallback-ish decision line names, as "provider/model", defaulting each half
70
+ * to pi's own default when the event omits it and slicing both through field. Shared by the
71
+ * budget_fallback and model_fallback lines so the default and its slice rule stay in one place. */
72
+ function providerModel(ev) {
73
+ return `${field(ev.provider ?? "pi default")}/${field(ev.model ?? "pi default")}`;
74
+ }
75
+ /** A compact, bounded one-liner for one harness decision event, for the Team state changes
69
76
  * section. Every free string is sliced (field) and the whole line is capped again at
70
77
  * STATE_CHANGE_MAX so the digest's byte bound holds for any event shape — the cap cut falls on
71
78
  * a word boundary and carries truncateExample's `… (+N chars)` marker, so a cut line can never
@@ -78,7 +85,7 @@ export function describeStateChange(ev) {
78
85
  // A breaker demotion (BUGS.md 2026-09-20) leads with its cause instead of the spend: the
79
86
  // spend line is the budget_fallback just before it, and the line cap would cut the pair.
80
87
  if (ev.fallbackDemoted) {
81
- text = `budget paused — ${field(ev.failures ?? "?")} ticks failed on fallback ${field(ev.fallbackDemoted)}`;
88
+ text = `budget paused — ${field(ev.failures ?? "?")} runs failed on over-cap model ${field(ev.fallbackDemoted)}`;
82
89
  break;
83
90
  }
84
91
  const refused = ev.fallbackRejected
@@ -88,16 +95,16 @@ export function describeStateChange(ev) {
88
95
  break;
89
96
  }
90
97
  case "budget_fallback":
91
- text = `budget fallback — ${budgetPhrase(ev.spentUsd, ev.capUsd)} daily cost reached; on ${field(ev.provider ?? "pi default")}/${field(ev.model ?? "pi default")} (cost n/a)`;
98
+ text = `over-cap model — ${budgetPhrase(ev.spentUsd, ev.capUsd)} daily cost reached; roles continue on ${providerModel(ev)} (cost n/a)`;
92
99
  break;
93
100
  case "budget_resumed":
94
101
  text = `budget resumed (${budgetPhrase(ev.spentUsd, ev.capUsd)} today)`;
95
102
  break;
96
- case "fleet_paused":
97
- text = fleetPausePhrase();
103
+ case "team_paused":
104
+ text = teamPausePhrase();
98
105
  break;
99
- case "fleet_resumed":
100
- text = fleetResumePhrase();
106
+ case "team_resumed":
107
+ text = teamResumePhrase();
101
108
  break;
102
109
  case "role_paused":
103
110
  text = rolePausePhrase(field(ev.role ?? "?"));
@@ -108,10 +115,10 @@ export function describeStateChange(ev) {
108
115
  case "role_streak_paused":
109
116
  // The error-streak breaker auto-paused the role; the digest names the depth so the
110
117
  // operator can connect the pause to the failing ticks the clusters above show.
111
- text = `role ${field(ev.role ?? "?")} paused — ${field(ev.streak ?? "?")} ticks failed in a row`;
118
+ text = `role ${field(ev.role ?? "?")} paused — ${field(ev.streak ?? "?")} runs failed in a row`;
112
119
  break;
113
120
  case "role_cap_paused":
114
- // The role reached its own daily spend cap and stopped starting ticks.
121
+ // The role reached its own daily spend cap and stopped starting runs.
115
122
  text = `role ${field(ev.role ?? "?")} paused — ${budgetPhrase(ev.spentUsd, ev.capUsd)} of its daily cap spent`;
116
123
  break;
117
124
  case "role_cap_resumed":
@@ -119,12 +126,12 @@ export function describeStateChange(ev) {
119
126
  break;
120
127
  case "model_fallback_started":
121
128
  // The role tripped off its primary onto its fallback after provider-class failures.
122
- text = `model fallback — ${field(ev.provider ?? "pi default")}/${field(ev.model ?? "pi default")} engaged (${field(ev.reason ?? "backend failure")})`;
129
+ text = `model fallback — ${providerModel(ev)} engaged (${field(ev.reason ?? "backend failure")})`;
123
130
  break;
124
131
  case "model_fallback_ended":
125
132
  // The return counterpart: names the primary the role resumed on and how long the
126
133
  // off-model episode ran.
127
- text = `model fallback ended — back on ${field(ev.provider ?? "pi default")}/${field(ev.model ?? "pi default")} after ${shortSpanPhrase(finiteNumber(ev.durationMs, 0))}`;
134
+ text = `model fallback ended — back on ${providerModel(ev)} after ${shortSpanPhrase(finiteNumber(ev.durationMs, 0))}`;
128
135
  break;
129
136
  case "rate_limit_hold": {
130
137
  // Same split as the event feed's rendering (event-format.ts): a rate-limit hold keeps
@@ -142,8 +149,8 @@ export function describeStateChange(ev) {
142
149
  // carries the ended hold's kind, so a backend hold's lift names itself.
143
150
  text =
144
151
  ev.kind && ev.kind !== "rate-limit"
145
- ? `backend hold lifted (${backendKindPhrase(ev.kind)}) — role loops tick again`
146
- : "429 hold lifted — role loops tick again";
152
+ ? `backend hold lifted (${backendKindPhrase(ev.kind)}) — roles run again`
153
+ : "429 hold lifted — roles run again";
147
154
  break;
148
155
  case "max_concurrent_changed":
149
156
  text = `maxConcurrent ${field(ev.from)} → ${field(ev.to)}`;
@@ -171,13 +178,13 @@ export function describeStateChange(ev) {
171
178
  break;
172
179
  }
173
180
  case "build_stale":
174
- text = `build ${shortSha(ev.build)} stale — main ${shortSha(ev.head)} ${plural(finiteNumber(ev.aheadCommits, 0), "commit")} ahead`;
181
+ text = `update available (${plural(finiteNumber(ev.aheadCommits, 0), "commit")} behind) — restarts when idle`;
175
182
  break;
176
183
  case "restart_pending":
177
- text = `restart pending — main ${shortSha(ev.head)} green; compiling`;
184
+ text = `restart pending — main ${shortSha(ev.head)} green`;
178
185
  break;
179
186
  case "restart":
180
- text = `restarting onto build ${shortSha(ev.to)}`;
187
+ text = `restarting onto update ${shortSha(ev.to)}`;
181
188
  break;
182
189
  case "restart_refused":
183
190
  text = `restart onto ${shortSha(ev.to)} refused: ${field(ev.reason)}`;
@@ -185,17 +192,17 @@ export function describeStateChange(ev) {
185
192
  case "restart_blocked":
186
193
  text = `restart blocked — ${field(ev.reason)}; staying on build ${shortSha(ev.from)}`;
187
194
  break;
188
- case "tick_deferred":
195
+ case "run_deferred":
189
196
  text = `deferred — ${deferredReasonText(ev)}`;
190
197
  break;
191
198
  case "orchestrator_start":
192
- text = `orchestrator started (pid ${field(ev.pid)}${ev.build ? `, build ${shortSha(ev.build)}` : ""})`;
199
+ text = `tumwater started (pid ${field(ev.pid)}${ev.build ? `, build ${shortSha(ev.build)}` : ""})`;
193
200
  break;
194
201
  case "orchestrator_stop":
195
202
  text = ORCHESTRATOR_STOPPED;
196
203
  break;
197
204
  case "supervisor_exit":
198
- text = `fleet down — generation ${field(ev.generation)} ${ev.signal ? `killed by ${field(ev.signal)}` : `exited ${field(ev.code)}`}${ev.reason ? `: ${field(ev.reason)}` : ""}`;
205
+ text = `tumwater down — restart ${field(ev.generation)} ${ev.signal ? `killed by ${field(ev.signal)}` : `exited ${field(ev.code)}`}${ev.reason ? `: ${field(ev.reason)}` : ""}`;
199
206
  break;
200
207
  default:
201
208
  text = ev.type;