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
@@ -2,21 +2,21 @@
2
2
  * The dashboard's POST operator-lifecycle command handlers (barrelled into
3
3
  * gui/gui-endpoint-commands.ts, which routes to them from src/gui/gui-server.ts): reset-counters,
4
4
  * restart, reclaim, abort, stop-schedule, role-notes, retire, revert, brief-edit, queue-cancel,
5
- * queue-top, post-review-accept, and commit-backlog — every POST handler that is not
6
- * prompt/backlog/config/team. Each handler
5
+ * queue-top, post-review-accept, commit-backlog, and archive-backlog — every POST handler
6
+ * that is not prompt/backlog/config/team. Each handler
7
7
  * adapts HTTP onto the domain work
8
8
  * one layer down (operator-intent, role-notes-write, brief-edit) and touches no socket beyond
9
9
  * its own `res`.
10
10
  */
11
11
  import { resyncRootInstall, rootInstallResyncLines } from "../build/dep-install.js";
12
- import { cancelScheduledStopMessage, requestAbort, requestResetCounters, requestRestart, requestScheduledStop, runManualReclaim } from "../operator/operator-intent.js";
13
- import { runDoctor } from "../doctor/doctor.js";
12
+ import { cancelScheduledStopMessage, requestAbort, requestAbortAll, requestResetCounters, requestRestart, requestScheduledStop, runManualReclaim } from "../operator/operator-intent.js";
14
13
  import { killOrphans, orphanKillLines } from "../doctor/doctor-orphans.js";
15
14
  import { dropBrokenPins, brokenPinDropLines, retireLeftovers, leftoverRetireLines } from "../doctor/doctor-contention-checks.js";
16
15
  import { commitUncommittedBacklog, backlogCommitMessage } from "../doctor/doctor-backlog.js";
16
+ import { archiveFinishedOnMain, describeArchivedFile } from "../backlog/backlog-archive.js";
17
17
  import { loadConfigSafe } from "../config/config.js";
18
18
  import { saveRoleNote } from "../agent/role-notes-write.js";
19
- import { roleNotesPath } from "../paths.js";
19
+ import { roleNotesPath, MERGE_LOCK_WAIT_MS } from "../paths.js";
20
20
  import { DIRECTOR_ROLE } from "../roles/roles.js";
21
21
  import { artifactPhrase, retireRole } from "../operator/retire.js";
22
22
  import { previewRevert, revertFollowUp, revertMergedCommit } from "../operator/revert.js";
@@ -30,7 +30,7 @@ import { orchestratorAlive } from "../team/orchestrator-info.js";
30
30
  import { shortSha } from "../text/format.js";
31
31
  import { editBriefText } from "../brief/brief-edit.js";
32
32
  import { requireStringField, validRoleIds } from "./gui-args.js";
33
- import { badRequest, sendJson } from "./http-body.js";
33
+ import { badRequest, sendJson } from "./gui-http-body.js";
34
34
  import { errorMessage, gotSuffix } from "../text/text.js";
35
35
  import { readPostBody, readRoleBody, sendRequestResult } from "./gui-endpoint-common.js";
36
36
  import { wakeInSecondsMs } from "./gui-team-commands.js";
@@ -140,6 +140,35 @@ export async function handleCommitBacklog(req, res, root, commit = (r) => commit
140
140
  const report = await commit(root);
141
141
  sendJson(res, 200, { ok: report.failed.length === 0, message: backlogCommitMessage(report), committed: report.committed, failed: report.failed, skipped: report.skipped, base: report.base });
142
142
  }
143
+ /** Handle POST /api/doctor-archive-backlog: the dashboard Health card's Archive backlog
144
+ * button — the same archiveFinishedOnMain `tumwater archive` runs (holding the merge lock),
145
+ * answering its message verbatim. `ok` is true when the finished entries were archived or
146
+ * there was nothing to archive; `skipped` (an off-base checkout, uncommitted backlog/archive
147
+ * files, or an unreadable file) answers `ok: false` with the skip reason and writes nothing.
148
+ * The archived body also carries the full commit sha and the moved files so the card can
149
+ * repaint precisely. Same body discipline as /api/kill-orphans; `archive` is injectable so a
150
+ * test need not touch git or hold the merge lock. */
151
+ export async function handleArchiveBacklog(req, res, root, archive = (r) => archiveFinishedOnMain(r, { lockTimeoutMs: MERGE_LOCK_WAIT_MS })) {
152
+ const body = await readPostBody(req, res, "{}");
153
+ if (!body)
154
+ return;
155
+ const outcome = await archive(root);
156
+ if (outcome.status === "nothing") {
157
+ sendJson(res, 200, { ok: true, message: "nothing to archive: PLANS.md ## Done and BUGS.md ## Fixed hold no finished entries" });
158
+ return;
159
+ }
160
+ if (outcome.status === "skipped") {
161
+ sendJson(res, 200, { ok: false, message: `nothing archived: ${outcome.reason}` });
162
+ return;
163
+ }
164
+ sendJson(res, 200, {
165
+ ok: true,
166
+ message: [...outcome.moved.map(describeArchivedFile), `committed ${shortSha(outcome.commit)}`].join("; "),
167
+ archived: true,
168
+ commit: outcome.commit,
169
+ moved: outcome.moved,
170
+ });
171
+ }
143
172
  /** Handle POST /api/doctor-resync-install: the dashboard Health card's Re-sync install button —
144
173
  * the terminal twin of `tumwater doctor --resync-install` and the TUI's Ctrl+U, all reached
145
174
  * through the same resyncRootInstall. It answers the same lines the CLI prints
@@ -154,17 +183,6 @@ export async function handleResyncInstall(req, res, root, resync = resyncRootIns
154
183
  const r = await resync(root);
155
184
  sendJson(res, 200, { ok: r.ok, message: rootInstallResyncLines(r).join("; ") });
156
185
  }
157
- /** Handle POST /api/doctor-reprobe: the dashboard Health card's Re-probe models button, the
158
- * twin of `tumwater doctor --reprobe`. A POST because it spends one model call per claimed pi
159
- * image-input model and rewrites `.tumwater/state/image-probes.json`; GET /api/doctor stays
160
- * probe-free. Same body discipline as /api/kill-orphans; `run` takes the probe mode so a test
161
- * can pin that the handler asks for "all" without spawning a probe. */
162
- export async function handleDoctorReprobe(req, res, root, run = (r, p) => runDoctor(r, undefined, undefined, p)) {
163
- const body = await readPostBody(req, res, "{}");
164
- if (!body)
165
- return;
166
- sendJson(res, 200, await run(root, "all"));
167
- }
168
186
  /** Handle POST /api/abort: the dashboard's per-row abort control — the same marker-writing
169
187
  * core `tumwater abort` calls (requestAbort). The role is required and validated like
170
188
  * /api/transcript; requestAbort's not-live error comes back 409 — the marker is valid but
@@ -176,6 +194,14 @@ export async function handleAbort(req, res, root) {
176
194
  return;
177
195
  sendRequestResult(res, requestAbort(root, body.role));
178
196
  }
197
+ /** Handle POST /api/abort-all: the dashboard Roles card's Abort all button — the same
198
+ * marker-writing core `tumwater abort --all` calls (requestAbortAll). It reads no body (the
199
+ * request targets every enabled runner), so a stale `role` field from another request changes
200
+ * nothing; requestAbortAll's not-live error comes back 409 like /api/abort's, through
201
+ * sendRequestResult. */
202
+ export async function handleAbortAll(_req, res, root) {
203
+ sendRequestResult(res, requestAbortAll(root));
204
+ }
179
205
  /** Handle POST /api/role-notes: the role drawer's notes Edit/Clear — the same saveRoleNote
180
206
  * `tumwater role <id> --notes-file` / `--clear-notes` call, so the 4 KB cap and its wording
181
207
  * cannot drift. `"text": ""` clears. The director has no notes (400, the CLI's wording); a
@@ -17,7 +17,7 @@ import { parseDurationMs } from "../cli/cli-args.js";
17
17
  import { PAUSE_FOR_MAX_MS, submitRolePromptAndWake } from "../operator/operator-intent.js";
18
18
  import { DIRECTOR_ROLE } from "../roles/roles.js";
19
19
  import { rejectBadQueueFileName, rejectBadRole, requirePromptText } from "./gui-args.js";
20
- import { badRequest, OK_GONE, sendJson } from "./http-body.js";
20
+ import { badRequest, OK_GONE, sendJson } from "./gui-http-body.js";
21
21
  import { errorMessage, gotSuffix, joinParts } from "../text/text.js";
22
22
  import { readPostBody, readRoleBody } from "./gui-endpoint-common.js";
23
23
  /** Handle POST /api/prompt: queue a director prompt. An over-long prompt
@@ -92,6 +92,23 @@ function checkedPromptFields(res, body, role = DIRECTOR_ROLE) {
92
92
  return null;
93
93
  return { text, images, notBeforeMs };
94
94
  }
95
+ /** The GUI prompt handlers' shared image-input gate (plans/image-input.md "Attachments",
96
+ * part 5/5): when the body carries images, refuse (400) an attachment the target role
97
+ * cannot read and that no tier reroutes — the same gate submitRolePrompt enforces, surfaced
98
+ * as a user-input error rather than the server's 500 — and return the notice a blinder's
99
+ * reroute should flash, or "" when there is nothing to say (image-less, or a rerouted role).
100
+ * Callers answer 400 and stop when it returns null. The role-queue submit and file-edit
101
+ * handlers share this one gate so their refusal text cannot drift from each other's. */
102
+ function promptImageGate(res, root, role, hasImages) {
103
+ if (!hasImages)
104
+ return "";
105
+ const guidance = imagePromptGuidance(root, role);
106
+ if (guidance.refusal) {
107
+ badRequest(res, guidance.refusal);
108
+ return null;
109
+ }
110
+ return guidance.notice ?? "";
111
+ }
95
112
  /** Handle POST /api/prompt-role: queue a prompt for one role — the dashboard's per-row
96
113
  * prompt affordance submits here, calling the same path `tumwater prompt --role <id>` uses
97
114
  * (submitRolePrompt enqueues into that role's own queue and logs under it, then a single-role
@@ -113,15 +130,9 @@ export async function handlePromptRole(req, res, root) {
113
130
  // anything is queued — the same gate submitRolePrompt enforces, surfaced as a user-input
114
131
  // error rather than the server's 500. A role that reroutes a blind read is not refused; its
115
132
  // notice rides in the confirmation the composer flashes.
116
- let notice = "";
117
- if (fields.images && fields.images.length > 0) {
118
- const guidance = imagePromptGuidance(root, role);
119
- if (guidance.refusal) {
120
- badRequest(res, guidance.refusal);
121
- return;
122
- }
123
- notice = guidance.notice ?? "";
124
- }
133
+ const notice = promptImageGate(res, root, role, (fields.images?.length ?? 0) > 0);
134
+ if (notice === null)
135
+ return;
125
136
  const message = joinParts([
126
137
  submitRolePromptAndWake(root, role, fields.text, fields.images, fields.notBeforeMs),
127
138
  notice,
@@ -183,17 +194,12 @@ export async function handlePromptEdit(req, res, root) {
183
194
  const fields = checkedPromptFields(res, body, role);
184
195
  if (!fields)
185
196
  return;
186
- // Image-input gate (plans/image-input.md "Attachments", part 5/5), the edit twin of
187
- // handlePromptRole's: an image edited into the queued prompt answers 400 naming the fix
188
- // when the target role cannot read it (and no tier reroutes it) — the same gate
189
- // submitRolePrompt enforces — before anything is written.
190
- if (fields.images && fields.images.length > 0) {
191
- const guidance = imagePromptGuidance(root, role);
192
- if (guidance.refusal) {
193
- badRequest(res, guidance.refusal);
194
- return;
195
- }
196
- }
197
+ // Image-input gate (plans/image-input.md "Attachments", part 5/5): an image edited into
198
+ // the queued prompt answers 400 naming the fix when the target role cannot read it (and no
199
+ // tier reroutes it) — the same gate submitRolePrompt enforces — before anything is written.
200
+ // The edit handler does not flash a reroute notice, so it ignores the helper's return.
201
+ if (promptImageGate(res, root, role, (fields.images?.length ?? 0) > 0) === null)
202
+ return;
197
203
  let outcome;
198
204
  try {
199
205
  outcome = editQueuedFile(root, role, body.file, fields.text, fields.images);
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The dashboard's POST flake-quarantine command handler (barrelled into
3
+ * gui/gui-endpoint-commands.ts, which routes to it from src/gui/gui-server.ts):
4
+ * quarantine-release — the twin of `tumwater quarantine --release <n>`. The dashboard Failures
5
+ * view's per-entry "Release" button and the CLI predicate run the same releaseQuarantinedTest
6
+ * core, so the operator who decides a quarantined test's filed bug is fixed can retire it from
7
+ * the card without opening a terminal. The handler adapts HTTP onto that domain work and
8
+ * touches no socket beyond its own `res`.
9
+ */
10
+ import { releaseQuarantinedTest } from "../review/known-flakes.js";
11
+ import { readPostBody } from "./gui-endpoint-common.js";
12
+ import { badRequest, sendJson } from "./gui-http-body.js";
13
+ /** Handle POST /api/quarantine-release: release one quarantined test (the body's `test`), so
14
+ * the review gate no longer excuses its failures. A missing/empty `test` 400s (nothing
15
+ * written); a name not currently quarantined answers ok:false (nothing written); a hit removes
16
+ * it from .tumwater/state/quarantine.json and answers ok:true naming the test. */
17
+ export async function handleQuarantineRelease(req, res, root) {
18
+ const body = await readPostBody(req, res, "{}");
19
+ if (!body)
20
+ return;
21
+ const test = typeof body.test === "string" ? body.test : "";
22
+ if (test === "") {
23
+ badRequest(res, "a quarantined test name is required");
24
+ return;
25
+ }
26
+ const ok = releaseQuarantinedTest(root, test);
27
+ sendJson(res, 200, ok
28
+ ? { ok: true, message: `released quarantined test: ${test} — the gate no longer excuses it` }
29
+ : { ok: false, message: `not in the flake quarantine: ${test}` });
30
+ }
@@ -5,15 +5,17 @@
5
5
  * this module only adapts HTTP onto it. Barrelled into gui/gui-endpoints.ts beside the other
6
6
  * per-family GET endpoint modules.
7
7
  */
8
- import { collectReport, collectReportSince } from "../report/report-data.js";
8
+ import { collectReport } from "../report/report-data.js";
9
+ import { collectReportSince } from "../report/report-since-data.js";
9
10
  import { collectFailureReport, collectFailureReportSince } from "../failure/failure-data.js";
10
11
  import { renderFailureMarkdown } from "../failure/failure-render.js";
11
12
  import { renderSinceReportMarkdown } from "../report/report-render.js";
12
13
  import { loadQuarantine } from "../review/known-flakes.js";
13
- import { quarantineRows, renderQuarantineSection } from "../review/quarantine-render.js";
14
+ import { quarantineRows, quarantineViewRows } from "../review/quarantine-render.js";
15
+ import { quarantineWarning } from "../baseline/main-guardrails.js";
14
16
  import { REPORT_SINCE_MAX_MS, reportSinceMsg } from "../events/event-window.js";
15
17
  import { roleListQuery, sinceMsQuery, untilMsQuery, windowDays } from "./gui-args.js";
16
- import { badRequest, sendJson } from "./http-body.js";
18
+ import { badRequest, sendJson } from "./gui-http-body.js";
17
19
  /** Parse and validate the role-scoped, sinceMs-or-days query the report and failures endpoints
18
20
  * both take: the `?role=` filter, a comma-separated list (each id trimmed, deduplicated, and
19
21
  * validated by rejectBadRole — an unknown id or an empty list → 400 naming it) and, when
@@ -73,11 +75,12 @@ export function handleReport(q, res, root) {
73
75
  * same bounded Markdown failure digest the selfcheck role feeds on and `tumwater report
74
76
  * --failures` prints, as JSON ({ markdown }) — the dashboard's failures tab renders it; with
75
77
  * sinceMs it is the trailing-window digest (collectFailureReportSince) through the same render,
76
- * the CLI's `report --failures --since` shape. The response also carries `quarantine`, the
77
- * Markdown section (renderQuarantineSection over the current loadQuarantine list, an empty
78
- * string when it is empty) the Failures view paints below the digest — the read twin of the
79
- * amber quarantine alert, current rather than window- or role-scoped. The digest's `markdown`
80
- * is unchanged. The days window follows windowDays (so it can
78
+ * the CLI's `report --failures --since` shape. The response also carries `quarantine` —
79
+ * `{ warning, rows }` (quarantineViewRows over the current loadQuarantine list, an empty
80
+ * rows list when it is empty) the Failures view's card paints below the digest — the interactive
81
+ * twin of `tumwater quarantine --release <n>` (each row's Release button posts to
82
+ * POST /api/quarantine-release), current rather than window- or role-scoped. The digest's
83
+ * `markdown` is unchanged. The days window follows windowDays (so it can
81
84
  * never drift from /api/report's). role rides the same rule as
82
85
  * handleReport: absent → the team digest, present → the validated listed roles, summed. Reads files
83
86
  * directly, so it works with no team running. */
@@ -88,7 +91,8 @@ export function handleFailures(q, res, root) {
88
91
  const untilMs = untilMsQuery(q, res, scope.sinceMs);
89
92
  if (untilMs === null)
90
93
  return;
91
- const quarantine = renderQuarantineSection(quarantineRows(loadQuarantine(root)), Date.now());
94
+ const rows = quarantineRows(loadQuarantine(root));
95
+ const quarantine = { warning: quarantineWarning(rows, Date.now()), rows: quarantineViewRows(rows, Date.now()) };
92
96
  if (scope.sinceMs !== undefined) {
93
97
  sendJson(res, 200, { markdown: renderFailureMarkdown(collectFailureReportSince(root, scope.sinceMs, scope.roles, untilMs)), quarantine });
94
98
  return;
@@ -13,8 +13,8 @@ import { captureStartupBuild, createReloadWatch, reexecSelf, watchReloadSupervis
13
13
  import { warnEvent, TUMWATER_SOURCE } from "../events/events.js";
14
14
  import { errorMessage } from "../text/text.js";
15
15
  import { handleBacklog, handleBacklogMatch, handleBrief, handleConfig, handleDiff, handleFailures, handleReport, handleHistory, handleLogs, handlePromptText, handleRun, handleRole, handleTranscript, handleDoctor, handleRetireList, } from "./gui-endpoints.js";
16
- import { handleAbort, handleBriefEdit, handleBudget, handleConfigSet, handleConfigUnset, handleRoleCap, handleRoleReviewAfterMerge, handleRoleEnable, handleRoleDisable, handleRoleInstances, handleRoleInterval, handleRoleInstructions, handleRoleModel, handleRoleQuietHours, handleNotifyTest, handlePause, handlePauseRole, handlePrompt, handleBacklogFile, handleBacklogCancel, handleBacklogRelease, handleBacklogTop, handleBacklogEdit, handlePromptCancel, handlePromptEdit, handlePromptRole, handlePromptTop, handleQuestionAnswer, handleReclaim, handleKillOrphans, handleDropBrokenPins, handleCommitBacklog, handleResyncInstall, handleRetireLeftovers, handleDoctorReprobe, handleRestart, handleStopSchedule, handleResetCounters, handleRoleNotes, handleRetire, handleRevert, handleQueueCancel, handleQueueTop, handlePostReviewAccept, handleWake, handleWakeCancel, } from "./gui-endpoint-commands.js";
17
- import { sendJson } from "./http-body.js";
16
+ import { handleAbort, handleAbortAll, handleBriefEdit, handleBudget, handleConfigSet, handleConfigUnset, handleRoleCap, handleRoleReviewAfterMerge, handleRoleEnable, handleRoleDisable, handleRoleInstances, handleRoleInterval, handleRoleInstructions, handleRoleModel, handleRoleQuietHours, handleNotifyTest, handlePause, handlePauseRole, handlePrompt, handleBacklogFile, handleBacklogCancel, handleBacklogRelease, handleBacklogTop, handleBacklogEdit, handlePromptCancel, handlePromptEdit, handlePromptRole, handlePromptTop, handleQuestionAnswer, handleReclaim, handleKillOrphans, handleDropBrokenPins, handleCommitBacklog, handleResyncInstall, handleRetireLeftovers, handleArchiveBacklog, handleRestart, handleStopSchedule, handleResetCounters, handleRoleNotes, handleRetire, handleRevert, handleQueueCancel, handleQueueTop, handlePostReviewAccept, handleQuarantineRelease, handleWake, handleWakeCancel, } from "./gui-endpoint-commands.js";
17
+ import { sendJson } from "./gui-http-body.js";
18
18
  /** Constant-time credential comparison for the shared-token gate: `timingSafeEqual` throws
19
19
  * on unequal lengths, so the length equality is the guard. A naive `===` string compare
20
20
  * would leak the token's length and prefix byte by byte. */
@@ -194,6 +194,7 @@ export function startGui(root, port, allInterfaces = false, token = "", watch =
194
194
  "POST /api/queue-cancel": (req, res) => handleQueueCancel(req, res, root),
195
195
  "POST /api/queue-top": (req, res) => handleQueueTop(req, res, root),
196
196
  "POST /api/post-review-accept": (req, res) => handlePostReviewAccept(req, res, root),
197
+ "POST /api/quarantine-release": (req, res) => handleQuarantineRelease(req, res, root),
197
198
  "POST /api/restart": (req, res) => handleRestart(req, res, root),
198
199
  "POST /api/stop-schedule": (req, res) => handleStopSchedule(req, res, root),
199
200
  "POST /api/reclaim": (req, res) => handleReclaim(req, res, root),
@@ -202,8 +203,9 @@ export function startGui(root, port, allInterfaces = false, token = "", watch =
202
203
  "POST /api/doctor-commit-backlog": (req, res) => handleCommitBacklog(req, res, root),
203
204
  "POST /api/doctor-resync-install": (req, res) => handleResyncInstall(req, res, root),
204
205
  "POST /api/doctor-retire-leftovers": (req, res) => handleRetireLeftovers(req, res, root),
205
- "POST /api/doctor-reprobe": (req, res) => handleDoctorReprobe(req, res, root),
206
+ "POST /api/doctor-archive-backlog": (req, res) => handleArchiveBacklog(req, res, root),
206
207
  "POST /api/abort": (req, res) => handleAbort(req, res, root),
208
+ "POST /api/abort-all": (req, res) => handleAbortAll(req, res, root),
207
209
  "POST /api/pause-role": (req, res) => handlePauseRole(req, res, root),
208
210
  };
209
211
  const server = http.createServer(async (req, res) => {
@@ -9,7 +9,7 @@ import { baseRoleOf, INSTANCE_ROLES } from "../roles/runner-ids.js";
9
9
  import { normalizePauseReason, pauseTeam, pauseRole, resumeTeam, resumeRole } from "../team/team-state.js";
10
10
  import { cancelWakes, PAUSE_FOR_MAX_MS, requestWake } from "../operator/operator-intent.js";
11
11
  import { requirePausedFlag, validRoleIds } from "./gui-args.js";
12
- import { badRequest, sendJson } from "./http-body.js";
12
+ import { badRequest, sendJson } from "./gui-http-body.js";
13
13
  import { gotSuffix } from "../text/text.js";
14
14
  import { readPostBody, readRoleBody } from "./gui-endpoint-common.js";
15
15
  /** The shared optional-seconds rule behind the timed pause's `forSeconds` and the scheduled
@@ -17,6 +17,7 @@ import { shortSpanPhrase } from "../text/phrases.js";
17
17
  import { outcomeText, usageText } from "../events/event-format.js";
18
18
  import { compareCodeUnits } from "../collections.js";
19
19
  import { bucketMergeEvents, resolveQueuedResult } from "./history-queued.js";
20
+ import { nonNegativeNumber } from "../files/json-object.js";
20
21
  /** `history`'s default row count and ceiling. The default shows a working hour of a quiet
21
22
  * team; the ceiling bounds how much log one ask re-reads — more rows only re-read more log
22
23
  * with no added signal, so an explicit flag above it fails fast (the report's --days pattern). */
@@ -68,6 +69,8 @@ function tallyRows(rows, keyOf) {
68
69
  t.costUsd += row.costUsd;
69
70
  if (row.durationMs !== null)
70
71
  t.durationMs = (t.durationMs ?? 0) + row.durationMs;
72
+ if (row.peakContextTokens !== undefined)
73
+ t.peakContextTokens = Math.max(t.peakContextTokens ?? 0, row.peakContextTokens);
71
74
  if (cls === "changes")
72
75
  t.lastChangeTs = Math.max(t.lastChangeTs ?? row.ts, row.ts);
73
76
  }
@@ -118,7 +121,7 @@ export function runRowCells(row) {
118
121
  return {
119
122
  role: roleLabel(row.role),
120
123
  run: String(row.run),
121
- result: eventTypeLabel(row.result),
124
+ result: row.cutOff === true ? `${eventTypeLabel(row.result)} (cut off)` : eventTypeLabel(row.result),
122
125
  model: row.model ?? "",
123
126
  duration: row.durationMs === null ? "—" : shortSpanPhrase(row.durationMs),
124
127
  usage: row.usage,
@@ -209,11 +212,14 @@ export function runRows(events, limit, role, join) {
209
212
  ?.result ?? rawResult)
210
213
  : rawResult;
211
214
  const usage = eventUsage(e);
215
+ const peak = nonNegativeNumber(e.peakContextTokens, 0);
212
216
  rows.push({
213
217
  ts: e.ts,
214
218
  time: formatTimestamp(e.ts),
215
219
  tokens: usage.tokens,
216
220
  costUsd: usage.costUsd,
221
+ ...(peak > 0 ? { peakContextTokens: peak } : {}),
222
+ ...(e.cutOff === true ? { cutOff: true } : {}),
217
223
  role: eventRole(e),
218
224
  run: eventRun(e),
219
225
  result,
@@ -77,20 +77,25 @@ export function renderModelSummary(summary) {
77
77
  }
78
78
  function renderSummaryTable(header, all) {
79
79
  const roleWidth = Math.max(...[header, ...all.map((r) => r.label)].map(displayWidth));
80
- const cols = ["runs", "changes", "none", "refused", "problems", "tokens", "cost", "time"];
80
+ const cols = ["runs", "changes", "none", "refused", "problems", "tokens", "peak", "cost", "time"];
81
+ const colLabel = { runs: "runs", changes: "changes", none: "none", refused: "refused", problems: "problems", tokens: "tokens", peak: "peak ctx", cost: "cost", time: "time" };
81
82
  const cell = (r, c) => c === "tokens"
82
83
  ? compactTokens(r.tokens)
83
- : c === "cost"
84
- ? usd(r.costUsd)
85
- : c === "time"
86
- ? r.durationMs === undefined
87
- ? "—"
88
- : humanSeconds(r.durationMs / 1000)
89
- : String(c === "runs" ? r.runs : r[c]);
90
- const widths = cols.map((c) => Math.max(c.length, ...all.map((r) => cell(r.row, c).length)));
84
+ : c === "peak"
85
+ ? r.peakContextTokens === undefined
86
+ ? "—"
87
+ : compactTokens(r.peakContextTokens)
88
+ : c === "cost"
89
+ ? usd(r.costUsd)
90
+ : c === "time"
91
+ ? r.durationMs === undefined
92
+ ? "—"
93
+ : humanSeconds(r.durationMs / 1000)
94
+ : String(c === "runs" ? r.runs : r[c]);
95
+ const widths = cols.map((c) => Math.max(colLabel[c].length, ...all.map((r) => cell(r.row, c).length)));
91
96
  const line = (role, cells, lastChange) => [padToWidth(role, roleWidth), ...cells.map((c, i) => c.padStart(widths[i] ?? 0)), lastChange].join(" ").trimEnd();
92
97
  return [
93
- line(header, [...cols], "last change"),
98
+ line(header, cols.map((c) => colLabel[c]), "last change"),
94
99
  ...all.map(({ label, row }) => line(label, cols.map((c) => cell(row, c)), row.lastChangeTs === null ? "—" : formatTimestamp(row.lastChangeTs))),
95
100
  ].join("\n");
96
101
  }