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
@@ -15,6 +15,7 @@ import { rulesWords } from "./approval-policy.js";
15
15
  import { assignmentOf } from "./assignment.js";
16
16
  import { projectName } from "./project.js";
17
17
  import { changedFilesOf } from "./result-completion.js";
18
+ import { html, htmlString, joinHtml, styleElement } from "./html.js";
18
19
  export const EVIDENCE_PACK_FORMAT = "standing-orders/evidence-pack/v1";
19
20
  /** How to recheck one entry's seal, for whoever reads the JSON. */
20
21
  export const SEAL_RECIPE = 'hash = sha256(prev + "\\n" + JSON.stringify([id, at, actor, repo, taskId, runId, action, outcome, source, detail]))';
@@ -173,13 +174,12 @@ export function* ledgerExportChunks(store, range, scope, access, who, now, evide
173
174
  yield `],"truncated":${JSON.stringify({ entries: moreEntries, packs: morePacks })}}`;
174
175
  }
175
176
  // ---- The printable page -----------------------------------------------------
176
- const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
177
- const when = (at) => at === null ? "—" : `${e(at.slice(0, 16).replace("T", " "))} UTC`;
178
- const short = (hash) => hash === null ? "—" : `<code title="${e(hash)}">${e(hash.slice(0, 12))}</code>`;
177
+ const when = (at) => at === null ? html `—` : html `${at.slice(0, 16).replace("T", " ")} UTC`;
178
+ const short = (hash) => hash === null ? html `—` : html `<code title="${hash}">${hash.slice(0, 12)}</code>`;
179
179
  const usd = (value) => value === null ? "—" : `$${value.toFixed(value < 1 ? 4 : 2)}`;
180
180
  /** Only a web address links; anything else (a stored value that isn't one) is shown as text. */
181
- const link = (url) => /^https:\/\/[^\s"'<>]+$/.test(url) ? `<a href="${e(url)}">${e(url)}</a>` : e(url);
182
- const filer = (who) => who === null || who.name === null ? "automation" : who.kind === "person" ? e(who.name) : `${e(who.name)} (${e(who.kind)})`;
181
+ const link = (url) => /^https:\/\/[^\s"'<>]+$/.test(url) ? html `<a href="${url}">${url}</a>` : html `${url}`;
182
+ const filer = (who) => who === null || who.name === null ? html `automation` : who.kind === "person" ? html `${who.name}` : html `${who.name} (${who.kind})`;
183
183
  export const EVIDENCE_PACK_CSS = `.evidence-pack{max-width:960px;min-width:0}.evidence-pack h1{margin:0}.evidence-pack h2{font-size:.9375rem;margin:24px 0 8px}.evidence-pack h3{font-size:.875rem;margin:16px 0 6px}` +
184
184
  `.evidence-head{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:baseline}.evidence-pack dl{display:grid;grid-template-columns:minmax(120px,180px) minmax(0,1fr);gap:4px 16px;margin:0}` +
185
185
  `.evidence-pack dt{color:var(--muted-foreground);font-size:.8125rem}.evidence-pack dd{margin:0;min-width:0;overflow-wrap:anywhere}.evidence-pack table{width:100%;border-collapse:collapse;font-size:.8125rem}` +
@@ -193,66 +193,62 @@ export const EVIDENCE_PACK_CSS = `.evidence-pack{max-width:960px;min-width:0}.ev
193
193
  export function evidencePackHtml(pack) {
194
194
  const task = pack.task;
195
195
  const chain = pack.ledger.chain.ok
196
- ? `<p class="evidence-chain ok" data-evidence-chain="ok"><strong>Ledger chain verified</strong> · ${pack.ledger.chain.entries} entries · head ${short(pack.ledger.chain.head)}${pack.ledger.chain.checkedAt === null ? "" : ` · checked in full ${when(pack.ledger.chain.checkedAt)}`}${pack.ledger.checkpoint === null ? "" : ` · last checkpoint at entry #${pack.ledger.checkpoint.through}`}</p>`
197
- : `<p class="evidence-chain problem" role="alert" data-evidence-chain="broken"><strong>Ledger chain broken</strong> · ${e(pack.ledger.chain.problem)}</p>`;
196
+ ? html `<p class="evidence-chain ok" data-evidence-chain="ok"><strong>Ledger chain verified</strong> · ${pack.ledger.chain.entries} entries · head ${short(pack.ledger.chain.head)}${pack.ledger.chain.checkedAt === null ? "" : html ` · checked in full ${when(pack.ledger.chain.checkedAt)}`}${pack.ledger.checkpoint === null ? "" : html ` · last checkpoint at entry #${pack.ledger.checkpoint.through}`}</p>`
197
+ : html `<p class="evidence-chain problem" role="alert" data-evidence-chain="broken"><strong>Ledger chain broken</strong> · ${pack.ledger.chain.problem}</p>`;
198
198
  const versions = pack.versions.map((version, index) => {
199
199
  const scope = version.scope;
200
200
  const approved = scope?.approved ?? null;
201
201
  const approvals = scope === null ? [] : scope.approvals.filter(one => one.digest === (approved?.digest ?? scope.digest));
202
202
  const rows = [
203
- ["Filed", `${when(version.filedAt)} by ${filer(version.filedBy)}`],
204
- ...(scope === null ? [["Scope", "None written"]] : [
205
- ["Goal", e(scope.goal)],
206
- ...(scope.touches.length ? [["Touches", scope.touches.map(e).join(", ")]] : []),
207
- ...(scope.acceptance.length ? [["Acceptance", `<ol class="criteria">${scope.acceptance.map(one => `<li>${e(one.statement)}</li>`).join("")}</ol>`]] : []),
208
- ...(scope.writtenBy.length ? [["Scope written by", [...new Set(scope.writtenBy.map(one => one.author))].map(e).join(", ")]] : []),
209
- ["Approved", approved === null ? "Not approved" : `${e(approved.by)}${approved.basis === "mode" ? " (operating mode)" : ""} · ${when(approved.at)}${approved.current ? "" : " · the scope changed after this"}`],
210
- ...(approvals.length > 1 ? [["Approvals", approvals.map(one => `${e(one.approver)} · ${when(one.at)}`).join("<br>")]] : []),
203
+ ["Filed", html `${when(version.filedAt)} by ${filer(version.filedBy)}`],
204
+ ...(scope === null ? [["Scope", html `None written`]] : [
205
+ ["Goal", html `${scope.goal}`],
206
+ ...(scope.touches.length ? [["Touches", joinHtml(scope.touches, ", ")]] : []),
207
+ ...(scope.acceptance.length ? [["Acceptance", html `<ol class="criteria">${scope.acceptance.map(one => html `<li>${one.statement}</li>`)}</ol>`]] : []),
208
+ ...(scope.writtenBy.length ? [["Scope written by", joinHtml([...new Set(scope.writtenBy.map(one => one.author))], ", ")]] : []),
209
+ ["Approved", approved === null ? html `Not approved` : html `${approved.by}${approved.basis === "mode" ? " (operating mode)" : ""} · ${when(approved.at)}${approved.current ? "" : " · the scope changed after this"}`],
210
+ ...(approvals.length > 1 ? [["Approvals", joinHtml(approvals.map(one => html `${one.approver} · ${when(one.at)}`), html `<br>`)]] : []),
211
211
  ["Scope digest", short(scope.digest)],
212
212
  ]),
213
213
  ];
214
- const runs = version.runs.length === 0 ? `<p class="meta">No agent ran.</p>` :
215
- `<div class="evidence-table"><table><thead><tr><th>Run</th><th>Role</th><th>Agent</th><th>Outcome</th><th>Cost</th><th>Finished</th><th>Commit</th></tr></thead><tbody>` +
216
- version.runs.map(run => `<tr><td><a href="/r/${run.id}">#${run.id}</a></td><td>${e(run.role)}</td><td>${e(run.agent)}${run.model ? ` · ${e(run.model)}` : ""}</td><td>${e(run.outcome ?? "running")}</td><td>${usd(run.costUsd)}</td><td>${when(run.finishedAt)}</td><td>${short(run.head)}</td></tr>`).join("") +
217
- `</tbody></table></div>`;
218
- const changes = version.runs.filter(run => run.role === "builder" || run.role === "repair").map(run => `<p class="meta">Run #${run.id}${run.diff === null ? "" : ` · diff ${short(run.diff.sha256)}${run.diff.complete ? "" : " (partial)"}`}</p>` +
219
- (run.changedFiles === null ? `<p class="meta">Its changed files couldn't be read.</p>` : run.changedFiles.length === 0 ? `<p class="meta">No files changed.</p>` : `<ul class="files">${run.changedFiles.map(file => `<li>${e(file)}</li>`).join("")}</ul>`)).join("");
220
- return `<section data-evidence-version="${e(version.id)}"><h2>${pack.versions.length > 1 ? `${index === 0 ? "Request" : `Revision ${index}`} · ` : "Request and approval · "}${e(version.id)}</h2><dl>${rows.map(([label, value]) => `<dt>${label}</dt><dd>${value}</dd>`).join("")}</dl>` +
221
- `<h3>Agents</h3>${runs}${changes === "" ? "" : `<h3>Changes</h3>${changes}`}</section>`;
222
- }).join("");
214
+ const runs = version.runs.length === 0 ? html `<p class="meta">No agent ran.</p>` :
215
+ html `<div class="evidence-table"><table><thead><tr><th>Run</th><th>Role</th><th>Agent</th><th>Outcome</th><th>Cost</th><th>Finished</th><th>Commit</th></tr></thead><tbody>${version.runs.map(run => html `<tr><td><a href="/r/${run.id}">#${run.id}</a></td><td>${run.role}</td><td>${run.agent}${run.model ? html ` · ${run.model}` : ""}</td><td>${run.outcome ?? "running"}</td><td>${usd(run.costUsd)}</td><td>${when(run.finishedAt)}</td><td>${short(run.head)}</td></tr>`)}</tbody></table></div>`;
216
+ const changes = version.runs.filter(run => run.role === "builder" || run.role === "repair").map(run => html `<p class="meta">Run #${run.id}${run.diff === null ? "" : html ` · diff ${short(run.diff.sha256)}${run.diff.complete ? "" : " (partial)"}`}</p>${run.changedFiles === null ? html `<p class="meta">Its changed files couldn't be read.</p>` : run.changedFiles.length === 0 ? html `<p class="meta">No files changed.</p>` : html `<ul class="files">${run.changedFiles.map(file => html `<li>${file}</li>`)}</ul>`}`);
217
+ return html `<section data-evidence-version="${version.id}"><h2>${pack.versions.length > 1 ? `${index === 0 ? "Request" : `Revision ${index}`} · ` : "Request and approval · "}${version.id}</h2><dl>${rows.map(([label, value]) => html `<dt>${label}</dt><dd>${value}</dd>`)}</dl><h3>Agents</h3>${runs}${changes.length === 0 ? "" : html `<h3>Changes</h3>${changes}`}</section>`;
218
+ });
223
219
  const result = pack.result;
224
- const outcome = result === null ? `<section><h2>Result</h2><p class="meta">No finished result yet.</p></section>` :
225
- `<section data-evidence-result><h2>Result</h2><dl>` +
226
- `<dt>Checks</dt><dd>${e(result.checks.detail)}${result.checks.command ? ` <code>${e(result.checks.command)}</code>` : ""}</dd>` +
227
- `<dt>Marked complete</dt><dd>${result.completedBy === null ? "Not yet" : `${e(result.completedBy)} · ${when(result.completedAt)}`}</dd>` +
228
- `<dt>Published</dt><dd>${result.publication === null ? "Not published" : `${e(result.publication.state)}${result.publication.prUrl ? ` · ${link(result.publication.prUrl)}` : ""}${result.publication.remoteState ? ` · ${e(result.publication.remoteState.toLowerCase())}` : ""}`}</dd>` +
229
- `<dt>Commit</dt><dd>${short(result.base)} → ${short(result.head)}</dd>` +
230
- (result.caveats.length ? `<dt>Caveats</dt><dd>${result.caveats.map(e).join("<br>")}</dd>` : "") +
231
- `</dl></section>`;
220
+ const outcome = result === null ? html `<section><h2>Result</h2><p class="meta">No finished result yet.</p></section>` :
221
+ joinHtml([html `<section data-evidence-result><h2>Result</h2><dl>`,
222
+ html `<dt>Checks</dt><dd>${result.checks.detail}${result.checks.command ? html ` <code>${result.checks.command}</code>` : ""}</dd>`,
223
+ html `<dt>Marked complete</dt><dd>${result.completedBy === null ? "Not yet" : html `${result.completedBy} · ${when(result.completedAt)}`}</dd>`,
224
+ html `<dt>Published</dt><dd>${result.publication === null ? "Not published" : html `${result.publication.state}${result.publication.prUrl ? html ` · ${link(result.publication.prUrl)}` : ""}${result.publication.remoteState ? html ` · ${result.publication.remoteState.toLowerCase()}` : ""}`}</dd>`,
225
+ html `<dt>Commit</dt><dd>${short(result.base)} → ${short(result.head)}</dd>`,
226
+ result.caveats.length ? html `<dt>Caveats</dt><dd>${joinHtml(result.caveats, html `<br>`)}</dd>` : "",
227
+ html `</dl></section>`]);
232
228
  const decisions = pack.ledger.entries.filter(one => one.source !== "request");
233
229
  const requests = pack.ledger.entries.length - decisions.length;
234
230
  const askedWords = `${requests} ${requests === 1 ? "request" : "requests"} made through the console (accepted or refused)`;
235
- const ledger = `<section><h2>Ledger</h2>${decisions.length === 0 ? `<p class="meta">No entries.</p>` :
236
- `<div class="evidence-table"><table><thead><tr><th>#</th><th>Time</th><th>Who</th><th>What</th><th>Outcome</th><th>Seal</th></tr></thead><tbody>` +
237
- decisions.map(one => `<tr data-ledger-id="${one.id}"><td>${one.id}</td><td>${when(one.at)}</td><td>${e(one.actor)}</td><td>${e(one.action)}${one.detail ? `<br><span class="meta">${e(one.detail)}</span>` : ""}</td><td>${e(one.outcome)}</td><td>${short(one.seal?.hash ?? null)}</td></tr>`).join("") +
238
- `</tbody></table></div>`}${requests > 0 ? `<p class="meta">Plus ${askedWords}, listed in the JSON.</p>` : ""}${pack.ledger.truncated ? `<p class="meta">This pack holds ${decisions.length} of the task's entries and the newest ${requests} requests; the ledger export has the rest.</p>` : ""}</section>`;
231
+ const ledger = html `<section><h2>Ledger</h2>${decisions.length === 0 ? html `<p class="meta">No entries.</p>` :
232
+ html `<div class="evidence-table"><table><thead><tr><th>#</th><th>Time</th><th>Who</th><th>What</th><th>Outcome</th><th>Seal</th></tr></thead><tbody>${decisions.map(one => html `<tr data-ledger-id="${one.id}"><td>${one.id}</td><td>${when(one.at)}</td><td>${one.actor}</td><td>${one.action}${one.detail ? html `<br><span class="meta">${one.detail}</span>` : ""}</td><td>${one.outcome}</td><td>${short(one.seal?.hash ?? null)}</td></tr>`)}</tbody></table></div>`}${requests > 0 ? html `<p class="meta">Plus ${askedWords}, listed in the JSON.</p>` : ""}${pack.ledger.truncated ? html `<p class="meta">This pack holds ${decisions.length} of the task's entries and the newest ${requests} requests; the ledger export has the rest.</p>` : ""}</section>`;
239
233
  const cost = pack.totals.runs === 0 ? "None ran" : `${pack.totals.runs} ${pack.totals.runs === 1 ? "run" : "runs"} · ${usd(pack.totals.costUsd)}`;
240
234
  // The rules as they stand, and each change to them (the approval above was under whichever applied then).
241
- const rules = pack.rules === null ? "None" : `${e(pack.rules.now)}${pack.rules.changes.length === 0 ? "" :
242
- `<ul class="criteria">${pack.rules.changes.slice(-5).map(one => `<li class="meta">${when(one.at)} · ${e(one.by)}: ${e(one.change)}</li>`).join("")}</ul>`}`;
243
- return `<article class="evidence-pack" data-evidence-pack="${e(task.id)}">` +
244
- `<div class="evidence-head"><h1>Evidence pack</h1><p class="evidence-actions"><a href="/t/${encodeURIComponent(task.id)}/evidence?format=json" download>Download JSON</a> · <a href="/t/${encodeURIComponent(task.id)}">Back to task</a></p></div>` +
245
- `<p><strong>${e(task.title)}</strong></p>` +
246
- `<dl><dt>Task</dt><dd>${e(task.id)}${task.project === null ? "" : ` · ${e(task.project)}`} · ${e(task.state)}</dd>` +
247
- `<dt>Approval rules</dt><dd>${rules}</dd><dt>Agents</dt><dd>${cost}</dd>` +
248
- `<dt>Prepared</dt><dd>${when(pack.generatedAt)} for ${e(pack.generatedBy)}</dd></dl>${chain}` +
249
- versions + outcome + ledger +
250
- `<p class="evidence-foot">Pack digest <code>${e(pack.digest)}</code>. Each ledger entry's seal can be rechecked from the JSON: ${e(pack.ledger.recipe)}.</p></article>`;
235
+ const rules = pack.rules === null ? html `None` : html `${pack.rules.now}${pack.rules.changes.length === 0 ? "" :
236
+ html `<ul class="criteria">${pack.rules.changes.slice(-5).map(one => html `<li class="meta">${when(one.at)} · ${one.by}: ${one.change}</li>`)}</ul>`}`;
237
+ return joinHtml([html `<article class="evidence-pack" data-evidence-pack="${task.id}">`,
238
+ html `<div class="evidence-head"><h1>Evidence pack</h1><p class="evidence-actions"><a href="/t/${encodeURIComponent(task.id)}/evidence?format=json" download>Download JSON</a> · <a href="/t/${encodeURIComponent(task.id)}">Back to task</a></p></div>`,
239
+ html `<p><strong>${task.title}</strong></p>`,
240
+ html `<dl><dt>Task</dt><dd>${task.id}${task.project === null ? "" : ` · ${task.project}`} · ${task.state}</dd>`,
241
+ html `<dt>Approval rules</dt><dd>${rules}</dd><dt>Agents</dt><dd>${cost}</dd>`,
242
+ html `<dt>Prepared</dt><dd>${when(pack.generatedAt)} for ${pack.generatedBy}</dd></dl>${chain}`,
243
+ versions, outcome, ledger,
244
+ html `<p class="evidence-foot">Pack digest <code>${pack.digest}</code>. Each ledger entry's seal can be rechecked from the JSON: ${pack.ledger.recipe}.</p></article>`]);
251
245
  }
252
246
  /** The page as a file on its own (the CLI's --html): the same facts with just enough style to print. */
253
247
  export function standaloneEvidenceHtml(pack) {
254
- return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Evidence pack · ${e(pack.task.id)}</title>` +
255
- `<style>:root{--muted-foreground:#5c5c5c;--border:#dedede;--success:#1a7f37;--font-mono:ui-monospace,SFMono-Regular,Menlo,monospace}` +
248
+ const css = `:root{--muted-foreground:#5c5c5c;--border:#dedede;--success:#1a7f37;--font-mono:ui-monospace,SFMono-Regular,Menlo,monospace}` +
256
249
  `body{font:14px/1.5 system-ui,-apple-system,sans-serif;margin:32px auto;padding:0 16px;max-width:960px;color:#171717;background:#fff}.meta{color:var(--muted-foreground);font-size:.8125rem}.evidence-actions{display:none}` +
257
- EVIDENCE_PACK_CSS + `</style></head><body>${evidencePackHtml(pack)}</body></html>\n`;
250
+ EVIDENCE_PACK_CSS;
251
+ // The file is the sink: its bytes.
252
+ return htmlString(html `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Evidence pack · ${pack.task.id}</title>${styleElement(css)}</head><body>${evidencePackHtml(pack)}</body></html>
253
+ `);
258
254
  }
@@ -1,4 +1,9 @@
1
+ /**
2
+ * Settings → Data (v105): an instance operator downloads everything Standing
3
+ * Orders knows as one .zip. One form: the password, typed again, and a button.
4
+ */
5
+ import { type Html } from "./html.js";
1
6
  export declare const EXPORT_CSS: string;
2
- export declare function dataExportHtml(csrf: string, notice: {
7
+ export declare function dataExportHtml(notice: {
3
8
  problem?: string | null;
4
- }): string;
9
+ }): Html;
package/dist/export-ui.js CHANGED
@@ -2,16 +2,10 @@
2
2
  * Settings → Data (v105): an instance operator downloads everything Standing
3
3
  * Orders knows as one .zip. One form: the password, typed again, and a button.
4
4
  */
5
- const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
5
+ import { html, postForm } from "./html.js";
6
6
  export const EXPORT_CSS = `.data-export{max-width:720px;min-width:0}.data-export label{display:grid;gap:4px;margin:14px 0 0;font-size:.875rem}` +
7
7
  `.data-export input[type=password]{width:100%;max-width:360px;box-sizing:border-box}.data-export button[type=submit]{margin-top:14px;min-height:44px;white-space:nowrap}`;
8
- export function dataExportHtml(csrf, notice) {
9
- const note = notice.problem ? `<p class="problem" role="alert">${e(notice.problem)}</p>` : "";
10
- return `<section class="data-export">${note}` +
11
- `<p>Everything Toolroll knows, as one .zip: projects, tasks, runs and cost, the action ledger, evidence packs, chats, flows, teammates and settings. ` +
12
- `Passwords, keys and tokens are never included.</p>` +
13
- `<form method="post" action="/settings/data"><input type="hidden" name="csrf" value="${e(csrf)}">` +
14
- `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>` +
15
- `<button type="submit">Download export</button></form>` +
16
- `<p class="meta">The same export from the command line: <code>toolroll export --out &lt;path&gt; [--zip]</code></p></section>`;
8
+ export function dataExportHtml(notice) {
9
+ const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : "";
10
+ return html `<section class="data-export">${note}<p>Everything Toolroll knows, as one .zip: projects, tasks, runs and cost, the action ledger, evidence packs, chats, flows, subagents and settings. Passwords, keys and tokens are never included.</p>${postForm("/settings/data", html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label><button type="submit">Download export</button>`)}<p class="meta">The same export from the command line: <code>toolroll export --out &lt;path&gt; [--zip]</code></p></section>`;
17
11
  }
package/dist/export.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Full export (v105): everything Toolroll knows, for an instance
3
3
  * operator to take away — every table of the database as JSON Lines, grouped
4
4
  * by what it's about (projects, tasks, runs, the ledger, chats, flows,
5
- * teammates, people, settings), an evidence pack per task, the settings kept
5
+ * subagents, people, settings), an evidence pack per task, the settings kept
6
6
  * in files beside the database, a README explaining the layout, and a
7
7
  * manifest with each file's SHA-256. One folder, or the same files in a .zip.
8
8
  *
@@ -35,14 +35,14 @@ const DERIVED_TABLE = /^(sqlite_|memory_search)/;
35
35
  const SECRET_COLUMNS = new Set([
36
36
  "api_token.secret_hash", "lead_credential.secret_hash", "approver.credential_hash", "coordinator_credential.credential_hash", "runner.credential_hash", "invite.token_hash",
37
37
  "flow_trigger.hook_hash", "push_subscription.endpoint", "push_subscription.p256dh", "push_subscription.auth",
38
- "chat_turn.credential_key", "mate_session.credential_key", "mate_turn.credential_key", "chat_runtime.push_url", "workflow_preview.token",
38
+ "chat_turn.credential_key", "lead_session.credential_key", "mate_turn.credential_key", "chat_runtime.push_url", "workflow_preview.token",
39
39
  "quota.credential_fp", "oauth_grant.renew_hash", "oauth_client.source_hash",
40
40
  ]);
41
41
  /** A column named like this holds a secret unless it is reviewed below; an unreviewed one is dropped. */
42
42
  const SECRET_NAME = /hash|secret|token|passw|cookie|csrf|credential|p256dh|^auth$|key|nonce|endpoint|url/i;
43
43
  /** Reviewed: named like a secret, but it isn't one (a seal, a digest, a lookup name, an address with no key). */
44
44
  const REVIEWED_COLUMNS = new Set([
45
- "artifact.key", "budget.scope_key", "mate_thread.scope_key", "diff_comment.source_key", "chat_meta.key",
45
+ "artifact.key", "budget.scope_key", "lead_thread.scope_key", "diff_comment.source_key", "chat_meta.key",
46
46
  "flow_trigger_event.key", "installation_fact.key", "mcp_idempotency.key", "memory_gap.key", "mutation.idempotency_key", "notification.dedupe_key",
47
47
  "service_cursor.key", "ledger_checkpoint.hash", "ledger_seal.hash", "merge_intent.grant_terms_hash", "plan_revision.parent_hash",
48
48
  "publication.body_hash", "publication.pr_url", "team_message.payload_hash", "team_request.payload_hash", "backend_grant.credential_scope",
@@ -53,9 +53,9 @@ const USAGE_COLUMN = /(^|_)tokens(_|$)|output_tokens/;
53
53
  export function categoryOf(table) {
54
54
  const rules = [
55
55
  [/^(action_ledger|ledger_seal|ledger_checkpoint|sync_ledger)$/, "ledger"],
56
- [/^teammate/, "teammates"],
56
+ [/^subagent/, "subagents"],
57
57
  [/^(flow|workflow_)/, "flows"],
58
- [/^(chat_|mate_|team_|notification|push_)/, "chats"],
58
+ [/^(chat_|lead_|team_|notification|push_)/, "chats"],
59
59
  [/^(approver|api_token|lead_credential|invite|sso_identity|coordinator_|oauth_)/, "people"],
60
60
  [/^(run|artifact|claim|worktree$|execution_slot|criterion_review|diff_comment|proof_|review_request|repair_chain|incident|publication$|merge_|side_spend|knowledge_snapshot|learning_snapshot|skill_snapshot)/, "runs"],
61
61
  [/^(task|hold$|plan_|scope_|tool_seal|decision|external_|skill_test)/, "tasks"],
@@ -214,9 +214,9 @@ Everything this installation knew on ${now.toISOString().slice(0, 16).replace("T
214
214
  - \`runs/\`: agent runs with usage and cost (\`run\`, \`run_spend\`), their records of artifacts, proofs, reviews and publications.
215
215
  - \`ledger/\`: the action ledger (\`action_ledger\`), each entry's seal (\`ledger_seal\`) and the checkpoints (\`ledger_checkpoint\`).
216
216
  - \`evidence-packs/\`: one JSON file per task: who asked, the exact terms and who approved them, runs and cost, checks, completion and its ledger entries.
217
- - \`chats/\`: conversations with the lead and teammates, and Telegram, Slack, Discord and Teams messages and notifications.
217
+ - \`chats/\`: conversations with the lead and subagents, and Telegram, Slack, Discord and Teams messages and notifications.
218
218
  - \`flows/\`: flows, their cards, scripts and triggers (schedules included) and saved recipes.
219
- - \`teammates/\`: teammates, their versions, memory, tools and turns.
219
+ - \`subagents/\`: subagents, their versions, memory, tools and turns.
220
220
  - \`people/\`: accounts, API token names and invitations (no passwords or tokens).
221
221
  - \`settings/\`: models, budgets, spend and permission defaults, provider readings; \`files.json\` holds the settings kept in files.
222
222
  - \`other/\`: everything else the database keeps (workers, leases, queues).
@@ -72,8 +72,8 @@ export declare function decideFlowCard(store: Store, input: {
72
72
  draft?: string | null;
73
73
  /** The visit the person saw (a chat button's): a card that moved on since is refused. */
74
74
  entry?: number;
75
- /** v92: the AI teammate (by handle) deciding a zone it staffs; `actor` is then how it reads in history. */
76
- teammate?: string;
75
+ /** v92: the subagent (by handle) deciding a zone it staffs; `actor` is then how it reads in history. */
76
+ subagent?: string;
77
77
  /** Where the decision was made, for its ledger line: "the console", "the command line", "Telegram". */
78
78
  where?: string;
79
79
  }, now: Date): FlowDecision;
@@ -18,7 +18,7 @@ import { readVerifiedReport, scanForSecrets } from "./evidence.js";
18
18
  import { cardFollowers, notifyPeople } from "./flow-people.js";
19
19
  import { DRAFT_CHARS, keptDraft } from "./flow-draft.js";
20
20
  import { chooseStep, flowPersonOf, sendStep } from "./flow-send.js";
21
- import { parseSoul, teammateLabel } from "./teammates.js";
21
+ import { parseSoul, subagentLabel } from "./subagents.js";
22
22
  import { withStageHandoff } from "./contracts/stage-output.js";
23
23
  /** The flow's drawing, or null when it can't be read (the card then says so and waits). */
24
24
  export function flowDefinitionOf(flow) {
@@ -42,15 +42,15 @@ function fill(template, card) {
42
42
  }
43
43
  const titleIn = (definition, id) => definition.stages.find(one => one.id === id)?.title ?? id;
44
44
  const flowDefinitionStage = (definition, id) => definition.stages.find(one => one.id === id);
45
- /** A teammate's name as its soul file gives it (its handle when the file can't be read). */
46
- function teammateName(mate) {
45
+ /** A subagent's name as its soul file gives it (its handle when the file can't be read). */
46
+ function subagentName(mate) {
47
47
  const read = parseSoul(mate.soul);
48
48
  return read.ok ? read.soul.name : mate.handle;
49
49
  }
50
- /** What a teammate said when it handed a card to its person, or why it couldn't decide. */
50
+ /** What a subagent said when it handed a card to its person, or why it couldn't decide. */
51
51
  function handoffWords(mate, turn) {
52
52
  const read = parseSoul(mate.soul);
53
- const label = read.ok ? teammateLabel(read.soul) : mate.handle;
53
+ const label = read.ok ? subagentLabel(read.soul) : mate.handle;
54
54
  let note = "";
55
55
  try {
56
56
  const decided = JSON.parse(turn.decisionJson ?? "{}");
@@ -187,20 +187,20 @@ function advanceCard(store, flow, definition, card, now, options, outcome) {
187
187
  case "approval": {
188
188
  const decider = deciderOf(stage, flow);
189
189
  const who = decider ?? "an approver";
190
- // v92: a zone an active teammate staffs is its to decide first (the step pass runs it). A person hears
191
- // about the card only when the teammate hands it over, can't decide, or is paused or gone.
192
- // v102: on a protected project an AI teammate never decides; the person does.
193
- const mate = stage.teammate === undefined || store.approvalRules(flow.repo).protectProject ? null : store.teammateByHandle(flow.repo, stage.teammate);
190
+ // v92: a zone an active subagent staffs is its to decide first (the step pass runs it). A person hears
191
+ // about the card only when the subagent hands it over, can't decide, or is paused or gone.
192
+ // v102: on a protected project a subagent never decides; the person does.
193
+ const mate = stage.subagent === undefined || store.approvalRules(flow.repo).protectProject ? null : store.subagentByHandle(flow.repo, stage.subagent);
194
194
  const turn = mate === null ? null : store.flowStepRun(card.id, card.entry);
195
- const handed = turn !== null && turn.kind === "teammate" && (turn.state === "failed" || turn.state === "passed");
195
+ const handed = turn !== null && turn.kind === "subagent" && (turn.state === "failed" || turn.state === "passed");
196
196
  if (mate !== null && mate.state === "active" && !handed) {
197
- const name = teammateName(mate);
197
+ const name = subagentName(mate);
198
198
  if (card.waiting !== `${name} is deciding`)
199
199
  store.updateFlowCard(card.id, { waiting: `${name} is deciding` }, now);
200
200
  return;
201
201
  }
202
202
  const handoff = handed && mate !== null ? handoffWords(mate, turn) : null;
203
- if (card.waiting === null || (mate !== null && card.waiting === `${teammateName(mate)} is deciding`)) {
203
+ if (card.waiting === null || (mate !== null && card.waiting === `${subagentName(mate)} is deciding`)) {
204
204
  // A named decider hears it alone; "anyone who approves" pages everyone who can.
205
205
  // With a draft in front of it, the decision carries the draft itself: it can be read (and answered) where it arrives.
206
206
  const draft = draftFor(definition, stage);
@@ -447,8 +447,8 @@ export function cancelFlowCard(store, card, actor, now) {
447
447
  /** The draft a decision is about: the draft zone that sends its cards to this one. */
448
448
  export function draftFor(definition, stage) {
449
449
  return definition.stages.find(one => one.kind === "draft" && one.next === stage.id)
450
- // A teammate's zone that leads here (by its next, or one of its answers) wrote the draft this decision checks (starter kits).
451
- ?? definition.stages.find(one => one.kind === "teammate" && (one.next === stage.id || (one.routes ?? []).some(route => route.to === stage.id))) ?? null;
450
+ // A subagent's zone that leads here (by its next, or one of its answers) wrote the draft this decision checks (starter kits).
451
+ ?? definition.stages.find(one => one.kind === "subagent" && (one.next === stage.id || (one.routes ?? []).some(route => route.to === stage.id))) ?? null;
452
452
  }
453
453
  /** Back to a build zone with a finished result: a revision of that same work, carrying the note; null when it isn't one. */
454
454
  export function revisionWithNote(store, card, target, note, actor, repos, evidenceRoot, now) {
@@ -478,21 +478,21 @@ export function decideFlowCard(store, input, now) {
478
478
  const stage = definition.stages.find(one => one.id === card.stage);
479
479
  if (stage === undefined || stage.kind !== "approval" || (input.entry !== undefined && input.entry !== card.entry))
480
480
  return { ok: false, message: "That card has moved on since; nothing was changed." };
481
- // A teammate decides only a zone it staffs; its person (and only them) can always decide instead.
481
+ // A subagent decides only a zone it staffs; its person (and only them) can always decide instead.
482
482
  const decider = deciderOf(stage, flow);
483
- if (input.teammate !== undefined ? stage.teammate !== input.teammate : decider !== null && decider !== input.actor)
483
+ if (input.subagent !== undefined ? stage.subagent !== input.subagent : decider !== null && decider !== input.actor)
484
484
  return { ok: false, message: `Only ${decider ?? "an approver"} decides here.` };
485
- // v102: a protected project's decisions are a person's, never an AI teammate's.
486
- if (input.teammate !== undefined && store.approvalRules(flow.repo).protectProject)
485
+ // v102: a protected project's decisions are a person's, never a subagent's.
486
+ if (input.subagent !== undefined && store.approvalRules(flow.repo).protectProject)
487
487
  return { ok: false, message: "This project is protected: a person decides here." };
488
488
  // Every decision is one ledger line, the same wherever it was made; only `via` says where.
489
489
  const ledger = (outcome) => store.recordAction({ at: now.toISOString(), actor: input.actor, repo: flow.repo, taskId: card.primaryTask, runId: null,
490
- action: "flow decision", outcome, source: "request", detail: `${flow.name} · card ${card.id} · ${stage.title}${input.teammate === undefined ? "" : ` · teammate ${input.teammate}`} · via ${input.where ?? "Toolroll"}` });
490
+ action: "flow decision", outcome, source: "request", detail: `${flow.name} · card ${card.id} · ${stage.title}${input.subagent === undefined ? "" : ` · subagent ${input.subagent}`} · via ${input.where ?? "Toolroll"}` });
491
491
  if (input.decision === "approve") {
492
492
  // An edited draft replaces the one Claude wrote, so the steps after this send what the person approved.
493
493
  const draft = draftFor(definition, stage);
494
- // A person's edit over the draft limit is refused with it, never cut. (A teammate's was asked once to shorten and is kept whole.)
495
- if (draft !== null && input.teammate === undefined && typeof input.draft === "string" && input.draft.trim().length > DRAFT_CHARS) {
494
+ // A person's edit over the draft limit is refused with it, never cut. (A subagent's was asked once to shorten and is kept whole.)
495
+ if (draft !== null && input.subagent === undefined && typeof input.draft === "string" && input.draft.trim().length > DRAFT_CHARS) {
496
496
  return { ok: false, message: `Keep the draft to ${DRAFT_CHARS.toLocaleString("en-US")} characters; this is ${input.draft.trim().length.toLocaleString("en-US")}.` };
497
497
  }
498
498
  if (draft !== null && typeof input.draft === "string" && input.draft.trim() !== "" && input.draft.trim() !== card.outputs[draft.id]?.trim()) {
@@ -1,11 +1,12 @@
1
1
  import { type GalleryAnswers, type GalleryPreview, type GalleryTemplate, type GalleryTool } from "./flow-gallery.js";
2
2
  import { type FlowDefinition, type FlowStage } from "./flows.js";
3
+ import { type Html } from "./html.js";
3
4
  export declare const GALLERY_CSS: string;
4
5
  /** A flow's main path, as a person reads it: from the start, each step's first way on (a sort's first answer, a choose's
5
6
  * first option that goes somewhere, else next; a check's failure when passing just ends), never back to a step shown. */
6
7
  export declare function mainPath(definition: FlowDefinition): FlowStage[];
7
8
  /** A flow's steps in order, joined by arrows; a person's decisions carry the accent. */
8
- export declare function stepStrip(definition: FlowDefinition): string;
9
+ export declare function stepStrip(definition: FlowDefinition): Html;
9
10
  /** The gallery: every template, grouped, each with one "Use this". `repo` carries a chosen project to the template's page. */
10
11
  export declare function galleryHtml(input: {
11
12
  repo: string | null;
@@ -14,7 +15,7 @@ export declare function galleryHtml(input: {
14
15
  id: string;
15
16
  state: "connected" | "open" | "taken";
16
17
  }[] | null;
17
- }): string;
18
+ }): Html;
18
19
  /** A template's page: its questions, then what it will do and never do, then Create. */
19
20
  export declare function galleryUseHtml(input: {
20
21
  template: GalleryTemplate;
@@ -32,4 +33,4 @@ export declare function galleryUseHtml(input: {
32
33
  /** The template's tools in this project; `said` is how the last Connect went. */
33
34
  tools?: readonly GalleryTool[];
34
35
  said?: string | null;
35
- }): string;
36
+ }): Html;
@@ -7,7 +7,7 @@
7
7
  import { brandMarkHtml } from "./brand-mark.js";
8
8
  import { BLANK, GALLERY, GALLERY_GROUPS, galleryDiagram, galleryToolsOf, OUTDATED_COMMANDS, SEND_RESULT, sendsAlready } from "./flow-gallery.js";
9
9
  import { choiceTargets } from "./flows.js";
10
- const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
10
+ import { html, joinHtml, postForm } from "./html.js";
11
11
  export const GALLERY_CSS = `.gallery{max-width:1120px;min-width:0}.gallery h2{margin:28px 0 10px;font-size:.9375rem}.gallery h2:first-of-type{margin-top:8px}` +
12
12
  `.gallery-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px}` +
13
13
  `.gallery-card{display:flex;flex-direction:column;gap:8px;padding:16px;border:1px solid var(--so-line);border-radius:10px;background:var(--so-paper);min-width:0}` +
@@ -53,30 +53,28 @@ export function mainPath(definition) {
53
53
  const stepName = (stage) => stage.kind === "choose" ? "You choose" : stage.kind === "approval" ? "You approve" : stage.kind === "send" ? "Sent to you" : stage.title;
54
54
  /** A flow's steps in order, joined by arrows; a person's decisions carry the accent. */
55
55
  export function stepStrip(definition) {
56
- return `<ol class="gallery-steps" aria-label="Steps">${mainPath(definition).map((one, i) => {
57
- const name = e(stepName(one));
58
- return `<li>${i === 0 ? "" : `<span aria-hidden="true">→</span>`}${one.kind === "choose" || one.kind === "approval" ? `<b data-person>${name}</b>` : name}</li>`;
59
- }).join("")}</ol>`;
56
+ return html `<ol class="gallery-steps" aria-label="Steps">${mainPath(definition).map((one, i) => {
57
+ const name = stepName(one);
58
+ return html `<li>${i === 0 ? "" : html `<span aria-hidden="true">→</span>`}${one.kind === "choose" || one.kind === "approval" ? html `<b data-person>${name}</b>` : name}</li>`;
59
+ })}</ol>`;
60
60
  }
61
- const needsList = (template) => template.needs.length === 0 ? (template.tools ?? []).length > 0 ? "" : `<ul class="gallery-needs"><li>Nothing to connect</li></ul>`
62
- : `<ul class="gallery-needs" aria-label="Needs">${template.needs.map(one => `<li>${e(one)}</li>`).join("")}</ul>`;
61
+ const needsList = (template) => template.needs.length === 0 ? (template.tools ?? []).length > 0 ? "" : html `<ul class="gallery-needs"><li>Nothing to connect</li></ul>`
62
+ : html `<ul class="gallery-needs" aria-label="Needs">${template.needs.map(one => html `<li>${one}</li>`)}</ul>`;
63
63
  /** A tool's state in words, as Settings → Integrations badges it; nothing without a project. */
64
64
  const STATE = { connected: ["connected", "Connected"], open: ["not-set-up", "Not connected"], taken: ["not-set-up", "Added another way"] };
65
- const toolState = (tool) => tool.state === null ? "" : `<span class="integration-state integration-state--${STATE[tool.state][0]}"><i aria-hidden="true"></i>${STATE[tool.state][1]}</span>`;
65
+ const toolState = (tool) => tool.state === null ? "" : html `<span class="integration-state integration-state--${STATE[tool.state][0]}"><i aria-hidden="true"></i>${STATE[tool.state][1]}</span>`;
66
66
  const toolMark = (tool) => brandMarkHtml(tool.id.replace(/-desktop$/, ""), tool.label, tool.state === "connected");
67
67
  const toolsList = (tools) => tools.length === 0 ? ""
68
- : `<ul class="gallery-tools" aria-label="Tools">${tools.map(one => `<li data-tool="${e(one.id)}" data-state="${one.state ?? "unknown"}">${toolMark(one)}<strong>${e(one.label)}</strong>${toolState(one)}</li>`).join("")}</ul>`;
68
+ : html `<ul class="gallery-tools" aria-label="Tools">${tools.map(one => html `<li data-tool="${one.id}" data-state="${one.state ?? "unknown"}">${toolMark(one)}<strong>${one.label}</strong>${toolState(one)}</li>`)}</ul>`;
69
69
  /** The gallery: every template, grouped, each with one "Use this". `repo` carries a chosen project to the template's page. */
70
70
  export function galleryHtml(input) {
71
71
  const href = (id) => `/flows/new/${id}${input.repo === null ? "" : `?repo=${encodeURIComponent(input.repo)}`}`;
72
72
  const card = (template) => {
73
73
  const definition = galleryDiagram(template);
74
- return `<article class="gallery-card" data-template="${e(template.id)}"><h3>${e(template.name)}</h3><p class="gallery-promise">${e(template.promise)}</p>` +
75
- stepStrip(definition) + toolsList(galleryToolsOf(template, definition, input.repo === null ? null : input.connections ?? null)) + needsList(template) +
76
- (input.canUse ? `<a class="button-link" href="${e(href(template.id))}" aria-label="Use ${e(template.name)}">Use this</a>` : "") + `</article>`;
74
+ return html `<article class="gallery-card" data-template="${template.id}"><h3>${template.name}</h3><p class="gallery-promise">${template.promise}</p>${stepStrip(definition)}${toolsList(galleryToolsOf(template, definition, input.repo === null ? null : input.connections ?? null))}${needsList(template)}${input.canUse && html `<a class="button-link" href="${href(template.id)}" aria-label="Use ${template.name}">Use this</a>`}</article>`;
77
75
  };
78
- const groups = GALLERY_GROUPS.map(group => `<h2 id="gallery-${group.id}">${e(group.label)}</h2><div class="gallery-grid" data-group="${group.id}">${GALLERY.filter(one => one.group === group.id).map(card).join("")}</div>`).join("");
79
- return `<section class="gallery">${groups}${input.canUse ? `<p class="gallery-blank meta"><a href="${e(href(BLANK.id))}">Start from a blank flow</a></p>` : `<p class="meta">An approver creates flows.</p>`}</section>`;
76
+ const groups = GALLERY_GROUPS.map(group => html `<h2 id="gallery-${group.id}">${group.label}</h2><div class="gallery-grid" data-group="${group.id}">${GALLERY.filter(one => one.group === group.id).map(card)}</div>`);
77
+ return html `<section class="gallery">${groups}${input.canUse ? html `<p class="gallery-blank meta"><a href="${href(BLANK.id)}">Start from a blank flow</a></p>` : html `<p class="meta">An approver creates flows.</p>`}</section>`;
80
78
  }
81
79
  /** A template's page: its questions, then what it will do and never do, then Create. */
82
80
  export function galleryUseHtml(input) {
@@ -86,43 +84,30 @@ export function galleryUseHtml(input) {
86
84
  const missing = tools.filter(one => one.state !== "connected" && one.state !== "taken");
87
85
  // Each tool with its state; one the project lacks offers its own Connect (the Tools page's, with the same password), and comes back here.
88
86
  const connect = (tool) => {
89
- const head = `<p class="gallery-tool-head">${toolMark(tool)} <strong>${e(tool.label)}</strong> ${toolState(tool)}</p>`;
87
+ const head = html `<p class="gallery-tool-head">${toolMark(tool)} <strong>${tool.label}</strong> ${toolState(tool)}</p>`;
90
88
  // Added another way, it is there: nothing to connect.
91
89
  if (tool.state === "connected" || tool.state === "taken" || input.csrf === "")
92
- return `<div class="gallery-connect" data-tool="${e(tool.id)}" data-state="${tool.state}">${head}</div>`;
90
+ return html `<div class="gallery-connect" data-tool="${tool.id}" data-state="${String(tool.state)}">${head}</div>`;
93
91
  const how = tool.id === "figma-desktop" ? "Open the Figma desktop app and turn on its Dev Mode MCP server in Preferences." : `You sign in on ${tool.label}, then come back here.`;
94
- return `<div class="gallery-connect" data-tool="${e(tool.id)}" data-state="open">${head}<form method="post" action="/settings/tools/connect">` +
95
- `<input type="hidden" name="csrf" value="${e(input.csrf)}"><input type="hidden" name="repo" value="${e(input.repo)}"><input type="hidden" name="shown" value="${e(input.repo)}"><input type="hidden" name="template" value="${e(template.id)}">` +
96
- `<p class="meta">${e(how)}</p><label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>` +
97
- `<button name="service" value="${e(tool.id)}">Connect ${e(tool.label)}</button></form></div>`;
92
+ return html `<div class="gallery-connect" data-tool="${tool.id}" data-state="open">${head}${postForm("/settings/tools/connect", html `<p class="meta">${how}</p><label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label><button name="service" value="${tool.id}">Connect ${tool.label}</button>`, { hidden: { repo: input.repo, shown: input.repo, template: template.id } })}</div>`;
98
93
  };
99
- const toolsBlock = tools.length === 0 ? "" : `<section class="gallery-tools-use" aria-label="Tools">${tools.map(connect).join("")}</section>`;
94
+ const toolsBlock = tools.length === 0 ? "" : html `<section class="gallery-tools-use" aria-label="Tools">${tools.map(connect)}</section>`;
100
95
  // Made without a tool, the preview names the zone that needs it.
101
- const zones = (tool) => tool.zones.map(zone => `“${e(zone)}”`).join(" and ");
102
- const needing = missing.filter(one => one.zones.length > 0).map(one => `<p class="meta" data-needs-tool="${e(one.id)}">${zones(one)} need${one.zones.length === 1 ? "s" : ""} ${e(one.label)}, which isn't connected.</p>`).join("") +
103
- tools.filter(one => one.state === "taken" && one.zones.length > 0).map(one => `<p class="meta" data-tool-taken="${e(one.id)}">${zones(one)} use${one.zones.length === 1 ? "s" : ""} ${e(one.label)}, added another way.</p>`).join("");
96
+ const zones = (tool) => joinHtml(tool.zones.map(zone => html `“${zone}”`), " and ");
97
+ const needing = html `${missing.filter(one => one.zones.length > 0).map(one => html `<p class="meta" data-needs-tool="${one.id}">${zones(one)} need${one.zones.length === 1 ? "s" : ""} ${one.label}, which isn't connected.</p>`)}${tools.filter(one => one.state === "taken" && one.zones.length > 0).map(one => html `<p class="meta" data-tool-taken="${one.id}">${zones(one)} use${one.zones.length === 1 ? "s" : ""} ${one.label}, added another way.</p>`)}`;
104
98
  const field = (ask) => {
105
99
  const value = input.answers[ask.key] ?? ask.default;
106
- const hint = ask.hint === undefined ? "" : ` <small>${e(ask.hint)}</small>`;
100
+ const hint = ask.hint === undefined ? "" : html ` <small>${ask.hint}</small>`;
107
101
  if (ask.key === "outdated")
108
- return `<label>${e(ask.label)}<select name="outdated">${Object.entries(OUTDATED_COMMANDS).map(([id, one]) => `<option value="${id}"${id === value ? " selected" : ""}>${e(one.label)}</option>`).join("")}</select></label>`;
109
- return `<label>${e(ask.label)}${hint}<input name="${ask.key}" value="${e(value)}" required maxlength="${ask.key === "command" ? 500 : ask.key === "team" ? 12 : 100}"${ask.key === "command" ? ' spellcheck="false" autocapitalize="off"' : ask.key === "team" ? ' spellcheck="false" autocapitalize="characters"' : ""}></label>`;
102
+ return html `<label>${ask.label}<select name="outdated">${Object.entries(OUTDATED_COMMANDS).map(([id, one]) => html `<option value="${id}"${id === value && html ` selected`}>${one.label}</option>`)}</select></label>`;
103
+ return html `<label>${ask.label}${hint}<input name="${ask.key}" value="${value}" required maxlength="${ask.key === "command" ? 500 : ask.key === "team" ? 12 : 100}"${ask.key === "command" ? html ` spellcheck="false" autocapitalize="off"` : ask.key === "team" ? html ` spellcheck="false" autocapitalize="characters"` : ""}></label>`;
110
104
  };
111
105
  const project = input.projects.length > 1
112
- ? `<label>Project<select name="repo">${input.projects.map(one => `<option value="${e(one.path)}"${one.path === input.repo ? " selected" : ""}>${e(one.name)}</option>`).join("")}</select></label>`
113
- : `<input type="hidden" name="repo" value="${e(input.repo)}">`;
114
- const preview = input.preview === null ? "" : `<section class="gallery-preview" aria-labelledby="gallery-will">` +
115
- `<h2 id="gallery-will">What it will do</h2><ul>${input.preview.built.does.map(one => `<li>${e(one)}</li>`).join("")}</ul>` +
116
- `<p class="gallery-never">${e(input.preview.built.never)}</p>${needing}` +
117
- (input.preview.startsFrom.length === 0 ? `<p class="meta">Cards start when you add them.</p>` : input.preview.startsFrom.map(one => `<p class="meta">Starts from: ${e(one)}</p>`).join("")) +
118
- `<details><summary>Every step</summary><ol>${input.preview.steps.map(one => `<li>${e(one.replace(/^\d+\.\s/, ""))}</li>`).join("")}</ol></details></section>`;
119
- return `<section class="gallery-use" data-gallery-template="${e(template.id)}">` +
120
- (input.problem === null ? "" : `<p class="problem" role="alert">${e(input.problem)}</p>`) + (input.said ? `<p role="status">${e(input.said)}</p>` : "") +
121
- `<p>${e(template.promise)}</p>${input.diagram === null ? "" : stepStrip(input.diagram)}${needsList(template)}${toolsBlock}` +
122
- `<form method="post" action="/flows/new/${e(template.id)}" data-gallery-use><input type="hidden" name="csrf" value="${e(input.csrf)}">` +
123
- `<input type="hidden" name="previewed" value="${e(input.preview?.digest ?? "")}">${project}${template.asks.map(field).join("")}` +
124
- `<label>Name<input name="name" value="${e(input.name)}" maxlength="80"></label>` +
125
- (sendsAlready(template) ? "" : `<label class="gallery-check"><input type="checkbox" name="${SEND_RESULT.key}" value="yes"${input.answers[SEND_RESULT.key] === "yes" ? " checked" : ""} data-send-result><span>${e(SEND_RESULT.title)}<small>When a card finishes, what was done comes to you in your chat apps.</small></span></label>`) + preview +
126
- `<p class="gallery-actions">${input.preview === null ? "" : `<button type="submit" name="intent" value="create"${missing.length > 0 ? ' class="secondary" data-without-tools' : ""}>Create flow</button>`}<button type="submit" name="intent" value="preview" class="secondary">${input.preview === null ? "Preview" : "Update preview"}</button></p>` +
127
- `</form><p class="meta"><a href="/flows/new">All templates</a></p></section>`;
106
+ ? html `<label>Project<select name="repo">${input.projects.map(one => html `<option value="${one.path}"${one.path === input.repo && html ` selected`}>${one.name}</option>`)}</select></label>`
107
+ : html `<input type="hidden" name="repo" value="${input.repo}">`;
108
+ const preview = input.preview === null ? "" : html `<section class="gallery-preview" aria-labelledby="gallery-will"><h2 id="gallery-will">What it will do</h2><ul>${input.preview.built.does.map(one => html `<li>${one}</li>`)}</ul><p class="gallery-never">${input.preview.built.never}</p>${needing}${input.preview.startsFrom.length === 0 ? html `<p class="meta">Cards start when you add them.</p>` : input.preview.startsFrom.map(one => html `<p class="meta">Starts from: ${one}</p>`)}<details><summary>Every step</summary><ol>${input.preview.steps.map(one => html `<li>${one.replace(/^\d+\.\s/, "")}</li>`)}</ol></details></section>`;
109
+ const sendResult = !sendsAlready(template) && html `<label class="gallery-check"><input type="checkbox" name="${SEND_RESULT.key}" value="yes"${input.answers[SEND_RESULT.key] === "yes" && html ` checked`} data-send-result><span>${SEND_RESULT.title}<small>When a card finishes, what was done comes to you in your chat apps.</small></span></label>`;
110
+ const actions = html `<p class="gallery-actions">${input.preview !== null && html `<button type="submit" name="intent" value="create"${missing.length > 0 && html ` class="secondary" data-without-tools`}>Create flow</button>`}<button type="submit" name="intent" value="preview" class="secondary">${input.preview === null ? "Preview" : "Update preview"}</button></p>`;
111
+ const form = postForm(`/flows/new/${template.id}`, html `${project}${template.asks.map(field)}<label>Name<input name="name" value="${input.name}" maxlength="80"></label>${sendResult}${preview}${actions}`, { attrs: { "data-gallery-use": true }, hidden: { previewed: input.preview?.digest ?? "" } });
112
+ return html `<section class="gallery-use" data-gallery-template="${template.id}">${input.problem !== null && html `<p class="problem" role="alert">${input.problem}</p>`}${input.said ? html `<p role="status">${input.said}</p>` : ""}<p>${template.promise}</p>${input.diagram !== null && stepStrip(input.diagram)}${needsList(template)}${toolsBlock}${form}<p class="meta"><a href="/flows/new">All templates</a></p></section>`;
128
113
  }
@@ -58,7 +58,7 @@ export type StepRunInsight = {
58
58
  entry: number;
59
59
  zone: string;
60
60
  zoneTitle: string;
61
- kind: "check" | "update" | "sort" | "draft" | "request" | "email" | "tool" | "teammate";
61
+ kind: "check" | "update" | "sort" | "draft" | "request" | "email" | "tool" | "subagent";
62
62
  script: string | null;
63
63
  version: number | null;
64
64
  state: FlowStepRunRow["state"];