@desplega.ai/agent-swarm 2.7.4 → 2.7.6

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 (326) hide show
  1. package/README.md +1 -1
  2. package/dist/acp-adapter-bp91d37f.js +15 -0
  3. package/dist/{actions-gfrr9vb7.js → actions-yj0eeaqh.js} +19 -18
  4. package/dist/{amp-adapter-0s58gm46.js → amp-adapter-4pg9h34q.js} +3 -3
  5. package/dist/{app-stvq692g.js → app-909e97cr.js} +3 -3
  6. package/dist/{assistant-5s37fr9n.js → assistant-qdyb5fyy.js} +32 -23
  7. package/dist/{boot-reembed-yzy1841n.js → boot-reembed-fk0s9z18.js} +1 -1
  8. package/dist/{boot-reembed-cv82gz7y.js → boot-reembed-gs1rzrnh.js} +1 -1
  9. package/dist/{boot-scrub-sweep-8k4q95ap.js → boot-scrub-sweep-4qfd4kfx.js} +4 -4
  10. package/dist/{boot-seeds-ftt9htah.js → boot-seeds-yr00k13d.js} +4 -4
  11. package/dist/{boxd-515v1t8z.js → boxd-vdmfssd9.js} +5 -5
  12. package/dist/{claude-adapter-t00wrzeh.js → claude-adapter-qta1f1xn.js} +7 -7
  13. package/dist/{claude-managed-adapter-g6vmjccb.js → claude-managed-adapter-dvs2xw5n.js} +2 -2
  14. package/dist/{claude-managed-setup-afpa82f7.js → claude-managed-setup-sc6j4dm0.js} +3 -3
  15. package/dist/{claude-sdk-session-vj0c2azx.js → claude-sdk-session-zgrz6yvc.js} +9175 -9076
  16. package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
  17. package/dist/{cli-edmm9d9y.js → cli-0at8w34w.js} +7 -7
  18. package/dist/{cli-a81qevyg.js → cli-0qatpy8q.js} +20 -36
  19. package/dist/{cli-bcz074zx.js → cli-0yv5z2pg.js} +1 -1
  20. package/dist/{cli-a4btqqft.js → cli-1b7btd38.js} +1 -1
  21. package/dist/{cli-a2jw6ee5.js → cli-1p89xny0.js} +1 -1
  22. package/dist/{cli-v726afdm.js → cli-2pnswgh8.js} +1 -1
  23. package/dist/{cli-jkt52mgt.js → cli-386tyxxg.js} +2 -2
  24. package/dist/{cli-az0bpf8p.js → cli-3ypk9vk1.js} +3 -3
  25. package/dist/{cli-wk9md5px.js → cli-436532ck.js} +4 -4
  26. package/dist/{cli-7n5sbw0z.js → cli-4necwtsv.js} +5 -5
  27. package/dist/cli-54t00gt8.js +151 -0
  28. package/dist/{cli-655xm9ms.js → cli-583frbyr.js} +1 -1
  29. package/dist/{cli-4r12t6w6.js → cli-5c4hjzne.js} +101 -4
  30. package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
  31. package/dist/{cli-rtk9g7av.js → cli-73jzwksr.js} +6 -2
  32. package/dist/{cli-jg0nfda6.js → cli-75gkpjvw.js} +52 -21
  33. package/dist/{cli-wcmnan2g.js → cli-89knwags.js} +5 -5
  34. package/dist/{cli-pwyevs29.js → cli-8bc1w0tw.js} +22 -21
  35. package/dist/{cli-t282jgab.js → cli-8snvgs5r.js} +41 -16
  36. package/dist/{cli-vqgnyh76.js → cli-98rw5dt5.js} +15 -3
  37. package/dist/{cli-9cctx1tn.js → cli-a22h291q.js} +1 -1
  38. package/dist/{cli-ycm6180s.js → cli-a7v854rf.js} +5 -3
  39. package/dist/{cli-98pke4vk.js → cli-ae7mrdf9.js} +2 -2
  40. package/dist/{cli-je8yeyc5.js → cli-bjb3z51z.js} +5 -5
  41. package/dist/{cli-mc97xy2s.js → cli-c60vetmm.js} +16 -4
  42. package/dist/{cli-j4kw3rsx.js → cli-cm5yqn35.js} +18 -0
  43. package/dist/{cli-wndv9p7g.js → cli-decfb6v8.js} +6 -6
  44. package/dist/{cli-h55y2bjc.js → cli-dhar9qay.js} +2 -2
  45. package/dist/{cli-8yy525ad.js → cli-drr5v6av.js} +2 -2
  46. package/dist/{cli-5gr1g711.js → cli-dxae2yg7.js} +2 -2
  47. package/dist/{cli-pe09p6dv.js → cli-e3r2neck.js} +8 -7
  48. package/dist/{cli-13jvmew3.js → cli-ekwks2vs.js} +7 -7
  49. package/dist/{cli-jg4p5t36.js → cli-es6va8jk.js} +121 -51
  50. package/dist/{cli-j4kt1mpb.js → cli-faxe6thg.js} +3 -3
  51. package/dist/{cli-tzrpv5sc.js → cli-ftyxyspd.js} +1 -1
  52. package/dist/cli-fw2w0kva.js +47 -0
  53. package/dist/{cli-2dfbv8qr.js → cli-fy6x31ps.js} +1 -0
  54. package/dist/{cli-aq58fk5m.js → cli-g83s9ewh.js} +7 -1
  55. package/dist/cli-gdhpcbhf.js +55 -0
  56. package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
  57. package/dist/{cli-8veytvbh.js → cli-he9wchme.js} +1 -1
  58. package/dist/{cli-k76tgq03.js → cli-hgaazk69.js} +2 -2
  59. package/dist/{cli-d2pzqej1.js → cli-hr2e2fft.js} +741 -7
  60. package/dist/{cli-dae4sbgy.js → cli-jm2vfpfd.js} +2 -2
  61. package/dist/{cli-1s1dqn1j.js → cli-k2gzwsj2.js} +297 -140
  62. package/dist/{cli-b1afgtj3.js → cli-kqdg1bsb.js} +3 -3
  63. package/dist/cli-m2z8jagn.js +12078 -0
  64. package/dist/{cli-mvckssyq.js → cli-m48g6p95.js} +5 -5
  65. package/dist/{cli-bwty35gx.js → cli-mthwvjre.js} +1 -1
  66. package/dist/{cli-41z8xwj0.js → cli-nhrvpaj7.js} +34 -8
  67. package/dist/{cli-ahd90bwt.js → cli-nwj0ee2w.js} +6 -6
  68. package/dist/{cli-kvtc7aez.js → cli-p5gp8pcx.js} +44 -14
  69. package/dist/{cli-avgcz4ra.js → cli-pd7vagab.js} +436 -171
  70. package/dist/{cli-zn15c46z.js → cli-q0k1w5zd.js} +1 -1
  71. package/dist/{cli-bq2pcphn.js → cli-rmmmrjkd.js} +1 -1
  72. package/dist/{cli-2qpvmmkw.js → cli-s0qnd4wv.js} +1 -1
  73. package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
  74. package/dist/{cli-1yekbyqm.js → cli-shpbx5ee.js} +283 -82
  75. package/dist/{cli-9y354g3j.js → cli-srye3vdt.js} +1 -1
  76. package/dist/{cli-byqfbnjw.js → cli-svj5090z.js} +9 -9
  77. package/dist/{cli-gcbdjt47.js → cli-vfym7wmz.js} +6 -6
  78. package/dist/{cli-v1yw3wha.js → cli-vvpx3gsx.js} +4 -4
  79. package/dist/{cli-a1fhj4n9.js → cli-w6dt9ged.js} +2 -2
  80. package/dist/{cli-1gm8jcyr.js → cli-waqbxg08.js} +5232 -68
  81. package/dist/{cli-5hjced2v.js → cli-wp9sp1qb.js} +6 -6
  82. package/dist/{cli-eny54typ.js → cli-wx3rhnm0.js} +1 -1
  83. package/dist/{cli-s0exq74r.js → cli-xbbndvjy.js} +3 -3
  84. package/dist/{cli-f255yaqn.js → cli-xds5f77d.js} +3 -3
  85. package/dist/{cli-0sbkss7h.js → cli-xnqgpshy.js} +2 -2
  86. package/dist/{cli-0fgr1w9b.js → cli-xnrwazav.js} +20 -3
  87. package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
  88. package/dist/{cli-cq1pf336.js → cli-yn4kxs06.js} +124 -11
  89. package/dist/{cli-224epwwf.js → cli-yx4kfngt.js} +3 -3
  90. package/dist/{cli-eqx6k993.js → cli-yy3rhsdw.js} +47 -13
  91. package/dist/{cli-36g23z21.js → cli-zbbwez0r.js} +4 -4
  92. package/dist/{cli-ktmvb20s.js → cli-zm72anhg.js} +12 -12
  93. package/dist/cli.js +26 -25
  94. package/dist/{codex-adapter-g0gctw29.js → codex-adapter-ayd1fb6h.js} +5 -5
  95. package/dist/{codex-hook-vrzz2xhr.js → codex-hook-9efm2x1m.js} +1 -1
  96. package/dist/{codex-login-0gg46ygn.js → codex-login-y0pmff1c.js} +1 -1
  97. package/dist/{codex-session-runner-dr2cxe1c.js → codex-session-runner-jesa32xc.js} +6 -6
  98. package/dist/{commands-1p07cvff.js → commands-hanewgf2.js} +9 -3
  99. package/dist/{controller-z0s7sv6r.js → controller-dt1n3c1q.js} +9 -4
  100. package/dist/{controllers-connect-x9kvvhyk.js → controllers-connect-xx8x64n5.js} +22 -21
  101. package/dist/{cursor-adapter-m5b98pvk.js → cursor-adapter-9s4dwwdw.js} +2 -2
  102. package/dist/{db-capk2raj.js → db-3vttcbv4.js} +3 -3
  103. package/dist/{db-rsh04kh3.js → db-9dv0g1ab.js} +12 -4
  104. package/dist/{devin-adapter-yxm87fe9.js → devin-adapter-r9phbnc9.js} +1 -1
  105. package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
  106. package/dist/{dispatcher-304q6fwa.js → dispatcher-tx9hd1sv.js} +18 -17
  107. package/dist/{docker-7rxt0rry.js → docker-c1a3gqmf.js} +91 -24
  108. package/dist/{dsh-adapter-btewem0y.js → dsh-adapter-007y4ejs.js} +2 -2
  109. package/dist/{e2b-renc0k8q.js → e2b-a19d28rz.js} +4 -4
  110. package/dist/{e2b-7fc9b9bd.js → e2b-we6hs6q5.js} +6 -6
  111. package/dist/{easter-egg-3d-fm1p3n4d.js → easter-egg-3d-rgbrqb8s.js} +1 -1
  112. package/dist/{embedding-space-s68v49ct.js → embedding-space-ny1e2940.js} +6 -6
  113. package/dist/{embeddings-4ms3604w.js → embeddings-2t386t13.js} +1 -1
  114. package/dist/{event-bus-dfjx2qdh.js → event-bus-g0ka3pbw.js} +1 -1
  115. package/dist/{execute-mcdgccjs.js → execute-pksh0ceh.js} +1 -1
  116. package/dist/{external-channel-9dxwfztn.js → external-channel-phgmhmd7.js} +2 -2
  117. package/dist/fx-adapter-fy8rr921.js +18 -0
  118. package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-8y3m3k4b.js} +1 -1
  119. package/dist/{google-vertex-jrtdh9gs.js → google-vertex-w3v98n7b.js} +1 -1
  120. package/dist/{grok-adapter-05ecv4h0.js → grok-adapter-q0abvpaj.js} +8 -8
  121. package/dist/handlers-by3sfxyx.js +32 -0
  122. package/dist/{heartbeat-3n94heh3.js → heartbeat-kadyek8p.js} +21 -20
  123. package/dist/{hook-aadrn69x.js → hook-5zs3msf6.js} +6 -6
  124. package/dist/{hook-zxwje86c.js → hook-9k551xnj.js} +4 -4
  125. package/dist/{http-xzxk9mst.js → http-mhz9vw1f.js} +272 -103
  126. package/dist/index-4f2pvxae.js +37 -0
  127. package/dist/index-98gz7tmp.js +30 -0
  128. package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
  129. package/dist/index-k08z2cak.js +33 -0
  130. package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
  131. package/dist/{index-d34f6r87.js → index-q361cg2f.js} +1 -1
  132. package/dist/{index-9j4zwcht.js → index-rvr00sg7.js} +168 -675
  133. package/dist/{index-qeb82z37.js → index-vdygbq10.js} +1 -1
  134. package/dist/{keepalive-w4aakbyk.js → keepalive-w56f32cg.js} +4 -4
  135. package/dist/lead-enq35nqp.js +70 -0
  136. package/dist/{maintenance-hbptpk6p.js → maintenance-7mdgxcea.js} +1 -1
  137. package/dist/{oauth-refresh-sweep-217semrv.js → oauth-refresh-sweep-29txy9vk.js} +3 -3
  138. package/dist/{onboard-vz2s5df3.js → onboard-kpsrkarj.js} +5 -5
  139. package/dist/{opencode-adapter-gtfd7d7v.js → opencode-adapter-j25vm3rr.js} +5 -5
  140. package/dist/{otel-impl-aefave37.js → otel-impl-6kkct7a8.js} +2 -2
  141. package/dist/{pi-mono-adapter-f8h5yseh.js → pi-mono-adapter-exhkzhn2.js} +117 -23
  142. package/dist/{pricing-refresh-m1yhh9ya.js → pricing-refresh-n2x7jh6k.js} +3 -3
  143. package/dist/{rbac-roles-rrw8twdz.js → rbac-roles-81ypx6es.js} +1 -1
  144. package/dist/{rbac-roles-svj72cdf.js → rbac-roles-b5htkjby.js} +2 -2
  145. package/dist/{render-v2-amwbnfxh.js → render-v2-qn6jmpk4.js} +3 -3
  146. package/dist/{resume-9cd6wf6z.js → resume-wd30wx23.js} +7 -7
  147. package/dist/{runtime-fp4vrx7m.js → runtime-4hsg7h60.js} +1 -1
  148. package/dist/{session-access-21t6yjyg.js → session-access-5qvfevhk.js} +4 -4
  149. package/dist/{session-source-backfill-pxbzcsc6.js → session-source-backfill-n1czavbr.js} +3 -3
  150. package/dist/{setup-4592tqz4.js → setup-dnb56qy5.js} +3 -3
  151. package/dist/{virtual-modules-gzfnpmnz.js → virtual-modules-76xxv9fs.js} +2 -2
  152. package/dist/{work-objects-t9tcf174.js → work-objects-4n21tfkm.js} +21 -20
  153. package/dist/{worker-follow-up-q32jh4yk.js → worker-follow-up-77hvzxww.js} +18 -17
  154. package/dist/worker-gg1576j3.js +70 -0
  155. package/dist/{x-b6wxntdt.js → x-vmmye1kb.js} +2 -2
  156. package/openapi.json +189 -32
  157. package/package.json +8 -7
  158. package/src/be/db/script-runs.ts +23 -0
  159. package/src/be/db/session-summaries.ts +8 -2
  160. package/src/be/db/session-tree.ts +174 -61
  161. package/src/be/db/session-visibility.ts +5 -4
  162. package/src/be/db/tasks/read.ts +122 -3
  163. package/src/be/db-client.ts +8 -5
  164. package/src/be/db.ts +23 -8
  165. package/src/be/memory/providers/sqlite-store.ts +49 -12
  166. package/src/be/migrations/259_session_summaries_open_tasks.sql +111 -0
  167. package/src/be/migrations/260_runtime_pool_cache.sql +20 -0
  168. package/src/be/migrations/262_session_visibility_indexes.sql +18 -0
  169. package/src/be/migrations/263_task_list_filter_index.sql +11 -0
  170. package/src/be/migrations/264_slack_thread_cover_index.sql +9 -0
  171. package/src/be/migrations/266_script_runs_source_task.sql +11 -0
  172. package/src/be/model-validation.ts +5 -1
  173. package/src/be/pricing-normalize.ts +7 -2
  174. package/src/be/runtime-pools/cache-cleanup.ts +109 -0
  175. package/src/be/runtime-pools/db.ts +65 -6
  176. package/src/be/runtime-pools/remote-controller.ts +5 -0
  177. package/src/be/runtime-pools/supervisor.ts +33 -2
  178. package/src/be/scripts/typecheck.ts +23 -0
  179. package/src/be/seed/schedules-seeder.ts +135 -9
  180. package/src/be/seed/workflows-seeder.ts +65 -1
  181. package/src/be/seed-pricing.ts +23 -0
  182. package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
  183. package/src/be/steering.ts +50 -3
  184. package/src/be/swarm-config-guard.ts +1 -0
  185. package/src/commands/controller.ts +4 -0
  186. package/src/commands/entrypoint-api-readiness.ts +74 -0
  187. package/src/commands/provider-credentials.ts +29 -2
  188. package/src/commands/runner.ts +86 -5
  189. package/src/extensions/contract-types.generated.ts +1 -1
  190. package/src/extensions/contract.ts +27 -1
  191. package/src/extensions/dispatcher.ts +4 -0
  192. package/src/http/agents.ts +1 -0
  193. package/src/http/index.ts +1 -1
  194. package/src/http/list-total-cache.ts +14 -7
  195. package/src/http/mcp-bridge.ts +4 -0
  196. package/src/http/memory.ts +38 -9
  197. package/src/http/script-runs.ts +174 -5
  198. package/src/http/status.ts +10 -2
  199. package/src/http/tasks.ts +4 -2
  200. package/src/integrations/chat-sdk/inbound.ts +15 -5
  201. package/src/integrations/chat-sdk/steering.ts +3 -1
  202. package/src/kv-reserved-namespaces.ts +2 -0
  203. package/src/prompts/base-prompt.ts +4 -1
  204. package/src/prompts/session-templates.ts +26 -0
  205. package/src/providers/acp-adapter.ts +190 -26
  206. package/src/providers/acp-targets.ts +306 -6
  207. package/src/providers/amp-adapter.ts +1 -1
  208. package/src/providers/codex-adapter.ts +9 -44
  209. package/src/providers/codex-oauth/flow.ts +24 -3
  210. package/src/providers/codex-oauth/storage.ts +56 -17
  211. package/src/providers/codex-oauth/subscription.ts +67 -0
  212. package/src/providers/fx-adapter.ts +211 -0
  213. package/src/providers/index.ts +5 -1
  214. package/src/providers/mcp-loopback-proxy.ts +94 -0
  215. package/src/providers/pi-mono-adapter.ts +98 -13
  216. package/src/providers/pricing-sources.md +8 -0
  217. package/src/providers/reasoning-effort.ts +17 -1
  218. package/src/providers/types.ts +2 -1
  219. package/src/runtime-providers/controller-protocol.ts +14 -1
  220. package/src/runtime-providers/controller.ts +5 -0
  221. package/src/runtime-providers/docker.ts +108 -23
  222. package/src/runtime-providers/types.ts +16 -0
  223. package/src/scheduler/scheduler.ts +36 -3
  224. package/src/script-workflows/workflow-ctx.ts +8 -0
  225. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  226. package/src/scripts-runtime/types/stdlib.d.ts +27 -0
  227. package/src/scripts-runtime/types/swarm-extension.d.ts +33 -0
  228. package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -0
  229. package/src/server-user.ts +1 -0
  230. package/src/server.ts +7 -0
  231. package/src/slack/assistant.ts +17 -1
  232. package/src/slack/commands.ts +5 -1
  233. package/src/slack/handlers.ts +53 -5
  234. package/src/slack/left-threads.ts +88 -0
  235. package/src/slack/steering.ts +3 -0
  236. package/src/slack/thread-buffer.ts +40 -16
  237. package/src/tasks/follow-up-redelegation-guard.ts +155 -0
  238. package/src/tests/acp-adapter.test.ts +79 -0
  239. package/src/tests/base-prompt.test.ts +12 -0
  240. package/src/tests/chat-sdk-inbound.test.ts +30 -0
  241. package/src/tests/chatgpt-subscription-model-groups.test.ts +62 -0
  242. package/src/tests/chatgpt-subscription-pi.test.ts +170 -0
  243. package/src/tests/chatgpt-subscription-session-bench.test.ts +198 -0
  244. package/src/tests/claude-artifacts-read-script.test.ts +209 -0
  245. package/src/tests/codex-oauth-credential-info.test.ts +40 -0
  246. package/src/tests/codex-oauth-refresh-egress.test.ts +194 -0
  247. package/src/tests/context-window.test.ts +7 -0
  248. package/src/tests/db-client.test.ts +21 -0
  249. package/src/tests/db-task-read-contract.test.ts +8 -2
  250. package/src/tests/db-task-write-contract.test.ts +5 -1
  251. package/src/tests/entrypoint-api-readiness.test.ts +416 -210
  252. package/src/tests/explicit-model-validation.test.ts +113 -8
  253. package/src/tests/extensions-run-context.test.ts +11 -0
  254. package/src/tests/extensions-steering.test.ts +353 -0
  255. package/src/tests/extensions-validate.test.ts +5 -0
  256. package/src/tests/fixtures/extensions/steering-policy/hooks.ts +39 -0
  257. package/src/tests/fixtures/extensions/steering-policy/manifest.json +7 -0
  258. package/src/tests/fixtures/fake-fx.ts +167 -0
  259. package/src/tests/fixtures/fx/acp-responses-recorded.json +157 -0
  260. package/src/tests/fixtures/fx/acp-updates-recorded.jsonl +15 -0
  261. package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
  262. package/src/tests/fx-adapter.test.ts +771 -0
  263. package/src/tests/grok-adapter.test.ts +36 -0
  264. package/src/tests/list-total-cache.test.ts +27 -2
  265. package/src/tests/mcp-loopback-proxy.test.ts +193 -0
  266. package/src/tests/memory-fts-populate.test.ts +11 -9
  267. package/src/tests/memory-retrieval-purge.test.ts +80 -0
  268. package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
  269. package/src/tests/model-tier-resolution.test.ts +3 -3
  270. package/src/tests/pricing-refresh.test.ts +5 -5
  271. package/src/tests/prompt-template-session.test.ts +5 -3
  272. package/src/tests/provider-registration.test.ts +2 -1
  273. package/src/tests/query-audit-plans.test.ts +30 -2
  274. package/src/tests/runtime-cache-cleanup.test.ts +367 -0
  275. package/src/tests/runtime-e2b-pools.test.ts +41 -0
  276. package/src/tests/runtime-pools-db.test.ts +98 -0
  277. package/src/tests/runtime-pools-http.test.ts +29 -0
  278. package/src/tests/runtime-providers-docker.test.ts +253 -0
  279. package/src/tests/runtime-remote-controller.test.ts +66 -2
  280. package/src/tests/runtime-supervisor.test.ts +66 -2
  281. package/src/tests/script-connections.test.ts +13 -0
  282. package/src/tests/script-runs-http.test.ts +684 -2
  283. package/src/tests/seed-model-profiles.test.ts +2 -2
  284. package/src/tests/seed-schedules.test.ts +138 -0
  285. package/src/tests/seed-scripts.test.ts +26 -0
  286. package/src/tests/session-cost-recompute-unit.test.ts +39 -0
  287. package/src/tests/session-costs-golden.test.ts +26 -0
  288. package/src/tests/session-summaries-plans.test.ts +120 -0
  289. package/src/tests/session-summaries.test.ts +72 -12
  290. package/src/tests/sessions-tree.test.ts +11 -3
  291. package/src/tests/slack-leave-thread.test.ts +571 -0
  292. package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
  293. package/src/tests/status.test.ts +11 -0
  294. package/src/tests/tasks-readable-row.test.ts +155 -0
  295. package/src/tests/tool-annotations.test.ts +2 -1
  296. package/src/tests/workflow-engine-v2.test.ts +42 -0
  297. package/src/tests/workflow-script-principal.test.ts +207 -7
  298. package/src/tools/send-task.ts +35 -47
  299. package/src/tools/slack-leave-thread.ts +104 -0
  300. package/src/tools/steer-task.ts +3 -2
  301. package/src/tools/task-dedup.ts +45 -21
  302. package/src/tools/tool-config.ts +2 -1
  303. package/src/types.ts +57 -0
  304. package/src/utils/codex-auth-failure.ts +30 -0
  305. package/src/utils/context-window.ts +4 -1
  306. package/src/utils/credentials.ts +7 -0
  307. package/src/utils/fx-models.ts +43 -0
  308. package/src/workflows/cooldown.ts +3 -2
  309. package/src/workflows/engine.ts +24 -9
  310. package/templates/schedules/dreaming-daily/config.json +1 -1
  311. package/templates/schedules/dreaming-daily/content.md +6 -14
  312. package/templates/skills/composio-google-docs/content.md +10 -0
  313. package/templates/skills/script-workflows/content.md +19 -1
  314. package/templates/skills/swarm-docs/content.md +2 -0
  315. package/templates/skills/swarm-scripts/SKILL.md +2 -0
  316. package/templates/skills/swarm-scripts/content.md +2 -0
  317. package/templates/workflows/dreaming/content.md +5 -1
  318. package/dist/acp-adapter-96dc64m2.js +0 -15
  319. package/dist/cli-dznrgaep.js +0 -30
  320. package/dist/cli-w3hcrxht.js +0 -6985
  321. package/dist/handlers-jg7v74ty.js +0 -31
  322. package/dist/index-8c1zbz59.js +0 -29
  323. package/dist/index-8k8tmfxd.js +0 -32
  324. package/dist/index-hq617xdj.js +0 -36
  325. package/dist/lead-567kw7x3.js +0 -68
  326. package/dist/worker-fjdqra6q.js +0 -68
@@ -112,10 +112,15 @@ afterAll(async () => {
112
112
  await removeDbFiles(TEST_DB_PATH);
113
113
  });
114
114
 
115
- async function api(method: string, path: string, body: unknown) {
115
+ async function api(
116
+ method: string,
117
+ path: string,
118
+ body: unknown,
119
+ headers: Record<string, string> = {},
120
+ ) {
116
121
  const res = await fetch(`${baseUrl}${path}`, {
117
122
  method,
118
- headers: { "content-type": "application/json" },
123
+ headers: { "content-type": "application/json", ...headers },
119
124
  body: JSON.stringify(body),
120
125
  });
121
126
  const text = await res.text();
@@ -331,6 +336,44 @@ describe("grok model namespace", () => {
331
336
  });
332
337
  });
333
338
 
339
+ describe("fx model routes", () => {
340
+ test("an unrouted namespace, a bare non-OpenAI id or latest:anthropic is refused, even with allowCustomModel", async () => {
341
+ for (const [model, reason] of [
342
+ ["nan/glm5.3-flash", "InconsistentFinishReason"],
343
+ ["helmcode/qwen3.6", "Helmcode is not verified on fx"],
344
+ ["anthropic/claude-opus-5-5", "Anthropic Messages API"],
345
+ ["claude-opus-5-5", "is in the anthropic catalog"],
346
+ ["latest:anthropic/opus", "latest:anthropic alias"],
347
+ ] as const) {
348
+ for (const allowCustomModel of [false, true]) {
349
+ const error = await explicitModelError({ model, harnessProvider: "fx", allowCustomModel });
350
+ expect(error).toContain(`Model "${model}" does not run on the fx harness`);
351
+ expect(error).toContain(reason);
352
+ }
353
+ }
354
+ });
355
+
356
+ test("the OpenRouter, AI Gateway and OpenAI routes pass", async () => {
357
+ for (const model of [
358
+ "openrouter/deepseek/deepseek-v4.1-flash",
359
+ "vercel/anthropic/claude-opus-5.5",
360
+ "gpt-5-mini",
361
+ "latest:openrouter/deepseek/deepseek-v4.1-*",
362
+ ]) {
363
+ expect(await explicitModelError({ model, harnessProvider: "fx" })).toBeNull();
364
+ }
365
+ });
366
+
367
+ test("MODEL_TIER_FX_<TIER> refuses a model fx cannot route", () => {
368
+ expect(validateTierConfigValue("MODEL_TIER_FX_SMART", "nan/glm5.3-flash")).toContain(
369
+ 'model "nan/glm5.3-flash" does not run on the fx harness',
370
+ );
371
+ expect(
372
+ validateTierConfigValue("MODEL_TIER_FX_SMOL", "openrouter/deepseek/deepseek-v4.1-flash"),
373
+ ).toBeNull();
374
+ });
375
+ });
376
+
334
377
  describe("MODEL_TIER_<PROVIDER>_<TIER> values", () => {
335
378
  test("a model from another harness family is rejected on write", () => {
336
379
  expect(validateTierConfigValue("MODEL_TIER_CODEX_SMART", "claude-opus-5-5")).toBe(
@@ -574,12 +617,13 @@ describe("harness compatibility at every create entry point", () => {
574
617
  test("internal script-run agent-task route: a cross-harness model is a 400", async () => {
575
618
  const runId = crypto.randomUUID();
576
619
  await createScriptRun({ id: runId, agentId: workerId, source: "inline", args: {} });
577
- const res = await api("POST", `/api/internal/script-runs/${runId}/agent-task`, {
578
- stepKey: "step-1",
579
- task: "script step",
580
- agentId: codexId,
581
- model: "claude-opus-5-5",
582
- });
620
+ // The run's own harness calls this route with the run's agent id.
621
+ const res = await api(
622
+ "POST",
623
+ `/api/internal/script-runs/${runId}/agent-task`,
624
+ { stepKey: "step-1", task: "script step", agentId: codexId, model: "claude-opus-5-5" },
625
+ { "X-Agent-ID": workerId },
626
+ );
583
627
  expect(res.status).toBe(400);
584
628
  expect(String(res.body.error)).toContain("does not run on the codex harness");
585
629
  });
@@ -766,3 +810,64 @@ describe("nan and helmcode catalog models", () => {
766
810
  expect(res.status).toBe(200);
767
811
  });
768
812
  });
813
+
814
+ describe("ChatGPT subscription models (openai-codex/<id>)", () => {
815
+ let piId = "";
816
+
817
+ beforeAll(async () => {
818
+ piId = (
819
+ await createAgent({
820
+ name: "validation-pi-subscription",
821
+ isLead: false,
822
+ status: "idle",
823
+ harnessProvider: "pi",
824
+ })
825
+ ).id;
826
+ });
827
+
828
+ test("known when the bare id is an openai catalog model", () => {
829
+ const catalog = { openai: { models: { "gpt-5.6-sol": {} } } };
830
+ expect(isKnownCatalogModel("openai-codex/gpt-5.6-sol", catalog)).toBe(true);
831
+ expect(isKnownCatalogModel("openai-codex/gpt-9", catalog)).toBe(false);
832
+ expect(isKnownCatalogModel("openai-codex/openai/gpt-5.6-sol", catalog)).toBe(false);
833
+ });
834
+
835
+ test("pi takes it; opencode, codex, claude and grok get a mismatch", async () => {
836
+ const model = "openai-codex/gpt-5.6-sol";
837
+ expect(await explicitModelError({ model, harnessProvider: "pi" })).toBeNull();
838
+ for (const harnessProvider of ["opencode", "codex", "claude", "grok"]) {
839
+ const error = await explicitModelError({ model, harnessProvider, allowCustomModel: true });
840
+ expect(error).toContain(`does not run on the ${harnessProvider} harness`);
841
+ }
842
+ expect(await explicitModelError({ model, harnessProvider: "codex" })).toContain(
843
+ 'On codex, use "gpt-5.6-sol"',
844
+ );
845
+ });
846
+
847
+ test("send-task to a pi agent and the agent runtime PATCH accept it", async () => {
848
+ const sender = ownerCtx({ agentId: leadId });
849
+ const accepted = await sendTaskHandler(sender, {
850
+ task: "run on the chatgpt subscription",
851
+ offerMode: false,
852
+ allowDuplicate: false,
853
+ agentId: piId,
854
+ model: "openai-codex/gpt-5.6-sol",
855
+ routingReason: "human_pinned",
856
+ routingNote: "pinned to check the subscription model id",
857
+ });
858
+ expect({ ok: accepted.ok, message: accepted.message }).toEqual({
859
+ ok: true,
860
+ message: expect.any(String),
861
+ });
862
+
863
+ const res = await api("PATCH", `/api/agents/${piId}/runtime`, {
864
+ harness_provider: "pi",
865
+ model: "openai-codex/gpt-5.6-sol",
866
+ reasoning_effort: "high",
867
+ });
868
+ expect({ status: res.status, body: res.body }).toEqual({
869
+ status: 200,
870
+ body: expect.anything(),
871
+ });
872
+ });
873
+ });
@@ -24,6 +24,17 @@ describe("runContext", () => {
24
24
  expect(context.subject?.includes("sk-ant-")).toBe(false);
25
25
  });
26
26
 
27
+ test("steering records its source and sender agent without message content", () => {
28
+ expect(
29
+ runContext("pre.task.steer", {
30
+ task: { id: "task-1", agentId: "worker", task: "Private task" },
31
+ source: "mcp",
32
+ sender: { kind: "agent", agentId: "lead" },
33
+ message: "Private correction",
34
+ }),
35
+ ).toEqual({ agentId: "lead", subject: "mcp: task task-1" });
36
+ });
37
+
27
38
  test("post task events record the task's agent", () => {
28
39
  expect(
29
40
  runContext("post.task.completed", {
@@ -0,0 +1,353 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
+ import { createServer, type Server } from "node:http";
3
+ import {
4
+ closeDb,
5
+ createAgent,
6
+ createTaskExtended,
7
+ createUser,
8
+ getChildTasks,
9
+ getDbClient,
10
+ getKv,
11
+ getLogsByTaskId,
12
+ getSteeringMessagesForTask,
13
+ getTaskById,
14
+ initDb,
15
+ pauseTask,
16
+ startTask,
17
+ } from "../be/db";
18
+ import { getExtensionByName, installExtension, listExtensionRuns } from "../be/extensions/db";
19
+ import { requestSteering, SteeringBlockedError } from "../be/steering";
20
+ import type { TaskSteerEvent } from "../extensions/contract";
21
+ import { enableExtension, stopExtensionRuntime } from "../extensions/lifecycle";
22
+ import { handleMcpBridge } from "../http/mcp-bridge";
23
+ import { handleTasks } from "../http/tasks";
24
+ import { SwarmConfig } from "../scripts-runtime/swarm-config";
25
+ import { createSwarmSdk } from "../scripts-runtime/swarm-sdk";
26
+ import * as slackApp from "../slack/app";
27
+ import { requestSlackThreadSteering } from "../slack/steering";
28
+ import { bufferThreadMessage, instantFlush } from "../slack/thread-buffer";
29
+ import { steerTaskHandler } from "../tools/steer-task";
30
+ import { ownerCtx } from "../tools/task-tool-ctx";
31
+ import { loadBundleFixture } from "./fixtures/extensions/load";
32
+ import { listenOnFreePort } from "./test-net";
33
+
34
+ const TEST_DB_PATH = `./test-extensions-steering-${process.pid}.sqlite`;
35
+ const ENV_KEYS = [
36
+ "STEERING_ENABLED",
37
+ "SLACK_THREAD_STEERING",
38
+ "SLACK_THREAD_STEERING_MODE",
39
+ "SLACK_RENDER_V2",
40
+ ];
41
+ const savedEnv = new Map(ENV_KEYS.map((key) => [key, process.env[key]]));
42
+ let leadId: string;
43
+ let server: Server;
44
+ let baseUrl: string;
45
+
46
+ async function removeDbFiles() {
47
+ for (const suffix of ["", "-wal", "-shm"]) {
48
+ await Bun.file(TEST_DB_PATH + suffix)
49
+ .delete()
50
+ .catch(() => {});
51
+ }
52
+ }
53
+
54
+ async function installPolicy(config: Record<string, unknown>, name = "steering-policy") {
55
+ const bundle = await loadBundleFixture("steering-policy");
56
+ const installed = await installExtension({
57
+ ...bundle,
58
+ manifest: { ...bundle.manifest, name },
59
+ config,
60
+ });
61
+ return await enableExtension(installed.extension.id);
62
+ }
63
+
64
+ async function runningTask() {
65
+ const task = await createTaskExtended("Existing task", {
66
+ agentId: leadId,
67
+ source: "slack",
68
+ slackChannelId: "C_STEER_POLICY",
69
+ slackThreadTs: "1000.0001",
70
+ });
71
+ await startTask(task.id);
72
+ return task;
73
+ }
74
+
75
+ async function lastEvent(name = "steering-policy"): Promise<TaskSteerEvent | undefined> {
76
+ const extension = await getExtensionByName(name);
77
+ return (await getKv(`task:agent:${extension?.agentId}`, `ext:${name}:last`))?.value as
78
+ | TaskSteerEvent
79
+ | undefined;
80
+ }
81
+
82
+ async function expectNoSteering(taskId: string) {
83
+ expect(await getSteeringMessagesForTask(taskId)).toEqual([]);
84
+ expect(
85
+ (await getLogsByTaskId(taskId)).filter((log) => log.eventType === "task_steering"),
86
+ ).toEqual([]);
87
+ expect(await getChildTasks(taskId)).toEqual([]);
88
+ }
89
+
90
+ describe("extension steering boundary", () => {
91
+ beforeAll(async () => {
92
+ process.env.STEERING_ENABLED = "true";
93
+ process.env.SLACK_THREAD_STEERING = "lead";
94
+ process.env.SLACK_THREAD_STEERING_MODE = "queue";
95
+ process.env.SLACK_RENDER_V2 = "false";
96
+ await removeDbFiles();
97
+ initDb(TEST_DB_PATH);
98
+ server = createServer(async (req, res) => {
99
+ res.setHeader("Content-Type", "application/json");
100
+ const path = new URL(req.url ?? "/", "http://localhost").pathname.split("/").filter(Boolean);
101
+ if (await handleMcpBridge(req, res, path, new URLSearchParams(), leadId)) return;
102
+ await handleTasks(req, res, path, new URLSearchParams(), leadId);
103
+ });
104
+ baseUrl = `http://127.0.0.1:${await listenOnFreePort(server)}`;
105
+ });
106
+
107
+ beforeEach(async () => {
108
+ await stopExtensionRuntime();
109
+ const client = getDbClient();
110
+ await client.run("DELETE FROM agent_tasks");
111
+ await client.run("DELETE FROM extensions");
112
+ await client.run("DELETE FROM kv_entries WHERE key LIKE 'ext:%'");
113
+ await client.run("DELETE FROM agents");
114
+ leadId = (
115
+ await createAgent({
116
+ name: "Steering policy lead",
117
+ isLead: true,
118
+ status: "busy",
119
+ maxTasks: 10,
120
+ harnessProvider: "pi",
121
+ })
122
+ ).id;
123
+ });
124
+
125
+ afterAll(async () => {
126
+ await stopExtensionRuntime();
127
+ await new Promise<void>((resolve) => server.close(() => resolve()));
128
+ closeDb();
129
+ await removeDbFiles();
130
+ for (const [key, value] of savedEnv) {
131
+ if (value === undefined) delete process.env[key];
132
+ else process.env[key] = value;
133
+ }
134
+ });
135
+
136
+ test("continue records the task, source, mode and sender before queuing the original text", async () => {
137
+ await installPolicy({ action: "continue" });
138
+ const task = await runningTask();
139
+ const result = await requestSteering({
140
+ taskId: task.id,
141
+ message: "Add the charts",
142
+ mode: "steer",
143
+ source: "mcp",
144
+ createdByKind: "agent",
145
+ createdByAgentId: leadId,
146
+ });
147
+ expect(result.outcome).toBe("steered");
148
+ expect(await lastEvent()).toMatchObject({
149
+ task: { id: task.id, status: "in_progress" },
150
+ message: "Add the charts",
151
+ mode: "steer",
152
+ source: "mcp",
153
+ sender: { kind: "agent", agentId: leadId },
154
+ });
155
+ expect((await getSteeringMessagesForTask(task.id))[0]?.body).toBe("Add the charts");
156
+ });
157
+
158
+ test("valid modifications flow through successive handlers and are the only text delivered", async () => {
159
+ await installPolicy({ action: "modify", prefix: "first: " });
160
+ await installPolicy({ action: "modify", prefix: "second: " }, "steering-policy-z");
161
+ const task = await runningTask();
162
+ await requestSteering({ taskId: task.id, message: "Add the charts" });
163
+ expect((await lastEvent("steering-policy-z"))?.message).toBe("first: Add the charts");
164
+ expect((await getSteeringMessagesForTask(task.id))[0]?.body).toBe(
165
+ "second: first: Add the charts",
166
+ );
167
+ });
168
+
169
+ test("block returns an actionable refusal without steering rows, logs or follow-up tasks", async () => {
170
+ const extension = await installPolicy({ action: "block" });
171
+ const task = await runningTask();
172
+ try {
173
+ await requestSteering({ taskId: task.id, message: "Do not deliver" });
174
+ throw new Error("Expected steering to be blocked");
175
+ } catch (error) {
176
+ expect(error).toBeInstanceOf(SteeringBlockedError);
177
+ expect(error).toMatchObject({
178
+ statusCode: 403,
179
+ extension: { id: extension.id },
180
+ reason: "Steering denied by policy",
181
+ });
182
+ }
183
+ await expectNoSteering(task.id);
184
+ expect((await listExtensionRuns(extension.id)).map((run) => run.action)).toEqual(["block"]);
185
+ });
186
+
187
+ test("a blocked paused task is not resumed", async () => {
188
+ await installPolicy({ action: "block" });
189
+ const task = await runningTask();
190
+ await pauseTask(task.id);
191
+ await expect(
192
+ requestSteering({ taskId: task.id, message: "Do not resume" }),
193
+ ).rejects.toBeInstanceOf(SteeringBlockedError);
194
+ expect((await getTaskById(task.id))?.status).toBe("paused");
195
+ await expectNoSteering(task.id);
196
+ });
197
+
198
+ for (const action of ["empty", "retarget", "throw"]) {
199
+ test(`${action} fails open and records an extension error`, async () => {
200
+ const extension = await installPolicy({ action });
201
+ const task = await runningTask();
202
+ await requestSteering({ taskId: task.id, message: "Keep the original" });
203
+ expect((await getSteeringMessagesForTask(task.id))[0]?.body).toBe("Keep the original");
204
+ expect((await listExtensionRuns(extension.id))[0]?.action).toBe("error");
205
+ });
206
+ }
207
+
208
+ test("valid rewritten text is secret-scrubbed before storage", async () => {
209
+ const secret = `sk-ant-${"a".repeat(40)}`;
210
+ await installPolicy({ action: "modify", prefix: `${secret} ` });
211
+ const task = await runningTask();
212
+ await requestSteering({ taskId: task.id, message: "Keep this" });
213
+ const body = (await getSteeringMessagesForTask(task.id))[0]?.body;
214
+ expect(body).not.toContain(secret);
215
+ expect(body).toContain("Keep this");
216
+ });
217
+
218
+ test("extension-originated steering skips its own handler", async () => {
219
+ const extension = await installPolicy({ action: "block" });
220
+ const task = await runningTask();
221
+ await requestSteering({
222
+ taskId: task.id,
223
+ message: "From the extension",
224
+ createdByKind: "agent",
225
+ createdByAgentId: extension.agentId!,
226
+ });
227
+ expect(await lastEvent()).toBeUndefined();
228
+ expect((await getSteeringMessagesForTask(task.id))[0]?.body).toBe("From the extension");
229
+ });
230
+
231
+ test("HTTP steering returns 403 for a policy block", async () => {
232
+ await installPolicy({ action: "block" });
233
+ const task = await runningTask();
234
+ const response = await fetch(`${baseUrl}/api/tasks/${task.id}/steer`, {
235
+ method: "POST",
236
+ headers: { "Content-Type": "application/json", "X-Agent-ID": leadId },
237
+ body: JSON.stringify({ message: "Do not deliver" }),
238
+ });
239
+ expect(response.status).toBe(403);
240
+ expect(await response.text()).toContain("Steering denied by policy");
241
+ await expectNoSteering(task.id);
242
+ });
243
+
244
+ test("MCP steering reports a policy block as a tool error", async () => {
245
+ await installPolicy({ action: "block" });
246
+ const task = await runningTask();
247
+ const result = await steerTaskHandler(ownerCtx({ agentId: leadId }), {
248
+ taskId: task.id,
249
+ message: "Do not deliver",
250
+ });
251
+ expect(result.ok).toBe(false);
252
+ expect(result.message).toContain("Steering denied by policy");
253
+ expect(await lastEvent()).toMatchObject({ source: "mcp" });
254
+ await expectNoSteering(task.id);
255
+ });
256
+
257
+ test("script SDK steering reaches the hook through the bridge with the script source", async () => {
258
+ await installPolicy({ action: "continue" });
259
+ const task = await runningTask();
260
+ const sdk = createSwarmSdk(
261
+ new SwarmConfig({
262
+ system: {
263
+ apiKey: { value: "steering-sdk-test-key", isSecret: true },
264
+ agentId: { value: leadId, isSecret: false },
265
+ mcpBaseUrl: { value: baseUrl, isSecret: false },
266
+ },
267
+ user: {},
268
+ }),
269
+ );
270
+
271
+ const result = await sdk.task_steer({ taskId: task.id, message: "From the script SDK" });
272
+
273
+ expect(result).toMatchObject({
274
+ success: true,
275
+ status: 200,
276
+ data: { success: true, outcome: "queued" },
277
+ });
278
+ expect(await lastEvent()).toMatchObject({
279
+ task: { id: task.id },
280
+ message: "From the script SDK",
281
+ mode: "queue",
282
+ source: "script",
283
+ sender: { kind: "agent", agentId: leadId },
284
+ });
285
+ expect(await getSteeringMessagesForTask(task.id)).toEqual([
286
+ expect.objectContaining({ source: "script", body: "From the script SDK" }),
287
+ ]);
288
+ });
289
+
290
+ test("Slack supplies message timestamps and sender metadata without acknowledgement log rows on block", async () => {
291
+ await installPolicy({ action: "block" });
292
+ const task = await runningTask();
293
+ const user = await createUser({ name: "Steering sender" });
294
+ await expect(
295
+ requestSlackThreadSteering({
296
+ channelId: "C_STEER_POLICY",
297
+ threadTs: "1000.0001",
298
+ message: "Do not deliver",
299
+ messageTimestamps: ["1000.0002", "1000.0003"],
300
+ requestedByUserId: user.id,
301
+ slackUserId: "U_SENDER",
302
+ }),
303
+ ).rejects.toBeInstanceOf(SteeringBlockedError);
304
+ expect(await lastEvent()).toMatchObject({
305
+ source: "slack",
306
+ sender: { kind: "user", userId: user.id },
307
+ messageTimestamps: ["1000.0002", "1000.0003"],
308
+ slack: { channelId: "C_STEER_POLICY", threadTs: "1000.0001", userId: "U_SENDER" },
309
+ });
310
+ await expectNoSteering(task.id);
311
+ });
312
+
313
+ test("a blocked buffer flush is consumed and later senders still get delivered", async () => {
314
+ await installPolicy({ action: "block", slackUserId: "U_BLOCKED" });
315
+ const task = await runningTask();
316
+ const acked: string[] = [];
317
+ const posted: string[] = [];
318
+ const appSpy = spyOn(slackApp, "getSlackApp").mockReturnValue({
319
+ client: {
320
+ users: { info: async () => ({ ok: false }) },
321
+ reactions: {
322
+ add: async (args: { timestamp: string; name: string }) => {
323
+ acked.push(`${args.timestamp}:${args.name}`);
324
+ return { ok: true };
325
+ },
326
+ remove: async () => ({ ok: true }),
327
+ },
328
+ chat: {
329
+ postMessage: async (args: { text: string }) => {
330
+ posted.push(args.text);
331
+ return { ok: true };
332
+ },
333
+ },
334
+ },
335
+ } as unknown as NonNullable<ReturnType<typeof slackApp.getSlackApp>>);
336
+ try {
337
+ bufferThreadMessage("C_STEER_POLICY", "1000.0001", "Drop this", "U_BLOCKED", "1000.0002");
338
+ bufferThreadMessage("C_STEER_POLICY", "1000.0001", "Keep this", "U_ALLOWED", "1000.0003");
339
+ await instantFlush("C_STEER_POLICY:1000.0001");
340
+ expect((await getSteeringMessagesForTask(task.id)).map((row) => row.body)).toEqual([
341
+ "Keep this",
342
+ ]);
343
+ expect(acked).toContain("1000.0002:no_entry");
344
+ expect(posted).toHaveLength(1);
345
+ expect(await getChildTasks(task.id)).toEqual([]);
346
+ expect(
347
+ (await getLogsByTaskId(task.id)).filter((log) => log.newValue === "slack_reaction"),
348
+ ).toEqual([expect.objectContaining({ metadata: expect.stringContaining("1000.0003") })]);
349
+ } finally {
350
+ appSpy.mockRestore();
351
+ }
352
+ });
353
+ });
@@ -29,6 +29,11 @@ describe("extension bundle validation", () => {
29
29
  expect(await validateBundle(bundle)).toEqual({ ok: true, manifest: bundle.manifest });
30
30
  });
31
31
 
32
+ test("accepts the steering event and message rewrite types", async () => {
33
+ const bundle = await loadBundleFixture("steering-policy");
34
+ expect(await validateBundle(bundle)).toEqual({ ok: true, manifest: bundle.manifest });
35
+ });
36
+
32
37
  test("accepts zod and stdlib imports", async () => {
33
38
  const bundle = await loadBundleFixture("minimal");
34
39
  bundle.files["hooks.ts"] = `import { table } from "stdlib";
@@ -0,0 +1,39 @@
1
+ import { block, modify, type SwarmExtension } from "swarm-extension";
2
+ import { z } from "zod";
3
+
4
+ export const config = z.object({
5
+ action: z.enum(["continue", "modify", "block", "empty", "retarget", "throw"]),
6
+ prefix: z.string().default("rewritten: "),
7
+ slackUserId: z.string().optional(),
8
+ });
9
+ const manifest = {
10
+ name: "steering-policy",
11
+ description: "Records, rewrites, or blocks steering messages",
12
+ version: "1.0.0",
13
+ runtime: "api",
14
+ assets: { hooks: "hooks.ts" },
15
+ config,
16
+ } as const;
17
+
18
+ const extension: SwarmExtension<typeof manifest> = (api) => {
19
+ api.on("pre.task.steer", async (event, ctx) => {
20
+ await ctx.state.set("last", event);
21
+ if (ctx.config.slackUserId && event.slack?.userId !== ctx.config.slackUserId) return;
22
+ switch (ctx.config.action) {
23
+ case "continue":
24
+ return { action: "continue" };
25
+ case "modify":
26
+ return modify({ message: ctx.config.prefix + event.message });
27
+ case "block":
28
+ return block("Steering denied by policy");
29
+ case "empty":
30
+ return modify({ message: " \n " });
31
+ case "retarget":
32
+ return modify({ message: "Unauthorized rewrite", mode: "steer" });
33
+ case "throw":
34
+ throw new Error("Steering policy failed");
35
+ }
36
+ });
37
+ };
38
+
39
+ export default extension;
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "steering-policy",
3
+ "description": "Records, rewrites, or blocks steering messages",
4
+ "version": "1.0.0",
5
+ "runtime": "api",
6
+ "assets": { "hooks": "hooks.ts" }
7
+ }