@desplega.ai/agent-swarm 2.7.4 → 2.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/README.md +1 -1
  2. package/dist/acp-adapter-g3ads55s.js +15 -0
  3. package/dist/{actions-gfrr9vb7.js → actions-zwfykqqk.js} +18 -18
  4. package/dist/{amp-adapter-0s58gm46.js → amp-adapter-z3s0ssdt.js} +3 -3
  5. package/dist/{app-stvq692g.js → app-vmp4fgep.js} +3 -3
  6. package/dist/{assistant-5s37fr9n.js → assistant-vwde653f.js} +28 -21
  7. package/dist/{boot-reembed-yzy1841n.js → boot-reembed-375t7tbk.js} +1 -1
  8. package/dist/{boot-reembed-cv82gz7y.js → boot-reembed-gq9rcpnj.js} +1 -1
  9. package/dist/{boot-scrub-sweep-8k4q95ap.js → boot-scrub-sweep-nd938kyf.js} +4 -4
  10. package/dist/{boot-seeds-ftt9htah.js → boot-seeds-aykx51v3.js} +4 -4
  11. package/dist/{boxd-515v1t8z.js → boxd-k1j26g3h.js} +1 -1
  12. package/dist/{claude-adapter-t00wrzeh.js → claude-adapter-8j4hm099.js} +7 -7
  13. package/dist/{claude-managed-adapter-g6vmjccb.js → claude-managed-adapter-dvs2xw5n.js} +2 -2
  14. package/dist/{claude-managed-setup-afpa82f7.js → claude-managed-setup-0wyynpn1.js} +3 -3
  15. package/dist/{claude-sdk-session-vj0c2azx.js → claude-sdk-session-q6wj5hmy.js} +9 -9
  16. package/dist/{cli-wndv9p7g.js → cli-0731hvbw.js} +6 -6
  17. package/dist/{cli-d2pzqej1.js → cli-0bxqnvq9.js} +719 -7
  18. package/dist/{cli-jkt52mgt.js → cli-0gey5fzy.js} +2 -2
  19. package/dist/{cli-1yekbyqm.js → cli-1338796d.js} +205 -48
  20. package/dist/{cli-1s1dqn1j.js → cli-1ayk1eas.js} +171 -123
  21. package/dist/{cli-a4btqqft.js → cli-1b7btd38.js} +1 -1
  22. package/dist/{cli-a2jw6ee5.js → cli-1p89xny0.js} +1 -1
  23. package/dist/{cli-v726afdm.js → cli-2pnswgh8.js} +1 -1
  24. package/dist/{cli-kvtc7aez.js → cli-3h4nhjn8.js} +42 -14
  25. package/dist/{cli-655xm9ms.js → cli-583frbyr.js} +1 -1
  26. package/dist/{cli-4r12t6w6.js → cli-5c4hjzne.js} +101 -4
  27. package/dist/{cli-az0bpf8p.js → cli-5pag6rmn.js} +3 -3
  28. package/dist/{cli-36g23z21.js → cli-6s7vdcta.js} +4 -4
  29. package/dist/{cli-rtk9g7av.js → cli-73jzwksr.js} +6 -2
  30. package/dist/{cli-jg0nfda6.js → cli-75gkpjvw.js} +52 -21
  31. package/dist/{cli-a81qevyg.js → cli-7a42nfy0.js} +20 -36
  32. package/dist/{cli-0sbkss7h.js → cli-7wcyk2s5.js} +2 -2
  33. package/dist/{cli-41z8xwj0.js → cli-7wspm5gm.js} +14 -4
  34. package/dist/{cli-byqfbnjw.js → cli-82ae98pz.js} +9 -9
  35. package/dist/{cli-bq2pcphn.js → cli-8hs41zy2.js} +1 -1
  36. package/dist/{cli-vqgnyh76.js → cli-98rw5dt5.js} +15 -3
  37. package/dist/{cli-9cctx1tn.js → cli-a22h291q.js} +1 -1
  38. package/dist/{cli-j4kt1mpb.js → cli-a47xy8eq.js} +3 -3
  39. package/dist/{cli-ycm6180s.js → cli-a7v854rf.js} +5 -3
  40. package/dist/{cli-98pke4vk.js → cli-ae7mrdf9.js} +2 -2
  41. package/dist/{cli-ahd90bwt.js → cli-afrrppsw.js} +6 -6
  42. package/dist/{cli-8yy525ad.js → cli-bhjt02p8.js} +2 -2
  43. package/dist/{cli-v1yw3wha.js → cli-bnz7a7qf.js} +4 -4
  44. package/dist/{cli-j4kw3rsx.js → cli-cm5yqn35.js} +18 -0
  45. package/dist/{cli-h55y2bjc.js → cli-dhar9qay.js} +2 -2
  46. package/dist/{cli-5gr1g711.js → cli-fqzx7gwa.js} +2 -2
  47. package/dist/{cli-tzrpv5sc.js → cli-ftyxyspd.js} +1 -1
  48. package/dist/cli-fw2w0kva.js +47 -0
  49. package/dist/{cli-2dfbv8qr.js → cli-fy6x31ps.js} +1 -0
  50. package/dist/{cli-mvckssyq.js → cli-g61qxmge.js} +5 -5
  51. package/dist/{cli-aq58fk5m.js → cli-g83s9ewh.js} +7 -1
  52. package/dist/{cli-224epwwf.js → cli-hn9hxmz9.js} +3 -3
  53. package/dist/{cli-5hjced2v.js → cli-j3jvnpe8.js} +6 -6
  54. package/dist/{cli-dae4sbgy.js → cli-jm2vfpfd.js} +2 -2
  55. package/dist/{cli-zn15c46z.js → cli-mjzynrse.js} +1 -1
  56. package/dist/{cli-a1fhj4n9.js → cli-mmwm7zbf.js} +2 -2
  57. package/dist/{cli-cq1pf336.js → cli-mry0qzym.js} +5 -5
  58. package/dist/{cli-bwty35gx.js → cli-mthwvjre.js} +1 -1
  59. package/dist/{cli-s0exq74r.js → cli-nmbmy64z.js} +3 -3
  60. package/dist/{cli-wcmnan2g.js → cli-nn4d8dne.js} +5 -5
  61. package/dist/{cli-jg4p5t36.js → cli-nzxr556s.js} +121 -51
  62. package/dist/{cli-bcz074zx.js → cli-p599f1qm.js} +1 -1
  63. package/dist/{cli-k76tgq03.js → cli-pywf3eh5.js} +2 -2
  64. package/dist/{cli-wk9md5px.js → cli-qxnf8z2z.js} +2 -2
  65. package/dist/{cli-je8yeyc5.js → cli-rhw40jgb.js} +5 -5
  66. package/dist/{cli-avgcz4ra.js → cli-rm6hqj3z.js} +406 -35
  67. package/dist/{cli-0fgr1w9b.js → cli-rpm2sgv0.js} +2 -2
  68. package/dist/{cli-t282jgab.js → cli-rqkxrmgw.js} +12 -12
  69. package/dist/{cli-2qpvmmkw.js → cli-s0qnd4wv.js} +1 -1
  70. package/dist/{cli-pwyevs29.js → cli-spknvxbq.js} +22 -21
  71. package/dist/{cli-9y354g3j.js → cli-srye3vdt.js} +1 -1
  72. package/dist/{cli-pe09p6dv.js → cli-tqgg40ek.js} +1 -1
  73. package/dist/{cli-f255yaqn.js → cli-vbyfwh92.js} +3 -3
  74. package/dist/{cli-gcbdjt47.js → cli-vfym7wmz.js} +6 -6
  75. package/dist/{cli-edmm9d9y.js → cli-vqypxzh4.js} +7 -7
  76. package/dist/{cli-1gm8jcyr.js → cli-wvaawacd.js} +13 -13
  77. package/dist/{cli-eny54typ.js → cli-wx3rhnm0.js} +1 -1
  78. package/dist/{cli-b1afgtj3.js → cli-xbkdm710.js} +3 -3
  79. package/dist/{cli-mc97xy2s.js → cli-xtjhfmpc.js} +16 -4
  80. package/dist/{cli-eqx6k993.js → cli-y24sc29k.js} +25 -13
  81. package/dist/{cli-ktmvb20s.js → cli-z6jkx23q.js} +12 -12
  82. package/dist/{cli-7n5sbw0z.js → cli-z8eh3ex8.js} +5 -5
  83. package/dist/cli-zh3fp8v2.js +151 -0
  84. package/dist/{cli-13jvmew3.js → cli-zppwg9p9.js} +7 -7
  85. package/dist/cli.js +18 -18
  86. package/dist/{codex-adapter-g0gctw29.js → codex-adapter-q6k3gnyg.js} +5 -5
  87. package/dist/{codex-hook-vrzz2xhr.js → codex-hook-9efm2x1m.js} +1 -1
  88. package/dist/{codex-login-0gg46ygn.js → codex-login-y0pmff1c.js} +1 -1
  89. package/dist/{codex-session-runner-dr2cxe1c.js → codex-session-runner-3mwmk3d9.js} +6 -6
  90. package/dist/{commands-1p07cvff.js → commands-evrenjn9.js} +2 -2
  91. package/dist/{controller-z0s7sv6r.js → controller-xjp8vswh.js} +4 -4
  92. package/dist/{controllers-connect-x9kvvhyk.js → controllers-connect-pp5jnxvc.js} +20 -20
  93. package/dist/{cursor-adapter-m5b98pvk.js → cursor-adapter-yf47w4t0.js} +2 -2
  94. package/dist/{db-rsh04kh3.js → db-rgfsh91f.js} +2 -2
  95. package/dist/{db-capk2raj.js → db-zxpw6aht.js} +3 -3
  96. package/dist/{devin-adapter-yxm87fe9.js → devin-adapter-r9phbnc9.js} +1 -1
  97. package/dist/{dispatcher-304q6fwa.js → dispatcher-smasgws7.js} +17 -17
  98. package/dist/{docker-7rxt0rry.js → docker-rrjhaqt9.js} +2 -2
  99. package/dist/{dsh-adapter-btewem0y.js → dsh-adapter-ftyy8dnp.js} +2 -2
  100. package/dist/{e2b-renc0k8q.js → e2b-qcaj675y.js} +2 -2
  101. package/dist/{e2b-7fc9b9bd.js → e2b-w1p0bnpm.js} +2 -2
  102. package/dist/{easter-egg-3d-fm1p3n4d.js → easter-egg-3d-yyts7p16.js} +1 -1
  103. package/dist/{embedding-space-s68v49ct.js → embedding-space-1z0mbg95.js} +6 -6
  104. package/dist/{embeddings-4ms3604w.js → embeddings-ygeg7tm7.js} +1 -1
  105. package/dist/{event-bus-dfjx2qdh.js → event-bus-g0ka3pbw.js} +1 -1
  106. package/dist/{execute-mcdgccjs.js → execute-14qhqp4d.js} +1 -1
  107. package/dist/{external-channel-9dxwfztn.js → external-channel-ttgrs3sx.js} +2 -2
  108. package/dist/fx-adapter-gt6pncj2.js +18 -0
  109. package/dist/{grok-adapter-05ecv4h0.js → grok-adapter-sm4m5k4g.js} +8 -8
  110. package/dist/handlers-vwmba1m2.js +31 -0
  111. package/dist/{heartbeat-3n94heh3.js → heartbeat-qahz4p62.js} +20 -20
  112. package/dist/{hook-zxwje86c.js → hook-4fd5wq5w.js} +4 -4
  113. package/dist/{hook-aadrn69x.js → hook-kj3d398m.js} +6 -6
  114. package/dist/{http-xzxk9mst.js → http-1ted0j1k.js} +100 -64
  115. package/dist/index-5r71rf98.js +32 -0
  116. package/dist/{index-d34f6r87.js → index-6rq3r5zb.js} +1 -1
  117. package/dist/{index-9j4zwcht.js → index-7v51awbn.js} +158 -672
  118. package/dist/index-anmwxs87.js +29 -0
  119. package/dist/{index-qeb82z37.js → index-q4b917zs.js} +1 -1
  120. package/dist/index-spp0ra5c.js +36 -0
  121. package/dist/{keepalive-w4aakbyk.js → keepalive-6cf311t5.js} +4 -4
  122. package/dist/lead-fjy418cw.js +69 -0
  123. package/dist/{maintenance-hbptpk6p.js → maintenance-a4bm1bmt.js} +1 -1
  124. package/dist/{oauth-refresh-sweep-217semrv.js → oauth-refresh-sweep-3h0dfw1k.js} +3 -3
  125. package/dist/{onboard-vz2s5df3.js → onboard-myn5hraa.js} +3 -3
  126. package/dist/{opencode-adapter-gtfd7d7v.js → opencode-adapter-p2nkb5am.js} +5 -5
  127. package/dist/{otel-impl-aefave37.js → otel-impl-a1a0ef6b.js} +2 -2
  128. package/dist/{pi-mono-adapter-f8h5yseh.js → pi-mono-adapter-rzmpb72a.js} +117 -23
  129. package/dist/{pricing-refresh-m1yhh9ya.js → pricing-refresh-83drrnxj.js} +3 -3
  130. package/dist/{rbac-roles-svj72cdf.js → rbac-roles-0fb6e7pj.js} +2 -2
  131. package/dist/{rbac-roles-rrw8twdz.js → rbac-roles-s8y6ecb5.js} +1 -1
  132. package/dist/{render-v2-amwbnfxh.js → render-v2-twmbgrz4.js} +3 -3
  133. package/dist/{resume-9cd6wf6z.js → resume-zfqc36xj.js} +7 -7
  134. package/dist/{runtime-fp4vrx7m.js → runtime-qwggdpqr.js} +1 -1
  135. package/dist/{session-access-21t6yjyg.js → session-access-sc9pn9da.js} +4 -4
  136. package/dist/{session-source-backfill-pxbzcsc6.js → session-source-backfill-qh07xr8g.js} +3 -3
  137. package/dist/{setup-4592tqz4.js → setup-4fr0m7a1.js} +1 -1
  138. package/dist/{virtual-modules-gzfnpmnz.js → virtual-modules-fh4ha15b.js} +2 -2
  139. package/dist/{work-objects-t9tcf174.js → work-objects-0wr8zxct.js} +20 -20
  140. package/dist/worker-0c5tyzqe.js +69 -0
  141. package/dist/{worker-follow-up-q32jh4yk.js → worker-follow-up-ktyyazyp.js} +17 -17
  142. package/dist/{x-b6wxntdt.js → x-vmmye1kb.js} +2 -2
  143. package/openapi.json +97 -32
  144. package/package.json +1 -1
  145. package/src/be/db/session-summaries.ts +8 -2
  146. package/src/be/db/session-tree.ts +174 -61
  147. package/src/be/db/session-visibility.ts +5 -4
  148. package/src/be/db.ts +8 -4
  149. package/src/be/migrations/259_session_summaries_open_tasks.sql +111 -0
  150. package/src/be/model-validation.ts +5 -1
  151. package/src/be/pricing-normalize.ts +7 -2
  152. package/src/be/scripts/typecheck.ts +1 -0
  153. package/src/be/seed/schedules-seeder.ts +135 -9
  154. package/src/be/seed/workflows-seeder.ts +65 -1
  155. package/src/be/seed-pricing.ts +23 -0
  156. package/src/be/steering.ts +50 -3
  157. package/src/be/swarm-config-guard.ts +1 -0
  158. package/src/commands/provider-credentials.ts +29 -2
  159. package/src/commands/runner.ts +86 -5
  160. package/src/extensions/contract-types.generated.ts +1 -1
  161. package/src/extensions/contract.ts +27 -1
  162. package/src/extensions/dispatcher.ts +4 -0
  163. package/src/http/agents.ts +1 -0
  164. package/src/http/mcp-bridge.ts +4 -0
  165. package/src/integrations/chat-sdk/inbound.ts +15 -5
  166. package/src/integrations/chat-sdk/steering.ts +3 -1
  167. package/src/kv-reserved-namespaces.ts +2 -0
  168. package/src/prompts/base-prompt.ts +4 -1
  169. package/src/prompts/session-templates.ts +26 -0
  170. package/src/providers/acp-adapter.ts +190 -26
  171. package/src/providers/acp-targets.ts +306 -6
  172. package/src/providers/amp-adapter.ts +1 -1
  173. package/src/providers/codex-adapter.ts +9 -44
  174. package/src/providers/codex-oauth/flow.ts +24 -3
  175. package/src/providers/codex-oauth/storage.ts +56 -17
  176. package/src/providers/codex-oauth/subscription.ts +67 -0
  177. package/src/providers/fx-adapter.ts +211 -0
  178. package/src/providers/index.ts +5 -1
  179. package/src/providers/mcp-loopback-proxy.ts +94 -0
  180. package/src/providers/pi-mono-adapter.ts +98 -13
  181. package/src/providers/pricing-sources.md +8 -0
  182. package/src/providers/reasoning-effort.ts +17 -1
  183. package/src/providers/types.ts +2 -1
  184. package/src/scheduler/scheduler.ts +36 -3
  185. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  186. package/src/scripts-runtime/types/stdlib.d.ts +5 -0
  187. package/src/scripts-runtime/types/swarm-extension.d.ts +33 -0
  188. package/src/scripts-runtime/types/swarm-sdk.d.ts +5 -0
  189. package/src/server.ts +7 -0
  190. package/src/slack/assistant.ts +17 -1
  191. package/src/slack/handlers.ts +18 -5
  192. package/src/slack/left-threads.ts +65 -0
  193. package/src/slack/steering.ts +3 -0
  194. package/src/slack/thread-buffer.ts +40 -16
  195. package/src/tests/acp-adapter.test.ts +79 -0
  196. package/src/tests/base-prompt.test.ts +12 -0
  197. package/src/tests/chat-sdk-inbound.test.ts +30 -0
  198. package/src/tests/chatgpt-subscription-model-groups.test.ts +62 -0
  199. package/src/tests/chatgpt-subscription-pi.test.ts +170 -0
  200. package/src/tests/chatgpt-subscription-session-bench.test.ts +198 -0
  201. package/src/tests/codex-oauth-credential-info.test.ts +40 -0
  202. package/src/tests/codex-oauth-refresh-egress.test.ts +194 -0
  203. package/src/tests/context-window.test.ts +7 -0
  204. package/src/tests/explicit-model-validation.test.ts +99 -0
  205. package/src/tests/extensions-run-context.test.ts +11 -0
  206. package/src/tests/extensions-steering.test.ts +353 -0
  207. package/src/tests/extensions-validate.test.ts +5 -0
  208. package/src/tests/fixtures/extensions/steering-policy/hooks.ts +39 -0
  209. package/src/tests/fixtures/extensions/steering-policy/manifest.json +7 -0
  210. package/src/tests/fixtures/fake-fx.ts +167 -0
  211. package/src/tests/fixtures/fx/acp-responses-recorded.json +157 -0
  212. package/src/tests/fixtures/fx/acp-updates-recorded.jsonl +15 -0
  213. package/src/tests/fx-adapter.test.ts +771 -0
  214. package/src/tests/grok-adapter.test.ts +36 -0
  215. package/src/tests/mcp-loopback-proxy.test.ts +193 -0
  216. package/src/tests/model-tier-resolution.test.ts +3 -3
  217. package/src/tests/pricing-refresh.test.ts +5 -5
  218. package/src/tests/prompt-template-session.test.ts +5 -3
  219. package/src/tests/provider-registration.test.ts +2 -1
  220. package/src/tests/query-audit-plans.test.ts +3 -2
  221. package/src/tests/script-connections.test.ts +2 -0
  222. package/src/tests/seed-model-profiles.test.ts +2 -2
  223. package/src/tests/seed-schedules.test.ts +138 -0
  224. package/src/tests/session-cost-recompute-unit.test.ts +39 -0
  225. package/src/tests/session-costs-golden.test.ts +26 -0
  226. package/src/tests/session-summaries-plans.test.ts +120 -0
  227. package/src/tests/session-summaries.test.ts +72 -12
  228. package/src/tests/sessions-tree.test.ts +11 -3
  229. package/src/tests/slack-leave-thread.test.ts +431 -0
  230. package/src/tests/tool-annotations.test.ts +2 -1
  231. package/src/tests/workflow-engine-v2.test.ts +42 -0
  232. package/src/tests/workflow-script-principal.test.ts +207 -7
  233. package/src/tools/slack-leave-thread.ts +104 -0
  234. package/src/tools/steer-task.ts +3 -2
  235. package/src/tools/tool-config.ts +2 -1
  236. package/src/types.ts +19 -0
  237. package/src/utils/codex-auth-failure.ts +30 -0
  238. package/src/utils/context-window.ts +4 -1
  239. package/src/utils/credentials.ts +7 -0
  240. package/src/utils/fx-models.ts +43 -0
  241. package/src/workflows/cooldown.ts +3 -2
  242. package/src/workflows/engine.ts +24 -9
  243. package/templates/schedules/dreaming-daily/config.json +1 -1
  244. package/templates/schedules/dreaming-daily/content.md +6 -14
  245. package/templates/skills/swarm-docs/content.md +1 -0
  246. package/templates/workflows/dreaming/content.md +5 -1
  247. package/dist/acp-adapter-96dc64m2.js +0 -15
  248. package/dist/handlers-jg7v74ty.js +0 -31
  249. package/dist/index-8c1zbz59.js +0 -29
  250. package/dist/index-8k8tmfxd.js +0 -32
  251. package/dist/index-hq617xdj.js +0 -36
  252. package/dist/lead-567kw7x3.js +0 -68
  253. package/dist/worker-fjdqra6q.js +0 -68
@@ -4,14 +4,18 @@
4
4
  import {
5
5
  client as acpClient,
6
6
  type ClientContext,
7
+ type ContentBlock,
8
+ type InitializeResponse,
7
9
  type McpServer,
8
10
  methods,
11
+ type NewSessionRequest,
9
12
  ndJsonStream,
10
13
  PROTOCOL_VERSION,
11
14
  type RequestPermissionRequest,
12
15
  type RequestPermissionResponse,
13
16
  type SessionConfigOption,
14
17
  type SessionNotification,
18
+ type SessionUpdate,
15
19
  type SetSessionConfigOptionRequest,
16
20
  type SetSessionConfigOptionResponse,
17
21
  type Usage,
@@ -28,6 +32,11 @@ import {
28
32
  import { scrubSecrets } from "../utils/secret-scrubber";
29
33
  import { acpReportedCostUsd, translateAcpSessionNotification } from "./acp-swarm-events";
30
34
  import { type AcpTargetProfile, resolveAcpTarget } from "./acp-targets";
35
+ import {
36
+ type McpLoopbackProxy,
37
+ needsLoopbackProxy,
38
+ startMcpLoopbackProxy,
39
+ } from "./mcp-loopback-proxy";
31
40
  import type {
32
41
  CostData,
33
42
  ProviderAdapter,
@@ -36,11 +45,13 @@ import type {
36
45
  ProviderSession,
37
46
  ProviderSessionConfig,
38
47
  ProviderTraits,
48
+ SteerDelivery,
49
+ SteerDeliveryResult,
39
50
  } from "./types";
40
51
 
41
52
  type EventListener = (event: ProviderEvent) => void;
42
53
  /** Providers that run on this ACP client. */
43
- export type AcpProviderName = "acp" | "grok";
54
+ export type AcpProviderName = "acp" | "grok" | "fx";
44
55
  const ACP_LOG_MAX_CHARS = 30_000;
45
56
  /**
46
57
  * How long an abort waits after `session/cancel` for the prompt to answer
@@ -163,6 +174,7 @@ function boundedAcpLogScalar(value: unknown): unknown {
163
174
  class SwarmAcpClient {
164
175
  /** Latest cumulative USD cost the target reported for the session. */
165
176
  reportedCostUsd: number | null = null;
177
+ private readonly updateListeners = new Set<(update: SessionUpdate) => void>();
166
178
 
167
179
  constructor(
168
180
  private readonly emit: (event: ProviderEvent) => void,
@@ -183,11 +195,29 @@ class SwarmAcpClient {
183
195
  const costUsd = acpReportedCostUsd(params.update);
184
196
  if (costUsd !== null) this.reportedCostUsd = costUsd;
185
197
  for (const event of translateAcpSessionNotification(params)) {
186
- this.emit(this.target?.rewriteEvent?.(event) ?? event);
198
+ this.emit(this.target?.rewriteEvent?.(event, params.update) ?? event);
187
199
  }
200
+ for (const listener of this.updateListeners) listener(params.update);
201
+ }
202
+
203
+ /** Observe raw session updates (a target's steer hook); returns the unsubscribe. */
204
+ onUpdate(listener: (update: SessionUpdate) => void): () => void {
205
+ this.updateListeners.add(listener);
206
+ return () => {
207
+ this.updateListeners.delete(listener);
208
+ };
188
209
  }
189
210
  }
190
211
 
212
+ /** What `ACPSession` needs beyond the connection, for the opt-in target hooks. */
213
+ interface AcpSessionExtras {
214
+ initResult: InitializeResponse;
215
+ /** `harnessVariantMeta` on `session_init` (the agent's `agentInfo.version`). */
216
+ harnessVariantMeta?: Record<string, unknown>;
217
+ /** Loopback MCP forwarders this session owns; stopped when it finishes. */
218
+ proxies: McpLoopbackProxy[];
219
+ }
220
+
191
221
  class ACPSession implements ProviderSession {
192
222
  readonly sessionId: string;
193
223
 
@@ -203,6 +233,8 @@ class ACPSession implements ProviderSession {
203
233
  * the fallback operator key is in use (e.g. during tests or if the API is
204
234
  * not yet upgraded). Revoked in finish(). */
205
235
  private ephemeralTokenId: string | null = null;
236
+ /** Present only when the target has a `steer` hook. */
237
+ readonly deliverSteering?: (delivery: SteerDelivery) => Promise<SteerDeliveryResult>;
206
238
 
207
239
  constructor(
208
240
  private readonly agent: ClientContext,
@@ -214,14 +246,37 @@ class ACPSession implements ProviderSession {
214
246
  private readonly target: AcpTargetProfile,
215
247
  providerMeta?: Record<string, unknown>,
216
248
  ephemeralTokenId?: string,
249
+ private readonly extras?: AcpSessionExtras,
217
250
  ) {
218
251
  this.sessionId = sessionId;
219
252
  this.ephemeralTokenId = ephemeralTokenId ?? null;
220
253
  this.completionPromise = new Promise((resolve) => {
221
254
  this.completionResolve = resolve;
222
255
  });
256
+ const steer = target.steer;
257
+ if (steer && extras) {
258
+ const initResult = extras.initResult;
259
+ this.deliverSteering = async (delivery) => {
260
+ if (this.completed || this.aborted) {
261
+ return { delivered: false, reason: "The ACP session has already finished" };
262
+ }
263
+ return steer({
264
+ sessionId,
265
+ delivery,
266
+ initResult,
267
+ prompt: (params) => this.agent.request(methods.agent.session.prompt, params),
268
+ onUpdate: (listener) => this.client.onUpdate(listener),
269
+ });
270
+ };
271
+ }
223
272
  void this.consumeStderr();
224
- this.emit({ type: "session_init", sessionId, provider: providerName, providerMeta });
273
+ this.emit({
274
+ type: "session_init",
275
+ sessionId,
276
+ provider: providerName,
277
+ providerMeta,
278
+ ...(extras?.harnessVariantMeta ? { harnessVariantMeta: extras.harnessVariantMeta } : {}),
279
+ });
225
280
  void this.runPrompt();
226
281
  }
227
282
 
@@ -385,6 +440,7 @@ class ACPSession implements ProviderSession {
385
440
  if (this.completed) return;
386
441
  this.completed = true;
387
442
  this.completionResolve(result);
443
+ void stopProxies(this.extras?.proxies ?? []);
388
444
  // Revoke the ephemeral token now that the session is done. Best-effort:
389
445
  // the token expires on its own, so a failure here is not critical.
390
446
  if (this.ephemeralTokenId) {
@@ -455,8 +511,9 @@ export class ACPAdapter implements ProviderAdapter {
455
511
  .connect(stream).agent;
456
512
 
457
513
  let ephemeralToken: { tokenId: string; plaintext: string } | null = null;
514
+ const proxies: McpLoopbackProxy[] = [];
458
515
  try {
459
- await agent.request(methods.agent.initialize, {
516
+ const initResult = await agent.request(methods.agent.initialize, {
460
517
  protocolVersion: PROTOCOL_VERSION,
461
518
  clientInfo: { name: "agent-swarm", version: pkg.version },
462
519
  clientCapabilities: { session: { configOptions: { boolean: {} } } },
@@ -483,39 +540,76 @@ export class ACPAdapter implements ProviderAdapter {
483
540
  // (mcp/connect, mcp/message, mcp/disconnect) instead of a network hop -- the
484
541
  // shape for an ACP agent with no network route to the swarm API. Gated on
485
542
  // `mcpCapabilities.acp` and UNSTABLE; not adopted here.
543
+ let mcpServers: McpServer[] = [
544
+ {
545
+ type: "http",
546
+ name: "swarm",
547
+ url: `${config.apiUrl.replace(/\/+$/, "")}/mcp`,
548
+ headers: [
549
+ { name: "Authorization", value: `Bearer ${ephemeralToken.plaintext}` },
550
+ { name: "X-Agent-ID", value: config.agentId },
551
+ { name: "X-Source-Task-Id", value: config.taskId },
552
+ ],
553
+ },
554
+ ...toAcpMcpServers(installedServers),
555
+ ];
556
+ if (target.requiresSecureMcpUrl) {
557
+ mcpServers = await secureMcpServers(
558
+ mcpServers,
559
+ initResult.agentCapabilities?.mcpCapabilities?.sse === true,
560
+ proxies,
561
+ );
562
+ }
486
563
  const sessionMeta = target.sessionMeta?.(config);
487
- const newSession = await agent.request(methods.agent.session.new, {
488
- ...(sessionMeta ? { _meta: sessionMeta } : {}),
489
- cwd: config.cwd,
490
- mcpServers: [
491
- {
492
- type: "http",
493
- name: "swarm",
494
- url: `${config.apiUrl.replace(/\/+$/, "")}/mcp`,
495
- headers: [
496
- { name: "Authorization", value: `Bearer ${ephemeralToken.plaintext}` },
497
- { name: "X-Agent-ID", value: config.agentId },
498
- { name: "X-Source-Task-Id", value: config.taskId },
499
- ],
500
- },
501
- ...toAcpMcpServers(installedServers),
502
- ],
503
- });
564
+ const resumeId = target.resume ? config.resumeSessionId?.trim() : undefined;
565
+ let sessionId: string;
566
+ let advertisedOptions: SessionConfigOption[];
567
+ if (resumeId && initResult.agentCapabilities?.sessionCapabilities?.resume) {
568
+ // The target restores the session's history and system prompt itself.
569
+ const resumed = await agent.request(methods.agent.session.resume, {
570
+ ...(sessionMeta ? { _meta: sessionMeta } : {}),
571
+ sessionId: resumeId,
572
+ cwd: config.cwd,
573
+ mcpServers,
574
+ });
575
+ sessionId = resumeId;
576
+ advertisedOptions = resumed.configOptions ?? [];
577
+ } else {
578
+ if (resumeId) {
579
+ console.warn(
580
+ "\x1b[33m[acp]\x1b[0m Target does not advertise session/resume; starting a new session",
581
+ );
582
+ }
583
+ const systemPrompt = await sessionSystemPromptBlocks(target, config, initResult);
584
+ // fx 0.0.15 takes `systemPrompt` as a bare content-block array (append
585
+ // mode) and rejects the `{ mode, content }` object the SDK types, so the
586
+ // request is built untyped.
587
+ const newSessionParams = {
588
+ ...(sessionMeta ? { _meta: sessionMeta } : {}),
589
+ cwd: config.cwd,
590
+ mcpServers,
591
+ ...(systemPrompt ? { systemPrompt } : {}),
592
+ } as NewSessionRequest;
593
+ const newSession = await agent.request(methods.agent.session.new, newSessionParams);
594
+ sessionId = newSession.sessionId;
595
+ advertisedOptions = newSession.configOptions ?? [];
596
+ }
504
597
  const configOptions = await applyConfiguredOptions(
505
598
  {
506
599
  setSessionConfigOption: (params) =>
507
600
  agent.request(methods.agent.session.setConfigOption, params),
508
601
  },
509
- newSession.sessionId,
510
- newSession.configOptions ?? [],
602
+ sessionId,
603
+ advertisedOptions,
511
604
  target.configuredOptions(config),
512
605
  );
606
+ const agentVersion = initResult.agentInfo?.version?.trim();
513
607
  session = new ACPSession(
514
608
  agent,
515
609
  client,
516
610
  proc,
517
611
  config,
518
- newSession.sessionId,
612
+ sessionId,
519
613
  this.name,
520
614
  target,
521
615
  {
@@ -523,6 +617,11 @@ export class ACPAdapter implements ProviderAdapter {
523
617
  configOptions: sanitizeAcpConfigOptions(configOptions),
524
618
  },
525
619
  ephemeralToken.tokenId,
620
+ {
621
+ initResult,
622
+ ...(agentVersion ? { harnessVariantMeta: { version: scrubSecrets(agentVersion) } } : {}),
623
+ proxies,
624
+ },
526
625
  );
527
626
  for (const event of preSessionEvents) session.emitFromAcp(event);
528
627
  void session.waitForCompletion().finally(cleanupArtifact);
@@ -535,6 +634,7 @@ export class ACPAdapter implements ProviderAdapter {
535
634
  void revokeAcpSessionToken(config.apiUrl, config.apiKey, ephemeralToken.tokenId);
536
635
  }
537
636
  await terminateProcessGroup(proc.pid);
637
+ await stopProxies(proxies);
538
638
  await cleanupArtifact();
539
639
  throw new Error(
540
640
  `ACP target failed during startup: ${describeTargetError(target, scrubSecrets(formatError(err)))}`,
@@ -542,8 +642,9 @@ export class ACPAdapter implements ProviderAdapter {
542
642
  }
543
643
  }
544
644
 
545
- async canResume(_sessionId: string): Promise<boolean> {
546
- return false;
645
+ async canResume(sessionId: string): Promise<boolean> {
646
+ const resume = this.options.target?.resume;
647
+ return resume ? resume.canResume(sessionId) : false;
547
648
  }
548
649
 
549
650
  formatCommand(commandName: string): string {
@@ -680,6 +781,69 @@ export function toAcpMcpServers(
680
781
  return servers;
681
782
  }
682
783
 
784
+ /**
785
+ * The `systemPrompt` content blocks a target's `sessionSystemPrompt` hook
786
+ * produces, when the agent advertises `sessionCapabilities.systemPrompt`.
787
+ */
788
+ async function sessionSystemPromptBlocks(
789
+ target: AcpTargetProfile,
790
+ config: ProviderSessionConfig,
791
+ initResult: InitializeResponse,
792
+ ): Promise<ContentBlock[] | undefined> {
793
+ if (!target.sessionSystemPrompt) return undefined;
794
+ if (!initResult.agentCapabilities?.sessionCapabilities?.systemPrompt) {
795
+ console.warn(
796
+ "\x1b[33m[acp]\x1b[0m Target does not advertise sessionCapabilities.systemPrompt; the swarm system prompt is not sent",
797
+ );
798
+ return undefined;
799
+ }
800
+ const plan = await target.sessionSystemPrompt(config);
801
+ return plan?.blocks.length ? plan.blocks : undefined;
802
+ }
803
+
804
+ /**
805
+ * MCP servers a target that only accepts HTTPS or explicit-loopback URLs can
806
+ * reach: each plain-HTTP, non-loopback origin gets one 127.0.0.1 forwarder
807
+ * (added to `proxies`), and SSE servers are dropped unless `sseSupported`.
808
+ */
809
+ export async function secureMcpServers(
810
+ servers: McpServer[],
811
+ sseSupported: boolean,
812
+ proxies: McpLoopbackProxy[],
813
+ ): Promise<McpServer[]> {
814
+ const byOrigin = new Map<string, McpLoopbackProxy>();
815
+ const result: McpServer[] = [];
816
+ for (const server of servers) {
817
+ if (!("url" in server)) {
818
+ result.push(server);
819
+ continue;
820
+ }
821
+ if (server.type === "sse" && !sseSupported) {
822
+ console.warn(
823
+ `\x1b[33m[acp]\x1b[0m Skipping installed MCP server "${server.name}": the target does not support SSE MCP servers`,
824
+ );
825
+ continue;
826
+ }
827
+ if (!needsLoopbackProxy(server.url)) {
828
+ result.push(server);
829
+ continue;
830
+ }
831
+ const url = new URL(server.url);
832
+ let proxy = byOrigin.get(url.origin);
833
+ if (!proxy) {
834
+ proxy = await startMcpLoopbackProxy(url.origin);
835
+ byOrigin.set(url.origin, proxy);
836
+ proxies.push(proxy);
837
+ }
838
+ result.push({ ...server, url: `${proxy.url}${url.pathname}${url.search}` });
839
+ }
840
+ return result;
841
+ }
842
+
843
+ async function stopProxies(proxies: McpLoopbackProxy[]): Promise<void> {
844
+ await Promise.all(proxies.splice(0).map((proxy) => proxy.stop().catch(() => {})));
845
+ }
846
+
683
847
  function describeTargetError(target: AcpTargetProfile, message: string): string {
684
848
  return target.describeError?.(message) ?? message;
685
849
  }