@desplega.ai/agent-swarm 2.6.0 → 2.7.1

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 (324) hide show
  1. package/README.md +2 -0
  2. package/dist/{acp-adapter-dgfakfpw.js → acp-adapter-8dn5tq56.js} +6 -6
  3. package/dist/{actions-fbqma8aa.js → actions-3r6gw7dy.js} +18 -18
  4. package/dist/{amp-adapter-en0jy6hs.js → amp-adapter-wjtq413p.js} +3 -3
  5. package/dist/{anthropic-messages-txs190dd.js → anthropic-messages-n4cby1rz.js} +4 -4
  6. package/dist/{app-k8a5cgpp.js → app-d53yhykk.js} +3 -3
  7. package/dist/{assistant-a16433gr.js → assistant-ksrawj3k.js} +20 -20
  8. package/dist/{boot-reembed-6c1sjfnr.js → boot-reembed-fxfw17m2.js} +11 -3
  9. package/dist/{boot-reembed-f4nn561n.js → boot-reembed-k5y5xbj0.js} +15 -8
  10. package/dist/{boot-scrub-sweep-d4w9gajf.js → boot-scrub-sweep-se41j9fd.js} +3 -3
  11. package/dist/{boot-seeds-9bmkspyw.js → boot-seeds-r3kwwv8b.js} +4 -4
  12. package/dist/{claude-adapter-nys1kjxa.js → claude-adapter-ahmyf231.js} +8 -8
  13. package/dist/{claude-managed-adapter-55207973.js → claude-managed-adapter-5knamegc.js} +3 -3
  14. package/dist/{claude-managed-setup-3cm35gey.js → claude-managed-setup-mdc7dy6b.js} +3 -3
  15. package/dist/{claude-sdk-session-nw5yd8cp.js → claude-sdk-session-n5wen9zk.js} +8729 -8600
  16. package/dist/{cli-683y0b3a.js → cli-0dqtv2jg.js} +2 -2
  17. package/dist/{cli-a34pr3w1.js → cli-0tww7dnz.js} +2 -2
  18. package/dist/{cli-w2656ka1.js → cli-0w4e4v58.js} +7 -7
  19. package/dist/{cli-5sckpqcx.js → cli-2sjm3t4r.js} +1 -1
  20. package/dist/{cli-gf2850q7.js → cli-3cywb44q.js} +18 -1
  21. package/dist/{cli-8wkb09q5.js → cli-3k9jej0c.js} +2 -2
  22. package/dist/{cli-3aqd4jaa.js → cli-3v2cyqb8.js} +3 -3
  23. package/dist/{cli-emhjcj2y.js → cli-5qt9vzmq.js} +89 -20
  24. package/dist/{cli-abkm71vr.js → cli-5rp1w4cr.js} +1 -1
  25. package/dist/{cli-gf95q3bm.js → cli-6vwjsetx.js} +4 -4
  26. package/dist/{cli-k21gx5ht.js → cli-6w2yrrc0.js} +10 -10
  27. package/dist/{cli-m70bajzr.js → cli-75tdydgv.js} +1 -1
  28. package/dist/{cli-5rj83bam.js → cli-8feswf2v.js} +47 -36
  29. package/dist/{cli-sxgtf5fw.js → cli-8wd3mbyj.js} +1 -1
  30. package/dist/{cli-ca6banwz.js → cli-9csfthbw.js} +10 -8
  31. package/dist/{cli-dqygeeh5.js → cli-b41p764a.js} +31 -3
  32. package/dist/{cli-m5a9m0ak.js → cli-c1t533hh.js} +3 -3
  33. package/dist/{cli-2sjhpsns.js → cli-c28jxpcv.js} +1 -1
  34. package/dist/{cli-bqztfyve.js → cli-cb50g9cd.js} +1 -1
  35. package/dist/{cli-ag2yrnt5.js → cli-d8gaphtr.js} +2 -2
  36. package/dist/{cli-9abdgn5t.js → cli-dgtfysne.js} +3 -3
  37. package/dist/{cli-nq677csy.js → cli-dscnhn18.js} +1 -1
  38. package/dist/{cli-3fwqy71j.js → cli-dv11pgpv.js} +39 -32
  39. package/dist/{cli-v49fkghp.js → cli-ekptgk2m.js} +6 -6
  40. package/dist/{cli-c2habvsf.js → cli-f3mzy662.js} +17 -17
  41. package/dist/{cli-zqne1qcj.js → cli-fk7k8s9x.js} +2 -2
  42. package/dist/{cli-1tem6ns9.js → cli-gt4h8cxw.js} +3 -2
  43. package/dist/{cli-vbhgkw9h.js → cli-h3e7bhvn.js} +3 -3
  44. package/dist/{cli-ffvsj2qw.js → cli-hkxqdmnb.js} +3 -3
  45. package/dist/{cli-a2rr0fmg.js → cli-hpkvgdad.js} +7 -4
  46. package/dist/{cli-art7c1zh.js → cli-hq2v4rr5.js} +37 -9
  47. package/dist/{cli-sqma6vw1.js → cli-k1zb7raw.js} +5 -5
  48. package/dist/{cli-4rvr7zfx.js → cli-k4awgvzm.js} +5 -0
  49. package/dist/{cli-3ndmpxxv.js → cli-k7d3hvya.js} +2 -2
  50. package/dist/{cli-3gfvmzht.js → cli-kbtdftbw.js} +74 -26
  51. package/dist/{cli-hfeb68z4.js → cli-kj69b9tw.js} +34 -6
  52. package/dist/{cli-az25fn5f.js → cli-kk9jy4nm.js} +2 -2
  53. package/dist/{cli-39aet655.js → cli-m52t4cta.js} +1 -1
  54. package/dist/{cli-cz37jqss.js → cli-mdadewqp.js} +5 -5
  55. package/dist/{cli-esakngh8.js → cli-nf6k60sn.js} +1 -1
  56. package/dist/{cli-j6rktc91.js → cli-nzdf09js.js} +1 -1
  57. package/dist/{cli-rr7zx19j.js → cli-prcyzecg.js} +12 -12
  58. package/dist/{cli-4hxrf0p4.js → cli-pzafyqtk.js} +393 -62
  59. package/dist/{cli-g8ka5j4j.js → cli-qvaa1gjp.js} +173 -52
  60. package/dist/{cli-d2dpf95n.js → cli-rg4b2g6j.js} +1 -1
  61. package/dist/{cli-kf4jree9.js → cli-rm66vqdz.js} +1 -1
  62. package/dist/{cli-4rf0f5vg.js → cli-s504c3w7.js} +5 -3
  63. package/dist/{cli-nn6vx2mq.js → cli-swkx9jm2.js} +7 -5
  64. package/dist/{cli-g20jjwt4.js → cli-thsaq4pd.js} +16 -16
  65. package/dist/{cli-aj8djyqt.js → cli-twjnyvs8.js} +2 -2
  66. package/dist/{cli-03gg9pd1.js → cli-typdpe8g.js} +4 -4
  67. package/dist/{cli-mtnj4fwa.js → cli-vg8pwmsd.js} +6 -6
  68. package/dist/{cli-gnsb1zxa.js → cli-vhpckzvp.js} +1 -1
  69. package/dist/{cli-7enjwya9.js → cli-vz78s5wm.js} +1 -1
  70. package/dist/{cli-dv97kdzv.js → cli-wdxgsdz5.js} +2 -2
  71. package/dist/{cli-7a6p4e65.js → cli-wtjg6je3.js} +1 -1
  72. package/dist/{cli-6y51ryva.js → cli-x3cngb1h.js} +44 -12
  73. package/dist/{cli-n7asmce6.js → cli-xbz6mcg4.js} +3 -3
  74. package/dist/{cli-24j1jeeb.js → cli-y4tdxsqb.js} +3 -3
  75. package/dist/{cli-mvjz5an3.js → cli-yqrsp3ny.js} +7 -7
  76. package/dist/cli.js +31 -21
  77. package/dist/{codex-adapter-d95q4qsn.js → codex-adapter-7wepzq15.js} +6 -6
  78. package/dist/{codex-hook-brpnycn5.js → codex-hook-pbdh4xhx.js} +2 -2
  79. package/dist/{codex-session-runner-gdwd8h0c.js → codex-session-runner-02sb49e4.js} +6 -6
  80. package/dist/{commands-jqmgk54b.js → commands-3rcspxct.js} +2 -2
  81. package/dist/{controller-z3jq589f.js → controller-8ar4avd6.js} +3 -3
  82. package/dist/{controllers-connect-r2za39ef.js → controllers-connect-ajvh9h0v.js} +19 -19
  83. package/dist/{cursor-adapter-4mr4jgkw.js → cursor-adapter-gd0sa7bh.js} +1 -1
  84. package/dist/{db-w36wwb5g.js → db-dv9j7ts5.js} +2 -2
  85. package/dist/{db-ppv2tze7.js → db-z26vgdvj.js} +3 -3
  86. package/dist/{dispatcher-pv5h77vf.js → dispatcher-8m0htjq8.js} +17 -17
  87. package/dist/{dsh-adapter-d4pdpb3r.js → dsh-adapter-y8gyk130.js} +1 -1
  88. package/dist/{e2b-fn3zacvt.js → e2b-74vg9b7e.js} +1 -1
  89. package/dist/{e2b-222hsajc.js → e2b-jyay8wzn.js} +1 -1
  90. package/dist/{easter-egg-3d-4sp4dvmc.js → easter-egg-3d-28f880xx.js} +1 -1
  91. package/dist/{execute-jev839y1.js → execute-9seb79vh.js} +1 -1
  92. package/dist/{external-channel-y6nkzhnn.js → external-channel-smz7jyth.js} +2 -2
  93. package/dist/{getMachineId-bsd-brn0xym3.js → getMachineId-bsd-whfre6gr.js} +3 -3
  94. package/dist/{getMachineId-darwin-t339sfxm.js → getMachineId-darwin-5zjynb19.js} +3 -3
  95. package/dist/{getMachineId-linux-5eq95d8b.js → getMachineId-linux-8mksrrsq.js} +3 -3
  96. package/dist/{getMachineId-unsupported-qavrtc7j.js → getMachineId-unsupported-fxtqsdt2.js} +3 -3
  97. package/dist/{getMachineId-win-q7fe1wx7.js → getMachineId-win-nzfmz857.js} +3 -3
  98. package/dist/{grok-adapter-t08b9ntm.js → grok-adapter-z0nnvtbn.js} +7 -7
  99. package/dist/handlers-pspryh6g.js +41 -0
  100. package/dist/{heartbeat-vn31xwdk.js → heartbeat-f928aqyp.js} +20 -20
  101. package/dist/{hook-cpjs8q51.js → hook-d7c605r4.js} +7 -7
  102. package/dist/{hook-6d0zbvvt.js → hook-q9hv5xf9.js} +5 -5
  103. package/dist/{http-9c6medda.js → http-q6j4k989.js} +1000 -251
  104. package/dist/{index-k0yt1tvq.js → index-5nb266jr.js} +17 -17
  105. package/dist/{index-ysd45kgc.js → index-8fxt5eah.js} +10 -10
  106. package/dist/{index-zgvqzjsw.js → index-cekzwhez.js} +5 -5
  107. package/dist/{index-38t35h0n.js → index-ch0c1xff.js} +17 -17
  108. package/dist/{index-pkma1yf8.js → index-ebnjhxh1.js} +1 -1
  109. package/dist/{index-yqgbfgaj.js → index-ed8dy8rn.js} +20 -20
  110. package/dist/{index-qhjf2htm.js → index-ejqdkkqx.js} +262 -96
  111. package/dist/{index-dr94zbnh.js → index-farm1zsn.js} +32661 -29443
  112. package/dist/{index-g6k9tctj.js → index-yn1yxg85.js} +1 -1
  113. package/dist/{keepalive-em0117bp.js → keepalive-zzc3hks1.js} +3 -3
  114. package/dist/lead-c0pjcaw4.js +79 -0
  115. package/dist/{maintenance-kbme3qy9.js → maintenance-kqsxzpzx.js} +1 -1
  116. package/dist/{oauth-refresh-sweep-xysj4yse.js → oauth-refresh-sweep-7qt0g55z.js} +8 -3
  117. package/dist/{onboard-p6q0vz97.js → onboard-k7x13fqg.js} +121 -8
  118. package/dist/{opencode-adapter-esv3k9yn.js → opencode-adapter-tqvrtter.js} +5 -5
  119. package/dist/{otel-impl-54qhqh1z.js → otel-impl-sqkf6k3b.js} +97 -97
  120. package/dist/{pi-mono-adapter-mymbwhj0.js → pi-mono-adapter-m2qnafxm.js} +89 -14
  121. package/dist/{pricing-refresh-yb75wv33.js → pricing-refresh-wkpwhgvs.js} +3 -3
  122. package/dist/{rbac-roles-dexhc9x8.js → rbac-roles-dpn0c0wj.js} +7 -4
  123. package/dist/{rbac-roles-1vp2mw4p.js → rbac-roles-wk7xd6a6.js} +2 -2
  124. package/dist/{render-v2-tjay5hhf.js → render-v2-ex2m4g2k.js} +3 -3
  125. package/dist/{resume-8ayf37g5.js → resume-725g9b0b.js} +7 -7
  126. package/dist/{runtime-hqe5cqcd.js → runtime-kajas15g.js} +1 -1
  127. package/dist/{session-access-fdhp987s.js → session-access-0j2ezjje.js} +8 -4
  128. package/dist/{session-source-backfill-qgtc86z1.js → session-source-backfill-62202awe.js} +2 -2
  129. package/dist/{setup-4rrmqcq3.js → setup-9sj11ax6.js} +1 -1
  130. package/dist/{virtual-modules-52km00d6.js → virtual-modules-c6fcq6zr.js} +2 -2
  131. package/dist/{work-objects-p28fgd70.js → work-objects-0y07495y.js} +20 -20
  132. package/dist/worker-d4q9sqbc.js +79 -0
  133. package/openapi.json +1536 -407
  134. package/package.json +7 -5
  135. package/src/be/automation-preflight.ts +2 -0
  136. package/src/be/connector-codes.ts +65 -22
  137. package/src/be/db/api-keys.ts +9 -0
  138. package/src/be/db/workflow-runs.ts +36 -5
  139. package/src/be/db.ts +80 -22
  140. package/src/be/memory/boot-reembed.ts +4 -1
  141. package/src/be/memory/constants.ts +6 -0
  142. package/src/be/memory/embedding-recovery.ts +48 -0
  143. package/src/be/memory/index-content.ts +5 -2
  144. package/src/be/memory/providers/openai-embedding.ts +65 -14
  145. package/src/be/memory/task-learning.ts +257 -0
  146. package/src/be/memory/types.ts +13 -2
  147. package/src/be/migrations/245_script_executor.sql +3 -0
  148. package/src/be/migrations/246_connector_code_kind.sql +11 -0
  149. package/src/be/oauth-refresh-sweep.ts +2 -1
  150. package/src/be/onboarding.ts +125 -15
  151. package/src/be/rbac-roles.ts +4 -1
  152. package/src/be/scripts/boot-reembed.ts +2 -0
  153. package/src/be/scripts/db.ts +14 -4
  154. package/src/be/scripts/embeddings.ts +4 -2
  155. package/src/be/scripts/run-saved.ts +1 -0
  156. package/src/be/scripts/typecheck.ts +9 -3
  157. package/src/be/seed/agent-fs-provision.ts +75 -14
  158. package/src/be/seed-skills/index.ts +7 -0
  159. package/src/be/swarm-config-guard.ts +3 -0
  160. package/src/cli.tsx +9 -0
  161. package/src/comb/config.ts +6 -1
  162. package/src/commands/onboard/agent-fs-version.ts +5 -0
  163. package/src/commands/onboard/compose-generator.ts +117 -4
  164. package/src/commands/onboard/env-generator.ts +9 -0
  165. package/src/commands/onboard/tei-image.ts +12 -0
  166. package/src/commands/onboard/types.ts +7 -0
  167. package/src/commands/onboard.tsx +11 -1
  168. package/src/commands/provider-credentials.ts +2 -1
  169. package/src/commands/runner.ts +14 -1
  170. package/src/heartbeat/heartbeat.ts +14 -1
  171. package/src/heartbeat/script-runner-probe.ts +47 -0
  172. package/src/http/agentfs-proxy.ts +369 -0
  173. package/src/http/all-routes.ts +1 -0
  174. package/src/http/api-keys.ts +24 -9
  175. package/src/http/codex-oauth-device.ts +8 -1
  176. package/src/http/fs.ts +64 -10
  177. package/src/http/idempotency-key.ts +1 -1
  178. package/src/http/inbox-state.ts +27 -1
  179. package/src/http/index.ts +7 -4
  180. package/src/http/memory.ts +7 -4
  181. package/src/http/onboarding.ts +139 -11
  182. package/src/http/pages-public.ts +38 -5
  183. package/src/http/scripts.ts +27 -0
  184. package/src/http/session-access.ts +24 -0
  185. package/src/http/sessions.ts +10 -6
  186. package/src/http/tasks.ts +85 -11
  187. package/src/http/telemetry-ui.ts +1 -1
  188. package/src/http/user-roles.ts +4 -1
  189. package/src/http/users.ts +272 -3
  190. package/src/http/utils.ts +22 -3
  191. package/src/http/workflows.ts +10 -0
  192. package/src/http/x.ts +5 -0
  193. package/src/prompts/base-prompt.ts +45 -10
  194. package/src/prompts/session-templates.ts +43 -5
  195. package/src/providers/amp-adapter.ts +1 -1
  196. package/src/providers/codex-adapter.ts +6 -1
  197. package/src/providers/cursor-adapter.ts +1 -1
  198. package/src/providers/dsh-adapter.ts +1 -1
  199. package/src/providers/pi-mono-adapter.ts +99 -14
  200. package/src/rbac/legacy-policy.ts +1 -0
  201. package/src/rbac/permissions.ts +4 -0
  202. package/src/scheduler/scheduler.ts +2 -1
  203. package/src/scripts-runtime/eval-harness.ts +8 -1
  204. package/src/scripts-runtime/executors/native.ts +22 -16
  205. package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
  206. package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
  207. package/src/scripts-runtime/executors/quickjs.ts +7 -0
  208. package/src/scripts-runtime/executors/registry.ts +32 -0
  209. package/src/scripts-runtime/executors/types.ts +7 -0
  210. package/src/scripts-runtime/host-validated-args.ts +133 -0
  211. package/src/scripts-runtime/loader.ts +30 -3
  212. package/src/scripts-runtime/swarm-sdk.ts +9 -2
  213. package/src/scripts-runtime/types/stdlib.d.ts +9 -1
  214. package/src/scripts-runtime/types/swarm-sdk.d.ts +9 -1
  215. package/src/tests/agent-fs-provision-seeder.test.ts +261 -92
  216. package/src/tests/agentfs-proxy.test.ts +1106 -0
  217. package/src/tests/api-key-status-by-suffix.test.ts +242 -0
  218. package/src/tests/base-prompt.test.ts +139 -4
  219. package/src/tests/codex-adapter.test.ts +46 -8
  220. package/src/tests/codex-oauth-device.test.ts +12 -1
  221. package/src/tests/comb-config-guard.test.ts +20 -0
  222. package/src/tests/comb-links.test.ts +79 -1
  223. package/src/tests/comb-status.test.ts +44 -0
  224. package/src/tests/community-linear-delivery-loop.test.ts +2099 -0
  225. package/src/tests/credential-check.test.ts +4 -2
  226. package/src/tests/docs-index-footprint.test.js +3 -3
  227. package/src/tests/docs-index.test.ts +135 -0
  228. package/src/tests/dsh-adapter.test.ts +1 -1
  229. package/src/tests/fetch-resolved-env.test.ts +16 -2
  230. package/src/tests/fixtures/onboard/baseline-states.ts +68 -0
  231. package/src/tests/fixtures/onboard/content.compose.yml.txt +190 -0
  232. package/src/tests/fixtures/onboard/content.env.txt +29 -0
  233. package/src/tests/fixtures/onboard/dev.compose.yml.txt +155 -0
  234. package/src/tests/fixtures/onboard/dev.env.txt +28 -0
  235. package/src/tests/fixtures/onboard/full-all.compose.yml.txt +582 -0
  236. package/src/tests/fixtures/onboard/full-all.env.txt +59 -0
  237. package/src/tests/fixtures/onboard/full.compose.yml.txt +435 -0
  238. package/src/tests/fixtures/onboard/full.env.txt +36 -0
  239. package/src/tests/fixtures/onboard/research.compose.yml.txt +155 -0
  240. package/src/tests/fixtures/onboard/research.env.txt +28 -0
  241. package/src/tests/fixtures/onboard/solo.compose.yml.txt +85 -0
  242. package/src/tests/fixtures/onboard/solo.env.txt +26 -0
  243. package/src/tests/heartbeat-checklist.test.ts +89 -0
  244. package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
  245. package/src/tests/http-log-scrubbing.test.ts +34 -1
  246. package/src/tests/http-task-write-ownership.test.ts +5 -1
  247. package/src/tests/invites-http.test.ts +464 -0
  248. package/src/tests/memory-embedding-input-cap.test.ts +122 -1
  249. package/src/tests/memory-embedding-recovery.test.ts +137 -0
  250. package/src/tests/oauth-refresh-sweep.test.ts +34 -1
  251. package/src/tests/onboard-compose.test.ts +162 -0
  252. package/src/tests/onboard-env.test.ts +24 -0
  253. package/src/tests/onboard-non-interactive.test.ts +29 -0
  254. package/src/tests/onboard-tei-image.test.ts +36 -0
  255. package/src/tests/onboarding.test.ts +451 -4
  256. package/src/tests/openai-embedding-provider.test.ts +105 -2
  257. package/src/tests/opencode-adapter.test.ts +2 -1
  258. package/src/tests/pages-frame-ancestors.test.ts +162 -0
  259. package/src/tests/pi-codemode.test.ts +2 -1
  260. package/src/tests/pi-mono-adapter.test.ts +54 -0
  261. package/src/tests/pi-mono-session-cost-on-abort.test.ts +270 -0
  262. package/src/tests/pi-native-mcp.test.ts +13 -1
  263. package/src/tests/pi-tool-exposure.test.ts +8 -8
  264. package/src/tests/preload-env.test.ts +4 -0
  265. package/src/tests/preload.ts +4 -0
  266. package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
  267. package/src/tests/prompt-template-session.test.ts +20 -3
  268. package/src/tests/rbac-admission-e2e.test.ts +22 -6
  269. package/src/tests/rbac-admission.test.ts +342 -4
  270. package/src/tests/rbac-engine.test.ts +1 -0
  271. package/src/tests/rbac-roles.test.ts +9 -1
  272. package/src/tests/request-composition-derive.test.ts +329 -0
  273. package/src/tests/script-connections.test.ts +2 -0
  274. package/src/tests/script-executor-conformance.test.ts +36 -1
  275. package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
  276. package/src/tests/script-executor-quickjs.test.ts +140 -0
  277. package/src/tests/script-host-validated-args-native.test.ts +196 -0
  278. package/src/tests/script-per-script-executor.test.ts +415 -0
  279. package/src/tests/scripts-external-api.test.ts +45 -0
  280. package/src/tests/sdk-allowlist.test.ts +3 -2
  281. package/src/tests/self-improvement.test.ts +4 -4
  282. package/src/tests/send-task-requester-gate.test.ts +64 -3
  283. package/src/tests/sessions-move.test.ts +39 -1
  284. package/src/tests/store-progress-learning.test.ts +381 -0
  285. package/src/tests/ui-logs-parser.test.ts +180 -0
  286. package/src/tests/whoami-route.test.ts +41 -0
  287. package/src/tests/workflow-engine-v2.test.ts +116 -1
  288. package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
  289. package/src/tests/workflow-run-finished-event.test.ts +263 -0
  290. package/src/tests/workflow-swarm-script.test.ts +34 -0
  291. package/src/tools/inject-learning.ts +1 -1
  292. package/src/tools/memory-edit.ts +1 -1
  293. package/src/tools/memory-search.ts +1 -1
  294. package/src/tools/script-common.ts +1 -0
  295. package/src/tools/script-run.ts +7 -0
  296. package/src/tools/script-upsert.ts +7 -0
  297. package/src/tools/store-progress.ts +71 -1
  298. package/src/tools/utils.ts +12 -1
  299. package/src/tools/workflows/trigger-workflow.ts +104 -10
  300. package/src/types.ts +12 -1
  301. package/src/utils/constants.ts +39 -0
  302. package/src/utils/credentials.ts +5 -2
  303. package/src/utils/task-attachment-links.ts +15 -3
  304. package/src/workflows/engine.ts +18 -16
  305. package/src/workflows/executors/swarm-script.ts +5 -0
  306. package/src/workflows/foreach-driver.ts +5 -11
  307. package/src/workflows/node-timeout.ts +30 -0
  308. package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
  309. package/templates/community/linear-delivery-loop/README.md +38 -0
  310. package/templates/community/linear-delivery-loop/config.example.json +31 -0
  311. package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
  312. package/templates/community/linear-delivery-loop/schedule.json +10 -0
  313. package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
  314. package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
  315. package/templates/skills/swarm-docs/config.json +14 -0
  316. package/templates/skills/swarm-docs/content.md +144 -0
  317. package/dist/handlers-eqsnmv95.js +0 -41
  318. package/dist/lead-23e7z59t.js +0 -79
  319. package/dist/worker-e3w7vye1.js +0 -79
  320. package/dist/{azure-openai-responses-9kfwhmn7.js → azure-openai-responses-43asa23s.js} +0 -0
  321. package/dist/{node-vxx896fa.js → node-t28cc56b.js} +0 -0
  322. package/dist/{openai-codex-responses-79s8xnbh.js → openai-codex-responses-jhr3sjtm.js} +3 -3
  323. package/dist/{openai-completions-tdbyts0x.js → openai-completions-gs6yn37k.js} +3 -3
  324. /package/dist/{openai-responses-dm3t5vqp.js → openai-responses-jqm9vwwk.js} +0 -0
@@ -0,0 +1,137 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import { randomUUID } from "node:crypto";
3
+ import { closeDb, createAgent, getDbClient, initDb } from "../be/db";
4
+ import { getEmbeddingProvider, getMemoryStore, resetEmbeddingProvider } from "../be/memory";
5
+ import { EMBEDDING_DIMENSIONS } from "../be/memory/constants";
6
+ import {
7
+ pendingRecoveryBackfill,
8
+ resetEmbeddingRecoveryForTests,
9
+ } from "../be/memory/embedding-recovery";
10
+
11
+ // A fake OpenAI-compatible endpoint that is down (503), rejects inputs (400)
12
+ // or works, like a local TEI that restarts.
13
+ let mode: "down" | "reject" | "up" = "up";
14
+ let server: ReturnType<typeof Bun.serve>;
15
+ const agentId = randomUUID();
16
+ const saved = {
17
+ baseUrl: process.env.EMBEDDING_API_BASE_URL,
18
+ key: process.env.EMBEDDING_API_KEY,
19
+ };
20
+
21
+ beforeAll(async () => {
22
+ server = Bun.serve({
23
+ hostname: "127.0.0.1",
24
+ port: 0,
25
+ async fetch(req) {
26
+ const body = (await req.json()) as { input: string | string[] };
27
+ const inputs = Array.isArray(body.input) ? body.input : [body.input];
28
+ if (mode === "down") return new Response("model loading", { status: 503 });
29
+ if (mode === "reject") {
30
+ return Response.json({ error: { message: "bad input" } }, { status: 400 });
31
+ }
32
+ return Response.json({
33
+ object: "list",
34
+ data: inputs.map((_, index) => ({
35
+ object: "embedding",
36
+ index,
37
+ embedding: new Array(EMBEDDING_DIMENSIONS).fill(0.1),
38
+ })),
39
+ model: "nomic-ai/nomic-embed-text-v1.5",
40
+ usage: { prompt_tokens: 1, total_tokens: 1 },
41
+ });
42
+ },
43
+ });
44
+ process.env.EMBEDDING_API_BASE_URL = `http://127.0.0.1:${server.port}/v1`;
45
+ process.env.EMBEDDING_API_KEY = "local-tei";
46
+ resetEmbeddingProvider();
47
+ initDb(":memory:");
48
+ await createAgent({ id: agentId, name: "recovery", isLead: false, status: "idle" });
49
+ });
50
+
51
+ afterAll(() => {
52
+ server.stop(true);
53
+ closeDb();
54
+ for (const [env, value] of [
55
+ ["EMBEDDING_API_BASE_URL", saved.baseUrl],
56
+ ["EMBEDDING_API_KEY", saved.key],
57
+ ] as const) {
58
+ if (value === undefined) delete process.env[env];
59
+ else process.env[env] = value;
60
+ }
61
+ resetEmbeddingProvider();
62
+ });
63
+
64
+ beforeEach(() => {
65
+ resetEmbeddingRecoveryForTests();
66
+ mode = "up";
67
+ });
68
+
69
+ async function memoryWithoutVector(): Promise<string> {
70
+ const memory = await getMemoryStore().store({
71
+ agentId,
72
+ scope: "agent",
73
+ name: `written while down ${randomUUID()}`,
74
+ content: "violet umbrella 37",
75
+ source: "manual",
76
+ });
77
+ return memory.id;
78
+ }
79
+
80
+ /** A stored script with no vector, like one seeded while the endpoint was down. */
81
+ async function scriptWithoutVector(): Promise<string> {
82
+ const id = randomUUID();
83
+ await getDbClient().run(
84
+ `INSERT INTO scripts (id, name, scope, source, description, intent, signatureJson, contentHash)
85
+ VALUES (?, ?, 'global', 'export default async () => 1', 'umbrella counter', 'count', '{}', ?)`,
86
+ [id, `recovery-${id}`, id],
87
+ );
88
+ return id;
89
+ }
90
+
91
+ async function scriptVectorBytes(id: string): Promise<number | null> {
92
+ const row = await getDbClient().get<{ bytes: number | null }>(
93
+ "SELECT length(embedding) AS bytes FROM script_embeddings WHERE scriptId = ?",
94
+ [id],
95
+ );
96
+ return row?.bytes ?? null;
97
+ }
98
+
99
+ async function vectorBytes(id: string): Promise<number | null> {
100
+ const row = await getDbClient().get<{ bytes: number | null }>(
101
+ "SELECT length(embedding) AS bytes FROM agent_memory WHERE id = ?",
102
+ [id],
103
+ );
104
+ return row?.bytes ?? null;
105
+ }
106
+
107
+ describe("backfill after the embeddings endpoint recovers", () => {
108
+ test("a memory or script written while the endpoint was down gets its vector on the next working call", async () => {
109
+ mode = "down";
110
+ expect(await getEmbeddingProvider().embed("first try", { kind: "document" })).toBeNull();
111
+ const id = await memoryWithoutVector();
112
+ const scriptId = await scriptWithoutVector();
113
+ expect(await vectorBytes(id)).toBeNull();
114
+ expect(await scriptVectorBytes(scriptId)).toBeNull();
115
+
116
+ mode = "up";
117
+ expect(await getEmbeddingProvider().embed("a search", { kind: "query" })).not.toBeNull();
118
+ const backfill = pendingRecoveryBackfill();
119
+ expect(backfill).not.toBeNull();
120
+ await backfill;
121
+ expect(await vectorBytes(id)).toBe(EMBEDDING_DIMENSIONS * 4);
122
+ expect(await scriptVectorBytes(scriptId)).toBe(EMBEDDING_DIMENSIONS * 4);
123
+ }, 30_000);
124
+
125
+ test("a rejected input does not start a backfill", async () => {
126
+ mode = "reject";
127
+ expect(await getEmbeddingProvider().embed("bad input")).toBeNull();
128
+ mode = "up";
129
+ expect(await getEmbeddingProvider().embed("a search", { kind: "query" })).not.toBeNull();
130
+ expect(pendingRecoveryBackfill()).toBeNull();
131
+ });
132
+
133
+ test("working calls without an earlier failure start no backfill", async () => {
134
+ expect(await getEmbeddingProvider().embed("a search", { kind: "query" })).not.toBeNull();
135
+ expect(pendingRecoveryBackfill()).toBeNull();
136
+ });
137
+ });
@@ -8,7 +8,11 @@ import {
8
8
  storeOAuthTokens,
9
9
  upsertOAuthApp,
10
10
  } from "../be/db-queries/oauth";
11
- import { sweepOAuthTokenRefresh } from "../be/oauth-refresh-sweep";
11
+ import {
12
+ startOAuthRefreshSweep,
13
+ stopOAuthRefreshSweep,
14
+ sweepOAuthTokenRefresh,
15
+ } from "../be/oauth-refresh-sweep";
12
16
 
13
17
  async function authorizationStatus(provider: string): Promise<string | undefined> {
14
18
  const id = await getDefaultAuthorizationIdForProvider(provider);
@@ -242,3 +246,32 @@ describe("sweepOAuthTokenRefresh", () => {
242
246
  expect((await getOAuthTokens("vendor_a"))?.accessToken).toBe("example-new-access-token");
243
247
  });
244
248
  });
249
+
250
+ describe("startOAuthRefreshSweep", () => {
251
+ const originalDisable = process.env.OAUTH_REFRESH_SWEEP_DISABLE;
252
+
253
+ afterAll(async () => {
254
+ await stopOAuthRefreshSweep();
255
+ if (originalDisable === undefined) {
256
+ delete process.env.OAUTH_REFRESH_SWEEP_DISABLE;
257
+ } else {
258
+ process.env.OAUTH_REFRESH_SWEEP_DISABLE = originalDisable;
259
+ }
260
+ });
261
+
262
+ for (const val of ["true", "1", "TRUE"]) {
263
+ test(`does not start when OAUTH_REFRESH_SWEEP_DISABLE=${val}`, async () => {
264
+ process.env.OAUTH_REFRESH_SWEEP_DISABLE = val;
265
+ const logs: string[] = [];
266
+ const origLog = console.log;
267
+ console.log = (...args: unknown[]) => logs.push(args.map(String).join(" "));
268
+ try {
269
+ startOAuthRefreshSweep();
270
+ } finally {
271
+ console.log = origLog;
272
+ await stopOAuthRefreshSweep();
273
+ }
274
+ expect(logs).toEqual(["[oauth-refresh-sweep] Disabled via OAUTH_REFRESH_SWEEP_DISABLE"]);
275
+ });
276
+ }
277
+ });
@@ -1,8 +1,15 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
2
5
  import { generateCompose as generateTemplateCompose } from "../../apps/templates-ui/src/lib/compose-generator.ts";
6
+ import { readAgentFsImageTag } from "../../scripts/agent-fs-version-utils.ts";
7
+ import { applyPins, PINS } from "../../scripts/bump-agent-fs.ts";
8
+ import { AGENT_FS_VERSION } from "../commands/onboard/agent-fs-version.ts";
3
9
  import { generateCompose } from "../commands/onboard/compose-generator.ts";
4
10
  import { getPresetById, PRESETS } from "../commands/onboard/presets.ts";
5
11
  import { INITIAL_STATE, type OnboardState } from "../commands/onboard/types.ts";
12
+ import { BASELINE_CASES, baselineState } from "./fixtures/onboard/baseline-states.ts";
6
13
 
7
14
  function makeState(overrides: Partial<OnboardState>): OnboardState {
8
15
  return { ...INITIAL_STATE, ...overrides };
@@ -136,6 +143,26 @@ describe("generateCompose", () => {
136
143
  expect(yaml).not.toContain("pull_policy: always");
137
144
  });
138
145
 
146
+ test.each([
147
+ "docker",
148
+ "podman",
149
+ ] as const)("%s agent services mount volumes where the worker reads and writes", (containerEngine) => {
150
+ const doc = Bun.YAML.parse(generateCompose({ ...devState, containerEngine })) as {
151
+ services: Record<string, { volumes?: string[] }>;
152
+ };
153
+ const agents = Object.entries(doc.services).filter(([name]) => name !== "swarm-api");
154
+
155
+ expect(agents.map(([name]) => name)).toEqual(["lead", "worker-coder-1", "worker-coder-2"]);
156
+ for (const [name, service] of agents) {
157
+ expect(service.volumes).toEqual([
158
+ "swarm_logs:/logs",
159
+ "swarm_shared:/workspace/shared",
160
+ `swarm_${name.replaceAll("-", "_")}:/workspace/personal`,
161
+ ]);
162
+ }
163
+ expect(doc.services["swarm-api"]?.volumes).toEqual(["swarm_data:/app/data"]);
164
+ });
165
+
139
166
  test("dev preset produces 3 agent services + 1 API service", () => {
140
167
  const yaml = generateCompose(devState);
141
168
  // Only count service definitions in the services section (before volumes:)
@@ -359,3 +386,138 @@ describe("generateCompose", () => {
359
386
  expect(yaml).toContain("condition: service_healthy");
360
387
  });
361
388
  });
389
+
390
+ describe("generateCompose with the agent-fs bundle flag", () => {
391
+ const serviceHeaders = (yaml: string) =>
392
+ yaml.split("\nvolumes:\n")[0]?.match(/^ {2}[a-z][\w-]+:$/gm) ?? [];
393
+ const serviceBlock = (yaml: string, name: string) =>
394
+ yaml.split(`\n ${name}:\n`)[1]?.split(/\n {2}[a-z][\w-]+:\n|\nvolumes:\n/)[0] ?? "";
395
+
396
+ test("INITIAL_STATE keeps the bundle off", () => {
397
+ expect(INITIAL_STATE.bundleAgentFs).toBe(false);
398
+ expect(INITIAL_STATE.teiImage).toBeNull();
399
+ });
400
+
401
+ for (const { name, state } of BASELINE_CASES) {
402
+ test(`flag off: ${name} compose equals the pre-bundle output`, async () => {
403
+ const expected = await Bun.file(
404
+ new URL(`./fixtures/onboard/${name}.compose.yml.txt`, import.meta.url),
405
+ ).text();
406
+ const yaml = generateCompose(state);
407
+ expect(yaml).toBe(expected);
408
+ for (const s of ["agent-fs", "tei", "COMB_ENABLED", "EMBEDDING_", "AGENT_FS_"]) {
409
+ expect(yaml).not.toContain(s);
410
+ }
411
+ });
412
+ }
413
+
414
+ for (const { name, state } of BASELINE_CASES) {
415
+ test(`flag on: ${name} adds agent-fs and tei without changing the rest`, () => {
416
+ const off = generateCompose(state);
417
+ const yaml = generateCompose({ ...state, bundleAgentFs: true });
418
+ const agents = serviceHeaders(off).filter((h) => h !== " swarm-api:");
419
+
420
+ expect(serviceHeaders(yaml)).toEqual([...serviceHeaders(off), " agent-fs:", " tei:"]);
421
+ expect(yaml).toMatch(/\n {2}agent_fs_data:\n {2}tei_data:\n$/);
422
+ expect(yaml.match(/^ {4}pull_policy: /gm)).toHaveLength(agents.length + 3);
423
+ expect(yaml.match(new RegExp(`pull_policy: ${state.pullPolicy}`, "g"))).toHaveLength(
424
+ agents.length + 3,
425
+ );
426
+
427
+ // Every line of the flag-off output survives, in order.
428
+ const onLines = yaml.split("\n");
429
+ let cursor = 0;
430
+ for (const line of off.split("\n")) {
431
+ const found = onLines.indexOf(line, cursor);
432
+ expect(found).toBeGreaterThanOrEqual(cursor);
433
+ cursor = found + 1;
434
+ }
435
+
436
+ // API: proxy, Comb, dashboard links, TEI memory with quoted prefixes.
437
+ const api = serviceBlock(yaml, "swarm-api");
438
+ for (const line of [
439
+ " - AGENT_FS_API_URL=http://agent-fs:7433",
440
+ " - AGENT_FS_PROXY=true",
441
+ " - COMB_ENABLED=true",
442
+ " - AGENT_FS_LINK_TARGET=dashboard",
443
+ " - EMBEDDING_API_BASE_URL=http://tei:80/v1",
444
+ " - EMBEDDING_MODEL=nomic-ai/nomic-embed-text-v1.5",
445
+ " - EMBEDDING_API_KEY=local-tei",
446
+ ' - "EMBEDDING_QUERY_PREFIX=search_query: "',
447
+ ' - "EMBEDDING_DOCUMENT_PREFIX=search_document: "',
448
+ ]) {
449
+ expect(api.split("\n")).toContain(line);
450
+ }
451
+ expect(api).toContain(
452
+ " depends_on:\n agent-fs:\n condition: service_healthy\n tei:\n condition: service_started\n",
453
+ );
454
+
455
+ // Every agent reaches agent-fs and builds dashboard links.
456
+ expect(yaml.match(/^ {6}- AGENT_FS_API_URL=http:\/\/agent-fs:7433$/gm)).toHaveLength(
457
+ agents.length + 1,
458
+ );
459
+ for (const header of agents) {
460
+ const agent = serviceBlock(yaml, header.trim().slice(0, -1));
461
+ expect(agent).toContain(" - AGENT_FS_API_URL=http://agent-fs:7433\n");
462
+ expect(agent).toContain(" - APP_URL=https://app.agent-swarm.dev\n");
463
+ expect(agent).toContain(" - AGENT_FS_LINK_TARGET=dashboard\n");
464
+ }
465
+
466
+ // agent-fs: local disk, no MinIO, no host port.
467
+ const afs = serviceBlock(yaml, "agent-fs");
468
+ expect(afs).toContain("AGENT_FS_STORAGE_PROVIDER=local");
469
+ expect(afs).toContain("AGENT_FS_LOCAL_ROOT=/data/storage");
470
+ expect(afs).toContain(" - agent_fs_data:/data");
471
+ expect(afs).toContain(
472
+ `AGENT_FS_PUBLIC_URL=\${PUBLIC_MCP_BASE_URL:-http://localhost:${state.apiPort || 3013}}/agentfs`,
473
+ );
474
+ expect(afs).toContain("node -e");
475
+ expect(afs).not.toContain("ports:");
476
+
477
+ // tei: no host port, the OOM and batch flags, a 2 GB cap.
478
+ const tei = serviceBlock(yaml, "tei");
479
+ expect(tei).toContain(
480
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: Docker Compose env var syntax
481
+ 'image: "${TEI_IMAGE:-ghcr.io/huggingface/text-embeddings-inference:cpu-1.9.4}"',
482
+ );
483
+ expect(tei).toContain('"--max-batch-tokens", "2048"');
484
+ expect(tei).toContain('"--max-client-batch-size", "256"');
485
+ expect(tei).toContain("mem_limit: 2g");
486
+ expect(tei).not.toContain("ports:");
487
+
488
+ expect(yaml).not.toContain("minio");
489
+ expect(yaml).not.toContain("platform:");
490
+ expect(yaml).not.toContain("EMBEDDING_DIMENSIONS");
491
+ });
492
+ }
493
+
494
+ test("flag on: the agent-fs pin equals the Helm chart tag", async () => {
495
+ const values = new URL("../../charts/agent-swarm/values.yaml", import.meta.url);
496
+ const tag = readAgentFsImageTag(await Bun.file(values).text(), "values.yaml");
497
+ expect(AGENT_FS_VERSION).toBe(tag);
498
+ const yaml = generateCompose({ ...baselineState("solo"), bundleAgentFs: true });
499
+ expect(yaml).toContain(` image: "ghcr.io/desplega-ai/agent-fs:${tag}"\n`);
500
+ });
501
+
502
+ test("bump-agent-fs rewrites the onboard pin and keeps the closing quote", async () => {
503
+ const root = await mkdtemp(join(tmpdir(), "bump-agent-fs-"));
504
+ try {
505
+ const repo = new URL("../../", import.meta.url);
506
+ const onboardPath = "src/commands/onboard/agent-fs-version.ts";
507
+ // Copy the onboard file even if PINS drops it, so the failure is the stale version.
508
+ for (const path of new Set([onboardPath, ...PINS.map((pin) => pin.path)])) {
509
+ await Bun.write(join(root, path), Bun.file(new URL(path, repo)));
510
+ }
511
+ const onboard = join(root, onboardPath);
512
+ expect(await Bun.file(onboard).text()).toContain(
513
+ `export const AGENT_FS_VERSION = "${AGENT_FS_VERSION}";`,
514
+ );
515
+ await applyPins("9.9.9", root);
516
+ const bumped = await Bun.file(onboard).text();
517
+ expect(bumped).toContain('export const AGENT_FS_VERSION = "9.9.9";');
518
+ expect(bumped).not.toContain(`"${AGENT_FS_VERSION}"`);
519
+ } finally {
520
+ await rm(root, { recursive: true, force: true });
521
+ }
522
+ });
523
+ });
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { generateEnv } from "../commands/onboard/env-generator.ts";
3
3
  import { INITIAL_STATE, type OnboardState } from "../commands/onboard/types.ts";
4
+ import { BASELINE_CASES, baselineState } from "./fixtures/onboard/baseline-states.ts";
4
5
 
5
6
  function makeState(overrides: Partial<OnboardState>): OnboardState {
6
7
  return { ...INITIAL_STATE, ...overrides };
@@ -232,3 +233,26 @@ describe("generateEnv", () => {
232
233
  expect(env).toContain("SENTRY_ORG=sentry-org");
233
234
  });
234
235
  });
236
+
237
+ describe("generateEnv with the agent-fs bundle flag", () => {
238
+ for (const { name, state } of BASELINE_CASES) {
239
+ test(`flag off: ${name} .env equals the pre-bundle output`, async () => {
240
+ expect(state.bundleAgentFs).toBe(false);
241
+ const expected = await Bun.file(
242
+ new URL(`./fixtures/onboard/${name}.env.txt`, import.meta.url),
243
+ ).text();
244
+ expect(generateEnv(state)).toBe(expected);
245
+ });
246
+ }
247
+
248
+ test("flag on: adds only a comment block; every bundle value lives in the compose", () => {
249
+ const off = generateEnv(baselineState("solo"));
250
+ const on = generateEnv({ ...baselineState("solo"), bundleAgentFs: true });
251
+ const added = on.split("\n").filter((line) => !off.split("\n").includes(line));
252
+ expect(added.filter((line) => line && !line.startsWith("#"))).toEqual([]);
253
+ expect(on).toContain("--no-agent-fs");
254
+ for (const s of ["COMB_ENABLED", "EMBEDDING_", "AGENT_FS_", "EMBEDDING_DIMENSIONS"]) {
255
+ expect(on).not.toContain(s);
256
+ }
257
+ });
258
+ });
@@ -94,6 +94,34 @@ describe("non-interactive onboarding CLI", () => {
94
94
  CHILD_PROCESS_TEST_BUDGET_MS,
95
95
  );
96
96
 
97
+ test(
98
+ "--yes bundles agent-fs by default and --no-agent-fs turns it off (dry run writes nothing)",
99
+ async () => {
100
+ const outputDir = await mkdtemp(join(tmpdir(), "onboard-agent-fs-"));
101
+ const env = { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: "example-test-oauth" };
102
+ try {
103
+ const bundled = await runChild(
104
+ ["bun", CLI_PATH, "onboard", "--yes", "--preset=solo", "--dry-run"],
105
+ { cwd: outputDir, env },
106
+ );
107
+ expect(bundled.exitCode).toBe(0);
108
+ expect(bundled.stdout).toContain("Bundling agent-fs and TEI");
109
+
110
+ const skipped = await runChild(
111
+ ["bun", CLI_PATH, "onboard", "--yes", "--preset=solo", "--dry-run", "--no-agent-fs"],
112
+ { cwd: outputDir, env },
113
+ );
114
+ expect(skipped.exitCode).toBe(0);
115
+ expect(skipped.stdout).toContain("Write docker-compose.yml");
116
+ expect(skipped.stdout).not.toContain("Bundling agent-fs");
117
+ expect(await readdir(outputDir)).toEqual([]);
118
+ } finally {
119
+ await rm(outputDir, { recursive: true, force: true });
120
+ }
121
+ },
122
+ CHILD_PROCESS_TEST_BUDGET_MS,
123
+ );
124
+
97
125
  test(
98
126
  "documents explicit unattended presets and conservative concurrency",
99
127
  async () => {
@@ -104,6 +132,7 @@ describe("non-interactive onboarding CLI", () => {
104
132
  expect(result.stdout).toContain("onboard --yes --preset=full");
105
133
  expect(result.stdout).toContain("default: lead 2, worker 1");
106
134
  expect(result.stdout).not.toContain("default: full");
135
+ expect(result.stdout).toContain("--no-agent-fs");
107
136
  },
108
137
  CHILD_PROCESS_TEST_BUDGET_MS,
109
138
  );
@@ -0,0 +1,36 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { generateEnv } from "../commands/onboard/env-generator.ts";
3
+ import { TEI_ARM64_IMAGE, teiImageForArch } from "../commands/onboard/tei-image.ts";
4
+ import { baselineState } from "./fixtures/onboard/baseline-states.ts";
5
+
6
+ describe("teiImageForArch", () => {
7
+ test("arm64 and aarch64 get the arm64 TEI tag", () => {
8
+ expect(TEI_ARM64_IMAGE).toBe("ghcr.io/huggingface/text-embeddings-inference:cpu-arm64-1.9.4");
9
+ expect(teiImageForArch("arm64")).toBe(TEI_ARM64_IMAGE);
10
+ expect(teiImageForArch("aarch64")).toBe(TEI_ARM64_IMAGE);
11
+ });
12
+
13
+ test("x64 and other arches keep the compose default", () => {
14
+ expect(teiImageForArch("x64")).toBeNull();
15
+ expect(teiImageForArch("ia32")).toBeNull();
16
+ expect(teiImageForArch("")).toBeNull();
17
+ });
18
+ });
19
+
20
+ describe("TEI_IMAGE in .env", () => {
21
+ test("is written only with the arm64 tag", () => {
22
+ const on = { ...baselineState("solo"), bundleAgentFs: true };
23
+ const arm = generateEnv({ ...on, teiImage: teiImageForArch("arm64") });
24
+ expect(arm).toContain(`\nTEI_IMAGE=${TEI_ARM64_IMAGE}\n`);
25
+ expect(arm.match(/^TEI_IMAGE=/gm)).toHaveLength(1);
26
+
27
+ const x64 = generateEnv({ ...on, teiImage: teiImageForArch("x64") });
28
+ expect(x64).not.toContain("TEI_IMAGE");
29
+ expect(x64).toContain("--no-agent-fs");
30
+ });
31
+
32
+ test("is never written with the bundle off", () => {
33
+ const env = generateEnv({ ...baselineState("solo"), teiImage: TEI_ARM64_IMAGE });
34
+ expect(env).not.toContain("TEI_IMAGE");
35
+ });
36
+ });