@desplega.ai/agent-swarm 2.4.3 → 2.4.5

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 (270) hide show
  1. package/dist/{acp-adapter-1885dpr7.js → acp-adapter-2wdkktv6.js} +1 -2
  2. package/dist/{actions-pn76n32q.js → actions-c40ebcm4.js} +18 -18
  3. package/dist/{amp-adapter-af84rx1d.js → amp-adapter-wb67m95z.js} +2 -2
  4. package/dist/{app-6sv8mhzm.js → app-353vre8g.js} +3 -3
  5. package/dist/{assistant-sc2tf825.js → assistant-sj1njh23.js} +21 -21
  6. package/dist/{boot-reembed-y3m0ydzj.js → boot-reembed-b9c3az3c.js} +1 -1
  7. package/dist/{boot-reembed-gvxjs2py.js → boot-reembed-qdkvxeem.js} +1 -1
  8. package/dist/{boot-scrub-sweep-1j9mk05f.js → boot-scrub-sweep-m3wh8eks.js} +2 -2
  9. package/dist/{boot-seeds-37sp626b.js → boot-seeds-j2273s54.js} +4 -4
  10. package/dist/{claude-adapter-rp0jtnwr.js → claude-adapter-m7q0a085.js} +2 -2
  11. package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-5ptb6tzs.js} +2 -2
  12. package/dist/{claude-managed-setup-rdt248k7.js → claude-managed-setup-smrqk5j5.js} +3 -3
  13. package/dist/{claude-sdk-session-53rcw5we.js → claude-sdk-session-38xnz21e.js} +8284 -8267
  14. package/dist/{cli-6eyncg0y.js → cli-018r4jq6.js} +34 -6
  15. package/dist/{cli-sds5zzs8.js → cli-1f3qzw6y.js} +78 -12
  16. package/dist/{cli-f0p0f6ja.js → cli-1sp861s4.js} +7 -7
  17. package/dist/{cli-dm8p2dpf.js → cli-1syn2xy1.js} +10 -10
  18. package/dist/{cli-tkf4x6ha.js → cli-37j04k05.js} +11 -11
  19. package/dist/{cli-v87dr6e3.js → cli-3a45zcgm.js} +1 -1
  20. package/dist/{cli-1n9gdg56.js → cli-43x64hv3.js} +4 -4
  21. package/dist/{cli-p0qerd2r.js → cli-4zfqgjc5.js} +2 -2
  22. package/dist/{cli-aa3tb29r.js → cli-7c9qs530.js} +1 -1
  23. package/dist/{cli-9qtjfd60.js → cli-848hxdwj.js} +1 -1
  24. package/dist/{cli-1xsraagq.js → cli-94gwyg7m.js} +8 -8
  25. package/dist/{cli-0dxxwfkt.js → cli-9hstj09h.js} +17 -14
  26. package/dist/{cli-1012xb7j.js → cli-9yqgm9nd.js} +22 -22
  27. package/dist/{cli-pygdsgh8.js → cli-a92r1127.js} +1 -1
  28. package/dist/{cli-8vverzf7.js → cli-ba8ne1es.js} +5 -0
  29. package/dist/{cli-bxrqvpg5.js → cli-be2161h1.js} +7 -7
  30. package/dist/{cli-npfe9t1s.js → cli-bnes4z6b.js} +2 -2
  31. package/dist/{cli-aypbg9r2.js → cli-cxxe61f6.js} +1 -1
  32. package/dist/{cli-vq9gqarq.js → cli-dk44dxec.js} +1 -1
  33. package/dist/{cli-836ehxmt.js → cli-eww120g8.js} +3 -2
  34. package/dist/{cli-a0kcty51.js → cli-f01h25yt.js} +1 -1
  35. package/dist/{cli-mzkwkbdy.js → cli-f0g3pgvf.js} +1 -1
  36. package/dist/{cli-1ma335fz.js → cli-ftc1g5tj.js} +2 -2
  37. package/dist/{cli-4yef8ghv.js → cli-fv5hepyy.js} +3 -3
  38. package/dist/{cli-58vwn100.js → cli-g35m2xwm.js} +4 -4
  39. package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
  40. package/dist/{cli-gx0j2khs.js → cli-j99azg3g.js} +3 -3
  41. package/dist/{cli-er83cjbn.js → cli-k8g16gz3.js} +416 -116
  42. package/dist/{cli-qp2m4btv.js → cli-kpw04va0.js} +2 -2
  43. package/dist/{cli-8y99kfey.js → cli-ms85309d.js} +33 -15
  44. package/dist/{cli-kgnkyabk.js → cli-n72rg53p.js} +1555 -645
  45. package/dist/{cli-e4d78xsv.js → cli-p14m2cnp.js} +1 -1
  46. package/dist/{cli-1eszr343.js → cli-qfxc84gh.js} +1 -1
  47. package/dist/{cli-wk28hzqn.js → cli-qwhy2qma.js} +1 -1
  48. package/dist/{cli-73ezfhha.js → cli-st8aw1m6.js} +228 -11
  49. package/dist/{cli-zvbb4n0e.js → cli-tw4p90jb.js} +92 -192
  50. package/dist/{cli-038fga5k.js → cli-v0yrfmdh.js} +148 -183
  51. package/dist/{cli-6aen3ezb.js → cli-v8fjakwf.js} +1 -1
  52. package/dist/{cli-99jez72r.js → cli-w9etnnxk.js} +6 -1
  53. package/dist/{cli-vcgscaan.js → cli-wt0jf9hm.js} +217 -80
  54. package/dist/{cli-s9rr4gr2.js → cli-xpa64rek.js} +1065 -305
  55. package/dist/{cli-h0mgxann.js → cli-z7d29rw9.js} +2 -2
  56. package/dist/cli.js +45 -20
  57. package/dist/{codex-adapter-2zrmab9b.js → codex-adapter-tn6k5mkg.js} +1 -1
  58. package/dist/{codex-session-runner-kbkz62vc.js → codex-session-runner-7vjt7jbw.js} +1 -1
  59. package/dist/{commands-hd574th6.js → commands-t7rz9146.js} +2 -2
  60. package/dist/{controller-pz5mcfg4.js → controller-m2jasma5.js} +2 -2
  61. package/dist/{controllers-connect-5rgq2hfk.js → controllers-connect-9ftpex99.js} +19 -19
  62. package/dist/{cursor-adapter-mgaprq0j.js → cursor-adapter-mjv79e4z.js} +1 -1
  63. package/dist/{db-6nf1207q.js → db-6sfrn9g3.js} +1 -1
  64. package/dist/{db-h06dxp05.js → db-rn673r06.js} +3 -3
  65. package/dist/{dispatcher-zthpy4m1.js → dispatcher-c155xz0p.js} +17 -17
  66. package/dist/{dsh-adapter-64dhw0j7.js → dsh-adapter-3mmszc1x.js} +1 -1
  67. package/dist/{external-channel-x01t7ke6.js → external-channel-35k6tkv8.js} +2 -2
  68. package/dist/{grok-adapter-h21jv594.js → grok-adapter-tfrq7tg1.js} +2 -3
  69. package/dist/handlers-7gw64dxw.js +39 -0
  70. package/dist/{heartbeat-zzfksckz.js → heartbeat-dyg4npkp.js} +20 -20
  71. package/dist/{hook-z4zhzj1f.js → hook-drspbwpe.js} +1 -1
  72. package/dist/{hook-3xfqhh2s.js → hook-x26b31bg.js} +1 -1
  73. package/dist/{http-q813tmya.js → http-pk972xyw.js} +571 -294
  74. package/dist/{index-va3y63es.js → index-6srwvd5b.js} +1 -2
  75. package/dist/{index-x8xbyn1t.js → index-arytgj5q.js} +1 -1
  76. package/dist/{index-wbbs14c5.js → index-bgdyvcnv.js} +17 -17
  77. package/dist/{index-s98037v0.js → index-cbxkmfc5.js} +1 -2
  78. package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
  79. package/dist/{index-m0a4ydqj.js → index-gabbcdn2.js} +17 -17
  80. package/dist/{index-220tk9bw.js → index-jwxkkex2.js} +139 -76
  81. package/dist/index-tfb26zhy.js +41 -0
  82. package/dist/{keepalive-y5t1my7m.js → keepalive-7yrng7px.js} +3 -3
  83. package/dist/{lead-2xwtq66b.js → lead-7e02z839.js} +29 -30
  84. package/dist/{maintenance-ybfsaedv.js → maintenance-e6cemdqt.js} +1 -1
  85. package/dist/{node-3v3f08f7.js → node-de6akczr.js} +10 -9
  86. package/dist/{oauth-refresh-sweep-909jmn12.js → oauth-refresh-sweep-yw3xdbcj.js} +2 -2
  87. package/dist/{onboard-xn6fkkys.js → onboard-0a9y5yrc.js} +2 -2
  88. package/dist/{otel-impl-e6mn0z13.js → otel-impl-trjpkfb7.js} +1 -1
  89. package/dist/{pricing-refresh-ygd362ep.js → pricing-refresh-d2b5my0y.js} +3 -3
  90. package/dist/{rbac-roles-mb470p4e.js → rbac-roles-hey3yv9d.js} +2 -2
  91. package/dist/{rbac-roles-c9axvfnz.js → rbac-roles-nxh5ahwk.js} +2 -2
  92. package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
  93. package/dist/{render-v2-g0306nh1.js → render-v2-rr3p2atg.js} +3 -3
  94. package/dist/{resume-v315hnq6.js → resume-2efkf1s1.js} +5 -5
  95. package/dist/{session-access-2zg285a1.js → session-access-s2mttxpw.js} +6 -4
  96. package/dist/{setup-a4mtf02k.js → setup-vypma5yt.js} +1 -1
  97. package/dist/{work-objects-9fr0fgtn.js → work-objects-rvhckdkp.js} +20 -20
  98. package/dist/{worker-qae22jeb.js → worker-5snytpbd.js} +29 -30
  99. package/dist/worker-token-xf11yyds.js +136 -0
  100. package/openapi.json +303 -13
  101. package/package.json +8 -8
  102. package/src/apps/action-runner.ts +4 -0
  103. package/src/apps/catalog.generated.json +209 -4
  104. package/src/apps/page-validator.ts +21 -3
  105. package/src/be/chunking.ts +42 -9
  106. package/src/be/db/tasks/draft-text.ts +15 -0
  107. package/src/be/db/tasks/read.ts +14 -1
  108. package/src/be/db.ts +5 -1
  109. package/src/be/memory/index-content.ts +36 -17
  110. package/src/be/memory/providers/openai-embedding.ts +115 -61
  111. package/src/be/memory/providers/sqlite-store.ts +69 -23
  112. package/src/be/memory/reranker.ts +31 -1
  113. package/src/be/memory/search-candidates.ts +47 -0
  114. package/src/be/memory/types.ts +14 -0
  115. package/src/be/migrations/232_agent_session_tokens.sql +41 -0
  116. package/src/be/scripts/typecheck.ts +2 -0
  117. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +116 -55
  118. package/src/be/seed-scripts/index.ts +2 -2
  119. package/src/be/swarm-config-guard.ts +40 -1
  120. package/src/be/task-attachment-store.ts +30 -0
  121. package/src/be/users.ts +169 -12
  122. package/src/cli.tsx +24 -0
  123. package/src/commands/worker-token.ts +164 -0
  124. package/src/extensions/catalog.generated.json +4 -4
  125. package/src/extensions/contract-types.generated.ts +1 -1
  126. package/src/extensions/contract.ts +7 -0
  127. package/src/fs/provider.ts +13 -1
  128. package/src/heartbeat/heartbeat.ts +31 -11
  129. package/src/http/apps.ts +43 -5
  130. package/src/http/codex-oauth-keep-warm.ts +27 -9
  131. package/src/http/config.ts +5 -3
  132. package/src/http/core.ts +2 -0
  133. package/src/http/fs.ts +54 -34
  134. package/src/http/mcp-body.ts +37 -0
  135. package/src/http/mcp-user.ts +3 -5
  136. package/src/http/mcp.ts +6 -7
  137. package/src/http/memory.ts +26 -15
  138. package/src/http/page-proxy.ts +61 -5
  139. package/src/http/pages-public.ts +27 -44
  140. package/src/http/pages.ts +4 -4
  141. package/src/http/script-runs.ts +2 -0
  142. package/src/http/session-access.ts +51 -2
  143. package/src/http/sessions.ts +249 -10
  144. package/src/http/tasks.ts +4 -32
  145. package/src/http/users.ts +12 -1
  146. package/src/integrations/chat-sdk/adapters.ts +7 -3
  147. package/src/integrations/chat-sdk/attachments.ts +90 -12
  148. package/src/integrations/chat-sdk/facts.ts +120 -0
  149. package/src/integrations/chat-sdk/inbound.ts +156 -8
  150. package/src/integrations/chat-sdk/limits.ts +24 -0
  151. package/src/integrations/chat-sdk/outbound-files.ts +186 -0
  152. package/src/integrations/chat-sdk/outbound.ts +234 -4
  153. package/src/integrations/chat-sdk/relay.ts +4 -1
  154. package/src/integrations/chat-sdk/router.ts +28 -2
  155. package/src/integrations/chat-sdk/steering.ts +9 -0
  156. package/src/integrations/chat-sdk/templates.ts +14 -0
  157. package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
  158. package/src/mcp-limits.ts +20 -0
  159. package/src/pages/browser-sdk.ts +8 -3
  160. package/src/pages/feedback-overlay.ts +1 -1
  161. package/src/providers/acp-adapter.ts +33 -13
  162. package/src/providers/acp-swarm-events.ts +1 -1
  163. package/src/providers/amp-adapter.ts +1 -1
  164. package/src/providers/codex-adapter.ts +106 -119
  165. package/src/providers/codex-agents-md.ts +35 -229
  166. package/src/providers/codex-oauth/storage.ts +1 -1
  167. package/src/providers/cursor-adapter.ts +1 -1
  168. package/src/rbac/legacy-policy.ts +3 -1
  169. package/src/rbac/permissions.ts +4 -0
  170. package/src/realtime/transport.ts +1 -2
  171. package/src/script-workflows/workflow-ctx.ts +1 -0
  172. package/src/scripts-runtime/types/stdlib.d.ts +2 -0
  173. package/src/scripts-runtime/types/swarm-extension.d.ts +8 -0
  174. package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
  175. package/src/server-user.ts +2 -0
  176. package/src/server.ts +2 -0
  177. package/src/slack/assistant.ts +2 -2
  178. package/src/slack/files.ts +2 -0
  179. package/src/slack/handlers.ts +4 -4
  180. package/src/slack/inbound-files.ts +39 -6
  181. package/src/slack/thread-buffer.ts +2 -1
  182. package/src/tasks/task-terminal-effects.ts +21 -17
  183. package/src/tests/acp-adapter.test.ts +10 -23
  184. package/src/tests/acp-fake-agent.ts +36 -0
  185. package/src/tests/acp-swarm-events.test.ts +1 -1
  186. package/src/tests/agent-session-tokens.test.ts +452 -0
  187. package/src/tests/apps-action-parent-task.test.ts +295 -0
  188. package/src/tests/apps-card-grid.test.ts +197 -0
  189. package/src/tests/apps-catalog-additions.test.ts +120 -0
  190. package/src/tests/apps-tree.test.ts +129 -0
  191. package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
  192. package/src/tests/chat-sdk-config.test.ts +11 -0
  193. package/src/tests/chat-sdk-facts.test.ts +23 -0
  194. package/src/tests/chat-sdk-inbound.test.ts +558 -4
  195. package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
  196. package/src/tests/codex-adapter.test.ts +167 -242
  197. package/src/tests/codex-oauth-keep-warm.test.ts +85 -1
  198. package/src/tests/config-api-only-keys.test.ts +9 -0
  199. package/src/tests/config-session-auth.test.ts +14 -0
  200. package/src/tests/cors-allowlist.test.ts +1 -1
  201. package/src/tests/docs-index-footprint.test.js +2 -2
  202. package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
  203. package/src/tests/grok-adapter.test.ts +3 -3
  204. package/src/tests/heartbeat-checklist.test.ts +33 -0
  205. package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
  206. package/src/tests/heartbeat-jev-gate.test.ts +265 -1
  207. package/src/tests/kv-page-proxy.test.ts +91 -5
  208. package/src/tests/launch-password-rejection.test.ts +2 -2
  209. package/src/tests/mcp-input-caps.test.ts +294 -0
  210. package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
  211. package/src/tests/memory-embedding-input-cap.test.ts +114 -0
  212. package/src/tests/memory.test.ts +14 -0
  213. package/src/tests/migration-232-agent-session-tokens.test.ts +67 -0
  214. package/src/tests/page-guest-session.test.ts +19 -8
  215. package/src/tests/page-proxy-authed.test.ts +1 -1
  216. package/src/tests/page-proxy.test.ts +17 -10
  217. package/src/tests/page-session.test.ts +102 -1
  218. package/src/tests/pages-authed-mode.test.ts +82 -12
  219. package/src/tests/pages-password-mode.test.ts +5 -5
  220. package/src/tests/rbac-engine.test.ts +1 -0
  221. package/src/tests/realtime-transport.test.ts +13 -7
  222. package/src/tests/runtime-child-token-cleanup.test.ts +3 -3
  223. package/src/tests/script-connections.test.ts +2 -0
  224. package/src/tests/script-runs-http.test.ts +84 -0
  225. package/src/tests/scripts-http.test.ts +10 -1
  226. package/src/tests/session-route-posture.test.ts +2 -0
  227. package/src/tests/swarm-config-export-to-env.test.ts +22 -3
  228. package/src/tests/task-audio-transcription.test.ts +299 -0
  229. package/src/tests/task-completion-memory-chunking.test.ts +434 -0
  230. package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
  231. package/src/tests/transcription.test.ts +299 -0
  232. package/src/tests/worker-token-cli.test.ts +76 -0
  233. package/src/tests/workflow-executors.test.ts +4 -2
  234. package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
  235. package/src/tests/workflow-system-one-decision.test.ts +2 -1
  236. package/src/tests/workflow-system-one-extraction.test.ts +805 -0
  237. package/src/tools/chat-access.ts +10 -0
  238. package/src/tools/chat-post.ts +31 -11
  239. package/src/tools/chat-reply.ts +32 -10
  240. package/src/tools/memory-search.ts +3 -1
  241. package/src/tools/system-one-decide.ts +17 -4
  242. package/src/types.ts +8 -1
  243. package/src/utils/config-env-export.ts +3 -1
  244. package/src/utils/page-session.ts +133 -20
  245. package/src/utils/request-auth-context.ts +2 -1
  246. package/src/utils/transcription.ts +246 -0
  247. package/src/workflows/definition.ts +7 -1
  248. package/src/workflows/engine.ts +18 -12
  249. package/src/workflows/executors/index.ts +1 -0
  250. package/src/workflows/executors/registry.ts +2 -0
  251. package/src/workflows/executors/system-one-decision.ts +84 -262
  252. package/src/workflows/executors/system-one-extract-wire.ts +57 -0
  253. package/src/workflows/executors/system-one-extraction.ts +830 -0
  254. package/src/workflows/executors/system-one-providers.ts +316 -114
  255. package/src/workflows/executors/system-one-transport.ts +264 -0
  256. package/templates/extensions/slack-reply-gate/README.md +24 -22
  257. package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
  258. package/templates/extensions/slack-reply-gate/manifest.json +2 -2
  259. package/templates/official/lead/HEARTBEAT.md +1 -1
  260. package/templates/skills/apps/SKILL.md +31 -7
  261. package/templates/skills/apps/content.md +31 -7
  262. package/templates/skills/chat-sdk/content.md +6 -2
  263. package/templates/skills/code-quality/content.md +2 -1
  264. package/templates/skills/heartbeat-runbook/content.md +8 -8
  265. package/templates/skills/pages/content.md +6 -4
  266. package/templates/skills/swarm-scripts/SKILL.md +2 -2
  267. package/templates/skills/swarm-scripts/content.md +2 -2
  268. package/dist/cli-v1sjhn52.js +0 -7
  269. package/dist/handlers-2qjw434w.js +0 -39
  270. package/dist/index-d0g18bax.js +0 -41
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Speech-to-text for inbound audio. `TRANSCRIPTION_PROVIDER` picks the request
3
+ * shape:
4
+ *
5
+ * - `openai`: `POST <base>/audio/transcriptions`, `Authorization: Bearer`, form
6
+ * field `model`. OpenAI Whisper and self-hosted whisper servers speak this
7
+ * shape.
8
+ * - `groq`: the `openai` shape against Groq's OpenAI-compatible endpoint.
9
+ * - `elevenlabs`: `POST <base>/speech-to-text`, `xi-api-key` header, form
10
+ * field `model_id`.
11
+ *
12
+ * The key: an explicit `TRANSCRIPTION_API_KEY` wins, with `TRANSCRIPTION_PROVIDER`
13
+ * (default `openai`). Without it, transcription reuses a provider key the
14
+ * swarm already has: the pinned provider's key when `TRANSCRIPTION_PROVIDER` is
15
+ * set, else the first of `ELEVENLABS_API_KEY`, `OPENAI_API_KEY`, `GROQ_API_KEY`.
16
+ * Off when none is set.
17
+ *
18
+ * `TRANSCRIPTION_MODEL` overrides the provider's default model.
19
+ * `TRANSCRIPTION_BASE_URL` overrides its base URL for an explicit key only: an
20
+ * auto-detected key always goes to its provider's own endpoint. Deepgram and AssemblyAI use other request shapes and are not
21
+ * supported. Read at call time, so a config reload applies without a restart.
22
+ */
23
+ import { createValueScrubber, scrubSecrets } from "./secret-scrubber";
24
+
25
+ const TRANSCRIPTION_TIMEOUT_MS = 60_000;
26
+
27
+ /** The config key that switches transcription on explicitly. */
28
+ export const TRANSCRIPTION_API_KEY_ENV = "TRANSCRIPTION_API_KEY";
29
+
30
+ type ProviderSpec = {
31
+ /** The provider's own key, reused when `TRANSCRIPTION_API_KEY` is unset. */
32
+ keyEnv: string;
33
+ defaultBaseUrl: string;
34
+ defaultModel: string;
35
+ path: string;
36
+ modelField: string;
37
+ authHeaders: (apiKey: string) => Record<string, string>;
38
+ };
39
+
40
+ const openAIShape = {
41
+ path: "/audio/transcriptions",
42
+ modelField: "model",
43
+ authHeaders: (apiKey: string) => ({ authorization: `Bearer ${apiKey}` }),
44
+ };
45
+
46
+ const PROVIDERS = {
47
+ openai: {
48
+ keyEnv: "OPENAI_API_KEY",
49
+ defaultBaseUrl: "https://api.openai.com/v1",
50
+ defaultModel: "whisper-1",
51
+ ...openAIShape,
52
+ },
53
+ groq: {
54
+ keyEnv: "GROQ_API_KEY",
55
+ defaultBaseUrl: "https://api.groq.com/openai/v1",
56
+ defaultModel: "whisper-large-v3-turbo",
57
+ ...openAIShape,
58
+ },
59
+ elevenlabs: {
60
+ keyEnv: "ELEVENLABS_API_KEY",
61
+ defaultBaseUrl: "https://api.elevenlabs.io/v1",
62
+ defaultModel: "scribe_v2",
63
+ path: "/speech-to-text",
64
+ modelField: "model_id",
65
+ authHeaders: (apiKey) => ({ "xi-api-key": apiKey }),
66
+ },
67
+ } satisfies Record<string, ProviderSpec>;
68
+
69
+ export type TranscriptionProvider = keyof typeof PROVIDERS;
70
+
71
+ /** Order in which an existing provider key is picked when nothing is pinned. */
72
+ const AUTO_DETECT_ORDER: TranscriptionProvider[] = ["elevenlabs", "openai", "groq"];
73
+
74
+ /** Every env key the provider and key resolution reads. */
75
+ export const TRANSCRIPTION_ENV_KEYS = [
76
+ TRANSCRIPTION_API_KEY_ENV,
77
+ "TRANSCRIPTION_PROVIDER",
78
+ "TRANSCRIPTION_BASE_URL",
79
+ "TRANSCRIPTION_MODEL",
80
+ ...AUTO_DETECT_ORDER.map((name) => PROVIDERS[name].keyEnv),
81
+ ];
82
+
83
+ export class TranscriptionError extends Error {
84
+ constructor(message: string) {
85
+ super(message);
86
+ this.name = "TranscriptionError";
87
+ }
88
+ }
89
+
90
+ /** The provider and key a transcription call uses. */
91
+ export type TranscriptionConfig = {
92
+ provider: string;
93
+ apiKey: string;
94
+ /** The env key the API key came from. */
95
+ keyEnv: string;
96
+ /** `explicit`: `TRANSCRIPTION_API_KEY`. `auto`: a provider key the swarm already had. */
97
+ source: "explicit" | "auto";
98
+ };
99
+
100
+ function envValue(key: string): string | undefined {
101
+ return process.env[key]?.trim() || undefined;
102
+ }
103
+
104
+ /** Resolve provider and key, or null when no key is set. Unsupported provider names pass through. */
105
+ export function resolveTranscriptionConfig(): TranscriptionConfig | null {
106
+ const pinned = envValue("TRANSCRIPTION_PROVIDER")?.toLowerCase();
107
+ const explicitKey = envValue(TRANSCRIPTION_API_KEY_ENV);
108
+ if (explicitKey) {
109
+ return {
110
+ provider: pinned ?? "openai",
111
+ apiKey: explicitKey,
112
+ keyEnv: TRANSCRIPTION_API_KEY_ENV,
113
+ source: "explicit",
114
+ };
115
+ }
116
+ // A pinned provider only uses its own key, never another provider's.
117
+ const candidates = pinned
118
+ ? AUTO_DETECT_ORDER.filter((name) => name === pinned)
119
+ : AUTO_DETECT_ORDER;
120
+ for (const name of candidates) {
121
+ const { keyEnv } = PROVIDERS[name];
122
+ const apiKey = envValue(keyEnv);
123
+ if (apiKey) return { provider: name, apiKey, keyEnv, source: "auto" };
124
+ }
125
+ return null;
126
+ }
127
+
128
+ export function isTranscriptionConfigured(): boolean {
129
+ return resolveTranscriptionConfig() !== null;
130
+ }
131
+
132
+ function providerSpec(name: string): ProviderSpec {
133
+ if (!Object.hasOwn(PROVIDERS, name)) {
134
+ throw new TranscriptionError(
135
+ `TRANSCRIPTION_PROVIDER "${name}" is not supported (use ${Object.keys(PROVIDERS).join(", ")})`,
136
+ );
137
+ }
138
+ return PROVIDERS[name as TranscriptionProvider];
139
+ }
140
+
141
+ /** OpenAI puts the reason in `error.message`; ElevenLabs in `detail.message` or `detail`. */
142
+ function errorDetail(payload: Record<string, unknown> | null, status: number): string {
143
+ const error = payload?.error as { message?: unknown } | undefined;
144
+ if (typeof error?.message === "string") return error.message;
145
+ const detail = payload?.detail as { message?: unknown } | string | undefined;
146
+ if (typeof detail === "string") return detail;
147
+ if (typeof detail?.message === "string") return detail.message;
148
+ return `HTTP ${status}`;
149
+ }
150
+
151
+ /** Transcribe one audio file. Throws `TranscriptionError` when unconfigured or when the call fails. */
152
+ export async function transcribeAudio(input: {
153
+ data: Blob;
154
+ filename: string;
155
+ mimeType?: string;
156
+ }): Promise<string> {
157
+ const config = resolveTranscriptionConfig();
158
+ if (!config) throw new TranscriptionError("no transcription provider key is set");
159
+ const { apiKey } = config;
160
+ const provider = providerSpec(config.provider);
161
+ // An auto-detected key only goes to its own provider: a base URL set in
162
+ // config must not receive a key that was stored for another purpose.
163
+ const baseUrlOverride =
164
+ config.source === "explicit" ? process.env.TRANSCRIPTION_BASE_URL?.trim() : undefined;
165
+ const baseUrl = (baseUrlOverride || provider.defaultBaseUrl).replace(/\/+$/, "");
166
+ // scrubSecrets skips known values under 12 chars, and a self-hosted key can
167
+ // be shorter, so the submitted key is scrubbed by value from every error.
168
+ const keyScrubber = createValueScrubber([{ value: apiKey, name: config.keyEnv }]);
169
+ const scrub = (text: string) => scrubSecrets(keyScrubber.text(text));
170
+ const form = new FormData();
171
+ const type = input.mimeType ?? input.data.type;
172
+ form.append("file", type ? new Blob([input.data], { type }) : input.data, input.filename);
173
+ form.append(
174
+ provider.modelField,
175
+ process.env.TRANSCRIPTION_MODEL?.trim() || provider.defaultModel,
176
+ );
177
+
178
+ let response: Response;
179
+ try {
180
+ response = await fetch(`${baseUrl}${provider.path}`, {
181
+ method: "POST",
182
+ headers: provider.authHeaders(apiKey),
183
+ body: form,
184
+ signal: AbortSignal.timeout(TRANSCRIPTION_TIMEOUT_MS),
185
+ });
186
+ } catch (error) {
187
+ if (error instanceof Error && error.name === "TimeoutError") {
188
+ throw new TranscriptionError(`no answer within ${TRANSCRIPTION_TIMEOUT_MS / 1000} s`);
189
+ }
190
+ throw new TranscriptionError(scrub(error instanceof Error ? error.message : String(error)));
191
+ }
192
+ const payload = (await response.json().catch(() => null)) as Record<string, unknown> | null;
193
+ if (!response.ok || typeof payload?.text !== "string") {
194
+ throw new TranscriptionError(scrub(errorDetail(payload, response.status)));
195
+ }
196
+ return payload.text.trim();
197
+ }
198
+
199
+ /** The task-text note for an audio file when no provider is configured. */
200
+ export const NOT_TRANSCRIBED_NOTE = `(not transcribed: no transcription provider configured, set ${TRANSCRIPTION_API_KEY_ENV} or one of ${AUTO_DETECT_ORDER.map((name) => PROVIDERS[name].keyEnv).join(", ")})`;
201
+
202
+ const AUDIO_EXTENSIONS: Record<string, string> = {
203
+ "audio/ogg": "ogg",
204
+ "audio/opus": "ogg",
205
+ "audio/mpeg": "mp3",
206
+ "audio/mp4": "m4a",
207
+ "audio/x-m4a": "m4a",
208
+ "audio/wav": "wav",
209
+ "audio/x-wav": "wav",
210
+ "audio/webm": "webm",
211
+ };
212
+
213
+ /** One audio file to transcribe. `key` names its note in the returned map. */
214
+ export type AudioFile = { key: string; name: string; data: Blob; mimeType?: string };
215
+
216
+ /**
217
+ * Transcribe audio files into task-text notes, one per file: `[Transcript of
218
+ * <name>]` and the text, `(transcription failed: <reason>)`, or (when no
219
+ * provider is configured) `NOT_TRANSCRIBED_NOTE`. Never throws, so a failed
220
+ * call never blocks the task it belongs to. `logPrefix` tags the warning.
221
+ */
222
+ export async function transcribeAudioNotes(
223
+ files: AudioFile[],
224
+ logPrefix: string,
225
+ ): Promise<Map<string, string>> {
226
+ const notes = new Map<string, string>();
227
+ if (files.length === 0) return notes;
228
+ if (!isTranscriptionConfigured()) {
229
+ for (const file of files) notes.set(file.key, NOT_TRANSCRIBED_NOTE);
230
+ return notes;
231
+ }
232
+ for (const file of files) {
233
+ // Voice notes can carry no file name; the endpoint reads the format from the extension.
234
+ const extension = AUDIO_EXTENSIONS[file.mimeType ?? ""] ?? "ogg";
235
+ const filename = /\.\w+$/.test(file.name) ? file.name : `${file.name}.${extension}`;
236
+ try {
237
+ const text = await transcribeAudio({ data: file.data, filename, mimeType: file.mimeType });
238
+ notes.set(file.key, `[Transcript of ${file.name}]\n${text || "(no speech recognized)"}`);
239
+ } catch (error) {
240
+ const reason = scrubSecrets(error instanceof Error ? error.message : String(error));
241
+ console.warn(`${logPrefix} could not transcribe file ${file.key}: ${reason}`);
242
+ notes.set(file.key, `(transcription failed: ${reason})`);
243
+ }
244
+ }
245
+ return notes;
246
+ }
@@ -10,6 +10,7 @@ import {
10
10
  systemOneRetryViolations,
11
11
  systemOneStaticShapeViolations,
12
12
  } from "./executors/system-one-decision";
13
+ import { systemOneExtractionStaticShapeViolations } from "./executors/system-one-extraction";
13
14
 
14
15
  /** Extract all target node IDs from a node's `next` field */
15
16
  export function getNextTargets(next: string | string[] | Record<string, string>): string[] {
@@ -212,7 +213,12 @@ export function validateDefinition(
212
213
  }
213
214
  // A system-one-decision node's question ids and return types are static, and it never carries
214
215
  // an engine retry policy (it retries transient transport errors itself).
215
- errors.push(...systemOneStaticShapeViolations(node), ...systemOneRetryViolations(node));
216
+ // A system-one-extraction node's field ids and types are static in the same way.
217
+ errors.push(
218
+ ...systemOneStaticShapeViolations(node),
219
+ ...systemOneExtractionStaticShapeViolations(node),
220
+ ...systemOneRetryViolations(node),
221
+ );
216
222
  if (node.type === "foreach") {
217
223
  validateForeachNode(node, errors);
218
224
  // A legacy `#` id may stay editable as a NORMAL node, but never as a foreach
@@ -30,7 +30,7 @@ import { findEntryNodes, getNextTargets, getSuccessors, resolveValidationPort }
30
30
  import type { AsyncExecutorResult } from "./executors/base";
31
31
  import type { ExecutorRegistry } from "./executors/registry";
32
32
  import {
33
- SYSTEM_ONE_DECISION_NODE_TYPE,
33
+ isSystemOneNodeType,
34
34
  systemOneRetryViolations,
35
35
  systemOneUnresolvedError,
36
36
  } from "./executors/system-one-decision";
@@ -854,7 +854,7 @@ async function runClaimedStep(
854
854
  await checkpointStepFailure(
855
855
  runId,
856
856
  stepId,
857
- systemOneUnresolvedError(node.id, strictUnresolved),
857
+ systemOneUnresolvedError(node.id, strictUnresolved, node.type),
858
858
  0,
859
859
  );
860
860
  return { outcome: "failed", successors: [] };
@@ -1150,6 +1150,9 @@ function buildScriptBodyCtx(
1150
1150
  return safe;
1151
1151
  }
1152
1152
 
1153
+ /** System One config fields whose whole-token references keep their JSON type. */
1154
+ const SYSTEM_ONE_DATA_FIELDS = new Set(["state", "questions", "fields", "images"]);
1155
+
1153
1156
  export function interpolateNodeConfig(
1154
1157
  node: Pick<WorkflowNode, "type" | "config" | "inputs">,
1155
1158
  interpolationCtx: Record<string, unknown>,
@@ -1160,17 +1163,20 @@ export function interpolateNodeConfig(
1160
1163
  /** Tokens that must resolve before dispatch; the caller fails the step when non-empty. */
1161
1164
  strictUnresolved?: string[];
1162
1165
  } {
1163
- if (node.type === SYSTEM_ONE_DECISION_NODE_TYPE) {
1164
- // `state` and `questions` keep a whole-token reference's JSON type (an object
1165
- // stays an object; string interpolation would flatten it to JSON text). The
1166
- // resolved value is data, never re-interpolated, so a literal `{{` inside a
1167
- // lead's text is not read as another template. Every other field, and every
1168
- // unresolved token anywhere in the config, is strict: no blank reaches the API.
1169
- const { state, questions, ...rest } = node.config;
1170
- const restResult = deepInterpolate(rest, interpolationCtx);
1166
+ if (isSystemOneNodeType(node.type)) {
1167
+ // `state` and the question / field specs (and extraction `images`) keep a
1168
+ // whole-token reference's JSON type (an object stays an object; string
1169
+ // interpolation would flatten it to JSON text). The resolved value is data,
1170
+ // never re-interpolated, so a literal `{{` inside a lead's text is not read as
1171
+ // another template. Every other field, and every unresolved token anywhere in
1172
+ // the config, is strict: no blank reaches the API.
1173
+ const rest: Record<string, unknown> = {};
1171
1174
  const dynamic: Record<string, unknown> = {};
1172
- if (Object.hasOwn(node.config, "state")) dynamic.state = state;
1173
- if (Object.hasOwn(node.config, "questions")) dynamic.questions = questions;
1175
+ for (const [key, value] of Object.entries(node.config)) {
1176
+ if (SYSTEM_ONE_DATA_FIELDS.has(key)) dynamic[key] = value;
1177
+ else rest[key] = value;
1178
+ }
1179
+ const restResult = deepInterpolate(rest, interpolationCtx);
1174
1180
  const dynamicResult = deepInterpolate(dynamic, interpolationCtx, { preserveRawTokens: true });
1175
1181
  const unresolved = [...restResult.unresolved, ...dynamicResult.unresolved];
1176
1182
  return {
@@ -14,6 +14,7 @@ export { createExecutorRegistry, ExecutorRegistry } from "./registry";
14
14
  export { ScriptExecutor } from "./script";
15
15
  export { SwarmScriptExecutor } from "./swarm-script";
16
16
  export { SystemOneDecisionExecutor } from "./system-one-decision";
17
+ export { SystemOneExtractionExecutor } from "./system-one-extraction";
17
18
  export { ValidateExecutor } from "./validate";
18
19
  export { VcsExecutor } from "./vcs";
19
20
  export { WaitExecutor } from "./wait";
@@ -11,6 +11,7 @@ import { ScriptExecutor } from "./script";
11
11
  import { SubWorkflowExecutor } from "./sub-workflow";
12
12
  import { SwarmScriptExecutor } from "./swarm-script";
13
13
  import { SystemOneDecisionExecutor } from "./system-one-decision";
14
+ import { SystemOneExtractionExecutor } from "./system-one-extraction";
14
15
  import { ValidateExecutor } from "./validate";
15
16
  import { VcsExecutor } from "./vcs";
16
17
  import { WaitExecutor } from "./wait";
@@ -72,6 +73,7 @@ export function createExecutorRegistry(deps: ExecutorDependencies): ExecutorRegi
72
73
  registry.register(new NotifyExecutor(deps));
73
74
  registry.register(new RawLlmExecutor(deps));
74
75
  registry.register(new SystemOneDecisionExecutor(deps));
76
+ registry.register(new SystemOneExtractionExecutor(deps));
75
77
  registry.register(new ScriptExecutor(deps));
76
78
  registry.register(new SwarmScriptExecutor(deps));
77
79
  registry.register(new VcsExecutor(deps));