@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
@@ -55,7 +55,7 @@ export function HarnessCredentialsStep({ goToNext, addLog }: StepProps) {
55
55
  setCliError(msg);
56
56
  setSubStep("manual_oauth");
57
57
  }
58
- })();
58
+ })().catch((err) => addLog(`Harness credential setup failed: ${err}`));
59
59
 
60
60
  return () => {
61
61
  cancelled = true;
@@ -92,7 +92,7 @@ export function HealthCheckStep({ state, addLog, goToNext }: StepProps) {
92
92
  }
93
93
  };
94
94
 
95
- poll();
95
+ poll().catch((err) => addLog(`Health poll failed: ${err}`));
96
96
  return () => {
97
97
  cancelled = true;
98
98
  };
@@ -35,7 +35,7 @@ export function StartStep({ state, addLog, goToNext }: StepProps) {
35
35
  }
36
36
  };
37
37
 
38
- run();
38
+ run().catch((err) => addLog(`Start failed: ${err}`));
39
39
  }, [state.outputDir, addLog]);
40
40
 
41
41
  useEffect(() => {
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { getApiKey } from "../utils/api-key.ts";
4
+ import { getMcpBaseUrl } from "../utils/constants.ts";
5
+ import { scrubSecrets } from "../utils/secret-scrubber.ts";
6
+ import {
7
+ auditProfileDivergences,
8
+ type FileReader,
9
+ type ProfileDivergence,
10
+ } from "./profile-sync.ts";
11
+
12
+ export interface ProfileSyncAuditConfig {
13
+ agentId: string;
14
+ apiUrl: string;
15
+ apiKey: string;
16
+ claudeMdPath?: string;
17
+ }
18
+
19
+ export async function runProfileSyncAudit(
20
+ config: ProfileSyncAuditConfig,
21
+ deps: { fetchImpl?: typeof fetch; readFile?: FileReader } = {},
22
+ ): Promise<{ agentId: string; divergences: ProfileDivergence[] }> {
23
+ const divergences = await auditProfileDivergences(
24
+ { ...config, fetchImpl: deps.fetchImpl },
25
+ deps.readFile,
26
+ );
27
+ return { agentId: config.agentId, divergences };
28
+ }
29
+
30
+ export function profileSyncAuditExitCode(divergences: ProfileDivergence[]): 0 | 2 {
31
+ return divergences.length > 0 ? 2 : 0;
32
+ }
33
+
34
+ if (import.meta.main) {
35
+ const agentId = process.env.AGENT_ID;
36
+ const apiUrl = getMcpBaseUrl();
37
+ const apiKey = getApiKey();
38
+ if (!agentId || !apiUrl || !apiKey) {
39
+ console.error("profile-sync-audit requires AGENT_ID, MCP_BASE_URL, and an API key");
40
+ process.exit(1);
41
+ }
42
+
43
+ try {
44
+ const result = await runProfileSyncAudit({
45
+ agentId,
46
+ apiUrl,
47
+ apiKey,
48
+ });
49
+ console.log(JSON.stringify(result, null, 2));
50
+ process.exit(profileSyncAuditExitCode(result.divergences));
51
+ } catch (error) {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+ console.error(scrubSecrets(`profile-sync-audit failed: ${message}`));
54
+ process.exit(1);
55
+ }
56
+ }
@@ -2,7 +2,7 @@
2
2
  * Harness-agnostic FS → DB profile sync (worker-side, HTTP-only).
3
3
  *
4
4
  * Persists an agent's self-editable identity / config files back to the API:
5
- * - SOUL.md / IDENTITY.md / TOOLS.md / HEARTBEAT.md (bundled identity POST)
5
+ * - SOUL.md / IDENTITY.md / TOOLS.md / HEARTBEAT.md (independent POSTs)
6
6
  * - ~/.claude/CLAUDE.md (claude POST)
7
7
  * - /workspace/start-up.sh (agent-managed section) (setup POST)
8
8
  *
@@ -27,8 +27,15 @@
27
27
  * but it must be VISIBLE.
28
28
  */
29
29
 
30
+ import { resolveTemplateAsync } from "../prompts/resolver.ts";
31
+ import type { Agent, SwarmEvent } from "../types.ts";
30
32
  import { MAX_PROFILE_FILE_LENGTH } from "../utils/constants.ts";
33
+ import {
34
+ type BudgetedIdentityField,
35
+ IDENTITY_FIELD_BUDGETS,
36
+ } from "../utils/identity-field-budget.ts";
31
37
  import { scrubSecrets } from "../utils/secret-scrubber.ts";
38
+ import "./templates.ts";
32
39
 
33
40
  export const SOUL_MD_PATH = "/workspace/SOUL.md";
34
41
  export const IDENTITY_MD_PATH = "/workspace/IDENTITY.md";
@@ -56,6 +63,223 @@ export function contentSha256(content: string): string {
56
63
  return new Bun.CryptoHasher("sha256").update(content).digest("hex");
57
64
  }
58
65
 
66
+ export const PROFILE_MARKDOWN_FIELDS = [
67
+ "soulMd",
68
+ "identityMd",
69
+ "claudeMd",
70
+ "toolsMd",
71
+ "heartbeatMd",
72
+ ] as const;
73
+
74
+ export type ProfileMarkdownField = (typeof PROFILE_MARKDOWN_FIELDS)[number];
75
+ export type ProfileMarkdownSnapshot = Partial<
76
+ Record<ProfileMarkdownField, string | null | undefined>
77
+ >;
78
+
79
+ export interface ProfileDivergence {
80
+ field: ProfileMarkdownField;
81
+ diskSize: number | null;
82
+ dbSize: number;
83
+ budget: number | null;
84
+ delta: number | null;
85
+ diskHash: string | null;
86
+ dbHash: string;
87
+ diskMissing: boolean;
88
+ }
89
+
90
+ /** Pure disk-vs-DB comparison used by local profile-sync audits. */
91
+ export function findProfileDivergences(
92
+ disk: ProfileMarkdownSnapshot,
93
+ db: ProfileMarkdownSnapshot,
94
+ ): ProfileDivergence[] {
95
+ const divergences: ProfileDivergence[] = [];
96
+
97
+ for (const field of PROFILE_MARKDOWN_FIELDS) {
98
+ const diskValue = disk[field];
99
+ const dbValue = db[field] ?? "";
100
+ const budget =
101
+ field === "heartbeatMd" ? null : IDENTITY_FIELD_BUDGETS[field as BudgetedIdentityField];
102
+ if (diskValue === undefined || diskValue === null) {
103
+ if (dbValue.length === 0) continue;
104
+ divergences.push({
105
+ field,
106
+ diskSize: null,
107
+ dbSize: dbValue.length,
108
+ budget,
109
+ delta: null,
110
+ diskHash: null,
111
+ dbHash: contentSha256(dbValue),
112
+ diskMissing: true,
113
+ });
114
+ continue;
115
+ }
116
+ if (diskValue === dbValue) continue;
117
+
118
+ divergences.push({
119
+ field,
120
+ diskSize: diskValue.length,
121
+ dbSize: dbValue.length,
122
+ budget,
123
+ delta: diskValue.length - dbValue.length,
124
+ diskHash: contentSha256(diskValue),
125
+ dbHash: contentSha256(dbValue),
126
+ diskMissing: false,
127
+ });
128
+ }
129
+
130
+ return divergences;
131
+ }
132
+
133
+ const PROFILE_FIELD_DETAILS: Record<
134
+ BudgetedIdentityField,
135
+ { label: string; recoveryGuidance: string }
136
+ > = {
137
+ soulMd: {
138
+ label: `soulMd (${SOUL_MD_PATH})`,
139
+ recoveryGuidance: `Inspect and shrink the live ${SOUL_MD_PATH} first, moving durable tail content into memory. After a worker restart, use ${SOUL_MD_PATH}.pre-boot-<timestamp>.bak as the fallback copy of the rejected content.`,
140
+ },
141
+ identityMd: {
142
+ label: `identityMd (${IDENTITY_MD_PATH})`,
143
+ recoveryGuidance: `Inspect and shrink the live ${IDENTITY_MD_PATH} first, moving durable tail content into memory. After a worker restart, use ${IDENTITY_MD_PATH}.pre-boot-<timestamp>.bak as the fallback copy of the rejected content.`,
144
+ },
145
+ claudeMd: {
146
+ label:
147
+ "claudeMd (/workspace/CLAUDE.md for non-Claude harnesses; ~/.claude/CLAUDE.md for Claude)",
148
+ recoveryGuidance:
149
+ "Inspect and shrink the live CLAUDE.md source first, moving durable tail content into memory. After a worker restart, non-Claude harnesses can use /workspace/CLAUDE.md.pre-boot-<timestamp>.bak as the fallback copy; Claude's native ~/.claude/CLAUDE.md source is not archived by that boot step.",
150
+ },
151
+ toolsMd: {
152
+ label: `toolsMd (${TOOLS_MD_PATH})`,
153
+ recoveryGuidance: `Inspect and shrink the live ${TOOLS_MD_PATH} first, moving durable tail content into memory. After a worker restart, use ${TOOLS_MD_PATH}.pre-boot-<timestamp>.bak as the fallback copy of the rejected content.`,
154
+ },
155
+ };
156
+
157
+ /** Render one persisted sync rejection through the prompt-template registry. */
158
+ export async function renderProfileSyncRejectionBanner(event: SwarmEvent): Promise<string | null> {
159
+ const data = event.data;
160
+ const field = data?.field;
161
+ const diskSize = data?.diskSize;
162
+ const dbSize = data?.dbSize;
163
+ const budget = data?.budget;
164
+ const delta = data?.delta;
165
+ if (
166
+ typeof field !== "string" ||
167
+ !(field in PROFILE_FIELD_DETAILS) ||
168
+ typeof diskSize !== "number" ||
169
+ typeof dbSize !== "number" ||
170
+ typeof budget !== "number" ||
171
+ typeof delta !== "number"
172
+ ) {
173
+ return null;
174
+ }
175
+
176
+ const details = PROFILE_FIELD_DETAILS[field as BudgetedIdentityField];
177
+ const result = await resolveTemplateAsync("task.profile_sync_rejection", {
178
+ timestamp: event.createdAt,
179
+ field_label: details.label,
180
+ disk_size: diskSize,
181
+ db_size: dbSize,
182
+ budget,
183
+ delta: delta >= 0 ? `+${delta}` : String(delta),
184
+ event_id: event.id,
185
+ recovery_guidance: details.recoveryGuidance,
186
+ });
187
+ return result.skipped ? null : result.text;
188
+ }
189
+
190
+ /** Fetch every field's latest unresolved rejection before a provider session. */
191
+ export async function fetchProfileSyncRejectionBanner(
192
+ config: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey" | "claudeMdPath">,
193
+ fetchImpl: typeof fetch = fetch,
194
+ readFile: FileReader = readFileIfExists,
195
+ ): Promise<string> {
196
+ try {
197
+ const responses = await Promise.all(
198
+ (Object.keys(IDENTITY_FIELD_BUDGETS) as BudgetedIdentityField[]).map(async (field) => {
199
+ const fetchLatestEvent = async (event: SwarmEvent["event"]): Promise<SwarmEvent | null> => {
200
+ const query = new URLSearchParams({
201
+ event,
202
+ agentId: config.agentId,
203
+ dataField: field,
204
+ limit: "1",
205
+ });
206
+ const response = await fetchImpl(`${config.apiUrl}/api/events?${query}`, {
207
+ headers: {
208
+ Authorization: `Bearer ${config.apiKey}`,
209
+ "X-Agent-ID": config.agentId,
210
+ },
211
+ });
212
+ if (!response.ok) return null;
213
+ const payload = (await response.json()) as { events?: SwarmEvent[] };
214
+ return payload.events?.[0] ?? null;
215
+ };
216
+ const [rejection, reconciliation] = await Promise.all([
217
+ fetchLatestEvent("system.profile_sync_rejected"),
218
+ fetchLatestEvent("system.profile_sync_reconciled"),
219
+ ]);
220
+ return { rejection, reconciliation };
221
+ }),
222
+ );
223
+ const latestEvents = responses.flatMap(({ rejection, reconciliation }) =>
224
+ rejection ? [{ rejection, reconciliation }] : [],
225
+ );
226
+ if (latestEvents.length === 0) return "";
227
+
228
+ let profile: Record<string, unknown> | null = null;
229
+ const profileResponse = await fetchImpl(`${config.apiUrl}/me`, {
230
+ headers: {
231
+ Authorization: `Bearer ${config.apiKey}`,
232
+ "X-Agent-ID": config.agentId,
233
+ },
234
+ });
235
+ if (profileResponse.ok) {
236
+ profile = (await profileResponse.json()) as Record<string, unknown>;
237
+ }
238
+
239
+ let disk: Partial<Record<BudgetedIdentityField, string | undefined>> | null = null;
240
+ if (config.claudeMdPath) {
241
+ disk = {
242
+ soulMd: await readFile(SOUL_MD_PATH),
243
+ identityMd: await readFile(IDENTITY_MD_PATH),
244
+ claudeMd: await readFile(config.claudeMdPath),
245
+ toolsMd: await readFile(TOOLS_MD_PATH),
246
+ };
247
+ }
248
+
249
+ const unresolved = latestEvents.flatMap(({ rejection, reconciliation }) => {
250
+ if (reconciliation && reconciliation.createdAt > rejection.createdAt) return [];
251
+ const event = rejection;
252
+ const field = event.data?.field;
253
+ const rejectedDbHash = event.data?.dbHash;
254
+ const currentValue = typeof field === "string" ? profile?.[field] : undefined;
255
+ const diskValue =
256
+ typeof field === "string" ? disk?.[field as BudgetedIdentityField] : undefined;
257
+ if (typeof currentValue === "string" && diskValue === currentValue) return [];
258
+ return !(
259
+ typeof rejectedDbHash === "string" &&
260
+ typeof currentValue === "string" &&
261
+ contentSha256(currentValue) !== rejectedDbHash
262
+ )
263
+ ? [event]
264
+ : [];
265
+ });
266
+ const banners = await Promise.all(unresolved.map(renderProfileSyncRejectionBanner));
267
+ return banners.filter((banner): banner is string => banner !== null).join("");
268
+ } catch {
269
+ return "";
270
+ }
271
+ }
272
+
273
+ export async function prependProfileSyncRejectionBanner(
274
+ prompt: string,
275
+ config: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey" | "claudeMdPath">,
276
+ fetchImpl: typeof fetch = fetch,
277
+ readFile: FileReader = readFileIfExists,
278
+ ): Promise<{ prompt: string; injected: boolean }> {
279
+ const banner = await fetchProfileSyncRejectionBanner(config, fetchImpl, readFile);
280
+ return banner ? { prompt: banner + prompt, injected: true } : { prompt, injected: false };
281
+ }
282
+
59
283
  export async function writeIdentityBaselines(baselines: IdentityBaselines): Promise<void> {
60
284
  await Bun.write(IDENTITY_BASELINES_PATH, JSON.stringify(baselines));
61
285
  }
@@ -168,9 +392,23 @@ export function resolveClaudeMdPath(completedProviders: readonly string[]): stri
168
392
  }
169
393
 
170
394
  /** A single profile-update POST body, tagged with a label for logging. */
171
- interface ProfilePayload {
395
+ export interface ProfilePayload {
172
396
  label: string;
173
- body: Record<string, unknown>;
397
+ body: Record<string, string>;
398
+ }
399
+
400
+ /**
401
+ * Keep identity-file sync independent: one rejected ratcheting-budget write
402
+ * must not discard valid edits to other files from the same session.
403
+ */
404
+ export function buildIndependentIdentityPayloads(
405
+ updates: Record<string, string>,
406
+ changeSource: ProfileChangeSource,
407
+ ): ProfilePayload[] {
408
+ return Object.entries(updates).map(([field, value]) => ({
409
+ label: `identity.${field}`,
410
+ body: { [field]: value, changeSource },
411
+ }));
174
412
  }
175
413
 
176
414
  /**
@@ -229,8 +467,6 @@ export function buildIdentityPayload(
229
467
  const content = files.soulMd;
230
468
  if (baselines?.soulMd && contentSha256(content) === baselines.soulMd) {
231
469
  // File unchanged during session — skip to preserve Lead's DB edits
232
- } else if (content.length > MAX_PROFILE_FILE_LENGTH) {
233
- warnProfileFileTooLarge(agentId, "soulMd", content.length);
234
470
  } else if (content.trim()) {
235
471
  if (content.length < IDENTITY_FILE_MIN_LENGTH) {
236
472
  console.error(
@@ -246,8 +482,6 @@ export function buildIdentityPayload(
246
482
  const content = files.identityMd;
247
483
  if (baselines?.identityMd && contentSha256(content) === baselines.identityMd) {
248
484
  // File unchanged during session — skip to preserve Lead's DB edits
249
- } else if (content.length > MAX_PROFILE_FILE_LENGTH) {
250
- warnProfileFileTooLarge(agentId, "identityMd", content.length);
251
485
  } else if (content.trim()) {
252
486
  if (content.length < IDENTITY_FILE_MIN_LENGTH) {
253
487
  console.error(
@@ -263,8 +497,6 @@ export function buildIdentityPayload(
263
497
  const content = files.toolsMd;
264
498
  if (baselines?.toolsMd && contentSha256(content) === baselines.toolsMd) {
265
499
  // File unchanged during session — skip
266
- } else if (content.length > MAX_PROFILE_FILE_LENGTH) {
267
- warnProfileFileTooLarge(agentId, "toolsMd", content.length);
268
500
  } else if (content.trim()) {
269
501
  updates.toolsMd = content;
270
502
  }
@@ -298,6 +530,50 @@ async function readFileIfExists(path: string): Promise<string | undefined> {
298
530
  }
299
531
  }
300
532
 
533
+ /**
534
+ * Compare the five local profile markdown files with the authenticated agent's
535
+ * stored DB values. This must run inside the agent's local environment; a
536
+ * central script cannot read another worker container's `/workspace` files.
537
+ */
538
+ export async function auditProfileDivergences(
539
+ opts: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey" | "claudeMdPath" | "fetchImpl">,
540
+ readFile: FileReader = readFileIfExists,
541
+ ): Promise<ProfileDivergence[]> {
542
+ const doFetch = opts.fetchImpl ?? fetch;
543
+ const response = await doFetch(`${opts.apiUrl}/me`, {
544
+ headers: {
545
+ Authorization: `Bearer ${opts.apiKey}`,
546
+ "X-Agent-ID": opts.agentId,
547
+ },
548
+ });
549
+ if (!response.ok) {
550
+ throw new Error(`Profile divergence audit failed: HTTP ${response.status}`);
551
+ }
552
+
553
+ const stored = (await response.json()) as Agent;
554
+ const claudeMdPath =
555
+ opts.claudeMdPath ??
556
+ (stored.harnessProvider
557
+ ? stored.harnessProvider === "claude"
558
+ ? CLAUDE_MD_PATH
559
+ : WORKSPACE_CLAUDE_MD_PATH
560
+ : null);
561
+ if (!claudeMdPath) {
562
+ throw new Error(
563
+ "Profile divergence audit requires a registered harness provider or an explicit claudeMdPath",
564
+ );
565
+ }
566
+ const disk: ProfileMarkdownSnapshot = {
567
+ soulMd: await readFile(SOUL_MD_PATH),
568
+ identityMd: await readFile(IDENTITY_MD_PATH),
569
+ claudeMd: await readFile(claudeMdPath),
570
+ toolsMd: await readFile(TOOLS_MD_PATH),
571
+ heartbeatMd: await readFile(HEARTBEAT_MD_PATH),
572
+ };
573
+
574
+ return findProfileDivergences(disk, stored);
575
+ }
576
+
301
577
  /**
302
578
  * Collect the profile-update POST bodies to send. Each entry is one POST.
303
579
  * `fields` selects which groups to include. The file reader is injectable so
@@ -331,16 +607,12 @@ export async function collectProfilePayloads(
331
607
  baselines,
332
608
  agentId,
333
609
  );
334
- if (Object.keys(updates).length > 0) {
335
- payloads.push({ label: "identity", body: { ...updates, changeSource } });
336
- }
610
+ payloads.push(...buildIndependentIdentityPayloads(updates, changeSource));
337
611
  }
338
612
 
339
613
  if (fields.includes("claude")) {
340
614
  const raw = await readFile(claudeMdPath);
341
- if (raw && raw.length > MAX_PROFILE_FILE_LENGTH) {
342
- warnProfileFileTooLarge(agentId, "claudeMd", raw.length);
343
- } else if (raw?.trim()) {
615
+ if (raw?.trim()) {
344
616
  if (baselines?.claudeMd && contentSha256(raw) === baselines.claudeMd) {
345
617
  // CLAUDE.md unchanged during session — skip to preserve Lead's DB edits
346
618
  } else {
@@ -444,35 +444,62 @@ export async function buildCredStatusReport(
444
444
  }
445
445
 
446
446
  /**
447
- * Fire-and-forget POST of a `cred_status` snapshot to the API. Used by the
448
- * worker boot path (`runner.ts` after `awaitCredentials`) and the post-task
449
- * cache-miss path (also in `runner.ts`). Failures are logged, not thrown —
450
- * a stale dashboard is acceptable; blocking the worker is not.
447
+ * Strict PUT of credential readiness to the API: propagates network errors
448
+ * and non-2xx responses. The post-credential-wait boot path uses this — after
449
+ * a stale-runtime revival this write is the only transition out of
450
+ * `waiting_for_credentials`, so the caller must be able to observe (and
451
+ * retry) a failure instead of proceeding as though it landed.
451
452
  *
452
453
  * Posts to the existing `PUT /api/agents/:id/credential-status` endpoint
453
- * which (per migration 055) now accepts an optional `cred_status` field.
454
- * `ready` and `missing` are also included so legacy `agents.credentialMissing`
455
- * and `agents.status='waiting_for_credentials'` keep tracking.
454
+ * which (per migration 055) accepts an optional `cred_status` field. `ready`
455
+ * and `missing` are always included so legacy `agents.credentialMissing` and
456
+ * `agents.status='waiting_for_credentials'` keep tracking; the snapshot is
457
+ * optional so a hiccup while building it cannot lose the transition.
458
+ */
459
+ export async function sendCredStatusReport(
460
+ apiUrl: string,
461
+ apiKey: string,
462
+ agentId: string,
463
+ runtimeInstanceId: string | undefined,
464
+ report: { ready: boolean; missing: string[]; credStatus?: AgentCredStatus },
465
+ ): Promise<void> {
466
+ const res = await fetch(`${apiUrl}/api/agents/${encodeURIComponent(agentId)}/credential-status`, {
467
+ method: "PUT",
468
+ headers: {
469
+ Authorization: `Bearer ${apiKey}`,
470
+ "X-Agent-ID": agentId,
471
+ ...(runtimeInstanceId ? { "X-Runtime-Instance-ID": runtimeInstanceId } : {}),
472
+ "Content-Type": "application/json",
473
+ },
474
+ body: JSON.stringify({
475
+ ready: report.ready,
476
+ missing: report.missing,
477
+ ...(report.credStatus ? { cred_status: report.credStatus } : {}),
478
+ }),
479
+ });
480
+ if (!res.ok) {
481
+ const body = await res.text().catch(() => "");
482
+ throw new Error(`credential-status report failed: ${res.status} ${body}`.trim());
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Fire-and-forget wrapper around {@link sendCredStatusReport}. Used by the
488
+ * post-task cache-miss path (`runner.ts`), where a stale dashboard is
489
+ * acceptable and blocking the worker is not.
456
490
  */
457
491
  export async function reportCredStatus(
458
492
  apiUrl: string,
459
493
  apiKey: string,
460
494
  agentId: string,
495
+ runtimeInstanceId: string | undefined,
461
496
  credStatus: AgentCredStatus,
462
497
  ): Promise<void> {
463
498
  try {
464
- await fetch(`${apiUrl}/api/agents/${encodeURIComponent(agentId)}/credential-status`, {
465
- method: "PUT",
466
- headers: {
467
- Authorization: `Bearer ${apiKey}`,
468
- "X-Agent-ID": agentId,
469
- "Content-Type": "application/json",
470
- },
471
- body: JSON.stringify({
472
- ready: credStatus.ready,
473
- missing: credStatus.missing,
474
- cred_status: credStatus,
475
- }),
499
+ await sendCredStatusReport(apiUrl, apiKey, agentId, runtimeInstanceId, {
500
+ ready: credStatus.ready,
501
+ missing: credStatus.missing,
502
+ credStatus,
476
503
  });
477
504
  } catch (err) {
478
505
  console.warn(`[cred-status] POST failed (non-fatal): ${err}`);