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
@@ -0,0 +1,73 @@
1
+ /** The shared `/` search editor on the prompt line: over the History pane's tick rows or the
2
+ * Activity feed's events. It owns its mode flag (`searchMode`), its target pane (`searchFor`),
3
+ * its saved-draft pair, and the applyHistorySearch / applyActivitySearch leaves. Mutually
4
+ * exclusive with the other editors via the session's held registry. */
5
+ export function createSearchEditor(session) {
6
+ let searchMode = false;
7
+ // Which pane the shared `/` editor feeds: History's tick search or Activity's event search.
8
+ // The target is captured when `/` opens so a roster change mid-edit cannot retarget Enter.
9
+ let searchFor = "history";
10
+ let searchSavedInput = "";
11
+ let searchSavedCursor = 0;
12
+ // Leave the shared search editor (Enter applies, Esc cancels, Ctrl+T cycles): restore the
13
+ // saved prompt text, and reset the recall state with it — the same settle contract the budget
14
+ // and role-prompt editors honor. The applied activityGrep/historyGrep itself persists across
15
+ // view cycling, like historyWindow/historyResult.
16
+ const exitHistorySearch = () => {
17
+ if (!searchMode)
18
+ return;
19
+ searchMode = false;
20
+ session.held.search = false;
21
+ session.restorePromptFrom(searchSavedInput, searchSavedCursor);
22
+ };
23
+ const handleKey = (str, key, ctx) => {
24
+ if (!key.ctrl && str === "/" && !searchMode && !session.held.budget &&
25
+ !session.held.rolePromptFor && !session.held.pause &&
26
+ !session.held.queued && !session.held.answer && !session.held.entry &&
27
+ !session.held.attach && !session.held.retire &&
28
+ (ctx.view === 0 || ctx.view === ctx.roleIds.length + 4) && session.input === "") {
29
+ // `/` opens a free-text search on the prompt line (the pager's search key; this project
30
+ // already reserves `/` for a meta action): over the History pane's tick rows on History,
31
+ // over the Activity feed's events on Activity. Guarded to those two views and to an empty
32
+ // line, so elsewhere it types into the prompt as today and a director draft is never
33
+ // disturbed. Pre-fills the applied pattern so Enter re-submits it and the operator can
34
+ // edit it; Esc cancels without applying.
35
+ session.settleDraft((t) => (searchSavedInput = t), (n) => (searchSavedCursor = n));
36
+ searchFor = ctx.view === 0 ? "activity" : "history";
37
+ searchMode = true;
38
+ session.held.search = true;
39
+ session.input = (searchFor === "activity" ? ctx.activityGrep : ctx.historyGrep) ?? "";
40
+ session.cursor = session.input.length;
41
+ session.flashMessage(searchFor === "activity" ? "search events: Enter to filter, Esc to cancel" : "search runs: Enter to filter, Esc to cancel");
42
+ return true;
43
+ }
44
+ if (key.name === "escape" && searchMode) {
45
+ // Esc cancels the History search without applying it; the applied pattern is unchanged.
46
+ exitHistorySearch();
47
+ return true;
48
+ }
49
+ if (key.name === "return" && searchMode) {
50
+ // Enter applies the trimmed line to the pane the editor opened over: Activity sets
51
+ // activityGrep (an empty line clears it) and resets the scroll to the live tail — the
52
+ // offset indexed the old filtered list; History recomputes its row cache now (the pane
53
+ // is showing) and re-enters at its head. A successful apply restores the saved prompt
54
+ // draft through exitHistorySearch, the shared editor's exit.
55
+ const pattern = session.input.trim();
56
+ if (searchFor === "activity") {
57
+ const applied = pattern === "" ? null : pattern;
58
+ session.applyActivitySearch(applied);
59
+ exitHistorySearch();
60
+ session.flashMessage(applied === null ? "search cleared" : `search: "${applied}"`);
61
+ }
62
+ else {
63
+ const applied = pattern === "" ? null : pattern;
64
+ session.applyHistorySearch(applied);
65
+ exitHistorySearch();
66
+ session.flashMessage(applied === null ? "search cleared" : `search: "${applied}"`);
67
+ }
68
+ return true;
69
+ }
70
+ return false;
71
+ };
72
+ return { handleKey, exit: exitHistorySearch };
73
+ }
@@ -0,0 +1,97 @@
1
+ // vocabulary: plain
2
+ import { newPromptHistory, resetPromptRecall, settlePromptRecall, } from "./tui-prompt-history.js";
3
+ /** Build the shared prompt-line session from the disk-side deps the aggregator received. The
4
+ * editor modules then take this session plus each keypress's `TuiEditorCtx`. */
5
+ export function createTuiEditorSession(deps) {
6
+ const session = {
7
+ input: "",
8
+ cursor: 0,
9
+ promptHistory: newPromptHistory(),
10
+ images: [],
11
+ held: {
12
+ budget: false,
13
+ rolePromptFor: null,
14
+ search: false,
15
+ pause: false,
16
+ pauseEditRole: null,
17
+ pauseEditWake: false,
18
+ pauseEditStop: false,
19
+ queued: null,
20
+ answer: null,
21
+ entry: null,
22
+ attach: false,
23
+ notes: null,
24
+ brief: false,
25
+ retire: false,
26
+ quiet: false,
27
+ interval: false,
28
+ },
29
+ root: deps.root,
30
+ now: deps.now,
31
+ flat: deps.flat,
32
+ refreshQueued: deps.refreshQueued,
33
+ refreshNotes: deps.refreshNotes,
34
+ refreshBrief: deps.refreshBrief,
35
+ clearBacklogSelection: deps.clearBacklogSelection,
36
+ applyHistorySearch: deps.applyHistorySearch,
37
+ applyActivitySearch: deps.applyActivitySearch,
38
+ flashMessage: deps.flashMessage,
39
+ flashError: deps.flashError,
40
+ commitBacklogEdit: deps.commitBacklogEdit,
41
+ restorePromptFrom(text, pos) {
42
+ session.input = text;
43
+ session.cursor = pos;
44
+ session.promptHistory = resetPromptRecall(session.promptHistory);
45
+ },
46
+ settleDraft(saveInput, saveCursor) {
47
+ const settled = settlePromptRecall(session.promptHistory, session.input, session.cursor);
48
+ session.promptHistory = settled.history;
49
+ saveInput(settled.text);
50
+ saveCursor(settled.cursor);
51
+ },
52
+ busyFlash(subject) {
53
+ return `finish or cancel the ${subject} first (Esc cancels)`;
54
+ },
55
+ heldEditorFlash(entering) {
56
+ const h = session.held;
57
+ if (h.search)
58
+ return session.busyFlash("search");
59
+ if (h.pause)
60
+ return session.busyFlash("pause");
61
+ if (h.queued)
62
+ return session.busyFlash("inbox edit");
63
+ if (h.answer)
64
+ return session.busyFlash("answer");
65
+ if (h.entry)
66
+ return session.busyFlash("entry edit");
67
+ if (h.rolePromptFor)
68
+ return session.busyFlash(`prompt for ${h.rolePromptFor}`);
69
+ if (h.attach && entering !== "attach")
70
+ return session.busyFlash("image attach");
71
+ if (h.notes && entering !== "notes")
72
+ return session.busyFlash("notes edit");
73
+ if (h.brief && entering !== "brief")
74
+ return session.busyFlash("brief edit");
75
+ if (h.retire && entering !== "retire")
76
+ return session.busyFlash("retire role");
77
+ if (h.quiet && entering !== "quiet")
78
+ return session.busyFlash("quiet hours");
79
+ if (h.interval && entering !== "interval")
80
+ return session.busyFlash("min run interval");
81
+ if (entering === "attach" && h.budget)
82
+ return session.busyFlash("budget edit");
83
+ if (entering === "notes" && (h.attach || h.budget))
84
+ return session.busyFlash(h.attach ? "image attach" : "budget edit");
85
+ if (entering === "brief" && h.budget)
86
+ return session.busyFlash("budget edit");
87
+ if (entering === "retire" && (h.attach || h.notes || h.budget))
88
+ return session.busyFlash(h.attach ? "image attach" : h.notes ? "notes edit" : "budget edit");
89
+ if (entering === "quiet" && (h.attach || h.notes || h.budget || h.brief || h.retire))
90
+ return session.busyFlash(h.attach ? "image attach" : h.notes ? "notes edit" : h.brief ? "brief edit" : h.retire ? "retire role" : "budget edit");
91
+ if (entering === "interval" && (h.attach || h.notes || h.budget || h.brief || h.retire || h.quiet))
92
+ return session.busyFlash(h.attach ? "image attach" : h.notes ? "notes edit" : h.brief ? "brief edit" : h.retire ? "retire role" : h.quiet ? "quiet hours" : "budget edit");
93
+ return null;
94
+ },
95
+ };
96
+ return session;
97
+ }
@@ -0,0 +1,152 @@
1
+ // vocabulary: plain
2
+ import { submitPrompt } from "../../inbox/inbox-submit.js";
3
+ import { applyKey } from "./tui-input.js";
4
+ import { pushPromptHistory, recallPromptHistory } from "./tui-prompt-history.js";
5
+ import { createTuiEditorSession } from "./tui-editor-session.js";
6
+ import { createBudgetEditor } from "./tui-editor-budget.js";
7
+ import { createQuietEditor } from "./tui-editor-quiet.js";
8
+ import { createIntervalEditor } from "./tui-editor-interval.js";
9
+ import { createAttachEditor } from "./tui-editor-attach.js";
10
+ import { createNotesEditor } from "./tui-editor-notes.js";
11
+ import { createBriefEditor } from "./tui-editor-brief.js";
12
+ import { createRetireEditor } from "./tui-editor-retire.js";
13
+ import { createPromptEditor } from "./tui-editor-prompt.js";
14
+ import { createPauseEditor } from "./tui-editor-pause.js";
15
+ import { createQueuedEditor } from "./tui-editor-queued.js";
16
+ import { createAnswerEditor } from "./tui-editor-answer.js";
17
+ import { createEntryEditor } from "./tui-editor-entry.js";
18
+ import { createSearchEditor } from "./tui-editor-search.js";
19
+ /** The prompt-line editor half of the createTuiKeys closure: a thin aggregator that builds the
20
+ * shared prompt-line session (tui-editor-session.ts) and the set of mutually-exclusive modal
21
+ * editors on it — budget Ctrl+B, quiet-hours Ctrl+O, role-prompt Ctrl+R, pause
22
+ * Ctrl+F/Ctrl+Q, answer, entry-edit, queued-edit, and the `/` search — each with its own
23
+ * saved-draft pair and exit
24
+ * helper in its own module beside the session. `handleKey` routes each key to the editors in
25
+ * today's priority order (each module returns true when it consumed the key and false to fall
26
+ * through, so the mutually-exclusive/busy-guard ordering is preserved exactly); the session's
27
+ * `held` registry and `heldEditorFlash` hold the exclusive-line invariants once, at the module
28
+ * boundary, instead of being re-stated per editor. The disk/commit responsibilities and the pane
29
+ * state stay on the tui-keys.ts and tui-panes.ts side; this factory receives them as deps and
30
+ * the per-keypress pane reads as ctx. */
31
+ export function createTuiEditors(deps) {
32
+ const session = createTuiEditorSession(deps);
33
+ const editors = [
34
+ createBudgetEditor(session),
35
+ createQuietEditor(session),
36
+ createIntervalEditor(session),
37
+ createAttachEditor(session),
38
+ createNotesEditor(session),
39
+ createPauseEditor(session),
40
+ createPromptEditor(session),
41
+ createQueuedEditor(session),
42
+ createAnswerEditor(session),
43
+ createEntryEditor(session),
44
+ createSearchEditor(session),
45
+ createBriefEditor(session),
46
+ createRetireEditor(session),
47
+ ];
48
+ const handleKey = (str, key, ctx) => {
49
+ // Route the key to each editor in today's priority order; each module returns true when it
50
+ // consumed the key (the mutually-exclusive/busy-guard ordering is preserved exactly) and
51
+ // false to fall through to the next.
52
+ for (const editor of editors) {
53
+ if (editor.handleKey(str, key, ctx))
54
+ return true;
55
+ }
56
+ // The Enter key submits the director prompt when no modal editor holds the line — the
57
+ // shared/none case of the Enter arm. Only reached when the key was "return" and no editor
58
+ // consumed it; any other unhandled key falls through to the pane arms.
59
+ if (key.name === "return") {
60
+ const prompt = session.input.trim();
61
+ if (!prompt) {
62
+ // Whitespace-only input queues nothing; drop it like an empty line.
63
+ session.input = "";
64
+ session.cursor = 0;
65
+ }
66
+ else {
67
+ const images = session.images.length > 0 ? session.images : undefined;
68
+ try {
69
+ submitPrompt(session.root, prompt, images);
70
+ session.promptHistory = pushPromptHistory(session.promptHistory, prompt);
71
+ // A successful send consumes the pending image attachments; a thrown submit below
72
+ // keeps them so the operator can resubmit with the images still attached.
73
+ session.images = [];
74
+ session.input = "";
75
+ session.cursor = 0;
76
+ session.flashMessage("added to the director's inbox");
77
+ }
78
+ catch (err) {
79
+ // The queue write failed (disk full, permissions): keep the operator's text so
80
+ // it can be resubmitted, and flash the reason — the same contract the GUI's
81
+ // prompt form honors. Clearing the line first would silently lose the prompt,
82
+ // and an unguarded throw would escape the keypress handler and kill the TUI.
83
+ session.flashError(err);
84
+ }
85
+ }
86
+ return true;
87
+ }
88
+ return false;
89
+ };
90
+ const onArrow = (direction) => {
91
+ // Up/Down walk the submitted-prompt history the way readline does (tui-prompt-history.ts's
92
+ // recall rules). The backlog pane keeps the arrows for entry browsing (its branch
93
+ // above already returned), and budget, pause and search modes keep them out —
94
+ // recalling a prompt into the cap/field text would be a paste, not a recall. No
95
+ // history in this direction: fall through to applyKey, which ignores arrows as before.
96
+ if (!session.held.budget && !session.held.quiet && !session.held.interval && !session.held.attach && !session.held.pause && !session.held.search && !session.held.notes && !session.held.brief && !session.held.retire) {
97
+ const recalled = recallPromptHistory(session.promptHistory, session.input, session.cursor, direction === "up" ? "older" : "newer");
98
+ if (recalled) {
99
+ session.promptHistory = recalled.history;
100
+ session.input = recalled.text;
101
+ session.cursor = recalled.cursor;
102
+ return true;
103
+ }
104
+ }
105
+ return false;
106
+ };
107
+ const apply = (str, key) => {
108
+ const next = applyKey(session.input, session.cursor, str, key);
109
+ session.input = next.text;
110
+ session.cursor = next.cursor;
111
+ };
112
+ // Leave every editor (Ctrl+T): view cycling is orthogonal to them, so a cycle never strands
113
+ // an editor's pre-filled text in the prompt line. Each restores its own saved draft.
114
+ const exitAll = () => {
115
+ for (const editor of editors)
116
+ editor.exit();
117
+ };
118
+ const sync = (capUsd, budgetFree) => {
119
+ // Only the budget editor reads the snapshot-fed cap/free flags.
120
+ editors.forEach((editor) => editor.sync?.(capUsd, budgetFree));
121
+ };
122
+ return {
123
+ handleKey,
124
+ onArrow,
125
+ apply,
126
+ exitAll,
127
+ sync,
128
+ state() {
129
+ return {
130
+ input: session.input,
131
+ cursor: session.cursor,
132
+ budgetMode: session.held.budget,
133
+ rolePromptFor: session.held.rolePromptFor,
134
+ pauseEditMode: session.held.pause,
135
+ pauseEditRole: session.held.pauseEditRole,
136
+ pauseEditWake: session.held.pauseEditWake,
137
+ pauseEditStop: session.held.pauseEditStop,
138
+ queuedEditFor: session.held.queued,
139
+ searchMode: session.held.search,
140
+ answerFor: session.held.answer,
141
+ entryEditFor: session.held.entry,
142
+ attachMode: session.held.attach,
143
+ images: session.images.length,
144
+ notesEditFor: session.held.notes,
145
+ briefEditMode: session.held.brief,
146
+ retireMode: session.held.retire,
147
+ quietMode: session.held.quiet,
148
+ intervalMode: session.held.interval,
149
+ };
150
+ },
151
+ };
152
+ }
@@ -1,54 +1,39 @@
1
- import { clipSpans } from "../status-render.js";
1
+ import { alertLines, clipSpans, toneLine } from "../status-render.js";
2
+ // Re-export the attention-banner helpers so the TUI's consumers (tui.tsx, tui-pane.ts, and
3
+ // the tui-frame tests) keep importing them from this module; their definitions now live with
4
+ // the other shared status renderers and the `tumwater status` banner block.
5
+ export { alertLines, toneLine };
2
6
  import { eventResult } from "../../events/event-format.js";
7
+ import { eventTypeLabel } from "../../events/event-labels.js";
3
8
  import { eventKind, resultTone } from "../tone.js";
9
+ import { roleLabel } from "../../roles/role-labels.js";
4
10
  import { displayWidth } from "../../text/text-width.js";
11
+ import { plural } from "../../text/phrases.js";
5
12
  import { stripTerminalControls } from "../../text/text.js";
6
- /** A plain line clipped to the pane, in one tone. */
7
- export function toneLine(text, width, tone) {
8
- return clipSpans([{ text, ...(tone ? { tone } : {}) }], width);
9
- }
10
13
  /** Strip terminal control characters from every span of a composed frame — the TUI's single
11
14
  * terminal boundary, applied in tui.tsx to the whole line list (status table, attention
12
15
  * lines, queued-prompt previews, the pane body, flash/hint/input) just before ink draws it.
13
16
  * This is what closes the same gap say() closes for the CLI: model-written text (tick
14
17
  * summaries, lastError, backlog/QUESTION titles, transcript turns) reaches the terminal
15
- * through fleetAlerts, renderStatusSpans, and paneBody, not through say(), so sanitizing the
18
+ * through teamAlerts, renderStatusSpans, and paneBody, not through say(), so sanitizing the
16
19
  * final spans — rather than each producer — covers every current and future line builder. */
17
20
  export function sanitizeFrameLines(lines) {
18
21
  return lines.map((line) => line.map((span) => ({ ...span, text: stripTerminalControls(span.text) })));
19
22
  }
20
- const ALERT_MARKS = {
21
- red: { mark: "!", tone: "red" },
22
- amber: { mark: "!", tone: "yellow" },
23
- indigo: { mark: "?", tone: "magenta" },
24
- blue: { mark: "i", tone: "blue" },
25
- gray: { mark: "i", tone: "dim" },
26
- };
27
- /** The attention lines under the header: one per alert — its mark and title in its tone, then
28
- * the detail — at most `max` of them, the rest counted on a last line. */
29
- export function alertLines(alerts, width, max = 3) {
30
- const shown = alerts.length > max ? alerts.slice(0, max - 1) : alerts;
31
- const lines = shown.map((a) => {
32
- const { mark, tone } = ALERT_MARKS[a.tone];
33
- const t = tone ? { tone } : {};
34
- return clipSpans([{ text: `${mark} `, ...t }, { text: a.title, ...t }, { text: a.detail ? ` — ${a.detail}` : "" }], width);
35
- });
36
- if (shown.length < alerts.length)
37
- lines.push(toneLine(` +${alerts.length - shown.length} more — the dashboard lists them all`, width, "dim"));
38
- return lines;
39
- }
40
23
  /** The pane's tab strip: every view by name, the current one bracketed (and bright), so the
41
24
  * Ctrl+T cycle can be seen rather than remembered. */
42
25
  export function tabStrip(view, width) {
43
26
  const tabs = [
44
- ["activity", view.kind === "activity" && view.role ? `Activity: ${view.role}` : "Activity"],
45
- ["transcript", view.kind === "transcript" ? `${view.note ? "Notebook" : view.change ? "Change" : "Transcript"}: ${view.role} (${view.index}/${view.count})` : "Transcript"],
27
+ ["activity", view.kind === "activity" && view.role ? `Activity: ${roleLabel(view.role)}` : "Activity"],
28
+ ["transcript", view.kind === "transcript" ? `${view.note ? "Notes" : view.change ? "Change" : "Transcript"}: ${roleLabel(view.role)} (${view.index}/${view.count})` : "Transcript"],
46
29
  ["backlog", "Backlog"],
47
30
  ["usage", view.kind === "usage" && view.scope ? `Usage: ${view.scope}` : "Usage"],
48
31
  ["failures", view.kind === "failures" && view.scope ? `Failures: ${view.scope}` : "Failures"],
49
- ["history", view.kind === "history" ? `${view.trail ? "Tick" : "History"}${view.role ? ": " + view.role : ""}` : "History"],
32
+ ["history", view.kind === "history" ? `${view.trail ? "Run" : "History"}${view.role ? ": " + roleLabel(view.role) : ""}` : "History"],
50
33
  ["pending", "Pending"],
51
- ["queued", "Queued"],
34
+ ["queued", "Inbox"],
35
+ ["health", "Health"],
36
+ ["brief", "Brief"],
52
37
  ];
53
38
  const spans = [];
54
39
  tabs.forEach(([kind, label], i) => {
@@ -65,41 +50,53 @@ export function hintLine(view, mode, width) {
65
50
  if (mode.entryEditFor?.position === null)
66
51
  keys = [["Enter", `file ${mode.entryEditFor.kind}`], ["Ctrl+N", `switch to ${mode.entryEditFor.kind === "plan" ? "bug" : "plan"}`], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
67
52
  else if (mode.entryEditFor)
68
- keys = [["Enter", "save title"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
53
+ keys = [["Enter", "save title"], ["Ctrl+E", "body file"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
69
54
  else if (mode.answerFor)
70
55
  keys = [["Enter", "record answer"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
71
56
  else if (mode.queuedEditFor)
72
57
  keys = [["Enter", "save the edit"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
58
+ else if (mode.pauseEditMode && mode.pauseEditStop)
59
+ keys = [["Enter", "schedule stop"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
73
60
  else if (mode.pauseEditMode)
74
61
  keys = [["Enter", mode.pauseEditRole ? `${mode.pauseEditWake ? "wake" : "pause"} ${mode.pauseEditRole}` : "pause"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
75
- else if (mode.deferMode)
76
- keys = [["Enter", "save the delay"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
62
+ else if (mode.attachMode)
63
+ keys = [["Enter", "attach image path"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
64
+ else if (mode.notesEditFor)
65
+ keys = [["Enter", "replace notes from file"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
66
+ else if (mode.retireMode)
67
+ keys = [["Enter", "retire role"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
77
68
  else if (mode.budget)
78
69
  keys = [["Enter", "save the daily cap"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
70
+ else if (mode.quietMode)
71
+ keys = [["Enter", "save quiet hours"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
72
+ else if (mode.intervalMode)
73
+ keys = [["Enter", "save interval"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
79
74
  else if (mode.rolePromptFor)
80
75
  keys = [["Enter", `send to ${mode.rolePromptFor}`], ["↑↓", "history"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
76
+ else if (mode.briefEditMode)
77
+ keys = [["Enter", "replace brief from file"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
81
78
  else if (view.kind === "transcript") {
82
- keys = [["Enter", "send"], ["↑↓", "history"], ["Ctrl+R", `prompt ${view.role}`], ["Ctrl+N", view.note ? "change" : view.change ? "transcript" : "notebook"], ...(view.note ? [["Ctrl+X", "clear notebook"]] : []), ["Ctrl+P", "pause/resume"], ["Ctrl+W", "wake"], ["Ctrl+A", "abort"],
79
+ keys = [["Enter", "send"], ["↑↓", "history"], ["Ctrl+R", `prompt ${view.role}`], ["Ctrl+N", view.note ? "change" : view.change ? "transcript" : "notes"], ...(view.note ? [["Ctrl+X", "clear notes"], ["Ctrl+E", "edit notes"]] : []), ["Ctrl+P", "pause/resume"], ["Ctrl+W", "wake"], ["Ctrl+A", "stop run"], ["Ctrl+V", "reset counters"], ["Ctrl+B", "daily cap"], ["Ctrl+O", "quiet hours"],
83
80
  ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
84
81
  }
85
82
  else if (view.kind === "backlog")
86
- keys = [["Enter", "send"], ["↑↓", "open entries"], ["Ctrl+X", "cancel entry"], ["Ctrl+E", "edit entry"], ["Ctrl+N", "new plan/bug"], ["Ctrl+A", "answer question"], ["PgUp/PgDn", "scroll"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
83
+ keys = [["Enter", "send"], ["↑↓", "open entries"], ["Ctrl+X", "cancel entry"], ["Ctrl+R", "release entry"], ["Ctrl+V", "top entry"], ["Ctrl+E", "edit entry"], ["Ctrl+N", "new plan/bug"], ["Ctrl+A", "answer question"], ["PgUp/PgDn", "scroll"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
87
84
  else if (view.kind === "usage" || view.kind === "failures")
88
- keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+G", view.scope ? `scope: ${view.scope}` : "scope: fleet"], ["Ctrl+E", view.window ? `window: ${view.window}` : "window: default"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
85
+ keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+G", view.scope ? `scope: ${view.scope}` : "scope: team"], ["Ctrl+E", view.window ? `window: ${view.window}` : "window: default"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
89
86
  else if (view.kind === "history")
90
- keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+G", view.role ? `scope: ${view.role}` : "scope: fleet"], ["Ctrl+S", view.since ? `window: ${view.since}` : "window: default"], ["Ctrl+O", view.result ? `result: ${view.result}` : "result: all"], ["Ctrl+N", view.trail ? "history" : "tick trail"], ["/", view.grep ? `search: ${view.grep}` : "search"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
87
+ keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+G", view.role ? `scope: ${view.role}` : "scope: team"], ["Ctrl+S", view.since ? `window: ${view.since}` : "window: default"], ["Ctrl+O", view.result ? `result: ${view.result}` : "result: all"], ["Ctrl+Y", view.summary ? "rows" : "summary"], ["Ctrl+N", view.trail ? "history" : "run trail"], ["/", view.grep ? `search: ${view.grep}` : "search"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
91
88
  else if (view.kind === "pending")
92
- keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
89
+ keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+R", "refresh"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
90
+ else if (view.kind === "health")
91
+ keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+O", view.problems ? "problems only" : "all checks"], ["Ctrl+R", "re-run checks"], ["Ctrl+E", "reclaim disk"], ["Ctrl+N", "test notify"], ["Ctrl+X", "retire role"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
92
+ else if (view.kind === "brief")
93
+ keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+E", "edit brief"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
93
94
  else if (view.kind === "queued")
94
- keys = [["Enter", "send"], ["↑↓", "history"], ["Ctrl+E", "edit"], ["Ctrl+X", "cancel"], ["PgUp/PgDn", "scroll"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
95
+ keys = [["Enter", "send"], ["↑↓", "history"], ["Ctrl+V", "top"], ["Ctrl+E", "edit"], ["Ctrl+X", "cancel"], ["PgUp/PgDn", "scroll"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
95
96
  else
96
- keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+B", "daily cap"], ["Ctrl+G", view.role ? `scope: ${view.role}` : "scope: fleet"], ["/", view.grep ? `search: ${view.grep}` : "search"], ["Ctrl+T", "next view"], ["Ctrl+F", "pause fleet"], ["Ctrl+C", "interrupt director"], ["Ctrl+D", "quit"]];
97
- // The defer editor is the prompt line's fourth meta-editor (after budget, role, and
98
- // search), so every view whose hint is about sending names its state: the pending delay
99
- // when one is set, otherwise the way to open it. The mode branches above already name
100
- // their own keys and keep the line, so this only rides the view hints.
101
- if (!mode.deferMode && !mode.pauseEditMode && !mode.budget && !mode.rolePromptFor && !mode.queuedEditFor && !mode.answerFor && !mode.entryEditFor)
102
- keys.push(["Ctrl+L", mode.defer ? `defer: ${mode.defer}` : "defer"]);
97
+ keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+B", "daily cap"], ["Ctrl+O", view.type ? `type: ${eventTypeLabel(view.type)}` : "type: all"], ["Ctrl+G", view.role ? `scope: ${view.role}` : "scope: team"], ["/", view.grep ? `search: ${view.grep}` : "search"], ["Ctrl+T", "next view"], ["Ctrl+F", "pause team"], ["Ctrl+Q", "stop later"], ["Ctrl+C", "interrupt director"], ["Ctrl+V", "attach image"], ["Ctrl+R", "restart"], ["Ctrl+D", "quit"]];
98
+ // The mode branches above name their own keys and keep the line, so every remaining
99
+ // view hint always shows the send affordance. This comment marks that ordering.
103
100
  const spans = [];
104
101
  keys.forEach(([key, what], i) => {
105
102
  if (i > 0)
@@ -112,37 +109,52 @@ export function hintLine(view, mode, width) {
112
109
  export function promptPrefix(mode) {
113
110
  if (mode.budget)
114
111
  return [{ text: "daily cap $", tone: "brand" }, { text: " " }];
112
+ if (mode.quietMode)
113
+ return [{ text: "quiet hours", tone: "brand" }, { text: " › " }];
114
+ if (mode.intervalMode)
115
+ return [{ text: "min run interval", tone: "brand" }, { text: " › " }];
116
+ if (mode.briefEditMode)
117
+ return [{ text: "brief", tone: "brand" }, { text: " › " }];
118
+ if (mode.retireMode)
119
+ return [{ text: "retire", tone: "brand" }, { text: " › " }];
120
+ if (mode.notesEditFor)
121
+ return [{ text: `notes for ${mode.notesEditFor}`, tone: "brand" }, { text: " › " }];
115
122
  if (mode.entryEditFor)
116
123
  return [{ text: mode.entryEditFor.position === null ? `new ${mode.entryEditFor.kind}` : `edit ${mode.entryEditFor.kind} #${mode.entryEditFor.position}`, tone: "brand" }, { text: " › " }];
117
124
  if (mode.answerFor)
118
125
  return [{ text: `answer #${mode.answerFor.position}`, tone: "brand" }, { text: " › " }];
119
126
  if (mode.queuedEditFor)
120
127
  return [{ text: `edit ${mode.queuedEditFor.role}`, tone: "brand" }, { text: " › " }];
128
+ if (mode.pauseEditMode && mode.pauseEditStop)
129
+ return [{ text: "stop in", tone: "brand" }, { text: " › " }];
121
130
  if (mode.pauseEditMode)
122
131
  return [{ text: mode.pauseEditRole ? `${mode.pauseEditWake ? "wake" : "pause"} ${mode.pauseEditRole}` : "pause", tone: "brand" }, { text: " › " }];
123
- if (mode.deferMode)
124
- return [{ text: "defer", tone: "brand" }, { text: " › " }];
125
- // A pending delay is named beside the target (e.g. `director (defer 45m) › `), so the
126
- // operator sees what the next Enter will schedule before pressing it.
127
- const target = mode.defer ? `${mode.rolePromptFor ?? "director"} (defer ${mode.defer})` : (mode.rolePromptFor ?? "director");
128
- return [{ text: target, tone: "brand" }, { text: " › " }];
132
+ // The pending image count (e.g. `director (2 images) › `) is named beside the target, so
133
+ // the user sees what the next Enter will send before pressing it. The image count names the
134
+ // director prompt only: session.images ride into the next director submit (submitPrompt),
135
+ // while a role prompt (submitRolePromptAndWake) passes images undefined, so the count must
136
+ // not claim an attachment a role-targeted Enter won't send.
137
+ const target = mode.rolePromptFor ?? "director";
138
+ const withImages = mode.rolePromptFor === null && (mode.images ?? 0) > 0;
139
+ const named = withImages ? `${target} (${plural(mode.images ?? 0, "image")})` : target;
140
+ return [{ text: named, tone: "brand" }, { text: " › " }];
129
141
  }
130
142
  /** The prefix's width in terminal columns, for sizing the input beside it. */
131
143
  export function prefixWidth(prefix) {
132
144
  return displayWidth(prefix.map((sp) => sp.text).join(""));
133
145
  }
134
146
  /** An activity line's tone, by the event's kind (tone.ts's eventKind — the dashboard's
135
- * activity card uses the same rule): landings green, problems in their outcome's color,
147
+ * activity card uses the same rule): merges green, problems in their outcome's color,
136
148
  * questions magenta, routine bookkeeping dim. */
137
149
  export function eventTone(e) {
138
150
  const result = eventResult(e);
139
151
  const kind = eventKind(e.type, result);
140
152
  if (kind === "landing")
141
- return "green";
153
+ return "green"; // vocabulary-ok: event kind code
142
154
  if (kind === "attention")
143
155
  return "magenta";
144
156
  if (kind === "problem")
145
- return (e.type === "tick_end" ? resultTone(result) : undefined) ?? "red";
157
+ return (e.type === "run_end" ? resultTone(result) : undefined) ?? "red";
146
158
  if (kind === "routine")
147
159
  return "dim";
148
160
  return undefined;
@@ -1,3 +1,4 @@
1
+ // vocabulary: plain
1
2
  /** The TUI's History pane body: the completed-tick rows `tumwater history` prints (and the
2
3
  * dashboard's History view renders), one line each, newest first. history-data.ts already
3
4
  * builds the rows (`readTickRows`); this only renders them for the pane, so the TUI, the CLI,
@@ -6,19 +7,23 @@
6
7
  * rows and their fields, not the CLI table's padding. Pure: a function of the rows it is
7
8
  * handed. */
8
9
  import { tickRowCells } from "../../history/history-data.js";
9
- /** The pane body for a set of tick rows: `time loop #tick result duration`, then `usage`
10
- * and `detail` when they carry anything, joined newest-first by newlines. The cells come from
11
- * history-data.ts's tickRowCells, the one home the CLI's table builds on too, so a null
12
- * `durationMs` (the tick's start sits outside the scanned window) shows the em dash here the
13
- * same way; an empty `usage` (neither tokens nor cost) and an empty
10
+ /** The pane body for a set of tick rows: `time loop #tick result duration`, then `model`
11
+ * (between the result and the duration), and `usage` and `detail` when they carry anything,
12
+ * joined newest-first by newlines. The cells come from history-data.ts's tickRowCells, the one
13
+ * home the CLI's table builds on too, so a null `durationMs` (the tick's start sits outside
14
+ * the scanned window) shows the em dash here the same way; an empty `model` (the start named
15
+ * none or sat outside the window), an empty `usage` (neither tokens nor cost), and an empty
14
16
  * `detail` are omitted rather than leaving a gap. No rows renders `""`, which the pane's
15
17
  * `emptyNote` replaces. `row.result` is the string history-data.ts resolved (including a
16
- * joined landing verdict), never reclassified here. */
18
+ * joined merge verdict), never reclassified here. */
17
19
  export function historyPaneText(rows) {
18
20
  return rows
19
21
  .map((row) => {
20
22
  const { duration } = tickRowCells(row);
21
- const parts = [row.time, `${row.loop} #${row.tick}`, row.result, duration];
23
+ const parts = [row.time, `${row.role} #${row.run}`, row.result];
24
+ if (row.model)
25
+ parts.push(row.model);
26
+ parts.push(duration);
22
27
  if (row.usage)
23
28
  parts.push(row.usage);
24
29
  if (row.detail)