tumwater 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. package/README.md +27 -13
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +8 -9
  4. package/dist/src/agent/agent-backend.js +8 -6
  5. package/dist/src/agent/backend-traits.js +42 -25
  6. package/dist/src/agent/claude/claude-account.js +71 -204
  7. package/dist/src/agent/claude/claude-args.js +9 -6
  8. package/dist/src/agent/claude/claude-backend.js +10 -10
  9. package/dist/src/agent/claude/claude-env.js +1 -1
  10. package/dist/src/agent/claude/claude-stream.js +4 -3
  11. package/dist/src/agent/claude/claude-write-guard.js +49 -0
  12. package/dist/src/agent/claude/write-guard-hook.js +13 -0
  13. package/dist/src/agent/codex/codex-account.js +119 -0
  14. package/dist/src/agent/codex/codex-args.js +181 -0
  15. package/dist/src/agent/codex/codex-backend.js +279 -0
  16. package/dist/src/agent/codex/codex-catalog.js +97 -0
  17. package/dist/src/agent/codex/codex-env.js +18 -0
  18. package/dist/src/agent/codex/codex-hook-trust.js +246 -0
  19. package/dist/src/agent/codex/codex-patch-paths.js +36 -0
  20. package/dist/src/agent/codex/codex-rollout.js +179 -0
  21. package/dist/src/agent/codex/codex-stream.js +402 -0
  22. package/dist/src/agent/codex/codex-write-guard.js +86 -0
  23. package/dist/src/agent/codex/write-guard-hook.js +12 -0
  24. package/dist/src/agent/image-input.js +29 -24
  25. package/dist/src/agent/notes-shim.js +11 -12
  26. package/dist/src/agent/opencode/opencode-args.js +0 -1
  27. package/dist/src/agent/opencode/opencode-backend.js +12 -9
  28. package/dist/src/agent/opencode/opencode-catalog.js +55 -29
  29. package/dist/src/agent/opencode/opencode-config.js +19 -14
  30. package/dist/src/agent/opencode/opencode-env.js +0 -1
  31. package/dist/src/agent/opencode/opencode-session-id.js +1 -2
  32. package/dist/src/agent/opencode/opencode-stream.js +1 -1
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +5 -6
  35. package/dist/src/agent/run-agent.js +465 -448
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +86 -16
  38. package/dist/src/agent/subscription-account.js +220 -0
  39. package/dist/src/agent/write-guard.js +148 -0
  40. package/dist/src/backlog/backlog-archive.js +246 -55
  41. package/dist/src/backlog/backlog-cli-view.js +280 -0
  42. package/dist/src/backlog/backlog-cli.js +85 -274
  43. package/dist/src/backlog/backlog-commit.js +4 -5
  44. package/dist/src/backlog/backlog-eligibility.js +175 -242
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +54 -6
  47. package/dist/src/backlog/backlog-parts.js +254 -0
  48. package/dist/src/backlog/backlog-release.js +117 -0
  49. package/dist/src/backlog/backlog-render.js +27 -0
  50. package/dist/src/backlog/backlog-structure.js +99 -156
  51. package/dist/src/backlog/backlog-write.js +40 -127
  52. package/dist/src/backlog/backlog.js +21 -6
  53. package/dist/src/backlog/question-answer.js +21 -15
  54. package/dist/src/baseline/main-baseline.js +89 -20
  55. package/dist/src/baseline/main-culprit.js +94 -0
  56. package/dist/src/baseline/main-guardrails.js +209 -0
  57. package/dist/src/baseline/main-red.js +139 -32
  58. package/dist/src/baseline/main-revert.js +133 -0
  59. package/dist/src/baseline/main-watch.js +184 -0
  60. package/dist/src/brief/brief-edit.js +9 -11
  61. package/dist/src/brief/brief.js +31 -51
  62. package/dist/src/budget/budget.js +30 -20
  63. package/dist/src/budget/fallback-breaker.js +26 -27
  64. package/dist/src/build/build-check-detect.js +23 -15
  65. package/dist/src/build/build-check-events.js +25 -6
  66. package/dist/src/build/build-check-report.js +49 -9
  67. package/dist/src/build/build-check-scoped.js +18 -11
  68. package/dist/src/build/build-check.js +2 -1
  69. package/dist/src/build/build-info.js +2 -3
  70. package/dist/src/build/build-stage.js +9 -14
  71. package/dist/src/build/dep-install.js +1 -2
  72. package/dist/src/change/change-data.js +123 -31
  73. package/dist/src/change/change-render.js +46 -5
  74. package/dist/src/cli/brief-command.js +3 -4
  75. package/dist/src/cli/cli-args.js +118 -33
  76. package/dist/src/cli/cli-command-args.js +46 -18
  77. package/dist/src/cli/cli-editor.js +2 -1
  78. package/dist/src/cli/cli-flag-specs.js +150 -45
  79. package/dist/src/cli/cli-init.js +51 -0
  80. package/dist/src/cli/cli-marker-commands.js +4 -4
  81. package/dist/src/cli/cli-query-commands.js +308 -590
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-role-command.js +420 -0
  84. package/dist/src/cli/cli-start-summary.js +160 -0
  85. package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
  86. package/dist/src/cli/completion.js +149 -22
  87. package/dist/src/cli/config-commands.js +41 -18
  88. package/dist/src/cli/follow-view.js +75 -0
  89. package/dist/src/cli/help.js +189 -73
  90. package/dist/src/cli/log-commands.js +148 -82
  91. package/dist/src/cli/question-commands.js +205 -10
  92. package/dist/src/cli.js +106 -43
  93. package/dist/src/collections.js +26 -10
  94. package/dist/src/concurrency/check-permit.js +20 -3
  95. package/dist/src/concurrency/lock.js +17 -13
  96. package/dist/src/concurrency/provider-lease.js +44 -8
  97. package/dist/src/concurrency/semaphore.js +102 -26
  98. package/dist/src/config/config-aliases.js +0 -1
  99. package/dist/src/config/config-editable-keys.js +13 -1
  100. package/dist/src/config/config-fallbacks.js +93 -56
  101. package/dist/src/config/config-field-checks.js +40 -10
  102. package/dist/src/config/config-keys.js +30 -8
  103. package/dist/src/config/config-live.js +63 -24
  104. package/dist/src/config/config-request.js +137 -0
  105. package/dist/src/config/config-schema.js +31 -24
  106. package/dist/src/config/config-selector-checks.js +123 -0
  107. package/dist/src/config/config-validation-agents.js +80 -0
  108. package/dist/src/config/config-validation-model.js +116 -0
  109. package/dist/src/config/config-validation.js +141 -199
  110. package/dist/src/config/config-views.js +127 -53
  111. package/dist/src/config/config-write-core.js +135 -0
  112. package/dist/src/config/config-write.js +131 -264
  113. package/dist/src/config/config.js +48 -38
  114. package/dist/src/config/model-selector.js +34 -34
  115. package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
  116. package/dist/src/doctor/doctor-backlog-plans.js +263 -0
  117. package/dist/src/doctor/doctor-backlog.js +100 -496
  118. package/dist/src/doctor/doctor-check-names.js +81 -0
  119. package/dist/src/doctor/doctor-checks.js +52 -579
  120. package/dist/src/doctor/doctor-claude-checks.js +145 -0
  121. package/dist/src/doctor/doctor-codex-checks.js +279 -0
  122. package/dist/src/doctor/doctor-contention-checks.js +380 -0
  123. package/dist/src/doctor/doctor-holds.js +82 -66
  124. package/dist/src/doctor/doctor-install.js +2 -1
  125. package/dist/src/doctor/doctor-model-checks.js +236 -402
  126. package/dist/src/doctor/doctor-notify.js +3 -4
  127. package/dist/src/doctor/doctor-opencode-checks.js +162 -0
  128. package/dist/src/doctor/doctor-orphans.js +182 -63
  129. package/dist/src/doctor/doctor-project-checks.js +303 -0
  130. package/dist/src/doctor/doctor.js +121 -77
  131. package/dist/src/doctor/image-probe.js +21 -8
  132. package/dist/src/doctor/outcome.js +3 -2
  133. package/dist/src/doctor/probe.js +7 -11
  134. package/dist/src/events/event-format.js +181 -86
  135. package/dist/src/events/event-labels.js +12 -5
  136. package/dist/src/events/event-legacy.js +39 -15
  137. package/dist/src/events/event-read.js +39 -39
  138. package/dist/src/events/event-tally.js +16 -0
  139. package/dist/src/events/event-window.js +60 -24
  140. package/dist/src/events/events.js +30 -13
  141. package/dist/src/events/notify.js +7 -7
  142. package/dist/src/failure/failure-cluster.js +12 -12
  143. package/dist/src/failure/failure-data.js +56 -56
  144. package/dist/src/failure/failure-render.js +31 -23
  145. package/dist/src/failure/failure-state-change.js +17 -12
  146. package/dist/src/failure/time-spend.js +99 -35
  147. package/dist/src/files/files.js +111 -35
  148. package/dist/src/files/json-files.js +3 -3
  149. package/dist/src/files/json-object.js +28 -7
  150. package/dist/src/files/stat-cache.js +2 -2
  151. package/dist/src/files/tail.js +1 -1
  152. package/dist/src/gates/bootstrap-gates.js +4 -5
  153. package/dist/src/gates/budget-gates.js +177 -84
  154. package/dist/src/gates/disk-gate.js +1 -1
  155. package/dist/src/gates/gate-polls.js +108 -58
  156. package/dist/src/gates/maintenance-quota.js +10 -13
  157. package/dist/src/gates/pause-gates.js +28 -10
  158. package/dist/src/gates/readiness.js +7 -3
  159. package/dist/src/gates/role-cap-gates.js +8 -8
  160. package/dist/src/gates/startup-gate.js +94 -32
  161. package/dist/src/gates/streak-gate.js +54 -30
  162. package/dist/src/git/commit-message.js +77 -46
  163. package/dist/src/git/git-diff.js +16 -10
  164. package/dist/src/git/git-run.js +79 -38
  165. package/dist/src/git/git.js +36 -34
  166. package/dist/src/git/slot-salvage.js +253 -0
  167. package/dist/src/git/slots-state.js +6 -6
  168. package/dist/src/git/worktree-pool.js +132 -38
  169. package/dist/src/git/worktree-use.js +4 -11
  170. package/dist/src/git/worktree.js +94 -37
  171. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  172. package/dist/src/gui/gui-args.js +79 -8
  173. package/dist/src/gui/gui-backlog-commands.js +42 -18
  174. package/dist/src/gui/gui-backlog-endpoints.js +155 -0
  175. package/dist/src/gui/gui-command.js +8 -13
  176. package/dist/src/gui/gui-config-commands.js +94 -72
  177. package/dist/src/gui/gui-endpoint-commands.js +4 -3
  178. package/dist/src/gui/gui-endpoint-common.js +4 -4
  179. package/dist/src/gui/gui-endpoints.js +11 -380
  180. package/dist/src/gui/gui-history-endpoints.js +301 -0
  181. package/dist/src/gui/gui-operator-commands.js +108 -13
  182. package/dist/src/gui/gui-prompt-commands.js +11 -14
  183. package/dist/src/gui/gui-report-endpoints.js +90 -0
  184. package/dist/src/gui/gui-server.js +11 -4
  185. package/dist/src/gui/gui-team-commands.js +7 -7
  186. package/dist/src/gui/http-body.js +1 -1
  187. package/dist/src/history/history-data.js +122 -234
  188. package/dist/src/history/history-queued.js +147 -0
  189. package/dist/src/history/history-render.js +42 -22
  190. package/dist/src/history/history.js +193 -49
  191. package/dist/src/inbox/inbox-attachments.js +29 -30
  192. package/dist/src/inbox/inbox-cancel.js +10 -11
  193. package/dist/src/inbox/inbox-edit.js +9 -9
  194. package/dist/src/inbox/inbox-submit.js +18 -18
  195. package/dist/src/inbox/inbox-top.js +25 -9
  196. package/dist/src/inbox/inbox.js +42 -43
  197. package/dist/src/inbox/pending-prompt.js +26 -26
  198. package/dist/src/inbox/prompt-commands.js +62 -49
  199. package/dist/src/inbox/prompt-not-before.js +2 -2
  200. package/dist/src/init/init-templates.js +1 -2
  201. package/dist/src/init/init.js +28 -23
  202. package/dist/src/merge/backlog-conflicts.js +215 -35
  203. package/dist/src/merge/merge-batch.js +14 -8
  204. package/dist/src/merge/merge-cancel.js +53 -0
  205. package/dist/src/merge/merge-check-failures.js +44 -22
  206. package/dist/src/merge/merge-core.js +81 -32
  207. package/dist/src/merge/merge-diff.js +5 -4
  208. package/dist/src/merge/merge-drain.js +150 -11
  209. package/dist/src/merge/merge-git.js +177 -38
  210. package/dist/src/merge/merge-pipeline.js +15 -3
  211. package/dist/src/merge/merge-post-review.js +90 -0
  212. package/dist/src/merge/merge-queue-render.js +49 -11
  213. package/dist/src/merge/merge-queue.js +31 -15
  214. package/dist/src/merge/merge-requeue.js +102 -0
  215. package/dist/src/merge/merge-slot.js +61 -16
  216. package/dist/src/merge/merge-stack.js +33 -11
  217. package/dist/src/merge/merge-to-main.js +86 -27
  218. package/dist/src/merge/merge-top.js +69 -0
  219. package/dist/src/merge/merge-verify.js +12 -10
  220. package/dist/src/notes-cli.js +30 -15
  221. package/dist/src/operator/operator-commands.js +192 -81
  222. package/dist/src/operator/operator-intent.js +69 -42
  223. package/dist/src/operator/operator-requests.js +35 -40
  224. package/dist/src/operator/retire.js +5 -6
  225. package/dist/src/operator/revert.js +142 -34
  226. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  227. package/dist/src/orchestrator/orchestrator-poll.js +320 -0
  228. package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
  229. package/dist/src/orchestrator/orchestrator.js +128 -369
  230. package/dist/src/orchestrator/retention.js +42 -8
  231. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  232. package/dist/src/orchestrator/scheduling-scope.js +201 -0
  233. package/dist/src/orchestrator/scheduling-skip.js +185 -0
  234. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  235. package/dist/src/orchestrator/startup-migrations.js +11 -7
  236. package/dist/src/paths.js +116 -49
  237. package/dist/src/pi/command-shape.js +12 -1
  238. package/dist/src/pi/pi-args.js +6 -4
  239. package/dist/src/pi/pi-backend.js +6 -6
  240. package/dist/src/pi/pi-bin.js +54 -63
  241. package/dist/src/pi/pi-listed-models.js +53 -0
  242. package/dist/src/pi/pi-models.js +63 -30
  243. package/dist/src/pi/pi-run-result.js +1 -1
  244. package/dist/src/pi/pi-settings.js +1 -1
  245. package/dist/src/pi/pi-stream.js +6 -6
  246. package/dist/src/pi/pi-watchdogs.js +18 -19
  247. package/dist/src/pi-extension/bounded-output.js +5 -3
  248. package/dist/src/pi-extension/context-budget.js +2 -2
  249. package/dist/src/pi-extension/context-shake.js +1 -1
  250. package/dist/src/pi-extension/context-window.js +0 -1
  251. package/dist/src/pi-extension/full-output.js +3 -10
  252. package/dist/src/pi-extension/strip-thinking.js +0 -1
  253. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  254. package/dist/src/pi-extension/write-guard.js +37 -0
  255. package/dist/src/process/launch-services.js +1 -2
  256. package/dist/src/process/node-bin.js +16 -0
  257. package/dist/src/process/process-group.js +8 -18
  258. package/dist/src/process/process.js +41 -2
  259. package/dist/src/process/run-marker.js +18 -6
  260. package/dist/src/process/supervisor.js +22 -21
  261. package/dist/src/prompt/dialect.js +17 -2
  262. package/dist/src/prompt/gate-notes.js +203 -0
  263. package/dist/src/prompt/gate-prompts.js +121 -211
  264. package/dist/src/prompt/notes-backlog.js +181 -0
  265. package/dist/src/prompt/principles.js +1 -2
  266. package/dist/src/prompt/prompt-followup.js +19 -14
  267. package/dist/src/prompt/prompt.js +129 -204
  268. package/dist/src/redeploy/redeploy-policy.js +24 -19
  269. package/dist/src/redeploy/redeploy-probes.js +1 -1
  270. package/dist/src/redeploy/redeploy.js +2 -3
  271. package/dist/src/redeploy/redeployer.js +20 -20
  272. package/dist/src/redeploy/self-reload.js +2 -1
  273. package/dist/src/report/report-data.js +161 -77
  274. package/dist/src/report/report-fold.js +129 -52
  275. package/dist/src/report/report-main-red.js +55 -0
  276. package/dist/src/report/report-render.js +133 -64
  277. package/dist/src/report/report-types.js +1 -0
  278. package/dist/src/report/report.js +26 -9
  279. package/dist/src/request-timeouts.js +2 -2
  280. package/dist/src/review/known-flakes.js +100 -1
  281. package/dist/src/review/post-merge-review.js +106 -0
  282. package/dist/src/review/protected-edits.js +4 -5
  283. package/dist/src/review/review-followup.js +8 -8
  284. package/dist/src/review/review-precheck.js +66 -23
  285. package/dist/src/review/review-run.js +395 -0
  286. package/dist/src/review/review-verdict.js +2 -2
  287. package/dist/src/review/review.js +155 -360
  288. package/dist/src/review/secret-leaks.js +36 -16
  289. package/dist/src/review/suite-rerun.js +0 -1
  290. package/dist/src/roles/legacy-role-migration.js +80 -7
  291. package/dist/src/roles/role-catalog.js +69 -12
  292. package/dist/src/roles/role-guidance.js +55 -20
  293. package/dist/src/roles/role-labels.js +1 -2
  294. package/dist/src/roles/role-render.js +67 -50
  295. package/dist/src/roles/role-view.js +59 -47
  296. package/dist/src/roles/roles.js +31 -31
  297. package/dist/src/roles/runner-ids.js +97 -0
  298. package/dist/src/run/acceptance-greps.js +119 -0
  299. package/dist/src/run/feedback-filings.js +51 -0
  300. package/dist/src/{tick → run}/qa-coverage.js +9 -27
  301. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  302. package/dist/src/run/run-detail-data.js +179 -0
  303. package/dist/src/run/run-detail-render.js +98 -0
  304. package/dist/src/run/run-detail.js +168 -0
  305. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
  306. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  307. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
  308. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  309. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
  310. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  311. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  312. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
  313. package/dist/src/run/selfcheck-digest.js +40 -0
  314. package/dist/src/run/stage-backlog-checks.js +249 -0
  315. package/dist/src/run/stage-check.js +172 -0
  316. package/dist/src/run/stage-path-checks.js +730 -0
  317. package/dist/src/{loop → runner}/leftover.js +80 -25
  318. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  319. package/dist/src/runner/revision.js +202 -0
  320. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  321. package/dist/src/runner/runner-outcome.js +107 -0
  322. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  323. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
  324. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
  325. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  326. package/dist/src/runner/runner-state.js +102 -0
  327. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  328. package/dist/src/scheduling/backoff.js +38 -38
  329. package/dist/src/scheduling/claims.js +35 -22
  330. package/dist/src/scheduling/once-round.js +22 -22
  331. package/dist/src/scheduling/quiet-hours.js +5 -6
  332. package/dist/src/scheduling/scheduling.js +74 -64
  333. package/dist/src/scheduling/work-merged-cache.js +2 -2
  334. package/dist/src/status/status-budget.js +55 -47
  335. package/dist/src/status/status-data.js +43 -16
  336. package/dist/src/status/status-inbox.js +2 -2
  337. package/dist/src/status/status-install.js +0 -1
  338. package/dist/src/status/status-polls.js +28 -28
  339. package/dist/src/status/status-roles.js +39 -13
  340. package/dist/src/team/error-storm.js +8 -8
  341. package/dist/src/team/failure-spread.js +2 -2
  342. package/dist/src/team/orchestrator-info.js +15 -9
  343. package/dist/src/team/reclaim.js +5 -6
  344. package/dist/src/team/team-hold.js +7 -7
  345. package/dist/src/team/team-polls.js +91 -50
  346. package/dist/src/team/team-state.js +8 -2
  347. package/dist/src/text/datetime.js +63 -17
  348. package/dist/src/text/format.js +48 -11
  349. package/dist/src/text/markdown.js +8 -5
  350. package/dist/src/text/phrases.js +57 -23
  351. package/dist/src/text/suggest.js +1 -1
  352. package/dist/src/text/text.js +67 -13
  353. package/dist/src/ui/badges.js +65 -37
  354. package/dist/src/ui/gui/gui-client-backlog.js +292 -0
  355. package/dist/src/ui/gui/gui-client-boot.js +32 -26
  356. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  357. package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
  358. package/dist/src/ui/gui/gui-client-drawer.js +126 -285
  359. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  360. package/dist/src/ui/gui/gui-client-history.js +434 -67
  361. package/dist/src/ui/gui/gui-client-labels.js +1 -2
  362. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  363. package/dist/src/ui/gui/gui-client-model.js +27 -36
  364. package/dist/src/ui/gui/gui-client-operator.js +55 -51
  365. package/dist/src/ui/gui/gui-client-pending.js +227 -23
  366. package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
  367. package/dist/src/ui/gui/gui-client-report.js +87 -427
  368. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  369. package/dist/src/ui/gui/gui-client-settings.js +56 -4
  370. package/dist/src/ui/gui/gui-client-team.js +25 -465
  371. package/dist/src/ui/gui/gui-client.js +27 -25
  372. package/dist/src/ui/gui/gui-icons.js +0 -2
  373. package/dist/src/ui/gui/gui-page.js +9 -7
  374. package/dist/src/ui/gui/gui-styles.js +34 -34
  375. package/dist/src/ui/progress-data.js +9 -11
  376. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
  377. package/dist/src/ui/status-model.js +133 -125
  378. package/dist/src/ui/status-payload.js +136 -49
  379. package/dist/src/ui/status-render.js +101 -82
  380. package/dist/src/ui/team-alerts.js +248 -57
  381. package/dist/src/ui/tone.js +5 -6
  382. package/dist/src/ui/transcript-run.js +58 -0
  383. package/dist/src/ui/transcript-tail.js +28 -20
  384. package/dist/src/ui/transcript.js +63 -23
  385. package/dist/src/ui/tui/tui-app.js +0 -1
  386. package/dist/src/ui/tui/tui-backlog.js +51 -14
  387. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  388. package/dist/src/ui/tui/tui-editor-attach.js +3 -5
  389. package/dist/src/ui/tui/tui-editor-backends.js +132 -0
  390. package/dist/src/ui/tui/tui-editor-brief.js +10 -12
  391. package/dist/src/ui/tui/tui-editor-budget.js +35 -31
  392. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  393. package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
  394. package/dist/src/ui/tui/tui-editor-config.js +86 -0
  395. package/dist/src/ui/tui/tui-editor-entry.js +36 -47
  396. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  397. package/dist/src/ui/tui/tui-editor-instances.js +37 -0
  398. package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
  399. package/dist/src/ui/tui/tui-editor-interval.js +11 -9
  400. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  401. package/dist/src/ui/tui/tui-editor-notes.js +3 -5
  402. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  403. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  404. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  405. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  406. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  407. package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
  408. package/dist/src/ui/tui/tui-editor-retire.js +4 -6
  409. package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
  410. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  411. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  412. package/dist/src/ui/tui/tui-editor-session.js +60 -34
  413. package/dist/src/ui/tui/tui-editor-slots.js +23 -0
  414. package/dist/src/ui/tui/tui-editors.js +35 -2
  415. package/dist/src/ui/tui/tui-frame.js +56 -11
  416. package/dist/src/ui/tui/tui-history.js +8 -9
  417. package/dist/src/ui/tui/tui-input.js +13 -11
  418. package/dist/src/ui/tui/tui-keymap.js +0 -1
  419. package/dist/src/ui/tui/tui-keys.js +24 -9
  420. package/dist/src/ui/tui/tui-pane-activity.js +14 -11
  421. package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
  422. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  423. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  424. package/dist/src/ui/tui/tui-pane-health.js +58 -8
  425. package/dist/src/ui/tui/tui-pane-history.js +402 -82
  426. package/dist/src/ui/tui/tui-pane-pending.js +308 -13
  427. package/dist/src/ui/tui/tui-pane-queued.js +71 -55
  428. package/dist/src/ui/tui/tui-pane-report.js +46 -14
  429. package/dist/src/ui/tui/tui-pane-session.js +25 -6
  430. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  431. package/dist/src/ui/tui/tui-pane.js +80 -23
  432. package/dist/src/ui/tui/tui-panes.js +100 -30
  433. package/dist/src/ui/tui/tui.js +14 -14
  434. package/dist/src/verdict/fix-claim.js +82 -32
  435. package/dist/src/verdict/no-change.js +4 -4
  436. package/dist/src/verdict/refusal.js +40 -16
  437. package/dist/src/verdict/reply-contract.js +42 -31
  438. package/dist/src/version.js +0 -1
  439. package/package.json +2 -1
  440. package/dist/src/loop/revision.js +0 -70
  441. package/dist/src/roles/loop-ids.js +0 -77
  442. package/dist/src/tick/stage-check.js +0 -419
  443. package/dist/src/tick/telemetry-digest.js +0 -25
  444. package/dist/src/tick/tick-detail-data.js +0 -108
  445. package/dist/src/tick/tick-detail.js +0 -163
  446. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,11 +1,10 @@
1
- // vocabulary: plain
2
1
  /** The dashboard's browser-side twins of roleLabel and eventTypeLabel. The script is
3
2
  * String.raw text and cannot import server modules, so the data is interpolated at module build
4
3
  * and the few lines of logic are hand-kept copies, pinned against their TypeScript originals by
5
4
  * test/gui-client-labels.test.ts. Spliced into gui-client.ts's script right after the format
6
5
  * helpers, so every later client module can call the two functions. */
7
6
  import { EVENT_TYPE_LABELS, RUN_RESULT_LABELS } from "../../events/event-labels.js";
8
- import { INSTANCE_ID_SOURCE } from "../../roles/loop-ids.js";
7
+ import { INSTANCE_ID_SOURCE } from "../../roles/runner-ids.js";
9
8
  export const GUI_CLIENT_LABELS_JS = String.raw ` // labels:start
10
9
  const INSTANCE_ID = new RegExp(${JSON.stringify(INSTANCE_ID_SOURCE)});
11
10
  const EVENT_TYPE_LABELS = ${JSON.stringify(EVENT_TYPE_LABELS)};
@@ -1,6 +1,5 @@
1
- // vocabulary: plain
2
1
  /** A small, safe Markdown renderer for the dashboard, browser-side: the failure digest and the
3
- * backlog entries (PLANS/BUGS/QUESTIONS.md bodies) are Markdown the loops write, and reading
2
+ * backlog entries (PLANS/BUGS/QUESTIONS.md bodies) are Markdown the roles write, and reading
4
3
  * them as rendered headings, lists, and tables beats a pre-formatted text blob. It covers what
5
4
  * those files use — ATX headings, paragraphs, bullet and numbered lists (continuation lines
6
5
  * folded in), pipe tables with right-aligned columns, fenced code, block quotes, and inline
@@ -1,7 +1,6 @@
1
- // vocabulary: plain
2
1
  /** The dashboard's view model, browser-side — the pure layer between the status payload and
3
- * what the page shows, with no DOM of its own: each loop's phase pill (label, tone, live
4
- * detail), its last result in words with the tone of its outcome, the loop sort order and
2
+ * what the page shows, with no DOM of its own: each role's phase pill (label, tone, live
3
+ * detail), its last result in words with the tone of its outcome, the role sort order and
5
4
  * rank groups, the alerts list with the page's own offline entry, the activity feed's
6
5
  * kind/tone classification, the backlog title's trailing "(planned …)" metadata, and the
7
6
  * queued prompts in execution order. It is the browser twin of the rules status-model.ts
@@ -11,14 +10,14 @@
11
10
  * ENTRY_STAMP_META_SOURCE — String.raw interpolates substitutions normally — so the
12
11
  * browser regex cannot drift from the server twin the tests pin it against. */
13
12
  import { ENTRY_STAMP_META_SOURCE } from "../../backlog/backlog-structure.js";
14
- import { STALL_SOURCE } from "../tick-progress-model.js";
13
+ import { STALL_SOURCE } from "../run-progress-model.js";
15
14
  import { PROBLEM_EVENTS, PROBLEM_RESULTS, ROUTINE_BUILD_RESULTS, ROUTINE_EVENTS } from "../tone.js";
16
- import { LOOP_RANK_RULES } from "../status-model.js";
15
+ import { ROLE_RANK_RULES } from "../status-model.js";
17
16
  export const GUI_CLIENT_MODEL_JS = String.raw ` // view-model:start
18
- // How a loop's phase label (status-model.ts loopPhase) reads here: a status word and tone for
17
+ // How a role's phase label (status-model.ts rolePhase) reads here: a status word and tone for
19
18
  // its pill, whether it is live work, and the detail the label carried after its first word.
20
19
  function phaseInfo(phase) {
21
- // status-model.ts loopPhase appends a " · no change ×N" streak suffix to a resting idle
20
+ // status-model.ts rolePhase appends a " · no change ×N" streak suffix to a resting idle
22
21
  // cell's label. Strip it so the exact/startsWith matches and the parsed detail see the
23
22
  // plain phase; phaseDetailBase re-appends the streak once itself from the payload's
24
23
  // noChangeStreak, so the GUI renders it exactly once.
@@ -49,6 +48,7 @@ export const GUI_CLIENT_MODEL_JS = String.raw ` // view-model:start
49
48
  if (p === "waiting: daily cap spent") return { key: "cap", label: "Waiting", tone: "amber", live: false, detail: "daily cap" };
50
49
  if (p === "waiting: low disk") return { key: "disk", label: "Waiting", tone: "amber", live: false, detail: "low disk" };
51
50
  if (p === "waiting: bootstrap") return { key: "bootstrap", label: "Waiting", tone: "amber", live: false, detail: "bootstrap" };
51
+ if (p.startsWith("waiting: maintenance allowance spent")) return { key: "quota", label: "Waiting", tone: "amber", live: false, detail: "maintenance allowance spent this poll" };
52
52
  if (p.startsWith("waiting: maintenance allowance used")) return { key: "quota", label: "Waiting", tone: "amber", live: false, detail: "maintenance " + after(35) };
53
53
  if (p.startsWith("sleeping")) return { key: "sleeping", label: "Sleeping", tone: "gray", live: false, detail: "" };
54
54
  if (p.startsWith("idle: ")) return { key: "idle", label: "Idle", tone: "gray", live: false, detail: p.slice(6) };
@@ -58,12 +58,12 @@ export const GUI_CLIENT_MODEL_JS = String.raw ` // view-model:start
58
58
  if (p === "stopped") return { key: "stopped", label: "Stopped", tone: "gray", live: false, detail: "the team is not running" };
59
59
  return { key: "other", label: p || "unknown", tone: "gray", live: false, detail: "" };
60
60
  }
61
- // A stalled tool call or a long silence, as the phase label names them (tick-progress-model.ts's
62
- // inFlightDetail) — the cue that a working loop may be stuck. The pattern is that module's
61
+ // A stalled tool call or a long silence, as the phase label names them (run-progress-model.ts's
62
+ // inFlightDetail) — the cue that a working role may be stuck. The pattern is that module's
63
63
  // STALL_SOURCE (interpolated at module build), shared with team-alerts.ts's server-side
64
64
  // matcher so the two cannot drift from the wording the cell renders.
65
65
  const STALL = new RegExp(${JSON.stringify(STALL_SOURCE)});
66
- // A tick result (tick-outcome.ts TickResult) in words, with the tone of its outcome.
66
+ // A run result (run-outcome.ts RunResult) in words, with the tone of its outcome.
67
67
  const RESULT_TONES = {
68
68
  changed: "green",
69
69
  queued: "indigo",
@@ -88,31 +88,31 @@ export const GUI_CLIENT_MODEL_JS = String.raw ` // view-model:start
88
88
  // build (like STALL above) so the page cannot drift from the server's eventKind.
89
89
  const PROBLEM_RESULTS = ${JSON.stringify(PROBLEM_RESULTS)};
90
90
  const ROUTINE_BUILD_RESULTS = ${JSON.stringify(ROUTINE_BUILD_RESULTS)};
91
- // What explains a loop's last result: its summary, or — for a problem that carries none — the
92
- // last error the loop recorded.
91
+ // What explains a role's last result: its summary, or — for a problem that carries none — the
92
+ // last error the role recorded.
93
93
  const resultWhy = (l) => l.lastSummary || (PROBLEM_RESULTS.includes(l.lastResult) || l.phase === "failing" ? l.lastError || "" : "");
94
- // loop-sort:start
95
- // Loop order — status-model.ts's loopRank/sortLoopsByState, the TUI's order too: the running
94
+ // role-sort:start
95
+ // Role order — status-model.ts's roleRank/sortRolesByState, the TUI's order too: the running
96
96
  // director first (its own live work, outside the maxConcurrent cap it bypasses — BUGS.md
97
97
  // 2026-10-06), then live work (working, reviewing, merge), then work waiting in the pipe
98
- // line (approved, awaiting a slot), then loops that need attention (failing, main red), then
99
- // paused, then idle; within a rank the most recent tick first, a never-ticked loop last, ties
100
- // by name. The phase → rank rules are status-model.ts's LOOP_RANK_RULES (interpolated at
98
+ // line (approved, awaiting a slot), then roles that need attention (failing, main red), then
99
+ // paused, then idle; within a rank the most recent run first, a never-ran role last, ties
100
+ // by name. The phase → rank rules are status-model.ts's ROLE_RANK_RULES (interpolated at
101
101
  // module build, like STALL/PROBLEM_RESULTS above), so the literals cannot drift from the
102
102
  // server's copy.
103
- const LOOP_RANK_RULES = ${JSON.stringify(LOOP_RANK_RULES)};
104
- function loopRank(phase) {
103
+ const ROLE_RANK_RULES = ${JSON.stringify(ROLE_RANK_RULES)};
104
+ function roleRank(phase) {
105
105
  const p = String(phase || "");
106
- for (const r of LOOP_RANK_RULES) {
106
+ for (const r of ROLE_RANK_RULES) {
107
107
  if (r.active ? isActivePhase(p) : (r.phases || []).includes(p) || (r.prefixes || []).some((x) => p.startsWith(x)))
108
108
  return r.rank;
109
109
  }
110
110
  return 4;
111
111
  }
112
- function sortLoops(loops) {
113
- return loops.slice().sort((a, b) => {
114
- const ra = loopRank(a.phase);
115
- const rb = loopRank(b.phase);
112
+ function sortRoles(roles) {
113
+ return roles.slice().sort((a, b) => {
114
+ const ra = roleRank(a.phase);
115
+ const rb = roleRank(b.phase);
116
116
  if (ra !== rb) return ra - rb;
117
117
  const ta = a.lastRunEndedAt ?? 0;
118
118
  const tb = b.lastRunEndedAt ?? 0;
@@ -120,12 +120,12 @@ export const GUI_CLIENT_MODEL_JS = String.raw ` // view-model:start
120
120
  return a.role.localeCompare(b.role);
121
121
  });
122
122
  }
123
- // loop-sort:end
123
+ // role-sort:end
124
124
  // The team table's section for each rank. The running director has its own section so it
125
125
  // does not join the In-progress rows an operator scans against maxConcurrent; the permit
126
126
  // holders (rank 0) and the pipeline waiters (rank 1, labeled "Waiting to start") keep
127
127
  // sharing In progress (BUGS.md 2026-10-06).
128
- const LOOP_GROUPS = [["Director", -1], ["In progress", 0, 1], ["Needs attention", 2], ["Paused", 3], ["Idle", 4]];
128
+ const ROLE_GROUPS = [["Director", -1], ["In progress", 0, 1], ["Needs attention", 2], ["Paused", 3], ["Idle", 4]];
129
129
  // The payload's alerts (team-alerts.ts teamAlerts — the TUI's attention lines use the same
130
130
  // list), plus the one only the page can know: that its server stopped answering.
131
131
  const OFFLINE_ALERT = { key: "offline", tone: "red", title: "Lost contact with the dashboard server",
@@ -140,15 +140,6 @@ export const GUI_CLIENT_MODEL_JS = String.raw ` // view-model:start
140
140
  // are information.
141
141
  const NEEDS_YOU = (a) => a.tone === "red" || a.tone === "amber" || a.tone === "indigo";
142
142
  const needsYou = (alerts) => alerts.filter(NEEDS_YOU).length;
143
- // The needs-you alerts' keys — the set the sound cue diffs each poll against the last poll's.
144
- const needsYouKeys = (alerts) => alerts.filter(NEEDS_YOU).map((a) => a.key);
145
- // The needs-you keys of the given alerts that prevKeys (the previous poll's set, or null on
146
- // the first poll) lacks — exactly the alerts a new cue should announce. A null prev yields
147
- // every needs-you key, so a page opened onto an already-alerting team cues once.
148
- const newNeedsYouKeys = (prevKeys, alerts) => {
149
- const prior = new Set(prevKeys || []);
150
- return needsYouKeys(alerts).filter((k) => !prior.has(k));
151
- };
152
143
  // needs-you:end
153
144
  // An activity item's kind — its icon and tone, and whether the Notable filter keeps it.
154
145
  const ROUTINE_EVENTS = ${JSON.stringify(ROUTINE_EVENTS)};
@@ -172,7 +163,7 @@ export const GUI_CLIENT_MODEL_JS = String.raw ` // view-model:start
172
163
  const m = ENTRY_STAMP_RE.exec(String(t));
173
164
  return m ? { title: String(t).slice(0, m.index), meta: m[1] } : { title: String(t), meta: "" };
174
165
  }
175
- // Queued prompts in execution order — the director's first, then each loop's — each with the
166
+ // Queued prompts in execution order — the director's first, then each role's — each with the
176
167
  // queue-file address its Cancel button sends (never a list position, so a stale frame can
177
168
  // never cancel the wrong prompt).
178
169
  function queuedPrompts(d) {
@@ -1,4 +1,3 @@
1
- // vocabulary: plain
2
1
  import { PAUSE_REASON_MAX } from "../../team/team-state.js";
3
2
  /** The dashboard's team-wide operator controls, browser-side, all living in the sidebar so
4
3
  * they are in reach from every view: the daily spend badge — today's spend against the cap
@@ -6,16 +5,14 @@ import { PAUSE_REASON_MAX } from "../../team/team-state.js";
6
5
  * setter as the TUI's Ctrl+B) — the pause control, a menu of timed pauses with an
7
6
  * optional reason (POST /api/pause with `forSeconds` and `reason`, the CLI's
8
7
  * `pause --for`/`--reason`) that turns into a Resume button with the countdown while the
9
- * team is paused, and the sound toggle that mutes the alert cue (a per-browser preference,
10
- * no server call). Neither keeps optimistic state: after a successful
8
+ * team is paused. Neither keeps optimistic state: after a successful
11
9
  * POST the next status poll re-renders them from the server's answer, and a failed one toasts
12
10
  * the error and leaves the editor open. Spliced into gui-client.ts's script, where it reaches
13
11
  * the other sections' shared plumbing through that concatenation: the DOM and HTML helpers
14
12
  * ($, esc, icon), the formatters (fmtUsd, fmtCap, humanSeconds), the slot painter and toast
15
13
  * (paintPanel, showFlash), the POST helper (postJson), the click hook (onClick), the menu
16
14
  * registry (closeMenus, openMenu), the poll re-render (refresh, hoisted from gui-client-boot.ts
17
- * later in the join), the sound state it flips (soundMuted, setSoundMuted — gui-client-sound.ts
18
- * earlier in the join), and the shared page state it reads and invalidates (lastStatus,
15
+ * later in the join), and the shared page state it reads and invalidates (lastStatus,
19
16
  * lastPaint). Its own two popovers share their scaffolding through openPopover (each
20
17
  * supplies the html plus an onClosed that resets its flag and re-renders the slot), and
21
18
  * its two POSTs share their send-toast-close-repaint tail through the core's postAction. */
@@ -68,8 +65,8 @@ export const GUI_CLIENT_OPERATOR_JS = String.raw ` // shared-operator:start
68
65
  // The subscription usage window (plans/agent-backends.md §8, PLANS.md part 6d/7): the
69
66
  // sidebar's spend card renders it beside the daily spend, or in its place for a
70
67
  // subscription-only team. The label/status/clock rules mirror badges.ts's
71
- // claudeWindowBadge — the preformatted header fragment the TUI uses cannot be reused here
72
- // because the card lists EVERY window, not just the most constrained one.
68
+ // subscriptionWindowBadge — the preformatted header fragment the TUI uses cannot be reused here
69
+ // because the card lists EVERY window of EVERY block, not just the most constrained one.
73
70
  function windowLabel(type) {
74
71
  const m = /^(\w+)_(hour|day|week)$/.exec(String(type));
75
72
  if (!m) return String(type).replace(/_/g, " ");
@@ -94,6 +91,12 @@ export const GUI_CLIENT_OPERATOR_JS = String.raw ` // shared-operator:start
94
91
  if (hours < 24) return "resets in " + hours + "h " + (minutes % 60) + "m";
95
92
  return "resets in " + Math.floor(hours / 24) + "d " + (hours % 24) + "h";
96
93
  }
94
+ // A window's consumed fraction as a whole-number percent, the same shape as node-side
95
+ // utilizationPct - the browser script keeps its inline copy, so the text percentage and
96
+ // the meter width share this one home instead of each rounding w.utilization by hand.
97
+ function windowPct(w) {
98
+ return Math.round(w.utilization * 100);
99
+ }
97
100
  function chainMoved(chain) {
98
101
  return Object.values(chain || {}).some((t) => t.reason === "window" || t.reason === "reserve");
99
102
  }
@@ -106,12 +109,12 @@ export const GUI_CLIENT_OPERATOR_JS = String.raw ` // shared-operator:start
106
109
  return "<span class='side-card-value window-line'>" + esc(tier + " → " + t.pair + money + until) + "</span>";
107
110
  }).join("");
108
111
  }
109
- function windowLine(w, cw, chain) {
110
- const parts = ["Claude " + windowLabel(w.type)];
112
+ function windowLine(w, block, chain) {
113
+ const parts = [block.label + " " + windowLabel(w.type)];
111
114
  const word = windowWord(w.status);
112
- parts.push(typeof w.utilization === "number" ? Math.round(w.utilization * 100) + "%" : word === "ok" ? "ok · usage not reported" : word);
115
+ parts.push(typeof w.utilization === "number" ? windowPct(w) + "%" : word === "ok" ? "ok · usage not reported" : word);
113
116
  if (w.isUsingOverage) parts.push("overage");
114
- if (w.status === "rejected" && cw.heldUntil != null) parts.push((chainMoved(chain) ? "on fallback until " : "held until ") + windowClock(cw.heldUntil));
117
+ if (w.status === "rejected" && block.heldUntil != null) parts.push((chainMoved(chain) ? "on fallback until " : block.label + " model jobs wait until ") + windowClock(block.heldUntil));
115
118
  else if (w.status !== "reset" && typeof w.resetsAt === "number") parts.push(windowResets(w.resetsAt));
116
119
  return parts.join(" · ");
117
120
  }
@@ -120,35 +123,41 @@ export const GUI_CLIENT_OPERATOR_JS = String.raw ` // shared-operator:start
120
123
  if (w.status === "allowed_warning") return " t-amber";
121
124
  return "";
122
125
  }
123
- function windowLinesHtml(cw, chain) {
124
- if (!cw) return chainLinesHtml(chain);
125
- // Shortest window first (5h above 7d), whatever order the server ranks severity in; an
126
- // unlabelled type sorts last, then by type name. A copy, so cw.windows keeps its order.
126
+ function windowLinesHtml(blocks, only, chain) {
127
+ if (!blocks || blocks.length === 0) return chainLinesHtml(chain);
128
+ // One window line per block, its windows shortest-first (5h above 7d), whatever order
129
+ // the server ranks severity in; an unlabelled type sorts last, then by type name. A copy,
130
+ // so block.windows keeps its order.
131
+ return blocks.map((block) => blockLinesHtml(block, chain)).join("") + chainLinesHtml(chain);
132
+ }
133
+ function blockLinesHtml(block, chain) {
127
134
  const hours = (w) => {
128
135
  const m = /^(\d+)([hdw])$/.exec(windowLabel(w.type));
129
136
  return m ? Number(m[1]) * (m[2] === "h" ? 1 : m[2] === "d" ? 24 : 168) : Infinity;
130
137
  };
131
- const shortestFirst = cw.windows.slice().sort((a, b) => (hours(a) === hours(b) ? (a.type < b.type ? -1 : a.type > b.type ? 1 : 0) : hours(a) < hours(b) ? -1 : 1));
138
+ const shortestFirst = block.windows.slice().sort((a, b) => (hours(a) === hours(b) ? (a.type < b.type ? -1 : a.type > b.type ? 1 : 0) : hours(a) < hours(b) ? -1 : 1));
132
139
  return shortestFirst.map((w) => {
133
- const line = "<span class='side-card-value window-line" + windowLineTone(w) + "'>" + esc(windowLine(w, cw, chain)) + "</span>";
140
+ const line = "<span class='side-card-value window-line" + windowLineTone(w) + "'>" + esc(windowLine(w, block, chain)) + "</span>";
134
141
  if (typeof w.utilization !== "number") return line;
135
- const pct = Math.min(100, Math.round(w.utilization * 100));
142
+ const pct = Math.min(100, windowPct(w));
136
143
  return line + meterBar(windowLineTone(w), pct);
137
- }).join("") + chainLinesHtml(chain);
144
+ }).join("");
138
145
  }
139
146
  // The sidebar's spend card for a payload's budget block: a plain card for an all-free team
140
147
  // (no spend a cap could bind, so no editor), otherwise the button that opens the cap editor,
141
148
  // with a meter while a cap is set.
142
- function budgetCardHtml(b, cw, chain) {
149
+ function budgetCardHtml(b, blocks, only, chain) {
143
150
  // A subscription-only team has no priced spend for a cap to bind, so the window card
144
- // stands in place of the spend card and carries no editor.
145
- if (cw && cw.only) {
151
+ // stands in place of the spend card and carries no editor — but only while a window block
152
+ // actually exists; an all-free subscription-only team (only and free, no windows) falls
153
+ // through to the "Free models" spend card below so it never renders an empty window card.
154
+ if (only && blocks && blocks.length > 0) {
146
155
  return "<div class='side-card' title='Claude.ai subscription usage windows — a rejected window pauses claude model jobs until it resets'>" +
147
- "<span class='side-card-label'>" + icon("clock") + "Claude window</span>" + windowLinesHtml(cw, chain) + "</div>";
156
+ "<span class='side-card-label'>" + icon("clock") + "Claude window</span>" + windowLinesHtml(blocks, only, chain) + "</div>";
148
157
  }
149
158
  if (b.free) {
150
159
  return "<div class='side-card' title='Every model the team can use is cost n/a, so there is nothing to cap'>" +
151
- "<span class='side-card-label'>" + icon("dollar") + "Spend</span><span class='side-card-value'>Free models</span>" + windowLinesHtml(cw, chain) + "</div>";
160
+ "<span class='side-card-label'>" + icon("dollar") + "Spend</span><span class='side-card-value'>Free models</span>" + windowLinesHtml(blocks, only, chain) + "</div>";
152
161
  }
153
162
  const capped = b.capUsd > 0;
154
163
  const pct = capped ? Math.min(100, (b.spentUsd / b.capUsd) * 100) : 0;
@@ -157,12 +166,12 @@ export const GUI_CLIENT_OPERATOR_JS = String.raw ` // shared-operator:start
157
166
  esc(capped ? "Today's spend against the daily cap — click to change the cap" : "No daily cap — click to set one") + "'>" +
158
167
  "<span class='side-card-label'>" + icon("dollar") + "Spent today<span class='edit'>" + (capped ? "Edit cap" : "Set a cap") + "</span></span>" +
159
168
  "<span class='side-card-value'>" + esc(fmtUsd(b.spentUsd)) + " <small>" + esc(capped ? "of " + fmtCap(b.capUsd) : "no daily cap") + "</small></span>" +
160
- (capped ? meterBar(tone, pct.toFixed(1)) : "") + windowLinesHtml(cw, chain) + "</button>";
169
+ (capped ? meterBar(tone, pct.toFixed(1)) : "") + windowLinesHtml(blocks, only, chain) + "</button>";
161
170
  }
162
171
  function renderBudgetBadge(d) {
163
172
  if (budgetEditing) return; // the open editor owns the slot until save or cancel
164
173
  const b = d && d.budget;
165
- paintPanel("budgetwrap", b ? budgetCardHtml(b, d && d.claudeWindow, d && d.chain) : "");
174
+ paintPanel("budgetwrap", b ? budgetCardHtml(b, d && d.subscriptionWindows, d && d.subscriptionOnly, d && d.chain) : "");
166
175
  }
167
176
  function openBudgetEditor() {
168
177
  const b = lastStatus && lastStatus.budget;
@@ -198,7 +207,19 @@ export const GUI_CLIENT_OPERATOR_JS = String.raw ` // shared-operator:start
198
207
  const t = String(raw).trim();
199
208
  if (t === "") return { ok: true, value: 0 };
200
209
  const n = Number(t);
201
- if (!/^\d*\.?\d+$/.test(t) || !Number.isFinite(n) || n < 0 || n > Number.MAX_SAFE_INTEGER) {
210
+ // The two rejections are genuinely different rules, so they name their own one, mirroring
211
+ // the TUI's parseBudgetInput: the shape rejection (a fine number like "1e2" or "0x10"
212
+ // that is within the bound but that no one means as USD) names the plain-decimal rule,
213
+ // while the bounds rejection names the bound because a 25-digit run IS "a number of 0 or
214
+ // more". A single message would mislead: "1e2" is rejected as shape, yet would be told
215
+ // it is not a number of 0 or more.
216
+ if (!/^\d*\.?\d+$/.test(t)) {
217
+ return {
218
+ ok: false,
219
+ error: "the cap must be a plain dollar amount like 25, 12.50, or .5 — no +/-, exponent, or hex notation — leave it empty for no cap",
220
+ };
221
+ }
222
+ if (!Number.isFinite(n) || n < 0 || n > Number.MAX_SAFE_INTEGER) {
202
223
  return {
203
224
  ok: false,
204
225
  error: "the cap must be a number of 0 or more, at most " + Number.MAX_SAFE_INTEGER + " — leave it empty for no cap",
@@ -223,8 +244,12 @@ export const GUI_CLIENT_OPERATOR_JS = String.raw ` // shared-operator:start
223
244
  }
224
245
  const value = parsed.value;
225
246
  // A failure leaves the editor open so the value can be fixed.
226
- await postAction("/api/budget", { maxDailyCostUsd: value },
227
- value > 0 ? "Daily cap set to " + fmtCap(value) : "Daily cap removed");
247
+ await postAction("/api/budget", { maxDailyCostUsd: value }, (d) => {
248
+ if (d && d.unchanged) {
249
+ return "Daily cap is already " + (value > 0 ? fmtCap(value) : "removed") + " — nothing saved";
250
+ }
251
+ return value > 0 ? "Daily cap set to " + fmtCap(value) : "Daily cap removed";
252
+ });
228
253
  }
229
254
  onClick((t, ev) => {
230
255
  if (t.closest("#budgetbadge")) {
@@ -370,25 +395,4 @@ export const GUI_CLIENT_OPERATOR_JS = String.raw ` // shared-operator:start
370
395
  if (choice) postStopSchedule({ inSeconds: Number(choice.dataset.stopin) });
371
396
  });
372
397
  // stop-control:end
373
-
374
- // sound-control:start
375
- // The sidebar's sound control, beside the theme toggle: a speaker icon that flips between
376
- // the unmuted and muted glyph. A per-browser preference (setSoundMuted stores it), not team
377
- // state — no server call; the choice takes effect on the next cue.
378
- function soundControlHtml() {
379
- return "<button type='button' class='icon-btn' id='soundtoggle' title='" +
380
- (soundMuted ? "Sound is muted — click to unmute the alert cue" : "Sound is on — click to mute the alert cue") +
381
- "' aria-label='" + (soundMuted ? "Unmute" : "Mute") + " the alert cue'>" +
382
- icon(soundMuted ? "mute" : "sound") + "</button>";
383
- }
384
- function renderSoundBadge() {
385
- paintPanel("soundwrap", soundControlHtml());
386
- }
387
- function toggleSound() {
388
- setSoundMuted(!soundMuted);
389
- renderSoundBadge();
390
- }
391
- onClick((t) => {
392
- if (t.closest("#soundtoggle")) toggleSound();
393
- });
394
- // sound-control:end`;
398
+ `;