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,7 +1,6 @@
1
- // vocabulary: plain
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
- import { removeQuiet, unlinkAllMissingTolerant } from "../files/files.js";
3
+ import { dirEntryNames, removeQuiet, unlinkAllMissingTolerant } from "../files/files.js";
5
4
  import { isJsonObject } from "../files/json-object.js";
6
5
  import { roleInboxDir } from "../paths.js";
7
6
  import { errorMessage, isNonBlankString } from "../text/text.js";
@@ -10,7 +9,7 @@ import { agree, plural } from "../text/phrases.js";
10
9
  /** Images attached to queued prompts: a GUI drop or paste into the composer saves each image
11
10
  * beside the prompt's queue file (same stem, an image extension — the `.md` filter in
12
11
  * listQueueFiles never sees them) and appends one `[image attached: <absolute path>]` line per
13
- * image to the queued text, so the receiving loop's pi agent can view the file with its read
12
+ * image to the queued text, so the receiving role's pi agent can view the file with its read
14
13
  * tool. Validation lives here once; every surface (the GUI endpoints through submitRolePrompt,
15
14
  * the requeue policy through stripVanishedImageReferences) shares it. */
16
15
  /** The image extensions pi's read tool renders — exactly what an attachment may carry.
@@ -32,6 +31,17 @@ export function isPromptImageSibling(stem, entry) {
32
31
  return ((rest.startsWith(".") && PROMPT_IMAGE_EXTENSIONS.includes(rest.toLowerCase())) ||
33
32
  (/^-\d+\./.test(rest) && PROMPT_IMAGE_EXTENSIONS.includes(rest.slice(rest.indexOf(".")).toLowerCase())));
34
33
  }
34
+ /** The image siblings of `stem` already in `dir`, as the collision set for a new image's name.
35
+ * A directory that is missing or unreadable yields an empty set: nothing there to collide
36
+ * with, and the write that follows reports the real failure, if any. */
37
+ function takenImageSiblings(dir, stem) {
38
+ const taken = new Set();
39
+ for (const entry of dirEntryNames(dir)) {
40
+ if (isPromptImageSibling(stem, entry))
41
+ taken.add(entry);
42
+ }
43
+ return taken;
44
+ }
35
45
  /** Pick a file name with extension `ext` under `stem` that does not collide with any name
36
46
  * already in `taken`, and record it: `<stem><ext>` when free, else the first
37
47
  * `<stem>-<n><ext>` (n from 2 up) that is. The one home of the dedup-suffix rule
@@ -60,7 +70,7 @@ const BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
60
70
  /** The image-count rule: at most PROMPT_IMAGES_MAX_COUNT entries. Split out of
61
71
  * promptImagesProblem so a surface holding the raw path list can refuse an over-long
62
72
  * `--attach` list before reading or encoding a single file, with the count wording still the
63
- * one definition both call sites render. Null when the count is acceptable. */
73
+ * one definition every raw-path surface renders. Null when the count is acceptable. */
64
74
  export function promptImagesCountProblem(count) {
65
75
  return count > PROMPT_IMAGES_MAX_COUNT
66
76
  ? `at most ${PROMPT_IMAGES_MAX_COUNT} images per prompt (got ${count})`
@@ -186,16 +196,7 @@ export function savePromptImages(root, role, queueFile, images) {
186
196
  // that adds a `.png` to a prompt whose image is already `<stem>.png` lands as `<stem>-2.png`
187
197
  // instead of overwriting (and orphaning the reference to) the existing file. A directory
188
198
  // that does not exist yet leaves the set empty; the write below creates it.
189
- const taken = new Set();
190
- try {
191
- for (const entry of fs.readdirSync(dir)) {
192
- if (isPromptImageSibling(stem, entry))
193
- taken.add(entry);
194
- }
195
- }
196
- catch {
197
- // No inbox dir yet (or unreadable): the write below reports the real failure, if any.
198
- }
199
+ const taken = takenImageSiblings(dir, stem);
199
200
  const paths = [];
200
201
  // The image whose write is in flight: it only joins `paths` once its write returns, so a
201
202
  // write that throws after creating the file (ENOSPC, EIO) would otherwise be the one
@@ -237,7 +238,7 @@ export function imageReferenceLines(paths) {
237
238
  * counterpart to imageReferenceLines. Used by the edit path to carry an attached prompt's
238
239
  * references (and so their sibling files) over a text rewrite: an edit replaces the prompt's
239
240
  * text, not its attachments, which travel with the prompt until a cancel or the fulfilling
240
- * tick's sweep removes them. A line that is not exactly the writer's shape is content, not a
241
+ * run's sweep removes them. A line that is not exactly the writer's shape is content, not a
241
242
  * reference, and is ignored. */
242
243
  export function imageReferencePaths(text) {
243
244
  const paths = [];
@@ -250,7 +251,7 @@ export function imageReferencePaths(text) {
250
251
  }
251
252
  /** Move a re-queued prompt's attachments from its old queue file's stem to the new queue
252
253
  * file's stem and rewrite their `[image attached: …]` lines, so an attachment always travels
253
- * with the prompt's current queue file: the cancel sweep and the fulfilling tick's sweep
254
+ * with the prompt's current queue file: the cancel sweep and the fulfilling run's sweep
254
255
  * (removeSameStemSiblings, keyed on that file's stem) then find it, and a requeue can never
255
256
  * strand a file under a stem no queue file names. Only a line naming a same-stem sibling of
256
257
  * `fromFile` inside its directory is treated as an attachment — a hand-typed or edited
@@ -259,23 +260,16 @@ export function imageReferencePaths(text) {
259
260
  * referenced attachment that is already gone is left for stripVanishedImageReferences to drop
260
261
  * and record. Each attachment moves on its own: one file that is gone or cannot be renamed is
261
262
  * left in place and named in `problem`, while the attachments around it still move, so a
262
- * single missing or unmovable file cannot strand the rest under a stem no queue file names. */
263
+ * single missing or unmovable file cannot strand the rest under a stem no queue file names.
264
+ * `moves` lists each rename done, so a caller whose later write fails can move them back. */
263
265
  export function movePromptImages(fromFile, toFile, text) {
264
266
  const dir = path.resolve(path.dirname(fromFile));
265
267
  const fromStem = path.basename(fromFile, ".md");
266
268
  const toStem = path.basename(toFile, ".md");
267
269
  const toDir = path.resolve(path.dirname(toFile));
268
- const taken = new Set();
269
- try {
270
- for (const entry of fs.readdirSync(toDir)) {
271
- if (isPromptImageSibling(toStem, entry))
272
- taken.add(entry);
273
- }
274
- }
275
- catch {
276
- // The queue directory is gone; nothing to collide with.
277
- }
270
+ const taken = takenImageSiblings(toDir, toStem);
278
271
  const problems = [];
272
+ const moves = [];
279
273
  const out = [];
280
274
  for (const line of text.split("\n")) {
281
275
  const referenced = referencedImagePath(line);
@@ -306,9 +300,14 @@ export function movePromptImages(fromFile, toFile, text) {
306
300
  out.push(line);
307
301
  continue;
308
302
  }
303
+ moves.push({ from: resolved, to: target });
309
304
  out.push(line.replace(referenced, target));
310
305
  }
311
- return { text: out.join("\n"), problem: problems.length > 0 ? problems.join("; ") : null };
306
+ return {
307
+ text: out.join("\n"),
308
+ problem: problems.length > 0 ? problems.join("; ") : null,
309
+ moves,
310
+ };
312
311
  }
313
312
  /** The prompt text without its `[image attached: …]` reference lines, trailing blank lines
314
313
  * trimmed — the editable body an editor should show. The reference block is plumbing the edit
@@ -322,8 +321,8 @@ export function stripImageReferenceLines(text) {
322
321
  return kept.join("\n");
323
322
  }
324
323
  /** Drop the `[image attached: …]` lines whose files are no longer on disk from a prompt being
325
- * re-queued after a tick whose attached image is gone (removed out of band, or by a cancel),
326
- * so a re-queued copy would otherwise send the loop's agent after files that are gone. Each
324
+ * re-queued after a run whose attached image is gone (removed out of band, or by a cancel),
325
+ * so a re-queued copy would otherwise send the role's agent after files that are gone. Each
327
326
  * vanished line is replaced by one honest note naming what
328
327
  * happened; references whose files still exist are kept. Unchanged text comes back untouched
329
328
  * (dropped: []) so the common no-images case costs nothing. */
@@ -1,4 +1,3 @@
1
- // vocabulary: plain
2
1
  import path from "node:path";
3
2
  import { logEventBestEffort } from "../events/events.js";
4
3
  import { roleInboxDir } from "../paths.js";
@@ -7,8 +6,8 @@ import { gotSuffix } from "../text/text.js";
7
6
  import { promptPreview, queuedFileAtPosition, queuedFiles, removeSameStemSiblings, takeQueuedFile } from "./inbox.js";
8
7
  /** Read one addressed queue file and turn it into a cancel outcome — the one home of the
9
8
  * race-policy-and-event pairing both cancel paths go through: { status: "gone" } when the
10
- * loop already dequeued or another cancel removed the file (takeQueuedFile's race policy,
11
- * a normal race, never an error), else one prompt_cancelled event under that loop (preview
9
+ * role already dequeued or another cancel removed the file (takeQueuedFile's race policy,
10
+ * a normal race, never an error), else one prompt_cancelled event under that role (preview
12
11
  * via promptPreview, exactly like the prompt_enqueued sibling) logged, best-effort, only
13
12
  * after a successful removal — the removal is the durable effect, so an unwritable feed must
14
13
  * not report the cancel as failed;
@@ -19,14 +18,14 @@ function takeCancelledPrompt(root, role, file) {
19
18
  const text = takeQueuedFile(file);
20
19
  if (text === null)
21
20
  return { status: "gone" };
22
- // A cancelled prompt's image attachments go with it; a dequeue leaves them for the tick to
21
+ // A cancelled prompt's image attachments go with it; a dequeue leaves them for the run to
23
22
  // read and they are removed on fulfillment (src/inbox/pending-prompt.ts).
24
23
  removeSameStemSiblings(root, role, file);
25
24
  logEventBestEffort(root, { source: role, type: "prompt_cancelled", preview: promptPreview(text) });
26
25
  return { status: "cancelled", text };
27
26
  }
28
- /** Remove the Nth prompt queued for one loop — 1-based, as shown by `tumwater prompt --list` —
29
- * and log one prompt_cancelled event under that loop (preview via promptPreview, exactly like
27
+ /** Remove the Nth prompt queued for one role — 1-based, as shown by `tumwater prompt --list` —
28
+ * and log one prompt_cancelled event under that role (preview via promptPreview, exactly like
30
29
  * its prompt_enqueued sibling). Throws for out-of-range positions with no side effects; returns
31
30
  * { status: "gone" } when the file disappears between listing and removal instead of throwing.
32
31
  * The race policy and event live in takeCancelledPrompt. */
@@ -34,8 +33,8 @@ export function cancelRolePrompt(root, role, position) {
34
33
  return takeCancelledPrompt(root, role, queuedFileAtPosition(root, role, position));
35
34
  }
36
35
  /** Forward a resolution to a list-wide command's outcome: a miss shape returns verbatim, a
37
- * found one calls `found` with the resolved loop (so the command's own side-effecting queue
38
- * action stays lazy — it runs only for the single loop that holds the position). Both
36
+ * found one calls `found` with the resolved role (so the command's own side-effecting queue
37
+ * action stays lazy — it runs only for the single role that holds the position). Both
39
38
  * list-wide commands — cancelListedPrompt and editListedPrompt — are the call sites. */
40
39
  export function listedQueueOutcome(resolved, found) {
41
40
  switch (resolved.status) {
@@ -47,7 +46,7 @@ export function listedQueueOutcome(resolved, found) {
47
46
  }
48
47
  }
49
48
  /** Resolve a list-wide 1-based position (a `prompt --cancel`/`--edit` with no --role) to the
50
- * one loop whose queue is long enough to hold it, in `scope` order — the semantics the
49
+ * one role whose queue is long enough to hold it, in `scope` order — the semantics the
51
50
  * ListedQueueResolution doc above specifies. Sizes only: no queue content is read. */
52
51
  export function resolveListedQueue(root, scope, position) {
53
52
  const candidates = scope.filter((role) => queuedFiles(root, role).length >= position);
@@ -62,7 +61,7 @@ export function resolveListedQueue(root, scope, position) {
62
61
  return { status: "found", role };
63
62
  }
64
63
  /** Cancel by the position numbering `tumwater prompt --list` prints with no --role: the
65
- * position resolves through the shared resolveListedQueue (above), then one loop's queue
64
+ * position resolves through the shared resolveListedQueue (above), then one role's queue
66
65
  * takes the cancel (cancelRolePrompt's race policy and event). */
67
66
  export function cancelListedPrompt(root, scope, position) {
68
67
  const resolved = resolveListedQueue(root, scope, position);
@@ -73,7 +72,7 @@ export function cancelListedPrompt(root, scope, position) {
73
72
  }));
74
73
  }
75
74
  /** The queue-file-name guard for a file-addressed cancel: a name arriving over HTTP is
76
- * trusted only as a plain basename inside the loop's queue directory — anything containing a
75
+ * trusted only as a plain basename inside the role's queue directory — anything containing a
77
76
  * path separator or equal to `..` could name a file elsewhere on disk, a NUL byte is not a
78
77
  * character any filename can hold (the fs layer throws on it rather than answering ENOENT,
79
78
  * so the endpoint's 400 contract needs it rejected before anything touches the disk), and a
@@ -8,20 +8,20 @@ import { assertQueueFileName, listedQueueOutcome, resolveListedQueue } from "./i
8
8
  import { notBeforeMs, notBeforeMarker, stripNotBeforeMarker } from "./prompt-not-before.js";
9
9
  import { roleInboxDir } from "../paths.js";
10
10
  /** Rewrite one queued prompt's file in place — the shared core behind editRolePrompt (address
11
- * by per-loop position) and editQueuedFile (address by queue-file basename): same queue file
11
+ * by per-role position) and editQueuedFile (address by queue-file basename): same queue file
12
12
  * (so the filename's enqueue stamp and the entry's position behind older prompts are
13
13
  * untouched), same atomic write enqueueRolePrompt uses
14
14
  * (writeTextAtomic's temp file + rename, so a concurrent dequeue or another edit can never
15
15
  * expose a half-written file), and the existing not-before deferral carried over — when the
16
16
  * old file starts with the marker, the new text is written under the same marker; a
17
17
  * non-deferred prompt stays marker-free even if the new text's first line resembles a
18
- * marker. Logs one prompt_edited event under that loop (preview via promptPreview, exactly
18
+ * marker. Logs one prompt_edited event under that role (preview via promptPreview, exactly
19
19
  * like its prompt_cancelled sibling) after a successful write, best-effort: the rewrite is
20
20
  * the durable effect, so an unwritable feed must not report the edit as failed.
21
21
  *
22
22
  * Carries an attached prompt's `[image attached: …]` reference lines (and so their sibling
23
23
  * files) over the rewrite — the edit replaces the prompt's text, not its attachments, which
24
- * stay beside the edited queue file until a cancel or the fulfilling tick's sweep removes
24
+ * stay beside the edited queue file until a cancel or the fulfilling run's sweep removes
25
25
  * them. An optional `images` array (the CLI's
26
26
  * `prompt --edit … --attach`) appends its newly saved reference lines after the carried ones,
27
27
  * so one command corrects the text and the images in place instead of cancel-and-re-queue;
@@ -29,7 +29,7 @@ import { roleInboxDir } from "../paths.js";
29
29
  * new files again.
30
30
  *
31
31
  * Enforces the same prefill length cap as a submission (promptLengthProblem): the edited
32
- * text plus its carried image lines ride into the target tick's prefill exactly like a
32
+ * text plus its carried image lines ride into the target run's prefill exactly like a
33
33
  * queued prompt, so an edit to an over-cap value must not bypass the cap every enqueue path
34
34
  * applies. The initial text check throws before any read or write; the composed check (body
35
35
  * plus carried image lines) throws before the write when the body alone fit.
@@ -38,7 +38,7 @@ import { roleInboxDir } from "../paths.js";
38
38
  * the image-address form throws for a name failing queueFileNameProblem before touching disk.
39
39
  * The race policy pairs with the outcome: the file is read first, and a read that hits ENOENT
40
40
  * returns { status: "gone" } —
41
- * the loop dequeued it or a cancel removed it between listing and write, a normal race
41
+ * the role dequeued it or a cancel removed it between listing and write, a normal race
42
42
  * exactly like takeCancelledPrompt's; any other read error propagates (a permission failure
43
43
  * is not a race). The write itself is unconditional once the read succeeded: a dequeue that
44
44
  * lands between that read and the rename is a narrower race than takeQueuedFile's own
@@ -78,7 +78,7 @@ function editPromptAtFile(root, role, file, newText, images) {
78
78
  throw new Error(saved.problem);
79
79
  savedImagePaths = saved.paths;
80
80
  }
81
- // The composed text is what a tick's prefill receives, so the submission cap covers the
81
+ // The composed text is what a run's prefill receives, so the submission cap covers the
82
82
  // body plus every reference line; the carried paths stay first, the new ones append after
83
83
  // them in one reference block.
84
84
  composed = text + imageReferenceLines([...carriedPaths, ...savedImagePaths]);
@@ -96,7 +96,7 @@ function editPromptAtFile(root, role, file, newText, images) {
96
96
  logEventBestEffort(root, { source: role, type: "prompt_edited", preview: promptPreview(composed) });
97
97
  return { status: "edited", oldText, newText: composed };
98
98
  }
99
- /** Rewrite the Nth prompt queued for one loop — 1-based, as shown by `tumwater prompt
99
+ /** Rewrite the Nth prompt queued for one role — 1-based, as shown by `tumwater prompt
100
100
  * --list` — in place, through the shared editPromptAtFile. See that helper for the full
101
101
  * contract (deferral carry-over, attachment carry-over, the prompt_edited event, the race
102
102
  * policy). */
@@ -118,7 +118,7 @@ export function editQueuedFile(root, role, name, newText, images) {
118
118
  * edit affordance (PLANS.md 2026-10-11): stripNotBeforeMarker drops the deferral plumbing,
119
119
  * stripImageReferenceLines drops the `[image attached: …]` reference block the edit carries
120
120
  * over unchanged (so it is never shown in the editor and cannot be edited away), and trimEnd
121
- * drops any blank lines left behind. null when the file is gone — the loop dequeued it or
121
+ * drops any blank lines left behind. null when the file is gone — the role dequeued it or
122
122
  * another operator cancelled it, which the endpoint reports as { status: "gone" } rather than
123
123
  * an error. Throws for a name failing queueFileNameProblem. Reading the one named file directly
124
124
  * keeps the dashboard's lazy fetch off the per-poll path. */
@@ -131,7 +131,7 @@ export function readQueuedPromptBody(root, role, name) {
131
131
  }
132
132
  /** Edit by the position numbering `tumwater prompt --list` prints with no --role: the same
133
133
  * candidate resolution cancelListedPrompt uses — resolveListedQueue, in inbox-cancel.ts, so
134
- * the two cannot drift — followed by editRolePrompt in the resolved loop. One loop holding
134
+ * the two cannot drift — followed by editRolePrompt in the resolved role. One role holding
135
135
  * the position resolves, several are an ambiguity the caller reports with a --role escape
136
136
  * hatch, none is a miss carrying the largest queue length for the error's count. */
137
137
  export function editListedPrompt(root, scope, position, newText, images) {
@@ -1,28 +1,28 @@
1
- // vocabulary: plain
2
1
  import { enqueueRolePrompt, promptPreview } from "./inbox.js";
3
2
  import { unlinkAllMissingTolerant } from "../files/files.js";
4
3
  import { imageReferenceLines, promptImagesProblem, savePromptImages, } from "./inbox-attachments.js";
5
4
  import { logEventBestEffort } from "../events/events.js";
6
- import { INITIAL_PROMPT_MAX_CHARS } from "../brief/brief.js";
5
+ import { BRIEF_TEXT_MAX_CHARS } from "../brief/brief.js";
7
6
  import { DIRECTOR_ROLE } from "../roles/roles.js";
8
7
  import { tooLongMessage } from "../text/text.js";
9
8
  import { defaultConfig, loadConfigSafe } from "../config/config.js";
10
9
  import { roleImageInfo } from "../agent/image-input.js";
10
+ import { readPiListedModels } from "../pi/pi-listed-models.js";
11
11
  import { readImageProbeCache } from "../doctor/image-probe.js";
12
12
  import { roleLabel } from "../roles/role-labels.js";
13
13
  /** The user-facing submission pipeline for the prompt queues (the store's mechanics —
14
14
  * listing, peeking, dequeuing, cancelling, and their race policy — live in src/inbox/inbox.ts,
15
15
  * and the image side of a submission in src/inbox/inbox-attachments.ts): the shared length cap,
16
16
  * the validation, and the submit wrappers the TUI, GUI, and CLI all go through. */
17
- /** Cap on a submitted prompt's length: the same ceiling for every loop's queue, director and
18
- * role alike, because every prompt rides into its target tick's prefill — a megabyte pasted
17
+ /** Cap on a submitted prompt's length: the same ceiling for every role's queue, director and
18
+ * role alike, because every prompt rides into its target run's prefill — a megabyte pasted
19
19
  * into the TUI, a GUI POST, or a shell-mistaken `tumwater prompt $(cat …)` would otherwise
20
- * ride into the next tick's context wholesale. submitPrompt and submitRolePrompt reject
20
+ * ride into the next run's context wholesale. submitPrompt and submitRolePrompt reject
21
21
  * over-long text before it is queued, so no surface can enqueue it. */
22
- export const DIRECTOR_PROMPT_MAX_CHARS = INITIAL_PROMPT_MAX_CHARS;
23
- /** The one length rule for a submitted prompt, scoped to the loop it targets: the error
22
+ export const DIRECTOR_PROMPT_MAX_CHARS = BRIEF_TEXT_MAX_CHARS;
23
+ /** The one length rule for a submitted prompt, scoped to the role it targets: the error
24
24
  * message when the trimmed text exceeds DIRECTOR_PROMPT_MAX_CHARS, null when it fits. The
25
- * message names the target loop's tick — `--role qa` must not be told its text rides into
25
+ * message names the target role's run — `--role qa` must not be told its text rides into
26
26
  * the director's prefill. submitPrompt/submitRolePrompt throw it before anything is queued
27
27
  * or logged; the GUI asks it first so an over-long prompt answers 400 (a user-input error)
28
28
  * while an unexpected submit failure (a broken inbox's EACCES) stays the 500 its
@@ -33,12 +33,12 @@ export function promptLengthProblem(text, role = DIRECTOR_ROLE) {
33
33
  return null;
34
34
  return tooLongMessage("the prompt", prompt.length, DIRECTOR_PROMPT_MAX_CHARS, `it rides into the ${roleLabel(role)} run's prefill`);
35
35
  }
36
- /** The image-input submit guidance for one loop (plans/image-input.md "Surfaces" and
36
+ /** The image-input submit guidance for one role (plans/image-input.md "Surfaces" and
37
37
  * "Attachments", part 5/5): what an image-carrying prompt to `role` means. `refusal` is the
38
- * throw-and-submit-nothing message when the loop's seam reads no images and no peer tier can
39
- * (an attachment no configured model can read is refused at submit, not discovered at tick
38
+ * throw-and-submit-nothing message when the role's seam reads no images and no peer tier can
39
+ * (an attachment no configured model can read is refused at submit, not discovered at run
40
40
  * time); `notice` is what a surface prints/shows when the prompt will instead rerun on another
41
- * tier. Both null when the loop's own model reads images (or capability is unknown — an
41
+ * tier. Both null when the role's own model reads images (or capability is unknown — an
42
42
  * unknown seam is never assumed incapable, mirroring the doctor's `tier models` stance), so
43
43
  * the common text-only or vision path says nothing. One resolver (roleImageInfo), shared with
44
44
  * the status rows and role page, so the gate cannot disagree with what `tumwater role` or the
@@ -49,7 +49,7 @@ export function imagePromptGuidance(root, role) {
49
49
  // The same probe cache the status rows, role page, and images alert read (roleImageInfo's
50
50
  // fourth argument): a claimed pi model whose doctor probe failed reads text-only here too,
51
51
  // so the gate never grants image capability that the surfaces report as absent.
52
- const { imageInput, imageReroute } = roleImageInfo(cfg, role, undefined, readImageProbeCache(root));
52
+ const { imageInput, imageReroute } = roleImageInfo(cfg, role, undefined, readImageProbeCache(root), readPiListedModels(root));
53
53
  if (imageInput !== false)
54
54
  return {};
55
55
  if (imageReroute) {
@@ -61,8 +61,8 @@ export function imagePromptGuidance(root, role) {
61
61
  refusal: `${role}'s model can't read images and no configured model reads images for it — declare image input on a vision model in pi's models.json, or point a tier at one`,
62
62
  };
63
63
  }
64
- /** A user submits a new prompt for one loop (TUI, GUI, or CLI): enqueue it there and record it
65
- * in the event log under that loop, best-effort — the queue write is the durable effect the
64
+ /** A user submits a new prompt for one role (TUI, GUI, or CLI): enqueue it there and record it
65
+ * in the event log under that role, best-effort — the queue write is the durable effect the
66
66
  * caller asked for, so an unwritable feed must not report the submit as failed. Returns the
67
67
  * trimmed prompt that was queued. The logged
68
68
  * preview goes through promptPreview — not a raw slice — so an over-long prompt is marked with
@@ -72,7 +72,7 @@ export function imagePromptGuidance(root, role) {
72
72
  * operator. An optional images array (the GUI composer's drop/paste attachments) rides
73
73
  * through savePromptImages beside the queue file, with one [image attached: …] reference line
74
74
  * appended to the queued text; image problems throw before anything is queued — and so does
75
- * an over-cap composed text: the reference lines ride into the tick's prefill just like the
75
+ * an over-cap composed text: the reference lines ride into the run's prefill just like the
76
76
  * prompt body, so the length cap is enforced on the final text (body + image lines), not the
77
77
  * body alone.
78
78
  * An optional `notBeforeMs` defers the prompt (PLANS.md "tumwater prompt --at <duration>"):
@@ -87,7 +87,7 @@ export function submitRolePrompt(root, role, text, images, notBeforeMs) {
87
87
  throw new Error(imageProblem);
88
88
  // Image input gate (plans/image-input.md "Attachments", part 5/5): an attachment no
89
89
  // configured model can read is refused here, before anything is queued or written — the
90
- // tick would only fail discovering it. A loop that reroutes a blind read is not refused;
90
+ // run would only fail discovering it. A role that reroutes a blind read is not refused;
91
91
  // the surface prints/shows the notice instead (imagePromptGuidance is called again there).
92
92
  const guidance = imagePromptGuidance(root, role);
93
93
  if (guidance.refusal)
@@ -118,7 +118,7 @@ function submitPromptWithImages(root, role, text, images, notBeforeMs) {
118
118
  throw new Error(saved.problem); // Unreachable: validated above.
119
119
  savedImagePaths = saved.paths;
120
120
  final = prompt + imageReferenceLines(saved.paths);
121
- // The image reference lines ride into the target tick's prefill exactly like the
121
+ // The image reference lines ride into the target run's prefill exactly like the
122
122
  // prompt body, so the shared length cap applies to the composed text, not the body
123
123
  // alone. This runs inside the decorate hook, before enqueueRolePrompt's one atomic
124
124
  // write, so an over-cap composition queues nothing; the images saved above are
@@ -1,6 +1,5 @@
1
- // vocabulary: plain
2
1
  import path from "node:path";
3
- import { existsSync } from "node:fs";
2
+ import { existsSync, renameSync } from "node:fs";
4
3
  import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
5
4
  import { queueFileName, queueFileStamp, removeQueueFile } from "../files/file-queue.js";
6
5
  import { readTextIfExists, writeTextAtomic } from "../files/files.js";
@@ -13,9 +12,11 @@ import { movePromptImages, stripVanishedImageReferences } from "./inbox-attachme
13
12
  * `tumwater:not-before` marker, which rides in the text) byte-for-byte to the new stem, its
14
13
  * image attachments moving to that new stem too (movePromptImages, with
15
14
  * stripVanishedImageReferences dropping any already-gone reference) — and one `prompt_topped`
16
- * event is logged under the loop. Returns { status: "gone" } when the file disappears between
15
+ * event is logged under the role. Returns { status: "gone" } when the file disappears between
17
16
  * the caller's listing and the read here (a concurrent pop is a normal race, never an
18
- * error), sharing the store's takeQueuedFile convention. */
17
+ * error), sharing the store's takeQueuedFile convention. A failed write of the new queue file
18
+ * moves the attachments back and rethrows, so the prompt stays queued at its old position with
19
+ * its images. */
19
20
  function moveFileToTop(root, role, file) {
20
21
  const text = readTextIfExists(file);
21
22
  if (text === null)
@@ -40,18 +41,33 @@ function moveFileToTop(root, role, file) {
40
41
  if (moved.problem !== null)
41
42
  warnEventBestEffort(root, role, moved.problem);
42
43
  const carried = stripVanishedImageReferences(moved.text).text;
43
- writeTextAtomic(newFile, carried);
44
+ try {
45
+ writeTextAtomic(newFile, carried);
46
+ }
47
+ catch (err) {
48
+ // Put the attachments back under the old stem, so the prompt still queued there keeps its
49
+ // images; one failed rename must not stop the others or mask the write error.
50
+ for (const m of moved.moves) {
51
+ try {
52
+ renameSync(m.to, m.from);
53
+ }
54
+ catch {
55
+ // Best effort: the write error is the one the caller sees.
56
+ }
57
+ }
58
+ throw err;
59
+ }
44
60
  removeQueueFile(file);
45
61
  logEventBestEffort(root, { source: role, type: "prompt_topped", preview: promptPreview(carried) });
46
62
  return { status: "topped", preview: promptPreview(carried) };
47
63
  }
48
- /** Move the Nth prompt queued for one loop — 1-based, as shown by `tumwater prompt --list` —
49
- * to the head of its queue, so the loop dequeues it next. Inbox order is the queue-file name
64
+ /** Move the Nth prompt queued for one role — 1-based, as shown by `tumwater prompt --list` —
65
+ * to the head of its queue, so the role dequeues it next. Inbox order is the queue-file name
50
66
  * (`queueFileName`'s timestamp stamp), so the only way to make one prompt next is to give it a
51
67
  * stamp one below the queue's minimum; the file's text (and its `tumwater:not-before` marker,
52
68
  * which rides in the text) is carried byte-for-byte to the new stem, its image attachments
53
69
  * move to that new stem too (movePromptImages, with stripVanishedImageReferences dropping any
54
- * already-gone reference), and one `prompt_topped` event is logged under the loop.
70
+ * already-gone reference), and one `prompt_topped` event is logged under the role.
55
71
  *
56
72
  * Throws for out-of-range positions and for position 1 ("already first"), both with no side
57
73
  * effects — the moveEntryToTop precedent. Returns { status: "gone" } when the file disappears
@@ -68,7 +84,7 @@ export function movePromptToTop(root, role, position) {
68
84
  * queue — the file-addressed twin of movePromptToTop, for the dashboard's per-row Move to
69
85
  * top affordance: the address is the file itself, so a stale poll can never reorder the
70
86
  * wrong prompt. Throws for a name failing queueFileNameProblem (re-checked through
71
- * assertQueueFileName, so a caller that skips the guard fails closed); a file the loop
87
+ * assertQueueFileName, so a caller that skips the guard fails closed); a file the role
72
88
  * already dequeued answers "gone", and the queue head answers "already_first" with no write.
73
89
  * Reuses moveFileToTop's rename-and-event pairing, so both address forms cannot drift. */
74
90
  export function topQueuedFile(root, role, name) {