@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,277 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ contentSha256,
4
+ fetchProfileSyncRejectionBanner,
5
+ prependProfileSyncRejectionBanner,
6
+ renderProfileSyncRejectionBanner,
7
+ TOOLS_MD_PATH,
8
+ WORKSPACE_CLAUDE_MD_PATH,
9
+ } from "../commands/profile-sync";
10
+ import type { SwarmEvent } from "../types";
11
+
12
+ const rejectionEvent: SwarmEvent = {
13
+ id: "00000000-0000-4000-8000-000000000001",
14
+ category: "system",
15
+ event: "system.profile_sync_rejected",
16
+ status: "error",
17
+ source: "api",
18
+ agentId: "agent-1",
19
+ createdAt: "2026-08-18T15:55:00.000Z",
20
+ data: {
21
+ field: "toolsMd",
22
+ diskSize: 84_574,
23
+ dbSize: 83_679,
24
+ budget: 20_000,
25
+ delta: 895,
26
+ dbHash: contentSha256("db tools"),
27
+ changeSource: "session_sync",
28
+ },
29
+ };
30
+
31
+ const reconciliationEvent: SwarmEvent = {
32
+ ...rejectionEvent,
33
+ id: "00000000-0000-4000-8000-000000000003",
34
+ event: "system.profile_sync_reconciled",
35
+ status: "ok",
36
+ createdAt: "2026-08-18T16:00:00.000Z",
37
+ };
38
+
39
+ describe("profile sync rejection session warning", () => {
40
+ test("carries field, disk size, DB size, budget, delta, and recovery path", async () => {
41
+ const banner = await renderProfileSyncRejectionBanner(rejectionEvent);
42
+
43
+ expect(banner).toContain("/workspace/TOOLS.md");
44
+ expect(banner).toContain("Disk size: 84574");
45
+ expect(banner).toContain("DB size: 83679");
46
+ expect(banner).toContain("budget: 20000");
47
+ expect(banner).toContain("delta: +895");
48
+ expect(banner).toContain(".pre-boot-<timestamp>.bak");
49
+ expect(banner.indexOf("live /workspace/TOOLS.md")).toBeLessThan(
50
+ banner.indexOf("/workspace/TOOLS.md.pre-boot-<timestamp>.bak"),
51
+ );
52
+ expect(banner).toContain(rejectionEvent.id);
53
+ });
54
+
55
+ test("queries the latest persisted event for the current agent", async () => {
56
+ const requests: Request[] = [];
57
+ const fetchImpl = (async (input: string | URL | Request, init?: RequestInit) => {
58
+ const request = new Request(input, init);
59
+ requests.push(request);
60
+ const field = new URL(request.url).searchParams.get("dataField");
61
+ const body = request.url.endsWith("/me")
62
+ ? { toolsMd: "db tools" }
63
+ : { events: field === "toolsMd" ? [rejectionEvent] : [] };
64
+ return new Response(JSON.stringify(body), {
65
+ status: 200,
66
+ headers: { "Content-Type": "application/json" },
67
+ });
68
+ }) as typeof fetch;
69
+
70
+ const result = await prependProfileSyncRejectionBanner(
71
+ "ORIGINAL TASK PROMPT",
72
+ {
73
+ apiUrl: "https://api.example.test",
74
+ apiKey: "secret-key",
75
+ agentId: "agent-1",
76
+ },
77
+ fetchImpl,
78
+ );
79
+
80
+ expect(requests[0]?.url).toContain("event=system.profile_sync_rejected");
81
+ expect(requests[0]?.url).toContain("agentId=agent-1");
82
+ expect(requests[0]?.url).toContain("limit=1");
83
+ expect(requests[0]?.url).toContain("dataField=soulMd");
84
+ expect(requests[0]?.headers.get("X-Agent-ID")).toBe("agent-1");
85
+ expect(requests.at(-1)?.url).toBe("https://api.example.test/me");
86
+ expect(result.injected).toBeTrue();
87
+ expect(result.prompt).toContain("PERSISTED PROFILE SYNC REJECTION");
88
+ expect(result.prompt).toEndWith("ORIGINAL TASK PROMPT");
89
+ });
90
+
91
+ test("stops warning after the stored field changes", async () => {
92
+ const fetchImpl = (async (input: string | URL | Request) => {
93
+ const url = String(input);
94
+ const field = new URL(url).searchParams.get("dataField");
95
+ const body = url.endsWith("/me")
96
+ ? { toolsMd: "reconciled tools" }
97
+ : { events: field === "toolsMd" ? [rejectionEvent] : [] };
98
+ return new Response(JSON.stringify(body), {
99
+ status: 200,
100
+ headers: { "Content-Type": "application/json" },
101
+ });
102
+ }) as typeof fetch;
103
+
104
+ const banner = await fetchProfileSyncRejectionBanner(
105
+ {
106
+ apiUrl: "https://api.example.test",
107
+ apiKey: "secret-key",
108
+ agentId: "agent-1",
109
+ },
110
+ fetchImpl,
111
+ );
112
+
113
+ expect(banner).toBe("");
114
+ });
115
+
116
+ test("stops warning after a successful no-op reconciliation", async () => {
117
+ const fetchImpl = (async (input: string | URL | Request) => {
118
+ const url = new URL(String(input));
119
+ const field = url.searchParams.get("dataField");
120
+ const event = url.searchParams.get("event");
121
+ const body = url.pathname.endsWith("/me")
122
+ ? { toolsMd: "db tools" }
123
+ : {
124
+ events:
125
+ field !== "toolsMd"
126
+ ? []
127
+ : event === "system.profile_sync_reconciled"
128
+ ? [reconciliationEvent]
129
+ : [rejectionEvent],
130
+ };
131
+ return new Response(JSON.stringify(body), {
132
+ status: 200,
133
+ headers: { "Content-Type": "application/json" },
134
+ });
135
+ }) as typeof fetch;
136
+
137
+ const banner = await fetchProfileSyncRejectionBanner(
138
+ {
139
+ apiUrl: "https://api.example.test",
140
+ apiKey: "secret-key",
141
+ agentId: "agent-1",
142
+ },
143
+ fetchImpl,
144
+ );
145
+
146
+ expect(banner).toBe("");
147
+ });
148
+
149
+ test("keeps warning when the latest reconciliation predates the rejection", async () => {
150
+ const olderReconciliation = {
151
+ ...reconciliationEvent,
152
+ createdAt: "2026-08-18T15:50:00.000Z",
153
+ };
154
+ const fetchImpl = (async (input: string | URL | Request) => {
155
+ const url = new URL(String(input));
156
+ const field = url.searchParams.get("dataField");
157
+ const event = url.searchParams.get("event");
158
+ const body = url.pathname.endsWith("/me")
159
+ ? { toolsMd: "db tools" }
160
+ : {
161
+ events:
162
+ field !== "toolsMd"
163
+ ? []
164
+ : event === "system.profile_sync_reconciled"
165
+ ? [olderReconciliation]
166
+ : [rejectionEvent],
167
+ };
168
+ return new Response(JSON.stringify(body), {
169
+ status: 200,
170
+ headers: { "Content-Type": "application/json" },
171
+ });
172
+ }) as typeof fetch;
173
+
174
+ const banner = await fetchProfileSyncRejectionBanner(
175
+ {
176
+ apiUrl: "https://api.example.test",
177
+ apiKey: "secret-key",
178
+ agentId: "agent-1",
179
+ },
180
+ fetchImpl,
181
+ );
182
+
183
+ expect(banner).toContain("PERSISTED PROFILE SYNC REJECTION");
184
+ });
185
+
186
+ test("clears a warning only when boot restored the local file to the DB value", async () => {
187
+ const fetchImpl = (async (input: string | URL | Request) => {
188
+ const url = new URL(String(input));
189
+ const field = url.searchParams.get("dataField");
190
+ const event = url.searchParams.get("event");
191
+ const body = url.pathname.endsWith("/me")
192
+ ? { toolsMd: "db tools" }
193
+ : {
194
+ events:
195
+ field === "toolsMd" && event === "system.profile_sync_rejected"
196
+ ? [rejectionEvent]
197
+ : [],
198
+ };
199
+ return new Response(JSON.stringify(body), {
200
+ status: 200,
201
+ headers: { "Content-Type": "application/json" },
202
+ });
203
+ }) as typeof fetch;
204
+ const config = {
205
+ apiUrl: "https://api.example.test",
206
+ apiKey: "secret-key",
207
+ agentId: "agent-1",
208
+ claudeMdPath: WORKSPACE_CLAUDE_MD_PATH,
209
+ };
210
+
211
+ const restored = await fetchProfileSyncRejectionBanner(config, fetchImpl, async (path) =>
212
+ path === TOOLS_MD_PATH ? "db tools" : undefined,
213
+ );
214
+ const stillDiverged = await fetchProfileSyncRejectionBanner(config, fetchImpl, async (path) =>
215
+ path === TOOLS_MD_PATH ? "rejected tools" : undefined,
216
+ );
217
+
218
+ expect(restored).toBe("");
219
+ expect(stillDiverged).toContain("PERSISTED PROFILE SYNC REJECTION");
220
+ });
221
+
222
+ test("surfaces the latest unresolved rejection for every affected field", async () => {
223
+ const claudeEvent: SwarmEvent = {
224
+ ...rejectionEvent,
225
+ id: "00000000-0000-4000-8000-000000000002",
226
+ data: {
227
+ ...rejectionEvent.data,
228
+ field: "claudeMd",
229
+ diskSize: 99_617,
230
+ dbSize: 99_602,
231
+ delta: 15,
232
+ dbHash: contentSha256("db claude"),
233
+ },
234
+ };
235
+ const fetchImpl = (async (input: string | URL | Request) => {
236
+ const url = String(input);
237
+ const field = new URL(url).searchParams.get("dataField");
238
+ const body = url.endsWith("/me")
239
+ ? { toolsMd: "db tools", claudeMd: "db claude" }
240
+ : {
241
+ events:
242
+ field === "claudeMd" ? [claudeEvent] : field === "toolsMd" ? [rejectionEvent] : [],
243
+ };
244
+ return new Response(JSON.stringify(body), {
245
+ status: 200,
246
+ headers: { "Content-Type": "application/json" },
247
+ });
248
+ }) as typeof fetch;
249
+
250
+ const banner = await fetchProfileSyncRejectionBanner(
251
+ {
252
+ apiUrl: "https://api.example.test",
253
+ apiKey: "secret-key",
254
+ agentId: "agent-1",
255
+ },
256
+ fetchImpl,
257
+ );
258
+
259
+ expect(banner).toContain("/workspace/CLAUDE.md");
260
+ expect(banner).toContain("~/.claude/CLAUDE.md");
261
+ expect(banner).toContain("is not archived by that boot step");
262
+ expect(banner).toContain("/workspace/TOOLS.md");
263
+ });
264
+
265
+ test("fails open when the audit event cannot be fetched", async () => {
266
+ const banner = await fetchProfileSyncRejectionBanner(
267
+ {
268
+ apiUrl: "https://api.example.test",
269
+ apiKey: "secret-key",
270
+ agentId: "agent-1",
271
+ },
272
+ (async () => new Response("unavailable", { status: 503 })) as typeof fetch,
273
+ );
274
+
275
+ expect(banner).toBe("");
276
+ });
277
+ });
@@ -1,11 +1,14 @@
1
1
  import { describe, expect, spyOn, test } from "bun:test";
2
2
  import {
3
3
  buildIdentityPayload,
4
+ buildIndependentIdentityPayloads,
4
5
  CLAUDE_MD_PATH,
5
6
  collectProfilePayloads,
6
7
  contentSha256,
7
8
  extractSetupScriptContent,
8
9
  type FileReader,
10
+ findProfileDivergences,
11
+ HEARTBEAT_MD_PATH,
9
12
  IDENTITY_BASELINES_PATH,
10
13
  IDENTITY_MD_PATH,
11
14
  type IdentityBaselines,
@@ -17,7 +20,9 @@ import {
17
20
  TOOLS_MD_PATH,
18
21
  WORKSPACE_CLAUDE_MD_PATH,
19
22
  } from "../commands/profile-sync";
23
+ import { profileSyncAuditExitCode, runProfileSyncAudit } from "../commands/profile-sync-audit";
20
24
  import { MAX_PROFILE_FILE_LENGTH } from "../utils/constants";
25
+ import { IDENTITY_FIELD_BUDGETS } from "../utils/identity-field-budget";
21
26
 
22
27
  const MARKER_START = "# === Agent-managed setup (from DB) ===";
23
28
  const MARKER_END = "# === End agent-managed setup ===";
@@ -25,6 +30,182 @@ const MARKER_END = "# === End agent-managed setup ===";
25
30
  // A SOUL/IDENTITY body long enough to clear the 500-char min-length guard.
26
31
  const LONG = "x".repeat(600);
27
32
 
33
+ describe("profile divergence audit", () => {
34
+ test("compares all four budgeted fields plus ungated heartbeatMd", () => {
35
+ const divergences = findProfileDivergences(
36
+ {
37
+ soulMd: "disk soul",
38
+ identityMd: "disk identity",
39
+ claudeMd: "disk claude",
40
+ toolsMd: "disk tools",
41
+ heartbeatMd: "disk heartbeat",
42
+ },
43
+ {
44
+ soulMd: "db soul",
45
+ identityMd: "db identity",
46
+ claudeMd: "db claude",
47
+ toolsMd: "db tools",
48
+ heartbeatMd: "db heartbeat",
49
+ },
50
+ );
51
+
52
+ expect(divergences.map((entry) => entry.field)).toEqual([
53
+ "soulMd",
54
+ "identityMd",
55
+ "claudeMd",
56
+ "toolsMd",
57
+ "heartbeatMd",
58
+ ]);
59
+ expect(divergences[0]?.budget).toBe(IDENTITY_FIELD_BUDGETS.soulMd);
60
+ expect(divergences[2]?.budget).toBe(IDENTITY_FIELD_BUDGETS.claudeMd);
61
+ expect(divergences[4]?.budget).toBeNull();
62
+ expect(divergences[4]?.diskSize).toBe("disk heartbeat".length);
63
+ expect(divergences[4]?.dbSize).toBe("db heartbeat".length);
64
+ });
65
+
66
+ test("fetches the DB profile and reads the canonical local paths", async () => {
67
+ const local = {
68
+ [SOUL_MD_PATH]: "same soul",
69
+ [IDENTITY_MD_PATH]: "disk identity",
70
+ [WORKSPACE_CLAUDE_MD_PATH]: "same claude",
71
+ [TOOLS_MD_PATH]: "same tools",
72
+ [HEARTBEAT_MD_PATH]: "disk heartbeat",
73
+ };
74
+ const readFile: FileReader = async (path) => local[path as keyof typeof local];
75
+ let request: Request | null = null;
76
+ const fetchImpl = (async (input: string | URL | Request, init?: RequestInit) => {
77
+ request = new Request(input, init);
78
+ return new Response(
79
+ JSON.stringify({
80
+ soulMd: "same soul",
81
+ identityMd: "db identity",
82
+ claudeMd: "same claude",
83
+ toolsMd: "same tools",
84
+ heartbeatMd: "db heartbeat",
85
+ }),
86
+ { status: 200, headers: { "Content-Type": "application/json" } },
87
+ );
88
+ }) as typeof fetch;
89
+
90
+ const result = await runProfileSyncAudit(
91
+ {
92
+ agentId: "agent-1",
93
+ apiUrl: "https://api.example.test",
94
+ apiKey: "secret-key",
95
+ claudeMdPath: WORKSPACE_CLAUDE_MD_PATH,
96
+ },
97
+ { fetchImpl, readFile },
98
+ );
99
+
100
+ expect(request?.url).toBe("https://api.example.test/me");
101
+ expect(request?.headers.get("X-Agent-ID")).toBe("agent-1");
102
+ expect(result.divergences.map((entry) => entry.field)).toEqual(["identityMd", "heartbeatMd"]);
103
+ });
104
+
105
+ test("uses the registered effective provider to select the Claude profile path", async () => {
106
+ const readPaths: string[] = [];
107
+ const readFile: FileReader = async (path) => {
108
+ readPaths.push(path);
109
+ return "same";
110
+ };
111
+ const fetchImpl = (async () =>
112
+ new Response(
113
+ JSON.stringify({
114
+ harnessProvider: "codex",
115
+ soulMd: "same",
116
+ identityMd: "same",
117
+ claudeMd: "same",
118
+ toolsMd: "same",
119
+ heartbeatMd: "same",
120
+ }),
121
+ { status: 200, headers: { "Content-Type": "application/json" } },
122
+ )) as typeof fetch;
123
+
124
+ const result = await runProfileSyncAudit(
125
+ {
126
+ agentId: "agent-codex",
127
+ apiUrl: "https://api.example.test",
128
+ apiKey: "secret-key",
129
+ },
130
+ { fetchImpl, readFile },
131
+ );
132
+
133
+ expect(result.divergences).toEqual([]);
134
+ expect(readPaths).toContain(WORKSPACE_CLAUDE_MD_PATH);
135
+ expect(readPaths).not.toContain(CLAUDE_MD_PATH);
136
+ });
137
+
138
+ test("refuses to guess the audit path when no effective provider is registered", async () => {
139
+ const fetchImpl = (async () =>
140
+ new Response(JSON.stringify({}), {
141
+ status: 200,
142
+ headers: { "Content-Type": "application/json" },
143
+ })) as typeof fetch;
144
+
145
+ expect(
146
+ runProfileSyncAudit(
147
+ {
148
+ agentId: "agent-unregistered",
149
+ apiUrl: "https://api.example.test",
150
+ apiKey: "secret-key",
151
+ },
152
+ { fetchImpl, readFile: async () => undefined },
153
+ ),
154
+ ).rejects.toThrow("registered harness provider");
155
+ });
156
+
157
+ test("treats profile fields absent on disk and empty in the DB as equal", async () => {
158
+ const readFile: FileReader = async () => undefined;
159
+ const fetchImpl = (async () =>
160
+ new Response(JSON.stringify({ soulMd: "" }), {
161
+ status: 200,
162
+ headers: { "Content-Type": "application/json" },
163
+ })) as typeof fetch;
164
+
165
+ const result = await runProfileSyncAudit(
166
+ {
167
+ agentId: "agent-unset",
168
+ apiUrl: "https://api.example.test",
169
+ apiKey: "secret-key",
170
+ claudeMdPath: WORKSPACE_CLAUDE_MD_PATH,
171
+ },
172
+ { fetchImpl, readFile },
173
+ );
174
+
175
+ expect(result.divergences).toEqual([]);
176
+ expect(profileSyncAuditExitCode(result.divergences)).toBe(0);
177
+ });
178
+
179
+ test("reports a missing local file when the DB value is non-empty", () => {
180
+ const divergences = findProfileDivergences(
181
+ {
182
+ soulMd: undefined,
183
+ identityMd: "same identity",
184
+ claudeMd: "same claude",
185
+ toolsMd: "same tools",
186
+ heartbeatMd: "same heartbeat",
187
+ },
188
+ {
189
+ soulMd: "stored soul",
190
+ identityMd: "same identity",
191
+ claudeMd: "same claude",
192
+ toolsMd: "same tools",
193
+ heartbeatMd: "same heartbeat",
194
+ },
195
+ );
196
+
197
+ expect(divergences).toHaveLength(1);
198
+ expect(divergences[0]).toMatchObject({
199
+ field: "soulMd",
200
+ diskMissing: true,
201
+ diskSize: null,
202
+ diskHash: null,
203
+ dbSize: "stored soul".length,
204
+ delta: null,
205
+ });
206
+ });
207
+ });
208
+
28
209
  describe("extractSetupScriptContent (marker extraction)", () => {
29
210
  test("extracts ONLY the content between the agent-managed markers", () => {
30
211
  const raw = [
@@ -97,6 +278,19 @@ describe("buildIdentityPayload (min-length guard)", () => {
97
278
  expect(payload.heartbeatMd).toBe("");
98
279
  });
99
280
 
281
+ test("forwards oversized gated identity fields so the server ratchet can decide", () => {
282
+ const oversized = "x".repeat(MAX_PROFILE_FILE_LENGTH + 1);
283
+ const payload = buildIdentityPayload({
284
+ soulMd: oversized,
285
+ identityMd: oversized,
286
+ toolsMd: oversized,
287
+ });
288
+
289
+ expect(payload.soulMd).toBe(oversized);
290
+ expect(payload.identityMd).toBe(oversized);
291
+ expect(payload.toolsMd).toBe(oversized);
292
+ });
293
+
100
294
  test("logs the agent, field, actual length, and cap when a file is too large", () => {
101
295
  const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
102
296
  try {
@@ -142,7 +336,13 @@ describe("collectProfilePayloads (field gate)", () => {
142
336
  "session_sync",
143
337
  files,
144
338
  );
145
- expect(all.map((p) => p.label).sort()).toEqual(["claude", "identity", "setup"]);
339
+ expect(all.map((p) => p.label).sort()).toEqual([
340
+ "claude",
341
+ "identity.identityMd",
342
+ "identity.soulMd",
343
+ "identity.toolsMd",
344
+ "setup",
345
+ ]);
146
346
  });
147
347
 
148
348
  test("a missing file yields no payload for that group (no empty POST)", async () => {
@@ -413,11 +613,13 @@ describe("collectProfilePayloads (baseline integration)", () => {
413
613
  });
414
614
 
415
615
  const payloads = await collectProfilePayloads(["identity"], "self_edit", files);
416
- expect(payloads).toHaveLength(1);
417
- // self_edit should include ALL files regardless of baselines
418
- expect(payloads[0]?.body.soulMd).toBe(identityContent);
419
- expect(payloads[0]?.body.identityMd).toBe(identityContent);
420
- expect(payloads[0]?.body.toolsMd).toBe("tools");
616
+ expect(payloads).toHaveLength(3);
617
+ // self_edit should include ALL files regardless of baselines, independently.
618
+ expect(payloads.map((payload) => payload.body)).toEqual([
619
+ { soulMd: identityContent, changeSource: "self_edit" },
620
+ { identityMd: identityContent, changeSource: "self_edit" },
621
+ { toolsMd: "tools", changeSource: "self_edit" },
622
+ ]);
421
623
  });
422
624
 
423
625
  test("session_sync skips unchanged CLAUDE.md when baseline matches", async () => {
@@ -482,3 +684,27 @@ describe("collectProfilePayloads (baseline integration)", () => {
482
684
  expect(payloads).toEqual([]);
483
685
  });
484
686
  });
687
+
688
+ describe("buildIndependentIdentityPayloads", () => {
689
+ test("splits fields so one rejected write cannot discard valid siblings", () => {
690
+ expect(
691
+ buildIndependentIdentityPayloads(
692
+ { soulMd: "rejected growth", toolsMd: "valid edit", heartbeatMd: "ungated edit" },
693
+ "session_sync",
694
+ ),
695
+ ).toEqual([
696
+ {
697
+ label: "identity.soulMd",
698
+ body: { soulMd: "rejected growth", changeSource: "session_sync" },
699
+ },
700
+ {
701
+ label: "identity.toolsMd",
702
+ body: { toolsMd: "valid edit", changeSource: "session_sync" },
703
+ },
704
+ {
705
+ label: "identity.heartbeatMd",
706
+ body: { heartbeatMd: "ungated edit", changeSource: "session_sync" },
707
+ },
708
+ ]);
709
+ });
710
+ });