@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
@@ -2,7 +2,8 @@ import OpenAI from "openai";
2
2
  import { selectCredential } from "../../../utils/credentials";
3
3
  import { getOpenRouterAttributionHeaders } from "../../../utils/openrouter-base-url";
4
4
  import { DEFAULT_EMBEDDING_MODEL, EMBEDDING_DIMENSIONS } from "../constants";
5
- import type { EmbeddingProvider } from "../types";
5
+ import { noteEmbeddingFailure, noteEmbeddingSuccess } from "../embedding-recovery";
6
+ import type { EmbeddingInputKind, EmbeddingProvider, EmbedOptions } from "../types";
6
7
 
7
8
  // text-embedding-3-* reject any input over 8,192 tokens with a 400, and one
8
9
  // such input fails every input in its request. There is no tokenizer here, and
@@ -18,13 +19,19 @@ interface OpenAIEmbeddingConfig {
18
19
  apiKey?: string;
19
20
  model?: string;
20
21
  dimensions?: number;
22
+ queryPrefix?: string;
23
+ documentPrefix?: string;
21
24
  }
22
25
 
23
- function cleanInput(text: string): string {
24
- return text
25
- .replace(/[\n\r]/g, " ")
26
- .trim()
27
- .slice(0, MAX_EMBEDDING_INPUT_CHARS);
26
+ /**
27
+ * Flatten and trim the input, then prepend the task prefix. The cleaned text
28
+ * is cut so that prefix plus text stays within MAX_EMBEDDING_INPUT_CHARS. An
29
+ * empty input stays empty, so it is skipped and never embeds a bare prefix.
30
+ */
31
+ function cleanInput(text: string, prefix = ""): string {
32
+ const cleaned = text.replace(/[\n\r]/g, " ").trim();
33
+ if (!cleaned) return "";
34
+ return prefix + cleaned.slice(0, Math.max(0, MAX_EMBEDDING_INPUT_CHARS - prefix.length));
28
35
  }
29
36
 
30
37
  /** A 400 is about the request's inputs, so the other inputs can still embed. */
@@ -32,12 +39,36 @@ function isInputRejection(err: unknown): boolean {
32
39
  return err instanceof OpenAI.APIError && err.status === 400;
33
40
  }
34
41
 
42
+ /**
43
+ * The server rejected the request for its size, not for an input: a 413, or a
44
+ * 422 about the batch size (TEI: "batch size 70 > maximum allowed batch size
45
+ * 32"). One request per input then fits. Any other 422 (TEI also answers 422
46
+ * for a bad `dimensions` value) stays a plain failure, so it is not repeated
47
+ * once per input.
48
+ */
49
+ function isBatchTooLarge(err: unknown): boolean {
50
+ if (!(err instanceof OpenAI.APIError)) return false;
51
+ if (err.status === 413) return true;
52
+ return err.status === 422 && /batch size/i.test(err.message ?? "");
53
+ }
54
+
35
55
  function isInputTooLong(err: unknown): boolean {
36
56
  return (
37
57
  isInputRejection(err) && /maximum (context|input) length/i.test((err as Error).message ?? "")
38
58
  );
39
59
  }
40
60
 
61
+ /**
62
+ * The endpoint was down or overloaded (no HTTP response, a 5xx or a 429), so the
63
+ * same input can embed later. Only these start a backfill once calls work again:
64
+ * an input the endpoint rejects would fail again on every backfill.
65
+ */
66
+ function isOutage(err: unknown): boolean {
67
+ if (!(err instanceof OpenAI.APIError)) return true;
68
+ const status = err.status;
69
+ return status === undefined || status === 429 || status >= 500;
70
+ }
71
+
41
72
  export class OpenAIEmbeddingProvider implements EmbeddingProvider {
42
73
  readonly name: string;
43
74
  readonly dimensions: number;
@@ -45,6 +76,8 @@ export class OpenAIEmbeddingProvider implements EmbeddingProvider {
45
76
  private client: OpenAI | null = null;
46
77
  private readonly model: string;
47
78
  private readonly apiKey: string | undefined;
79
+ private readonly queryPrefix: string;
80
+ private readonly documentPrefix: string;
48
81
 
49
82
  constructor(config?: OpenAIEmbeddingConfig) {
50
83
  const apiKey = config?.apiKey ?? process.env.EMBEDDING_API_KEY ?? process.env.OPENAI_API_KEY;
@@ -59,6 +92,17 @@ export class OpenAIEmbeddingProvider implements EmbeddingProvider {
59
92
  this.name = config?.model
60
93
  ? `openai/${config.model}`
61
94
  : (process.env.EMBEDDING_MODEL ?? DEFAULT_EMBEDDING_MODEL);
95
+
96
+ // Read once here: a config reload builds a new provider. Used verbatim,
97
+ // so a trailing space (nomic: "search_query: ") is kept.
98
+ this.queryPrefix = config?.queryPrefix ?? process.env.EMBEDDING_QUERY_PREFIX ?? "";
99
+ this.documentPrefix = config?.documentPrefix ?? process.env.EMBEDDING_DOCUMENT_PREFIX ?? "";
100
+ }
101
+
102
+ private prefixFor(kind: EmbeddingInputKind | undefined): string {
103
+ if (kind === "query") return this.queryPrefix;
104
+ if (kind === "document") return this.documentPrefix;
105
+ return "";
62
106
  }
63
107
 
64
108
  isConfigured(): boolean {
@@ -107,7 +151,9 @@ export class OpenAIEmbeddingProvider implements EmbeddingProvider {
107
151
  while (true) {
108
152
  try {
109
153
  const response = await this.request(client, input);
110
- return this.toVector(response.data[0]?.embedding, "Embedding");
154
+ const vector = this.toVector(response.data[0]?.embedding, "Embedding");
155
+ if (vector) noteEmbeddingSuccess();
156
+ return vector;
111
157
  } catch (err) {
112
158
  const message = (err as Error).message;
113
159
  if (isInputTooLong(err) && input.length > MIN_EMBEDDING_INPUT_CHARS) {
@@ -119,26 +165,28 @@ export class OpenAIEmbeddingProvider implements EmbeddingProvider {
119
165
  continue;
120
166
  }
121
167
  console.error("[memory] Embedding failed:", message);
168
+ if (isOutage(err)) noteEmbeddingFailure();
122
169
  return null;
123
170
  }
124
171
  }
125
172
  }
126
173
 
127
- async embed(text: string): Promise<Float32Array | null> {
174
+ async embed(text: string, opts?: EmbedOptions): Promise<Float32Array | null> {
128
175
  const client = this.getClient();
129
176
  if (!client) return null;
130
177
 
131
- const cleaned = cleanInput(text);
178
+ const cleaned = cleanInput(text, this.prefixFor(opts?.kind));
132
179
  if (!cleaned) return null;
133
180
  return this.embedOne(client, cleaned);
134
181
  }
135
182
 
136
- async embedBatch(texts: string[]): Promise<(Float32Array | null)[]> {
183
+ async embedBatch(texts: string[], opts?: EmbedOptions): Promise<(Float32Array | null)[]> {
137
184
  const results: (Float32Array | null)[] = texts.map(() => null);
138
185
  const client = this.getClient();
139
186
  if (!client) return results;
140
187
 
141
- const cleaned = texts.map(cleanInput);
188
+ const prefix = this.prefixFor(opts?.kind);
189
+ const cleaned = texts.map((text) => cleanInput(text, prefix));
142
190
  const groups: number[][] = [];
143
191
  let group: number[] = [];
144
192
  let groupChars = 0;
@@ -166,13 +214,16 @@ export class OpenAIEmbeddingProvider implements EmbeddingProvider {
166
214
  if (originalIndex === undefined) continue;
167
215
  results[originalIndex] = this.toVector(item.embedding, "Batch embedding");
168
216
  }
217
+ if (indices.some((i) => results[i])) noteEmbeddingSuccess();
169
218
  } catch (err) {
170
- if (!isInputRejection(err)) {
219
+ if (!isInputRejection(err) && !isBatchTooLarge(err)) {
171
220
  console.error("[memory] Batch embedding failed:", (err as Error).message);
221
+ if (isOutage(err)) noteEmbeddingFailure();
172
222
  continue;
173
223
  }
174
- // One rejected input fails the whole request: embed each input alone so
175
- // only that input is cut down, and the rest embed as they are.
224
+ // One rejected input fails the whole request, and a server with a low
225
+ // batch cap rejects the request for its size: embed each input alone so
226
+ // only a bad input is cut down, and the rest embed as they are.
176
227
  console.warn(
177
228
  `[memory] Batch embedding rejected, embedding ${indices.length} inputs one by one: ${(err as Error).message}`,
178
229
  );
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Learnings captured through `store-progress`: one swarm-scope `manual` memory
3
+ * per learning, keyed `/learnings/<workstream>/<slug>` and tied to its task by
4
+ * `sourceTaskId`, so "what did we learn doing X" is one `keyPrefix` search.
5
+ *
6
+ * Source stays `manual` so nightly curation (memtax B1 reads only manual and
7
+ * file_index rows) picks learnings up without a new source value.
8
+ *
9
+ * A learning close to an existing swarm memory (raw cosine at or above
10
+ * LEARNING_DUPLICATE_MIN_SIMILARITY) is not stored: the existing row gets a
11
+ * positive rating instead. Known gap: embeddings are written in the background,
12
+ * so two near-identical learnings seconds apart can both pass the check.
13
+ * Nightly curation catches those.
14
+ */
15
+ import { getDbClient } from "@/be/db";
16
+ import { getEmbeddingProvider, getMemoryStore } from "@/be/memory";
17
+ import { LEARNING_DUPLICATE_MIN_SIMILARITY } from "@/be/memory/constants";
18
+ import { indexMemoryContent } from "@/be/memory/index-content";
19
+ import { applyRating } from "@/be/memory/raters/store";
20
+ import { contentSha256 } from "@/commands/profile-sync";
21
+ import type { AgentTask } from "@/types";
22
+ import { scrubSecrets } from "@/utils/secret-scrubber";
23
+
24
+ /** ~500 tokens at ceil(chars / 4); also keeps a learning to one memory chunk. */
25
+ export const LEARNING_MAX_CHARS = 2000;
26
+ export const LEARNING_MAX_TAGS = 5;
27
+ const LEARNINGS_ROOT = "/learnings";
28
+ const SEGMENT_MAX_CHARS = 60;
29
+ const KEY_ATTEMPTS = 4;
30
+ /** Where a near duplicate can already live: any workstream's learnings and the workstream digests. */
31
+ const DUPLICATE_SEARCH_PREFIXES = [`${LEARNINGS_ROOT}/`, "/longterm/workstreams/"];
32
+ /** memory_rating.source for the positive rating a duplicate gives the existing row. */
33
+ export const LEARNING_DUPLICATE_RATING_SOURCE = "learning-duplicate";
34
+
35
+ export type TaskLearningInput = string | { text: string; workstream?: string; tags?: string[] };
36
+
37
+ export type TaskLearningResult =
38
+ | { status: "stored"; memoryId: string; key: string; workstream: string }
39
+ | { status: "replayed"; memoryId: string; key: string }
40
+ | { status: "duplicate"; memoryId: string; key: string; similarity: number }
41
+ | { status: "failed"; error: string };
42
+
43
+ /** Lowercase kebab slug: `Agent Swarm!` -> `agent-swarm`. Empty when nothing usable is left. */
44
+ export function toKebab(value: string, maxChars = SEGMENT_MAX_CHARS): string {
45
+ return value
46
+ .normalize("NFKD")
47
+ .toLowerCase()
48
+ .replace(/[^a-z0-9]+/g, "-")
49
+ .replace(/^-+/, "")
50
+ .slice(0, maxChars)
51
+ .replace(/-+$/, "");
52
+ }
53
+
54
+ /** Explicit workstream, else the repo name from `vcsRepo` (`owner/name` or a URL), else `general`. */
55
+ export function resolveLearningWorkstream(
56
+ explicit: string | undefined,
57
+ vcsRepo: string | undefined,
58
+ ): string {
59
+ const fromExplicit = explicit ? toKebab(explicit) : "";
60
+ if (fromExplicit) return fromExplicit;
61
+ const repoName = vcsRepo
62
+ ?.trim()
63
+ .replace(/\/+$/, "")
64
+ .split("/")
65
+ .pop()
66
+ ?.replace(/\.git$/, "");
67
+ const fromRepo = repoName ? toKebab(repoName) : "";
68
+ return fromRepo || "general";
69
+ }
70
+
71
+ export async function storeTaskLearning(params: {
72
+ agentId: string;
73
+ isLead: boolean;
74
+ task: Pick<AgentTask, "id" | "vcsRepo">;
75
+ learning: TaskLearningInput;
76
+ }): Promise<TaskLearningResult> {
77
+ const { agentId, isLead, task } = params;
78
+ const input = typeof params.learning === "string" ? { text: params.learning } : params.learning;
79
+ // Scrub before anything is derived: a token in the title, workstream or a tag
80
+ // would otherwise survive (kebab-cased) in the key and tags, and the replay
81
+ // hash must match the hash of the scrubbed content the store persists.
82
+ const text = scrubSecrets(input.text).trim();
83
+ if (!text) return { status: "failed", error: "learning text is empty" };
84
+ if (text.length > LEARNING_MAX_CHARS) {
85
+ return {
86
+ status: "failed",
87
+ error: `learning text is over ~500 tokens (${LEARNING_MAX_CHARS} characters); shorten it`,
88
+ };
89
+ }
90
+ const tags = [
91
+ ...new Set((input.tags ?? []).map((tag) => toKebab(scrubSecrets(tag))).filter(Boolean)),
92
+ ].filter((tag) => tag !== "learning");
93
+ if (tags.length > LEARNING_MAX_TAGS) {
94
+ return { status: "failed", error: `learning takes at most ${LEARNING_MAX_TAGS} tags` };
95
+ }
96
+
97
+ const workstream = resolveLearningWorkstream(
98
+ input.workstream === undefined ? undefined : scrubSecrets(input.workstream),
99
+ task.vcsRepo === undefined ? undefined : scrubSecrets(task.vcsRepo),
100
+ );
101
+
102
+ try {
103
+ // Replay first: the same text on the same task is a retry, not a duplicate.
104
+ const replay = await findReplay(task.id, contentSha256(text));
105
+ if (replay) return replay;
106
+ // Outside the write transaction: the embedding call goes over the network.
107
+ const duplicate = await findNearDuplicate({ agentId, isLead, text });
108
+ if (duplicate) {
109
+ await rateDuplicate(duplicate, task.id);
110
+ return duplicate;
111
+ }
112
+ // One write transaction for the replay check and the insert, so two
113
+ // overlapping calls with the same learning store it once.
114
+ return await getDbClient().transaction(() =>
115
+ insertLearning({ agentId, isLead, taskId: task.id, text, tags, workstream }),
116
+ );
117
+ } catch (err) {
118
+ return {
119
+ status: "failed",
120
+ error: scrubSecrets(err instanceof Error ? err.message : String(err)),
121
+ };
122
+ }
123
+ }
124
+
125
+ async function findReplay(
126
+ taskId: string,
127
+ contentHash: string,
128
+ ): Promise<Extract<TaskLearningResult, { status: "replayed" }> | null> {
129
+ const replay = await getDbClient().get<{ id: string; key: string }>(
130
+ `SELECT id, key FROM agent_memory
131
+ WHERE sourceTaskId = ? AND contentHash = ? AND key LIKE '${LEARNINGS_ROOT}/%'
132
+ LIMIT 1`,
133
+ [taskId, contentHash],
134
+ );
135
+ return replay ? { status: "replayed", memoryId: replay.id, key: replay.key } : null;
136
+ }
137
+
138
+ /**
139
+ * The closest swarm memory under the duplicate prefixes when its raw cosine to
140
+ * the learning reaches the threshold. No `queryText`, so the store takes the
141
+ * vector or brute-force path and `similarity` is raw cosine, not an FTS rank or
142
+ * a fused score. Any failure (no embedding provider, a search error) returns
143
+ * null so the learning is stored as usual.
144
+ */
145
+ async function findNearDuplicate(params: {
146
+ agentId: string;
147
+ isLead: boolean;
148
+ text: string;
149
+ }): Promise<Extract<TaskLearningResult, { status: "duplicate" }> | null> {
150
+ try {
151
+ const embedding = await getEmbeddingProvider().embed(params.text);
152
+ if (!embedding) return null;
153
+ const store = getMemoryStore();
154
+ const results = await Promise.all(
155
+ DUPLICATE_SEARCH_PREFIXES.map((keyPrefix) =>
156
+ store.search(embedding, params.agentId, {
157
+ scope: "swarm",
158
+ isLead: params.isLead,
159
+ keyPrefix,
160
+ limit: 1,
161
+ }),
162
+ ),
163
+ );
164
+ const best = results
165
+ .flat()
166
+ .filter((candidate) => candidate.key)
167
+ .sort((a, b) => b.similarity - a.similarity)[0];
168
+ if (!best || best.similarity < LEARNING_DUPLICATE_MIN_SIMILARITY) return null;
169
+ return {
170
+ status: "duplicate",
171
+ memoryId: best.id,
172
+ key: best.key!,
173
+ similarity: Math.round(best.similarity * 1000) / 1000,
174
+ };
175
+ } catch (err) {
176
+ console.warn(
177
+ "[task-learning] near-duplicate check failed; storing the learning:",
178
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
179
+ );
180
+ return null;
181
+ }
182
+ }
183
+
184
+ /** A repeated lesson is evidence the existing row is useful. A failed rating never fails the call. */
185
+ async function rateDuplicate(
186
+ duplicate: Extract<TaskLearningResult, { status: "duplicate" }>,
187
+ taskId: string,
188
+ ): Promise<void> {
189
+ try {
190
+ await applyRating(
191
+ [
192
+ {
193
+ memoryId: duplicate.memoryId,
194
+ signal: 1,
195
+ weight: 1,
196
+ source: LEARNING_DUPLICATE_RATING_SOURCE,
197
+ reasoning: `store-progress learning matched at cosine ${duplicate.similarity}`,
198
+ },
199
+ ],
200
+ { taskId },
201
+ );
202
+ } catch (err) {
203
+ console.warn(
204
+ "[task-learning] duplicate rating failed:",
205
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
206
+ );
207
+ }
208
+ }
209
+
210
+ async function insertLearning(params: {
211
+ agentId: string;
212
+ isLead: boolean;
213
+ taskId: string;
214
+ text: string;
215
+ tags: string[];
216
+ workstream: string;
217
+ }): Promise<TaskLearningResult> {
218
+ const { agentId, isLead, taskId, text, tags, workstream } = params;
219
+ // indexMemoryContent stores text this short as one trimmed chunk, so this
220
+ // hash is the row's contentHash: a replayed call finds its own earlier write.
221
+ const replay = await findReplay(taskId, contentSha256(text));
222
+ if (replay) return replay;
223
+
224
+ const title = text.split(/\r?\n/, 1)[0]!.trim().slice(0, 200);
225
+ const baseKey = `${LEARNINGS_ROOT}/${workstream}/${toKebab(title) || "learning"}`;
226
+
227
+ // Keys are unique per owner and a reused key fails rather than upserts.
228
+ // Keep the path unique across the swarm: on a taken key, add a short suffix.
229
+ for (let attempt = 0; attempt < KEY_ATTEMPTS; attempt++) {
230
+ const key = attempt === 0 ? baseKey : `${baseKey}-${crypto.randomUUID().slice(0, 6)}`;
231
+ const taken = await getDbClient().get<{ id: string }>(
232
+ "SELECT id FROM agent_memory WHERE scope = 'swarm' AND key = ? LIMIT 1",
233
+ [key],
234
+ );
235
+ if (taken) continue;
236
+ try {
237
+ const result = await indexMemoryContent({
238
+ agentId,
239
+ content: text,
240
+ name: title,
241
+ scope: "swarm",
242
+ source: "manual",
243
+ sourceTaskId: taskId,
244
+ tags: ["learning", ...tags],
245
+ intent: "store-progress learning",
246
+ key,
247
+ writer: { principal: { kind: "agent", agentId, isLead }, source: "mcp" },
248
+ });
249
+ return { status: "stored", memoryId: result.memoryIds[0]!, key, workstream };
250
+ } catch (err) {
251
+ // The key is taken after all: try a suffixed one.
252
+ if (/UNIQUE constraint failed/i.test((err as Error).message)) continue;
253
+ throw err;
254
+ }
255
+ }
256
+ return { status: "failed", error: `no free key under ${baseKey}` };
257
+ }
@@ -4,11 +4,22 @@ import type { AgentMemory, AgentMemoryScope, AgentMemorySource } from "@/types";
4
4
  // EmbeddingProvider — text to vector, swappable
5
5
  // ============================================================================
6
6
 
7
+ /**
8
+ * What an input is for. Some models (nomic-embed) need a task prefix on each
9
+ * input: one for search queries, another for stored documents. No kind means
10
+ * no prefix.
11
+ */
12
+ export type EmbeddingInputKind = "query" | "document";
13
+
14
+ export interface EmbedOptions {
15
+ kind?: EmbeddingInputKind;
16
+ }
17
+
7
18
  export interface EmbeddingProvider {
8
19
  readonly name: string;
9
20
  readonly dimensions: number;
10
- embed(text: string): Promise<Float32Array | null>;
11
- embedBatch(texts: string[]): Promise<(Float32Array | null)[]>;
21
+ embed(text: string, opts?: EmbedOptions): Promise<Float32Array | null>;
22
+ embedBatch(texts: string[], opts?: EmbedOptions): Promise<(Float32Array | null)[]>;
12
23
  /** True when the provider has a usable API key — no network call. */
13
24
  isConfigured(): boolean;
14
25
  }
@@ -0,0 +1,3 @@
1
+ -- Optional per-script executor override ("native" | "quickjs").
2
+ -- When NULL, the deployment's SCRIPT_EXECUTOR default applies.
3
+ ALTER TABLE scripts ADD COLUMN executor TEXT DEFAULT NULL;
@@ -0,0 +1,11 @@
1
+ -- Kind of single-use code in connector_codes.
2
+ --
3
+ -- `connector`: the agent-swarm.dev ChatGPT connector code (10 minute TTL).
4
+ -- `invite`: a teammate invite from `POST /api/invites` (7 day TTL). Its
5
+ -- exchange at `POST /api/invites/exchange` also flips the user from
6
+ -- `invited` to `active`.
7
+ --
8
+ -- Each exchange route redeems only its own kind. Existing rows are connector
9
+ -- codes, so they keep today's behavior.
10
+ ALTER TABLE connector_codes ADD COLUMN kind TEXT NOT NULL DEFAULT 'connector'
11
+ CHECK (kind IN ('connector', 'invite'));
@@ -20,6 +20,7 @@
20
20
  * same per-authorization locks in ensure-token, so overlap is safe.
21
21
  */
22
22
  import { forceRefreshAuthorizationOrThrow } from "../oauth/ensure-token";
23
+ import { isEnvFlagEnabled } from "../utils/env-flag";
23
24
  import { scrubSecrets } from "../utils/secret-scrubber";
24
25
  import {
25
26
  type AuthorizationSweepRow,
@@ -138,7 +139,7 @@ async function runSweepTick(trigger: "startup" | "interval"): Promise<void> {
138
139
  * already running.
139
140
  */
140
141
  export function startOAuthRefreshSweep(): void {
141
- if (process.env.OAUTH_REFRESH_SWEEP_DISABLE === "true") {
142
+ if (isEnvFlagEnabled("OAUTH_REFRESH_SWEEP_DISABLE", false)) {
142
143
  console.log("[oauth-refresh-sweep] Disabled via OAUTH_REFRESH_SWEEP_DISABLE");
143
144
  return;
144
145
  }