@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
package/src/http/users.ts CHANGED
@@ -20,6 +20,8 @@
20
20
  * DELETE /api/users/:id/mcp-tokens/:tokenId
21
21
  * POST /api/users/:id/connector-codes
22
22
  * POST /api/connector/exchange (public: the code is the credential)
23
+ * POST /api/invites
24
+ * POST /api/invites/exchange (public: the code is the credential)
23
25
  * POST /api/users/:id/merge
24
26
  * GET /api/users/:id/events
25
27
  * POST /api/users/:id/identities
@@ -31,7 +33,10 @@ import { z } from "zod";
31
33
  import {
32
34
  createConnectorCode,
33
35
  DEFAULT_CONNECTOR_CODE_LABEL,
36
+ DEFAULT_INVITE_CODE_LABEL,
37
+ deleteUnusedInviteCodes,
34
38
  exchangeConnectorCode,
39
+ INVITE_CODE_TTL_MS,
35
40
  } from "../be/connector-codes";
36
41
  import {
37
42
  createUser,
@@ -45,8 +50,15 @@ import {
45
50
  updateUser,
46
51
  upsertBudget,
47
52
  } from "../be/db";
48
- import { assertNotLastAdmin, LastAdminError } from "../be/rbac-roles";
49
53
  import {
54
+ assertNotLastAdmin,
55
+ getUserGrant,
56
+ LastAdminError,
57
+ listUserRoles,
58
+ setUserRoles,
59
+ } from "../be/rbac-roles";
60
+ import {
61
+ findUserByEmail,
50
62
  getUserIdentities,
51
63
  type IdentityEvent,
52
64
  linkIdentity,
@@ -57,12 +69,16 @@ import {
57
69
  revokeToken,
58
70
  unlinkIdentity,
59
71
  } from "../be/users";
72
+ import { isRbacEnabled } from "../rbac";
60
73
  import { UserCommsPrefsSchema, UserSchema } from "../types";
74
+ import { DEFAULT_APP_URL } from "../utils/constants";
75
+ import { assertUrlSafe } from "../utils/egress-guard";
61
76
  import { getRequestAuth } from "../utils/request-auth-context";
62
77
  import { resolveAppUrl, resolveMcpBaseUrl } from "./config-values";
63
78
  import { clientIp, createIpRateLimiter } from "./ip-rate-limit";
64
79
  import { getOperatorActor } from "./operator-actor";
65
80
  import { route } from "./route-def";
81
+ import { ensureRoleManager } from "./user-roles";
66
82
  import { jsonError } from "./utils";
67
83
 
68
84
  // ─── Response composition ────────────────────────────────────────────────────
@@ -172,6 +188,15 @@ const whoamiRoute = route({
172
188
  // `components.schemas.User` to `object | null` for every consumer;
173
189
  // the union keeps nullability local to this response field.
174
190
  user: z.union([UserSchema, z.null()]),
191
+ // Optional so older clients and older servers stay compatible. The
192
+ // dashboard hides what the caller cannot do; the server still gates.
193
+ grants: z
194
+ .object({
195
+ grantsAll: z.boolean(),
196
+ verbs: z.array(z.string()),
197
+ roles: z.array(z.string()),
198
+ })
199
+ .optional(),
175
200
  }),
176
201
  },
177
202
  401: { description: "Unauthorized" },
@@ -448,6 +473,80 @@ const exchangeConnectorCodeRoute = route({
448
473
  },
449
474
  });
450
475
 
476
+ const INVITE_ROLES = ["requester", "viewer", "admin"] as const;
477
+
478
+ const createInviteRoute = route({
479
+ method: "post",
480
+ path: "/api/invites",
481
+ pattern: ["api", "invites"],
482
+ summary: "Invite a teammate with a one-time API link",
483
+ description:
484
+ "Creates an invited user with the given role (requester by default) and returns a 7-day single-use API link. The API redirects to the current APP_URL dashboard with the code in the URL fragment. The browser exchanges the code at POST /api/invites/exchange for a personal MCP token. Re-inviting an invited user replaces the role and link. Active or suspended users return 409 user_exists. No email is sent.",
485
+ tags: ["Users"],
486
+ body: z.object({
487
+ email: z.email(),
488
+ name: z.string().trim().min(1).max(200).optional(),
489
+ role: z.enum(INVITE_ROLES).default("requester"),
490
+ }),
491
+ responses: {
492
+ 201: {
493
+ description: "The invited user, API link, dashboard URL, expiry and API reachability",
494
+ schema: z.object({
495
+ user: ComposedUserSchema.nullable(),
496
+ inviteUrl: z.string(),
497
+ appUrl: z.string(),
498
+ expiresAt: z.string(),
499
+ apiReachable: z.enum(["public", "local"]),
500
+ }),
501
+ },
502
+ 401: { description: "Unauthorized" },
503
+ 403: { description: "Not allowed to manage roles" },
504
+ 409: { description: "The email belongs to an active or suspended user" },
505
+ },
506
+ auth: { apiKey: true },
507
+ rbac: { permission: "user.role.manage" },
508
+ });
509
+
510
+ const openInviteRoute = route({
511
+ method: "get",
512
+ path: "/invite/{code}",
513
+ pattern: ["invite", null],
514
+ summary: "Open an invite in the configured dashboard",
515
+ description:
516
+ "Redirects to the current APP_URL dashboard with the code in the URL fragment. Does not validate or consume the invite.",
517
+ tags: ["Users"],
518
+ params: z.object({ code: z.string() }),
519
+ responses: {
520
+ 302: { description: "Redirect to the dashboard", unstructured: "Dashboard redirect" },
521
+ 404: { description: "Malformed code (code_invalid)" },
522
+ },
523
+ auth: { apiKey: false },
524
+ });
525
+
526
+ const exchangeInviteCodeRoute = route({
527
+ method: "post",
528
+ path: "/api/invites/exchange",
529
+ pattern: ["api", "invites", "exchange"],
530
+ summary: "Exchange an invite code for the invited user's MCP token",
531
+ description:
532
+ "Unauthenticated: the single-use invite code is the credential. Rate limited per IP. Activates the invited user. Unknown, expired, used and connector codes, and codes for users who are no longer invited, all return 404 code_invalid.",
533
+ tags: ["Users"],
534
+ body: z.object({ code: z.string().min(1).max(256) }),
535
+ // Public route: cap the body before it is buffered and parsed.
536
+ maxBodyBytes: 1024,
537
+ responses: {
538
+ 200: {
539
+ description: "Minted MCP token for the invited user",
540
+ schema: z.object({ token: z.string(), userId: z.string() }),
541
+ },
542
+ 404: { description: "Code unknown, malformed, expired, already used or not an invite" },
543
+ 413: { description: "Request body too large" },
544
+ 429: { description: "Rate limited" },
545
+ },
546
+ auth: { apiKey: false },
547
+ rbac: { ungated: "the one-time invite code is the credential" },
548
+ });
549
+
451
550
  const connectorDiscoveryRoute = route({
452
551
  method: "get",
453
552
  path: "/api/connector/discovery",
@@ -681,6 +780,35 @@ export function _resetConnectorExchangeRateLimitForTests(): void {
681
780
  connectorExchangeRateLimiter.reset();
682
781
  }
683
782
 
783
+ /** 10 invite exchanges per minute per client IP. */
784
+ const inviteExchangeRateLimiter = createIpRateLimiter({
785
+ capacity: 10,
786
+ refillPerMs: 10 / 60_000,
787
+ });
788
+
789
+ export function _resetInviteExchangeRateLimitForTests(): void {
790
+ inviteExchangeRateLimiter.reset();
791
+ }
792
+
793
+ /**
794
+ * `local` when teammates' browsers likely cannot reach the API: a loopback or
795
+ * private IP host, a single-label name (a compose service such as
796
+ * `swarm-api`), or a `.local` / `.internal` / `.lan` name.
797
+ */
798
+ function apiReachability(apiUrl: string): "public" | "local" {
799
+ let host: string;
800
+ try {
801
+ host = assertUrlSafe(apiUrl, { allowInsecure: true }).hostname.toLowerCase();
802
+ } catch {
803
+ return "local";
804
+ }
805
+ if (!host.includes(".") && !host.includes(":")) return "local";
806
+ if (/\.(local|internal|lan|home\.arpa)\.?$/.test(host)) return "local";
807
+ return "public";
808
+ }
809
+
810
+ class InviteUserExistsError extends Error {}
811
+
684
812
  // ─── Handler ─────────────────────────────────────────────────────────────────
685
813
 
686
814
  export async function handleUsers(
@@ -695,9 +823,26 @@ export async function handleUsers(
695
823
  if (!parsed) return true;
696
824
  const auth = getRequestAuth(req);
697
825
  if (auth?.kind === "user") {
698
- whoamiRoute.respond(res, 200, { kind: "user", user: auth.user });
826
+ const [grant, roles] = await Promise.all([
827
+ getUserGrant(auth.user.id),
828
+ listUserRoles(auth.user.id),
829
+ ]);
830
+ const grantsAll = !isRbacEnabled() || grant.grantsAll;
831
+ whoamiRoute.respond(res, 200, {
832
+ kind: "user",
833
+ user: auth.user,
834
+ grants: {
835
+ grantsAll,
836
+ verbs: grantsAll ? [] : [...grant.verbs].sort(),
837
+ roles: roles.map((role) => role.name),
838
+ },
839
+ });
699
840
  } else {
700
- whoamiRoute.respond(res, 200, { kind: "operator", user: null });
841
+ whoamiRoute.respond(res, 200, {
842
+ kind: "operator",
843
+ user: null,
844
+ grants: { grantsAll: true, verbs: [], roles: [] },
845
+ });
701
846
  }
702
847
  return true;
703
848
  }
@@ -934,6 +1079,130 @@ export async function handleUsers(
934
1079
  return true;
935
1080
  }
936
1081
 
1082
+ // ─── POST /api/invites ─────────────────────────────────────────────────────
1083
+ if (createInviteRoute.match(req.method, pathSegments)) {
1084
+ const parsed = await createInviteRoute.parse(req, res, pathSegments, queryParams);
1085
+ if (!parsed) return true;
1086
+ // Same gate as PUT /api/users/{id}/roles: an invite assigns a role, so a
1087
+ // custom role holding user.role.manage must not hand out admin.
1088
+ if (!(await ensureRoleManager(req, res))) return true;
1089
+ const actor = getOperatorActor(req, res);
1090
+ if (!actor) return true;
1091
+ const { email, name, role } = parsed.body;
1092
+ const actorUserId = actor.kind === "user" ? actor.id : null;
1093
+
1094
+ let invited: { userId: string; code: string; expiresAt: string };
1095
+ try {
1096
+ invited = await getDbClient().transaction(async () => {
1097
+ const existing = await findUserByEmail(email);
1098
+ if (existing && existing.status !== "invited") throw new InviteUserExistsError();
1099
+ let userId: string;
1100
+ if (existing) {
1101
+ userId = existing.id;
1102
+ await deleteUnusedInviteCodes(userId);
1103
+ } else {
1104
+ const user = await createUser({
1105
+ name: name ?? (email.split("@")[0] || email),
1106
+ email,
1107
+ status: "invited",
1108
+ });
1109
+ userId = user.id;
1110
+ await recordIdentityEvent(userId, "identity_added", actor, null, {
1111
+ email,
1112
+ source: "invite",
1113
+ });
1114
+ }
1115
+ const { before, after } = await setUserRoles(userId, [role], actorUserId);
1116
+ if (before.join(",") !== after.join(",")) {
1117
+ await recordIdentityEvent(
1118
+ userId,
1119
+ "profile_changed",
1120
+ actor,
1121
+ { rbacRoles: before },
1122
+ { rbacRoles: after },
1123
+ );
1124
+ }
1125
+ const { code, expiresAt } = await createConnectorCode(
1126
+ userId,
1127
+ DEFAULT_INVITE_CODE_LABEL,
1128
+ actor,
1129
+ new Date(),
1130
+ { kind: "invite", ttlMs: INVITE_CODE_TTL_MS },
1131
+ );
1132
+ return { userId, code, expiresAt };
1133
+ });
1134
+ } catch (err) {
1135
+ if (err instanceof InviteUserExistsError) {
1136
+ jsonError(res, "user_exists", 409);
1137
+ return true;
1138
+ }
1139
+ // Never echo the error: it must not carry the code or the link.
1140
+ jsonError(res, "Failed to create invite", 500);
1141
+ return true;
1142
+ }
1143
+
1144
+ const apiUrl = await resolveMcpBaseUrl();
1145
+ const appUrl = (await resolveAppUrl()) ?? DEFAULT_APP_URL;
1146
+ const inviteUrl = `${apiUrl}/invite/${invited.code}`;
1147
+ createInviteRoute.respond(res, 201, {
1148
+ user: await composeUser(invited.userId),
1149
+ inviteUrl,
1150
+ appUrl,
1151
+ expiresAt: invited.expiresAt,
1152
+ apiReachable: apiReachability(apiUrl),
1153
+ });
1154
+ return true;
1155
+ }
1156
+
1157
+ // GET /invite/:code only redirects. Link previews must not consume invites.
1158
+ if (openInviteRoute.match(req.method, pathSegments)) {
1159
+ const parsed = await openInviteRoute.parse(req, res, pathSegments, queryParams);
1160
+ if (!parsed) return true;
1161
+ const { code } = parsed.params;
1162
+ if (!/^[A-Za-z0-9_-]{16,256}$/.test(code)) {
1163
+ jsonError(res, "code_invalid", 404);
1164
+ return true;
1165
+ }
1166
+ const apiUrl = await resolveMcpBaseUrl();
1167
+ const appUrl = (await resolveAppUrl()) ?? DEFAULT_APP_URL;
1168
+ res.writeHead(302, {
1169
+ Location: `${appUrl}/join?swarm=${encodeURIComponent(apiUrl)}#code=${code}`,
1170
+ "Cache-Control": "no-store",
1171
+ "Referrer-Policy": "no-referrer",
1172
+ });
1173
+ res.end();
1174
+ return true;
1175
+ }
1176
+
1177
+ // ─── POST /api/invites/exchange ────────────────────────────────────────────
1178
+ if (exchangeInviteCodeRoute.match(req.method, pathSegments)) {
1179
+ if (!inviteExchangeRateLimiter.take(clientIp(req))) {
1180
+ res.setHeader("Retry-After", "6");
1181
+ jsonError(res, "rate_limited", 429);
1182
+ return true;
1183
+ }
1184
+ const parsed = await exchangeInviteCodeRoute.parse(req, res, pathSegments, queryParams);
1185
+ if (!parsed) return true;
1186
+
1187
+ // Same generated shape as connector codes; reject anything else before hashing.
1188
+ if (!CONNECTOR_CODE_PATTERN.test(parsed.body.code)) {
1189
+ jsonError(res, "code_invalid", 404);
1190
+ return true;
1191
+ }
1192
+
1193
+ try {
1194
+ const result = await exchangeConnectorCode(parsed.body.code, { kind: "invite" });
1195
+ if (!result) {
1196
+ jsonError(res, "code_invalid", 404);
1197
+ return true;
1198
+ }
1199
+ exchangeInviteCodeRoute.respond(res, 200, result);
1200
+ } catch {
1201
+ jsonError(res, "Failed to exchange code", 500);
1202
+ }
1203
+ return true;
1204
+ }
1205
+
937
1206
  // ─── DELETE /api/users/:id/mcp-tokens/:tokenId ────────────────────────────
938
1207
  if (revokeUserMcpTokenRoute.match(req.method, pathSegments)) {
939
1208
  const parsed = await revokeUserMcpTokenRoute.parse(req, res, pathSegments, queryParams);
package/src/http/utils.ts CHANGED
@@ -255,19 +255,38 @@ export function wireHttpSpanLifecycle(
255
255
  });
256
256
  }
257
257
 
258
+ /**
259
+ * Redact invite codes and agent-fs share tokens from logs and traces.
260
+ * Share paths also accept the `/agentfs` proxy prefix.
261
+ */
262
+ export function redactShareTokenInPath(pathname: string): string {
263
+ const segments = pathname.split("/");
264
+ if (segments[1] === "invite" && segments[2]) {
265
+ segments[2] = "[REDACTED]";
266
+ return segments.join("/");
267
+ }
268
+ const at = segments[1] === "agentfs" ? 2 : 1;
269
+ if ((segments[at] === "share" || segments[at] === "site") && segments[at + 1]) {
270
+ segments[at + 1] = "[REDACTED]";
271
+ return segments.join("/");
272
+ }
273
+ return pathname;
274
+ }
275
+
258
276
  export function safeRequestUrlForLog(rawUrl: string | undefined): string {
259
277
  if (!rawUrl) return "";
260
278
 
261
279
  try {
262
280
  const url = new URL(rawUrl, "http://localhost");
281
+ const pathname = redactShareTokenInPath(url.pathname);
263
282
  const params = Array.from(url.searchParams.keys());
264
- if (params.length === 0) return url.pathname;
283
+ if (params.length === 0) return pathname;
265
284
 
266
285
  const redactedQuery = params.map((key) => `${key}=[REDACTED]`).join("&");
267
- return `${url.pathname}?${redactedQuery}`;
286
+ return `${pathname}?${redactedQuery}`;
268
287
  } catch {
269
288
  const pathOnly = rawUrl.split("?")[0] || rawUrl;
270
- return scrubSecrets(pathOnly);
289
+ return scrubSecrets(redactShareTokenInPath(pathOnly));
271
290
  }
272
291
  }
273
292
 
@@ -307,6 +307,15 @@ const triggerWorkflowRoute = route({
307
307
  "With an `Idempotency-Key`, a retry returns the accepted run's `runId` and `skipped` without starting, resuming or waiting for anything, even after the workflow was disabled or edited and whatever state the run is in now.",
308
308
  tags: ["Workflows"],
309
309
  params: z.object({ id: z.string() }),
310
+ query: z.object({
311
+ wait: z
312
+ .enum(["true", "false"])
313
+ .optional()
314
+ .describe(
315
+ "Default true: respond after the run's instant steps ran, when it finishes or parks at its first async step. " +
316
+ "false: respond as soon as the run is committed and walk it in the background.",
317
+ ),
318
+ }),
310
319
  headers: idempotencyKeyHeader("starts one run"),
311
320
  responses: {
312
321
  201: {
@@ -1002,6 +1011,7 @@ export async function handleWorkflows(
1002
1011
  runId = await startWorkflowExecution(workflow, body, getExecutorRegistry(), {
1003
1012
  triggerType: "api",
1004
1013
  requestedByUserId: (await resolveHttpAuditUserId(req, myAgentId)) ?? undefined,
1014
+ detach: parsed.query.wait === "false",
1005
1015
  // Recorded in the run's own transaction, before the walk starts.
1006
1016
  accept: claim
1007
1017
  ? async (run) => {
package/src/http/x.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  MAX_SCRIPT_WALL_CLOCK_MS,
21
21
  MIN_SCRIPT_WALL_CLOCK_MS,
22
22
  } from "../scripts-runtime/executors/types";
23
+ import { HOST_VALIDATED_ARGS } from "../scripts-runtime/host-validated-args";
23
24
  import type { RunScriptOutput } from "../scripts-runtime/loader";
24
25
  import { scrubObject, scrubSecrets } from "../utils/secret-scrubber";
25
26
  import { validateJsonSchema } from "../workflows/json-schema-validator";
@@ -214,6 +215,7 @@ export async function handleX(
214
215
  // Scripts predating the schema column, or where extraction failed, have a
215
216
  // null schema — we skip and let the in-subprocess Zod check surface as a
216
217
  // runtime_error instead.
218
+ let hostValidatedArgs = false;
217
219
  if (script.argsJsonSchema) {
218
220
  try {
219
221
  const schema = JSON.parse(script.argsJsonSchema) as Record<string, unknown>;
@@ -227,6 +229,7 @@ export async function handleX(
227
229
  });
228
230
  return true;
229
231
  }
232
+ hostValidatedArgs = true;
230
233
  } catch {
231
234
  // Malformed stored schema — don't block execution on it.
232
235
  }
@@ -245,7 +248,9 @@ export async function handleX(
245
248
  const output = await runScriptWithRunCredential({
246
249
  source: script.source,
247
250
  args: args ?? null,
251
+ [HOST_VALIDATED_ARGS]: hostValidatedArgs,
248
252
  fsMode: "none",
253
+ executor: script.executor,
249
254
  agentId: endpoint.agentId,
250
255
  // timeoutMs only raises the wall-clock limit (network-bound scripts can use
251
256
  // the full window); the CPU ulimit (`ulimit -t`) is intentionally left at
@@ -22,6 +22,7 @@
22
22
  import type { ProviderTraits } from "../providers/types";
23
23
  import { getSlackConfiguration } from "../slack/config";
24
24
  import type { ProviderName } from "../types";
25
+ import { resolveAgentFsLinkBase } from "../utils/constants";
25
26
  import { isSteeringEnabled } from "../utils/steering-enabled";
26
27
  import { matchesDefaultClaudeMd, matchesDefaultIdentityMd } from "./defaults";
27
28
  import { resolveTemplateAsync } from "./resolver";
@@ -74,6 +75,26 @@ function claudeMdSourcePath(provider: string): string {
74
75
  /** Providers that get the repo CLAUDE.md inlined until native loading is verified. */
75
76
  const REPO_CLAUDE_MD_INLINE_PROVIDERS: ReadonlySet<string> = new Set(["opencode"]);
76
77
 
78
+ /**
79
+ * Template event for the tool-discovery line. Amp, Codex and OpenCode run MCP
80
+ * tools inside their own code sandbox (`code_exec`, `exec`, `execute`), each
81
+ * with its own call shape, so the generic lines are wrong for them.
82
+ */
83
+ function toolDiscoveryEvent(provider: string, hasToolSearch: boolean): string {
84
+ switch (provider) {
85
+ case "amp":
86
+ return "system.agent.tool_discovery.amp";
87
+ case "codex":
88
+ return "system.agent.tool_discovery.codex";
89
+ case "opencode":
90
+ return "system.agent.tool_discovery.opencode";
91
+ default:
92
+ return hasToolSearch
93
+ ? "system.agent.tool_discovery.search"
94
+ : "system.agent.tool_discovery.direct";
95
+ }
96
+ }
97
+
77
98
  export function areSlackPromptToolsEnabled(
78
99
  env: NodeJS.ProcessEnv = process.env,
79
100
  serverCapabilities?: string[],
@@ -229,7 +250,8 @@ export const getBasePrompt = async (args: BasePromptArgs): Promise<string> => {
229
250
  const agentFsConfigured = hasLocalEnv && Boolean(process.env.AGENT_FS_API_URL);
230
251
  const outputsResult = await resolveTemplateAsync(
231
252
  agentFsConfigured ? "system.agent.outputs" : "system.agent.outputs.no_agent_fs",
232
- {},
253
+ // Unset AGENT_FS_LINK_TARGET keeps the literal the agent expands in its shell.
254
+ { fileLinkBase: resolveAgentFsLinkBase("prompt") ?? `\${AGENT_FS_LIVE_URL}` },
233
255
  );
234
256
  prompt += outputsResult.text;
235
257
  }
@@ -257,26 +279,29 @@ export const getBasePrompt = async (args: BasePromptArgs): Promise<string> => {
257
279
 
258
280
  // I. Tools and skills. Skipped without MCP: the discovery tools are MCP tools.
259
281
  if (hasMcp) {
260
- // Amp's MCP tools sit behind its own `tool_search` + `code_exec` with
261
- // underscored names, so the generic "load with your tool search" line is not enough.
262
- const discoveryEvent =
263
- provider === "amp"
264
- ? "system.agent.tool_discovery.amp"
265
- : hasToolSearch
266
- ? "system.agent.tool_discovery.search"
267
- : "system.agent.tool_discovery.direct";
282
+ const discoveryEvent = toolDiscoveryEvent(provider, hasToolSearch);
268
283
  const discoveryResult = await resolveTemplateAsync(discoveryEvent, {});
269
284
  const toolsResult = await resolveTemplateAsync(
270
285
  "system.agent.tools_skills",
271
286
  renderToolsAndSkillsVars({
272
287
  skillsSummary: args.skillsSummary,
273
288
  mcpServers: args.mcpServers,
289
+ // pi defers installed MCP servers together with swarm tools.
290
+ mcpToolsDeferred: provider === "pi" && hasToolSearch,
274
291
  nativeSkillDiscovery,
275
292
  toolDiscovery: discoveryResult.text,
276
293
  hasLocalEnv,
277
294
  }),
278
295
  );
279
296
  prompt += toolsResult.text;
297
+
298
+ // Lead docs pointer: only when the lead has the `swarm-docs` skill. The
299
+ // skill list is unknown on claude-managed (the cloud sandbox owns skill
300
+ // delivery) and before the first skill load, so render it then.
301
+ if (role === "lead" && hasSwarmDocsSkill(args.skillsSummary)) {
302
+ const docsResult = await resolveTemplateAsync("system.agent.lead.docs", {});
303
+ prompt += docsResult.text;
304
+ }
280
305
  }
281
306
 
282
307
  // J. Agent notes (CLAUDE.md), only when it differs from the generated
@@ -339,6 +364,11 @@ export const getTaskContextPrompt = async (args: TaskContextPromptArgs): Promise
339
364
  return prompt.replace(/\n{3,}/g, "\n\n").trim();
340
365
  };
341
366
 
367
+ /** True when the skill list includes `swarm-docs`, or when the list is unknown. */
368
+ function hasSwarmDocsSkill(skills: { name: string }[] | undefined): boolean {
369
+ return skills === undefined || skills.some((skill) => skill.name === "swarm-docs");
370
+ }
371
+
342
372
  /**
343
373
  * Dynamic lines for `system.agent.tools_skills`. The static text lives in the
344
374
  * template so operators can override or skip the section; only the lists that
@@ -347,6 +377,7 @@ export const getTaskContextPrompt = async (args: TaskContextPromptArgs): Promise
347
377
  function renderToolsAndSkillsVars(input: {
348
378
  skillsSummary?: { name: string; description: string }[];
349
379
  mcpServers?: string[];
380
+ mcpToolsDeferred?: boolean;
350
381
  nativeSkillDiscovery: boolean;
351
382
  hasLocalEnv: boolean;
352
383
  toolDiscovery: string;
@@ -382,7 +413,11 @@ function renderToolsAndSkillsVars(input: {
382
413
  const servers = (input.mcpServers ?? []).filter((name) => name.trim().length > 0);
383
414
  const mcpLine =
384
415
  servers.length > 0
385
- ? `Connected MCP servers: ${servers.join(", ")}. Their tools are in your tool list.\n`
416
+ ? `Connected MCP servers: ${servers.join(", ")}. ${
417
+ input.mcpToolsDeferred
418
+ ? "Load their tools with your harness tool search."
419
+ : "Their tools are in your tool list."
420
+ }\n`
386
421
  : "";
387
422
 
388
423
  return {
@@ -15,8 +15,9 @@
15
15
  * G secrets system.agent.secrets
16
16
  *
17
17
  * Deployment-gated blocks (E outputs, H slack, steering) and the per-agent and
18
- * per-task sections (tools and skills, agent notes, repository, requester
19
- * profile) are appended by base-prompt.ts after the composite.
18
+ * per-task sections (tools and skills, the lead docs pointer, agent notes,
19
+ * repository, requester profile) are appended by base-prompt.ts after the
20
+ * composite.
20
21
  *
21
22
  * Writing rules for every body: sentence case headings, one idea per sentence,
22
23
  * active voice, plain words, no em dashes, a prohibition only where no positive
@@ -109,6 +110,19 @@ Your heartbeat runbook is the \`heartbeatMd\` profile field. Edit it with \`upda
109
110
  category: "system",
110
111
  });
111
112
 
113
+ // Lead docs pointer. base-prompt.ts appends it after the tools and skills
114
+ // section when the lead has the `swarm-docs` skill (or when the skill list is
115
+ // unknown). An operator override with an empty body switches it off.
116
+ registerTemplate({
117
+ eventType: "system.agent.lead.docs",
118
+ header: "",
119
+ defaultBody: `
120
+ Load the \`swarm-docs\` skill only when a person asks for something no skill, workflow, schedule or memory of yours covers, or asks where something is documented. Then follow its offer rules.
121
+ `,
122
+ variables: [],
123
+ category: "system",
124
+ });
125
+
112
126
  // Remote providers (no MCP, no container): the task arrives in the session
113
127
  // prompt and the final message is the output. No tools to name.
114
128
  registerTemplate({
@@ -168,7 +182,7 @@ registerTemplate({
168
182
  ## Memory
169
183
 
170
184
  Memories from past sessions are in your task message. Read them before you start. For a wider search, run the \`task-context-gathering\` script with the task ID and two to four queries.
171
- Store a learning with \`memory-store\` when you solve something that will come back: a fix, a pattern, a gotcha. Completed task outputs are stored for you.
185
+ When you learn something that would change how the next agent works (a fix, a pattern, a gotcha), pass it as \`learning\` on a \`store-progress\` call before the task ends. Completed task outputs are stored for you.
172
186
  You MUST use the \`memory\` skill before you store, edit, or delete a memory.
173
187
  `,
174
188
  variables: [],
@@ -202,9 +216,15 @@ agent-fs is the shared drive between agents and people. Documents (reports, dige
202
216
  Pages: only for polished artifacts shared with a wider audience or explicitly requested; see \`pages\` skill.
203
217
  A tool a person will use, with data and actions: build an app. See the \`apps\` skill.
204
218
  Link env: \`APP_URL\` for pages, \`MCP_BASE_URL\` for the API, \`AGENT_FS_LIVE_URL\` for files. Report missing variables.
205
- Give people the live app link \`\${AGENT_FS_LIVE_URL}/file/~/<org>/<drive>/<path>\`. Make an \`agent-fs share-create\` link only when the requester asks for one or the reader has no agent-fs login.
219
+ Give people the live app link \`{{fileLinkBase}}/file/~/<org>/<drive>/<path>\`. Make an \`agent-fs share-create\` link only when the requester asks for one or the reader has no agent-fs login.
206
220
  `,
207
- variables: [],
221
+ variables: [
222
+ {
223
+ name: "fileLinkBase",
224
+ description:
225
+ "Base URL of people-facing file links (AGENT_FS_LINK_TARGET); the literal AGENT_FS_LIVE_URL shell reference when unset",
226
+ },
227
+ ],
208
228
  category: "system",
209
229
  });
210
230
 
@@ -428,6 +448,24 @@ registerTemplate({
428
448
  category: "system",
429
449
  });
430
450
 
451
+ registerTemplate({
452
+ eventType: "system.agent.tool_discovery.codex",
453
+ header: "",
454
+ defaultBody:
455
+ "Swarm tools are not in your tool list. Call them from `exec` on the `tools` object, with dashes turned into underscores: `store-progress` is `await tools.mcp__agent_swarm__store_progress({ ... })`. Call a tool you know by that name directly. To find one, filter `ALL_TOOLS` by `name` and `description`.",
456
+ variables: [],
457
+ category: "system",
458
+ });
459
+
460
+ registerTemplate({
461
+ eventType: "system.agent.tool_discovery.opencode",
462
+ header: "",
463
+ defaultBody:
464
+ 'Swarm tools are not in your tool list. Call them from `execute` under `tools.swarm`, by their exact names: `store-progress` is `await tools.swarm["store-progress"]({ ... })`. To find one, use `search({ query })` inside `execute`.',
465
+ variables: [],
466
+ category: "system",
467
+ });
468
+
431
469
  registerTemplate({
432
470
  eventType: "system.agent.tool_discovery.direct",
433
471
  header: "",
@@ -45,7 +45,7 @@ import type {
45
45
  * default; the image installs exactly this version and the per-task settings
46
46
  * set `amp.updates.mode: "disabled"`. Bump it deliberately, with a live run.
47
47
  */
48
- export const AMP_PACKAGE = "@ampcode/cli@0.0.1792929650-g000512";
48
+ export const AMP_PACKAGE = "@ampcode/cli@0.0.1793073650-g23dd39";
49
49
 
50
50
  /** MCP server name. Amp exposes its tools as `agent_swarm` / `store_progress` (dashes become underscores). */
51
51
  const AMP_MCP_SERVER_NAME = "agent-swarm";
@@ -1613,8 +1613,13 @@ export class CodexSession implements ProviderSession {
1613
1613
  "invalid 'refresh_token'",
1614
1614
  ];
1615
1615
  if (authPatterns.some((p) => normalized.includes(p))) {
1616
+ const slot = this.config.codexSlot;
1617
+ const guidance =
1618
+ slot === undefined
1619
+ ? "Codex authentication failed — check OPENAI_API_KEY or ChatGPT login."
1620
+ : `Codex pool slot ${slot} authentication failed — re-run agent-swarm codex-login --slot ${slot}.`;
1616
1621
  return {
1617
- message: `[auth-error] Codex authentication failed — check OPENAI_API_KEY or ChatGPT login. Original error: ${raw}`,
1622
+ message: `[auth-error] ${guidance} Original error: ${raw}`,
1618
1623
  category: "authentication_failed",
1619
1624
  };
1620
1625
  }
@@ -35,7 +35,7 @@ import type {
35
35
  } from "./types";
36
36
 
37
37
  /** Pinned in package.json and Dockerfile.worker; bump all three together. */
38
- export const CURSOR_SDK_VERSION = "1.0.40";
38
+ export const CURSOR_SDK_VERSION = "1.1.0";
39
39
 
40
40
  /** MCP server name; Cursor reports its tools as `mcp` calls with this `providerIdentifier`. */
41
41
  const CURSOR_MCP_SERVER_NAME = "agent-swarm";