tumwater 0.4.2 → 0.4.4

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 (354) hide show
  1. package/README.md +6 -6
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-backend.js +6 -6
  4. package/dist/src/agent/{run-agent.js → agent-run.js} +27 -13
  5. package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
  6. package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
  7. package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
  8. package/dist/src/agent/backend-traits.js +2 -2
  9. package/dist/src/agent/claude/claude-account.js +6 -27
  10. package/dist/src/agent/claude/claude-backend.js +3 -3
  11. package/dist/src/agent/claude/claude-env.js +27 -31
  12. package/dist/src/agent/claude/claude-stream.js +111 -54
  13. package/dist/src/agent/codex/codex-account.js +8 -0
  14. package/dist/src/agent/codex/codex-args.js +5 -5
  15. package/dist/src/agent/codex/codex-backend.js +2 -2
  16. package/dist/src/agent/codex/codex-env.js +28 -15
  17. package/dist/src/agent/codex/codex-hook-trust.js +39 -20
  18. package/dist/src/agent/codex/codex-rollout.js +93 -56
  19. package/dist/src/agent/codex/codex-stream.js +1 -1
  20. package/dist/src/agent/env-scrub.js +18 -0
  21. package/dist/src/agent/image-input.js +68 -11
  22. package/dist/src/agent/notes-shim.js +2 -2
  23. package/dist/src/agent/opencode/opencode-backend.js +26 -24
  24. package/dist/src/agent/opencode/opencode-catalog.js +85 -36
  25. package/dist/src/agent/opencode/opencode-config.js +32 -14
  26. package/dist/src/agent/opencode/opencode-env.js +12 -13
  27. package/dist/src/agent/opencode/opencode-stream.js +47 -25
  28. package/dist/src/agent/session-ledger.js +23 -27
  29. package/dist/src/agent/subscription-account.js +2 -83
  30. package/dist/src/backlog/backlog-archive.js +3 -4
  31. package/dist/src/backlog/backlog-cli-view.js +81 -41
  32. package/dist/src/backlog/backlog-cli.js +57 -26
  33. package/dist/src/backlog/backlog-eligibility.js +56 -122
  34. package/dist/src/backlog/backlog-md.js +5 -5
  35. package/dist/src/backlog/backlog-parts.js +65 -27
  36. package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
  37. package/dist/src/backlog/backlog-release.js +12 -12
  38. package/dist/src/backlog/backlog-structure.js +114 -61
  39. package/dist/src/backlog/backlog.js +3 -2
  40. package/dist/src/baseline/main-baseline.js +148 -84
  41. package/dist/src/baseline/main-guardrails.js +2 -2
  42. package/dist/src/baseline/main-watch.js +84 -50
  43. package/dist/src/brief/brief.js +26 -13
  44. package/dist/src/budget/budget-fallback-view.js +100 -243
  45. package/dist/src/budget/budget-wiring.js +29 -65
  46. package/dist/src/budget/budget.js +4 -5
  47. package/dist/src/budget/fallback-breaker.js +2 -2
  48. package/dist/src/build/build-check-detect.js +43 -14
  49. package/dist/src/build/build-check-events.js +2 -2
  50. package/dist/src/build/build-check-report.js +10 -0
  51. package/dist/src/build/build-check-scoped.js +40 -18
  52. package/dist/src/build/build-check.js +13 -5
  53. package/dist/src/build/build-stage.js +6 -4
  54. package/dist/src/change/change-data.js +1 -4
  55. package/dist/src/cli/cli-command-args.js +89 -52
  56. package/dist/src/cli/cli-completion-command.js +75 -0
  57. package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
  58. package/dist/src/cli/cli-doctor-command.js +103 -0
  59. package/dist/src/cli/cli-flag-specs.js +42 -22
  60. package/dist/src/cli/cli-init.js +36 -14
  61. package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
  62. package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
  63. package/dist/src/cli/cli-query-commands.js +2 -2
  64. package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
  65. package/dist/src/cli/cli-repo-gate.js +2 -2
  66. package/dist/src/cli/cli-role-command.js +2 -41
  67. package/dist/src/cli/cli-start-summary.js +4 -3
  68. package/dist/src/cli/cli-start-supervisor.js +190 -0
  69. package/dist/src/cli/cli-start.js +94 -244
  70. package/dist/src/cli/completion.js +85 -49
  71. package/dist/src/cli/follow-view.js +20 -2
  72. package/dist/src/cli/help.js +6 -13
  73. package/dist/src/cli.js +70 -176
  74. package/dist/src/collections.js +39 -27
  75. package/dist/src/concurrency/check-slots.js +70 -0
  76. package/dist/src/concurrency/lock.js +23 -1
  77. package/dist/src/concurrency/run-permit.js +49 -0
  78. package/dist/src/concurrency/semaphore.js +46 -250
  79. package/dist/src/config/config-editable-keys.js +4 -7
  80. package/dist/src/config/config-fallbacks.js +81 -101
  81. package/dist/src/config/config-field-checks.js +66 -37
  82. package/dist/src/config/config-keys.js +66 -40
  83. package/dist/src/config/config-live.js +4 -28
  84. package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
  85. package/dist/src/config/config-request.js +163 -102
  86. package/dist/src/config/config-schema.js +10 -12
  87. package/dist/src/config/config-selector-checks.js +1 -1
  88. package/dist/src/config/config-validation-agents.js +2 -2
  89. package/dist/src/config/config-validation-roles.js +8 -72
  90. package/dist/src/config/config-validation.js +4 -39
  91. package/dist/src/config/config-views.js +26 -50
  92. package/dist/src/config/config-write.js +128 -102
  93. package/dist/src/config/config.js +5 -14
  94. package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
  95. package/dist/src/doctor/doctor-backlog-plans.js +53 -120
  96. package/dist/src/doctor/doctor-backlog.js +16 -14
  97. package/dist/src/doctor/doctor-check-names.js +3 -6
  98. package/dist/src/doctor/doctor-checks.js +8 -8
  99. package/dist/src/doctor/doctor-claude-checks.js +70 -36
  100. package/dist/src/doctor/doctor-codex-checks.js +6 -6
  101. package/dist/src/doctor/doctor-contention-checks.js +57 -117
  102. package/dist/src/doctor/doctor-fallback-checks.js +48 -26
  103. package/dist/src/doctor/doctor-holds.js +8 -47
  104. package/dist/src/doctor/doctor-install.js +1 -1
  105. package/dist/src/doctor/doctor-launch-services.js +1 -1
  106. package/dist/src/doctor/doctor-model-checks.js +178 -94
  107. package/dist/src/doctor/doctor-notify.js +2 -2
  108. package/dist/src/doctor/doctor-opencode-checks.js +5 -4
  109. package/dist/src/doctor/doctor-orphans.js +122 -21
  110. package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
  111. package/dist/src/doctor/doctor-pi-checks.js +2 -2
  112. package/dist/src/doctor/doctor-principles.js +3 -3
  113. package/dist/src/doctor/doctor-project-checks.js +24 -11
  114. package/dist/src/doctor/doctor-role-notes.js +1 -1
  115. package/dist/src/doctor/doctor.js +8 -11
  116. package/dist/src/events/event-format.js +44 -64
  117. package/dist/src/events/event-labels.js +1 -5
  118. package/dist/src/events/event-legacy.js +3 -5
  119. package/dist/src/events/{notify.js → event-notify.js} +6 -6
  120. package/dist/src/events/event-read.js +1 -1
  121. package/dist/src/events/event-window.js +1 -1
  122. package/dist/src/events/events.js +8 -0
  123. package/dist/src/failure/failure-cluster.js +9 -18
  124. package/dist/src/failure/failure-data.js +12 -8
  125. package/dist/src/failure/failure-render.js +46 -36
  126. package/dist/src/failure/failure-state-change.js +54 -120
  127. package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
  128. package/dist/src/files/files.js +30 -0
  129. package/dist/src/files/json-object.js +8 -8
  130. package/dist/src/files/stat-cache.js +12 -8
  131. package/dist/src/gates/budget-gates.js +85 -84
  132. package/dist/src/gates/gate-polls.js +32 -74
  133. package/dist/src/gates/pause-gates.js +16 -2
  134. package/dist/src/gates/readiness.js +1 -1
  135. package/dist/src/gates/role-cap-gates.js +44 -35
  136. package/dist/src/gates/{startup-gate.js → startup-gates.js} +9 -8
  137. package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
  138. package/dist/src/git/git-diff.js +6 -2
  139. package/dist/src/git/git.js +75 -45
  140. package/dist/src/git/worktree-pool.js +3 -6
  141. package/dist/src/gui/gui-admin-endpoints.js +3 -3
  142. package/dist/src/gui/gui-args.js +12 -0
  143. package/dist/src/gui/gui-backlog-commands.js +4 -4
  144. package/dist/src/gui/gui-backlog-endpoints.js +61 -41
  145. package/dist/src/gui/gui-command.js +43 -24
  146. package/dist/src/gui/gui-config-commands.js +2 -64
  147. package/dist/src/gui/gui-endpoint-commands.js +1 -1
  148. package/dist/src/gui/gui-endpoint-common.js +19 -1
  149. package/dist/src/gui/gui-history-endpoints.js +100 -52
  150. package/dist/src/gui/gui-operator-commands.js +43 -48
  151. package/dist/src/gui/gui-prompt-commands.js +25 -25
  152. package/dist/src/gui/gui-report-endpoints.js +1 -1
  153. package/dist/src/gui/gui-server.js +1 -2
  154. package/dist/src/gui/gui-team-commands.js +2 -1
  155. package/dist/src/history/history-data.js +71 -44
  156. package/dist/src/history/history-queued.js +49 -27
  157. package/dist/src/history/history.js +54 -36
  158. package/dist/src/inbox/prompt-commands.js +59 -42
  159. package/dist/src/init/init.js +88 -40
  160. package/dist/src/merge/merge-backlog-conflicts.js +1 -1
  161. package/dist/src/merge/merge-check-failures.js +12 -9
  162. package/dist/src/merge/merge-core.js +7 -32
  163. package/dist/src/merge/merge-drain.js +11 -13
  164. package/dist/src/merge/merge-pipeline.js +6 -7
  165. package/dist/src/merge/merge-post-review-drain.js +11 -14
  166. package/dist/src/merge/merge-queue-render.js +3 -3
  167. package/dist/src/merge/merge-requeue.js +1 -1
  168. package/dist/src/merge/merge-slot.js +0 -8
  169. package/dist/src/merge/merge-stack.js +17 -18
  170. package/dist/src/merge/merge-to-main.js +13 -58
  171. package/dist/src/merge/merge-verify.js +12 -21
  172. package/dist/src/notes-cli.js +52 -42
  173. package/dist/src/operator/operator-commands.js +80 -47
  174. package/dist/src/operator/operator-intent.js +3 -3
  175. package/dist/src/orchestrator/orchestrator-launch.js +31 -63
  176. package/dist/src/orchestrator/orchestrator-poll.js +44 -69
  177. package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
  178. package/dist/src/orchestrator/orchestrator.js +8 -21
  179. package/dist/src/orchestrator/scheduling-admit.js +101 -68
  180. package/dist/src/orchestrator/scheduling-scope.js +18 -27
  181. package/dist/src/orchestrator/scheduling-skip.js +10 -14
  182. package/dist/src/orchestrator/startup-migrations.js +1 -1
  183. package/dist/src/paths.js +10 -9
  184. package/dist/src/pi/pi-args.js +5 -2
  185. package/dist/src/pi/pi-backend.js +2 -2
  186. package/dist/src/pi/pi-bin.js +3 -3
  187. package/dist/src/pi/pi-event-line.js +28 -1
  188. package/dist/src/pi/pi-listed-models.js +13 -0
  189. package/dist/src/pi/pi-models.js +43 -29
  190. package/dist/src/pi/pi-settings.js +43 -20
  191. package/dist/src/pi/pi-stream.js +41 -8
  192. package/dist/src/pi/pi-watchdogs.js +43 -15
  193. package/dist/src/pi/pi.js +2 -2
  194. package/dist/src/pi-extension/context-shake.js +32 -20
  195. package/dist/src/pi-extension/kill-guard.js +69 -0
  196. package/dist/src/process/process-group.js +51 -26
  197. package/dist/src/process/process.js +20 -12
  198. package/dist/src/process/run-marker.js +33 -15
  199. package/dist/src/process/supervisor.js +40 -28
  200. package/dist/src/prompt/dialect.js +1 -1
  201. package/dist/src/prompt/gate-notes.js +2 -2
  202. package/dist/src/prompt/gate-prompts.js +1 -44
  203. package/dist/src/prompt/notes-backlog.js +66 -37
  204. package/dist/src/prompt/principles.js +6 -4
  205. package/dist/src/prompt/prompt-followup.js +12 -4
  206. package/dist/src/prompt/prompt.js +88 -38
  207. package/dist/src/redeploy/redeployer.js +21 -1
  208. package/dist/src/report/report-data.js +53 -37
  209. package/dist/src/report/report-fold.js +16 -9
  210. package/dist/src/report/report-render.js +21 -194
  211. package/dist/src/report/report-since-data.js +4 -3
  212. package/dist/src/report/report-since-render.js +173 -0
  213. package/dist/src/report/report-usage-fold.js +8 -8
  214. package/dist/src/report/report.js +46 -22
  215. package/dist/src/review/diff-rules.js +35 -21
  216. package/dist/src/review/post-merge-review-render.js +9 -3
  217. package/dist/src/review/post-merge-review.js +48 -41
  218. package/dist/src/review/review-precheck.js +14 -4
  219. package/dist/src/review/review.js +48 -170
  220. package/dist/src/review/secret-leaks.js +26 -16
  221. package/dist/src/roles/legacy-role-migration.js +6 -3
  222. package/dist/src/roles/role-catalog.js +11 -3
  223. package/dist/src/roles/role-guidance.js +16 -12
  224. package/dist/src/roles/role-render.js +7 -46
  225. package/dist/src/roles/role-view.js +10 -33
  226. package/dist/src/run/acceptance-greps.js +88 -45
  227. package/dist/src/run/ci-parity.js +19 -8
  228. package/dist/src/run/run-apply.js +12 -15
  229. package/dist/src/run/run-detail-data.js +121 -68
  230. package/dist/src/run/run-detail-render.js +92 -35
  231. package/dist/src/run/run-detail.js +72 -41
  232. package/dist/src/run/run-prompt.js +63 -50
  233. package/dist/src/run/run-resume.js +76 -29
  234. package/dist/src/run/run-stage.js +141 -90
  235. package/dist/src/run/run-timing.js +8 -2
  236. package/dist/src/run/run-usage.js +31 -1
  237. package/dist/src/run/run-verdict.js +128 -50
  238. package/dist/src/run/stage-check.js +60 -50
  239. package/dist/src/run/stage-checks-shared.js +28 -0
  240. package/dist/src/run/stage-name-checks.js +111 -92
  241. package/dist/src/run/stage-path-checks.js +69 -42
  242. package/dist/src/run/stage-prose-checks.js +55 -40
  243. package/dist/src/runner/{leftover.js → runner-leftover.js} +9 -26
  244. package/dist/src/runner/runner-outcome.js +7 -2
  245. package/dist/src/runner/runner-pi.js +9 -22
  246. package/dist/src/runner/runner-recovery.js +5 -13
  247. package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
  248. package/dist/src/runner/runner-run.js +103 -50
  249. package/dist/src/runner/runner-state-load.js +18 -28
  250. package/dist/src/runner/runner-state.js +0 -14
  251. package/dist/src/runner/runner.js +41 -25
  252. package/dist/src/scheduling/backoff.js +8 -3
  253. package/dist/src/scheduling/claim-entries.js +2 -1
  254. package/dist/src/scheduling/claims.js +0 -4
  255. package/dist/src/scheduling/scheduling.js +53 -66
  256. package/dist/src/status/status-budget.js +4 -43
  257. package/dist/src/status/status-data.js +35 -39
  258. package/dist/src/status/status-role-notes.js +2 -2
  259. package/dist/src/status/status-roles.js +9 -18
  260. package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
  261. package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
  262. package/dist/src/team/team-hold.js +105 -70
  263. package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
  264. package/dist/src/team/team-polls.js +53 -201
  265. package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
  266. package/dist/src/team/team-state.js +4 -13
  267. package/dist/src/text/datetime.js +16 -7
  268. package/dist/src/text/markdown.js +1 -1
  269. package/dist/src/text/phrases.js +29 -15
  270. package/dist/src/text/suggest.js +1 -1
  271. package/dist/src/text/text.js +6 -5
  272. package/dist/src/ui/alert-feeds.js +46 -111
  273. package/dist/src/ui/badges.js +17 -88
  274. package/dist/src/ui/gui/gui-client-boot.js +2 -8
  275. package/dist/src/ui/gui/gui-client-composer.js +1 -1
  276. package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
  277. package/dist/src/ui/gui/gui-client-drawer.js +1 -1
  278. package/dist/src/ui/gui/gui-client-feed.js +1 -1
  279. package/dist/src/ui/gui/gui-client-history.js +28 -14
  280. package/dist/src/ui/gui/gui-client-model.js +0 -12
  281. package/dist/src/ui/gui/gui-client-operator.js +7 -5
  282. package/dist/src/ui/gui/gui-client-pending.js +7 -6
  283. package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
  284. package/dist/src/ui/gui/gui-client-report.js +1 -1
  285. package/dist/src/ui/gui/gui-client-roles.js +0 -1
  286. package/dist/src/ui/gui/gui-client-settings.js +43 -84
  287. package/dist/src/ui/gui/gui-client-team.js +12 -6
  288. package/dist/src/ui/gui/gui-client.js +7 -1
  289. package/dist/src/ui/progress-data.js +107 -44
  290. package/dist/src/ui/run-progress-model.js +20 -4
  291. package/dist/src/ui/status-model.js +2 -24
  292. package/dist/src/ui/status-payload.js +11 -30
  293. package/dist/src/ui/status-render.js +13 -24
  294. package/dist/src/ui/team-alert-backlog.js +14 -204
  295. package/dist/src/ui/team-alert-shared.js +6 -29
  296. package/dist/src/ui/team-alert-status.js +9 -170
  297. package/dist/src/ui/team-alert-tail.js +19 -215
  298. package/dist/src/ui/team-alerts.js +8 -15
  299. package/dist/src/ui/transcript-run.js +27 -16
  300. package/dist/src/ui/tui/tui-backlog.js +2 -2
  301. package/dist/src/ui/tui/tui-editor-answer.js +12 -10
  302. package/dist/src/ui/tui/tui-editor-attach.js +4 -3
  303. package/dist/src/ui/tui/tui-editor-backends.js +4 -3
  304. package/dist/src/ui/tui/tui-editor-brief.js +6 -9
  305. package/dist/src/ui/tui/tui-editor-budget.js +4 -3
  306. package/dist/src/ui/tui/tui-editor-config.js +80 -68
  307. package/dist/src/ui/tui/tui-editor-entry.js +12 -20
  308. package/dist/src/ui/tui/tui-editor-pause.js +51 -57
  309. package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
  310. package/dist/src/ui/tui/tui-editor-queued.js +11 -14
  311. package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
  312. package/dist/src/ui/tui/tui-editor-release.js +4 -3
  313. package/dist/src/ui/tui/tui-editor-retire.js +7 -7
  314. package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
  315. package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
  316. package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
  317. package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
  318. package/dist/src/ui/tui/tui-editor-search.js +4 -3
  319. package/dist/src/ui/tui/tui-editor-session.js +17 -4
  320. package/dist/src/ui/tui/tui-editors.js +5 -10
  321. package/dist/src/ui/tui/tui-frame.js +182 -134
  322. package/dist/src/ui/tui/tui-guard.js +20 -0
  323. package/dist/src/ui/tui/tui-input.js +167 -98
  324. package/dist/src/ui/tui/tui-keymap.js +51 -29
  325. package/dist/src/ui/tui/tui-keys.js +1 -1
  326. package/dist/src/ui/tui/tui-pane-activity.js +5 -4
  327. package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
  328. package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
  329. package/dist/src/ui/tui/tui-pane-health.js +252 -230
  330. package/dist/src/ui/tui/tui-pane-history.js +67 -74
  331. package/dist/src/ui/tui/tui-pane-pending.js +288 -247
  332. package/dist/src/ui/tui/tui-pane-queued.js +25 -27
  333. package/dist/src/ui/tui/tui-pane-report.js +2 -1
  334. package/dist/src/ui/tui/tui-pane-session.js +15 -10
  335. package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
  336. package/dist/src/ui/tui/tui-pane.js +14 -11
  337. package/dist/src/ui/tui/tui-panes.js +38 -33
  338. package/dist/src/ui/tui/tui.js +4 -7
  339. package/dist/src/verdict/fix-claim.js +107 -56
  340. package/dist/src/verdict/refusal.js +1 -1
  341. package/dist/src/verdict/reply-contract.js +3 -4
  342. package/package.json +5 -5
  343. package/dist/src/concurrency/provider-lease.js +0 -105
  344. package/dist/src/review/review-followup.js +0 -87
  345. package/dist/src/review/review-run.js +0 -395
  346. package/dist/src/runner/model-fallback.js +0 -76
  347. package/dist/src/runner/runner-fallback.js +0 -66
  348. package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
  349. package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
  350. /package/dist/src/cli/{brief-command.js → cli-brief-command.js} +0 -0
  351. /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
  352. /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
  353. /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
  354. /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
@@ -1,20 +1,16 @@
1
1
  /** The status/capacity and role-phase team alerts, in emission order: a spent budget
2
- * (chain/fallback/budget), per-role cap, disk, install, and main-red holds, roles failing run
3
- * after run or stuck, and a stale running build. One of the per-domain builders split out of
4
- * src/ui/team-alerts.ts; the shared types and wording helpers live in team-alert-shared.ts. */
5
- import { staleClaimDetail } from "../scheduling/claim-entries.js";
6
- import { openRoleActions, seeFailures, listRoles, listRoleIds, stuckDetail, localizeInstants, } from "./team-alert-shared.js";
2
+ * (chain/fallback/budget), disk, install, and main-red holds, roles failing run after run or
3
+ * stuck. One of the per-domain builders split out of src/ui/team-alerts.ts; the shared types
4
+ * and wording helpers live in team-alert-shared.ts. */
5
+ import { openRoleActions, seeFailures, listRoles, listRoleIds, stuckDetail, } from "./team-alert-shared.js";
7
6
  import { chainFragments } from "./badges.js";
8
7
  import { fallbackTierEntries } from "../budget/budget.js";
9
8
  import { fallbackDisplayPair } from "../config/config-views.js";
10
9
  import { agree, plural } from "../text/phrases.js";
11
- import { BRIEF_TEXT_MAX_CHARS } from "../brief/brief.js";
12
10
  import { formatGB, shortSha, usd, usdCap } from "../text/format.js";
13
11
  import { joinWithMore, squash } from "../text/text.js";
14
12
  import { roleLabel } from "../roles/role-labels.js";
15
13
  import { STALL_RE } from "./run-progress-model.js";
16
- import { ROLE_NOTES_MAX_BYTES } from "../agent/role-notes-write.js";
17
- import { PRINCIPLES_MAX_CHARS } from "../prompt/principles.js";
18
14
  function pushChainAlert(out, snap) {
19
15
  const onFallback = chainFragments(snap.chain);
20
16
  if (onFallback) {
@@ -60,23 +56,6 @@ function pushSpentBudgetAlert(out, snap) {
60
56
  });
61
57
  }
62
58
  }
63
- // A role over its maxDailyCostUsdPerRole cap (status-model.ts "waiting: daily cap spent") is
64
- // a per-role hold — one role sits idle while the rest of the team keeps working — so the team
65
- // banner names it the way the budget, disk, and quiet banners name theirs, instead of leaving
66
- // an operator to read every row. An amber ask (it needs a raise to resume); clears on its own
67
- // at local midnight when the spend window rolls over.
68
- function pushRoleCapAlert(out, snap, roles) {
69
- const capPausedRoles = roles.filter((l) => snap.capPaused.includes(l.role));
70
- if (snap.capPaused.length > 0) {
71
- out.push({
72
- key: "role-cap",
73
- tone: "amber",
74
- title: `${listRoleIds(snap.capPaused)} ${agree(snap.capPaused.length, "is over its daily cap", "are over their daily caps")}`,
75
- detail: `The daily cap (maxDailyCostUsdPerRole) holds ${listRoleIds(snap.capPaused)}: no new runs until local midnight. Raise the cap to resume ${agree(snap.capPaused.length, "it", "them")}.`,
76
- actions: openRoleActions(capPausedRoles),
77
- });
78
- }
79
- }
80
59
  // The disk floor holding new work (plans/disk-floor.md, part 4/4): the header badge and the
81
60
  // role rows say what is happening, but the banner names the threshold and the way out, so an
82
61
  // operator hitting a full volume does not have to read events. An amber ask — the team is
@@ -109,86 +88,6 @@ function pushInstallDriftAlert(out, snap) {
109
88
  });
110
89
  }
111
90
  }
112
- // No deterministic build check is declared (detectBuildCheck returned null): the review
113
- // gate's build pre-check, the red-main baseline and redeploy's green check have nothing to
114
- // run, and the header badge renders nothing (mainCheckBadge needs a build_check event).
115
- // The same state the doctor `check` row warns on, raised as a standing alert on the always-on
116
- // surfaces. Independent of team state (running or stopped), like the install alert beside it.
117
- function pushNoCheckAlert(out, snap) {
118
- if (snap.noDeclaredCheck) {
119
- out.push({
120
- key: "no-check",
121
- tone: "amber",
122
- title: "No project check is declared — the safety gates are off",
123
- detail: "The review gate's build pre-check, the red-main baseline and redeploy's green check have nothing to run. The plan role will plan this project's check and the feature role declare it — a test/typecheck/build npm script, or check.command. To skip the wait, declare it yourself; a project with nothing to verify yet stays as it is.",
124
- actions: [],
125
- });
126
- }
127
- }
128
- // A brief candidate exists but none resolves to a usable prompt block (briefBlockMissing):
129
- // every run reaches its model with no project prompt, so the team burns tokens producing
130
- // work unrelated to the project. The same marker-less/unreadable/empty-block state the doctor
131
- // `brief` row warns on, raised as a standing alert on the always-on surfaces (the bare
132
- // pre-init repo stays silent). Independent of team state, like the no-check alert beside it.
133
- function pushNoBriefAlert(out, snap) {
134
- if (snap.noBrief) {
135
- out.push({
136
- key: "no-brief",
137
- tone: "amber",
138
- title: "The project brief's prompt block is missing or empty — every role runs without its project prompt",
139
- detail: "A brief file exists but carries no usable tumwater:prompt block (no markers, or no text between them), so each run's prompt reaches the model with no project brief. Restore the block in the file the doctor `brief` row names, or re-run `tumwater init`.",
140
- actions: [],
141
- });
142
- }
143
- }
144
- // A brief block past the read cap (briefBlockOverCap): every run's prompt silently carries
145
- // only the head plus a truncation marker, so each role plans and codes against a cut brief.
146
- // The same over-cap state the doctor `brief` row warns on, raised as a standing alert on the
147
- // always-on surfaces; mutually exclusive with `no-brief` (an over-cap block is non-empty).
148
- function pushBriefTruncatedAlert(out, snap) {
149
- if (snap.briefTruncated) {
150
- out.push({
151
- key: "brief-truncated",
152
- tone: "amber",
153
- title: "The project brief is over its prompt cap — every role runs on a truncated brief",
154
- detail: "The brief block exceeds the read cap, so each run's prompt carries only its head plus a truncation marker. Shorten the block to at most " + BRIEF_TEXT_MAX_CHARS + " characters in the file the doctor `brief` row names (a hand edit, or the dashboard's Brief card), then save it.",
155
- actions: [],
156
- });
157
- }
158
- }
159
- // A role note past the read bound (collectRoleNotesOverCap's predicate, the doctor `role
160
- // notes` row's own): `readRoleNote` truncates that role's note on read, so the role's next
161
- // prompt carries only the note's head plus a truncation marker while the operator sees the
162
- // file whole — a silently degraded next run on a green-looking status. The standing twin of
163
- // the doctor `role notes` row's warn, raised as an amber alert on the always-on surfaces.
164
- // Independent of team state, like the no-brief alert beside it.
165
- function pushRoleNotesTruncatedAlert(out, snap) {
166
- const notesOver = snap.roleNotesOverCap ?? [];
167
- if (notesOver.length > 0) {
168
- out.push({
169
- key: "role-notes-truncated",
170
- tone: "amber",
171
- title: `${plural(notesOver.length, "role note is", "role notes are")} over the read bound — those roles run on truncated notes`,
172
- detail: `${joinWithMore(notesOver.map((o) => `${o.role} (${o.chars})`), 3)} exceed the ${ROLE_NOTES_MAX_BYTES}-character read bound, so each of those roles' prompt carries only the note's head plus a truncation marker. Rewrite them under the cap (the run's \`tumwater-notes write\` shim, \`tumwater role <id> --notes-file <path>\`, the dashboard drawer, or the TUI notes editor).`,
173
- actions: [],
174
- });
175
- }
176
- }
177
- // PRINCIPLES.md is over its prompt cap (principlesBlockOverCap): every role's <principles>
178
- // block carries only its head plus a truncation marker, so the team runs on truncated design
179
- // guidance. The same over-cap state the doctor `principles` row warns on, raised as a standing
180
- // alert on the always-on surfaces. Independent of team state, like the no-brief alert beside it.
181
- function pushPrinciplesTruncatedAlert(out, snap) {
182
- if (snap.principlesTruncated) {
183
- out.push({
184
- key: "principles-truncated",
185
- tone: "amber",
186
- title: "PRINCIPLES.md is over its prompt cap — every role runs on truncated design guidance",
187
- detail: "The file exceeds the " + PRINCIPLES_MAX_CHARS + "-character prompt cap, so every run's <principles> block carries only its head plus a truncation marker and only the leading bullets survive. Keep PRINCIPLES.md to its list bullets and move long-form rationale into docs/, which is not injected.",
188
- actions: [],
189
- });
190
- }
191
- }
192
91
  function pushMainRedAlert(out, snap, roles) {
193
92
  const red = roles.filter((l) => l.phase === "main's check is failing");
194
93
  if (red.length) {
@@ -246,81 +145,21 @@ function pushStuckRoleAlert(out, roles) {
246
145
  });
247
146
  }
248
147
  }
249
- function pushBuildAlert(out, snap) {
250
- if (snap.build?.stale) {
251
- const behind = plural(snap.build.aheadCommits ?? 0, "commit");
252
- // The cooldown scopes the deferral to the redeploy, not the team (PLANS.md 2026-10-23): a
253
- // restart held by the cooldown leaves the roles running on the stale build, so its alert
254
- // reads blue "work continues" with the deadline, while a genuinely blocked restart (red main,
255
- // a failed build, a boot refusal) stays the amber "restart blocked", and the momentary drain
256
- // of an in-flight restart — the one stale state that actually holds new runs and merges —
257
- // keeps the plain wording.
258
- const deferred = snap.build.restartBlocked?.startsWith("cooldown until ");
259
- out.push(deferred
260
- ? {
261
- key: "build",
262
- tone: "blue",
263
- title: `Update available (${behind} behind) — restarts after cooldown; work continues`,
264
- detail: `Work continues on the running build; the restart lands when the cooldown lapses or you force it (${localizeInstants(snap.build.restartBlocked ?? "")}).`,
265
- actions: [],
266
- }
267
- : snap.build.restartBlocked
268
- ? {
269
- key: "build",
270
- tone: "amber",
271
- title: `Update available (${behind} behind) — restart blocked`,
272
- detail: localizeInstants(snap.build.restartBlocked),
273
- actions: [],
274
- }
275
- : {
276
- key: "build",
277
- tone: "blue",
278
- title: `Update available (${behind} behind)${snap.build.restartPending ? " — restarts when idle" : ""}`,
279
- detail: snap.build.restartPending ? "The team restarts onto it once running work finishes." : "Restart tumwater start to pick it up.",
280
- actions: [],
281
- });
282
- }
283
- }
284
- // A work-instance claim the operator should act on without running `tumwater doctor`
285
- // (src/scheduling/claim-entries.ts, the doctor's `parallel instances` row): a stopped
286
- // instance holding an entry that left its role's section, or idle past the scheduler's
287
- // release window, is an amber ask. One detail line per claim, worded like the doctor row so
288
- // the two cannot drift; actions open the first three claimed roles. Nothing when empty.
289
- function pushStaleClaimsAlert(out, staleClaims) {
290
- if (staleClaims.length > 0) {
291
- out.push({
292
- key: "stale-claims",
293
- tone: "amber",
294
- title: `${plural(staleClaims.length, "work-instance claim is", "work-instance claims are")} stale`,
295
- detail: staleClaims.map(staleClaimDetail).join("; "),
296
- actions: openRoleActions(staleClaims.map((c) => ({ role: c.id }))),
297
- });
298
- }
299
- }
300
148
  /** The status/capacity and role-phase alerts, in emission order: a spent budget
301
- * (chain/fallback/budget), per-role cap, disk, install, and main-red holds, roles
302
- * failing run after run or stuck, and a stale running build. Extracted from
303
- * teamAlerts (part 1/5 of the teamAlerts split) so the shared module stays small;
304
- * the input names exactly the fields this run reads. The caller pushes the returned
305
- * alerts at the position this run used to occupy, so the emitted key order is
149
+ * (chain/fallback/budget), disk, install, and main-red holds, roles failing run after run or
150
+ * stuck. Extracted from teamAlerts (part 1/5 of the teamAlerts split) so the shared module
151
+ * stays small; the input names exactly the fields this run reads. The caller pushes the
152
+ * returned alerts at the position this run used to occupy, so the emitted key order is
306
153
  * unchanged. */
307
154
  export function buildStatusAlerts(input) {
308
- const { snap, roles, staleClaims } = input;
155
+ const { snap, roles } = input;
309
156
  const out = [];
310
157
  pushChainAlert(out, snap);
311
158
  pushSpentBudgetAlert(out, snap);
312
- pushRoleCapAlert(out, snap, roles);
313
159
  pushDiskAlert(out, snap);
314
160
  pushInstallDriftAlert(out, snap);
315
- pushNoCheckAlert(out, snap);
316
- pushNoBriefAlert(out, snap);
317
- pushBriefTruncatedAlert(out, snap);
318
- pushRoleNotesTruncatedAlert(out, snap);
319
- pushPrinciplesTruncatedAlert(out, snap);
320
161
  pushMainRedAlert(out, snap, roles);
321
162
  pushFailingStreakAlert(out, roles);
322
163
  pushStuckRoleAlert(out, roles);
323
- pushBuildAlert(out, snap);
324
- pushStaleClaimsAlert(out, staleClaims);
325
164
  return out;
326
165
  }
@@ -1,173 +1,6 @@
1
- /** The tail team alerts, in emission order: a run of quiet hours holding the team, a maintenance
2
- * allowance held at its rolling cap, a standing provider failure hold, an operator pause with
3
- * its countdown, the last day's capacity contention, a stale workspace pin, a fixed test that
4
- * regressed, a stopped tumwater, and roles that cannot or may not read images. One of the
5
- * per-domain builders split out of src/ui/team-alerts.ts; the shared types and wording helpers
6
- * live in team-alert-shared.ts. */
7
- import { listRoleIds, listRoles, openRoleActions, seeFailures } from "./team-alert-shared.js";
8
- import { stalePinPhrase } from "../doctor/doctor-contention-checks.js";
9
- import { quietWindowEnd } from "../scheduling/quiet-hours.js";
10
- import { pauseCountdown } from "./badges.js";
11
- import { agree, pauseReasonSuffix, plural } from "../text/phrases.js";
12
- import { humanUntil } from "../text/datetime.js";
13
- import { squash } from "../text/text.js";
14
- function pushQuietAlert(out, snap) {
15
- // Quiet hours (plans: "Quiet hours … part 2/2, observability") informs rather than asks:
16
- // the team holding to a schedule is not a problem, but an operator watching idle roles
17
- // inside the window needs the same explanation the pause alert gives a paused team —
18
- // why nothing runs, and when it starts again. The window end comes from quiet-hours.ts's
19
- // quietWindowEnd, the same helper quietBadge uses for the header.
20
- if (snap.inQuietHours && snap.quietHours) {
21
- const end = quietWindowEnd(snap.quietHours);
22
- out.push({
23
- key: "quiet",
24
- tone: "blue",
25
- title: `Quiet hours — roles start no new runs until ${end}`,
26
- detail: `The schedule (${snap.quietHours} local time, quietHours in tumwater.json) holds them. Runs in progress finish, and the director still runs your prompts.`,
27
- actions: [],
28
- });
29
- }
30
- }
31
- function pushMaintenanceAlert(out, snap) {
32
- // The maintenance allowance (plans/work-ratio.md, part 1b/4): by design and self-clearing —
33
- // the scheduler holds maintenance roles at the rolling 24 h allowance until merges free room,
34
- // then they run again on their own — so it informs (gray, no actions), the quiet alert's
35
- // tone, rather than amber like the failed/refused asks. An operator watching idle maintenance
36
- // roles gets the same explanation the hold's own row carries, team-wide.
37
- if (snap.maintenanceQuota.held.length > 0) {
38
- const mq = snap.maintenanceQuota;
39
- out.push({
40
- key: "maintenance",
41
- tone: "gray",
42
- title: "Maintenance is held at its rolling allowance",
43
- detail: `${mq.used} of ${mq.allowance} maintenance allowance used over the last 24 h; ${listRoleIds(mq.held)} ${agree(mq.held.length, "resumes", "resume")} as work merges.`,
44
- actions: [],
45
- });
46
- }
47
- }
48
- function pushProviderHoldAlert(out, snap, now) {
49
- // A standing provider failure hold (src/team/team-hold.ts `standingHolds`, published through
50
- // the orchestrator info file): a provider storm holds roles on that provider, so an operator
51
- // watching idle roles with no log open sees why nothing new starts there. Self-clearing by
52
- // design — the hold re-opens at its own deadline — so it informs (blue, no actions), the
53
- // quiet and maintenance alerts' tone, rather than asking. The snapshot carries the holds only
54
- // while a live orchestrator publishes one.
55
- if (snap.providerHolds && snap.providerHolds.length > 0) {
56
- const phrased = snap.providerHolds
57
- .map((h) => `${h.provider ?? "pi's default backend"} (${h.kind}) until it re-opens in ${humanUntil(h.until, now)}`)
58
- .join("; ");
59
- out.push({
60
- key: "provider-hold",
61
- tone: "blue",
62
- title: agree(snap.providerHolds.length, "Provider limit is holding new runs", "Provider limits are holding new runs"),
63
- detail: `${phrased}. Runs in progress finish; roles on a held provider start no new runs until it re-opens.`,
64
- actions: [],
65
- });
66
- }
67
- }
68
- function pushProviderCapsAlert(out, providerCaps) {
69
- // A `maxConcurrentPerProvider` map that cannot do its job — the always-on twin of the doctor's
70
- // `provider concurrency` row (doctor-contention-checks.ts checkProviderConcurrency): a cap key
71
- // no seam resolves to, a cap at or above maxConcurrent, or a keyless seam in a capped team.
72
- // The provider-hold and contention alerts cover a cap that is merely too small; this one
73
- // names the static misconfiguration itself, which the doctor row is the only other surface
74
- // that exposes. alert-feeds.ts's providerCapProblems feeds it the row's own warning strings,
75
- // so the alert and the row cannot drift on the trigger or the wording.
76
- if (providerCaps.length > 0) {
77
- out.push({
78
- key: "provider-caps",
79
- tone: "amber",
80
- title: "maxConcurrentPerProvider is misconfigured",
81
- detail: `${providerCaps.join("; ")} — fix the map in tumwater.json (each key must name a provider the team runs on, and each cap must sit below maxConcurrent); the provider-hold and contention alerts cover a cap that is merely too small.`,
82
- actions: [],
83
- });
84
- }
85
- }
86
- function pushPausedAlert(out, snap, now) {
87
- if (snap.paused) {
88
- const left = pauseCountdown(snap.pausedUntil, now);
89
- out.push({
90
- key: "paused",
91
- tone: "amber",
92
- // The operator's pause reason (`pause --reason <text>`) states verbatim, so an
93
- // operator returning to a paused team knows why without digging through history.
94
- title: left
95
- ? `The team is paused and resumes in ${left}${pauseReasonSuffix(snap.pauseReason)}`
96
- : `The team is paused${pauseReasonSuffix(snap.pauseReason)}`,
97
- detail: "Roles start no new runs. Runs in progress finish, and the director still runs your prompts.",
98
- actions: [{ label: "Resume now", act: "resume" }],
99
- });
100
- }
101
- }
102
- function pushContentionAlert(out, contention) {
103
- // Last day's capacity contention — the always-on twin of the doctor's `workspaces`,
104
- // `provider contention` and `check contention` rows (doctor-contention-checks.ts). The
105
- // instantaneous badges show only current waits, and the 24 h total is the tuning signal that
106
- // says a cap is too small; alert-feeds.ts's capacityContention feeds it from the same
107
- // `*_wait` events and ten-minute thresholds the doctor sums, so the alert and the doctor
108
- // cannot drift on the trigger or the knob-raising advice.
109
- if (contention.length > 0) {
110
- out.push({
111
- key: "contention",
112
- tone: "amber",
113
- title: plural(contention.length, "capacity bottleneck in the last day", "capacity bottlenecks in the last day"),
114
- detail: contention.map((c) => `${c.name}: ${squash(c.detail, 200)}`).join(" · "),
115
- actions: [],
116
- });
117
- }
118
- }
119
- function pushWorkspacePinsAlert(out, workspacePins) {
120
- // A stale workspace pin — the always-on twin of the doctor's `worktree pool` row's stuck-pin
121
- // clause (doctor-contention-checks.ts checkWorktreePool): a slot pinned past a day by a run
122
- // that never resumed silently shrinks the pool, and no other observer surface names it until
123
- // an operator runs `tumwater doctor`. alert-feeds.ts feeds it the same staleWorkspacePins
124
- // predicate the doctor's row uses, so the alert and the row cannot drift on what is stale;
125
- // the "Open <role>" buttons reach the role drawer like the other role alerts.
126
- if (workspacePins.length > 0) {
127
- out.push({
128
- key: "workspace-pins",
129
- tone: "amber",
130
- title: plural(workspacePins.length, "workspace pinned past a day", "workspaces pinned past a day"),
131
- detail: `${workspacePins
132
- .map(stalePinPhrase)
133
- .join("; ")} — a pinned workspace is held out of the pool; resume the role, or retire its leftovers and run \`tumwater doctor\``,
134
- actions: openRoleActions(workspacePins),
135
- });
136
- }
137
- }
138
- function pushInstancePoolAlert(out, instancePool) {
139
- // An enabled multi-instance role sharing a workspace pool smaller than the run cap — the
140
- // always-on twin of the doctor `parallel instances` row's pool-mismatch clause
141
- // (doctor-contention-checks.ts smallPoolProblems): instances then contend for the same
142
- // pooled checkouts, unseen until an operator runs `tumwater doctor`. alert-feeds.ts feeds
143
- // it the same smallPoolProblems selector the row uses, so the alert and the row cannot
144
- // drift on the trigger, the role list, or the wording.
145
- if (instancePool.length > 0) {
146
- out.push({
147
- key: "instance-pool",
148
- tone: "amber",
149
- title: "Parallel instances share a workspace pool smaller than the run cap",
150
- detail: `${instancePool.join("; ")} — runs wait for a pooled checkout; raise worktreeSlots to at least maxConcurrent, or lower maxConcurrent.`,
151
- actions: [],
152
- });
153
- }
154
- }
155
- function pushRegressedFixesAlert(out, regressedFixes) {
156
- // A Fixed record whose quoted test failed again in the trailing day — the always-on twin of
157
- // the doctor's `regressed fixes` row (doctor-backlog-bugs.ts checkRegressedFixes).
158
- // alert-feeds.ts's regressedFixesAlert feeds it the same candidate selection, failureMentions
159
- // match and verbatim sentence, so the alert and the doctor cannot drift on the trigger or the
160
- // wording; the Failures view shows the naming events.
161
- if (regressedFixes) {
162
- out.push({
163
- key: "regressed-fixes",
164
- tone: "amber",
165
- title: plural(regressedFixes.count, "Fixed test has regressed", "Fixed tests have regressed"),
166
- detail: regressedFixes.detail,
167
- actions: seeFailures(),
168
- });
169
- }
170
- }
1
+ /** The tail team alerts: a stopped tumwater. One of the per-domain builders split out of
2
+ * src/ui/team-alerts.ts; the shared types and wording helpers live in team-alert-shared.ts. */
3
+ import { plural } from "../text/phrases.js";
171
4
  function pushStoppedAlert(out, snap) {
172
5
  if (!snap.running) {
173
6
  out.push({
@@ -179,60 +12,31 @@ function pushStoppedAlert(out, snap) {
179
12
  });
180
13
  }
181
14
  }
182
- function pushImagesAlert(out, roles) {
183
- // Image input (plans/image-input.md "Surfaces", part 5/5): a role whose model cannot read
184
- // images. Blue when every text-only role has a reroute target (a run that reads an image
185
- // stops and reruns — nothing fails); amber when some text-only role has none (a blind read
186
- // fails and image submissions are refused). No alert
187
- // when every enabled role reads images (imageInput true or unknown — an unknown seam is not
188
- // counted as text-only, mirroring the submit gate and the doctor's `tier models` stance).
189
- const textOnly = roles.filter((l) => l.imageInput === false);
190
- const noTarget = textOnly.filter((l) => l.imageReroute == null || l.imageReroute === "");
191
- if (noTarget.length > 0) {
15
+ function pushOrphansAlert(out, orphans) {
16
+ // Orphaned worktree processes — the always-on twin of the doctor's `orphans` row
17
+ // (doctor-orphans.ts checkOrphans): a detected orphan holds no lock, writes no event and
18
+ // touches no state file, so nothing else on the always-on surfaces names it until an operator
19
+ // runs `tumwater doctor`, and it then burns CPU for days unnoticed. alert-feeds.ts's
20
+ // getOrphanAlert feeds it the doctor's own orphanFailDetail sentence and count, so the alert
21
+ // and the row cannot drift on the trigger, the cpu, or the remediation.
22
+ if (orphans) {
192
23
  out.push({
193
- key: "images",
24
+ key: "orphans",
194
25
  tone: "amber",
195
- title: `No configured model can read images for ${listRoles(noTarget)}`,
196
- detail: "A run that reads an image fails, and image attachments to these roles are refused. Declare image input on a vision model in pi's models.json, or point a tier at one.",
197
- actions: openRoleActions(noTarget),
198
- });
199
- }
200
- else if (textOnly.length > 0) {
201
- const targets = [...new Set(textOnly.map((l) => l.imageReroute ?? ""))].filter(Boolean);
202
- out.push({
203
- key: "images",
204
- tone: "blue",
205
- title: `${listRoles(textOnly)} ${agree(textOnly.length, "runs", "run")} on models that can't read images`,
206
- detail: `A run that reads an image stops and reruns on ${targets.join(", ")}.`,
26
+ title: `${plural(orphans.count, "orphaned process", "orphaned processes")} are burning CPU (PPID 1)`,
27
+ detail: orphans.detail,
207
28
  actions: [],
208
29
  });
209
30
  }
210
31
  }
211
- /** The quiet-hours, maintenance, provider-hold, pause, capacity-contention, provider-cap,
212
- * instance-pool, stale-workspace-pin, regressed-fix, stopped and image-input alerts, in
213
- * emission order: a run of quiet hours
214
- * holding the team, a
215
- * maintenance allowance held at its rolling cap, a standing provider failure hold, an operator
216
- * pause with its countdown, the last day's capacity contention, a stale workspace pin, a
217
- * fixed test that regressed, a
218
- * stopped tumwater, and roles that cannot or may not read images. Extracted from teamAlerts
32
+ /** The stopped-tumwater alert, in emission order: a stopped team. Extracted from teamAlerts
219
33
  * (part 5/5 of the teamAlerts split) so the shared module stays small; the input names exactly
220
- * the fields this run reads, including `now`, which the pause countdown and provider-hold
221
- * re-open time need. The caller pushes the returned alerts at the position this run used to
222
- * occupy, so the emitted key order is unchanged. */
34
+ * the fields this run reads. The caller pushes the returned alerts at the position this run
35
+ * used to occupy, so the emitted key order is unchanged. */
223
36
  export function buildTailAlerts(input) {
224
37
  const out = [];
225
- const { snap, now, contention, regressedFixes, roles, workspacePins, instancePool, providerCaps } = input;
226
- pushQuietAlert(out, snap);
227
- pushMaintenanceAlert(out, snap);
228
- pushProviderHoldAlert(out, snap, now);
229
- pushProviderCapsAlert(out, providerCaps);
230
- pushPausedAlert(out, snap, now);
231
- pushContentionAlert(out, contention);
232
- pushWorkspacePinsAlert(out, workspacePins);
233
- pushInstancePoolAlert(out, instancePool);
234
- pushRegressedFixesAlert(out, regressedFixes);
38
+ const { snap, orphans } = input;
39
+ pushOrphansAlert(out, orphans);
235
40
  pushStoppedAlert(out, snap);
236
- pushImagesAlert(out, roles);
237
41
  return out;
238
42
  }
@@ -15,22 +15,15 @@ import { buildStatusAlerts } from "./team-alert-status.js";
15
15
  import { buildBacklogAlerts } from "./team-alert-backlog.js";
16
16
  import { buildTailAlerts } from "./team-alert-tail.js";
17
17
  /** Everything about the team that needs the operator, most urgent first: a spent budget, a red
18
- * main, roles failing run after run, roles that look stuck, a stale running build, open
19
- * questions, refused entries, a plan stalled by a Needs
20
- * review note, the team
21
- * waiting on capacity slots (workspace, provider or
22
- * check), parallel instances sharing a workspace pool smaller than the run cap, a plan
23
- * filed under the wrong backlog section, a duplicated
24
- * backlog section heading, finished history still under a live backlog file's `## Done` or
25
- * `## Fixed`, a Fixed record with a bad Validation gap trace, a pause, a
26
- * stopped team.
27
- * `now` is passed so a timed
28
- * pause's countdown agrees with the rest of the frame. */
29
- export function teamAlerts(snap, questions, roles, now, refused = [], planDependency = null, stalledEntries = [], strandedPlans = [], duplicatedHeadings = [], validationGaps = [], contention = [], regressedFixes = null, staleClaims = [], staleQuestions = [], fixClaims = [], workspacePins = [], liveFinished = [], instancePool = [], providerCaps = []) {
18
+ * main, roles failing run after run, roles that look stuck, disk and install drift, open
19
+ * questions, refused entries, the team waiting on capacity slots (workspace, provider or
20
+ * check), and a stopped team.
21
+ */
22
+ export function teamAlerts(snap, questions, roles, _now, refused = [], orphans = null) {
30
23
  const out = [];
31
- out.push(...buildStatusAlerts({ snap, roles, staleClaims }));
32
- out.push(...buildBacklogAlerts({ questions, staleQuestions, refused, stalledEntries, planDependency, strandedPlans, duplicatedHeadings, liveFinished, validationGaps, fixClaims }));
33
- out.push(...buildTailAlerts({ snap, now, contention, regressedFixes, roles, workspacePins, instancePool, providerCaps }));
24
+ out.push(...buildStatusAlerts({ snap, roles }));
25
+ out.push(...buildBacklogAlerts({ questions, refused }));
26
+ out.push(...buildTailAlerts({ snap, orphans }));
34
27
  return out;
35
28
  }
36
29
  /** Does this alert ask something of the operator (red, amber, indigo), rather than inform? */
@@ -8,6 +8,31 @@ import { parsePiEventLine } from "../pi/pi-event-line.js";
8
8
  * renderer stamps its separator from). A segment with neither is skipped. */
9
9
  const MARKER_ONLY = new Set(["tumwater_run"]);
10
10
  const MESSAGE_ONLY = new Set(["message_end"]);
11
+ function segmentsInText(text) {
12
+ const segments = [];
13
+ for (const line of text.split("\n")) {
14
+ if (line === "")
15
+ continue;
16
+ if (line.includes('"tumwater_run"') && parsePiEventLine(line, MARKER_ONLY))
17
+ segments.push([line]);
18
+ else
19
+ segments.at(-1)?.push(line);
20
+ }
21
+ return segments;
22
+ }
23
+ function keepInWindow(segments, fromTs, toTs) {
24
+ const kept = [];
25
+ for (const seg of segments) {
26
+ const start = segmentStart(seg);
27
+ if (start !== null && start >= fromTs && start <= (toTs ?? Infinity))
28
+ kept.push(...seg);
29
+ }
30
+ return kept;
31
+ }
32
+ function applyTranscriptGrep(entries, grep) {
33
+ const keep = createTranscriptFilter(grep);
34
+ return entries.flatMap((e) => keep(e) ?? []);
35
+ }
11
36
  function segmentStart(lines) {
12
37
  const marker = parsePiEventLine(lines[0], MARKER_ONLY);
13
38
  if (typeof marker?.ts === "number")
@@ -35,24 +60,10 @@ export function readRunTranscript(file, fromTs, toTs, opts = {}) {
35
60
  catch {
36
61
  continue;
37
62
  }
38
- const segments = [];
39
- for (const line of text.split("\n")) {
40
- if (line === "")
41
- continue;
42
- if (line.includes('"tumwater_run"') && parsePiEventLine(line, MARKER_ONLY))
43
- segments.push([line]);
44
- else
45
- segments.at(-1)?.push(line);
46
- }
47
- for (const seg of segments) {
48
- const start = segmentStart(seg);
49
- if (start !== null && start >= fromTs && start <= (toTs ?? Infinity))
50
- kept.push(...seg);
51
- }
63
+ kept.push(...keepInWindow(segmentsInText(text), fromTs, toTs));
52
64
  }
53
65
  const entries = formatTranscript(kept, opts);
54
66
  if (opts.grep === undefined)
55
67
  return entries.flat();
56
- const keep = createTranscriptFilter(opts.grep);
57
- return entries.flatMap((e) => keep(e) ?? []);
68
+ return applyTranscriptGrep(entries, opts.grep);
58
69
  }
@@ -15,9 +15,9 @@ import { DASHBOARD_ANSWERED_QUESTIONS, DASHBOARD_RECENT_FINISHED } from "../stat
15
15
  * — the one definition shared by the keypress handlers and the render (src/ui/tui/tui-keys.ts,
16
16
  * src/ui/tui/tui-pane.ts), so stale-selection clamping cannot drift. */
17
17
  export function labeledBacklogEntries(root) {
18
- const marks = backlogMarks(root);
19
- const holds = backlogHolds(root);
20
18
  const runners = backlogInProgress(root);
19
+ const marks = backlogMarks(root, runners);
20
+ const holds = backlogHolds(root);
21
21
  return [
22
22
  ...plannedPlanEntries(root).map((e, i) => ({
23
23
  label: "plan",
@@ -1,12 +1,14 @@
1
- import { questionAsker, tryAnswerQuestion, UNCLOSED_FENCE_MESSAGE, } from "../../backlog/question-answer.js";
1
+ import { questionAsker, tryAnswerQuestion, UNCLOSED_FENCE_MESSAGE, } from "../../backlog/backlog-question-answer.js";
2
2
  import { wakeQuestionAsker } from "../../operator/operator-intent.js";
3
+ import { savedPromptDraft } from "./tui-editor-session.js";
4
+ import { guardError } from "./tui-guard.js";
3
5
  /** The backlog pane's answer editor (Ctrl+A on a selected open question) on the shared prompt
4
6
  * line. It owns its target (`answerFor`), its saved-draft pair, its exit helper, and the
5
7
  * tryAnswerQuestion / wake arms that record the decision. Mutually exclusive with the other
6
8
  * editors via the session's held registry. */
7
9
  export function createAnswerEditor(session) {
8
10
  let answerFor = null;
9
- let restoreDraft = () => { };
11
+ const draft = savedPromptDraft(session);
10
12
  // Leave the backlog pane's answer editor (Esc, a recorded answer, or Ctrl+T): restore the
11
13
  // saved prompt text and reset the recall state, mirroring exitQueuedEdit.
12
14
  const exitAnswerMode = () => {
@@ -14,7 +16,7 @@ export function createAnswerEditor(session) {
14
16
  return;
15
17
  answerFor = null;
16
18
  session.held.answer = null;
17
- restoreDraft();
19
+ draft.restore();
18
20
  };
19
21
  // Ctrl+A opens the answer editor on the selected open question (mnemonic: **A**nswer);
20
22
  // Enter records the decision through tryAnswerQuestion, the core `tumwater questions
@@ -23,26 +25,26 @@ export function createAnswerEditor(session) {
23
25
  const answerOpenEdit = (ctx) => {
24
26
  if (!session.anyEditorHeld() &&
25
27
  ctx.view === ctx.roleIds.length + 1 && ctx.selectedEntry !== null) {
26
- try {
28
+ const selected = ctx.selectedEntry;
29
+ if (selected === null)
30
+ return false;
31
+ guardError(session, () => {
27
32
  const entries = session.flat();
28
- const sel = Math.min(ctx.selectedEntry, entries.length - 1);
33
+ const sel = Math.min(selected, entries.length - 1);
29
34
  const entry = entries[sel];
30
35
  if (entry?.label !== "question") {
31
36
  session.flashMessage("only an open question can be answered here");
32
37
  }
33
38
  else {
34
39
  const position = sel - entries.filter((e) => e.label === "plan" || e.label === "bug").length + 1;
35
- restoreDraft = session.takeDraft();
40
+ draft.save();
36
41
  answerFor = { position, title: entry.title };
37
42
  session.held.answer = answerFor;
38
43
  session.input = "";
39
44
  session.cursor = 0;
40
45
  session.flashMessage(`answering question #${position} — Enter to record, Esc to cancel`);
41
46
  }
42
- }
43
- catch (err) {
44
- session.flashError(err);
45
- }
47
+ });
46
48
  return true;
47
49
  }
48
50
  return false;