@desplega.ai/agent-swarm 2.6.0 → 2.7.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 (213) hide show
  1. package/README.md +2 -0
  2. package/dist/{acp-adapter-dgfakfpw.js → acp-adapter-ma1js5n7.js} +6 -6
  3. package/dist/{actions-fbqma8aa.js → actions-1vhmtvg9.js} +18 -18
  4. package/dist/{amp-adapter-en0jy6hs.js → amp-adapter-sxqqvmdk.js} +3 -3
  5. package/dist/{app-k8a5cgpp.js → app-mwx1d5a9.js} +3 -3
  6. package/dist/{assistant-a16433gr.js → assistant-yx1p6g82.js} +20 -20
  7. package/dist/{boot-reembed-f4nn561n.js → boot-reembed-5h37a2c2.js} +1 -1
  8. package/dist/{boot-reembed-6c1sjfnr.js → boot-reembed-7h78vmvv.js} +2 -1
  9. package/dist/{boot-scrub-sweep-d4w9gajf.js → boot-scrub-sweep-8mawd4gw.js} +3 -3
  10. package/dist/{boot-seeds-9bmkspyw.js → boot-seeds-ca84dhnn.js} +4 -4
  11. package/dist/{claude-adapter-nys1kjxa.js → claude-adapter-wqt4hfkq.js} +7 -7
  12. package/dist/{claude-managed-adapter-55207973.js → claude-managed-adapter-6fjqtr2z.js} +2 -2
  13. package/dist/{claude-managed-setup-3cm35gey.js → claude-managed-setup-rqv98sea.js} +2 -2
  14. package/dist/{claude-sdk-session-nw5yd8cp.js → claude-sdk-session-f29f3b2x.js} +7 -7
  15. package/dist/{cli-sxgtf5fw.js → cli-07q03xm2.js} +1 -1
  16. package/dist/{cli-k21gx5ht.js → cli-1hhbzkbb.js} +6 -6
  17. package/dist/{cli-v49fkghp.js → cli-1m9x8dr5.js} +1 -1
  18. package/dist/{cli-24j1jeeb.js → cli-2pvzmas1.js} +3 -3
  19. package/dist/{cli-w2656ka1.js → cli-2y7v133v.js} +7 -7
  20. package/dist/{cli-g8ka5j4j.js → cli-48gxjmcz.js} +94 -36
  21. package/dist/{cli-2sjhpsns.js → cli-54sj64fc.js} +1 -1
  22. package/dist/{cli-aj8djyqt.js → cli-60sty27p.js} +2 -2
  23. package/dist/{cli-4hxrf0p4.js → cli-6a9qfa8b.js} +308 -47
  24. package/dist/{cli-n7asmce6.js → cli-6mgdba42.js} +3 -3
  25. package/dist/{cli-gf95q3bm.js → cli-6srv653c.js} +3 -3
  26. package/dist/{cli-9abdgn5t.js → cli-86ehn4s5.js} +3 -3
  27. package/dist/{cli-bqztfyve.js → cli-8mbc9za9.js} +1 -1
  28. package/dist/{cli-a2rr0fmg.js → cli-8wmj18cz.js} +1 -1
  29. package/dist/{cli-c2habvsf.js → cli-9gsm2rzp.js} +8 -8
  30. package/dist/{cli-4rf0f5vg.js → cli-9pshx6rd.js} +4 -2
  31. package/dist/{cli-abkm71vr.js → cli-a6ds2epe.js} +1 -1
  32. package/dist/{cli-esakngh8.js → cli-a89zdqz9.js} +1 -1
  33. package/dist/{cli-3ndmpxxv.js → cli-ay40dnj3.js} +2 -2
  34. package/dist/{cli-kf4jree9.js → cli-aypd09nf.js} +1 -1
  35. package/dist/{cli-6y51ryva.js → cli-b3yzyc57.js} +1 -1
  36. package/dist/{cli-ag2yrnt5.js → cli-c668vvcf.js} +2 -2
  37. package/dist/{cli-5sckpqcx.js → cli-d20qvpr2.js} +1 -1
  38. package/dist/{cli-a34pr3w1.js → cli-e1595bgx.js} +2 -2
  39. package/dist/{cli-m70bajzr.js → cli-f12mr20z.js} +1 -1
  40. package/dist/{cli-dqygeeh5.js → cli-f1e0ptjj.js} +10 -1
  41. package/dist/{cli-mvjz5an3.js → cli-f4dbh7a5.js} +7 -7
  42. package/dist/{cli-az25fn5f.js → cli-gsfk58nw.js} +2 -2
  43. package/dist/{cli-1tem6ns9.js → cli-gt4h8cxw.js} +3 -2
  44. package/dist/{cli-cz37jqss.js → cli-h5jeg46p.js} +2 -2
  45. package/dist/{cli-emhjcj2y.js → cli-he9gbdx0.js} +60 -10
  46. package/dist/{cli-3fwqy71j.js → cli-j0ppywdr.js} +38 -31
  47. package/dist/{cli-5rj83bam.js → cli-j3y0x2n4.js} +34 -33
  48. package/dist/{cli-3gfvmzht.js → cli-j9bpmykh.js} +73 -25
  49. package/dist/{cli-m5a9m0ak.js → cli-kv7sbc2c.js} +3 -3
  50. package/dist/{cli-g20jjwt4.js → cli-m30k9777.js} +12 -12
  51. package/dist/{cli-683y0b3a.js → cli-m5e802r7.js} +2 -2
  52. package/dist/{cli-nn6vx2mq.js → cli-ptfq202x.js} +5 -3
  53. package/dist/{cli-d2dpf95n.js → cli-qj442fws.js} +1 -1
  54. package/dist/{cli-03gg9pd1.js → cli-rd5m70gj.js} +4 -4
  55. package/dist/{cli-gnsb1zxa.js → cli-s09j9vp5.js} +1 -1
  56. package/dist/{cli-zqne1qcj.js → cli-s0mgx214.js} +2 -2
  57. package/dist/{cli-sqma6vw1.js → cli-sgqm2582.js} +5 -5
  58. package/dist/{cli-nq677csy.js → cli-sjctjr48.js} +1 -1
  59. package/dist/{cli-3aqd4jaa.js → cli-twctnkng.js} +3 -3
  60. package/dist/{cli-j6rktc91.js → cli-v6shg7na.js} +1 -1
  61. package/dist/{cli-7enjwya9.js → cli-vz78s5wm.js} +1 -1
  62. package/dist/{cli-art7c1zh.js → cli-wqr7cea4.js} +4 -4
  63. package/dist/{cli-hfeb68z4.js → cli-x37r7qwf.js} +11 -3
  64. package/dist/{cli-ffvsj2qw.js → cli-yd2nreqw.js} +2 -2
  65. package/dist/{cli-ca6banwz.js → cli-ymjpkxgn.js} +6 -6
  66. package/dist/{cli-mtnj4fwa.js → cli-yxbsmx56.js} +5 -5
  67. package/dist/cli.js +18 -16
  68. package/dist/{codex-adapter-d95q4qsn.js → codex-adapter-zk59x2zh.js} +5 -5
  69. package/dist/{codex-hook-brpnycn5.js → codex-hook-xyjhvxwd.js} +1 -1
  70. package/dist/{codex-session-runner-gdwd8h0c.js → codex-session-runner-kh6s4j3n.js} +5 -5
  71. package/dist/{commands-jqmgk54b.js → commands-68et1qv2.js} +2 -2
  72. package/dist/{controller-z3jq589f.js → controller-fyrbnzg4.js} +2 -2
  73. package/dist/{controllers-connect-r2za39ef.js → controllers-connect-nep9h2xv.js} +19 -19
  74. package/dist/{cursor-adapter-4mr4jgkw.js → cursor-adapter-yss3pg7t.js} +1 -1
  75. package/dist/{db-w36wwb5g.js → db-28gjyvxj.js} +2 -2
  76. package/dist/{db-ppv2tze7.js → db-8ftwvzba.js} +3 -3
  77. package/dist/{dispatcher-pv5h77vf.js → dispatcher-hayexmkr.js} +17 -17
  78. package/dist/{dsh-adapter-d4pdpb3r.js → dsh-adapter-46wawff9.js} +1 -1
  79. package/dist/{easter-egg-3d-4sp4dvmc.js → easter-egg-3d-8d1e0h4m.js} +1 -1
  80. package/dist/{execute-jev839y1.js → execute-cjrfxfnm.js} +1 -1
  81. package/dist/{external-channel-y6nkzhnn.js → external-channel-xbmxcg09.js} +2 -2
  82. package/dist/{grok-adapter-t08b9ntm.js → grok-adapter-ag4wks2a.js} +7 -7
  83. package/dist/handlers-tzmevqb5.js +41 -0
  84. package/dist/{heartbeat-vn31xwdk.js → heartbeat-psfvcgwp.js} +20 -20
  85. package/dist/{hook-6d0zbvvt.js → hook-8pz04697.js} +5 -5
  86. package/dist/{hook-cpjs8q51.js → hook-cyq4mryn.js} +6 -6
  87. package/dist/{http-9c6medda.js → http-haj1nd6k.js} +109 -114
  88. package/dist/{index-qhjf2htm.js → index-jwt2bj6f.js} +261 -96
  89. package/dist/{index-g6k9tctj.js → index-q52f47pe.js} +1 -1
  90. package/dist/{index-pkma1yf8.js → index-syvhnvtw.js} +1 -1
  91. package/dist/{index-yqgbfgaj.js → index-y1ajx04a.js} +20 -20
  92. package/dist/{index-k0yt1tvq.js → index-z5h22pes.js} +17 -17
  93. package/dist/{index-38t35h0n.js → index-zyq8ct52.js} +17 -17
  94. package/dist/{keepalive-em0117bp.js → keepalive-xdvsamvj.js} +3 -3
  95. package/dist/{lead-23e7z59t.js → lead-hqg57kdp.js} +35 -35
  96. package/dist/{maintenance-kbme3qy9.js → maintenance-4y2xzh3a.js} +1 -1
  97. package/dist/{oauth-refresh-sweep-xysj4yse.js → oauth-refresh-sweep-jd4r7zbs.js} +8 -3
  98. package/dist/{onboard-p6q0vz97.js → onboard-18447q9q.js} +5 -5
  99. package/dist/{opencode-adapter-esv3k9yn.js → opencode-adapter-26b2frdf.js} +5 -5
  100. package/dist/{otel-impl-54qhqh1z.js → otel-impl-f9tgr8yw.js} +1 -1
  101. package/dist/{pi-mono-adapter-mymbwhj0.js → pi-mono-adapter-qn6qbga1.js} +33 -12
  102. package/dist/{pricing-refresh-yb75wv33.js → pricing-refresh-dw2tgb53.js} +3 -3
  103. package/dist/{rbac-roles-1vp2mw4p.js → rbac-roles-aygm2760.js} +2 -2
  104. package/dist/{rbac-roles-dexhc9x8.js → rbac-roles-s4nwj2m3.js} +1 -1
  105. package/dist/{render-v2-tjay5hhf.js → render-v2-yfvdfr0x.js} +3 -3
  106. package/dist/{resume-8ayf37g5.js → resume-1bmvjbf3.js} +7 -7
  107. package/dist/{runtime-hqe5cqcd.js → runtime-f7tpzedy.js} +1 -1
  108. package/dist/{session-access-fdhp987s.js → session-access-r5garej6.js} +4 -4
  109. package/dist/{session-source-backfill-qgtc86z1.js → session-source-backfill-424wzv0r.js} +2 -2
  110. package/dist/{setup-4rrmqcq3.js → setup-8tygvxkq.js} +1 -1
  111. package/dist/{virtual-modules-52km00d6.js → virtual-modules-xsw323b3.js} +2 -2
  112. package/dist/{work-objects-p28fgd70.js → work-objects-ggnghpgf.js} +20 -20
  113. package/dist/{worker-e3w7vye1.js → worker-sqjd1nqf.js} +35 -35
  114. package/openapi.json +69 -1
  115. package/package.json +5 -3
  116. package/src/be/automation-preflight.ts +2 -0
  117. package/src/be/db/api-keys.ts +9 -0
  118. package/src/be/db/workflow-runs.ts +36 -5
  119. package/src/be/db.ts +80 -22
  120. package/src/be/memory/task-learning.ts +155 -0
  121. package/src/be/migrations/245_script_executor.sql +3 -0
  122. package/src/be/oauth-refresh-sweep.ts +2 -1
  123. package/src/be/scripts/boot-reembed.ts +2 -0
  124. package/src/be/scripts/db.ts +14 -4
  125. package/src/be/scripts/embeddings.ts +2 -0
  126. package/src/be/scripts/run-saved.ts +1 -0
  127. package/src/be/scripts/typecheck.ts +9 -3
  128. package/src/be/seed-skills/index.ts +7 -0
  129. package/src/commands/onboard/compose-generator.ts +5 -3
  130. package/src/commands/provider-credentials.ts +2 -1
  131. package/src/heartbeat/heartbeat.ts +14 -1
  132. package/src/heartbeat/script-runner-probe.ts +47 -0
  133. package/src/http/api-keys.ts +24 -9
  134. package/src/http/idempotency-key.ts +1 -1
  135. package/src/http/index.ts +3 -3
  136. package/src/http/pages-public.ts +38 -5
  137. package/src/http/scripts.ts +27 -0
  138. package/src/http/workflows.ts +10 -0
  139. package/src/http/x.ts +5 -0
  140. package/src/prompts/base-prompt.ts +21 -1
  141. package/src/prompts/session-templates.ts +17 -3
  142. package/src/providers/amp-adapter.ts +1 -1
  143. package/src/providers/dsh-adapter.ts +1 -1
  144. package/src/providers/pi-mono-adapter.ts +41 -11
  145. package/src/scheduler/scheduler.ts +2 -1
  146. package/src/scripts-runtime/eval-harness.ts +8 -1
  147. package/src/scripts-runtime/executors/native.ts +22 -16
  148. package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
  149. package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
  150. package/src/scripts-runtime/executors/quickjs.ts +7 -0
  151. package/src/scripts-runtime/executors/registry.ts +32 -0
  152. package/src/scripts-runtime/executors/types.ts +7 -0
  153. package/src/scripts-runtime/host-validated-args.ts +133 -0
  154. package/src/scripts-runtime/loader.ts +30 -3
  155. package/src/scripts-runtime/swarm-sdk.ts +9 -2
  156. package/src/scripts-runtime/types/stdlib.d.ts +9 -1
  157. package/src/scripts-runtime/types/swarm-sdk.d.ts +9 -1
  158. package/src/tests/api-key-status-by-suffix.test.ts +242 -0
  159. package/src/tests/base-prompt.test.ts +70 -2
  160. package/src/tests/community-linear-delivery-loop.test.ts +2099 -0
  161. package/src/tests/credential-check.test.ts +4 -2
  162. package/src/tests/docs-index.test.ts +135 -0
  163. package/src/tests/dsh-adapter.test.ts +1 -1
  164. package/src/tests/fetch-resolved-env.test.ts +16 -2
  165. package/src/tests/heartbeat-checklist.test.ts +89 -0
  166. package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
  167. package/src/tests/oauth-refresh-sweep.test.ts +34 -1
  168. package/src/tests/onboard-compose.test.ts +20 -0
  169. package/src/tests/opencode-adapter.test.ts +2 -1
  170. package/src/tests/pages-frame-ancestors.test.ts +162 -0
  171. package/src/tests/pi-codemode.test.ts +2 -1
  172. package/src/tests/pi-mono-adapter.test.ts +54 -0
  173. package/src/tests/pi-native-mcp.test.ts +13 -1
  174. package/src/tests/pi-tool-exposure.test.ts +8 -8
  175. package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
  176. package/src/tests/prompt-template-session.test.ts +4 -3
  177. package/src/tests/request-composition-derive.test.ts +329 -0
  178. package/src/tests/script-connections.test.ts +2 -0
  179. package/src/tests/script-executor-conformance.test.ts +36 -1
  180. package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
  181. package/src/tests/script-executor-quickjs.test.ts +140 -0
  182. package/src/tests/script-host-validated-args-native.test.ts +196 -0
  183. package/src/tests/script-per-script-executor.test.ts +415 -0
  184. package/src/tests/scripts-external-api.test.ts +45 -0
  185. package/src/tests/sdk-allowlist.test.ts +3 -2
  186. package/src/tests/self-improvement.test.ts +4 -4
  187. package/src/tests/store-progress-learning.test.ts +288 -0
  188. package/src/tests/ui-logs-parser.test.ts +180 -0
  189. package/src/tests/workflow-engine-v2.test.ts +116 -1
  190. package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
  191. package/src/tests/workflow-run-finished-event.test.ts +263 -0
  192. package/src/tests/workflow-swarm-script.test.ts +34 -0
  193. package/src/tools/script-common.ts +1 -0
  194. package/src/tools/script-run.ts +7 -0
  195. package/src/tools/script-upsert.ts +7 -0
  196. package/src/tools/store-progress.ts +67 -1
  197. package/src/tools/workflows/trigger-workflow.ts +104 -10
  198. package/src/types.ts +12 -1
  199. package/src/utils/credentials.ts +5 -2
  200. package/src/workflows/engine.ts +18 -16
  201. package/src/workflows/executors/swarm-script.ts +5 -0
  202. package/src/workflows/foreach-driver.ts +5 -11
  203. package/src/workflows/node-timeout.ts +30 -0
  204. package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
  205. package/templates/community/linear-delivery-loop/README.md +38 -0
  206. package/templates/community/linear-delivery-loop/config.example.json +31 -0
  207. package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
  208. package/templates/community/linear-delivery-loop/schedule.json +10 -0
  209. package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
  210. package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
  211. package/templates/skills/swarm-docs/config.json +14 -0
  212. package/templates/skills/swarm-docs/content.md +144 -0
  213. package/dist/handlers-eqsnmv95.js +0 -41
@@ -104,3 +104,53 @@ describe("QuickJS pool configuration", () => {
104
104
  );
105
105
  }
106
106
  });
107
+
108
+ // A fresh pool spawns its first worker, so the run waits on the worker's boot
109
+ // (`slot.ready`) when the abort lands. The server counts the script's fetch.
110
+ const abortDuringBootProbe = `
111
+ import { QuickJSScriptExecutor } from "./src/scripts-runtime/executors/quickjs";
112
+ import { DEFAULT_SCRIPT_RESOURCES } from "./src/scripts-runtime/executors/types";
113
+ let hits = 0;
114
+ const server = Bun.serve({ port: 0, fetch() { hits++; return new Response("ran"); } });
115
+ try {
116
+ const controller = new AbortController();
117
+ const run = new QuickJSScriptExecutor().run({
118
+ source: 'export default async (args) => (await fetch(args.url)).text();',
119
+ args: { url: "http://127.0.0.1:" + server.port },
120
+ configPayload: { system: {
121
+ apiKey: { value: "pool-test", isSecret: true },
122
+ agentId: { value: crypto.randomUUID(), isSecret: false },
123
+ mcpBaseUrl: { value: "http://127.0.0.1:" + server.port, isSecret: false },
124
+ }, user: {} },
125
+ resources: DEFAULT_SCRIPT_RESOURCES,
126
+ fsMode: "none", network: "open", signal: controller.signal,
127
+ });
128
+ // One macrotask: the run has its slot and awaits the worker's boot.
129
+ await Bun.sleep(0);
130
+ controller.abort();
131
+ const output = await run;
132
+ await Bun.sleep(200);
133
+ console.log(JSON.stringify({ error: output.error ?? null, result: output.result ?? null, hits }));
134
+ } finally {
135
+ server.stop(true);
136
+ }
137
+ `;
138
+
139
+ describe("QuickJS abort while the worker boots", () => {
140
+ test(
141
+ "an abort before the worker is ready kills the run and the script never runs",
142
+ async () => {
143
+ const result = expectChildOk(
144
+ await runChild([process.execPath, "--eval", abortDuringBootProbe], { env: process.env }),
145
+ "QuickJS abort-during-boot probe",
146
+ );
147
+ const out = JSON.parse(result.stdout.trim()) as {
148
+ error: string | null;
149
+ result: string | null;
150
+ hits: number;
151
+ };
152
+ expect(out).toEqual({ error: "killed", result: null, hits: 0 });
153
+ },
154
+ CHILD_PROCESS_TEST_BUDGET_MS,
155
+ );
156
+ });
@@ -640,3 +640,143 @@ describe("quickjs egress policy", () => {
640
640
  expect(JSON.stringify(output)).not.toContain("quickjs-test-secret");
641
641
  });
642
642
  });
643
+
644
+ describe("quickjs hostValidatedArgs (#2011)", () => {
645
+ const countedStructuralScript = `
646
+ import { z } from "zod";
647
+ export const argsSchema = z.object({
648
+ value: z.number(),
649
+ label: z.string().optional(),
650
+ });
651
+ let parseCalls = 0;
652
+ const origParse = argsSchema.parse.bind(argsSchema);
653
+ argsSchema.parse = ((v: unknown) => {
654
+ parseCalls++;
655
+ return origParse(v);
656
+ }) as typeof argsSchema.parse;
657
+
658
+ export default async function (args: z.infer<typeof argsSchema>) {
659
+ return { doubled: args.value * 2, label: args.label ?? null, parseCalls };
660
+ }
661
+ `;
662
+
663
+ test("skips duplicate argsSchema.parse when hostValidatedArgs is true and schema is structural", async () => {
664
+ const out = await executor.run(
665
+ input(countedStructuralScript, {
666
+ args: { value: 21, label: "ok" },
667
+ hostValidatedArgs: true,
668
+ }),
669
+ );
670
+ expect(out.exitCode).toBe(0);
671
+ expect(out.error).toBeUndefined();
672
+ expect(out.result).toEqual({ doubled: 42, label: "ok", parseCalls: 0 });
673
+ });
674
+
675
+ test("still runs argsSchema.parse when hostValidatedArgs is omitted or false", async () => {
676
+ const out = await executor.run(input(countedStructuralScript, { args: { value: 21 } }));
677
+ expect(out.exitCode).toBe(0);
678
+ expect(out.result).toEqual({ doubled: 42, label: null, parseCalls: 1 });
679
+ });
680
+
681
+ test("still runs argsSchema.parse when args contain extra keys that Zod strips", async () => {
682
+ const out = await executor.run(
683
+ input(countedStructuralScript, {
684
+ args: { value: 21, extra: "stripped-by-zod" },
685
+ hostValidatedArgs: true,
686
+ }),
687
+ );
688
+ expect(out.exitCode).toBe(0);
689
+ expect(out.result).toEqual({ doubled: 42, label: null, parseCalls: 1 });
690
+ });
691
+
692
+ test("still runs argsSchema.parse when schema uses defaults, transforms, or refinements", async () => {
693
+ const scriptWithDefaultsAndTransforms = `
694
+ import { z } from "zod";
695
+ export const argsSchema = z.object({
696
+ value: z.number().default(10),
697
+ tag: z.string().transform((s) => s.toUpperCase()),
698
+ });
699
+ let parseCalls = 0;
700
+ const origParse = argsSchema.parse.bind(argsSchema);
701
+ argsSchema.parse = ((v: unknown) => {
702
+ parseCalls++;
703
+ return origParse(v);
704
+ }) as typeof argsSchema.parse;
705
+
706
+ export default async function (args: z.infer<typeof argsSchema>) {
707
+ return { value: args.value, tag: args.tag, parseCalls };
708
+ }
709
+ `;
710
+ const out = await executor.run(
711
+ input(scriptWithDefaultsAndTransforms, {
712
+ args: { tag: "hello" },
713
+ hostValidatedArgs: true,
714
+ }),
715
+ );
716
+ expect(out.exitCode).toBe(0);
717
+ expect(out.result).toEqual({ value: 10, tag: "HELLO", parseCalls: 1 });
718
+ });
719
+
720
+ test("caller-supplied args payload cannot spoof the trusted host validation marker", async () => {
721
+ const out = await executor.run(
722
+ input(countedStructuralScript, {
723
+ args: {
724
+ value: 7,
725
+ hostValidatedArgs: true,
726
+ __swarm_host_validated_args: true,
727
+ },
728
+ }),
729
+ );
730
+ expect(out.exitCode).toBe(0);
731
+ expect(out.result).toEqual({ doubled: 14, label: null, parseCalls: 1 });
732
+ });
733
+
734
+ test("retains Zod property semantics on inherited Object.prototype keys (constructor, toString) and reverse numeric enum keys", async () => {
735
+ for (const propName of ["constructor", "toString"] as const) {
736
+ const protoScript = `
737
+ import { z } from "zod";
738
+ export const argsSchema = z.object({
739
+ ${propName}: z.string().nullable().optional(),
740
+ });
741
+ export default async function (args: z.infer<typeof argsSchema>) {
742
+ return { val: args.${propName} ?? null };
743
+ }
744
+ `;
745
+ const out = await executor.run(
746
+ input(protoScript, {
747
+ args: {},
748
+ hostValidatedArgs: true,
749
+ }),
750
+ );
751
+ expect(out.exitCode).toBe(1);
752
+ expect(out.stderr).toContain("argsSchema validation failed");
753
+ }
754
+
755
+ const numericEnumScript = `
756
+ import { z } from "zod";
757
+ export const argsSchema = z.object({
758
+ mode: z.enum({ 0: "Read", Read: 0 }).nullable(),
759
+ });
760
+ export default async function (args: z.infer<typeof argsSchema>) {
761
+ return { mode: args.mode };
762
+ }
763
+ `;
764
+ const rejected = await executor.run(
765
+ input(numericEnumScript, {
766
+ args: { mode: "Read" },
767
+ hostValidatedArgs: true,
768
+ }),
769
+ );
770
+ expect(rejected.exitCode).toBe(1);
771
+ expect(rejected.stderr).toContain("argsSchema validation failed");
772
+
773
+ const accepted = await executor.run(
774
+ input(numericEnumScript, {
775
+ args: { mode: 0 },
776
+ hostValidatedArgs: true,
777
+ }),
778
+ );
779
+ expect(accepted.exitCode).toBe(0);
780
+ expect(accepted.result).toEqual({ mode: 0 });
781
+ });
782
+ });
@@ -0,0 +1,196 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import os from "node:os";
3
+ import { z } from "zod";
4
+ import * as zm from "zod/mini";
5
+ import { NativeScriptExecutor } from "../scripts-runtime/executors/native";
6
+ import { DEFAULT_SCRIPT_RESOURCES, type ExecutorInput } from "../scripts-runtime/executors/types";
7
+ import {
8
+ HOST_VALIDATED_ARGS,
9
+ isHostValidationEquivalent,
10
+ } from "../scripts-runtime/host-validated-args";
11
+
12
+ process.env.TMPDIR ??= os.tmpdir().replaceAll("\\", "/");
13
+
14
+ function makeInput(
15
+ source: string,
16
+ args: unknown,
17
+ opts: { hostValidatedArgs?: boolean } = {},
18
+ ): ExecutorInput {
19
+ return {
20
+ source,
21
+ args,
22
+ ...(opts.hostValidatedArgs !== undefined ? { hostValidatedArgs: opts.hostValidatedArgs } : {}),
23
+ fsMode: "none",
24
+ network: "open",
25
+ resources: DEFAULT_SCRIPT_RESOURCES,
26
+ configPayload: {
27
+ system: {
28
+ apiKey: { value: "test-key", isSecret: true },
29
+ agentId: { value: "00000000-0000-0000-0000-000000000001", isSecret: false },
30
+ mcpBaseUrl: { value: "http://127.0.0.1:19999", isSecret: false },
31
+ },
32
+ user: {},
33
+ },
34
+ };
35
+ }
36
+
37
+ describe("isHostValidationEquivalent", () => {
38
+ test("accepts pure structural zod and zod/mini schemas with matching values", () => {
39
+ const schema = z.object({
40
+ name: z.string(),
41
+ count: z.number(),
42
+ active: z.boolean(),
43
+ tags: z.array(z.string()),
44
+ mode: z.enum(["fast", "slow"]),
45
+ version: z.literal(1),
46
+ note: z.string().optional(),
47
+ parent: z.string().nullable(),
48
+ });
49
+
50
+ expect(
51
+ isHostValidationEquivalent(schema, {
52
+ name: "demo",
53
+ count: 3,
54
+ active: true,
55
+ tags: ["a", "b"],
56
+ mode: "fast",
57
+ version: 1,
58
+ parent: null,
59
+ }),
60
+ ).toBe(true);
61
+
62
+ const miniSchema = zm.object({
63
+ id: zm.string(),
64
+ limit: zm.optional(zm.number()),
65
+ });
66
+ expect(isHostValidationEquivalent(miniSchema, { id: "abc" })).toBe(true);
67
+ expect(isHostValidationEquivalent(miniSchema, { id: "abc", limit: 10 })).toBe(true);
68
+ });
69
+
70
+ test("rejects schemas with defaults, transforms, refinements, coercions, checks, or unknown keys", () => {
71
+ expect(isHostValidationEquivalent(z.object({ x: z.string().default("hi") }), {})).toBe(false);
72
+ expect(
73
+ isHostValidationEquivalent(z.object({ x: z.string().default("hi").optional() }), {}),
74
+ ).toBe(false);
75
+ expect(
76
+ isHostValidationEquivalent(z.object({ x: z.string().transform((s) => s.trim()) }), {
77
+ x: " hi ",
78
+ }),
79
+ ).toBe(false);
80
+ expect(
81
+ isHostValidationEquivalent(z.object({ x: z.string().refine((s) => s.length > 0) }), {
82
+ x: "hi",
83
+ }),
84
+ ).toBe(false);
85
+ expect(isHostValidationEquivalent(z.object({ x: z.coerce.number() }), { x: 42 })).toBe(false);
86
+ expect(isHostValidationEquivalent(z.object({ x: z.string().min(1) }), { x: "hi" })).toBe(false);
87
+ expect(isHostValidationEquivalent(z.object({ x: z.string().email() }), { x: "a@b.co" })).toBe(
88
+ false,
89
+ );
90
+ expect(isHostValidationEquivalent(z.object({ x: z.number().int() }), { x: 5 })).toBe(false);
91
+ // Extra unknown keys require .parse() so z.object strips them (or z.strictObject rejects them)
92
+ expect(isHostValidationEquivalent(z.object({ x: z.string() }), { x: "hi", extra: 1 })).toBe(
93
+ false,
94
+ );
95
+ // Reverse numeric enum keys (e.g. { 0: "Read", Read: 0 }) must not pass as valid values
96
+ const numericEnumSchema = z.enum({ 0: "Read", Read: 0 }).nullable();
97
+ expect(isHostValidationEquivalent(numericEnumSchema, 0)).toBe(true);
98
+ expect(isHostValidationEquivalent(numericEnumSchema, null)).toBe(true);
99
+ expect(isHostValidationEquivalent(numericEnumSchema, "Read")).toBe(false);
100
+ // Inherited Object.prototype properties (constructor, toString) on `{}` must fall back to .parse()
101
+ expect(
102
+ isHostValidationEquivalent(z.object({ constructor: z.string().nullable().optional() }), {}),
103
+ ).toBe(false);
104
+ expect(
105
+ isHostValidationEquivalent(z.object({ toString: z.string().nullable().optional() }), {}),
106
+ ).toBe(false);
107
+ });
108
+ });
109
+
110
+ describe("NativeScriptExecutor hostValidatedArgs (#2010)", () => {
111
+ const executor = new NativeScriptExecutor();
112
+
113
+ const countedStructuralScript = `
114
+ import { z } from "zod";
115
+ export const argsSchema = z.object({
116
+ value: z.number(),
117
+ label: z.string().optional(),
118
+ });
119
+ let parseCalls = 0;
120
+ const origParse = argsSchema.parse.bind(argsSchema);
121
+ argsSchema.parse = ((v: unknown) => {
122
+ parseCalls++;
123
+ return origParse(v);
124
+ }) as typeof argsSchema.parse;
125
+
126
+ export default async function (args: z.infer<typeof argsSchema>) {
127
+ return { doubled: args.value * 2, label: args.label ?? null, parseCalls };
128
+ }
129
+ `;
130
+
131
+ test("skips duplicate argsSchema.parse when hostValidatedArgs is true and schema is structural", async () => {
132
+ const out = await executor.run(
133
+ makeInput(countedStructuralScript, { value: 21, label: "ok" }, { hostValidatedArgs: true }),
134
+ );
135
+ expect(out.exitCode).toBe(0);
136
+ expect(out.error).toBeUndefined();
137
+ expect(out.result).toEqual({ doubled: 42, label: "ok", parseCalls: 0 });
138
+ });
139
+
140
+ test("still runs argsSchema.parse when hostValidatedArgs is omitted or false", async () => {
141
+ const out = await executor.run(makeInput(countedStructuralScript, { value: 21 }));
142
+ expect(out.exitCode).toBe(0);
143
+ expect(out.result).toEqual({ doubled: 42, label: null, parseCalls: 1 });
144
+ });
145
+
146
+ test("still runs argsSchema.parse when args contain extra keys that Zod strips", async () => {
147
+ const out = await executor.run(
148
+ makeInput(
149
+ countedStructuralScript,
150
+ { value: 21, extra: "stripped-by-zod" },
151
+ { hostValidatedArgs: true },
152
+ ),
153
+ );
154
+ expect(out.exitCode).toBe(0);
155
+ expect(out.result).toEqual({ doubled: 42, label: null, parseCalls: 1 });
156
+ });
157
+
158
+ test("still runs argsSchema.parse when schema uses defaults, transforms, or refinements", async () => {
159
+ const scriptWithDefaultsAndTransforms = `
160
+ import { z } from "zod";
161
+ export const argsSchema = z.object({
162
+ value: z.number().default(10),
163
+ tag: z.string().transform((s) => s.toUpperCase()),
164
+ });
165
+ let parseCalls = 0;
166
+ const origParse = argsSchema.parse.bind(argsSchema);
167
+ argsSchema.parse = ((v: unknown) => {
168
+ parseCalls++;
169
+ return origParse(v);
170
+ }) as typeof argsSchema.parse;
171
+
172
+ export default async function (args: z.infer<typeof argsSchema>) {
173
+ return { value: args.value, tag: args.tag, parseCalls };
174
+ }
175
+ `;
176
+ const out = await executor.run(
177
+ makeInput(scriptWithDefaultsAndTransforms, { tag: "hello" }, { hostValidatedArgs: true }),
178
+ );
179
+ expect(out.exitCode).toBe(0);
180
+ expect(out.result).toEqual({ value: 10, tag: "HELLO", parseCalls: 1 });
181
+ });
182
+
183
+ test("caller-supplied args payload cannot spoof the trusted host validation marker", async () => {
184
+ expect(Symbol.keyFor(HOST_VALIDATED_ARGS)).toBeUndefined();
185
+ expect(Symbol.for("swarm.hostValidatedArgs")).not.toBe(HOST_VALIDATED_ARGS);
186
+ const out = await executor.run(
187
+ makeInput(countedStructuralScript, {
188
+ value: 7,
189
+ hostValidatedArgs: true,
190
+ SWARM_SCRIPT_HOST_VALIDATED_ARGS: "1",
191
+ }),
192
+ );
193
+ expect(out.exitCode).toBe(0);
194
+ expect(out.result).toEqual({ doubled: 14, label: null, parseCalls: 1 });
195
+ });
196
+ });