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,30 +1,32 @@
1
- // vocabulary: plain
2
1
  /** The browser-side dashboard app inlined as the GUI page's only <script>. It polls /api/status
3
2
  * every second and renders, in the order an operator needs them: the masthead's team status
4
3
  * (running, build, main check) and team controls (budget, pause); alerts for whatever needs a
5
- * human (a failing loop, a red main, a spent budget, an old build, open questions, a pause);
6
- * the composer that steers the director or one loop; and the team view — today's progress,
7
- * the loops grouped by what they are doing, the backlog, and the notable activity. The
4
+ * human (a failing role, a red main, a spent budget, an old build, open questions, a pause);
5
+ * the composer that steers the director or one role; and the team view — today's progress,
6
+ * the roles grouped by what they are doing, the backlog, and the notable activity. The
8
7
  * view-model, the drawer, the History/Usage/Failures/Pending views, the operator controls, the
9
8
  * composer, the Markdown renderer, and the routing/poll boot live in their own modules and
10
9
  * are spliced in below; everything shares one scope. The page
11
10
  * cannot import the harness modules, so it keeps its own copies of the few display rules it
12
- * needs (formatters, loop order), each pinned against its TypeScript twin by test. Written as
11
+ * needs (formatters, role order), each pinned against its TypeScript twin by test. Written as
13
12
  * String.raw templates so the served script is exactly the text below — no double escaping. */
13
+ import { GUI_CLIENT_BACKLOG_JS } from "./gui-client-backlog.js";
14
14
  import { GUI_CLIENT_BOOT_JS } from "./gui-client-boot.js";
15
15
  import { GUI_CLIENT_COMPOSER_JS } from "./gui-client-composer.js";
16
16
  import { GUI_CLIENT_DRAWER_JS } from "./gui-client-drawer.js";
17
+ import { GUI_CLIENT_DRAWER_CONFIG_JS } from "./gui-client-drawer-config.js";
18
+ import { GUI_CLIENT_FEED_JS } from "./gui-client-feed.js";
17
19
  import { GUI_CLIENT_TEAM_JS } from "./gui-client-team.js";
18
20
  import { GUI_CLIENT_HISTORY_JS } from "./gui-client-history.js";
19
21
  import { GUI_CLIENT_LABELS_JS } from "./gui-client-labels.js";
20
- import { GUI_CLIENT_LOOPS_JS } from "./gui-client-loops.js";
22
+ import { GUI_CLIENT_ROLES_JS } from "./gui-client-roles.js";
21
23
  import { GUI_CLIENT_MARKDOWN_JS } from "./gui-client-markdown.js";
22
24
  import { GUI_CLIENT_MODEL_JS } from "./gui-client-model.js";
23
25
  import { GUI_CLIENT_OPERATOR_JS } from "./gui-client-operator.js";
24
26
  import { GUI_CLIENT_PENDING_JS } from "./gui-client-pending.js";
27
+ import { GUI_CLIENT_REPORT_CHART_JS } from "./gui-client-report-chart.js";
25
28
  import { GUI_CLIENT_REPORT_JS } from "./gui-client-report.js";
26
29
  import { GUI_CLIENT_SETTINGS_JS } from "./gui-client-settings.js";
27
- import { GUI_CLIENT_SOUND_JS } from "./gui-client-sound.js";
28
30
  import { ICON_PATHS } from "./gui-icons.js";
29
31
  import { ACTIVE_PHASE_PREFIXES } from "../status-model.js";
30
32
  /** The client's opening: escaping and the guarded API helpers every call goes through. Kept
@@ -86,7 +88,7 @@ const CORE_JS = String.raw ` const esc = (s) => String(s).replace(/[&<>"']/g, (
86
88
  // accepts the connection but never answers
87
89
  // would otherwise hold the poll open forever, leaving the page frozen on stale data with no
88
90
  // offline alert — the catch in refresh() only runs when fetch rejects. The timeout aborts the
89
- // request, so the poll fails, the red "Lost contact" alert shows, and the loop keeps polling.
91
+ // request, so the poll fails, the red "Lost contact" alert shows, and polling continues.
90
92
  // AbortSignal.timeout is standard since 2022; where a browser lacks it the fetch stays
91
93
  // unbounded, exactly the old behavior.
92
94
  const POLL_TIMEOUT_MS = 15000;
@@ -141,11 +143,11 @@ const FORMAT_JS = String.raw ` // format:start
141
143
  const agree = (n, one, many) => (n === 1 ? one : many);
142
144
  const plural = (n, one, many) => n + " " + agree(n, one, many || one + "s");
143
145
  // The merged-commit work/maintenance split the sidebar's "Merged today" tile and the report's
144
- // "Commits merged" tile both print. report-data.ts leaves the counts optional, so a missing
146
+ // "Commits merged" tile both print. report-types.ts leaves the counts optional, so a missing
145
147
  // count reads 0 here rather than "undefined".
146
148
  const workSplit = (t) => (t.workCommits ?? 0) + " work / " + (t.maintenanceCommits ?? 0) + " maintenance";
147
149
  // The unmerged-commit list both change views paint — each commit's sha and subject on its
148
- // own clamped line. The Pending roster's Ahead cell and the loop drawer's Pending change
150
+ // own clamped line. The Pending roster's Ahead cell and the role drawer's Pending change
149
151
  // section had the byte-identical map; a missing or non-array list paints nothing.
150
152
  const commitRowsHtml = (commits) =>
151
153
  (Array.isArray(commits) ? commits : []).map((c) => "<div class='mono clamp1'>" + esc(c.sha) + " " + esc(c.subject || "—") + "</div>").join("");
@@ -168,7 +170,7 @@ const FORMAT_JS = String.raw ` // format:start
168
170
  };
169
171
  // human-seconds-fmt:end
170
172
  // active-phase-fmt:start
171
- // A PERMIT-HOLDING loop in flight — status-model.ts's isActivePhase. The three prefixes are
173
+ // A PERMIT-HOLDING role in flight — status-model.ts's isActivePhase. The three prefixes are
172
174
  // interpolated from status-model.ts's ACTIVE_PHASE_PREFIXES (String.raw still substitutes),
173
175
  // so this copy cannot drift from the server's; the running director's own director-working
174
176
  // label is checked beside it rather than folded in, because it holds no permit (BUGS.md
@@ -176,14 +178,14 @@ const FORMAT_JS = String.raw ` // format:start
176
178
  const ACTIVE_PHASE_PREFIXES = ${JSON.stringify(ACTIVE_PHASE_PREFIXES)};
177
179
  const isActivePhase = (phase) => ACTIVE_PHASE_PREFIXES.some((p) => phase.startsWith(p));
178
180
  // active-phase-fmt:end
179
- // last-tick-fmt:start
180
- // The absolute local time of a tick end with its age ("14:32:05 · 3m ago"), prefixed MM-DD
181
- // once older than a day; "-" when never ticked — status-render.ts's lastTickCell.
181
+ // last-run-fmt:start
182
+ // The absolute local time of a run end with its age ("14:32:05 · 3m ago"), prefixed MM-DD
183
+ // once older than a day; "-" when never ran — status-render.ts's lastRunCell.
182
184
  // Whole elapsed seconds since an instant, clamped at zero and rounded to the nearest
183
- // second — datetime.ts's elapsedSeconds (the browser runtime cannot import it), shared by
184
- // fmtLastTick and fmtAgo below so the clamp cannot drift between the page's ages.
185
+ // second — datetime.ts's secondsSince (the browser runtime cannot import it), shared by
186
+ // fmtLastRun and fmtAgo below so the clamp cannot drift between the page's ages.
185
187
  const ageSec = (ts) => Math.max(0, Math.round((Date.now() - ts) / 1000));
186
- const fmtLastTick = (ts) => {
188
+ const fmtLastRun = (ts) => {
187
189
  if (!ts) return "-";
188
190
  const d = new Date(ts);
189
191
  const p = (n) => String(n).padStart(2, "0");
@@ -193,16 +195,16 @@ const FORMAT_JS = String.raw ` // format:start
193
195
  const age = humanSeconds(sec) + " ago";
194
196
  return s + " · " + age;
195
197
  };
196
- // last-tick-fmt:end
198
+ // last-run-fmt:end
197
199
  // next-run-fmt:start
198
- // When an idle loop may tick again — status-render.ts's nextRunCell: "-" for a loop in flight
200
+ // When an idle role may run again — status-render.ts's nextRunCell: "-" for a role in flight
199
201
  // or parked awaiting a slot (it has no next run to speak of) and for a stopped team; "now"
200
202
  // when due; otherwise the remaining time, prefixed "backoff " while backing off. A yield-scaled
201
203
  // clock rides as a " ×N" suffix: a quiet role's effective gap is longer than the countdown.
202
204
  const fmtNextRun = (l, teamRunning) => {
203
- // A torn loop object — a payload missing its phase field — reads as the empty (inactive)
205
+ // A torn role object — a payload missing its phase field — reads as the empty (inactive)
204
206
  // phase rather than throwing: the browser copy is untyped, and its sibling phaseInfo/
205
- // loopRank already coerce the same way, so one bad row must not blank the whole table.
207
+ // roleRank already coerce the same way, so one bad row must not blank the whole table.
206
208
  const phase = String(l.phase || "");
207
209
  const active = isActivePhase(phase) || phase.startsWith("waiting to start") || phase.startsWith("waiting for ") || phase.startsWith("director working");
208
210
  if (!teamRunning || active) return "-";
@@ -220,7 +222,7 @@ const FORMAT_JS = String.raw ` // format:start
220
222
  return sec < 60 ? "just now" : humanSeconds(sec) + " ago";
221
223
  };
222
224
  const fmtClock = (ts) => new Date(ts).toLocaleString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit" });
223
- // A tick's duration: seconds under two minutes, then minutes, then hours. Each bucket is
225
+ // A run's duration: seconds under two minutes, then minutes, then hours. Each bucket is
224
226
  // chosen from the rounded count so a span just under a cutoff never prints "120s" or "120m".
225
227
  const fmtSpan = (ms) => {
226
228
  const sec = Math.round(ms / 1000);
@@ -230,7 +232,7 @@ const FORMAT_JS = String.raw ` // format:start
230
232
  return (ms / 3600000).toFixed(1) + "h";
231
233
  };
232
234
  // A run's wall-clock span cell: fmtSpan when a row carries a duration, a dash when it does not
233
- // (a tick whose start sits outside the window). Shared by the History table and summary rows
235
+ // (a run whose start sits outside the window). Shared by the History table and summary rows
234
236
  // (and any future duration cell) so the null rule and its em-dash cannot drift between them.
235
237
  const durCell = (ms) => (ms === null || ms === undefined ? "—" : esc(fmtSpan(Number(ms))));
236
238
  // Server text sometimes carries ISO instants (a restart cooldown's deadline); show them in
@@ -349,5 +351,5 @@ const DOM_JS = String.raw ` const ICONS = ${JSON.stringify(ICON_PATHS)};
349
351
  let offline = false;
350
352
  let activeView = "team";
351
353
  `;
352
- export const GUI_CLIENT_JS = [CORE_JS, FORMAT_JS, GUI_CLIENT_LABELS_JS, GUI_CLIENT_MODEL_JS, DOM_JS, GUI_CLIENT_MARKDOWN_JS, GUI_CLIENT_SOUND_JS, GUI_CLIENT_OPERATOR_JS, GUI_CLIENT_LOOPS_JS, GUI_CLIENT_TEAM_JS, GUI_CLIENT_COMPOSER_JS,
353
- GUI_CLIENT_DRAWER_JS, GUI_CLIENT_HISTORY_JS, GUI_CLIENT_PENDING_JS, GUI_CLIENT_REPORT_JS, GUI_CLIENT_SETTINGS_JS, GUI_CLIENT_BOOT_JS].join("\n");
354
+ export const GUI_CLIENT_JS = [CORE_JS, FORMAT_JS, GUI_CLIENT_LABELS_JS, GUI_CLIENT_MODEL_JS, DOM_JS, GUI_CLIENT_MARKDOWN_JS, GUI_CLIENT_OPERATOR_JS, GUI_CLIENT_ROLES_JS, GUI_CLIENT_TEAM_JS, GUI_CLIENT_BACKLOG_JS, GUI_CLIENT_FEED_JS, GUI_CLIENT_COMPOSER_JS,
355
+ GUI_CLIENT_DRAWER_JS, GUI_CLIENT_DRAWER_CONFIG_JS, GUI_CLIENT_HISTORY_JS, GUI_CLIENT_PENDING_JS, GUI_CLIENT_REPORT_CHART_JS, GUI_CLIENT_REPORT_JS, GUI_CLIENT_SETTINGS_JS, GUI_CLIENT_BOOT_JS].join("\n");
@@ -32,8 +32,6 @@ export const ICON_PATHS = {
32
32
  dot: `<circle cx="12" cy="12" r="3" ${FILLED}/>`,
33
33
  fail: '<circle cx="12" cy="12" r="9"/><path d="M9 9l6 6M15 9l-6 6"/>',
34
34
  offline: '<path d="M3 3l18 18M8.5 16.5a5 5 0 0 1 7 0M5 12.6a10 10 0 0 1 4.2-2.3M14.8 10.3A10 10 0 0 1 19 12.6M12 20h.01"/>',
35
- sound: '<path d="M4 9.5v5h3l4.5 4v-13L7 9.5z"/><path d="M15.5 8.7a5 5 0 0 1 0 6.6M18 6.2a9 9 0 0 1 0 11.6"/>',
36
- mute: '<path d="M4 9.5v5h3l4.5 4v-13L7 9.5z"/><path d="M15.5 9.5l5 5M20.5 9.5l-5 5"/>',
37
35
  settings: '<circle cx="12" cy="12" r="3.2"/><path d="M12 3v2.6M12 18.4V21M3 12h2.6M18.4 12H21M5.6 5.6l1.9 1.9M16.5 16.5l1.9 1.9M5.6 18.4l1.9-1.9M16.5 7.5l1.9-1.9"/>',
38
36
  };
39
37
  /** One icon's markup — the same shape the browser script's icon() builds. */
@@ -5,9 +5,9 @@
5
5
  * views — Team's carrying a badge while something needs a human — and the team-wide
6
6
  * controls: today's spend against the cap, and pause. The main column shows one view at a
7
7
  * time: Team (alerts for whatever needs a human, the composer that steers the director or
8
- * one loop, today's progress, every loop grouped by what it is doing, the backlog, and the
8
+ * one role, today's progress, every role grouped by what it is doing, the backlog, and the
9
9
  * notable activity), History, Usage, Failures, Pending, or Settings. A drawer opens any
10
- * loop's detail and live transcript, or any backlog entry in full. Kept apart from
10
+ * role's detail and live transcript, or any backlog entry in full. Kept apart from
11
11
  * gui/gui-server.ts so the server module stays about serving. */
12
12
  import { GUI_CLIENT_JS } from "./gui-client.js";
13
13
  import { GUI_STYLES } from "./gui-styles.js";
@@ -30,7 +30,6 @@ export const GUI_PAGE = `<!doctype html>
30
30
  <div class="side-top">
31
31
  <a class="brand" href="#team" aria-label="tumwater team">${LOGO_SVG}<span>tumwater</span></a>
32
32
  <div class="side-top-actions">
33
- <span id="soundwrap"></span>
34
33
  <button type="button" class="icon-btn" id="themetoggle" title="Switch between light and dark" aria-label="Switch between light and dark">${iconSvg("sun", "theme-sun")}${iconSvg("moon", "theme-moon")}</button>
35
34
  </div>
36
35
  </div>
@@ -73,17 +72,17 @@ export const GUI_PAGE = `<!doctype html>
73
72
  </div>
74
73
  </form>
75
74
  <div id="stats" class="stats"></div>
76
- <section class="card" aria-labelledby="loops-title">
75
+ <section class="card" aria-labelledby="roles-title">
77
76
  <header class="card-head">
78
- <h2 id="loops-title">Roles</h2>
77
+ <h2 id="roles-title">Roles</h2>
79
78
  <div class="spacer"></div>
80
79
  <button type="button" class="btn btn-sm" id="resetall" title="Zero every role's runs, commits, tokens, and cost — scheduling is untouched">${iconSvg("refresh")}<span>Reset counters</span></button>
81
80
  <div class="menu-anchor" id="wakelaterwrap"><button type="button" class="btn btn-sm" id="wakelater" aria-haspopup="menu" title="Wake every role later">${iconSvg("clock")}<span>Wake later</span></button></div>
82
81
  <button type="button" class="btn btn-sm" id="wakeall" title="Wake every role now, skipping any sleep">${iconSvg("bolt")}<span>Wake all</span></button>
83
82
  </header>
84
83
  <div class="table-wrap">
85
- <table class="table loops" id="loopstable">
86
- <thead><tr><th class="c-loop">Role</th><th class="c-status">Status</th><th class="c-activity">Now, or the last result</th><th class="c-today num">Spent today</th><th class="c-actions"><span class="sr-only">Actions</span></th></tr></thead>
84
+ <table class="table roles" id="rolestable">
85
+ <thead><tr><th class="c-role">Role</th><th class="c-status">Status</th><th class="c-activity">Now, or the last result</th><th class="c-today num">Spent today</th><th class="c-actions"><span class="sr-only">Actions</span></th></tr></thead>
87
86
  <tbody id="rolerows"></tbody>
88
87
  </table>
89
88
  </div>
@@ -94,6 +93,7 @@ export const GUI_PAGE = `<!doctype html>
94
93
  <h2 id="backlog-title">Backlog</h2>
95
94
  <div class="spacer"></div>
96
95
  <div class="seg" id="backlogtabs" role="tablist" aria-label="Backlog sections"></div>
96
+ <label><span class="sr-only">Search</span><input class="field" id="backloggrep" type="search" placeholder="Search backlog…" autocomplete="off"></label>
97
97
  </header>
98
98
  <div id="backlog" class="list"></div>
99
99
  </section>
@@ -105,6 +105,8 @@ export const GUI_PAGE = `<!doctype html>
105
105
  <button type="button" data-filter="notable" class="active">Notable</button><button type="button" data-filter="all">All events</button>
106
106
  </div>
107
107
  <label><span class="sr-only">Role</span><select class="field" id="feedrole"><option value="">All roles</option></select></label>
108
+ <label><span class="sr-only">Window</span><select class="field" id="feedwindow"></select></label>
109
+ <span id="feedstep" style="display:none"><button type="button" class="btn btn-sm" id="feedearlier">◀ Earlier</button><button type="button" class="btn btn-sm" id="feedlater">Later ▶</button></span>
108
110
  <label><span class="sr-only">Search</span><input class="field" id="feedgrep" type="search" placeholder="Search activity…" autocomplete="off"></label>
109
111
  </header>
110
112
  <ol id="feed" class="feed"></ol>
@@ -250,7 +250,7 @@ export const GUI_STYLES = String.raw `
250
250
  .settings-row .field { flex: 1; min-width: 0; }
251
251
  .settings-row .btn { flex: none; }
252
252
 
253
- /* ---- composer: the director prompt (or one loop's), always in reach ---- */
253
+ /* ---- composer: the director prompt (or one role's), always in reach ---- */
254
254
  .composer { padding: 8px; border: 1px solid var(--line); border-radius: 12px;
255
255
  background: var(--surface); box-shadow: var(--shadow-sm); }
256
256
  .composer:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--accent-bg); }
@@ -320,7 +320,7 @@ export const GUI_STYLES = String.raw `
320
320
  font-size: 12px; font-weight: 600; color: var(--text-3); }
321
321
  .table tr.group:not(:first-child) td { border-top: 1px solid var(--line); }
322
322
  .table tr.group .badge { margin-left: 6px; }
323
- /* The history view's tick drill-down: the rows' details toggle and the expanded card that
323
+ /* The history view's run drill-down: the rows' details toggle and the expanded card that
324
324
  rides beneath its row, full width. */
325
325
  .history .c-x { width: 40px; white-space: nowrap; }
326
326
  .hist-x { padding: 0 6px; }
@@ -336,22 +336,22 @@ export const GUI_STYLES = String.raw `
336
336
  .clamp2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
337
337
  .clamp1 { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
338
338
 
339
- table.loops .c-loop { width: 15%; }
340
- table.loops .c-status { width: 24%; }
341
- table.loops .c-today { width: 110px; }
342
- table.loops .c-actions { width: 136px; }
343
- table.loops.free .c-today { display: none; }
344
- .loops tr.loop.selected td { background: var(--accent-bg); }
345
- .loops tr.loop.selected td:first-child { box-shadow: inset 3px 0 0 var(--primary); }
346
- .loops tr.loop.live td:first-child { box-shadow: inset 3px 0 0 var(--blue); }
347
- .loops tr.loop.live.selected td:first-child { box-shadow: inset 3px 0 0 var(--primary); }
348
- .loop-name { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
349
- .loop-name .role { font-family: var(--font-mono); font-weight: 600; font-size: 13.5px; }
339
+ table.roles .c-role { width: 15%; }
340
+ table.roles .c-status { width: 24%; }
341
+ table.roles .c-today { width: 110px; }
342
+ table.roles .c-actions { width: 136px; }
343
+ table.roles.free .c-today { display: none; }
344
+ .roles tr.role-row.selected td { background: var(--accent-bg); }
345
+ .roles tr.role-row.selected td:first-child { box-shadow: inset 3px 0 0 var(--primary); }
346
+ .roles tr.role-row.live td:first-child { box-shadow: inset 3px 0 0 var(--blue); }
347
+ .roles tr.role-row.live.selected td:first-child { box-shadow: inset 3px 0 0 var(--primary); }
348
+ .role-name { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
349
+ .role-name .role { font-family: var(--font-mono); font-weight: 600; font-size: 13.5px; }
350
350
  .now { color: var(--text); }
351
351
  .actions { display: flex; justify-content: flex-end; gap: 2px; }
352
- tr.loop .actions { opacity: 0.55; transition: opacity 0.12s; }
353
- tr.loop:hover .actions, tr.loop:focus-within .actions, tr.loop.selected .actions { opacity: 1; }
354
- @media (hover: none) { tr.loop .actions { opacity: 1; } }
352
+ tr.role-row .actions { opacity: 0.55; transition: opacity 0.12s; }
353
+ tr.role-row:hover .actions, tr.role-row:focus-within .actions, tr.role-row.selected .actions { opacity: 1; }
354
+ @media (hover: none) { tr.role-row .actions { opacity: 1; } }
355
355
 
356
356
  /* ---- backlog list ---- */
357
357
  .list { max-height: 460px; overflow-y: auto; }
@@ -380,14 +380,14 @@ export const GUI_STYLES = String.raw `
380
380
  .feed-icon svg.i { width: 15px; height: 15px; }
381
381
  .feed-msg { color: var(--text-2); overflow-wrap: anywhere; }
382
382
  .feed-item.k-routine .feed-msg { color: var(--text-3); }
383
- .feed-loop { font-family: var(--font-mono); font-size: 12.5px; font-weight: 600; color: var(--text); margin-right: 4px; }
383
+ .feed-source { font-family: var(--font-mono); font-size: 12.5px; font-weight: 600; color: var(--text); margin-right: 4px; }
384
384
  .feed-time { font-size: 12px; color: var(--text-4); white-space: nowrap; font-variant-numeric: tabular-nums; padding-top: 1px; }
385
385
  .feed-tally { display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 16px 6px; border-bottom: 1px solid var(--line-soft); }
386
386
  .feed-tally .chip { height: 22px; padding: 0 9px; font-size: 12px; font-weight: 500; }
387
387
  .feed-tally button.chip { cursor: pointer; }
388
388
  .feed-tally .chip.active { border-color: var(--accent); background: var(--accent-bg); color: var(--text); }
389
389
 
390
- /* ---- drawer: a loop's detail and live transcript, or one backlog entry ---- */
390
+ /* ---- drawer: a role's detail and live transcript, or one backlog entry ---- */
391
391
  .drawer { position: fixed; top: 0; right: 0; bottom: 0; z-index: 50; width: min(660px, 100vw); display: flex;
392
392
  flex-direction: column; background: var(--bg); border-left: 1px solid var(--line); box-shadow: var(--shadow-lg);
393
393
  animation: slidein 0.16s ease-out; }
@@ -412,11 +412,11 @@ export const GUI_STYLES = String.raw `
412
412
  .kv dt { font-size: 12px; color: var(--text-3); }
413
413
  .kv dd { margin: 1px 0 0; font-weight: 600; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
414
414
  .note { padding: 11px 13px; border-radius: 10px; border: 1px solid var(--line); background: var(--surface); overflow-wrap: anywhere; }
415
- .ticks { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--surface); }
416
- .ticks li { display: grid; grid-template-columns: 116px minmax(0, 1fr) auto; gap: 10px; padding: 8px 12px;
415
+ .run-list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--surface); }
416
+ .run-list li { display: grid; grid-template-columns: 116px minmax(0, 1fr) auto; gap: 10px; padding: 8px 12px;
417
417
  border-bottom: 1px solid var(--line-soft); font-size: 13px; }
418
- .ticks li:last-child { border-bottom: 0; }
419
- .ticks .when { color: var(--text-4); white-space: nowrap; font-variant-numeric: tabular-nums; }
418
+ .run-list li:last-child { border-bottom: 0; }
419
+ .run-list .when { color: var(--text-4); white-space: nowrap; font-variant-numeric: tabular-nums; }
420
420
  .transcript-sec { flex: 1; min-height: 320px; display: flex; flex-direction: column; }
421
421
  .transcript { flex: 1; min-height: 280px; margin: 0; overflow: auto; padding: 12px 14px; border-radius: 10px;
422
422
  border: 1px solid var(--line); background: var(--surface); font: 12.5px/1.6 var(--font-mono);
@@ -500,7 +500,7 @@ export const GUI_STYLES = String.raw `
500
500
  .menu-anchor.up .popover { top: calc(100% + 8px); bottom: auto; }
501
501
  .main { padding-left: 16px; padding-right: 16px; }
502
502
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
503
- table.loops .c-today { display: none; }
503
+ table.roles .c-today { display: none; }
504
504
  .kv { grid-template-columns: repeat(2, minmax(0, 1fr)); }
505
505
  .history .c-usage, .history .c-dur, .history .c-model { display: none; }
506
506
  }
@@ -508,22 +508,22 @@ export const GUI_STYLES = String.raw `
508
508
  .stats.six { grid-template-columns: repeat(2, minmax(0, 1fr)); }
509
509
  .stat-value { font-size: 21px; }
510
510
  .composer-foot #prompthint { display: none; }
511
- table.loops thead { display: none; }
512
- table.loops, table.loops tbody { display: block; }
513
- table.loops tr.loop { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 12px;
511
+ table.roles thead { display: none; }
512
+ table.roles, table.roles tbody { display: block; }
513
+ table.roles tr.role-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 12px;
514
514
  padding: 10px 16px; border-bottom: 1px solid var(--line-soft); }
515
- table.loops tr.loop td { display: block; width: auto; padding: 0; border: 0; background: none !important; box-shadow: none !important; }
516
- table.loops tr.loop td.c-status, table.loops tr.loop td.c-activity { grid-column: 1 / -1; margin-top: 6px; }
517
- table.loops tr.loop td.c-actions { grid-row: 1; grid-column: 2; }
518
- table.loops tr.loop td.c-today { display: none; }
515
+ table.roles tr.role-row td { display: block; width: auto; padding: 0; border: 0; background: none !important; box-shadow: none !important; }
516
+ table.roles tr.role-row td.c-status, table.roles tr.role-row td.c-activity { grid-column: 1 / -1; margin-top: 6px; }
517
+ table.roles tr.role-row td.c-actions { grid-row: 1; grid-column: 2; }
518
+ table.roles tr.role-row td.c-today { display: none; }
519
519
  .composer-row { flex-wrap: wrap; }
520
520
  .composer textarea { order: -1; flex-basis: 100%; }
521
521
  .composer-target { flex: 1; }
522
522
  .composer-target select { max-width: none; width: 100%; }
523
- table.loops tr.group, table.loops tr.group td { display: block; }
524
- .history .c-tick, .history .c-detail { display: none; }
525
- .ticks li { grid-template-columns: minmax(0, 1fr) auto; }
526
- .ticks .when:first-child { grid-column: 1 / -1; }
523
+ table.roles tr.group, table.roles tr.group td { display: block; }
524
+ .history .c-run, .history .c-detail { display: none; }
525
+ .run-list li { grid-template-columns: minmax(0, 1fr) auto; }
526
+ .run-list .when:first-child { grid-column: 1 / -1; }
527
527
  .md { padding: 4px 16px 16px; }
528
528
  }
529
529
  @media (prefers-reduced-motion: reduce) {
@@ -1,8 +1,6 @@
1
- // vocabulary: plain
2
1
  import path from "node:path";
3
2
  import { applyToolExecutionEvent, parsePiEventLine, toolCallCommand, } from "../pi/pi-event-line.js";
4
- import { bufferedCommandStallMs, commandBuffersOutput } from "../pi/command-shape.js";
5
- import { streamsToolOutput } from "../agent/backend-traits.js";
3
+ import { bufferedCommandStallMs, callBuffersOutput } from "../pi/command-shape.js";
6
4
  import { describeToolCall } from "../text/phrases.js";
7
5
  import { squash } from "../text/text.js";
8
6
  import { defaultConfig, liveConfig } from "../config/config.js";
@@ -17,7 +15,7 @@ const TAIL_BYTES = 4 * 1024 * 1024;
17
15
  /** Byte offset a first observation of the log (size `size`) seeds from: the start of the
18
16
  * smallest suffix window, grown from TAIL_BYTES in doubling steps, that contains a complete
19
17
  * `session` event line — or 0 when the log carries none. A bare `size - TAIL_BYTES` seed
20
- * opens mid-run whenever the log (which accumulates across ticks up to logMaxBytes) is
18
+ * opens mid-run whenever the log (which accumulates across runs up to logMaxBytes) is
21
19
  * larger than the window, and the first observation then counts every assistant turn after
22
20
  * that offset as if it were one run — a number with no defined meaning (BUGS.md 2026-09-22).
23
21
  * Seeding at the window that contains the last session anchors the read at a real run
@@ -103,7 +101,7 @@ const PROGRESS_TYPES = new Set([
103
101
  /** Apply one parsed progress event to a progress object (mutates it). */
104
102
  function feedLine(progress, event) {
105
103
  switch (event.type) {
106
- case "session": // A new run starts: everything before it was a previous tick — restore the at-time-zero state.
104
+ case "session": // A new run starts: everything before it was a previous run — restore the at-time-zero state.
107
105
  Object.assign(progress, freshProgress(progress.quietMs));
108
106
  break;
109
107
  case "tool_execution_start": {
@@ -143,7 +141,7 @@ function feedLine(progress, event) {
143
141
  progress.outputTokens += nonNegativeNumber(usage.output, 0);
144
142
  progress.peakContextTokens = Math.max(progress.peakContextTokens, nonNegativeNumber(usage.totalTokens, 0));
145
143
  }
146
- // The first text the loop speaks in this run is its work item ("I'll implement plan X");
144
+ // The first text the role speaks in this run is its work item ("I'll implement plan X");
147
145
  // later messages never replace it.
148
146
  if (!progress.currentWork)
149
147
  progress.currentWork = workItemFromContent(event.message.content);
@@ -180,7 +178,7 @@ export function toolCallStallMs(root) {
180
178
  * line, so this runs on every read rather than in feedLine; `now` and `stallMs` are injectable
181
179
  * for tests (freshly fed lines stamp Date.now(), so nothing is stalled right after parsing).
182
180
  * A call whose stdout is piped or redirected holds its bytes away from pi until it exits
183
- * (BUGS.md 2026-09-28: the tick prompt itself prescribes `npm run test 2>&1 | tail`), so its
181
+ * (BUGS.md 2026-09-28: the run prompt itself prescribes `npm run test 2>&1 | tail`), so its
184
182
  * silence is the prescribed shape and never evidence of a hang — but the command can still
185
183
  * hang (a leaked grandchild holding the pipe open), so past a wall-clock threshold it is
186
184
  * flagged anyway (BUGS.md 2026-10-06), the same threshold runPi's warning applies so the
@@ -192,7 +190,7 @@ export function stalledToolLabel(open, now = Date.now(), stallMs = defaultConfig
192
190
  // claude never sends content-bearing tool updates, so silence is expected for every call
193
191
  // and only the wall-clock duration can show a hang (plans/agent-backends.md "Watchdogs and
194
192
  // progress"); pi keeps the per-command classification.
195
- if (!streamsToolOutput(agent) || commandBuffersOutput(call.command || call.label)) {
193
+ if (callBuffersOutput(agent, call.command || call.label)) {
196
194
  if (now - call.lastActivityAt >= bufferedCommandStallMs(stallMs))
197
195
  return call.label;
198
196
  continue;
@@ -233,7 +231,7 @@ function feedDemuxed(tail, line, gateCwd) {
233
231
  // accumulator as soon as the marker's stage says `reviewing` — a poll that can land
234
232
  // before the session event. Resetting here means a previous run's turns/context (the
235
233
  // last review, or another run the gate spent) can never show in the new run's cell.
236
- // Safe for the in-tick reviewing cell too: a role's gate is serial, so a marker ends
234
+ // Safe for the in-run reviewing cell too: a role's gate is serial, so a marker ends
237
235
  // the previous run.
238
236
  tail.cur = kind;
239
237
  tail.kindFromMarker = true;
@@ -249,8 +247,8 @@ function feedDemuxed(tail, line, gateCwd) {
249
247
  }
250
248
  feedLine(tail[tail.cur], event);
251
249
  }
252
- /** Live progress for one of a loop's pi run kinds (`author` — the in-flight tick's run in
253
- * the role's own worktree, the default; `gate` — the review gate's runs in a leased pool
250
+ /** Live progress for one of a role's agent run kinds (`author` — the in-flight authoring agent
251
+ * run in the role's own worktree, the default; `gate` — the review gate's runs in a leased pool
254
252
  * slot, what the reviewing cell shows), or null when there is no log yet. The raw log is
255
253
  * append-only while pi runs and every run starts with a `tumwater_run` marker
256
254
  * (src/agent/run-agent.ts), so after seeding from the tail window once we only read and parse
@@ -1,15 +1,15 @@
1
1
  import { readLiveProgress } from "./progress-data.js";
2
2
  import { compactTokens } from "../text/format.js";
3
- /** The in-flight tick's PROGRESS display model (split from status-model.ts, which keeps the
4
- * loop-phase ladder, the merge cells, and the row derivations): how a running tick renders
3
+ /** The in-flight run's PROGRESS display model (split from status-model.ts, which keeps the
4
+ * role-phase ladder, the merge cells, and the row derivations): how an in-flight run renders
5
5
  * its live detail — elapsed, turn, context, current tool, stall — derived from the live log
6
6
  * tail (progress-data.ts) and shared by BOTH observer surfaces: the terminal table
7
7
  * (status-render.ts's state cell) and the JSON/GUI payload (status-payload.ts), plus the
8
- * phase ladder's in-flight branches (status-model.ts's loopPhase), so the three cannot
8
+ * phase ladder's in-flight branches (status-model.ts's rolePhase), so the three cannot
9
9
  * drift apart. This is the disk-reading half: every helper here reads (or accepts a caller's
10
10
  * precomputed) live log tail, while status-model.ts derives labels from the snapshot. */
11
11
  /** Seconds/minutes/hours elapsed formatting shared by the progress helpers and the phase
12
- * ladder's elapsed branches (loopPhase's merge and parked branches). The hours bucket
12
+ * ladder's elapsed branches (rolePhase's merge and parked branches). The hours bucket
13
13
  * rounds to whole minutes off the total, so a rounded-up 59m5Xs carries into the next hour
14
14
  * ("2h0m"); rounding the remainder alone would print the impossible "1h60m". */
15
15
  export function duration(ms) {
@@ -21,37 +21,37 @@ export function duration(ms) {
21
21
  const totalMinutes = Math.round(s / 60);
22
22
  return `${Math.floor(totalMinutes / 60)}h${totalMinutes % 60}m`;
23
23
  }
24
- /** The label for a loop's in-flight tick: `<label>` plus how long it has been running
24
+ /** The label for a role's in-flight run: `<label>` plus how long it has been running
25
25
  * ("working 3m", "reviewing 2m") — shared by workingDetail and the review-gate branch of
26
- * loopPhase so their elapsed formatting cannot drift. A tick with no recorded start time
27
- * renders as just the bare label. (A merge's elapsed is its marker's, never the tick's —
28
- * loopPhase's merge branch builds that head itself.) */
26
+ * rolePhase so their elapsed formatting cannot drift. A run with no recorded start time
27
+ * renders as just the bare label. (A merge's elapsed is its marker's, never the run's —
28
+ * rolePhase's merge branch builds that head itself.) */
29
29
  export function inFlightLabel(s, label) {
30
30
  const elapsed = s.lastRunStartedAt ? duration(Date.now() - s.lastRunStartedAt) : "";
31
31
  return `${label} ${elapsed}`.trim();
32
32
  }
33
- /** One rule for whether a log tail's progress describes THIS tick: the log's newest write
34
- * (quietMs is measured from the raw log's mtime) must have happened after the tick started.
35
- * Until this tick's pi run writes its first line, the log's tail is the PREVIOUS run — the
36
- * baseline check (worktree reset plus suite) can take 30–130 s, and a tick waking after 5+
33
+ /** One rule for whether a log tail's progress describes THIS run: the log's newest write
34
+ * (quietMs is measured from the raw log's mtime) must have happened after the run started.
35
+ * Until the current agent run writes its first line, the log's tail is the PREVIOUS run — the
36
+ * baseline check (worktree reset plus suite) can take 30–130 s, and a run waking after 5+
37
37
  * minutes idle would otherwise show the earlier run's turn/context/tool and a "no pi output"
38
- * quiet time measured from before the tick even began, tripping the STALL alert on every
39
- * such tick's baseline (BUGS.md 2026-09-30). Once the run writes, its mtime is inside the
40
- * tick, so quietMs can never exceed the tick's own age and the honest quiet measure survives.
41
- * Applied by every progress reader (workingDetail, the reviewing branch of loopPhase, the
42
- * token metrics, loopRowCells' precomputed tail) and by status-render's state cell via
43
- * tickProgress, so the TUI, the GUI payload, and the alert cannot drift apart. A state with
38
+ * quiet time measured from before the run even began, tripping the STALL alert on every
39
+ * such run's baseline (BUGS.md 2026-09-30). Once the run writes, its mtime is inside the
40
+ * run, so quietMs can never exceed the run's own age and the honest quiet measure survives.
41
+ * Applied by every progress reader (workingDetail, the reviewing branch of rolePhase, the
42
+ * token metrics, roleRowCells' precomputed tail) and by status-render's state cell via
43
+ * runProgress, so the TUI, the GUI payload, and the alert cannot drift apart. A state with
44
44
  * no recorded start (a stale `running` flag after a crash) cannot be judged and keeps the
45
45
  * tail as before. `p` may be undefined (the caller's "no tail fetched" sentinel) or null
46
46
  * ("no log"); both read as no progress. */
47
- function progressOfTick(s, p) {
47
+ function progressOfRun(s, p) {
48
48
  if (!p)
49
49
  return null;
50
50
  if (s.lastRunStartedAt === undefined)
51
51
  return p;
52
52
  const elapsed = Math.max(0, Date.now() - s.lastRunStartedAt);
53
53
  if (p.quietMs > elapsed)
54
- return null; // Newest write predates this tick: a previous run's tail.
54
+ return null; // Newest write predates this run: a previous run's tail.
55
55
  return { ...p, quietMs: Math.min(p.quietMs, elapsed) };
56
56
  }
57
57
  /** The stall phrases a phase label can carry — inFlightDetail appends "tool call stalled:
@@ -65,7 +65,7 @@ export const STALL_SOURCE = String.raw `tool call stalled[^·]*|no agent output
65
65
  /** STALL_SOURCE compiled — the server-side matcher (team-alerts.ts's stuck alert). */
66
66
  export const STALL_RE = new RegExp(STALL_SOURCE);
67
67
  /** The shared parts assembly for an in-flight state cell: `head` (the phase label with its
68
- * own elapsed — inFlightLabel for a tick, the merge branch's own for a merge) plus turn,
68
+ * own elapsed — inFlightLabel for a run, the merge branch's own for a merge) plus turn,
69
69
  * live context, current tool, and the ≥5-min no-output stall flag. `p` is null when there is
70
70
  * no progress (no log yet) — falls back to the bare head. */
71
71
  export function inFlightDetail(head, p) {
@@ -87,31 +87,31 @@ export function inFlightDetail(head, p) {
87
87
  parts.push(`no agent output for ${duration(p.quietMs)}`);
88
88
  return parts.join(" · ");
89
89
  }
90
- /** Which pi run kind (progress-data.ts's ProgressRunKind) a loop's in-flight cells should read:
91
- * the review gate's reviewer run ("gate") while the tick is under review — its `session`
90
+ /** Which pi run kind (progress-data.ts's ProgressRunKind) a role's in-flight cells should read:
91
+ * the review gate's reviewer run ("gate") while the run is under review — its `session`
92
92
  * event is the log's newest and its counts are what "reviewing" describes — the author's
93
93
  * own run ("author") for every other in-flight phase. One home for the rule so the TUI
94
94
  * table and the GUI payload cannot drift (the precomputed `live` tail must name the same
95
- * run the phase label does). status-model's loopRowCells is its only reader. */
95
+ * run the phase label does). status-model's roleRowCells is its only reader. */
96
96
  export function progressKind(s) {
97
97
  return s.phase === "review" ? "gate" : "author";
98
98
  }
99
- /** progressOfTick with the tail read folded in — the one home for the
99
+ /** progressOfRun with the tail read folded in — the one home for the
100
100
  * `live === undefined ? readLiveProgress(...) : live` idiom. Callers holding this frame's
101
- * already-fetched tail pass it as `live` (renderStatus reads once per running loop and
101
+ * already-fetched tail pass it as `live` (renderStatus reads once per running role and
102
102
  * threads it through every helper); standalone callers pass undefined and this reads the
103
103
  * role's log itself — the author accumulator by default, the gate one via `kind`. Every
104
104
  * tail-reading caller outside this module routes through it; status-model.ts's staged branch
105
105
  * is the one exception, keeping its raw readLiveProgress call because it feeds inFlightDetail
106
106
  * a freshly read gate tail for the merge label, not a per-frame threaded one. */
107
- export function tickProgress(root, s, live, kind = "author") {
108
- return progressOfTick(s, live === undefined ? readLiveProgress(root, s.role, kind) : live);
107
+ export function runProgress(root, s, live, kind = "author") {
108
+ return progressOfRun(s, live === undefined ? readLiveProgress(root, s.role, kind) : live);
109
109
  }
110
- /** The state cell for a working loop: elapsed · turns · live context · current tool. Pass
111
- * `live` — this frame's already-fetched tail (renderStatus reads once per running loop and
110
+ /** The state cell for a working role: elapsed · turns · live context · current tool. Pass
111
+ * `live` — this frame's already-fetched tail (renderStatus reads once per running role and
112
112
  * threads it through every helper) — to avoid re-reading the log; without it, this reads on
113
113
  * its own for standalone callers. */
114
114
  export function workingDetail(root, s, live) {
115
- const p = tickProgress(root, s, live);
115
+ const p = runProgress(root, s, live);
116
116
  return inFlightDetail(inFlightLabel(s, "working"), p);
117
117
  }