@desplega.ai/agent-swarm 1.152.2 → 1.154.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 (284) hide show
  1. package/README.md +2 -2
  2. package/dist/{acp-adapter-pfeje2v5.js → acp-adapter-eb2aj8xy.js} +4 -4
  3. package/dist/{actions-0sttc7f5.js → actions-kpnwp1kz.js} +31 -27
  4. package/dist/{anthropic-messages-2y8nktar.js → anthropic-messages-9vqsbznt.js} +112 -84
  5. package/dist/{app-p8teva95.js → app-zk1apkf6.js} +10 -7
  6. package/dist/{assistant-r45jnsrt.js → assistant-kwq4y50q.js} +32 -28
  7. package/dist/{azure-openai-responses-f38x2avq.js → azure-openai-responses-04qwdwfq.js} +27 -13
  8. package/dist/{boot-reembed-je26h5y7.js → boot-reembed-22hd3jjn.js} +9 -7
  9. package/dist/{boot-reembed-1qmybsd4.js → boot-reembed-qe168424.js} +10 -8
  10. package/dist/{boot-scrub-logs-gte3h1mm.js → boot-scrub-logs-pkdpsrjd.js} +7 -5
  11. package/dist/{claude-adapter-csc1mzka.js → claude-adapter-3fx90nww.js} +18 -16
  12. package/dist/{claude-managed-adapter-dsmkcrjy.js → claude-managed-adapter-ea2aga6w.js} +3 -3
  13. package/dist/{claude-sdk-session-fcd6f078.js → claude-sdk-session-z7fahf1k.js} +18 -16
  14. package/dist/{cli-r2fpe7cx.js → cli-00w2c66p.js} +61 -55
  15. package/dist/{cli-wbyk465z.js → cli-09y3tjxz.js} +1 -1
  16. package/dist/{cli-ht5rs63n.js → cli-0e31nm4n.js} +16 -7
  17. package/dist/cli-0x5qzvkg.js +4279 -0
  18. package/dist/{cli-d3p82erb.js → cli-18amys3y.js} +9 -9
  19. package/dist/{cli-4ahpr4sw.js → cli-1jpjafw4.js} +5 -5
  20. package/dist/{cli-jbmc0r7r.js → cli-243tzws8.js} +1 -1
  21. package/dist/{cli-ch38q5z4.js → cli-24k806sc.js} +6789 -2542
  22. package/dist/cli-3bkz2p7j.js +2828 -0
  23. package/dist/cli-3er6gcsn.js +1849 -0
  24. package/dist/cli-4k7jazkq.js +3339 -0
  25. package/dist/{cli-c4eeb5q2.js → cli-5b6b62tz.js} +1 -1
  26. package/dist/{cli-qd65dvr6.js → cli-5vs0nh43.js} +65 -44
  27. package/dist/{cli-z3wkng35.js → cli-6r3cgpk2.js} +224 -156
  28. package/dist/{cli-pgj4a5fb.js → cli-71cj99hx.js} +4 -3
  29. package/dist/cli-8sea27q4.js +14075 -0
  30. package/dist/{cli-jvy9qgjs.js → cli-913yakhv.js} +22 -7
  31. package/dist/{cli-pf9q3696.js → cli-927erqqt.js} +5 -2
  32. package/dist/cli-95jnpnqg.js +230 -0
  33. package/dist/{cli-wpv5rgg2.js → cli-abp3g4ax.js} +15 -2
  34. package/dist/{cli-ckz5t2hb.js → cli-acy6bfhr.js} +10 -7
  35. package/dist/{cli-4yc5d8fk.js → cli-avkd6mqv.js} +50 -0
  36. package/dist/{cli-8wrympd7.js → cli-bzr0ftht.js} +26 -7
  37. package/dist/{cli-qn042eh7.js → cli-c1v10xb2.js} +194 -1913
  38. package/dist/cli-cgj4hc12.js +18902 -0
  39. package/dist/{cli-2nnd56zt.js → cli-e0pg4zeh.js} +3 -2
  40. package/dist/{cli-r84ctdxn.js → cli-e31ehqwy.js} +2 -2
  41. package/dist/{cli-gphgnvpf.js → cli-e3229na9.js} +3 -3
  42. package/dist/{cli-gpqcs935.js → cli-e3cyf59d.js} +2 -2
  43. package/dist/{cli-s25avqg9.js → cli-e781c6f9.js} +1 -0
  44. package/dist/{cli-s2qjdd6z.js → cli-eetrvv91.js} +1 -1
  45. package/dist/{cli-j4nv2wqy.js → cli-fdzg6jhf.js} +10 -4
  46. package/dist/{cli-fm4ytsdk.js → cli-g2h29qar.js} +2 -2
  47. package/dist/{cli-zvgj5zg8.js → cli-ge67p080.js} +302 -3497
  48. package/dist/cli-h38x0qhg.js +6912 -0
  49. package/dist/{cli-9xvdm3bx.js → cli-h60qfasa.js} +742 -278
  50. package/dist/{cli-jew3jda5.js → cli-hws0vn86.js} +2 -2
  51. package/dist/{cli-j8dggbcr.js → cli-k0g1sjzm.js} +23 -31
  52. package/dist/{cli-xw7vak96.js → cli-kp3qs53m.js} +5 -5
  53. package/dist/{cli-rgvp6r2x.js → cli-mcnhgnn1.js} +193 -4
  54. package/dist/{cli-zvkq5csn.js → cli-myaxtgh8.js} +7 -6
  55. package/dist/{cli-56h18ng2.js → cli-n62atwh9.js} +1 -1
  56. package/dist/cli-n7h1kqpf.js +29 -0
  57. package/dist/{cli-fmy65jdn.js → cli-pf1v8xhb.js} +7 -5
  58. package/dist/{cli-xy1cchaf.js → cli-q85qhckh.js} +238 -11
  59. package/dist/{cli-3bpzj8me.js → cli-r1d3dzt6.js} +2 -2
  60. package/dist/{pi-mono-adapter-jcqt71cg.js → cli-rxkqvngf.js} +67343 -101083
  61. package/dist/{cli-kj76p1ez.js → cli-scwq51gn.js} +13 -7
  62. package/dist/{cli-b9dd0h3k.js → cli-stwsmd6k.js} +3 -2
  63. package/dist/{cli-c2mk1kgf.js → cli-v4en75c5.js} +8 -1
  64. package/dist/{cli-z61ykvam.js → cli-wg04s9kr.js} +4 -3
  65. package/dist/cli-yn0a417a.js +4069 -0
  66. package/dist/{cli-zfzznn7c.js → cli-ytxs7mn1.js} +26 -2
  67. package/dist/{cli-4j8k1m4w.js → cli-zt1227k1.js} +1162 -192
  68. package/dist/cli.js +28 -35
  69. package/dist/{codex-adapter-fxmpsabw.js → codex-adapter-ttdm995v.js} +12 -11
  70. package/dist/{codex-hook-8jexc34x.js → codex-hook-8gx7bcz0.js} +3 -3
  71. package/dist/{codex-session-runner-rr1e47ea.js → codex-session-runner-3v8nh4c8.js} +12 -11
  72. package/dist/{commands-0f5rxz51.js → commands-1cxwbhhq.js} +7 -5
  73. package/dist/{db-r3jxf3wy.js → db-y9zwsr79.js} +25 -5
  74. package/dist/{dispatcher-06vph6q4.js → dispatcher-9m9zss36.js} +30 -26
  75. package/dist/dsh-adapter-gsb2qqf9.js +19 -0
  76. package/dist/{e2b-413b8t4v.js → e2b-93x1k0j5.js} +6 -7
  77. package/dist/{google-generative-ai-ndjmj5fa.js → google-generative-ai-gwwrnzqn.js} +33 -73
  78. package/dist/{google-vertex-fr665tqs.js → google-vertex-5wfawpwz.js} +38 -68
  79. package/dist/{handlers-0hgtc910.js → handlers-36fm1bwn.js} +33 -29
  80. package/dist/{hook-9kvrv1er.js → hook-pntw4r8h.js} +12 -10
  81. package/dist/{hook-fbfhkat3.js → hook-vwq8y26p.js} +13 -11
  82. package/dist/{http-xbyx8k5j.js → http-20jesakk.js} +357 -261
  83. package/dist/{index-jsw3aynb.js → index-6artrehg.js} +30 -26
  84. package/dist/{cli-pdz9qde4.js → index-9ch1n761.js} +10 -11
  85. package/dist/{index-rnyahfn5.js → index-9n5z2492.js} +30 -26
  86. package/dist/{index-66xz5e9j.js → index-h5gs261z.js} +521 -9
  87. package/dist/{index-tfsdxyv6.js → index-j5wrf1yw.js} +40 -25
  88. package/dist/{index-2sp0384z.js → index-kkeftv5h.js} +738 -21
  89. package/dist/{index-sgk50mbg.js → index-m2xqpz5r.js} +31 -27
  90. package/dist/{index-gpfn1741.js → index-m9ks8a7t.js} +9 -7
  91. package/dist/index-w097eg0x.js +5 -0
  92. package/dist/jiti-loader-b4837mtm.js +34 -0
  93. package/dist/jiti-static-loader-q2g5ds0y.js +31267 -0
  94. package/dist/{keepalive-s3xpr7sw.js → keepalive-pwxvxd2c.js} +9 -7
  95. package/dist/{lead-rq4n5r16.js → lead-m789wqa3.js} +46 -41
  96. package/dist/{maintenance-f0atj1ab.js → maintenance-bvg6w7e2.js} +10 -8
  97. package/dist/{mistral-conversations-qhr84ph3.js → mistral-conversations-zx0kn554.js} +24 -18
  98. package/dist/{node-x1g4v0pq.js → node-5qtycf6a.js} +16 -11
  99. package/dist/{oauth-refresh-sweep-q0t3bxre.js → oauth-refresh-sweep-6v4bd737.js} +8 -6
  100. package/dist/{onboard-br5j1kch.js → onboard-snckpcj1.js} +115 -35
  101. package/dist/{openai-codex-responses-wy6a43wd.js → openai-codex-responses-4vjqzvjp.js} +33 -27
  102. package/dist/{openai-completions-32rdwmze.js → openai-completions-pxsmym7g.js} +52 -64
  103. package/dist/{openai-responses-6xwf7bzg.js → openai-responses-tmn0rqp0.js} +23 -24
  104. package/dist/{opencode-adapter-28axqa09.js → opencode-adapter-qbmv4tpy.js} +6 -6
  105. package/dist/{otel-impl-vdqge2nt.js → otel-impl-e0ytens5.js} +4 -4
  106. package/dist/{pi-messages-b1mbr6gp.js → pi-messages-txpjj39q.js} +3 -3
  107. package/dist/pi-mono-adapter-c4ggemhz.js +1316 -0
  108. package/dist/{pricing-refresh-hb3691fd.js → pricing-refresh-52g2ywrw.js} +9 -7
  109. package/dist/{rbac-roles-t78xt39b.js → rbac-roles-5455kfdn.js} +8 -6
  110. package/dist/{rbac-roles-jh280k9g.js → rbac-roles-hbf0xa52.js} +9 -7
  111. package/dist/{render-v2-7p8r2ymg.js → render-v2-npm8xdzh.js} +16 -9
  112. package/dist/{seed-pricing-hf889enh.js → seed-pricing-650wbr1n.js} +8 -6
  113. package/dist/{setup-w6mb03jv.js → setup-k1mew5rf.js} +4 -5
  114. package/dist/virtual-modules-2q5b2pk4.js +54 -0
  115. package/dist/{work-objects-nt8gy1kj.js → work-objects-2ea3kvwg.js} +33 -29
  116. package/dist/{worker-dwy617t0.js → worker-ec6hvc4m.js} +46 -41
  117. package/openapi.json +109 -16
  118. package/package.json +7 -6
  119. package/plugin/opencode-plugins/lib/memory-raters.ts +7 -0
  120. package/src/be/automation-preflight.ts +6 -4
  121. package/src/be/db/agents.ts +75 -2
  122. package/src/be/db/tasks/read.ts +2 -0
  123. package/src/be/db/tasks/write.ts +7 -1
  124. package/src/be/db.ts +140 -18
  125. package/src/be/extensions/db.ts +35 -4
  126. package/src/be/memory/raters/llm.ts +2 -6
  127. package/src/be/memory/raters/noop.ts +2 -3
  128. package/src/be/memory/raters/registry.ts +4 -10
  129. package/src/be/migrations/157_defer_provenance_and_marker.sql +27 -0
  130. package/src/be/models-catalog.ts +2 -0
  131. package/src/be/modelsdev-cache.json +213 -0
  132. package/src/be/rbac-audit.ts +2 -0
  133. package/src/be/seed/agent-fs-provision.ts +23 -0
  134. package/src/be/seed-pricing.ts +3 -1
  135. package/src/be/swarm-config-guard.ts +1 -0
  136. package/src/commands/onboard/compose-generator.ts +16 -1
  137. package/src/commands/onboard/env-generator.ts +14 -1
  138. package/src/commands/onboard/manifest.ts +1 -0
  139. package/src/commands/onboard/steps/integration-slack.tsx +56 -16
  140. package/src/commands/onboard/steps/review.tsx +5 -0
  141. package/src/commands/onboard/types.ts +6 -0
  142. package/src/commands/onboard.tsx +5 -1
  143. package/src/commands/provider-credentials.ts +14 -2
  144. package/src/commands/runner.ts +18 -2
  145. package/src/extensions/identity.ts +15 -7
  146. package/src/extensions/lifecycle.ts +15 -21
  147. package/src/hooks/codex-hook.ts +7 -1
  148. package/src/http/agents.ts +12 -4
  149. package/src/http/config.ts +1 -1
  150. package/src/http/core.ts +5 -3
  151. package/src/http/extensions.ts +108 -34
  152. package/src/http/poll.ts +7 -0
  153. package/src/http/schedules.ts +10 -0
  154. package/src/http/script-runs.ts +35 -26
  155. package/src/http/status.ts +24 -11
  156. package/src/http/tasks.ts +6 -1
  157. package/src/prompts/base-prompt.ts +15 -7
  158. package/src/prompts/memories.ts +5 -7
  159. package/src/prompts/session-templates.ts +3 -2
  160. package/src/prompts/steering-delivery.ts +2 -0
  161. package/src/providers/claude-managed-models.ts +8 -0
  162. package/src/providers/codex-adapter.ts +3 -6
  163. package/src/providers/codex-models.ts +15 -1
  164. package/src/providers/dsh-adapter.ts +248 -0
  165. package/src/providers/index.ts +5 -1
  166. package/src/providers/modelsdev-reasoning.json +50 -0
  167. package/src/providers/pi-mono-extension.ts +2 -5
  168. package/src/rbac/legacy-policy.ts +15 -1
  169. package/src/rbac/types.ts +2 -0
  170. package/src/server.ts +7 -1
  171. package/src/slack/ack.ts +43 -8
  172. package/src/slack/app.ts +32 -10
  173. package/src/slack/config.ts +29 -0
  174. package/src/slack/handlers.ts +7 -1
  175. package/src/slack/render-v2.ts +186 -17
  176. package/src/slack/responses.ts +1 -3
  177. package/src/slack/task-output.ts +66 -8
  178. package/src/slack/thread-buffer.ts +27 -3
  179. package/src/telemetry.ts +2 -3
  180. package/src/tests/agent-fs-provision-seeder.test.ts +56 -2
  181. package/src/tests/agents-harness-provider.test.ts +19 -0
  182. package/src/tests/agents-list-model-display.test.ts +60 -1
  183. package/src/tests/base-prompt.test.ts +25 -0
  184. package/src/tests/claude-managed-adapter.test.ts +6 -0
  185. package/src/tests/codex-gpt6-release.test.ts +97 -0
  186. package/src/tests/codex-steering-hook.test.ts +2 -2
  187. package/src/tests/config-api-only-keys.test.ts +9 -0
  188. package/src/tests/context-window.test.ts +1 -0
  189. package/src/tests/db-task-read-contract.test.ts +9 -0
  190. package/src/tests/db-task-write-contract.test.ts +9 -0
  191. package/src/tests/defer-task.test.ts +115 -16
  192. package/src/tests/dsh-adapter.test.ts +276 -0
  193. package/src/tests/e2e-nightly-report.test.ts +3 -3
  194. package/src/tests/e2e-redact.test.ts +2 -0
  195. package/src/tests/env-flag.test.ts +6 -0
  196. package/src/tests/extension-agent-visibility.test.ts +474 -0
  197. package/src/tests/extensions-db.test.ts +41 -0
  198. package/src/tests/extensions-http.test.ts +148 -4
  199. package/src/tests/extensions-lifecycle.test.ts +21 -1
  200. package/src/tests/extensions-mcp-tools.test.ts +9 -5
  201. package/src/tests/fetch-resolved-env.test.ts +43 -0
  202. package/src/tests/internal-ai/complete-structured.test.ts +32 -9
  203. package/src/tests/internal-ai/credentials.test.ts +4 -4
  204. package/src/tests/internal-ai/models.test.ts +20 -0
  205. package/src/tests/memory-rate-tool.test.ts +7 -10
  206. package/src/tests/memory-rater-defaults.test.ts +23 -0
  207. package/src/tests/memory-rater-e2e.test.ts +2 -3
  208. package/src/tests/migration-046-budgets.test.ts +2 -0
  209. package/src/tests/model-control.test.ts +2 -2
  210. package/src/tests/onboard-compose.test.ts +14 -0
  211. package/src/tests/onboard-env.test.ts +17 -0
  212. package/src/tests/onboard-manifest.test.ts +12 -0
  213. package/src/tests/opus-5-5-registry.test.ts +48 -0
  214. package/src/tests/pricing-refresh.test.ts +1 -1
  215. package/src/tests/pricing-routes.test.ts +15 -3
  216. package/src/tests/provider-registration.test.ts +5 -2
  217. package/src/tests/rbac-admission-e2e.test.ts +54 -0
  218. package/src/tests/rbac-e2e-helpers.ts +2 -0
  219. package/src/tests/rbac-engine.test.ts +32 -0
  220. package/src/tests/rbac-wire-e2e.test.ts +33 -0
  221. package/src/tests/reasoning-effort.test.ts +8 -0
  222. package/src/tests/reload-config.test.ts +33 -0
  223. package/src/tests/script-connections.test.ts +3 -0
  224. package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
  225. package/src/tests/slack-config.test.ts +136 -0
  226. package/src/tests/slack-mode-lifecycle.test.ts +69 -0
  227. package/src/tests/slack-render-v2.test.ts +446 -16
  228. package/src/tests/slack-steering.test.ts +69 -1
  229. package/src/tests/slack-task-output.test.ts +33 -12
  230. package/src/tests/status.test.ts +22 -2
  231. package/src/tests/steering-core.test.ts +35 -0
  232. package/src/tests/steering-delivery.test.ts +39 -0
  233. package/src/tests/steering-transport.test.ts +2 -2
  234. package/src/tests/task-tool-preload-mcp.test.ts +3 -1
  235. package/src/tests/telemetry-init.test.ts +18 -0
  236. package/src/tests/workflow-live-recovery.test.ts +126 -0
  237. package/src/tests/workflow-llm.test.ts +3 -3
  238. package/src/tools/defer-task.ts +64 -26
  239. package/src/tools/extension-common.ts +4 -0
  240. package/src/tools/extension-install.ts +1 -1
  241. package/src/tools/extension-list.ts +4 -3
  242. package/src/tools/join-swarm.ts +1 -1
  243. package/src/tools/schedules/create-schedule.ts +11 -1
  244. package/src/tools/schedules/patch-schedule.ts +5 -0
  245. package/src/tools/schedules/update-schedule.ts +5 -0
  246. package/src/tools/send-task.ts +6 -0
  247. package/src/tools/utils.ts +1 -1
  248. package/src/types.ts +30 -8
  249. package/src/utils/context-window.ts +1 -0
  250. package/src/utils/credentials.ts +8 -0
  251. package/src/utils/internal-ai/complete-structured.ts +8 -0
  252. package/src/utils/internal-ai/models.ts +5 -5
  253. package/src/utils/memory-raters.ts +3 -0
  254. package/src/workflows/engine.ts +28 -0
  255. package/src/workflows/recovery.ts +7 -4
  256. package/templates/schedules/daily-status-report/content.md +25 -12
  257. package/templates/schedules/daily-workflow-health-audit/content.md +1 -1
  258. package/templates/skills/swarm-extensions/content.md +9 -5
  259. package/dist/cli-4wppfkcm.js +0 -16384
  260. package/dist/cli-55qrx3q8.js +0 -6041
  261. package/dist/cli-cg4vnyh4.js +0 -305
  262. package/dist/cli-jyetr64j.js +0 -52
  263. package/dist/cli-pp9ax9pa.js +0 -520
  264. package/dist/cli-qa66qaf0.js +0 -6076
  265. package/dist/cli-qghpvca2.js +0 -590
  266. package/dist/cli-rembwgt8.js +0 -861
  267. package/dist/cli-tcvj7j2r.js +0 -1971
  268. package/dist/cli-vfcth11k.js +0 -36
  269. package/dist/cli-w1d1bgkk.js +0 -1422
  270. package/dist/cli-y6x0gqtg.js +0 -5977
  271. package/dist/es5-tj3tn4jj.js +0 -844
  272. package/dist/index-1kq5hw2p.js +0 -467
  273. package/dist/index-1p8wdstt.js +0 -7
  274. package/dist/index-8fdjzb2f.js +0 -100
  275. package/dist/index-bxmwzbgw.js +0 -365
  276. package/dist/index-nzerxd5z.js +0 -7
  277. package/dist/index-pyymnzat.js +0 -1168
  278. package/dist/index-qv6atbab.js +0 -619
  279. package/dist/index-rja32qa7.js +0 -801
  280. package/dist/index-tzg5srfp.js +0 -211
  281. package/dist/index-vhncw983.js +0 -90
  282. package/dist/index-xk036zsk.js +0 -859
  283. package/dist/index-zyazbzcb.js +0 -6
  284. package/dist/{claude-managed-setup-48dwg9bv.js → claude-managed-setup-f5pmsyrp.js} +3 -3
@@ -610,7 +610,11 @@ async function pollAndStartSteeringDispatches(
610
610
  prepared.push(item);
611
611
  item.text =
612
612
  session.deliverSteering && !state.outcomes.has(message.id)
613
- ? await renderSteeringDelivery(message.id, message.body)
613
+ ? await renderSteeringDelivery(
614
+ message.id,
615
+ message.body,
616
+ message.senderLabel ?? message.createdByKind,
617
+ )
614
618
  : undefined;
615
619
  }
616
620
  } catch (error) {
@@ -765,6 +769,9 @@ export interface ResolvedEnvResult {
765
769
  resolvedProvider: ProviderName;
766
770
  }
767
771
 
772
+ // Preserve the deployment value before config reloads mutate process.env.
773
+ const deploymentMemoryRaters = process.env.MEMORY_RATERS;
774
+
768
775
  export async function fetchResolvedEnv(
769
776
  apiUrl: string,
770
777
  apiKey: string,
@@ -792,6 +799,12 @@ export async function fetchResolvedEnv(
792
799
  configs: Array<{ key: string; value: string }>;
793
800
  };
794
801
 
802
+ // A deleted row restores the deployment value (including unset), while
803
+ // an explicit empty row below remains the disable-all-raters override.
804
+ // Only reset after a successful fetch so outages retain the current value.
805
+ env.MEMORY_RATERS =
806
+ baseEnv === process.env ? deploymentMemoryRaters : baseEnv.MEMORY_RATERS;
807
+
795
808
  if (data.configs?.length) {
796
809
  scriptsOnlyConfigValue = data.configs.find(
797
810
  (config) => config.key === "SCRIPTS_ONLY_MCP",
@@ -1060,7 +1073,10 @@ export function applyResolvedEnvToProcessEnv(
1060
1073
  const changed: string[] = [];
1061
1074
  for (const key of RELOADABLE_ENV_KEYS) {
1062
1075
  const next = freshEnv[key];
1063
- if (next !== undefined && next !== process.env[key]) {
1076
+ if (key === "MEMORY_RATERS" && next === undefined && process.env[key] !== undefined) {
1077
+ delete process.env[key];
1078
+ changed.push(key);
1079
+ } else if (next !== undefined && next !== process.env[key]) {
1064
1080
  const previous = process.env[key];
1065
1081
  process.env[key] = next;
1066
1082
  changed.push(key);
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  createAgent,
3
+ EXTENSION_AGENT_ROLE,
3
4
  getAllAgents,
4
5
  getDbClient,
5
6
  updateAgentMaxTasks,
@@ -14,7 +15,9 @@ function extensionAgentName(name: string): string {
14
15
  export async function ensureExtensionAgent(name: string): Promise<string> {
15
16
  const agentName = extensionAgentName(name);
16
17
  return await getDbClient().transaction(async () => {
17
- let agent = (await getAllAgents()).find((candidate) => candidate.name === agentName);
18
+ let agent = (await getAllAgents({ includeExtensions: true })).find(
19
+ (candidate) => candidate.name === agentName,
20
+ );
18
21
  if (!agent) {
19
22
  agent = await createAgent({
20
23
  name: agentName,
@@ -27,17 +30,22 @@ export async function ensureExtensionAgent(name: string): Promise<string> {
27
30
 
28
31
  if (agent.status !== "offline") await updateAgentStatus(agent.id, "offline");
29
32
  if (agent.maxTasks !== 0) await updateAgentMaxTasks(agent.id, 0);
30
- await updateAgentProfile(agent.id, {
31
- description: `System agent for extension ${name}`,
32
- role: "extension",
33
- capabilities: [],
34
- });
33
+ await updateAgentProfile(
34
+ agent.id,
35
+ {
36
+ description: `System agent for extension ${name}`,
37
+ role: EXTENSION_AGENT_ROLE,
38
+ capabilities: [],
39
+ },
40
+ undefined,
41
+ { allowExtensionRole: true },
42
+ );
35
43
  return agent.id;
36
44
  });
37
45
  }
38
46
 
39
47
  export async function deactivateExtensionAgent(name: string): Promise<void> {
40
- const agent = (await getAllAgents()).find(
48
+ const agent = (await getAllAgents({ includeExtensions: true })).find(
41
49
  (candidate) => candidate.name === extensionAgentName(name),
42
50
  );
43
51
  if (agent && agent.status !== "offline") await updateAgentStatus(agent.id, "offline");
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  activateExtensionVersionSnapshot,
3
3
  getExtensionById,
4
- getExtensionFiles,
5
4
  getExtensionVersion,
6
5
  insertExtensionRun,
7
6
  listExtensions,
@@ -66,28 +65,23 @@ function maxUpdatedAt(extensions: Extension[]): string {
66
65
  }
67
66
 
68
67
  async function loadableExtension(record: Extension): Promise<LoadableExtension> {
69
- if (record.activeVersion !== record.version) {
70
- const version = await getExtensionVersion(record.id, record.activeVersion);
71
- if (!version) {
72
- throw new ExtensionLifecycleError(
73
- `Extension version ${record.activeVersion} was not found`,
74
- 404,
75
- );
76
- }
77
- return {
78
- record: {
79
- ...record,
80
- manifestJson: version.manifestJson,
81
- contentHash: version.contentHash,
82
- },
83
- manifest: ExtensionManifestSchema.parse(JSON.parse(version.manifestJson)),
84
- files: JSON.parse(version.filesJson) as Record<string, string>,
85
- };
68
+ // Always load the immutable selected version. The mutable current-file projection
69
+ // can advance while an agent stages a draft after we read the extension row.
70
+ const version = await getExtensionVersion(record.id, record.activeVersion);
71
+ if (!version) {
72
+ throw new ExtensionLifecycleError(
73
+ `Extension version ${record.activeVersion} was not found`,
74
+ 404,
75
+ );
86
76
  }
87
77
  return {
88
- record,
89
- manifest: ExtensionManifestSchema.parse(JSON.parse(record.manifestJson)),
90
- files: await getExtensionFiles(record.id),
78
+ record: {
79
+ ...record,
80
+ manifestJson: version.manifestJson,
81
+ contentHash: version.contentHash,
82
+ },
83
+ manifest: ExtensionManifestSchema.parse(JSON.parse(version.manifestJson)),
84
+ files: JSON.parse(version.filesJson) as Record<string, string>,
91
85
  };
92
86
  }
93
87
 
@@ -100,7 +100,13 @@ export async function collectDeliverableSteering(
100
100
  } catch {
101
101
  continue;
102
102
  }
103
- delivered.push(await renderSteeringDelivery(message.id, message.body));
103
+ delivered.push(
104
+ await renderSteeringDelivery(
105
+ message.id,
106
+ message.body,
107
+ message.senderLabel ?? message.createdByKind,
108
+ ),
109
+ );
104
110
  }
105
111
  return delivered;
106
112
  }
@@ -5,6 +5,7 @@ import {
5
5
  computeContentHash,
6
6
  createAgent,
7
7
  deleteSwarmConfigByKey,
8
+ EXTENSION_AGENT_ROLE,
8
9
  getAgentById,
9
10
  getAgentWithTasks,
10
11
  getAllAgents,
@@ -12,6 +13,7 @@ import {
12
13
  getDbClient,
13
14
  getResolvedConfig,
14
15
  getSwarmConfigs,
16
+ ReservedAgentRoleError,
15
17
  resetEmptyPollCount,
16
18
  setAgentHarnessProvider,
17
19
  updateAgentActivity,
@@ -181,7 +183,7 @@ const setAgentHarnessProviderRoute = route({
181
183
  },
182
184
  });
183
185
 
184
- const LocalHarnessProviderSchema = z.enum(["claude", "codex", "pi", "opencode", "acp"]);
186
+ const LocalHarnessProviderSchema = z.enum(["claude", "codex", "pi", "opencode", "acp", "dsh"]);
185
187
  const AcpRuntimeConfigSchema = z
186
188
  .object({
187
189
  target: z.enum(ACP_TARGET_IDS),
@@ -339,7 +341,7 @@ const listAgents = route({
339
341
  pattern: ["api", "agents"],
340
342
  summary: "List all agents",
341
343
  description:
342
- "Returns agents WITHOUT the six identity-markdown blobs (`claudeMd`/`soulMd`/`identityMd`/`toolsMd`/`heartbeatMd`/`setupScript`) by default — they bloat the list by ~16 KB/agent and the overview never renders them. Pass `fields=full` to restore them, or fetch a single agent via `GET /api/agents/{id}`.",
344
+ "Returns agents WITHOUT the six identity-markdown blobs (`claudeMd`/`soulMd`/`identityMd`/`toolsMd`/`heartbeatMd`/`setupScript`) by default — they bloat the list by ~16 KB/agent and the overview never renders them. Pass `fields=full` to restore them, or fetch a single agent via `GET /api/agents/{id}`. Extension identities (`ext:<name>`, role `extension`) are never listed; they are API principals, not workers.",
343
345
  tags: ["Agents"],
344
346
  query: z.object({
345
347
  include: z.enum(["tasks"]).optional(),
@@ -578,6 +580,12 @@ export async function handleAgentRegister(
578
580
  const parsed = await registerAgent.parse(req, res, pathSegments, new URLSearchParams());
579
581
  if (!parsed) return true;
580
582
 
583
+ // Extension identities are created only by the extension lifecycle.
584
+ if (parsed.body.role === EXTENSION_AGENT_ROLE) {
585
+ jsonError(res, `Role "${EXTENSION_AGENT_ROLE}" is reserved for extension identities.`, 400);
586
+ return true;
587
+ }
588
+
581
589
  const agentId = myAgentId || crypto.randomUUID();
582
590
  // Read once so one mode applies consistently even if a config reload
583
591
  // flips the flag mid-request.
@@ -877,7 +885,7 @@ export async function handleAgentsRest(
877
885
  },
878
886
  );
879
887
  } catch (error) {
880
- if (error instanceof HeartbeatExpiryError) {
888
+ if (error instanceof HeartbeatExpiryError || error instanceof ReservedAgentRoleError) {
881
889
  jsonError(res, error.message, 400);
882
890
  return true;
883
891
  }
@@ -1046,7 +1054,7 @@ export async function handleAgentsRest(
1046
1054
  })
1047
1055
  )[0]?.value ?? "");
1048
1056
  const allowedLevels =
1049
- harness_provider === "acp"
1057
+ harness_provider === "acp" || harness_provider === "dsh"
1050
1058
  ? []
1051
1059
  : reasoningCapability(harness_provider, modelForValidation ?? "").levels;
1052
1060
  if (!allowedLevels.includes(reasoning_effort)) {
@@ -37,7 +37,7 @@ const SECRETS_FORCE_MASK_NOTE =
37
37
  // bootstrap admin key would let a compromised worker administer the shared org.
38
38
  // The API materializes these into its own process.env at boot via
39
39
  // getInjectableGlobalConfigs, so no HTTP consumer legitimately needs them.
40
- const API_ONLY_CONFIG_KEYS = new Set(["API_AGENT_FS_API_KEY"]);
40
+ const API_ONLY_CONFIG_KEYS = new Set(["API_AGENT_FS_API_KEY", "SLACK_SIGNING_SECRET"]);
41
41
 
42
42
  function stripApiOnlyKeys<T extends { key: string }>(configs: T[]): T[] {
43
43
  return configs.filter((config) => !API_ONLY_CONFIG_KEYS.has(config.key));
package/src/http/core.ts CHANGED
@@ -150,6 +150,10 @@ async function reloadGlobalConfigsAndIntegrationsInner(): Promise<ReloadConfigRe
150
150
 
151
151
  const updated = await loadGlobalConfigsIntoEnv(true);
152
152
 
153
+ // Retire the old transport before any other integration can fail during
154
+ // reload. HTTP/invalid selection must never leave the previous socket live.
155
+ await stopSlackApp();
156
+
153
157
  // File-storage provider selection reads process.env once and memoizes; the
154
158
  // env we just (re)hydrated may flip it (local-fs → agent-fs after late
155
159
  // provisioning, or a rotated bootstrap key). Reset so the next fs request
@@ -206,9 +210,7 @@ async function reloadGlobalConfigsAndIntegrationsInner(): Promise<ReloadConfigRe
206
210
  resetJira();
207
211
  if (await initJira()) integrations.push("jira");
208
212
 
209
- await stopSlackApp();
210
- await startSlackApp();
211
- integrations.push("slack");
213
+ if (await startSlackApp()) integrations.push("slack");
212
214
 
213
215
  return {
214
216
  configsLoaded: updated.length,
@@ -4,9 +4,11 @@ import { resolveHttpAuditUserId } from "../be/audit-user";
4
4
  import { getAgentById } from "../be/db";
5
5
  import {
6
6
  deleteExtension,
7
+ ExtensionOwnershipError,
7
8
  getExtensionById,
8
9
  getExtensionByName,
9
10
  getExtensionFiles,
11
+ type InstallExtensionResult,
10
12
  installExtension,
11
13
  listExtensionRuns,
12
14
  listExtensions,
@@ -23,8 +25,15 @@ import {
23
25
  reloadExtension,
24
26
  } from "../extensions/lifecycle";
25
27
  import { ExtensionConfigError } from "../extensions/loader";
26
- import { can, type PermissionVerb, type RbacPrincipal } from "../rbac";
27
28
  import {
29
+ can,
30
+ LEGACY_RULES,
31
+ type PermissionVerb,
32
+ type RbacPrincipal,
33
+ type RbacResource,
34
+ } from "../rbac";
35
+ import {
36
+ type Extension,
28
37
  ExtensionInstallBodySchema,
29
38
  ExtensionManifestSchema,
30
39
  ExtensionRunSchema,
@@ -44,6 +53,8 @@ const installRoute = route({
44
53
  pattern: ["api", "extensions", "install"],
45
54
  operationId: "extensions_install",
46
55
  summary: "Validate and install an extension bundle",
56
+ description:
57
+ "Any authenticated agent can install a disabled draft owned by its agent ID. Workers can update only their own bundles; activation remains lead/operator-only.",
47
58
  tags: ["Extensions"],
48
59
  body: ExtensionInstallBodySchema,
49
60
  responses: {
@@ -153,6 +164,8 @@ const patchRoute = route({
153
164
  pattern: ["api", "extensions", null],
154
165
  operationId: "extensions_update",
155
166
  summary: "Update extension priority, config, or description",
167
+ description:
168
+ "Workers may edit their own disabled extensions. Leads, operators, and dashboard users retain access to all extensions.",
156
169
  tags: ["Extensions"],
157
170
  params: idParamsSchema,
158
171
  body: z
@@ -177,6 +190,8 @@ const deleteRoute = route({
177
190
  pattern: ["api", "extensions", null],
178
191
  operationId: "extensions_delete",
179
192
  summary: "Uninstall a disabled extension",
193
+ description:
194
+ "Workers may uninstall their own disabled extensions. Leads, operators, and dashboard users retain access to all extensions.",
180
195
  tags: ["Extensions"],
181
196
  params: idParamsSchema,
182
197
  responses: {
@@ -316,14 +331,23 @@ async function extensionPrincipal(
316
331
  return { kind: "agent", agentId: "", isLead: false };
317
332
  }
318
333
 
334
+ /** Restrict ordinary agents; retain the existing blanket access for elevated writers. */
335
+ function ownerOnlyAgentId(principal: RbacPrincipal): string | undefined {
336
+ return principal.kind === "agent" &&
337
+ !LEGACY_RULES["lead-or-operator-or-user"].evaluate(principal, undefined)
338
+ ? principal.agentId
339
+ : undefined;
340
+ }
341
+
319
342
  async function requirePermission(
320
343
  req: IncomingMessage,
321
344
  res: ServerResponse,
322
345
  callerAgentId: string | undefined,
323
346
  verb: Extract<PermissionVerb, "extension.write" | "extension.activate">,
347
+ resource: RbacResource = { kind: "none" },
324
348
  ): Promise<RbacPrincipal | null> {
325
349
  const principal = await extensionPrincipal(req, callerAgentId);
326
- const decision = can({ principal, verb, resource: { kind: "none" }, source: "http" });
350
+ const decision = can({ principal, verb, resource, source: "http" });
327
351
  if (decision.allow) return principal;
328
352
  jsonError(res, `Forbidden: ${decision.reason}`, 403);
329
353
  return null;
@@ -331,11 +355,13 @@ async function requirePermission(
331
355
 
332
356
  function respondLifecycleError(res: ServerResponse, error: unknown): void {
333
357
  const status =
334
- error instanceof ExtensionConfigError
335
- ? 400
336
- : error instanceof ExtensionLifecycleError
337
- ? error.status
338
- : 500;
358
+ error instanceof ExtensionOwnershipError
359
+ ? 403
360
+ : error instanceof ExtensionConfigError
361
+ ? 400
362
+ : error instanceof ExtensionLifecycleError
363
+ ? error.status
364
+ : 500;
339
365
  jsonError(res, scrubSecrets(error instanceof Error ? error.message : String(error)), status);
340
366
  }
341
367
 
@@ -349,26 +375,41 @@ export async function handleExtensions(
349
375
  if (installRoute.match(req.method, pathSegments)) {
350
376
  const parsed = await installRoute.parse(req, res, pathSegments, queryParams);
351
377
  if (!parsed) return true;
352
- const principal = await requirePermission(req, res, agentId, "extension.write");
378
+ const existing = await getExtensionByName(parsed.body.manifest.name);
379
+ const principal = await requirePermission(req, res, agentId, "extension.write", {
380
+ kind: "extension",
381
+ extensionId: existing?.id,
382
+ createdByAgentId: existing?.createdByAgentId,
383
+ });
353
384
  if (!principal) return true;
354
385
  const validation = await validateBundle(parsed.body);
355
386
  if (!validation.ok) {
356
387
  json(res, { error: "extension_validation_failed", diagnostics: validation.diagnostics }, 400);
357
388
  return true;
358
389
  }
359
- const existing = await getExtensionByName(validation.manifest.name);
360
390
  const writerAgentId = principal.kind === "agent" ? principal.agentId : undefined;
361
391
  const updatedBy = await resolveHttpAuditUserId(req, writerAgentId);
362
392
  const shouldActivate = principal.kind !== "agent" && existing?.enabled === true;
363
- const result = await installExtension({
364
- manifest: validation.manifest,
365
- files: parsed.body.files,
366
- priority: parsed.body.priority,
367
- config: parsed.body.config,
368
- agentId: writerAgentId,
369
- createdBy: updatedBy,
370
- activate: shouldActivate,
371
- });
393
+ // Enforce ownership again inside the upsert transaction: validation can yield
394
+ // while another agent installs the same name.
395
+ const ownerOnly = ownerOnlyAgentId(principal);
396
+ let result: InstallExtensionResult;
397
+ try {
398
+ result = await installExtension({
399
+ manifest: validation.manifest,
400
+ files: parsed.body.files,
401
+ priority: parsed.body.priority,
402
+ config: parsed.body.config,
403
+ agentId: writerAgentId,
404
+ createdBy: updatedBy,
405
+ activate: shouldActivate,
406
+ ownerOnly,
407
+ });
408
+ } catch (error) {
409
+ if (!(error instanceof ExtensionOwnershipError)) throw error;
410
+ jsonError(res, error.message, 403);
411
+ return true;
412
+ }
372
413
  let extension = result.extension;
373
414
  if (shouldActivate) {
374
415
  try {
@@ -446,19 +487,42 @@ export async function handleExtensions(
446
487
  if (patchRoute.match(req.method, pathSegments)) {
447
488
  const parsed = await patchRoute.parse(req, res, pathSegments, queryParams);
448
489
  if (!parsed) return true;
449
- const principal = await requirePermission(req, res, agentId, "extension.write");
490
+ const stored = await getExtensionById(parsed.params.id);
491
+ if (!stored) {
492
+ jsonError(res, "Extension not found", 404);
493
+ return true;
494
+ }
495
+ const principal = await requirePermission(req, res, agentId, "extension.write", {
496
+ kind: "extension",
497
+ extensionId: stored.id,
498
+ createdByAgentId: stored.createdByAgentId,
499
+ });
450
500
  if (!principal) return true;
451
501
  const writerAgentId = principal.kind === "agent" ? principal.agentId : undefined;
452
- const stored = parsed.body.config ? await getExtensionById(parsed.params.id) : null;
453
- const config =
454
- parsed.body.config && stored
455
- ? preserveRedactedConfigValues(parsed.body.config, stored.configJson)
456
- : parsed.body.config;
457
- let extension = await updateExtensionMeta(parsed.params.id, {
458
- ...parsed.body,
459
- ...(config === undefined ? {} : { config }),
460
- updatedBy: await resolveHttpAuditUserId(req, writerAgentId),
461
- });
502
+ const ownerOnly = ownerOnlyAgentId(principal);
503
+ // PATCH reloads live code; ordinary owners may edit only disabled drafts.
504
+ if (
505
+ stored.enabled &&
506
+ ownerOnly &&
507
+ !(await requirePermission(req, res, agentId, "extension.activate"))
508
+ ) {
509
+ return true;
510
+ }
511
+ const config = parsed.body.config
512
+ ? preserveRedactedConfigValues(parsed.body.config, stored.configJson)
513
+ : parsed.body.config;
514
+ let extension: Extension | null;
515
+ try {
516
+ extension = await updateExtensionMeta(parsed.params.id, {
517
+ ...parsed.body,
518
+ ...(config === undefined ? {} : { config }),
519
+ updatedBy: await resolveHttpAuditUserId(req, writerAgentId),
520
+ ownerOnly,
521
+ });
522
+ } catch (error) {
523
+ respondLifecycleError(res, error);
524
+ return true;
525
+ }
462
526
  if (!extension) {
463
527
  jsonError(res, "Extension not found", 404);
464
528
  return true;
@@ -478,18 +542,28 @@ export async function handleExtensions(
478
542
  if (deleteRoute.match(req.method, pathSegments)) {
479
543
  const parsed = await deleteRoute.parse(req, res, pathSegments, queryParams);
480
544
  if (!parsed) return true;
481
- const principal = await requirePermission(req, res, agentId, "extension.write");
482
- if (!principal) return true;
483
- const extension = await getExtensionById(parsed.params.id);
484
- if (!extension) {
545
+ const stored = await getExtensionById(parsed.params.id);
546
+ if (!stored) {
485
547
  jsonError(res, "Extension not found", 404);
486
548
  return true;
487
549
  }
550
+ const principal = await requirePermission(req, res, agentId, "extension.write", {
551
+ kind: "extension",
552
+ extensionId: stored.id,
553
+ createdByAgentId: stored.createdByAgentId,
554
+ });
555
+ if (!principal) return true;
556
+ const extension = stored;
488
557
  if (extension.enabled) {
489
558
  jsonError(res, "Disable the extension before uninstalling it", 409);
490
559
  return true;
491
560
  }
492
- await deleteExtension(extension.id);
561
+ try {
562
+ await deleteExtension(extension.id, ownerOnlyAgentId(principal));
563
+ } catch (error) {
564
+ respondLifecycleError(res, error);
565
+ return true;
566
+ }
493
567
  deleteRoute.respond(res, 200, { deleted: true });
494
568
  return true;
495
569
  }
package/src/http/poll.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  getUnassignedTaskIdsForAgent,
22
22
  getUserById,
23
23
  hasCapacity,
24
+ isExtensionAgent,
24
25
  recordBudgetRefusalNotification,
25
26
  startTask,
26
27
  updateAgentStatusFromCapacity,
@@ -310,6 +311,12 @@ export async function handlePoll(
310
311
  return { error: "Agent not found", status: 404 };
311
312
  }
312
313
 
314
+ // Extension identities authenticate to the API but never execute
315
+ // work: no offers, no pending assignments, no pool claims.
316
+ if (isExtensionAgent(agent)) {
317
+ return { trigger: null };
318
+ }
319
+
313
320
  // A process whose runtime has been retired must not be handed work: it
314
321
  // would execute alongside whatever replaced it. Dispatch is gated on a
315
322
  // live runtime identity rather than on X-Agent-ID alone, which only
@@ -6,11 +6,13 @@ import { resolveHttpAuditUserId } from "../be/audit-user";
6
6
  import {
7
7
  createScheduledTask,
8
8
  deleteScheduledTask,
9
+ extensionAgentAssignmentError,
9
10
  getAgentById,
10
11
  getScheduledTaskById,
11
12
  getScheduledTaskByName,
12
13
  getScheduledTasks,
13
14
  getWorkflow,
15
+ isExtensionAgent,
14
16
  updateScheduledTask,
15
17
  withFavoriteFlags,
16
18
  } from "../be/db";
@@ -355,6 +357,10 @@ export async function handleSchedules(
355
357
  jsonError(res, "Target agent not found", 400);
356
358
  return true;
357
359
  }
360
+ if (isExtensionAgent(agent)) {
361
+ jsonError(res, extensionAgentAssignmentError(agent), 400);
362
+ return true;
363
+ }
358
364
  }
359
365
 
360
366
  const targetType = body.targetType ?? "agent-task";
@@ -583,6 +589,10 @@ export async function handleSchedules(
583
589
  jsonError(res, "Target agent not found", 400);
584
590
  return true;
585
591
  }
592
+ if (isExtensionAgent(agent)) {
593
+ jsonError(res, extensionAgentAssignmentError(agent), 400);
594
+ return true;
595
+ }
586
596
  }
587
597
 
588
598
  // Cross-field targetType validation — merge patch over existing, then
@@ -7,6 +7,7 @@ import {
7
7
  countScriptRuns,
8
8
  createScriptRun,
9
9
  createTaskExtended,
10
+ ExtensionAgentAssignmentError,
10
11
  getAgentById,
11
12
  getDbClient,
12
13
  getLatestScriptRunStepTaskByContextKey,
@@ -618,32 +619,40 @@ export async function handleScriptRuns(
618
619
  const contextKey = `script-run:${run.id}:${parsed.body.stepKey}`;
619
620
  let task = await getLatestScriptRunStepTaskByContextKey(contextKey);
620
621
  if (!task) {
621
- task = await createTaskExtended(
622
- parsed.body.template ?? parsed.body.task ?? parsed.body.stepKey,
623
- {
624
- agentId: parsed.body.agentId,
625
- routingReason:
626
- parsed.body.routingReason ?? (parsed.body.agentId ? "human_pinned" : undefined),
627
- routingNote: parsed.body.routingNote,
628
- routingSource: parsed.body.routingReason
629
- ? "declared"
630
- : parsed.body.agentId
631
- ? "engine_default"
632
- : undefined,
633
- tags: parsed.body.tags,
634
- priority: parsed.body.priority,
635
- offeredTo: parsed.body.offerMode ? parsed.body.agentId : undefined,
636
- taskType: "script-run-step",
637
- source: "mcp",
638
- dir: parsed.body.dir,
639
- vcsRepo: parsed.body.vcsRepo,
640
- model: parsed.body.model,
641
- parentTaskId: parsed.body.parentTaskId,
642
- requestedByUserId: parsed.body.requestedByUserId ?? run.requestedByUserId,
643
- outputSchema: parsed.body.outputSchema,
644
- contextKey,
645
- },
646
- );
622
+ try {
623
+ task = await createTaskExtended(
624
+ parsed.body.template ?? parsed.body.task ?? parsed.body.stepKey,
625
+ {
626
+ agentId: parsed.body.agentId,
627
+ routingReason:
628
+ parsed.body.routingReason ?? (parsed.body.agentId ? "human_pinned" : undefined),
629
+ routingNote: parsed.body.routingNote,
630
+ routingSource: parsed.body.routingReason
631
+ ? "declared"
632
+ : parsed.body.agentId
633
+ ? "engine_default"
634
+ : undefined,
635
+ tags: parsed.body.tags,
636
+ priority: parsed.body.priority,
637
+ offeredTo: parsed.body.offerMode ? parsed.body.agentId : undefined,
638
+ taskType: "script-run-step",
639
+ source: "mcp",
640
+ dir: parsed.body.dir,
641
+ vcsRepo: parsed.body.vcsRepo,
642
+ model: parsed.body.model,
643
+ parentTaskId: parsed.body.parentTaskId,
644
+ requestedByUserId: parsed.body.requestedByUserId ?? run.requestedByUserId,
645
+ outputSchema: parsed.body.outputSchema,
646
+ contextKey,
647
+ },
648
+ );
649
+ } catch (error) {
650
+ if (error instanceof ExtensionAgentAssignmentError) {
651
+ jsonError(res, error.message, 400);
652
+ return true;
653
+ }
654
+ throw error;
655
+ }
647
656
  }
648
657
 
649
658
  const deadline = Date.now() + 30_000;