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
@@ -24,7 +24,7 @@ import { addFlowTriggerTo, describeTrigger, HOOK_PATH, readHooksBase, triggerCon
24
24
  import { durationMinutes, durationWords, FlowContractError, flowTerms, inStepWords, LANGUAGE_WORDS, validateFlowDefinition } from "./flows.js";
25
25
  import { stageReferenceProblems } from "./contracts/stage-output.js";
26
26
  import { readVersioned } from "./contracts/contract.js";
27
- import { FLOW_ALIASES, FLOW_FILE_FORMAT, FLOW_FILE_VERSION, flowFileSchema, PARAMETER_ID } from "./contracts/flow.js";
27
+ import { FLOW_ALIASES, FLOW_FILE_FORMAT, legacyFlowNeed, legacySubagentStep, FLOW_FILE_VERSION, flowFileSchema, PARAMETER_ID } from "./contracts/flow.js";
28
28
  import { parseSchedule } from "./flow-schedule.js";
29
29
  export { FLOW_FILE_FORMAT, FLOW_FILE_VERSION };
30
30
  /** The largest flow file Toolroll reads or fetches. */
@@ -38,7 +38,7 @@ function stepOf(stage, ask) {
38
38
  const step = { id: stage.id, title: stage.title, kind: stage.kind };
39
39
  const words = (minutes) => durationMinutes(durationWords(minutes)) === minutes ? durationWords(minutes) : minutes;
40
40
  // Only what the step's kind says: words a zone kept from a kind it was before stay behind.
41
- if (stage.instructions !== null && (stage.kind === "task" || stage.kind === "report" || stage.kind === "draft" || stage.kind === "teammate"))
41
+ if (stage.instructions !== null && (stage.kind === "task" || stage.kind === "report" || stage.kind === "draft" || stage.kind === "subagent"))
42
42
  step["instructions"] = stage.instructions;
43
43
  if (stage.kind === "task")
44
44
  step["planning"] = stage.planning;
@@ -79,8 +79,8 @@ function stepOf(stage, ask) {
79
79
  ...(stage.wait.for === "hours" ? { from: stage.wait.from, until: stage.wait.to, ...(stage.wait.timeZone === undefined ? {} : { timeZone: stage.wait.timeZone }) } : { wait: words(stage.wait.minutes) }) });
80
80
  if (stage.merge !== undefined)
81
81
  step["merge"] = stage.merge;
82
- if (stage.teammate !== undefined)
83
- step["teammate"] = stage.teammate;
82
+ if (stage.subagent !== undefined)
83
+ step["subagent"] = stage.subagent;
84
84
  if (stage.reply === true)
85
85
  step["reply"] = true;
86
86
  // A choice's buttons; "end" ignores the card. A build in another project names a path on this computer, so it stays here.
@@ -135,8 +135,8 @@ function needsOf(definition, triggers, scripts) {
135
135
  needs.add("email");
136
136
  if (stage.tool !== undefined)
137
137
  needs.add(`tool:${stage.tool.server}`);
138
- if (stage.teammate !== undefined)
139
- needs.add(`teammate:${stage.teammate}`);
138
+ if (stage.subagent !== undefined)
139
+ needs.add(`subagent:${stage.subagent}`);
140
140
  for (const name of stage.secrets ?? [])
141
141
  needs.add(`secret:${name}`);
142
142
  for (const value of Object.values(stage.request?.headers ?? {}))
@@ -296,7 +296,7 @@ function stageInputOf(step, index, find) {
296
296
  ...(kind === "email" ? { email: { to: step["to"], subject: step["subject"], body: step["body"] } } : {}),
297
297
  ...(kind === "tool" ? { tool: { server: step["server"], name: step["tool"], args: typeof step["args"] === "object" && step["args"] !== null ? JSON.stringify(step["args"]) : step["args"] } } : {}),
298
298
  ...(kind === "wait" ? { wait: step["waitFor"] === "hours" ? { for: "hours", from: step["from"], to: step["until"], timeZone: step["timeZone"] } : { for: step["waitFor"] ?? "reply", minutes: minutes(step["wait"] ?? "3 days", "wait") } } : {}),
299
- merge: step["merge"], teammate: step["teammate"], reply: step["reply"],
299
+ merge: step["merge"], subagent: step["subagent"], reply: step["reply"],
300
300
  ...(kind === "choose" ? { options: (list("options") ?? []).map((one, n) => ({ label: one["label"], to: one["goesTo"] === "end" ? "end" : find(one["goesTo"], `${at}.options[${n}].goesTo`) })) } : {}),
301
301
  ...(step["remindAfter"] === undefined ? {} : { limit: { minutes: minutes(step["remindAfter"], "remindAfter"), to: find(step["thenMoveTo"], `${at}.thenMoveTo`) } }),
302
302
  next: find(step["next"], `${at}.next`), onFail: find(fail, `${at}.${failKey}`),
@@ -371,6 +371,11 @@ export function parseFlowFile(text) {
371
371
  if (kind === "flow")
372
372
  refuse(`triggers[${index}].kind: a trigger from another flow can't come from a file; it names a flow on the installation it was made on`);
373
373
  });
374
+ // D5: a file written before subagents were named so (a "teammate" zone, a "teammate:" need) reads as one written since.
375
+ if (Array.isArray(input["zones"]))
376
+ input["zones"] = input["zones"].map(legacySubagentStep);
377
+ if (Array.isArray(input["needs"]))
378
+ input["needs"] = input["needs"].map(legacyFlowNeed);
374
379
  const read = readVersioned(flowFileSchema, input, {}, FLOW_ALIASES);
375
380
  if (!read.ok)
376
381
  return refuse(read.issues.map(one => one.line).join("\n"));
@@ -443,7 +448,7 @@ const NEED_WORDS = {
443
448
  };
444
449
  export function needWords(need) {
445
450
  const [kind, name] = need.includes(":") ? [need.slice(0, need.indexOf(":")), need.slice(need.indexOf(":") + 1)] : [need, ""];
446
- return NEED_WORDS[need] ?? (kind === "secret" ? `A saved secret called ${name}` : kind === "tool" ? `The ${name} tool (Settings → Tools)` : kind === "teammate" ? `An AI teammate called ${name}` : kind === "script" ? `The ${name} script` : need);
451
+ return NEED_WORDS[need] ?? (kind === "secret" ? `A saved secret called ${name}` : kind === "tool" ? `The ${name} tool (Settings → Tools)` : kind === "subagent" ? `A subagent called ${name}` : kind === "script" ? `The ${name} script` : need);
447
452
  }
448
453
  /** The import in plain words, checked against the project it lands in. Throws FlowFileError. */
449
454
  export function planFlowImport(store, repo, file, given, actor) {
@@ -4,6 +4,7 @@
4
4
  * that's on links to its flow; one that can't run here says why.
5
5
  */
6
6
  import type { StarterView } from "./flow-starters.js";
7
+ import { type Html } from "./html.js";
7
8
  export declare const STARTERS_CSS: string;
8
9
  export declare function startersHtml(input: {
9
10
  repo: string;
@@ -17,4 +18,4 @@ export declare function startersHtml(input: {
17
18
  suggested: string | null;
18
19
  said: string | null;
19
20
  problem: string | null;
20
- }): string;
21
+ }): Html;
@@ -1,4 +1,4 @@
1
- const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
1
+ import { html, postForm } from "./html.js";
2
2
  export const STARTERS_CSS = `.starters{max-width:760px;min-width:0}.starters h2{margin:24px 0 4px;font-size:.9375rem}` +
3
3
  `.starter{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:4px 16px;align-items:start;padding:14px 0;border-top:1px solid var(--so-line);min-width:0}` +
4
4
  `.starter:first-of-type{border-top:0}.starter>:not(.starter-action){grid-column:1}.starter h3{margin:0;font-size:.9375rem;display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center}` +
@@ -13,23 +13,20 @@ export const STARTERS_CSS = `.starters{max-width:760px;min-width:0}.starters h2{
13
13
  `@media (max-width:560px){.starter{grid-template-columns:minmax(0,1fr)}.starter .starter-action{grid-column:1;grid-row:auto;margin-top:10px}` +
14
14
  `.starter .starter-action button,.starter .starter-action .button-link{width:100%;justify-content:center}.starters select{font-size:16px}}`;
15
15
  export function startersHtml(input) {
16
- const note = input.problem ? `<p class="problem" role="alert">${e(input.problem)}</p>` : input.said ? `<p role="status">${e(input.said)}</p>` : "";
16
+ const note = input.problem ? html `<p class="problem" role="alert">${input.problem}</p>` : input.said ? html `<p role="status">${input.said}</p>` : "";
17
17
  const selector = input.projects.length > 1
18
- ? `<form class="starters-project" method="get" action="/settings/flows"><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><button>Show</button></form>` : "";
18
+ ? html `<form class="starters-project" method="get" action="/settings/flows"><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><button>Show</button></form>` : "";
19
19
  // The one asked about ("Do this every time…") comes first, marked.
20
20
  const ordered = [...input.starters].sort((a, b) => Number(b.id === input.suggested) - Number(a.id === input.suggested));
21
21
  const rows = ordered.map(one => {
22
- const action = one.on !== null ? `<p class="starter-action"><a class="button-link starter-open" href="/flows/${one.on.flow}">Open flow</a></p>`
22
+ const action = one.on !== null ? html `<p class="starter-action"><a class="button-link starter-open" href="/flows/${one.on.flow}">Open flow</a></p>`
23
23
  : one.blocked !== null || !input.canSwitch ? ""
24
- : `<form method="post" action="/settings/flows/on" class="starter-action"><input type="hidden" name="csrf" value="${e(input.csrf)}"><input type="hidden" name="repo" value="${e(input.repo)}">` +
25
- `<input type="hidden" name="starter" value="${e(one.id)}"><button type="submit">Switch on</button></form>`;
26
- return `<div class="starter" id="starter-${e(one.id)}" data-starter="${e(one.id)}" data-suggested="${one.id === input.suggested && one.on === null}">` +
27
- `<h3>${e(one.name)}${one.on === null ? "" : ` <span class="starter-on"><i aria-hidden="true"></i>On</span>`}</h3>` +
28
- `<p>${e(one.summary)}</p>` +
29
- // What the yes agrees to, in full, before it; nothing to agree to once it's on or can't be.
30
- (one.on === null && one.blocked === null ? `<ul>${one.does.map(line => `<li>${e(line)}</li>`).join("")}</ul><p class="starter-never">${e(one.never)}</p>` : "") +
31
- (one.on === null && one.blocked !== null ? `<p class="starter-blocked">${e(one.blocked)}</p>` : "") + action + `</div>`;
32
- }).join("");
33
- const nobody = !input.canSwitch && input.starters.some(one => one.on === null) ? `<p class="meta">An approver switches these on.</p>` : "";
34
- return `<section class="starters">${note}${selector}<h2>Starter flows</h2>${rows}${nobody}<p class="meta"><a href="/flows">All flows</a></p></section>`;
24
+ : postForm("/settings/flows/on", html `<button type="submit">Switch on</button>`, { attrs: { class: "starter-action" }, hidden: { repo: input.repo, starter: one.id } });
25
+ // What the yes agrees to, in full, before it; nothing to agree to once it's on or can't be.
26
+ const agrees = one.on === null && one.blocked === null && html `<ul>${one.does.map(line => html `<li>${line}</li>`)}</ul><p class="starter-never">${one.never}</p>`;
27
+ const head = html `<h3>${one.name}${one.on === null ? "" : html ` <span class="starter-on"><i aria-hidden="true"></i>On</span>`}</h3>`;
28
+ return html `<div class="starter" id="starter-${one.id}" data-starter="${one.id}" data-suggested="${String(one.id === input.suggested && one.on === null)}">${head}<p>${one.summary}</p>${agrees}${one.on === null && one.blocked !== null && html `<p class="starter-blocked">${one.blocked}</p>`}${action}</div>`;
29
+ });
30
+ const nobody = !input.canSwitch && input.starters.some(one => one.on === null) ? html `<p class="meta">An approver switches these on.</p>` : "";
31
+ return html `<section class="starters">${note}${selector}<h2>Starter flows</h2>${rows}${nobody}<p class="meta"><a href="/flows">All flows</a></p></section>`;
35
32
  }
@@ -2,8 +2,8 @@ import type { Runner } from "./backend.js";
2
2
  import { type DraftRunner } from "./flow-draft.js";
3
3
  import { type MailSender, type ToolCaller } from "./flow-actions.js";
4
4
  import type { Store } from "./store.js";
5
- import type { TurnRunner } from "./teammates.js";
6
- import type { ToolLister } from "./teammate-tools.js";
5
+ import type { TurnRunner } from "./subagents.js";
6
+ import type { ToolLister } from "./subagent-tools.js";
7
7
  export type StepIo = {
8
8
  /** `gh` for GitHub; git and the check's shell, both without a model. */
9
9
  gh: Runner;
@@ -27,9 +27,9 @@ export type StepIo = {
27
27
  callTool?: ToolCaller;
28
28
  /** Where the project tools' secrets live (default: this computer's home). */
29
29
  toolHome?: string;
30
- /** Takes a teammate's turn (default: Claude through this computer's sign-in, answering in its fixed shape). */
31
- teammate?: TurnRunner;
32
- /** Lists what a project tool offers a teammate (default: starts its MCP server and asks). */
30
+ /** Takes a subagent's turn (default: Claude through this computer's sign-in, answering in its fixed shape). */
31
+ subagent?: TurnRunner;
32
+ /** Lists what a project tool offers a subagent (default: starts its MCP server and asks). */
33
33
  listTools?: ToolLister;
34
34
  };
35
35
  export type StepPass = {
@@ -40,14 +40,14 @@ import { agentFence } from "./agent-fence.js";
40
40
  import { readProviderKey } from "./keys.js";
41
41
  import { replyInChannel } from "./chat-inbox.js";
42
42
  import { recordSent, threadOf } from "./flow-replies.js";
43
- import { ASKED, teammateReady, teammateTurn } from "./teammate-work.js";
43
+ import { ASKED, subagentReady, subagentTurn } from "./subagent-work.js";
44
44
  import { budgetHoldWords, claudeMachineBilling, monthOf } from "./spend.js";
45
45
  import { toolRefusal } from "./policy.js";
46
46
  import { pullRequestStep } from "./flow-pull-request.js";
47
47
  import { withStageHandoff } from "./contracts/stage-output.js";
48
48
  const RETRY_MS = [5 * 60_000, 15 * 60_000];
49
- /** A teammate's turn tries again sooner: a card waiting on a person's colleague shouldn't wait long for a hiccup. */
50
- const TEAMMATE_RETRY_MS = [60_000, 5 * 60_000];
49
+ /** A subagent's turn tries again sooner: a card waiting on a person's colleague shouldn't wait long for a hiccup. */
50
+ const SUBAGENT_RETRY_MS = [60_000, 5 * 60_000];
51
51
  const OUTPUT_CHARS = 3000;
52
52
  /** How much of a step's output its run keeps as the log. */
53
53
  const LOG_CHARS = 64_000;
@@ -71,9 +71,9 @@ export async function runFlowSteps(store, repo, now, io) {
71
71
  const stage = known.definition?.stages.find(one => one.id === card.stage);
72
72
  if (stage === undefined)
73
73
  continue;
74
- // v92: a teammate's turn — a zone it handles, or a decision it staffs (a person decides when it's paused or gone).
75
- if (stage.kind === "teammate" || (stage.kind === "approval" && stage.teammate !== undefined)) {
76
- await teammateStep(store, known.flow, known.definition, stage, card, now, io, pass);
74
+ // v92: a subagent's turn — a zone it handles, or a decision it staffs (a person decides when it's paused or gone).
75
+ if (stage.kind === "subagent" || (stage.kind === "approval" && stage.subagent !== undefined)) {
76
+ await subagentStep(store, known.flow, known.definition, stage, card, now, io, pass);
77
77
  continue;
78
78
  }
79
79
  // A pull request: opened for the card's result, then CI decides where it goes (flow-pull-request.ts).
@@ -108,7 +108,7 @@ export async function runFlowSteps(store, repo, now, io) {
108
108
  // (or everything) that holds key work holds them.
109
109
  const agents = stage.kind === "sort" ? [{ provider: "openrouter", billing: "api-key" }]
110
110
  : stage.kind === "draft" ? [{ provider: "claude", billing: claudeMachineBilling(store.handle) }] : null;
111
- const held = agents === null ? null : budgetGate({ project: repo, person: null, teammate: null, agents });
111
+ const held = agents === null ? null : budgetGate({ project: repo, person: null, subagent: null, agents });
112
112
  if (held !== null && held.over !== null) {
113
113
  const waiting = `${budgetHoldWords(held, monthOf(now).name)}. Raise it on Spend to go on.`;
114
114
  if (card.waiting !== waiting)
@@ -183,32 +183,32 @@ function claimStep(store, card, step, now) {
183
183
  return store.claimFlowStep({ card: card.id, entry: card.entry, ...step }, now);
184
184
  });
185
185
  }
186
- /** One teammate turn, when one is due for this card: claimed like any step, retried on failure, and waiting while its question is open. */
187
- async function teammateStep(store, flow, definition, stage, card, now, io, pass) {
188
- // v102: on a protected project an AI teammate never decides an approval; the engine asks the person.
186
+ /** One subagent turn, when one is due for this card: claimed like any step, retried on failure, and waiting while its question is open. */
187
+ async function subagentStep(store, flow, definition, stage, card, now, io, pass) {
188
+ // v102: on a protected project a subagent never decides an approval; the engine asks the person.
189
189
  if (stage.kind === "approval" && store.approvalRules(flow.repo).protectProject)
190
190
  return true;
191
- const mate = stage.teammate === undefined ? null : store.teammateByHandle(flow.repo, stage.teammate);
192
- const ready = teammateReady(store, mate, now);
191
+ const mate = stage.subagent === undefined ? null : store.subagentByHandle(flow.repo, stage.subagent);
192
+ const ready = subagentReady(store, mate, now);
193
193
  if (!ready.ok) {
194
194
  // A decision goes to its person (the engine asks them). A zone only it handles waits, saying why.
195
195
  if (stage.kind === "approval")
196
196
  return true;
197
- const waiting = ready.why === "gone" ? `There's no teammate called ${stage.teammate ?? "(none)"} in this project.` : `${mate.handle} is ${ready.why === "paused" ? "paused" : `waiting: ${ready.why}`}.`;
197
+ const waiting = ready.why === "gone" ? `There's no subagent called ${stage.subagent ?? "(none)"} in this project.` : `${mate.handle} is ${ready.why === "paused" ? "paused" : `waiting: ${ready.why}`}.`;
198
198
  if (card.waiting !== waiting)
199
199
  store.updateFlowCard(card.id, { waiting }, now);
200
200
  return true;
201
201
  }
202
202
  // Waiting on a person: its own question, or (v94) a tool call they approve first.
203
- if (store.openTeammateQuestionOn(card.id, card.entry) !== null)
203
+ if (store.openSubagentQuestionOn(card.id, card.entry) !== null)
204
204
  return true;
205
- if (!claimStep(store, card, { stage: stage.id, kind: "teammate", script: null, scriptVersion: null }, now))
205
+ if (!claimStep(store, card, { stage: stage.id, kind: "subagent", script: null, scriptVersion: null }, now))
206
206
  return true;
207
207
  pass.ran++;
208
208
  const started = Date.now();
209
209
  let outcome;
210
210
  try {
211
- outcome = await teammateTurn(store, flow, definition, stage, card, mate, now, { ...(io.teammate === undefined ? {} : { turn: io.teammate }), ...(io.evidenceRoot === undefined ? {} : { evidenceRoot: io.evidenceRoot }),
211
+ outcome = await subagentTurn(store, flow, definition, stage, card, mate, now, { ...(io.subagent === undefined ? {} : { turn: io.subagent }), ...(io.evidenceRoot === undefined ? {} : { evidenceRoot: io.evidenceRoot }),
212
212
  ...(io.callTool === undefined ? {} : { callTool: io.callTool }), ...(io.listTools === undefined ? {} : { listTools: io.listTools }), ...(io.toolHome === undefined ? {} : { toolHome: io.toolHome }) });
213
213
  }
214
214
  catch (error) {
@@ -218,15 +218,15 @@ async function teammateStep(store, flow, definition, stage, card, now, io, pass)
218
218
  const kept = { log: outcome.log === undefined ? null : keptLog(outcome.log), durationMs: Date.now() - started, ...(outcome.decisionJson === undefined ? {} : { decisionJson: outcome.decisionJson }) };
219
219
  if (outcome.state === "retry") {
220
220
  const attempts = run?.attempts ?? 1;
221
- if (attempts <= TEAMMATE_RETRY_MS.length) {
222
- store.finishFlowStep(card.id, card.entry, { state: "waiting", result: outcome.said, nextAt: new Date(now.getTime() + TEAMMATE_RETRY_MS[attempts - 1]).toISOString(), ...kept }, now);
221
+ if (attempts <= SUBAGENT_RETRY_MS.length) {
222
+ store.finishFlowStep(card.id, card.entry, { state: "waiting", result: outcome.said, nextAt: new Date(now.getTime() + SUBAGENT_RETRY_MS[attempts - 1]).toISOString(), ...kept }, now);
223
223
  if (stage.kind !== "approval")
224
224
  store.updateFlowCard(card.id, { waiting: `${outcome.said} Trying again in ${attempts === 1 ? "a minute" : "5 minutes"}.` }, now);
225
225
  pass.problems.push(`flow card ${card.id}: ${outcome.said}`);
226
226
  return true;
227
227
  }
228
228
  outcome = { state: "failed", said: `${outcome.said} It didn't work after three tries.` };
229
- store.addTeammateEvent({ teammate: mate.id, card: card.id, entry: card.entry, kind: "failed", said: `Couldn't take its turn on “${card.title}”: ${outcome.said}` }, now);
229
+ store.addSubagentEvent({ subagent: mate.id, card: card.id, entry: card.entry, kind: "failed", said: `Couldn't take its turn on “${card.title}”: ${outcome.said}` }, now);
230
230
  if (stage.kind !== "approval")
231
231
  store.updateFlowCard(card.id, { waiting: outcome.said }, now);
232
232
  }
@@ -155,7 +155,7 @@ export declare function planeStatusLink(config: TriggerConfig | null, status: st
155
155
  label: string;
156
156
  href: string;
157
157
  } | null;
158
- /** v96: a schedule (a teammate's routine) makes its card now, once per press, and keeps its own times. */
158
+ /** v96: a schedule (a subagent's routine) makes its card now, once per press, and keeps its own times. */
159
159
  export declare function runScheduleNow(store: Store, trigger: FlowTriggerRow, actor: string, now: Date): {
160
160
  ok: true;
161
161
  card: number;
@@ -208,6 +208,7 @@ export declare const FORM_PATH = "/hooks/form/";
208
208
  /** Share a button as a secret link anyone can fill in, without signing in. The link is shown once; only its hash is kept. */
209
209
  export declare function shareFlowButton(store: Store, trigger: FlowTriggerRow, now: Date, dir: string | null): TriggerMade;
210
210
  export declare function stopSharingFlowButton(store: Store, trigger: FlowTriggerRow, now: Date): void;
211
+ /** The page's bytes (the public route writes them itself, with its own headers). */
211
212
  export type FormAnswer = {
212
213
  status: number;
213
214
  html: string;
@@ -1,4 +1,5 @@
1
1
  import { adapterPolicy } from "./server/route-policy.js";
2
+ import { html, htmlString, postForm, styleElement } from "./html.js";
2
3
  /**
3
4
  * Flow triggers (v82): what starts cards in a flow without someone adding
4
5
  * them by hand.
@@ -730,7 +731,7 @@ export const NEEDS_YOU_HREF = "/work?view=needs-you";
730
731
  export function planeStatusLink(config, status) {
731
732
  return config?.kind === "plane-review" && status !== null && /waited over \d+ days for you\.$/.test(status) ? { label: "Needs you", href: NEEDS_YOU_HREF } : null;
732
733
  }
733
- /** v96: a schedule (a teammate's routine) makes its card now, once per press, and keeps its own times. */
734
+ /** v96: a schedule (a subagent's routine) makes its card now, once per press, and keeps its own times. */
734
735
  export function runScheduleNow(store, trigger, actor, now) {
735
736
  const config = triggerConfigOf(trigger);
736
737
  if (config?.kind !== "schedule" || config.script !== undefined)
@@ -1107,11 +1108,7 @@ export function shareFlowButton(store, trigger, now, dir) {
1107
1108
  export function stopSharingFlowButton(store, trigger, now) {
1108
1109
  store.updateFlowTrigger(trigger.id, { hookHash: null }, now);
1109
1110
  }
1110
- const esc = (value) => value.replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
1111
- /** The public page: the button's name and its questions, nothing about the flow, the project or anyone in it. */
1112
- function formHtml(label, body) {
1113
- return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="robots" content="noindex">
1114
- <title>${esc(label)}</title><style>
1111
+ const FORM_CSS = `
1115
1112
  :root{color-scheme:light dark;--bg:#f7f7f5;--card:#fff;--text:#1c1d1b;--muted:#666a63;--line:#dcded8;--accent:#2d5a45}
1116
1113
  @media (prefers-color-scheme:dark){:root{--bg:#141614;--card:#1d201d;--text:#ecefe9;--muted:#9ea39a;--line:#343934;--accent:#8fd0ae}}
1117
1114
  *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:16px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
@@ -1119,18 +1116,23 @@ main{max-width:560px;margin:0 auto;padding:40px 16px}h1{font-size:1.5rem;margin:
1119
1116
  label{display:grid;gap:6px;margin:0 0 16px;font-weight:600}input,textarea{font:inherit;color:inherit;background:transparent;border:1px solid var(--line);border-radius:10px;padding:10px 12px;width:100%}
1120
1117
  textarea{min-height:96px;resize:vertical}button{font:inherit;font-weight:600;border:0;border-radius:10px;padding:12px 18px;min-height:44px;background:var(--accent);color:var(--bg);cursor:pointer}
1121
1118
  p{color:var(--muted)}.trap{position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden}a{color:var(--accent)}
1122
- </style></head><body><main><h1>${esc(label)}</h1><div class="card">${body}</div></main></body></html>`;
1119
+ `;
1120
+ /** The public page: the button's name and its questions, nothing about the flow, the project or anyone in it. */
1121
+ function formHtml(label, body) {
1122
+ return htmlString(html `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="robots" content="noindex">
1123
+ <title>${label}</title>${styleElement(FORM_CSS)}</head><body><main><h1>${label}</h1><div class="card">${body}</div></main></body></html>`);
1123
1124
  }
1124
1125
  /** GET a shared form: its questions. */
1125
1126
  export function flowFormPage(store, token, now) {
1126
1127
  const trigger = /^[A-Za-z0-9_-]{20,64}$/.test(token) ? store.flowTriggerByHook(hookHash(token)) : null;
1127
1128
  const config = trigger === null ? null : triggerConfigOf(trigger);
1128
1129
  if (trigger === null || config?.kind !== "button")
1129
- return { status: 404, html: formHtml("Not found", "<p>This form isn't available. Ask whoever shared it for a new link.</p>") };
1130
+ return { status: 404, html: formHtml("Not found", html `<p>This form isn't available. Ask whoever shared it for a new link.</p>`) };
1130
1131
  if (!adapterPolicy({ caller: "anonymous", capability: "none" }, undefined, source => source === "flow" && store.getFlow(trigger.flow) !== null).ok)
1131
1132
  return { status: 403, html: "" };
1132
- const fields = config.questions.map((question, index) => `<label>${esc(question)}${index === 0 ? `<input name="a${index}" required maxlength="200" autocomplete="off">` : `<textarea name="a${index}" maxlength="3000"></textarea>`}</label>`).join("");
1133
- return { status: 200, html: formHtml(config.label, `<form method="post">${fields}<div class="trap" aria-hidden="true"><label>Leave this empty<input name="website" tabindex="-1" autocomplete="off"></label></div><input type="hidden" name="t" value="${now.getTime()}"><button>Send</button></form>`) };
1133
+ const fields = config.questions.map((question, index) => html `<label>${question}${index === 0 ? html `<input name="a${index}" required maxlength="200" autocomplete="off">` : html `<textarea name="a${index}" maxlength="3000"></textarea>`}</label>`);
1134
+ // Signed out by nature: no session, so no CSRF; the trap, the shown time and the hourly limit guard it. It posts back to its own address.
1135
+ return { status: 200, html: formHtml(config.label, postForm("", html `${fields}<div class="trap" aria-hidden="true"><label>Leave this empty<input name="website" tabindex="-1" autocomplete="off"></label></div><input type="hidden" name="t" value="${now.getTime()}"><button>Send</button>`, { signedOut: true })) };
1134
1136
  }
1135
1137
  /** POST a shared form: at most one card per submission, keys refused, a limit per hour, and quiet about what a bot did wrong. */
1136
1138
  export function receiveFlowForm(store, token, fields, now) {
@@ -1140,8 +1142,8 @@ export function receiveFlowForm(store, token, fields, now) {
1140
1142
  return flowFormPage(store, token, now);
1141
1143
  if (!adapterPolicy({ caller: "anonymous", capability: "none" }, undefined, source => source === "flow" && store.getFlow(trigger.flow) !== null).ok)
1142
1144
  return { status: 403, html: "" };
1143
- const again = `<p><a href="${esc(FORM_PATH + token)}">Send another</a></p>`;
1144
- const thanks = formHtml(config.label, `<p>Thanks — it's been sent.</p>${again}`);
1145
+ const again = html `<p><a href="${FORM_PATH + token}">Send another</a></p>`;
1146
+ const thanks = formHtml(config.label, html `<p>Thanks — it's been sent.</p>${again}`);
1145
1147
  // A filled trap, or a page posted faster than a person could, gets thanks and makes nothing.
1146
1148
  const form = readFormSubmission(fields, config.questions.length);
1147
1149
  const shown = Number(form.t);
@@ -1149,14 +1151,14 @@ export function receiveFlowForm(store, token, fields, now) {
1149
1151
  return { status: 200, html: thanks };
1150
1152
  const recent = store.handle.prepare("SELECT COUNT(*) AS n FROM flow_trigger_event WHERE trigger = ? AND key LIKE 'form:%' AND at > ?").get(trigger.id, new Date(now.getTime() - 3_600_000).toISOString());
1151
1153
  if (Number(recent?.["n"] ?? 0) >= FORM_CARDS_PER_HOUR)
1152
- return { status: 429, html: formHtml(config.label, `<p>Too many sent in the last hour. Try again later.</p>`) };
1154
+ return { status: 429, html: formHtml(config.label, html `<p>Too many sent in the last hour. Try again later.</p>`) };
1153
1155
  const answers = form.answers.map((answer, index) => (answer ?? "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").trim().slice(0, index === 0 ? 200 : 3000));
1154
1156
  if (answers[0] === "")
1155
- return { status: 400, html: formHtml(config.label, `<p>Answer “${esc(config.questions[0])}” first.</p>${again}`) };
1157
+ return { status: 400, html: formHtml(config.label, html `<p>Answer “${config.questions[0]}” first.</p>${again}`) };
1156
1158
  const details = config.questions.slice(1).map((question, index) => answers[index + 1] === "" ? null : `${question}\n${answers[index + 1]}`).filter(one => one !== null).join("\n\n");
1157
1159
  const made = makeCard(store, trigger, config, { key: `form:${now.toISOString()}:${randomUUID().slice(0, 8)}`, title: answers[0], description: `From the “${config.label}” form:\n\n${details}`.trim(), source: { kind: "form", label: `Form: ${config.label}`, url: null } }, "Form", now);
1158
1160
  if (made.made !== "added")
1159
- return { status: 400, html: formHtml(config.label, `<p>${made.note?.includes("key or password") ? "That looks like it holds a key or password. Take it out and send again." : "That couldn't be sent."}</p>${again}`) };
1161
+ return { status: 400, html: formHtml(config.label, html `<p>${made.note?.includes("key or password") ? "That looks like it holds a key or password. Take it out and send again." : "That couldn't be sent."}</p>${again}`) };
1160
1162
  store.updateFlowTrigger(trigger.id, { lastAt: now.toISOString(), lastOutcome: "Someone sent the form." }, now);
1161
1163
  return { status: 200, html: thanks };
1162
1164
  }
package/dist/flows-cli.js CHANGED
@@ -516,7 +516,7 @@ function zoneOf(stage, flow) {
516
516
  ...(stage.kind === 'approval' ? { decider: deciderOf(stage, flow) } : {}),
517
517
  ...(stage.script === null ? {} : { script: stage.script }),
518
518
  ...(stage.merge === undefined ? {} : { merge: stage.merge }),
519
- ...(stage.teammate === undefined ? {} : { teammate: stage.teammate }),
519
+ ...(stage.subagent === undefined ? {} : { subagent: stage.subagent }),
520
520
  ...(stage.options === undefined ? {} : { options: stage.options.map(one => ({ label: one.label, to: one.to })) }),
521
521
  ...(stage.repo === undefined ? {} : { repo: stage.repo }),
522
522
  };
@@ -3,9 +3,10 @@
3
3
  import type { BrowserFlowView } from "./browser-workspace.js";
4
4
  import type { FlowRow, Store } from "./store.js";
5
5
  import { type FlowFile, type FlowImportPlan } from "./flow-share.js";
6
+ import { type Html } from "./html.js";
6
7
  export declare 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}}";
7
8
  /** The flows a person can open, with how many cards wait in each, and the new-flow form. */
8
- export declare function flowsListHtml(store: Store, flows: readonly FlowRow[], projects: readonly string[], csrf: string, canCreate: boolean, problem: string | null): string;
9
+ export declare function flowsListHtml(store: Store, flows: readonly FlowRow[], projects: readonly string[], canCreate: boolean, problem: string | null): Html;
9
10
  /** Reads the chosen flow file into the form (the server reads it again, whole, before anything is made). */
10
11
  export declare 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>262144){say('That file is too big: a flow file is at most 256 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.');}});})();";
11
12
  /** An import's preview: the plan in plain words, what it asks for, and one Import button after the terms. */
@@ -18,7 +19,7 @@ export declare function flowImportHtml(input: {
18
19
  values: Record<string, string>;
19
20
  previewed: string;
20
21
  problem: string | null;
21
- }): string;
22
+ }): Html;
22
23
  /** One flow's canvas for one person. */
23
24
  export declare function flowView(store: Store, flow: FlowRow, viewer: {
24
25
  name: string;
@@ -31,4 +32,4 @@ export declare function flowView(store: Store, flow: FlowRow, viewer: {
31
32
  toolHome?: string;
32
33
  }): BrowserFlowView;
33
34
  /** The page the canvas replaces when scripts can't run: each zone and its cards, in order. */
34
- export declare function flowFallbackHtml(view: BrowserFlowView): string;
35
+ export declare function flowFallbackHtml(view: BrowserFlowView): Html;