@desplega.ai/agent-swarm 2.0.0 → 2.1.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 (238) hide show
  1. package/dist/{acp-adapter-sj2g0m84.js → acp-adapter-kr4abrsk.js} +10 -10
  2. package/dist/{actions-fegk44gr.js → actions-eh4g6w2r.js} +27 -27
  3. package/dist/{amp-adapter-ad8ft89x.js → amp-adapter-7h7wdt8q.js} +5 -5
  4. package/dist/{app-kfvgbvzy.js → app-7k8s9w5p.js} +9 -9
  5. package/dist/{assistant-z7zdy9ja.js → assistant-wswt2y7p.js} +28 -28
  6. package/dist/{boot-reembed-j8xa181e.js → boot-reembed-9apm9qw6.js} +7 -7
  7. package/dist/{boot-reembed-5c2z0mbe.js → boot-reembed-whzbdthj.js} +7 -7
  8. package/dist/{boot-scrub-sweep-hnkn3rgt.js → boot-scrub-sweep-ndzmvmgt.js} +8 -8
  9. package/dist/{boot-seeds-kwjq4d2z.js → boot-seeds-e9sta0yt.js} +13 -11
  10. package/dist/{claude-adapter-9vmvpvgd.js → claude-adapter-4mqd3r6r.js} +11 -11
  11. package/dist/{claude-managed-adapter-k9qae05j.js → claude-managed-adapter-9wswb52m.js} +3 -3
  12. package/dist/{claude-managed-setup-q54zx5q2.js → claude-managed-setup-tpe1effx.js} +4 -4
  13. package/dist/{claude-sdk-session-9jye6cxr.js → claude-sdk-session-q49ghmh9.js} +10340 -10197
  14. package/dist/{cli-ke896fcf.js → cli-01xvj1rq.js} +10 -10
  15. package/dist/{cli-tr4sxp68.js → cli-035vekcw.js} +2 -2
  16. package/dist/{cli-z5jjpaxw.js → cli-0pd8hzhc.js} +3 -3
  17. package/dist/{cli-n5vwg842.js → cli-0veb92a3.js} +6 -1
  18. package/dist/{cli-wayw93nt.js → cli-10jesr25.js} +3 -3
  19. package/dist/{cli-se8w6wny.js → cli-1y7yywmj.js} +1 -1
  20. package/dist/{cli-v1yb0rqb.js → cli-20krdhz5.js} +47 -19
  21. package/dist/{cli-d5f9ed5c.js → cli-29rja109.js} +39 -20
  22. package/dist/{cli-apx87jne.js → cli-2pwa0q4d.js} +15 -4
  23. package/dist/{cli-ee9gtz70.js → cli-4mengw46.js} +348 -25
  24. package/dist/{cli-03gedhng.js → cli-5twdke5x.js} +6 -6
  25. package/dist/{cli-6razj6t4.js → cli-711vwbxn.js} +2 -2
  26. package/dist/{cli-s2sp957d.js → cli-78vv7446.js} +1 -1
  27. package/dist/{cli-3fbmmawm.js → cli-7n6s5man.js} +4 -4
  28. package/dist/{cli-hnhtk6cj.js → cli-8wwxw8a3.js} +1 -1
  29. package/dist/{cli-hnmhtzvh.js → cli-9jzdj90z.js} +2 -0
  30. package/dist/{cli-skxvvpap.js → cli-cvv9k0hh.js} +28 -25
  31. package/dist/{cli-74ahz61x.js → cli-d9286m9h.js} +2 -2
  32. package/dist/{cli-4jxm2qtz.js → cli-gxzkg1de.js} +28 -16
  33. package/dist/{cli-zkpks3r3.js → cli-h1arqn31.js} +1 -1
  34. package/dist/{cli-r62a35ea.js → cli-hqr88ew6.js} +1 -1
  35. package/dist/{cli-ra0xzggj.js → cli-j67mh2vg.js} +2 -2
  36. package/dist/{cli-29jc5q7w.js → cli-ksphgh48.js} +2 -2
  37. package/dist/{cli-eejsgvm4.js → cli-mgz4nbgg.js} +4 -4
  38. package/dist/{cli-vatyhshy.js → cli-mz5as5ha.js} +4 -4
  39. package/dist/{cli-f7kr7svz.js → cli-n8ndw8j7.js} +2 -2
  40. package/dist/{cli-1e4b6bzh.js → cli-naqzr17j.js} +7 -7
  41. package/dist/{cli-q0ke1yww.js → cli-nm1p8jfr.js} +6 -6
  42. package/dist/{cli-m50t4309.js → cli-p3zbc1rw.js} +5 -5
  43. package/dist/{cli-cgtksawx.js → cli-pe05f8yk.js} +23 -3
  44. package/dist/{cli-vdach5a9.js → cli-qmzsbkvy.js} +4 -4
  45. package/dist/{cli-n3hzg89b.js → cli-qnm4e9e4.js} +57 -579
  46. package/dist/{cli-s4setq91.js → cli-qqy4q48b.js} +3 -3
  47. package/dist/{cli-k382tshb.js → cli-r35rw5de.js} +8 -7
  48. package/dist/{cli-k3dp5tpf.js → cli-r610p9bs.js} +2 -2
  49. package/dist/{cli-e6mpjhed.js → cli-rc6pf5kv.js} +1 -1
  50. package/dist/{cli-3dj8k4vy.js → cli-rdxm77k3.js} +1 -1
  51. package/dist/{cli-tqaazemc.js → cli-s69nfyeh.js} +1 -1
  52. package/dist/{cli-6jyj59nb.js → cli-sawv63e8.js} +2 -2
  53. package/dist/{cli-q50942rs.js → cli-sg19gbf0.js} +1 -1
  54. package/dist/{cli-p1x85d2t.js → cli-shk7r5cs.js} +12 -9
  55. package/dist/{cli-przpmdgr.js → cli-sksetb2q.js} +11 -1
  56. package/dist/{cli-d2qr7aj4.js → cli-t02z59f2.js} +1 -1
  57. package/dist/{cli-j0pfptdp.js → cli-v7g551te.js} +39 -43
  58. package/dist/{cli-bzszjf2p.js → cli-vgqzp4em.js} +519 -126
  59. package/dist/cli-wfnemr1t.js +86 -0
  60. package/dist/{cli-ne236afy.js → cli-wms6x9db.js} +8 -8
  61. package/dist/{cli-5hkm7whv.js → cli-x1r545x8.js} +1 -1
  62. package/dist/{cli-kt6zqg0e.js → cli-xczsgshn.js} +2 -2
  63. package/dist/{cli-ef1hg1wv.js → cli-y3167wt1.js} +430 -238
  64. package/dist/{cli-tsde1hdr.js → cli-yjmqpxt3.js} +12 -11
  65. package/dist/{cli-9nfx44eh.js → cli-z780ec7c.js} +1 -1
  66. package/dist/{cli-r2pnvj8g.js → cli-zm9t2vde.js} +2 -2
  67. package/dist/cli.js +24 -23
  68. package/dist/{codex-adapter-y81sssk0.js → codex-adapter-4z16v9n0.js} +8 -8
  69. package/dist/{codex-login-f2rcfpp9.js → codex-login-pkepy2j1.js} +2 -2
  70. package/dist/{codex-session-runner-8vx4j6a9.js → codex-session-runner-rvms9y2x.js} +8 -8
  71. package/dist/{commands-kcpvpcrr.js → commands-2a9rtyat.js} +8 -8
  72. package/dist/{controller-ywrmt6yd.js → controller-3k3madbn.js} +4 -4
  73. package/dist/{controllers-connect-kwpvj3rf.js → controllers-connect-v9yn8zb9.js} +30 -30
  74. package/dist/{cursor-adapter-cehv1qns.js → cursor-adapter-98gbhyzn.js} +4 -4
  75. package/dist/{db-ww5d7jq3.js → db-eywwsnjw.js} +9 -7
  76. package/dist/{db-k8ankj20.js → db-ncypk9np.js} +9 -9
  77. package/dist/{devin-adapter-x1022qb1.js → devin-adapter-zeq8b9gz.js} +2 -2
  78. package/dist/{dispatcher-e8gq5m68.js → dispatcher-w94kc575.js} +26 -26
  79. package/dist/{docker-ff6y1190.js → docker-b1e4cqab.js} +2 -2
  80. package/dist/{dsh-adapter-3epjm3af.js → dsh-adapter-47ztej36.js} +4 -4
  81. package/dist/{e2b-8ygwhg0f.js → e2b-bbt8ey45.js} +3 -3
  82. package/dist/{e2b-swhnpw3n.js → e2b-dp804sen.js} +3 -3
  83. package/dist/{event-bus-6axnngdj.js → event-bus-j2jdg3qb.js} +2 -2
  84. package/dist/{external-channel-h3359cwn.js → external-channel-zrkg8ar9.js} +8 -8
  85. package/dist/{grok-adapter-0d4x7gkx.js → grok-adapter-s9ncp9vy.js} +11 -11
  86. package/dist/{handlers-an1gzv21.js → handlers-kqftvjgw.js} +29 -29
  87. package/dist/{heartbeat-6dakeb61.js → heartbeat-9q96p8v9.js} +31 -31
  88. package/dist/{hook-6v6cv6d5.js → hook-56qe10dp.js} +7 -7
  89. package/dist/{hook-sa5xye4y.js → hook-yy6rw42k.js} +8 -8
  90. package/dist/{http-r7f3eky0.js → http-r2dnb8f8.js} +963 -75
  91. package/dist/{index-7ep4gzdk.js → index-3gtmkhc6.js} +26 -26
  92. package/dist/{index-v1v6n7hg.js → index-3k5hkz7r.js} +31 -31
  93. package/dist/{index-rmpw9a8q.js → index-5e101240.js} +26 -26
  94. package/dist/{index-s2wxgzr0.js → index-5wyfjc2c.js} +392 -15
  95. package/dist/{index-zk96aq1k.js → index-vxvhxxbn.js} +7 -7
  96. package/dist/{keepalive-be9vwy5h.js → keepalive-psj6j54c.js} +9 -9
  97. package/dist/{lead-0861cwrf.js → lead-hajz4688.js} +43 -43
  98. package/dist/{maintenance-mzzgc8bn.js → maintenance-ps02sb5m.js} +7 -7
  99. package/dist/{node-mn1zs7an.js → node-mwb5r697.js} +1 -1
  100. package/dist/{oauth-refresh-sweep-cpdnfwdj.js → oauth-refresh-sweep-0284h93b.js} +8 -8
  101. package/dist/{onboard-fh8qs4r3.js → onboard-rbf6bg01.js} +2 -2
  102. package/dist/{opencode-adapter-nm42wwj8.js → opencode-adapter-13dcatgq.js} +8 -8
  103. package/dist/{otel-impl-x6c1c9g4.js → otel-impl-zvqcbhp7.js} +2 -2
  104. package/dist/{pi-mono-adapter-9n2ya240.js → pi-mono-adapter-xxy47q4v.js} +7 -7
  105. package/dist/{pricing-refresh-qb0rv0ap.js → pricing-refresh-kefx9v3t.js} +9 -9
  106. package/dist/{rbac-roles-1gzdwbh1.js → rbac-roles-3m36rxm2.js} +8 -8
  107. package/dist/{rbac-roles-3mraey52.js → rbac-roles-z76xv0hq.js} +9 -9
  108. package/dist/{render-v2-agpa45zz.js → render-v2-wz73bs25.js} +9 -9
  109. package/dist/{resume-x0ydy9s9.js → resume-k4wzge8w.js} +14 -14
  110. package/dist/{setup-as9ga0d6.js → setup-2fp2pypp.js} +2 -2
  111. package/dist/{work-objects-y5g1y7ev.js → work-objects-6k0ywz3m.js} +29 -29
  112. package/dist/{worker-860c3e38.js → worker-5v3rxsdh.js} +43 -43
  113. package/dist/{x-m477fr0h.js → x-4mtdeym1.js} +2 -2
  114. package/openapi.json +777 -6
  115. package/package.json +8 -7
  116. package/src/agentmail/handlers.ts +21 -11
  117. package/src/apps/definition.ts +16 -0
  118. package/src/azure-devops/api.ts +52 -2
  119. package/src/azure-devops/boards-sync.ts +50 -20
  120. package/src/azure-devops/index.ts +1 -0
  121. package/src/azure-devops/outbound.ts +95 -0
  122. package/src/be/boot-seeds.ts +11 -0
  123. package/src/be/db/model-profiles.ts +315 -0
  124. package/src/be/db/tasks/read.ts +7 -0
  125. package/src/be/db.ts +53 -47
  126. package/src/be/migrations/219_relabel_pi_context_compactions.sql +22 -0
  127. package/src/be/migrations/220_model_profiles.sql +137 -0
  128. package/src/be/model-tier-resolution.ts +145 -11
  129. package/src/be/model-validation.ts +8 -1
  130. package/src/be/scripts/typecheck.ts +12 -0
  131. package/src/be/secret-registry.ts +6 -2
  132. package/src/be/seed-model-profiles.ts +41 -0
  133. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +368 -0
  134. package/src/be/seed-scripts/index.ts +9 -0
  135. package/src/be/swarm-config-guard.ts +1 -0
  136. package/src/commands/runner.ts +3 -17
  137. package/src/extensions/catalog.generated.json +17 -0
  138. package/src/extensions/contract-types.generated.ts +1 -1
  139. package/src/extensions/loader.ts +10 -5
  140. package/src/heartbeat/heartbeat.ts +2 -1
  141. package/src/http/agents.ts +49 -2
  142. package/src/http/all-routes.ts +1 -0
  143. package/src/http/apps.ts +30 -3
  144. package/src/http/config.ts +26 -2
  145. package/src/http/core.ts +4 -0
  146. package/src/http/index.ts +11 -1
  147. package/src/http/model-profiles.ts +303 -0
  148. package/src/http/pages-public.ts +11 -5
  149. package/src/http/poll.ts +16 -5
  150. package/src/http/script-runs.ts +1 -1
  151. package/src/http/scripts.ts +25 -2
  152. package/src/http/tasks.ts +18 -0
  153. package/src/http/webhooks.ts +1 -2
  154. package/src/pages/feedback-overlay.ts +65 -6
  155. package/src/providers/acp-targets.ts +1 -1
  156. package/src/providers/amp-adapter.ts +1 -1
  157. package/src/providers/dsh-adapter.ts +1 -1
  158. package/src/providers/pi-mono-extension.ts +17 -3
  159. package/src/providers/reasoning-effort.ts +10 -0
  160. package/src/rbac/legacy-policy.ts +8 -0
  161. package/src/rbac/permissions.ts +5 -0
  162. package/src/scheduler/deferred-task-waits.ts +18 -5
  163. package/src/scheduler/scheduler.ts +7 -1
  164. package/src/scripts-runtime/executors/quickjs-prelude.ts +75 -6
  165. package/src/scripts-runtime/executors/quickjs-runner.ts +85 -8
  166. package/src/scripts-runtime/executors/quickjs-web-shims.ts +1041 -0
  167. package/src/scripts-runtime/extract-args-schema.ts +9 -8
  168. package/src/scripts-runtime/quickjs-compat.ts +464 -0
  169. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  170. package/src/scripts-runtime/types/stdlib.d.ts +12 -0
  171. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  172. package/src/scripts-runtime/types/swarm-sdk.d.ts +12 -0
  173. package/src/server.ts +10 -1
  174. package/src/slack/blocks.ts +4 -2
  175. package/src/slack/reaction-shortcode.ts +14 -0
  176. package/src/slack/render-v2.ts +12 -7
  177. package/src/slack/responses.ts +5 -4
  178. package/src/slack/thread-buffer.ts +41 -22
  179. package/src/tasks/worker-follow-up.ts +11 -2
  180. package/src/tests/agentmail-filters.test.ts +47 -0
  181. package/src/tests/apps-spike2.test.ts +35 -0
  182. package/src/tests/azure-devops-outbound.test.ts +420 -0
  183. package/src/tests/db-task-read-contract.test.ts +2 -0
  184. package/src/tests/db-task-write-contract.test.ts +1 -0
  185. package/src/tests/deferred-task-wake.test.ts +78 -1
  186. package/src/tests/docs-index-footprint.test.js +2 -2
  187. package/src/tests/dsh-adapter.test.ts +1 -1
  188. package/src/tests/env-flag.test.ts +1 -0
  189. package/src/tests/extensions-catalog.test.ts +1 -0
  190. package/src/tests/extensions-example-comms-adherence-gate.test.ts +413 -0
  191. package/src/tests/extensions-loader.test.ts +15 -0
  192. package/src/tests/heartbeat-jev-gate-credentials.test.ts +168 -0
  193. package/src/tests/heartbeat-jev-gate.test.ts +385 -0
  194. package/src/tests/model-profile-backcompat.test.ts +327 -0
  195. package/src/tests/model-profile-providers.test.ts +302 -0
  196. package/src/tests/model-profile-runtime-secrets.test.ts +223 -0
  197. package/src/tests/model-profiles-api.test.ts +614 -0
  198. package/src/tests/pages-feedback-keys.test.ts +121 -0
  199. package/src/tests/pages-feedback.test.ts +3 -0
  200. package/src/tests/pi-context-compaction.test.ts +193 -0
  201. package/src/tests/profile-claim.test.ts +269 -0
  202. package/src/tests/quickjs-compat.test.ts +213 -0
  203. package/src/tests/rbac-engine.test.ts +21 -0
  204. package/src/tests/script-connections.test.ts +4 -0
  205. package/src/tests/script-executor-conformance.test.ts +252 -1
  206. package/src/tests/script-executor-quickjs.test.ts +96 -0
  207. package/src/tests/script-runs-http.test.ts +89 -0
  208. package/src/tests/scripts-http.test.ts +16 -0
  209. package/src/tests/secret-scrubber.test.ts +10 -0
  210. package/src/tests/seed-model-profiles.test.ts +174 -0
  211. package/src/tests/seed-scripts.test.ts +2 -2
  212. package/src/tests/skill-invocations.test.ts +4 -2
  213. package/src/tests/slack-blocks.test.ts +36 -0
  214. package/src/tests/slack-inline-output.test.ts +14 -0
  215. package/src/tests/slack-reaction-config.test.ts +20 -0
  216. package/src/tests/slack-render-v2.test.ts +37 -0
  217. package/src/tests/slack-thread-buffer.test.ts +98 -1
  218. package/src/tests/workflow-llm.test.ts +61 -0
  219. package/src/tools/model-profiles/list-model-profiles.ts +54 -0
  220. package/src/tools/request-human-input.ts +11 -3
  221. package/src/tools/script-apis.ts +5 -1
  222. package/src/tools/script-upsert.ts +4 -1
  223. package/src/tools/send-task.ts +24 -1
  224. package/src/tools/system-one-decide.ts +104 -0
  225. package/src/tools/task-action.ts +22 -1
  226. package/src/tools/tool-config.ts +6 -0
  227. package/src/types.ts +36 -1
  228. package/src/utils/secret-scrubber.ts +19 -12
  229. package/src/utils/vcs-repo-match.ts +29 -0
  230. package/src/workflows/executors/raw-llm.ts +8 -3
  231. package/src/workflows/executors/system-one-decision.ts +21 -11
  232. package/src/workflows/executors/workflow-llm.ts +22 -0
  233. package/src/workflows/index.ts +21 -12
  234. package/templates/extensions/README.md +1 -0
  235. package/templates/extensions/comms-adherence-gate/README.md +88 -0
  236. package/templates/extensions/comms-adherence-gate/hooks.ts +489 -0
  237. package/templates/extensions/comms-adherence-gate/manifest.json +8 -0
  238. package/dist/cli-5t4f29ez.js +0 -33
@@ -1,5 +1,5 @@
1
1
  import { tmpdir } from "node:os";
2
- import { resolve } from "node:path";
2
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import type { z } from "zod";
5
5
  import { insertExtensionRun, setExtensionState } from "../be/extensions/db";
@@ -122,12 +122,18 @@ export async function removeExtensionTmpRoot(): Promise<void> {
122
122
 
123
123
  export async function loadExtension(input: LoadableExtension): Promise<LoadedExtension> {
124
124
  const { record } = input;
125
- const directory = `${EXTENSIONS_TMP_ROOT}/${record.name}/${record.contentHash}`;
125
+ const directory = resolve(EXTENSIONS_TMP_ROOT, record.name, record.contentHash);
126
126
 
127
127
  try {
128
128
  const bundlePath = (path: string): string => {
129
129
  const filePath = resolve(directory, path);
130
- if (!isSafeBundlePath(path) || !filePath.startsWith(`${directory}/`)) {
130
+ const relativePath = relative(directory, filePath);
131
+ if (
132
+ !isSafeBundlePath(path) ||
133
+ relativePath === ".." ||
134
+ relativePath.startsWith(`..${sep}`) ||
135
+ isAbsolute(relativePath)
136
+ ) {
131
137
  throw new Error(`Unsafe bundle file path: ${JSON.stringify(path)}`);
132
138
  }
133
139
  return filePath;
@@ -138,8 +144,7 @@ export async function loadExtension(input: LoadableExtension): Promise<LoadedExt
138
144
  );
139
145
  await Bun.$`mkdir -p ${directory}`.quiet();
140
146
  for (const [filePath, content] of files) {
141
- const parent = filePath.slice(0, filePath.lastIndexOf("/"));
142
- await Bun.$`mkdir -p ${parent}`.quiet();
147
+ await Bun.$`mkdir -p ${dirname(filePath)}`.quiet();
143
148
  await Bun.write(filePath, content);
144
149
  }
145
150
  await writeRuntimeShims(directory);
@@ -43,6 +43,7 @@ import {
43
43
  releaseStaleOfferedTasksForOfflineAgents,
44
44
  releaseStaleProcessingInbox,
45
45
  releaseStaleReviewingTasks,
46
+ SUPERSEDED_RESUME_REPAIR_WINDOW_MS,
46
47
  settleSupersededTaskDependents,
47
48
  supersedeTask,
48
49
  updateAgentStatus,
@@ -588,7 +589,7 @@ async function detectAndRemediateStalledTasks(findings: HeartbeatFindings): Prom
588
589
 
589
590
  /** Repair window: skip in-flight supersedes, stop at a day of history. */
590
591
  const ORPHAN_SUPERSEDE_MIN_AGE_MS = 60 * 1000;
591
- const ORPHAN_SUPERSEDE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
592
+ const ORPHAN_SUPERSEDE_MAX_AGE_MS = SUPERSEDED_RESUME_REPAIR_WINDOW_MS;
592
593
 
593
594
  /**
594
595
  * Supersede and resume creation are separate writes. If the API process dies
@@ -28,6 +28,11 @@ import {
28
28
  updateAgentStatus,
29
29
  upsertSwarmConfig,
30
30
  } from "../be/db";
31
+ import {
32
+ getModelProfileByName,
33
+ MODEL_PROFILE_CONFIG_KEY,
34
+ modelProfileRefError,
35
+ } from "../be/db/model-profiles";
31
36
  import { createEvent } from "../be/events";
32
37
  import { setAgentHarnessCliVersion } from "../be/harness-model-support";
33
38
  import {
@@ -278,7 +283,7 @@ const updateAgentRuntimeRoute = route({
278
283
  pattern: ["api", "agents", null, "runtime"],
279
284
  summary: "Update an agent's runtime harness and default model",
280
285
  description:
281
- "Updates `agents.harness_provider` and agent-scoped runtime config. The settings apply to future provider sessions. For `model`, `reasoning_effort`, and `claude.transport`: omit the field to leave it unchanged, send `null` to clear the corresponding override, or send a value to set it. A `model` the model catalog does not list is a 400 unless `allow_custom_model` is true. A `latest:` alias is not accepted here (aliases resolve for a task's model and for `MODEL_TIER_*` values); `reasoning_effort` must be one the harness and model support.",
286
+ "Updates `agents.harness_provider` and agent-scoped runtime config. The settings apply to future provider sessions. For `model`, `reasoning_effort`, and `claude.transport`: omit the field to leave it unchanged, send `null` to clear the corresponding override, or send a value to set it. A `model` the model catalog does not list is a 400 unless `allow_custom_model` is true. A `latest:` alias is not accepted here (aliases resolve for a task's model and for `MODEL_TIER_*` values); `reasoning_effort` must be one the harness and model support. `profile` sets the agent's default model profile (same tri-state): it must exist and run on `harness_provider`, and it cannot be sent with `model`. Sending `model`, or changing the model or effort override by any path, clears the default profile.",
282
287
  tags: ["Agents"],
283
288
  params: z.object({ id: z.string() }),
284
289
  query: z.object({ repoId: z.string().optional() }),
@@ -287,6 +292,7 @@ const updateAgentRuntimeRoute = route({
287
292
  model: z.string().trim().min(1).nullable().optional(),
288
293
  allow_custom_model: z.boolean().optional().default(false),
289
294
  reasoning_effort: ReasoningEffortSchema.nullable().optional(),
295
+ profile: z.string().trim().min(1).nullable().optional(),
290
296
  acp: AcpRuntimeConfigSchema.optional(),
291
297
  claude: ClaudeRuntimeConfigSchema.optional(),
292
298
  }),
@@ -1129,9 +1135,29 @@ export async function handleAgentsRest(
1129
1135
  if (updateAgentRuntimeRoute.match(req.method, pathSegments)) {
1130
1136
  const parsed = await updateAgentRuntimeRoute.parse(req, res, pathSegments, queryParams);
1131
1137
  if (!parsed) return true;
1132
- const { harness_provider, model, allow_custom_model, reasoning_effort, acp, claude } =
1138
+ const { harness_provider, model, allow_custom_model, reasoning_effort, profile, acp, claude } =
1133
1139
  parsed.body;
1134
1140
 
1141
+ if (typeof profile === "string") {
1142
+ if (model !== undefined) {
1143
+ jsonError(res, "Send `profile` or `model`, not both", 400);
1144
+ return true;
1145
+ }
1146
+ const picked = await getModelProfileByName(profile);
1147
+ if (!picked) {
1148
+ jsonError(res, `Unknown model profile "${profile}"`, 400);
1149
+ return true;
1150
+ }
1151
+ if (picked.harness !== harness_provider) {
1152
+ jsonError(
1153
+ res,
1154
+ `Model profile "${profile}" runs on the ${picked.harness} harness, not ${harness_provider}`,
1155
+ 400,
1156
+ );
1157
+ return true;
1158
+ }
1159
+ }
1160
+
1135
1161
  if (acp && harness_provider !== "acp") {
1136
1162
  jsonError(res, "ACP configuration requires harness_provider=acp", 400);
1137
1163
  return true;
@@ -1250,6 +1276,22 @@ export async function handleAgentsRest(
1250
1276
  });
1251
1277
  }
1252
1278
 
1279
+ // Written after the overrides above: changing either of them clears
1280
+ // MODEL_PROFILE, so a stale default never beats a fresh override.
1281
+ if (model !== undefined || profile === null) {
1282
+ await deleteSwarmConfigByKey("agent", parsed.params.id, MODEL_PROFILE_CONFIG_KEY);
1283
+ } else if (profile !== undefined) {
1284
+ await upsertSwarmConfig({
1285
+ scope: "agent",
1286
+ scopeId: parsed.params.id,
1287
+ key: MODEL_PROFILE_CONFIG_KEY,
1288
+ value: profile,
1289
+ description: "Default model profile, set via PATCH /api/agents/{id}/runtime",
1290
+ // Read by the API at claim only; workers never need it in their env.
1291
+ exportToEnv: false,
1292
+ });
1293
+ }
1294
+
1253
1295
  if (claude?.transport === null) {
1254
1296
  await deleteSwarmConfigByKey("agent", parsed.params.id, "CLAUDE_TRANSPORT");
1255
1297
  } else if (claude?.transport !== undefined) {
@@ -1341,6 +1383,11 @@ export async function handleAgentsRest(
1341
1383
  jsonError(res, error.message, 400);
1342
1384
  return true;
1343
1385
  }
1386
+ // The profile was deleted between the check above and this write.
1387
+ if (modelProfileRefError(error) === "missing") {
1388
+ jsonError(res, `Unknown model profile "${profile}"`, 400);
1389
+ return true;
1390
+ }
1344
1391
  throw error;
1345
1392
  }
1346
1393
 
@@ -33,6 +33,7 @@ import "./integrations";
33
33
  import "./kv";
34
34
  import "./memory";
35
35
  import "./metrics";
36
+ import "./model-profiles";
36
37
  import "./models-catalog";
37
38
  import "./oauth-locks";
38
39
  import "./oauth-callback";
package/src/http/apps.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  parseAppDefinition,
13
13
  SORTABLE_SYSTEM_DATE_COLUMNS,
14
14
  SYSTEM_COLUMN_KINDS,
15
+ scriptCompatWarnings,
15
16
  } from "../apps/definition";
16
17
  import {
17
18
  type AppRow,
@@ -227,9 +228,14 @@ const AppUserConfigFieldSchema = z.object({
227
228
  /** Mirrors an app definition's `userConfig` declaration map. */
228
229
  const AppUserConfigSchema = z.record(AppNameSchema, AppUserConfigFieldSchema);
229
230
 
231
+ const compatWarningsSchema = z
232
+ .array(z.string())
233
+ .optional()
234
+ .describe("Advisory quickjs-executor compatibility findings for the app's scripts.");
230
235
  const appWriteResponseSchema = z.object({
231
236
  app: AppRecordSchema,
232
237
  migration: AppMigrationReportSchema,
238
+ warnings: compatWarningsSchema,
233
239
  });
234
240
  const userConfigBodySchema = z.object({ values: z.record(z.string(), z.unknown()) }).strict();
235
241
 
@@ -307,7 +313,10 @@ const createAppRoute = route({
307
313
  forceElementBreak: ForceElementBreakSchema.optional(),
308
314
  }),
309
315
  responses: {
310
- 201: { description: "Created app", schema: z.object({ app: AppRecordSchema }) },
316
+ 201: {
317
+ description: "Created app",
318
+ schema: z.object({ app: AppRecordSchema, warnings: compatWarningsSchema }),
319
+ },
311
320
  400: { description: "Invalid app definition" },
312
321
  403: { description: "Permission denied" },
313
322
  },
@@ -1020,12 +1029,14 @@ export async function handleApps(
1020
1029
  invalidDefinition(res, definition.issues);
1021
1030
  return true;
1022
1031
  }
1032
+ const warnings = await scriptCompatWarnings(definition.definition);
1023
1033
  createAppRoute.respond(res, 201, {
1024
1034
  app: await createApp({
1025
1035
  name: parsed.body.name,
1026
1036
  description: parsed.body.description,
1027
1037
  definition: definition.definition,
1028
1038
  }),
1039
+ ...(warnings.length > 0 ? { warnings } : {}),
1029
1040
  });
1030
1041
  return true;
1031
1042
  }
@@ -1482,6 +1493,7 @@ export async function handleApps(
1482
1493
  }
1483
1494
  let app: Awaited<ReturnType<typeof updateApp>> = null;
1484
1495
  let migration!: AppMigrationReport;
1496
+ let writtenDefinition: unknown;
1485
1497
  let responseHandled = false;
1486
1498
  try {
1487
1499
  await withAppDefinitionLock(parsed.params.id, async () => {
@@ -1513,6 +1525,7 @@ export async function handleApps(
1513
1525
  responseHandled = true;
1514
1526
  return;
1515
1527
  }
1528
+ if (parsed.body.definition !== undefined) writtenDefinition = definition.definition;
1516
1529
  const migrated = await migrateAppSchema({
1517
1530
  appId: parsed.params.id,
1518
1531
  previousDefinition: existing.definition,
@@ -1553,7 +1566,13 @@ export async function handleApps(
1553
1566
  jsonError(res, "app not found", 404);
1554
1567
  return true;
1555
1568
  }
1556
- patchAppRoute.respond(res, 200, { app, migration });
1569
+ const warnings =
1570
+ writtenDefinition === undefined ? [] : await scriptCompatWarnings(writtenDefinition);
1571
+ patchAppRoute.respond(res, 200, {
1572
+ app,
1573
+ migration,
1574
+ ...(warnings.length > 0 ? { warnings } : {}),
1575
+ });
1557
1576
  return true;
1558
1577
  }
1559
1578
 
@@ -1568,6 +1587,7 @@ export async function handleApps(
1568
1587
  }
1569
1588
  let app: Awaited<ReturnType<typeof updateApp>> = null;
1570
1589
  let migration!: AppMigrationReport;
1590
+ let writtenDefinition: unknown;
1571
1591
  let responseHandled = false;
1572
1592
  try {
1573
1593
  await withAppDefinitionLock(parsed.params.id, async () => {
@@ -1597,6 +1617,7 @@ export async function handleApps(
1597
1617
  return;
1598
1618
  }
1599
1619
  const nextDefinition = definition ?? existing.definition;
1620
+ writtenDefinition = definition;
1600
1621
  const migrated = await migrateAppSchema({
1601
1622
  appId: parsed.params.id,
1602
1623
  previousDefinition: appDefinitionNeedsRepair(existing) ? undefined : existing.definition,
@@ -1637,7 +1658,13 @@ export async function handleApps(
1637
1658
  jsonError(res, "app not found", 404);
1638
1659
  return true;
1639
1660
  }
1640
- updateAppRoute.respond(res, 200, { app, migration });
1661
+ const warnings =
1662
+ writtenDefinition === undefined ? [] : await scriptCompatWarnings(writtenDefinition);
1663
+ updateAppRoute.respond(res, 200, {
1664
+ app,
1665
+ migration,
1666
+ ...(warnings.length > 0 ? { warnings } : {}),
1667
+ });
1641
1668
  return true;
1642
1669
  }
1643
1670
 
@@ -9,6 +9,7 @@ import {
9
9
  getSwarmConfigs,
10
10
  maskSecrets,
11
11
  } from "../be/db";
12
+ import { modelProfileRefError } from "../be/db/model-profiles";
12
13
  import {
13
14
  AGENT_MAX_TASKS_CONFIG_KEY,
14
15
  resetAgentMaxTasksMirror,
@@ -27,7 +28,7 @@ import {
27
28
  } from "../be/swarm-config-guard";
28
29
  import { can, isRbacEnabled, type PermissionVerb } from "../rbac";
29
30
  import { SwarmConfigSchema } from "../types";
30
- import { getRequestAuth } from "../utils/request-auth-context";
31
+ import { getRequestAuth, type HttpRequestAuth } from "../utils/request-auth-context";
31
32
  import { registerVolatileSecret } from "../utils/secret-scrubber";
32
33
  import { reloadGlobalConfigsAndIntegrations, scheduleIntegrationsReload } from "./core";
33
34
  import { route } from "./route-def";
@@ -48,6 +49,25 @@ function singleHeader(req: IncomingMessage, name: string): string | undefined {
48
49
  return Array.isArray(raw) ? raw[0] : raw;
49
50
  }
50
51
 
52
+ /**
53
+ * Whether this caller's `/api/config/resolved` carries secret repo rows (the
54
+ * `includeRepoSecrets` of {@link resolveSecretsRead}): a runtime-token agent
55
+ * gets them only with `config.read.secrets`. Claim uses it so an agent's
56
+ * default profile yields only to repo rows that worker's env really gets.
57
+ */
58
+ export function readsRepoSecrets(
59
+ auth: HttpRequestAuth | null,
60
+ agent: { isLead?: boolean } | null | undefined,
61
+ ): boolean {
62
+ if (auth?.kind !== "agent" || !auth.runtimeId) return true;
63
+ return can({
64
+ principal: { kind: "agent", agentId: auth.agentId, isLead: agent?.isLead ?? false },
65
+ verb: "config.read.secrets",
66
+ resource: { kind: "none" },
67
+ source: "http",
68
+ }).allow;
69
+ }
70
+
51
71
  async function requestSecretReadScope(
52
72
  req: IncomingMessage,
53
73
  resolvedScope?: { agentId?: string; repoId?: string },
@@ -512,7 +532,11 @@ export async function handleConfig(
512
532
  }
513
533
  const result = includeSecrets || !config.isSecret ? config : maskSecrets([config])[0]!;
514
534
  upsertConfig.respond(res, 200, result);
515
- } catch (_error) {
535
+ } catch (error) {
536
+ if (modelProfileRefError(error) === "missing") {
537
+ jsonError(res, `Unknown model profile "${String(value)}"`, 400);
538
+ return true;
539
+ }
516
540
  jsonError(res, "Failed to upsert config", 500);
517
541
  }
518
542
  return true;
package/src/http/core.ts CHANGED
@@ -3,9 +3,11 @@ import { z } from "zod";
3
3
  import { initAgentMail, resetAgentMail } from "../agentmail";
4
4
  import {
5
5
  initAzureDevOps,
6
+ initAzureDevOpsOutbound,
6
7
  isAzureDevOpsEnabled,
7
8
  resetAzureDevOps,
8
9
  resetAzureDevOpsBotIdCache,
10
+ teardownAzureDevOpsOutbound,
9
11
  } from "../azure-devops";
10
12
  import {
11
13
  getAgentById,
@@ -235,6 +237,8 @@ async function reloadGlobalConfigsAndIntegrationsInner(): Promise<ReloadConfigRe
235
237
  resetAzureDevOpsBotIdCache();
236
238
  initAzureDevOps();
237
239
  if (isAzureDevOpsEnabled()) integrations.push("azure-devops");
240
+ teardownAzureDevOpsOutbound();
241
+ if (isAzureDevOpsEnabled()) initAzureDevOpsOutbound();
238
242
 
239
243
  resetLinear();
240
244
  if (await initLinear()) integrations.push("linear");
package/src/http/index.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8
8
  import { getEnabledCapabilities, hasCapability } from "@/server";
9
9
  import { initAgentMail } from "../agentmail";
10
- import { initAzureDevOps } from "../azure-devops";
10
+ import { initAzureDevOps, initAzureDevOpsOutbound } from "../azure-devops";
11
11
  import { drainApi, isApiDraining } from "../be/api-drain";
12
12
  import {
13
13
  closeDb,
@@ -104,6 +104,7 @@ import { handleMcpServers } from "./mcp-servers";
104
104
  import { closeIdleMcpUserTransports, handleMcpUser } from "./mcp-user";
105
105
  import { handleMemory, startMemoryGc, stopMemoryGc } from "./memory";
106
106
  import { handleMetrics } from "./metrics";
107
+ import { handleModelProfiles } from "./model-profiles";
107
108
  import { handleModelsCatalog } from "./models-catalog";
108
109
  import { handleOAuthCallback, startOAuthPendingGc, stopOAuthPendingGc } from "./oauth-callback";
109
110
  import { handleGenericOAuth } from "./oauth-generic";
@@ -376,6 +377,7 @@ const httpServer = createHttpServer(async (req, res) => {
376
377
  () => handleDbQuery(req, res, pathSegments, queryParams),
377
378
  () => handleMetrics(req, res, pathSegments, queryParams, myAgentId),
378
379
  () => handleModelsCatalog(req, res, pathSegments, queryParams),
380
+ () => handleModelProfiles(req, res, pathSegments, queryParams, myAgentId),
379
381
  () => handleRepos(req, res, pathSegments, queryParams, myAgentId),
380
382
  () => handleSkills(req, res, pathSegments, queryParams, myAgentId),
381
383
  () => handleScriptConnections(req, res, pathSegments, queryParams, myAgentId),
@@ -665,6 +667,13 @@ try {
665
667
  console.error("[startup] Failed to seed pricing rows:", err);
666
668
  }
667
669
 
670
+ try {
671
+ const { ensureModelProfilesSeeded } = await import("../be/boot-seeds");
672
+ await ensureModelProfilesSeeded();
673
+ } catch (err) {
674
+ console.error("[startup] Failed to seed model profiles:", err);
675
+ }
676
+
668
677
  try {
669
678
  const { ensureRbacSeeded } = await import("../be/boot-seeds");
670
679
  ensureRbacSeeded();
@@ -785,6 +794,7 @@ httpServer
785
794
 
786
795
  // Initialize Azure DevOps service-hook handler (if configured)
787
796
  initAzureDevOps();
797
+ initAzureDevOpsOutbound();
788
798
 
789
799
  // Initialize AgentMail webhook handler (if configured)
790
800
  initAgentMail();