toolroll 0.9.63 → 0.9.67

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 (302) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +33 -5
  3. package/dist/acp-agents.d.ts +43 -0
  4. package/dist/acp-agents.js +62 -0
  5. package/dist/acp-attest.d.ts +44 -0
  6. package/dist/acp-attest.js +269 -0
  7. package/dist/acp-check.d.ts +28 -0
  8. package/dist/acp-check.js +410 -0
  9. package/dist/acp-client.d.ts +163 -0
  10. package/dist/acp-client.js +738 -0
  11. package/dist/acp-inspect.d.ts +23 -0
  12. package/dist/acp-inspect.js +109 -0
  13. package/dist/agent-board.d.ts +19 -6
  14. package/dist/agent-board.js +30 -9
  15. package/dist/agent-fence.d.ts +117 -2
  16. package/dist/agent-fence.js +439 -12
  17. package/dist/agent-pulse.js +9 -2
  18. package/dist/agent-state-words.d.ts +19 -0
  19. package/dist/agent-state-words.js +20 -0
  20. package/dist/agent-state.d.ts +65 -0
  21. package/dist/agent-state.js +229 -0
  22. package/dist/agentconfig.js +39 -13
  23. package/dist/agents-board.d.ts +34 -2
  24. package/dist/agents-board.js +110 -1
  25. package/dist/approval-rules-ui.js +2 -2
  26. package/dist/assignment-delivery.d.ts +14 -0
  27. package/dist/assignment-delivery.js +13 -0
  28. package/dist/assignment.js +13 -0
  29. package/dist/attest.js +7 -0
  30. package/dist/backup-ui.js +2 -2
  31. package/dist/batch-checks.js +3 -2
  32. package/dist/browser/THIRD_PARTY_NOTICES.txt +94 -0
  33. package/dist/browser/terminal.css +37 -0
  34. package/dist/browser/terminal.js +228 -0
  35. package/dist/browser/workspace.css +2 -2
  36. package/dist/browser/workspace.js +42 -38
  37. package/dist/browser-shell.js +6 -3
  38. package/dist/browser-workspace.d.ts +235 -1
  39. package/dist/browser-workspace.js +60 -0
  40. package/dist/builder.d.ts +10 -1
  41. package/dist/builder.js +87 -24
  42. package/dist/chat-actions.d.ts +13 -1
  43. package/dist/chat-actions.js +197 -15
  44. package/dist/chat-ask.d.ts +4 -0
  45. package/dist/chat-ask.js +14 -0
  46. package/dist/chat-channel.js +20 -0
  47. package/dist/chat-continuity.js +5 -4
  48. package/dist/chat-decide.d.ts +0 -14
  49. package/dist/chat-decide.js +4 -1
  50. package/dist/chat-delivery.d.ts +9 -0
  51. package/dist/chat-delivery.js +45 -6
  52. package/dist/chat-messages.d.ts +8 -0
  53. package/dist/chat-messages.js +36 -4
  54. package/dist/check-levels-ui.js +2 -2
  55. package/dist/checkout-proof.d.ts +28 -0
  56. package/dist/checkout-proof.js +62 -0
  57. package/dist/claim.d.ts +12 -0
  58. package/dist/claim.js +19 -0
  59. package/dist/cli-help.d.ts +1 -1
  60. package/dist/cli-help.js +4 -1
  61. package/dist/cli.d.ts +3 -0
  62. package/dist/cli.js +21 -2
  63. package/dist/coding-ui.js +6 -4
  64. package/dist/console-instance.d.ts +124 -0
  65. package/dist/console-instance.js +337 -0
  66. package/dist/contracts/chat-actions.d.ts +16 -0
  67. package/dist/contracts/chat-actions.js +4 -0
  68. package/dist/contracts/chat-content.d.ts +19 -0
  69. package/dist/contracts/chat-content.js +6 -0
  70. package/dist/contracts/console-api.d.ts +13 -4
  71. package/dist/contracts/console-api.js +13 -4
  72. package/dist/contracts/flow-card.d.ts +4 -0
  73. package/dist/contracts/flow-card.js +3 -0
  74. package/dist/contracts/flow.d.ts +46 -0
  75. package/dist/contracts/flow.js +11 -2
  76. package/dist/contracts/lead-tools.d.ts +92 -0
  77. package/dist/contracts/lead-tools.js +13 -0
  78. package/dist/contracts/provider.d.ts +11 -1
  79. package/dist/contracts/provider.js +1 -1
  80. package/dist/contracts/route.d.ts +76 -1
  81. package/dist/contracts/route.js +1 -1
  82. package/dist/contracts/store-json.d.ts +2 -0
  83. package/dist/control-setup.d.ts +1 -1
  84. package/dist/control-ui.js +2 -2
  85. package/dist/converse.d.ts +16 -0
  86. package/dist/converse.js +32 -4
  87. package/dist/demo-chat.js +4 -4
  88. package/dist/demo.js +2 -2
  89. package/dist/desktop-update-gate.js +6 -0
  90. package/dist/desktop-update.d.ts +1 -1
  91. package/dist/discord-chat.js +4 -1
  92. package/dist/doctor.js +1 -1
  93. package/dist/egress-bridge.d.ts +17 -0
  94. package/dist/egress-bridge.js +111 -0
  95. package/dist/egress.d.ts +119 -0
  96. package/dist/egress.js +423 -0
  97. package/dist/exec.d.ts +78 -0
  98. package/dist/exec.js +367 -9
  99. package/dist/export-ui.js +2 -2
  100. package/dist/flow-engine.js +24 -5
  101. package/dist/flow-gallery-ui.js +2 -2
  102. package/dist/flow-gallery.js +27 -8
  103. package/dist/flow-share.js +16 -5
  104. package/dist/flow-triggers.d.ts +77 -8
  105. package/dist/flow-triggers.js +517 -70
  106. package/dist/flows-cli.js +8 -2
  107. package/dist/flows-ui.d.ts +0 -2
  108. package/dist/flows-ui.js +52 -4
  109. package/dist/flows.d.ts +4 -0
  110. package/dist/flows.js +9 -1
  111. package/dist/html.d.ts +9 -0
  112. package/dist/html.js +11 -0
  113. package/dist/invoke.d.ts +35 -0
  114. package/dist/invoke.js +347 -28
  115. package/dist/keys.d.ts +18 -14
  116. package/dist/keys.js +5 -0
  117. package/dist/lead-capabilities.js +3 -2
  118. package/dist/lead-contract.js +2 -0
  119. package/dist/lead-doors.d.ts +2 -0
  120. package/dist/lead-doors.js +7 -0
  121. package/dist/lead-follow.d.ts +2 -0
  122. package/dist/lead-follow.js +58 -2
  123. package/dist/lead-progress.js +1 -0
  124. package/dist/lead-queue-runtime.d.ts +46 -0
  125. package/dist/lead-queue-runtime.js +191 -0
  126. package/dist/lead-queue.d.ts +104 -0
  127. package/dist/lead-queue.js +187 -0
  128. package/dist/lead-sweep.d.ts +67 -0
  129. package/dist/lead-sweep.js +99 -0
  130. package/dist/lead-tools.d.ts +22 -0
  131. package/dist/lead-tools.js +152 -1
  132. package/dist/lead.d.ts +13 -5
  133. package/dist/lead.js +89 -29
  134. package/dist/limits-ui.d.ts +8 -1
  135. package/dist/limits-ui.js +26 -0
  136. package/dist/live-bus.d.ts +8 -0
  137. package/dist/live-bus.js +40 -4
  138. package/dist/live-detail.d.ts +27 -0
  139. package/dist/live-detail.js +57 -0
  140. package/dist/live-notes.d.ts +20 -0
  141. package/dist/live-notes.js +47 -0
  142. package/dist/live-records.d.ts +140 -0
  143. package/dist/live-records.js +935 -0
  144. package/dist/live-terminal.d.ts +269 -0
  145. package/dist/live-terminal.js +686 -0
  146. package/dist/live.d.ts +38 -27
  147. package/dist/live.js +142 -124
  148. package/dist/mcp-oauth.d.ts +6 -3
  149. package/dist/mcp-oauth.js +8 -4
  150. package/dist/memory-pass.js +2 -2
  151. package/dist/mobile-viewport.d.ts +5 -1
  152. package/dist/mobile-viewport.js +18 -2
  153. package/dist/model-catalog.js +4 -0
  154. package/dist/monitoring-ui.js +2 -2
  155. package/dist/needs-you.d.ts +1 -1
  156. package/dist/needs-you.js +11 -4
  157. package/dist/operate.d.ts +4 -0
  158. package/dist/operate.js +335 -19
  159. package/dist/passwordless.d.ts +23 -0
  160. package/dist/passwordless.js +26 -0
  161. package/dist/peek-cli.d.ts +12 -4
  162. package/dist/peek-cli.js +19 -11
  163. package/dist/phase-routing.js +10 -3
  164. package/dist/planner-source.d.ts +3 -0
  165. package/dist/planner-source.js +14 -0
  166. package/dist/planner.d.ts +2 -1
  167. package/dist/planner.js +7 -1
  168. package/dist/policy-ui.js +2 -2
  169. package/dist/policy.d.ts +1 -20
  170. package/dist/policy.js +29 -2
  171. package/dist/project-delete-ui.d.ts +18 -0
  172. package/dist/project-delete-ui.js +60 -9
  173. package/dist/project-delete.js +2 -2
  174. package/dist/project-memory.d.ts +3 -1
  175. package/dist/project-memory.js +57 -25
  176. package/dist/project-tools.d.ts +5 -3
  177. package/dist/project-tools.js +12 -9
  178. package/dist/provider-auth.d.ts +4 -3
  179. package/dist/provider-auth.js +5 -4
  180. package/dist/provider-connection.d.ts +2 -1
  181. package/dist/provider-connection.js +5 -0
  182. package/dist/provider.d.ts +25 -2
  183. package/dist/provider.js +63 -9
  184. package/dist/pull-request-ui.js +2 -2
  185. package/dist/push.js +1 -1
  186. package/dist/quick-start.d.ts +24 -0
  187. package/dist/quick-start.js +127 -0
  188. package/dist/release-adapters.d.ts +6 -0
  189. package/dist/release-adapters.js +70 -3
  190. package/dist/release.d.ts +10 -1
  191. package/dist/release.js +34 -15
  192. package/dist/request-budget-ui.js +2 -2
  193. package/dist/result-follow-ups.js +3 -2
  194. package/dist/result-review.d.ts +1 -1
  195. package/dist/result-review.js +9 -3
  196. package/dist/retention-ui.js +2 -2
  197. package/dist/retention.js +10 -1
  198. package/dist/run-efficiency.d.ts +73 -0
  199. package/dist/run-efficiency.js +228 -0
  200. package/dist/runner.d.ts +5 -1
  201. package/dist/runner.js +22 -1
  202. package/dist/scope.d.ts +183 -5
  203. package/dist/scope.js +256 -31
  204. package/dist/scout-net.d.ts +4 -1
  205. package/dist/scout-net.js +37 -12
  206. package/dist/scout.d.ts +20 -6
  207. package/dist/scout.js +27 -3
  208. package/dist/serve.js +248 -40
  209. package/dist/server/chat.d.ts +1 -1
  210. package/dist/server/chat.js +122 -31
  211. package/dist/server/chrome.d.ts +16 -2
  212. package/dist/server/chrome.js +63 -11
  213. package/dist/server/console.css +107 -42
  214. package/dist/server/flows.js +38 -8
  215. package/dist/server/guards.d.ts +1 -0
  216. package/dist/server/guards.js +23 -9
  217. package/dist/server/http.d.ts +7 -0
  218. package/dist/server/http.js +3 -2
  219. package/dist/server/live.d.ts +34 -1
  220. package/dist/server/live.js +39 -17
  221. package/dist/server/pages-assets.js +5 -0
  222. package/dist/server/pages.js +90 -13
  223. package/dist/server/people-tokens.d.ts +3 -0
  224. package/dist/server/people-tokens.js +55 -17
  225. package/dist/server/remote-hooks.js +12 -6
  226. package/dist/server/remote.d.ts +2 -1
  227. package/dist/server/remote.js +14 -5
  228. package/dist/server/render-chat.d.ts +12 -3
  229. package/dist/server/render-chat.js +47 -16
  230. package/dist/server/render-pages.d.ts +32 -4
  231. package/dist/server/render-pages.js +83 -13
  232. package/dist/server/render-people.d.ts +3 -2
  233. package/dist/server/render-people.js +3 -2
  234. package/dist/server/render-results.d.ts +18 -2
  235. package/dist/server/render-results.js +37 -28
  236. package/dist/server/render-settings.js +6 -5
  237. package/dist/server/render-tasks.d.ts +35 -10
  238. package/dist/server/render-tasks.js +111 -27
  239. package/dist/server/request-context.d.ts +2 -0
  240. package/dist/server/request-context.js +2 -1
  241. package/dist/server/route-table.d.ts +3 -0
  242. package/dist/server/route-table.js +47 -5
  243. package/dist/server/runtime.d.ts +33 -2
  244. package/dist/server/session.d.ts +25 -1
  245. package/dist/server/session.js +18 -4
  246. package/dist/server/settings.js +142 -10
  247. package/dist/server/sweeps.d.ts +4 -0
  248. package/dist/server/sweeps.js +58 -0
  249. package/dist/server/task-views.d.ts +2 -1
  250. package/dist/server/task-views.js +96 -6
  251. package/dist/server/tasks.js +327 -78
  252. package/dist/server/terminals.d.ts +31 -0
  253. package/dist/server/terminals.js +365 -0
  254. package/dist/setup-guide.js +3 -2
  255. package/dist/slack-chat.js +4 -1
  256. package/dist/spend-ui.js +3 -3
  257. package/dist/spend.js +2 -1
  258. package/dist/steer-capabilities.d.ts +32 -0
  259. package/dist/steer-capabilities.js +60 -0
  260. package/dist/steer-controls.d.ts +14 -0
  261. package/dist/steer-controls.js +36 -0
  262. package/dist/steer-status.js +2 -2
  263. package/dist/storage-ui.js +4 -4
  264. package/dist/store-baseline.d.ts +1 -1
  265. package/dist/store-baseline.js +1 -1
  266. package/dist/store.d.ts +440 -15
  267. package/dist/store.js +1072 -52
  268. package/dist/subagent-desk.js +1 -1
  269. package/dist/summary.js +3 -0
  270. package/dist/task-source.d.ts +58 -0
  271. package/dist/task-source.js +93 -0
  272. package/dist/team-leads.js +1 -1
  273. package/dist/teams-chat.js +4 -1
  274. package/dist/teardown-git.d.ts +22 -0
  275. package/dist/teardown-git.js +136 -2
  276. package/dist/telegram-lead.d.ts +3 -0
  277. package/dist/telegram-lead.js +44 -7
  278. package/dist/telegram.d.ts +11 -2
  279. package/dist/telegram.js +51 -17
  280. package/dist/terminal-catalog.d.ts +11 -0
  281. package/dist/terminal-catalog.js +129 -0
  282. package/dist/text-limits.d.ts +2 -0
  283. package/dist/text-limits.js +2 -0
  284. package/dist/tool-broker.d.ts +14 -0
  285. package/dist/tool-broker.js +67 -2
  286. package/dist/toolroll-update-ui.js +2 -2
  287. package/dist/toolroll-update.js +13 -3
  288. package/dist/tools-ui.js +2 -2
  289. package/dist/unfenced-test-support.d.ts +2 -0
  290. package/dist/unfenced-test-support.js +11 -0
  291. package/dist/webhook-limits.d.ts +23 -0
  292. package/dist/webhook-limits.js +30 -0
  293. package/dist/webhook-readers.d.ts +56 -0
  294. package/dist/webhook-readers.js +138 -0
  295. package/dist/webhook-signing.d.ts +27 -0
  296. package/dist/webhook-signing.js +119 -0
  297. package/dist/work-index.d.ts +29 -0
  298. package/dist/work-index.js +16 -2
  299. package/dist/workspace-revision.js +3 -1
  300. package/dist/writes-mode.d.ts +9 -0
  301. package/dist/writes-mode.js +8 -0
  302. package/package.json +15 -2
@@ -182,7 +182,7 @@
182
182
  the act that resolves the screen. Cards, frames, and seals are neutral. */
183
183
  .badge-open, .badge-parked { color: var(--foreground); }
184
184
  .count {
185
- min-width: 1.25rem; padding-inline: .35rem; border-radius: 9999px;
185
+ min-width: 1.25rem; padding-inline: .35rem; border-radius: var(--so-radius-full);
186
186
  }
187
187
  .count.badge-open {
188
188
  background: var(--brand-soft); color: var(--brand);
@@ -627,7 +627,7 @@
627
627
  .requirement-heading .badge { margin: 0; white-space: normal; }
628
628
  .requirement-statement { margin: .5rem 0; line-height: 1.55; white-space: pre-wrap; }
629
629
  .requirement-review { margin: .4rem 0; font-size: .78rem; color: var(--muted-foreground); }
630
- .requirement-warning { margin: .65rem 0; padding: .6rem .75rem; border: 1px solid color-mix(in srgb, var(--warning) 35%, var(--border)); border-radius: 8px; background: var(--warning-soft); font-size: .8125rem; }
630
+ .requirement-warning { margin: .65rem 0; padding: .6rem .75rem; border: 1px solid color-mix(in srgb, var(--warning) 35%, var(--border)); border-radius: var(--so-radius-md); background: var(--warning-soft); font-size: .8125rem; }
631
631
  .requirement-warning strong { font-size: .78rem; }
632
632
  .requirement-warning p { margin: .3rem 0 0; }
633
633
  .requirement-issues { font-size: .8125rem; }
@@ -663,7 +663,7 @@
663
663
 
664
664
  .filters { font-size: 0.8125rem; color: var(--muted-foreground); }
665
665
  .filters a, .filters strong {
666
- display: inline-block; padding: .25rem .625rem; border-radius: 9999px; text-decoration: none;
666
+ display: inline-block; padding: .25rem .625rem; border-radius: var(--so-radius-full); text-decoration: none;
667
667
  color: var(--muted-foreground); font-weight: 500;
668
668
  }
669
669
  .filters strong { background: var(--muted); color: var(--foreground); }
@@ -884,13 +884,18 @@
884
884
  }
885
885
  .banner .badge { margin-right: .5rem; }
886
886
  /* Chat in `toolroll demo`: the scripted lead's conversation. */
887
- .demo-chat { display: flex; flex-direction: column; gap: 1rem; max-width: 46rem; margin: 0 auto; padding-bottom: 1rem; }
887
+ .demo-chat { display: flex; flex-direction: column; gap: 1rem; max-width: 46rem; min-height: 100%; margin: 0 auto; }
888
+ .demo-thread { flex: 1 0 auto; }
889
+ /* Empty: the greeting, the suggestions and the composer sit together in the middle, as in any modern chat. */
890
+ .demo-chat--empty { justify-content: center; min-height: min(70vh, 40rem); }
891
+ .demo-chat--empty .demo-thread { flex: 0 0 auto; }
888
892
  .demo-thread { display: flex; flex-direction: column; gap: 1.25rem; }
889
893
  .demo-turn { display: flex; flex-direction: column; gap: .75rem; scroll-margin-top: 1rem; }
890
894
  .demo-said p { margin: 0; }
891
- .demo-you { align-self: flex-end; max-width: min(34rem, 85%); background: var(--so-ink); color: var(--so-paper); border-radius: .875rem .875rem .25rem .875rem; padding: .55rem .85rem; overflow-wrap: anywhere; }
895
+ .demo-you { align-self: flex-end; max-width: min(34rem, 85%); background: var(--so-soft); color: var(--so-ink); border: 1px solid var(--so-line); border-radius: 1rem 1rem .3rem 1rem; padding: .5rem .85rem; overflow-wrap: anywhere; }
892
896
  .demo-lead { max-width: 40rem; }
893
- .demo-who { font-size: .75rem; font-weight: 600; color: var(--so-muted); margin-bottom: .15rem !important; }
897
+ .demo-who { display: flex; align-items: center; gap: .45rem; font-size: .8125rem; font-weight: 600; color: var(--so-ink); margin-bottom: .3rem !important; }
898
+ .demo-avatar { display: inline-grid; place-items: center; width: 1.25rem; height: 1.25rem; border-radius: 50%; background: var(--so-ink); color: var(--so-paper); font-size: .6875rem; font-weight: 700; }
894
899
  .demo-chat .card { margin: 0; padding: 1rem 1.125rem; }
895
900
  .demo-chat .card h2 { font-size: .9375rem; margin: .1rem 0 .4rem; }
896
901
  .demo-chat .card h3 { font-size: .75rem; font-weight: 600; color: var(--so-muted); margin: .75rem 0 .25rem; }
@@ -938,13 +943,22 @@
938
943
  .demo-evidence figure { margin: 0 0 .5rem; }
939
944
  .demo-evidence img { display: block; width: 100%; height: auto; border: 1px solid var(--so-line); border-radius: .5rem; }
940
945
  .demo-evidence figcaption { margin-top: .35rem; }
941
- .demo-hint { padding: 1.25rem 0 .25rem; }
942
- .demo-hint-title { font-size: .9375rem; font-weight: 600; margin: 0 0 .75rem; }
943
- .demo-suggestions { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0; }
944
- .demo-suggestions button { min-height: 2.5rem; border-radius: 999px; padding: .35rem .95rem; }
945
- .demo-composer { position: sticky; bottom: 0; padding: .75rem 0 .25rem; background: var(--so-paper); flex-direction: row; align-items: flex-end; }
946
- .demo-chat .demo-composer textarea { flex: 1 1 auto; width: auto; min-width: 0; min-height: 2.75rem; }
947
- .demo-chat .demo-composer button { flex: none; width: auto; min-height: 2.75rem; padding-inline: 1.1rem; }
946
+ .demo-hint { text-align: center; padding: 0 0 .5rem; }
947
+ .demo-hint-title { font-size: 1.375rem; font-weight: 600; letter-spacing: -.01em; margin: 0 0 .35rem; }
948
+ .demo-hint-sub { color: var(--so-muted); font-size: .875rem; margin: 0 0 1rem; }
949
+ .demo-suggestions { display: flex; flex-wrap: wrap; justify-content: center; gap: .5rem; margin: 0; padding: 0; border: 0; background: none; }
950
+ .demo-chat .demo-suggestions button { min-height: 2.25rem; border-radius: 999px; padding: .3rem .9rem; border: 1px solid var(--so-line); background: var(--so-paper); color: var(--so-muted); font-weight: 500; box-shadow: none; }
951
+ @media (hover: hover) and (pointer: fine) { .demo-chat .demo-suggestions button:hover { color: var(--so-ink); border-color: var(--so-input-line); background: var(--so-soft); } }
952
+ /* The composer: one card docked at the bottom, a borderless box that grows, the hint and a round send inside. */
953
+ .demo-composer { position: sticky; bottom: 0; margin: 0; padding: .75rem 0 1rem; border: 0; border-radius: 0; box-shadow: none; background: linear-gradient(to bottom, transparent, var(--so-paper) 1.25rem); }
954
+ .demo-composer-card { display: flex; flex-direction: column; gap: .35rem; padding: .7rem .7rem .55rem .9rem; border: 1px solid var(--so-input-line); border-radius: 1rem; background: var(--so-paper); box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 4px 16px rgba(0,0,0,.04); transition: border-color .12s var(--so-ease-out), box-shadow .12s var(--so-ease-out); }
955
+ .demo-composer-card:focus-within { border-color: var(--so-muted); box-shadow: 0 0 0 3px var(--so-soft); }
956
+ .demo-chat .demo-composer textarea { width: 100%; min-height: 1.6rem; max-height: 12rem; field-sizing: content; padding: .15rem 0; border: 0; border-radius: 0; background: none; box-shadow: none; resize: none; font-size: .9375rem; line-height: 1.55; }
957
+ .demo-chat .demo-composer textarea:focus-visible { outline: 0; }
958
+ .demo-composer-bar { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
959
+ .demo-composer-bar > span { color: var(--so-muted); font-size: .75rem; }
960
+ .demo-chat .demo-composer .demo-send { display: inline-grid; place-items: center; flex: none; width: 2rem; min-height: 2rem; height: 2rem; padding: 0; border-radius: 50%; }
961
+ .demo-composer-card:has(textarea:placeholder-shown) .demo-send { opacity: .35; }
948
962
  .banner a { color: var(--muted-foreground); }
949
963
  .sign-in-banner form { display: inline; margin: 0 0 0 .5rem; padding: 0; border: 0; background: transparent; }
950
964
 
@@ -1190,26 +1204,26 @@
1190
1204
  rows, who builds in one line, one Approve & start beside the password,
1191
1205
  and every other signed term in one Details fold at the end. Sentence
1192
1206
  case, Geist, 12px and up; prose capped at 75ch. */
1193
- .approval-sheet { display: grid; gap: 16px; margin: 0; padding: 20px; border: 1px solid var(--border); border-radius: 10px; background: var(--card); }
1207
+ .approval-sheet { display: grid; gap: var(--so-space-lg); margin: 0; padding: var(--so-space-xl); border: 1px solid var(--border); border-radius: var(--so-radius-card); background: var(--card); }
1194
1208
  .approval-sheet.approve-form > :not(input) { margin: 0; }
1195
1209
  .approval-sheet-title { margin: 0; font-size: 15px; font-weight: 600; line-height: 1.4; letter-spacing: -.01em; }
1196
1210
  .approval-revision, .approval-note { max-width: 75ch; font-size: 14px; line-height: 1.6; overflow-wrap: anywhere; }
1197
1211
  .approval-note { color: var(--muted-foreground); }
1198
1212
  .approval-rows { display: grid; gap: 14px; margin: 0; }
1199
- .approval-row { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); gap: 16px; }
1213
+ .approval-row { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); gap: var(--so-space-lg); }
1200
1214
  .approval-row dt { margin: 0; color: var(--muted-foreground); font-size: 13px; font-weight: 500; line-height: 1.6; }
1201
1215
  .approval-row dd { min-width: 0; max-width: 75ch; margin: 0; font-size: 14px; line-height: 1.6; overflow-wrap: anywhere; }
1202
1216
  .approval-row dd p, .approval-sheet .approval-goal { margin: 0; font-size: 14px; line-height: 1.6; }
1203
- .approval-row dd :is(ol, ul) { display: grid; gap: 4px; margin: 0; padding-left: 1.25rem; }
1217
+ .approval-row dd :is(ol, ul) { display: grid; gap: var(--so-space-xs); margin: 0; padding-left: 1.25rem; }
1204
1218
  .approval-row dd ol + p { margin-top: 6px; }
1205
1219
  .approval-row dd .approval-paths { margin-top: 2px; padding-left: 0; }
1206
1220
  .approval-row dd .approval-paths li { font-size: 13px; }
1207
- .approval-amendment { max-width: 75ch; padding: 12px 14px; border-radius: 8px; background: var(--muted); font-size: 14px; line-height: 1.55; overflow-wrap: anywhere; }
1221
+ .approval-amendment { max-width: 75ch; padding: var(--so-space-md) 14px; border-radius: var(--so-radius-md); background: var(--muted); font-size: 14px; line-height: 1.55; overflow-wrap: anywhere; }
1208
1222
  .approval-amendment p { margin: 0; }
1209
- .approval-amendment ul { display: grid; gap: 4px; margin: 6px 0; padding-left: 1.25rem; }
1223
+ .approval-amendment ul { display: grid; gap: var(--so-space-xs); margin: 6px 0; padding-left: 1.25rem; }
1210
1224
  .agents-size { margin: 0; font-size: 13px; line-height: 1.5; }
1211
1225
  .approval-who { max-width: 75ch; color: var(--muted-foreground); font-size: 13px; line-height: 1.5; }
1212
- .approval-act { display: grid; grid-template-columns: minmax(0, 20rem) auto; justify-content: start; align-items: center; gap: 8px; }
1226
+ .approval-act { display: grid; grid-template-columns: minmax(0, 20rem) auto; justify-content: start; align-items: center; gap: var(--so-space-sm); }
1213
1227
  .approval-password { margin: 0; }
1214
1228
  .approval-act input[type=password] { margin: 0; font-family: var(--font-sans); }
1215
1229
  .approval-act button { margin: 0; min-height: 2.125rem; white-space: nowrap; }
@@ -1218,7 +1232,7 @@
1218
1232
  .approval-sheet .approval-allowing + .approval-you-check { margin-top: -10px; }
1219
1233
  .approval-act > button { grid-column: 2; grid-row: 1; }
1220
1234
  .approval-password-note { grid-column: 1; grid-row: 2; margin: 0; color: var(--muted-foreground); font-size: 13px; line-height: 1.5; }
1221
- .approval-secondary { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
1235
+ .approval-secondary { display: flex; flex-wrap: wrap; align-items: center; gap: var(--so-space-sm); }
1222
1236
  details.approval-edit, details.approval-edit[open] { margin: 0; padding: 0; border: 0; border-radius: 0; background: none; }
1223
1237
  /* Edit plan, in place: the rows become fields where they stood; the
1224
1238
  approval act and Details step aside until it is saved or cancelled. */
@@ -1233,32 +1247,32 @@
1233
1247
  .approval-secondary:has(> .approval-edit[open]) > a { display: none; }
1234
1248
  .approval-editor { display: grid; gap: 14px; }
1235
1249
  .approval-editor .problem { margin: 0; }
1236
- .approval-field { display: grid; gap: 4px; min-width: 0; margin: 0; padding: 0; border: 0; }
1250
+ .approval-field { display: grid; gap: var(--so-space-xs); min-width: 0; margin: 0; padding: 0; border: 0; }
1237
1251
  .approval-field-label { padding: 0; color: var(--muted-foreground); font-size: 13px; font-weight: 500; line-height: 1.6; }
1238
1252
  .approval-field-hint { color: var(--muted-foreground); font-size: 12px; line-height: 1.5; }
1239
1253
  .approval-field textarea, .approval-field input[type=text] { width: 100%; max-width: 75ch; margin: 0; font-family: var(--font-sans); font-size: 14px; line-height: 1.55; }
1240
1254
  .approval-requirements { display: grid; gap: 6px; margin: 2px 0 0; padding: 0; list-style: none; }
1241
- .approval-edit-act { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
1255
+ .approval-edit-act { display: flex; flex-wrap: wrap; align-items: center; gap: var(--so-space-sm); }
1242
1256
  .approval-edit-act button { margin: 0; min-height: 2.125rem; white-space: nowrap; }
1243
- .approval-link { display: inline-flex; align-items: center; min-height: 32px; padding: 0 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--card); color: var(--foreground); font-size: 13px; font-weight: 500; text-decoration: none; }
1257
+ .approval-link { display: inline-flex; align-items: center; min-height: 32px; padding: 0 var(--so-space-md); border: 1px solid var(--border); border-radius: var(--so-radius-md); background: var(--card); color: var(--foreground); font-size: 13px; font-weight: 500; text-decoration: none; }
1244
1258
  .approval-secondary > .approval-link:not(:first-child) { border-color: transparent; background: none; color: var(--muted-foreground); }
1245
1259
  @media (hover: hover) and (pointer: fine) { .approval-link:hover { background: var(--muted); color: var(--foreground); } }
1246
1260
  details.approval-details { margin: 0; padding: 0; border: 0; border-top: 1px solid var(--border); border-radius: 0; background: none; }
1247
1261
  details.approval-details[open] { padding-bottom: 0; }
1248
- .approval-details > summary { display: flex; align-items: center; min-height: 40px; padding: 4px 0 0; font-size: 13px; }
1249
- .approval-detail { max-width: 75ch; margin: 12px 0 0; font-size: 13px; line-height: 1.55; overflow-wrap: anywhere; }
1250
- .approval-detail h3 { margin: 0 0 4px; font-size: 13px; font-weight: 600; letter-spacing: 0; text-transform: none; }
1251
- .approval-detail p, .approval-detail ul, .approval-detail dl { margin: 4px 0; }
1262
+ .approval-details > summary { display: flex; align-items: center; min-height: 40px; padding: var(--so-space-xs) 0 0; font-size: 13px; }
1263
+ .approval-detail { max-width: 75ch; margin: var(--so-space-md) 0 0; font-size: 13px; line-height: 1.55; overflow-wrap: anywhere; }
1264
+ .approval-detail h3 { margin: 0 0 var(--so-space-xs); font-size: 13px; font-weight: 600; letter-spacing: 0; text-transform: none; }
1265
+ .approval-detail p, .approval-detail ul, .approval-detail dl { margin: var(--so-space-xs) 0; }
1252
1266
  .approval-detail ul { padding-left: 1.25rem; }
1253
1267
  .approval-detail .revision-card { margin: 0; padding: 0; border: 0; background: none; }
1254
- .approval-roles { display: grid; gap: 8px; }
1255
- .approval-roles > div { display: grid; grid-template-columns: 6rem minmax(0, 1fr); gap: 12px; }
1268
+ .approval-roles { display: grid; gap: var(--so-space-sm); }
1269
+ .approval-roles > div { display: grid; grid-template-columns: 6rem minmax(0, 1fr); gap: var(--so-space-md); }
1256
1270
  .approval-roles dt { color: var(--muted-foreground); font-weight: 500; }
1257
1271
  .approval-roles dd { margin: 0; }
1258
1272
  .approval-roles dd ul { margin: 2px 0 0; color: var(--muted-foreground); }
1259
1273
  .chat-plan .approval-sheet { padding: 0; border: 0; background: none; }
1260
1274
  @media (max-width: 760px) {
1261
- .approval-sheet { gap: 12px; padding: 14px; }
1275
+ .approval-sheet { gap: var(--so-space-md); padding: 14px; }
1262
1276
  .approval-row { grid-template-columns: 1fr; gap: 2px; }
1263
1277
  .approval-act { grid-template-columns: 1fr; }
1264
1278
  .approval-act > button, .approval-password-note { grid-column: auto; grid-row: auto; }
@@ -1269,7 +1283,7 @@
1269
1283
  position: sticky; bottom: 0; z-index: 20; margin: 0 -14px; padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
1270
1284
  border-top: 1px solid var(--border); background: var(--card);
1271
1285
  }
1272
- .approval-link { min-height: 44px; padding: 0 16px; }
1286
+ .approval-link { min-height: 44px; padding: 0 var(--so-space-lg); }
1273
1287
  .approval-edit[open] > summary { top: 6px; right: 6px; }
1274
1288
  .approval-field textarea, .approval-field input[type=text] { font-size: 16px; }
1275
1289
  .approval-edit-act button { width: 100%; min-height: 44px; font-size: 16px; }
@@ -1719,7 +1733,7 @@
1719
1733
  .tabbar a { position: relative; }
1720
1734
  .tabbar a .dot-badge {
1721
1735
  position: absolute; top: .3125rem; left: calc(50% + .375rem);
1722
- width: .375rem; height: .375rem; border-radius: 9999px; background: var(--brand);
1736
+ width: .375rem; height: .375rem; border-radius: var(--so-radius-full); background: var(--brand);
1723
1737
  }
1724
1738
  .content > main { padding: 1rem 1rem calc(4.5rem + env(safe-area-inset-bottom, 0rem)); }
1725
1739
  .project-add-card { padding: 1rem; }
@@ -1752,7 +1766,7 @@
1752
1766
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1753
1767
  .stat-card .v { font-size: .75rem; color: var(--muted-foreground); margin-top: .25rem;
1754
1768
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1755
- .dot { display: inline-block; width: .5rem; height: .5rem; border-radius: 9999px; flex: none; }
1769
+ .dot { display: inline-block; width: .5rem; height: .5rem; border-radius: var(--so-radius-full); flex: none; }
1756
1770
  .dot-ok { background: var(--success); }
1757
1771
  /* Caution without a claim on the operator: quiet, not magenta. */
1758
1772
  .dot-warn { background: var(--muted-foreground); }
@@ -1787,13 +1801,13 @@
1787
1801
  .sso-step-up { display: inline-flex; align-items: center; min-height: 36px; font-size: .9rem; }
1788
1802
  .sso-step-up[data-sso-step-up="confirmed"] { color: var(--so-success); }
1789
1803
  .sso-step-up[data-sso-step-up="confirmed"]::before { content: "✓"; margin-right: 6px; }
1790
- .login-shell .login-sso { display: flex; justify-content: center; align-items: center; min-height: 44px; border-radius: 8px; background: var(--so-accent); color: var(--so-on-accent); font-weight: 600; text-decoration: none; margin: 0 0 1rem; }
1804
+ .login-shell .login-sso { display: flex; justify-content: center; align-items: center; min-height: 44px; border-radius: var(--so-radius-md); background: var(--so-accent); color: var(--so-on-accent); font-weight: 600; text-decoration: none; margin: 0 0 1rem; }
1791
1805
  @media (hover: hover) and (pointer: fine) { .login-shell .login-sso:hover { background: var(--so-accent-hover); } }
1792
1806
  .login-shell .login-password > summary { text-align: center; font-size: .875rem; color: var(--muted-foreground); cursor: pointer; margin: 0 0 .75rem; }
1793
1807
  .focus-page { max-width: 40rem; margin: 0 auto; padding: 2rem 1.25rem 3rem; }
1794
1808
  .focus-page .focus-brand { display: inline-flex; margin: 0 0 2.5rem; }
1795
1809
  .focus-page h1 { margin: 0 0 1rem; }
1796
- .secret-value { overflow-wrap: anywhere; font-size: .95rem; padding: .75rem .875rem; margin: .75rem 0; border: 1px solid var(--border); border-radius: 8px; background: var(--background); user-select: all; }
1810
+ .secret-value { overflow-wrap: anywhere; font-size: .95rem; padding: .75rem .875rem; margin: .75rem 0; border: 1px solid var(--border); border-radius: var(--so-radius-md); background: var(--background); user-select: all; }
1797
1811
 
1798
1812
  @media (max-width: 640px) {
1799
1813
  button, form.option button { min-height: 2.75rem; }
@@ -1811,7 +1825,7 @@
1811
1825
  building, then the rest. A header is a 2.75rem tap; a drawn chevron
1812
1826
  says which way it folds. */
1813
1827
  @media (max-width: 760px) {
1814
- .board { display: flex; flex-direction: column; gap: .5rem; padding-bottom: 0; }
1828
+ .board { display: flex; flex-direction: column; align-items: stretch; gap: .5rem; padding-bottom: 0; }
1815
1829
  .board .lane { min-height: 0; padding: 0 .625rem .125rem; }
1816
1830
  .board .lane > summary {
1817
1831
  cursor: pointer; display: flex; align-items: center; min-height: 2.75rem; margin: 0;
@@ -1845,12 +1859,12 @@
1845
1859
  .lane .hint { margin-top: 0; }
1846
1860
  .lane-count { color: var(--muted-foreground); font-weight: 400; font-variant-numeric: tabular-nums; }
1847
1861
  .lane h2::before {
1848
- content: ""; width: .375rem; height: .375rem; border-radius: 9999px; flex: none;
1862
+ content: ""; width: .375rem; height: .375rem; border-radius: var(--so-radius-full); flex: none;
1849
1863
  background: var(--muted-foreground); opacity: .55;
1850
1864
  }
1851
1865
  .lane-attention h2::before { background: var(--brand); opacity: 1; }
1852
1866
  .lane-building h2::before { background: var(--running); opacity: 1; }
1853
- .lane-done h2::before { background: var(--success); opacity: 1; }
1867
+ .lane-done h2::before { background: var(--muted-foreground); opacity: 1; }
1854
1868
  .lane-card {
1855
1869
  display: block; text-decoration: none; color: inherit;
1856
1870
  background: var(--card); border: 1px solid var(--border);
@@ -1877,6 +1891,37 @@
1877
1891
  font-family: var(--font-mono); font-size: .6875rem; color: var(--running); font-variant-numeric: tabular-nums;
1878
1892
  }
1879
1893
  .lane-card .live-line .clock { color: var(--muted-foreground); }
1894
+ /* The build's steps — set up, writing, checking and committing: three quiet
1895
+ ticks, the current one in the running hue. A stage, never a percent. */
1896
+ .lane-card .steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .1875rem; margin-top: .5rem; }
1897
+ .lane-card .steps i { height: .1875rem; border-radius: 999px; background: var(--border); }
1898
+ .lane-card .steps i.on { background: color-mix(in srgb, var(--running) 45%, var(--border)); }
1899
+ .lane-card .steps i.now { background: var(--running); }
1900
+
1901
+ /* The worker strip: one quiet tile per worker above the lanes — what it is
1902
+ building here, the stage, the clock. Ink and lines only; the running dot
1903
+ is the one hue. */
1904
+ .crew { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: .625rem; margin: .25rem 0 .875rem; }
1905
+ .crew-empty { margin: .25rem 0 .875rem; }
1906
+ .worker {
1907
+ border: 1px solid var(--border); border-radius: var(--radius); background: var(--card);
1908
+ padding: .625rem .75rem; min-width: 0; display: flex; flex-direction: column; gap: .375rem;
1909
+ }
1910
+ .worker-busy { border-color: color-mix(in srgb, var(--running) 30%, var(--border)); }
1911
+ .worker-offline { opacity: .6; }
1912
+ .worker-head { display: flex; align-items: center; gap: .5rem; min-width: 0; }
1913
+ .worker-head .name { font-family: var(--font-mono); font-size: .75rem; font-weight: 500; }
1914
+ .worker-head .kind { font-size: .6875rem; color: var(--muted-foreground); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; }
1915
+ .worker-head .slots { font-family: var(--font-mono); font-size: .6875rem; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
1916
+ .worker-job { display: flex; flex-direction: column; gap: .0625rem; text-decoration: none; color: inherit; min-width: 0; margin: 0; }
1917
+ .worker-job .t { font-size: .8125rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1918
+ .worker-job .sub { display: flex; justify-content: space-between; gap: .5rem; font-family: var(--font-mono); font-size: .6875rem; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
1919
+ .worker-job.idle { font-size: .75rem; color: var(--muted-foreground); }
1920
+ @media (hover: hover) and (pointer: fine) { a.worker-job:hover .t { text-decoration: underline; } }
1921
+ @media (max-width: 760px) {
1922
+ .crew { grid-template-columns: minmax(0, 1fr); gap: .375rem; }
1923
+ .worker { padding: .5rem .625rem; }
1924
+ }
1880
1925
  .lane-empty { margin: .75rem 0 .25rem; }
1881
1926
  .plan-doc { white-space: pre-wrap; overflow-wrap: anywhere; font-size: .8125rem; max-height: 24rem; overflow-y: auto; }
1882
1927
  .lane-more { display: block; margin-top: .5rem; font-size: .75rem; }
@@ -1901,7 +1946,7 @@
1901
1946
  .command-metric .value { display: block; margin-top: .25rem; font-size: 1.5rem; font-weight: 600; line-height: 1.15; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
1902
1947
  .command-metric .label { display: flex; align-items: center; gap: .4rem; font-weight: 500; font-size: .8125rem; }
1903
1948
  .command-metric .label::before {
1904
- content: ""; width: .375rem; height: .375rem; border-radius: 9999px;
1949
+ content: ""; width: .375rem; height: .375rem; border-radius: var(--so-radius-full);
1905
1950
  background: var(--muted-foreground); flex: none;
1906
1951
  }
1907
1952
  .command-metric .detail { display: block; color: var(--muted-foreground); font-size: .6875rem; margin-top: .18rem; }
@@ -2433,7 +2478,7 @@
2433
2478
  }
2434
2479
  .workspace-stats .pulse-stat b { display: block; color: var(--foreground); font-weight: 600; font-family: var(--font-mono); font-size: 1.125rem; line-height: 1.2; font-variant-numeric: tabular-nums; }
2435
2480
  .workspace-stats .pulse-stat.hot b { color: var(--brand); }
2436
- .workspace-bar { display: flex; gap: 2px; height: .375rem; margin-top: .625rem; border-radius: 9999px; overflow: hidden; background: var(--muted); }
2481
+ .workspace-bar { display: flex; gap: 2px; height: .375rem; margin-top: .625rem; border-radius: var(--so-radius-full); overflow: hidden; background: var(--muted); }
2437
2482
  .workspace-bar .seg { flex: 1 1 0; }
2438
2483
  .workspace-bar .seg.attention { background: var(--brand); }
2439
2484
  .workspace-bar .seg.building { background: var(--running); }
@@ -2660,8 +2705,10 @@ button { min-height: 44px; }
2660
2705
  @media (hover: hover) and (pointer: fine) { .next-pager .skip:hover { border-color: color-mix(in srgb, var(--border) 60%, var(--muted-foreground)); } }
2661
2706
 
2662
2707
  /* Motion: only where a human caused the change — navigation, presses,
2663
- overlays. Liveness swaps stay instant; the pulse dot is the one "alive"
2664
- signal. Everything dies under prefers-reduced-motion; auto-refresh pages
2708
+ overlays — and one exception, the live board: a card that changes lane
2709
+ slides there and a worker that picks up work flashes once (the board's
2710
+ swap, html.so-swap, below). Every other liveness swap stays instant; the
2711
+ pulse dot is the one "alive" signal. Everything dies under prefers-reduced-motion; auto-refresh pages
2665
2712
  opt out of the navigation cross-fade separately (see shell()). */
2666
2713
  @view-transition { navigation: auto; }
2667
2714
  /* A page change fades THROUGH, not across: the old page leaves before the new
@@ -2762,3 +2809,21 @@ button.pick-file { min-height: 1.75rem; padding: 0 .55rem; font-size: .75rem; }
2762
2809
  .meta, .hint { line-height: 1.35; }
2763
2810
  details { margin: var(--so-phone-row) 0; }
2764
2811
  }
2812
+
2813
+ /* The live board's swap (regionScript animate): only cards move — the page
2814
+ itself does not fade — a short slide for a card that changed lane, a drop
2815
+ for a new one, a fade for one that left. Transform and opacity only, and
2816
+ the reduce block kills it with everything else (the script also skips the
2817
+ transition under reduced motion). */
2818
+ html.so-swap::view-transition-old(root), html.so-swap::view-transition-new(root) { animation: none; }
2819
+ html.so-swap::view-transition-group(*) { animation-duration: 320ms; animation-timing-function: cubic-bezier(.2, .8, .2, 1); }
2820
+ html.so-swap::view-transition-new(*):only-child { animation: so-card-in 240ms ease-out both; }
2821
+ html.so-swap::view-transition-old(*):only-child { animation: so-card-out 160ms ease-in both; }
2822
+ @keyframes so-card-in { from { opacity: 0; transform: translateY(-.375rem); } }
2823
+ @keyframes so-card-out { to { opacity: 0; } }
2824
+ .worker.flash { animation: so-worker-claim 900ms ease-out; }
2825
+ @keyframes so-worker-claim { from { background: color-mix(in srgb, var(--running) 14%, var(--card)); } }
2826
+ @media (prefers-reduced-motion: reduce) {
2827
+ html.so-swap::view-transition-group(*), html.so-swap::view-transition-new(*), html.so-swap::view-transition-old(*) { animation: none !important; }
2828
+ .worker.flash { animation: none; }
2829
+ }
@@ -18,7 +18,7 @@ import { assignFlowCard, commentOnFlowCard, watchFlowCard } from "../flow-people
18
18
  import { saveScript } from "../flow-scripts.js";
19
19
  import { chooseFlowCard } from "../flow-send.js";
20
20
  import { exportFlow, fetchFlowFile, FlowFileError, importFlow, parseFlowFile, planFlowImport } from "../flow-share.js";
21
- import { addFlowTriggerTo, checkFlowTriggerNow, pressFlowButton, removeFlowTrigger, removeLinearKey, setFlowTriggerOn, renewFlowHook, saveHooksBase, saveLinearKey, saveLinearSigningSecret, shareFlowButton, stopSharingFlowButton } from "../flow-triggers.js";
21
+ import { addFlowTriggerTo, checkFlowTriggerNow, pressFlowButton, removeFlowTrigger, removeLinearKey, setFlowTriggerOn, renewFlowHook, saveHooksBase, saveLinearKey, saveSigningSecret, setUpFlowHook, changeFlowHookSender, setFlowHookScope, shareFlowButton, stopSharingFlowButton, rawTakesDeliveries } from "../flow-triggers.js";
22
22
  import { FLOW_IMPORT_SCRIPT, flowFallbackHtml, flowImportHtml, flowsListHtml, flowView } from "../flows-ui.js";
23
23
  import { FLOW_TEMPLATES, FlowContractError, validateFlowDefinition, withZoneNames } from "../flows.js";
24
24
  import { prepareUnattendedCandidates, parseUnattendedAsk, revokeUnattended, signUnattended, untilWords } from "../flow-unattended.js";
@@ -43,7 +43,7 @@ import { redirect, respond, SAFETY, taskHref } from "./http.js";
43
43
  import { html } from "../html.js";
44
44
  import {} from "./session.js";
45
45
  export function createFlowsHandlers(runtime) {
46
- const { store, clock, sendScreen, chromeFor, consoleProjects, options, visible, flowRooms, identify, liveCeiling, providerHome, toolHome, restricted, evidenceRoot, authenticateApprover, projectOf, unscopedMode, ceiling, authorizeMutation } = runtime;
46
+ const { store, clock, sendScreen, chromeFor, consoleProjects, options, visible, flowRooms, identify, liveCeiling, providerHome, toolHome, restricted, evidenceRoot, authenticateApprover, authenticateCredential, projectOf, unscopedMode, ceiling, authorizeMutation } = runtime;
47
47
  // ---- reads ---------------------------------------------------------------
48
48
  /** A gallery template's page (Flows → New → Use this): its questions, the preview of these answers, and Create. */
49
49
  function sendGalleryUse(response, who, template, projects, repo, given, name, notice, said = null) {
@@ -572,7 +572,11 @@ export function createFlowsHandlers(runtime) {
572
572
  catch {
573
573
  return answer(400, { ok: false, said: "That trigger couldn't be read." });
574
574
  }
575
- const made = addFlowTriggerTo(store, flow, raw, who.name, now, dir);
575
+ // A trigger that takes deliveries mints an address: a credential act (docs/plans/passwordless.md).
576
+ const ingress = rawTakesDeliveries(raw);
577
+ if (ingress && !authenticateCredential(who, body.get("password") ?? "").ok)
578
+ return answer(403, { ok: false, said: "Enter your Toolroll password to make a webhook address." });
579
+ const made = addFlowTriggerTo(store, flow, raw, who.name, now, dir, ingress ? { ingress: "proved" } : {});
576
580
  if (made.ok)
577
581
  await prepareUnattendedCandidates(store, flow.id);
578
582
  return made.ok ? settle(made.said, made.reveal === null ? {} : { reveal: made.reveal }) : answer(400, { ok: false, said: made.message });
@@ -729,6 +733,10 @@ export function createFlowsHandlers(runtime) {
729
733
  removeFlowTrigger(store, trigger, now, dir, who.name);
730
734
  return settle("Trigger removed.");
731
735
  }
736
+ // A public form link and a new webhook address each open a way in: credential acts, a password typed now.
737
+ if ((verb === "share" || verb === "renew") && !authenticateCredential(who, body.get("password") ?? "").ok) {
738
+ return answer(403, { ok: false, said: verb === "share" ? "Enter your Toolroll password to share a form link." : "Enter your Toolroll password to make a new address." });
739
+ }
732
740
  if (verb === "share") {
733
741
  const shared = shareFlowButton(store, trigger, now, dir);
734
742
  return shared.ok ? settle(shared.said, { reveal: shared.reveal }) : answer(409, { ok: false, said: shared.message });
@@ -737,6 +745,11 @@ export function createFlowsHandlers(runtime) {
737
745
  stopSharingFlowButton(store, trigger, now);
738
746
  return settle("The form link no longer works.");
739
747
  }
748
+ // v126: a person has seen that deliveries were turned away (the banner stays until then).
749
+ if (verb === "dropped") {
750
+ store.clearFlowDeliveriesDropped(trigger.id);
751
+ return settle("Cleared.");
752
+ }
740
753
  if (verb === "press") {
741
754
  let answers;
742
755
  try {
@@ -764,11 +777,24 @@ export function createFlowsHandlers(runtime) {
764
777
  const renewed = renewFlowHook(store, trigger, now, dir);
765
778
  return renewed.ok ? settle(renewed.said, { reveal: renewed.reveal }) : answer(409, { ok: false, said: renewed.message });
766
779
  }
767
- // Linear's signing secret, pasted on this secure panel behind the password — never in chat.
768
- if (!authenticateApprover(who, body.get("password") ?? "").ok)
769
- return answer(403, { ok: false, said: "Enter your Toolroll password to save a secret." });
770
- const saved = saveLinearSigningSecret(trigger, body.get("secret") ?? "", dir);
771
- return saved.ok ? settle("Signing secret saved. Linear's deliveries can be proved now.") : answer(400, { ok: false, said: saved.message });
780
+ // A webhook's credentials change only on this secure panel, behind the password — never in chat (v126): a secret
781
+ // pasted (Linear's, Sentry's, Vercel's), one made and shown once with a new address, or who signs its deliveries.
782
+ if (!authenticateCredential(who, body.get("password") ?? "").ok)
783
+ return answer(403, { ok: false, said: verb === "secret" ? "Enter your Toolroll password to save a secret." : "Enter your Toolroll password to change how this webhook is proved." });
784
+ if (verb === "setup") {
785
+ const made = setUpFlowHook(store, trigger, now, dir);
786
+ return made.ok ? settle(made.said, { reveal: made.reveal }) : answer(409, { ok: false, said: made.message });
787
+ }
788
+ if (verb === "scope") {
789
+ const scoped = setFlowHookScope(store, trigger, { project: body.get("project"), team: body.get("team") }, who.name, now);
790
+ return scoped.ok ? settle(scoped.said) : answer(400, { ok: false, said: scoped.message });
791
+ }
792
+ if (verb === "sender") {
793
+ const changed = changeFlowHookSender(store, trigger, body.get("sender"), who.name, now, dir);
794
+ return changed.ok ? settle(changed.said) : answer(400, { ok: false, said: changed.message });
795
+ }
796
+ const saved = saveSigningSecret(store, trigger, body.get("secret") ?? "", now, dir);
797
+ return saved.ok ? settle(saved.said) : answer(400, { ok: false, said: saved.message });
772
798
  };
773
799
  const recipeAct = (step) => async (ctx) => {
774
800
  const { who, request, response, now, posted } = ctx;
@@ -924,6 +950,10 @@ export function createFlowsHandlers(runtime) {
924
950
  "flow.trigger.press": triggerAct("press"),
925
951
  "flow.trigger.renew": triggerAct("renew"),
926
952
  "flow.trigger.secret": triggerAct("secret"),
953
+ "flow.trigger.setup": triggerAct("setup"),
954
+ "flow.trigger.sender": triggerAct("sender"),
955
+ "flow.trigger.scope": triggerAct("scope"),
956
+ "flow.trigger.dropped": triggerAct("dropped"),
927
957
  "flow.trigger.share": triggerAct("share"),
928
958
  "flow.trigger.unshare": triggerAct("unshare"),
929
959
  "recipes.prepare-send": recipeAct("prepare"),
@@ -41,6 +41,7 @@ export interface GuardsRuntime {
41
41
  }
42
42
  export declare function createSharedGuards(runtime: GuardsRuntime): {
43
43
  authenticateApprover: (who: Who, token: string, repo?: string | null) => ReturnType<typeof checkApproverPassword>;
44
+ authenticateCredential: (who: Who, token: string, repo?: string | null) => ReturnType<typeof checkApproverPassword>;
44
45
  authorizeMutation: (request: IncomingMessage, who: Who, body: FormView<FormFieldOf<"mutationGuard">>) => {
45
46
  status: number;
46
47
  message: string;
@@ -6,23 +6,29 @@ import { tokenLive, tokenProjects } from "../api-tokens.js";
6
6
  import { oauthProjects, oauthTokenAllowed } from "../mcp-oauth.js";
7
7
  import { REMOTE_MESSAGES, reproveRemote } from "../operate-remote.js";
8
8
  import { canonicalProject, rowVisible } from "../project.js";
9
- import { authenticateAccount, authenticateApprover as checkApproverPassword } from "../scope.js";
9
+ import { authenticateAccount, authenticateApprover as checkApproverPassword, credentialProof } from "../scope.js";
10
10
  import { SourceBudget } from "../sign-in-guard.js";
11
11
  import {} from "../store.js";
12
12
  import { requestContext } from "./request-context.js";
13
13
  import { refuse } from "./chrome.js";
14
14
  import { matchTaskPath } from "./http.js";
15
- import { PersistentSessions, SESSION_ABSOLUTE_MS, SESSION_COOKIE, SESSION_IDLE_MS } from "./session.js";
15
+ import { PersistentSessions, SESSION_COOKIE, sessionWithinLifetime } from "./session.js";
16
16
  export function createSharedGuards(runtime) {
17
17
  // v99: wrong passwords. One address guessing across names runs out of tries here; each name
18
18
  // locks on its own (sign-in-guard.ts), and every lock, whatever road it came by, is kept.
19
19
  /**
20
- * Every step-up in the console (v100): a person the identity provider
21
- * checked in the last ten minutes confirms with that (an empty password
22
- * field); anyone else types their password. Standing (an approver, with
23
- * access to the project) is checked either way.
20
+ * Every step-up in the console: a password typed now, or (an empty field) an explicit identity-provider re-check
21
+ * made on request and not yet spent, or — inside an allowlisted act only — a remembered password (cockpit B2,
22
+ * scope.ts proveAccountNow / freshnessFor, reading the request's live facts). Standing (an approver, with access to
23
+ * the project) is checked either way.
24
24
  */
25
- // v100: the shared check honours a fresh identity-provider sign-in itself (freshIdentitySignIn, set per request below).
25
+ /** A credential act (docs/plans/passwordless.md): only a password typed now (or a one-use provider re-check) proves it. */
26
+ function authenticateCredential(who, token, repo) {
27
+ if (who.via !== "cookie")
28
+ return { ok: false, reason: "not-an-approver" };
29
+ const proved = checkApproverPassword(runtime.store, who.name, token, repo, { credential: true });
30
+ return !proved.ok || credentialProof(proved.method) ? proved : { ok: false, reason: "not-an-approver" };
31
+ }
26
32
  function authenticateApprover(who, token, repo) {
27
33
  // Re-entering a password never upgrades a machine credential into a browser ceremony.
28
34
  if (who.via !== "cookie")
@@ -332,7 +338,7 @@ export function createSharedGuards(runtime) {
332
338
  }
333
339
  for (const [id, session] of found === null ? [] : [found]) {
334
340
  const now = Date.now();
335
- if (now - session.lastSeen > SESSION_IDLE_MS || now - session.createdAt > SESSION_ABSOLUTE_MS) {
341
+ if (!sessionWithinLifetime(session, now)) {
336
342
  runtime.sessions.delete(id);
337
343
  return null;
338
344
  }
@@ -340,10 +346,18 @@ export function createSharedGuards(runtime) {
340
346
  runtime.sessions.delete(id);
341
347
  return null;
342
348
  }
349
+ // Ended in another process (or from Sessions): the cached copy is dropped, never used.
350
+ if (!runtime.sessions.stillSaved(id)) {
351
+ Map.prototype.delete.call(runtime.sessions, id);
352
+ return null;
353
+ }
343
354
  if (touch) {
344
355
  session.lastSeen = now;
345
356
  runtime.sessions.persist(session, true);
346
357
  }
358
+ // A touch that found the session ended elsewhere dropped it: this request is not signed in by it.
359
+ if (touch && !runtime.sessions.holds(session))
360
+ return null;
347
361
  return session;
348
362
  }
349
363
  return null;
@@ -356,5 +370,5 @@ export function createSharedGuards(runtime) {
356
370
  function runVisible(run) {
357
371
  return visible(taskRepoOf(run.taskRef));
358
372
  }
359
- return { authenticateApprover, authorizeMutation, projectOf, resolveRouteProject, projectRequestAllowed, identify, lookupSession, restricted, visible, admissionList, codingActorAllowed, codingProjectAllowed, workAccess, familyOf, taskRepoOf, runVisible };
373
+ return { authenticateApprover, authenticateCredential, authorizeMutation, projectOf, resolveRouteProject, projectRequestAllowed, identify, lookupSession, restricted, visible, admissionList, codingActorAllowed, codingProjectAllowed, workAccess, familyOf, taskRepoOf, runVisible };
360
374
  }
@@ -1,4 +1,5 @@
1
1
  /** HTTP answers every console route shares: server options, body limits, safe responses, redirects and return paths. */
2
+ import type { TerminalHost } from "../live-terminal.js";
2
3
  import { type Html } from "../html.js";
3
4
  import { type CliHttpOptions, type RunOperateAs } from "../cli-http.js";
4
5
  import { type CodingWorkspace } from "../coding-workspace.js";
@@ -25,6 +26,8 @@ export type ServeOptions = {
25
26
  installBin?: string;
26
27
  /** Native coding workspace injection for isolated integration tests. */
27
28
  codingWorkspace?: CodingWorkspace;
29
+ /** Live terminals (tests stand their own in; null turns them off). Default: one on a machine that runs agents. */
30
+ terminalHost?: TerminalHost | null;
28
31
  /** Read-only worker connections for heavy reads (read-executor.ts); 0 reads in-process. Default: off under tests. */
29
32
  readWorkers?: number;
30
33
  store: Store;
@@ -141,6 +144,10 @@ export type ServeOptions = {
141
144
  connectFetch?: typeof fetch;
142
145
  /** Tests: every request sign-in with the identity provider makes (discovery, keys, tokens). */
143
146
  ssoFetch?: typeof fetch;
147
+ /** Cockpit B1: this `up` console's instance secret (null: none — the instance route answers 404). */
148
+ instanceSecret?: () => string | null;
149
+ /** Cockpit E: how Finder is asked to reveal a proved checkout (tests pass their own; never a launch). True: shown. */
150
+ revealPath?: (path: string) => Promise<boolean> | boolean;
144
151
  chatEnv?: Record<string, string | undefined>;
145
152
  /**
146
153
  * The live peek's locality ASSERTION (live-peek v3 §3): the administrator
@@ -1,4 +1,3 @@
1
- /** HTTP answers every console route shares: server options, body limits, safe responses, redirects and return paths. */
2
1
  import { html, htmlString, isHtml } from "../html.js";
3
2
  import { chatResultHref as sharedResultHref } from "../chat-controls.js";
4
3
  import {} from "../cli-http.js";
@@ -117,7 +116,9 @@ export function goOutside(response, to, words, bind) {
117
116
  response.end(htmlString(html `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta http-equiv="refresh" content="0;url=${to}"><title>Toolroll</title>${HANDOFF_STYLE}<p>${words} <a href="${to}">Continue</a></p>`));
118
117
  }
119
118
  export function redirect(response, to) {
120
- response.writeHead(303, { ...SAFETY, Location: to });
119
+ // Set as a header (not only in writeHead) so the answer can be read back after: a refused act's redirect names it.
120
+ response.setHeader("Location", to);
121
+ response.writeHead(303, { ...SAFETY });
121
122
  response.end();
122
123
  }
123
124
  export const taskChatHref = (taskId) => `/chat?task=${encodeURIComponent(taskId)}`;
@@ -1,10 +1,32 @@
1
+ /**
2
+ * GET /live: one stream per page, joined to every room the page shows.
3
+ *
4
+ * room=workspace the reader's workspace may have changed (any meaningful write, or a lapse)
5
+ * room=task:<id> the task family changed; who else has it open
6
+ * room=flow:<id>?card=&editing= the flow changed; who else is on it, and on which card
7
+ * room=chat[?task=|?project=] the reader's own lead reply, as it runs (the only room that carries content)
8
+ * room=team[?conversation=] the team conversation's cursor moved
9
+ * room=agent:<run> one agent on the Agents board changed; while open, this person watches it (no pings)
10
+ * room=summary the reader's project states (the sidebar): each change carries the snapshot; never watching or seeing
11
+ *
12
+ * Every room is validated before anything is looked up, and admitted with
13
+ * the same checks and answers as the page it belongs to, before the stream
14
+ * opens; each is re-proved on every signal and its safety-net check, and a
15
+ * viewer who no longer passes hears `gone`. Events name their room. A page
16
+ * that falls behind gets one `reload` and the stream ends, so its
17
+ * EventSource reconnects (live-bus.ts).
18
+ */
19
+ import type { IncomingMessage } from "node:http";
1
20
  import { type LiveBus } from "../live-bus.js";
2
21
  import type { WorkspaceRevision } from "../workspace-revision.js";
3
22
  import type { ServerRuntime } from "./runtime.js";
23
+ import type { Who } from "./session.js";
4
24
  /** A page asks for a few rooms at once: its workspace, what it shows, its conversation. */
5
- export declare const LIVE_ROOMS_PER_PAGE = 5;
25
+ export declare const LIVE_ROOMS_PER_PAGE = 6;
6
26
  export type LiveRoom = {
7
27
  kind: "workspace";
28
+ } | {
29
+ kind: "summary";
8
30
  } | {
9
31
  kind: "task";
10
32
  task: string;
@@ -26,10 +48,21 @@ export type LiveRoom = {
26
48
  };
27
49
  /** A room's name, read strictly: anything else is not a room. */
28
50
  export declare function parseLiveRoom(name: string): LiveRoom | null;
51
+ /** The same session, still signed in, from the cookie and the account (never this request's context): a live room's and
52
+ * a live terminal's re-proof. Null when it was signed out, ended elsewhere, revoked, rotated or re-roled. */
53
+ export declare function rereadBrowser(runtime: Pick<ServerRuntime, "identify" | "sessions" | "store">, request: IncomingMessage, who: Who & {
54
+ via: "cookie";
55
+ }): (Who & {
56
+ via: "cookie";
57
+ }) | null;
29
58
  export declare function createLiveHandlers(runtime: ServerRuntime, live: {
30
59
  bus: LiveBus;
31
60
  workspaceRevision: WorkspaceRevision;
32
61
  chatProjectOf: (projects: string[]) => string | null | undefined;
62
+ /** One account's sidebar snapshot for one scope (serve.ts projectSummaryFor); null: gone, or the scope moved. */
63
+ projectSummaryFor: (account: string, scope: string) => string | null;
64
+ /** What one account may see now (serve.ts projectScopeFor); null: no such account. */
65
+ projectScopeFor: (account: string) => string | null;
33
66
  }): {
34
67
  registrations: import("./handler-registry.js").Registration[];
35
68
  turnStarted: (thread: number) => void;