@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
@@ -0,0 +1,321 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { afterEach, describe, expect, test } from "bun:test";
3
+ import { runMigrations } from "../be/migrations/runner";
4
+ import { extractGitHubPullRequestUrls } from "../utils/github-pull-request";
5
+
6
+ const DB_PATH = "./test-task-pull-request-backfill-migration.sqlite";
7
+ const ECMASCRIPT_WHITESPACE_CODE_POINTS = [
8
+ 9, 10, 11, 12, 13, 32, 160, 5760, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201,
9
+ 8202, 8232, 8233, 8239, 8287, 12288, 65279,
10
+ ];
11
+
12
+ async function removeDb(): Promise<void> {
13
+ for (const suffix of ["", "-wal", "-shm"]) {
14
+ try {
15
+ await Bun.file(DB_PATH + suffix).delete();
16
+ } catch (error) {
17
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
18
+ }
19
+ }
20
+ }
21
+
22
+ afterEach(removeDb);
23
+
24
+ describe("migration 135 task pull-request attachment backfill", () => {
25
+ test("applies fresh, backfills existing outputs, and replays idempotently", async () => {
26
+ await removeDb();
27
+ const db = new Database(DB_PATH, { create: true });
28
+ try {
29
+ runMigrations(db);
30
+ expect(
31
+ db.query<{ count: number }, []>("SELECT COUNT(*) AS count FROM task_attachments").get()
32
+ ?.count,
33
+ ).toBe(0);
34
+ db.run("DELETE FROM _migrations WHERE version = 135");
35
+
36
+ const now = new Date().toISOString();
37
+ const insertTask = db.prepare(
38
+ `INSERT INTO agent_tasks
39
+ (id, task, status, source, output, createdAt, lastUpdatedAt)
40
+ VALUES (?, 'fixture', 'completed', 'api', ?, ?, ?)`,
41
+ );
42
+ const firstTaskId = "11111111-1111-4111-8111-111111111111";
43
+ const secondTaskId = "22222222-2222-4222-8222-222222222222";
44
+ const thirdTaskId = "44444444-4444-4444-8444-444444444444";
45
+ const vcsOnlyTaskId = "66666666-6666-4666-8666-666666666666";
46
+ insertTask.run(
47
+ firstTaskId,
48
+ "Ignore https://notgithub.com/wrong/repo/pull/88. Shipped " +
49
+ "https://GitHub.com/desplega-ai/agent-swarm/pull/41/files and " +
50
+ "github.com/desplega-ai/docs/pull/9). Duplicate: " +
51
+ "https://github.com/desplega-ai/agent-swarm/pull/41#discussion",
52
+ now,
53
+ now,
54
+ );
55
+ insertTask.run(
56
+ secondTaskId,
57
+ "Existing https://github.com/desplega-ai/agent-swarm/pull/42. Reject " +
58
+ "https://github.com/org/repo/tree/pull/123 and " +
59
+ "https://github.com/org/repo/issues/1/pull/2 and " +
60
+ "https://_github.com/org/repo/pull/3 and " +
61
+ "https://evil.example/github.com/org/repo/pull/4 and " +
62
+ "https://github.com/org/repo/pull/123abc",
63
+ now,
64
+ now,
65
+ );
66
+ insertTask.run(
67
+ thirdTaskId,
68
+ "Valid output https://github.com/desplega-ai/agent-swarm/pull/43",
69
+ now,
70
+ now,
71
+ );
72
+ insertTask.run(vcsOnlyTaskId, "Completed through the historical VCS path", now, now);
73
+ db.run(
74
+ `UPDATE agent_tasks
75
+ SET vcsProvider = 'github', vcsRepo = 'desplega-ai/agent-swarm',
76
+ vcsNumber = 44, vcsUrl = 'https://github.com/desplega-ai/agent-swarm/pull/44'
77
+ WHERE id = ?`,
78
+ [vcsOnlyTaskId],
79
+ );
80
+ const existingId = "33333333-3333-4333-8333-333333333333";
81
+ db.run(
82
+ `INSERT INTO task_attachments
83
+ (id, task_id, name, kind, url, provider_id, provider_key, intent)
84
+ VALUES (?, ?, 'Caller supplied', 'url', ?, 'url', ?, 'review')`,
85
+ [
86
+ existingId,
87
+ secondTaskId,
88
+ "http://GitHub.com/desplega-ai/agent-swarm/pull/42/files",
89
+ "http://GitHub.com/desplega-ai/agent-swarm/pull/42/files",
90
+ ],
91
+ );
92
+ db.run(
93
+ `INSERT INTO task_attachments
94
+ (id, task_id, name, kind, url, provider_id, provider_key, intent)
95
+ VALUES (?, ?, 'Malformed attachment', 'url', ?, 'url', ?, 'review')`,
96
+ [
97
+ "55555555-5555-4555-8555-555555555555",
98
+ thirdTaskId,
99
+ "https://github.com/desplega-ai/agent-swarm/pull/43abc",
100
+ "https://github.com/desplega-ai/agent-swarm/pull/43abc",
101
+ ],
102
+ );
103
+
104
+ runMigrations(db);
105
+
106
+ const rows = db
107
+ .query<
108
+ {
109
+ id: string;
110
+ taskId: string;
111
+ name: string;
112
+ url: string;
113
+ providerId: string | null;
114
+ providerKey: string | null;
115
+ capabilities: Record<string, unknown> | null;
116
+ intent: string | null;
117
+ },
118
+ []
119
+ >(
120
+ `SELECT id, task_id AS taskId, name, url,
121
+ provider_id AS providerId, provider_key AS providerKey,
122
+ json(capabilities) AS capabilities, intent
123
+ FROM task_attachments
124
+ ORDER BY task_id, url`,
125
+ )
126
+ .all();
127
+ expect(rows).toHaveLength(6);
128
+ expect(
129
+ rows.map((row) => ({
130
+ ...row,
131
+ id: undefined,
132
+ capabilities: row.capabilities ? JSON.parse(String(row.capabilities)) : null,
133
+ })),
134
+ ).toEqual([
135
+ {
136
+ id: undefined,
137
+ taskId: firstTaskId,
138
+ name: "GitHub pull request #41",
139
+ url: "https://github.com/desplega-ai/agent-swarm/pull/41",
140
+ providerId: "github",
141
+ providerKey: "https://github.com/desplega-ai/agent-swarm/pull/41",
142
+ capabilities: {
143
+ _agentSwarmGeneratedBy: "task-pull-request-recorder",
144
+ },
145
+ intent: "task-deliverable",
146
+ },
147
+ {
148
+ id: undefined,
149
+ taskId: firstTaskId,
150
+ name: "GitHub pull request #9",
151
+ url: "https://github.com/desplega-ai/docs/pull/9",
152
+ providerId: "github",
153
+ providerKey: "https://github.com/desplega-ai/docs/pull/9",
154
+ capabilities: {
155
+ _agentSwarmGeneratedBy: "task-pull-request-recorder",
156
+ },
157
+ intent: "task-deliverable",
158
+ },
159
+ {
160
+ id: undefined,
161
+ taskId: secondTaskId,
162
+ name: "Caller supplied",
163
+ url: "http://GitHub.com/desplega-ai/agent-swarm/pull/42/files",
164
+ providerId: "url",
165
+ providerKey: "http://GitHub.com/desplega-ai/agent-swarm/pull/42/files",
166
+ capabilities: null,
167
+ intent: "review",
168
+ },
169
+ {
170
+ id: undefined,
171
+ taskId: thirdTaskId,
172
+ name: "GitHub pull request #43",
173
+ url: "https://github.com/desplega-ai/agent-swarm/pull/43",
174
+ providerId: "github",
175
+ providerKey: "https://github.com/desplega-ai/agent-swarm/pull/43",
176
+ capabilities: {
177
+ _agentSwarmGeneratedBy: "task-pull-request-recorder",
178
+ },
179
+ intent: "task-deliverable",
180
+ },
181
+ {
182
+ id: undefined,
183
+ taskId: thirdTaskId,
184
+ name: "Malformed attachment",
185
+ url: "https://github.com/desplega-ai/agent-swarm/pull/43abc",
186
+ providerId: "url",
187
+ providerKey: "https://github.com/desplega-ai/agent-swarm/pull/43abc",
188
+ capabilities: null,
189
+ intent: "review",
190
+ },
191
+ {
192
+ id: undefined,
193
+ taskId: vcsOnlyTaskId,
194
+ name: "GitHub pull request #44",
195
+ url: "https://github.com/desplega-ai/agent-swarm/pull/44",
196
+ providerId: "github",
197
+ providerKey: "https://github.com/desplega-ai/agent-swarm/pull/44",
198
+ capabilities: {
199
+ _agentSwarmGeneratedBy: "task-pull-request-recorder",
200
+ },
201
+ intent: "task-deliverable",
202
+ },
203
+ ]);
204
+ for (const row of rows) {
205
+ expect(row.id).toMatch(
206
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
207
+ );
208
+ }
209
+ expect(rows.find((row) => row.taskId === secondTaskId)?.id).toBe(existingId);
210
+
211
+ db.run("DELETE FROM _migrations WHERE version = 135");
212
+ runMigrations(db);
213
+ expect(
214
+ db.query<{ count: number }, []>("SELECT COUNT(*) AS count FROM task_attachments").get()
215
+ ?.count,
216
+ ).toBe(6);
217
+ } finally {
218
+ db.close();
219
+ }
220
+ });
221
+
222
+ test("deduplicates caller URLs ending in ECMAScript whitespace", async () => {
223
+ await removeDb();
224
+ const db = new Database(DB_PATH, { create: true });
225
+ try {
226
+ runMigrations(db);
227
+ db.run("DELETE FROM _migrations WHERE version = 135");
228
+ const now = new Date().toISOString();
229
+ const whitespaceCodePoints = [11, 12, 160];
230
+
231
+ whitespaceCodePoints.forEach((codePoint, index) => {
232
+ const suffix = String(index + 1).padStart(12, "0");
233
+ const taskId = `bbbbbbbb-bbbb-4bbb-8bbb-${suffix}`;
234
+ const canonicalUrl = `https://github.com/owner/repo/pull/${index + 1}`;
235
+ db.run(
236
+ `INSERT INTO agent_tasks
237
+ (id, task, status, source, output, createdAt, lastUpdatedAt)
238
+ VALUES (?, 'fixture', 'completed', 'api', ?, ?, ?)`,
239
+ [taskId, `Shipped ${canonicalUrl}`, now, now],
240
+ );
241
+ db.run(
242
+ `INSERT INTO task_attachments (id, task_id, name, kind, url)
243
+ VALUES (?, ?, 'Caller supplied', 'url', ?)`,
244
+ [
245
+ `cccccccc-cccc-4ccc-8ccc-${suffix}`,
246
+ taskId,
247
+ `${canonicalUrl}${String.fromCodePoint(codePoint)}`,
248
+ ],
249
+ );
250
+ });
251
+
252
+ runMigrations(db);
253
+
254
+ expect(
255
+ db.query<{ count: number }, []>("SELECT COUNT(*) AS count FROM task_attachments").get()
256
+ ?.count,
257
+ ).toBe(whitespaceCodePoints.length);
258
+ expect(
259
+ db
260
+ .query<{ count: number }, []>(
261
+ "SELECT COUNT(*) AS count FROM task_attachments WHERE capabilities IS NOT NULL",
262
+ )
263
+ .get()?.count,
264
+ ).toBe(0);
265
+ } finally {
266
+ db.close();
267
+ }
268
+ });
269
+
270
+ test("matches the TypeScript extractor across host and token boundaries", async () => {
271
+ await removeDb();
272
+ const db = new Database(DB_PATH, { create: true });
273
+ try {
274
+ runMigrations(db);
275
+ db.run("DELETE FROM _migrations WHERE version = 135");
276
+ const fixtures = [
277
+ "https://evil.example/?next=github.com/o/r/pull/8",
278
+ "xhttps://github.com/o/r/pull/1",
279
+ "https://github.com/o/r/pull/8/files",
280
+ "github.com/o/r/pull/8",
281
+ "Shipped https://github.com/o/r/pull/8, with follow-up prose.",
282
+ "https://notgithub.com/o/r/pull/8",
283
+ "https://_github.com/o/r/pull/8",
284
+ "https://evil.example/github.com/o/r/pull/8",
285
+ "https://github.com/o/r/pull/123abc",
286
+ "https://github.com/o/r/pull/123.foo",
287
+ "https://github.com/o/r/pull/123,abc",
288
+ "https://github.com/o/./pull/1",
289
+ "https://github.com/../r/pull/1",
290
+ ...ECMASCRIPT_WHITESPACE_CODE_POINTS.map(
291
+ (codePoint) => `done${String.fromCodePoint(codePoint)}https://github.com/o/r/pull/8`,
292
+ ),
293
+ ];
294
+ const now = new Date().toISOString();
295
+ const insertTask = db.prepare(
296
+ `INSERT INTO agent_tasks
297
+ (id, task, status, source, output, createdAt, lastUpdatedAt)
298
+ VALUES (?, 'fixture', 'completed', 'api', ?, ?, ?)`,
299
+ );
300
+ fixtures.forEach((fixture, index) => {
301
+ const suffix = String(index + 1).padStart(12, "0");
302
+ insertTask.run(`aaaaaaaa-aaaa-4aaa-8aaa-${suffix}`, fixture, now, now);
303
+ });
304
+
305
+ runMigrations(db);
306
+
307
+ fixtures.forEach((fixture, index) => {
308
+ const suffix = String(index + 1).padStart(12, "0");
309
+ const taskId = `aaaaaaaa-aaaa-4aaa-8aaa-${suffix}`;
310
+ const count = db
311
+ .query<{ count: number }, [string]>(
312
+ "SELECT COUNT(*) AS count FROM task_attachments WHERE task_id = ?",
313
+ )
314
+ .get(taskId)?.count;
315
+ expect(Boolean(count)).toBe(extractGitHubPullRequestUrls(fixture).length > 0);
316
+ });
317
+ } finally {
318
+ db.close();
319
+ }
320
+ });
321
+ });
@@ -111,6 +111,7 @@ describe("Tool Annotations & Classification", () => {
111
111
  "delete-page",
112
112
  "unregister-service",
113
113
  "slack-delete",
114
+ "slack-archive-channel",
114
115
  ];
115
116
 
116
117
  for (const name of expectedDestructive) {
@@ -164,6 +165,9 @@ describe("Tool Annotations & Classification", () => {
164
165
  "slack-read",
165
166
  "slack-post",
166
167
  "slack-start-thread",
168
+ "slack-create-channel",
169
+ "slack-invite-to-channel",
170
+ "slack-archive-channel",
167
171
  "slack-upload-file",
168
172
  "slack-download-file",
169
173
  "slack-list-channels",
@@ -2,6 +2,12 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import { unlink } from "node:fs/promises";
3
3
  import { createServer as createHttpServer, type Server } from "node:http";
4
4
  import { closeDb, createAgent, getAgentById, initDb, updateAgentProfile } from "../be/db";
5
+ import type { Agent } from "../types";
6
+ import {
7
+ IDENTITY_MD_MAX_CHARS,
8
+ IdentityFieldBudgetError,
9
+ SOUL_MD_MAX_CHARS,
10
+ } from "../utils/identity-field-budget";
5
11
 
6
12
  const TEST_DB_PATH = "./test-update-profile-api.sqlite";
7
13
  const TEST_PORT = 13020;
@@ -72,30 +78,23 @@ async function handleRequest(
72
78
  return { status: 400, body: { error: "Capabilities must be an array of strings" } };
73
79
  }
74
80
 
75
- // Validate claudeMd size if provided (max 64KB)
76
- if (body.claudeMd !== undefined && body.claudeMd.length > 65536) {
77
- return { status: 400, body: { error: "claudeMd must be 64KB or less" } };
78
- }
79
-
80
- // Validate soulMd size if provided (max 64KB)
81
- if (body.soulMd !== undefined && body.soulMd.length > 65536) {
82
- return { status: 400, body: { error: "soulMd must be 64KB or less" } };
83
- }
84
-
85
- // Validate identityMd size if provided (max 64KB)
86
- if (body.identityMd !== undefined && body.identityMd.length > 65536) {
87
- return { status: 400, body: { error: "identityMd must be 64KB or less" } };
81
+ let agent: Agent | null;
82
+ try {
83
+ agent = updateAgentProfile(agentId, {
84
+ role: body.role,
85
+ description: body.description,
86
+ capabilities: body.capabilities,
87
+ claudeMd: body.claudeMd,
88
+ soulMd: body.soulMd,
89
+ identityMd: body.identityMd,
90
+ });
91
+ } catch (error) {
92
+ if (error instanceof IdentityFieldBudgetError) {
93
+ return { status: 400, body: { error: error.message } };
94
+ }
95
+ throw error;
88
96
  }
89
97
 
90
- const agent = updateAgentProfile(agentId, {
91
- role: body.role,
92
- description: body.description,
93
- capabilities: body.capabilities,
94
- claudeMd: body.claudeMd,
95
- soulMd: body.soulMd,
96
- identityMd: body.identityMd,
97
- });
98
-
99
98
  if (!agent) {
100
99
  return { status: 404, body: { error: "Agent not found" } };
101
100
  }
@@ -487,9 +486,16 @@ describe("PUT /api/agents/:id/profile", () => {
487
486
  expect(agent?.claudeMd).toBe(claudeMdContent);
488
487
  });
489
488
 
490
- test("should return 400 if claudeMd exceeds 64KB", async () => {
491
- const largeContent = "a".repeat(65537); // 64KB + 1 byte
492
- const response = await fetch(`${baseUrl}/api/agents/test-agent/profile`, {
489
+ test("should return the ratchet reason if claudeMd exceeds its session budget", async () => {
490
+ const agentId = "test-agent-claudemd-over-budget";
491
+ createAgent({
492
+ id: agentId,
493
+ name: "Test Agent ClaudeMd Over Budget",
494
+ isLead: false,
495
+ status: "idle",
496
+ });
497
+ const largeContent = "a".repeat(20_001);
498
+ const response = await fetch(`${baseUrl}/api/agents/${agentId}/profile`, {
493
499
  method: "PUT",
494
500
  headers: { "Content-Type": "application/json" },
495
501
  body: JSON.stringify({ claudeMd: largeContent }),
@@ -497,19 +503,27 @@ describe("PUT /api/agents/:id/profile", () => {
497
503
 
498
504
  expect(response.status).toBe(400);
499
505
  const data = (await response.json()) as { error?: string };
500
- expect(data.error).toContain("64KB");
506
+ expect(data.error).toContain("claudeMd");
507
+ expect(data.error).toContain("current size 0");
508
+ expect(data.error).toContain("budget 20000");
509
+ expect(data.error).toContain("delta +20001");
510
+ expect(data.error).toBe(
511
+ "Update rejected for claudeMd: current size 0 characters, budget 20000 characters, delta +20001 characters." +
512
+ " The tail past the 20000-character cap is dropped from the base prompt and only reaches harnesses with a native CLAUDE.md loader." +
513
+ " Move durable content into memories and keep pointers to it in this field.",
514
+ );
501
515
  });
502
516
 
503
- test("should allow claudeMd at exactly 64KB", async () => {
504
- const agentId = "test-agent-claudemd-64kb";
517
+ test("should allow claudeMd at exactly its session budget", async () => {
518
+ const agentId = "test-agent-claudemd-budget";
505
519
  createAgent({
506
520
  id: agentId,
507
- name: "Test Agent ClaudeMd 64KB",
521
+ name: "Test Agent ClaudeMd Budget",
508
522
  isLead: false,
509
523
  status: "idle",
510
524
  });
511
525
 
512
- const exactContent = "a".repeat(65536); // Exactly 64KB
526
+ const exactContent = "a".repeat(20_000);
513
527
  const response = await fetch(`${baseUrl}/api/agents/${agentId}/profile`, {
514
528
  method: "PUT",
515
529
  headers: { "Content-Type": "application/json" },
@@ -518,7 +532,7 @@ describe("PUT /api/agents/:id/profile", () => {
518
532
 
519
533
  expect(response.status).toBe(200);
520
534
  const data = (await response.json()) as { claudeMd?: string };
521
- expect(data.claudeMd?.length).toBe(65536);
535
+ expect(data.claudeMd?.length).toBe(20_000);
522
536
  });
523
537
 
524
538
  test("should allow clearing claudeMd by setting empty string", async () => {
@@ -607,9 +621,16 @@ describe("PUT /api/agents/:id/profile", () => {
607
621
  expect(agent?.soulMd).toBe(soulContent);
608
622
  });
609
623
 
610
- test("should return 400 if soulMd exceeds 64KB", async () => {
611
- const largeContent = "a".repeat(65537);
612
- const response = await fetch(`${baseUrl}/api/agents/test-agent/profile`, {
624
+ test("should return 400 if soulMd exceeds its session budget", async () => {
625
+ const agentId = "test-agent-soulmd-over-budget";
626
+ createAgent({
627
+ id: agentId,
628
+ name: "Test Agent SoulMd Over Budget",
629
+ isLead: false,
630
+ status: "idle",
631
+ });
632
+ const largeContent = "a".repeat(SOUL_MD_MAX_CHARS + 1);
633
+ const response = await fetch(`${baseUrl}/api/agents/${agentId}/profile`, {
613
634
  method: "PUT",
614
635
  headers: { "Content-Type": "application/json" },
615
636
  body: JSON.stringify({ soulMd: largeContent }),
@@ -617,7 +638,7 @@ describe("PUT /api/agents/:id/profile", () => {
617
638
 
618
639
  expect(response.status).toBe(400);
619
640
  const data = (await response.json()) as { error?: string };
620
- expect(data.error).toContain("64KB");
641
+ expect(data.error).toContain(`budget ${SOUL_MD_MAX_CHARS}`);
621
642
  });
622
643
  });
623
644
 
@@ -648,9 +669,16 @@ describe("PUT /api/agents/:id/profile", () => {
648
669
  expect(agent?.identityMd).toBe(identityContent);
649
670
  });
650
671
 
651
- test("should return 400 if identityMd exceeds 64KB", async () => {
652
- const largeContent = "a".repeat(65537);
653
- const response = await fetch(`${baseUrl}/api/agents/test-agent/profile`, {
672
+ test("should return 400 if identityMd exceeds its session budget", async () => {
673
+ const agentId = "test-agent-identitymd-over-budget";
674
+ createAgent({
675
+ id: agentId,
676
+ name: "Test Agent IdentityMd Over Budget",
677
+ isLead: false,
678
+ status: "idle",
679
+ });
680
+ const largeContent = "a".repeat(IDENTITY_MD_MAX_CHARS + 1);
681
+ const response = await fetch(`${baseUrl}/api/agents/${agentId}/profile`, {
654
682
  method: "PUT",
655
683
  headers: { "Content-Type": "application/json" },
656
684
  body: JSON.stringify({ identityMd: largeContent }),
@@ -658,7 +686,7 @@ describe("PUT /api/agents/:id/profile", () => {
658
686
 
659
687
  expect(response.status).toBe(400);
660
688
  const data = (await response.json()) as { error?: string };
661
- expect(data.error).toContain("64KB");
689
+ expect(data.error).toContain(`budget ${IDENTITY_MD_MAX_CHARS}`);
662
690
  });
663
691
 
664
692
  test("should preserve soulMd and identityMd when updating other fields", async () => {
@@ -1,8 +1,15 @@
1
1
  import { afterAll, beforeAll, describe, expect, spyOn, test } from "bun:test";
2
- import { unlink } from "node:fs/promises";
3
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import { closeDb, createAgent, getAgentById, getLatestContextVersion, initDb } from "../be/db";
3
+ import {
4
+ closeDb,
5
+ createAgent,
6
+ deleteAgent,
7
+ getAgentById,
8
+ getLatestContextVersion,
9
+ initDb,
10
+ } from "../be/db";
5
11
  import { registerUpdateProfileTool } from "../tools/update-profile";
12
+ import { SOUL_MD_MAX_CHARS } from "../utils/identity-field-budget";
6
13
 
7
14
  const TEST_DB_PATH = "./test-update-profile-auth.sqlite";
8
15
 
@@ -54,7 +61,7 @@ describe("update-profile authorization", () => {
54
61
  beforeAll(async () => {
55
62
  for (const suffix of ["", "-wal", "-shm"]) {
56
63
  try {
57
- await unlink(TEST_DB_PATH + suffix);
64
+ await Bun.file(TEST_DB_PATH + suffix).delete();
58
65
  } catch {
59
66
  // File doesn't exist
60
67
  }
@@ -75,7 +82,7 @@ describe("update-profile authorization", () => {
75
82
  closeDb();
76
83
  for (const suffix of ["", "-wal", "-shm"]) {
77
84
  try {
78
- await unlink(TEST_DB_PATH + suffix);
85
+ await Bun.file(TEST_DB_PATH + suffix).delete();
79
86
  } catch {
80
87
  // ignore
81
88
  }
@@ -208,6 +215,21 @@ describe("update-profile authorization", () => {
208
215
  expect(agent?.setupScript).toBe(before);
209
216
  });
210
217
 
218
+ test("rejected combined name and identity update leaves the name unchanged", async () => {
219
+ const before = getAgentById(WORKER_ID);
220
+
221
+ const result = await callUpdateProfile(server, WORKER_ID, {
222
+ name: "Partially Renamed Worker",
223
+ soulMd: "x".repeat(SOUL_MD_MAX_CHARS + 1),
224
+ });
225
+
226
+ expect(result.structuredContent.success).toBe(false);
227
+ expect(result.structuredContent.message).toContain("Update rejected for soulMd");
228
+ const after = getAgentById(WORKER_ID);
229
+ expect(after?.name).toBe(before?.name);
230
+ expect(after?.soulMd).toBe(before?.soulMd);
231
+ });
232
+
211
233
  test("logs setupScript audit diff for accepted updates", async () => {
212
234
  const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
213
235
 
@@ -227,4 +249,26 @@ describe("update-profile authorization", () => {
227
249
  warnSpy.mockRestore();
228
250
  }
229
251
  });
252
+
253
+ test("missing self agent does not write supplied profile files", async () => {
254
+ const originalAgentId = process.env.AGENT_ID;
255
+ const writeSpy = spyOn(Bun, "write").mockImplementation(async () => 0);
256
+
257
+ try {
258
+ process.env.AGENT_ID = WORKER_ID;
259
+ expect(deleteAgent(WORKER_ID)).toBe(true);
260
+
261
+ const result = await callUpdateProfile(server, WORKER_ID, {
262
+ soulMd: "must not reach the workspace",
263
+ });
264
+
265
+ expect(result.structuredContent.success).toBe(false);
266
+ expect(result.structuredContent.message).toContain("Agent not found");
267
+ expect(writeSpy).not.toHaveBeenCalled();
268
+ } finally {
269
+ writeSpy.mockRestore();
270
+ if (originalAgentId === undefined) delete process.env.AGENT_ID;
271
+ else process.env.AGENT_ID = originalAgentId;
272
+ }
273
+ });
230
274
  });
@@ -158,3 +158,57 @@ describe("migration 127 user attribution backfill", () => {
158
158
  }
159
159
  });
160
160
  });
161
+
162
+ describe("migration 136 requester provenance backfill", () => {
163
+ test("marks matching descendant requesters as inherited", async () => {
164
+ await removeDb();
165
+ const db = new Database(DB_PATH, { create: true });
166
+ try {
167
+ db.run(`CREATE TABLE agent_tasks (
168
+ id TEXT PRIMARY KEY,
169
+ parentTaskId TEXT,
170
+ requestedByUserId TEXT,
171
+ taskType TEXT,
172
+ tags TEXT,
173
+ created_by TEXT
174
+ )`);
175
+ db.run(`INSERT INTO agent_tasks
176
+ (id, parentTaskId, requestedByUserId, taskType, tags, created_by) VALUES
177
+ ('root', NULL, 'user-a', 'heartbeat-checklist', '[]', NULL),
178
+ ('child', 'root', 'user-a', NULL, '[]', NULL),
179
+ ('grandchild', 'child', 'user-a', NULL, '[]', NULL),
180
+ ('handoff', 'root', 'user-b', NULL, '[]', NULL),
181
+ ('handoff-child', 'handoff', 'user-b', NULL, '[]', NULL),
182
+ ('same-user-handoff', 'root', 'user-a', NULL, '[]', 'user-a'),
183
+ ('same-user-handoff-child', 'same-user-handoff', 'user-a', NULL, '[]', NULL),
184
+ ('unattributed-child', 'root', NULL, NULL, '[]', NULL),
185
+ ('ordinary-root', NULL, 'user-a', NULL, '[]', NULL),
186
+ ('ordinary-child', 'ordinary-root', 'user-a', NULL, '[]', NULL)`);
187
+
188
+ const migration = await Bun.file(
189
+ new URL("../be/migrations/136_task_requester_provenance.sql", import.meta.url),
190
+ ).text();
191
+ db.exec(migration);
192
+
193
+ const rows = db
194
+ .query<{ id: string; inherited: number }, []>(
195
+ "SELECT id, requestedByUserIdInherited AS inherited FROM agent_tasks ORDER BY id",
196
+ )
197
+ .all();
198
+ expect(Object.fromEntries(rows.map((row) => [row.id, row.inherited]))).toEqual({
199
+ child: 1,
200
+ grandchild: 1,
201
+ handoff: 0,
202
+ "handoff-child": 0,
203
+ "ordinary-child": 0,
204
+ "ordinary-root": 0,
205
+ root: 0,
206
+ "same-user-handoff": 0,
207
+ "same-user-handoff-child": 0,
208
+ "unattributed-child": 0,
209
+ });
210
+ } finally {
211
+ db.close();
212
+ }
213
+ });
214
+ });