@desplega.ai/agent-swarm 1.158.0 → 1.159.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 (272) hide show
  1. package/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-qhch0n2v.js} +1 -1
  2. package/dist/{actions-p9hz5yat.js → actions-dtsh1mfb.js} +33 -29
  3. package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-f2h06k64.js} +8 -3
  4. package/dist/{app-g26wdcz6.js → app-y1b9srva.js} +10 -9
  5. package/dist/{assistant-wydfqkb3.js → assistant-31hwy5xj.js} +34 -30
  6. package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-0k1mfax5.js} +12 -13
  7. package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-48d2qpkt.js} +11 -11
  8. package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-w0bryhkr.js} +12 -12
  9. package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-t65wbrn7.js} +9 -8
  10. package/dist/{boot-seeds-s3q01xj3.js → boot-seeds-h7c32hvm.js} +12 -11
  11. package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-1t6611ne.js} +26 -21
  12. package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-werp659j.js} +4 -2
  13. package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-y6rdgbc1.js} +11 -2
  14. package/dist/claude-sdk-session-zhkyk8yw.js +29319 -0
  15. package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
  16. package/dist/{cli-wscdvcqz.js → cli-0n2e4x49.js} +1 -1
  17. package/dist/cli-0xqde53b.js +176 -0
  18. package/dist/{cli-xberxa1r.js → cli-10ym38qc.js} +2 -2
  19. package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
  20. package/dist/{cli-rqz8qbw7.js → cli-20035trh.js} +50 -18
  21. package/dist/{cli-k0g1sjzm.js → cli-31nkw3g3.js} +3 -3
  22. package/dist/cli-3gvrr8vj.js +1422 -0
  23. package/dist/{cli-c07r9a2m.js → cli-48527fpw.js} +9 -8
  24. package/dist/{cli-24k806sc.js → cli-4aj25szq.js} +2 -2
  25. package/dist/{cli-7gtvvpbh.js → cli-4tvgz99m.js} +7 -7
  26. package/dist/cli-503ya656.js +455 -0
  27. package/dist/{cli-cw9zck8j.js → cli-5f2hq128.js} +8 -4
  28. package/dist/{cli-s8eet254.js → cli-68bmme5p.js} +11078 -27833
  29. package/dist/{cli-ftfvggzx.js → cli-79aq9cgb.js} +794 -563
  30. package/dist/cli-7cxw8b60.js +14910 -0
  31. package/dist/cli-7kyk9jr3.js +22 -0
  32. package/dist/cli-7zttzetj.js +324 -0
  33. package/dist/{cli-y95wp3tg.js → cli-96qd5psa.js} +1 -1
  34. package/dist/{cli-g8djp6qe.js → cli-9yg65c1j.js} +22 -0
  35. package/dist/{cli-3gmcwfyx.js → cli-ardpzhjm.js} +5 -5
  36. package/dist/{cli-7s3syk22.js → cli-b6thtzac.js} +5 -5
  37. package/dist/{cli-00w2c66p.js → cli-bgv70kym.js} +13 -2
  38. package/dist/{cli-j55n64b6.js → cli-bv0a38v9.js} +163 -44
  39. package/dist/cli-ca2w7zv7.js +115 -0
  40. package/dist/cli-cpvsaqsw.js +333 -0
  41. package/dist/cli-cygyqj3b.js +754 -0
  42. package/dist/{cli-x24pshtq.js → cli-dvjpkkjd.js} +6 -6
  43. package/dist/{cli-5d10vd2m.js → cli-e0ndxhtk.js} +4 -4
  44. package/dist/{cli-4198qc9v.js → cli-e4xnc0qp.js} +15 -3
  45. package/dist/{cli-rjehvrts.js → cli-eg7cdg68.js} +8 -8
  46. package/dist/cli-ejpygnqz.js +3815 -0
  47. package/dist/{cli-fgqxwrpv.js → cli-fjtp3463.js} +1631 -127
  48. package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
  49. package/dist/{cli-47m6dkd3.js → cli-g9df2m37.js} +409 -168
  50. package/dist/cli-gzyh0j0g.js +8455 -0
  51. package/dist/cli-hgqfttfc.js +183 -0
  52. package/dist/cli-hj65wsts.js +17755 -0
  53. package/dist/cli-j9z8mfx3.js +476 -0
  54. package/dist/{cli-aehm1rbq.js → cli-kc3dp84d.js} +1135 -61
  55. package/dist/{cli-h38x0qhg.js → cli-kdnehe0t.js} +4 -3
  56. package/dist/{cli-20cj37vt.js → cli-m4excttz.js} +136 -2216
  57. package/dist/{cli-8qmme6vd.js → cli-mg24hkt4.js} +2 -2
  58. package/dist/{cli-xhaeg2nc.js → cli-mzkqpxee.js} +1 -1
  59. package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
  60. package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
  61. package/dist/{cli-rj19hc34.js → cli-qjxy52w7.js} +1 -1
  62. package/dist/{cli-53sz8gkz.js → cli-qv4jv8bx.js} +1 -1
  63. package/dist/cli-qvnnx4ct.js +1051 -0
  64. package/dist/cli-rb9m6rpe.js +4980 -0
  65. package/dist/{cli-tt9k8r35.js → cli-rfezr056.js} +476 -909
  66. package/dist/{cli-ggm92wcn.js → cli-rgjprct3.js} +1 -0
  67. package/dist/{cli-9httdpyq.js → cli-rrqmqr1s.js} +1 -1
  68. package/dist/cli-rykfmgwg.js +102 -0
  69. package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
  70. package/dist/{cli-68w09sjw.js → cli-vn0qv9cj.js} +25 -88
  71. package/dist/{cli-987hg0aa.js → cli-vwbbaecp.js} +2 -2
  72. package/dist/cli-wd9hz40d.js +69 -0
  73. package/dist/{cli-j57qkrpq.js → cli-wgjx0q33.js} +13 -13
  74. package/dist/cli-xgpf00sk.js +2149 -0
  75. package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
  76. package/dist/{cli-aky8tctm.js → cli-z9x0sh1t.js} +3 -3
  77. package/dist/{cli-1hkrch3k.js → cli-zw65mab6.js} +5 -5
  78. package/dist/cli.js +38 -29
  79. package/dist/cloudflare-workers-ai-system-one-1sg586qv.js +46 -0
  80. package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-jqfqqa21.js} +21 -14
  81. package/dist/{codex-hook-9hptmdhj.js → codex-hook-5eys3dya.js} +2 -2
  82. package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-zd341dsm.js} +21 -14
  83. package/dist/{commands-e3bn0p75.js → commands-vfsysz4s.js} +9 -8
  84. package/dist/{db-03v9kzr2.js → db-s3znhvbn.js} +15 -10
  85. package/dist/{dispatcher-nrmg8yht.js → dispatcher-5xn0c7rf.js} +32 -28
  86. package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-894mgfvs.js} +3 -3
  87. package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
  88. package/dist/execute-71ysgv35.js +965 -0
  89. package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-1nfcdphp.js} +5 -4
  90. package/dist/{google-vertex-809af514.js → google-vertex-1tqcd7b1.js} +5 -4
  91. package/dist/{handlers-aqcedbyb.js → handlers-5w4jwycr.js} +35 -31
  92. package/dist/{hook-7vkskkre.js → hook-853bxrr6.js} +16 -13
  93. package/dist/{hook-ybvmrmhq.js → hook-z0hbxj85.js} +16 -13
  94. package/dist/{http-2eqa8t24.js → http-akc3wy3m.js} +600 -123
  95. package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
  96. package/dist/{index-a3vn96n8.js → index-5sz16ksh.js} +187 -47
  97. package/dist/{index-c6zsv1bd.js → index-bjmjh9h4.js} +11 -11
  98. package/dist/{index-chgdkq36.js → index-cd151qkq.js} +32 -28
  99. package/dist/{index-x71bvq0p.js → index-k661dzzr.js} +33 -29
  100. package/dist/{index-1yks3qcr.js → index-ttpbhnjg.js} +32 -28
  101. package/dist/{keepalive-vtkf5n99.js → keepalive-8p9rpvz5.js} +11 -10
  102. package/dist/{lead-h3sp357x.js → lead-jp69z5h3.js} +49 -45
  103. package/dist/llama-cpp-classify-t2tccams.js +335 -0
  104. package/dist/{maintenance-jzeqtk3q.js → maintenance-fwx4ev0k.js} +12 -12
  105. package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-bhvwqcnk.js} +14 -17
  106. package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-cwcq1ctx.js} +10 -9
  107. package/dist/{onboard-tmsp0qbz.js → onboard-2b4q0t84.js} +6 -5
  108. package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-nekthgks.js} +21 -8
  109. package/dist/{openai-completions-9hjrqbw8.js → openai-completions-hxtzwj0s.js} +8 -11
  110. package/dist/{openai-responses-7b1pn0xt.js → openai-responses-w4r118w7.js} +22 -17
  111. package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-zz7xvjj6.js} +6 -4
  112. package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-b6sqqjgn.js} +4 -6
  113. package/dist/{otel-impl-9mz73q65.js → otel-impl-g8rs4vwr.js} +1 -1
  114. package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
  115. package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-n0me28wt.js} +29 -17
  116. package/dist/pricing-refresh-0v77385v.js +39 -0
  117. package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-krjyazbj.js} +10 -9
  118. package/dist/{render-v2-gryrh9h8.js → render-v2-3wjsp9aw.js} +10 -9
  119. package/dist/runtime-ktjysacx.js +24 -0
  120. package/dist/{setup-q9720bt6.js → setup-yeqryke6.js} +6 -5
  121. package/dist/typesafe-system-one-96960jxp.js +27 -0
  122. package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-dzdeq1sf.js} +21 -13
  123. package/dist/{work-objects-b76x7deb.js → work-objects-3pakpkw0.js} +35 -31
  124. package/dist/{worker-6vrxbv41.js → worker-85testgj.js} +49 -45
  125. package/openapi.json +863 -14
  126. package/package.json +9 -8
  127. package/src/be/db/agents.ts +20 -0
  128. package/src/be/db/tasks/human-free.ts +442 -0
  129. package/src/be/db/tasks/read.ts +56 -1
  130. package/src/be/db/tasks/write.ts +107 -11
  131. package/src/be/db.ts +89 -165
  132. package/src/be/harness-model-support.ts +182 -0
  133. package/src/be/human-free-drain.ts +67 -0
  134. package/src/be/migrations/182_task_human_free_flag.sql +63 -0
  135. package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
  136. package/src/be/migrations/184_model_catalog.sql +120 -0
  137. package/src/be/model-catalog-store.ts +644 -0
  138. package/src/be/model-tier-keys.ts +31 -0
  139. package/src/be/model-tier-resolution.ts +489 -0
  140. package/src/be/model-validation.ts +112 -0
  141. package/src/be/models-catalog.ts +61 -7
  142. package/src/be/modelsdev-cache.json +82 -0
  143. package/src/be/modelsdev-cache.ts +2 -0
  144. package/src/be/pricing-refresh.ts +154 -12
  145. package/src/be/scripts/typecheck.ts +15 -0
  146. package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
  147. package/src/be/swarm-config-guard.ts +6 -0
  148. package/src/cli.tsx +10 -0
  149. package/src/commands/claude-managed-setup.ts +3 -1
  150. package/src/commands/runner.ts +76 -2
  151. package/src/extensions/contract-types.generated.ts +1 -1
  152. package/src/heartbeat/heartbeat.ts +64 -15
  153. package/src/heartbeat/templates.ts +2 -2
  154. package/src/http/agents.ts +49 -1
  155. package/src/http/index.ts +8 -0
  156. package/src/http/models-catalog.ts +357 -8
  157. package/src/http/poll.ts +45 -2
  158. package/src/http/schedules.ts +36 -6
  159. package/src/http/session-data.ts +2 -5
  160. package/src/http/stats.ts +6 -8
  161. package/src/http/tasks.ts +26 -1
  162. package/src/http/usage-cache.ts +56 -15
  163. package/src/http/workflows.ts +35 -8
  164. package/src/prompts/session-templates.ts +1 -0
  165. package/src/providers/claude-managed-models.ts +47 -161
  166. package/src/providers/codex-adapter.ts +4 -4
  167. package/src/providers/codex-models.ts +65 -198
  168. package/src/providers/reasoning-effort.ts +44 -140
  169. package/src/rbac/legacy-policy.ts +25 -0
  170. package/src/rbac/permissions.ts +10 -0
  171. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  172. package/src/scripts-runtime/types/stdlib.d.ts +15 -0
  173. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  174. package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
  175. package/src/server.ts +6 -0
  176. package/src/slack/message-text.ts +56 -2
  177. package/src/slack/thread-buffer.ts +10 -2
  178. package/src/tests/agents-list-model-display.test.ts +4 -6
  179. package/src/tests/base-prompt.test.ts +3 -0
  180. package/src/tests/claude-adapter.test.ts +10 -0
  181. package/src/tests/claude-managed-adapter.test.ts +12 -18
  182. package/src/tests/codex-adapter.test.ts +2 -1
  183. package/src/tests/codex-gpt6-release.test.ts +66 -5
  184. package/src/tests/context-window.test.ts +7 -5
  185. package/src/tests/db-extraction-contract.test.ts +1 -0
  186. package/src/tests/db-task-read-contract.test.ts +5 -1
  187. package/src/tests/db-task-write-contract.test.ts +4 -1
  188. package/src/tests/docs-index-footprint.test.js +3 -3
  189. package/src/tests/explicit-model-validation.test.ts +332 -0
  190. package/src/tests/harness-model-support.test.ts +374 -0
  191. package/src/tests/heartbeat-checklist-config.test.ts +71 -0
  192. package/src/tests/heartbeat-checklist.test.ts +4 -2
  193. package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
  194. package/src/tests/human-free-flag.test.ts +279 -0
  195. package/src/tests/human-free-oracle.ts +57 -0
  196. package/src/tests/human-free-reclassify.test.ts +669 -0
  197. package/src/tests/migration-046-budgets.test.ts +3 -3
  198. package/src/tests/model-catalog-migration.test.ts +134 -0
  199. package/src/tests/model-catalog-table.test.ts +249 -0
  200. package/src/tests/model-catalog-write-access.test.ts +387 -0
  201. package/src/tests/model-tier-resolution.test.ts +519 -0
  202. package/src/tests/models-catalog.test.ts +1 -1
  203. package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
  204. package/src/tests/package-publish.test.ts +137 -36
  205. package/src/tests/pricing-routes.test.ts +2 -2
  206. package/src/tests/rbac-engine.test.ts +42 -2
  207. package/src/tests/reasoning-effort.test.ts +109 -0
  208. package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
  209. package/src/tests/runtime-model-catalog.test.ts +86 -0
  210. package/src/tests/script-connections.test.ts +5 -0
  211. package/src/tests/seed-scripts.test.ts +89 -0
  212. package/src/tests/session-costs.test.ts +14 -11
  213. package/src/tests/slack-message-text.test.ts +101 -0
  214. package/src/tests/slack-thread-buffer-context.test.ts +93 -0
  215. package/src/tests/task-cascade-fail.test.ts +148 -2
  216. package/src/tests/tool-annotations.test.ts +1 -1
  217. package/src/tests/usage-cache.test.ts +166 -0
  218. package/src/tests/workflow-executors.test.ts +5 -2
  219. package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
  220. package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
  221. package/src/tests/workflow-system-one-decision.test.ts +2063 -0
  222. package/src/tools/model-catalog/index.ts +137 -0
  223. package/src/tools/schedules/create-schedule.ts +16 -1
  224. package/src/tools/schedules/patch-schedule.ts +17 -0
  225. package/src/tools/schedules/update-schedule.ts +17 -0
  226. package/src/tools/send-task.ts +14 -0
  227. package/src/tools/task-action.ts +12 -0
  228. package/src/tools/tool-config.ts +3 -1
  229. package/src/tools/workflows/create-workflow.ts +9 -2
  230. package/src/tools/workflows/patch-workflow-node.ts +17 -7
  231. package/src/tools/workflows/patch-workflow.ts +9 -2
  232. package/src/tools/workflows/update-workflow.ts +9 -2
  233. package/src/types.ts +30 -1
  234. package/src/utils/context-window.ts +47 -53
  235. package/src/utils/harness-cli-version.ts +115 -0
  236. package/src/utils/harness-model-error.ts +33 -0
  237. package/src/utils/runtime-model-catalog.ts +137 -0
  238. package/src/workflows/approval-resolution.ts +39 -0
  239. package/src/workflows/definition.ts +7 -0
  240. package/src/workflows/engine.ts +69 -1
  241. package/src/workflows/executors/agent-task.ts +9 -0
  242. package/src/workflows/executors/base.ts +53 -0
  243. package/src/workflows/executors/human-in-the-loop.ts +8 -8
  244. package/src/workflows/executors/index.ts +1 -0
  245. package/src/workflows/executors/registry.ts +2 -0
  246. package/src/workflows/executors/system-one-decision.ts +1232 -0
  247. package/src/workflows/executors/system-one-providers.ts +301 -0
  248. package/src/workflows/executors/workflow-llm.ts +13 -1
  249. package/src/workflows/readiness.ts +86 -0
  250. package/src/workflows/recovery.ts +12 -12
  251. package/src/workflows/resume.ts +23 -9
  252. package/templates/schedules/daily-blocker-digest/content.md +4 -5
  253. package/templates/skills/agent-browser/SKILL.md +6 -5
  254. package/templates/skills/agent-browser/content.md +6 -5
  255. package/templates/skills/heartbeat-runbook/config.json +1 -1
  256. package/templates/skills/heartbeat-runbook/content.md +2 -2
  257. package/templates/skills/swarm-scripts/SKILL.md +1 -1
  258. package/templates/skills/swarm-scripts/content.md +1 -1
  259. package/templates/skills/workflow-iterate/content.md +81 -1
  260. package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
  261. package/templates/workflows/pr-review-status-sweep/content.md +2 -0
  262. package/dist/claude-sdk-session-t442qz6f.js +0 -39108
  263. package/dist/cli-0xjkj1j7.js +0 -61
  264. package/dist/cli-65hjmev5.js +0 -1141
  265. package/dist/cli-aw1jwp1r.js +0 -14075
  266. package/dist/cli-h967nb6r.js +0 -6603
  267. package/dist/cli-vapyk8sw.js +0 -20
  268. package/dist/cli-ws6rqycm.js +0 -247
  269. package/dist/pricing-refresh-v1cbr9te.js +0 -27
  270. package/src/providers/modelsdev-reasoning.json +0 -6453
  271. package/dist/{codex-login-x3jk4etn.js → codex-login-he9vrn90.js} +3 -3
  272. package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
@@ -0,0 +1,365 @@
1
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import crypto from "node:crypto";
3
+ import { unlink } from "node:fs/promises";
4
+ import {
5
+ createServer as createHttpServer,
6
+ type IncomingMessage,
7
+ type Server,
8
+ type ServerResponse,
9
+ } from "node:http";
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import { closeDb, getDbClient, getWorkflow, initDb, upsertSwarmConfig } from "../be/db";
12
+ import { getPathSegments, parseQueryParams } from "../http/utils";
13
+ import { handleWorkflows } from "../http/workflows";
14
+ import { registerCreateWorkflowTool } from "../tools/workflows/create-workflow";
15
+ import { registerPatchWorkflowTool } from "../tools/workflows/patch-workflow";
16
+ import { registerPatchWorkflowNodeTool } from "../tools/workflows/patch-workflow-node";
17
+ import { registerUpdateWorkflowTool } from "../tools/workflows/update-workflow";
18
+ import type { Workflow, WorkflowDefinition } from "../types";
19
+ import { initWorkflows, stopRetryPoller } from "../workflows";
20
+ import { listenOnFreePort } from "./test-net";
21
+
22
+ const TEST_DB_PATH = "./test-workflow-system-one-decision-save-warnings.sqlite";
23
+ const KEY_VALUE = "tsk_example-save-warning-key.0123456789";
24
+
25
+ // ─── MCP harness (same shape as workflow-mcp-trigger-schema.test.ts) ─────────
26
+
27
+ type ToolResult = {
28
+ structuredContent?: {
29
+ success: boolean;
30
+ message: string;
31
+ details?: string;
32
+ warnings?: string[];
33
+ workflow?: { id: string; definition: WorkflowDefinition } & Record<string, unknown>;
34
+ };
35
+ };
36
+
37
+ function buildTools() {
38
+ const server = new McpServer({
39
+ name: "test-workflow-system-one-decision-save-warnings",
40
+ version: "1.0.0",
41
+ });
42
+ registerCreateWorkflowTool(server);
43
+ registerUpdateWorkflowTool(server);
44
+ registerPatchWorkflowTool(server);
45
+ registerPatchWorkflowNodeTool(server);
46
+ const registered = (server as unknown as Record<string, unknown>)._registeredTools as Record<
47
+ string,
48
+ { handler: (args: unknown, extra: unknown) => Promise<unknown> }
49
+ >;
50
+ const call = (name: string) => async (args: Record<string, unknown>) => {
51
+ const tool = registered[name];
52
+ expect(tool).toBeDefined();
53
+ const extra = {
54
+ sessionId: "session-test",
55
+ requestInfo: { headers: { "x-agent-id": "agent-test" } },
56
+ };
57
+ return (await tool?.handler(args, extra)) as ToolResult;
58
+ };
59
+ return {
60
+ create: call("create-workflow"),
61
+ update: call("update-workflow"),
62
+ patch: call("patch-workflow"),
63
+ patchNode: call("patch-workflow-node"),
64
+ };
65
+ }
66
+
67
+ // ─── HTTP harness ────────────────────────────────────────────────────────────
68
+
69
+ let baseUrl = "";
70
+ const headers = { "Content-Type": "application/json", "X-Agent-ID": crypto.randomUUID() };
71
+
72
+ function createTestServer(): Server {
73
+ return createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
74
+ res.setHeader("Content-Type", "application/json");
75
+ const handled = await handleWorkflows(
76
+ req,
77
+ res,
78
+ getPathSegments(req.url || ""),
79
+ parseQueryParams(req.url || ""),
80
+ req.headers["x-agent-id"] as string | undefined,
81
+ );
82
+ if (!handled) {
83
+ res.writeHead(404);
84
+ res.end("{}");
85
+ }
86
+ });
87
+ }
88
+
89
+ async function http(method: string, path: string, body?: unknown) {
90
+ const res = await fetch(`${baseUrl}${path}`, {
91
+ method,
92
+ headers,
93
+ body: body === undefined ? undefined : JSON.stringify(body),
94
+ });
95
+ return { status: res.status, body: (await res.json()) as Workflow & { warnings?: string[] } };
96
+ }
97
+
98
+ // ─── Fixtures ────────────────────────────────────────────────────────────────
99
+
100
+ const systemOneConfig = (overrides: Record<string, unknown> = {}) => ({
101
+ state: "{{lead}}",
102
+ questions: { fit: { type: "noul", instructions: "Is this a real lead?" } },
103
+ returns: { fit: { type: "noul" } },
104
+ ...overrides,
105
+ });
106
+
107
+ const plainDefinition = (): WorkflowDefinition => ({
108
+ nodes: [{ id: "step", type: "agent-task", config: { template: "Hello" } }],
109
+ });
110
+
111
+ const systemOneDefinition = (config: Record<string, unknown> = {}): WorkflowDefinition => ({
112
+ nodes: [
113
+ {
114
+ id: "qualify",
115
+ type: "system-one-decision",
116
+ inputs: { lead: "trigger.lead" },
117
+ config: systemOneConfig(config),
118
+ },
119
+ ],
120
+ });
121
+
122
+ let nameCounter = 0;
123
+ const uniqueName = () => `system-one-save-${++nameCounter}-${Date.now()}`;
124
+
125
+ const setTypeSafeKey = () =>
126
+ upsertSwarmConfig({
127
+ scope: "global",
128
+ key: "TYPESAFE_API_KEY",
129
+ value: KEY_VALUE,
130
+ isSecret: true,
131
+ });
132
+
133
+ // ─── Tests ───────────────────────────────────────────────────────────────────
134
+
135
+ describe("saving a workflow whose system-one-decision node has no working key", () => {
136
+ let tools: ReturnType<typeof buildTools>;
137
+ let server: Server;
138
+ let savedOpenRouterKey: string | undefined;
139
+
140
+ beforeAll(async () => {
141
+ for (const suffix of ["", "-wal", "-shm"]) await unlink(TEST_DB_PATH + suffix).catch(() => {});
142
+ initDb(TEST_DB_PATH);
143
+ await initWorkflows();
144
+ tools = buildTools();
145
+ server = createTestServer();
146
+ baseUrl = `http://localhost:${await listenOnFreePort(server)}`;
147
+ });
148
+
149
+ afterAll(async () => {
150
+ stopRetryPoller();
151
+ await new Promise<void>((resolve) => server.close(() => resolve()));
152
+ closeDb();
153
+ for (const suffix of ["", "-wal", "-shm"]) await unlink(TEST_DB_PATH + suffix).catch(() => {});
154
+ });
155
+
156
+ beforeEach(async () => {
157
+ savedOpenRouterKey = process.env.OPENROUTER_API_KEY;
158
+ delete process.env.OPENROUTER_API_KEY;
159
+ await getDbClient().run(
160
+ "DELETE FROM swarm_config WHERE key IN ('TYPESAFE_API_KEY', 'LAYA_URL', 'LAYA_API_KEY')",
161
+ );
162
+ });
163
+
164
+ afterEach(() => {
165
+ if (savedOpenRouterKey === undefined) delete process.env.OPENROUTER_API_KEY;
166
+ else process.env.OPENROUTER_API_KEY = savedOpenRouterKey;
167
+ });
168
+
169
+ // The save is never blocked. The run-start check is the guarantee, so a
170
+ // definition can be saved before a human has supplied the key.
171
+
172
+ test("create-workflow saves and warns: names the key, where to set it, the node", async () => {
173
+ const result = await tools.create({ name: uniqueName(), definition: systemOneDefinition() });
174
+ const out = result.structuredContent;
175
+
176
+ expect(out?.success).toBe(true);
177
+ expect(out?.workflow?.id).toBeTruthy();
178
+ expect(await getWorkflow(out?.workflow?.id as string)).not.toBeNull();
179
+ expect(out?.warnings).toHaveLength(1);
180
+ for (const text of [out?.message, out?.details, out?.warnings?.[0]]) {
181
+ expect(text).toContain("TYPESAFE_API_KEY");
182
+ expect(text).toContain("Secrets page");
183
+ }
184
+ expect(out?.message).toContain("Warning:");
185
+ expect(out?.warnings?.[0]).toContain('system-one-decision node "qualify"');
186
+ expect(JSON.stringify(result)).not.toContain(KEY_VALUE);
187
+ });
188
+
189
+ test("the warning names the key of the node's provider", async () => {
190
+ const result = await tools.create({
191
+ name: uniqueName(),
192
+ definition: systemOneDefinition({ provider: "openrouter" }),
193
+ });
194
+ expect(result.structuredContent?.warnings?.[0]).toContain(
195
+ "OPENROUTER_API_KEY is not configured",
196
+ );
197
+ expect(result.structuredContent?.warnings?.[0]).not.toContain("TYPESAFE_API_KEY");
198
+ });
199
+
200
+ test("a laya node warns for LAYA_URL and LAYA_API_KEY, never for a TypeSafe key", async () => {
201
+ await setTypeSafeKey();
202
+ const result = await tools.create({
203
+ name: uniqueName(),
204
+ definition: systemOneDefinition({ provider: "laya" }),
205
+ });
206
+ const out = result.structuredContent;
207
+
208
+ expect(out?.success).toBe(true);
209
+ expect(out?.warnings).toHaveLength(1);
210
+ const warning = out?.warnings?.[0] ?? "";
211
+ expect(warning).toContain("LAYA_URL is not configured");
212
+ expect(warning).toContain("LAYA_API_KEY is not configured");
213
+ expect(warning).toContain('system-one-decision node "qualify"');
214
+ expect(warning).not.toMatch(/TypeSafe|TYPESAFE/);
215
+
216
+ // Only the URL is set: the warning shrinks to the key.
217
+ await upsertSwarmConfig({
218
+ scope: "global",
219
+ key: "LAYA_URL",
220
+ value: "https://laya.example.test",
221
+ });
222
+ const half = await tools.create({
223
+ name: uniqueName(),
224
+ definition: systemOneDefinition({ provider: "laya" }),
225
+ });
226
+ expect(half.structuredContent?.warnings?.[0]).toContain("LAYA_API_KEY is not configured");
227
+ expect(half.structuredContent?.warnings?.[0]).not.toContain("LAYA_URL");
228
+
229
+ await upsertSwarmConfig({
230
+ scope: "global",
231
+ key: "LAYA_API_KEY",
232
+ value: "laya-example-save-warning-key-0123456789",
233
+ isSecret: true,
234
+ });
235
+ const ready = await tools.create({
236
+ name: uniqueName(),
237
+ definition: systemOneDefinition({ provider: "laya" }),
238
+ });
239
+ expect(ready.structuredContent?.warnings).toBeUndefined();
240
+ expect(JSON.stringify(ready)).not.toContain("laya-example-save-warning-key");
241
+ });
242
+
243
+ test("HTTP create of a laya node carries the same warning", async () => {
244
+ const created = await http("POST", "/api/workflows", {
245
+ name: uniqueName(),
246
+ definition: systemOneDefinition({ provider: "laya" }),
247
+ });
248
+ expect(created.status).toBe(201);
249
+ expect(created.body.warnings).toHaveLength(1);
250
+ expect(created.body.warnings?.[0]).toContain("LAYA_URL is not configured");
251
+ expect(created.body.warnings?.[0]).toContain("LAYA_API_KEY is not configured");
252
+ });
253
+
254
+ test("no warning once the key is configured, or when there is no system-one-decision node", async () => {
255
+ const plain = await tools.create({ name: uniqueName(), definition: plainDefinition() });
256
+ expect(plain.structuredContent?.success).toBe(true);
257
+ expect(plain.structuredContent?.warnings).toBeUndefined();
258
+ expect(plain.structuredContent?.message).not.toContain("Warning");
259
+
260
+ await setTypeSafeKey();
261
+ const keyed = await tools.create({ name: uniqueName(), definition: systemOneDefinition() });
262
+ expect(keyed.structuredContent?.success).toBe(true);
263
+ expect(keyed.structuredContent?.warnings).toBeUndefined();
264
+
265
+ process.env.OPENROUTER_API_KEY = "sk-or-example-save-warning-key-0123456789";
266
+ const openrouter = await tools.create({
267
+ name: uniqueName(),
268
+ definition: systemOneDefinition({ provider: "openrouter" }),
269
+ });
270
+ expect(openrouter.structuredContent?.warnings).toBeUndefined();
271
+ });
272
+
273
+ test("update-workflow warns when the new definition adds a system-one-decision node, and on a rename", async () => {
274
+ const created = await tools.create({ name: uniqueName(), definition: plainDefinition() });
275
+ const id = created.structuredContent?.workflow?.id as string;
276
+
277
+ const withSystemOne = await tools.update({ id, definition: systemOneDefinition() });
278
+ expect(withSystemOne.structuredContent?.success).toBe(true);
279
+ expect(withSystemOne.structuredContent?.warnings?.[0]).toContain("TYPESAFE_API_KEY");
280
+
281
+ // A change that does not touch the definition still tells the author what is still true.
282
+ const renamed = await tools.update({ id, name: uniqueName() });
283
+ expect(renamed.structuredContent?.success).toBe(true);
284
+ expect(renamed.structuredContent?.warnings?.[0]).toContain("TYPESAFE_API_KEY");
285
+
286
+ await setTypeSafeKey();
287
+ const fixed = await tools.update({ id, name: uniqueName() });
288
+ expect(fixed.structuredContent?.warnings).toBeUndefined();
289
+ });
290
+
291
+ test("patch-workflow warns when a system-one-decision node is created", async () => {
292
+ const created = await tools.create({ name: uniqueName(), definition: plainDefinition() });
293
+ const id = created.structuredContent?.workflow?.id as string;
294
+
295
+ const patched = await tools.patch({
296
+ id,
297
+ update: [{ nodeId: "step", node: { next: "qualify" } }],
298
+ create: [
299
+ { id: "qualify", type: "system-one-decision", config: systemOneConfig(), inputs: {} },
300
+ ],
301
+ });
302
+ expect(patched.structuredContent?.success).toBe(true);
303
+ expect(patched.structuredContent?.warnings?.[0]).toContain("TYPESAFE_API_KEY");
304
+ expect(patched.structuredContent?.message).toContain("Warning:");
305
+ });
306
+
307
+ test("patch-workflow-node warns when a node is switched to another provider", async () => {
308
+ await setTypeSafeKey();
309
+ const created = await tools.create({ name: uniqueName(), definition: systemOneDefinition() });
310
+ const id = created.structuredContent?.workflow?.id as string;
311
+ expect(created.structuredContent?.warnings).toBeUndefined();
312
+
313
+ const patched = await tools.patchNode({
314
+ id,
315
+ nodeId: "qualify",
316
+ config: systemOneConfig({ provider: "openrouter" }),
317
+ });
318
+ expect(patched.structuredContent?.success).toBe(true);
319
+ expect(patched.structuredContent?.warnings?.[0]).toContain("OPENROUTER_API_KEY");
320
+ expect(patched.structuredContent?.warnings?.[0]).not.toContain("TYPESAFE_API_KEY");
321
+ });
322
+
323
+ test("HTTP create, update, patch, and node patch carry the same warning", async () => {
324
+ const created = await http("POST", "/api/workflows", {
325
+ name: uniqueName(),
326
+ definition: systemOneDefinition(),
327
+ });
328
+ expect(created.status).toBe(201);
329
+ expect(created.body.warnings).toHaveLength(1);
330
+ expect(created.body.warnings?.[0]).toContain("TYPESAFE_API_KEY");
331
+ expect(created.body.warnings?.[0]).toContain("Secrets page");
332
+ expect(JSON.stringify(created.body)).not.toContain(KEY_VALUE);
333
+ const id = created.body.id;
334
+
335
+ const updated = await http("PUT", `/api/workflows/${id}`, { description: "edited" });
336
+ expect(updated.status).toBe(200);
337
+ expect(updated.body.warnings?.[0]).toContain("TYPESAFE_API_KEY");
338
+
339
+ const nodePatched = await http("PATCH", `/api/workflows/${id}/nodes/qualify`, {
340
+ config: systemOneConfig({ provider: "openrouter" }),
341
+ });
342
+ expect(nodePatched.status).toBe(200);
343
+ expect(nodePatched.body.warnings?.[0]).toContain("OPENROUTER_API_KEY");
344
+
345
+ const patched = await http("PATCH", `/api/workflows/${id}`, {
346
+ update: [{ nodeId: "qualify", node: { config: systemOneConfig() } }],
347
+ });
348
+ expect(patched.status).toBe(200);
349
+ expect(patched.body.warnings?.[0]).toContain("TYPESAFE_API_KEY");
350
+
351
+ await setTypeSafeKey();
352
+ const clean = await http("PUT", `/api/workflows/${id}`, { description: "edited again" });
353
+ expect(clean.status).toBe(200);
354
+ expect(clean.body.warnings).toBeUndefined();
355
+ });
356
+
357
+ test("HTTP create without a system-one-decision node has no warnings field", async () => {
358
+ const created = await http("POST", "/api/workflows", {
359
+ name: uniqueName(),
360
+ definition: plainDefinition(),
361
+ });
362
+ expect(created.status).toBe(201);
363
+ expect(Object.hasOwn(created.body, "warnings")).toBe(false);
364
+ });
365
+ });