@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
@@ -19,12 +19,13 @@ import {
19
19
  MAX_RATE_LIMIT_RESET_MS
20
20
  } from "./cli-2pdaysab.js";
21
21
  import {
22
- reasoningCapability
23
- } from "./cli-n5vwg842.js";
22
+ reasoningCapability,
23
+ supportedReasoningLevels
24
+ } from "./cli-0veb92a3.js";
24
25
  import {
25
26
  ensureAgentFsCredentialsForAgent,
26
27
  inviteEmailToSharedOrg
27
- } from "./cli-r2pnvj8g.js";
28
+ } from "./cli-zm9t2vde.js";
28
29
  import"./cli-6zx22mv6.js";
29
30
  import {
30
31
  DEFAULT_CONNECTOR_CODE_LABEL,
@@ -35,16 +36,16 @@ import {
35
36
  startPoolRouting,
36
37
  stopHeartbeat,
37
38
  stopPoolRouting
38
- } from "./cli-tsde1hdr.js";
39
+ } from "./cli-yjmqpxt3.js";
39
40
  import {
40
41
  Anthropic
41
- } from "./cli-4jxm2qtz.js";
42
+ } from "./cli-gxzkg1de.js";
42
43
  import {
43
44
  runtimeSupervisorStatus,
44
45
  startRuntimeSupervisor,
45
46
  stopRuntimeSupervisor
46
- } from "./cli-ke896fcf.js";
47
- import"./cli-f7kr7svz.js";
47
+ } from "./cli-01xvj1rq.js";
48
+ import"./cli-n8ndw8j7.js";
48
49
  import"./cli-knd276ed.js";
49
50
  import {
50
51
  AGENT_MAX_TASKS_CONFIG_KEY,
@@ -202,6 +203,7 @@ import {
202
203
  initAgentMail,
203
204
  initApiTelemetry,
204
205
  initAzureDevOps,
206
+ initAzureDevOpsOutbound,
205
207
  initGitHub,
206
208
  initGitLab,
207
209
  initJira,
@@ -344,13 +346,13 @@ import {
344
346
  wireHttpSpanLifecycle,
345
347
  workflowModelErrors,
346
348
  writeUnhandledError
347
- } from "./cli-bzszjf2p.js";
348
- import"./cli-zkpks3r3.js";
349
+ } from "./cli-vgqzp4em.js";
350
+ import"./cli-h1arqn31.js";
349
351
  import {
350
352
  AMP_ESTIMATE_MODELS,
351
353
  ampBillsCacheWrites,
352
354
  resolveAmpModel
353
- } from "./cli-hnhtk6cj.js";
355
+ } from "./cli-8wwxw8a3.js";
354
356
  import {
355
357
  RATING_MODEL_MAX_LENGTH,
356
358
  REFERENCES_SOURCE_MAX_LENGTH,
@@ -364,7 +366,7 @@ import {
364
366
  loadModelsCatalog,
365
367
  requestModelCatalogRefresh,
366
368
  upsertModelCatalogOverlay
367
- } from "./cli-z5jjpaxw.js";
369
+ } from "./cli-0pd8hzhc.js";
368
370
  import {
369
371
  ConnectionAuthInputSchema,
370
372
  MAX_SCRIPT_WALL_CLOCK_MS,
@@ -381,6 +383,7 @@ import {
381
383
  buildScriptCredentialBindingsWithFailures,
382
384
  callMcpServerTool,
383
385
  checkImportAllowlist,
386
+ checkQuickJSCompat,
384
387
  collectScriptReferences,
385
388
  computeExpiresAt,
386
389
  connectionAuthInputFromFlat,
@@ -421,6 +424,7 @@ import {
421
424
  oauthAppToProviderConfig,
422
425
  parseSkillContent,
423
426
  publicEndpointSsrfOptions,
427
+ quickJSCompatWarnings,
424
428
  readStreamCapped,
425
429
  refreshMcpToken,
426
430
  refreshScriptConnection,
@@ -443,7 +447,7 @@ import {
443
447
  upsertCredentialBinding,
444
448
  upsertMcpOAuthToken,
445
449
  upsertScriptConnection
446
- } from "./cli-ee9gtz70.js";
450
+ } from "./cli-4mengw46.js";
447
451
  import"./cli-epan13p1.js";
448
452
  import {
449
453
  detachedProcessGroup,
@@ -458,11 +462,11 @@ import {
458
462
  forceRefreshAuthorizationOrThrow,
459
463
  forceRefreshTokenOrThrow,
460
464
  oauthAppRowToProviderConfig
461
- } from "./cli-6razj6t4.js";
462
- import"./cli-5t4f29ez.js";
465
+ } from "./cli-711vwbxn.js";
466
+ import"./cli-wfnemr1t.js";
463
467
  import {
464
468
  normalizeModelKey
465
- } from "./cli-r62a35ea.js";
469
+ } from "./cli-hqr88ew6.js";
466
470
  import"./cli-6cvfxs8r.js";
467
471
  import {
468
472
  AgentFsProvider,
@@ -482,14 +486,14 @@ import {
482
486
  startWorkflowExecution,
483
487
  validateJsonSchema,
484
488
  workflowSaveWarnings
485
- } from "./cli-1e4b6bzh.js";
489
+ } from "./cli-naqzr17j.js";
486
490
  import {
487
491
  createApprovalFollowUpTask,
488
492
  definitionNodeIds,
489
493
  generateEdges,
490
494
  validateDefinition
491
- } from "./cli-v1yb0rqb.js";
492
- import"./cli-se8w6wny.js";
495
+ } from "./cli-20krdhz5.js";
496
+ import"./cli-1y7yywmj.js";
493
497
  import {
494
498
  CLIENT_ID,
495
499
  MAX_CODEX_OAUTH_SLOT,
@@ -500,7 +504,7 @@ import {
500
504
  getAccountId,
501
505
  getValidCodexOAuth,
502
506
  loadAllCodexOAuthSlots
503
- } from "./cli-e6mpjhed.js";
507
+ } from "./cli-rc6pf5kv.js";
504
508
  import"./cli-2x0e561b.js";
505
509
  import"./cli-txqb9s38.js";
506
510
  import"./cli-h6vtvt5c.js";
@@ -529,7 +533,7 @@ import {
529
533
  withRemoteContext,
530
534
  withSpan,
531
535
  withSpanContext
532
- } from "./cli-skxvvpap.js";
536
+ } from "./cli-cvv9k0hh.js";
533
537
  import"./cli-n2wq4j7m.js";
534
538
  import {
535
539
  require_content_type
@@ -538,7 +542,7 @@ import"./cli-3er6gcsn.js";
538
542
  import"./cli-hjwmhcg1.js";
539
543
  import"./cli-rhs9pz33.js";
540
544
  import"./cli-9ha6hs2d.js";
541
- import"./cli-k3dp5tpf.js";
545
+ import"./cli-r610p9bs.js";
542
546
  import {
543
547
  InvalidRuntimeTokenError,
544
548
  RuntimePoolError,
@@ -577,8 +581,8 @@ import {
577
581
  unlinkIdentity,
578
582
  updateRuntimeController,
579
583
  updateRuntimePool
580
- } from "./cli-eejsgvm4.js";
581
- import"./cli-ra0xzggj.js";
584
+ } from "./cli-mgz4nbgg.js";
585
+ import"./cli-j67mh2vg.js";
582
586
  import {
583
587
  bedrockProviderModule
584
588
  } from "./cli-hvqbtna4.js";
@@ -605,7 +609,7 @@ import"./cli-pzxba7gw.js";
605
609
  import {
606
610
  getUserGrant,
607
611
  init_rbac_roles
608
- } from "./cli-74ahz61x.js";
612
+ } from "./cli-d9286m9h.js";
609
613
  import {
610
614
  LEGACY_RULES,
611
615
  can,
@@ -614,7 +618,7 @@ import {
614
618
  init_rbac,
615
619
  isRbacEnabled,
616
620
  setAuditSink
617
- } from "./cli-przpmdgr.js";
621
+ } from "./cli-sksetb2q.js";
618
622
  import {
619
623
  API_DRAINING_HEADER,
620
624
  CANDIDATE_SET_MULTIPLIER,
@@ -630,6 +634,7 @@ import {
630
634
  KvTypeCollisionError,
631
635
  MAX_DB_RETENTION_DAYS,
632
636
  MEMORY_KEY_MAX_LENGTH,
637
+ MODEL_PROFILE_CONFIG_KEY,
633
638
  NOT_EXTENSION_AGENT_SQL,
634
639
  OpenAI,
635
640
  ReservedAgentRoleError,
@@ -677,6 +682,7 @@ import {
677
682
  createLogEntry,
678
683
  createMcpServer,
679
684
  createMetric,
685
+ createModelProfile,
680
686
  createOAuthApp,
681
687
  createPage,
682
688
  createScheduledTask,
@@ -698,6 +704,7 @@ import {
698
704
  deleteKv,
699
705
  deleteMcpServer,
700
706
  deleteMetric,
707
+ deleteModelProfile,
701
708
  deleteOAuthTokens,
702
709
  deletePage,
703
710
  deletePricingRow,
@@ -772,6 +779,7 @@ import {
772
779
  getMetric,
773
780
  getMetricVersion,
774
781
  getMetricVersions,
782
+ getModelProfileByName,
775
783
  getOAuthApp,
776
784
  getOAuthAppById,
777
785
  getOAuthTokens,
@@ -861,6 +869,7 @@ import {
861
869
  init_human_free,
862
870
  init_key_paths,
863
871
  init_memory,
872
+ init_model_profiles,
864
873
  init_oauth,
865
874
  init_openai,
866
875
  init_org_website,
@@ -909,6 +918,7 @@ import {
909
918
  listKv,
910
919
  listMcpServers,
911
920
  listMetricsByAgent,
921
+ listModelProfiles,
912
922
  listPagesByAgent,
913
923
  listRecentSessions,
914
924
  listScriptApisForScript,
@@ -928,6 +938,8 @@ import {
928
938
  markSteeringHandled,
929
939
  markSteeringUndeliverable,
930
940
  maskSecrets,
941
+ modelProfileRefError,
942
+ modelProfileReferences,
931
943
  moveAssetKey,
932
944
  normalizeDate,
933
945
  normalizeOrgWebsite,
@@ -977,6 +989,7 @@ import {
977
989
  startTask,
978
990
  supersedeTask,
979
991
  sweepExpiredKv,
992
+ taskProfileError,
980
993
  telemetry,
981
994
  touchScratchScriptLastUsed,
982
995
  trackRelayed,
@@ -995,6 +1008,7 @@ import {
995
1008
  updateAgentStatusFromCapacity,
996
1009
  updateAuthorizationIdentity,
997
1010
  updateMetric,
1011
+ updateModelProfile,
998
1012
  updateOAuthAppById,
999
1013
  updatePage,
1000
1014
  updateScheduledTask,
@@ -1023,8 +1037,8 @@ import {
1023
1037
  validateConfigValue,
1024
1038
  validateExportToEnv,
1025
1039
  withFavoriteFlags
1026
- } from "./cli-ef1hg1wv.js";
1027
- import"./cli-hnmhtzvh.js";
1040
+ } from "./cli-y3167wt1.js";
1041
+ import"./cli-9jzdj90z.js";
1028
1042
  import {
1029
1043
  ActiveSessionSchema,
1030
1044
  AgentAcpStatusSchema,
@@ -1083,6 +1097,8 @@ import {
1083
1097
  MetricSchema,
1084
1098
  MetricVersionSchema,
1085
1099
  MetricWidgetSchema,
1100
+ ModelProfileNameSchema,
1101
+ ModelProfileSchema,
1086
1102
  ModelTierSchema,
1087
1103
  OnUnsupportedSchema,
1088
1104
  PageAuthModeSchema,
@@ -1159,12 +1175,12 @@ import {
1159
1175
  isSafeBundlePath,
1160
1176
  isTerminalTaskStatus,
1161
1177
  splitLegacyModelAlias
1162
- } from "./cli-cgtksawx.js";
1178
+ } from "./cli-pe05f8yk.js";
1163
1179
  import {
1164
1180
  IDENTITY_FIELD_BUDGETS,
1165
1181
  IdentityFieldBudgetError,
1166
1182
  init_identity_field_budget
1167
- } from "./cli-s2sp957d.js";
1183
+ } from "./cli-78vv7446.js";
1168
1184
  import {
1169
1185
  getSlackConfiguration,
1170
1186
  init_config
@@ -1173,7 +1189,7 @@ import {
1173
1189
  init_steering_enabled,
1174
1190
  isSteeringEnabled
1175
1191
  } from "./cli-7mm2r1kp.js";
1176
- import"./cli-5hkm7whv.js";
1192
+ import"./cli-x1r545x8.js";
1177
1193
  import"./cli-mvzyxwg3.js";
1178
1194
  import {
1179
1195
  getAllTemplateDefinitions,
@@ -1222,17 +1238,17 @@ import {
1222
1238
  import {
1223
1239
  init_package,
1224
1240
  package_default
1225
- } from "./cli-k382tshb.js";
1241
+ } from "./cli-r35rw5de.js";
1226
1242
  import {
1227
1243
  init_event_bus,
1228
1244
  workflowEventBus
1229
- } from "./cli-9nfx44eh.js";
1245
+ } from "./cli-z780ec7c.js";
1230
1246
  import {
1231
1247
  init_bus,
1232
1248
  realtimeBus
1233
1249
  } from "./cli-zf8jjwex.js";
1234
1250
  import"./cli-h8w3fg83.js";
1235
- import"./cli-d2qr7aj4.js";
1251
+ import"./cli-t02z59f2.js";
1236
1252
  import {
1237
1253
  MAX_PROFILE_FILE_LENGTH,
1238
1254
  agentFsFileRoute,
@@ -1248,7 +1264,7 @@ import {
1248
1264
  parseEnvFlag
1249
1265
  } from "./cli-ej6gbxfb.js";
1250
1266
  import"./cli-1srmzpr3.js";
1251
- import"./cli-tqaazemc.js";
1267
+ import"./cli-s69nfyeh.js";
1252
1268
  import {
1253
1269
  getApiKey,
1254
1270
  init_api_key
@@ -1259,7 +1275,7 @@ import {
1259
1275
  registerVolatileSecret,
1260
1276
  scrubObject,
1261
1277
  scrubSecrets
1262
- } from "./cli-p1x85d2t.js";
1278
+ } from "./cli-shk7r5cs.js";
1263
1279
  import {
1264
1280
  init_src,
1265
1281
  isAlias
@@ -4048,6 +4064,7 @@ async function handleActiveSessions(req, res, pathSegments, queryParams, myAgent
4048
4064
  init_src();
4049
4065
  init_zod();
4050
4066
  init_db3();
4067
+ init_model_profiles();
4051
4068
  init_db2();
4052
4069
  init_telemetry();
4053
4070
  init_types();
@@ -4195,7 +4212,7 @@ var updateAgentRuntimeRoute = route({
4195
4212
  path: "/api/agents/{id}/runtime",
4196
4213
  pattern: ["api", "agents", null, "runtime"],
4197
4214
  summary: "Update an agent's runtime harness and default model",
4198
- description: "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.",
4215
+ description: "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.",
4199
4216
  tags: ["Agents"],
4200
4217
  params: exports_external.object({ id: exports_external.string() }),
4201
4218
  query: exports_external.object({ repoId: exports_external.string().optional() }),
@@ -4204,6 +4221,7 @@ var updateAgentRuntimeRoute = route({
4204
4221
  model: exports_external.string().trim().min(1).nullable().optional(),
4205
4222
  allow_custom_model: exports_external.boolean().optional().default(false),
4206
4223
  reasoning_effort: ReasoningEffortSchema.nullable().optional(),
4224
+ profile: exports_external.string().trim().min(1).nullable().optional(),
4207
4225
  acp: AcpRuntimeConfigSchema.optional(),
4208
4226
  claude: ClaudeRuntimeConfigSchema.optional()
4209
4227
  }),
@@ -4848,7 +4866,22 @@ async function handleAgentsRest(req, res, pathSegments, queryParams, _myAgentId)
4848
4866
  const parsed = await updateAgentRuntimeRoute.parse(req, res, pathSegments, queryParams);
4849
4867
  if (!parsed)
4850
4868
  return true;
4851
- const { harness_provider, model, allow_custom_model, reasoning_effort, acp, claude } = parsed.body;
4869
+ const { harness_provider, model, allow_custom_model, reasoning_effort, profile, acp, claude } = parsed.body;
4870
+ if (typeof profile === "string") {
4871
+ if (model !== undefined) {
4872
+ jsonError(res, "Send `profile` or `model`, not both", 400);
4873
+ return true;
4874
+ }
4875
+ const picked = await getModelProfileByName(profile);
4876
+ if (!picked) {
4877
+ jsonError(res, `Unknown model profile "${profile}"`, 400);
4878
+ return true;
4879
+ }
4880
+ if (picked.harness !== harness_provider) {
4881
+ jsonError(res, `Model profile "${profile}" runs on the ${picked.harness} harness, not ${harness_provider}`, 400);
4882
+ return true;
4883
+ }
4884
+ }
4852
4885
  if (acp && harness_provider !== "acp") {
4853
4886
  jsonError(res, "ACP configuration requires harness_provider=acp", 400);
4854
4887
  return true;
@@ -4925,6 +4958,18 @@ async function handleAgentsRest(req, res, pathSegments, queryParams, _myAgentId)
4925
4958
  description: "Set via PATCH /api/agents/{id}/runtime"
4926
4959
  });
4927
4960
  }
4961
+ if (model !== undefined || profile === null) {
4962
+ await deleteSwarmConfigByKey("agent", parsed.params.id, MODEL_PROFILE_CONFIG_KEY);
4963
+ } else if (profile !== undefined) {
4964
+ await upsertSwarmConfig({
4965
+ scope: "agent",
4966
+ scopeId: parsed.params.id,
4967
+ key: MODEL_PROFILE_CONFIG_KEY,
4968
+ value: profile,
4969
+ description: "Default model profile, set via PATCH /api/agents/{id}/runtime",
4970
+ exportToEnv: false
4971
+ });
4972
+ }
4928
4973
  if (claude?.transport === null) {
4929
4974
  await deleteSwarmConfigByKey("agent", parsed.params.id, "CLAUDE_TRANSPORT");
4930
4975
  } else if (claude?.transport !== undefined) {
@@ -5011,6 +5056,10 @@ async function handleAgentsRest(req, res, pathSegments, queryParams, _myAgentId)
5011
5056
  jsonError(res, error.message, 400);
5012
5057
  return true;
5013
5058
  }
5059
+ if (modelProfileRefError(error) === "missing") {
5060
+ jsonError(res, `Unknown model profile "${profile}"`, 400);
5061
+ return true;
5062
+ }
5014
5063
  throw error;
5015
5064
  }
5016
5065
  if (!agent) {
@@ -7511,6 +7560,7 @@ init_secret_scrubber();
7511
7560
  // src/http/config.ts
7512
7561
  init_zod();
7513
7562
  init_db3();
7563
+ init_model_profiles();
7514
7564
 
7515
7565
  // src/be/org-profile.ts
7516
7566
  init_org_website();
@@ -7553,6 +7603,16 @@ function singleHeader(req, name) {
7553
7603
  const raw = req.headers[name];
7554
7604
  return Array.isArray(raw) ? raw[0] : raw;
7555
7605
  }
7606
+ function readsRepoSecrets(auth, agent) {
7607
+ if (auth?.kind !== "agent" || !auth.runtimeId)
7608
+ return true;
7609
+ return can({
7610
+ principal: { kind: "agent", agentId: auth.agentId, isLead: agent?.isLead ?? false },
7611
+ verb: "config.read.secrets",
7612
+ resource: { kind: "none" },
7613
+ source: "http"
7614
+ }).allow;
7615
+ }
7556
7616
  async function requestSecretReadScope(req, resolvedScope) {
7557
7617
  const auth = getRequestAuth(req);
7558
7618
  if (auth?.kind === "operator")
@@ -7908,7 +7968,11 @@ async function handleConfig(req, res, pathSegments, queryParams) {
7908
7968
  }
7909
7969
  const result = includeSecrets || !config.isSecret ? config : maskSecrets([config])[0];
7910
7970
  upsertConfig.respond(res, 200, result);
7911
- } catch (_error) {
7971
+ } catch (error) {
7972
+ if (modelProfileRefError(error) === "missing") {
7973
+ jsonError(res, `Unknown model profile "${String(value)}"`, 400);
7974
+ return true;
7975
+ }
7912
7976
  jsonError(res, "Failed to upsert config", 500);
7913
7977
  }
7914
7978
  return true;
@@ -9118,6 +9182,7 @@ declare module "swarm-extension" {
9118
9182
  model?: string | undefined;
9119
9183
  modelTier?: "smol" | "regular" | "smart" | "ultra" | undefined;
9120
9184
  effort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
9185
+ profile?: string | undefined;
9121
9186
  scheduleId?: string | undefined;
9122
9187
  workflowRunId?: string | undefined;
9123
9188
  workflowRunStepId?: string | undefined;
@@ -9252,6 +9317,8 @@ declare module "swarm-extension" {
9252
9317
  modelSource?: string | undefined;
9253
9318
  modelAlias?: string | undefined;
9254
9319
  resolvedEffort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
9320
+ profile?: string | undefined;
9321
+ resolvedProfile?: string | undefined;
9255
9322
  scheduleId?: string | undefined;
9256
9323
  workflowRunId?: string | null | undefined;
9257
9324
  workflowRunStepId?: string | null | undefined;
@@ -9931,6 +9998,512 @@ void _configIsValid;
9931
9998
 
9932
9999
  // src/extensions/catalog.generated.json
9933
10000
  var catalog_generated_default = {
10001
+ "comms-adherence-gate": {
10002
+ manifestFile: "manifest.json",
10003
+ manifest: {
10004
+ $schema: "../manifest.schema.json",
10005
+ name: "comms-adherence-gate",
10006
+ description: "Judges every Slack-facing store-progress completion against the requester's comms preferences and latest request with Jev (TypeSafe or OpenRouter), and in enforce blocks a non-adherent completion once per task",
10007
+ version: "1.5.0",
10008
+ runtime: "api",
10009
+ assets: {
10010
+ hooks: "hooks.ts"
10011
+ }
10012
+ },
10013
+ files: {
10014
+ "hooks.ts": `import { type ApiCtx, block, type SwarmExtension } from "swarm-extension";
10015
+ import { z } from "zod";
10016
+
10017
+ /**
10018
+ * comms-adherence-gate
10019
+ *
10020
+ * When an agent calls \`store-progress\` with status "completed" on a task that
10021
+ * reaches a human in Slack, judge the \`output\` against the requesting user's
10022
+ * stored comms preferences (users.metadata.$.comms) with Jev, and against what
10023
+ * that user actually asked for in their latest message. An explicit request
10024
+ * ("tell me a long joke", "explain in detail") outranks the stored default, so
10025
+ * a requested long answer is not a verbosity failure.
10026
+ *
10027
+ * Transport (config.provider): Jev is served by TypeSafe directly and by
10028
+ * OpenRouter's Decisions endpoint. Both take the same {model, state, questions}
10029
+ * body and return the same \`answers\` shape. \`auto\` uses TYPESAFE_API_KEY when
10030
+ * set, else OPENROUTER_API_KEY.
10031
+ *
10032
+ * modes (config.mode):
10033
+ * off -> never evaluate
10034
+ * shadow -> evaluate + record, never block (default)
10035
+ * enforce -> record, and block the completion up to maxBlocksPerTask times
10036
+ * per task with actionable feedback
10037
+ *
10038
+ * Fails OPEN on every error: a judge outage must never wall the swarm.
10039
+ * Every verdict lands in KV namespace \`ext:comms-gate\`.
10040
+ */
10041
+
10042
+ export const config = z.object({
10043
+ mode: z.enum(["off", "shadow", "enforce"]).default("shadow"),
10044
+ /** A dimension scoring below this fails. */
10045
+ threshold: z.number().min(0).max(1).default(0.5),
10046
+ timeoutMs: z.number().int().positive().default(4000),
10047
+ maxBlocksPerTask: z.number().int().min(0).default(1),
10048
+ provider: z.enum(["auto", "typesafe", "openrouter"]).default("auto"),
10049
+ /** Jev model on the TypeSafe transport. */
10050
+ model: z.string().default("jev-latest"),
10051
+ /** Jev model on the OpenRouter transport. */
10052
+ openrouterModel: z.string().default("typesafe/jev-1.13"),
10053
+ /** Explicit TypeSafe key; otherwise TYPESAFE_API_KEY from the API process env. */
10054
+ apiKey: z.string().optional(),
10055
+ });
10056
+
10057
+ type Cfg = z.infer<typeof config>;
10058
+ type Swarm = ApiCtx["swarm"];
10059
+
10060
+ const NS = "ext:comms-gate";
10061
+ const TYPESAFE_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
10062
+ const OPENROUTER_ENDPOINT = "https://openrouter.ai/api/alpha/decisions";
10063
+ const UUID_RE = /^[0-9a-fA-F-]{36}$/;
10064
+ // The dispatcher cancels a handler after 5 s; leave room for the DB read and KV writes.
10065
+ const MAX_TIMEOUT_MS = 4000;
10066
+
10067
+ export const cfgOf = (raw: unknown): Cfg => {
10068
+ const parsed = config.safeParse(raw ?? {});
10069
+ return parsed.success ? parsed.data : config.parse({});
10070
+ };
10071
+
10072
+ // kv keys accept [a-zA-Z0-9._:/%-]
10073
+ const safeKey = (raw: string | undefined | null): string =>
10074
+ (raw && raw.length > 0 ? raw : "unknown").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 120);
10075
+
10076
+ /** SDK envelopes: {success, data:{...}} or the bare payload. */
10077
+ type Envelope = {
10078
+ data?: { rows?: unknown[]; value?: unknown };
10079
+ rows?: unknown[];
10080
+ value?: unknown;
10081
+ } | null;
10082
+
10083
+ const cell = (row: unknown, i: number, key: string): unknown =>
10084
+ Array.isArray(row) ? row[i] : (row as Record<string, unknown>)?.[key];
10085
+
10086
+ /**
10087
+ * The newest human message sits at the END of a Slack task: the
10088
+ * \`<thread_context>\` blob comes first and can run to thousands of chars, so a
10089
+ * leading slice of \`agent_tasks.task\` shows the judge everything except the
10090
+ * actual request. Take the tail, then the part after the last \`</thread_context>\`.
10091
+ */
10092
+ const CTX_END = "</thread_context>";
10093
+ export const latestAsk = (tail: string): string => {
10094
+ const end = tail.lastIndexOf(CTX_END);
10095
+ let out = (end >= 0 ? tail.slice(end + CTX_END.length) : tail)
10096
+ .replace(/\\[Thread follow-up[^\\]]*\\]/g, "")
10097
+ .trim();
10098
+ // Some tasks end with the context block; fall back to the last messages in it.
10099
+ if (out.length < 8) out = (end >= 0 ? tail.slice(0, end) : tail).trim().slice(-800);
10100
+ return out.slice(0, 900);
10101
+ };
10102
+
10103
+ // Preferences are the DEFAULT. A direct request outranks them.
10104
+ const JUDGING_RULES = [
10105
+ "The requester's explicit instruction in latest_request OUTRANKS requester_preferences. Preferences describe the DEFAULT; they never override a direct request.",
10106
+ "If latest_request asks for something long, detailed, creative, a story, a joke, a full explanation or a document, then length and structure serving that request are CORRECT and must NOT be penalized.",
10107
+ "Judge whether agent_message serves what latest_request actually asked for, written in the requester's manner. Do not judge it against the default preference when the request overrides it.",
10108
+ ];
10109
+
10110
+ type Prefs = { verbosity?: unknown; tone?: unknown; language?: unknown } & Record<string, unknown>;
10111
+
10112
+ /** One \`noul\` question per stated preference the gate knows how to judge. */
10113
+ export const buildQuestions = (prefs: Prefs): Record<string, unknown> => {
10114
+ const questions: Record<string, unknown> = {};
10115
+ if (prefs.verbosity) {
10116
+ questions.verbosity = {
10117
+ type: "noul",
10118
+ instructions:
10119
+ "Does agent_message respect the requester's verbosity preference, AS OVERRIDDEN by any explicit instruction in latest_request? Apply judging_rules.",
10120
+ criteria: {
10121
+ true: "Length and structure fit what latest_request asked for (or the stated preference when the request does not override it), and it leads with the result.",
10122
+ false:
10123
+ "Too long for what was asked, or it opens with preamble, recaps the request, narrates the process, or ends with an offer to elaborate. Length explicitly requested in latest_request is NOT a failure.",
10124
+ },
10125
+ };
10126
+ }
10127
+ if (prefs.tone) {
10128
+ questions.tone = {
10129
+ type: "noul",
10130
+ instructions:
10131
+ "Does agent_message match the requester's stated tone preference, as overridden by any explicit instruction in latest_request? Apply judging_rules.",
10132
+ criteria: {
10133
+ true: "The tone matches what the requester asked for.",
10134
+ false:
10135
+ "The tone conflicts with it (for example hedging, flattery or filler when the requester asked for direct and plain). A voice appropriate to an explicitly requested creative or narrative form is NOT a failure.",
10136
+ },
10137
+ };
10138
+ }
10139
+ if (prefs.language) {
10140
+ questions.language = {
10141
+ type: "noul",
10142
+ instructions: "Is agent_message written in the requester's stated language?",
10143
+ criteria: {
10144
+ true: "Written in the stated language.",
10145
+ false: "Written in a different language.",
10146
+ },
10147
+ };
10148
+ }
10149
+ return questions;
10150
+ };
10151
+
10152
+ const secretCache = new Map<string, { value: string; at: number }>();
10153
+ const KEY_TTL_MS = 5 * 60 * 1000;
10154
+ const usable = (v: string): boolean => Boolean(v) && !v.startsWith("[REDACTED");
10155
+ const envOf = (name: string): string => String(process.env[name] ?? "");
10156
+
10157
+ /**
10158
+ * Resolve a secret: explicit value, then the API process env. The API process loads
10159
+ * global swarm_config rows into process.env at boot and on config reload.
10160
+ * ctx.swarm.config_get cannot serve it: the SDK scrubs secrets to "[REDACTED:<name>]".
10161
+ */
10162
+ const secret = (name: string, explicit?: string): string => {
10163
+ const cached = secretCache.get(name);
10164
+ if (cached && Date.now() - cached.at < KEY_TTL_MS) return cached.value;
10165
+ const value = [String(explicit ?? ""), envOf(name)].find(usable) ?? "";
10166
+ if (usable(value)) secretCache.set(name, { value, at: Date.now() });
10167
+ return value;
10168
+ };
10169
+
10170
+ /** Test hook: forget cached secrets. */
10171
+ export const resetKeyCache = (): void => {
10172
+ secretCache.clear();
10173
+ };
10174
+
10175
+ export type JevTransport = {
10176
+ provider: "typesafe" | "openrouter";
10177
+ endpoint: string;
10178
+ key: string;
10179
+ model: string;
10180
+ };
10181
+
10182
+ /**
10183
+ * Pick the Jev transport. \`auto\` prefers TypeSafe and falls back to OpenRouter.
10184
+ * Throws when the chosen provider has no usable key; the handler fails open.
10185
+ */
10186
+ export const resolveTransport = (cfg: Cfg): JevTransport => {
10187
+ const typesafe = (): JevTransport | null => {
10188
+ const key = secret("TYPESAFE_API_KEY", cfg.apiKey);
10189
+ return usable(key)
10190
+ ? { provider: "typesafe", endpoint: TYPESAFE_ENDPOINT, key, model: cfg.model }
10191
+ : null;
10192
+ };
10193
+ const openrouter = (): JevTransport | null => {
10194
+ const key = secret("OPENROUTER_API_KEY");
10195
+ return usable(key)
10196
+ ? { provider: "openrouter", endpoint: OPENROUTER_ENDPOINT, key, model: cfg.openrouterModel }
10197
+ : null;
10198
+ };
10199
+ const transport =
10200
+ cfg.provider === "typesafe"
10201
+ ? typesafe()
10202
+ : cfg.provider === "openrouter"
10203
+ ? openrouter()
10204
+ : (typesafe() ?? openrouter());
10205
+ if (transport) return transport;
10206
+ throw new Error(
10207
+ cfg.provider === "typesafe"
10208
+ ? "no TYPESAFE_API_KEY"
10209
+ : cfg.provider === "openrouter"
10210
+ ? "no OPENROUTER_API_KEY"
10211
+ : "no TYPESAFE_API_KEY or OPENROUTER_API_KEY",
10212
+ );
10213
+ };
10214
+
10215
+ /** One Jev call; returns the \`noul\` score per question. Throws on any failure. */
10216
+ export const askJev = async (
10217
+ state: Record<string, unknown>,
10218
+ questions: Record<string, unknown>,
10219
+ transport: JevTransport,
10220
+ cfg: Cfg,
10221
+ outer?: AbortSignal,
10222
+ ): Promise<{ scores: Record<string, number>; latencyMs: number }> => {
10223
+ const controller = new AbortController();
10224
+ const timer = setTimeout(() => controller.abort(), Math.min(cfg.timeoutMs, MAX_TIMEOUT_MS));
10225
+ const onOuterAbort = () => controller.abort();
10226
+ outer?.addEventListener("abort", onOuterAbort);
10227
+ const t0 = Date.now();
10228
+ try {
10229
+ const res = await fetch(transport.endpoint, {
10230
+ method: "POST",
10231
+ headers: { "Content-Type": "application/json", Authorization: \`Bearer \${transport.key}\` },
10232
+ body: JSON.stringify({ state, model: transport.model, questions }),
10233
+ signal: controller.signal,
10234
+ });
10235
+ if (!res.ok) throw new Error(\`\${transport.provider} \${res.status}\`);
10236
+ const body = (await res.json()) as { answers?: Record<string, { noul?: unknown }> } | null;
10237
+ const answers = body?.answers;
10238
+ if (!answers || typeof answers !== "object") throw new Error("no answers in response");
10239
+ const scores: Record<string, number> = {};
10240
+ for (const [id, a] of Object.entries(answers)) {
10241
+ if (typeof a?.noul === "number") scores[id] = a.noul;
10242
+ }
10243
+ if (Object.keys(scores).length === 0) throw new Error("no scores in response");
10244
+ return { scores, latencyMs: Date.now() - t0 };
10245
+ } finally {
10246
+ clearTimeout(timer);
10247
+ outer?.removeEventListener("abort", onOuterAbort);
10248
+ }
10249
+ };
10250
+
10251
+ const SELFTEST = "comms-gate-selftest";
10252
+ const SELFTEST_PREFS = {
10253
+ tone: "direct, plain, no hedging",
10254
+ language: "English",
10255
+ verbosity:
10256
+ "very terse - answer in the first sentence, ~400 chars max. No preamble, no recap, no closing offer.",
10257
+ };
10258
+ const SELFTEST_BAD =
10259
+ "Great question! Let me walk you through what I did here. First I looked at the task, then I reviewed the relevant files, and after careful consideration I decided to go with option B. I hope this helps - let me know if you would like me to elaborate further on any of the above!";
10260
+ const SELFTEST_GOOD =
10261
+ "Went with option B: it is the only one that keeps the migration reversible. PR #123.";
10262
+
10263
+ /**
10264
+ * Run the judge on a known-bad (positive control, must score below threshold) and a
10265
+ * known-good (negative control, must score at or above it) message and record both
10266
+ * under \`selftest:last\`. Proves key, egress and KV from inside the API runtime
10267
+ * without touching real traffic.
10268
+ */
10269
+ const selfTest = async (swarm: Swarm, cfg: Cfg, signal?: AbortSignal): Promise<void> => {
10270
+ const started = Date.now();
10271
+ try {
10272
+ const transport = resolveTransport(cfg);
10273
+ const questions = { verbosity: buildQuestions(SELFTEST_PREFS).verbosity };
10274
+ const judge = (message: string) =>
10275
+ askJev(
10276
+ { requester_preferences: SELFTEST_PREFS, agent_message: message },
10277
+ questions,
10278
+ transport,
10279
+ cfg,
10280
+ signal,
10281
+ ).then((r) => r.scores.verbosity ?? null);
10282
+ const [bad, good] = await Promise.all([judge(SELFTEST_BAD), judge(SELFTEST_GOOD)]);
10283
+ await swarm.kv_set({
10284
+ namespace: NS,
10285
+ key: "selftest:last",
10286
+ value: {
10287
+ at: new Date().toISOString(),
10288
+ provider: transport.provider,
10289
+ threshold: cfg.threshold,
10290
+ positiveControlBadScore: bad,
10291
+ negativeControlGoodScore: good,
10292
+ controlsOk:
10293
+ typeof bad === "number" &&
10294
+ typeof good === "number" &&
10295
+ bad < cfg.threshold &&
10296
+ good >= cfg.threshold,
10297
+ latencyMs: Date.now() - started,
10298
+ },
10299
+ });
10300
+ } catch (err) {
10301
+ await swarm
10302
+ .kv_set({
10303
+ namespace: NS,
10304
+ key: "selftest:last",
10305
+ value: {
10306
+ at: new Date().toISOString(),
10307
+ error: String(err).slice(0, 400),
10308
+ latencyMs: Date.now() - started,
10309
+ },
10310
+ })
10311
+ .catch(() => {});
10312
+ }
10313
+ };
10314
+
10315
+ const TASK_SQL = \`SELECT t.source, t.slackThreadTs, t.agentId, t.requestedByUserId,
10316
+ json_extract(u.metadata, '$.comms') AS comms, u.name,
10317
+ substr(t.task, 1, 400) AS askHead,
10318
+ substr(t.task, -1400) AS askTail
10319
+ FROM agent_tasks t
10320
+ LEFT JOIN users u ON u.id = t.requestedByUserId
10321
+ WHERE t.id = ?\`;
10322
+
10323
+ const LEAD_SQL = "SELECT isLead FROM agents WHERE id = ?";
10324
+
10325
+ /**
10326
+ * Mirrors the \`task.progress.write\` rule store-progress enforces
10327
+ * (src/rbac/legacy-policy.ts): the caller owns the task, the task is
10328
+ * unassigned, or the caller is a lead. Lead status comes from the caller's
10329
+ * agents row, never from tool args. Extension callers return before this.
10330
+ */
10331
+ const mayWriteProgress = async (
10332
+ swarm: Swarm,
10333
+ callerId: string | undefined,
10334
+ taskAgentId: string,
10335
+ ): Promise<boolean> => {
10336
+ if (!callerId) return false;
10337
+ if (!taskAgentId || taskAgentId === callerId) return true;
10338
+ const q = (await swarm.db_query({ sql: LEAD_SQL, params: [callerId] })) as Envelope;
10339
+ const row = (q?.data?.rows ?? q?.rows ?? [])[0];
10340
+ return Number(cell(row, 0, "isLead") ?? 0) === 1;
10341
+ };
10342
+
10343
+ const extension: SwarmExtension = (api) => {
10344
+ api.on("pre.tool.call", async (event, ctx) => {
10345
+ if (event.tool !== "store-progress") return;
10346
+ if (event.requestInfo?.callOrigin === "extension") return;
10347
+
10348
+ const cfg = cfgOf(ctx.config);
10349
+ if (cfg.mode === "off") return;
10350
+
10351
+ const args = (event.args ?? {}) as Record<string, unknown>;
10352
+
10353
+ // Self-test hatch: a progress call whose text starts with the sentinel runs
10354
+ // the controls and records them under \`selftest:last\`.
10355
+ if (args.status !== "completed") {
10356
+ if (typeof args.progress === "string" && args.progress.startsWith(SELFTEST)) {
10357
+ await selfTest(ctx.swarm, cfg, ctx.signal);
10358
+ }
10359
+ return;
10360
+ }
10361
+
10362
+ const output = typeof args.output === "string" ? args.output.trim() : "";
10363
+ if (!output) return;
10364
+
10365
+ const taskId = String(args.taskId ?? event.requestInfo?.sourceTaskId ?? "");
10366
+ if (!UUID_RE.test(taskId)) return;
10367
+
10368
+ try {
10369
+ const q = (await ctx.swarm.db_query({ sql: TASK_SQL, params: [taskId] })) as Envelope;
10370
+ const row = (q?.data?.rows ?? q?.rows ?? [])[0];
10371
+ if (!row) return;
10372
+
10373
+ const source = String(cell(row, 0, "source") ?? "");
10374
+ const slackThreadTs = cell(row, 1, "slackThreadTs");
10375
+ const agentId = String(cell(row, 2, "agentId") ?? "");
10376
+ const requesterId = String(cell(row, 3, "requestedByUserId") ?? "");
10377
+ const commsRaw = cell(row, 4, "comms");
10378
+ const requesterName = String(cell(row, 5, "name") ?? requesterId);
10379
+ const threadOpening = String(cell(row, 6, "askHead") ?? "");
10380
+ const ask = latestAsk(String(cell(row, 7, "askTail") ?? ""));
10381
+
10382
+ // Judge only a completion store-progress will accept, so no agent can ship
10383
+ // another task's thread and preferences to the external judge.
10384
+ if (!(await mayWriteProgress(ctx.swarm, event.requestInfo?.agentId, agentId))) return;
10385
+
10386
+ // Only where the text actually reaches a human in Slack.
10387
+ if (source !== "slack" && !slackThreadTs) return;
10388
+ if (!commsRaw) return; // no stated preferences -> nothing to judge against
10389
+ const prefs = (typeof commsRaw === "string" ? JSON.parse(commsRaw) : commsRaw) as Prefs;
10390
+ if (!prefs || typeof prefs !== "object") return;
10391
+
10392
+ const questions = buildQuestions(prefs);
10393
+ if (Object.keys(questions).length === 0) return;
10394
+
10395
+ const transport = resolveTransport(cfg);
10396
+ const { scores, latencyMs } = await askJev(
10397
+ {
10398
+ role: "You audit an AI agent's final message to the human who requested the work.",
10399
+ requester: requesterName,
10400
+ requester_preferences: prefs,
10401
+ latest_request: ask,
10402
+ thread_opening: threadOpening,
10403
+ judging_rules: JUDGING_RULES,
10404
+ agent_message: output,
10405
+ },
10406
+ questions,
10407
+ transport,
10408
+ cfg,
10409
+ ctx.signal,
10410
+ );
10411
+
10412
+ const failed = Object.entries(scores)
10413
+ .filter(([, v]) => v < cfg.threshold)
10414
+ .map(([k]) => k);
10415
+
10416
+ const verdict = {
10417
+ at: new Date().toISOString(),
10418
+ taskId,
10419
+ agentId,
10420
+ source,
10421
+ requester: requesterName,
10422
+ requesterId,
10423
+ provider: transport.provider,
10424
+ scores,
10425
+ failed,
10426
+ threshold: cfg.threshold,
10427
+ mode: cfg.mode,
10428
+ latencyMs,
10429
+ preferences: prefs,
10430
+ askPreview: ask.slice(0, 400),
10431
+ outputPreview: output.slice(0, 700),
10432
+ };
10433
+
10434
+ await ctx.swarm.kv_incr({ namespace: NS, key: "total" });
10435
+ await ctx.swarm.kv_set({ namespace: NS, key: "last", value: verdict });
10436
+
10437
+ if (failed.length === 0) {
10438
+ await ctx.swarm.kv_incr({ namespace: NS, key: "pass" });
10439
+ return;
10440
+ }
10441
+
10442
+ await ctx.swarm.kv_incr({ namespace: NS, key: "fail" });
10443
+ for (const dim of failed) {
10444
+ await ctx.swarm.kv_incr({ namespace: NS, key: \`dim:\${safeKey(dim)}\` });
10445
+ }
10446
+
10447
+ const blocksKey = \`blocks:\${safeKey(taskId)}\`;
10448
+ // kv_get answers {success, data:{value}}, and {success:false} when absent.
10449
+ const prior = (await ctx.swarm.kv_get({ namespace: NS, key: blocksKey })) as Envelope;
10450
+ const priorBlocks = Number(prior?.data?.value ?? prior?.value ?? 0) || 0;
10451
+ const willBlock = cfg.mode === "enforce" && priorBlocks < cfg.maxBlocksPerTask;
10452
+
10453
+ await ctx.swarm.kv_set({
10454
+ namespace: NS,
10455
+ key: \`fail:\${Date.now()}:\${safeKey(taskId)}\`,
10456
+ value: { ...verdict, action: willBlock ? "blocked" : "recorded" },
10457
+ });
10458
+
10459
+ if (!willBlock) {
10460
+ await ctx.swarm.kv_incr({
10461
+ namespace: NS,
10462
+ key: cfg.mode === "enforce" ? "escaped" : "shadowed",
10463
+ });
10464
+ return;
10465
+ }
10466
+
10467
+ await ctx.swarm.kv_incr({ namespace: NS, key: "blocked" });
10468
+ await ctx.swarm.kv_incr({ namespace: NS, key: blocksKey });
10469
+
10470
+ const detail = failed
10471
+ .map(
10472
+ (d) =>
10473
+ \`\${d} (\${scores[d]!.toFixed(2)} vs \${cfg.threshold}) — requested: \${String(prefs[d] ?? "").slice(0, 220)}\`,
10474
+ )
10475
+ .join("\\n");
10476
+
10477
+ return block(
10478
+ [
10479
+ \`Comms gate: this output does not match \${requesterName}'s stated preferences.\`,
10480
+ detail,
10481
+ "Rewrite the output to match and call store-progress again. Do not change the facts, only how they are said.",
10482
+ ].join("\\n"),
10483
+ );
10484
+ } catch (err) {
10485
+ // Fail open, always.
10486
+ try {
10487
+ await ctx.swarm.kv_incr({ namespace: NS, key: "error" });
10488
+ await ctx.swarm.kv_set({
10489
+ namespace: NS,
10490
+ key: "error:last",
10491
+ value: { at: new Date().toISOString(), taskId, error: String(err).slice(0, 400) },
10492
+ });
10493
+ } catch {
10494
+ /* ignore */
10495
+ }
10496
+ ctx.log.warn("comms-adherence-gate: failing open", { error: String(err) });
10497
+ return;
10498
+ }
10499
+ });
10500
+ };
10501
+
10502
+ export default extension;
10503
+ `
10504
+ },
10505
+ readme: '# comms-adherence-gate\n\nAgents finish Slack tasks with a final message a human reads. This extension judges that message against the requester\'s stated comms preferences and their latest request with Jev, [TypeSafe](https://typesafe.ai)\'s decision model. In `enforce` it blocks a non-adherent completion once and tells the agent what to fix.\n\nInstall config:\n\n```json\n{ "mode": "shadow" }\n```\n\nStart in `shadow`, read the verdicts in KV for a few days, then switch to `enforce`.\n\n## Modes\n\n| `mode` | Effect |\n|---|---|\n| `off` | Never evaluates. |\n| `shadow` (default) | Asks Jev and records the verdict. Never blocks. |\n| `enforce` | Asks Jev, records the verdict, and blocks the `store-progress` call when a dimension fails, at most `maxBlocksPerTask` times per task. The agent gets the failed dimensions, their scores and the stated preference, and calls again. |\n\n## What it gates\n\nHook: `pre.tool.call` on `store-progress` with `status: "completed"` and a non-empty `output`. Calls made by extensions are skipped. The task must:\n\n1. Come from Slack (`source = "slack"` or a `slackThreadTs`). Other tasks pass without a Jev call.\n2. Have a requester with stated preferences in `users.metadata.comms`. A requester with none passes. Set them with the `manage-user` tool.\n\n## Decision\n\nThe gate reads the task row and asks one `noul` question per stated preference it knows: `verbosity`, `tone`, `language`. Any other key in `comms` is context only.\n\nJev sees the preferences, the requester\'s latest message (the text after the last `</thread_context>`, from the last 1,400 characters of the task), the first 400 characters of the task, and the output. Its judging rules say an explicit request outranks the stored default. A requested long answer, story or document is not a verbosity failure.\n\nA dimension fails when its score is below `threshold`. Any failed dimension fails the output.\n\n## Jev transport\n\nJev is reachable two ways. Both take the same request and return the same answer.\n\n| `provider` | Endpoint | Key | Model |\n|---|---|---|---|\n| `typesafe` | `POST https://api.typesafe.ai/v1/systemone` | `TYPESAFE_API_KEY` (or `apiKey`) | `model`, default `jev-latest` |\n| `openrouter` | `POST https://openrouter.ai/api/alpha/decisions` | `OPENROUTER_API_KEY` | `openrouterModel`, default `typesafe/jev-1.13` |\n| `auto` (default) | TypeSafe when its key is set, else OpenRouter | | |\n\nEach verdict row records the `provider` that answered.\n\n## Fail open\n\nEvery error lets the completion through: a missing key, an HTTP error, a network error, a timeout, a malformed answer, or a failed DB read. The handler never throws, so an outage cannot auto-disable the extension. Each failure bumps the `error` counter and overwrites `error:last`.\n\n## Self-test\n\nCall `store-progress` with a non-completed status and `progress` starting with `comms-gate-selftest`. The gate judges a known-bad and a known-good message against a terse preference and writes `selftest:last`: both scores, the threshold, the provider, and `controlsOk` (bad below threshold and good at or above it). Real traffic is untouched.\n\n## Config\n\n| Key | Default | Notes |\n|---|---|---|\n| `mode` | `shadow` | `off`, `shadow`, or `enforce` |\n| `threshold` | `0.5` | A dimension scoring below this fails |\n| `timeoutMs` | `4000` | Jev call timeout, capped at 4000 (the dispatcher cancels a handler at 5 s) |\n| `maxBlocksPerTask` | `1` | Blocks per task in `enforce`; after that the gate records and lets it through |\n| `provider` | `auto` | `auto`, `typesafe`, or `openrouter`. See [Jev transport](#jev-transport) |\n| `model` | `jev-latest` | Jev model on TypeSafe |\n| `openrouterModel` | `typesafe/jev-1.13` | Jev model on OpenRouter |\n| `apiKey` | unset | Explicit TypeSafe key. Prefer the `TYPESAFE_API_KEY` secret below |\n\n## Required secrets\n\nRead from the API process environment at call time. Set them as global `swarm_config` secrets: the API process loads global rows into `process.env` at boot and on config reload.\n\n- `TYPESAFE_API_KEY` or `OPENROUTER_API_KEY`: one is required for any evaluation. Under `auto`, TypeSafe wins when both are set. Without either, every completion fails open.\n\nNo key is written to config, KV, logs, or a block reason.\n\n## KV namespace `ext:comms-gate`\n\n- `last`: the latest verdict, with `scores`, `failed`, `provider`, `latencyMs`, the preferences, a 400-character `askPreview` and a 700-character `outputPreview`.\n- `fail:<epochMs>:<taskId>`: one row per failed verdict, with `action` (`blocked` or `recorded`).\n- `blocks:<taskId>`: blocks issued for that task.\n- `error:last`, `selftest:last`.\n- Counters: `total`, `pass`, `fail`, `blocked`, `escaped` (enforce, block budget spent), `shadowed`, `error`, and `dim:<dimension>`.\n\n## Known limits\n\n- Only the last 1,400 characters of the task are searched for the latest request. A very long final message is cut to its last 900 characters.\n- The block budget is per task, so a retried completion after a block goes through once the budget is spent, even if it still fails.\n'
10506
+ },
9934
10507
  "deploy-awareness": {
9935
10508
  manifestFile: "manifest.json",
9936
10509
  manifest: {
@@ -18907,6 +19480,237 @@ async function handleMetrics(req, res, pathSegments, queryParams, myAgentId) {
18907
19480
  return false;
18908
19481
  }
18909
19482
 
19483
+ // src/http/model-profiles.ts
19484
+ init_src();
19485
+ init_zod();
19486
+ init_model_profiles();
19487
+ init_rbac();
19488
+ init_types();
19489
+ var listProfiles = route({
19490
+ method: "get",
19491
+ path: "/api/model-profiles",
19492
+ pattern: ["api", "model-profiles"],
19493
+ summary: "List model profiles",
19494
+ description: "Every model profile, ordered by harness then name. Filter by `harness` to see what an agent on that harness can run.",
19495
+ tags: ["Models"],
19496
+ query: exports_external.object({ harness: ProviderNameSchema.optional() }),
19497
+ responses: {
19498
+ 200: {
19499
+ description: "Model profiles",
19500
+ schema: exports_external.object({ profiles: exports_external.array(ModelProfileSchema) })
19501
+ }
19502
+ }
19503
+ });
19504
+ var getProfile = route({
19505
+ method: "get",
19506
+ path: "/api/model-profiles/{name}",
19507
+ pattern: ["api", "model-profiles", null],
19508
+ summary: "Get one model profile",
19509
+ tags: ["Models"],
19510
+ params: exports_external.object({ name: exports_external.string() }),
19511
+ responses: {
19512
+ 200: { description: "The model profile", schema: ModelProfileSchema },
19513
+ 404: { description: "No profile with that name" }
19514
+ }
19515
+ });
19516
+ var createProfile = route({
19517
+ method: "post",
19518
+ path: "/api/model-profiles",
19519
+ pattern: ["api", "model-profiles"],
19520
+ summary: "Create a model profile",
19521
+ description: "A profile runs on one harness. `model` must run on that harness and be in the model catalog unless `allowCustomModel` is true (a gateway id, a fresh launch); a `latest:` alias is accepted and resolves at claim. `effort` must be a level the harness and model support. Operator only (the shared API key).",
19522
+ tags: ["Models"],
19523
+ rbac: { permission: "models.profiles.write" },
19524
+ body: exports_external.object({
19525
+ name: ModelProfileNameSchema,
19526
+ harness: ProviderNameSchema,
19527
+ model: exports_external.string().trim().min(1),
19528
+ effort: ReasoningEffortSchema.nullable().optional(),
19529
+ allowCustomModel: exports_external.boolean().optional().default(false),
19530
+ description: exports_external.string().trim().max(500).nullable().optional()
19531
+ }),
19532
+ responses: {
19533
+ 201: { description: "Profile created", schema: ModelProfileSchema },
19534
+ 400: { description: "Validation error" },
19535
+ 403: { description: "Forbidden" },
19536
+ 409: { description: "A profile with that name exists" }
19537
+ }
19538
+ });
19539
+ var updateProfile = route({
19540
+ method: "patch",
19541
+ path: "/api/model-profiles/{name}",
19542
+ pattern: ["api", "model-profiles", null],
19543
+ summary: "Update a model profile",
19544
+ description: "Changes the model, effort, custom-model flag or description; the name and harness are fixed. Omit a field to keep it; `effort: null` clears it. Applies to tasks claimed after the change. Operator only (the shared API key).",
19545
+ tags: ["Models"],
19546
+ rbac: { permission: "models.profiles.write" },
19547
+ params: exports_external.object({ name: exports_external.string() }),
19548
+ body: exports_external.object({
19549
+ model: exports_external.string().trim().min(1).optional(),
19550
+ effort: ReasoningEffortSchema.nullable().optional(),
19551
+ allowCustomModel: exports_external.boolean().optional(),
19552
+ description: exports_external.string().trim().max(500).nullable().optional()
19553
+ }),
19554
+ responses: {
19555
+ 200: { description: "Profile updated", schema: ModelProfileSchema },
19556
+ 400: { description: "Validation error" },
19557
+ 403: { description: "Forbidden" },
19558
+ 404: { description: "No profile with that name" }
19559
+ }
19560
+ });
19561
+ var deleteProfile = route({
19562
+ method: "delete",
19563
+ path: "/api/model-profiles/{name}",
19564
+ pattern: ["api", "model-profiles", null],
19565
+ summary: "Delete a model profile",
19566
+ description: "Refused (409) for a shipped `<harness>-<tier>` seed (edit it instead; it would come back at the next boot), and while an agent has the profile as its default or a task holds it (unfinished, or superseded and still owed its resume). Operator only (the shared API key).",
19567
+ tags: ["Models"],
19568
+ rbac: { permission: "models.profiles.write" },
19569
+ params: exports_external.object({ name: exports_external.string() }),
19570
+ responses: {
19571
+ 200: { description: "Profile deleted", schema: exports_external.object({ deleted: exports_external.literal(true) }) },
19572
+ 403: { description: "Forbidden" },
19573
+ 404: { description: "No profile with that name" },
19574
+ 409: { description: "The profile is a seed or still in use" }
19575
+ }
19576
+ });
19577
+ async function profileValueError(input) {
19578
+ const modelError = await explicitModelError({
19579
+ model: input.model,
19580
+ allowCustomModel: input.allowCustomModel,
19581
+ harnessProvider: input.harness
19582
+ });
19583
+ if (modelError)
19584
+ return modelError;
19585
+ if (input.effort && !isAlias(input.model) && !supportedReasoningLevels(input.harness, input.model).includes(input.effort)) {
19586
+ return `Effort "${input.effort}" is not supported by ${input.model} on ${input.harness}.`;
19587
+ }
19588
+ return null;
19589
+ }
19590
+ async function canWriteProfiles(req, res, myAgentId) {
19591
+ const decision = can({
19592
+ principal: await agentFirstPrincipal(req, myAgentId),
19593
+ verb: "models.profiles.write",
19594
+ resource: { kind: "none" },
19595
+ source: "http"
19596
+ });
19597
+ if (!decision.allow) {
19598
+ jsonError(res, "Writing model profiles requires the operator", 403);
19599
+ return false;
19600
+ }
19601
+ return true;
19602
+ }
19603
+ async function handleModelProfiles(req, res, pathSegments, queryParams, myAgentId) {
19604
+ if (listProfiles.match(req.method, pathSegments)) {
19605
+ const parsed = await listProfiles.parse(req, res, pathSegments, queryParams);
19606
+ if (!parsed)
19607
+ return true;
19608
+ listProfiles.respond(res, 200, {
19609
+ profiles: await listModelProfiles({ harness: parsed.query.harness })
19610
+ });
19611
+ return true;
19612
+ }
19613
+ if (getProfile.match(req.method, pathSegments)) {
19614
+ const parsed = await getProfile.parse(req, res, pathSegments, queryParams);
19615
+ if (!parsed)
19616
+ return true;
19617
+ const profile = await getModelProfileByName(parsed.params.name);
19618
+ if (!profile) {
19619
+ jsonError(res, "Model profile not found", 404);
19620
+ return true;
19621
+ }
19622
+ getProfile.respond(res, 200, profile);
19623
+ return true;
19624
+ }
19625
+ if (createProfile.match(req.method, pathSegments)) {
19626
+ const parsed = await createProfile.parse(req, res, pathSegments, queryParams);
19627
+ if (!parsed)
19628
+ return true;
19629
+ if (!await canWriteProfiles(req, res, myAgentId))
19630
+ return true;
19631
+ const body = parsed.body;
19632
+ const error = await profileValueError({ ...body, effort: body.effort ?? null });
19633
+ if (error) {
19634
+ jsonError(res, error, 400);
19635
+ return true;
19636
+ }
19637
+ const created = await createModelProfile(body, {
19638
+ userId: await resolveHttpAuditUserId(req, myAgentId)
19639
+ });
19640
+ if (!created) {
19641
+ jsonError(res, `Model profile "${body.name}" already exists`, 409);
19642
+ return true;
19643
+ }
19644
+ createProfile.respond(res, 201, created);
19645
+ return true;
19646
+ }
19647
+ if (updateProfile.match(req.method, pathSegments)) {
19648
+ const parsed = await updateProfile.parse(req, res, pathSegments, queryParams);
19649
+ if (!parsed)
19650
+ return true;
19651
+ if (!await canWriteProfiles(req, res, myAgentId))
19652
+ return true;
19653
+ const current = await getModelProfileByName(parsed.params.name);
19654
+ if (!current) {
19655
+ jsonError(res, "Model profile not found", 404);
19656
+ return true;
19657
+ }
19658
+ const body = parsed.body;
19659
+ const error = await profileValueError({
19660
+ harness: current.harness,
19661
+ model: body.model ?? current.model,
19662
+ effort: body.effort === undefined ? current.effort : body.effort,
19663
+ allowCustomModel: body.allowCustomModel ?? current.allowCustomModel
19664
+ });
19665
+ if (error) {
19666
+ jsonError(res, error, 400);
19667
+ return true;
19668
+ }
19669
+ const updated = await updateModelProfile(current.name, body, {
19670
+ userId: await resolveHttpAuditUserId(req, myAgentId)
19671
+ });
19672
+ if (!updated) {
19673
+ jsonError(res, "Model profile not found", 404);
19674
+ return true;
19675
+ }
19676
+ updateProfile.respond(res, 200, updated);
19677
+ return true;
19678
+ }
19679
+ if (deleteProfile.match(req.method, pathSegments)) {
19680
+ const parsed = await deleteProfile.parse(req, res, pathSegments, queryParams);
19681
+ if (!parsed)
19682
+ return true;
19683
+ if (!await canWriteProfiles(req, res, myAgentId))
19684
+ return true;
19685
+ const current = await getModelProfileByName(parsed.params.name);
19686
+ if (!current) {
19687
+ jsonError(res, "Model profile not found", 404);
19688
+ return true;
19689
+ }
19690
+ if (current.seededFrom?.startsWith("tier:")) {
19691
+ jsonError(res, `"${current.name}" is a shipped tier profile and comes back at the next boot; edit it instead.`, 409);
19692
+ return true;
19693
+ }
19694
+ const refs = await modelProfileReferences(current.name);
19695
+ if (refs.agentIds.length > 0 || refs.openTaskCount > 0) {
19696
+ jsonError(res, `"${current.name}" is in use: default of ${refs.agentIds.length} agent(s), held by ${refs.openTaskCount} unfinished or awaiting-resume task(s).`, 409);
19697
+ return true;
19698
+ }
19699
+ const outcome = await deleteModelProfile(current.name);
19700
+ if (outcome === "in-use") {
19701
+ jsonError(res, `"${current.name}" is in use by a task or an agent default created just now.`, 409);
19702
+ return true;
19703
+ }
19704
+ if (outcome === "missing") {
19705
+ jsonError(res, "Model profile not found", 404);
19706
+ return true;
19707
+ }
19708
+ deleteProfile.respond(res, 200, { deleted: true });
19709
+ return true;
19710
+ }
19711
+ return false;
19712
+ }
19713
+
18910
19714
  // src/http/models-catalog.ts
18911
19715
  init_zod();
18912
19716
  init_db3();
@@ -20793,6 +21597,7 @@ init_request_auth_context();
20793
21597
  // src/http/tasks.ts
20794
21598
  init_zod();
20795
21599
  init_db3();
21600
+ init_model_profiles();
20796
21601
  init_steering();
20797
21602
  init_users();
20798
21603
  init_rbac();
@@ -20989,6 +21794,7 @@ var createTask = route({
20989
21794
  modelTier: ModelTierSchema.optional(),
20990
21795
  allowCustomModel: exports_external.boolean().optional(),
20991
21796
  effort: ReasoningEffortSchema.optional(),
21797
+ profile: exports_external.string().trim().min(1).optional(),
20992
21798
  draft: exports_external.boolean().optional()
20993
21799
  }).superRefine((body, ctx) => {
20994
21800
  if ((body.agentId !== undefined || body.offeredTo !== undefined) && !body.routingReason) {
@@ -21530,6 +22336,15 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
21530
22336
  jsonError(res, modelError, 400);
21531
22337
  return true;
21532
22338
  }
22339
+ const profileError = await taskProfileError({
22340
+ profile: parsed.body.profile,
22341
+ ...splitLegacyModelAlias({ model: parsed.body.model, modelTier: parsed.body.modelTier }),
22342
+ agentId: defaultAgentId
22343
+ });
22344
+ if (profileError) {
22345
+ jsonError(res, profileError, 400);
22346
+ return true;
22347
+ }
21533
22348
  const parentTask = parsed.body.parentTaskId ? await getTaskById(parsed.body.parentTaskId) : null;
21534
22349
  const principal = await resolveTaskWritePrincipal(req, myAgentId);
21535
22350
  if (parentTask && !can({
@@ -21587,7 +22402,8 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
21587
22402
  model: parsed.body.model,
21588
22403
  modelTier: parsed.body.modelTier
21589
22404
  }),
21590
- effort: parsed.body.effort
22405
+ effort: parsed.body.effort,
22406
+ profile: parsed.body.profile
21591
22407
  }, { origin: "rest", allowCustomModel: parsed.body.allowCustomModel });
21592
22408
  ensure({
21593
22409
  id: "created",
@@ -21614,6 +22430,10 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
21614
22430
  jsonError(res, error.message, 400);
21615
22431
  return true;
21616
22432
  }
22433
+ if (modelProfileRefError(error) === "missing") {
22434
+ jsonError(res, `Unknown model profile "${parsed.body.profile}"`, 400);
22435
+ return true;
22436
+ }
21617
22437
  console.error("[HTTP] Failed to create task:", error);
21618
22438
  jsonError(res, "Failed to create task", 500);
21619
22439
  }
@@ -23813,6 +24633,33 @@ function isPageFeedbackRequested(queryParams) {
23813
24633
  const value = (queryParams.get(PAGE_FEEDBACK_PARAM) ?? "").toLowerCase();
23814
24634
  return value !== "0" && value !== "false";
23815
24635
  }
24636
+ var FEEDBACK_KEY_GUARD_JS = `
24637
+ (function () {
24638
+ if (window.__swarmFeedbackKeys) return;
24639
+ var keys = window.__swarmFeedbackKeys = { host: null, handle: null };
24640
+ var owned = {};
24641
+ function id(e) { return e.code || e.key; }
24642
+ function fromOverlay(e) {
24643
+ return !!keys.host && typeof e.composedPath === 'function' && e.composedPath().indexOf(keys.host) !== -1;
24644
+ }
24645
+ function guard(e) {
24646
+ var k = id(e);
24647
+ var mine = fromOverlay(e);
24648
+ if (!mine && !owned[k]) return;
24649
+ if (e.type === 'keydown') {
24650
+ if (mine) owned[k] = true;
24651
+ else if (!e.repeat) { delete owned[k]; return; }
24652
+ }
24653
+ if (e.type === 'keyup') delete owned[k];
24654
+ e.stopImmediatePropagation();
24655
+ if (mine && keys.handle) keys.handle(e);
24656
+ }
24657
+ ['keydown', 'keypress', 'keyup'].forEach(function (type) {
24658
+ window.addEventListener(type, guard, true);
24659
+ });
24660
+ window.addEventListener('blur', function () { owned = {}; });
24661
+ })();
24662
+ `;
23816
24663
  var FEEDBACK_OVERLAY_JS = `
23817
24664
  (function () {
23818
24665
  var cfg = window.__swarmFeedback;
@@ -23923,6 +24770,7 @@ var FEEDBACK_OVERLAY_JS = `
23923
24770
  var panel = document.createElement('div');
23924
24771
  panel.className = 'panel';
23925
24772
  var composer = null;
24773
+ var composerInput = null;
23926
24774
  root.appendChild(hl);
23927
24775
  root.appendChild(markers);
23928
24776
  root.appendChild(panel);
@@ -23995,11 +24843,25 @@ var FEEDBACK_OVERLAY_JS = `
23995
24843
  }
23996
24844
  render();
23997
24845
  }
23998
- document.addEventListener('keydown', function (e) {
23999
- if (e.key !== 'Escape') return;
24846
+ function onEscape() {
24000
24847
  if (state.draft) { closeComposer(); return; }
24001
24848
  if (state.selecting) setSelecting(false);
24849
+ }
24850
+ // Escape pressed on the page while selecting. Keys typed in the overlay never
24851
+ // get here: the key guard stops them and calls onOverlayKey instead.
24852
+ document.addEventListener('keydown', function (e) {
24853
+ if (e.key === 'Escape') onEscape();
24002
24854
  }, true);
24855
+ function onOverlayKey(e) {
24856
+ if (e.type !== 'keydown') return;
24857
+ if (e.key === 'Escape') { onEscape(); return; }
24858
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey) && composerInput && e.composedPath()[0] === composerInput) {
24859
+ e.preventDefault();
24860
+ commitDraft(composerInput.value);
24861
+ }
24862
+ }
24863
+ window.__swarmFeedbackKeys.host = host;
24864
+ window.__swarmFeedbackKeys.handle = onOverlayKey;
24003
24865
 
24004
24866
  // ─── Composer ──────────────────────────────────────────────────────────
24005
24867
  function openComposer(target) {
@@ -24018,9 +24880,7 @@ var FEEDBACK_OVERLAY_JS = `
24018
24880
  add.type = 'button';
24019
24881
  cancel.addEventListener('click', closeComposer);
24020
24882
  add.addEventListener('click', function () { commitDraft(input.value); });
24021
- input.addEventListener('keydown', function (e) {
24022
- if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); commitDraft(input.value); }
24023
- });
24883
+ composerInput = input;
24024
24884
  row.appendChild(cancel);
24025
24885
  row.appendChild(add);
24026
24886
  composer.appendChild(row);
@@ -24041,6 +24901,7 @@ var FEEDBACK_OVERLAY_JS = `
24041
24901
  function closeComposer() {
24042
24902
  if (composer) composer.remove();
24043
24903
  composer = null;
24904
+ composerInput = null;
24044
24905
  state.draft = null;
24045
24906
  hideHighlight();
24046
24907
  }
@@ -24306,7 +25167,10 @@ var FEEDBACK_OVERLAY_JS = `
24306
25167
  `;
24307
25168
  function feedbackOverlayScripts(pageId) {
24308
25169
  const config = JSON.stringify({ pageId }).replace(/</g, "\\u003c");
24309
- return `<script>window.__swarmFeedback = ${config};</script><script>${FEEDBACK_OVERLAY_JS}</script>`;
25170
+ return `<script>window.__swarmFeedback = ${config};</script><script>${FEEDBACK_KEY_GUARD_JS}${FEEDBACK_OVERLAY_JS}</script>`;
25171
+ }
25172
+ function feedbackKeyGuardScript() {
25173
+ return `<script>${FEEDBACK_KEY_GUARD_JS}</script>`;
24310
25174
  }
24311
25175
 
24312
25176
  // src/http/pages-public.ts
@@ -24414,8 +25278,8 @@ var PAGE_HEAD_DEFAULTS = `<base target="_blank">
24414
25278
  .swarm-card, swarm-diff { break-inside: avoid; }
24415
25279
  }
24416
25280
  </style>`;
24417
- function injectBrowserSdk(html) {
24418
- const injection = `${PAGE_HEAD_DEFAULTS}<script>${BROWSER_SDK_JS}</script><script>${SWARM_UI_JS}</script>`;
25281
+ function injectBrowserSdk(html, headFirst = "") {
25282
+ const injection = `${headFirst}${PAGE_HEAD_DEFAULTS}<script>${BROWSER_SDK_JS}</script><script>${SWARM_UI_JS}</script>`;
24419
25283
  const headOpenMatch = html.match(/<head\b[^>]*>/i);
24420
25284
  if (headOpenMatch) {
24421
25285
  const idx = headOpenMatch.index + headOpenMatch[0].length;
@@ -24718,7 +25582,8 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
24718
25582
  res.end();
24719
25583
  return true;
24720
25584
  }
24721
- const html = injectBrowserSdk(page.body) + (wantsPrint ? PRINT_AUTOTRIGGER_SCRIPT : "") + (isPageFeedbackRequested(queryParams) ? feedbackOverlayScripts(page.id) : "");
25585
+ const feedback = isPageFeedbackRequested(queryParams);
25586
+ const html = injectBrowserSdk(page.body, feedback ? feedbackKeyGuardScript() : "") + (wantsPrint ? PRINT_AUTOTRIGGER_SCRIPT : "") + (feedback ? feedbackOverlayScripts(page.id) : "");
24722
25587
  const headers2 = {
24723
25588
  "Content-Type": "text/html; charset=utf-8",
24724
25589
  "Cache-Control": "no-store",
@@ -24850,6 +25715,7 @@ async function fetchAllBotChannels(client) {
24850
25715
  // src/http/poll.ts
24851
25716
  init_telemetry_trigger();
24852
25717
  init_types();
25718
+ init_request_auth_context();
24853
25719
  init_runtime_pools_flag();
24854
25720
  function buildBudgetRefusedTrigger(refusal) {
24855
25721
  const trigger = {
@@ -24946,18 +25812,21 @@ async function attachmentsForTrigger(taskId) {
24946
25812
  sizeBytes: a.sizeBytes
24947
25813
  }));
24948
25814
  }
24949
- async function claimModelFields(task, agent) {
25815
+ async function claimModelFields(task, agent, auth) {
24950
25816
  const pin = taskModelPin(task);
24951
25817
  const cleared = {
24952
25818
  model: pin,
24953
25819
  resolvedModel: undefined,
24954
25820
  modelSource: undefined,
24955
- modelAlias: undefined
25821
+ modelAlias: undefined,
25822
+ resolvedProfile: undefined
24956
25823
  };
24957
25824
  try {
24958
25825
  return {
24959
25826
  ...cleared,
24960
- ...await recordClaimModelResolution({ ...task, model: pin }, agent)
25827
+ ...await recordClaimModelResolution({ ...task, model: pin }, agent, {
25828
+ includeRepoSecrets: readsRepoSecrets(auth, agent)
25829
+ })
24961
25830
  };
24962
25831
  } catch (error) {
24963
25832
  console.warn(`[/api/poll] model resolution failed for task ${task.id}:`, error);
@@ -25135,7 +26004,7 @@ async function handlePoll(req, res, pathSegments, queryParams, myAgentId) {
25135
26004
  triggerSurface: assignedTriggerSurface,
25136
26005
  task: {
25137
26006
  ...pendingTask,
25138
- ...await claimModelFields(pendingTask, agent),
26007
+ ...await claimModelFields(pendingTask, agent, getRequestAuth(req)),
25139
26008
  status: "in_progress",
25140
26009
  attachments: await attachmentsForTrigger(pendingTask.id)
25141
26010
  },
@@ -25226,7 +26095,7 @@ async function handlePoll(req, res, pathSegments, queryParams, myAgentId) {
25226
26095
  triggerSurface: claimedTriggerSurface,
25227
26096
  task: {
25228
26097
  ...claimed,
25229
- ...await claimModelFields(claimed, agent),
26098
+ ...await claimModelFields(claimed, agent, getRequestAuth(req)),
25230
26099
  attachments: await attachmentsForTrigger(claimed.id)
25231
26100
  },
25232
26101
  ...claimedRequestedBy && { requestedBy: claimedRequestedBy }
@@ -30132,7 +31001,7 @@ async function handleScriptRuns(req, res, pathSegments, queryParams, agentId) {
30132
31001
  return true;
30133
31002
  const result = await executeRawLlm(parsed.body);
30134
31003
  if (result.status === "failed") {
30135
- json(res, { error: result.error }, 500);
31004
+ jsonError(res, result.error, 500);
30136
31005
  return true;
30137
31006
  }
30138
31007
  rawLlmRoute.respond(res, 200, result.output);
@@ -30336,7 +31205,8 @@ var upsertRoute2 = route({
30336
31205
  schema: exports_external.object({
30337
31206
  name: exports_external.string(),
30338
31207
  version: exports_external.number(),
30339
- contentDeduped: exports_external.boolean()
31208
+ contentDeduped: exports_external.boolean(),
31209
+ warnings: exports_external.array(exports_external.string()).optional().describe("Advisory quickjs-executor compatibility findings. Never blocks the save.")
30340
31210
  })
30341
31211
  },
30342
31212
  400: { description: "Validation or typecheck failure" },
@@ -30496,7 +31366,12 @@ var createScriptApiRoute = route({
30496
31366
  params: idParamsSchema4,
30497
31367
  body: createScriptApiBodySchema,
30498
31368
  responses: {
30499
- 201: { description: "Endpoint created", schema: scriptApiWithSecretSchema },
31369
+ 201: {
31370
+ description: "Endpoint created",
31371
+ schema: scriptApiWithSecretSchema.extend({
31372
+ warnings: exports_external.array(exports_external.string()).optional().describe("Advisory quickjs-executor compatibility findings for the script.")
31373
+ })
31374
+ },
30500
31375
  400: { description: "Validation error or script has no owning agent" },
30501
31376
  404: { description: "Script not found" }
30502
31377
  },
@@ -30705,10 +31580,12 @@ async function handleScripts(req, res, pathSegments, queryParams, agentId) {
30705
31580
  isPromotion: Boolean(existingAgentScript)
30706
31581
  });
30707
31582
  }
31583
+ const warnings = quickJSCompatWarnings(checkQuickJSCompat(parsed.body.source, { fsMode: parsed.body.fsMode }));
30708
31584
  upsertRoute2.respond(res, 200, {
30709
31585
  name: result.script.name,
30710
31586
  version: result.script.version,
30711
- contentDeduped: result.contentDeduped
31587
+ contentDeduped: result.contentDeduped,
31588
+ ...warnings.length > 0 ? { warnings } : {}
30712
31589
  });
30713
31590
  return true;
30714
31591
  }
@@ -31005,7 +31882,11 @@ async function handleScripts(req, res, pathSegments, queryParams, agentId) {
31005
31882
  label: parsed.body.label ?? null,
31006
31883
  createdBy: await resolveHttpAuditUserId(req, agentId)
31007
31884
  });
31008
- createScriptApiRoute.respond(res, 201, endpoint);
31885
+ const warnings = quickJSCompatWarnings(checkQuickJSCompat(script.source, { fsMode: script.fsMode }));
31886
+ createScriptApiRoute.respond(res, 201, {
31887
+ ...endpoint,
31888
+ ...warnings.length > 0 ? { warnings } : {}
31889
+ });
31009
31890
  return true;
31010
31891
  }
31011
31892
  if (listScriptApisRoute.match(req.method, pathSegments)) {
@@ -36037,8 +36918,7 @@ async function handleWebhooks(req, res, pathSegments) {
36037
36918
  }
36038
36919
  agentmailWebhook.respond(res, 200, { received: true });
36039
36920
  if (payload.message && !isInboxAllowed(payload.message.inbox_id, process.env.AGENTMAIL_INBOX_DOMAIN_FILTER)) {
36040
- const domain = payload.message.inbox_id.split("@")[1] ?? "unknown";
36041
- console.log(`[AgentMail] Ignoring event for inbox domain "${domain}" (not in AGENTMAIL_INBOX_DOMAIN_FILTER)`);
36921
+ console.log(`[AgentMail] Ignoring event for inbox "${payload.message.inbox_id}" (not in AGENTMAIL_INBOX_DOMAIN_FILTER)`);
36042
36922
  return true;
36043
36923
  }
36044
36924
  if (payload.message && !isSenderAllowed(payload.message.from_, process.env.AGENTMAIL_SENDER_DOMAIN_FILTER)) {
@@ -37401,6 +38281,7 @@ var httpServer = createHttpServer(async (req, res) => {
37401
38281
  () => handleDbQuery(req, res, pathSegments, queryParams),
37402
38282
  () => handleMetrics(req, res, pathSegments, queryParams, myAgentId),
37403
38283
  () => handleModelsCatalog(req, res, pathSegments, queryParams),
38284
+ () => handleModelProfiles(req, res, pathSegments, queryParams, myAgentId),
37404
38285
  () => handleRepos(req, res, pathSegments, queryParams, myAgentId),
37405
38286
  () => handleSkills(req, res, pathSegments, queryParams, myAgentId),
37406
38287
  () => handleScriptConnections(req, res, pathSegments, queryParams, myAgentId),
@@ -37461,7 +38342,7 @@ var httpServer = createHttpServer(async (req, res) => {
37461
38342
  });
37462
38343
  });
37463
38344
  var detachRealtime = attachRealtimeTransport(httpServer);
37464
- var { attachControllerTransport } = await import("./controllers-connect-kwpvj3rf.js");
38345
+ var { attachControllerTransport } = await import("./controllers-connect-v9yn8zb9.js");
37465
38346
  var detachControllers = attachControllerTransport(httpServer);
37466
38347
  var stopWorkerNudges = startWorkerNudgeListener();
37467
38348
  var stopRoomDeletion = realtimeBus.subscribe("room:namespace-deleted", (namespace) => {
@@ -37490,7 +38371,7 @@ async function shutdown() {
37490
38371
  sessionsProcessed: getServerSessionsProcessed()
37491
38372
  });
37492
38373
  if (hasCapability("scheduling")) {
37493
- const { stopScheduler } = await import("./index-7ep4gzdk.js");
38374
+ const { stopScheduler } = await import("./index-3gtmkhc6.js");
37494
38375
  stopScheduler();
37495
38376
  }
37496
38377
  stopHeartbeat();
@@ -37502,7 +38383,7 @@ async function shutdown() {
37502
38383
  await stopSlackApp();
37503
38384
  await stopChatSdk();
37504
38385
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
37505
- const { stopOAuthKeepalive } = await import("./keepalive-be9vwy5h.js");
38386
+ const { stopOAuthKeepalive } = await import("./keepalive-psj6j54c.js");
37506
38387
  await stopOAuthKeepalive();
37507
38388
  }
37508
38389
  stopOAuthPendingGc();
@@ -37582,15 +38463,21 @@ try {
37582
38463
  console.warn("[startup] CAPABILITIES upgrade seed failed (non-fatal):", err);
37583
38464
  }
37584
38465
  try {
37585
- const { ensurePricingSeeded } = await import("./boot-seeds-kwjq4d2z.js");
38466
+ const { ensurePricingSeeded } = await import("./boot-seeds-e9sta0yt.js");
37586
38467
  ensurePricingSeeded();
37587
- const { startPricingRefreshLoop } = await import("./pricing-refresh-qb0rv0ap.js");
38468
+ const { startPricingRefreshLoop } = await import("./pricing-refresh-kefx9v3t.js");
37588
38469
  startPricingRefreshLoop();
37589
38470
  } catch (err) {
37590
38471
  console.error("[startup] Failed to seed pricing rows:", err);
37591
38472
  }
37592
38473
  try {
37593
- const { ensureRbacSeeded } = await import("./boot-seeds-kwjq4d2z.js");
38474
+ const { ensureModelProfilesSeeded } = await import("./boot-seeds-e9sta0yt.js");
38475
+ await ensureModelProfilesSeeded();
38476
+ } catch (err) {
38477
+ console.error("[startup] Failed to seed model profiles:", err);
38478
+ }
38479
+ try {
38480
+ const { ensureRbacSeeded } = await import("./boot-seeds-e9sta0yt.js");
37594
38481
  ensureRbacSeeded();
37595
38482
  } catch (err) {
37596
38483
  console.error("[startup] Failed to sync RBAC seed rows:", err);
@@ -37598,7 +38485,7 @@ try {
37598
38485
  throw err;
37599
38486
  }
37600
38487
  try {
37601
- const { runAllSeeders } = await import("./index-s2wxgzr0.js");
38488
+ const { runAllSeeders } = await import("./index-5wyfjc2c.js");
37602
38489
  await runAllSeeders({ scriptEmbeddingMode: "skip" });
37603
38490
  } catch (err) {
37604
38491
  console.error("[startup] Failed to seed built-in entities:", err);
@@ -37653,45 +38540,46 @@ httpServer.listen(port, async () => {
37653
38540
  initGitHub();
37654
38541
  initGitLab();
37655
38542
  initAzureDevOps();
38543
+ initAzureDevOpsOutbound();
37656
38544
  initAgentMail();
37657
38545
  await initLinear();
37658
38546
  await initJira();
37659
38547
  await initWorkflows();
37660
38548
  await startScriptRunSupervisor(getMcpBaseUrl());
37661
38549
  if (hasCapability("scheduling")) {
37662
- const { startScheduler } = await import("./index-7ep4gzdk.js");
37663
- const { getExecutorRegistry: getExecutorRegistry2 } = await import("./index-rmpw9a8q.js");
38550
+ const { startScheduler } = await import("./index-3gtmkhc6.js");
38551
+ const { getExecutorRegistry: getExecutorRegistry2 } = await import("./index-5e101240.js");
37664
38552
  const intervalMs = Number(process.env.SCHEDULER_INTERVAL_MS) || 1e4;
37665
38553
  startScheduler(getExecutorRegistry2(), intervalMs, {
37666
38554
  runId: globalState.__runId
37667
38555
  });
37668
38556
  }
37669
38557
  if (!isEnvFlagEnabled("HEARTBEAT_DISABLE", false)) {
37670
- const { startHeartbeat } = await import("./index-v1v6n7hg.js");
38558
+ const { startHeartbeat } = await import("./index-3k5hkz7r.js");
37671
38559
  const heartbeatMs = Number(process.env.HEARTBEAT_INTERVAL_MS) || 90000;
37672
38560
  startHeartbeat(heartbeatMs);
37673
38561
  }
37674
38562
  startPoolRouting();
37675
38563
  startRuntimeSupervisor();
37676
38564
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
37677
- const { startOAuthKeepalive } = await import("./keepalive-be9vwy5h.js");
38565
+ const { startOAuthKeepalive } = await import("./keepalive-psj6j54c.js");
37678
38566
  startOAuthKeepalive();
37679
38567
  }
37680
38568
  if (process.env.OAUTH_REFRESH_SWEEP_DISABLE !== "true") {
37681
- const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-cpdnfwdj.js");
38569
+ const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-0284h93b.js");
37682
38570
  startOAuthRefreshSweep();
37683
38571
  }
37684
38572
  startOAuthPendingGc();
37685
38573
  await startMemoryGc();
37686
38574
  await startDbRetention();
37687
38575
  startHumanFreeDrain();
37688
- import("./boot-reembed-5c2z0mbe.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
38576
+ import("./boot-reembed-whzbdthj.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
37689
38577
  console.error("[boot-reembed] startup backfill failed (non-fatal):", err);
37690
38578
  });
37691
- import("./boot-reembed-j8xa181e.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
38579
+ import("./boot-reembed-9apm9qw6.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
37692
38580
  console.error("[boot-reembed-scripts] startup backfill failed (non-fatal):", err);
37693
38581
  });
37694
- import("./boot-scrub-sweep-hnkn3rgt.js").then(({ runBootScrubSweep }) => runBootScrubSweep()).catch((err) => {
38582
+ import("./boot-scrub-sweep-ndzmvmgt.js").then(({ runBootScrubSweep }) => runBootScrubSweep()).catch((err) => {
37695
38583
  console.error("[boot-scrub-sweep] startup scrub failed (non-fatal):", err);
37696
38584
  });
37697
38585
  }).on("error", (err) => {