tumwater 0.4.2 → 0.4.3

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 (305) hide show
  1. package/README.md +6 -6
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-backend.js +6 -6
  4. package/dist/src/agent/{run-agent.js → agent-run.js} +25 -12
  5. package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
  6. package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
  7. package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
  8. package/dist/src/agent/backend-traits.js +2 -2
  9. package/dist/src/agent/claude/claude-account.js +1 -8
  10. package/dist/src/agent/claude/claude-backend.js +3 -3
  11. package/dist/src/agent/claude/claude-env.js +27 -31
  12. package/dist/src/agent/claude/claude-stream.js +111 -54
  13. package/dist/src/agent/codex/codex-account.js +9 -0
  14. package/dist/src/agent/codex/codex-backend.js +2 -2
  15. package/dist/src/agent/codex/codex-env.js +28 -15
  16. package/dist/src/agent/codex/codex-hook-trust.js +39 -20
  17. package/dist/src/agent/codex/codex-rollout.js +93 -56
  18. package/dist/src/agent/codex/codex-stream.js +1 -1
  19. package/dist/src/agent/env-scrub.js +18 -0
  20. package/dist/src/agent/image-input.js +68 -11
  21. package/dist/src/agent/notes-shim.js +2 -2
  22. package/dist/src/agent/opencode/opencode-backend.js +2 -2
  23. package/dist/src/agent/opencode/opencode-catalog.js +50 -27
  24. package/dist/src/agent/opencode/opencode-config.js +32 -14
  25. package/dist/src/agent/opencode/opencode-env.js +12 -13
  26. package/dist/src/agent/opencode/opencode-stream.js +47 -25
  27. package/dist/src/agent/session-ledger.js +1 -1
  28. package/dist/src/agent/subscription-account.js +3 -3
  29. package/dist/src/backlog/backlog-cli-view.js +81 -41
  30. package/dist/src/backlog/backlog-cli.js +57 -26
  31. package/dist/src/backlog/backlog-eligibility.js +58 -102
  32. package/dist/src/backlog/backlog-md.js +5 -5
  33. package/dist/src/backlog/backlog-parts.js +65 -27
  34. package/dist/src/backlog/backlog-release.js +12 -12
  35. package/dist/src/backlog/backlog-structure.js +114 -61
  36. package/dist/src/backlog/backlog.js +1 -1
  37. package/dist/src/baseline/main-baseline.js +148 -84
  38. package/dist/src/budget/budget-fallback-view.js +100 -243
  39. package/dist/src/budget/budget-wiring.js +29 -65
  40. package/dist/src/budget/fallback-breaker.js +2 -2
  41. package/dist/src/build/build-check-detect.js +43 -14
  42. package/dist/src/build/build-check-events.js +2 -2
  43. package/dist/src/build/build-check-scoped.js +40 -16
  44. package/dist/src/build/build-check.js +13 -5
  45. package/dist/src/cli/cli-command-args.js +89 -52
  46. package/dist/src/cli/cli-flag-specs.js +42 -22
  47. package/dist/src/cli/cli-init.js +34 -12
  48. package/dist/src/cli/cli-query-commands.js +1 -1
  49. package/dist/src/cli/cli-repo-gate.js +2 -2
  50. package/dist/src/cli/cli-start.js +91 -58
  51. package/dist/src/cli/completion.js +82 -46
  52. package/dist/src/cli/config-commands.js +82 -71
  53. package/dist/src/cli/help.js +4 -6
  54. package/dist/src/cli/log-commands.js +43 -23
  55. package/dist/src/cli/notify-command.js +2 -2
  56. package/dist/src/cli/question-commands.js +86 -60
  57. package/dist/src/cli.js +181 -135
  58. package/dist/src/collections.js +31 -16
  59. package/dist/src/concurrency/check-slots.js +70 -0
  60. package/dist/src/concurrency/lock.js +22 -0
  61. package/dist/src/concurrency/run-permit.js +49 -0
  62. package/dist/src/concurrency/semaphore.js +46 -250
  63. package/dist/src/config/config-editable-keys.js +4 -7
  64. package/dist/src/config/config-fallbacks.js +81 -101
  65. package/dist/src/config/config-field-checks.js +66 -37
  66. package/dist/src/config/config-keys.js +5 -20
  67. package/dist/src/config/config-live.js +4 -28
  68. package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
  69. package/dist/src/config/config-request.js +153 -102
  70. package/dist/src/config/config-schema.js +9 -11
  71. package/dist/src/config/config-selector-checks.js +1 -1
  72. package/dist/src/config/config-validation-agents.js +2 -2
  73. package/dist/src/config/config-validation-roles.js +4 -52
  74. package/dist/src/config/config-validation.js +4 -38
  75. package/dist/src/config/config-views.js +9 -34
  76. package/dist/src/config/config-write.js +6 -23
  77. package/dist/src/config/config.js +4 -5
  78. package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
  79. package/dist/src/doctor/doctor-backlog-plans.js +24 -24
  80. package/dist/src/doctor/doctor-backlog.js +7 -5
  81. package/dist/src/doctor/doctor-check-names.js +3 -6
  82. package/dist/src/doctor/doctor-checks.js +5 -5
  83. package/dist/src/doctor/doctor-claude-checks.js +44 -28
  84. package/dist/src/doctor/doctor-codex-checks.js +6 -6
  85. package/dist/src/doctor/doctor-contention-checks.js +44 -112
  86. package/dist/src/doctor/doctor-fallback-checks.js +2 -2
  87. package/dist/src/doctor/doctor-holds.js +6 -45
  88. package/dist/src/doctor/doctor-install.js +1 -1
  89. package/dist/src/doctor/doctor-launch-services.js +1 -1
  90. package/dist/src/doctor/doctor-model-checks.js +178 -94
  91. package/dist/src/doctor/doctor-notify.js +2 -2
  92. package/dist/src/doctor/doctor-opencode-checks.js +5 -4
  93. package/dist/src/doctor/doctor-orphans.js +120 -20
  94. package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
  95. package/dist/src/doctor/doctor-pi-checks.js +2 -2
  96. package/dist/src/doctor/doctor-principles.js +3 -3
  97. package/dist/src/doctor/doctor-project-checks.js +3 -3
  98. package/dist/src/doctor/doctor-role-notes.js +1 -1
  99. package/dist/src/doctor/doctor.js +8 -11
  100. package/dist/src/events/event-format.js +30 -54
  101. package/dist/src/events/event-labels.js +1 -5
  102. package/dist/src/events/event-legacy.js +1 -3
  103. package/dist/src/events/{notify.js → event-notify.js} +2 -2
  104. package/dist/src/events/event-read.js +1 -1
  105. package/dist/src/failure/failure-cluster.js +6 -6
  106. package/dist/src/failure/failure-data.js +8 -4
  107. package/dist/src/failure/failure-render.js +44 -34
  108. package/dist/src/failure/failure-state-change.js +4 -34
  109. package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
  110. package/dist/src/files/files.js +30 -0
  111. package/dist/src/files/json-object.js +8 -8
  112. package/dist/src/files/stat-cache.js +12 -8
  113. package/dist/src/gates/budget-gates.js +85 -84
  114. package/dist/src/gates/gate-polls.js +27 -68
  115. package/dist/src/gates/pause-gates.js +16 -2
  116. package/dist/src/gates/readiness.js +1 -1
  117. package/dist/src/gates/role-cap-gates.js +44 -35
  118. package/dist/src/gates/{startup-gate.js → startup-gates.js} +7 -7
  119. package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
  120. package/dist/src/git/git.js +75 -45
  121. package/dist/src/git/worktree-pool.js +3 -6
  122. package/dist/src/gui/gui-admin-endpoints.js +3 -3
  123. package/dist/src/gui/gui-backlog-commands.js +4 -4
  124. package/dist/src/gui/gui-backlog-endpoints.js +61 -41
  125. package/dist/src/gui/gui-command.js +43 -24
  126. package/dist/src/gui/gui-history-endpoints.js +100 -52
  127. package/dist/src/gui/gui-operator-commands.js +22 -23
  128. package/dist/src/gui/gui-prompt-commands.js +23 -23
  129. package/dist/src/gui/gui-report-endpoints.js +1 -1
  130. package/dist/src/history/history-data.js +71 -44
  131. package/dist/src/history/history-queued.js +49 -27
  132. package/dist/src/history/history.js +51 -33
  133. package/dist/src/inbox/prompt-commands.js +59 -42
  134. package/dist/src/init/init.js +88 -40
  135. package/dist/src/merge/merge-backlog-conflicts.js +1 -1
  136. package/dist/src/merge/merge-core.js +1 -1
  137. package/dist/src/merge/merge-drain.js +8 -11
  138. package/dist/src/merge/merge-pipeline.js +6 -7
  139. package/dist/src/merge/merge-post-review-drain.js +10 -13
  140. package/dist/src/merge/merge-requeue.js +1 -1
  141. package/dist/src/merge/merge-slot.js +0 -8
  142. package/dist/src/merge/merge-verify.js +8 -18
  143. package/dist/src/notes-cli.js +52 -42
  144. package/dist/src/operator/operator-commands.js +76 -44
  145. package/dist/src/operator/operator-intent.js +3 -3
  146. package/dist/src/orchestrator/orchestrator-launch.js +31 -63
  147. package/dist/src/orchestrator/orchestrator-poll.js +27 -32
  148. package/dist/src/orchestrator/orchestrator.js +8 -21
  149. package/dist/src/orchestrator/scheduling-admit.js +101 -68
  150. package/dist/src/orchestrator/scheduling-scope.js +1 -1
  151. package/dist/src/orchestrator/scheduling-skip.js +1 -1
  152. package/dist/src/orchestrator/startup-migrations.js +1 -1
  153. package/dist/src/paths.js +8 -7
  154. package/dist/src/pi/pi-args.js +5 -2
  155. package/dist/src/pi/pi-backend.js +2 -2
  156. package/dist/src/pi/pi-bin.js +3 -3
  157. package/dist/src/pi/pi-event-line.js +1 -1
  158. package/dist/src/pi/pi-models.js +43 -29
  159. package/dist/src/pi/pi-settings.js +43 -20
  160. package/dist/src/pi/pi-stream.js +32 -6
  161. package/dist/src/pi/pi-watchdogs.js +12 -5
  162. package/dist/src/pi/pi.js +2 -2
  163. package/dist/src/pi-extension/context-shake.js +32 -20
  164. package/dist/src/pi-extension/kill-guard.js +69 -0
  165. package/dist/src/process/process-group.js +49 -5
  166. package/dist/src/process/process.js +8 -8
  167. package/dist/src/process/run-marker.js +31 -13
  168. package/dist/src/process/supervisor.js +40 -28
  169. package/dist/src/prompt/dialect.js +1 -1
  170. package/dist/src/prompt/gate-notes.js +2 -2
  171. package/dist/src/prompt/notes-backlog.js +66 -37
  172. package/dist/src/prompt/principles.js +6 -4
  173. package/dist/src/prompt/prompt-followup.js +12 -4
  174. package/dist/src/prompt/prompt.js +88 -38
  175. package/dist/src/report/report-data.js +53 -37
  176. package/dist/src/report/report-fold.js +16 -9
  177. package/dist/src/report/report-render.js +21 -194
  178. package/dist/src/report/report-since-data.js +4 -3
  179. package/dist/src/report/report-since-render.js +173 -0
  180. package/dist/src/report/report-usage-fold.js +8 -8
  181. package/dist/src/report/report.js +46 -22
  182. package/dist/src/review/post-merge-review-render.js +9 -3
  183. package/dist/src/review/post-merge-review.js +1 -1
  184. package/dist/src/review/review-precheck.js +11 -1
  185. package/dist/src/review/review.js +40 -153
  186. package/dist/src/review/secret-leaks.js +26 -16
  187. package/dist/src/roles/role-catalog.js +11 -3
  188. package/dist/src/roles/role-guidance.js +16 -12
  189. package/dist/src/roles/role-render.js +1 -14
  190. package/dist/src/roles/role-view.js +9 -21
  191. package/dist/src/run/acceptance-greps.js +88 -45
  192. package/dist/src/run/run-apply.js +12 -6
  193. package/dist/src/run/run-detail-data.js +121 -68
  194. package/dist/src/run/run-detail-render.js +92 -35
  195. package/dist/src/run/run-detail.js +72 -41
  196. package/dist/src/run/run-prompt.js +63 -50
  197. package/dist/src/run/run-resume.js +76 -29
  198. package/dist/src/run/run-stage.js +135 -84
  199. package/dist/src/run/run-timing.js +8 -2
  200. package/dist/src/run/run-usage.js +31 -1
  201. package/dist/src/run/run-verdict.js +119 -50
  202. package/dist/src/run/stage-check.js +49 -32
  203. package/dist/src/run/stage-checks-shared.js +28 -0
  204. package/dist/src/run/stage-name-checks.js +111 -92
  205. package/dist/src/run/stage-path-checks.js +69 -42
  206. package/dist/src/run/stage-prose-checks.js +55 -40
  207. package/dist/src/runner/runner-outcome.js +6 -1
  208. package/dist/src/runner/runner-pi.js +3 -10
  209. package/dist/src/runner/runner-recovery.js +1 -1
  210. package/dist/src/runner/{revision.js → runner-revision.js} +1 -1
  211. package/dist/src/runner/runner-run.js +97 -35
  212. package/dist/src/runner/runner-state-load.js +12 -9
  213. package/dist/src/runner/runner.js +41 -25
  214. package/dist/src/scheduling/claims.js +0 -4
  215. package/dist/src/scheduling/scheduling.js +53 -66
  216. package/dist/src/status/status-data.js +2 -2
  217. package/dist/src/status/status-role-notes.js +2 -2
  218. package/dist/src/status/status-roles.js +9 -17
  219. package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
  220. package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
  221. package/dist/src/team/team-hold.js +98 -65
  222. package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +2 -15
  223. package/dist/src/team/team-polls.js +28 -126
  224. package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
  225. package/dist/src/team/team-state.js +4 -13
  226. package/dist/src/text/markdown.js +1 -1
  227. package/dist/src/text/phrases.js +20 -15
  228. package/dist/src/text/text.js +3 -3
  229. package/dist/src/ui/alert-feeds.js +49 -73
  230. package/dist/src/ui/badges.js +3 -13
  231. package/dist/src/ui/gui/gui-client-boot.js +2 -2
  232. package/dist/src/ui/gui/gui-client-composer.js +1 -1
  233. package/dist/src/ui/gui/gui-client-drawer-config.js +7 -17
  234. package/dist/src/ui/gui/gui-client-drawer.js +1 -1
  235. package/dist/src/ui/gui/gui-client-history.js +28 -14
  236. package/dist/src/ui/gui/gui-client-model.js +0 -12
  237. package/dist/src/ui/gui/gui-client-pending.js +7 -6
  238. package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
  239. package/dist/src/ui/gui/gui-client-report.js +1 -1
  240. package/dist/src/ui/gui/gui-client-roles.js +0 -1
  241. package/dist/src/ui/gui/gui-client-settings.js +43 -84
  242. package/dist/src/ui/gui/gui-client-team.js +4 -4
  243. package/dist/src/ui/gui/gui-client.js +7 -1
  244. package/dist/src/ui/progress-data.js +53 -40
  245. package/dist/src/ui/run-progress-model.js +9 -2
  246. package/dist/src/ui/status-model.js +2 -24
  247. package/dist/src/ui/status-payload.js +3 -7
  248. package/dist/src/ui/status-render.js +5 -9
  249. package/dist/src/ui/team-alert-backlog.js +6 -6
  250. package/dist/src/ui/team-alert-status.js +9 -170
  251. package/dist/src/ui/team-alert-tail.js +19 -215
  252. package/dist/src/ui/team-alerts.js +7 -8
  253. package/dist/src/ui/transcript-run.js +27 -16
  254. package/dist/src/ui/tui/tui-editor-answer.js +12 -10
  255. package/dist/src/ui/tui/tui-editor-attach.js +4 -3
  256. package/dist/src/ui/tui/tui-editor-backends.js +4 -3
  257. package/dist/src/ui/tui/tui-editor-brief.js +6 -9
  258. package/dist/src/ui/tui/tui-editor-budget.js +4 -3
  259. package/dist/src/ui/tui/tui-editor-config.js +80 -68
  260. package/dist/src/ui/tui/tui-editor-entry.js +12 -20
  261. package/dist/src/ui/tui/tui-editor-pause.js +51 -57
  262. package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
  263. package/dist/src/ui/tui/tui-editor-queued.js +11 -14
  264. package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
  265. package/dist/src/ui/tui/tui-editor-release.js +4 -3
  266. package/dist/src/ui/tui/tui-editor-retire.js +7 -7
  267. package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
  268. package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
  269. package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
  270. package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
  271. package/dist/src/ui/tui/tui-editor-search.js +4 -3
  272. package/dist/src/ui/tui/tui-editor-session.js +17 -4
  273. package/dist/src/ui/tui/tui-editors.js +1 -7
  274. package/dist/src/ui/tui/tui-frame.js +182 -134
  275. package/dist/src/ui/tui/tui-guard.js +20 -0
  276. package/dist/src/ui/tui/tui-input.js +159 -92
  277. package/dist/src/ui/tui/tui-keymap.js +51 -29
  278. package/dist/src/ui/tui/tui-keys.js +1 -1
  279. package/dist/src/ui/tui/tui-pane-activity.js +3 -3
  280. package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
  281. package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
  282. package/dist/src/ui/tui/tui-pane-health.js +252 -228
  283. package/dist/src/ui/tui/tui-pane-history.js +61 -70
  284. package/dist/src/ui/tui/tui-pane-pending.js +284 -247
  285. package/dist/src/ui/tui/tui-pane-queued.js +25 -27
  286. package/dist/src/ui/tui/tui-pane-report.js +2 -1
  287. package/dist/src/ui/tui/tui-pane-session.js +11 -7
  288. package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
  289. package/dist/src/ui/tui/tui-pane.js +14 -11
  290. package/dist/src/ui/tui/tui-panes.js +29 -32
  291. package/dist/src/ui/tui/tui.js +3 -4
  292. package/dist/src/verdict/fix-claim.js +76 -54
  293. package/dist/src/verdict/refusal.js +1 -1
  294. package/package.json +5 -5
  295. package/dist/src/concurrency/provider-lease.js +0 -105
  296. package/dist/src/runner/model-fallback.js +0 -76
  297. package/dist/src/runner/runner-fallback.js +0 -66
  298. package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
  299. package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
  300. /package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +0 -0
  301. /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
  302. /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
  303. /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
  304. /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
  305. /package/dist/src/runner/{leftover.js → runner-leftover.js} +0 -0
@@ -3,6 +3,7 @@ import { loadConfigSafe } from "../../config/config.js";
3
3
  import { DIRECTOR_ROLE } from "../../roles/roles.js";
4
4
  import { baseRoleOf } from "../../roles/runner-ids.js";
5
5
  import { viewedRole } from "./tui-editor-ctx.js";
6
+ import { savedPromptDraft } from "./tui-editor-session.js";
6
7
  /** The Ctrl+O per-role / Ctrl+S team-wide quiet-hours editor on the shared prompt line, the
7
8
  * terminal twin of the dashboard's `Quiet hours` rows and `tumwater role <id> --quiet-hours`
8
9
  * / `tumwater config set quietHours`. It owns its mode flags, its saved-draft pair, and the
@@ -18,7 +19,7 @@ import { viewedRole } from "./tui-editor-ctx.js";
18
19
  export function createQuietEditor(session) {
19
20
  let quietMode = false;
20
21
  let quietTeam = false;
21
- let restoreDraft = () => { };
22
+ const draft = savedPromptDraft(session);
22
23
  // The role scope pinned when the editor opened (per-role scope only), so Enter writes the
23
24
  // same role's window even if syncRoster shrinks or reorders the roster while the editor is
24
25
  // open. The team scope pins no role: it writes the top-level `quietHours` key.
@@ -32,14 +33,14 @@ export function createQuietEditor(session) {
32
33
  quietTeam = false;
33
34
  session.held.quiet = false;
34
35
  session.held.quietTeam = false;
35
- restoreDraft();
36
+ draft.restore();
36
37
  lockedRole = null;
37
38
  };
38
39
  // The shared way both open paths put the editor on the line: take the prompt draft to
39
40
  // restore on exit, raise the mode flags and the held registry for the chosen scope, pre-fill
40
41
  // the prompt text (cursor at its end) and flash the guidance line.
41
42
  const openQuietEditor = (team, existing, message) => {
42
- restoreDraft = session.takeDraft();
43
+ draft.save();
43
44
  quietMode = true;
44
45
  quietTeam = team;
45
46
  session.held.quiet = true;
@@ -1,4 +1,5 @@
1
1
  import { releaseQuarantinedTest } from "../../review/known-flakes.js";
2
+ import { savedPromptDraft } from "./tui-editor-session.js";
2
3
  /** The Failures view's Ctrl+X release editor on the shared prompt line, the terminal twin of
3
4
  * `tumwater quarantine --release <n>` and the dashboard Failures card's Release button. Like
4
5
  * the retire editor it owns its `releaseMode` flag, its saved-draft pair, and the handleKey
@@ -14,7 +15,7 @@ import { releaseQuarantinedTest } from "../../review/known-flakes.js";
14
15
  * STAYS open so the operator can fix the name; an empty name flashes and stays. */
15
16
  export function createQuarantineReleaseEditor(session) {
16
17
  let releaseMode = false;
17
- let restoreDraft = () => { };
18
+ const draft = savedPromptDraft(session);
18
19
  // Leave release-edit mode (Esc, Enter's success path, or Ctrl+T): restore the saved prompt
19
20
  // text.
20
21
  const exitReleaseMode = () => {
@@ -22,7 +23,7 @@ export function createQuarantineReleaseEditor(session) {
22
23
  return;
23
24
  releaseMode = false;
24
25
  session.held.release = false;
25
- restoreDraft();
26
+ draft.restore();
26
27
  };
27
28
  // Open release-edit mode on the Failures view's prompt line (Ctrl+X). The Failures index
28
29
  // guard keeps the same key's other arms (Transcript notes-clear, Backlog cancel, and the
@@ -38,7 +39,7 @@ export function createQuarantineReleaseEditor(session) {
38
39
  // Mid-recall, hand the arrows' saved draft back before the release editor takes the line —
39
40
  // the same settle the retire and role editors do — so leaving restores the draft, not a
40
41
  // history entry, with no stale recall state riding along.
41
- restoreDraft = session.takeDraft();
42
+ draft.save();
42
43
  releaseMode = true;
43
44
  session.held.release = true;
44
45
  session.input = "";
@@ -1,4 +1,6 @@
1
- import { artifactPhrase, retireRole } from "../../operator/retire.js";
1
+ import { artifactPhrase, retireRole } from "../../operator/operator-retire.js";
2
+ import { savedPromptDraft } from "./tui-editor-session.js";
3
+ import { flashReject } from "./tui-guard.js";
2
4
  /** The Health view's Ctrl+X retire editor on the shared prompt line, the terminal twin of
3
5
  * `tumwater retire --role <id>` and the dashboard Settings card's Retire button. It owns its
4
6
  * `retireMode` flag, its saved-draft pair, and the handleKey arms for its key (the toggle with
@@ -11,14 +13,14 @@ import { artifactPhrase, retireRole } from "../../operator/retire.js";
11
13
  * so the operator can fix the id, Esc cancels and restores the draft. */
12
14
  export function createRetireEditor(session) {
13
15
  let retireMode = false;
14
- let restoreDraft = () => { };
16
+ const draft = savedPromptDraft(session);
15
17
  // Leave retire-edit mode (Esc, Enter's success path, or Ctrl+T): restore the saved prompt text.
16
18
  const exitRetireMode = () => {
17
19
  if (!retireMode)
18
20
  return;
19
21
  retireMode = false;
20
22
  session.held.retire = false;
21
- restoreDraft();
23
+ draft.restore();
22
24
  };
23
25
  // Ctrl+X toggles retire-edit mode on the Health view's prompt line (mnemonic: **x**
24
26
  // remove — the same key the Transcript notes-clear, Backlog cancel, and Inbox cancel
@@ -37,7 +39,7 @@ export function createRetireEditor(session) {
37
39
  // Mid-recall, hand the arrows' saved draft back before the retire editor takes the line —
38
40
  // the same settle the role editors do — so leaving restores the draft, not a
39
41
  // history entry, with no stale recall state riding along.
40
- restoreDraft = session.takeDraft();
42
+ draft.save();
41
43
  retireMode = true;
42
44
  session.held.retire = true;
43
45
  session.input = "";
@@ -68,9 +70,7 @@ export function createRetireEditor(session) {
68
70
  session.flashMessage(removed.length === 0
69
71
  ? `nothing to remove for role ${role}`
70
72
  : removed.map((a) => `removed ${artifactPhrase(a, role)}`).join("; "));
71
- }, (err) => {
72
- session.flashError(err);
73
- });
73
+ }, flashReject(session));
74
74
  return true;
75
75
  };
76
76
  const handleKey = (_str, key, ctx) => {
@@ -2,6 +2,7 @@ import { defaultConfig, knownRoleIds, loadConfigSafe } from "../../config/config
2
2
  import { setConfigKey } from "../../config/config-write.js";
3
3
  import { DIRECTOR_ROLE, unknownRoleMessage } from "../../roles/roles.js";
4
4
  import { baseRoleOf } from "../../roles/runner-ids.js";
5
+ import { savedPromptDraft } from "./tui-editor-session.js";
5
6
  /** The Health view's Ctrl+V enable/disable editor on the shared prompt line, the terminal twin
6
7
  * of the dashboard Settings card's Enable and the role drawer's two-click Disable (and of the
7
8
  * CLI's `tumwater role <id> --enable`/`--disable`). It owns its `roleEnabledMode` flag, its
@@ -18,7 +19,7 @@ import { baseRoleOf } from "../../roles/runner-ids.js";
18
19
  export function createRoleEnabledEditor(session) {
19
20
  let roleEnabledMode = false;
20
21
  let confirmBase = null;
21
- let restoreDraft = () => { };
22
+ const draft = savedPromptDraft(session);
22
23
  // Leave enable/disable mode (Esc, Enter's success path, or Ctrl+T): restore the saved prompt
23
24
  // text and drop the armed disable confirm (the line no longer names it).
24
25
  const exitRoleEnabledMode = () => {
@@ -27,7 +28,7 @@ export function createRoleEnabledEditor(session) {
27
28
  roleEnabledMode = false;
28
29
  confirmBase = null;
29
30
  session.held.roleEnabled = false;
30
- restoreDraft();
31
+ draft.restore();
31
32
  };
32
33
  // Ctrl+V toggles enable/disable-edit mode on the Health view's prompt line (the same key
33
34
  // the Activity attach, Pending top, History revert, Inbox top, and Transcript
@@ -45,7 +46,7 @@ export function createRoleEnabledEditor(session) {
45
46
  return false; // off Health, keep the key's other meanings
46
47
  if (session.editorEntryDenied("roleEnabled"))
47
48
  return true;
48
- restoreDraft = session.takeDraft();
49
+ draft.save();
49
50
  roleEnabledMode = true;
50
51
  session.held.roleEnabled = true;
51
52
  session.input = "";
@@ -1,5 +1,5 @@
1
1
  import { viewedRole } from "./tui-editor-ctx.js";
2
- import { readEditorPath } from "./tui-editor-session.js";
2
+ import { readEditorPath, savedPromptDraft } from "./tui-editor-session.js";
3
3
  /** Build a role-located file-read editor. It takes the line only on a role's Transcript /
4
4
  * notes view (the caller's mayOpen guard) and only while no other editor holds the line, reads
5
5
  * the typed path whole, and writes through the caller's shared setter. A refused write flashes
@@ -8,7 +8,7 @@ import { readEditorPath } from "./tui-editor-session.js";
8
8
  export function createRoleFileEditor(session, spec) {
9
9
  // The role pinned when the editor opened, or null when closed.
10
10
  let editFor = null;
11
- let restoreDraft = () => { };
11
+ const draft = savedPromptDraft(session);
12
12
  // Leave the editor (Esc, the key again, or a successful Enter): restore the saved prompt
13
13
  // text with no write.
14
14
  const exit = () => {
@@ -16,7 +16,7 @@ export function createRoleFileEditor(session, spec) {
16
16
  return;
17
17
  editFor = null;
18
18
  session.held[spec.held] = null;
19
- restoreDraft();
19
+ draft.restore();
20
20
  };
21
21
  // The Ctrl+<openCtrl> open/toggle arm: toggles the editor out while open; otherwise opens on
22
22
  // the guarded view and elsewhere keeps its own meaning and falls through to the pane arms.
@@ -36,7 +36,7 @@ export function createRoleFileEditor(session, spec) {
36
36
  // same settle the role editors do — so leaving restores the draft, not a history entry,
37
37
  // with no stale recall state riding along.
38
38
  const role = viewedRole(ctx); // defined: the open-arm range guard above pins the view into roleIds
39
- restoreDraft = session.takeDraft();
39
+ draft.save();
40
40
  editFor = role;
41
41
  session.held[spec.held] = role;
42
42
  session.input = "";
@@ -1,12 +1,13 @@
1
1
  import { baseRoleOf } from "../../roles/runner-ids.js";
2
2
  import { viewedRole } from "./tui-editor-ctx.js";
3
+ import { savedPromptDraft } from "./tui-editor-session.js";
3
4
  /** Build a role-pinned editor. It may take the line only on a role's Transcript view
4
5
  * (view 1..roleIds.length) or when it already holds it (the toggle-out arm), and only while no
5
6
  * other editor holds the line. An invalid value flashes and STAYS in edit mode so the operator
6
7
  * can fix it; success restores the saved prompt text. */
7
8
  export function createRoleKeyEditor(session, spec) {
8
9
  let mode = false;
9
- let restoreDraft = () => { };
10
+ const draft = savedPromptDraft(session);
10
11
  // The role pinned when the editor opened, so Enter writes the same role's key even if
11
12
  // syncRoster shrinks or reorders the roster while the editor is open.
12
13
  let lockedRole = null;
@@ -16,7 +17,7 @@ export function createRoleKeyEditor(session, spec) {
16
17
  return;
17
18
  mode = false;
18
19
  session.held[spec.mode] = false;
19
- restoreDraft();
20
+ draft.restore();
20
21
  lockedRole = null;
21
22
  };
22
23
  // The Ctrl+<key> arm: while open it toggles the editor out. Otherwise it opens only on a
@@ -37,7 +38,7 @@ export function createRoleKeyEditor(session, spec) {
37
38
  // source; a broken config degrades to no pre-fill and the write reports the error.
38
39
  lockedRole = role;
39
40
  const existing = spec.prefill(session, baseRoleOf(role));
40
- restoreDraft = session.takeDraft();
41
+ draft.save();
41
42
  mode = true;
42
43
  session.held[spec.mode] = true;
43
44
  session.input = existing;
@@ -1,28 +1,110 @@
1
1
  import { setRoleModelFields } from "../../config/config-write.js";
2
2
  import { rawRoleField } from "../../roles/role-view.js";
3
3
  import { createRoleKeyEditor } from "./tui-editor-role-key.js";
4
+ /** Parse a Ctrl+X model-overrides line into its triple. The line is whitespace-separated
5
+ * tokens: a bare token is the model (so today's single-token input keeps working), and
6
+ * `provider=<value>`, `model=<value>` and `thinking=<level>` set those fields explicitly.
7
+ * A field the line leaves out stays `""` — the caller clears it. A second model token (a
8
+ * second bare token, or a bare token plus `model=`, or a repeated `model=`), any other
9
+ * repeated field, a `key=` that names no field of the triple (the error names the three
10
+ * accepted keys), or an empty value such as `thinking=` is an input error. */
11
+ export function parseRoleModelLine(line) {
12
+ const tokens = line.trim().split(/\s+/).filter((t) => t.length > 0);
13
+ const fields = { provider: "", model: "", thinking: "" };
14
+ for (const token of tokens) {
15
+ const eq = token.indexOf("=");
16
+ // A bare token IS the model; `model=<value>` is the explicit spelling of the same slot.
17
+ const key = eq === -1 ? "model" : token.slice(0, eq);
18
+ const value = eq === -1 ? token : token.slice(eq + 1);
19
+ if (key !== "provider" && key !== "model" && key !== "thinking")
20
+ return {
21
+ ok: false,
22
+ error: `unknown key ${key}: only provider, model and thinking are accepted`,
23
+ };
24
+ if (value === "")
25
+ return { ok: false, error: `${key}= needs a value` };
26
+ if (fields[key] !== "")
27
+ return {
28
+ ok: false,
29
+ error: key === "model"
30
+ ? "the model may be given only once (a bare token or model=, not both or twice)"
31
+ : `repeated ${key}=`,
32
+ };
33
+ fields[key] = value;
34
+ }
35
+ return { ok: true, fields };
36
+ }
37
+ /** Format a triple as the Ctrl+X line's pre-fill, the inverse of parseRoleModelLine: the model
38
+ * first as a bare token, then `provider=…` then `thinking=…`, each only when set. */
39
+ export function formatRoleModelLine(fields) {
40
+ const parts = [];
41
+ if (fields.model)
42
+ parts.push(fields.model);
43
+ if (fields.provider)
44
+ parts.push(`provider=${fields.provider}`);
45
+ if (fields.thinking)
46
+ parts.push(`thinking=${fields.thinking}`);
47
+ return parts.join(" ");
48
+ }
4
49
  /** The Ctrl+X role-model editor on the Transcript view's shared prompt line, the terminal twin
5
- * of `tumwater role <id> --model` / `--clear-model` and the dashboard role drawer's Model
6
- * overrides editor. Enter writes the viewed base role's `roles.<base>.model` through the shared
7
- * setRoleModelFields the CLI and the drawer use — so checkModelTripleField (a non-empty model id
8
- * or a bare tier name like `strong`) owns the value's truth, and an empty line clears the
9
- * override. Pre-fill reads the FILE's raw `roles.<base>.model`: overlayDefaults seeds every
10
- * built-in role's model from the catalog, so the merged value cannot tell an operator override
11
- * from a seeded default. On the notes view Ctrl+X keeps its clear-notes meaning, and on every
12
- * non-Transcript view it falls through to its own pane arms (the backlog cancel, the queued
13
- * cancel, the Health retire). The shape it shares with the instances editor lives in
14
- * createRoleKeyEditor. */
50
+ * of `tumwater role <id> --model` / `--provider` / `--thinking` and the dashboard role
51
+ * drawer's Model overrides editor. Enter parses the whole triple on one line and writes all
52
+ * three through the shared setRoleModelFields the CLI and the drawer use — so validateConfig's
53
+ * triple check owns the values (a bad thinking level writes nothing and flashes with the editor
54
+ * still open). A field the line leaves out is cleared, and an empty line clears all three.
55
+ * `backend` stays out of the line and is never touched. Pre-fill reads the FILE's raw
56
+ * `roles.<base>.provider` / `.model` / `.thinking`: overlayDefaults seeds every built-in role's
57
+ * model from the catalog, so the merged value cannot tell an operator override from a seeded
58
+ * default (and the seed is not offered as a pre-fill). On the notes view Ctrl+X keeps its
59
+ * clear-notes meaning, and on every non-Transcript view it falls through to its own pane arms
60
+ * (the backlog cancel, the queued cancel, the Health retire). The shape it shares with the
61
+ * instances and interval editors lives in createRoleKeyEditor. */
15
62
  export function createRoleModelEditor(session) {
16
63
  return createRoleKeyEditor(session, {
17
64
  mode: "roleModel",
18
65
  openCtrl: "x",
19
66
  yieldsOn: (ctx) => ctx.noteMode,
20
67
  prefill: (s, base) => {
21
- const existing = rawRoleField(s.root, base, "model");
22
- return typeof existing === "string" ? existing : "";
68
+ const provider = rawRoleField(s.root, base, "provider");
69
+ const model = rawRoleField(s.root, base, "model");
70
+ const thinking = rawRoleField(s.root, base, "thinking");
71
+ return formatRoleModelLine({
72
+ provider: typeof provider === "string" ? provider : "",
73
+ model: typeof model === "string" ? model : "",
74
+ thinking: typeof thinking === "string" ? thinking : "",
75
+ });
76
+ },
77
+ openFlash: (role) => `edit ${role}'s model overrides (model, provider=<provider>, thinking=<level>; a field you leave out is cleared, an empty line clears all): Enter to save, Esc to cancel`,
78
+ commit: (s, base, text) => {
79
+ const parsed = parseRoleModelLine(text);
80
+ if (!parsed.ok)
81
+ return parsed;
82
+ // The line IS the whole triple: a field it leaves out is "" and thus cleared, an empty
83
+ // field string deletes the file key, and clearing the last override removes roles.<base>
84
+ // (the setter's rule). `undefined` is never used, so `backend` is never touched.
85
+ const { provider, model, thinking } = parsed.fields;
86
+ const result = setRoleModelFields(s.root, base, { provider, model, thinking });
87
+ if (result.ok && result.unchanged)
88
+ return { ok: true, noop: true };
89
+ return result;
90
+ },
91
+ savedFlash: (role, text) => {
92
+ const parsed = parseRoleModelLine(text);
93
+ if (!parsed.ok)
94
+ return `${role}: model overrides saved`;
95
+ const { provider, model, thinking } = parsed.fields;
96
+ const set = [];
97
+ if (model)
98
+ set.push(`model ${model}`);
99
+ if (provider)
100
+ set.push(`provider ${provider}`);
101
+ if (thinking)
102
+ set.push(`thinking ${thinking}`);
103
+ if (set.length === 0)
104
+ return `${role}: all model overrides cleared`;
105
+ const rest = set.length === 3 ? "" : "; the rest cleared";
106
+ return `${role}: model overrides set to ${set.join(", ")}${rest}`;
23
107
  },
24
- openFlash: (role) => `edit ${role}'s model override (a model id or tier name like strong; empty clears): Enter to save, Esc to cancel`,
25
- commit: (s, base, text) => setRoleModelFields(s.root, base, { model: text }),
26
- savedFlash: (role, text) => text === "" ? `${role}: model override cleared` : `${role}: model override set to ${text}`,
108
+ noopFlash: (role) => `${role}: no model override changed`,
27
109
  });
28
110
  }
@@ -1,3 +1,4 @@
1
+ import { savedPromptDraft } from "./tui-editor-session.js";
1
2
  /** The shared `/` search editor on the prompt line: over the History pane's run rows or the
2
3
  * Activity feed's events. It owns its mode flag (`searchMode`), its target pane (`searchFor`),
3
4
  * its saved-draft pair, and the applyHistorySearch / applyActivitySearch leaves. Mutually
@@ -8,7 +9,7 @@ export function createSearchEditor(session) {
8
9
  // Backlog pane's entry search, or a role's live-transcript search. The target is captured when
9
10
  // `/` opens so a roster change mid-edit cannot retarget Enter.
10
11
  let searchFor = "history";
11
- let restoreDraft = () => { };
12
+ const draft = savedPromptDraft(session);
12
13
  // Leave the shared search editor (Enter applies, Esc cancels, Ctrl+T cycles): restore the
13
14
  // saved prompt text, and reset the recall state with it — the same settle contract the budget
14
15
  // and role-prompt editors honor. The applied activityGrep/historyGrep itself persists across
@@ -18,7 +19,7 @@ export function createSearchEditor(session) {
18
19
  return;
19
20
  searchMode = false;
20
21
  session.held.search = false;
21
- restoreDraft();
22
+ draft.restore();
22
23
  };
23
24
  // `/` opens a free-text search on the prompt line (the pager's search key; this project
24
25
  // already reserves `/` for a meta action): over the History pane's run rows on History,
@@ -32,7 +33,7 @@ export function createSearchEditor(session) {
32
33
  !(ctx.view === 0 || ctx.view === ctx.roleIds.length + 1 || ctx.view === ctx.roleIds.length + 4 || ctx.view === ctx.roleIds.length + 6 ||
33
34
  (ctx.view >= 1 && ctx.view <= ctx.roleIds.length && !ctx.noteMode && !ctx.changeMode)))
34
35
  return false;
35
- restoreDraft = session.takeDraft();
36
+ draft.save();
36
37
  if (ctx.view === 0)
37
38
  searchFor = "activity";
38
39
  else if (ctx.view === ctx.roleIds.length + 1)
@@ -13,7 +13,6 @@ const HOLDER_SUBJECTS = [
13
13
  ["model", "model edit"],
14
14
  ["interval", "min run interval"],
15
15
  ["instructions", "instructions edit"],
16
- ["providerCaps", "provider caps"],
17
16
  ["concurrency", "run cap edit"],
18
17
  ["slots", "worktree slots"],
19
18
  ["notify", "notify hook"],
@@ -21,7 +20,6 @@ const HOLDER_SUBJECTS = [
21
20
  ["fallback", "fallback edit"],
22
21
  ["roleModel", "role model edit"],
23
22
  ["instances", "role instances"],
24
- ["claudeReserve", "claude reserve"],
25
23
  ["checks", "check cap edit"],
26
24
  ["check", "declared-check edit"],
27
25
  ["budget", "budget edit"],
@@ -55,7 +53,6 @@ export function createTuiEditorSession(deps) {
55
53
  quietTeam: false,
56
54
  interval: false,
57
55
  instructions: null,
58
- providerCaps: false,
59
56
  concurrency: false,
60
57
  slots: false,
61
58
  notify: false,
@@ -64,7 +61,6 @@ export function createTuiEditorSession(deps) {
64
61
  roleModel: false,
65
62
  model: false,
66
63
  instances: false,
67
- claudeReserve: false,
68
64
  checks: false,
69
65
  check: false,
70
66
  },
@@ -165,3 +161,20 @@ export function readEditorPath(session) {
165
161
  }
166
162
  return text;
167
163
  }
164
+ /** One prompt-line editor's mutable saved-draft pair: `save` takes the prompt line's current
165
+ * draft (settling recall, via takeDraft) and `restore` puts it back — a no-op until a `save`
166
+ * happens, so an exit path that never took the line is safe. The shared entry and exit every
167
+ * editor that takes the prompt line implements; owning the pair here removes the repeated
168
+ * `let restoreDraft = () => {}` / `restoreDraft = session.takeDraft()` / `restoreDraft()`
169
+ * triple each editor module hand-rolled, so the save/restore contract is stated once instead of
170
+ * per editor. `save` may run again to re-park a newer draft, like the editors that take the
171
+ * line from more than one arm. */
172
+ export function savedPromptDraft(session) {
173
+ let restoreDraft = () => { };
174
+ return {
175
+ save: () => {
176
+ restoreDraft = session.takeDraft();
177
+ },
178
+ restore: () => restoreDraft(),
179
+ };
180
+ }
@@ -4,8 +4,6 @@ import { pushPromptHistory, recallPromptHistory } from "./tui-prompt-history.js"
4
4
  import { createTuiEditorSession } from "./tui-editor-session.js";
5
5
  import { createBudgetEditor } from "./tui-editor-budget.js";
6
6
  import { createFallbackEditor } from "./tui-editor-fallback.js";
7
- import { createProviderCapsEditor } from "./tui-editor-provider-caps.js";
8
- import { createClaudeReserveEditor } from "./tui-editor-claude-reserve.js";
9
7
  import { createNotifyEditor } from "./tui-editor-notify.js";
10
8
  import { createBackendsEditor } from "./tui-editor-backends.js";
11
9
  import { createConcurrencyEditor } from "./tui-editor-concurrency.js";
@@ -47,8 +45,6 @@ export function createTuiEditors(deps) {
47
45
  const editors = [
48
46
  createBudgetEditor(session),
49
47
  createFallbackEditor(session),
50
- createProviderCapsEditor(session),
51
- createClaudeReserveEditor(session),
52
48
  createNotifyEditor(session),
53
49
  createBackendsEditor(session),
54
50
  createConcurrencyEditor(session),
@@ -122,7 +118,7 @@ export function createTuiEditors(deps) {
122
118
  // above already returned), and budget, pause and search modes keep them out —
123
119
  // recalling a prompt into the cap/field text would be a paste, not a recall. No
124
120
  // history in this direction: fall through to applyKey, which ignores arrows as before.
125
- if (!session.held.budget && !session.held.concurrency && !session.held.checks && !session.held.check && !session.held.slots && !session.held.model && !session.held.quiet && !session.held.interval && !session.held.roleModel && !session.held.instances && !session.held.instructions && !session.held.attach && !session.held.pause && !session.held.search && !session.held.notes && !session.held.brief && !session.held.retire && !session.held.release && !session.held.roleEnabled && !session.held.providerCaps && !session.held.notify && !session.held.backends && !session.held.fallback && !session.held.claudeReserve) {
121
+ if (!session.held.budget && !session.held.concurrency && !session.held.checks && !session.held.check && !session.held.slots && !session.held.model && !session.held.quiet && !session.held.interval && !session.held.roleModel && !session.held.instances && !session.held.instructions && !session.held.attach && !session.held.pause && !session.held.search && !session.held.notes && !session.held.brief && !session.held.retire && !session.held.release && !session.held.roleEnabled && !session.held.notify && !session.held.backends && !session.held.fallback) {
126
122
  const recalled = recallPromptHistory(session.promptHistory, session.input, session.cursor, direction === "up" ? "older" : "newer");
127
123
  if (recalled) {
128
124
  session.promptHistory = recalled.history;
@@ -181,8 +177,6 @@ export function createTuiEditors(deps) {
181
177
  roleEnabledMode: session.held.roleEnabled,
182
178
  quietMode: session.held.quiet,
183
179
  quietTeam: session.held.quietTeam,
184
- providerCapsMode: session.held.providerCaps,
185
- claudeReserveMode: session.held.claudeReserve,
186
180
  notifyMode: session.held.notify,
187
181
  fallbackMode: session.held.fallback,
188
182
  intervalMode: session.held.interval,