toolroll 0.9.54 → 0.9.56

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 (350) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +29 -12
  3. package/dist/action-ledger.d.ts +2 -4
  4. package/dist/action-ledger.js +2 -10
  5. package/dist/agent-onboard.js +4 -1
  6. package/dist/approval-policy.d.ts +5 -5
  7. package/dist/approval-policy.js +4 -4
  8. package/dist/approval-rules-ui.d.ts +3 -2
  9. package/dist/approval-rules-ui.js +17 -22
  10. package/dist/assignment-ui.d.ts +3 -2
  11. package/dist/assignment-ui.js +4 -11
  12. package/dist/backup-ui.d.ts +2 -1
  13. package/dist/backup-ui.js +16 -27
  14. package/dist/brand-mark.d.ts +3 -2
  15. package/dist/brand-mark.js +4 -4
  16. package/dist/browser/workspace.css +1 -1
  17. package/dist/browser/workspace.js +25 -25
  18. package/dist/browser-shell.d.ts +2 -1
  19. package/dist/browser-shell.js +9 -9
  20. package/dist/browser-workspace.d.ts +14 -15
  21. package/dist/browser-workspace.js +0 -3
  22. package/dist/budget-alerts.js +3 -3
  23. package/dist/chat-actions.d.ts +23 -18
  24. package/dist/chat-actions.js +110 -88
  25. package/dist/chat-ask.d.ts +2 -2
  26. package/dist/chat-ask.js +5 -5
  27. package/dist/chat-channel.d.ts +15 -15
  28. package/dist/chat-channel.js +20 -20
  29. package/dist/chat-controls.d.ts +0 -4
  30. package/dist/chat-controls.js +0 -1
  31. package/dist/chat-core.js +1 -1
  32. package/dist/chat-delivery-state.js +2 -2
  33. package/dist/chat-delivery.d.ts +2 -2
  34. package/dist/chat-delivery.js +27 -34
  35. package/dist/chat-flow.js +3 -3
  36. package/dist/chat-messages.d.ts +1 -1
  37. package/dist/chat-messages.js +4 -4
  38. package/dist/chat-migration.d.ts +1 -1
  39. package/dist/chat-migration.js +11 -11
  40. package/dist/chat-polish.d.ts +5 -4
  41. package/dist/chat-polish.js +9 -10
  42. package/dist/chat-rooms.js +3 -3
  43. package/dist/chat-warmth.d.ts +2 -2
  44. package/dist/check-levels-ui.d.ts +2 -1
  45. package/dist/check-levels-ui.js +26 -53
  46. package/dist/cli.d.ts +3 -1
  47. package/dist/cli.js +15 -10
  48. package/dist/coding-shipping-ui.d.ts +2 -1
  49. package/dist/coding-shipping-ui.js +6 -6
  50. package/dist/coding-ui.d.ts +3 -1
  51. package/dist/coding-ui.js +30 -29
  52. package/dist/contracts/chat-actions.d.ts +32 -27
  53. package/dist/contracts/chat-actions.js +29 -11
  54. package/dist/contracts/chat-content.d.ts +1 -1
  55. package/dist/contracts/chat-content.js +1 -1
  56. package/dist/contracts/chat-tables.js +4 -4
  57. package/dist/contracts/cli.d.ts +1 -3
  58. package/dist/contracts/console-api.d.ts +7 -7
  59. package/dist/contracts/console-api.js +13 -12
  60. package/dist/contracts/flow-propose.d.ts +3 -3
  61. package/dist/contracts/flow.d.ts +132 -125
  62. package/dist/contracts/flow.js +27 -12
  63. package/dist/contracts/health.d.ts +1 -3
  64. package/dist/contracts/lead-context.d.ts +1 -1
  65. package/dist/contracts/lead-context.js +2 -2
  66. package/dist/contracts/lead-tools.d.ts +20 -18
  67. package/dist/contracts/lead-tools.js +15 -15
  68. package/dist/contracts/stage-output.d.ts +1 -1
  69. package/dist/contracts/stage-output.js +1 -1
  70. package/dist/contracts/store-json.d.ts +23 -22
  71. package/dist/contracts/store-json.js +17 -17
  72. package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
  73. package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
  74. package/dist/control-ui.d.ts +5 -4
  75. package/dist/control-ui.js +9 -34
  76. package/dist/converse.d.ts +23 -23
  77. package/dist/converse.js +28 -28
  78. package/dist/coordinator-proposals.d.ts +2 -2
  79. package/dist/coordinator-proposals.js +1 -1
  80. package/dist/coordinator.js +1 -1
  81. package/dist/credentials-ui.d.ts +5 -4
  82. package/dist/credentials-ui.js +23 -24
  83. package/dist/demo-chat.d.ts +3 -3
  84. package/dist/demo-chat.js +29 -76
  85. package/dist/deprecations.d.ts +12 -0
  86. package/dist/deprecations.js +20 -0
  87. package/dist/desktop-update-gate.js +8 -4
  88. package/dist/desktop-update.js +6 -1
  89. package/dist/discord-chat.js +6 -6
  90. package/dist/discord-settings.d.ts +3 -2
  91. package/dist/discord-settings.js +17 -42
  92. package/dist/dispatch.d.ts +1 -1
  93. package/dist/dispatch.js +1 -1
  94. package/dist/envelope.js +10 -1
  95. package/dist/evidence-pack.d.ts +2 -1
  96. package/dist/evidence-pack.js +45 -49
  97. package/dist/export-ui.d.ts +7 -2
  98. package/dist/export-ui.js +4 -10
  99. package/dist/export.js +7 -7
  100. package/dist/flow-engine.d.ts +2 -2
  101. package/dist/flow-engine.js +21 -21
  102. package/dist/flow-gallery-ui.d.ts +4 -3
  103. package/dist/flow-gallery-ui.js +28 -43
  104. package/dist/flow-insights.d.ts +1 -1
  105. package/dist/flow-share.js +13 -8
  106. package/dist/flow-starters-ui.d.ts +2 -1
  107. package/dist/flow-starters-ui.js +12 -15
  108. package/dist/flow-steps.d.ts +5 -5
  109. package/dist/flow-steps.js +19 -19
  110. package/dist/flow-triggers.d.ts +2 -1
  111. package/dist/flow-triggers.js +17 -15
  112. package/dist/flows-cli.js +1 -1
  113. package/dist/flows-ui.d.ts +4 -3
  114. package/dist/flows-ui.js +38 -49
  115. package/dist/flows.js +21 -20
  116. package/dist/guides.js +112 -6
  117. package/dist/html.d.ts +76 -0
  118. package/dist/html.js +173 -0
  119. package/dist/integrations-ui.d.ts +2 -1
  120. package/dist/integrations-ui.js +19 -23
  121. package/dist/invoke.js +1 -1
  122. package/dist/kits-ui.d.ts +5 -4
  123. package/dist/kits-ui.js +22 -24
  124. package/dist/kits.d.ts +12 -12
  125. package/dist/kits.js +31 -31
  126. package/dist/knowledge-ui.d.ts +6 -5
  127. package/dist/knowledge-ui.js +26 -25
  128. package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
  129. package/dist/{mate-cli.js → lead-cli.js} +38 -38
  130. package/dist/lead-commitments.js +4 -4
  131. package/dist/lead-context.d.ts +12 -2
  132. package/dist/lead-context.js +15 -17
  133. package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
  134. package/dist/{mate-contract.js → lead-contract.js} +7 -6
  135. package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
  136. package/dist/{mate-doors.js → lead-doors.js} +24 -24
  137. package/dist/lead-follow.d.ts +5 -5
  138. package/dist/lead-follow.js +9 -9
  139. package/dist/lead-people.d.ts +5 -5
  140. package/dist/lead-people.js +14 -14
  141. package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
  142. package/dist/{mate-progress.js → lead-progress.js} +3 -3
  143. package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
  144. package/dist/{mate-tools.js → lead-tools.js} +109 -101
  145. package/dist/{mate.d.ts → lead.d.ts} +27 -27
  146. package/dist/{mate.js → lead.js} +102 -102
  147. package/dist/ledger-view.d.ts +2 -1
  148. package/dist/ledger-view.js +27 -29
  149. package/dist/limits-ui.d.ts +3 -2
  150. package/dist/limits-ui.js +8 -8
  151. package/dist/live-bus.d.ts +50 -8
  152. package/dist/live-bus.js +133 -50
  153. package/dist/mcp-connect.d.ts +1 -1
  154. package/dist/mcp-connect.js +3 -3
  155. package/dist/mcp-core.js +1 -1
  156. package/dist/mcp-oauth.js +35 -36
  157. package/dist/memory-pass.d.ts +2 -2
  158. package/dist/memory-pass.js +7 -7
  159. package/dist/models-ui.d.ts +2 -1
  160. package/dist/models-ui.js +25 -35
  161. package/dist/monitoring-ui.d.ts +4 -3
  162. package/dist/monitoring-ui.js +13 -26
  163. package/dist/notification-rows.d.ts +2 -2
  164. package/dist/openrouter-models.d.ts +2 -1
  165. package/dist/openrouter-models.js +5 -9
  166. package/dist/operate.d.ts +3 -3
  167. package/dist/operate.js +46 -41
  168. package/dist/people-audit-ui.d.ts +2 -1
  169. package/dist/people-audit-ui.js +13 -13
  170. package/dist/policy-ui.d.ts +3 -2
  171. package/dist/policy-ui.js +17 -23
  172. package/dist/policy.d.ts +1 -1
  173. package/dist/principal.d.ts +1 -1
  174. package/dist/principal.js +2 -2
  175. package/dist/project-delete-ui.d.ts +4 -3
  176. package/dist/project-delete-ui.js +16 -28
  177. package/dist/project-delete.d.ts +1 -1
  178. package/dist/project-delete.js +26 -26
  179. package/dist/project-memory.js +1 -1
  180. package/dist/project-tools.d.ts +1 -1
  181. package/dist/project-tools.js +1 -1
  182. package/dist/pull-request-ui.d.ts +2 -1
  183. package/dist/pull-request-ui.js +17 -32
  184. package/dist/recipe-ui.d.ts +8 -7
  185. package/dist/recipe-ui.js +39 -32
  186. package/dist/release-adapters.d.ts +60 -0
  187. package/dist/release-adapters.js +435 -0
  188. package/dist/release-cli.d.ts +24 -0
  189. package/dist/release-cli.js +181 -0
  190. package/dist/release-gate.d.ts +80 -0
  191. package/dist/release-gate.js +150 -0
  192. package/dist/release-links.d.ts +29 -0
  193. package/dist/release-links.js +102 -0
  194. package/dist/release.d.ts +331 -0
  195. package/dist/release.js +550 -0
  196. package/dist/reply-shape.d.ts +2 -1
  197. package/dist/reply-shape.js +5 -5
  198. package/dist/repository-context-ui.d.ts +2 -1
  199. package/dist/repository-context-ui.js +2 -7
  200. package/dist/request-budget-ui.d.ts +2 -1
  201. package/dist/request-budget-ui.js +16 -16
  202. package/dist/result-review.d.ts +2 -3
  203. package/dist/result-review.js +3 -9
  204. package/dist/retention-ui.d.ts +7 -1
  205. package/dist/retention-ui.js +7 -11
  206. package/dist/retention.js +4 -4
  207. package/dist/scope.d.ts +2 -2
  208. package/dist/scout-report.d.ts +1 -1
  209. package/dist/scout-report.js +1 -1
  210. package/dist/serve.d.ts +11 -2
  211. package/dist/serve.js +105 -99
  212. package/dist/server/chat.d.ts +7 -10
  213. package/dist/server/chat.js +984 -1021
  214. package/dist/server/chrome.d.ts +291 -0
  215. package/dist/server/chrome.js +747 -0
  216. package/dist/server/console.css +2819 -0
  217. package/dist/server/flows.d.ts +2 -6
  218. package/dist/server/flows.js +854 -773
  219. package/dist/server/guards.d.ts +1 -1
  220. package/dist/server/guards.js +8 -5
  221. package/dist/server/handler-context.d.ts +1 -1
  222. package/dist/server/handler-context.js +1 -1
  223. package/dist/server/handler-registry.d.ts +8 -6
  224. package/dist/server/handler-registry.js +14 -2
  225. package/dist/server/http.d.ts +274 -0
  226. package/dist/server/http.js +239 -0
  227. package/dist/server/live.d.ts +34 -0
  228. package/dist/server/live.js +309 -0
  229. package/dist/server/pages.d.ts +2 -6
  230. package/dist/server/pages.js +808 -789
  231. package/dist/server/people-tokens.d.ts +1 -6
  232. package/dist/server/people-tokens.js +581 -573
  233. package/dist/server/remote.d.ts +4 -7
  234. package/dist/server/remote.js +39 -70
  235. package/dist/server/render-chat.d.ts +402 -0
  236. package/dist/server/render-chat.js +927 -0
  237. package/dist/server/render-pages.d.ts +535 -0
  238. package/dist/server/render-pages.js +1277 -0
  239. package/dist/server/render-people.d.ts +32 -0
  240. package/dist/server/render-people.js +121 -0
  241. package/dist/server/render-results.d.ts +744 -0
  242. package/dist/server/render-results.js +2237 -0
  243. package/dist/server/render-settings.d.ts +154 -0
  244. package/dist/server/render-settings.js +580 -0
  245. package/dist/server/render-tasks.d.ts +632 -0
  246. package/dist/server/render-tasks.js +2345 -0
  247. package/dist/server/request-context.d.ts +46 -0
  248. package/dist/server/request-context.js +17 -0
  249. package/dist/server/route-table.d.ts +25 -3
  250. package/dist/server/route-table.js +167 -77
  251. package/dist/server/runtime.d.ts +28 -20
  252. package/dist/server/runtime.js +7 -1
  253. package/dist/server/session.d.ts +138 -0
  254. package/dist/server/session.js +94 -0
  255. package/dist/server/settings.d.ts +1 -5
  256. package/dist/server/settings.js +2186 -2121
  257. package/dist/server/task-views.d.ts +11 -4
  258. package/dist/server/task-views.js +26 -22
  259. package/dist/server/tasks.d.ts +9 -9
  260. package/dist/server/tasks.js +1489 -1420
  261. package/dist/server-telemetry.d.ts +4 -3
  262. package/dist/server-telemetry.js +4 -8
  263. package/dist/setup-guide.d.ts +2 -2
  264. package/dist/skills-ui.d.ts +4 -3
  265. package/dist/skills-ui.js +14 -17
  266. package/dist/skills.d.ts +1 -1
  267. package/dist/skills.js +2 -1
  268. package/dist/slack-chat.d.ts +2 -2
  269. package/dist/slack-chat.js +6 -6
  270. package/dist/slack-settings.d.ts +3 -2
  271. package/dist/slack-settings.js +17 -42
  272. package/dist/spend-ui.d.ts +5 -4
  273. package/dist/spend-ui.js +17 -40
  274. package/dist/spend.d.ts +14 -14
  275. package/dist/spend.js +31 -31
  276. package/dist/sso-ui.d.ts +3 -2
  277. package/dist/sso-ui.js +26 -27
  278. package/dist/storage-ui.d.ts +2 -1
  279. package/dist/storage-ui.js +14 -33
  280. package/dist/store-baseline.d.ts +19 -0
  281. package/dist/store-baseline.js +2873 -0
  282. package/dist/store.d.ts +197 -249
  283. package/dist/store.js +747 -3084
  284. package/dist/subagent-admin.d.ts +53 -0
  285. package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
  286. package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
  287. package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
  288. package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
  289. package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
  290. package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
  291. package/dist/{teammate-question.js → subagent-question.js} +17 -17
  292. package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
  293. package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
  294. package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
  295. package/dist/{teammate-week.js → subagent-week.js} +23 -23
  296. package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
  297. package/dist/{teammate-work.js → subagent-work.js} +50 -50
  298. package/dist/subagents-ui.d.ts +22 -0
  299. package/dist/subagents-ui.js +137 -0
  300. package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
  301. package/dist/{teammates.js → subagents.js} +21 -21
  302. package/dist/subscription-chat.d.ts +13 -13
  303. package/dist/subscription-chat.js +19 -19
  304. package/dist/surface.d.ts +19 -2
  305. package/dist/surface.js +20 -33
  306. package/dist/task-composer.d.ts +2 -1
  307. package/dist/task-composer.js +7 -7
  308. package/dist/task-status.d.ts +4 -3
  309. package/dist/task-status.js +15 -16
  310. package/dist/team-cli.d.ts +1 -0
  311. package/dist/team-cli.js +14 -2
  312. package/dist/team-http.d.ts +5 -9
  313. package/dist/team-http.js +5 -63
  314. package/dist/team-leads.js +13 -13
  315. package/dist/team-runtime.d.ts +2 -2
  316. package/dist/team-runtime.js +8 -8
  317. package/dist/team-ui.d.ts +2 -1
  318. package/dist/team-ui.js +2 -5
  319. package/dist/teams-chat.js +6 -6
  320. package/dist/teams-settings.d.ts +3 -2
  321. package/dist/teams-settings.js +20 -31
  322. package/dist/telegram-flow.d.ts +3 -3
  323. package/dist/telegram-flow.js +3 -3
  324. package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
  325. package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
  326. package/dist/telegram-settings.d.ts +3 -2
  327. package/dist/telegram-settings.js +14 -25
  328. package/dist/telegram-status.js +1 -1
  329. package/dist/telegram-team.d.ts +1 -1
  330. package/dist/telegram-team.js +5 -5
  331. package/dist/telegram.d.ts +1 -1
  332. package/dist/telegram.js +9 -30
  333. package/dist/text-limits.d.ts +18 -18
  334. package/dist/text-limits.js +18 -18
  335. package/dist/toolroll-update-ui.d.ts +3 -2
  336. package/dist/toolroll-update-ui.js +35 -39
  337. package/dist/toolroll-update.d.ts +8 -0
  338. package/dist/toolroll-update.js +45 -8
  339. package/dist/tools-ui.d.ts +5 -2
  340. package/dist/tools-ui.js +31 -31
  341. package/dist/when-html.d.ts +11 -3
  342. package/dist/when-html.js +5 -6
  343. package/dist/workspace-ui.d.ts +4 -2
  344. package/dist/workspace-ui.js +10 -10
  345. package/package.json +1 -2
  346. package/dist/server/shared.d.ts +0 -3151
  347. package/dist/server/shared.js +0 -12001
  348. package/dist/teammate-admin.d.ts +0 -53
  349. package/dist/teammates-ui.d.ts +0 -20
  350. package/dist/teammates-ui.js +0 -166
package/dist/flows-ui.js CHANGED
@@ -12,58 +12,47 @@ import { itemCaption, whyLines } from "./flow-items.js";
12
12
  import { FLOW_FILE_MAX_BYTES } from "./flow-share.js";
13
13
  import { googleConnected } from "./google-mail.js";
14
14
  import { mailboxReady } from "./mailbox.js";
15
- import { labelOf, nameOf } from "./teammate-admin.js";
16
- import { callWords, receiptWords } from "./teammate-tools.js";
17
- import { undoFor } from "./teammate-week.js";
15
+ import { labelOf, nameOf } from "./subagent-admin.js";
16
+ import { callWords, receiptWords } from "./subagent-tools.js";
17
+ import { undoFor } from "./subagent-week.js";
18
18
  import { flowCardTaskLine } from "./flow-card-task.js";
19
19
  import { readFlowSecrets } from "./flow-secrets.js";
20
- const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
20
+ import { html, joinHtml, postForm } from "./html.js";
21
21
  const projectName = (repo) => repo.split(/[\\/]/).filter(Boolean).pop() ?? repo;
22
22
  export const FLOWS_CSS = `.flows{max-width:880px;min-width:0}.flows .card{padding:16px 18px;margin:12px 0}.flows .flow-row{display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap}.flows .flow-row h2{font-size:1.05rem;margin:0}.flows .flow-counts{font-size:.85rem;color:var(--so-muted)}.flows form{display:grid;gap:10px;margin:0}.flows label{display:grid;gap:6px}.flows input,.flows select{box-sizing:border-box;width:100%;max-width:100%}.flows button{justify-self:start;min-height:44px}.flows .flow-fallback ol{padding-left:20px}.flows summary{cursor:pointer;min-height:44px;display:flex;align-items:center;font-weight:600}.flows .flow-buttons{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:10px 0 0}.flows .flow-import{margin-top:18px;padding-top:14px;border-top:1px solid var(--so-line)}.flows .flow-import h3{font-size:.95rem;margin:0}.flows .flow-terms{list-style:none;padding:0;margin:8px 0 16px;display:grid;gap:10px}.flows .flow-terms li{white-space:pre-line;overflow-wrap:anywhere}.flows .flow-terms pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:.8rem;margin:6px 0 0}.flows .flow-import-preview h2{font-size:1rem;margin:20px 0 4px}.flows .flow-untrusted{font-weight:600}.flows button.primary{background:var(--primary);color:var(--primary-foreground);border-color:var(--primary)}.flows fieldset{border:0;padding:0;margin:0;display:grid;gap:10px;min-width:0}.flows legend{font-weight:600;padding:0;margin-bottom:6px}.flows textarea[hidden]{display:none}.flows button.secondary{background:transparent;color:var(--foreground);border:1px solid var(--border)}@media(max-width:600px){.flows input,.flows select{font-size:16px}}@media(max-width:760px){.flows .card{padding:10px 12px;margin:8px 0;position:relative}.flows .flow-row{row-gap:2px}.flows .flow-row h2 a::after{content:"";position:absolute;inset:0;border-radius:inherit}.flows .flow-counts{line-height:1.35}}`;
23
23
  /** The flows a person can open, with how many cards wait in each, and the new-flow form. */
24
- export function flowsListHtml(store, flows, projects, csrf, canCreate, problem) {
24
+ export function flowsListHtml(store, flows, projects, canCreate, problem) {
25
25
  const rows = flows.map(flow => {
26
26
  const cards = store.flowCards(flow.id, false);
27
27
  const definition = flowDefinitionOf(flow);
28
28
  const waiting = cards.filter(card => ["approval", "choose"].includes(definition?.stages.find(one => one.id === card.stage)?.kind ?? "")).length;
29
29
  const buttons = store.flowTriggers(flow.id).filter(one => one.state === "active").flatMap(one => { const config = triggerConfigOf(one); return config?.kind === "button" ? [{ id: one.id, label: config.label }] : []; });
30
30
  const trouble = troubleWords(flowInsights(store, flow, new Date(), 7));
31
- return `<article class="card"><div class="flow-row"><h2><a href="/flows/${flow.id}">${e(flow.name)}</a></h2><span class="flow-counts">${e(projectName(flow.repo))} · ${cards.length} card${cards.length === 1 ? "" : "s"} in progress${waiting > 0 ? ` · ${waiting} waiting for a decision` : ""}${trouble === null ? "" : ` · ${e(trouble)}`}</span></div>` +
32
- (buttons.length === 0 ? "" : `<p class="flow-buttons">${buttons.map(one => `<a class="button-link" href="/flows/${flow.id}?start=${one.id}">${e(one.label)}</a>`).join(" ")}</p>`) + `</article>`;
33
- }).join("");
34
- const projectField = projects.length === 1 ? `<input type="hidden" name="repo" value="${e(projects[0])}">` : `<label>Project<select name="repo">${projects.map(repo => `<option value="${e(repo)}">${e(projectName(repo))}</option>`).join("")}</select></label>`;
31
+ return html `<article class="card"><div class="flow-row"><h2><a href="/flows/${flow.id}">${flow.name}</a></h2><span class="flow-counts">${projectName(flow.repo)} · ${cards.length} card${cards.length === 1 ? "" : "s"} in progress${waiting > 0 ? ` · ${waiting} waiting for a decision` : ""}${trouble === null ? "" : ` · ${trouble}`}</span></div>${buttons.length > 0 && html `<p class="flow-buttons">${joinHtml(buttons.map(one => html `<a class="button-link" href="/flows/${flow.id}?start=${one.id}">${one.label}</a>`), " ")}</p>`}</article>`;
32
+ });
33
+ const projectField = projects.length === 1 ? html `<input type="hidden" name="repo" value="${projects[0]}">` : html `<label>Project<select name="repo">${projects.map(repo => html `<option value="${repo}">${projectName(repo)}</option>`)}</select></label>`;
35
34
  // A flow file (flow-share.ts): chosen here or fetched from a gist, previewed in plain words before anything is made.
36
- const importForm = `<form method="post" action="/flows/import" class="flow-import" data-flow-import><input type="hidden" name="csrf" value="${e(csrf)}"><h3>Or import a flow file</h3>${projectField}<label>File<input type="file" accept=".json,application/json" data-flow-file></label><textarea name="document" hidden data-flow-document></textarea><label>Or its address<input name="url" type="url" inputmode="url" placeholder="https://gist.github.com/…"></label><p class="problem" role="alert" data-flow-file-note hidden></p><button>Preview import</button></form>`;
35
+ const importForm = postForm("/flows/import", html `<h3>Or import a flow file</h3>${projectField}<label>File<input type="file" accept=".json,application/json" data-flow-file></label><textarea name="document" hidden data-flow-document></textarea><label>Or its address<input name="url" type="url" inputmode="url" placeholder="https://gist.github.com/…"></label><p class="problem" role="alert" data-flow-file-note hidden></p><button>Preview import</button>`, { attrs: { class: "flow-import", "data-flow-import": true } });
37
36
  // A new flow starts from the gallery (Flows → New): templates grouped by what they're for.
38
- const create = canCreate && projects.length > 0 ? `<p class="flow-buttons"><a class="button-link" href="/flows/new">New flow</a></p>${importForm}` : "";
37
+ const create = canCreate && projects.length > 0 && html `<p class="flow-buttons"><a class="button-link" href="/flows/new">New flow</a></p>${importForm}`;
39
38
  // No flows yet: one click makes a working example with a sample question in it.
40
39
  const example = flows.length === 0 && canCreate && projects.length > 0
41
- ? `<form method="post" action="/flows/example" class="card flow-example"><input type="hidden" name="csrf" value="${e(csrf)}"><h2>See a flow work</h2><p class="meta">Claude drafts a reply to a sample customer question; you approve it here or in your chat app.</p>${projects.length === 1 ? `<input type="hidden" name="repo" value="${e(projects[0])}">` : `<label>Project<select name="repo">${projects.map(repo => `<option value="${e(repo)}">${e(projectName(repo))}</option>`).join("")}</select></label>`}<button>Try an example</button></form>`
42
- : "";
43
- const intro = `<p class="meta">A flow is your process drawn as zones. Cards move through them: agents do the work, people approve, and the team hears about it.</p>` +
44
- (canCreate && projects.length > 0 ? `<p class="flow-chat">Describe how work should move and your lead drafts the flow for you to confirm, or start from a template. <a href="/chat?draft=${encodeURIComponent("Make a flow for ")}">Describe it in chat</a></p>` : "") +
45
- `<p class="flow-chat">AI teammates can decide and handle cards for you, within rules you write. <a href="/teammates">Teammates</a> · or set one up with its flow in one click: <a href="/kits">Starter kits</a></p>`;
46
- return `<section class="flows">${problem === null ? "" : `<p class="problem" role="alert">${e(problem)}</p>`}${intro}${create}${rows || example || '<p class="meta">No flows yet.</p>'}</section>`;
40
+ ? postForm("/flows/example", html `<h2>See a flow work</h2><p class="meta">Claude drafts a reply to a sample customer question; you approve it here or in your chat app.</p>${projectField}<button>Try an example</button>`, { attrs: { class: "card flow-example" } })
41
+ : null;
42
+ const intro = html `<p class="meta">A flow is your process drawn as zones. Cards move through them: agents do the work, people approve, and the team hears about it.</p>${canCreate && projects.length > 0 && html `<p class="flow-chat">Describe how work should move and your lead drafts the flow for you to confirm, or start from a template. <a href="/chat?draft=${encodeURIComponent("Make a flow for ")}">Describe it in chat</a></p>`}<p class="flow-chat">subagents can decide and handle cards for you, within rules you write. <a href="/settings/lead/subagents">Subagents</a> · or set one up with its flow in one click: <a href="/kits">Starter kits</a></p>`;
43
+ return html `<section class="flows">${problem !== null && html `<p class="problem" role="alert">${problem}</p>`}${intro}${create}${rows.length > 0 ? rows : example ?? html `<p class="meta">No flows yet.</p>`}</section>`;
47
44
  }
48
45
  /** Reads the chosen flow file into the form (the server reads it again, whole, before anything is made). */
49
46
  export const FLOW_IMPORT_SCRIPT = `(function(){var form=document.querySelector('[data-flow-import]');if(!form)return;var input=form.querySelector('[data-flow-file]'),text=form.querySelector('[data-flow-document]'),note=form.querySelector('[data-flow-file-note]'),address=form.querySelector('[name=url]');function say(words){note.textContent=words;note.hidden=words==='';}var reading=null;input.addEventListener('change',function(){text.value='';say('');var file=input.files&&input.files[0];if(!file){reading=null;return;}if(file.size>${FLOW_FILE_MAX_BYTES}){say('That file is too big: a flow file is at most ${FLOW_FILE_MAX_BYTES / 1024} KB.');input.value='';reading=null;return;}reading=file.text().then(function(value){text.value=value;});});form.addEventListener('submit',function(event){if(reading!==null&&text.value===''){event.preventDefault();reading.then(function(){form.requestSubmit();});return;}if(text.value===''&&address.value.trim()===''){event.preventDefault();say('Choose a flow file or give its address.');}});})();`;
50
47
  /** An import's preview: the plan in plain words, what it asks for, and one Import button after the terms. */
51
48
  export function flowImportHtml(input) {
52
49
  const { plan, file } = input;
53
- const fields = file.parameters.map(one => `<label><span>${e(one.about)}${one.optional === true ? ' <span class="meta">(optional)</span>' : ""}</span><input name="param.${e(one.id)}" value="${e(input.values[one.id] ?? plan?.values[one.id] ?? one.default ?? "")}" maxlength="200" autocomplete="off"></label>`).join("");
54
- const list = (items) => items.length === 0 ? "" : `<ul class="flow-terms">${items.map(term => `<li>${e(term)}</li>`).join("")}</ul>`;
50
+ const fields = file.parameters.map(one => html `<label><span>${one.about}${one.optional === true && html ` <span class="meta">(optional)</span>`}</span><input name="param.${one.id}" value="${input.values[one.id] ?? plan?.values[one.id] ?? one.default ?? ""}" maxlength="200" autocomplete="off"></label>`);
51
+ const list = (items) => items.length === 0 ? "" : html `<ul class="flow-terms">${items.map(term => html `<li>${term}</li>`)}</ul>`;
55
52
  // The steps, what they mean, the triggers and the scripts; each script's code one tap away, as on the Scripts panel.
56
- const terms = plan === null ? "" : (plan.parts.about === null ? "" : `<p>${e(plan.parts.about)}</p>`) +
57
- `<p class="flow-untrusted">Its instructions come from the file, not from you: read what each step is asked before you import.</p>` +
58
- list(plan.parts.steps) +
59
- (plan.parts.triggers.length === 0 ? "" : `<h2>Triggers</h2>${list(plan.parts.triggers)}`) +
60
- (plan.parts.scripts.length + plan.parts.kept.length === 0 ? "" : `<h2>Scripts</h2><ul class="flow-terms">${plan.scripts.add.map((one, index) => `<li>${e(plan.parts.scripts[index])}${one.body === undefined ? "" : `<details><summary>Show the script</summary><pre>${e(one.body)}</pre></details>`}</li>`).join("")}${plan.parts.kept.map(one => `<li>${e(one)}</li>`).join("")}</ul>`) +
61
- `<h2>Good to know</h2>${list([...plan.parts.notes, ...(plan.parts.needs === null ? [] : [plan.parts.needs])])}`;
62
- return `<section class="flows flow-import-preview">${input.problem === null ? "" : `<p class="problem" role="alert">${e(input.problem)}</p>`}` +
63
- `<p class="meta">Into ${e(projectName(input.repo))}. Nothing is made until you import it.</p>${terms}` +
64
- `<form method="post" action="/flows/import" class="card"><input type="hidden" name="csrf" value="${e(input.csrf)}"><input type="hidden" name="repo" value="${e(input.repo)}"><input type="hidden" name="previewed" value="${e(input.previewed)}"><textarea name="document" hidden>${e(input.document)}</textarea>` +
65
- (fields === "" ? "" : `<fieldset class="flow-import-fields"><legend>Fill in</legend>${fields}</fieldset>`) +
66
- `<p class="flow-buttons">${plan === null ? "" : '<button name="confirm" value="yes" class="primary">Import flow</button>'}${fields === "" ? "" : `<button name="confirm" value="" class="${plan === null ? "" : "secondary"}">Update preview</button>`}<a href="/flows">Cancel</a></p></form></section>`;
53
+ const terms = plan === null ? "" : html `${plan.parts.about !== null && html `<p>${plan.parts.about}</p>`}<p class="flow-untrusted">Its instructions come from the file, not from you: read what each step is asked before you import.</p>${list(plan.parts.steps)}${plan.parts.triggers.length > 0 && html `<h2>Triggers</h2>${list(plan.parts.triggers)}`}${plan.parts.scripts.length + plan.parts.kept.length > 0 && html `<h2>Scripts</h2><ul class="flow-terms">${plan.scripts.add.map((one, index) => html `<li>${plan.parts.scripts[index]}${one.body !== undefined && html `<details><summary>Show the script</summary><pre>${one.body}</pre></details>`}</li>`)}${plan.parts.kept.map(one => html `<li>${one}</li>`)}</ul>`}<h2>Good to know</h2>${list([...plan.parts.notes, ...(plan.parts.needs === null ? [] : [plan.parts.needs])])}`;
54
+ const form = postForm("/flows/import", html `<textarea name="document" hidden>${input.document}</textarea>${fields.length > 0 && html `<fieldset class="flow-import-fields"><legend>Fill in</legend>${fields}</fieldset>`}<p class="flow-buttons">${plan !== null && html `<button name="confirm" value="yes" class="primary">Import flow</button>`}${fields.length > 0 && html `<button name="confirm" value="" class="${plan === null ? "" : "secondary"}">Update preview</button>`}<a href="/flows">Cancel</a></p>`, { attrs: { class: "card" }, hidden: { repo: input.repo, previewed: input.previewed } });
55
+ return html `<section class="flows flow-import-preview">${input.problem !== null && html `<p class="problem" role="alert">${input.problem}</p>`}<p class="meta">Into ${projectName(input.repo)}. Nothing is made until you import it.</p>${terms}${form}</section>`;
67
56
  }
68
57
  const historyText = (event, title, sorts = new Set()) => {
69
58
  const who = event.actor === "flow" ? "" : ` by ${event.actor}`;
@@ -86,7 +75,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
86
75
  const title = (id) => stages.find(one => one.id === id)?.title ?? id;
87
76
  const sortZones = new Set(stages.filter(one => one.kind === "sort").map(one => one.id));
88
77
  const decisions = sortZones.size === 0 ? new Map() : store.flowSortDecisions(flow.id);
89
- const mates = new Map(store.teammates([flow.repo]).map(one => [one.id, one]));
78
+ const mates = new Map(store.subagents([flow.repo]).map(one => [one.id, one]));
90
79
  const taskSecrets = new Map();
91
80
  const secretsFor = (repo) => {
92
81
  if (!taskSecrets.has(repo))
@@ -103,9 +92,9 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
103
92
  const discussion = store.flowComments(card.id);
104
93
  const watchers = store.flowCardWatchers(card.id);
105
94
  const decider = stage?.kind === "approval" ? deciderOf(stage, flow) : null;
106
- // While its teammate is deciding (v92), the card isn't anyone else's to decide yet.
107
- const teammateDeciding = stage?.teammate !== undefined && card.waiting !== null && card.waiting.endsWith(" is deciding");
108
- const canDecide = viewer.approver && card.state === "active" && stage?.kind === "approval" && (decider === null || decider === viewer.name) && !teammateDeciding;
95
+ // While its subagent is deciding (v92), the card isn't anyone else's to decide yet.
96
+ const subagentDeciding = stage?.subagent !== undefined && card.waiting !== null && card.waiting.endsWith(" is deciding");
97
+ const canDecide = viewer.approver && card.state === "active" && stage?.kind === "approval" && (decider === null || decider === viewer.name) && !subagentDeciding;
109
98
  // History reads moves and ownership together, newest first.
110
99
  const owned = discussion.filter(one => one.kind === "owner").map(one => ({ text: one.body === "" ? `${one.author} left it without an owner` : one.body === one.author ? `${one.author} took it on` : `${one.author} made ${one.body} the owner`, at: one.at }));
111
100
  const moves = store.flowEvents(card.id).map(event => ({ text: historyText(event, title, sortZones), at: event.at }));
@@ -120,15 +109,15 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
120
109
  label: stage.wait !== undefined && stage.wait.for !== "hours" ? (stage.wait.for === "reply" ? "No reply by" : "Moves on at") : stage.limit.to !== null ? `Moves to ${title(stage.limit.to)} at` : "Reminder at",
121
110
  };
122
111
  // Its open question: one of its own, or (v94) a tool call waiting for approval.
123
- const asked = card.state === "active" ? store.openTeammateQuestionOn(card.id, card.entry) : null;
124
- const pending = asked?.toolCall == null ? null : store.teammateCall(asked.toolCall);
125
- // A decision its teammate handed over carries what the teammate said (v92).
126
- const turn = card.state === "active" && stage?.kind === "approval" && stage.teammate !== undefined ? store.flowStepRun(card.id, card.entry) : null;
112
+ const asked = card.state === "active" ? store.openSubagentQuestionOn(card.id, card.entry) : null;
113
+ const pending = asked?.toolCall == null ? null : store.subagentCall(asked.toolCall);
114
+ // A decision its subagent handed over carries what the subagent said (v92).
115
+ const turn = card.state === "active" && stage?.kind === "approval" && stage.subagent !== undefined ? store.flowStepRun(card.id, card.entry) : null;
127
116
  let handoff = null;
128
- if (turn !== null && turn.kind === "teammate" && turn.state === "passed") {
117
+ if (turn !== null && turn.kind === "subagent" && turn.state === "passed") {
129
118
  try {
130
119
  const said = JSON.parse(turn.decisionJson ?? "{}");
131
- const mate = store.teammateByHandle(flow.repo, stage.teammate);
120
+ const mate = store.subagentByHandle(flow.repo, stage.subagent);
132
121
  if (said.action === "hand_off" && typeof said.note === "string" && said.note !== "" && mate !== null)
133
122
  handoff = { from: labelOf(mate), note: said.note };
134
123
  }
@@ -136,7 +125,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
136
125
  handoff = null;
137
126
  }
138
127
  }
139
- const asker = asked === null || asked.state !== "open" ? null : store.getTeammate(asked.teammate);
128
+ const asker = asked === null || asked.state !== "open" ? null : store.getSubagent(asked.subagent);
140
129
  // "Person chooses" (flow-send.ts): what was sent, and the options, for the person it waits on; the latest send otherwise.
141
130
  const choosing = card.state === "active" && stage?.kind === "choose" ? flowChoiceAt(store, card.id, card.entry) : null;
142
131
  const latest = store.flowSends(card.id)[0] ?? null;
@@ -173,11 +162,11 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
173
162
  question: asked === null || asked.state !== "open" || asker === null ? null
174
163
  : { id: asked.id, from: labelOf(asker), question: asked.question, options: asked.options, askedOf: asked.askedOf, mine: asked.askedOf === viewer.name,
175
164
  call: pending === null ? null : { why: pending.why, rule: pending.result ?? "" } },
176
- calls: store.teammateCallsOn(card.id).slice(-30).map(call => {
177
- const mate = mates.get(call.teammate) ?? null;
165
+ calls: store.subagentCallsOn(card.id).slice(-30).map(call => {
166
+ const mate = mates.get(call.subagent) ?? null;
178
167
  // A person's undo (v97) reads as theirs, and its outcome says what it undid.
179
- return { id: call.id, who: call.undoOf !== null ? call.decidedBy ?? "A person" : mate === null ? "A teammate" : nameOf(mate), words: callWords(call.tool, call.action, call.input, 400), state: call.state, outcome: receiptWords(store, call),
180
- why: call.undoOf !== null ? "" : call.why, result: call.state === "asked" ? null : call.result, at: call.createdAt, teammate: call.teammate, undo: viewer.approver ? undoFor(store, call) : null };
168
+ return { id: call.id, who: call.undoOf !== null ? call.decidedBy ?? "A person" : mate === null ? "A subagent" : nameOf(mate), words: callWords(call.tool, call.action, call.input, 400), state: call.state, outcome: receiptWords(store, call),
169
+ why: call.undoOf !== null ? "" : call.why, result: call.state === "asked" ? null : call.result, at: call.createdAt, subagent: call.subagent, undo: viewer.approver ? undoFor(store, call) : null };
181
170
  }),
182
171
  };
183
172
  });
@@ -208,7 +197,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
208
197
  startTrigger: setup.startTrigger ?? null,
209
198
  me: viewer.name,
210
199
  sortReady: setup.sortReady ?? false,
211
- teammates: store.teammates([flow.repo]).map(mate => ({ handle: mate.handle, label: labelOf(mate), name: nameOf(mate), working: mate.state === "active", href: `/teammates/${mate.id}` })),
200
+ subagents: store.subagents([flow.repo]).map(mate => ({ handle: mate.handle, label: labelOf(mate), name: nameOf(mate), working: mate.state === "active", href: `/settings/lead/subagents/${mate.id}` })),
212
201
  emailReady: sendingReady(setup.dir),
213
202
  requestSecrets: flowSecretNames(setup.dir, flow.repo),
214
203
  tools: projectToolsOf(store, flow.repo).map(tool => ({ name: tool.name, about: tool.spec.about, functions: tool.lastTest?.ok === true ? tool.lastTest.tools : [],
@@ -230,8 +219,8 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
230
219
  }
231
220
  /** The page the canvas replaces when scripts can't run: each zone and its cards, in order. */
232
221
  export function flowFallbackHtml(view) {
233
- return `<section class="flows flow-fallback"><p class="meta">${e(view.flow.project)}</p><ol>${view.stages.map(stage => {
222
+ return html `<section class="flows flow-fallback"><p class="meta">${view.flow.project}</p><ol>${view.stages.map(stage => {
234
223
  const cards = view.cards.filter(card => card.stage === stage.id && card.state === "active");
235
- return `<li><strong>${e(stage.title)}</strong> · ${e(FLOW_KIND_WORDS[stage.kind].label)}${cards.length === 0 ? "" : `<ul>${cards.map(card => `<li>${e(card.title)}${card.waiting === null ? "" : ` — ${e(card.waiting)}`}</li>`).join("")}</ul>`}</li>`;
236
- }).join("")}</ol></section>`;
224
+ return html `<li><strong>${stage.title}</strong> · ${FLOW_KIND_WORDS[stage.kind].label}${cards.length > 0 && html `<ul>${cards.map(card => html `<li>${card.title}${card.waiting !== null && ` — ${card.waiting}`}</li>`)}</ul>`}</li>`;
225
+ })}</ol></section>`;
237
226
  }
package/dist/flows.js CHANGED
@@ -49,9 +49,10 @@
49
49
  */
50
50
  import { TEXT_LIMITS } from "./text-limits.js";
51
51
  import { createHash } from "node:crypto";
52
+ import { projectName } from "./project.js";
52
53
  import { parseContract, readVersioned } from "./contracts/contract.js";
53
54
  import { stageReferenceProblems } from "./contracts/stage-output.js";
54
- import { CHOICES_MAX, CHOICES_MIN, FLOW_ALIASES, FLOW_COLORS, FLOW_DEFINITION_VERSION, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, flowDefinitionSchema, flowStepsSchema, HEADERS_MAX, LONGEST_WAIT_MINUTES, savedFlowDefinitionSchema, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf, SURE_AT_MAX, SURE_AT_MIN, ZONE_ID, } from "./contracts/flow.js";
55
+ import { CHOICES_MAX, CHOICES_MIN, FLOW_ALIASES, FLOW_COLORS, FLOW_DEFINITION_VERSION, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, flowDefinitionSchema, flowStepsSchema, HEADERS_MAX, legacySubagentStep, LONGEST_WAIT_MINUTES, savedFlowDefinitionSchema, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf, SURE_AT_MAX, SURE_AT_MIN, ZONE_ID, } from "./contracts/flow.js";
55
56
  export { CHOICES_MAX, CHOICES_MIN, FLOW_COLORS, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, LONGEST_WAIT_MINUTES, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf };
56
57
  /** What each kind is called and does, in the words the canvas uses. */
57
58
  export const FLOW_KIND_WORDS = {
@@ -69,7 +70,7 @@ export const FLOW_KIND_WORDS = {
69
70
  draft: { label: "Draft", about: "Claude writes a reply, summary or note from the card in seconds. Nothing is sent until a later step sends it." },
70
71
  sort: { label: "Sort", about: "Jev reads the card in under a second and sends it where its answer leads. Cards it isn't sure about take the not-sure path." },
71
72
  wait: { label: "Wait", about: "Waits for a reply to the card's email, or for a set time. A reply moves the card on; if none comes in time, it takes the no-reply path." },
72
- teammate: { label: "Teammate handles it", about: "An AI teammate reads the card, decides where it goes within its rules, and writes what the next zones send. When its rules say to ask, it asks you first." },
73
+ subagent: { label: "Subagent handles it", about: "A subagent reads the card, decides where it goes within its rules, and writes what the next zones send. When its rules say to ask, it asks you first." },
73
74
  send: { label: "Send to me", about: "Sends the card's owner what the step before produced: its summary, links and any screenshots, in each chat app they use and on the card. Then moves on." },
74
75
  choose: { label: "Person chooses", about: "Sends the card's owner what was done with 2 to 4 buttons you name, in their chat apps and here. Each leads to a zone or ignores the card; a reply instead goes where replies go, as the note." },
75
76
  done: { label: "Done", about: "The end of the flow." },
@@ -169,7 +170,7 @@ function canonicalSort(input) {
169
170
  sureAt, notes,
170
171
  };
171
172
  }
172
- /** A script's or teammate's answers as every release has kept them: trimmed, none left out. */
173
+ /** A script's or subagent's answers as every release has kept them: trimmed, none left out. */
173
174
  function canonicalRoutes(input) {
174
175
  if (!given(input))
175
176
  return undefined;
@@ -199,7 +200,7 @@ function canonicalWait(input) {
199
200
  function canonicalStage(input, index) {
200
201
  if (!isRecord(input))
201
202
  return input;
202
- const stage = input;
203
+ const stage = legacySubagentStep(input);
203
204
  const kind = stage["kind"];
204
205
  const zone = isRecord(stage["zone"]) ? stage["zone"] : {};
205
206
  const planning = stage["planning"] === "required" || stage["planning"] === "skip" || stage["planning"] === "auto" ? stage["planning"] : null;
@@ -230,9 +231,9 @@ function canonicalStage(input, index) {
230
231
  ...(kind === "wait" ? { wait: canonicalWait(stage["wait"]) } : {}),
231
232
  // true is a squash, the default; false or none is no merge.
232
233
  ...(kind === "pull-request" && given(stage["merge"]) && stage["merge"] !== false ? { merge: stage["merge"] === true ? "squash" : stage["merge"] } : {}),
233
- ...((kind === "approval" || kind === "teammate") && given(stage["teammate"]) && stage["teammate"] !== "" ? { teammate: stage["teammate"] } : {}),
234
- ...(kind === "teammate" && routes !== undefined ? { routes } : {}),
235
- ...(kind === "teammate" && stage["reply"] === true ? { reply: true } : {}),
234
+ ...((kind === "approval" || kind === "subagent") && given(stage["subagent"]) && stage["subagent"] !== "" ? { subagent: stage["subagent"] } : {}),
235
+ ...(kind === "subagent" && routes !== undefined ? { routes } : {}),
236
+ ...(kind === "subagent" && stage["reply"] === true ? { reply: true } : {}),
236
237
  ...(kind === "choose" ? { options: Array.isArray(stage["options"]) ? stage["options"].map(one => { const row = isRecord(one) ? one : {}; return { label: said(row["label"]), to: said(row["to"]) }; }) : given(stage["options"]) ? stage["options"] : [] } : {}),
237
238
  ...(kind === "task" && given(stage["repo"]) && stage["repo"] !== "" ? { repo: said(stage["repo"]) } : {}),
238
239
  ...(limit === undefined ? {} : { limit }),
@@ -564,7 +565,7 @@ function fillOne(key, stage, card) {
564
565
  return stage === undefined ? "" : card.outputs[stage] ?? "";
565
566
  }
566
567
  }
567
- const KIND_COLORS = { inbox: "slate", task: "blue", report: "violet", approval: "amber", check: "blue", "pull-request": "blue", update: "green", notify: "green", sort: "violet", draft: "violet", request: "blue", email: "green", tool: "blue", wait: "slate", teammate: "violet", send: "green", choose: "amber", done: "green" };
568
+ const KIND_COLORS = { inbox: "slate", task: "blue", report: "violet", approval: "amber", check: "blue", "pull-request": "blue", update: "green", notify: "green", sort: "violet", draft: "violet", request: "blue", email: "green", tool: "blue", wait: "slate", subagent: "violet", send: "green", choose: "amber", done: "green" };
568
569
  /** A step id as the lead may write it (sort_by_hand, Sort-By-Hand) in the one form zones use. */
569
570
  const idOf = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
570
571
  const slugOf = (title) => title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 28) || "zone";
@@ -631,7 +632,7 @@ export function withKeptSteps(steps, previous) {
631
632
  }
632
633
  /** A flow's steps, read by their one schema: `steps[0].routes[0]: unknown key 'to' (did you mean goesTo?)`. */
633
634
  export function readFlowSteps(input, previous = null) {
634
- const read = parseContract(flowStepsSchema, { steps: withKeptSteps(input, previous) }, FLOW_ALIASES);
635
+ const read = parseContract(flowStepsSchema, { steps: withKeptSteps(Array.isArray(input) ? input.map(legacySubagentStep) : input, previous) }, FLOW_ALIASES);
635
636
  return read.ok ? { ok: true, value: read.value.steps } : read;
636
637
  }
637
638
  /**
@@ -706,7 +707,7 @@ export function flowFromSteps(input, previous = null) {
706
707
  id, title, kind,
707
708
  zone: old?.zone ?? { x: 0, y: 0, w: 260, h: kind === "done" || kind === "notify" || kind === "send" ? 220 : 300, color: KIND_COLORS[kind] },
708
709
  // A kept step on our default instructions gets the default again, so it reads any research step added before it.
709
- instructions: kind === "teammate" ? step.instructions?.trim() || old?.instructions || "Read the card and decide what happens next."
710
+ instructions: kind === "subagent" ? step.instructions?.trim() || old?.instructions || "Read the card and decide what happens next."
710
711
  : kind === "draft" ? step.instructions?.trim() || old?.instructions || DRAFT_DEFAULT
711
712
  : kind === "task" || kind === "report"
712
713
  ? step.instructions?.trim() || (old?.instructions && old.instructions !== defaultInstructions(kind, previous.stages.slice(0, previous.stages.indexOf(old))) ? old.instructions : defaultInstructions(kind, earlier))
@@ -730,10 +731,10 @@ export function flowFromSteps(input, previous = null) {
730
731
  ...(kind === "tool" ? { tool: { server: step.server ?? old?.tool?.server ?? "", name: step.tool ?? old?.tool?.name ?? "", args: typeof step.args === "string" ? step.args : step.args !== undefined ? JSON.stringify(step.args) : old?.tool?.args ?? "{}" } } : {}),
731
732
  ...(kind === "wait" ? { wait: waitFromStep(step, old?.wait ?? null, at) } : {}),
732
733
  ...(kind === "pull-request" && (step.merge === undefined ? old?.merge !== undefined : step.merge !== false) ? { merge: step.merge === undefined || step.merge === true || step.merge === false ? old?.merge ?? "squash" : step.merge } : {}),
733
- // v92: "nobody" (or "none") takes a teammate off an approval step.
734
- ...((kind === "approval" || kind === "teammate") && (step.teammate === undefined ? old?.teammate !== undefined : !/^(nobody|none|no one)$/i.test(step.teammate.trim())) ? { teammate: idOf(step.teammate ?? old.teammate) } : {}),
735
- ...(kind === "teammate" ? step.routes !== undefined ? routesOf(step.routes) : old?.routes === undefined ? {} : { routes: old.routes } : {}),
736
- ...(kind === "teammate" && (step.reply ?? old?.reply) === true ? { reply: true } : {}),
734
+ // v92: "nobody" (or "none") takes a subagent off an approval step.
735
+ ...((kind === "approval" || kind === "subagent") && (step.subagent === undefined ? old?.subagent !== undefined : !/^(nobody|none|no one)$/i.test(step.subagent.trim())) ? { subagent: idOf(step.subagent ?? old.subagent) } : {}),
736
+ ...(kind === "subagent" ? step.routes !== undefined ? routesOf(step.routes) : old?.routes === undefined ? {} : { routes: old.routes } : {}),
737
+ ...(kind === "subagent" && (step.reply ?? old?.reply) === true ? { reply: true } : {}),
737
738
  ...(kind === "choose" ? { options: choicesFromStep(step, old?.options ?? null, at, find) } : {}),
738
739
  ...(kind === "task" && (step.repo ?? old?.repo) !== undefined && (step.repo ?? old?.repo).trim() !== "" ? { repo: (step.repo ?? old?.repo).trim() } : {}),
739
740
  ...(() => {
@@ -869,7 +870,7 @@ export function flowTerms(definition, previous) {
869
870
  if (stage.kind === "task" && stage.planning !== "auto")
870
871
  lines.push(stage.planning === "required" ? "Plans first." : "Builds without a plan.");
871
872
  if (stage.kind === "task" && stage.repo !== undefined)
872
- lines.push(`Builds in another project: ${stage.repo}`);
873
+ lines.push(`Builds in project: ${projectName(stage.repo)}`);
873
874
  if (stage.kind === "draft")
874
875
  lines.push(`Claude writes: ${plain(stage.instructions ?? "")}`, `Then → ${to(stage.next)}`);
875
876
  else if (stage.kind === "request" && stage.request !== undefined) {
@@ -881,9 +882,9 @@ export function flowTerms(definition, previous) {
881
882
  else if (stage.kind === "tool" && stage.tool !== undefined)
882
883
  lines.push(`Uses ${stage.tool.server} → ${stage.tool.name} with ${plain(stage.tool.args).slice(0, 400)}`, `Then → ${to(stage.next)}${stage.onFail === null ? " If it fails → waits there." : ` If it fails → ${to(stage.onFail)}`}`);
883
884
  else if (stage.kind === "approval")
884
- lines.push(`Decides: ${stage.teammate !== undefined ? `the AI teammate ${stage.teammate}, within its rules, handing hard ones to ${stage.toOwner === true ? "the flow's owner" : stage.approver ?? "anyone who approves"}` : stage.toOwner === true ? "the flow's owner, in their chat app" : stage.approver ?? "anyone who approves on this project"}. Approve → ${to(stage.next)} Send back → ${stage.onFail === null ? "not possible." : to(stage.onFail)}`);
885
- else if (stage.kind === "teammate")
886
- lines.push(`The AI teammate ${stage.teammate ?? "(none)"} handles it${stage.instructions === null ? "" : `: ${plain(stage.instructions)}`}`, ...(stage.routes === undefined || stage.routes.length === 0 ? [`Then → ${to(stage.next)}`] : stage.routes.map(one => `${one.answer} → ${to(one.to)}`)), `It asks the flow's owner when its rules say to.${stage.reply === true ? " It sends what it writes back to whoever asked." : ""}${stage.onFail === null ? "" : ` If it can't → ${to(stage.onFail)}`}`);
885
+ lines.push(`Decides: ${stage.subagent !== undefined ? `the subagent ${stage.subagent}, within its rules, handing hard ones to ${stage.toOwner === true ? "the flow's owner" : stage.approver ?? "anyone who approves"}` : stage.toOwner === true ? "the flow's owner, in their chat app" : stage.approver ?? "anyone who approves on this project"}. Approve → ${to(stage.next)} Send back → ${stage.onFail === null ? "not possible." : to(stage.onFail)}`);
886
+ else if (stage.kind === "subagent")
887
+ lines.push(`The subagent ${stage.subagent ?? "(none)"} handles it${stage.instructions === null ? "" : `: ${plain(stage.instructions)}`}`, ...(stage.routes === undefined || stage.routes.length === 0 ? [`Then → ${to(stage.next)}`] : stage.routes.map(one => `${one.answer} → ${to(one.to)}`)), `It asks the flow's owner when its rules say to.${stage.reply === true ? " It sends what it writes back to whoever asked." : ""}${stage.onFail === null ? "" : ` If it can't → ${to(stage.onFail)}`}`);
887
888
  else if (stage.kind === "notify")
888
889
  lines.push(`Posts: ${plain(stage.message ?? "")}`, `Then → ${to(stage.next)}`);
889
890
  else if (stage.kind === "send")
@@ -944,8 +945,8 @@ export function flowTerms(definition, previous) {
944
945
  terms.push("Draft steps send each card's text to Claude through the lead chat's sign-in. Nothing a draft writes is sent until a later step sends it.");
945
946
  if (definition.stages.some(one => one.kind === "task" && one.repo !== undefined))
946
947
  terms.push("A build step in another project files its task there, only when the flow's owner may file work in that project.");
947
- if (definition.stages.some(one => one.teammate !== undefined))
948
- terms.push("AI teammates decide and act within their soul files' rules, reading each card through Claude on this computer's sign-in; they never approve code tasks or merges.");
948
+ if (definition.stages.some(one => one.subagent !== undefined))
949
+ terms.push("subagents decide and act within their soul files' rules, reading each card through Claude on this computer's sign-in; they never approve code tasks or merges.");
949
950
  return terms;
950
951
  }
951
952
  const zone = (x, y, color, h = 300) => ({ x, y, w: 260, h, color });
package/dist/guides.js CHANGED
@@ -65,7 +65,7 @@ ${AUTHORITY_LINE}
65
65
  terminal, answer from the \`console\` guide (\`skills get console\`):
66
66
  exact screens, exact verbs, and which acts are theirs alone.
67
67
  - Take work as a registered runner: \`claim <id> --runner <name> --token
68
- <t>\` → \`heartbeat <lease>\` while working → \`release <lease>\`. A
68
+ <t>\` → \`heartbeat <lease>\` while working → \`worker release <lease>\`. A
69
69
  replayed claim (same \`--key\`) answers with \`replayed: true\` — do not
70
70
  repeat first-time side effects on it.
71
71
  - Respect refusals — each is an ANSWER, never an error to retry blindly:
@@ -189,7 +189,7 @@ registers it; the token is shown once). Work moves through leases:
189
189
  not repeat first-time side effects.
190
190
  - \`heartbeat <lease>\` while working — it extends the lease. A lease
191
191
  that runs out is reaped and the task returns to the queue.
192
- - \`release <lease>\` when done. If the answer is \`fenced\`, STOP: a newer
192
+ - \`worker release <lease>\` when done. If the answer is \`fenced\`, STOP: a newer
193
193
  lease superseded yours and the work is no longer yours — do not write,
194
194
  commit, or report anything further for it.
195
195
  - \`reap\` releases every expired lease (safe to run any time).
@@ -318,7 +318,7 @@ ${AUTHORITY_LINE}
318
318
  transcripts following.
319
319
  - **projects** (\`/projects\`): enrol, open, or switch repositories; the
320
320
  project name in the header is the one-tap switcher on every screen.
321
- - **chat** (\`/chat\`): the mate — a conversation across every project
321
+ - **chat** (\`/chat\`): the lead — a conversation across every project
322
322
  that only PROPOSES (file, reorder, reserve, hold, rewrite a scope,
323
323
  cancel, answer a decision); each proposal is a card the person
324
324
  confirms. Coordinators over the MCP gateway propose the same way, and
@@ -340,7 +340,7 @@ ${AUTHORITY_LINE}
340
340
  - \`toolroll peek\`: one pane per live agent (digits focus, \`q\`
341
341
  leaves); \`peek <run>\` follows one; \`peek --tmux\` opens a tmux window
342
342
  per run.
343
- - \`toolroll chat\`: the mate in the terminal (\`--say "…"\` for one
343
+ - \`toolroll chat\`: the lead in the terminal (\`--say "…"\` for one
344
344
  turn); \`toolroll proposals\` lists and confirms coordinator
345
345
  proposals.
346
346
  - \`toolroll task add "<title>" --report\`: a SCOUT task — a
@@ -359,7 +359,8 @@ needs you · queued · waiting · building · done · failed; a *scope* is the
359
359
  contract a person approves; a *decision* is a typed question an agent
360
360
  parked; a *hold* pauses the next attempt; a *stale approval* means the
361
361
  approval no longer matches how builds are routed — approving again fixes
362
- it; a *scout* delivers a report; the *mate* proposes and never acts.
362
+ it; a *scout* delivers a report; the *lead* proposes and never acts, and
363
+ its *subagents* work within the rules you write for them.
363
364
  `,
364
365
  };
365
366
  const knowledge = {
@@ -416,7 +417,112 @@ and crew agent reads from it before working:
416
417
  and its date, and prefer the newest active decision.
417
418
  `,
418
419
  };
419
- export const GUIDES = [operating, runner, steering, externalWork, console_, knowledge];
420
+ const release = {
421
+ name: "release",
422
+ title: "Releasing Toolroll itself",
423
+ oneLiner: "toolroll release <branch>: one command from a gated commit to npm and Homebrew; the owner's approval is the only human step",
424
+ content: `# Releasing Toolroll itself
425
+
426
+ ${AUTHORITY_LINE}
427
+
428
+ \`toolroll release <branch> --repo <gate checkout>\` runs the whole release
429
+ of a pushed branch. The owner approving the release scope is the only
430
+ step that needs a person; the rest runs on its own, each waiting step
431
+ with a time limit.
432
+
433
+ ## Before you start
434
+
435
+ - The branch is pushed, with an open pull request into main.
436
+ - package.json has the new version, and the lead wrote its CHANGELOG.md
437
+ entry (\`## <version>\`). No tag or npm package has that version yet.
438
+ - \`gh\` is signed in, the console, worker and both CLI names
439
+ (\`toolroll\`, \`standing-orders\`) run one build, and the gate checkout
440
+ is a clone whose origin is the GitHub repository. Credentials are never
441
+ asked for in chat or written to the release journal.
442
+
443
+ ## What it does, in order
444
+
445
+ 1. **gate**: files task \`release-<version>-<sha7>\` pinned to the
446
+ branch's exact commit with \`--checks full\`, regardless of the project's default.
447
+ 2. **approval**: waits for the owner to approve that exact scope
448
+ (\`toolroll task approve <id> --digest <d>\`, or the console). A
449
+ mode, an AI teammate or the lead approving it stops the release: the
450
+ yes must come from an active account with the approver role. Its standing
451
+ is checked again before completion and deployment.
452
+ 3. **check**: requires a sealed, passing **Full** check on that commit.
453
+ Quick passes and Off checks are refused. Failures stay visible; nothing reruns them.
454
+ 4. **complete**: marks that exact result Complete.
455
+ 5. **quiet**: waits until nothing is building.
456
+ 6. **deploy**: the candidate's own deployment
457
+ (\`scripts/deploy-browser.mjs\` from the builder's checkout): drain,
458
+ backup, rehearsal, swap, health. The staging path is saved before it
459
+ starts, and the runtime its final JSON names is kept. It goes on only
460
+ when the console and worker run that runtime and it records the gated
461
+ commit; matching versions alone do not count.
462
+ 7. **link**: points \`toolroll\` and \`standing-orders\` at
463
+ \`<runtime>/bin.js\`, each link replaced whole. Where they pointed is
464
+ saved first; if either name doesn't answer as the new build, both go
465
+ back. It goes on only when the console, worker and both names run the
466
+ gated commit. A rerun relinks without deploying again.
467
+ 8. **ci**: waits for every pull-request check on the gated commit:
468
+ Ubuntu on Node 22 and 24, Linux and Windows containment. Pending,
469
+ missing, skipped, cancelled or failed never merges.
470
+ 9. **merge**: squash-merges with the head pinned to the gated commit,
471
+ once main has nothing the branch lacks, then deletes the remote branch
472
+ only if its head still matches. A resumed merge also finishes this cleanup.
473
+ 10. **tree**: main's merge commit must have exactly the gated tree, or
474
+ nothing is tagged.
475
+ 11. **tag**: pushes \`v<version>\` on that merge commit. In publish.yml,
476
+ macOS Node 22 and 24 must both pass before npm and GitHub publication.
477
+ macos.yml keeps the nightly matrix.
478
+ 12. **publish**: waits for npm, the GitHub release and the tag's macOS
479
+ and publish checks.
480
+ 13. **homebrew**: updates the tap's formula, waits for checks to register
481
+ and every registered check to pass, then merges. No checks means waiting,
482
+ within the Homebrew time limit; a failed or skipped check stops the release.
483
+
484
+ ## When it stops
485
+
486
+ A time limit, a failure or Ctrl-C ends with the step and the reason,
487
+ and the command to continue: the same \`toolroll release <branch> --repo
488
+ <checkout>\`. Its journal (\`releases/<branch>.json\` in the state folder,
489
+ private) records what each step was about to do before it does it, so a
490
+ rerun finds the task, completion, merge, tag and tap change already made
491
+ instead of making them twice.
492
+
493
+ - \`drift\`: the branch or pull request moved after gating. Release the
494
+ new commit with \`--new\` (refused once merging began).
495
+ - \`check-level\`: the result has no Full check. File the gate with Full
496
+ checks and obtain a passing result; Quick and Off cannot authorize a release.
497
+ - \`check-failed\`: fix the branch and release the new commit with \`--new\`.
498
+ - \`ci-failed\`: fix the failed GitHub check, then rerun. Nothing was merged.
499
+ - \`publish-failed\` / \`homebrew-failed\`: fix the failed workflow before
500
+ resuming. A failed tag check cannot finish publication or update Homebrew.
501
+ - \`base-moved\`: main gained a commit the branch doesn't have, so the
502
+ merge wouldn't be the checked tree. Nothing was merged: update the
503
+ branch and release it with \`--new\`.
504
+ - \`tree-mismatch\`: main changed what was checked. Nothing was tagged;
505
+ the deployed build stays.
506
+ - \`timeout\`: rerun to keep waiting; \`--limit <step>=<minutes>\`
507
+ changes one step's limit.
508
+ - An interrupted deployment resumes with \`deploy-browser.mjs --stage <saved
509
+ path> --phase recover\` before any new attempt, including \`--new\`.
510
+ Failed recovery stops there; the original deployment journal remains available.
511
+ - \`link\`: a CLI name didn't answer as the new build, so both were put
512
+ back. The service already runs the new build; fix the name and rerun.
513
+ - \`gate-held\`: the deployment stopped before its swap but left new work
514
+ paused, and its journal can't say so by itself (typically killed while
515
+ preparing). \`toolroll release --release-gate <id>\` shows the proof that
516
+ the swap never began: the pause is that deployment's, its journal is
517
+ before the swap, the database schema, service definition and running
518
+ service are still the previous build's, and no deployment is running.
519
+ Add \`--yes\` to lift that one pause, then rerun the release. Missing or
520
+ contradicting evidence refuses and leaves the pause in place.
521
+
522
+ A runner's lease is returned with \`toolroll worker release <lease>\`.
523
+ `,
524
+ };
525
+ export const GUIDES = [operating, runner, steering, externalWork, console_, knowledge, release];
420
526
  export function guideNamed(name) {
421
527
  return GUIDES.find(one => one.name === name) ?? null;
422
528
  }
package/dist/html.d.ts ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Server HTML, escaped by construction.
3
+ *
4
+ * - `Html` is markup this module made. Nothing else can make one: the class
5
+ * is private and branded, so a string never passes for markup.
6
+ * - `html` is the only way to write markup. Its literal text is the
7
+ * author's; every interpolated value is text and is escaped, unless it is
8
+ * already `Html`. Arrays render in order; null, undefined and booleans
9
+ * render nothing, so `${cond && html`…`}` reads naturally.
10
+ * - `postForm` is the only way to write a POST form. It adds the request's
11
+ * CSRF field itself, so a form cannot forget it.
12
+ * - Page sinks (the shell, `respond` for text/html, the workspace's page
13
+ * snapshot) take `Html` and turn it into bytes with `htmlString`.
14
+ *
15
+ * The script, style and JSON constructors exist for the page shell: their
16
+ * sources are code, and each refuses content that could close its element.
17
+ *
18
+ * This module has no server dependencies (the browser bundle shares time
19
+ * helpers that use it); the server's request context supplies the CSRF token.
20
+ */
21
+ declare const MADE_HERE: unique symbol;
22
+ /** Markup this module made. Only this module holds the token its constructor asks for. */
23
+ export declare class Html {
24
+ #private;
25
+ constructor(token: typeof MADE_HERE, markup: string);
26
+ /** The bytes, for a sink. */
27
+ static markupOf(fragment: Html): string;
28
+ toString(): string;
29
+ toJSON(): string;
30
+ }
31
+ export type HtmlValue = Html | string | number | bigint | boolean | null | undefined | readonly HtmlValue[];
32
+ export declare const isHtml: (value: unknown) => value is Html;
33
+ /** The markup of an `Html` value, for a sink that writes bytes. */
34
+ export declare function htmlString(fragment: Html): string;
35
+ /** Markup with every interpolation escaped as text, except values that are already `Html`. */
36
+ export declare function html(strings: TemplateStringsArray, ...values: readonly HtmlValue[]): Html;
37
+ /** Parts in order, with an optional separator between them. */
38
+ export declare function joinHtml(parts: Iterable<HtmlValue>, separator?: HtmlValue): Html;
39
+ /** Plain text, escaped (for a sink that takes Html when the content is only words). */
40
+ export declare const textHtml: (text: string) => Html;
41
+ export type AttributeValue = string | number | boolean | null | undefined;
42
+ /** Attributes as `name="value"` pairs: true is a bare attribute; false, null and undefined are left out. */
43
+ export declare function attributes(values: Readonly<Record<string, AttributeValue>>): Html;
44
+ export type PostFormOptions = {
45
+ /** Attributes on the form element (class, id, aria-label, data-…). */
46
+ attrs?: Readonly<Record<string, AttributeValue>>;
47
+ /** Hidden fields, after the CSRF field (null and undefined are left out). */
48
+ hidden?: Readonly<Record<string, string | number | null | undefined>>;
49
+ /** The open project's revision this page was rendered under: a stale tab's post is refused. */
50
+ projectRevision?: number | string | null;
51
+ /** Where the action returns afterwards (a same-site path). */
52
+ returnTo?: string | null;
53
+ /** Forms before a session exists (sign-in, sign-up, a join link) have no CSRF field. */
54
+ signedOut?: true;
55
+ };
56
+ /** The server's request context supplies the request's CSRF token (server/request-context.ts). */
57
+ export declare function provideFormToken(source: () => string | undefined): void;
58
+ /**
59
+ * A POST form: the action, the request's one CSRF field, the optional
60
+ * project revision and return fields, other hidden fields, then the body.
61
+ * Outside a console request there is no CSRF to give, so it refuses.
62
+ */
63
+ export declare function postForm(action: string, body: HtmlValue, options?: PostFormOptions): Html;
64
+ /**
65
+ * Markup edited after rendering: each match of `pattern` in the page is replaced by markup the replacer builds
66
+ * (with html`…`), or kept as it was (null), so nothing unescaped can enter. For whole-page passes (a sign-in that
67
+ * stands in for passwords, the browser shell's insertions).
68
+ */
69
+ export declare function replaceMarkup(page: Html, pattern: RegExp, replace: (match: string, ...groups: string[]) => Html | null): Html;
70
+ /** An inline script whose source is code. Its text may not close the element or open a comment. */
71
+ export declare function scriptElement(source: string, attrs?: Readonly<Record<string, AttributeValue>>): Html;
72
+ /** A style element whose rules are code. */
73
+ export declare function styleElement(css: string, attrs?: Readonly<Record<string, AttributeValue>>): Html;
74
+ /** Inert JSON for a page script to read: no character of it can end the element. */
75
+ export declare function jsonScript(value: unknown, attrs?: Readonly<Record<string, AttributeValue>>): Html;
76
+ export {};