tumwater 0.4.0 → 0.4.2

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 (291) hide show
  1. package/README.md +4 -6
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-backend.js +5 -2
  4. package/dist/src/agent/backend-traits.js +21 -0
  5. package/dist/src/agent/claude/claude-backend.js +1 -1
  6. package/dist/src/agent/claude/claude-stream.js +181 -182
  7. package/dist/src/agent/claude/{write-guard-hook.js → claude-write-guard-hook.js} +1 -1
  8. package/dist/src/agent/claude/claude-write-guard.js +5 -4
  9. package/dist/src/agent/codex/codex-backend.js +1 -1
  10. package/dist/src/agent/codex/codex-rollout.js +48 -45
  11. package/dist/src/agent/codex/codex-stream.js +269 -215
  12. package/dist/src/agent/codex/{write-guard-hook.js → codex-write-guard-hook.js} +2 -1
  13. package/dist/src/agent/codex/codex-write-guard.js +3 -3
  14. package/dist/src/agent/image-input.js +18 -87
  15. package/dist/src/agent/notes-shim.js +2 -2
  16. package/dist/src/agent/opencode/opencode-backend.js +2 -2
  17. package/dist/src/agent/opencode/opencode-catalog.js +2 -2
  18. package/dist/src/agent/opencode/opencode-config.js +2 -2
  19. package/dist/src/agent/opencode/opencode-stream.js +101 -92
  20. package/dist/src/agent/run-agent.js +76 -382
  21. package/dist/src/agent/run-settle.js +25 -15
  22. package/dist/src/agent/run-spawn.js +226 -0
  23. package/dist/src/agent/run-stream.js +185 -0
  24. package/dist/src/agent/session-ledger.js +16 -0
  25. package/dist/src/agent/write-guard.js +5 -3
  26. package/dist/src/backlog/backlog-archive.js +86 -170
  27. package/dist/src/backlog/backlog-cli-view.js +89 -70
  28. package/dist/src/backlog/backlog-eligibility.js +51 -59
  29. package/dist/src/backlog/backlog-file-bugs.js +133 -0
  30. package/dist/src/backlog/backlog-index-data.js +159 -0
  31. package/dist/src/backlog/backlog-index.js +7 -5
  32. package/dist/src/backlog/backlog-md.js +39 -28
  33. package/dist/src/backlog/backlog-release.js +21 -36
  34. package/dist/src/backlog/backlog-render.js +1 -1
  35. package/dist/src/backlog/backlog-structure.js +14 -145
  36. package/dist/src/backlog/backlog-write.js +31 -18
  37. package/dist/src/backlog/backlog.js +14 -4
  38. package/dist/src/backlog/question-answer.js +33 -15
  39. package/dist/src/baseline/main-baseline.js +71 -26
  40. package/dist/src/baseline/main-guardrails.js +76 -15
  41. package/dist/src/baseline/main-red.js +45 -94
  42. package/dist/src/baseline/main-watch.js +9 -3
  43. package/dist/src/brief/brief.js +40 -0
  44. package/dist/src/budget/budget-fallback-view.js +50 -8
  45. package/dist/src/budget/budget-wiring.js +15 -11
  46. package/dist/src/budget/budget.js +3 -2
  47. package/dist/src/build/build-check-events.js +4 -2
  48. package/dist/src/build/build-check-report.js +2 -1
  49. package/dist/src/build/build-check-scoped.js +70 -41
  50. package/dist/src/change/change-data.js +4 -2
  51. package/dist/src/cli/cli-args.js +9 -0
  52. package/dist/src/cli/cli-command-args.js +199 -98
  53. package/dist/src/cli/cli-flag-specs.js +6 -4
  54. package/dist/src/cli/cli-marker-commands.js +2 -2
  55. package/dist/src/cli/cli-query-commands.js +41 -6
  56. package/dist/src/cli/cli-review-commands.js +2 -1
  57. package/dist/src/cli/cli-role-command.js +46 -92
  58. package/dist/src/cli/cli-start.js +123 -47
  59. package/dist/src/cli/follow-view.js +3 -1
  60. package/dist/src/cli/help.js +19 -12
  61. package/dist/src/cli/log-commands.js +128 -107
  62. package/dist/src/cli/question-commands.js +82 -63
  63. package/dist/src/cli.js +396 -343
  64. package/dist/src/collections.js +46 -9
  65. package/dist/src/config/config-fallbacks.js +0 -4
  66. package/dist/src/config/config-live.js +179 -147
  67. package/dist/src/config/config-request.js +176 -51
  68. package/dist/src/config/config-validation-roles.js +271 -0
  69. package/dist/src/config/config-validation.js +10 -205
  70. package/dist/src/config/config-views.js +2 -7
  71. package/dist/src/config/config-write.js +205 -175
  72. package/dist/src/config/config.js +2 -1
  73. package/dist/src/doctor/doctor-backlog-bugs.js +24 -122
  74. package/dist/src/doctor/doctor-backlog-plans.js +24 -25
  75. package/dist/src/doctor/doctor-backlog.js +34 -42
  76. package/dist/src/doctor/doctor-check-names.js +4 -2
  77. package/dist/src/doctor/doctor-checks.js +16 -4
  78. package/dist/src/doctor/doctor-claude-checks.js +2 -2
  79. package/dist/src/doctor/doctor-codex-checks.js +9 -20
  80. package/dist/src/doctor/doctor-contention-checks.js +52 -19
  81. package/dist/src/doctor/doctor-holds.js +22 -20
  82. package/dist/src/doctor/doctor-model-checks.js +8 -184
  83. package/dist/src/doctor/doctor-notify.js +2 -5
  84. package/dist/src/doctor/doctor-opencode-checks.js +90 -41
  85. package/dist/src/doctor/doctor-principles.js +26 -0
  86. package/dist/src/doctor/doctor-project-checks.js +69 -19
  87. package/dist/src/doctor/doctor-role-notes.js +33 -0
  88. package/dist/src/doctor/doctor.js +13 -6
  89. package/dist/src/doctor/probe.js +1 -1
  90. package/dist/src/events/event-format.js +307 -162
  91. package/dist/src/events/event-labels.js +0 -1
  92. package/dist/src/events/event-legacy.js +1 -1
  93. package/dist/src/events/event-window.js +1 -1
  94. package/dist/src/events/events.js +15 -2
  95. package/dist/src/events/notify.js +1 -1
  96. package/dist/src/failure/failure-cluster.js +4 -3
  97. package/dist/src/failure/failure-data.js +126 -94
  98. package/dist/src/failure/failure-render.js +58 -17
  99. package/dist/src/failure/failure-state-change.js +308 -27
  100. package/dist/src/failure/time-spend.js +139 -106
  101. package/dist/src/files/files.js +7 -0
  102. package/dist/src/files/json-object.js +15 -2
  103. package/dist/src/gates/budget-gates.js +90 -68
  104. package/dist/src/gates/gate-polls.js +92 -61
  105. package/dist/src/gates/role-cap-gates.js +1 -1
  106. package/dist/src/gates/startup-gate.js +28 -28
  107. package/dist/src/gates/streak-gate.js +1 -1
  108. package/dist/src/git/git.js +1 -1
  109. package/dist/src/git/slot-salvage.js +9 -9
  110. package/dist/src/git/slots-state.js +12 -3
  111. package/dist/src/git/worktree-pool-startup.js +127 -0
  112. package/dist/src/git/worktree-pool.js +1 -120
  113. package/dist/src/gui/gui-admin-endpoints.js +3 -4
  114. package/dist/src/gui/gui-args.js +1 -1
  115. package/dist/src/gui/gui-backlog-commands.js +20 -11
  116. package/dist/src/gui/gui-backlog-endpoints.js +2 -8
  117. package/dist/src/gui/gui-config-commands.js +51 -76
  118. package/dist/src/gui/gui-endpoint-commands.js +4 -2
  119. package/dist/src/gui/gui-endpoint-common.js +4 -3
  120. package/dist/src/gui/gui-history-endpoints.js +36 -27
  121. package/dist/src/gui/gui-operator-commands.js +43 -17
  122. package/dist/src/gui/gui-prompt-commands.js +27 -21
  123. package/dist/src/gui/gui-quarantine-commands.js +30 -0
  124. package/dist/src/gui/gui-report-endpoints.js +13 -9
  125. package/dist/src/gui/gui-server.js +5 -3
  126. package/dist/src/gui/gui-team-commands.js +1 -1
  127. package/dist/src/history/history-data.js +7 -1
  128. package/dist/src/history/history-render.js +15 -10
  129. package/dist/src/history/history.js +103 -68
  130. package/dist/src/inbox/inbox-submit.js +1 -5
  131. package/dist/src/inbox/pending-prompt.js +3 -2
  132. package/dist/src/inbox/prompt-commands.js +194 -161
  133. package/dist/src/init/init.js +104 -25
  134. package/dist/src/merge/merge-core.js +1 -1
  135. package/dist/src/merge/merge-drain.js +1 -1
  136. package/dist/src/merge/merge-git.js +5 -5
  137. package/dist/src/merge/{merge-post-review.js → merge-post-review-drain.js} +1 -1
  138. package/dist/src/merge/merge-queue.js +2 -1
  139. package/dist/src/merge/merge-to-main.js +1 -1
  140. package/dist/src/merge/merge-verify.js +3 -2
  141. package/dist/src/operator/operator-commands.js +84 -52
  142. package/dist/src/operator/operator-intent.js +26 -3
  143. package/dist/src/operator/retire.js +2 -1
  144. package/dist/src/operator/revert.js +18 -12
  145. package/dist/src/orchestrator/orchestrator-poll.js +2 -2
  146. package/dist/src/orchestrator/orchestrator.js +1 -1
  147. package/dist/src/orchestrator/retention.js +6 -5
  148. package/dist/src/paths.js +10 -10
  149. package/dist/src/pi/pi-bin.js +2 -2
  150. package/dist/src/pi/pi-models.js +9 -0
  151. package/dist/src/pi/pi-stream.js +63 -41
  152. package/dist/src/pi/pi-watchdogs.js +84 -49
  153. package/dist/src/pi-extension/context-shake.js +55 -31
  154. package/dist/src/pi-extension/full-output.js +2 -3
  155. package/dist/src/process/process.js +24 -1
  156. package/dist/src/process/run-marker.js +2 -2
  157. package/dist/src/process/supervisor.js +24 -5
  158. package/dist/src/prompt/gate-notes.js +41 -16
  159. package/dist/src/prompt/notes-backlog.js +22 -29
  160. package/dist/src/prompt/principles.js +11 -0
  161. package/dist/src/prompt/prompt-followup.js +4 -12
  162. package/dist/src/prompt/prompt.js +15 -12
  163. package/dist/src/redeploy/redeploy-probes.js +11 -1
  164. package/dist/src/redeploy/redeployer.js +54 -7
  165. package/dist/src/redeploy/self-reload.js +7 -2
  166. package/dist/src/report/report-data.js +224 -320
  167. package/dist/src/report/report-fold.js +23 -231
  168. package/dist/src/report/report-main-red.js +70 -3
  169. package/dist/src/report/report-render.js +181 -48
  170. package/dist/src/report/report-since-data.js +218 -0
  171. package/dist/src/report/report-usage-fold.js +288 -0
  172. package/dist/src/report/report.js +2 -1
  173. package/dist/src/review/known-flakes.js +22 -3
  174. package/dist/src/review/post-merge-review-accept.js +3 -2
  175. package/dist/src/review/post-merge-review.js +16 -14
  176. package/dist/src/review/quarantine-render.js +29 -11
  177. package/dist/src/review/review-precheck.js +2 -1
  178. package/dist/src/roles/role-catalog.js +30 -29
  179. package/dist/src/roles/role-guidance.js +26 -43
  180. package/dist/src/roles/role-render.js +19 -0
  181. package/dist/src/roles/role-view.js +19 -5
  182. package/dist/src/roles/roles.js +2 -22
  183. package/dist/src/run/ci-parity.js +5 -6
  184. package/dist/src/run/feedback-filings.js +10 -1
  185. package/dist/src/run/qa-coverage.js +4 -4
  186. package/dist/src/run/run-apply.js +14 -2
  187. package/dist/src/run/run-detail-data.js +50 -10
  188. package/dist/src/run/run-detail-render.js +32 -1
  189. package/dist/src/run/run-detail.js +44 -22
  190. package/dist/src/run/run-dismiss.js +43 -0
  191. package/dist/src/run/run-finalize.js +75 -46
  192. package/dist/src/run/run-prompt.js +271 -187
  193. package/dist/src/run/run-resume.js +32 -31
  194. package/dist/src/run/run-stage.js +116 -64
  195. package/dist/src/run/run-timing.js +18 -4
  196. package/dist/src/run/run-verdict.js +119 -83
  197. package/dist/src/run/stage-backlog-checks.js +4 -31
  198. package/dist/src/run/stage-check.js +4 -12
  199. package/dist/src/run/stage-checks-shared.js +15 -0
  200. package/dist/src/run/stage-name-checks.js +3 -13
  201. package/dist/src/run/stage-path-checks.js +26 -29
  202. package/dist/src/runner/leftover.js +13 -4
  203. package/dist/src/runner/revision.js +2 -2
  204. package/dist/src/runner/runner-fallback.js +1 -1
  205. package/dist/src/runner/runner-outcome.js +0 -5
  206. package/dist/src/runner/runner-pi.js +72 -49
  207. package/dist/src/runner/runner-run.js +19 -102
  208. package/dist/src/runner/runner-state-load.js +0 -10
  209. package/dist/src/runner/runner.js +1 -32
  210. package/dist/src/scheduling/claims.js +4 -11
  211. package/dist/src/status/status-data.js +140 -91
  212. package/dist/src/status/status-polls.js +41 -5
  213. package/dist/src/status/status-role-notes.js +53 -0
  214. package/dist/src/status/status-roles.js +10 -37
  215. package/dist/src/text/datetime.js +8 -1
  216. package/dist/src/text/format.js +13 -1
  217. package/dist/src/text/phrases.js +16 -2
  218. package/dist/src/text/text.js +12 -0
  219. package/dist/src/ui/alert-feeds.js +115 -0
  220. package/dist/src/ui/badges.js +3 -4
  221. package/dist/src/ui/gui/gui-client-boot.js +78 -57
  222. package/dist/src/ui/gui/gui-client-drawer-config.js +8 -3
  223. package/dist/src/ui/gui/gui-client-drawer.js +18 -2
  224. package/dist/src/ui/gui/gui-client-feed.js +3 -8
  225. package/dist/src/ui/gui/gui-client-history.js +16 -16
  226. package/dist/src/ui/gui/gui-client-model.js +9 -9
  227. package/dist/src/ui/gui/gui-client-operator.js +6 -10
  228. package/dist/src/ui/gui/gui-client-pending.js +4 -4
  229. package/dist/src/ui/gui/gui-client-report-chart.js +111 -10
  230. package/dist/src/ui/gui/gui-client-report.js +70 -11
  231. package/dist/src/ui/gui/gui-client-roles.js +52 -12
  232. package/dist/src/ui/gui/gui-client-settings.js +31 -26
  233. package/dist/src/ui/gui/gui-client-team.js +28 -2
  234. package/dist/src/ui/gui/gui-client.js +33 -4
  235. package/dist/src/ui/gui/gui-icons.js +7 -1
  236. package/dist/src/ui/progress-data.js +2 -2
  237. package/dist/src/ui/run-progress-model.js +2 -2
  238. package/dist/src/ui/status-model.js +26 -16
  239. package/dist/src/ui/status-payload.js +149 -326
  240. package/dist/src/ui/status-render.js +131 -80
  241. package/dist/src/ui/team-alert-backlog.js +236 -0
  242. package/dist/src/ui/team-alert-shared.js +92 -0
  243. package/dist/src/ui/team-alert-status.js +326 -0
  244. package/dist/src/ui/team-alert-tail.js +238 -0
  245. package/dist/src/ui/team-alerts.js +16 -984
  246. package/dist/src/ui/transcript-tail.js +137 -94
  247. package/dist/src/ui/tui/tui-backlog.js +1 -1
  248. package/dist/src/ui/tui/tui-editor-answer.js +54 -44
  249. package/dist/src/ui/tui/tui-editor-attach.js +74 -63
  250. package/dist/src/ui/tui/tui-editor-backends.js +62 -56
  251. package/dist/src/ui/tui/tui-editor-brief.js +64 -54
  252. package/dist/src/ui/tui/tui-editor-budget.js +106 -89
  253. package/dist/src/ui/tui/tui-editor-config.js +2 -4
  254. package/dist/src/ui/tui/tui-editor-ctx.js +12 -1
  255. package/dist/src/ui/tui/tui-editor-entry.js +171 -153
  256. package/dist/src/ui/tui/tui-editor-instances.js +2 -1
  257. package/dist/src/ui/tui/tui-editor-instructions.js +50 -107
  258. package/dist/src/ui/tui/tui-editor-interval.js +39 -112
  259. package/dist/src/ui/tui/tui-editor-notes.js +28 -84
  260. package/dist/src/ui/tui/tui-editor-pause.js +210 -183
  261. package/dist/src/ui/tui/tui-editor-prompt.js +46 -35
  262. package/dist/src/ui/tui/tui-editor-queued.js +74 -64
  263. package/dist/src/ui/tui/tui-editor-quiet.js +122 -108
  264. package/dist/src/ui/tui/tui-editor-release.js +85 -0
  265. package/dist/src/ui/tui/tui-editor-retire.js +57 -46
  266. package/dist/src/ui/tui/tui-editor-role-enabled.js +90 -80
  267. package/dist/src/ui/tui/tui-editor-role-file.js +87 -0
  268. package/dist/src/ui/tui/tui-editor-role-key.js +50 -42
  269. package/dist/src/ui/tui/tui-editor-search.js +70 -58
  270. package/dist/src/ui/tui/tui-editor-session.js +3 -0
  271. package/dist/src/ui/tui/tui-editors.js +4 -1
  272. package/dist/src/ui/tui/tui-frame.js +7 -3
  273. package/dist/src/ui/tui/tui-history.js +12 -10
  274. package/dist/src/ui/tui/tui-keys.js +6 -1
  275. package/dist/src/ui/tui/tui-pane-activity.js +62 -50
  276. package/dist/src/ui/tui/tui-pane-backlog.js +4 -4
  277. package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
  278. package/dist/src/ui/tui/tui-pane-health.js +45 -26
  279. package/dist/src/ui/tui/tui-pane-history.js +296 -264
  280. package/dist/src/ui/tui/tui-pane-report.js +74 -57
  281. package/dist/src/ui/tui/tui-pane-session.js +6 -2
  282. package/dist/src/ui/tui/tui-pane-transcript.js +158 -131
  283. package/dist/src/ui/tui/tui-pane.js +201 -174
  284. package/dist/src/ui/tui/tui-panes.js +211 -148
  285. package/dist/src/ui/tui/tui.js +4 -4
  286. package/dist/src/verdict/fix-claim.js +18 -6
  287. package/dist/src/verdict/refusal.js +32 -7
  288. package/package.json +1 -1
  289. package/dist/src/doctor/image-probe.js +0 -257
  290. /package/dist/src/gui/{http-body.js → gui-http-body.js} +0 -0
  291. /package/dist/src/merge/{backlog-conflicts.js → merge-backlog-conflicts.js} +0 -0
@@ -1,99 +1,36 @@
1
1
  import { setConfigKey, unsetConfigKey } from "../../config/config-write.js";
2
2
  import { checkMinRunInterval } from "../../config/config-keys.js";
3
- import { baseRoleOf } from "../../roles/runner-ids.js";
4
3
  import { rawRoleField } from "../../roles/role-view.js";
5
4
  import { gotSuffix } from "../../text/text.js";
5
+ import { createRoleKeyEditor } from "./tui-editor-role-key.js";
6
6
  /** The Ctrl+S min-run-interval editor on the shared prompt line, the terminal twin of
7
7
  * `tumwater role <id> --min-run-interval` and the dashboard role drawer's `Min run interval`
8
- * row. It owns its mode flag, its saved-draft pair, and the role pinned when it opened, so a
9
- * roster shift cannot redirect the write. Enter writes through the shared
10
- * setConfigKey/unsetConfigKey family the CLI and the drawer use — so checkMinRunInterval
11
- * (naming `roles.<base>.minRunIntervalSeconds`) and validateConfig own the value's truth — a
12
- * non-plain-decimal spelling (hex/exponent, negative, or non-numeric) flashes the editor's own
13
- * shape message first, mirroring the budget editor's plain-decimal rule, and stays open — and
14
- * Esc, Ctrl+S again, and Ctrl+T cancel by restoring the prompt draft. Unlike the quiet editor
15
- * there is no director refusal: `tumwater role director --min-run-interval` is accepted, so the
16
- * director edits too. Mutually exclusive with the other editors on the one prompt line via the
17
- * session's held registry. */
8
+ * row, from the same createRoleKeyEditor factory the role-model and instance-count editors
9
+ * share. It owns its held-marker slot and the role pinned when it opened, so a roster shift
10
+ * cannot redirect the write. Enter writes through the shared setConfigKey/unsetConfigKey family
11
+ * the CLI and the drawer use — so checkMinRunInterval (naming `roles.<base>.minRunIntervalSeconds`)
12
+ * and validateConfig own the value's truth — a non-plain-decimal spelling (hex/exponent,
13
+ * negative, or non-numeric) flashes the editor's own shape message first, mirroring the budget
14
+ * editor's plain-decimal rule, and stays open — and Esc, Ctrl+S again, and Ctrl+T cancel by
15
+ * restoring the prompt draft. Unlike the quiet editor there is no director refusal:
16
+ * `tumwater role director --min-run-interval` is accepted, so the director edits too. Mutually
17
+ * exclusive with the other editors on the one prompt line via the session's held registry. */
18
18
  export function createIntervalEditor(session) {
19
- let intervalMode = false;
20
- let restoreDraft = () => { };
21
- // The role pinned when the editor opened, so Enter writes the same role's override even if
22
- // syncRoster shrinks or reorders the roster while the editor is open.
23
- let lockedRole = null;
24
- // Leave min-run-interval mode (Esc, Ctrl+S again, or Ctrl+T): restore the saved prompt text.
25
- const exitIntervalMode = () => {
26
- if (!intervalMode)
27
- return;
28
- intervalMode = false;
29
- session.held.interval = false;
30
- restoreDraft();
31
- lockedRole = null;
32
- };
33
- const handleKey = (_str, key, ctx) => {
34
- if (key.ctrl && key.name === "s") {
35
- // Ctrl+S edits the viewed role's own min-run-interval override on its Transcript view.
36
- // Every non-transcript view keeps Ctrl+S — the guard below lets the key fall through to
37
- // the pane arms (History's trailing-window cycle) unless the interval editor already
38
- // holds the line, in which case Ctrl+S is the toggle-out arm. Entering pre-fills the
39
- // override from the FILE's raw `roles.<base>.minRunIntervalSeconds` (empty when unset —
40
- // empty means "clear it, fall back to the team-wide interval" on save) and saves the
41
- // prompt text; leaving restores it. There is no director exemption: the director runs
42
- // on the same clock, so `--min-run-interval` is accepted for it too.
43
- if (!intervalMode && !(ctx.view >= 1 && ctx.view <= ctx.roleIds.length))
44
- return false;
45
- const role = ctx.roleIds[ctx.view - 1];
46
- if (session.editorEntryDenied("interval"))
47
- return true;
48
- if (intervalMode) {
49
- exitIntervalMode();
50
- return true;
51
- }
52
- // Pin the role first so the write below is safe against a mid-edit roster shift, then
53
- // pre-fill from the file's own entry — one fresh read per open is the smallest honest
54
- // source; a broken config degrades to no pre-fill and the write reports the error. The
55
- // FILE (rawRoleField) is the source of truth, not the merged config: overlayDefaults
56
- // seeds every built-in role's minRunIntervalSeconds from the catalog, so the merged
57
- // value cannot tell an operator override from a seeded default, and clearing a role that
58
- // only has the seed would otherwise be a no-op the operator could not see.
59
- lockedRole = role;
60
- const base = baseRoleOf(role);
61
- const existing = rawRoleField(session.root, base, "minRunIntervalSeconds");
62
- restoreDraft = session.takeDraft();
63
- intervalMode = true;
64
- session.held.interval = true;
65
- session.input = typeof existing === "number" ? String(existing) : "";
66
- session.cursor = session.input.length;
67
- session.flashMessage(`edit ${role}'s min run interval in seconds (0 allows back-to-back runs, empty clears): Enter to save, Esc to cancel`);
68
- return true;
69
- }
70
- if (key.name === "escape" && intervalMode) {
71
- // Esc cancels min-run-interval mode, restoring the previous prompt text.
72
- exitIntervalMode();
73
- return true;
74
- }
75
- if (key.name === "return" && intervalMode) {
76
- // Enter commits the override through the shared setter family the CLI's
77
- // `--min-run-interval` and the dashboard drawer use: an empty or whitespace-only line
78
- // clears the key (`unsetConfigKey`), otherwise checkMinRunInterval (naming the dotted
79
- // key) screens the value and `setConfigKey` writes `roles.<base>.minRunIntervalSeconds`
80
- // as a JSON number. An invalid or rejected value flashes and STAYS in edit mode so the
81
- // operator can fix it; success restores the saved prompt text.
82
- const role = lockedRole;
83
- const cfgKey = `roles.${baseRoleOf(role)}.minRunIntervalSeconds`;
84
- const text = session.input.trim();
19
+ return createRoleKeyEditor(session, {
20
+ mode: "interval",
21
+ openCtrl: "s",
22
+ prefill: (s, base) => {
23
+ const existing = rawRoleField(s.root, base, "minRunIntervalSeconds");
24
+ return typeof existing === "number" ? String(existing) : "";
25
+ },
26
+ openFlash: (role) => `edit ${role}'s min run interval in seconds (0 allows back-to-back runs, empty clears): Enter to save, Esc to cancel`,
27
+ commit: (s, base, text) => {
28
+ const cfgKey = `roles.${base}.minRunIntervalSeconds`;
29
+ // An empty or whitespace-only line clears the override (a no-op success when the file
30
+ // never set it, so the operator sees "is not set").
85
31
  if (text === "") {
86
- const result = unsetConfigKey(session.root, cfgKey);
87
- if (result.ok) {
88
- exitIntervalMode();
89
- session.flashMessage(result.oldValue === undefined
90
- ? `${role}: min run interval is not set in tumwater.json, so nothing was cleared`
91
- : `${role}: min run interval cleared`);
92
- }
93
- else {
94
- session.flashMessage(result.error); // broken config or write failure — stay in edit mode
95
- }
96
- return true;
32
+ const r = unsetConfigKey(s.root, cfgKey);
33
+ return r.ok && r.oldValue === undefined ? { ok: true, noop: true } : r;
97
34
  }
98
35
  // Plain decimal seconds only — bare Number() would also read hex ("0x10" → 16) and
99
36
  // exponent ("1e2" → 100) as an interval, neither of which an operator means when
@@ -105,31 +42,21 @@ export function createIntervalEditor(session) {
105
42
  // editor was about to write — and STAY in edit mode (negative and non-numeric text hit
106
43
  // this shape arm too, so the operator sees one consistent message).
107
44
  if (!/^\d*\.?\d+$/.test(text)) {
108
- session.flashMessage(`${cfgKey} must be a plain second count like 300, 0.5, or .5 — no +/-, exponent, or hex notation${gotSuffix(text)}`);
109
- return true;
45
+ return {
46
+ ok: false,
47
+ error: `${cfgKey} must be a plain second count like 300, 0.5, or .5 — no +/-, exponent, or hex notation${gotSuffix(text)}`,
48
+ };
110
49
  }
111
50
  const value = Number(text);
112
51
  const invalid = checkMinRunInterval(value, cfgKey);
113
- if (invalid) {
114
- session.flashMessage(invalid); // name the rejected value and stay open
115
- return true;
116
- }
117
- const result = setConfigKey(session.root, cfgKey, JSON.stringify(value));
118
- if (result.ok) {
119
- exitIntervalMode();
120
- session.flashMessage(result.unchanged
121
- ? `${role}: min run interval is already ${text}s, so tumwater.json was not written`
122
- : `${role}: min run interval set to ${text}s`);
123
- }
124
- else {
125
- session.flashMessage(result.error); // broken config or write failure — stay in edit mode
126
- }
127
- return true;
128
- }
129
- return false;
130
- };
131
- return {
132
- handleKey,
133
- exit: exitIntervalMode,
134
- };
52
+ if (invalid)
53
+ return { ok: false, error: invalid };
54
+ const result = setConfigKey(s.root, cfgKey, JSON.stringify(value));
55
+ return result.ok && result.unchanged ? { ok: true, noop: true } : result;
56
+ },
57
+ savedFlash: (role, text) => text === "" ? `${role}: min run interval cleared` : `${role}: min run interval set to ${text}s`,
58
+ noopFlash: (role, text) => text === ""
59
+ ? `${role}: min run interval is not set in tumwater.json, so nothing was cleared`
60
+ : `${role}: min run interval is already ${text}s, so tumwater.json was not written`,
61
+ });
135
62
  }
@@ -1,94 +1,38 @@
1
1
  import { saveRoleNote } from "../../agent/role-notes-write.js";
2
2
  import { roleNotesPath } from "../../paths.js";
3
- import { readEditorPath } from "./tui-editor-session.js";
3
+ import { createRoleFileEditor } from "./tui-editor-role-file.js";
4
4
  /** The notes view's Ctrl+E replace-notes-from-a-file editor on the shared prompt line, the
5
5
  * terminal twin of `tumwater role <id> --notes-file <path>` and the dashboard drawer's
6
- * textarea. It owns its `notesEditFor` flag, its saved-draft pair, and the handleKey arms for
7
- * its key (the toggle with the `heldEditorFlash(entering)` guard clause — the shared guard
8
- * priority lives in the session module — plus Esc, Ctrl+E again, and Enter). Guarded to the
9
- * notes view (`ctx.noteMode`), so the Report window's Ctrl+E arm keeps the key on its own
10
- * views. The prompt line is single-line and a note may be multiline up to ROLE_NOTES_MAX_BYTES,
11
- * so the editor takes a file path and reads it verbatim — exactly the CLI's own
12
- * non-interactive replacement, the way the Activity view's Ctrl+V attach editor also takes a
13
- * path. The write goes through saveRoleNote (the same write the CLI and the dashboard drawer
14
- * make), so the 4 KB cap and its wording cannot drift; the fresh read is repainted through the
15
- * session's refreshNotes seam. */
6
+ * textarea. Guarded to the notes view (`ctx.noteMode`), so the Report window's Ctrl+E arm keeps
7
+ * the key on its own views. The prompt line is single-line and a note may be multiline up to
8
+ * ROLE_NOTES_MAX_BYTES, so the editor takes a file path and reads it verbatim — exactly the
9
+ * CLI's own non-interactive replacement, the way the Activity view's Ctrl+V attach editor also
10
+ * takes a path. The write goes through saveRoleNote (the same write the CLI and the dashboard
11
+ * drawer make), so the 4 KB cap and its wording cannot drift; the fresh read is repainted
12
+ * through the session's refreshNotes seam. The open/exit/read framing lives in the shared
13
+ * createRoleFileEditor helper that also serves the instructions editor. */
16
14
  export function createNotesEditor(session) {
17
- let notesEditFor = null;
18
- let restoreDraft = () => { };
19
- // Leave the notes editor (Esc, Ctrl+E again, or Ctrl+T): restore the saved prompt text with
20
- // no note written.
21
- const exitNotesMode = () => {
22
- if (notesEditFor === null)
23
- return;
24
- notesEditFor = null;
25
- session.held.notes = null;
26
- restoreDraft();
27
- };
28
- const handleKey = (_str, key, ctx) => {
29
- if (key.ctrl && key.name === "e" && ctx.noteMode &&
30
- // The pane's noteMode closure goes stale when syncRoster shrinks the roster past the
31
- // viewed role (it never resets the pane's flags), so the same in-range guard every
32
- // transcript editor arm carries must hold here too before its view indexes roleIds.
33
- ctx.roleIds.length > 0 && ctx.view >= 1 && ctx.view <= ctx.roleIds.length) {
34
- // Ctrl+E toggles the replace-notes editor on the notes view's prompt line (mnemonic:
35
- // **e**dit notes — the same key the Report window's window-cycle arm and the Queued
36
- // pane's row-edit arm use on their own views, guarded away here by ctx.noteMode). The
37
- // line edits one file path that becomes the role's whole note through the same
38
- // saveRoleNote write the CLI and the dashboard drawer use. Entering saves the prompt
39
- // draft and blanks the line; leaving restores it.
40
- if (session.editorEntryDenied("notes"))
41
- return true;
42
- if (notesEditFor !== null) {
43
- exitNotesMode();
44
- }
45
- else {
46
- // Mid-recall, hand the arrows' saved draft back before the notes editor takes the
47
- // line — the same settle the role editors do — so leaving restores the
48
- // draft, not a history entry, with no stale recall state riding along.
49
- const role = ctx.roleIds[ctx.view - 1]; // defined: the open-arm range guard above pins the view into roleIds
50
- restoreDraft = session.takeDraft();
51
- notesEditFor = role;
52
- session.held.notes = role;
53
- session.input = "";
54
- session.cursor = 0;
55
- session.flashMessage(`replace ${role}'s notes from a file — Enter to save, Esc to cancel`);
56
- }
57
- return true;
58
- }
59
- if (key.name === "escape" && notesEditFor !== null) {
60
- // Esc cancels the notes editor, restoring the previous prompt text and writing nothing.
61
- exitNotesMode();
62
- session.flashMessage("notes edit cancelled");
63
- return true;
64
- }
65
- if (key.name === "return" && notesEditFor !== null) {
66
- // Enter reads the typed path whole and saves it through saveRoleNote — the shared write
67
- // the CLI's `--notes-file` and the dashboard drawer make, so the 4 KB cap and its wording
68
- // cannot drift. An empty path, a missing file, an unreadable file, and an over-cap note
69
- // all flash and STAY in the editor so the operator can fix the path; an empty file
70
- // clears the note. On success the notes view repaints the new note without leaving and
71
- // re-entering, and the editor exits with the shared success wording.
72
- const role = notesEditFor;
73
- // The trim/read/refuse prologue is shared with the brief, instructions and backlog-entry
74
- // editors (readEditorPath), which read one typed path the same way.
75
- const text = readEditorPath(session);
76
- if (text === null)
77
- return true;
15
+ return createRoleFileEditor(session, {
16
+ held: "notes",
17
+ openCtrl: "e",
18
+ mayOpen: (ctx) =>
19
+ // The pane's noteMode closure goes stale when syncRoster shrinks the roster past the
20
+ // viewed role (it never resets the pane's flags), so the same in-range guard every
21
+ // transcript editor arm carries must hold here too before its view indexes roleIds.
22
+ ctx.noteMode && ctx.roleIds.length > 0 && ctx.view >= 1 && ctx.view <= ctx.roleIds.length,
23
+ openFlash: (role) => `replace ${role}'s notes from a file — Enter to save, Esc to cancel`,
24
+ cancelFlash: "notes edit cancelled",
25
+ commit: (s, role, text) => {
78
26
  try {
79
- const message = saveRoleNote(roleNotesPath(session.root, role), text);
80
- session.refreshNotes(role);
81
- exitNotesMode();
82
- session.flashMessage(message);
27
+ return { ok: true, message: saveRoleNote(roleNotesPath(s.root, role), text) };
83
28
  }
84
29
  catch (err) {
85
- // The write or the 4 KB cap refused the note: flash the reason and stay in the editor
86
- // so the operator can fix the path.
87
- session.flashError(err);
30
+ // The write or the 4 KB cap refused the note: return the reason so the caller flashes
31
+ // it and stays in the editor for the operator to fix the path.
32
+ return { ok: false, reason: err };
88
33
  }
89
- return true;
90
- }
91
- return false;
92
- };
93
- return { handleKey, exit: exitNotesMode };
34
+ },
35
+ flashFailure: (s, reason) => s.flashError(reason),
36
+ refresh: (s, role) => s.refreshNotes(role),
37
+ });
94
38
  }