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
@@ -1,63 +1,67 @@
1
+ // vocabulary: plain
1
2
  import crypto from "node:crypto";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
- import { isFleetPaused } from "../fleet/fleet-state.js";
5
- import { orchestratorAlive, readOrchestratorInfo } from "../fleet/orchestrator-info.js";
5
+ import { cancelScheduledStop, isTeamPaused, scheduleStop } from "../team/team-state.js";
6
+ import { orchestratorAlive, readOrchestratorInfo } from "../team/orchestrator-info.js";
6
7
  import { durationLabel } from "../cli/cli-args.js";
7
- import { formatDate, formatTime, humanSeconds, secondsUntil } from "../text/datetime.js";
8
+ import { formatDate, formatTime, humanUntil } from "../text/datetime.js";
8
9
  import { submitRolePrompt } from "../inbox/inbox-submit.js";
10
+ import { questionAsker } from "../backlog/question-answer.js";
11
+ import { knownRoleIdsCached } from "../config/config.js";
9
12
  import { DIRECTOR_ROLE } from "../roles/roles.js";
10
13
  import { loadLoopState, saveLoopState, zeroCounters } from "../loop/loop-state.js";
11
14
  import { clearBackoff } from "../scheduling/backoff.js";
12
15
  import { writeJsonAtomic } from "../files/json-files.js";
13
- import { reclaimPass } from "../fleet/reclaim.js";
16
+ import { reclaimPass } from "../team/reclaim.js";
14
17
  import { formatGB } from "../text/format.js";
18
+ import { plural } from "../text/phrases.js";
15
19
  import { cancelScheduledWakes, pendingScheduledWakes } from "./operator-requests.js";
16
20
  import { abortRequestPath, reclaimRequestPath, resetRequestPath, restartRequestPath, scheduledWakeDir, wakeRequestPath, } from "../paths.js";
17
21
  /** The marker-writing cores of the operator-intent protocol, shared by every surface that writes
18
22
  * one (the `cmd*` CLI commands in src/operator/operator-commands.ts, the dashboard's POST routes
19
23
  * in src/gui/gui-endpoint-commands.ts, and the TUI's key bindings in src/ui/tui/tui-keys.ts): each
20
24
  * core returns the confirmation its caller prints verbatim, so the surfaces cannot drift on marker
21
- * format, idempotence, or wording. The fleet-side consumer half is
25
+ * format, idempotence, or wording. The team-side consumer half is
22
26
  * src/operator/operator-requests.ts; this module holds the producer half's shared core, split out
23
27
  * of operator/operator-commands.ts so the UI layer never depends on the CLI's arg parsing. */
24
28
  /** The error every live-harness-only command reports (abort, stop): one literal so the two
25
29
  * surfaces cannot drift on the message an operator sees when nothing is running. */
26
- export const NO_HARNESS_ERROR = "no harness is running — start it with `tumwater run` first";
30
+ export const NO_HARNESS_ERROR = "tumwater is not running — start it with `tumwater start` first";
27
31
  /** How a marker command reports when its marker takes effect, derived from one liveness
28
- * check: `live` says whether a fleet will consume it now, `when` is the " within ~2s" such a
29
- * fleet picks it up in, and `tail` names the fallback ("takes effect on the next `tumwater
30
- * run`") while no harness is running. Every marker command shares this so none can promise a
32
+ * check: `live` says whether a team will consume it now, `when` is the " within ~2s" such a
33
+ * team picks it up in, and `tail` names the fallback ("takes effect on the next `tumwater
34
+ * run`") while tumwater is not running. Every marker command shares this so none can promise a
31
35
  * timing the others deny — the one place that user-facing contract lives. */
32
36
  export function markerApplyNote(root) {
33
37
  const live = orchestratorAlive(root);
34
38
  return {
35
39
  live,
36
40
  when: live ? " within ~2s" : "",
37
- tail: live ? "" : "; no harness is running, so it takes effect on the next `tumwater run`",
41
+ tail: live ? "" : "; tumwater is not running, so it takes effect on the next `tumwater start`",
38
42
  };
39
43
  }
40
- /** The trailing liveness clause of a one-shot marker command's confirmation (reset-counters, wake,
41
- * and restart): with a live fleet, "a running fleet <verb><when>"; without one, where the marker
42
- * lands instead. The request* cores share this so their wording cannot drift — pause and resume
43
- * phrase their own confirmations around markerApplyNote's when/tail because their sentences
44
- * differ. */
44
+ /** The trailing liveness clause of a one-shot marker command's confirmation (reset-counters,
45
+ * wake, and restart): with a live team, "a running team <verb><when>"; without one, where the
46
+ * marker goes instead. The request* cores share this so their wording cannot drift — pause and
47
+ * resume phrase their own confirmations around markerApplyNote's when/tail because their
48
+ * sentences differ. */
45
49
  function applyClause(live, when, verb) {
46
50
  return live
47
- ? `a running fleet ${verb}${when}`
48
- : "takes effect on the next `tumwater run` (no harness is running)";
51
+ ? `the running team ${verb}${when}`
52
+ : "takes effect on the next `tumwater start` (tumwater is not running)";
49
53
  }
50
54
  /** The marker-writing core of `reset-counters`: zero each target's state file directly when no
51
- * fleet is running, and drop the fleet marker a running fleet consumes within one poll cycle.
55
+ * team is running, and drop the team marker a running team consumes within one poll cycle.
52
56
  * Returns the confirmation the CLI prints verbatim. The CLI and the GUI's
53
57
  * POST /api/reset-counters share this core. */
54
58
  export function requestResetCounters(root, roles) {
55
- // A live fleet's orchestrator is the state files' single writer: consumeResetRequest zeroes
59
+ // A live team's orchestrator is the state files' single writer: consumeResetRequest zeroes
56
60
  // the affected runners' in-memory copies and the runners save them, and writes the file for a
57
61
  // requested role that has no runner (a disabled role). Rewriting the files here too would be
58
62
  // a second writer racing those whole-file saves — an overwrite from a snapshot the
59
63
  // orchestrator has since moved past (a just-ended tick's result, a claim, a schedule), which
60
- // the lost update discards. So with a live fleet only the marker is written; with none there
64
+ // the lost update discards. So with a live team only the marker is written; with none there
61
65
  // is no concurrent writer and the direct write is what carries the reset to the next run.
62
66
  const { live, when } = markerApplyNote(root);
63
67
  if (!live)
@@ -67,18 +71,25 @@ export function requestResetCounters(root, roles) {
67
71
  // readJsonFile folds to null — the corrupt-marker superset that wakes/resets EVERY runner.
68
72
  // For a scheduled wake (`--in`) that superset drops notBeforeMs, firing the wake hours early.
69
73
  writeJsonAtomic(resetRequestPath(root), { at: Date.now(), roles });
70
- // Only a live fleet consumes the marker; without one the state files are already zeroed and
71
- // the next `tumwater run` is when the in-memory copies catch up. Name which case this is
74
+ // Only a live team consumes the marker; without one the state files are already zeroed and
75
+ // the next `tumwater start` is when the in-memory copies catch up. Name which case this is
72
76
  // rather than promising a ~2s pickup that no process will make.
73
77
  return `counters reset for ${roles.join(", ")} — ${applyClause(live, when, "picks this up")}`;
74
78
  }
79
+ /** The empty-list wording both wake-list renderers share — `cancelWakes`'s no-op arm and
80
+ * `renderWakeList`'s empty list: one literal so the two surfaces cannot drift on what an
81
+ * operator sees when nothing is pending, plus a ` for <role>` suffix when the view was
82
+ * role-scoped. */
83
+ function noWakePending(role) {
84
+ return `no scheduled wake pending${role ? ` for ${role}` : ""}`;
85
+ }
75
86
  /** Drop pending scheduled wakes (`tumwater wake --cancel`, the dashboard's POST /api/wake-cancel):
76
87
  * every pending wake, or only `role`'s share of them. Returns the confirmation the CLI prints
77
88
  * verbatim and the GUI flashes, so the surfaces cannot drift on wording. */
78
89
  export function cancelWakes(root, role) {
79
90
  const n = cancelScheduledWakes(root, role);
80
91
  const forRole = role ? ` for ${role}` : "";
81
- return n > 0 ? `cancelled ${n} scheduled wake${n === 1 ? "" : "s"}${forRole}` : `no scheduled wake pending${forRole}`;
92
+ return n > 0 ? `cancelled ${plural(n, "scheduled wake")}${forRole}` : noWakePending(role);
82
93
  }
83
94
  /** The pending scheduled wakes (`tumwater wake --list`), earliest first; with `role`, only the
84
95
  * wakes naming that loop. Entries keep their full roles list. Read-only. */
@@ -89,18 +100,18 @@ export function listWakes(root, role, now) {
89
100
  /** `wake --list`'s text: one numbered line per wake, or the empty wording `cancelWakes` uses. */
90
101
  export function renderWakeList(wakes, now, role) {
91
102
  if (wakes.length === 0)
92
- return `no scheduled wake pending${role ? ` for ${role}` : ""}`;
103
+ return noWakePending(role);
93
104
  return wakes
94
- .map((w, i) => `${i + 1}. in ${humanSeconds(secondsUntil(w.atMs, now))} — ${w.roles.join(", ")}`)
105
+ .map((w, i) => `${i + 1}. in ${humanUntil(w.atMs, now)} — ${w.roles.join(", ")}`)
95
106
  .join("\n");
96
107
  }
97
108
  /** The marker-writing core of `wake [--in <duration>]`, shared with the GUI's POST /api/wake:
98
109
  * clear the named roles' backoff and pull nextRunAt to now (touching ONLY the schedule —
99
110
  * counters, wake tracking, and session continuity are untouched), and drop the marker a
100
- * running fleet consumes within one poll. With a positive `inMs` (`wake --in`'s parsed
111
+ * running team consumes within one poll. With a positive `inMs` (`wake --in`'s parsed
101
112
  * duration) the wake is SCHEDULED: only the marker is written, its deadline this call's one
102
113
  * clock read plus inMs — the state change belongs to the deadline, not to the submit, so a
103
- * fleet stopped at submit (or restarted before the deadline) does not wake early;
114
+ * team stopped at submit (or restarted before the deadline) does not wake early;
104
115
  * consumeWakeRequest clears the schedules when it consumes the marker at or after the
105
116
  * deadline. The "wakes in" phrase is inMs itself, not the deadline minus a second clock read
106
117
  * (the rule timedPauseBits follows): a caller-computed deadline let a millisecond tick between
@@ -111,10 +122,10 @@ export function requestWake(root, roles, inMs) {
111
122
  // Deferred only for a genuinely positive delay: zero or less reads as immediate, so the
112
123
  // immediate and scheduled paths share one consumer contract.
113
124
  const deferred = inMs !== undefined && inMs > 0;
114
- // Same single-writer rule as requestResetCounters: with a live fleet consumeWakeRequest
125
+ // Same single-writer rule as requestResetCounters: with a live team consumeWakeRequest
115
126
  // clears the affected runners' in-memory schedules and the runners save them, and writes the
116
127
  // file for a requested role that has no runner (a disabled role), so a direct state rewrite
117
- // here would race those whole-file saves and lose one side's update. Without a fleet there is
128
+ // here would race those whole-file saves and lose one side's update. Without a team there is
118
129
  // no concurrent writer, and the file write is what carries the wake to the next run. A
119
130
  // scheduled wake already skips the direct write — see the docstring.
120
131
  const { live, when } = markerApplyNote(root);
@@ -130,27 +141,44 @@ export function requestWake(root, roles, inMs) {
130
141
  }
131
142
  else
132
143
  writeJsonAtomic(wakeRequestPath(root), { at: now, roles });
133
- // Same liveness contract as reset-counters and pause/resume: only a live fleet consumes the
144
+ // Same liveness contract as reset-counters and pause/resume: only a live team consumes the
134
145
  // marker, so say so instead of promising a poll that will not happen.
135
146
  if (deferred)
136
147
  return (`wake scheduled for ${roles.join(", ")} — wakes in ${durationLabel(inMs)} — ` +
137
148
  (live
138
- ? "a running fleet applies it within one poll after the deadline"
139
- : "no harness is running — the next `tumwater run` applies it after the deadline"));
149
+ ? "the running team applies it within one poll after the deadline"
150
+ : "tumwater is not running — the next `tumwater start` applies it after the deadline"));
140
151
  return `wake requested for ${roles.join(", ")} — ${applyClause(live, when, "applies it")}`;
141
152
  }
153
+ /** Schedule a graceful team stop `inMs` from now (`tumwater stop --in`, the dashboard's "Stop the
154
+ * team later"): the marker means nothing without a live team to consume it, so a stopped one
155
+ * refuses with NO_HARNESS_ERROR. The caller has already validated `inMs` (positive, within
156
+ * PAUSE_FOR_MAX_MS). */
157
+ export function requestScheduledStop(root, inMs) {
158
+ if (!orchestratorAlive(root))
159
+ return { ok: false, error: NO_HARNESS_ERROR };
160
+ scheduleStop(root, Date.now() + inMs);
161
+ return {
162
+ ok: true,
163
+ message: `stop scheduled in ${durationLabel(inMs)} — the team finishes current work, then stops; \`tumwater stop --cancel\` drops it`,
164
+ };
165
+ }
166
+ /** Drop a scheduled stop; needs no live team. The confirmation `stop --cancel` prints. */
167
+ export function cancelScheduledStopMessage(root) {
168
+ return cancelScheduledStop(root) ? "scheduled stop cancelled" : "no stop scheduled";
169
+ }
142
170
  /** The marker-writing core of the forced restart, shared with the GUI's POST /api/restart:
143
- * drop the marker a running fleet consumes within one poll, telling the redeployer to waive
171
+ * drop the marker a running team consumes within one poll, telling the redeployer to waive
144
172
  * its post-restart cooldown. Forcing never bypasses a refusal — a red main, a failed compile,
145
173
  * a swap error, or a boot refusal still blocks — so a press is only meaningful while a restart
146
- * is actually pending: with a live fleet whose published build is not stale, nothing is written
147
- * and the reply says so. With no fleet running the marker is written harmlessly (the next `run`
174
+ * is actually pending: with a live team whose published build is not stale, nothing is written
175
+ * and the reply says so. With no team running the marker is written harmlessly (the next `run`
148
176
  * consumes it on its first poll) and the reply carries the same liveness contract as `wake`.
149
177
  * Returns the structured confirmation the CLI prints verbatim and the GUI flashes. */
150
178
  export function requestRestart(root) {
151
179
  const info = readOrchestratorInfo(root);
152
180
  if (info?.build && info.build.stale !== true) {
153
- return { ok: false, error: "no restart is pending — the running build is current with main" };
181
+ return { ok: false, error: "no update is waiting — tumwater is current with main" };
154
182
  }
155
183
  writeJsonAtomic(restartRequestPath(root), { at: Date.now() });
156
184
  const { live, when } = markerApplyNote(root);
@@ -161,7 +189,7 @@ export function requestRestart(root) {
161
189
  /** The marker-writing core of `tumwater reclaim` (plans/disk-floor.md, part 3/4): drop the
162
190
  * marker a running orchestrator consumes within one poll and run one manual pass over every
163
191
  * candidate, in-use worktrees respected. Returns the confirmation the CLI prints. A caller
164
- * with no live fleet must run the pass in-process instead (cmdReclaim), because nothing would
192
+ * with no live team must run the pass in-process instead (cmdReclaim), because nothing would
165
193
  * consume the marker. */
166
194
  function requestReclaim(root) {
167
195
  writeJsonAtomic(reclaimRequestPath(root), { at: Date.now() });
@@ -171,9 +199,9 @@ function requestReclaim(root) {
171
199
  /** Queue a prompt for one loop and wake that loop — the one workflow `tumwater prompt
172
200
  * --role`, the dashboard's POST /api/prompt-role, and the TUI's Ctrl+R submit all share:
173
201
  * submitRolePrompt enqueues into the loop's own queue (length-capped by the shared rule)
174
- * and logs under the loop, then requestWake's single-role marker brings a live fleet's
202
+ * and logs under the loop, then requestWake's single-role marker brings a live team's
175
203
  * loop in within one poll instead of whenever its backoff next expires (and is safe with
176
- * no fleet running — the same contract as `tumwater wake`). Returns requestWake's
204
+ * no team running — the same contract as `tumwater wake`). Returns requestWake's
177
205
  * confirmation so a surface can show what the wake did. Keeping enqueue and wake in one
178
206
  * call keeps a surface from ever queueing a prompt without the wake that delivers it
179
207
  * promptly — the pairing is the invariant, not each surface's private discipline. An optional
@@ -185,7 +213,7 @@ export function submitRolePromptAndWake(root, role, text, images, notBeforeMs) {
185
213
  return requestWake(root, [role]);
186
214
  }
187
215
  /** The marker-writing core of `abort`, shared with the GUI's POST /api/abort: drop the
188
- * per-role marker a running fleet consumes within one poll cycle. Reports the liveness gate
216
+ * per-role marker a running team consumes within one poll cycle. Reports the liveness gate
189
217
  * and the director's discarded-prompt note structurally ({ok, message|error}) so the CLI can
190
218
  * fail() and the GUI can shape its 409/200 — neither re-derives the wording. */
191
219
  export function requestAbort(root, role) {
@@ -193,7 +221,7 @@ export function requestAbort(root, role) {
193
221
  return { ok: false, error: NO_HARNESS_ERROR };
194
222
  }
195
223
  writeJsonAtomic(abortRequestPath(root, role), { at: Date.now() });
196
- let confirmation = `abort requested for ${role} — a running fleet applies it within ~2s`;
224
+ let confirmation = `abort requested for ${role} — the running team applies it within ~2s`;
197
225
  if (role === DIRECTOR_ROLE) {
198
226
  // The director's in-flight prompt was dequeued from the inbox file at tick start and an
199
227
  // abort discards it without re-queueing — say so, since the discard is otherwise silent.
@@ -235,11 +263,11 @@ export function rolePauseMessage(root, role, changed, timed, now = Date.now()) {
235
263
  return `role ${role} is already paused`;
236
264
  const { when, tail } = markerApplyNote(root);
237
265
  const { forPhrase, note } = timedPauseBits(timed, now);
238
- return `role ${role} paused${forPhrase} — it stops starting new ticks at its next eligibility check${when} (in-flight ticks finish; the rest of the fleet is unaffected)${note}${tail}`;
266
+ return `role ${role} paused${forPhrase} — it starts no new runs${when} (current runs finish; the rest of the team is unaffected)${note}${tail}`;
239
267
  }
240
268
  /** The per-role resume confirmation, shared between `tumwater resume --role` and the TUI's
241
- * Ctrl+P for the same no-drift reason. Carries the fleet-pause interplay note cmdResume
242
- * phrases: the fleet pause is the stronger gate, so with its marker present a freshly
269
+ * Ctrl+P for the same no-drift reason. Carries the team-pause interplay note cmdResume
270
+ * phrases: the team pause is the stronger gate, so with its marker present a freshly
243
271
  * resumed role still starts no ticks (the director is exempt from that gate, so its
244
272
  * resumption is real) — saying nothing would promise ticking the scheduler then deny, the
245
273
  * same honest-confirmation contract markerApplyNote's tail serves. */
@@ -247,13 +275,13 @@ export function roleResumeMessage(root, role, changed) {
247
275
  if (!changed)
248
276
  return `role ${role} was not paused`;
249
277
  const { when, tail } = markerApplyNote(root);
250
- const fleetNote = isFleetPaused(root) && role !== DIRECTOR_ROLE
251
- ? " (the fleet pause is still active — `tumwater resume` lifts it)"
278
+ const teamNote = isTeamPaused(root) && role !== DIRECTOR_ROLE
279
+ ? " (the team pause is still active — `tumwater resume` lifts it)"
252
280
  : "";
253
- return `role ${role} resumed — it starts ticking again at its next eligibility check${when}${fleetNote}${tail}`;
281
+ return `role ${role} resumed — it starts new runs again${when}${teamNote}${tail}`;
254
282
  }
255
283
  /** The `tumwater pause --for <duration>` cap: 90 days. A timed pause is meant to answer "step
256
- * away for a while and come back to a resumed fleet" — the same bound the report's day windows
284
+ * away for a while and come back to a resumed team" — the same bound the report's day windows
257
285
  * enforce (REPORT_MAX_DAYS in event-window.ts), and comfortably past any absence a deadline
258
286
  * should encode. Beyond it the deadline stops being a timed pause and becomes a standing one:
259
287
  * bare `pause` (lifted with `resume`) is that, and it says so instead of silently accepting a
@@ -262,7 +290,7 @@ export function roleResumeMessage(root, role, changed) {
262
290
  * every surface shares, so no new `--for` consumer can outgrow it unnoticed. */
263
291
  export const PAUSE_FOR_MAX_MS = 90 * 24 * 60 * 60 * 1000;
264
292
  /** One manual reclaim, shared by `tumwater reclaim` and the dashboard's POST /api/reclaim: with
265
- * a live fleet the pass is requested through the marker the orchestrator consumes (so in-use
293
+ * a live team the pass is requested through the marker the orchestrator consumes (so in-use
266
294
  * worktrees are respected); with none running it runs in-process, since nothing is in use.
267
295
  * Returns the confirmation its caller prints verbatim and never fails, so a server can call it. */
268
296
  export async function runManualReclaim(root) {
@@ -273,3 +301,13 @@ export async function runManualReclaim(root) {
273
301
  return "nothing to reclaim";
274
302
  return `reclaimed ${result.worktrees.join(", ")} — freed ${formatGB(result.freedGB)}`;
275
303
  }
304
+ /** Wake the role named in an answered question's `(asked by <role> …)` clause, so it reads the
305
+ * decision it was blocked on without waiting out its backoff. Returns requestWake's confirmation,
306
+ * or null when the heading names no known role (asked by the user, an unknown or removed role,
307
+ * or no clause) — never throws for an unknown asker. */
308
+ export function wakeQuestionAsker(root, title) {
309
+ const asker = questionAsker(title);
310
+ if (asker === null || !knownRoleIdsCached(root).includes(asker))
311
+ return null;
312
+ return requestWake(root, [asker]);
313
+ }
@@ -1,11 +1,13 @@
1
+ // vocabulary: plain
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
- import { logEvent, warnEvent, warnEventBestEffort } from "../events/events.js";
4
+ import { logEvent, warnEvent, warnEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
4
5
  import { removeQuiet } from "../files/files.js";
5
6
  import { errorMessage } from "../text/text.js";
6
7
  import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
7
8
  import { finiteNumber } from "../files/json-object.js";
8
9
  import { loadLoopState, saveLoopState, zeroCounters } from "../loop/loop-state.js";
10
+ import { canonicalRoleId } from "../roles/legacy-role-ids.js";
9
11
  import { clearBackoff } from "../scheduling/backoff.js";
10
12
  import { abortRequestPath, reclaimRequestPath, resetRequestPath, restartRequestPath, scheduledWakeDir, statePath, wakeRequestPath, STATE_DIR, } from "../paths.js";
11
13
  /** Read the optional `roles` request marker the `reset-counters` and `wake` CLI commands drop
@@ -28,13 +30,13 @@ function roleRequestTargets(markerFile, runners) {
28
30
  function requestedRoles(markerFile) {
29
31
  const marker = readJsonFile(markerFile);
30
32
  return marker && Array.isArray(marker.roles) && marker.roles.every((r) => typeof r === "string")
31
- ? marker.roles
33
+ ? marker.roles.map((r) => canonicalRoleId(r))
32
34
  : null;
33
35
  }
34
36
  /** The requested roles that hold NO live runner and already have a persisted state file. A
35
37
  * runner carries the operation to the role's in-memory copy and saves it; a role without one
36
38
  * (disabled in tumwater.json, or outside a scoped round) is reached only through its state
37
- * file, and while a fleet is live this process is that file's single writer — the CLI wrote
39
+ * file, and while a team is live this process is that file's single writer — the CLI wrote
38
40
  * only the marker. A role with no state file has no counters or schedule to change, and a
39
41
  * corrupt marker (no role list) yields none: the runner superset must not conjure new files
40
42
  * for roles the marker never named. */
@@ -47,7 +49,7 @@ function runnerlessTargets(root, markerFile, runners) {
47
49
  }
48
50
  /** Best-effort write of one operator-request event. The request's side effects have already
49
51
  * applied by the time this runs, so an unwritable events log must neither un-acknowledge the
50
- * marker nor escape the orchestrator's poll body (whose loop has no catch) and end the fleet.
52
+ * marker nor escape the orchestrator's poll body (whose loop has no catch) and end the team.
51
53
  * One wake consumer logs per role, so a throw that escaped would also starve the roles after
52
54
  * the first. A failure is still reported on the same feed when that feed is writable. */
53
55
  function logRequestEvent(root, event) {
@@ -56,7 +58,7 @@ function logRequestEvent(root, event) {
56
58
  }
57
59
  catch (err) {
58
60
  try {
59
- warnEvent(root, "harness", `could not record ${event.type} request: ${errorMessage(err)}`);
61
+ warnEvent(root, TUMWATER_SOURCE, `could not record ${event.type} request: ${errorMessage(err)}`);
60
62
  }
61
63
  catch {
62
64
  // The event feed is the failure; there is nothing left to report on.
@@ -65,15 +67,15 @@ function logRequestEvent(root, event) {
65
67
  }
66
68
  /** Report one role's failed state save for a consumed request, best-effort. These consumers run
67
69
  * inside the orchestrator's catch-less poll loop, so an unwritable state file (ENOSPC, EACCES,
68
- * the path replaced by a directory) must not throw out of the poll and end the fleet — the very
69
- * disk pressure the fleet guards against would otherwise crash it. The caller has already applied
70
+ * the path replaced by a directory) must not throw out of the poll and end the team — the very
71
+ * disk pressure the team guards against would otherwise crash it. The caller has already applied
70
72
  * the in-memory side effect where it can (a live runner), and a runnerless role's file keeps its
71
73
  * previous content, so the warning is what names the un-persisted change. */
72
74
  function warnStatePersistFailed(root, role, err) {
73
- warnEventBestEffort(root, "harness", `could not persist ${role}'s state for an operator request: ${errorMessage(err)}`);
75
+ warnEventBestEffort(root, TUMWATER_SOURCE, `could not persist ${role}'s state for your request: ${errorMessage(err)}`);
74
76
  }
75
77
  /** Consume a pending reset request from `tumwater reset-counters`, if any: the CLI zeroed the
76
- * state files only when no fleet was running; this zeroes the affected runners' in-memory
78
+ * state files only when no team was running; this zeroes the affected runners' in-memory
77
79
  * copies (which then re-save), or their next tick's save would resurrect the pre-reset values,
78
80
  * and writes the zeroed state file directly for requested roles with no runner, which the CLI
79
81
  * left to its marker. */
@@ -109,27 +111,26 @@ export function consumeResetRequest(root, runners) {
109
111
  const [only] = applied;
110
112
  // One role → filed under that loop; several → one harness-level event listing them.
111
113
  if (applied.length === 1 && only)
112
- logRequestEvent(root, { loop: only, type: "counters_reset" });
114
+ logRequestEvent(root, { source: only, type: "counters_reset" });
113
115
  else
114
- logRequestEvent(root, { loop: "harness", type: "counters_reset", roles: applied });
116
+ logRequestEvent(root, { source: TUMWATER_SOURCE, type: "counters_reset", roles: applied });
115
117
  }
116
118
  removeQuiet(markerFile);
117
119
  }
118
- /** Consume a pending wake request from `tumwater wake [--role <id>] [--in <duration>]`, if
119
- * any. Immediate wakes find the state files already cleared by the CLI when no fleet was
120
- * running; this clears the affected runners' in-memory schedules (backoffSeconds, nextRunAt),
121
- * or their next save would resurrect the pre-wake sleep window and the loops would keep
122
- * sleeping until the original backoff expired, and writes the cleared schedule directly for
123
- * requested roles with no runner, which the CLI left to its marker. A scheduled wake
124
- * (`--in`) carries a `notBeforeMs`: before the deadline the
125
- * marker is left in place (a later poll retries it — the wake lands within one poll cycle
126
- * after the deadline, the same delivery granularity prompt --at's consumer gives), and the
127
- * deadline-crossing poll's `wake()` call is what applies the state change, since the submit
120
+ /** Consume a pending wake request from `tumwater wake [--role <id>] [--in <duration>]`, if any.
121
+ * Immediate wakes find the state files already cleared by the CLI when no team was running; this
122
+ * clears the affected runners' in-memory schedules (backoffSeconds, nextRunAt), or their next save
123
+ * would resurrect the pre-wake sleep window and the loops would keep sleeping until the original
124
+ * backoff expired, and writes the cleared schedule directly for requested roles with no runner,
125
+ * which the CLI left to its marker. A scheduled wake (`--in`) carries a `notBeforeMs`: before the
126
+ * deadline the marker is left in place (a later poll retries it — the wake takes effect within one
127
+ * poll cycle after the deadline, the same delivery granularity prompt --at's consumer gives), and
128
+ * the deadline-crossing poll's `wake()` call is what applies the state change, since the submit
128
129
  * deliberately skipped it. A non-numeric or non-finite value (a hand edit's `1e999`) reads as
129
- * immediate (defensive; validation happens at the CLI) — leaving Infinity as a deadline would
130
- * hold `notBefore > Date.now()` true forever and strand the marker, which nothing else deletes.
131
- * Each woken role logs the existing `wake` event with the operator reason, so the fleet's early
132
- * ticks read in the feed as deliberate. */
130
+ * immediate (defensive; validation happens at the CLI) — leaving Infinity as a deadline would hold
131
+ * `notBefore > Date.now()` true forever and strand the marker, which nothing else deletes. Each
132
+ * woken role logs the existing `wake` event with the operator reason, so the team's early ticks
133
+ * read in the feed as deliberate. */
133
134
  export function consumeWakeRequest(root, runners) {
134
135
  // A legacy wake.json (the pre-upgrade build wrote scheduled wakes here) keeps its deadline.
135
136
  const markerFile = wakeRequestPath(root);
@@ -229,7 +230,7 @@ function applyWakeMarker(root, markerFile, runners) {
229
230
  warnStatePersistFailed(root, r.role, err);
230
231
  continue; // warn stands in for the wake event; nothing persisted to celebrate
231
232
  }
232
- logRequestEvent(root, { loop: r.role, type: "wake", reason: "operator" });
233
+ logRequestEvent(root, { source: r.role, type: "wake", reason: "operator" }); // vocabulary-ok: wake reason code
233
234
  }
234
235
  for (const role of runnerlessTargets(root, markerFile, runners)) {
235
236
  try {
@@ -239,7 +240,7 @@ function applyWakeMarker(root, markerFile, runners) {
239
240
  warnStatePersistFailed(root, role, err);
240
241
  continue;
241
242
  }
242
- logRequestEvent(root, { loop: role, type: "wake", reason: "operator" });
243
+ logRequestEvent(root, { source: role, type: "wake", reason: "operator" }); // vocabulary-ok: wake reason code
243
244
  }
244
245
  removeQuiet(markerFile);
245
246
  }
@@ -256,7 +257,7 @@ function consumeMarkerRequest(markerFile, act) {
256
257
  /** Consume a pending restart request from the dashboard's build-stale alert (POST /api/restart),
257
258
  * if any: tell the redeployer to force its next poll past the restart cooldown, then remove the
258
259
  * marker. The redeployer logs the `restart_forced` event itself; with no redeployer (a
259
- * non-self-hosting fleet never has a stale build, so nothing could have written the marker) it
260
+ * non-self-hosting team never has a stale build, so nothing could have written the marker) it
260
261
  * is still cleaned up. */
261
262
  export function consumeRestartRequest(root, redeployer) {
262
263
  consumeMarkerRequest(restartRequestPath(root), () => redeployer?.forceRestart());
@@ -264,26 +265,25 @@ export function consumeRestartRequest(root, redeployer) {
264
265
  /** Consume a pending manual reclaim request from `tumwater reclaim` (plans/disk-floor.md, part
265
266
  * 3/4), if any: arm one background pass over every candidate on the controller — the same
266
267
  * single-flight runner as pressure and idle reclaim — then remove the marker. With no
267
- * controller (a fleet that predates the feature) the marker is still cleaned up. */
268
+ * controller (a team that predates the feature) the marker is still cleaned up. */
268
269
  export function consumeReclaimRequest(root, reclaim) {
269
270
  consumeMarkerRequest(reclaimRequestPath(root), () => reclaim?.requestManual());
270
271
  }
271
- /** Consume per-role abort requests from `tumwater abort --role <id>`: one marker file per
272
- * role (no parsing needed), so a request for an idle OR disabled loop is still cleaned up. A
273
- * running tick gets killed and logs exactly one event; anything else is a silent no-op — the
274
- * marker's presence IS the request, removing it acknowledges. Since merge queue 3/5 a role's
275
- * in-flight work is often a LANDING rather than a tick, so the landing's controller is passed
276
- * in: an abort for the role landing right now kills that too (the drain's task sees
277
- * `userAborted` and discards the pinned ref when the landing ends). There can be several
278
- * in-flight units at once (land-queue speed 2c) — one per change being vetted, the merge
279
- * slot's stack, and each vetted change waiting for it (flagged, then settled by the drain) — so
280
- * `landings` lists them all and the request stops whichever holds the role. The merge is a
281
- * whole STACK (merge queue 5/5), so the request matches ANY of its roles — `roles` is every role
282
- * it is landing right now, and a stop for one of them kills the whole stack: the lander cannot
283
- * split it (abandoning mid-stack would leave the pinned refs of the not-yet-processed changes
284
- * for one-at-a-time recovery, which is already the fallback), and the merge discards every
285
- * stacked ref when it ends. */
286
- export function consumeAbortRequests(root, runners, landings) {
272
+ /** Consume per-role abort requests from `tumwater abort --role <id>`: one marker file per role (no
273
+ * parsing needed), so a request for an idle OR disabled loop is still cleaned up. A running tick
274
+ * gets killed and logs exactly one event; anything else is a silent no-op — the marker's presence
275
+ * IS the request, removing it acknowledges. Since merge queue 3/5 a role's in-flight work is often
276
+ * a MERGE rather than a tick, so the merge's controller is passed in: an abort for the role being
277
+ * merged right now kills that too (the drain's task sees `userAborted` and discards the pinned ref
278
+ * when the merge ends). There can be several in-flight units at once (land-queue speed 2c) — one
279
+ * per change being verified, the merge slot's stack, and each verified change waiting for it
280
+ * (flagged, then settled by the drain) — so `merges` lists them all and the request stops
281
+ * whichever holds the role. The merge is a whole STACK (merge queue 5/5), so the request matches
282
+ * ANY of its roles — `roles` is every role it is merging right now, and a stop for one of them
283
+ * kills the whole stack: the merger cannot split it (abandoning mid-stack would leave the pinned
284
+ * refs of the not-yet-processed changes for one-at-a-time recovery, which is already the
285
+ * fallback), and the merge discards every stacked ref when it ends. */
286
+ export function consumeAbortRequests(root, runners, merges) {
287
287
  let markers;
288
288
  try {
289
289
  markers = fs.readdirSync(path.join(root, STATE_DIR));
@@ -295,7 +295,7 @@ export function consumeAbortRequests(root, runners, landings) {
295
295
  // unwritable event feed is what failed, and there is nothing left to report on then.
296
296
  const warnFailure = (role, err) => {
297
297
  try {
298
- warnEvent(root, "harness", `could not consume abort request for ${role}: ${errorMessage(err)}`);
298
+ warnEvent(root, TUMWATER_SOURCE, `could not consume abort request for ${role}: ${errorMessage(err)}`);
299
299
  }
300
300
  catch {
301
301
  // The feed is the failure; the remaining markers still get their turn below.
@@ -312,11 +312,11 @@ export function consumeAbortRequests(root, runners, landings) {
312
312
  // after it forever. Apply the side effects first; a failure there keeps the marker for the
313
313
  // next poll (retry), but still moves on so it cannot starve the others.
314
314
  try {
315
- for (const landing of landings) {
316
- if (!landing.roles.includes(role))
315
+ for (const merge of merges) {
316
+ if (!merge.roles.includes(role))
317
317
  continue;
318
- landing.userAborted = true;
319
- landing.controller.abort();
318
+ merge.userAborted = true;
319
+ merge.controller.abort();
320
320
  }
321
321
  if (runner?.state.running)
322
322
  runner.abortTick();
@@ -325,12 +325,12 @@ export function consumeAbortRequests(root, runners, landings) {
325
325
  warnFailure(role, err);
326
326
  continue;
327
327
  }
328
- // The side effects applied: acknowledge the request. The tick_aborted event is
328
+ // The side effects applied: acknowledge the request. The run_aborted event is
329
329
  // observability, so a log failure must not un-acknowledge an abort that already happened.
330
330
  removeQuiet(abortRequestPath(root, role));
331
331
  if (runner?.state.running) {
332
332
  try {
333
- logEvent(root, { loop: role, type: "tick_aborted" });
333
+ logEvent(root, { source: role, type: "run_aborted" });
334
334
  }
335
335
  catch (err) {
336
336
  warnFailure(role, err);