@desplega.ai/agent-swarm 1.162.0 → 1.164.0

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 (240) hide show
  1. package/README.md +2 -1
  2. package/dist/{acp-adapter-74m73ae9.js → acp-adapter-8t22d18g.js} +2 -2
  3. package/dist/{actions-4sr14gnt.js → actions-49xrhww7.js} +32 -29
  4. package/dist/amp-adapter-p230tajt.js +34 -0
  5. package/dist/{anthropic-messages-8xxrzky9.js → anthropic-messages-hst62ws9.js} +3 -3
  6. package/dist/{app-pt68fbwp.js → app-tgzw3f3d.js} +11 -10
  7. package/dist/{assistant-cv3jytks.js → assistant-dgfjm07j.js} +33 -30
  8. package/dist/{azure-openai-responses-f5cqsyj8.js → azure-openai-responses-mh8tvf78.js} +9 -68
  9. package/dist/{boot-reembed-4ht4gqth.js → boot-reembed-0qzs614r.js} +13 -12
  10. package/dist/{boot-reembed-nxpcvpj9.js → boot-reembed-dgp6peyx.js} +12 -11
  11. package/dist/{boot-scrub-logs-k93kyw8k.js → boot-scrub-logs-fxnk021a.js} +10 -9
  12. package/dist/{boot-seeds-1skcrs0e.js → boot-seeds-xe7adjvm.js} +14 -13
  13. package/dist/{claude-adapter-r5g0tzge.js → claude-adapter-hx0n8a31.js} +22 -20
  14. package/dist/{claude-managed-adapter-yxkfxg8j.js → claude-managed-adapter-hskapeen.js} +3 -3
  15. package/dist/{claude-managed-setup-pkyrgshb.js → claude-managed-setup-p7mt6dfq.js} +1 -1
  16. package/dist/{claude-sdk-session-jxyxnrwd.js → claude-sdk-session-d61a9c8x.js} +22 -20
  17. package/dist/{cli-twrh04ap.js → cli-0y30kqz4.js} +3 -3
  18. package/dist/{cli-5xhpb3kv.js → cli-0z9dj97t.js} +9 -9
  19. package/dist/{cli-t845n9xk.js → cli-1nzb8ftr.js} +2 -2
  20. package/dist/{cli-kdkwd3yr.js → cli-1wd1n2ya.js} +38 -10
  21. package/dist/{cli-vxe6qa4h.js → cli-23az412e.js} +1 -1
  22. package/dist/{cli-bsky8g7w.js → cli-26x4d89j.js} +1 -1
  23. package/dist/{cli-swfmp1fx.js → cli-2ar0x7cz.js} +9 -9
  24. package/dist/cli-2ksgp01n.js +933 -0
  25. package/dist/{cli-rnyzn44p.js → cli-2zhhnz2h.js} +1 -1
  26. package/dist/{cli-vc9bp061.js → cli-3qaw32we.js} +5 -5
  27. package/dist/{cli-vy3ed88x.js → cli-402m8btc.js} +31 -3
  28. package/dist/{cli-5bcd7sfy.js → cli-43fh8fzw.js} +3 -3
  29. package/dist/{cli-5k6k8ta3.js → cli-4f7s6k3q.js} +13 -5
  30. package/dist/{cli-sqvfa090.js → cli-4ntg6tkp.js} +6 -5
  31. package/dist/{cli-kh04eqwe.js → cli-4y9bnpvy.js} +1 -1
  32. package/dist/{cli-qjjvzwzd.js → cli-5c2j5yvx.js} +1 -1
  33. package/dist/{cli-2zy95vhm.js → cli-6891ft0y.js} +2 -2
  34. package/dist/{cli-tre4bdgq.js → cli-6behs2bt.js} +1 -1
  35. package/dist/cli-7mm2r1kp.js +17 -0
  36. package/dist/cli-7xcgj20z.js +68 -0
  37. package/dist/{cli-4cwxn7gp.js → cli-812vt3ht.js} +2 -2
  38. package/dist/{cli-mbssg1xv.js → cli-8kt0bmgx.js} +1 -1
  39. package/dist/{cli-wjgs5wxe.js → cli-8rpq35c0.js} +7 -7
  40. package/dist/{cli-jxs2ygaw.js → cli-9m8fk2v7.js} +74 -29
  41. package/dist/{cli-pkd6s2wy.js → cli-9q3wt23f.js} +2 -2
  42. package/dist/{cli-s3hwpt9v.js → cli-9s6fsg07.js} +30 -10
  43. package/dist/{cli-xqpk3rp7.js → cli-9x3bwfd3.js} +28 -5
  44. package/dist/{cli-qrgt5bqf.js → cli-agamgm0p.js} +1 -1
  45. package/dist/{cli-13hw4z91.js → cli-b0f70z24.js} +1 -1
  46. package/dist/{cli-gpqpmns7.js → cli-cgrvsz2t.js} +2 -2
  47. package/dist/{cli-2hhfhfq5.js → cli-cn53e7vc.js} +8 -1
  48. package/dist/{cli-p0r8sxj5.js → cli-crjazzsg.js} +222 -4
  49. package/dist/{cli-tcen9p5w.js → cli-d37n8b7c.js} +5 -5
  50. package/dist/{cli-wxkkd0e0.js → cli-dgh781z9.js} +3 -3
  51. package/dist/cli-dsdg2wrk.js +516 -0
  52. package/dist/{cli-q24re23n.js → cli-e2xnc4m5.js} +4 -4
  53. package/dist/{cli-f7kvq46p.js → cli-erxeg64j.js} +20 -0
  54. package/dist/{cli-8ev9xqnx.js → cli-fajbc7sa.js} +17 -219
  55. package/dist/{cli-g739zqzt.js → cli-fj86xr3c.js} +2 -2
  56. package/dist/{cli-zvhd3f64.js → cli-fpbvcf55.js} +3 -3
  57. package/dist/{cli-kp42yek0.js → cli-fyra5y8k.js} +424 -71
  58. package/dist/{cli-jyy6pm2f.js → cli-g3qgrzck.js} +15 -3
  59. package/dist/{cli-eg0b3p0k.js → cli-g5erf0zt.js} +23 -14
  60. package/dist/{cli-cvatsam7.js → cli-gcvzvq3e.js} +4 -4
  61. package/dist/{cli-89s4ngw3.js → cli-gvcgmx69.js} +1 -1
  62. package/dist/{cli-7tre70zp.js → cli-j261c831.js} +138 -128
  63. package/dist/{cli-sptrj5f5.js → cli-j3me0jw2.js} +2 -2
  64. package/dist/{cli-zxqx25nz.js → cli-k6w44yvq.js} +6 -6
  65. package/dist/cli-ndx5yz5n.js +3299 -0
  66. package/dist/{cli-ykk5jaax.js → cli-nexwh8zg.js} +34 -29
  67. package/dist/{cli-rfjw9jw2.js → cli-ng97fjq7.js} +2 -2
  68. package/dist/{cli-4x0csdjq.js → cli-ngne1dng.js} +2 -2
  69. package/dist/{cli-4dcpaefp.js → cli-pfzrhb8n.js} +2 -2
  70. package/dist/{cli-hb5c2x5y.js → cli-q38n4bq0.js} +2 -2
  71. package/dist/{cli-cebmgwsc.js → cli-r109nq7n.js} +4 -4
  72. package/dist/{cli-nq7yqw7b.js → cli-t7g0vmtc.js} +4 -4
  73. package/dist/{cli-jf7wmv1r.js → cli-tsvj33y0.js} +58 -19
  74. package/dist/{cli-7af8yhav.js → cli-vbr859rz.js} +7 -4
  75. package/dist/{cli-nt8hecg9.js → cli-wk3v6h55.js} +67 -38
  76. package/dist/{cli-vyqs9ya8.js → cli-wwvk0tsy.js} +7 -7
  77. package/dist/{cli-rj0gqwwp.js → cli-y2de3bzp.js} +1 -1
  78. package/dist/{cli-hmsw1gpy.js → cli-y74170pb.js} +4 -4
  79. package/dist/cli-ydt3zkwv.js +70 -0
  80. package/dist/{cli-xk79303e.js → cli-yj790e19.js} +9 -1
  81. package/dist/cli-zt3w1cn1.js +137 -0
  82. package/dist/{cli-trc24wk2.js → cli-zxg80zs9.js} +1 -1
  83. package/dist/cli.js +23 -22
  84. package/dist/{cloudflare-workers-ai-system-one-k8ddqjkq.js → cloudflare-workers-ai-system-one-tt484t61.js} +2 -2
  85. package/dist/{codex-adapter-esvmnhn7.js → codex-adapter-e3pky8aj.js} +14 -13
  86. package/dist/{codex-hook-m73jmqgx.js → codex-hook-b1fb7c2k.js} +4 -3
  87. package/dist/{codex-session-runner-ban9excr.js → codex-session-runner-1gq27w2t.js} +14 -13
  88. package/dist/{commands-75413p2n.js → commands-q07ezjzn.js} +10 -9
  89. package/dist/cursor-adapter-50cz7yba.js +32 -0
  90. package/dist/{db-7f5pecwg.js → db-wwt0sq9q.js} +18 -9
  91. package/dist/{dispatcher-fh9at8nz.js → dispatcher-wrg35vdd.js} +31 -28
  92. package/dist/{dsh-adapter-wpze65d4.js → dsh-adapter-sa6rxfpc.js} +6 -6
  93. package/dist/{e2b-gjgdjp91.js → e2b-v5np73yj.js} +4 -4
  94. package/dist/{easter-egg-3d-3w5c2n4c.js → easter-egg-3d-h2hx9b64.js} +2 -2
  95. package/dist/{event-bus-sfx8dx3t.js → event-bus-vkbhzt4n.js} +1 -1
  96. package/dist/{execute-f50wg9nh.js → execute-6gczxy8v.js} +42 -13
  97. package/dist/{google-generative-ai-jxxpnegy.js → google-generative-ai-d6nphg3q.js} +3 -3
  98. package/dist/{google-vertex-x7q1angm.js → google-vertex-cpshkref.js} +3 -3
  99. package/dist/{handlers-w13787bb.js → handlers-rv3gxvsp.js} +34 -31
  100. package/dist/{hook-46g1jrvx.js → hook-bcf5m80y.js} +15 -13
  101. package/dist/{hook-4tpts2ry.js → hook-z8b1jm2d.js} +16 -14
  102. package/dist/{http-19pzbfrt.js → http-56f7yt97.js} +932 -134
  103. package/dist/{index-scptvdxg.js → index-0kncjnrm.js} +30 -23
  104. package/dist/{index-dqmp15bq.js → index-1e6cg96n.js} +31 -28
  105. package/dist/{index-qb4q1y3e.js → index-8xgdb3k8.js} +31 -28
  106. package/dist/index-adw4zr9j.js +1020 -0
  107. package/dist/{index-h5gs261z.js → index-etdrp7yp.js} +140 -140
  108. package/dist/{index-8fd4dm4h.js → index-f9zscmft.js} +32 -29
  109. package/dist/index-fdk1t4d6.js +91297 -0
  110. package/dist/{index-s75wc4wd.js → index-jvb37h77.js} +12 -11
  111. package/dist/{keepalive-xa5ts9cd.js → keepalive-rp8daj4s.js} +12 -11
  112. package/dist/{lead-qp4xkvjq.js → lead-dfp77erc.js} +45 -40
  113. package/dist/{maintenance-rm5885hm.js → maintenance-wtmw3jqv.js} +13 -12
  114. package/dist/{mistral-conversations-rw7fbt22.js → mistral-conversations-8445ddjn.js} +2 -2
  115. package/dist/{oauth-refresh-sweep-1csdfebf.js → oauth-refresh-sweep-yhzqt95v.js} +11 -10
  116. package/dist/{onboard-r6b78cs4.js → onboard-9z8rwbcy.js} +2 -2
  117. package/dist/{openai-codex-responses-efvrgrq3.js → openai-codex-responses-9ynm88zy.js} +3 -3
  118. package/dist/{openai-completions-bbbrrz4z.js → openai-completions-nhqwjmaw.js} +2 -2
  119. package/dist/{openai-responses-9jjyrcm1.js → openai-responses-xx1c4kgm.js} +3 -3
  120. package/dist/{opencode-adapter-kb3g6pkf.js → opencode-adapter-1nxb6555.js} +5 -5
  121. package/dist/{otel-impl-0y9knpky.js → otel-impl-b83gaxw2.js} +1 -1
  122. package/dist/{pi-mono-adapter-z6s1bme2.js → pi-mono-adapter-ddxz8pjp.js} +18 -17
  123. package/dist/{pricing-refresh-mth26tgp.js → pricing-refresh-tmjvazb6.js} +12 -11
  124. package/dist/{rbac-roles-kwntb23q.js → rbac-roles-pa0gzjws.js} +11 -10
  125. package/dist/{rbac-roles-vpk4ebm8.js → rbac-roles-rhexa9s2.js} +12 -11
  126. package/dist/{render-v2-hcm7h00y.js → render-v2-ask84n8y.js} +11 -10
  127. package/dist/{runtime-vfjsfsc5.js → runtime-ckjnj3vf.js} +4 -4
  128. package/dist/{setup-hbyzd65h.js → setup-5ga5gdsy.js} +2 -2
  129. package/dist/{typesafe-system-one-2ef30pec.js → typesafe-system-one-datfr82p.js} +2 -2
  130. package/dist/{virtual-modules-9bv7ry91.js → virtual-modules-bmp3zwjf.js} +12 -11
  131. package/dist/{work-objects-m09sxnhe.js → work-objects-snnh0t7j.js} +34 -31
  132. package/dist/{worker-0h35rvk4.js → worker-rxyymrhb.js} +45 -40
  133. package/openapi.json +997 -39
  134. package/package.json +6 -5
  135. package/src/be/db/active-sessions.ts +30 -0
  136. package/src/be/db/approvals.ts +54 -0
  137. package/src/be/db.ts +25 -4
  138. package/src/be/mcp-server-checked-update.ts +34 -0
  139. package/src/be/mcp-server-stdio-gate.ts +34 -0
  140. package/src/be/memory/key-browser.ts +304 -0
  141. package/src/be/memory/providers/sqlite-store.ts +16 -3
  142. package/src/be/migrations/194_memory_orphan_chunks.sql +15 -0
  143. package/src/be/migrations/195_approval_request_approvals.sql +6 -0
  144. package/src/be/model-validation.ts +12 -2
  145. package/src/be/multi-runtime.ts +4 -3
  146. package/src/be/pricing-normalize.ts +11 -0
  147. package/src/be/repo-checked-update.ts +37 -0
  148. package/src/be/repo-merge-policy.ts +15 -0
  149. package/src/be/seed-pricing.ts +48 -1
  150. package/src/be/swarm-config-guard.ts +1 -0
  151. package/src/commands/provider-credentials.ts +44 -2
  152. package/src/extensions/contract-types.generated.ts +1 -1
  153. package/src/extensions/contract.ts +100 -0
  154. package/src/extensions/dispatcher.ts +19 -0
  155. package/src/extensions/post-bridge.ts +191 -0
  156. package/src/github/handlers.ts +128 -2
  157. package/src/gitlab/workflow-action-name.ts +15 -0
  158. package/src/heartbeat/heartbeat.ts +4 -3
  159. package/src/http/active-sessions.ts +6 -5
  160. package/src/http/agents.ts +10 -1
  161. package/src/http/approval-requests.ts +271 -35
  162. package/src/http/index.ts +1 -1
  163. package/src/http/mcp-servers.ts +74 -50
  164. package/src/http/memory.ts +275 -6
  165. package/src/http/repos.ts +58 -18
  166. package/src/http/request-principal.ts +19 -0
  167. package/src/http/session-cost-recompute.ts +64 -0
  168. package/src/http/session-data.ts +11 -0
  169. package/src/http/tasks.ts +9 -1
  170. package/src/http/webhooks.ts +17 -5
  171. package/src/prompts/base-prompt.ts +9 -4
  172. package/src/prompts/resolver.ts +1 -1
  173. package/src/prompts/session-templates.ts +28 -0
  174. package/src/providers/amp-adapter.ts +1232 -0
  175. package/src/providers/cursor-adapter.ts +638 -0
  176. package/src/providers/index.ts +9 -1
  177. package/src/providers/pricing-sources.md +39 -0
  178. package/src/providers/reasoning-effort.ts +15 -1
  179. package/src/providers/types.ts +11 -1
  180. package/src/rbac/legacy-policy.ts +23 -8
  181. package/src/rbac/permissions.ts +27 -1
  182. package/src/scripts-runtime/types/swarm-extension.d.ts +119 -0
  183. package/src/tests/amp-adapter.test.ts +822 -0
  184. package/src/tests/amp-budget-export-failed.test.ts +133 -0
  185. package/src/tests/amp-pricing-helpers.ts +16 -0
  186. package/src/tests/amp-pricing.test.ts +218 -0
  187. package/src/tests/api-drain-worker-e2e.test.ts +18 -7
  188. package/src/tests/approval-respond-auth.test.ts +371 -0
  189. package/src/tests/base-prompt.test.ts +13 -0
  190. package/src/tests/claude-adapter-binary.test.ts +24 -0
  191. package/src/tests/claude-adapter.test.ts +8 -0
  192. package/src/tests/cursor-adapter.test.ts +690 -0
  193. package/src/tests/db-task-read-contract.test.ts +5 -0
  194. package/src/tests/db-task-write-contract.test.ts +5 -0
  195. package/src/tests/docs-index-footprint.test.js +3 -3
  196. package/src/tests/extensions-post-bridge.test.ts +669 -0
  197. package/src/tests/extensions-run-context.test.ts +61 -0
  198. package/src/tests/fixtures/fake-amp.ts +304 -0
  199. package/src/tests/github-handlers-inline-comments.test.ts +147 -1
  200. package/src/tests/gitlab-workflow-event-names.test.ts +136 -0
  201. package/src/tests/mcp-input-ergonomics.test.ts +15 -10
  202. package/src/tests/mcp-server-stdio-gate.test.ts +438 -0
  203. package/src/tests/memory-http-access.test.ts +221 -0
  204. package/src/tests/memory-keys-endpoint.test.ts +406 -0
  205. package/src/tests/memory-store.test.ts +166 -0
  206. package/src/tests/model-tier-resolution.test.ts +3 -3
  207. package/src/tests/pricing-refresh.test.ts +5 -4
  208. package/src/tests/prompt-template-session.test.ts +5 -3
  209. package/src/tests/provider-process-group.test.ts +41 -0
  210. package/src/tests/provider-registration.test.ts +3 -1
  211. package/src/tests/rbac-charact-misc-tools.test.ts +18 -3
  212. package/src/tests/rbac-engine.test.ts +15 -11
  213. package/src/tests/rbac-wire-e2e.test.ts +4 -9
  214. package/src/tests/repo-allow-merge-gate.test.ts +382 -0
  215. package/src/tests/script-connections.test.ts +4 -0
  216. package/src/tests/send-task-requester-gate.test.ts +260 -0
  217. package/src/tests/session-activity-liveness.test.ts +248 -0
  218. package/src/tests/session-costs-golden.test.ts +28 -0
  219. package/src/tests/webhooks-task-blocked.test.ts +4 -4
  220. package/src/tests/write-gate.ts +68 -0
  221. package/src/tests/x402-client.test.ts +124 -0
  222. package/src/tests/x402-spending-tracker.test.ts +66 -0
  223. package/src/tools/mcp-servers/mcp-server-create.ts +23 -1
  224. package/src/tools/mcp-servers/mcp-server-update.ts +46 -28
  225. package/src/tools/memory-delete.ts +3 -3
  226. package/src/tools/repos/update-repo.ts +26 -5
  227. package/src/tools/send-task.ts +24 -1
  228. package/src/tools/steer-task.ts +1 -1
  229. package/src/types.ts +37 -2
  230. package/src/utils/amp-models.ts +114 -0
  231. package/src/utils/credentials.ts +8 -0
  232. package/src/utils/process-group.ts +79 -0
  233. package/src/workflows/event-bus.ts +28 -0
  234. package/src/x402/client.ts +36 -14
  235. package/src/x402/spending-tracker.ts +64 -3
  236. package/templates/skills/agent-browser/SKILL.md +5 -0
  237. package/templates/skills/agent-browser/content.md +5 -0
  238. package/templates/skills/heartbeat-runbook/content.md +1 -0
  239. package/templates/skills/swarm-extensions/content.md +4 -0
  240. package/dist/cli-y89t4nca.js +0 -67
@@ -0,0 +1,933 @@
1
+ import {
2
+ resolveSlashSkillPrompt
3
+ } from "./cli-wgbezhw2.js";
4
+ import {
5
+ CONTEXT_FORMULA,
6
+ clampContextPercent,
7
+ computeContextUsedUnified
8
+ } from "./cli-6behs2bt.js";
9
+ import {
10
+ applyReasoningEffort
11
+ } from "./cli-cn53e7vc.js";
12
+ import {
13
+ ampBillsCacheWrites,
14
+ resolveAmpModel
15
+ } from "./cli-7xcgj20z.js";
16
+ import {
17
+ swarmRuntimeInstanceId
18
+ } from "./cli-re8z5p8p.js";
19
+ import {
20
+ detachedProcessGroup,
21
+ listDescendantPids,
22
+ registerProcessGroup,
23
+ terminatePids,
24
+ terminateProcessGroup,
25
+ terminateProcessTree
26
+ } from "./cli-zt3w1cn1.js";
27
+ import {
28
+ init_secret_scrubber,
29
+ registerVolatileSecret,
30
+ scrubSecrets
31
+ } from "./cli-nexqx4ve.js";
32
+
33
+ // src/providers/amp-adapter.ts
34
+ import { existsSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
35
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
36
+ import { tmpdir } from "node:os";
37
+ import { join } from "node:path";
38
+ init_secret_scrubber();
39
+ var AMP_PACKAGE = "@ampcode/cli@0.0.1791216048-g3f2cac";
40
+ var AMP_MCP_SERVER_NAME = "agent-swarm";
41
+ var AMP_PLUGIN_MODE_KEY = "agent-swarm";
42
+ var AMP_LIVE_TEST_TIMEOUT_MS = 5000;
43
+ var AMP_EXPORT_TIMEOUT_MS = 20000;
44
+ var AMP_EXPORT_RETRY_MS = 3000;
45
+ var AMP_EXPORT_RETRIES = 1;
46
+ var AMP_EXIT_AFTER_INPUT_MS = 60000;
47
+ var AMP_STDERR_RECORD_MAX = 16384;
48
+ var AMP_QUEUED_ECHO_MS = 30000;
49
+ function checkAmpCredentials(env) {
50
+ return env.AMP_API_KEY?.trim() ? { ready: true, missing: [], satisfiedBy: "env" } : {
51
+ ready: false,
52
+ missing: ["AMP_API_KEY"],
53
+ hint: "Set AMP_API_KEY (an access token from ampcode.com/settings) for amp."
54
+ };
55
+ }
56
+ function resolveAmpBinary(env) {
57
+ return env.AMP_BINARY || Bun.which("amp", { PATH: env.PATH }) || null;
58
+ }
59
+ function ampEffort(level) {
60
+ return level === "off" ? "none" : level;
61
+ }
62
+ function buildAmpPlugin(options) {
63
+ const config = JSON.stringify({
64
+ baseMode: options.baseMode,
65
+ instructions: options.instructions,
66
+ model: options.pin ?? null,
67
+ reasoningEffort: options.reasoningEffort ?? null,
68
+ steeringInbox: options.steeringInbox ?? null
69
+ });
70
+ return `// @amp-agent-mode key=${AMP_PLUGIN_MODE_KEY} label=Agent Swarm
71
+ // Generated by the agent-swarm worker for one task. Do not edit.
72
+ import { readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
73
+
74
+ export const description = "Agent Swarm worker mode generated for one task.";
75
+
76
+ const CONFIG = ${config};
77
+
78
+ /**
79
+ * Claim each queued message by renaming it (.json -> .taken), so the worker and
80
+ * this hook never both deliver one. A message handed to Amp becomes .read,
81
+ * naming the tool call whose result carries it, and stays until the worker sees
82
+ * the model that received that result answer: a crash before that leaves it for
83
+ * the worker to report as undelivered.
84
+ */
85
+ function takeSteering(toolUseID: unknown): string[] {
86
+ if (!CONFIG.steeringInbox) return [];
87
+ let names: string[];
88
+ try {
89
+ names = readdirSync(CONFIG.steeringInbox).filter((name) => name.endsWith(".json")).sort();
90
+ } catch {
91
+ return [];
92
+ }
93
+ const texts: string[] = [];
94
+ for (const name of names) {
95
+ const path = CONFIG.steeringInbox + "/" + name;
96
+ const claimed = path + ".taken";
97
+ try {
98
+ renameSync(path, claimed);
99
+ } catch {
100
+ continue;
101
+ }
102
+ let text: string;
103
+ try {
104
+ text = JSON.parse(readFileSync(claimed, "utf8"));
105
+ } catch {
106
+ // Unreadable: dropped, and the worker reports it undelivered.
107
+ rmSync(claimed, { force: true });
108
+ continue;
109
+ }
110
+ texts.push(text);
111
+ try {
112
+ writeFileSync(claimed, JSON.stringify({ text, toolUseID }));
113
+ renameSync(claimed, path + ".read");
114
+ } catch {
115
+ // The worker treats a claim left as .taken as unconsumed.
116
+ }
117
+ }
118
+ return texts;
119
+ }
120
+
121
+ function withSteering(output: unknown, note: string): unknown {
122
+ if (Array.isArray(output)) return [...output, { type: "text", text: note }];
123
+ if (output === undefined || output === null) return note;
124
+ return (typeof output === "string" ? output : JSON.stringify(output)) + note;
125
+ }
126
+
127
+ export default function (amp: any) {
128
+ amp.on("tool.result", (event: any) => {
129
+ const texts = takeSteering(event.toolUseID);
130
+ if (texts.length === 0) return;
131
+ const note = texts.map((text) => "\\n\\n" + text).join("");
132
+ return {
133
+ status: event.status,
134
+ ...(event.error ? { error: event.error } : {}),
135
+ output: withSteering(event.output, note),
136
+ };
137
+ });
138
+ amp.registerAgentMode({
139
+ key: ${JSON.stringify(AMP_PLUGIN_MODE_KEY)},
140
+ label: "Agent Swarm",
141
+ agent: amp.createAgent({
142
+ extends: CONFIG.baseMode,
143
+ ...(CONFIG.instructions ? { instructions: CONFIG.instructions } : {}),
144
+ ...(CONFIG.model ? { model: CONFIG.model } : {}),
145
+ ...(CONFIG.reasoningEffort ? { reasoningEffort: CONFIG.reasoningEffort } : {}),
146
+ display: { label: "Agent Swarm" },
147
+ }).definition,
148
+ });
149
+ }
150
+ `;
151
+ }
152
+ function buildAmpSettings() {
153
+ return {
154
+ "amp.updates.mode": "disabled",
155
+ "amp.notifications.enabled": false,
156
+ "amp.notifications.system.enabled": false,
157
+ "amp.git.commit.coauthor.enabled": false,
158
+ "amp.git.commit.ampThread.enabled": false,
159
+ "amp.dangerouslyAllowAll": true
160
+ };
161
+ }
162
+ function swarmMcpConfig(config) {
163
+ const runtimeInstanceId = swarmRuntimeInstanceId();
164
+ return {
165
+ [AMP_MCP_SERVER_NAME]: {
166
+ url: `${config.apiUrl}/mcp`,
167
+ headers: {
168
+ Authorization: `Bearer ${config.apiKey}`,
169
+ "X-Agent-ID": config.agentId,
170
+ "X-Source-Task-Id": config.taskId,
171
+ ...config.contextKey ? { "X-Context-Key": config.contextKey } : {},
172
+ ...runtimeInstanceId ? { "X-Runtime-Instance-ID": runtimeInstanceId } : {}
173
+ }
174
+ }
175
+ };
176
+ }
177
+ async function liveTestAmpCredentials(env, timeoutMs = AMP_LIVE_TEST_TIMEOUT_MS) {
178
+ const binary = resolveAmpBinary(env);
179
+ if (!binary) {
180
+ return {
181
+ ok: false,
182
+ error: `amp CLI not found. Install ${AMP_PACKAGE} during image provisioning or set AMP_BINARY to a trusted executable.`
183
+ };
184
+ }
185
+ const apiKey = env.AMP_API_KEY?.trim();
186
+ if (apiKey)
187
+ registerVolatileSecret(apiKey, "AMP_API_KEY");
188
+ const configHome = await mkdtemp(join(tmpdir(), "swarm-amp-live-"));
189
+ try {
190
+ const proc = registerProcessGroup(Bun.spawn([binary, "usage"], {
191
+ env: {
192
+ ...env,
193
+ XDG_CONFIG_HOME: configHome,
194
+ AMP_SETTINGS_FILE: join(configHome, "amp", "settings.json")
195
+ },
196
+ stdin: "ignore",
197
+ stdout: "pipe",
198
+ stderr: "pipe",
199
+ detached: detachedProcessGroup
200
+ }));
201
+ let timedOut = false;
202
+ const timer = setTimeout(() => {
203
+ timedOut = true;
204
+ terminateProcessGroup(proc.pid);
205
+ }, timeoutMs);
206
+ try {
207
+ const [exitCode, stdout, stderr] = await Promise.all([
208
+ proc.exited,
209
+ new Response(proc.stdout).text(),
210
+ new Response(proc.stderr).text()
211
+ ]);
212
+ if (timedOut)
213
+ return { ok: false, error: `amp usage timed out after ${timeoutMs}ms` };
214
+ if (exitCode === 0)
215
+ return { ok: true };
216
+ const line = (stderr.trim() || stdout.trim()).split(`
217
+ `)[0]?.replace(/^Error:\s*/, "") || `amp usage exited ${exitCode}`;
218
+ return { ok: false, error: scrubSecrets(line).slice(0, 300) };
219
+ } finally {
220
+ clearTimeout(timer);
221
+ }
222
+ } finally {
223
+ await rm(configHome, { recursive: true, force: true });
224
+ }
225
+ }
226
+ function parseAmpThreadCost(text) {
227
+ const cost = /^Cost: \$([\d,]+(?:\.\d+)?)\s*$/m.exec(text);
228
+ if (!cost?.[1])
229
+ return;
230
+ const routing = text.split(/^## Model Routing\s*$/m)[1];
231
+ if (routing === undefined)
232
+ return;
233
+ const rows = routing.split(/^## /m)[0]?.split(`
234
+ `).map((line) => line.trim()).filter((line) => line.startsWith("|") && !/^\|\s*(Model ID|-)/.test(line));
235
+ for (const row of rows ?? []) {
236
+ if (row.split("|")[3]?.trim() !== "Via Amp")
237
+ return;
238
+ }
239
+ const usd = Number(cost[1].replaceAll(",", ""));
240
+ return Number.isFinite(usd) ? usd : undefined;
241
+ }
242
+ function count(value) {
243
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
244
+ }
245
+ function parseAmpThreadUsage(thread) {
246
+ const messages = thread?.messages;
247
+ if (!Array.isArray(messages))
248
+ return;
249
+ const byModel = new Map;
250
+ let last;
251
+ for (const message of messages) {
252
+ const usage = message?.usage;
253
+ if (message?.role !== "assistant" || !usage)
254
+ continue;
255
+ const model = typeof usage.model === "string" && usage.model ? usage.model : undefined;
256
+ if (!model)
257
+ continue;
258
+ const entry = byModel.get(model) ?? {
259
+ model,
260
+ inputTokens: 0,
261
+ outputTokens: 0,
262
+ cacheReadTokens: 0,
263
+ cacheWriteTokens: 0
264
+ };
265
+ const cacheCreation = count(usage.cacheCreationInputTokens);
266
+ entry.inputTokens += count(usage.inputTokens);
267
+ entry.outputTokens += count(usage.outputTokens);
268
+ entry.cacheReadTokens += count(usage.cacheReadInputTokens);
269
+ if (ampBillsCacheWrites(model))
270
+ entry.cacheWriteTokens += cacheCreation;
271
+ else
272
+ entry.inputTokens += cacheCreation;
273
+ byModel.set(model, entry);
274
+ last = usage;
275
+ }
276
+ if (byModel.size === 0)
277
+ return;
278
+ const models = [...byModel.values()];
279
+ const total = (m) => m.inputTokens + m.outputTokens + m.cacheReadTokens + m.cacheWriteTokens;
280
+ const primaryModel = [...models].sort((a, b) => total(b) - total(a))[0]?.model;
281
+ const window = count(last?.maxInputTokens);
282
+ return {
283
+ models,
284
+ primaryModel,
285
+ contextWindow: window || undefined,
286
+ contextUsedTokens: last ? count(last.totalInputTokens) + count(last.outputTokens) : undefined,
287
+ lastOutputTokens: last ? count(last.outputTokens) : undefined
288
+ };
289
+ }
290
+ function userMessage(text) {
291
+ return `${JSON.stringify({
292
+ type: "user",
293
+ message: { role: "user", content: [{ type: "text", text }] }
294
+ })}
295
+ `;
296
+ }
297
+
298
+ class AmpSession {
299
+ proc;
300
+ directory;
301
+ run;
302
+ sessionId;
303
+ deliverSteering;
304
+ listeners = [];
305
+ pending = [];
306
+ output;
307
+ lastAssistantText = "";
308
+ failure;
309
+ stderr = "";
310
+ aborted = false;
311
+ completion;
312
+ startedAt = Date.now();
313
+ result;
314
+ toolNames = new Map;
315
+ tokens = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
316
+ unechoed = [];
317
+ inputClosed = false;
318
+ echoWatchdog;
319
+ exitWatchdog;
320
+ termination;
321
+ descendants = new Set;
322
+ captures = [];
323
+ steeringSeq = 0;
324
+ steers = new Map;
325
+ constructor(proc, directory, prompt, run) {
326
+ this.proc = proc;
327
+ this.directory = directory;
328
+ this.run = run;
329
+ this.emit({
330
+ type: "raw_log",
331
+ content: JSON.stringify({
332
+ type: "system",
333
+ subtype: "model.selected",
334
+ provider: "amp",
335
+ model: run.selection.model,
336
+ mode: run.selection.baseMode,
337
+ ...run.reasoningEffort ? { reasoningEffort: run.reasoningEffort } : {}
338
+ })
339
+ });
340
+ this.deliverSteering = ({ text }) => {
341
+ if (this.inputClosed || this.aborted) {
342
+ return Promise.resolve({
343
+ delivered: false,
344
+ reason: "Amp is no longer reading input (the turn has ended)"
345
+ });
346
+ }
347
+ try {
348
+ return this.queueSteering(text);
349
+ } catch (error) {
350
+ return Promise.resolve({
351
+ delivered: false,
352
+ reason: `Amp steering inbox write failed: ${String(error)}`
353
+ });
354
+ }
355
+ };
356
+ this.completion = this.runSession(prompt);
357
+ }
358
+ onEvent(listener) {
359
+ this.listeners.push(listener);
360
+ for (const event of this.pending.splice(0))
361
+ listener(event);
362
+ }
363
+ emit(event) {
364
+ if (this.listeners.length === 0)
365
+ this.pending.push(event);
366
+ else
367
+ for (const listener of this.listeners)
368
+ listener(event);
369
+ }
370
+ get stdin() {
371
+ return this.proc.stdin;
372
+ }
373
+ async writeInput(text) {
374
+ this.unechoed.push(text.trim());
375
+ await this.stdin.write(userMessage(text));
376
+ await this.stdin.flush?.();
377
+ }
378
+ queueSteering(text) {
379
+ const name = `${String(++this.steeringSeq).padStart(6, "0")}.json`;
380
+ const temp = join(this.run.steeringInbox, `.${name}.tmp`);
381
+ writeFileSync(temp, JSON.stringify(text), { mode: 384 });
382
+ renameSync(temp, join(this.run.steeringInbox, name));
383
+ return new Promise((resolve) => {
384
+ this.steers.set(name, { text, via: "inbox", resolve });
385
+ });
386
+ }
387
+ inboxPath(name, suffix = "") {
388
+ return join(this.run.steeringInbox, name + suffix);
389
+ }
390
+ settleSteer(name, result) {
391
+ const steer = this.steers.get(name);
392
+ if (!steer)
393
+ return;
394
+ this.steers.delete(name);
395
+ for (const suffix of ["", ".taken", ".read", ".stdin"]) {
396
+ rmSync(this.inboxPath(name, suffix), { force: true });
397
+ }
398
+ steer.resolve(result);
399
+ }
400
+ settleOpenSteers(reason) {
401
+ for (const name of [...this.steers.keys()]) {
402
+ this.settleSteer(name, { delivered: false, reason });
403
+ }
404
+ }
405
+ carrierOf(name) {
406
+ try {
407
+ const claim = JSON.parse(readFileSync(this.inboxPath(name, ".read"), "utf8"));
408
+ return typeof claim?.toolUseID === "string" ? claim.toolUseID : undefined;
409
+ } catch {
410
+ return;
411
+ }
412
+ }
413
+ markReceived(toolUseId, receiver) {
414
+ if (!toolUseId)
415
+ return;
416
+ for (const [name, steer] of this.steers) {
417
+ if (steer.via !== "inbox" || steer.receiver !== undefined)
418
+ continue;
419
+ if (this.carrierOf(name) === toolUseId)
420
+ steer.receiver = receiver;
421
+ }
422
+ }
423
+ acknowledgeHookSteering(answeredBy) {
424
+ for (const [name, steer] of [...this.steers]) {
425
+ if (steer.via !== "inbox")
426
+ continue;
427
+ if (existsSync(this.inboxPath(name, ".read"))) {
428
+ if (steer.receiver === answeredBy)
429
+ this.settleSteer(name, { delivered: true, mode: "queue" });
430
+ } else if (!existsSync(this.inboxPath(name)) && !existsSync(this.inboxPath(name, ".taken"))) {
431
+ this.settleSteer(name, {
432
+ delivered: false,
433
+ reason: "Amp could not read the queued steering message"
434
+ });
435
+ }
436
+ }
437
+ }
438
+ takeSteering() {
439
+ const taken = [];
440
+ for (const [name, steer] of [...this.steers].sort(([a], [b]) => a.localeCompare(b))) {
441
+ if (steer.via !== "inbox")
442
+ continue;
443
+ try {
444
+ renameSync(this.inboxPath(name), this.inboxPath(name, ".stdin"));
445
+ } catch {
446
+ continue;
447
+ }
448
+ steer.via = "stdin";
449
+ taken.push({ name, text: steer.text });
450
+ }
451
+ return taken;
452
+ }
453
+ endInput() {
454
+ if (this.inputClosed)
455
+ return;
456
+ this.inputClosed = true;
457
+ clearTimeout(this.echoWatchdog);
458
+ this.settleOpenSteers("Amp stopped reading input before the steering message reached the model");
459
+ this.captureDescendants().finally(() => {
460
+ try {
461
+ Promise.resolve(this.stdin.end()).catch(() => {});
462
+ } catch {}
463
+ });
464
+ this.exitWatchdog = setTimeout(() => {
465
+ if (!this.failure)
466
+ this.failure = "amp did not exit after its turn ended";
467
+ this.terminate();
468
+ }, this.run.exitAfterInputMs);
469
+ }
470
+ terminate() {
471
+ this.termination ??= terminateProcessTree(this.proc.pid);
472
+ return this.termination;
473
+ }
474
+ captureDescendants() {
475
+ const capture = listDescendantPids(this.proc.pid).then((pids) => {
476
+ for (const pid of pids)
477
+ this.descendants.add(pid);
478
+ }).catch(() => {});
479
+ this.captures.push(capture);
480
+ return capture;
481
+ }
482
+ onTurnEnd() {
483
+ if (this.inputClosed)
484
+ return;
485
+ for (const { name, text } of this.takeSteering()) {
486
+ this.writeInput(text).catch((error) => {
487
+ this.settleSteer(name, {
488
+ delivered: false,
489
+ reason: `Amp stdin write failed: ${scrubSecrets(String(error))}`
490
+ });
491
+ this.endInput();
492
+ });
493
+ }
494
+ if (this.unechoed.length === 0) {
495
+ this.endInput();
496
+ return;
497
+ }
498
+ clearTimeout(this.echoWatchdog);
499
+ this.echoWatchdog = setTimeout(() => this.endInput(), AMP_QUEUED_ECHO_MS);
500
+ }
501
+ consumeLine(line) {
502
+ if (!line.trim())
503
+ return;
504
+ const clean = scrubSecrets(line);
505
+ this.emit({ type: "raw_log", content: clean });
506
+ let event;
507
+ try {
508
+ event = JSON.parse(clean);
509
+ } catch {
510
+ return;
511
+ }
512
+ if (event.session_id && !this.sessionId) {
513
+ this.sessionId = event.session_id;
514
+ this.emit({ type: "session_init", sessionId: event.session_id, provider: "amp" });
515
+ }
516
+ switch (event.type) {
517
+ case "system":
518
+ if (event.subtype === "init")
519
+ this.checkMcp(event);
520
+ break;
521
+ case "assistant":
522
+ this.onAssistant(event);
523
+ break;
524
+ case "user":
525
+ this.onUser(event);
526
+ break;
527
+ case "result":
528
+ this.result = event;
529
+ this.captureDescendants();
530
+ if (typeof event.result === "string")
531
+ this.output = event.result;
532
+ this.endInput();
533
+ break;
534
+ }
535
+ }
536
+ checkMcp(event) {
537
+ const server = event.mcp_servers?.find((s) => s.name === AMP_MCP_SERVER_NAME);
538
+ const status = server?.status;
539
+ if (status === "connected" || status === "connecting" || status === "pending")
540
+ return;
541
+ this.failure = `amp could not connect to the swarm MCP server (${status ?? "not listed"})`;
542
+ this.terminate();
543
+ }
544
+ onAssistant(event) {
545
+ const message = event.message;
546
+ if (!message)
547
+ return;
548
+ this.acknowledgeHookSteering(event.parent_tool_use_id ?? null);
549
+ const blocks = Array.isArray(message.content) ? message.content : [];
550
+ const topLevel = !event.parent_tool_use_id;
551
+ const text = blocks.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
552
+ if (text) {
553
+ if (topLevel)
554
+ this.lastAssistantText = text;
555
+ this.emit({ type: "message", role: "assistant", content: text });
556
+ }
557
+ let calledTool = false;
558
+ for (const block of blocks) {
559
+ if (block.type !== "tool_use" || !block.name)
560
+ continue;
561
+ calledTool = true;
562
+ const toolCallId = block.id ?? "";
563
+ this.toolNames.set(toolCallId, block.name);
564
+ this.emit({ type: "tool_start", toolCallId, toolName: block.name, args: block.input ?? {} });
565
+ }
566
+ const usage = message.usage;
567
+ if (usage) {
568
+ const input = count(usage.input_tokens);
569
+ const output = count(usage.output_tokens);
570
+ const cacheRead = count(usage.cache_read_input_tokens);
571
+ const cacheCreation = count(usage.cache_creation_input_tokens);
572
+ this.tokens.input += input;
573
+ this.tokens.output += output;
574
+ this.tokens.cacheRead += cacheRead;
575
+ this.tokens.cacheCreation += cacheCreation;
576
+ const used = computeContextUsedUnified({
577
+ inputTokens: input,
578
+ cacheReadTokens: cacheRead,
579
+ cacheCreateTokens: cacheCreation,
580
+ outputTokens: output
581
+ });
582
+ if (used > 0)
583
+ this.emitContext(used, output, null);
584
+ }
585
+ if (topLevel && !calledTool && message.stop_reason !== "tool_use")
586
+ this.onTurnEnd();
587
+ }
588
+ onUser(event) {
589
+ const blocks = Array.isArray(event.message?.content) ? event.message.content : [];
590
+ for (const block of blocks) {
591
+ if (block.type === "tool_result") {
592
+ const toolCallId = block.tool_use_id ?? "";
593
+ this.markReceived(toolCallId, event.parent_tool_use_id ?? null);
594
+ const toolName = this.toolNames.get(toolCallId) ?? "tool";
595
+ this.toolNames.delete(toolCallId);
596
+ this.emit({ type: "tool_end", toolCallId, toolName, result: block.content });
597
+ } else if (block.type === "text" && !event.parent_tool_use_id) {
598
+ const echoed = (block.text ?? "").trim();
599
+ const index = this.unechoed.indexOf(echoed);
600
+ if (index >= 0)
601
+ this.unechoed.splice(index, 1);
602
+ for (const [name, steer] of this.steers) {
603
+ if (steer.via === "stdin" && steer.text.trim() === echoed) {
604
+ this.settleSteer(name, { delivered: true, mode: "queue" });
605
+ break;
606
+ }
607
+ }
608
+ clearTimeout(this.echoWatchdog);
609
+ }
610
+ }
611
+ }
612
+ emitContext(used, output, window) {
613
+ this.emit({
614
+ type: "context_usage",
615
+ contextUsedTokens: used,
616
+ contextTotalTokens: window,
617
+ contextPercent: window ? clampContextPercent(used, window) ?? 0 : null,
618
+ outputTokens: output,
619
+ contextFormula: CONTEXT_FORMULA
620
+ });
621
+ }
622
+ async readStdout() {
623
+ const decoder = new TextDecoder;
624
+ let buffer = "";
625
+ for await (const chunk of this.proc.stdout) {
626
+ buffer += decoder.decode(chunk, { stream: true });
627
+ let newline = buffer.indexOf(`
628
+ `);
629
+ while (newline >= 0) {
630
+ this.consumeLine(buffer.slice(0, newline));
631
+ buffer = buffer.slice(newline + 1);
632
+ newline = buffer.indexOf(`
633
+ `);
634
+ }
635
+ }
636
+ this.consumeLine(buffer + decoder.decode());
637
+ }
638
+ async readStderr() {
639
+ const decoder = new TextDecoder;
640
+ let pending = "";
641
+ for await (const chunk of this.proc.stderr) {
642
+ pending += decoder.decode(chunk, { stream: true });
643
+ const end = Math.max(pending.lastIndexOf(`
644
+ `), pending.lastIndexOf("\r"));
645
+ if (end >= 0) {
646
+ this.emitStderr(pending.slice(0, end + 1));
647
+ pending = pending.slice(end + 1);
648
+ }
649
+ if (pending.length > AMP_STDERR_RECORD_MAX) {
650
+ const cut = pending.lastIndexOf(" ") + 1 || pending.length;
651
+ this.emitStderr(pending.slice(0, cut));
652
+ pending = pending.slice(cut);
653
+ }
654
+ }
655
+ this.emitStderr(pending + decoder.decode());
656
+ }
657
+ emitStderr(text) {
658
+ if (!text)
659
+ return;
660
+ const content = scrubSecrets(text);
661
+ this.stderr = (this.stderr + content).slice(-8000);
662
+ this.emit({ type: "raw_stderr", content });
663
+ }
664
+ async exportUsage() {
665
+ let usage = await this.exportUsageOnce();
666
+ for (let retry = 0;!usage && !this.result && retry < AMP_EXPORT_RETRIES; retry++) {
667
+ await Bun.sleep(AMP_EXPORT_RETRY_MS);
668
+ usage = await this.exportUsageOnce();
669
+ }
670
+ return usage;
671
+ }
672
+ async exportUsageOnce() {
673
+ const text = await this.threadCommand(["export"]);
674
+ if (text === undefined)
675
+ return;
676
+ try {
677
+ return parseAmpThreadUsage(JSON.parse(text));
678
+ } catch {
679
+ return;
680
+ }
681
+ }
682
+ async threadCost() {
683
+ for (let attempt = 0;; attempt++) {
684
+ const text = await this.threadCommand(["usage", "--details"]);
685
+ if (text === undefined)
686
+ return;
687
+ const cost = parseAmpThreadCost(text);
688
+ if (cost !== undefined || attempt >= AMP_EXPORT_RETRIES)
689
+ return cost;
690
+ if (!/currently unavailable/i.test(text))
691
+ return;
692
+ await Bun.sleep(AMP_EXPORT_RETRY_MS);
693
+ }
694
+ }
695
+ async threadCommand(command) {
696
+ if (!this.sessionId)
697
+ return;
698
+ const [subcommand, ...flags] = command;
699
+ try {
700
+ const proc = registerProcessGroup(Bun.spawn([this.run.binary, "threads", subcommand ?? "", this.sessionId, ...flags], {
701
+ cwd: this.run.cwd,
702
+ env: this.run.env,
703
+ stdin: "ignore",
704
+ stdout: "pipe",
705
+ stderr: "ignore",
706
+ detached: detachedProcessGroup
707
+ }));
708
+ const timer = setTimeout(() => void terminateProcessGroup(proc.pid), AMP_EXPORT_TIMEOUT_MS);
709
+ try {
710
+ const [exitCode, text] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
711
+ return exitCode === 0 ? text : undefined;
712
+ } finally {
713
+ clearTimeout(timer);
714
+ }
715
+ } catch {
716
+ return;
717
+ }
718
+ }
719
+ buildCost(isError, thread, billedUsd) {
720
+ if (!this.sessionId)
721
+ return;
722
+ const t = this.tokens;
723
+ const models = thread?.models;
724
+ const sum = (pick) => (models ?? []).reduce((total, m) => total + pick(m), 0);
725
+ const hasStreamTokens = t.input + t.output + t.cacheRead + t.cacheCreation > 0;
726
+ if (!models && !hasStreamTokens && !billedUsd)
727
+ return;
728
+ return {
729
+ sessionId: this.sessionId,
730
+ taskId: this.run.taskId,
731
+ agentId: this.run.agentId,
732
+ totalCostUsd: billedUsd ?? 0,
733
+ inputTokens: models ? sum((m) => m.inputTokens) : t.input,
734
+ outputTokens: models ? sum((m) => m.outputTokens) : t.output,
735
+ cacheReadTokens: models ? sum((m) => m.cacheReadTokens) : t.cacheRead,
736
+ cacheWriteTokens: models ? sum((m) => m.cacheWriteTokens) : t.cacheCreation,
737
+ ...models ? { models } : {},
738
+ durationMs: this.result?.duration_ms ?? Date.now() - this.startedAt,
739
+ numTurns: this.result?.num_turns ?? null,
740
+ model: thread?.primaryModel ?? this.run.selection.model,
741
+ isError,
742
+ provider: "amp"
743
+ };
744
+ }
745
+ async runSession(prompt) {
746
+ try {
747
+ const [exitCode] = await Promise.all([
748
+ this.proc.exited,
749
+ this.readStdout(),
750
+ this.readStderr(),
751
+ this.writeInput(prompt).catch((error) => {
752
+ this.failure = `Amp stdin write failed: ${scrubSecrets(String(error))}`;
753
+ this.endInput();
754
+ this.terminate();
755
+ })
756
+ ]);
757
+ clearTimeout(this.exitWatchdog);
758
+ clearTimeout(this.echoWatchdog);
759
+ this.settleOpenSteers("Amp exited before the steering message reached the model");
760
+ await this.termination;
761
+ if (!this.termination) {
762
+ await Promise.all(this.captures);
763
+ await terminatePids(this.descendants);
764
+ }
765
+ const resultFailed = this.result !== undefined && (this.result.is_error || this.result.subtype !== "success");
766
+ const output = this.output ?? (this.lastAssistantText || undefined);
767
+ const isError = this.aborted || exitCode !== 0 || !!this.failure || !this.result || resultFailed;
768
+ const failureReason = isError ? this.aborted ? "amp session aborted" : this.failure || (resultFailed ? this.result?.error || this.result?.result : undefined) || this.stderr.trim().split(`
769
+ `).slice(-3).join(`
770
+ `) || `amp exited ${exitCode} without a result` : undefined;
771
+ if (failureReason)
772
+ this.emit({ type: "error", message: failureReason });
773
+ const [thread, billedUsd] = await Promise.all([this.exportUsage(), this.threadCost()]);
774
+ if (thread?.contextUsedTokens && thread.contextWindow) {
775
+ this.emitContext(thread.contextUsedTokens, thread.lastOutputTokens ?? 0, thread.contextWindow);
776
+ }
777
+ if (thread?.primaryModel) {
778
+ this.emit({
779
+ type: "raw_log",
780
+ content: JSON.stringify({
781
+ type: "system",
782
+ subtype: "model.resolved",
783
+ provider: "amp",
784
+ model: thread.primaryModel,
785
+ mode: this.run.selection.baseMode
786
+ })
787
+ });
788
+ }
789
+ const cost = this.buildCost(isError, thread, billedUsd);
790
+ if (cost)
791
+ this.emit({ type: "result", cost, output, isError });
792
+ return {
793
+ exitCode: isError ? exitCode || 1 : 0,
794
+ sessionId: this.sessionId,
795
+ cost,
796
+ output,
797
+ isError,
798
+ failureReason,
799
+ appliedReasoningEffort: this.run.reasoningEffort
800
+ };
801
+ } catch (error) {
802
+ await this.terminate();
803
+ const failureReason = scrubSecrets(String(error));
804
+ this.emit({ type: "error", message: failureReason });
805
+ return {
806
+ exitCode: 1,
807
+ sessionId: this.sessionId,
808
+ cost: this.buildCost(true, undefined),
809
+ isError: true,
810
+ failureReason
811
+ };
812
+ } finally {
813
+ this.settleOpenSteers("Amp exited before the steering message reached the model");
814
+ await rm(this.directory, { recursive: true, force: true });
815
+ }
816
+ }
817
+ waitForCompletion() {
818
+ return this.completion;
819
+ }
820
+ async abort() {
821
+ this.aborted = true;
822
+ this.endInput();
823
+ await this.terminate();
824
+ }
825
+ }
826
+
827
+ class AmpAdapter {
828
+ options;
829
+ name = "amp";
830
+ traits = {
831
+ hasMcp: true,
832
+ hasToolSearch: true,
833
+ nativeSkillDiscovery: true,
834
+ hasLocalEnvironment: true,
835
+ steerModes: ["queue"]
836
+ };
837
+ constructor(options = {}) {
838
+ this.options = options;
839
+ }
840
+ async createSession(config) {
841
+ const env = { ...process.env, ...config.env };
842
+ const apiKey = env.AMP_API_KEY?.trim();
843
+ if (!apiKey)
844
+ throw new Error("amp requires AMP_API_KEY");
845
+ const selection = resolveAmpModel(config.model);
846
+ const binary = resolveAmpBinary(env);
847
+ if (!binary) {
848
+ throw new Error(`amp CLI not found. Install ${AMP_PACKAGE} during image provisioning or set AMP_BINARY to a trusted executable.`);
849
+ }
850
+ registerVolatileSecret(apiKey, "AMP_API_KEY");
851
+ const prompt = await resolveSlashSkillPrompt(config.prompt, {
852
+ providerLabel: "amp",
853
+ skillsDir: join(env.HOME ?? "/home/worker", ".agents", "skills")
854
+ });
855
+ const effort = applyReasoningEffort("amp", selection.model, config.reasoningEffort);
856
+ const reasoningEffort = effort.kind === "amp-effort" ? effort.reasoningEffort : null;
857
+ const directory = await mkdtemp(join(tmpdir(), "swarm-amp-"));
858
+ try {
859
+ const configRoot = join(directory, "config");
860
+ const pluginDir = join(configRoot, "amp", "plugins");
861
+ await mkdir(pluginDir, { recursive: true });
862
+ const settingsPath = join(configRoot, "amp", "settings.json");
863
+ const mcpPath = join(directory, "mcp.json");
864
+ const steeringInbox = join(directory, "steering");
865
+ await mkdir(steeringInbox, { mode: 448 });
866
+ await writeFile(join(pluginDir, "agent-swarm.ts"), buildAmpPlugin({
867
+ baseMode: selection.baseMode,
868
+ instructions: config.systemPrompt,
869
+ pin: selection.pin,
870
+ reasoningEffort: reasoningEffort ? ampEffort(reasoningEffort) : null,
871
+ steeringInbox
872
+ }), { mode: 384 });
873
+ await writeFile(settingsPath, JSON.stringify(buildAmpSettings()), { mode: 384 });
874
+ await writeFile(mcpPath, JSON.stringify(swarmMcpConfig(config)), { mode: 384 });
875
+ const childEnv = {
876
+ ...env,
877
+ AMP_API_KEY: apiKey,
878
+ XDG_CONFIG_HOME: configRoot,
879
+ AMP_SETTINGS_FILE: settingsPath
880
+ };
881
+ const proc = registerProcessGroup(Bun.spawn([
882
+ binary,
883
+ "-x",
884
+ "--stream-json",
885
+ "--stream-json-input",
886
+ "-m",
887
+ AMP_PLUGIN_MODE_KEY,
888
+ "--title",
889
+ `swarm task ${config.taskId.slice(0, 8)}`,
890
+ "--settings-file",
891
+ settingsPath,
892
+ "--mcp-config",
893
+ mcpPath,
894
+ "--plugin-ready-timeout",
895
+ "30",
896
+ "--visibility",
897
+ "private",
898
+ "--no-notifications",
899
+ "--no-ide",
900
+ "--no-color"
901
+ ], {
902
+ cwd: config.cwd,
903
+ env: childEnv,
904
+ stdin: "pipe",
905
+ stdout: "pipe",
906
+ stderr: "pipe",
907
+ detached: detachedProcessGroup
908
+ }));
909
+ return new AmpSession(proc, directory, prompt, {
910
+ selection,
911
+ reasoningEffort,
912
+ taskId: config.taskId,
913
+ agentId: config.agentId,
914
+ binary,
915
+ cwd: config.cwd,
916
+ env: childEnv,
917
+ exitAfterInputMs: this.options.exitAfterInputMs ?? AMP_EXIT_AFTER_INPUT_MS,
918
+ steeringInbox
919
+ });
920
+ } catch (error) {
921
+ await rm(directory, { recursive: true, force: true });
922
+ throw error;
923
+ }
924
+ }
925
+ async canResume(_sessionId) {
926
+ return false;
927
+ }
928
+ formatCommand(commandName) {
929
+ return `/${commandName}`;
930
+ }
931
+ }
932
+
933
+ export { AMP_PACKAGE, checkAmpCredentials, resolveAmpBinary, buildAmpPlugin, liveTestAmpCredentials, parseAmpThreadCost, parseAmpThreadUsage, AmpAdapter };