@desplega.ai/agent-swarm 1.152.2 → 1.154.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 (284) hide show
  1. package/README.md +2 -2
  2. package/dist/{acp-adapter-pfeje2v5.js → acp-adapter-eb2aj8xy.js} +4 -4
  3. package/dist/{actions-0sttc7f5.js → actions-kpnwp1kz.js} +31 -27
  4. package/dist/{anthropic-messages-2y8nktar.js → anthropic-messages-9vqsbznt.js} +112 -84
  5. package/dist/{app-p8teva95.js → app-zk1apkf6.js} +10 -7
  6. package/dist/{assistant-r45jnsrt.js → assistant-kwq4y50q.js} +32 -28
  7. package/dist/{azure-openai-responses-f38x2avq.js → azure-openai-responses-04qwdwfq.js} +27 -13
  8. package/dist/{boot-reembed-je26h5y7.js → boot-reembed-22hd3jjn.js} +9 -7
  9. package/dist/{boot-reembed-1qmybsd4.js → boot-reembed-qe168424.js} +10 -8
  10. package/dist/{boot-scrub-logs-gte3h1mm.js → boot-scrub-logs-pkdpsrjd.js} +7 -5
  11. package/dist/{claude-adapter-csc1mzka.js → claude-adapter-3fx90nww.js} +18 -16
  12. package/dist/{claude-managed-adapter-dsmkcrjy.js → claude-managed-adapter-ea2aga6w.js} +3 -3
  13. package/dist/{claude-sdk-session-fcd6f078.js → claude-sdk-session-z7fahf1k.js} +18 -16
  14. package/dist/{cli-r2fpe7cx.js → cli-00w2c66p.js} +61 -55
  15. package/dist/{cli-wbyk465z.js → cli-09y3tjxz.js} +1 -1
  16. package/dist/{cli-ht5rs63n.js → cli-0e31nm4n.js} +16 -7
  17. package/dist/cli-0x5qzvkg.js +4279 -0
  18. package/dist/{cli-d3p82erb.js → cli-18amys3y.js} +9 -9
  19. package/dist/{cli-4ahpr4sw.js → cli-1jpjafw4.js} +5 -5
  20. package/dist/{cli-jbmc0r7r.js → cli-243tzws8.js} +1 -1
  21. package/dist/{cli-ch38q5z4.js → cli-24k806sc.js} +6789 -2542
  22. package/dist/cli-3bkz2p7j.js +2828 -0
  23. package/dist/cli-3er6gcsn.js +1849 -0
  24. package/dist/cli-4k7jazkq.js +3339 -0
  25. package/dist/{cli-c4eeb5q2.js → cli-5b6b62tz.js} +1 -1
  26. package/dist/{cli-qd65dvr6.js → cli-5vs0nh43.js} +65 -44
  27. package/dist/{cli-z3wkng35.js → cli-6r3cgpk2.js} +224 -156
  28. package/dist/{cli-pgj4a5fb.js → cli-71cj99hx.js} +4 -3
  29. package/dist/cli-8sea27q4.js +14075 -0
  30. package/dist/{cli-jvy9qgjs.js → cli-913yakhv.js} +22 -7
  31. package/dist/{cli-pf9q3696.js → cli-927erqqt.js} +5 -2
  32. package/dist/cli-95jnpnqg.js +230 -0
  33. package/dist/{cli-wpv5rgg2.js → cli-abp3g4ax.js} +15 -2
  34. package/dist/{cli-ckz5t2hb.js → cli-acy6bfhr.js} +10 -7
  35. package/dist/{cli-4yc5d8fk.js → cli-avkd6mqv.js} +50 -0
  36. package/dist/{cli-8wrympd7.js → cli-bzr0ftht.js} +26 -7
  37. package/dist/{cli-qn042eh7.js → cli-c1v10xb2.js} +194 -1913
  38. package/dist/cli-cgj4hc12.js +18902 -0
  39. package/dist/{cli-2nnd56zt.js → cli-e0pg4zeh.js} +3 -2
  40. package/dist/{cli-r84ctdxn.js → cli-e31ehqwy.js} +2 -2
  41. package/dist/{cli-gphgnvpf.js → cli-e3229na9.js} +3 -3
  42. package/dist/{cli-gpqcs935.js → cli-e3cyf59d.js} +2 -2
  43. package/dist/{cli-s25avqg9.js → cli-e781c6f9.js} +1 -0
  44. package/dist/{cli-s2qjdd6z.js → cli-eetrvv91.js} +1 -1
  45. package/dist/{cli-j4nv2wqy.js → cli-fdzg6jhf.js} +10 -4
  46. package/dist/{cli-fm4ytsdk.js → cli-g2h29qar.js} +2 -2
  47. package/dist/{cli-zvgj5zg8.js → cli-ge67p080.js} +302 -3497
  48. package/dist/cli-h38x0qhg.js +6912 -0
  49. package/dist/{cli-9xvdm3bx.js → cli-h60qfasa.js} +742 -278
  50. package/dist/{cli-jew3jda5.js → cli-hws0vn86.js} +2 -2
  51. package/dist/{cli-j8dggbcr.js → cli-k0g1sjzm.js} +23 -31
  52. package/dist/{cli-xw7vak96.js → cli-kp3qs53m.js} +5 -5
  53. package/dist/{cli-rgvp6r2x.js → cli-mcnhgnn1.js} +193 -4
  54. package/dist/{cli-zvkq5csn.js → cli-myaxtgh8.js} +7 -6
  55. package/dist/{cli-56h18ng2.js → cli-n62atwh9.js} +1 -1
  56. package/dist/cli-n7h1kqpf.js +29 -0
  57. package/dist/{cli-fmy65jdn.js → cli-pf1v8xhb.js} +7 -5
  58. package/dist/{cli-xy1cchaf.js → cli-q85qhckh.js} +238 -11
  59. package/dist/{cli-3bpzj8me.js → cli-r1d3dzt6.js} +2 -2
  60. package/dist/{pi-mono-adapter-jcqt71cg.js → cli-rxkqvngf.js} +67343 -101083
  61. package/dist/{cli-kj76p1ez.js → cli-scwq51gn.js} +13 -7
  62. package/dist/{cli-b9dd0h3k.js → cli-stwsmd6k.js} +3 -2
  63. package/dist/{cli-c2mk1kgf.js → cli-v4en75c5.js} +8 -1
  64. package/dist/{cli-z61ykvam.js → cli-wg04s9kr.js} +4 -3
  65. package/dist/cli-yn0a417a.js +4069 -0
  66. package/dist/{cli-zfzznn7c.js → cli-ytxs7mn1.js} +26 -2
  67. package/dist/{cli-4j8k1m4w.js → cli-zt1227k1.js} +1162 -192
  68. package/dist/cli.js +28 -35
  69. package/dist/{codex-adapter-fxmpsabw.js → codex-adapter-ttdm995v.js} +12 -11
  70. package/dist/{codex-hook-8jexc34x.js → codex-hook-8gx7bcz0.js} +3 -3
  71. package/dist/{codex-session-runner-rr1e47ea.js → codex-session-runner-3v8nh4c8.js} +12 -11
  72. package/dist/{commands-0f5rxz51.js → commands-1cxwbhhq.js} +7 -5
  73. package/dist/{db-r3jxf3wy.js → db-y9zwsr79.js} +25 -5
  74. package/dist/{dispatcher-06vph6q4.js → dispatcher-9m9zss36.js} +30 -26
  75. package/dist/dsh-adapter-gsb2qqf9.js +19 -0
  76. package/dist/{e2b-413b8t4v.js → e2b-93x1k0j5.js} +6 -7
  77. package/dist/{google-generative-ai-ndjmj5fa.js → google-generative-ai-gwwrnzqn.js} +33 -73
  78. package/dist/{google-vertex-fr665tqs.js → google-vertex-5wfawpwz.js} +38 -68
  79. package/dist/{handlers-0hgtc910.js → handlers-36fm1bwn.js} +33 -29
  80. package/dist/{hook-9kvrv1er.js → hook-pntw4r8h.js} +12 -10
  81. package/dist/{hook-fbfhkat3.js → hook-vwq8y26p.js} +13 -11
  82. package/dist/{http-xbyx8k5j.js → http-20jesakk.js} +357 -261
  83. package/dist/{index-jsw3aynb.js → index-6artrehg.js} +30 -26
  84. package/dist/{cli-pdz9qde4.js → index-9ch1n761.js} +10 -11
  85. package/dist/{index-rnyahfn5.js → index-9n5z2492.js} +30 -26
  86. package/dist/{index-66xz5e9j.js → index-h5gs261z.js} +521 -9
  87. package/dist/{index-tfsdxyv6.js → index-j5wrf1yw.js} +40 -25
  88. package/dist/{index-2sp0384z.js → index-kkeftv5h.js} +738 -21
  89. package/dist/{index-sgk50mbg.js → index-m2xqpz5r.js} +31 -27
  90. package/dist/{index-gpfn1741.js → index-m9ks8a7t.js} +9 -7
  91. package/dist/index-w097eg0x.js +5 -0
  92. package/dist/jiti-loader-b4837mtm.js +34 -0
  93. package/dist/jiti-static-loader-q2g5ds0y.js +31267 -0
  94. package/dist/{keepalive-s3xpr7sw.js → keepalive-pwxvxd2c.js} +9 -7
  95. package/dist/{lead-rq4n5r16.js → lead-m789wqa3.js} +46 -41
  96. package/dist/{maintenance-f0atj1ab.js → maintenance-bvg6w7e2.js} +10 -8
  97. package/dist/{mistral-conversations-qhr84ph3.js → mistral-conversations-zx0kn554.js} +24 -18
  98. package/dist/{node-x1g4v0pq.js → node-5qtycf6a.js} +16 -11
  99. package/dist/{oauth-refresh-sweep-q0t3bxre.js → oauth-refresh-sweep-6v4bd737.js} +8 -6
  100. package/dist/{onboard-br5j1kch.js → onboard-snckpcj1.js} +115 -35
  101. package/dist/{openai-codex-responses-wy6a43wd.js → openai-codex-responses-4vjqzvjp.js} +33 -27
  102. package/dist/{openai-completions-32rdwmze.js → openai-completions-pxsmym7g.js} +52 -64
  103. package/dist/{openai-responses-6xwf7bzg.js → openai-responses-tmn0rqp0.js} +23 -24
  104. package/dist/{opencode-adapter-28axqa09.js → opencode-adapter-qbmv4tpy.js} +6 -6
  105. package/dist/{otel-impl-vdqge2nt.js → otel-impl-e0ytens5.js} +4 -4
  106. package/dist/{pi-messages-b1mbr6gp.js → pi-messages-txpjj39q.js} +3 -3
  107. package/dist/pi-mono-adapter-c4ggemhz.js +1316 -0
  108. package/dist/{pricing-refresh-hb3691fd.js → pricing-refresh-52g2ywrw.js} +9 -7
  109. package/dist/{rbac-roles-t78xt39b.js → rbac-roles-5455kfdn.js} +8 -6
  110. package/dist/{rbac-roles-jh280k9g.js → rbac-roles-hbf0xa52.js} +9 -7
  111. package/dist/{render-v2-7p8r2ymg.js → render-v2-npm8xdzh.js} +16 -9
  112. package/dist/{seed-pricing-hf889enh.js → seed-pricing-650wbr1n.js} +8 -6
  113. package/dist/{setup-w6mb03jv.js → setup-k1mew5rf.js} +4 -5
  114. package/dist/virtual-modules-2q5b2pk4.js +54 -0
  115. package/dist/{work-objects-nt8gy1kj.js → work-objects-2ea3kvwg.js} +33 -29
  116. package/dist/{worker-dwy617t0.js → worker-ec6hvc4m.js} +46 -41
  117. package/openapi.json +109 -16
  118. package/package.json +7 -6
  119. package/plugin/opencode-plugins/lib/memory-raters.ts +7 -0
  120. package/src/be/automation-preflight.ts +6 -4
  121. package/src/be/db/agents.ts +75 -2
  122. package/src/be/db/tasks/read.ts +2 -0
  123. package/src/be/db/tasks/write.ts +7 -1
  124. package/src/be/db.ts +140 -18
  125. package/src/be/extensions/db.ts +35 -4
  126. package/src/be/memory/raters/llm.ts +2 -6
  127. package/src/be/memory/raters/noop.ts +2 -3
  128. package/src/be/memory/raters/registry.ts +4 -10
  129. package/src/be/migrations/157_defer_provenance_and_marker.sql +27 -0
  130. package/src/be/models-catalog.ts +2 -0
  131. package/src/be/modelsdev-cache.json +213 -0
  132. package/src/be/rbac-audit.ts +2 -0
  133. package/src/be/seed/agent-fs-provision.ts +23 -0
  134. package/src/be/seed-pricing.ts +3 -1
  135. package/src/be/swarm-config-guard.ts +1 -0
  136. package/src/commands/onboard/compose-generator.ts +16 -1
  137. package/src/commands/onboard/env-generator.ts +14 -1
  138. package/src/commands/onboard/manifest.ts +1 -0
  139. package/src/commands/onboard/steps/integration-slack.tsx +56 -16
  140. package/src/commands/onboard/steps/review.tsx +5 -0
  141. package/src/commands/onboard/types.ts +6 -0
  142. package/src/commands/onboard.tsx +5 -1
  143. package/src/commands/provider-credentials.ts +14 -2
  144. package/src/commands/runner.ts +18 -2
  145. package/src/extensions/identity.ts +15 -7
  146. package/src/extensions/lifecycle.ts +15 -21
  147. package/src/hooks/codex-hook.ts +7 -1
  148. package/src/http/agents.ts +12 -4
  149. package/src/http/config.ts +1 -1
  150. package/src/http/core.ts +5 -3
  151. package/src/http/extensions.ts +108 -34
  152. package/src/http/poll.ts +7 -0
  153. package/src/http/schedules.ts +10 -0
  154. package/src/http/script-runs.ts +35 -26
  155. package/src/http/status.ts +24 -11
  156. package/src/http/tasks.ts +6 -1
  157. package/src/prompts/base-prompt.ts +15 -7
  158. package/src/prompts/memories.ts +5 -7
  159. package/src/prompts/session-templates.ts +3 -2
  160. package/src/prompts/steering-delivery.ts +2 -0
  161. package/src/providers/claude-managed-models.ts +8 -0
  162. package/src/providers/codex-adapter.ts +3 -6
  163. package/src/providers/codex-models.ts +15 -1
  164. package/src/providers/dsh-adapter.ts +248 -0
  165. package/src/providers/index.ts +5 -1
  166. package/src/providers/modelsdev-reasoning.json +50 -0
  167. package/src/providers/pi-mono-extension.ts +2 -5
  168. package/src/rbac/legacy-policy.ts +15 -1
  169. package/src/rbac/types.ts +2 -0
  170. package/src/server.ts +7 -1
  171. package/src/slack/ack.ts +43 -8
  172. package/src/slack/app.ts +32 -10
  173. package/src/slack/config.ts +29 -0
  174. package/src/slack/handlers.ts +7 -1
  175. package/src/slack/render-v2.ts +186 -17
  176. package/src/slack/responses.ts +1 -3
  177. package/src/slack/task-output.ts +66 -8
  178. package/src/slack/thread-buffer.ts +27 -3
  179. package/src/telemetry.ts +2 -3
  180. package/src/tests/agent-fs-provision-seeder.test.ts +56 -2
  181. package/src/tests/agents-harness-provider.test.ts +19 -0
  182. package/src/tests/agents-list-model-display.test.ts +60 -1
  183. package/src/tests/base-prompt.test.ts +25 -0
  184. package/src/tests/claude-managed-adapter.test.ts +6 -0
  185. package/src/tests/codex-gpt6-release.test.ts +97 -0
  186. package/src/tests/codex-steering-hook.test.ts +2 -2
  187. package/src/tests/config-api-only-keys.test.ts +9 -0
  188. package/src/tests/context-window.test.ts +1 -0
  189. package/src/tests/db-task-read-contract.test.ts +9 -0
  190. package/src/tests/db-task-write-contract.test.ts +9 -0
  191. package/src/tests/defer-task.test.ts +115 -16
  192. package/src/tests/dsh-adapter.test.ts +276 -0
  193. package/src/tests/e2e-nightly-report.test.ts +3 -3
  194. package/src/tests/e2e-redact.test.ts +2 -0
  195. package/src/tests/env-flag.test.ts +6 -0
  196. package/src/tests/extension-agent-visibility.test.ts +474 -0
  197. package/src/tests/extensions-db.test.ts +41 -0
  198. package/src/tests/extensions-http.test.ts +148 -4
  199. package/src/tests/extensions-lifecycle.test.ts +21 -1
  200. package/src/tests/extensions-mcp-tools.test.ts +9 -5
  201. package/src/tests/fetch-resolved-env.test.ts +43 -0
  202. package/src/tests/internal-ai/complete-structured.test.ts +32 -9
  203. package/src/tests/internal-ai/credentials.test.ts +4 -4
  204. package/src/tests/internal-ai/models.test.ts +20 -0
  205. package/src/tests/memory-rate-tool.test.ts +7 -10
  206. package/src/tests/memory-rater-defaults.test.ts +23 -0
  207. package/src/tests/memory-rater-e2e.test.ts +2 -3
  208. package/src/tests/migration-046-budgets.test.ts +2 -0
  209. package/src/tests/model-control.test.ts +2 -2
  210. package/src/tests/onboard-compose.test.ts +14 -0
  211. package/src/tests/onboard-env.test.ts +17 -0
  212. package/src/tests/onboard-manifest.test.ts +12 -0
  213. package/src/tests/opus-5-5-registry.test.ts +48 -0
  214. package/src/tests/pricing-refresh.test.ts +1 -1
  215. package/src/tests/pricing-routes.test.ts +15 -3
  216. package/src/tests/provider-registration.test.ts +5 -2
  217. package/src/tests/rbac-admission-e2e.test.ts +54 -0
  218. package/src/tests/rbac-e2e-helpers.ts +2 -0
  219. package/src/tests/rbac-engine.test.ts +32 -0
  220. package/src/tests/rbac-wire-e2e.test.ts +33 -0
  221. package/src/tests/reasoning-effort.test.ts +8 -0
  222. package/src/tests/reload-config.test.ts +33 -0
  223. package/src/tests/script-connections.test.ts +3 -0
  224. package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
  225. package/src/tests/slack-config.test.ts +136 -0
  226. package/src/tests/slack-mode-lifecycle.test.ts +69 -0
  227. package/src/tests/slack-render-v2.test.ts +446 -16
  228. package/src/tests/slack-steering.test.ts +69 -1
  229. package/src/tests/slack-task-output.test.ts +33 -12
  230. package/src/tests/status.test.ts +22 -2
  231. package/src/tests/steering-core.test.ts +35 -0
  232. package/src/tests/steering-delivery.test.ts +39 -0
  233. package/src/tests/steering-transport.test.ts +2 -2
  234. package/src/tests/task-tool-preload-mcp.test.ts +3 -1
  235. package/src/tests/telemetry-init.test.ts +18 -0
  236. package/src/tests/workflow-live-recovery.test.ts +126 -0
  237. package/src/tests/workflow-llm.test.ts +3 -3
  238. package/src/tools/defer-task.ts +64 -26
  239. package/src/tools/extension-common.ts +4 -0
  240. package/src/tools/extension-install.ts +1 -1
  241. package/src/tools/extension-list.ts +4 -3
  242. package/src/tools/join-swarm.ts +1 -1
  243. package/src/tools/schedules/create-schedule.ts +11 -1
  244. package/src/tools/schedules/patch-schedule.ts +5 -0
  245. package/src/tools/schedules/update-schedule.ts +5 -0
  246. package/src/tools/send-task.ts +6 -0
  247. package/src/tools/utils.ts +1 -1
  248. package/src/types.ts +30 -8
  249. package/src/utils/context-window.ts +1 -0
  250. package/src/utils/credentials.ts +8 -0
  251. package/src/utils/internal-ai/complete-structured.ts +8 -0
  252. package/src/utils/internal-ai/models.ts +5 -5
  253. package/src/utils/memory-raters.ts +3 -0
  254. package/src/workflows/engine.ts +28 -0
  255. package/src/workflows/recovery.ts +7 -4
  256. package/templates/schedules/daily-status-report/content.md +25 -12
  257. package/templates/schedules/daily-workflow-health-audit/content.md +1 -1
  258. package/templates/skills/swarm-extensions/content.md +9 -5
  259. package/dist/cli-4wppfkcm.js +0 -16384
  260. package/dist/cli-55qrx3q8.js +0 -6041
  261. package/dist/cli-cg4vnyh4.js +0 -305
  262. package/dist/cli-jyetr64j.js +0 -52
  263. package/dist/cli-pp9ax9pa.js +0 -520
  264. package/dist/cli-qa66qaf0.js +0 -6076
  265. package/dist/cli-qghpvca2.js +0 -590
  266. package/dist/cli-rembwgt8.js +0 -861
  267. package/dist/cli-tcvj7j2r.js +0 -1971
  268. package/dist/cli-vfcth11k.js +0 -36
  269. package/dist/cli-w1d1bgkk.js +0 -1422
  270. package/dist/cli-y6x0gqtg.js +0 -5977
  271. package/dist/es5-tj3tn4jj.js +0 -844
  272. package/dist/index-1kq5hw2p.js +0 -467
  273. package/dist/index-1p8wdstt.js +0 -7
  274. package/dist/index-8fdjzb2f.js +0 -100
  275. package/dist/index-bxmwzbgw.js +0 -365
  276. package/dist/index-nzerxd5z.js +0 -7
  277. package/dist/index-pyymnzat.js +0 -1168
  278. package/dist/index-qv6atbab.js +0 -619
  279. package/dist/index-rja32qa7.js +0 -801
  280. package/dist/index-tzg5srfp.js +0 -211
  281. package/dist/index-vhncw983.js +0 -90
  282. package/dist/index-xk036zsk.js +0 -859
  283. package/dist/index-zyazbzcb.js +0 -6
  284. package/dist/{claude-managed-setup-48dwg9bv.js → claude-managed-setup-f5pmsyrp.js} +3 -3
@@ -95,8 +95,12 @@ type:UpsertAssetKeyMappingInput
95
95
  type:WorkflowFilters
96
96
  type:WorkflowRunListOptions
97
97
  type:WorkflowRunPage
98
+ value:EXTENSION_AGENT_ROLE
99
+ value:ExtensionAgentAssignmentError
98
100
  value:KvTypeCollisionError
99
101
  value:MAX_EMPTY_POLLS
102
+ value:NOT_EXTENSION_AGENT_SQL
103
+ value:ReservedAgentRoleError
100
104
  value:SKILL_FILE_LIMITS
101
105
  value:UNATTRIBUTED_USER_ID
102
106
  value:__resetSqliteVecExtensionPathCacheForTests
@@ -196,6 +200,7 @@ value:emitBuiltInIntegrationConnectedOnce
196
200
  value:emitMcpServerConnectedTelemetry
197
201
  value:ensureSlackDelegationActivation
198
202
  value:ensureSlackRenderV2Activation
203
+ value:extensionAgentAssignmentError
199
204
  value:failPendingResumeIfUnclaimed
200
205
  value:failTask
201
206
  value:findCompletedTaskInThread
@@ -327,6 +332,7 @@ value:getRecentFailedTasks
327
332
  value:getRecentlyCancelledTasksForAgent
328
333
  value:getRecentlyFinishedWorkerTasks
329
334
  value:getRemainingCapacity
335
+ value:getResolvableDeferralOutcomes
330
336
  value:getResolvedConfig
331
337
  value:getRetryableSteps
332
338
  value:getRootTaskChain
@@ -419,6 +425,7 @@ value:installMcpServer
419
425
  value:installSkill
420
426
  value:installSystemDefaultSkillsForAgent
421
427
  value:isAgentEligibleForTask
428
+ value:isExtensionAgent
422
429
  value:isPendingSlackMessage
423
430
  value:isPoolAffinityEnforcementEnabled
424
431
  value:isSqliteVecAvailable
@@ -450,6 +457,7 @@ value:markInboxMessageDelegated
450
457
  value:markInboxMessageRead
451
458
  value:markInboxMessageResponded
452
459
  value:markKeyRateLimited
460
+ value:markSlackDeferralResolved
453
461
  value:markSlackRelayAttempted
454
462
  value:markSlackRelayDelivered
455
463
  value:markSlackTreeRendered
@@ -486,6 +494,7 @@ value:releaseStaleReviewingTasks
486
494
  value:releaseTask
487
495
  value:replaceTaskAttachment
488
496
  value:reserveSlackMessage
497
+ value:reservedRoleViolation
489
498
  value:resetEmptyPollCount
490
499
  value:resetOrphanedInProgressTasksForAgent
491
500
  value:resetPromptTemplateToDefault
@@ -184,16 +184,19 @@ describe("defer-task handler", () => {
184
184
 
185
185
  const stored = await getTaskById(task.id);
186
186
  expect(stored?.status).toBe("completed");
187
- // Human-facing output: one line, `Deferred until {date} {time} ({link}) -> {desc}`.
188
- // No requestedByUserId is set on this task, so it falls back to UTC and
187
+ expect(stored?.deferredAt).toBeTruthy();
188
+ // Human-facing output for a time-based defer: when it comes back, nothing
189
+ // else. No requestedByUserId is set here, so it falls back to UTC and
189
190
  // labels it explicitly.
190
- const shortId = schedule.id.slice(0, 8);
191
191
  expect(stored?.output).toMatch(
192
- /^Deferred until (today|tomorrow|\d{2}-\d{2}) \d{2}:\d{2}:\d{2} UTC \(/,
193
- );
194
- expect(stored?.output).toContain(
195
- `([${shortId}](https://app.agent-swarm.dev/schedules/${schedule.id})) -> deploy 42 is still running`,
192
+ /^Checking back (today|tomorrow|on \d{2}-\d{2}) at \d{2}:\d{2} UTC$/,
196
193
  );
194
+ // The agent's internal note, the checks, the schedule UUID and its link
195
+ // all stay out of the card — they live in the task log and the wake-up
196
+ // task's template.
197
+ expect(stored?.output).not.toContain("deploy 42 is still running");
198
+ expect(stored?.output).not.toContain(schedule.id);
199
+ expect(stored?.output).not.toContain("app.agent-swarm.dev");
197
200
  expect(stored?.output).not.toContain(schedule.nextRunAt!);
198
201
  expect(stored?.output).not.toContain("Checks:");
199
202
  expect(stored?.output).not.toContain("- smoke tests pass");
@@ -441,16 +444,15 @@ describe("defer-task handler", () => {
441
444
  )) as DeferTaskResult;
442
445
  expect(result.structuredContent.success).toBe(true);
443
446
  const stored = await getTaskById(task.id);
444
- const shortId = result.structuredContent.scheduleId!.slice(0, 8);
445
447
  expect(stored?.output).toMatch(
446
- /^Deferred until (today|tomorrow|\d{2}-\d{2}) \d{2}:\d{2}:\d{2} UTC \(/,
448
+ /^Checking back (today|tomorrow|on \d{2}-\d{2}) at \d{2}:\d{2} UTC$/,
447
449
  );
448
- expect(stored?.output).toContain(`([${shortId}](`);
449
- expect(stored?.output).toContain(") -> pending");
450
+ expect(stored?.output).not.toContain(result.structuredContent.scheduleId!);
451
+ expect(stored?.output).not.toContain("pending");
450
452
  expect(stored?.output).not.toContain(SUMMARY);
451
453
  });
452
454
 
453
- test("human-facing output strips a repeated Pending: prefix and caps a long note", async () => {
455
+ test("a long note never reaches the card, only the log and the wake-up task", async () => {
454
456
  const task = await startedTask("verbose note");
455
457
  const longNote = `Pending: ${"x".repeat(300)}\nsecond line is dropped`;
456
458
  const result = (await buildTool().handler(
@@ -460,10 +462,11 @@ describe("defer-task handler", () => {
460
462
 
461
463
  expect(result.structuredContent.success).toBe(true);
462
464
  const stored = await getTaskById(task.id);
463
- // No doubled "Pending: Pending:", no second line, capped with an ellipsis.
464
- expect(stored?.output).not.toContain("Pending: Pending:");
465
+ // The card answers "when", so there is nothing left to truncate: no note
466
+ // fragment, no ellipsis, no mid-sentence cut.
467
+ expect(stored?.output).not.toContain("x");
465
468
  expect(stored?.output).not.toContain("second line is dropped");
466
- expect(stored?.output).toContain(`-> ${"x".repeat(99)}…`);
469
+ expect(stored?.output).not.toContain("…");
467
470
 
468
471
  // The full untrimmed note still reaches the task log and the wake-up task.
469
472
  const logs = (await getLogsByTaskId(task.id)).filter(
@@ -489,7 +492,7 @@ describe("defer-task handler", () => {
489
492
  const stored = await getTaskById(task.id);
490
493
  // A real timezone is on file: no explicit "UTC" label on the time.
491
494
  expect(stored?.output).toMatch(
492
- /^Deferred until (today|tomorrow|\d{2}-\d{2}) \d{2}:\d{2}:\d{2} \(/,
495
+ /^Checking back (today|tomorrow|on \d{2}-\d{2}) at \d{2}:\d{2}$/,
493
496
  );
494
497
  });
495
498
 
@@ -562,4 +565,100 @@ describe("defer-task handler", () => {
562
565
  const stored = await getTaskById(task.id);
563
566
  expect(stored?.status).toBe("completed");
564
567
  });
568
+
569
+ test("a wakeOn defer names who it is waiting on, with the ceiling as the fallback", async () => {
570
+ const watchedOwner = (
571
+ await createAgent({
572
+ name: "Researcher",
573
+ description: "Agent whose task is watched",
574
+ role: "worker",
575
+ isLead: false,
576
+ status: "busy",
577
+ maxTasks: 1,
578
+ capabilities: [],
579
+ })
580
+ ).id;
581
+ const watched = await startedTask("the work being waited on", watchedOwner);
582
+ const task = await startedTask("waiting on a sibling");
583
+
584
+ const result = (await buildTool().handler(
585
+ {
586
+ taskId: task.id,
587
+ delayMs: 1_800_000,
588
+ summary: SUMMARY,
589
+ note: "internal handoff note that must not appear",
590
+ wakeOn: { event: "settled", taskIds: [watched.id] },
591
+ },
592
+ meta(),
593
+ )) as DeferTaskResult;
594
+
595
+ expect(result.structuredContent.success).toBe(true);
596
+ const stored = await getTaskById(task.id);
597
+ // Event-based AND time-based: `delayMs`/`runAt` is required even with
598
+ // wakeOn, so the ceiling is always the honest fallback.
599
+ expect(stored?.output).toMatch(
600
+ /^Waiting on Researcher — or (today|tomorrow|on \d{2}-\d{2}) at \d{2}:\d{2} UTC at the latest$/,
601
+ );
602
+ expect(stored?.output).not.toContain("internal handoff note");
603
+ expect(stored?.output).not.toContain(watched.id);
604
+ });
605
+
606
+ test("a wakeOn defer on unassigned tasks falls back to a count, not a UUID", async () => {
607
+ const watched = await createTaskExtended("unassigned watched work", { source: "mcp" });
608
+ const task = await startedTask("waiting on an unassigned task");
609
+
610
+ await buildTool().handler(
611
+ {
612
+ taskId: task.id,
613
+ delayMs: 60_000,
614
+ summary: SUMMARY,
615
+ note: "pending",
616
+ wakeOn: { event: "settled", taskIds: [watched.id] },
617
+ },
618
+ meta(),
619
+ );
620
+
621
+ const stored = await getTaskById(task.id);
622
+ expect(stored?.output).toStartWith("Waiting on 1 task — or ");
623
+ expect(stored?.output).not.toContain(watched.id);
624
+ });
625
+
626
+ test("the schedule keeps the delay that was actually requested", async () => {
627
+ const byDelay = await startedTask("delay provenance");
628
+ await buildTool().handler(
629
+ { taskId: byDelay.id, delayMs: 1_800_000, summary: SUMMARY, note: "pending" },
630
+ meta(),
631
+ );
632
+ const delaySchedule = (await schedulesForTask(byDelay.id))[0]!;
633
+ expect(delaySchedule.requestedDelayMs).toBe(1_800_000);
634
+ expect(delaySchedule.requestedRunAt).toBeUndefined();
635
+
636
+ const runAt = new Date(Date.now() + 3_600_000).toISOString();
637
+ const byRunAt = await startedTask("runAt provenance");
638
+ await buildTool().handler(
639
+ { taskId: byRunAt.id, runAt, summary: SUMMARY, note: "pending" },
640
+ meta(),
641
+ );
642
+ const runAtSchedule = (await schedulesForTask(byRunAt.id))[0]!;
643
+ expect(runAtSchedule.requestedRunAt).toBe(runAt);
644
+ expect(runAtSchedule.requestedDelayMs).toBeUndefined();
645
+ });
646
+
647
+ test("deferredAt marks the deferring task, and a plain completion leaves it unset", async () => {
648
+ const deferred = await startedTask("ends in a deferral");
649
+ await buildTool().handler(
650
+ { taskId: deferred.id, delayMs: 60_000, summary: SUMMARY, note: "pending" },
651
+ meta(),
652
+ );
653
+ expect((await getTaskById(deferred.id))?.deferredAt).toBeTruthy();
654
+
655
+ // Negative control: the same tag on a task that simply finished. A wake-up
656
+ // task inherits `deferred` from its schedule, so the tag alone cannot tell
657
+ // "parked" from "done" — only `deferredAt` can.
658
+ const finished = await startedTask("ends normally");
659
+ await completeTask(finished.id, "done", { addTags: ["deferred"] });
660
+ const storedFinished = await getTaskById(finished.id);
661
+ expect(storedFinished?.tags).toContain("deferred");
662
+ expect(storedFinished?.deferredAt).toBeUndefined();
663
+ });
565
664
  });
@@ -0,0 +1,276 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { chmod, mkdtemp, rename, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { checkProviderCredentials } from "../commands/provider-credentials";
6
+ import { createProviderAdapter } from "../providers";
7
+ import { checkDshCredentials, DshAdapter } from "../providers/dsh-adapter";
8
+ import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
9
+ import { DEFAULT_MODEL_TIER_MAP } from "../types";
10
+ import { getModelAwareCredentialVars } from "../utils/credentials";
11
+ import { resolveHarnessProvider } from "../utils/harness-provider";
12
+ import { DEFAULT_OPENROUTER_BASE_URL } from "../utils/openrouter-base-url";
13
+
14
+ const directories: string[] = [];
15
+ afterEach(async () => {
16
+ await Promise.all(
17
+ directories.splice(0).map((path) => rm(path, { recursive: true, force: true })),
18
+ );
19
+ });
20
+
21
+ async function fixture(mode = "success"): Promise<ProviderSessionConfig> {
22
+ const cwd = await mkdtemp(join(tmpdir(), "dsh-adapter-test-"));
23
+ directories.push(cwd);
24
+ const binary = join(cwd, "dsh");
25
+ await Bun.write(
26
+ binary,
27
+ `#!${process.execPath}
28
+ const args = process.argv.slice(2);
29
+ const patchPath = args[args.indexOf("--patch") + 1];
30
+ const patch = await Bun.file(patchPath).json();
31
+ const prompt = await Bun.stdin.text();
32
+ await Bun.write("invocation.json", JSON.stringify({ args, patchPath, patch, prompt, cwd: process.cwd() }));
33
+ const emit = (event) => console.log(JSON.stringify(event));
34
+ emit({ type: "session", sessionId: "dsh-test-session" });
35
+ const mode = process.env.DSH_TEST_MODE;
36
+ if (mode === "abort") { setInterval(() => {}, 1000); }
37
+ else {
38
+ if (mode === "malformed") console.log("bad json");
39
+ emit({ type: "status", phase: "turn_end", reason: { kind: mode === "failure" ? "error" : "completed" } });
40
+ if (mode !== "missing-final") {
41
+ // A split UTF-8 character plus a final line without a newline exercises both decoders.
42
+ const line = Buffer.from(JSON.stringify({ type: "final", text: "Done ✓" }));
43
+ const split = line.indexOf(Buffer.from("✓")) + 1;
44
+ process.stdout.write(line.subarray(0, split));
45
+ await Bun.sleep(10);
46
+ process.stdout.write(line.subarray(split));
47
+ }
48
+ if (mode === "failure") { console.error(process.env.DEEPSEEK_API_KEY); process.exitCode = 7; }
49
+ }
50
+ `,
51
+ );
52
+ await chmod(binary, 0o700);
53
+ return {
54
+ prompt: "--not-a-flag\nKeep $() and `quotes` literal.",
55
+ systemPrompt: "System instructions:\n!!js must remain text",
56
+ model: "deepseek-v4-pro",
57
+ role: "worker",
58
+ agentId: "agent-test",
59
+ taskId: "task-test",
60
+ apiUrl: "http://unused.invalid",
61
+ apiKey: "unused",
62
+ cwd,
63
+ logFile: join(cwd, "log.jsonl"),
64
+ env: {
65
+ DSH_BINARY: binary,
66
+ DEEPSEEK_API_KEY: "dsh-test-credential-value",
67
+ OPENROUTER_API_KEY: "",
68
+ OPENROUTER_BASE_URL: "",
69
+ DSH_TEST_MODE: mode,
70
+ },
71
+ };
72
+ }
73
+
74
+ describe("dsh harness", () => {
75
+ test("registry, model defaults, and credential dispatch select dsh", async () => {
76
+ expect(await createProviderAdapter("dsh")).toBeInstanceOf(DshAdapter);
77
+ expect(resolveHarnessProvider({ HARNESS_PROVIDER: "dsh" }, {})).toBe("dsh");
78
+ expect(DEFAULT_MODEL_TIER_MAP.dsh).toEqual(DEFAULT_MODEL_TIER_MAP.pi);
79
+ expect(checkDshCredentials({ OPENAI_API_KEY: "unrelated" }).ready).toBe(false);
80
+ expect(checkDshCredentials({ DEEPSEEK_API_KEY: " " }).ready).toBe(false);
81
+ expect(checkDshCredentials({}).missing).toEqual(["DEEPSEEK_API_KEY", "OPENROUTER_API_KEY"]);
82
+ expect((await checkProviderCredentials("dsh", { OPENROUTER_API_KEY: "present" })).ready).toBe(
83
+ true,
84
+ );
85
+ expect(getModelAwareCredentialVars("dsh", DEFAULT_MODEL_TIER_MAP.dsh.ultra)).toEqual([
86
+ "OPENROUTER_API_KEY",
87
+ ]);
88
+ expect(getModelAwareCredentialVars("dsh", "deepseek-v4-pro")).toEqual(["DEEPSEEK_API_KEY"]);
89
+ expect((await checkProviderCredentials("dsh", { DEEPSEEK_API_KEY: "present" })).ready).toBe(
90
+ true,
91
+ );
92
+ });
93
+
94
+ test("passes prompt on stdin, patches model/system, preserves cwd and cleans up", async () => {
95
+ const config = await fixture();
96
+ const session = await new DshAdapter().createSession(config);
97
+ const events: ProviderEvent[] = [];
98
+ session.onEvent((event) => events.push(event));
99
+ expect(await session.waitForCompletion()).toMatchObject({
100
+ exitCode: 0,
101
+ isError: false,
102
+ output: "Done ✓",
103
+ sessionId: "dsh-test-session",
104
+ });
105
+ const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
106
+ expect(invocation.prompt).toBe(config.prompt);
107
+ expect(invocation.cwd).toBe(config.cwd);
108
+ expect(invocation.args).toEqual([
109
+ "--profile",
110
+ "headless",
111
+ "--patch",
112
+ invocation.patchPath,
113
+ "--json",
114
+ "-",
115
+ ]);
116
+ expect(invocation.patch).toContainEqual({
117
+ id: "agent-default-model",
118
+ config: { provider: "deepseek-official", model: config.model },
119
+ });
120
+ expect(invocation.patch).toContainEqual({
121
+ id: "system-prompt",
122
+ config: { personaPrefix: config.systemPrompt },
123
+ });
124
+ expect(await Bun.file(invocation.patchPath).exists()).toBe(false);
125
+ expect(events).toContainEqual({
126
+ type: "session_init",
127
+ sessionId: "dsh-test-session",
128
+ provider: "dsh",
129
+ });
130
+ });
131
+
132
+ for (const model of [
133
+ DEFAULT_MODEL_TIER_MAP.dsh.regular,
134
+ DEFAULT_MODEL_TIER_MAP.dsh.ultra,
135
+ "openrouter/vendor/new-model",
136
+ ]) {
137
+ test(`routes ${model} through OpenRouter with only its key`, async () => {
138
+ const config = await fixture();
139
+ config.model = model;
140
+ config.env = {
141
+ ...config.env,
142
+ DEEPSEEK_API_KEY: "",
143
+ OPENROUTER_API_KEY: "openrouter-test-key",
144
+ };
145
+ const session = await new DshAdapter().createSession(config);
146
+ expect((await session.waitForCompletion()).isError).toBe(false);
147
+ const { patch } = await Bun.file(join(config.cwd, "invocation.json")).json();
148
+ const modelId = model.slice("openrouter/".length);
149
+ expect(patch).toContainEqual({
150
+ id: "agent-default-model",
151
+ config: { provider: "openrouter", model: modelId },
152
+ });
153
+ expect(patch).toContainEqual({
154
+ id: "llm-pi-ai",
155
+ config: {
156
+ providers: {
157
+ openrouter: {
158
+ apiKeyEnv: "OPENROUTER_API_KEY",
159
+ baseURL: DEFAULT_OPENROUTER_BASE_URL,
160
+ api: "openai-completions",
161
+ models: [{ id: modelId }],
162
+ },
163
+ },
164
+ },
165
+ });
166
+ expect(JSON.stringify(patch)).not.toContain("openrouter-test-key");
167
+ });
168
+ }
169
+
170
+ test("defaults to OpenRouter and honors its base URL when both keys exist", async () => {
171
+ const config = await fixture();
172
+ config.model = undefined;
173
+ config.env = {
174
+ ...config.env,
175
+ OPENROUTER_API_KEY: "openrouter-test-key",
176
+ OPENROUTER_BASE_URL: " https://gateway.example/proxy/v1/// ",
177
+ };
178
+ const session = await new DshAdapter().createSession(config);
179
+ expect((await session.waitForCompletion()).isError).toBe(false);
180
+ const { patch } = await Bun.file(join(config.cwd, "invocation.json")).json();
181
+ expect(patch[0].config).toEqual({
182
+ provider: "openrouter",
183
+ model: "deepseek/deepseek-v4.1-flash",
184
+ });
185
+ expect(patch[2].config.providers.openrouter.baseURL).toBe("https://gateway.example/proxy/v1");
186
+ });
187
+
188
+ test("bare models keep direct DeepSeek when both keys exist", async () => {
189
+ const config = await fixture();
190
+ config.env = { ...config.env, OPENROUTER_API_KEY: "openrouter-test-key" };
191
+ const session = await new DshAdapter().createSession(config);
192
+ expect((await session.waitForCompletion()).isError).toBe(false);
193
+ const { patch } = await Bun.file(join(config.cwd, "invocation.json")).json();
194
+ expect(patch[0].config.provider).toBe("deepseek-official");
195
+ expect(patch[2]).toEqual({ id: "llm-deepseek", config: { apiKeyEnv: "DEEPSEEK_API_KEY" } });
196
+ });
197
+
198
+ test("fails closed for absent or mismatched credentials and empty model IDs", async () => {
199
+ const config = await fixture();
200
+ config.env = { ...config.env, DEEPSEEK_API_KEY: "", OPENROUTER_API_KEY: "" };
201
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow(
202
+ "DEEPSEEK_API_KEY or OPENROUTER_API_KEY",
203
+ );
204
+ config.env.DEEPSEEK_API_KEY = "direct-test-key";
205
+ config.model = DEFAULT_MODEL_TIER_MAP.dsh.regular;
206
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow(
207
+ "requires OPENROUTER_API_KEY",
208
+ );
209
+ config.env = { ...config.env, DEEPSEEK_API_KEY: "", OPENROUTER_API_KEY: "openrouter-test-key" };
210
+ config.model = "deepseek-v4-pro";
211
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow(
212
+ "requires DEEPSEEK_API_KEY",
213
+ );
214
+ config.model = "openrouter/";
215
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow("requires a model ID");
216
+ expect(await Bun.file(join(config.cwd, "invocation.json")).exists()).toBe(false);
217
+ });
218
+
219
+ test("fails closed when only npx is installed", async () => {
220
+ const config = await fixture();
221
+ await rename(join(config.cwd, "dsh"), join(config.cwd, "npx"));
222
+ config.env = { ...config.env, DSH_BINARY: "", PATH: config.cwd };
223
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow(
224
+ "dsh CLI not found. Install @deepseek-ai/dsh@0.1.7-alpha.2",
225
+ );
226
+ expect(await Bun.file(join(config.cwd, "invocation.json")).exists()).toBe(false);
227
+ });
228
+
229
+ test("uses a preinstalled dsh from PATH without DSH_BINARY", async () => {
230
+ const config = await fixture();
231
+ config.env = { ...config.env, DSH_BINARY: "", PATH: config.cwd };
232
+ const session = await new DshAdapter().createSession(config);
233
+ expect((await session.waitForCompletion()).isError).toBe(false);
234
+ const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
235
+ expect(invocation.args[0]).toBe("--profile");
236
+ });
237
+
238
+ test("does not fall back when an explicit DSH_BINARY is missing", async () => {
239
+ const config = await fixture();
240
+ config.env = { ...config.env, DSH_BINARY: join(config.cwd, "missing"), PATH: config.cwd };
241
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow();
242
+ expect(await Bun.file(join(config.cwd, "invocation.json")).exists()).toBe(false);
243
+ });
244
+
245
+ for (const mode of ["failure", "missing-final", "malformed"]) {
246
+ test(`reports ${mode} as failure even with output or exit zero`, async () => {
247
+ const config = await fixture(mode);
248
+ const session = await new DshAdapter().createSession(config);
249
+ const events: ProviderEvent[] = [];
250
+ session.onEvent((event) => events.push(event));
251
+ const result = await session.waitForCompletion();
252
+ expect(result.isError).toBe(true);
253
+ expect(result.exitCode).toBe(mode === "failure" ? 7 : 1);
254
+ expect(JSON.stringify({ result, events })).not.toContain(config.env!.DEEPSEEK_API_KEY);
255
+ const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
256
+ expect(await Bun.file(invocation.patchPath).exists()).toBe(false);
257
+ });
258
+ }
259
+
260
+ test("abort terminates a running process and removes its patch", async () => {
261
+ const config = await fixture("abort");
262
+ const session = await new DshAdapter().createSession(config);
263
+ await new Promise<void>((resolve) =>
264
+ session.onEvent((event) => {
265
+ if (event.type === "session_init") resolve();
266
+ }),
267
+ );
268
+ await session.abort();
269
+ expect(await session.waitForCompletion()).toMatchObject({
270
+ isError: true,
271
+ failureReason: "dsh session aborted",
272
+ });
273
+ const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
274
+ expect(await Bun.file(invocation.patchPath).exists()).toBe(false);
275
+ });
276
+ });
@@ -85,11 +85,11 @@ describe("nightly report", () => {
85
85
  });
86
86
  expect(report.ok).toBe(true);
87
87
  expect(report.missingLegs).toEqual([]);
88
- expect(report.totalCostUsd).toBeCloseTo(0.0615, 6);
88
+ expect(report.totalCostUsd).toBeCloseTo(0.0738, 6);
89
89
  expect(report.warnings).toEqual([]);
90
90
  const markdown = nightlyMarkdown(report, [], DEFAULT_PROVIDERS);
91
91
  expect(markdown).toContain("## Nightly E2E: PASS");
92
- expect(markdown).toContain("5/5 harness legs passed");
92
+ expect(markdown).toContain("6/6 harness legs passed");
93
93
  expect(markdown).toContain("| claude | claude-model | PASS | 1 |");
94
94
  expect(markdown).not.toContain("### Warnings");
95
95
  });
@@ -159,7 +159,7 @@ describe("nightly report", () => {
159
159
  results: [contractResult(), harnessResult([leg("claude")]), harnessResult([failed])],
160
160
  });
161
161
  expect(report.ok).toBe(false);
162
- expect(report.missingLegs).toEqual(["claude-sdk", "pi", "opencode"]);
162
+ expect(report.missingLegs).toEqual(["claude-sdk", "pi", "opencode", "dsh"]);
163
163
  expect(report.warnings).toContain("pi: no result file from the leg.");
164
164
  expect(report.warnings).toContain("codex: needed 2 attempts.");
165
165
  const markdown = nightlyMarkdown(report, [], DEFAULT_PROVIDERS);
@@ -43,6 +43,7 @@ describe("e2e redaction", () => {
43
43
  const saved = { ...process.env };
44
44
  try {
45
45
  process.env.OPENROUTER_API_KEY = "example-sk-or-v1-known-value-1234";
46
+ process.env.DEEPSEEK_API_KEY = "deepseek-known-value-1234";
46
47
  process.env.CODEX_OAUTH = JSON.stringify({
47
48
  access: "access-token-value-1234",
48
49
  refresh: "refresh-token-value-1234",
@@ -51,6 +52,7 @@ describe("e2e redaction", () => {
51
52
  });
52
53
  const known = knownSecrets(["extra-secret-value"]);
53
54
  expect(known).toContain("example-sk-or-v1-known-value-1234");
55
+ expect(known).toContain("deepseek-known-value-1234");
54
56
  expect(known).toContain("access-token-value-1234");
55
57
  expect(known).toContain("refresh-token-value-1234");
56
58
  expect(known).toContain("extra-secret-value");
@@ -62,6 +62,12 @@ describe("swarm-config-guard: Configuration-page value validation", () => {
62
62
  expect(validateConfigValue("SLACK_THREAD_STEERING_MODE", "now")).toContain("must be one of");
63
63
  });
64
64
 
65
+ test("Slack transport is constrained", () => {
66
+ expect(validateConfigValue("SLACK_MODE", "socket")).toBeNull();
67
+ expect(validateConfigValue("SLACK_MODE", "http")).toBeNull();
68
+ expect(validateConfigValue("SLACK_MODE", "webhook")).toContain("must be one of");
69
+ });
70
+
65
71
  test("Slack reaction shortcode keys accept bare and colon-wrapped names", () => {
66
72
  const keys = [
67
73
  "SLACK_REACTION_ACCEPTED",