@desplega.ai/agent-swarm 1.131.1 → 1.134.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 (360) hide show
  1. package/README.md +3 -3
  2. package/dist/{actions-jgcy477k.js → actions-7kast243.js} +20 -18
  3. package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-706yad38.js} +33 -17
  4. package/dist/app-0g84t8wf.js +28 -0
  5. package/dist/{artifact-jysj3wrg.js → artifact-dje5ke4h.js} +37 -131
  6. package/dist/{assistant-f2vkk1hj.js → assistant-ac4htcky.js} +25 -22
  7. package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-be59fbsz.js} +13 -13
  8. package/dist/{boot-reembed-q7mp8hcr.js → boot-reembed-b1rcqn85.js} +13 -11
  9. package/dist/{boot-reembed-xmw8wcx6.js → boot-reembed-kw2541c4.js} +14 -12
  10. package/dist/{boot-scrub-logs-dsqrv79n.js → boot-scrub-logs-ja29t92g.js} +11 -9
  11. package/dist/{claude-adapter-q6x7yspx.js → claude-adapter-5cg1fv8s.js} +33 -29
  12. package/dist/{claude-managed-adapter-wsg37bt8.js → claude-managed-adapter-9z9sdytx.js} +9 -9
  13. package/dist/{claude-managed-setup-52z4mq9w.js → claude-managed-setup-3zqp51cr.js} +5 -5
  14. package/dist/{claude-1ex232fc.js → claude-wqn3dtca.js} +1 -1
  15. package/dist/{cli-4wgngmwv.js → cli-00w4hbam.js} +1 -1
  16. package/dist/{cli-er3a4bb0.js → cli-15429qdw.js} +6 -1
  17. package/dist/{cli-e5dabkx0.js → cli-1cwhyvta.js} +4 -4
  18. package/dist/{cli-zsspvvm2.js → cli-1ybr6vw2.js} +237 -237
  19. package/dist/{cli-xpxde7rm.js → cli-2wv4kj0e.js} +60 -31
  20. package/dist/cli-34w2584r.js +530 -0
  21. package/dist/{cli-se2p6rkb.js → cli-38taxznr.js} +6 -6
  22. package/dist/{cli-9sbhdpvb.js → cli-3cgfyfzm.js} +14 -14
  23. package/dist/{cli-5n6e5yfn.js → cli-3zmdzfjf.js} +79 -74
  24. package/dist/{cli-3ty0t082.js → cli-43x36mty.js} +197 -8
  25. package/dist/{cli-ycrnynpw.js → cli-480nehnh.js} +29 -22
  26. package/dist/{cli-0258203w.js → cli-492e7ap5.js} +140 -20
  27. package/dist/{cli-3fj60etf.js → cli-497b685t.js} +3 -3
  28. package/dist/{cli-pxfxkx5m.js → cli-4bap00k0.js} +1 -1
  29. package/dist/{cli-w2wxd56v.js → cli-4bc8vmhm.js} +848 -770
  30. package/dist/{cli-aca5pz9f.js → cli-589599p9.js} +1 -1
  31. package/dist/{cli-t930g0xh.js → cli-5tnr64pm.js} +1 -1
  32. package/dist/{cli-jxg3s9ey.js → cli-65d93ves.js} +7 -7
  33. package/dist/{cli-6dpytdr2.js → cli-6x363v1z.js} +2 -2
  34. package/dist/{cli-dxak0fqd.js → cli-787ph852.js} +11 -11
  35. package/dist/{cli-793jvbsh.js → cli-7bv8nckk.js} +16 -11
  36. package/dist/{cli-zyxs93y9.js → cli-7jcc9y1r.js} +3 -3
  37. package/dist/{cli-f1am9szv.js → cli-87t2gstf.js} +1 -1
  38. package/dist/{cli-9aryf9tv.js → cli-95m51ztz.js} +1 -1
  39. package/dist/{cli-4w4fmq9x.js → cli-9w49enmh.js} +2 -2
  40. package/dist/{cli-v69vk5h0.js → cli-9y5b02vr.js} +13 -13
  41. package/dist/{cli-x23me2mg.js → cli-a1rrkrb4.js} +1 -1
  42. package/dist/{cli-psrqtw2m.js → cli-a3pkfpqs.js} +2 -2
  43. package/dist/{cli-h0rgxd24.js → cli-a4ajmfvp.js} +6 -6
  44. package/dist/{cli-ygrbmjja.js → cli-aw8bb6sf.js} +6 -6
  45. package/dist/{cli-g74vpjp2.js → cli-brm3et47.js} +55 -22
  46. package/dist/{cli-czkae1fp.js → cli-c5ddbdfj.js} +9 -9
  47. package/dist/{cli-114fvtmj.js → cli-c79a9zrc.js} +1 -1
  48. package/dist/{cli-49n9f49x.js → cli-dx3dz0ms.js} +3 -3
  49. package/dist/{cli-6dp3ydxa.js → cli-e07exmsb.js} +18 -14
  50. package/dist/cli-et1bbwqf.js +13 -0
  51. package/dist/{cli-yazx6hap.js → cli-f3s9dry0.js} +14 -14
  52. package/dist/{cli-t8d349cy.js → cli-f4wj0s3m.js} +3 -3
  53. package/dist/{cli-d6n0thxw.js → cli-f64gdfxc.js} +1 -1
  54. package/dist/{cli-pwfsqc2x.js → cli-fyd58xgb.js} +55 -55
  55. package/dist/{cli-7dm2zqy2.js → cli-g0j14czv.js} +2 -2
  56. package/dist/{cli-b0p7rfnd.js → cli-g2p9bd1z.js} +14 -14
  57. package/dist/{cli-z905tvmm.js → cli-g814jbst.js} +2 -2
  58. package/dist/{cli-5dsbcjjw.js → cli-ghexzd2d.js} +970 -970
  59. package/dist/{cli-jvv0g8bh.js → cli-gpt9kf01.js} +15 -15
  60. package/dist/{cli-rgrfzg8z.js → cli-grt6qv91.js} +380 -380
  61. package/dist/{cli-wz9115x0.js → cli-gtafvdy0.js} +8 -8
  62. package/dist/{cli-q5dzzak0.js → cli-gy07xk4s.js} +2 -2
  63. package/dist/{cli-sy7jk3tx.js → cli-h0djenzm.js} +2 -2
  64. package/dist/{cli-nrkkknat.js → cli-h37gbjbf.js} +2 -281
  65. package/dist/{cli-jevgrb4w.js → cli-h39xxnq6.js} +1200 -491
  66. package/dist/{cli-gz9sbyk3.js → cli-jepmgn7r.js} +1 -1
  67. package/dist/{cli-ccf9vy7q.js → cli-jmvk6et8.js} +7 -7
  68. package/dist/{cli-2rk0cx7s.js → cli-jxj92gkt.js} +51 -41
  69. package/dist/{cli-bqw0mt1n.js → cli-k46t4664.js} +18 -3
  70. package/dist/{cli-50vbt00w.js → cli-m07313tr.js} +1153 -696
  71. package/dist/{cli-4j2mrzag.js → cli-mkq1rk7b.js} +1 -1
  72. package/dist/{cli-1z7048dy.js → cli-mnm7w46e.js} +1 -1
  73. package/dist/{cli-kd1ck4rx.js → cli-mp9sg4cg.js} +14 -8
  74. package/dist/{cli-jbcva0xj.js → cli-mrammd8c.js} +60 -35
  75. package/dist/cli-nanaxrz6.js +27 -0
  76. package/dist/{cli-p9swy5t3.js → cli-pq2ee5ja.js} +8 -6
  77. package/dist/{cli-2wadx7p2.js → cli-pt43pdp1.js} +2 -2
  78. package/dist/{cli-7sgrxg9a.js → cli-q6d0vvey.js} +4 -4
  79. package/dist/{cli-k9qkf1hw.js → cli-qf9kpsve.js} +9 -9
  80. package/dist/cli-qh4sqfws.js +15 -0
  81. package/dist/{cli-e15jhafb.js → cli-qntkxysp.js} +14 -14
  82. package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
  83. package/dist/{cli-z2zcxes1.js → cli-qt5bt3vk.js} +1 -1
  84. package/dist/{cli-wx82tw1k.js → cli-qtekrty1.js} +3 -3
  85. package/dist/{cli-g1re8r05.js → cli-rh6f3s9n.js} +3 -3
  86. package/dist/{cli-n0krnc6d.js → cli-rzhcrw2p.js} +2 -2
  87. package/dist/{cli-gecy3z2s.js → cli-snw62ze5.js} +4 -4
  88. package/dist/{cli-n6h8qt8n.js → cli-snz3p4yf.js} +15 -10
  89. package/dist/{cli-r6kqqbj0.js → cli-tkf8n6cw.js} +11 -11
  90. package/dist/{cli-4rkfg1wx.js → cli-tkrr24mz.js} +14 -9
  91. package/dist/{cli-9dr5qzc0.js → cli-tv9f1kyb.js} +54 -43
  92. package/dist/{cli-gm9cbhbq.js → cli-vtz6yj3v.js} +28 -9
  93. package/dist/{cli-2f07m0xr.js → cli-wf86d5t7.js} +1 -1
  94. package/dist/{cli-7zd05xb4.js → cli-whrvct19.js} +45 -45
  95. package/dist/{cli-ap1czx8f.js → cli-wrrea8xm.js} +3 -3
  96. package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
  97. package/dist/{cli-cwy5hqbq.js → cli-yjmk7tzc.js} +1 -1
  98. package/dist/{cli-6pcz9h15.js → cli-zykjrwcx.js} +180 -394
  99. package/dist/cli.js +45 -45
  100. package/dist/{codex-adapter-htjkn22b.js → codex-adapter-hsq6mz3m.js} +21 -19
  101. package/dist/{codex-hook-hq54t9ft.js → codex-hook-8c500gq8.js} +8 -7
  102. package/dist/{codex-login-z6yhbkmk.js → codex-login-x7603481.js} +3 -3
  103. package/dist/{codex-session-runner-wjfc7smz.js → codex-session-runner-c1x3x143.js} +15 -13
  104. package/dist/{commands-xhrfwqsk.js → commands-rwtddtst.js} +11 -9
  105. package/dist/{db-anvwb22f.js → db-j2b355vz.js} +458 -444
  106. package/dist/{devin-adapter-v1sarqtw.js → devin-adapter-8qmhqpy3.js} +3 -3
  107. package/dist/{devtools-2y1r7yqk.js → devtools-j2rnj8w7.js} +4 -4
  108. package/dist/{e2b-jdkwemv2.js → e2b-znpx78eg.js} +16 -16
  109. package/dist/{es5-vjgtkgp6.js → es5-fmqrjzy3.js} +2 -2
  110. package/dist/{event-bus-axn823e9.js → event-bus-n8sb35pw.js} +4 -4
  111. package/dist/{getMachineId-bsd-6tc9wxdj.js → getMachineId-bsd-wpzbhkm6.js} +4 -4
  112. package/dist/{getMachineId-darwin-sjt9sgnc.js → getMachineId-darwin-45zabjy0.js} +4 -4
  113. package/dist/{getMachineId-linux-v5n18y9p.js → getMachineId-linux-warnp5pd.js} +3 -3
  114. package/dist/{getMachineId-unsupported-yrjyqw3d.js → getMachineId-unsupported-5spent2a.js} +3 -3
  115. package/dist/{getMachineId-win-w3r8063c.js → getMachineId-win-v4bvcw7a.js} +4 -4
  116. package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-b0wzss55.js} +17 -14
  117. package/dist/{google-vertex-p5arrga0.js → google-vertex-e8q4dpya.js} +18 -15
  118. package/dist/{handlers-b9kbc621.js → handlers-hchgrxkh.js} +32 -29
  119. package/dist/{hook-xkvn6c5h.js → hook-p49egqc0.js} +66 -52
  120. package/dist/hook-spb00w76.js +34 -0
  121. package/dist/{http-9xh6ta4r.js → http-nh14kkms.js} +663 -181
  122. package/dist/index-1qthq7vc.js +6 -0
  123. package/dist/{index-0g68f0p6.js → index-20xqmn3v.js} +3 -3
  124. package/dist/{index-vdkr5z2w.js → index-3e03k6e4.js} +114 -114
  125. package/dist/{index-ywds4812.js → index-4cbyhq2g.js} +43 -41
  126. package/dist/index-67pzqfrb.js +36 -0
  127. package/dist/{index-bccrkdkn.js → index-7m8yxkw5.js} +16 -16
  128. package/dist/{index-eaxyqcmj.js → index-dvsk64sm.js} +163 -83
  129. package/dist/{index-dqkpnmqm.js → index-e19s2m8k.js} +18 -18
  130. package/dist/{index-j30k5zxn.js → index-emvxcy98.js} +16 -16
  131. package/dist/{index-7ndw6ecy.js → index-gqnmr60d.js} +9 -9
  132. package/dist/{index-9znhar3x.js → index-j02xvjtd.js} +184 -185
  133. package/dist/{index-1s2442yw.js → index-ns5kksc1.js} +7 -7
  134. package/dist/{index-dpy2hth8.js → index-pdxc8kfh.js} +71 -71
  135. package/dist/{index-z1m7pw0v.js → index-ptcqm8bc.js} +107 -107
  136. package/dist/{index-070v4eyb.js → index-pyp7rexm.js} +5 -6
  137. package/dist/{index-86znjz1p.js → index-shne41sd.js} +13 -13
  138. package/dist/{index-2qn2qndv.js → index-t36q2v4t.js} +183 -183
  139. package/dist/index-tcnvv8w6.js +7 -0
  140. package/dist/index-w91qbfv1.js +7 -0
  141. package/dist/{index-fzc0mwvj.js → index-yqweff5n.js} +32 -32
  142. package/dist/index-yrfphye1.js +39 -0
  143. package/dist/{keepalive-vc7s2aa6.js → keepalive-9k51nqhc.js} +21 -15
  144. package/dist/lead-2xq3dxbz.js +88 -0
  145. package/dist/maintenance-0c0g6nym.js +28 -0
  146. package/dist/mistral-conversations-2njwdwhj.js +717 -0
  147. package/dist/{multipart-parser-qaktc7p8.js → multipart-parser-6gftesw0.js} +2 -2
  148. package/dist/{oauth-refresh-sweep-yecgjpk4.js → oauth-refresh-sweep-273a64r2.js} +14 -12
  149. package/dist/{onboard-xj0f73f9.js → onboard-tsv9wwhz.js} +12 -12
  150. package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-kt84eq86.js} +26 -26
  151. package/dist/{openai-completions-3vkmfz2a.js → openai-completions-mrypq7qp.js} +17 -16
  152. package/dist/{openai-responses-dg4t7j7a.js → openai-responses-qhrk21ez.js} +20 -17
  153. package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-rar9676j.js} +13 -11
  154. package/dist/{openrouter-images-g3gm8931.js → openrouter-images-684fysx9.js} +5 -5
  155. package/dist/{otel-impl-nxg0n0b3.js → otel-impl-wp1mfzd2.js} +752 -537
  156. package/dist/{photon_rs-ky7dj5qw.js → photon_rs-8dwt2pzy.js} +581 -581
  157. package/dist/{pi-messages-tqj8q6b9.js → pi-messages-txp9f97r.js} +7 -7
  158. package/dist/{pi-mono-adapter-4g5kby31.js → pi-mono-adapter-yz47kk8e.js} +1910 -1180
  159. package/dist/pricing-refresh-jw1yw2fn.js +22 -0
  160. package/dist/rbac-roles-dg964c8p.js +32 -0
  161. package/dist/{rbac-roles-1hcxdtg7.js → rbac-roles-p53cg5cv.js} +19 -17
  162. package/dist/{registry-02cy9z5z.js → registry-mh12y17v.js} +4 -4
  163. package/dist/render-v2-56vn2rk9.js +36 -0
  164. package/dist/seed-pricing-52xx6z63.js +19 -0
  165. package/dist/{setup-46xtgq3c.js → setup-e8gp79sy.js} +7 -7
  166. package/dist/token-util-zy908s1a.js +6 -0
  167. package/dist/{token-fxkn2zf2.js → token-xg5711x5.js} +4 -4
  168. package/dist/worker-s17f1g46.js +88 -0
  169. package/dist/{x-6mg4kfpe.js → x-xkfh0w0v.js} +5 -5
  170. package/openapi.json +566 -22
  171. package/package.json +6 -6
  172. package/src/be/budget-refusal-notify.ts +14 -7
  173. package/src/be/db-queries/task-artifacts.ts +182 -0
  174. package/src/be/db.ts +834 -171
  175. package/src/be/events.ts +5 -0
  176. package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
  177. package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
  178. package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
  179. package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
  180. package/src/be/migrations/136_task_requester_provenance.sql +37 -0
  181. package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
  182. package/src/be/migrations/runner.ts +17 -11
  183. package/src/be/multi-runtime.ts +430 -0
  184. package/src/be/scripts/extract-schema.ts +3 -1
  185. package/src/be/scripts/typecheck.ts +6 -2
  186. package/src/be/seed-prompt-templates.ts +25 -2
  187. package/src/be/seed-skills/index.ts +7 -0
  188. package/src/be/steering.ts +1 -1
  189. package/src/be/swarm-config-guard.ts +18 -0
  190. package/src/commands/credential-wait.ts +54 -0
  191. package/src/commands/onboard/steps/core-credentials.tsx +1 -1
  192. package/src/commands/onboard/steps/custom-templates.tsx +1 -1
  193. package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
  194. package/src/commands/onboard/steps/health-check.tsx +1 -1
  195. package/src/commands/onboard/steps/start.tsx +1 -1
  196. package/src/commands/profile-sync-audit.ts +56 -0
  197. package/src/commands/profile-sync.ts +287 -15
  198. package/src/commands/provider-credentials.ts +46 -19
  199. package/src/commands/runner.ts +199 -33
  200. package/src/commands/templates.ts +22 -0
  201. package/src/github/handlers.ts +55 -9
  202. package/src/heartbeat/heartbeat.ts +109 -17
  203. package/src/hooks/hook.ts +49 -33
  204. package/src/http/active-sessions.ts +11 -1
  205. package/src/http/agents.ts +261 -30
  206. package/src/http/all-routes.ts +1 -0
  207. package/src/http/config.ts +13 -2
  208. package/src/http/core.ts +102 -4
  209. package/src/http/db-query-bounded.ts +313 -0
  210. package/src/http/db-query-shared.ts +249 -0
  211. package/src/http/db-query.ts +54 -42
  212. package/src/http/events.ts +2 -0
  213. package/src/http/index.ts +23 -2
  214. package/src/http/mcp-bridge.ts +37 -3
  215. package/src/http/mcp.ts +12 -0
  216. package/src/http/memory.ts +13 -2
  217. package/src/http/openapi.ts +1 -0
  218. package/src/http/poll.ts +70 -27
  219. package/src/http/prompt-templates.ts +36 -13
  220. package/src/http/route-def.ts +43 -9
  221. package/src/http/schedules.ts +2 -2
  222. package/src/http/scripts.ts +10 -1
  223. package/src/http/session-data.ts +44 -0
  224. package/src/http/tasks.ts +2 -2
  225. package/src/http/users.ts +74 -52
  226. package/src/http/webhooks.ts +5 -2
  227. package/src/http/workflows.ts +1 -4
  228. package/src/integrations/kapso/inbound.ts +1 -1
  229. package/src/jira/outbound.ts +39 -8
  230. package/src/jira/webhook-lifecycle.ts +13 -2
  231. package/src/linear/outbound.ts +43 -10
  232. package/src/oauth/keepalive.ts +13 -2
  233. package/src/prompts/base-prompt.ts +1 -1
  234. package/src/prompts/default-drift.ts +21 -0
  235. package/src/prompts/session-templates.ts +56 -7
  236. package/src/providers/claude-adapter.ts +6 -0
  237. package/src/providers/codex-adapter.ts +6 -0
  238. package/src/providers/opencode-adapter.ts +6 -0
  239. package/src/providers/pi-mono-adapter.ts +8 -0
  240. package/src/providers/pricing-sources.md +24 -0
  241. package/src/queue-stall-alarm.ts +216 -0
  242. package/src/rbac/legacy-policy.ts +3 -0
  243. package/src/rbac/permissions.ts +12 -0
  244. package/src/scheduler/scheduler.ts +7 -0
  245. package/src/script-workflows/executor.ts +2 -1
  246. package/src/scripts-runtime/executors/native.ts +2 -1
  247. package/src/scripts-runtime/executors/types.ts +5 -0
  248. package/src/scripts-runtime/loader.ts +5 -0
  249. package/src/scripts-runtime/sdk-allowlist.ts +3 -0
  250. package/src/scripts-runtime/swarm-config.ts +8 -0
  251. package/src/scripts-runtime/swarm-sdk.ts +7 -0
  252. package/src/scripts-runtime/types/stdlib.d.ts +4 -0
  253. package/src/scripts-runtime/types/swarm-sdk.d.ts +4 -0
  254. package/src/server.ts +6 -0
  255. package/src/slack/channel-join.ts +22 -3
  256. package/src/slack/channel-lifecycle.ts +114 -0
  257. package/src/tests/budget-claim-gate.test.ts +20 -0
  258. package/src/tests/claude-adapter.test.ts +32 -0
  259. package/src/tests/codex-adapter.test.ts +30 -0
  260. package/src/tests/create-task-validation.test.ts +128 -0
  261. package/src/tests/credential-wait.test.ts +67 -1
  262. package/src/tests/db-query.test.ts +711 -2
  263. package/src/tests/heartbeat-checklist.test.ts +28 -0
  264. package/src/tests/http-api-integration.test.ts +81 -0
  265. package/src/tests/http-users.test.ts +166 -1
  266. package/src/tests/identity-field-budget.test.ts +176 -0
  267. package/src/tests/mcp-tools-user.test.ts +34 -0
  268. package/src/tests/mcp-user-route.test.ts +17 -0
  269. package/src/tests/metrics-http.test.ts +44 -0
  270. package/src/tests/migration-failure.test.ts +86 -0
  271. package/src/tests/multi-runtime-registration.test.ts +2810 -0
  272. package/src/tests/openapi-response-contract.test.ts +24 -0
  273. package/src/tests/openapi-route-headers.test.ts +83 -0
  274. package/src/tests/opencode-adapter.test.ts +40 -0
  275. package/src/tests/outbound-listener-rejection.test.ts +138 -0
  276. package/src/tests/pi-mono-adapter.test.ts +98 -0
  277. package/src/tests/preload.ts +2 -0
  278. package/src/tests/profile-sync-warning.test.ts +277 -0
  279. package/src/tests/profile-sync.test.ts +232 -6
  280. package/src/tests/prompt-template-resolver.test.ts +177 -6
  281. package/src/tests/prompt-template-session.test.ts +39 -3
  282. package/src/tests/queue-stall-alarm.test.ts +226 -0
  283. package/src/tests/rbac-charact-slack.test.ts +29 -0
  284. package/src/tests/rbac-engine.test.ts +3 -0
  285. package/src/tests/requester-comms.test.ts +32 -0
  286. package/src/tests/runner-requester-profile.test.ts +38 -0
  287. package/src/tests/schedule-target-type.test.ts +38 -0
  288. package/src/tests/script-connections.test.ts +13 -1
  289. package/src/tests/scripts-runtime-identity.test.ts +375 -0
  290. package/src/tests/scripts-runtime.test.ts +4 -3
  291. package/src/tests/sdk-allowlist.test.ts +47 -0
  292. package/src/tests/send-task-requested-by.test.ts +9 -0
  293. package/src/tests/session-costs.test.ts +506 -1
  294. package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
  295. package/src/tests/slack-channel-lifecycle.test.ts +241 -0
  296. package/src/tests/slack-render-v2.test.ts +43 -0
  297. package/src/tests/steering-core.test.ts +28 -0
  298. package/src/tests/swarm-config-reserved-keys.test.ts +22 -0
  299. package/src/tests/task-artifacts.test.ts +386 -0
  300. package/src/tests/task-attachments-schema.test.ts +17 -1
  301. package/src/tests/task-completion-idempotency.test.ts +57 -0
  302. package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
  303. package/src/tests/tool-annotations.test.ts +4 -0
  304. package/src/tests/update-profile-api.test.ts +67 -39
  305. package/src/tests/update-profile-auth.test.ts +48 -4
  306. package/src/tests/user-attribution-migration.test.ts +54 -0
  307. package/src/tests/user-identity.test.ts +72 -0
  308. package/src/tests/workflow-async-v2.test.ts +39 -1
  309. package/src/tests/workflow-http-v2.test.ts +3 -2
  310. package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
  311. package/src/tests/workflow-retry-v2.test.ts +45 -1
  312. package/src/tools/db-query.ts +18 -8
  313. package/src/tools/get-task-details.ts +24 -3
  314. package/src/tools/manage-user.ts +36 -3
  315. package/src/tools/poll-task.ts +93 -14
  316. package/src/tools/script-common.ts +5 -0
  317. package/src/tools/send-task.ts +1 -4
  318. package/src/tools/slack-archive-channel.ts +70 -0
  319. package/src/tools/slack-create-channel.ts +74 -0
  320. package/src/tools/slack-invite-to-channel.ts +78 -0
  321. package/src/tools/store-progress.ts +13 -2
  322. package/src/tools/swarm-config/delete-config.ts +9 -0
  323. package/src/tools/swarm-config/set-config.ts +3 -2
  324. package/src/tools/task-action.ts +30 -0
  325. package/src/tools/task-tool-ctx.ts +8 -1
  326. package/src/tools/tool-config.ts +4 -1
  327. package/src/tools/update-profile.ts +16 -21
  328. package/src/tools/utils.ts +5 -0
  329. package/src/tools/workflows/trigger-workflow.ts +7 -1
  330. package/src/types.ts +165 -1
  331. package/src/utils/github-pull-request.ts +76 -0
  332. package/src/utils/identity-field-budget.ts +73 -0
  333. package/src/utils/multi-runtime.ts +20 -0
  334. package/src/utils/requester-comms.ts +24 -0
  335. package/src/workflows/engine.ts +22 -2
  336. package/src/workflows/resume.ts +19 -11
  337. package/src/workflows/retry-poller.ts +1 -0
  338. package/src/workflows/triggers.ts +4 -0
  339. package/templates/skills/db-query-guidance/config.json +14 -0
  340. package/templates/skills/db-query-guidance/content.md +57 -0
  341. package/dist/app-5da78bd4.js +0 -26
  342. package/dist/cli-3te4jdkm.js +0 -225
  343. package/dist/cli-bmze986d.js +0 -7
  344. package/dist/cli-kh3ba2zf.js +0 -607
  345. package/dist/hook-cgk4wxmx.js +0 -31
  346. package/dist/index-25wjsdv2.js +0 -34
  347. package/dist/index-76at2ymp.js +0 -7
  348. package/dist/index-hase6zbe.js +0 -6
  349. package/dist/index-vg4snay4.js +0 -36
  350. package/dist/index-vy8edt27.js +0 -7
  351. package/dist/lead-r2yqfmx7.js +0 -85
  352. package/dist/maintenance-mw70k468.js +0 -26
  353. package/dist/mistral-conversations-81fwav0j.js +0 -30578
  354. package/dist/otel-nqa12wf3.js +0 -3149
  355. package/dist/pricing-refresh-2cn6effr.js +0 -20
  356. package/dist/rbac-roles-vynday5g.js +0 -30
  357. package/dist/render-v2-tc0bh8kk.js +0 -34
  358. package/dist/seed-pricing-8kceb43f.js +0 -17
  359. package/dist/token-util-pqyc8kng.js +0 -6
  360. package/dist/worker-qrbz6hmv.js +0 -85
@@ -10,9 +10,10 @@ import {
10
10
  resolvePromptTemplate,
11
11
  upsertPromptTemplate,
12
12
  } from "../be/db";
13
+ import { getPromptTemplateDefaultDrift } from "../prompts/default-drift";
13
14
  import { getAllTemplateDefinitions, getTemplateDefinition } from "../prompts/registry";
14
15
  import { resolveTemplate } from "../prompts/resolver";
15
- import { PromptTemplateHistorySchema, PromptTemplateSchema } from "../types";
16
+ import { type PromptTemplate, PromptTemplateHistorySchema, PromptTemplateSchema } from "../types";
16
17
  import { interpolate } from "../utils/template";
17
18
  import { route } from "./route-def";
18
19
  import { jsonError } from "./utils";
@@ -35,6 +36,19 @@ const EventTemplateDefinitionResponseSchema = z.object({
35
36
  category: z.enum(["event", "system", "common", "task_lifecycle", "session"]),
36
37
  });
37
38
 
39
+ const PromptTemplateResponseSchema = PromptTemplateSchema.extend({
40
+ defaultDrifted: z.boolean(),
41
+ }).openapi("PromptTemplateResponse");
42
+
43
+ function toPromptTemplateResponse(template: PromptTemplate) {
44
+ const definition = getTemplateDefinition(template.eventType);
45
+ const defaultDrifted = definition
46
+ ? getPromptTemplateDefaultDrift(template, definition.defaultBody).defaultDrifted
47
+ : false;
48
+
49
+ return { ...template, defaultDrifted };
50
+ }
51
+
38
52
  /** Mirrors `ResolveResult` from src/prompts/resolver.ts */
39
53
  const ResolveResultSchema = z.object({
40
54
  text: z.string(),
@@ -46,7 +60,10 @@ const ResolveResultSchema = z.object({
46
60
 
47
61
  /** Mirrors the return type of `resolvePromptTemplate` in src/be/db.ts */
48
62
  const DbResolveResultSchema = z
49
- .union([z.object({ template: PromptTemplateSchema }), z.object({ skip: z.literal(true) })])
63
+ .union([
64
+ z.object({ template: PromptTemplateResponseSchema }),
65
+ z.object({ skip: z.literal(true) }),
66
+ ])
50
67
  .nullable();
51
68
 
52
69
  // ─── Route Definitions ───────────────────────────────────────────────────────
@@ -142,7 +159,7 @@ const checkoutRoute = route({
142
159
  responses: {
143
160
  200: {
144
161
  description: "Checked-out template",
145
- schema: z.object({ template: PromptTemplateSchema }),
162
+ schema: z.object({ template: PromptTemplateResponseSchema }),
146
163
  },
147
164
  400: { description: "Validation error" },
148
165
  404: { description: "Template or version not found" },
@@ -158,7 +175,10 @@ const resetRoute = route({
158
175
  tags: ["PromptTemplates"],
159
176
  params: z.object({ id: z.string() }),
160
177
  responses: {
161
- 200: { description: "Reset template", schema: z.object({ template: PromptTemplateSchema }) },
178
+ 200: {
179
+ description: "Reset template",
180
+ schema: z.object({ template: PromptTemplateResponseSchema }),
181
+ },
162
182
  404: { description: "Template not found or no code default available" },
163
183
  },
164
184
  auth: { apiKey: true },
@@ -175,7 +195,7 @@ const getByIdRoute = route({
175
195
  200: {
176
196
  description: "Template with history",
177
197
  schema: z.object({
178
- template: PromptTemplateSchema,
198
+ template: PromptTemplateResponseSchema,
179
199
  history: z.array(PromptTemplateHistorySchema),
180
200
  }),
181
201
  },
@@ -217,7 +237,7 @@ const listRoute = route({
217
237
  responses: {
218
238
  200: {
219
239
  description: "List of prompt templates",
220
- schema: z.object({ templates: z.array(PromptTemplateSchema) }),
240
+ schema: z.object({ templates: z.array(PromptTemplateResponseSchema) }),
221
241
  },
222
242
  },
223
243
  auth: { apiKey: true },
@@ -241,7 +261,7 @@ const upsertRoute = route({
241
261
  responses: {
242
262
  200: {
243
263
  description: "Upserted template",
244
- schema: z.object({ template: PromptTemplateSchema }),
264
+ schema: z.object({ template: PromptTemplateResponseSchema }),
245
265
  },
246
266
  400: { description: "Validation error" },
247
267
  },
@@ -273,7 +293,10 @@ export async function handlePromptTemplates(
273
293
 
274
294
  resolvedRoute.respond(res, 200, {
275
295
  resolution: result,
276
- dbResult,
296
+ dbResult:
297
+ dbResult && "template" in dbResult
298
+ ? { template: toPromptTemplateResponse(dbResult.template) }
299
+ : dbResult,
277
300
  definition: definition
278
301
  ? {
279
302
  eventType: definition.eventType,
@@ -339,7 +362,7 @@ export async function handlePromptTemplates(
339
362
 
340
363
  try {
341
364
  const template = checkoutPromptTemplate(parsed.params.id, parsed.body.version);
342
- checkoutRoute.respond(res, 200, { template });
365
+ checkoutRoute.respond(res, 200, { template: toPromptTemplateResponse(template) });
343
366
  } catch (error) {
344
367
  const message = error instanceof Error ? error.message : "Unknown error";
345
368
  if (message.includes("not found")) {
@@ -370,7 +393,7 @@ export async function handlePromptTemplates(
370
393
 
371
394
  try {
372
395
  const template = resetPromptTemplateToDefault(parsed.params.id, definition.defaultBody);
373
- resetRoute.respond(res, 200, { template });
396
+ resetRoute.respond(res, 200, { template: toPromptTemplateResponse(template) });
374
397
  } catch (error) {
375
398
  const message = error instanceof Error ? error.message : "Unknown error";
376
399
  jsonError(res, message, 400);
@@ -390,7 +413,7 @@ export async function handlePromptTemplates(
390
413
  }
391
414
 
392
415
  const history = getPromptTemplateHistory(parsed.params.id);
393
- getByIdRoute.respond(res, 200, { template, history });
416
+ getByIdRoute.respond(res, 200, { template: toPromptTemplateResponse(template), history });
394
417
  return true;
395
418
  }
396
419
 
@@ -425,7 +448,7 @@ export async function handlePromptTemplates(
425
448
  isDefault: parsed.query.isDefault ? parsed.query.isDefault === "true" : undefined,
426
449
  });
427
450
 
428
- listRoute.respond(res, 200, { templates });
451
+ listRoute.respond(res, 200, { templates: templates.map(toPromptTemplateResponse) });
429
452
  return true;
430
453
  }
431
454
 
@@ -464,7 +487,7 @@ export async function handlePromptTemplates(
464
487
  changedBy,
465
488
  changeReason,
466
489
  });
467
- upsertRoute.respond(res, 200, { template });
490
+ upsertRoute.respond(res, 200, { template: toPromptTemplateResponse(template) });
468
491
  } catch (error) {
469
492
  const message = error instanceof Error ? error.message : "Unknown error";
470
493
  jsonError(res, message, 500);
@@ -1,6 +1,7 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { z } from "zod";
3
3
  import type { PermissionVerb } from "../rbac";
4
+ import { scrubSecrets } from "../utils/secret-scrubber";
4
5
  import { jsonError, matchRoute, parseBody } from "./utils";
5
6
 
6
7
  // ─── Types ───────────────────────────────────────────────────────────────────
@@ -25,6 +26,24 @@ type ResponsesDef = Record<number, RouteResponseDef>;
25
26
  /** Extracts the payload type respond() accepts for one response def. */
26
27
  type ResponseData<R> = R extends { schema: infer S extends z.ZodType } ? z.input<S> : never;
27
28
 
29
+ /**
30
+ * Optional `X-Runtime-Instance-ID` header shared by the lifecycle and poll
31
+ * routes. Optional in the schema because the requirement is conditional:
32
+ * only multi-runtime mode needs it, and legacy deployments must keep working
33
+ * without it.
34
+ */
35
+ export const runtimeInstanceHeader = (operation: string) =>
36
+ z.object({
37
+ "X-Runtime-Instance-ID": z
38
+ .string()
39
+ .optional()
40
+ .describe(
41
+ "Identifies the concrete runtime instance (worker process) making the call, " +
42
+ `as generated at its boot. Required to ${operation} when multi-runtime mode ` +
43
+ "(MULTI_RUNTIME_ENABLED) is on; ignored otherwise.",
44
+ ),
45
+ });
46
+
28
47
  export interface RouteDef<
29
48
  TParams extends z.ZodType = z.ZodType,
30
49
  TQuery extends z.ZodType = z.ZodType,
@@ -41,6 +60,14 @@ export interface RouteDef<
41
60
  tags: string[];
42
61
  params?: TParams;
43
62
  query?: TQuery;
63
+ /**
64
+ * Request headers to publish as OpenAPI parameters, beyond the global
65
+ * `Authorization`/`X-Agent-ID` security schemes. Declaration-only: handlers
66
+ * still read headers off `req`, so adding this to a route changes the
67
+ * generated spec and nothing else. Mark each property `.optional()` unless
68
+ * the header is unconditionally required.
69
+ */
70
+ headers?: z.ZodObject;
44
71
  body?: TBody;
45
72
  responses: TResponses;
46
73
  auth?: {
@@ -187,10 +214,14 @@ export function describeRequestRoute(
187
214
  // ─── Factory ─────────────────────────────────────────────────────────────────
188
215
 
189
216
  /**
190
- * Runtime response validation gate for `respond()`. NODE_ENV is deliberately
191
- * unset on prod deploys, so this is opt-in outside `bun test` (which sets
192
- * NODE_ENV=test): response schemas must never introduce a production failure
193
- * mode — they fail loudly in tests and (opt-in) local dev instead.
217
+ * Runtime response validation gate for `respond()`. Only `bun test`
218
+ * (NODE_ENV=test) fails hard on a violation: one bad row must never take an
219
+ * endpoint down outside tests (2026-08-18 incident — a single priority='high'
220
+ * row 500'd every task listing). When the gate is on outside tests
221
+ * (NODE_ENV=development, or VALIDATE_HTTP_RESPONSES=true), `respond()` logs
222
+ * the violation and sends the response anyway. Note: the compiled Bun binary
223
+ * defaults NODE_ENV to "development", so deploys must also set
224
+ * NODE_ENV=production explicitly (see the deploy-env change in the fix PR).
194
225
  */
195
226
  const VALIDATE_RESPONSES =
196
227
  process.env.NODE_ENV === "test" ||
@@ -216,12 +247,15 @@ export function route<
216
247
  if (schema) {
217
248
  const result = schema.safeParse(data);
218
249
  if (!result.success) {
219
- throw new Error(
250
+ const detail =
220
251
  `Response schema violation: ${def.method.toUpperCase()} ${def.path} ${code} — ` +
221
- result.error.issues
222
- .map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
223
- .join("; "),
224
- );
252
+ result.error.issues
253
+ .map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
254
+ .join("; ");
255
+ if (process.env.NODE_ENV === "test") {
256
+ throw new Error(detail);
257
+ }
258
+ console.error(`[route-def] ${scrubSecrets(detail)}`);
225
259
  }
226
260
  }
227
261
  }
@@ -40,7 +40,7 @@ const scheduleUpdateBodySchema = z.object({
40
40
  taskTemplate: z.string().optional(),
41
41
  taskType: z.string().optional(),
42
42
  tags: z.array(z.string()).optional(),
43
- priority: z.number().int().optional(),
43
+ priority: z.number().int().min(0).max(100).optional(),
44
44
  targetAgentId: z.string().nullable().optional(),
45
45
  enabled: z.boolean().optional(),
46
46
  timezone: z.string().optional(),
@@ -96,7 +96,7 @@ const createSchedule = route({
96
96
  taskTemplate: z.string().min(1).optional(),
97
97
  taskType: z.string().optional(),
98
98
  tags: z.array(z.string()).optional(),
99
- priority: z.number().int().optional(),
99
+ priority: z.number().int().min(0).max(100).optional(),
100
100
  targetAgentId: z.string().optional(),
101
101
  enabled: z.boolean().optional(),
102
102
  timezone: z.string().optional(),
@@ -53,7 +53,7 @@ import {
53
53
  ScriptVersionRecordSchema,
54
54
  } from "../types";
55
55
  import { scrubObject, scrubSecrets } from "../utils/secret-scrubber";
56
- import { route } from "./route-def";
56
+ import { route, runtimeInstanceHeader } from "./route-def";
57
57
  import { json, jsonError } from "./utils";
58
58
 
59
59
  const scriptNameSchema = z.string().min(1).max(200);
@@ -244,6 +244,7 @@ const runRoute = route({
244
244
  description:
245
245
  "Inline source skips typecheck and is auto-saved as a scratch script only on success.",
246
246
  tags: ["Scripts"],
247
+ headers: runtimeInstanceHeader("acquire work through the script SDK"),
247
248
  body: runBodySchema,
248
249
  responses: {
249
250
  200: { description: "Script run completed", schema: scriptRunResponseSchema },
@@ -695,6 +696,13 @@ export async function handleScripts(
695
696
  const agent = requireAgent(res, agentId);
696
697
  if (!agent) return true;
697
698
 
699
+ // Per-boot identity of the invoking worker process. Carried into the
700
+ // script runtime as system context so SDK work-acquisition calls present
701
+ // the same runtime the worker itself would — never script input.
702
+ const runtimeInstanceId = ((h) => (Array.isArray(h) ? h[0] : h))(
703
+ req.headers["x-runtime-instance-id"],
704
+ );
705
+
698
706
  let source = parsed.body.source;
699
707
  let fsMode = parsed.body.fsMode;
700
708
  let namedScript: ScriptRecord | null = null;
@@ -726,6 +734,7 @@ export async function handleScripts(
726
734
  args: parsed.body.args,
727
735
  fsMode,
728
736
  agentId: agent.id,
737
+ runtimeInstanceId,
729
738
  egressSecrets: credentials.egressSecrets,
730
739
  failedBindings: credentials.failedBindings,
731
740
  apiConnections: getScriptApiConnectionDescriptors({ agentId: agent.id }),
@@ -5,6 +5,7 @@ import {
5
5
  createSessionLogs,
6
6
  getActivePricingRow,
7
7
  getAllSessionCosts,
8
+ getAttributionByPerson,
8
9
  getDashboardCostSummary,
9
10
  getSessionCostSummary,
10
11
  getSessionCostsByAgentId,
@@ -34,6 +35,9 @@ const SessionCostSummaryTotalsSchema = z.object({
34
35
  totalSessions: z.number().int(),
35
36
  avgCostPerSession: z.number(),
36
37
  attributedCostUsd: z.number(),
38
+ attributableCostUsd: z.number(),
39
+ excludedCostUsd: z.number(),
40
+ excludedTaskCount: z.number().int(),
37
41
  });
38
42
 
39
43
  /** Mirrors `SessionCostDailyRow` in src/be/db.ts. */
@@ -79,6 +83,17 @@ const DashboardCostSummarySchema = z.object({
79
83
  costMtd: z.number(),
80
84
  });
81
85
 
86
+ /** Mirrors `AttributionByPersonRow` in src/be/db.ts. */
87
+ const AttributionByPersonRowSchema = z.object({
88
+ userId: z.string(),
89
+ problemsInitiated: z.number().int(),
90
+ problemsShipped: z.number().int(),
91
+ agentsReached: z.number().int(),
92
+ reposReached: z.number().int(),
93
+ surfacesReached: z.number().int(),
94
+ firstPassYield: z.null(),
95
+ });
96
+
82
97
  // ─── Route Definitions ───────────────────────────────────────────────────────
83
98
 
84
99
  const createSessionLogsRoute = route({
@@ -203,6 +218,24 @@ const getSessionCostSummaryRoute = route({
203
218
  },
204
219
  });
205
220
 
221
+ const getAttributionByPersonRoute = route({
222
+ method: "get",
223
+ path: "/api/attribution/by-person",
224
+ pattern: ["api", "attribution", "by-person"],
225
+ summary: "Four-metric per-person attribution (problems initiated/shipped, reach)",
226
+ tags: ["Session Data"],
227
+ query: z.object({
228
+ startDate: z.string().optional(),
229
+ endDate: z.string().optional(),
230
+ }),
231
+ responses: {
232
+ 200: {
233
+ description: "Per-person attribution rows",
234
+ schema: z.object({ rows: z.array(AttributionByPersonRowSchema) }),
235
+ },
236
+ },
237
+ });
238
+
206
239
  const getDashboardCosts = route({
207
240
  method: "get",
208
241
  path: "/api/session-costs/dashboard",
@@ -403,6 +436,17 @@ export async function handleSessionData(
403
436
  return true;
404
437
  }
405
438
 
439
+ if (getAttributionByPersonRoute.match(req.method, pathSegments)) {
440
+ const parsed = await getAttributionByPersonRoute.parse(req, res, pathSegments, queryParams);
441
+ if (!parsed) return true;
442
+ const rows = getAttributionByPerson({
443
+ startDate: parsed.query.startDate || undefined,
444
+ endDate: parsed.query.endDate || undefined,
445
+ });
446
+ getAttributionByPersonRoute.respond(res, 200, { rows });
447
+ return true;
448
+ }
449
+
406
450
  if (listSessionCosts.match(req.method, pathSegments)) {
407
451
  const parsed = await listSessionCosts.parse(req, res, pathSegments, queryParams);
408
452
  if (!parsed) return true;
package/src/http/tasks.ts CHANGED
@@ -220,7 +220,7 @@ const createTask = route({
220
220
  agentId: z.string().optional(),
221
221
  taskType: z.string().optional(),
222
222
  tags: z.array(z.string()).optional(),
223
- priority: z.number().int().optional(),
223
+ priority: z.number().int().min(0).max(100).optional(),
224
224
  dependsOn: z.array(z.string()).optional(),
225
225
  offeredTo: z.string().optional(),
226
226
  dir: z.string().optional(),
@@ -760,7 +760,7 @@ export async function handleTasks(
760
760
  creatorAgentId: myAgentId || undefined,
761
761
  taskType: parsed.body.taskType || undefined,
762
762
  tags: parsed.body.tags || undefined,
763
- priority: parsed.body.priority || 50,
763
+ priority: parsed.body.priority,
764
764
  dependsOn: parsed.body.dependsOn || undefined,
765
765
  offeredTo: parsed.body.offeredTo || undefined,
766
766
  dir: parsed.body.dir || undefined,
package/src/http/users.ts CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  deleteKv,
33
33
  deleteUser,
34
34
  getAllUsers,
35
+ getDb,
35
36
  getUserById,
36
37
  listKv,
37
38
  updateUser,
@@ -48,7 +49,7 @@ import {
48
49
  revokeToken,
49
50
  unlinkIdentity,
50
51
  } from "../be/users";
51
- import { UserSchema } from "../types";
52
+ import { UserCommsPrefsSchema, UserSchema } from "../types";
52
53
  import { getRequestAuth } from "../utils/request-auth-context";
53
54
  import { getOperatorActor } from "./operator-actor";
54
55
  import { route } from "./route-def";
@@ -296,6 +297,11 @@ const updateUserRoute = route({
296
297
  preferredChannel: z.string().optional(),
297
298
  timezone: z.string().optional(),
298
299
  metadata: z.record(z.string(), z.unknown()).nullable().optional(),
300
+ comms: UserCommsPrefsSchema.nullable()
301
+ .optional()
302
+ .describe(
303
+ "Merges into metadata.comms without touching sibling metadata keys; null removes only the comms key. When metadata is also provided, it is applied first and replaces the whole blob.",
304
+ ),
299
305
  dailyBudgetUsd: z.number().nullable().optional(),
300
306
  status: z.enum(["invited", "active", "suspended"]).optional(),
301
307
  // Complete-list diff: passing this replaces the user's identity set,
@@ -776,60 +782,63 @@ export async function handleUsers(
776
782
  }
777
783
 
778
784
  try {
779
- // Move every identity from source → target.
780
- for (const ident of sourceBefore.identities) {
781
- unlinkIdentity(sourceId, ident.kind, ident.externalId, actor);
782
- linkIdentity(targetId, ident.kind, ident.externalId, actor);
783
- }
785
+ const mergedUser = getDb().transaction(() => {
786
+ // Move every identity from source → target.
787
+ for (const ident of sourceBefore.identities) {
788
+ unlinkIdentity(sourceId, ident.kind, ident.externalId, actor);
789
+ linkIdentity(targetId, ident.kind, ident.externalId, actor);
790
+ }
784
791
 
785
- // Merge email aliases — append source.email + source.emailAliases into
786
- // target.emailAliases (de-duped). Emit `email_added` per added alias.
787
- const targetAliases = new Set(targetBefore.emailAliases ?? []);
788
- const targetPrimary = (targetBefore.email ?? "").toLowerCase();
789
- const newAliases: string[] = [];
790
- const candidates = [
791
- ...(sourceBefore.email ? [sourceBefore.email] : []),
792
- ...(sourceBefore.emailAliases ?? []),
793
- ];
794
- for (const candidate of candidates) {
795
- const lower = candidate.toLowerCase();
796
- if (!lower || lower === targetPrimary) continue;
797
- if (
798
- ![...targetAliases].some((a) => a.toLowerCase() === lower) &&
799
- !newAliases.some((a) => a.toLowerCase() === lower)
800
- ) {
801
- newAliases.push(candidate);
792
+ // Merge email aliases — append source.email + source.emailAliases into
793
+ // target.emailAliases (de-duped). Emit `email_added` per added alias.
794
+ const targetAliases = new Set(targetBefore.emailAliases ?? []);
795
+ const targetPrimary = (targetBefore.email ?? "").toLowerCase();
796
+ const newAliases: string[] = [];
797
+ const candidates = [
798
+ ...(sourceBefore.email ? [sourceBefore.email] : []),
799
+ ...(sourceBefore.emailAliases ?? []),
800
+ ];
801
+ for (const candidate of candidates) {
802
+ const lower = candidate.toLowerCase();
803
+ if (!lower || lower === targetPrimary) continue;
804
+ if (
805
+ ![...targetAliases].some((a) => a.toLowerCase() === lower) &&
806
+ !newAliases.some((a) => a.toLowerCase() === lower)
807
+ ) {
808
+ newAliases.push(candidate);
809
+ }
802
810
  }
803
- }
804
- if (newAliases.length > 0) {
805
- const merged = [...(targetBefore.emailAliases ?? []), ...newAliases];
806
- updateUser(targetId, { emailAliases: merged });
807
- for (const alias of newAliases) {
808
- recordIdentityEvent(targetId, "email_added", actor, null, { email: alias });
811
+ if (newAliases.length > 0) {
812
+ const merged = [...(targetBefore.emailAliases ?? []), ...newAliases];
813
+ updateUser(targetId, { emailAliases: merged });
814
+ for (const alias of newAliases) {
815
+ recordIdentityEvent(targetId, "email_added", actor, null, { email: alias });
816
+ }
809
817
  }
810
- }
811
818
 
812
- // Delete source — CASCADE cleans up any leftover external_ids row that
813
- // we may have missed (and clears tasks.requestedByUserId pointers).
814
- deleteUser(sourceId);
815
-
816
- // Single manual_merge event on target capturing the before/after rows.
817
- // The source row is deleted above, so carry a minimal snapshot of the
818
- // source user ({id, name, email}) inside the `after` payload under
819
- // `source` — this lets the UI render "Merged manually from X → Y".
820
- const targetAfter = composeUser(targetId);
821
- recordIdentityEvent(targetId, "manual_merge", actor, targetBefore, {
822
- ...targetAfter,
823
- source: {
824
- id: sourceBefore.id,
825
- name: sourceBefore.name,
826
- email: sourceBefore.email,
827
- },
828
- });
819
+ // Delete source — CASCADE cleans up any leftover external_ids row that
820
+ // we may have missed. Retained user references move to the target.
821
+ deleteUser(sourceId, targetId);
822
+
823
+ // Single manual_merge event on target capturing the before/after rows.
824
+ // The source row is deleted above, so carry a minimal snapshot of the
825
+ // source user ({id, name, email}) inside the `after` payload under
826
+ // `source` — this lets the UI render "Merged manually from X → Y".
827
+ const targetAfter = composeUser(targetId);
828
+ recordIdentityEvent(targetId, "manual_merge", actor, targetBefore, {
829
+ ...targetAfter,
830
+ source: {
831
+ id: sourceBefore.id,
832
+ name: sourceBefore.name,
833
+ email: sourceBefore.email,
834
+ },
835
+ });
829
836
 
830
- // Re-compose AFTER the event so the response surfaces the merge event in
831
- // recentEvents (otherwise the timeline is missing the event we just wrote).
832
- mergeUsersRoute.respond(res, 200, { user: composeUser(targetId) });
837
+ // Re-compose AFTER the event so the response surfaces the merge event in
838
+ // recentEvents (otherwise the timeline is missing the event we just wrote).
839
+ return composeUser(targetId);
840
+ })();
841
+ mergeUsersRoute.respond(res, 200, { user: mergedUser });
833
842
  } catch (err) {
834
843
  jsonError(res, err instanceof Error ? err.message : "Failed to merge users", 500);
835
844
  }
@@ -863,10 +872,23 @@ export async function handleUsers(
863
872
  }
864
873
 
865
874
  try {
866
- const { identities, metadata, ...rest } = parsed.body;
875
+ const { identities, metadata, comms, ...rest } = parsed.body;
867
876
  // updateUser doesn't accept `metadata: null` directly — passthrough via cast.
868
877
  const update: Parameters<typeof updateUser>[1] = { ...rest };
869
- if (metadata !== undefined) {
878
+ if (comms !== undefined) {
879
+ // Merge-safe write: metadata (if provided) is applied first and
880
+ // replaces the whole blob, then comms is merged into it so sibling
881
+ // metadata keys survive.
882
+ const base: Record<string, unknown> = {
883
+ ...(metadata !== undefined ? (metadata ?? {}) : (before.metadata ?? {})),
884
+ };
885
+ if (comms === null) {
886
+ delete base.comms;
887
+ } else {
888
+ base.comms = comms;
889
+ }
890
+ update.metadata = Object.keys(base).length > 0 ? base : null;
891
+ } else if (metadata !== undefined) {
870
892
  update.metadata = metadata as Record<string, unknown> | null;
871
893
  }
872
894
  const updated = updateUser(parsed.params.id, update);
@@ -50,7 +50,7 @@ import {
50
50
  import type { KapsoConfig } from "../integrations/kapso/config";
51
51
  import { getKapsoConfig } from "../integrations/kapso/config";
52
52
  import type { KapsoWebhookPayload } from "../integrations/kapso/inbound";
53
- import { routeKapsoInbound } from "../integrations/kapso/inbound";
53
+ import { resolveKapsoRequestedByUserId, routeKapsoInbound } from "../integrations/kapso/inbound";
54
54
  import { getExecutorRegistry } from "../workflows";
55
55
  import { workflowEventBus } from "../workflows/event-bus";
56
56
  import { handleWebhookTrigger, verifyHmacSignature, WebhookError } from "../workflows/triggers";
@@ -600,7 +600,10 @@ export async function handleWebhooks(
600
600
  rawBody,
601
601
  req.headers,
602
602
  getExecutorRegistry(),
603
- { alreadyAuthenticated: true },
603
+ {
604
+ alreadyAuthenticated: true,
605
+ requestedByUserId: resolveKapsoRequestedByUserId(payload),
606
+ },
604
607
  );
605
608
  break;
606
609
  case "no_mapping":
@@ -31,7 +31,6 @@ import {
31
31
  WorkflowSchema,
32
32
  WorkflowVersionSchema,
33
33
  } from "../types";
34
- import { getRequestAuth } from "../utils/request-auth-context";
35
34
  import { getExecutorRegistry, startWorkflowExecution } from "../workflows";
36
35
  import {
37
36
  applyDefinitionPatch,
@@ -827,13 +826,11 @@ export async function handleWorkflows(
827
826
  return true;
828
827
  }
829
828
  const body = await parseBody<Record<string, unknown>>(req);
830
- const auth = getRequestAuth(req);
831
-
832
829
  let runId: string;
833
830
  try {
834
831
  runId = await startWorkflowExecution(workflow, body, getExecutorRegistry(), {
835
832
  triggerType: "api",
836
- requestedByUserId: auth?.kind === "user" ? auth.userId : workflow.createdBy,
833
+ requestedByUserId: resolveHttpAuditUserId(req, myAgentId) ?? undefined,
837
834
  });
838
835
  } catch (err) {
839
836
  if (err instanceof TriggerSchemaError) {
@@ -80,7 +80,7 @@ function resolveKapsoIdentity(externalId: string): IdentityResolution {
80
80
  return kapsoResolution;
81
81
  }
82
82
 
83
- function resolveKapsoRequestedByUserId(payload: KapsoWebhookPayload): string | undefined {
83
+ export function resolveKapsoRequestedByUserId(payload: KapsoWebhookPayload): string | undefined {
84
84
  const externalId = normalizeKapsoSender(payload);
85
85
  if (!externalId) return undefined;
86
86