@desplega.ai/agent-swarm 1.131.0 → 1.133.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 (265) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-w53mbns9.js → actions-cskpayd7.js} +11 -9
  3. package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-tpxtbx7b.js} +28 -12
  4. package/dist/{app-m9gwqsqv.js → app-ang77nbb.js} +8 -6
  5. package/dist/{assistant-bp65m6f3.js → assistant-b4exebdw.js} +16 -13
  6. package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-m3921t4y.js} +8 -8
  7. package/dist/{boot-reembed-62p0at2k.js → boot-reembed-ps2aftpb.js} +9 -7
  8. package/dist/{boot-reembed-xmfp6wdh.js → boot-reembed-znyk3nxb.js} +10 -8
  9. package/dist/{boot-scrub-logs-rr8q2dms.js → boot-scrub-logs-2ax3xgz1.js} +7 -5
  10. package/dist/{claude-adapter-2y47mrkd.js → claude-adapter-3ct8kgxw.js} +13 -9
  11. package/dist/{claude-managed-adapter-gv19tdew.js → claude-managed-adapter-akx4ntf2.js} +2 -2
  12. package/dist/{cli-t1xze5qh.js → cli-13w2hwkh.js} +1 -1
  13. package/dist/{cli-efxfnq6q.js → cli-1532szb1.js} +8 -6
  14. package/dist/{cli-d2tfkcga.js → cli-23d3rseb.js} +33 -17
  15. package/dist/{cli-nm5gtp1x.js → cli-2a75z9c5.js} +196 -7
  16. package/dist/{cli-fywms69x.js → cli-2t30rj0d.js} +1 -1
  17. package/dist/{cli-cvgjwnh1.js → cli-3c3tdgab.js} +10 -10
  18. package/dist/{cli-pm6xbh84.js → cli-3zzxa9jz.js} +46 -16
  19. package/dist/{cli-cdeg08wx.js → cli-4k6233gc.js} +1 -1
  20. package/dist/{cli-t55yw3a1.js → cli-5p2tz05r.js} +2 -2
  21. package/dist/{cli-0rcyq6d9.js → cli-5z2yhzvd.js} +125 -47
  22. package/dist/{cli-3kf51j15.js → cli-6zfq31nj.js} +9 -3
  23. package/dist/{cli-er3a4bb0.js → cli-8m9de10a.js} +6 -1
  24. package/dist/{cli-gm9cbhbq.js → cli-8t14vg9w.js} +27 -8
  25. package/dist/{cli-72q8m8gk.js → cli-9nfc2es8.js} +6 -6
  26. package/dist/{cli-5ze9rv97.js → cli-9ta3pwms.js} +11 -4
  27. package/dist/{cli-78xwvkhj.js → cli-avr0ns7z.js} +2 -2
  28. package/dist/{cli-n6h8qt8n.js → cli-c897wyhw.js} +12 -7
  29. package/dist/{cli-2q72qz2s.js → cli-dgk20ft7.js} +15 -6
  30. package/dist/cli-et1bbwqf.js +13 -0
  31. package/dist/{cli-bqw0mt1n.js → cli-f5w27pj3.js} +3 -3
  32. package/dist/{cli-2wadx7p2.js → cli-f8dre4bz.js} +2 -2
  33. package/dist/{cli-e7ckcg4v.js → cli-ftwpv451.js} +170 -387
  34. package/dist/{cli-g6b8x1h8.js → cli-g854s2dw.js} +1 -1
  35. package/dist/{cli-5kx36evn.js → cli-ggmdxeqa.js} +13 -8
  36. package/dist/cli-hkwfe3qj.js +530 -0
  37. package/dist/{cli-5dsbcjjw.js → cli-jqwnqx9m.js} +1 -1
  38. package/dist/{cli-9dr5qzc0.js → cli-ny122vh4.js} +21 -10
  39. package/dist/{cli-5v4rw6yd.js → cli-pvzqhbs9.js} +2 -2
  40. package/dist/cli-pzq5rjym.js +15 -0
  41. package/dist/{cli-xpxde7rm.js → cli-qes8ef67.js} +48 -30
  42. package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
  43. package/dist/cli-qys3rhhz.js +27 -0
  44. package/dist/{cli-rzbpmxdr.js → cli-r6tpm37h.js} +2 -2
  45. package/dist/{cli-qv61hp37.js → cli-rch80c76.js} +2 -2
  46. package/dist/{cli-0258203w.js → cli-rpp0x4zy.js} +137 -17
  47. package/dist/{cli-5n6e5yfn.js → cli-s1k0wxhe.js} +12 -7
  48. package/dist/{cli-psh3c4va.js → cli-sfd9v7sj.js} +1 -1
  49. package/dist/{cli-70xqj0z3.js → cli-sh3ytsyj.js} +49 -39
  50. package/dist/{cli-zsspvvm2.js → cli-w1mp9jy7.js} +1 -1
  51. package/dist/{cli-d3x3x1c9.js → cli-xdb4mpnp.js} +2 -2
  52. package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
  53. package/dist/{cli-yw8vghx2.js → cli-xv8t8p3c.js} +65 -32
  54. package/dist/{cli-f1am9szv.js → cli-xxv5pn4f.js} +1 -1
  55. package/dist/{cli-t76vw5db.js → cli-y08jvxzx.js} +601 -140
  56. package/dist/{cli-a4jzdpa3.js → cli-zfbf3srj.js} +650 -245
  57. package/dist/{cli-a40rnf71.js → cli-zmq89k34.js} +6 -6
  58. package/dist/{cli-qmdnhpej.js → cli-zr9q011v.js} +23 -13
  59. package/dist/cli.js +22 -22
  60. package/dist/{codex-adapter-d24q75wk.js → codex-adapter-bef93pq4.js} +9 -7
  61. package/dist/{codex-hook-hq54t9ft.js → codex-hook-e8dba65p.js} +3 -2
  62. package/dist/{codex-session-runner-g50vcsex.js → codex-session-runner-5pkxys7x.js} +9 -7
  63. package/dist/{commands-c3nrc5hg.js → commands-e23s1bff.js} +9 -7
  64. package/dist/{db-nt7fg3tw.js → db-m3nabatn.js} +17 -5
  65. package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-8maw55xh.js} +11 -8
  66. package/dist/{google-vertex-p5arrga0.js → google-vertex-8vjqmx8g.js} +11 -8
  67. package/dist/{handlers-fkr6swat.js → handlers-akazedvr.js} +16 -13
  68. package/dist/{hook-77wpvnjc.js → hook-ze4cpf0v.js} +55 -41
  69. package/dist/{hook-g019yehy.js → hook-zfqyyenw.js} +10 -7
  70. package/dist/{http-jewj0syk.js → http-sja0n6kw.js} +730 -150
  71. package/dist/index-0gr8vd6k.js +39 -0
  72. package/dist/{index-7n2xpbd4.js → index-354vkhdv.js} +154 -74
  73. package/dist/{index-070v4eyb.js → index-5qthek5t.js} +2 -3
  74. package/dist/{index-hfvggd0z.js → index-95t3cqq5.js} +16 -14
  75. package/dist/{index-9znhar3x.js → index-sea66619.js} +3 -4
  76. package/dist/{index-6gm5he8h.js → index-vpn36mma.js} +15 -13
  77. package/dist/{keepalive-j0fjvqfd.js → keepalive-3sp3meqj.js} +16 -10
  78. package/dist/{lead-1n8x30xe.js → lead-mefeewkf.js} +39 -36
  79. package/dist/{maintenance-gvev11sa.js → maintenance-8tbrbjfg.js} +10 -8
  80. package/dist/mistral-conversations-00zrp7g4.js +717 -0
  81. package/dist/{oauth-refresh-sweep-s9ecsvh0.js → oauth-refresh-sweep-srjwjz6m.js} +8 -6
  82. package/dist/{onboard-pvm5b38w.js → onboard-mt2tgyjg.js} +7 -7
  83. package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-map32h2s.js} +18 -18
  84. package/dist/{openai-completions-3vkmfz2a.js → openai-completions-w871b1cq.js} +12 -11
  85. package/dist/{openai-responses-dg4t7j7a.js → openai-responses-b0j7xnw8.js} +15 -12
  86. package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-b39jefcj.js} +5 -3
  87. package/dist/{openrouter-images-g3gm8931.js → openrouter-images-srfkywy0.js} +4 -4
  88. package/dist/{otel-impl-p6jvgd9c.js → otel-impl-z6pxt3j9.js} +224 -9
  89. package/dist/{pi-mono-adapter-t091s27g.js → pi-mono-adapter-btvqmj58.js} +1138 -408
  90. package/dist/{pricing-refresh-k49rcyed.js → pricing-refresh-kyq8cezh.js} +9 -7
  91. package/dist/{rbac-roles-cr80g96t.js → rbac-roles-3ryfrfna.js} +8 -6
  92. package/dist/{rbac-roles-30pspvy7.js → rbac-roles-6qfvbee1.js} +9 -7
  93. package/dist/{render-v2-xm457ng1.js → render-v2-p61ppjey.js} +8 -6
  94. package/dist/{seed-pricing-yhpv35fr.js → seed-pricing-j56bbj4y.js} +8 -6
  95. package/dist/{setup-nytnmwwr.js → setup-tpr6c1kn.js} +2 -2
  96. package/dist/{worker-msytr2cw.js → worker-9w1v0mbb.js} +39 -36
  97. package/openapi.json +542 -16
  98. package/package.json +6 -6
  99. package/src/be/budget-refusal-notify.ts +14 -7
  100. package/src/be/db-queries/task-artifacts.ts +182 -0
  101. package/src/be/db.ts +783 -169
  102. package/src/be/events.ts +5 -0
  103. package/src/be/migrations/130_scratch_script_retention_grace.sql +6 -0
  104. package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
  105. package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
  106. package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
  107. package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
  108. package/src/be/migrations/136_task_requester_provenance.sql +37 -0
  109. package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
  110. package/src/be/multi-runtime.ts +430 -0
  111. package/src/be/scripts/db.ts +43 -1
  112. package/src/be/scripts/extract-schema.ts +3 -1
  113. package/src/be/scripts/retention.ts +164 -0
  114. package/src/be/scripts/run-saved.ts +16 -1
  115. package/src/be/scripts/typecheck.ts +3 -2
  116. package/src/be/seed-skills/index.ts +7 -0
  117. package/src/be/steering.ts +1 -1
  118. package/src/be/swarm-config-guard.ts +18 -0
  119. package/src/commands/credential-wait.ts +54 -0
  120. package/src/commands/onboard/steps/core-credentials.tsx +1 -1
  121. package/src/commands/onboard/steps/custom-templates.tsx +1 -1
  122. package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
  123. package/src/commands/onboard/steps/health-check.tsx +1 -1
  124. package/src/commands/onboard/steps/start.tsx +1 -1
  125. package/src/commands/profile-sync-audit.ts +56 -0
  126. package/src/commands/profile-sync.ts +287 -15
  127. package/src/commands/provider-credentials.ts +46 -19
  128. package/src/commands/runner.ts +196 -33
  129. package/src/commands/templates.ts +22 -0
  130. package/src/github/handlers.ts +55 -9
  131. package/src/heartbeat/heartbeat.ts +71 -12
  132. package/src/hooks/hook.ts +49 -33
  133. package/src/http/active-sessions.ts +11 -1
  134. package/src/http/agents.ts +261 -30
  135. package/src/http/all-routes.ts +1 -0
  136. package/src/http/config.ts +13 -2
  137. package/src/http/core.ts +102 -4
  138. package/src/http/db-query-bounded.ts +313 -0
  139. package/src/http/db-query-shared.ts +249 -0
  140. package/src/http/db-query.ts +54 -42
  141. package/src/http/events.ts +2 -0
  142. package/src/http/index.ts +27 -2
  143. package/src/http/mcp-bridge.ts +37 -3
  144. package/src/http/mcp.ts +12 -0
  145. package/src/http/memory.ts +13 -2
  146. package/src/http/openapi.ts +1 -0
  147. package/src/http/poll.ts +70 -27
  148. package/src/http/route-def.ts +43 -9
  149. package/src/http/schedules.ts +2 -2
  150. package/src/http/scripts.ts +31 -4
  151. package/src/http/session-data.ts +44 -0
  152. package/src/http/tasks.ts +2 -2
  153. package/src/http/users.ts +74 -52
  154. package/src/http/webhooks.ts +5 -2
  155. package/src/http/workflows.ts +1 -4
  156. package/src/http/x.ts +14 -0
  157. package/src/integrations/kapso/inbound.ts +1 -1
  158. package/src/jira/outbound.ts +39 -8
  159. package/src/jira/webhook-lifecycle.ts +13 -2
  160. package/src/linear/outbound.ts +43 -10
  161. package/src/oauth/keepalive.ts +13 -2
  162. package/src/prompts/base-prompt.ts +1 -1
  163. package/src/prompts/session-templates.ts +45 -7
  164. package/src/providers/claude-adapter.ts +6 -0
  165. package/src/providers/claude-managed-models.ts +34 -10
  166. package/src/providers/codex-adapter.ts +6 -0
  167. package/src/providers/opencode-adapter.ts +6 -0
  168. package/src/providers/pi-mono-adapter.ts +8 -0
  169. package/src/providers/pricing-sources.md +24 -0
  170. package/src/queue-stall-alarm.ts +216 -0
  171. package/src/scheduler/scheduler.ts +7 -0
  172. package/src/script-workflows/executor.ts +2 -1
  173. package/src/scripts-runtime/executors/native.ts +2 -1
  174. package/src/scripts-runtime/executors/types.ts +5 -0
  175. package/src/scripts-runtime/loader.ts +5 -0
  176. package/src/scripts-runtime/swarm-config.ts +8 -0
  177. package/src/scripts-runtime/swarm-sdk.ts +7 -0
  178. package/src/scripts-runtime/types/stdlib.d.ts +1 -0
  179. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
  180. package/src/tests/budget-claim-gate.test.ts +20 -0
  181. package/src/tests/claude-adapter.test.ts +32 -0
  182. package/src/tests/claude-managed-adapter.test.ts +38 -7
  183. package/src/tests/codex-adapter.test.ts +30 -0
  184. package/src/tests/create-task-validation.test.ts +128 -0
  185. package/src/tests/credential-wait.test.ts +67 -1
  186. package/src/tests/db-query.test.ts +711 -2
  187. package/src/tests/http-api-integration.test.ts +81 -0
  188. package/src/tests/http-users.test.ts +166 -1
  189. package/src/tests/identity-field-budget.test.ts +176 -0
  190. package/src/tests/mcp-tools-user.test.ts +34 -0
  191. package/src/tests/mcp-user-route.test.ts +17 -0
  192. package/src/tests/metrics-http.test.ts +44 -0
  193. package/src/tests/multi-runtime-registration.test.ts +2703 -0
  194. package/src/tests/openapi-response-contract.test.ts +24 -0
  195. package/src/tests/openapi-route-headers.test.ts +83 -0
  196. package/src/tests/opencode-adapter.test.ts +40 -0
  197. package/src/tests/outbound-listener-rejection.test.ts +138 -0
  198. package/src/tests/pi-mono-adapter.test.ts +98 -0
  199. package/src/tests/profile-sync-warning.test.ts +277 -0
  200. package/src/tests/profile-sync.test.ts +232 -6
  201. package/src/tests/prompt-template-session.test.ts +32 -3
  202. package/src/tests/queue-stall-alarm.test.ts +226 -0
  203. package/src/tests/requester-comms.test.ts +32 -0
  204. package/src/tests/runner-requester-profile.test.ts +38 -0
  205. package/src/tests/sandboxed-process.test.ts +45 -0
  206. package/src/tests/schedule-target-type.test.ts +38 -0
  207. package/src/tests/script-connections.test.ts +16 -3
  208. package/src/tests/scripts-external-api.test.ts +8 -3
  209. package/src/tests/scripts-http.test.ts +12 -0
  210. package/src/tests/scripts-retention.test.ts +384 -0
  211. package/src/tests/scripts-runtime-identity.test.ts +375 -0
  212. package/src/tests/scripts-runtime.test.ts +4 -3
  213. package/src/tests/sdk-allowlist.test.ts +47 -0
  214. package/src/tests/send-task-requested-by.test.ts +9 -0
  215. package/src/tests/session-costs.test.ts +506 -1
  216. package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
  217. package/src/tests/steering-core.test.ts +28 -0
  218. package/src/tests/task-artifacts.test.ts +386 -0
  219. package/src/tests/task-attachments-schema.test.ts +17 -1
  220. package/src/tests/task-completion-idempotency.test.ts +57 -0
  221. package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
  222. package/src/tests/update-profile-api.test.ts +67 -39
  223. package/src/tests/update-profile-auth.test.ts +48 -4
  224. package/src/tests/user-attribution-migration.test.ts +54 -0
  225. package/src/tests/user-identity.test.ts +72 -0
  226. package/src/tests/workflow-async-v2.test.ts +39 -1
  227. package/src/tests/workflow-http-v2.test.ts +3 -2
  228. package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
  229. package/src/tests/workflow-retry-v2.test.ts +45 -1
  230. package/src/tests/workflow-swarm-script.test.ts +14 -5
  231. package/src/tools/db-query.ts +18 -8
  232. package/src/tools/get-task-details.ts +24 -3
  233. package/src/tools/manage-user.ts +36 -3
  234. package/src/tools/poll-task.ts +93 -14
  235. package/src/tools/script-common.ts +5 -0
  236. package/src/tools/send-task.ts +1 -4
  237. package/src/tools/store-progress.ts +13 -2
  238. package/src/tools/swarm-config/delete-config.ts +9 -0
  239. package/src/tools/swarm-config/set-config.ts +3 -2
  240. package/src/tools/task-action.ts +30 -0
  241. package/src/tools/task-tool-ctx.ts +8 -1
  242. package/src/tools/update-profile.ts +16 -21
  243. package/src/tools/utils.ts +5 -0
  244. package/src/tools/workflows/trigger-workflow.ts +7 -1
  245. package/src/types.ts +165 -1
  246. package/src/utils/github-pull-request.ts +76 -0
  247. package/src/utils/identity-field-budget.ts +73 -0
  248. package/src/utils/multi-runtime.ts +20 -0
  249. package/src/utils/requester-comms.ts +24 -0
  250. package/src/utils/sandboxed-process.ts +40 -14
  251. package/src/workflows/engine.ts +22 -2
  252. package/src/workflows/executors/swarm-script.ts +24 -1
  253. package/src/workflows/resume.ts +19 -11
  254. package/src/workflows/retry-poller.ts +1 -0
  255. package/src/workflows/triggers.ts +4 -0
  256. package/templates/schedules/daily-compounding-reflection/content.md +12 -3
  257. package/templates/skills/db-query-guidance/config.json +14 -0
  258. package/templates/skills/db-query-guidance/content.md +57 -0
  259. package/dist/cli-3te4jdkm.js +0 -225
  260. package/dist/cli-bmze986d.js +0 -7
  261. package/dist/cli-kh3ba2zf.js +0 -607
  262. package/dist/index-hp1kdbjj.js +0 -36
  263. package/dist/mistral-conversations-81fwav0j.js +0 -30578
  264. package/dist/otel-nqa12wf3.js +0 -3149
  265. package/dist/{pi-messages-tqj8q6b9.js → pi-messages-sssy32x4.js} +3 -3
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Runtime identity through the script path: MCP script tools → /api/scripts/run
3
+ * → SwarmConfigPayload (system context) → swarm SDK headers → the same
4
+ * work-acquisition gates the worker's own MCP/HTTP calls hit. Identity is
5
+ * process context end to end — scripts never supply or override it.
6
+ */
7
+
8
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
9
+ import { unlink } from "node:fs/promises";
10
+ import { createServer as createHttpServer, type Server } from "node:http";
11
+ import {
12
+ closeDb,
13
+ createAgent,
14
+ createTaskExtended,
15
+ getActiveTaskCount,
16
+ getDb,
17
+ getTaskById,
18
+ initDb,
19
+ } from "../be/db";
20
+ import { upsertRuntimeInstance } from "../be/multi-runtime";
21
+ import { handleMcpBridge } from "../http/mcp-bridge";
22
+ import { handlePoll } from "../http/poll";
23
+ import { getPathSegments, parseQueryParams } from "../http/utils";
24
+ import { runScript } from "../scripts-runtime/loader";
25
+ import { SwarmConfig } from "../scripts-runtime/swarm-config";
26
+ import { createSwarmSdk } from "../scripts-runtime/swarm-sdk";
27
+ import { proxyScriptsApi } from "../tools/script-common";
28
+
29
+ const TEST_DB_PATH = "./test-scripts-runtime-identity.sqlite";
30
+ const TEST_PORT = 14113 + (process.pid % 1000);
31
+ const baseUrl = `http://localhost:${TEST_PORT}`;
32
+ const API_KEY = "scripts-runtime-identity-key-1234567890";
33
+
34
+ const savedEnv = { ...process.env };
35
+ const originalFetch = globalThis.fetch;
36
+ let server: Server;
37
+
38
+ async function removeDbFiles(path: string): Promise<void> {
39
+ for (const suffix of ["", "-wal", "-shm"]) {
40
+ await unlink(path + suffix).catch(() => {});
41
+ }
42
+ }
43
+
44
+ /** Test server exposing the two real handlers the script SDK dispatches through. */
45
+ function createTestServer(): Server {
46
+ return createHttpServer(async (req, res) => {
47
+ const myAgentId = (req.headers["x-agent-id"] as string | undefined) ?? undefined;
48
+ const segments = getPathSegments(req.url ?? "");
49
+ const query = parseQueryParams(req.url ?? "");
50
+ if (req.url?.startsWith("/api/poll")) {
51
+ if (await handlePoll(req, res, segments, query, myAgentId)) return;
52
+ }
53
+ if (req.url?.startsWith("/api/mcp-bridge")) {
54
+ if (await handleMcpBridge(req, res, segments, query, myAgentId)) return;
55
+ }
56
+ res.writeHead(404, { "Content-Type": "application/json" });
57
+ res.end(JSON.stringify({ error: "Not found" }));
58
+ });
59
+ }
60
+
61
+ function makeAgent(maxTasks = 1): string {
62
+ const id = crypto.randomUUID();
63
+ createAgent({ id, name: "sri-agent", isLead: false, status: "idle", capabilities: [], maxTasks });
64
+ return id;
65
+ }
66
+
67
+ function registerRuntime(agentId: string): string {
68
+ const rt = crypto.randomUUID();
69
+ upsertRuntimeInstance({ id: rt, agentId, reportedSlots: 1 });
70
+ return rt;
71
+ }
72
+
73
+ function makeRuntimeStale(runtimeInstanceId: string, minutesAgo = 30): void {
74
+ const when = new Date(Date.now() - minutesAgo * 60 * 1000).toISOString();
75
+ getDb()
76
+ .prepare("UPDATE runtime_instances SET last_seen_at = ? WHERE id = ?")
77
+ .run(when, runtimeInstanceId);
78
+ }
79
+
80
+ function sdkFor(agentId: string, runtimeInstanceId?: string) {
81
+ return createSwarmSdk(
82
+ new SwarmConfig({
83
+ system: {
84
+ apiKey: { value: API_KEY, isSecret: true },
85
+ agentId: { value: agentId, isSecret: false },
86
+ mcpBaseUrl: { value: baseUrl, isSecret: false },
87
+ ...(runtimeInstanceId
88
+ ? { runtimeInstanceId: { value: runtimeInstanceId, isSecret: false as const } }
89
+ : {}),
90
+ },
91
+ user: {},
92
+ }),
93
+ );
94
+ }
95
+
96
+ beforeAll(async () => {
97
+ await removeDbFiles(TEST_DB_PATH);
98
+ initDb(TEST_DB_PATH);
99
+ server = createTestServer();
100
+ await new Promise<void>((resolve) => {
101
+ server.listen(TEST_PORT, () => resolve());
102
+ });
103
+ });
104
+
105
+ afterAll(async () => {
106
+ await new Promise<void>((resolve) => {
107
+ server.close(() => resolve());
108
+ });
109
+ closeDb();
110
+ await removeDbFiles(TEST_DB_PATH);
111
+ });
112
+
113
+ beforeEach(() => {
114
+ process.env.AGENT_SWARM_API_KEY = API_KEY;
115
+ delete process.env.API_KEY;
116
+ });
117
+
118
+ // Full env restore — a leaked AGENT_SWARM_API_KEY outranks the legacy API_KEY
119
+ // that other suites' spawned servers authenticate with.
120
+ afterEach(() => {
121
+ globalThis.fetch = originalFetch;
122
+ for (const key of Object.keys(process.env)) {
123
+ if (!(key in savedEnv)) delete process.env[key];
124
+ }
125
+ for (const [key, value] of Object.entries(savedEnv)) {
126
+ if (value === undefined) delete process.env[key];
127
+ else process.env[key] = value;
128
+ }
129
+ });
130
+
131
+ describe("script-run proxy forwards runtime identity", () => {
132
+ test("X-Runtime-Instance-ID from the MCP request context reaches the scripts API", async () => {
133
+ let captured: Record<string, string> | undefined;
134
+ globalThis.fetch = (async (_input: unknown, init?: RequestInit) => {
135
+ captured = init?.headers as Record<string, string>;
136
+ return new Response(JSON.stringify({ ok: true }), { status: 200 });
137
+ }) as typeof globalThis.fetch;
138
+
139
+ await proxyScriptsApi({
140
+ method: "POST",
141
+ path: "/api/scripts/run",
142
+ body: { name: "x" },
143
+ requestInfo: {
144
+ sessionId: "s",
145
+ agentId: "agent-1",
146
+ runtimeInstanceId: "rt-proxy-1",
147
+ sourceTaskId: "task-1",
148
+ contextKey: undefined,
149
+ callOrigin: "mcp",
150
+ },
151
+ successMessage: () => "ok",
152
+ });
153
+
154
+ expect(captured?.["X-Agent-ID"]).toBe("agent-1");
155
+ expect(captured?.["X-Runtime-Instance-ID"]).toBe("rt-proxy-1");
156
+ });
157
+
158
+ test("no runtime identity in the request context → no header", async () => {
159
+ let captured: Record<string, string> | undefined;
160
+ globalThis.fetch = (async (_input: unknown, init?: RequestInit) => {
161
+ captured = init?.headers as Record<string, string>;
162
+ return new Response(JSON.stringify({ ok: true }), { status: 200 });
163
+ }) as typeof globalThis.fetch;
164
+
165
+ await proxyScriptsApi({
166
+ method: "GET",
167
+ path: "/api/scripts",
168
+ requestInfo: {
169
+ sessionId: "s",
170
+ agentId: "agent-1",
171
+ runtimeInstanceId: undefined,
172
+ sourceTaskId: undefined,
173
+ contextKey: undefined,
174
+ callOrigin: "mcp",
175
+ },
176
+ successMessage: () => "ok",
177
+ });
178
+
179
+ expect(captured?.["X-Runtime-Instance-ID"]).toBeUndefined();
180
+ });
181
+ });
182
+
183
+ describe("swarm SDK emits the runtime header", () => {
184
+ test("task_poll and the generic MCP bridge both carry X-Runtime-Instance-ID", async () => {
185
+ const seen: Array<{ path: string; runtime: string | null }> = [];
186
+ const stub = Bun.serve({
187
+ port: 0,
188
+ fetch(req) {
189
+ const url = new URL(req.url);
190
+ seen.push({ path: url.pathname, runtime: req.headers.get("x-runtime-instance-id") });
191
+ return Response.json({ trigger: null });
192
+ },
193
+ });
194
+ try {
195
+ const sdk = createSwarmSdk(
196
+ new SwarmConfig({
197
+ system: {
198
+ apiKey: { value: API_KEY, isSecret: true },
199
+ agentId: { value: "agent-sdk", isSecret: false },
200
+ mcpBaseUrl: { value: `http://127.0.0.1:${stub.port}`, isSecret: false },
201
+ runtimeInstanceId: { value: "rt-sdk-1", isSecret: false },
202
+ },
203
+ user: {},
204
+ }),
205
+ );
206
+ await sdk.task_poll();
207
+ await sdk.task_action({ action: "claim", taskId: crypto.randomUUID() });
208
+
209
+ expect(seen).toEqual([
210
+ { path: "/api/poll", runtime: "rt-sdk-1" },
211
+ { path: "/api/mcp-bridge", runtime: "rt-sdk-1" },
212
+ ]);
213
+ } finally {
214
+ stub.stop(true);
215
+ }
216
+ });
217
+
218
+ test("without a runtime identity the SDK sends none (legacy compatibility)", async () => {
219
+ const seen: Array<string | null> = [];
220
+ const stub = Bun.serve({
221
+ port: 0,
222
+ fetch(req) {
223
+ seen.push(req.headers.get("x-runtime-instance-id"));
224
+ return Response.json({ trigger: null });
225
+ },
226
+ });
227
+ try {
228
+ const sdk = createSwarmSdk(
229
+ new SwarmConfig({
230
+ system: {
231
+ apiKey: { value: API_KEY, isSecret: true },
232
+ agentId: { value: "agent-sdk", isSecret: false },
233
+ mcpBaseUrl: { value: `http://127.0.0.1:${stub.port}`, isSecret: false },
234
+ },
235
+ user: {},
236
+ }),
237
+ );
238
+ await sdk.task_poll();
239
+ expect(seen).toEqual([null]);
240
+ } finally {
241
+ stub.stop(true);
242
+ }
243
+ });
244
+ });
245
+
246
+ describe("script subprocess carries the invoking worker's identity", () => {
247
+ const resources = { memoryMb: 2048, cpuTimeSec: 20, maxStdoutBytes: 1_048_576 };
248
+
249
+ test("ctx.swarm.task_poll() presents X-Runtime-Instance-ID from system config", async () => {
250
+ const seen: Array<string | null> = [];
251
+ const stub = Bun.serve({
252
+ port: 0,
253
+ fetch(req) {
254
+ seen.push(req.headers.get("x-runtime-instance-id"));
255
+ return Response.json({ trigger: null });
256
+ },
257
+ });
258
+ try {
259
+ const output = await runScript({
260
+ agentId: "agent-sub",
261
+ runtimeInstanceId: "rt-subprocess-1",
262
+ mcpBaseUrl: `http://127.0.0.1:${stub.port}`,
263
+ resources,
264
+ source: `
265
+ export default async (_args, ctx) => {
266
+ await ctx.swarm.task_poll();
267
+ return "polled";
268
+ };
269
+ `,
270
+ });
271
+
272
+ expect(output.error).toBeUndefined();
273
+ expect(output.result).toBe("polled");
274
+ expect(seen).toEqual(["rt-subprocess-1"]);
275
+ } finally {
276
+ stub.stop(true);
277
+ }
278
+ });
279
+
280
+ test("without a runtime identity the subprocess sends none", async () => {
281
+ const seen: Array<string | null> = [];
282
+ const stub = Bun.serve({
283
+ port: 0,
284
+ fetch(req) {
285
+ seen.push(req.headers.get("x-runtime-instance-id"));
286
+ return Response.json({ trigger: null });
287
+ },
288
+ });
289
+ try {
290
+ const output = await runScript({
291
+ agentId: "agent-sub",
292
+ mcpBaseUrl: `http://127.0.0.1:${stub.port}`,
293
+ resources,
294
+ source: `
295
+ export default async (_args, ctx) => {
296
+ await ctx.swarm.task_poll();
297
+ return "polled";
298
+ };
299
+ `,
300
+ });
301
+
302
+ expect(output.error).toBeUndefined();
303
+ expect(seen).toEqual([null]);
304
+ } finally {
305
+ stub.stop(true);
306
+ }
307
+ });
308
+ });
309
+
310
+ describe("script SDK dispatch hits the real runtime gates", () => {
311
+ test("a live runtime acquires pending work through task_poll; a stale one does not", async () => {
312
+ process.env.MULTI_RUNTIME_ENABLED = "true";
313
+ const agentId = makeAgent(1);
314
+ const live = registerRuntime(agentId);
315
+ const stale = registerRuntime(agentId);
316
+ makeRuntimeStale(stale);
317
+ const task = createTaskExtended("script-poll-work", { agentId });
318
+
319
+ const staleResult = (await sdkFor(agentId, stale).task_poll()) as {
320
+ data: { trigger: { type?: string } | null };
321
+ };
322
+ expect(staleResult.data.trigger).toBeNull();
323
+ expect(getTaskById(task.id)?.status).toBe("pending");
324
+
325
+ const liveResult = (await sdkFor(agentId, live).task_poll()) as {
326
+ data: { trigger: { type?: string } | null };
327
+ };
328
+ expect(liveResult.data.trigger?.type).toBe("task_assigned");
329
+ expect(getTaskById(task.id)?.status).toBe("in_progress");
330
+ expect(getActiveTaskCount(agentId)).toBe(1);
331
+ });
332
+
333
+ test("task_action claim through the bridge respects the live-runtime gate", async () => {
334
+ process.env.MULTI_RUNTIME_ENABLED = "true";
335
+ const agentId = makeAgent(1);
336
+ const live = registerRuntime(agentId);
337
+ const stale = registerRuntime(agentId);
338
+ makeRuntimeStale(stale);
339
+ const task = createTaskExtended("script-claim-work");
340
+
341
+ await sdkFor(agentId, stale).task_action({ action: "claim", taskId: task.id });
342
+ expect(getTaskById(task.id)?.agentId ?? null).toBeNull();
343
+ await sdkFor(agentId).task_action({ action: "claim", taskId: task.id });
344
+ expect(getTaskById(task.id)?.agentId ?? null).toBeNull();
345
+
346
+ await sdkFor(agentId, live).task_action({ action: "claim", taskId: task.id });
347
+ expect(getTaskById(task.id)?.agentId).toBe(agentId);
348
+ expect(getTaskById(task.id)?.status).toBe("in_progress");
349
+ });
350
+
351
+ test("task_action accept through the bridge respects the live-runtime gate", async () => {
352
+ process.env.MULTI_RUNTIME_ENABLED = "true";
353
+ const agentId = makeAgent(1);
354
+ const live = registerRuntime(agentId);
355
+ const stale = registerRuntime(agentId);
356
+ makeRuntimeStale(stale);
357
+ const task = createTaskExtended("script-accept-work", { offeredTo: agentId });
358
+
359
+ await sdkFor(agentId, stale).task_action({ action: "accept", taskId: task.id });
360
+ expect(getTaskById(task.id)?.status).toBe("offered");
361
+
362
+ await sdkFor(agentId, live).task_action({ action: "accept", taskId: task.id });
363
+ expect(getTaskById(task.id)?.status).toBe("pending");
364
+ expect(getTaskById(task.id)?.agentId).toBe(agentId);
365
+ });
366
+
367
+ test("with the flag off, the SDK dispatches without runtime identity as before", async () => {
368
+ delete process.env.MULTI_RUNTIME_ENABLED;
369
+ const agentId = makeAgent(1);
370
+ const task = createTaskExtended("legacy-claim-work");
371
+
372
+ await sdkFor(agentId).task_action({ action: "claim", taskId: task.id });
373
+ expect(getTaskById(task.id)?.agentId).toBe(agentId);
374
+ });
375
+ });
@@ -1,4 +1,5 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { fileURLToPath } from "node:url";
2
3
  import { createApiRegistryClient } from "../scripts-runtime/api-client";
3
4
  import { runScript } from "../scripts-runtime/loader";
4
5
  import {
@@ -327,7 +328,7 @@ describe("runScript", () => {
327
328
  const tmpdir = `${process.env.TMPDIR ?? "/tmp"}/script-runtime-test-${crypto.randomUUID()}`;
328
329
  await Bun.$`mkdir -p ${tmpdir}`;
329
330
  try {
330
- const runtimeSrc = new URL("../scripts-runtime", import.meta.url).pathname;
331
+ const runtimeSrc = fileURLToPath(new URL("../scripts-runtime", import.meta.url));
331
332
  await Bun.$`bun build ${runtimeSrc}/eval-harness.ts --target bun --no-splitting --outfile ${tmpdir}/eval-harness.bundle.js`.quiet();
332
333
  await Bun.$`bun build ${runtimeSrc}/stdlib/index.ts --target bun --no-splitting --outfile ${tmpdir}/stdlib.bundle.js`.quiet();
333
334
  await Bun.$`bun build ${runtimeSrc}/swarm-sdk.ts --target bun --no-splitting --outfile ${tmpdir}/swarm-sdk.bundle.js`.quiet();
@@ -377,7 +378,7 @@ describe("runScript", () => {
377
378
  const tmpdir = `${process.env.TMPDIR ?? "/tmp"}/script-runtime-test-${crypto.randomUUID()}`;
378
379
  await Bun.$`mkdir -p ${tmpdir}`;
379
380
  try {
380
- const runtimeSrc = new URL("../scripts-runtime", import.meta.url).pathname;
381
+ const runtimeSrc = fileURLToPath(new URL("../scripts-runtime", import.meta.url));
381
382
  await Bun.$`bun build ${runtimeSrc}/eval-harness.ts --target bun --no-splitting --outfile ${tmpdir}/eval-harness.bundle.js`.quiet();
382
383
  await Bun.$`bun build ${runtimeSrc}/stdlib/index.ts --target bun --no-splitting --outfile ${tmpdir}/stdlib.bundle.js`.quiet();
383
384
  await Bun.$`bun build ${runtimeSrc}/swarm-sdk.ts --target bun --no-splitting --outfile ${tmpdir}/swarm-sdk.bundle.js`.quiet();
@@ -410,7 +411,7 @@ describe("runScript", () => {
410
411
  const tmpdir = `${process.env.TMPDIR ?? "/tmp"}/script-runtime-test-${crypto.randomUUID()}`;
411
412
  await Bun.$`mkdir -p ${tmpdir}`;
412
413
  try {
413
- const runtimeSrc = new URL("../scripts-runtime", import.meta.url).pathname;
414
+ const runtimeSrc = fileURLToPath(new URL("../scripts-runtime", import.meta.url));
414
415
  await Bun.$`bun build ${runtimeSrc}/eval-harness.ts --target bun --no-splitting --outfile ${tmpdir}/eval-harness.bundle.js`.quiet();
415
416
  await Bun.$`bun build ${runtimeSrc}/stdlib/index.ts --target bun --no-splitting --outfile ${tmpdir}/stdlib.bundle.js`.quiet();
416
417
  await Bun.$`bun build ${runtimeSrc}/swarm-sdk.ts --target bun --no-splitting --outfile ${tmpdir}/swarm-sdk.bundle.js`.quiet();
@@ -98,6 +98,53 @@ describe("script SDK allowlist", () => {
98
98
  }
99
99
  });
100
100
 
101
+ test("schedule_list maps includeFull to the full HTTP field and keeps the default slim", async () => {
102
+ const requestUrls: URL[] = [];
103
+ const httpServer = Bun.serve({
104
+ port: 0,
105
+ fetch(req) {
106
+ const url = new URL(req.url);
107
+ requestUrls.push(url);
108
+ const full = url.searchParams.get("fields") === "full";
109
+ return Response.json({
110
+ schedules: [
111
+ full
112
+ ? { id: "schedule-1", taskTemplate: "full template" }
113
+ : { id: "schedule-1", taskTemplatePreview: "full..." },
114
+ ],
115
+ count: 1,
116
+ });
117
+ },
118
+ });
119
+ const config = new SwarmConfig({
120
+ system: {
121
+ apiKey: { value: "sdk-test-key", isSecret: true },
122
+ agentId: { value: "sdk-test-agent", isSecret: false },
123
+ mcpBaseUrl: { value: `http://127.0.0.1:${httpServer.port}`, isSecret: false },
124
+ },
125
+ user: {},
126
+ });
127
+ const sdk = createSwarmSdk(config);
128
+
129
+ try {
130
+ const full = await sdk.schedule_list({ includeFull: true });
131
+ expect(
132
+ (full as { data: { schedules: Array<{ taskTemplate?: string }> } }).data.schedules[0]
133
+ ?.taskTemplate,
134
+ ).toBe("full template");
135
+ expect(requestUrls[0]?.searchParams.get("fields")).toBe("full");
136
+
137
+ const slim = await sdk.schedule_list();
138
+ expect(
139
+ (slim as { data: { schedules: Array<{ taskTemplatePreview?: string }> } }).data.schedules[0]
140
+ ?.taskTemplatePreview,
141
+ ).toBe("full...");
142
+ expect(requestUrls[1]?.searchParams.has("fields")).toBe(false);
143
+ } finally {
144
+ httpServer.stop(true);
145
+ }
146
+ });
147
+
101
148
  test("bundled swarm-sdk.d.ts exposes only allowlisted methods", async () => {
102
149
  const types = await Bun.file("src/scripts-runtime/types/swarm-sdk.d.ts").text();
103
150
  for (const name of SDK_ALLOWLIST) {
@@ -79,6 +79,12 @@ afterAll(async () => {
79
79
  describe("send-task: requestedByUserId inheritance", () => {
80
80
  const server = new McpServer({ name: "test-send-task", version: "1.0.0" });
81
81
  registerSendTaskTool(server);
82
+ const requesterWasInherited = (taskId: string): number | undefined =>
83
+ getDb()
84
+ .query<{ inherited: number }, [string]>(
85
+ "SELECT requestedByUserIdInherited AS inherited FROM agent_tasks WHERE id = ?",
86
+ )
87
+ .get(taskId)?.inherited;
82
88
 
83
89
  test("child pool task inherits requestedByUserId from caller's sourceTaskId", async () => {
84
90
  // Parent task has no agentId so the auto-route won't force a lead assignment.
@@ -98,6 +104,7 @@ describe("send-task: requestedByUserId inheritance", () => {
98
104
  expect(s.task).toBeDefined();
99
105
  const created = getTaskById(s.task!.id);
100
106
  expect(created?.requestedByUserId).toBe(userAId);
107
+ expect(requesterWasInherited(s.task!.id)).toBe(1);
101
108
  });
102
109
 
103
110
  test("explicit requestedByUserId in args wins over inherited value", async () => {
@@ -116,6 +123,7 @@ describe("send-task: requestedByUserId inheritance", () => {
116
123
  expect(s.success).toBe(true);
117
124
  const created = getTaskById(s.task!.id);
118
125
  expect(created?.requestedByUserId).toBe(userBId);
126
+ expect(requesterWasInherited(s.task!.id)).toBe(0);
119
127
  });
120
128
 
121
129
  test("no crash when caller has no sourceTaskId and no requestedByUserId arg", async () => {
@@ -152,6 +160,7 @@ describe("send-task: requestedByUserId inheritance", () => {
152
160
  expect(s.success).toBe(true);
153
161
  const created = getTaskById(s.task!.id);
154
162
  expect(created?.requestedByUserId).toBe(userAId);
163
+ expect(requesterWasInherited(s.task!.id)).toBe(1);
155
164
  });
156
165
 
157
166
  test("skips creating a child when source task already owns a Linear tracker contextKey", async () => {