@desplega.ai/agent-swarm 1.173.0 → 2.1.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 (511) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-ccm9xrep.js → acp-adapter-kr4abrsk.js} +17 -16
  3. package/dist/{actions-sgeyv08k.js → actions-eh4g6w2r.js} +39 -34
  4. package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-7h7wdt8q.js} +8 -7
  5. package/dist/{app-km9qnv2f.js → app-7k8s9w5p.js} +18 -14
  6. package/dist/{assistant-5r8tcvvd.js → assistant-wswt2y7p.js} +40 -35
  7. package/dist/{boot-reembed-16rv9843.js → boot-reembed-9apm9qw6.js} +10 -9
  8. package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-whzbdthj.js} +10 -9
  9. package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-ndzmvmgt.js} +11 -10
  10. package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-e9sta0yt.js} +16 -13
  11. package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-4mqd3r6r.js} +19 -18
  12. package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-9wswb52m.js} +7 -5
  13. package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-tpe1effx.js} +6 -4
  14. package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-q49ghmh9.js} +12402 -11850
  15. package/dist/{cli-ne7smj1b.js → cli-01xvj1rq.js} +10 -10
  16. package/dist/{cli-nwrdx840.js → cli-035vekcw.js} +3 -3
  17. package/dist/{cli-9xf7fdtp.js → cli-0pd8hzhc.js} +29 -6
  18. package/dist/{cli-tmhsxhb8.js → cli-0veb92a3.js} +28 -6
  19. package/dist/{cli-c3rk1v0x.js → cli-10jesr25.js} +3 -3
  20. package/dist/{cli-6thk4jz3.js → cli-1y7yywmj.js} +6 -6
  21. package/dist/{cli-6r2f7edk.js → cli-20krdhz5.js} +47 -19
  22. package/dist/{cli-rfs4vddv.js → cli-29rja109.js} +39 -20
  23. package/dist/cli-2m1ktfzz.js +17795 -0
  24. package/dist/{cli-na2pxe4f.js → cli-2pwa0q4d.js} +15 -4
  25. package/dist/{cli-7a93cv8c.js → cli-4mengw46.js} +441 -43
  26. package/dist/{cli-94b48jhk.js → cli-5twdke5x.js} +16 -14
  27. package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
  28. package/dist/cli-6cvfxs8r.js +47 -0
  29. package/dist/{cli-w1apjecb.js → cli-711vwbxn.js} +2 -2
  30. package/dist/{cli-r4fqfpwf.js → cli-78vv7446.js} +50 -27
  31. package/dist/{cli-srqq3gc5.js → cli-7n6s5man.js} +5 -5
  32. package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
  33. package/dist/{cli-2bw3w47j.js → cli-8wwxw8a3.js} +1 -1
  34. package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
  35. package/dist/{cli-ywm1azg9.js → cli-9jzdj90z.js} +8 -4
  36. package/dist/cli-9traj3dn.js +638 -0
  37. package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
  38. package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
  39. package/dist/cli-cvv9k0hh.js +10143 -0
  40. package/dist/{cli-anzdk0z4.js → cli-d9286m9h.js} +2 -2
  41. package/dist/cli-e3g5mbkz.js +776 -0
  42. package/dist/cli-f527ce74.js +781 -0
  43. package/dist/{cli-gmdajfw5.js → cli-gxzkg1de.js} +5020 -1729
  44. package/dist/{cli-451ks6aw.js → cli-h1arqn31.js} +1 -1
  45. package/dist/cli-hjwmhcg1.js +10622 -0
  46. package/dist/{cli-jpc4r224.js → cli-hqr88ew6.js} +19 -3
  47. package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
  48. package/dist/{cli-cd0hv9nh.js → cli-j67mh2vg.js} +2 -2
  49. package/dist/cli-jta2eyzc.js +619 -0
  50. package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
  51. package/dist/cli-kceah1eh.js +92 -0
  52. package/dist/{cli-d603qf86.js → cli-ksphgh48.js} +10 -8
  53. package/dist/{cli-3nxdmjrc.js → cli-mgz4nbgg.js} +4 -4
  54. package/dist/{cli-saqyage0.js → cli-mz5as5ha.js} +179 -27
  55. package/dist/cli-n2wq4j7m.js +6985 -0
  56. package/dist/{cli-ppy4nnnh.js → cli-n8ndw8j7.js} +2 -2
  57. package/dist/{cli-1w0vj3gw.js → cli-naqzr17j.js} +7 -7
  58. package/dist/{cli-0a7mm3zp.js → cli-nm1p8jfr.js} +25 -21
  59. package/dist/{cli-wrh08zjd.js → cli-p3zbc1rw.js} +5 -18
  60. package/dist/{cli-hsem16hb.js → cli-pe05f8yk.js} +29 -6
  61. package/dist/{cli-5wpw4rab.js → cli-qmzsbkvy.js} +5 -5
  62. package/dist/{cli-bv2etw2y.js → cli-qnm4e9e4.js} +99 -590
  63. package/dist/{cli-1x7da0de.js → cli-qqy4q48b.js} +8 -8
  64. package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
  65. package/dist/{cli-5er5gjbm.js → cli-r35rw5de.js} +15 -15
  66. package/dist/{cli-v3zhnxsb.js → cli-r610p9bs.js} +2 -2
  67. package/dist/{cli-7jykv7ef.js → cli-rc6pf5kv.js} +1 -1
  68. package/dist/{cli-x119kdba.js → cli-rdxm77k3.js} +1 -1
  69. package/dist/cli-rhs9pz33.js +294 -0
  70. package/dist/{cli-0p3enk35.js → cli-s69nfyeh.js} +9 -1
  71. package/dist/{cli-0kxf81jj.js → cli-sawv63e8.js} +2 -2
  72. package/dist/{cli-e3pmyt39.js → cli-sg19gbf0.js} +1 -1
  73. package/dist/{cli-d161g69q.js → cli-shk7r5cs.js} +57 -6
  74. package/dist/{cli-7pz3b7x5.js → cli-sksetb2q.js} +36 -1
  75. package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
  76. package/dist/{cli-db3m6zdt.js → cli-t02z59f2.js} +1 -1
  77. package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
  78. package/dist/{cli-s8z6x4b4.js → cli-v7g551te.js} +225 -113
  79. package/dist/{cli-rfz2ve55.js → cli-vgqzp4em.js} +3341 -955
  80. package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
  81. package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
  82. package/dist/cli-wfnemr1t.js +86 -0
  83. package/dist/cli-wmgrkscj.js +23254 -0
  84. package/dist/{cli-qm1448f9.js → cli-wms6x9db.js} +8 -8
  85. package/dist/{cli-tadfgd0t.js → cli-x1r545x8.js} +33 -4
  86. package/dist/{cli-xc7p7s3g.js → cli-xczsgshn.js} +6 -6
  87. package/dist/{cli-fmhj1ehj.js → cli-y3167wt1.js} +882 -271
  88. package/dist/{cli-r0byd61h.js → cli-yjmqpxt3.js} +17 -16
  89. package/dist/{cli-81r1wjyv.js → cli-z780ec7c.js} +1 -1
  90. package/dist/{cli-phcqp1b9.js → cli-zm9t2vde.js} +2 -2
  91. package/dist/cli.js +34 -49
  92. package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-4z16v9n0.js} +15 -14
  93. package/dist/{codex-login-841kv5fk.js → codex-login-pkepy2j1.js} +3 -2
  94. package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-rvms9y2x.js} +15 -14
  95. package/dist/{commands-z8fq9qna.js → commands-2a9rtyat.js} +11 -10
  96. package/dist/{controller-hq5ggpcp.js → controller-3k3madbn.js} +5 -4
  97. package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-v9yn8zb9.js} +42 -37
  98. package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-98gbhyzn.js} +7 -6
  99. package/dist/{db-qfpq4bt7.js → db-eywwsnjw.js} +30 -9
  100. package/dist/{db-yyhqqrbf.js → db-ncypk9np.js} +12 -11
  101. package/dist/{devin-adapter-xgtj680j.js → devin-adapter-zeq8b9gz.js} +3 -2
  102. package/dist/{dispatcher-x0x7qfcq.js → dispatcher-w94kc575.js} +38 -33
  103. package/dist/{docker-f503k9p0.js → docker-b1e4cqab.js} +3 -2
  104. package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-47ztej36.js} +7 -6
  105. package/dist/{e2b-66xra4az.js → e2b-bbt8ey45.js} +4 -3
  106. package/dist/{e2b-xwhyj8cw.js → e2b-dp804sen.js} +4 -3
  107. package/dist/{event-bus-vjtv82gm.js → event-bus-j2jdg3qb.js} +3 -2
  108. package/dist/{external-channel-h1fjecwe.js → external-channel-zrkg8ar9.js} +11 -10
  109. package/dist/{google-generative-ai-szdxbpqg.js → google-generative-ai-g8db7x0f.js} +4 -2
  110. package/dist/{google-vertex-qzw3p36d.js → google-vertex-r2692q0r.js} +4 -2
  111. package/dist/{grok-adapter-e7mr287e.js → grok-adapter-s9ncp9vy.js} +18 -17
  112. package/dist/{handlers-znb9n7d4.js → handlers-kqftvjgw.js} +41 -36
  113. package/dist/{heartbeat-bm4qkn8d.js → heartbeat-9q96p8v9.js} +43 -38
  114. package/dist/{hook-254r4mza.js → hook-56qe10dp.js} +11 -23
  115. package/dist/{hook-xaa3q80s.js → hook-yy6rw42k.js} +12 -11
  116. package/dist/{http-3dgrgp8y.js → http-r2dnb8f8.js} +2411 -275
  117. package/dist/{index-fx3jqq1e.js → index-3gtmkhc6.js} +38 -33
  118. package/dist/{index-bdsqa799.js → index-3k5hkz7r.js} +43 -38
  119. package/dist/index-52j1aja4.js +3465 -0
  120. package/dist/{index-g2byveez.js → index-5e101240.js} +38 -33
  121. package/dist/{index-2ybk7khy.js → index-5wyfjc2c.js} +845 -683
  122. package/dist/index-9gw58n4k.js +193 -0
  123. package/dist/index-9j87mcm8.js +56305 -0
  124. package/dist/index-bsmcresk.js +79424 -0
  125. package/dist/index-mh85qhec.js +475 -0
  126. package/dist/{index-jd7sejm2.js → index-vxvhxxbn.js} +10 -9
  127. package/dist/index-we27kd31.js +12810 -0
  128. package/dist/{keepalive-6ywhpja1.js → keepalive-psj6j54c.js} +12 -11
  129. package/dist/{lead-cw45n21s.js → lead-hajz4688.js} +61 -56
  130. package/dist/{maintenance-ss1a1ge9.js → maintenance-ps02sb5m.js} +10 -9
  131. package/dist/{node-fkajaq96.js → node-mwb5r697.js} +2 -1
  132. package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-0284h93b.js} +11 -10
  133. package/dist/{onboard-8hzdhc1k.js → onboard-rbf6bg01.js} +11 -3
  134. package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-13dcatgq.js} +18 -13
  135. package/dist/{otel-impl-e6mnvc4v.js → otel-impl-zvqcbhp7.js} +242 -868
  136. package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-xxy47q4v.js} +244 -106
  137. package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-kefx9v3t.js} +12 -11
  138. package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-3m36rxm2.js} +11 -10
  139. package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-z76xv0hq.js} +12 -11
  140. package/dist/{render-v2-907cy6sa.js → render-v2-wz73bs25.js} +18 -14
  141. package/dist/{resume-42stcaz0.js → resume-k4wzge8w.js} +25 -21
  142. package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
  143. package/dist/{setup-adbc1c10.js → setup-2fp2pypp.js} +2 -2
  144. package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
  145. package/dist/{work-objects-wpj8ck95.js → work-objects-6k0ywz3m.js} +41 -36
  146. package/dist/{worker-bzdra9rf.js → worker-5v3rxsdh.js} +61 -56
  147. package/dist/{x-qfmygqqv.js → x-4mtdeym1.js} +3 -2
  148. package/openapi.json +1279 -329
  149. package/package.json +15 -15
  150. package/src/agentmail/handlers.ts +21 -11
  151. package/src/apps/definition.ts +16 -0
  152. package/src/azure-devops/api.ts +52 -2
  153. package/src/azure-devops/boards-sync.ts +50 -20
  154. package/src/azure-devops/index.ts +1 -0
  155. package/src/azure-devops/outbound.ts +95 -0
  156. package/src/be/boot-seeds.ts +11 -0
  157. package/src/be/config-refs.ts +60 -0
  158. package/src/be/db/model-profiles.ts +315 -0
  159. package/src/be/db/page-versions.ts +15 -0
  160. package/src/be/db/runtime.ts +35 -8
  161. package/src/be/db/skill-name-resolution.ts +36 -0
  162. package/src/be/db/tasks/read.ts +237 -0
  163. package/src/be/db/tasks/write.ts +7 -0
  164. package/src/be/db.ts +87 -36
  165. package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
  166. package/src/be/migrations/216_task_resolved_effort.sql +5 -0
  167. package/src/be/migrations/217_chat_sdk_state.sql +104 -0
  168. package/src/be/migrations/219_relabel_pi_context_compactions.sql +22 -0
  169. package/src/be/migrations/220_model_profiles.sql +137 -0
  170. package/src/be/model-catalog-store.ts +5 -1
  171. package/src/be/model-tier-resolution.ts +155 -10
  172. package/src/be/model-validation.ts +12 -2
  173. package/src/be/models-catalog.ts +26 -1
  174. package/src/be/modelsdev-cache.json +198 -63
  175. package/src/be/modelsdev-cache.ts +9 -1
  176. package/src/be/script-credential-broker.ts +39 -2
  177. package/src/be/scripts/run-saved.ts +1 -0
  178. package/src/be/scripts/typecheck.ts +22 -8
  179. package/src/be/secret-registry.ts +6 -2
  180. package/src/be/seed/runner.ts +13 -1
  181. package/src/be/seed/state-db.ts +5 -0
  182. package/src/be/seed/types.ts +6 -0
  183. package/src/be/seed-model-profiles.ts +41 -0
  184. package/src/be/seed-pricing.ts +19 -2
  185. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +368 -0
  186. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  187. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  188. package/src/be/seed-scripts/index.ts +9 -0
  189. package/src/be/seed-skills/bundled-files.generated.json +0 -18
  190. package/src/be/seed-skills/index.ts +104 -3
  191. package/src/be/swarm-config-guard.ts +53 -0
  192. package/src/cli.tsx +0 -16
  193. package/src/commands/onboard/non-interactive.ts +4 -2
  194. package/src/commands/provider-credentials.ts +52 -6
  195. package/src/commands/runner.ts +193 -71
  196. package/src/e2b/env.ts +5 -0
  197. package/src/extensions/catalog.generated.json +17 -0
  198. package/src/extensions/contract-types.generated.ts +1 -1
  199. package/src/extensions/contract.ts +45 -0
  200. package/src/extensions/dispatcher.ts +7 -0
  201. package/src/extensions/loader.ts +10 -5
  202. package/src/extensions/post-bridge.ts +40 -0
  203. package/src/github/handlers.ts +11 -4
  204. package/src/heartbeat/heartbeat.ts +2 -1
  205. package/src/heartbeat/templates.ts +8 -5
  206. package/src/hooks/hook.ts +0 -26
  207. package/src/http/agents.ts +49 -2
  208. package/src/http/all-routes.ts +1 -1
  209. package/src/http/apps.ts +30 -3
  210. package/src/http/config.ts +62 -19
  211. package/src/http/core.ts +8 -0
  212. package/src/http/index.ts +25 -3
  213. package/src/http/integrations.ts +88 -1
  214. package/src/http/mcp.ts +15 -5
  215. package/src/http/model-profiles.ts +303 -0
  216. package/src/http/page-proxy.ts +2 -4
  217. package/src/http/pages-public.ts +13 -7
  218. package/src/http/pages.ts +3 -13
  219. package/src/http/poll.ts +42 -71
  220. package/src/http/requested-by.ts +54 -0
  221. package/src/http/script-runs.ts +1 -1
  222. package/src/http/scripts.ts +26 -2
  223. package/src/http/stats.ts +1 -30
  224. package/src/http/tasks.ts +79 -5
  225. package/src/http/utils.ts +14 -0
  226. package/src/http/webhooks.ts +160 -3
  227. package/src/http/x.ts +1 -0
  228. package/src/integrations/chat-sdk/adapters.ts +129 -0
  229. package/src/integrations/chat-sdk/attachments.ts +232 -0
  230. package/src/integrations/chat-sdk/background.ts +36 -0
  231. package/src/integrations/chat-sdk/commands.ts +194 -0
  232. package/src/integrations/chat-sdk/config.ts +198 -0
  233. package/src/integrations/chat-sdk/discord-api.ts +63 -0
  234. package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
  235. package/src/integrations/chat-sdk/handlers.ts +28 -0
  236. package/src/integrations/chat-sdk/inbound.ts +426 -0
  237. package/src/integrations/chat-sdk/limits.ts +48 -0
  238. package/src/integrations/chat-sdk/logger.ts +81 -0
  239. package/src/integrations/chat-sdk/outbound.ts +123 -0
  240. package/src/integrations/chat-sdk/relay.ts +172 -0
  241. package/src/integrations/chat-sdk/router.ts +169 -0
  242. package/src/integrations/chat-sdk/runtime.ts +351 -0
  243. package/src/integrations/chat-sdk/schema.ts +245 -0
  244. package/src/integrations/chat-sdk/state.ts +243 -0
  245. package/src/integrations/chat-sdk/status.ts +244 -0
  246. package/src/integrations/chat-sdk/steering.ts +58 -0
  247. package/src/integrations/chat-sdk/teams-origin.ts +77 -0
  248. package/src/integrations/chat-sdk/telegram-api.ts +56 -0
  249. package/src/integrations/chat-sdk/templates.ts +16 -0
  250. package/src/integrations/chat-sdk/test-hooks.ts +37 -0
  251. package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
  252. package/src/integrations/chat-sdk/typing.ts +65 -0
  253. package/src/{artifact-sdk → pages}/feedback-overlay.ts +65 -6
  254. package/src/pages/version.ts +4 -5
  255. package/src/prompts/base-prompt.ts +111 -45
  256. package/src/providers/acp-target-catalog.ts +6 -0
  257. package/src/providers/acp-targets.ts +56 -3
  258. package/src/providers/amp-adapter.ts +1 -1
  259. package/src/providers/claude-adapter.ts +1 -1
  260. package/src/providers/claude-flag-settings.ts +17 -5
  261. package/src/providers/claude-managed-adapter.ts +1 -1
  262. package/src/providers/claude-session-events.ts +6 -1
  263. package/src/providers/dsh-adapter.ts +1 -1
  264. package/src/providers/opencode-adapter.ts +243 -26
  265. package/src/providers/opencode-swarm-plugin.ts +31 -0
  266. package/src/providers/pi-mono-adapter.ts +398 -153
  267. package/src/providers/pi-mono-extension.ts +17 -3
  268. package/src/providers/pricing-sources.md +33 -2
  269. package/src/providers/reasoning-effort.ts +37 -0
  270. package/src/providers/types.ts +6 -2
  271. package/src/rbac/legacy-policy.ts +16 -2
  272. package/src/rbac/permissions.ts +26 -0
  273. package/src/scheduler/deferred-task-waits.ts +18 -5
  274. package/src/scheduler/scheduler.ts +11 -3
  275. package/src/scripts-runtime/executors/quickjs-prelude.ts +75 -6
  276. package/src/scripts-runtime/executors/quickjs-runner.ts +85 -8
  277. package/src/scripts-runtime/executors/quickjs-web-shims.ts +1041 -0
  278. package/src/scripts-runtime/extract-args-schema.ts +9 -8
  279. package/src/scripts-runtime/loader.ts +19 -7
  280. package/src/scripts-runtime/quickjs-compat.ts +464 -0
  281. package/src/scripts-runtime/sdk-allowlist.ts +12 -6
  282. package/src/scripts-runtime/swarm-sdk.ts +0 -11
  283. package/src/scripts-runtime/types/stdlib.d.ts +39 -15
  284. package/src/scripts-runtime/types/swarm-extension.d.ts +66 -1
  285. package/src/scripts-runtime/types/swarm-sdk.d.ts +39 -15
  286. package/src/server.ts +62 -19
  287. package/src/slack/blocks.ts +4 -2
  288. package/src/slack/event-dedup.ts +1 -1
  289. package/src/slack/reaction-shortcode.ts +14 -0
  290. package/src/slack/render-v2.ts +12 -7
  291. package/src/slack/responses.ts +5 -4
  292. package/src/slack/thread-buffer.ts +41 -22
  293. package/src/tasks/additive-buffer.ts +4 -2
  294. package/src/tasks/context-key.ts +60 -1
  295. package/src/tasks/model-pin.ts +67 -0
  296. package/src/tasks/worker-follow-up.ts +15 -33
  297. package/src/tests/acp-adapter.test.ts +42 -0
  298. package/src/tests/agentmail-filters.test.ts +47 -0
  299. package/src/tests/api-drain-worker-e2e.test.ts +1 -6
  300. package/src/tests/apps-spike2.test.ts +35 -0
  301. package/src/tests/azure-devops-outbound.test.ts +420 -0
  302. package/src/tests/base-prompt.test.ts +58 -54
  303. package/src/tests/chat-sdk-commands.test.ts +191 -0
  304. package/src/tests/chat-sdk-config.test.ts +334 -0
  305. package/src/tests/chat-sdk-discord-api.test.ts +68 -0
  306. package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
  307. package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
  308. package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
  309. package/src/tests/chat-sdk-harness.ts +146 -0
  310. package/src/tests/chat-sdk-inbound.test.ts +363 -0
  311. package/src/tests/chat-sdk-relay.test.ts +470 -0
  312. package/src/tests/chat-sdk-reload.test.ts +128 -0
  313. package/src/tests/chat-sdk-shutdown.test.ts +220 -0
  314. package/src/tests/chat-sdk-slack.test.ts +268 -0
  315. package/src/tests/chat-sdk-state.test.ts +163 -0
  316. package/src/tests/chat-sdk-status-route.test.ts +382 -0
  317. package/src/tests/chat-sdk-teams.test.ts +277 -0
  318. package/src/tests/chat-sdk-tools.test.ts +520 -0
  319. package/src/tests/chat-sdk-typing.test.ts +120 -0
  320. package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
  321. package/src/tests/claude-adapter-binary.test.ts +9 -4
  322. package/src/tests/claude-flag-settings.test.ts +11 -3
  323. package/src/tests/claude-managed-adapter.test.ts +1 -1
  324. package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
  325. package/src/tests/claude-thinking-tokens.test.ts +57 -0
  326. package/src/tests/claude-worker-parity.test.ts +2 -7
  327. package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
  328. package/src/tests/config-refs.test.ts +66 -0
  329. package/src/tests/context-key.test.ts +19 -0
  330. package/src/tests/cors-allowlist.test.ts +25 -0
  331. package/src/tests/create-page-tool.test.ts +18 -0
  332. package/src/tests/credential-check.test.ts +139 -0
  333. package/src/tests/credentials.test.ts +21 -0
  334. package/src/tests/db-runtime.test.ts +61 -0
  335. package/src/tests/db-task-read-contract.test.ts +22 -0
  336. package/src/tests/db-task-write-contract.test.ts +11 -0
  337. package/src/tests/deferred-task-wake.test.ts +78 -1
  338. package/src/tests/docs-index-footprint.test.js +3 -3
  339. package/src/tests/dsh-adapter.test.ts +1 -1
  340. package/src/tests/enabled-capabilities.test.ts +20 -0
  341. package/src/tests/entrypoint-api-readiness.test.ts +14 -8
  342. package/src/tests/entrypoint-config-env-export.test.ts +5 -2
  343. package/src/tests/entrypoint-export-to-env.test.ts +78 -0
  344. package/src/tests/env-flag.test.ts +1 -0
  345. package/src/tests/explicit-model-validation.test.ts +105 -1
  346. package/src/tests/extensions-catalog.test.ts +1 -0
  347. package/src/tests/extensions-example-comms-adherence-gate.test.ts +413 -0
  348. package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
  349. package/src/tests/extensions-loader.test.ts +15 -0
  350. package/src/tests/extensions-post-bridge.test.ts +51 -0
  351. package/src/tests/fetch-resolved-env.test.ts +128 -0
  352. package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
  353. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
  354. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
  355. package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
  356. package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
  357. package/src/tests/github-handlers.test.ts +35 -0
  358. package/src/tests/harness-provider-resolution.test.ts +12 -0
  359. package/src/tests/heartbeat-checklist.test.ts +3 -2
  360. package/src/tests/heartbeat-jev-gate-credentials.test.ts +168 -0
  361. package/src/tests/heartbeat-jev-gate.test.ts +385 -0
  362. package/src/tests/hook-runtime-close.test.ts +0 -2
  363. package/src/tests/http-api-integration.test.ts +0 -25
  364. package/src/tests/migration-046-budgets.test.ts +17 -16
  365. package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
  366. package/src/tests/model-profile-backcompat.test.ts +327 -0
  367. package/src/tests/model-profile-providers.test.ts +302 -0
  368. package/src/tests/model-profile-runtime-secrets.test.ts +223 -0
  369. package/src/tests/model-profiles-api.test.ts +614 -0
  370. package/src/tests/model-tier-resolution.test.ts +93 -0
  371. package/src/tests/models-catalog.test.ts +42 -1
  372. package/src/tests/openai-compatible-providers.test.ts +134 -0
  373. package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
  374. package/src/tests/opencode-adapter.test.ts +216 -2
  375. package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
  376. package/src/tests/opencode-shell-env.test.ts +145 -0
  377. package/src/tests/openrouter-base-url.test.ts +101 -151
  378. package/src/tests/page-proxy.test.ts +1 -1
  379. package/src/tests/pages-browser-sdk.test.ts +76 -0
  380. package/src/tests/pages-feedback-keys.test.ts +121 -0
  381. package/src/tests/pages-feedback.test.ts +4 -1
  382. package/src/tests/paused-tasks-requested-by.test.ts +102 -0
  383. package/src/tests/pi-context-compaction.test.ts +193 -0
  384. package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
  385. package/src/tests/pi-mono-adapter.test.ts +385 -5
  386. package/src/tests/pi-shell-env.test.ts +170 -0
  387. package/src/tests/profile-claim.test.ts +269 -0
  388. package/src/tests/prompt-template-remaining.test.ts +35 -0
  389. package/src/tests/quickjs-compat.test.ts +213 -0
  390. package/src/tests/rbac-engine.test.ts +32 -3
  391. package/src/tests/runner-exit-after-tasks.test.ts +0 -4
  392. package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
  393. package/src/tests/script-connections.test.ts +6 -0
  394. package/src/tests/script-executor-conformance.test.ts +252 -1
  395. package/src/tests/script-executor-quickjs.test.ts +96 -0
  396. package/src/tests/script-runs-http.test.ts +89 -0
  397. package/src/tests/scripts-http.test.ts +217 -1
  398. package/src/tests/scripts-only-gating.test.ts +2 -3
  399. package/src/tests/scripts-runtime.test.ts +58 -40
  400. package/src/tests/secret-scrubber.test.ts +96 -0
  401. package/src/tests/seed-model-profiles.test.ts +174 -0
  402. package/src/tests/seed-scripts.test.ts +2 -2
  403. package/src/tests/seed-skills-bundled-files.test.ts +45 -53
  404. package/src/tests/seed-skills-retire.test.ts +182 -0
  405. package/src/tests/send-task-requester-gate.test.ts +58 -0
  406. package/src/tests/session-cost-recompute-unit.test.ts +46 -0
  407. package/src/tests/skill-get-name-resolution.test.ts +137 -0
  408. package/src/tests/skill-invocations.test.ts +4 -2
  409. package/src/tests/slack-blocks.test.ts +36 -0
  410. package/src/tests/slack-inline-output.test.ts +14 -0
  411. package/src/tests/slack-reaction-config.test.ts +20 -0
  412. package/src/tests/slack-render-v2.test.ts +37 -0
  413. package/src/tests/slack-thread-buffer.test.ts +98 -1
  414. package/src/tests/sonnet-5-5-registry.test.ts +9 -1
  415. package/src/tests/swarm-config-export-to-env.test.ts +267 -0
  416. package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
  417. package/src/tests/swarm-diff.test.ts +1 -1
  418. package/src/tests/task-model-effort-rerun.test.ts +164 -0
  419. package/src/tests/task-session-prompts.test.ts +132 -0
  420. package/src/tests/task-tool-manifest.test.ts +14 -0
  421. package/src/tests/task-tools-ctx.test.ts +2 -0
  422. package/src/tests/tool-annotations.test.ts +6 -6
  423. package/src/tests/ui-logs-parser.test.ts +43 -0
  424. package/src/tests/used-by-route.test.ts +6 -0
  425. package/src/tests/vercel-ai-gateway.test.ts +149 -0
  426. package/src/tests/workflow-llm.test.ts +61 -0
  427. package/src/tests/workflow-swarm-script.test.ts +38 -0
  428. package/src/tests/workflow-wait-filter.test.ts +5 -3
  429. package/src/tools/chat-access.ts +41 -0
  430. package/src/tools/chat-open-dm.ts +59 -0
  431. package/src/tools/chat-post.ts +74 -0
  432. package/src/tools/chat-read-thread.ts +100 -0
  433. package/src/tools/chat-reply.ts +75 -0
  434. package/src/tools/create-page.ts +3 -13
  435. package/src/tools/model-catalog/index.ts +2 -1
  436. package/src/tools/model-profiles/list-model-profiles.ts +54 -0
  437. package/src/tools/register-chat-channel.ts +139 -0
  438. package/src/tools/request-human-input.ts +11 -3
  439. package/src/tools/script-apis.ts +5 -1
  440. package/src/tools/script-upsert.ts +4 -1
  441. package/src/tools/send-task.ts +48 -1
  442. package/src/tools/skills/skill-get.ts +17 -9
  443. package/src/tools/swarm-config/get-config.ts +2 -1
  444. package/src/tools/swarm-config/list-config.ts +2 -1
  445. package/src/tools/swarm-config/set-config.ts +16 -3
  446. package/src/tools/system-one-decide.ts +104 -0
  447. package/src/tools/task-action.ts +22 -1
  448. package/src/tools/tool-config.ts +14 -6
  449. package/src/types.ts +48 -3
  450. package/src/utils/config-env-export.ts +74 -0
  451. package/src/utils/credentials.test.ts +92 -0
  452. package/src/utils/credentials.ts +52 -4
  453. package/src/utils/harness-provider.ts +15 -6
  454. package/src/utils/runtime-model-catalog.ts +11 -1
  455. package/src/utils/secret-scrubber.ts +86 -10
  456. package/src/utils/task-tool-manifest.ts +19 -2
  457. package/src/utils/vcs-repo-match.ts +29 -0
  458. package/src/workflows/executors/raw-llm.ts +8 -3
  459. package/src/workflows/executors/swarm-script.ts +1 -0
  460. package/src/workflows/executors/system-one-decision.ts +21 -11
  461. package/src/workflows/executors/workflow-llm.ts +22 -0
  462. package/src/workflows/index.ts +21 -12
  463. package/templates/extensions/README.md +1 -0
  464. package/templates/extensions/comms-adherence-gate/README.md +88 -0
  465. package/templates/extensions/comms-adherence-gate/hooks.ts +489 -0
  466. package/templates/extensions/comms-adherence-gate/manifest.json +8 -0
  467. package/templates/skills/agent-browser/SKILL.md +1 -1
  468. package/templates/skills/agent-browser/content.md +1 -1
  469. package/templates/skills/apps/SKILL.md +8 -1
  470. package/templates/skills/apps/content.md +8 -1
  471. package/templates/skills/chat-sdk/config.json +14 -0
  472. package/templates/skills/chat-sdk/content.md +84 -0
  473. package/templates/skills/composio-gmail/content.md +7 -0
  474. package/templates/skills/composio-google-docs/content.md +1 -1
  475. package/templates/skills/email-domain-health/config.json +14 -0
  476. package/templates/skills/email-domain-health/content.md +131 -0
  477. package/templates/skills/kv-storage/content.md +2 -2
  478. package/templates/skills/pages/content.md +8 -10
  479. package/templates/skills/swarm-extensions/content.md +2 -0
  480. package/templates/skills/swarm-scripts/SKILL.md +4 -2
  481. package/templates/skills/swarm-scripts/config.json +1 -1
  482. package/templates/skills/swarm-scripts/content.md +4 -2
  483. package/templates/skills/workflow-iterate/content.md +38 -0
  484. package/dist/artifact-eczsknc3.js +0 -3843
  485. package/dist/cli-42qhyvwx.js +0 -33
  486. package/dist/cli-bd957dgb.js +0 -153
  487. package/dist/cli-q51yprfq.js +0 -1104
  488. package/dist/cli-t1ze64h3.js +0 -811
  489. package/dist/cli-tcc4wd5d.js +0 -56471
  490. package/dist/index-9ch1n761.js +0 -1102
  491. package/src/artifact-sdk/index.ts +0 -2
  492. package/src/artifact-sdk/localtunnel.d.ts +0 -20
  493. package/src/artifact-sdk/port.ts +0 -12
  494. package/src/artifact-sdk/server.ts +0 -183
  495. package/src/artifact-sdk/tunnel.ts +0 -19
  496. package/src/commands/artifact.ts +0 -249
  497. package/src/http/ecosystem.ts +0 -77
  498. package/src/tests/artifact-commands.test.ts +0 -92
  499. package/src/tests/artifact-sdk.test.ts +0 -793
  500. package/src/tests/register-service-security.test.ts +0 -194
  501. package/src/tools/list-services.ts +0 -107
  502. package/src/tools/register-service.ts +0 -177
  503. package/src/tools/unregister-service.ts +0 -73
  504. package/src/tools/update-service-status.ts +0 -96
  505. package/templates/skills/artifacts/config.json +0 -14
  506. package/templates/skills/artifacts/content.md +0 -344
  507. package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
  508. package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
  509. package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
  510. package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
  511. /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
@@ -15,6 +15,7 @@ import {
15
15
  markdownToSlack,
16
16
  splitSlackSectionText,
17
17
  } from "./blocks";
18
+ import { completedStatusIcon } from "./reaction-shortcode";
18
19
  import { getSlackOutputAttachments } from "./task-attachments";
19
20
  import { slackTaskOutput } from "./task-output";
20
21
 
@@ -67,7 +68,7 @@ export function formatInlineCompletionOutputChunks(opts: {
67
68
  taskId: string;
68
69
  output: string;
69
70
  }): string[] {
70
- const header = `✅ *${opts.agentName}* completed with output (${getTaskLink(opts.taskId)}):\n\n`;
71
+ const header = `${completedStatusIcon()} *${opts.agentName}* completed with output (${getTaskLink(opts.taskId)}):\n\n`;
71
72
  const slackOutput = markdownToSlack(opts.output.trim());
72
73
  return splitSlackSectionText(header + slackOutput);
73
74
  }
@@ -169,7 +170,7 @@ export async function sendTaskResponse(task: AgentTask): Promise<boolean> {
169
170
  thread_ts: task.slackThreadTs,
170
171
  text:
171
172
  task.slackReplySent || i > 0
172
- ? `✅ ${agentName} completed${i > 0 ? ` (continued ${i + 1}/${blockBatches.length})` : ""}`
173
+ ? `${completedStatusIcon()} ${agentName} completed${i > 0 ? ` (continued ${i + 1}/${blockBatches.length})` : ""}`
173
174
  : body,
174
175
  username: getAgentDisplayName(agent),
175
176
  icon_emoji: getAgentEmoji(agent),
@@ -324,7 +325,7 @@ export async function updateToFinal(
324
325
  };
325
326
  completionBlockBatches = buildCompletedBlockBatches(completionOpts);
326
327
  blocks = completionBlockBatches[0] ?? buildCompletedBlocks(completionOpts);
327
- text = task.slackReplySent ? `✅ ${agentName} completed` : body;
328
+ text = task.slackReplySent ? `${completedStatusIcon()} ${agentName} completed` : body;
328
329
  } else if (task.status === "cancelled") {
329
330
  blocks = buildCancelledBlocks({ agentName, taskId: task.id });
330
331
  text = "Task cancelled";
@@ -351,7 +352,7 @@ export async function updateToFinal(
351
352
  await sendWithPersona(app.client, {
352
353
  channel: task.slackChannelId,
353
354
  thread_ts: task.slackThreadTs ?? messageTs,
354
- text: `✅ ${agentName} completed (continued ${i + 1}/${completionBlockBatches.length})`,
355
+ text: `${completedStatusIcon()} ${agentName} completed (continued ${i + 1}/${completionBlockBatches.length})`,
355
356
  username: getAgentDisplayName(agent),
356
357
  icon_emoji: getAgentEmoji(agent),
357
358
  blocks: completionBlockBatches[i],
@@ -7,6 +7,9 @@ import {
7
7
  import { findUserByExternalId } from "../be/users";
8
8
  import { createAdditiveBuffer } from "../tasks/additive-buffer";
9
9
  import { slackContextKey } from "../tasks/context-key";
10
+ import { TaskCreationBlockedError } from "../tasks/errors";
11
+ import { scrubSecrets } from "../utils/secret-scrubber";
12
+ import { ackSlackMessage } from "./ack";
10
13
  import { getSlackApp } from "./app";
11
14
  import { buildBufferFlushBlocks } from "./blocks";
12
15
  import { resolveSlackUserId, rewriteSlackMentions } from "./enrich";
@@ -286,28 +289,44 @@ async function slackFlush(
286
289
  const dependsOn = !immediate && latestActiveTask ? [latestActiveTask.id] : undefined;
287
290
 
288
291
  const mostRecentTask = await getMostRecentTaskInThread(channelId, threadTs);
289
- const { task, unattached } = await createSlackTaskWithFiles(
290
- fullDescription,
291
- {
292
- agentId: lead?.id,
293
- routingReason:
294
- lead && mostRecentTask?.agentId === lead.id ? "continuity" : lead ? "skill" : undefined,
295
- routingSource: lead ? "engine_default" : undefined,
296
- source: "slack",
297
- slackChannelId: channelId,
298
- slackThreadTs: threadTs,
299
- slackTriggerMessageTs: items.at(-1)!.ts,
300
- slackUserId: originalRequesterId,
301
- requestedByUserId,
302
- dependsOn,
303
- parentTaskId: mostRecentTask?.id,
304
- // A human message in the thread: never answer it in the latest thread
305
- // task's structured-output contract.
306
- inheritParentOutputSchema: false,
307
- contextKey: slackContextKey({ channelId, threadTs }),
308
- },
309
- inbound,
310
- );
292
+ let created: Awaited<ReturnType<typeof createSlackTaskWithFiles>>;
293
+ try {
294
+ created = await createSlackTaskWithFiles(
295
+ fullDescription,
296
+ {
297
+ agentId: lead?.id,
298
+ routingReason:
299
+ lead && mostRecentTask?.agentId === lead.id ? "continuity" : lead ? "skill" : undefined,
300
+ routingSource: lead ? "engine_default" : undefined,
301
+ source: "slack",
302
+ slackChannelId: channelId,
303
+ slackThreadTs: threadTs,
304
+ slackTriggerMessageTs: items.at(-1)!.ts,
305
+ slackUserId: originalRequesterId,
306
+ requestedByUserId,
307
+ dependsOn,
308
+ parentTaskId: mostRecentTask?.id,
309
+ // A human message in the thread: never answer it in the latest thread
310
+ // task's structured-output contract.
311
+ inheritParentOutputSchema: false,
312
+ contextKey: slackContextKey({ channelId, threadTs }),
313
+ },
314
+ inbound,
315
+ );
316
+ } catch (error) {
317
+ if (!(error instanceof TaskCreationBlockedError)) throw error;
318
+
319
+ console.info(
320
+ "[Slack] pre.task.create blocked:",
321
+ error.extension.name,
322
+ scrubSecrets(error.reason),
323
+ );
324
+ if (app) {
325
+ await ackSlackMessage(app.client, channelId, items.at(-1)!.ts, "no_entry");
326
+ }
327
+ return;
328
+ }
329
+ const { task, unattached } = created;
311
330
 
312
331
  if (app) await notifySlackFileFailures(app.client, channelId, threadTs, unattached);
313
332
 
@@ -22,8 +22,10 @@
22
22
  * the underlying context becomes irrelevant — e.g. user cancels).
23
23
  *
24
24
  * Concurrency: single-process, single-event-loop. No cross-instance locking.
25
- * Flush callbacks run sequentially per key (the buffer is cleared BEFORE the
26
- * callback fires, so re-enqueues during `onFlush` create a fresh buffer).
25
+ * The buffer is cleared BEFORE the callback fires, so re-enqueues during
26
+ * `onFlush` create a fresh buffer, and its flush can start while the earlier
27
+ * `onFlush` for the same key still runs. A caller that needs per-key order
28
+ * must serialize inside `onFlush`.
27
29
  */
28
30
 
29
31
  export type BufferFlushReason = "timer" | "manual";
@@ -14,6 +14,9 @@
14
14
  * Key schema:
15
15
  * task:slack:{channelId}:{threadTs}
16
16
  * task:agentmail:{threadId}
17
+ * task:chat-sdk:{adapter}:{encodeURIComponent(platformThreadId)}
18
+ * (Chat SDK thread ids carry `:`, so the
19
+ * part after the adapter is URI-encoded)
17
20
  * task:trackers:github:{owner}:{repo}:{issue|pr}:{number}
18
21
  * task:trackers:gitlab:{projectId}:{mr|issue}:{iid}
19
22
  * task:trackers:azure-devops:{repositoryId}:pr:{pullRequestId}
@@ -39,13 +42,20 @@
39
42
 
40
43
  const SEPARATOR = ":";
41
44
 
42
- export type ContextKeyFamily = "slack" | "agentmail" | "trackers" | "schedule" | "workflow";
45
+ export type ContextKeyFamily =
46
+ | "slack"
47
+ | "agentmail"
48
+ | "chat-sdk"
49
+ | "trackers"
50
+ | "schedule"
51
+ | "workflow";
43
52
 
44
53
  export type TrackerProvider = "github" | "gitlab" | "azure-devops" | "linear" | "jira";
45
54
 
46
55
  export type ParsedContextKey =
47
56
  | { family: "slack"; parts: { channelId: string; threadTs: string } }
48
57
  | { family: "agentmail"; parts: { threadId: string } }
58
+ | { family: "chat-sdk"; parts: { adapter: string; threadId: string } }
49
59
  | {
50
60
  family: "trackers";
51
61
  subFamily: "github";
@@ -101,6 +111,42 @@ export function slackContextKey(input: { channelId: string; threadTs: string }):
101
111
  return ["task", "slack", channelId, threadTs].join(SEPARATOR);
102
112
  }
103
113
 
114
+ /**
115
+ * Chat SDK thread ids are `<adapter>:<platform id>` and the platform id itself
116
+ * may contain `:` (Telegram `telegram:<chatId>:<topicId>`, Slack
117
+ * `slack:<channel>:<ts>`). The adapter stays a plain key part; the rest is
118
+ * URI-encoded so the key keeps a fixed part count. `parseContextKey` returns
119
+ * the original thread id in `parts.threadId`.
120
+ */
121
+ export function chatSdkContextKey(threadId: string): string {
122
+ const raw = typeof threadId === "string" ? threadId : "";
123
+ const separatorIndex = raw.indexOf(SEPARATOR);
124
+ if (separatorIndex <= 0 || separatorIndex === raw.length - 1) {
125
+ throw new Error(
126
+ `context-key: chat-sdk thread id must be "<adapter>:<id>" (got ${JSON.stringify(threadId)})`,
127
+ );
128
+ }
129
+ const adapter = assertSafePart(raw.slice(0, separatorIndex), "adapter");
130
+ const rest = encodeURIComponent(raw.slice(separatorIndex + 1));
131
+ return ["task", "chat-sdk", adapter, rest].join(SEPARATOR);
132
+ }
133
+
134
+ /**
135
+ * The Chat SDK thread a contextKey points at, or null when the key is missing,
136
+ * malformed, or of another family. Never throws.
137
+ */
138
+ export function chatThreadOfContextKey(
139
+ key: string | null | undefined,
140
+ ): { adapter: string; threadId: string } | null {
141
+ if (!key) return null;
142
+ try {
143
+ const parsed = parseContextKey(key);
144
+ return parsed.family === "chat-sdk" ? parsed.parts : null;
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
104
150
  export function agentmailContextKey(input: { threadId: string }): string {
105
151
  const threadId = assertSafePart(input.threadId, "threadId");
106
152
  return ["task", "agentmail", threadId].join(SEPARATOR);
@@ -255,6 +301,19 @@ export function parseContextKey(key: string): ParsedContextKey {
255
301
  }
256
302
  return { family: "agentmail", parts: { threadId: parts[2] as string } };
257
303
  }
304
+ case "chat-sdk": {
305
+ if (parts.length !== 4 || !parts[2] || !parts[3]) {
306
+ throw new Error(`context-key: malformed chat-sdk key: ${JSON.stringify(key)}`);
307
+ }
308
+ const adapter = parts[2];
309
+ let rest: string;
310
+ try {
311
+ rest = decodeURIComponent(parts[3]);
312
+ } catch {
313
+ throw new Error(`context-key: malformed chat-sdk key: ${JSON.stringify(key)}`);
314
+ }
315
+ return { family: "chat-sdk", parts: { adapter, threadId: `${adapter}:${rest}` } };
316
+ }
258
317
  case "schedule": {
259
318
  if (parts.length !== 3) {
260
319
  throw new Error(`context-key: malformed schedule key: ${JSON.stringify(key)}`);
@@ -0,0 +1,67 @@
1
+ import type { AgentTask } from "../types";
2
+
3
+ /** Missing fields read as unset, so a bare `{ model }` is its own pin. */
4
+ export type TaskModelPinFields = {
5
+ [K in
6
+ | "model"
7
+ | "modelSource"
8
+ | "modelAlias"
9
+ | "modelTier"
10
+ | "resolvedModel"
11
+ | "claudeSessionId"]?: AgentTask[K] | null;
12
+ };
13
+
14
+ /**
15
+ * The task's own `model` pin, or undefined when the task has none.
16
+ *
17
+ * `agent_tasks.model` is not always the creator's pin: the worker reports the
18
+ * model it actually ran on `PUT /api/tasks/{id}/session`, which overwrites the
19
+ * column. So the pin is read from the claim-time record instead:
20
+ * - `modelSource = "model"`: the task's own `model` won at claim. A `latest:`
21
+ * alias survives only in `modelAlias` (the session report replaced `model`
22
+ * with the concrete id), so the alias is the pin.
23
+ * - `modelSource = "fallback:cli-unsupported:model"`: the task's own
24
+ * `latest:` alias won, then the claiming CLI rejected what it resolved to.
25
+ * The alias in `modelAlias` is the pin, with or without a `modelTier`.
26
+ * - `modelSource = "fallback:cli-unsupported"` (a tier layer's alias fell
27
+ * back) is not a pin. Rows written before the `:model` variant existed are
28
+ * ambiguous; one with a `modelAlias` and no `modelTier` can only have come
29
+ * from a `model` alias, so its alias is the pin. With a `modelTier` it stays
30
+ * ambiguous and only the tier counts.
31
+ * - no claim record and no session: `model` is still the creator's value.
32
+ * Anything else (a tier layer won, or `model` is just the session report) is
33
+ * not a pin: re-running the row must resolve the model again, from the agent's
34
+ * current config, instead of replaying the model of the previous run.
35
+ */
36
+ export function taskModelPin(task: TaskModelPinFields): string | undefined {
37
+ if (task.modelSource === "model") return task.modelAlias ?? task.model ?? undefined;
38
+ if (task.modelSource === "fallback:cli-unsupported:model" && task.modelAlias) {
39
+ return task.modelAlias;
40
+ }
41
+ if (task.modelSource === "fallback:cli-unsupported" && task.modelAlias && !task.modelTier) {
42
+ return task.modelAlias;
43
+ }
44
+ if (!task.modelSource && !task.resolvedModel && !task.claudeSessionId) {
45
+ return task.model ?? undefined;
46
+ }
47
+ return undefined;
48
+ }
49
+
50
+ /**
51
+ * The model fields a re-run of the SAME task row (paused-task resume) passes to
52
+ * the spawn: the pin and the claim record that resolved it, or nothing, so the
53
+ * worker resolves the model from the agent's current config.
54
+ */
55
+ export function resumedTaskModelSelection(task: TaskModelPinFields): {
56
+ model: string | undefined;
57
+ resolvedModel: string | undefined;
58
+ modelSource: string | undefined;
59
+ } {
60
+ const pin = taskModelPin(task);
61
+ if (!pin) return { model: undefined, resolvedModel: undefined, modelSource: undefined };
62
+ return {
63
+ model: pin,
64
+ resolvedModel: task.resolvedModel ?? undefined,
65
+ modelSource: task.modelSource ?? undefined,
66
+ };
67
+ }
@@ -32,6 +32,7 @@ import {
32
32
  } from "../types";
33
33
  import { isEnvFlagEnabled } from "../utils/env-flag";
34
34
  import { taskAttachmentDisplayUrl } from "../utils/task-attachment-links";
35
+ import { taskModelPin } from "./model-pin";
35
36
  import { createTaskWithSiblingAwareness } from "./sibling-awareness";
36
37
  // Side-effect import: registers task lifecycle templates in the in-memory registry.
37
38
  import "../tools/templates";
@@ -165,23 +166,8 @@ export function getNextResumeGeneration(parent: Pick<AgentTask, "tags">): number
165
166
  * reboot retry) must carry: the parent's portable `modelTier`, plus its
166
167
  * concrete `model` only when that `model` was a task-level pin.
167
168
  *
168
- * `agent_tasks.model` is not always the creator's pin: the worker reports the
169
- * model it actually ran on `POST /api/tasks/{id}/session`, which overwrites the
170
- * column. So the pin is read from the claim-time record instead:
171
- * - `modelSource = "model"`: the task's own `model` won at claim. A `latest:`
172
- * alias survives only in `modelAlias` (the session report replaced `model`
173
- * with the concrete id), so the alias is what travels.
174
- * - `modelSource = "fallback:cli-unsupported:model"`: the task's own
175
- * `latest:` alias won, then the claiming CLI rejected what it resolved to.
176
- * The alias in `modelAlias` travels, with or without a `modelTier`.
177
- * - `modelSource = "fallback:cli-unsupported"` (a tier layer's alias fell
178
- * back) is not a pin. Rows written before the `:model` variant existed are
179
- * ambiguous; one with a `modelAlias` and no `modelTier` can only have come
180
- * from a `model` alias, so its alias travels. With a `modelTier` it stays
181
- * ambiguous and only the tier travels.
182
- * - no claim record and no session: `model` is still the creator's value.
183
- * Anything else (a tier layer won, or `model` is just the session report) is
184
- * not a pin and is left off, so the resume resolves on its claiming worker.
169
+ * The pin rules live in `taskModelPin` (`./model-pin`); anything that is not
170
+ * a pin is left off, so the resume resolves on its claiming worker.
185
171
  *
186
172
  * `resolvedModel`, `modelSource` and `modelAlias` themselves are NOT copied:
187
173
  * claim time writes all three together for the task being claimed, and the
@@ -193,26 +179,22 @@ export function getNextResumeGeneration(parent: Pick<AgentTask, "tags">): number
193
179
  export function inheritedModelSelection(
194
180
  parent: Pick<
195
181
  AgentTask,
196
- "model" | "modelTier" | "modelSource" | "modelAlias" | "resolvedModel" | "claudeSessionId"
182
+ | "model"
183
+ | "modelTier"
184
+ | "modelSource"
185
+ | "modelAlias"
186
+ | "resolvedModel"
187
+ | "claudeSessionId"
188
+ | "profile"
197
189
  >,
198
- ): Pick<CreateTaskOptions, "model" | "modelTier"> {
199
- let model: string | undefined;
200
- if (parent.modelSource === "model") {
201
- model = parent.modelAlias ?? parent.model;
202
- } else if (parent.modelSource === "fallback:cli-unsupported:model" && parent.modelAlias) {
203
- model = parent.modelAlias;
204
- } else if (
205
- parent.modelSource === "fallback:cli-unsupported" &&
206
- parent.modelAlias &&
207
- !parent.modelTier
208
- ) {
209
- model = parent.modelAlias;
210
- } else if (!parent.modelSource && !parent.resolvedModel && !parent.claudeSessionId) {
211
- model = parent.model;
212
- }
190
+ ): Pick<CreateTaskOptions, "model" | "modelTier" | "profile"> {
191
+ const model = taskModelPin(parent);
192
+ // The task's own profile travels like its tier; an agent-default profile
193
+ // (`resolvedProfile` only) is re-resolved by the child's claim.
213
194
  return {
214
195
  ...(model ? { model } : {}),
215
196
  ...(parent.modelTier ? { modelTier: parent.modelTier } : {}),
197
+ ...(parent.profile ? { profile: parent.profile } : {}),
216
198
  };
217
199
  }
218
200
 
@@ -667,6 +667,48 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
667
667
  expect(resolveAcpTarget(config).env(config).OPENCODE_CONFIG_CONTENT).toBe(content);
668
668
  });
669
669
 
670
+ test("OpenCode preset adds the helmcode block with an {env:} key reference, never the key", () => {
671
+ const sentinel = "sk-helmcode-acp-sentinel-0123456789";
672
+ const config = baseConfig({
673
+ model: "helmcode/glm5.3",
674
+ env: {
675
+ ACP_TARGET: "opencode",
676
+ HELMCODE_API_KEY: sentinel,
677
+ OPENCODE_CONFIG_CONTENT: JSON.stringify({
678
+ provider: { helmcode: { options: { timeout: 1000 } } },
679
+ }),
680
+ },
681
+ });
682
+ const env = resolveAcpTarget(config).env(config);
683
+ expect(env.HELMCODE_API_KEY).toBe(sentinel);
684
+ expect(env.OPENCODE_CONFIG_CONTENT).not.toContain(sentinel);
685
+ const helmcode = JSON.parse(env.OPENCODE_CONFIG_CONTENT!).provider.helmcode;
686
+ expect(helmcode).toMatchObject({
687
+ npm: "@ai-sdk/openai-compatible",
688
+ name: "Helmcode",
689
+ env: [],
690
+ options: {
691
+ baseURL: "https://api.helmcode.com/v1",
692
+ apiKey: "{env:HELMCODE_API_KEY}",
693
+ timeout: 1000,
694
+ },
695
+ });
696
+ expect(helmcode.models["glm5.3"]).toMatchObject({
697
+ tool_call: true,
698
+ limit: { context: 1048576, output: 32768 },
699
+ });
700
+ });
701
+
702
+ test("OpenCode preset adds no custom block for a nan model", () => {
703
+ const config = baseConfig({
704
+ model: "nan/glm5.3-flash",
705
+ env: { ACP_TARGET: "opencode", NAN_API_KEY: "example-test-key" },
706
+ });
707
+ const env = resolveAcpTarget(config).env(config);
708
+ expect(env.NAN_API_KEY).toBe("example-test-key");
709
+ expect(JSON.parse(env.OPENCODE_CONFIG_CONTENT!).provider?.helmcode).toBeUndefined();
710
+ });
711
+
670
712
  test("OpenCode preset injects gateway even without a model fallback", () => {
671
713
  const config = baseConfig({
672
714
  model: "",
@@ -38,6 +38,27 @@ describe("isInboxAllowed", () => {
38
38
  test("rejects empty inbox id", () => {
39
39
  expect(isInboxAllowed("", "x.dev")).toBe(false);
40
40
  });
41
+
42
+ test("allows exact full-address match", () => {
43
+ expect(isInboxAllowed("bot@x.dev", "bot@x.dev")).toBe(true);
44
+ });
45
+
46
+ test("rejects another address on the same domain as a full-address entry", () => {
47
+ expect(isInboxAllowed("other@x.dev", "bot@x.dev")).toBe(false);
48
+ });
49
+
50
+ test("full-address match is case-insensitive", () => {
51
+ expect(isInboxAllowed("Bot@X.Dev", "bot@x.dev")).toBe(true);
52
+ expect(isInboxAllowed("bot@x.dev", "BOT@X.DEV")).toBe(true);
53
+ });
54
+
55
+ test("supports mixed address and domain entries", () => {
56
+ const filter = "bot@x.dev, y.xyz";
57
+ expect(isInboxAllowed("bot@x.dev", filter)).toBe(true);
58
+ expect(isInboxAllowed("anyone@y.xyz", filter)).toBe(true);
59
+ expect(isInboxAllowed("other@x.dev", filter)).toBe(false);
60
+ expect(isInboxAllowed("bot@evil.com", filter)).toBe(false);
61
+ });
41
62
  });
42
63
 
43
64
  describe("isSenderAllowed", () => {
@@ -94,4 +115,30 @@ describe("isSenderAllowed", () => {
94
115
  test("rejects non-matching domain in 'Name <email>' format", () => {
95
116
  expect(isSenderAllowed("Taras <t@evil.com>", "desplega.ai")).toBe(false);
96
117
  });
118
+
119
+ test("allows exact full-address match", () => {
120
+ expect(isSenderAllowed("alice@a.com", "alice@a.com")).toBe(true);
121
+ });
122
+
123
+ test("rejects another address on the same domain as a full-address entry", () => {
124
+ expect(isSenderAllowed("bob@a.com", "alice@a.com")).toBe(false);
125
+ });
126
+
127
+ test("full-address match is case-insensitive", () => {
128
+ expect(isSenderAllowed("Alice@A.com", "alice@a.com")).toBe(true);
129
+ expect(isSenderAllowed("alice@a.com", "ALICE@A.COM")).toBe(true);
130
+ });
131
+
132
+ test("matches a full address in 'Name <email>' format", () => {
133
+ expect(isSenderAllowed("Taras <t@gmail.com>", "t@gmail.com")).toBe(true);
134
+ expect(isSenderAllowed(["Someone <s@gmail.com>"], "t@gmail.com")).toBe(false);
135
+ });
136
+
137
+ test("supports mixed address and domain entries", () => {
138
+ const filter = "t@gmail.com, desplega.ai";
139
+ expect(isSenderAllowed("t@gmail.com", filter)).toBe(true);
140
+ expect(isSenderAllowed("anyone@desplega.ai", filter)).toBe(true);
141
+ expect(isSenderAllowed("other@gmail.com", filter)).toBe(false);
142
+ expect(isSenderAllowed(["other@gmail.com", "Taras <t@gmail.com>"], filter)).toBe(true);
143
+ });
97
144
  });
@@ -28,7 +28,6 @@ let sut: Sut;
28
28
  let api: ApiClient;
29
29
  let fixtureDir: string;
30
30
  let fixturePath: string;
31
- let pm2StubPath: string;
32
31
 
33
32
  /** A `claude` that initializes, accepts the prompt, and then never answers. */
34
33
  const hangingFixtureSource = `#!/usr/bin/env bun
@@ -87,12 +86,8 @@ describe("API drain with a real worker", () => {
87
86
  beforeAll(async () => {
88
87
  fixtureDir = await mkdtemp("/tmp/api-drain-worker-");
89
88
  fixturePath = join(fixtureDir, "claude-fixture");
90
- pm2StubPath = join(fixtureDir, "bin");
91
- await Bun.$`mkdir -p ${pm2StubPath}`.quiet();
92
89
  await Bun.write(fixturePath, hangingFixtureSource);
93
- await Bun.write(join(pm2StubPath, "pm2"), "#!/bin/sh\nexit 0\n");
94
90
  await Bun.$`chmod 755 ${fixturePath}`.quiet();
95
- await Bun.$`chmod 755 ${join(pm2StubPath, "pm2")}`.quiet();
96
91
  sut = await startSut(
97
92
  false,
98
93
  {},
@@ -138,7 +133,7 @@ describe("API drain with a real worker", () => {
138
133
  cwd: repoRoot,
139
134
  env: {
140
135
  ...minimalEnv(),
141
- PATH: `${pm2StubPath}:${process.env.PATH ?? "/usr/bin:/bin"}`,
136
+ PATH: process.env.PATH ?? "/usr/bin:/bin",
142
137
  HOME: home,
143
138
  AGENT_SWARM_API_KEY: sut.apiKey,
144
139
  API_KEY: sut.apiKey,
@@ -987,6 +987,41 @@ describe("custom app actions", () => {
987
987
  );
988
988
  });
989
989
 
990
+ test("app writes return quickjs compat warnings for wired scripts without rejecting", async () => {
991
+ const saved = await upsertScriptByName({
992
+ name: `app_compat_${crypto.randomUUID().replaceAll("-", "")}`,
993
+ scope: "agent",
994
+ scopeId: AGENT_ID,
995
+ source: "export default async () => Buffer.from('aGk=', 'base64').toString();",
996
+ description: "Apps compat warning fixture",
997
+ intent: "Exercise quickjs compat warnings on app writes",
998
+ signatureJson: JSON.stringify({ args: { type: "object" }, result: { type: "string" } }),
999
+ agentId: AGENT_ID,
1000
+ typeChecked: true,
1001
+ });
1002
+ const created = await request<{ app: { id: string }; warnings?: string[] }>("/api/apps", {
1003
+ method: "POST",
1004
+ body: JSON.stringify({
1005
+ name: "Compat",
1006
+ definition: {
1007
+ ...baseDefinition,
1008
+ actions: { decode: { kind: "script", scriptId: saved.script.id } },
1009
+ },
1010
+ }),
1011
+ });
1012
+ expect(created.status).toBe(201);
1013
+ expect(created.body.warnings).toEqual([
1014
+ "actions.decode: quickjs compat (unsupportedGlobal, line 1): Buffer is not available under quickjs",
1015
+ ]);
1016
+
1017
+ const cleared = await request<{ warnings?: string[] }>(`/api/apps/${created.body.app.id}`, {
1018
+ method: "PUT",
1019
+ body: JSON.stringify({ definition: baseDefinition }),
1020
+ });
1021
+ expect(cleared.status).toBe(200);
1022
+ expect(cleared.body.warnings).toBeUndefined();
1023
+ });
1024
+
990
1025
  test("agent writers cannot wire another agent's script; operator wiring is grandfathered", async () => {
991
1026
  const OTHER_AGENT_ID = crypto.randomUUID();
992
1027
  const foreign = await upsertScriptByName({