tumwater 0.2.0 → 0.4.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 (464) 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 +55 -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 +6 -4
  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 +404 -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 +281 -55
  41. package/dist/src/backlog/backlog-cli-view.js +255 -0
  42. package/dist/src/backlog/backlog-cli.js +91 -335
  43. package/dist/src/backlog/backlog-commit.js +20 -13
  44. package/dist/src/backlog/backlog-eligibility.js +193 -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 +264 -0
  48. package/dist/src/backlog/backlog-release.js +134 -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-guardrails.js +136 -0
  56. package/dist/src/baseline/main-red.js +153 -36
  57. package/dist/src/baseline/main-watch.js +232 -0
  58. package/dist/src/brief/brief-edit.js +12 -12
  59. package/dist/src/brief/brief.js +31 -51
  60. package/dist/src/budget/budget-fallback-view.js +288 -0
  61. package/dist/src/budget/budget-wiring.js +158 -0
  62. package/dist/src/budget/budget.js +31 -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 +65 -3
  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 +181 -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 +183 -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 +291 -598
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-review-commands.js +113 -0
  84. package/dist/src/cli/cli-role-command.js +525 -0
  85. package/dist/src/cli/cli-start-summary.js +167 -0
  86. package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
  87. package/dist/src/cli/completion.js +169 -22
  88. package/dist/src/cli/config-commands.js +58 -26
  89. package/dist/src/cli/follow-view.js +75 -0
  90. package/dist/src/cli/help.js +258 -82
  91. package/dist/src/cli/log-commands.js +164 -85
  92. package/dist/src/cli/question-commands.js +211 -10
  93. package/dist/src/cli.js +173 -45
  94. package/dist/src/collections.js +56 -13
  95. package/dist/src/concurrency/check-permit.js +20 -3
  96. package/dist/src/concurrency/lock.js +17 -13
  97. package/dist/src/concurrency/provider-lease.js +44 -8
  98. package/dist/src/concurrency/semaphore.js +102 -26
  99. package/dist/src/config/config-aliases.js +0 -1
  100. package/dist/src/config/config-editable-keys.js +16 -1
  101. package/dist/src/config/config-fallbacks.js +94 -56
  102. package/dist/src/config/config-field-checks.js +40 -10
  103. package/dist/src/config/config-keys.js +30 -8
  104. package/dist/src/config/config-live.js +63 -24
  105. package/dist/src/config/config-request.js +137 -0
  106. package/dist/src/config/config-schema.js +31 -24
  107. package/dist/src/config/config-selector-checks.js +123 -0
  108. package/dist/src/config/config-validation-agents.js +80 -0
  109. package/dist/src/config/config-validation-model.js +116 -0
  110. package/dist/src/config/config-validation.js +169 -222
  111. package/dist/src/config/config-views.js +136 -54
  112. package/dist/src/config/config-write-core.js +135 -0
  113. package/dist/src/config/config-write.js +164 -265
  114. package/dist/src/config/config.js +53 -38
  115. package/dist/src/config/model-selector.js +34 -34
  116. package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
  117. package/dist/src/doctor/doctor-backlog-plans.js +398 -0
  118. package/dist/src/doctor/doctor-backlog.js +249 -512
  119. package/dist/src/doctor/doctor-check-names.js +85 -0
  120. package/dist/src/doctor/doctor-checks.js +61 -593
  121. package/dist/src/doctor/doctor-claude-checks.js +142 -0
  122. package/dist/src/doctor/doctor-codex-checks.js +276 -0
  123. package/dist/src/doctor/doctor-contention-checks.js +506 -0
  124. package/dist/src/doctor/doctor-fallback-checks.js +130 -0
  125. package/dist/src/doctor/doctor-holds.js +80 -66
  126. package/dist/src/doctor/doctor-install.js +2 -1
  127. package/dist/src/doctor/doctor-model-checks.js +216 -537
  128. package/dist/src/doctor/doctor-notify.js +9 -5
  129. package/dist/src/doctor/doctor-opencode-checks.js +164 -0
  130. package/dist/src/doctor/doctor-orphans.js +182 -63
  131. package/dist/src/doctor/doctor-pi-checks.js +62 -0
  132. package/dist/src/doctor/doctor-project-checks.js +397 -0
  133. package/dist/src/doctor/doctor.js +126 -77
  134. package/dist/src/doctor/image-probe.js +21 -8
  135. package/dist/src/doctor/outcome.js +3 -2
  136. package/dist/src/doctor/probe.js +21 -12
  137. package/dist/src/events/event-format.js +185 -89
  138. package/dist/src/events/event-labels.js +11 -5
  139. package/dist/src/events/event-legacy.js +39 -15
  140. package/dist/src/events/event-read.js +52 -39
  141. package/dist/src/events/event-tally.js +16 -0
  142. package/dist/src/events/event-window.js +80 -26
  143. package/dist/src/events/events.js +30 -13
  144. package/dist/src/events/notify.js +7 -7
  145. package/dist/src/failure/failure-cluster.js +12 -12
  146. package/dist/src/failure/failure-data.js +65 -60
  147. package/dist/src/failure/failure-render.js +29 -21
  148. package/dist/src/failure/failure-state-change.js +17 -12
  149. package/dist/src/failure/time-spend.js +35 -35
  150. package/dist/src/files/file-queue.js +17 -0
  151. package/dist/src/files/files.js +111 -35
  152. package/dist/src/files/json-files.js +3 -3
  153. package/dist/src/files/json-object.js +28 -7
  154. package/dist/src/files/stat-cache.js +2 -2
  155. package/dist/src/files/tail.js +1 -1
  156. package/dist/src/gates/bootstrap-gates.js +4 -5
  157. package/dist/src/gates/budget-gates.js +88 -225
  158. package/dist/src/gates/disk-gate.js +1 -1
  159. package/dist/src/gates/gate-polls.js +101 -143
  160. package/dist/src/gates/maintenance-quota.js +37 -30
  161. package/dist/src/gates/pause-gates.js +28 -10
  162. package/dist/src/gates/readiness.js +7 -3
  163. package/dist/src/gates/role-cap-gates.js +8 -8
  164. package/dist/src/gates/startup-gate.js +94 -32
  165. package/dist/src/gates/streak-gate.js +54 -30
  166. package/dist/src/git/commit-message.js +89 -46
  167. package/dist/src/git/git-diff.js +16 -10
  168. package/dist/src/git/git-run.js +79 -38
  169. package/dist/src/git/git.js +54 -34
  170. package/dist/src/git/slot-salvage.js +253 -0
  171. package/dist/src/git/slots-state.js +6 -6
  172. package/dist/src/git/worktree-pool.js +132 -38
  173. package/dist/src/git/worktree-use.js +4 -11
  174. package/dist/src/git/worktree.js +94 -37
  175. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  176. package/dist/src/gui/gui-args.js +79 -8
  177. package/dist/src/gui/gui-backlog-commands.js +43 -19
  178. package/dist/src/gui/gui-backlog-endpoints.js +156 -0
  179. package/dist/src/gui/gui-command.js +26 -13
  180. package/dist/src/gui/gui-config-commands.js +168 -80
  181. package/dist/src/gui/gui-endpoint-commands.js +5 -4
  182. package/dist/src/gui/gui-endpoint-common.js +4 -4
  183. package/dist/src/gui/gui-endpoints.js +11 -380
  184. package/dist/src/gui/gui-history-endpoints.js +301 -0
  185. package/dist/src/gui/gui-operator-commands.js +235 -13
  186. package/dist/src/gui/gui-prompt-commands.js +26 -20
  187. package/dist/src/gui/gui-report-endpoints.js +97 -0
  188. package/dist/src/gui/gui-server.js +26 -5
  189. package/dist/src/gui/gui-team-commands.js +7 -7
  190. package/dist/src/gui/http-body.js +1 -1
  191. package/dist/src/history/history-data.js +122 -234
  192. package/dist/src/history/history-queued.js +155 -0
  193. package/dist/src/history/history-render.js +42 -22
  194. package/dist/src/history/history.js +190 -49
  195. package/dist/src/inbox/inbox-attachments.js +29 -30
  196. package/dist/src/inbox/inbox-cancel.js +10 -11
  197. package/dist/src/inbox/inbox-edit.js +9 -9
  198. package/dist/src/inbox/inbox-submit.js +18 -18
  199. package/dist/src/inbox/inbox-top.js +28 -17
  200. package/dist/src/inbox/inbox.js +42 -43
  201. package/dist/src/inbox/pending-prompt.js +26 -26
  202. package/dist/src/inbox/prompt-commands.js +62 -49
  203. package/dist/src/inbox/prompt-not-before.js +2 -2
  204. package/dist/src/init/init-templates.js +1 -2
  205. package/dist/src/init/init.js +28 -23
  206. package/dist/src/merge/backlog-conflicts.js +215 -35
  207. package/dist/src/merge/merge-batch.js +14 -8
  208. package/dist/src/merge/merge-cancel.js +53 -0
  209. package/dist/src/merge/merge-check-failures.js +44 -22
  210. package/dist/src/merge/merge-core.js +58 -32
  211. package/dist/src/merge/merge-diff.js +5 -4
  212. package/dist/src/merge/merge-drain.js +45 -19
  213. package/dist/src/merge/merge-git.js +177 -38
  214. package/dist/src/merge/merge-pipeline.js +15 -3
  215. package/dist/src/merge/merge-post-review.js +214 -0
  216. package/dist/src/merge/merge-queue-render.js +48 -11
  217. package/dist/src/merge/merge-queue.js +52 -15
  218. package/dist/src/merge/merge-requeue.js +103 -0
  219. package/dist/src/merge/merge-slot.js +61 -16
  220. package/dist/src/merge/merge-stack.js +27 -11
  221. package/dist/src/merge/merge-to-main.js +77 -26
  222. package/dist/src/merge/merge-top.js +83 -0
  223. package/dist/src/merge/merge-verify.js +75 -18
  224. package/dist/src/notes-cli.js +35 -15
  225. package/dist/src/operator/operator-commands.js +192 -81
  226. package/dist/src/operator/operator-intent.js +69 -42
  227. package/dist/src/operator/operator-requests.js +35 -40
  228. package/dist/src/operator/retire.js +5 -6
  229. package/dist/src/operator/revert.js +142 -34
  230. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  231. package/dist/src/orchestrator/orchestrator-poll.js +352 -0
  232. package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
  233. package/dist/src/orchestrator/orchestrator.js +129 -369
  234. package/dist/src/orchestrator/retention.js +42 -8
  235. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  236. package/dist/src/orchestrator/scheduling-scope.js +245 -0
  237. package/dist/src/orchestrator/scheduling-skip.js +195 -0
  238. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  239. package/dist/src/orchestrator/startup-migrations.js +11 -7
  240. package/dist/src/paths.js +126 -49
  241. package/dist/src/pi/command-shape.js +12 -1
  242. package/dist/src/pi/pi-args.js +6 -4
  243. package/dist/src/pi/pi-backend.js +6 -6
  244. package/dist/src/pi/pi-bin.js +54 -63
  245. package/dist/src/pi/pi-listed-models.js +53 -0
  246. package/dist/src/pi/pi-models.js +63 -30
  247. package/dist/src/pi/pi-run-result.js +1 -1
  248. package/dist/src/pi/pi-settings.js +1 -1
  249. package/dist/src/pi/pi-stream.js +6 -6
  250. package/dist/src/pi/pi-watchdogs.js +18 -19
  251. package/dist/src/pi-extension/bounded-output.js +5 -3
  252. package/dist/src/pi-extension/context-budget.js +2 -2
  253. package/dist/src/pi-extension/context-shake.js +1 -1
  254. package/dist/src/pi-extension/context-window.js +0 -1
  255. package/dist/src/pi-extension/full-output.js +3 -10
  256. package/dist/src/pi-extension/strip-thinking.js +0 -1
  257. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  258. package/dist/src/pi-extension/write-guard.js +37 -0
  259. package/dist/src/process/launch-services.js +1 -2
  260. package/dist/src/process/node-bin.js +16 -0
  261. package/dist/src/process/process-group.js +8 -18
  262. package/dist/src/process/process.js +41 -2
  263. package/dist/src/process/run-marker.js +26 -6
  264. package/dist/src/process/supervisor.js +22 -21
  265. package/dist/src/prompt/dialect.js +17 -2
  266. package/dist/src/prompt/gate-notes.js +203 -0
  267. package/dist/src/prompt/gate-prompts.js +150 -217
  268. package/dist/src/prompt/notes-backlog.js +184 -0
  269. package/dist/src/prompt/principles.js +1 -2
  270. package/dist/src/prompt/prompt-followup.js +19 -14
  271. package/dist/src/prompt/prompt.js +131 -204
  272. package/dist/src/redeploy/redeploy-policy.js +24 -19
  273. package/dist/src/redeploy/redeploy-probes.js +1 -1
  274. package/dist/src/redeploy/redeploy.js +2 -3
  275. package/dist/src/redeploy/redeployer.js +20 -20
  276. package/dist/src/redeploy/self-reload.js +2 -1
  277. package/dist/src/report/report-data.js +188 -124
  278. package/dist/src/report/report-fold.js +130 -54
  279. package/dist/src/report/report-main-red.js +55 -0
  280. package/dist/src/report/report-render.js +136 -65
  281. package/dist/src/report/report-types.js +1 -0
  282. package/dist/src/report/report.js +23 -9
  283. package/dist/src/request-timeouts.js +2 -2
  284. package/dist/src/review/diff-rules.js +53 -0
  285. package/dist/src/review/known-flakes.js +112 -1
  286. package/dist/src/review/post-merge-review-accept.js +51 -0
  287. package/dist/src/review/post-merge-review-render.js +112 -0
  288. package/dist/src/review/post-merge-review.js +239 -0
  289. package/dist/src/review/protected-edits.js +24 -10
  290. package/dist/src/review/quarantine-render.js +65 -0
  291. package/dist/src/review/review-followup.js +8 -8
  292. package/dist/src/review/review-precheck.js +66 -23
  293. package/dist/src/review/review-run.js +395 -0
  294. package/dist/src/review/review-verdict.js +2 -2
  295. package/dist/src/review/review.js +180 -401
  296. package/dist/src/review/secret-leaks.js +36 -16
  297. package/dist/src/review/suite-rerun.js +0 -1
  298. package/dist/src/roles/legacy-role-migration.js +80 -7
  299. package/dist/src/roles/role-catalog.js +127 -12
  300. package/dist/src/roles/role-guidance.js +65 -20
  301. package/dist/src/roles/role-labels.js +7 -5
  302. package/dist/src/roles/role-render.js +108 -56
  303. package/dist/src/roles/role-view.js +95 -56
  304. package/dist/src/roles/roles.js +33 -33
  305. package/dist/src/roles/runner-ids.js +97 -0
  306. package/dist/src/run/acceptance-greps.js +119 -0
  307. package/dist/src/run/ci-parity.js +121 -0
  308. package/dist/src/run/feedback-filings.js +51 -0
  309. package/dist/src/run/qa-coverage.js +125 -0
  310. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  311. package/dist/src/run/run-detail-data.js +175 -0
  312. package/dist/src/run/run-detail-render.js +91 -0
  313. package/dist/src/run/run-detail.js +168 -0
  314. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
  315. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  316. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
  317. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  318. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
  319. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  320. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  321. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
  322. package/dist/src/run/selfcheck-digest.js +40 -0
  323. package/dist/src/run/stage-backlog-checks.js +249 -0
  324. package/dist/src/run/stage-check.js +178 -0
  325. package/dist/src/run/stage-checks-shared.js +65 -0
  326. package/dist/src/run/stage-name-checks.js +204 -0
  327. package/dist/src/run/stage-path-checks.js +231 -0
  328. package/dist/src/run/stage-prose-checks.js +261 -0
  329. package/dist/src/runner/leftover.js +216 -0
  330. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  331. package/dist/src/runner/revision.js +191 -0
  332. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  333. package/dist/src/runner/runner-outcome.js +109 -0
  334. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  335. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
  336. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
  337. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  338. package/dist/src/runner/runner-state.js +103 -0
  339. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  340. package/dist/src/scheduling/backoff.js +38 -38
  341. package/dist/src/scheduling/claim-entries.js +49 -0
  342. package/dist/src/scheduling/claims.js +35 -22
  343. package/dist/src/scheduling/once-round.js +22 -22
  344. package/dist/src/scheduling/quiet-hours.js +5 -6
  345. package/dist/src/scheduling/scheduling.js +74 -64
  346. package/dist/src/scheduling/work-merged-cache.js +2 -2
  347. package/dist/src/status/status-budget.js +55 -47
  348. package/dist/src/status/status-data.js +49 -17
  349. package/dist/src/status/status-inbox.js +2 -2
  350. package/dist/src/status/status-install.js +0 -1
  351. package/dist/src/status/status-polls.js +43 -28
  352. package/dist/src/status/status-roles.js +50 -13
  353. package/dist/src/team/error-storm.js +8 -8
  354. package/dist/src/team/failure-spread.js +2 -2
  355. package/dist/src/team/orchestrator-info.js +15 -9
  356. package/dist/src/team/reclaim.js +7 -8
  357. package/dist/src/team/team-hold.js +7 -7
  358. package/dist/src/team/team-polls.js +91 -50
  359. package/dist/src/team/team-state.js +8 -2
  360. package/dist/src/text/datetime.js +65 -18
  361. package/dist/src/text/format.js +48 -11
  362. package/dist/src/text/markdown.js +8 -5
  363. package/dist/src/text/phrases.js +60 -23
  364. package/dist/src/text/suggest.js +1 -1
  365. package/dist/src/text/text.js +78 -13
  366. package/dist/src/ui/badges.js +99 -38
  367. package/dist/src/ui/gui/gui-client-backlog.js +314 -0
  368. package/dist/src/ui/gui/gui-client-boot.js +89 -61
  369. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  370. package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
  371. package/dist/src/ui/gui/gui-client-drawer.js +155 -289
  372. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  373. package/dist/src/ui/gui/gui-client-history.js +437 -68
  374. package/dist/src/ui/gui/gui-client-labels.js +6 -3
  375. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  376. package/dist/src/ui/gui/gui-client-model.js +27 -36
  377. package/dist/src/ui/gui/gui-client-operator.js +60 -52
  378. package/dist/src/ui/gui/gui-client-pending.js +405 -24
  379. package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
  380. package/dist/src/ui/gui/gui-client-report.js +105 -430
  381. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  382. package/dist/src/ui/gui/gui-client-settings.js +163 -5
  383. package/dist/src/ui/gui/gui-client-team.js +36 -465
  384. package/dist/src/ui/gui/gui-client.js +38 -26
  385. package/dist/src/ui/gui/gui-icons.js +0 -2
  386. package/dist/src/ui/gui/gui-page.js +10 -7
  387. package/dist/src/ui/gui/gui-styles.js +38 -34
  388. package/dist/src/ui/progress-data.js +9 -11
  389. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
  390. package/dist/src/ui/status-model.js +133 -125
  391. package/dist/src/ui/status-payload.js +364 -49
  392. package/dist/src/ui/status-render.js +109 -82
  393. package/dist/src/ui/team-alerts.js +728 -62
  394. package/dist/src/ui/tone.js +12 -6
  395. package/dist/src/ui/transcript-run.js +58 -0
  396. package/dist/src/ui/transcript-tail.js +28 -20
  397. package/dist/src/ui/transcript.js +63 -23
  398. package/dist/src/ui/tui/tui-app.js +0 -1
  399. package/dist/src/ui/tui/tui-backlog.js +53 -14
  400. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  401. package/dist/src/ui/tui/tui-editor-attach.js +4 -12
  402. package/dist/src/ui/tui/tui-editor-backends.js +127 -0
  403. package/dist/src/ui/tui/tui-editor-brief.js +14 -36
  404. package/dist/src/ui/tui/tui-editor-budget.js +36 -39
  405. package/dist/src/ui/tui/tui-editor-check.js +29 -0
  406. package/dist/src/ui/tui/tui-editor-checks.js +28 -0
  407. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  408. package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
  409. package/dist/src/ui/tui/tui-editor-config.js +99 -0
  410. package/dist/src/ui/tui/tui-editor-entry.js +39 -64
  411. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  412. package/dist/src/ui/tui/tui-editor-instances.js +46 -0
  413. package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
  414. package/dist/src/ui/tui/tui-editor-interval.js +29 -16
  415. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  416. package/dist/src/ui/tui/tui-editor-notes.js +9 -29
  417. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  418. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  419. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  420. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  421. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  422. package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
  423. package/dist/src/ui/tui/tui-editor-retire.js +5 -13
  424. package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
  425. package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
  426. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  427. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  428. package/dist/src/ui/tui/tui-editor-session.js +101 -34
  429. package/dist/src/ui/tui/tui-editor-slots.js +24 -0
  430. package/dist/src/ui/tui/tui-editors.js +44 -2
  431. package/dist/src/ui/tui/tui-frame.js +68 -11
  432. package/dist/src/ui/tui/tui-history.js +8 -9
  433. package/dist/src/ui/tui/tui-input.js +13 -11
  434. package/dist/src/ui/tui/tui-keymap.js +0 -1
  435. package/dist/src/ui/tui/tui-keys.js +33 -9
  436. package/dist/src/ui/tui/tui-pane-activity.js +35 -19
  437. package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
  438. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  439. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  440. package/dist/src/ui/tui/tui-pane-health.js +168 -8
  441. package/dist/src/ui/tui/tui-pane-history.js +446 -82
  442. package/dist/src/ui/tui/tui-pane-pending.js +539 -15
  443. package/dist/src/ui/tui/tui-pane-queued.js +78 -57
  444. package/dist/src/ui/tui/tui-pane-report.js +54 -16
  445. package/dist/src/ui/tui/tui-pane-session.js +34 -6
  446. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  447. package/dist/src/ui/tui/tui-pane.js +91 -28
  448. package/dist/src/ui/tui/tui-panes.js +153 -34
  449. package/dist/src/ui/tui/tui.js +18 -15
  450. package/dist/src/verdict/fix-claim.js +129 -33
  451. package/dist/src/verdict/no-change.js +4 -4
  452. package/dist/src/verdict/refusal.js +40 -16
  453. package/dist/src/verdict/reply-contract.js +77 -31
  454. package/dist/src/version.js +0 -1
  455. package/package.json +2 -1
  456. package/dist/src/loop/leftover.js +0 -130
  457. package/dist/src/loop/revision.js +0 -70
  458. package/dist/src/roles/loop-ids.js +0 -77
  459. package/dist/src/tick/qa-coverage.js +0 -108
  460. package/dist/src/tick/stage-check.js +0 -419
  461. package/dist/src/tick/telemetry-digest.js +0 -25
  462. package/dist/src/tick/tick-detail-data.js +0 -108
  463. package/dist/src/tick/tick-detail.js +0 -163
  464. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,21 +1,26 @@
1
- // vocabulary: plain
2
1
  import { fail, say, sayJsonLine } from "./cli-output.js";
3
- import { durationLabel, failRivalShapes, flagValue, parseCountFlag, parseGrepFlag, parseRoleScope, parseSinceFlag, parseTypeFlag } from "./cli-args.js";
2
+ import { hasFollowFlag, LOGS_TRANSCRIPT_VALUE_ERROR } from "./cli-flag-specs.js";
3
+ import { durationLabel, failRivalShapes, failUntilShorterThanSince, flagValue, parseCountFlag, parseGrepFlag, parseRoleListScope, parseRoleScope, parseSinceFlag, parseTypeFlag } from "./cli-args.js";
4
4
  import { LOGS_SINCE_MAX_MS, readEventsSince, SPARSE_WINDOW_NOTE, } from "../events/event-window.js";
5
- import { compareCodeUnits, increment, sortByValueDesc } from "../collections.js";
6
- import { parseEventLine, readEventsTailWithEnd } from "../events/event-read.js";
7
- import { eventMatchesGrep, formatEvent } from "../events/event-format.js";
5
+ import { compareCodeUnits, increment } from "../collections.js";
6
+ import { eventTypeTally } from "../events/event-tally.js";
7
+ import { eventRole, parseEventLine, readEventsTailWithEnd } from "../events/event-read.js";
8
+ import { eventMatchesGrep, eventResult, formatEvent } from "../events/event-format.js";
8
9
  import { canonicalEventType } from "../events/event-legacy.js";
9
10
  import { EVENT_TYPE_LABELS, eventTypeLabel } from "../events/event-labels.js";
10
11
  import { followFile } from "../files/tail.js";
12
+ import { roleInScope } from "../roles/runner-ids.js";
13
+ import { startParentDeathWatch } from "../process/supervisor.js";
11
14
  import { typoSuffix } from "../text/suggest.js";
12
- import { createTranscriptRenderer } from "../ui/transcript.js";
15
+ import { splitCommaList } from "../text/text.js";
16
+ import { createTranscriptFilter, createTranscriptRenderer } from "../ui/transcript.js";
13
17
  import { readTranscriptTail } from "../ui/transcript-tail.js";
18
+ import { isNotableEvent } from "../ui/tone.js";
14
19
  import { eventsLogPath, piLogPath } from "../paths.js";
15
20
  /** The read-only observing half of the CLI's non-dispatch commands: `tumwater logs` and its
16
- * `--role` transcript view, split out of cli.ts so the entry point stays a dispatch table. Unlike
21
+ * `--transcript` view, split out of cli.ts so the entry point stays a dispatch table. Unlike
17
22
  * operator/operator-commands.ts these write nothing but stdout — they only read the event log and
18
- * each loop's agent transcript. It lives beside the other CLI command bodies (history.ts,
23
+ * each role's agent transcript. It lives beside the other CLI command bodies (history.ts,
19
24
  * cli/cli-query-commands.ts, operator/operator-commands.ts), which may import the ui/ rendering
20
25
  * layer they drive — here the event formatter and the transcript renderer — while src/ui/
21
26
  * itself stays off src/ module boundaries it does not own. */
@@ -36,28 +41,30 @@ function sayEventLine(e, json) {
36
41
  * In `--json` mode it emits one compact object mapping `type → count` with keys sorted, the
37
42
  * machine-readable twin of the prose tally. */
38
43
  function sayTally(events, json) {
39
- const counts = new Map();
40
- for (const e of events)
41
- increment(counts, e.type);
42
44
  if (json) {
45
+ // The machine-readable twin keeps its own alphabetical key sort; the prose tally uses the
46
+ // shared eventTypeTally ordering.
47
+ const counts = new Map();
48
+ for (const e of events)
49
+ increment(counts, e.type);
43
50
  const obj = {};
44
51
  for (const [type] of [...counts.entries()].sort((a, b) => compareCodeUnits(a[0], b[0])))
45
52
  obj[type] = counts.get(type);
46
53
  sayJsonLine(obj);
47
54
  return;
48
55
  }
49
- for (const [type, n] of sortByValueDesc([...counts.entries()]))
56
+ for (const [type, n] of eventTypeTally(events))
50
57
  say(`${n} × ${eventTypeLabel(type)} (${type})`);
51
58
  }
52
59
  /** The shared rival-shape guard for logs flags that only apply to the event-log feed:
53
- * `--role` swaps in a transcript rather than the event log (--prompt requires --role, so
54
- * it is excluded with it), so each such flag fails naming both. One wording for all of them —
55
- * the --json, --grep, and --since checks cannot drift apart in what they say or why. `active`
56
- * is the flag's own presence — --json alone composes with nothing here but --role, while
57
- * --grep and --since call from inside their own presence guards. */
58
- function rejectRoleViewRival(rest, flag, active) {
59
- if (active && (rest.includes("--role") || rest.includes("--prompt")))
60
- failRivalShapes(`logs ${flag}`, "--role", "the --role view is the role's agent transcript, not the event log; --prompt requires --role");
60
+ * `--transcript` swaps in a transcript rather than the event log (--prompt requires
61
+ * --transcript, so it is excluded with it), so each such flag fails naming both. One wording
62
+ * for all of them — the --json, --count, --type and --since checks cannot drift apart in what
63
+ * they say or why. `active` is the flag's own presence. --grep is not one of them: it also
64
+ * filters the transcript view. */
65
+ function rejectTranscriptViewRival(rest, flag, active) {
66
+ if (active && (rest.includes("--transcript") || rest.includes("--prompt")))
67
+ failRivalShapes(`logs ${flag}`, "--transcript", "the --transcript view is the role's agent transcript, not the event log; --prompt requires --transcript");
61
68
  }
62
69
  /** The missing-pattern error cmdLogs prints for a valueless `--grep`, exported so cli.ts's
63
70
  * rejectUnknownArgs spec for --grep can fail a trailing `logs --grep` with the same wording
@@ -73,97 +80,148 @@ export const TYPE_VALUE_ERROR = "logs --type needs an event type";
73
80
  * completion already offers the valid codes, so a bare no-match for a near-miss reads as a silent
74
81
  * mystery) — while a VALID type that merely has no matching events stays hint-free, a legitimate
75
82
  * empty result rather than a typo. A valid type or a legacy alias (canonicalEventType maps the
76
- * alias onto the current code, which is a label key) never hints. */
77
- function typeEmpty(type) {
78
- const canonical = canonicalEventType(type);
79
- const hint = Object.hasOwn(EVENT_TYPE_LABELS, canonical)
80
- ? ""
81
- : typoSuffix(type, Object.keys(EVENT_TYPE_LABELS));
83
+ * alias onto the current code, which is a label key) never hints. For a comma list the hint
84
+ * is for the first piece that is not a valid type; the prose names the value as typed. */
85
+ function typeEmpty(type, pieces) {
86
+ const bad = pieces.find((p) => !Object.hasOwn(EVENT_TYPE_LABELS, canonicalEventType(p)));
87
+ const hint = bad === undefined ? "" : typoSuffix(bad, Object.keys(EVENT_TYPE_LABELS));
82
88
  return `no events matching "${type}"${hint}`;
83
89
  }
84
- /** `tumwater logs [-f] [-n <count>] [--since <duration>] [--role <id>] [--prompt]`: follow or
85
- * dump the harness event log, with `--since` a bounded past window of it, or with `--role` one
86
- * loop's agent transcript (see cmdLogsTranscript). */
87
- export async function cmdLogs(root, args) {
88
- // `--grep <text>` filters the event feed (the -n view and its follow): case-insensitive
90
+ /** Follow `file` until Ctrl+C or until the process that started this one is gone (the shape of
91
+ * a `logs -f` leaked to PPID 1 that polled for days). Resolves once the parent is gone. */
92
+ export function followUntilParentGone(file, offset, onLines, seams) {
93
+ const stop = followFile(file, offset, onLines);
94
+ return new Promise((resolve) => {
95
+ startParentDeathWatch(() => {
96
+ stop();
97
+ resolve();
98
+ }, {
99
+ ...(seams.ppid ? { ppid: seams.ppid } : {}),
100
+ ...(seams.intervalMs !== undefined ? { intervalMs: seams.intervalMs } : {}),
101
+ });
102
+ });
103
+ }
104
+ /** `tumwater logs [-f] [-n <count>] [--since <duration>] [--until <duration>]
105
+ * [--role <id>[,<id>...]] [--transcript <id>] [--prompt]
106
+ * [--grep <text>]`: follow or dump the harness event log, with `--since` a bounded
107
+ * past window of it and `--role` keeping only the listed roles' events (a base role id takes its
108
+ * instances too),
109
+ * or with `--transcript` one role's agent transcript (see cmdLogsTranscript). `-f` follows until
110
+ * Ctrl+C or until its parent process exits. */
111
+ export async function cmdLogs(root, args, seams = {}) {
112
+ // `--grep <text>` filters the event feed (the -n view and its follow) and, with --transcript,
113
+ // the transcript's entries (see cmdLogsTranscript): case-insensitive
89
114
  // substring against `${e.type} ${formatEvent(e)}` — the rendered line is what the operator
90
115
  // would otherwise read (WYSIWYG), and prefixing the raw type id lets stable ids
91
116
  // (review_rejected, merge_failed) be filtered even where the rendering paraphrases them.
92
- // Rival shapes stay exclusive — --role swaps in a transcript rather than this event log
93
- // (--prompt requires --role, so it is excluded with it), and --since is a filter of its own
94
- // rather than a window to filter — and each failure names both flags. The flag scan itself
117
+ // --grep filters the --since window like --type and --role do. The flag scan itself
95
118
  // (the value lookup, the `rest` construction that keeps a flag-shaped pattern from
96
119
  // impersonating a rival flag, and the empty-value fail) is cli/cli-args.ts's parseGrepFlag,
97
120
  // the one home shared with history's identical preamble.
98
121
  const { rest: grepped, pattern: grepPattern } = parseGrepFlag(args, GREP_VALUE_ERROR);
99
122
  // `--type <eventType>` filters the event feed by EXACT event type (e.type === value):
100
123
  // deterministic where --grep substrings — a merge_failed whose rendered line quotes
101
- // "merge_failed" matches --grep but not --type. Unlike --grep it composes with --since
102
- // (the window, not a filter of its own) as well as -n, -f, --json, and --grep; like --json
103
- // it is a rival only of the --role/--prompt transcript view. The value scan (keeping a
124
+ // "merge_failed" matches --grep but not --type. It composes with --since
125
+ // (the window it filters) as well as -n, -f, --json, and --grep; like --json
126
+ // it is a rival only of the --transcript/--prompt transcript view. A comma list matches any
127
+ // of its types. The value scan (keeping a
104
128
  // flag-shaped value like --since as text, the empty-value fail) mirrors parseGrepFlag via
105
129
  // parseTypeFlag, so the two cannot drift; `rest` here has both flags' value tokens removed.
106
130
  const { rest, type } = parseTypeFlag(grepped, TYPE_VALUE_ERROR);
107
131
  const grepLower = grepPattern === null ? null : grepPattern.toLowerCase();
108
132
  // An older event name matches its current one; the empty-match prose still names what was typed.
109
- const wantedType = type === null ? null : canonicalEventType(type);
110
- const typeFilter = type === null ? null : (e) => e.type === wantedType;
133
+ const typePieces = type === null ? [] : splitCommaList(type);
134
+ if (type !== null && typePieces.length === 0)
135
+ fail(TYPE_VALUE_ERROR);
136
+ const wantedTypes = new Set(typePieces.map(canonicalEventType));
137
+ const typeFilter = type === null ? null : (e) => wantedTypes.has(e.type);
111
138
  // The composed feed rule every emit site threads today's grep-only predicate through:
112
139
  // an event passes when it is absent-or-matches the --grep substring AND absent-or-equals
113
140
  // the --type id, held in one place so the --since window, the -n tally, and the follow
114
141
  // cannot drift on what an event must satisfy.
142
+ // `--role <id>[,<id>...]` joins them: an event passes when the role it was filed under, or
143
+ // that role's base id (so `--role feature` takes `feature-2` too), is listed. It
144
+ // composes with --since. The value scan reads `rest`, which holds no --grep/--type value tokens.
145
+ const roles = parseRoleListScope(root, rest);
146
+ const roleFilter = roles === null
147
+ ? null
148
+ : (e) => roleInScope(eventRole(e), roles);
149
+ if (roles !== null)
150
+ rejectTranscriptViewRival(rest, "--role", true);
151
+ // `--notable` culls routine bookkeeping events the way the dashboard Activity feed's Notable
152
+ // choice does (src/ui/tone.ts eventKind): only non-routine rows pass the feed. One harness-side
153
+ // rule (isNotableEvent) shared with the TUI Activity pane, so the CLI feed and the pane cannot
154
+ // disagree about what a notable filter hides. It composes with --since, --grep, --type, --role,
155
+ // -n, -f, --count and --json, and — like --json/--count/--type/--since — is a rival only of the
156
+ // --transcript transcript view.
157
+ const notable = rest.includes("--notable");
158
+ rejectTranscriptViewRival(rest, "--notable", notable);
115
159
  const feedFilter = (e) => (grepLower === null || eventMatchesGrep(e, grepLower)) &&
116
- (typeFilter === null || typeFilter(e));
117
- const follow = rest.includes("-f") || rest.includes("--follow");
160
+ (typeFilter === null || typeFilter(e)) &&
161
+ (roleFilter === null || roleFilter(e)) &&
162
+ (!notable || isNotableEvent(e.type, eventResult(e)));
163
+ // Empty-match prose: grep/type keep their wording when given; a role-only filter names the roles.
164
+ const roleEmpty = roles === null ? "" : `no events from ${roles.join(", ")}`;
165
+ const filtersGrepOrType = grepPattern !== null || type !== null;
166
+ const follow = hasFollowFlag(rest);
118
167
  // `--json` switches the feed from formatEvent rendering to one JSON.stringify(e) per line —
119
168
  // the raw HarnessEvent objects exactly as stored in the log — so scripts read the canonical
120
169
  // schema instead of parsing a rendering that changes shape whenever the formatter evolves.
121
170
  // It composes with -n, --grep, --since and -f (the grep filter applies before serialization,
122
- // the follow streams JSON lines as events land), but not with --role: that view is a pi
123
- // transcript of human-oriented prose, not the event log (--prompt requires --role, so it is
171
+ // the follow streams JSON lines as events land), but not with --transcript: that view is a pi
172
+ // transcript of human-oriented prose, not the event log (--prompt requires --transcript, so it is
124
173
  // excluded with it).
125
174
  const json = rest.includes("--json");
126
- rejectRoleViewRival(rest, "--json", json);
175
+ rejectTranscriptViewRival(rest, "--json", json);
127
176
  // `--count` tallies the window's present event types instead of printing the rows. Rival
128
177
  // shapes stay exclusive: counting implies a bounded window (so -f/--follow is rejected)
129
- // and the --role view is the role's agent transcript rather than the event log.
178
+ // and the --transcript view is the role's agent transcript rather than the event log.
130
179
  const count = rest.includes("--count");
131
- rejectRoleViewRival(rest, "--count", count);
180
+ rejectTranscriptViewRival(rest, "--count", count);
132
181
  if (count && follow)
133
182
  failRivalShapes("logs --count", "-f/--follow", "counting implies a bounded window; -f follows from now on");
134
183
  if (type !== null)
135
- rejectRoleViewRival(rest, "--type", true);
136
- if (grepPattern !== null) {
137
- rejectRoleViewRival(rest, "--grep", true);
138
- if (rest.includes("--since"))
139
- failRivalShapes("logs --grep", "--since", "--since is a filter of its own; --grep filters the -n view and its follow");
140
- }
184
+ rejectTranscriptViewRival(rest, "--type", true);
141
185
  // `--since <duration>` is the window-shaped view over the same event log the -n view dumps:
142
186
  // it reads a bounded past window (event-window.ts's day-keyed backwards scan) and prints the
143
187
  // survivors oldest-first, so an operator can ask for "the hour around that 429 storm" without
144
188
  // guessing a count. Rival shapes stay exclusive — follow means "from now on", -n means "last
145
- // N", and --role swaps in a transcript rather than this log — and each failure names
146
- // both flags. (--prompt requires --role, so it is excluded with it.)
189
+ // N", and --transcript swaps in a transcript rather than this log — and each failure names
190
+ // both flags. (--prompt requires --transcript, so it is excluded with it.)
147
191
  const ms = parseSinceFlag(rest, "logs --since", LOGS_SINCE_MAX_MS);
192
+ const untilMs = parseSinceFlag(rest, "logs --until", LOGS_SINCE_MAX_MS, "--until");
193
+ failUntilShorterThanSince("logs", untilMs, ms);
148
194
  if (ms !== null) {
149
195
  if (follow)
150
196
  failRivalShapes("logs --since", "-f/--follow", 'follow means "from now on"; --since shows a bounded past window');
151
197
  if (rest.includes("-n"))
152
198
  failRivalShapes("logs --since", "-n", "a count and a window are rival shapes");
153
- rejectRoleViewRival(rest, "--since", true);
199
+ rejectTranscriptViewRival(rest, "--since", true);
154
200
  // covered is only consulted below when the window has rows (an empty window returns
155
201
  // earlier), so the helper's vacuous empty-log branch never reaches the note here.
156
- const { events, covered } = readEventsSince(root, ms);
157
- // `--type` composes with the window (it is NOT the --since rival --grep is): only the
158
- // window events of the exact type are printed, and an all-filtered window answers with
159
- // the no-match prose (the type's own line) instead of the generic empty-window one —
202
+ const { cutoff, events: windowEvents, covered } = readEventsSince(root, ms);
203
+ const events = untilMs === null ? windowEvents : windowEvents.filter((e) => e.ts < cutoff + ms - untilMs);
204
+ const windowPhrase = untilMs === null ? `in ${durationLabel(ms)}` : `between ${durationLabel(ms)} and ${durationLabel(untilMs)} ago`;
205
+ // `--grep`, `--type` and `--role` all filter the window through feedFilter: only the
206
+ // window events that pass are printed, and an all-filtered window answers with the
207
+ // no-match prose (the filter's own line) instead of the generic empty-window one —
160
208
  // "which merge_failed in the last hour" filters the same as it would the -n view.
161
- const filtered = type === null ? events : events.filter((e) => e.type === wantedType);
209
+ const filtered = events.filter(feedFilter);
162
210
  if (filtered.length === 0) {
163
211
  // In JSON mode an empty result answers silently — an empty output IS the machine-readable
164
212
  // answer, and prose would corrupt a consumer's NDJSON stream.
165
- if (!json)
166
- say(type === null ? `no events in ${durationLabel(ms)}` : typeEmpty(type));
213
+ if (!json) {
214
+ if (notable && grepPattern === null && type === null && roles === null)
215
+ say(`no notable events ${windowPhrase}`);
216
+ else if (grepPattern !== null && type !== null)
217
+ say(`no events matching "${grepPattern}" or ${typeEmpty(type, typePieces)}`);
218
+ else if (type !== null)
219
+ say(typeEmpty(type, typePieces));
220
+ else if (grepPattern !== null)
221
+ say(`no events matching "${grepPattern}" ${windowPhrase}`);
222
+ else
223
+ say(roles !== null ? `${roleEmpty} ${windowPhrase}` : `no events ${windowPhrase}`);
224
+ }
167
225
  return;
168
226
  }
169
227
  // `--count` composes with the window: the tally covers exactly the filtered window rows
@@ -187,14 +245,14 @@ export async function cmdLogs(root, args) {
187
245
  }
188
246
  const nRaw = flagValue(rest, "-n");
189
247
  const limit = nRaw !== null ? parseCountFlag("-n", nRaw) : 50;
190
- // The event feed (no --role) never needs the config at all: parseRoleScope reads it only
248
+ // The event feed (no --transcript) never needs the config at all: parseRoleScope reads it only
191
249
  // when the flag is present.
192
- const role = parseRoleScope(root, rest);
250
+ const role = parseRoleScope(root, rest, "--transcript", LOGS_TRANSCRIPT_VALUE_ERROR);
193
251
  const showPrompts = rest.includes("--prompt");
194
252
  if (showPrompts && role === null)
195
- fail("logs --prompt needs --role <id>");
253
+ fail("logs --prompt needs --transcript <id>");
196
254
  if (role !== null) {
197
- await cmdLogsTranscript(root, role, limit, follow, showPrompts);
255
+ await cmdLogsTranscript(root, role, limit, follow, showPrompts, grepLower, grepPattern, seams);
198
256
  return;
199
257
  }
200
258
  // -n bounds the scan, not the print: the matched subset of the last `limit` events is what
@@ -202,7 +260,7 @@ export async function cmdLogs(root, args) {
202
260
  // the tail-with-end shape (not the per-poll cached readEvents) so the follow below can seed
203
261
  // from the byte end this very read covered instead of a later stat of the file.
204
262
  const tail = readEventsTailWithEnd(root, limit);
205
- const shown = grepLower !== null || type !== null ? tail.events.filter(feedFilter) : tail.events;
263
+ const shown = grepLower !== null || type !== null || roleFilter !== null || notable ? tail.events.filter(feedFilter) : tail.events;
206
264
  // `--count` over the -n view answers with the tally of exactly the filtered rows the plain
207
265
  // view would print, and carries over the plain view's empty-match prose below (a `--type`
208
266
  // or `--grep` that matches nothing names its value; an empty unfiltered -n view prints
@@ -210,13 +268,17 @@ export async function cmdLogs(root, args) {
210
268
  if (count) {
211
269
  if (shown.length !== 0)
212
270
  sayTally(shown, json);
213
- else if (!json && (grepPattern !== null || type !== null)) {
214
- if (grepPattern !== null && type !== null)
215
- say(`no events matching "${grepPattern}" or ${typeEmpty(type)}`);
271
+ else if (!json && (filtersGrepOrType || roles !== null || notable)) {
272
+ if (notable && grepPattern === null && type === null && roles === null)
273
+ say("no notable events");
274
+ else if (grepPattern !== null && type !== null)
275
+ say(`no events matching "${grepPattern}" or ${typeEmpty(type, typePieces)}`);
216
276
  // Type-empty gets the hint (only the type branch); a grep-only empty stays the bare
217
277
  // no-match line, because a free-text pattern is not an event type to suggest against.
278
+ else if (!filtersGrepOrType)
279
+ say(roleEmpty);
218
280
  else
219
- say(type !== null ? typeEmpty(type) : `no events matching "${grepPattern ?? ""}"`);
281
+ say(type !== null ? typeEmpty(type, typePieces) : `no events matching "${grepPattern ?? ""}"`);
220
282
  }
221
283
  return;
222
284
  }
@@ -226,13 +288,17 @@ export async function cmdLogs(root, args) {
226
288
  // The empty-match line is prose: in JSON mode the empty output is the answer. It fires
227
289
  // for either filter, naming the flag's value(s) whose match came up empty so the operator
228
290
  // knows what to loosen.
229
- if (shown.length === 0 && !json && (grepPattern !== null || type !== null)) {
230
- if (grepPattern !== null && type !== null)
231
- say(`no events matching "${grepPattern}" or ${typeEmpty(type)}`);
291
+ if (shown.length === 0 && !json && (filtersGrepOrType || roles !== null || notable)) {
292
+ if (notable && grepPattern === null && type === null && roles === null)
293
+ say("no notable events");
294
+ else if (grepPattern !== null && type !== null)
295
+ say(`no events matching "${grepPattern}" or ${typeEmpty(type, typePieces)}`);
232
296
  // Type-empty gets the hint (only the type branch); a grep-only empty stays the bare
233
297
  // no-match line, because a free-text pattern is not an event type to suggest against.
298
+ else if (!filtersGrepOrType)
299
+ say(roleEmpty);
234
300
  else
235
- say(type !== null ? typeEmpty(type) : `no events matching "${grepPattern ?? ""}"`);
301
+ say(type !== null ? typeEmpty(type, typePieces) : `no events matching "${grepPattern ?? ""}"`);
236
302
  }
237
303
  return;
238
304
  }
@@ -245,7 +311,7 @@ export async function cmdLogs(root, args) {
245
311
  // log scans to coveredEnd 0, and followFile tolerates a missing file (its poll re-stats
246
312
  // every interval), so a read-only command never leaves a harness state file behind — the
247
313
  // module contract above promises writes to stdout only.
248
- followFile(file, tail.coveredEnd, (lines) => {
314
+ await followUntilParentGone(file, tail.coveredEnd, (lines) => {
249
315
  for (const line of lines.filter(Boolean)) {
250
316
  const e = parseEventLine(line);
251
317
  // The filter holds across rotation: every event the follow callback sees goes through
@@ -253,19 +319,31 @@ export async function cmdLogs(root, args) {
253
319
  if (e && feedFilter(e))
254
320
  sayEventLine(e, json);
255
321
  }
256
- });
257
- await new Promise(() => { }); // Follow until Ctrl+C.
322
+ }, seams);
258
323
  }
259
- /** `tumwater logs --role <id>`: print (and optionally follow) one loop's agent transcript —
324
+ /** `tumwater logs --transcript <id>`: print (and optionally follow) one role's agent transcript —
260
325
  * run separators, abbreviated thinking, assistant text, and tool calls; with `--prompt` also
261
326
  * each run's exact prompt text. Read-only; the raw log is pi's streaming event stream, so only
262
- * complete renderable events are shown. */
263
- async function cmdLogsTranscript(root, role, limit, follow, showPrompts) {
327
+ * complete renderable events are shown. With `--grep`, only entries with a rendered line
328
+ * containing the text (case-insensitively) print, each under its run's separator; `-n` still
329
+ * bounds the entries scanned, not printed. */
330
+ async function cmdLogsTranscript(root, role, limit, follow, showPrompts, needle, pattern, seams) {
264
331
  const file = piLogPath(root, role);
265
332
  const opts = { includePrompts: showPrompts };
333
+ const filter = needle === null ? null : createTranscriptFilter(needle);
334
+ let matched = 0;
266
335
  const printEntry = (lines) => {
267
- if (lines.length > 0)
336
+ if (lines.length === 0)
337
+ return;
338
+ if (filter === null) {
268
339
  say(lines.join("\n"));
340
+ return;
341
+ }
342
+ const out = filter(lines);
343
+ if (out === null)
344
+ return;
345
+ matched++;
346
+ say(out.join("\n"));
269
347
  };
270
348
  // Initial window: the last `limit` entries of what is on disk. readTranscriptTail scans back
271
349
  // from EOF only as far as needed instead of re-reading the whole (up to logMaxBytes) file,
@@ -280,6 +358,8 @@ async function cmdLogsTranscript(root, role, limit, follow, showPrompts) {
280
358
  for (const entry of tail.entries)
281
359
  printEntry(entry);
282
360
  offset = tail.end;
361
+ if (needle !== null && matched === 0)
362
+ say(`no transcript entries matching "${pattern ?? ""}" for ${role}`);
283
363
  }
284
364
  if (!follow)
285
365
  return;
@@ -288,9 +368,8 @@ async function cmdLogsTranscript(root, role, limit, follow, showPrompts) {
288
368
  // readTranscriptTail's formatTranscript already flushed any pending separator for what
289
369
  // was on disk.
290
370
  const renderer = createTranscriptRenderer(opts);
291
- followFile(file, offset, (lines) => {
371
+ await followUntilParentGone(file, offset, (lines) => {
292
372
  for (const line of lines)
293
373
  printEntry(renderer.feed(line));
294
- });
295
- await new Promise(() => { }); // Follow until Ctrl+C.
374
+ }, seams);
296
375
  }