@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
@@ -7,9 +7,12 @@
7
7
  *
8
8
  * Precedence (first hit wins):
9
9
  * 1. task `model` → modelSource "model"
10
- * 2. the claiming worker's MODEL_TIER_* → "worker-env" (agents.modelTierOverrides)
11
- * 3. swarm_config MODEL_TIER_<PROV>_<TIER> → "tier-config"
12
- * 4. DEFAULT_MODEL_TIER_MAP → "tier-default"
10
+ * 2. task `profile` → "profile:task"
11
+ * 3. the claiming worker's MODEL_TIER_* → "worker-env" (agents.modelTierOverrides)
12
+ * 4. swarm_config MODEL_TIER_<PROV>_<TIER> → "tier-config"
13
+ * 5. DEFAULT_MODEL_TIER_MAP → "tier-default"
14
+ * 6. the agent's default profile → "profile:agent-default" (no model, no tier,
15
+ * no repo-scoped MODEL_OVERRIDE on the task's repo)
13
16
  *
14
17
  * Any layer may hold a `latest:` alias. An alias that resolves to nothing
15
18
  * (empty catalog, everything filtered) falls through to the next layer.
@@ -45,8 +48,14 @@ import {
45
48
  type ProviderName,
46
49
  ProviderNameSchema,
47
50
  parseModelTier,
51
+ type ReasoningEffort,
48
52
  } from "../types";
49
53
  import { getDbClient } from "./db";
54
+ import {
55
+ getAgentDefaultProfileName,
56
+ getModelProfileByName,
57
+ repoModelOverrideKeys,
58
+ } from "./db/model-profiles";
50
59
  import {
51
60
  CLI_PINNED_HARNESSES,
52
61
  fallbackForUnsupportedModel,
@@ -74,7 +83,9 @@ export type ModelSource =
74
83
  | "tier-config"
75
84
  | "tier-default"
76
85
  | "fallback:cli-unsupported"
77
- | "fallback:cli-unsupported:model";
86
+ | "fallback:cli-unsupported:model"
87
+ | "profile:task"
88
+ | "profile:agent-default";
78
89
 
79
90
  export interface TaskModelResolution {
80
91
  resolvedModel: string;
@@ -490,15 +501,21 @@ async function applyCliSupport(
490
501
  if (!fallback) return resolution;
491
502
  noticeCliUnsupported(harness, cliVersion, resolution.resolvedModel, fallback);
492
503
  // Keep the winning layer in the source: a fallback from the task's own `latest:` alias is
493
- // still a task-level pin (a resume carries it), one from a tier layer is not.
494
- const modelSource =
495
- resolution.modelSource === "model"
504
+ // still a task-level pin (a resume carries it), one from a tier layer is not. A profile keeps
505
+ // its own source; `resolvedProfile` still names it and `resolvedModel` shows the swap.
506
+ const modelSource = resolution.modelSource.startsWith("profile:")
507
+ ? resolution.modelSource
508
+ : resolution.modelSource === "model"
496
509
  ? "fallback:cli-unsupported:model"
497
510
  : "fallback:cli-unsupported";
498
511
  return { ...resolution, resolvedModel: fallback, modelSource };
499
512
  }
500
513
 
501
514
  export type ClaimModelFields = Partial<TaskModelResolution> & {
515
+ /** The profile claim applied (`agent_tasks.resolvedProfile`). */
516
+ resolvedProfile?: string;
517
+ /** The profile's effort, when it has one and the task set none; the worker spawns with it. */
518
+ effort?: ReasoningEffort;
502
519
  /**
503
520
  * Set when the task's explicit model is rejected by this worker's CLI, or belongs to another
504
521
  * harness family (`[model-harness-mismatch]`); the worker fails the task without a spawn.
@@ -506,6 +523,55 @@ export type ClaimModelFields = Partial<TaskModelResolution> & {
506
523
  modelUnsupported?: string;
507
524
  };
508
525
 
526
+ /**
527
+ * Resolve a model profile for a claim. Null when the profile does not apply:
528
+ * an agent default that is missing or on another harness falls through to the
529
+ * tier chain. For a task's own `profile` both are a hard stop.
530
+ */
531
+ async function resolveClaimProfile(
532
+ name: string,
533
+ source: "profile:task" | "profile:agent-default",
534
+ harness: ProviderName | null,
535
+ ): Promise<
536
+ | { profile: { name: string; effort: ReasoningEffort | null }; resolution: TaskModelResolution }
537
+ | { modelUnsupported: string }
538
+ | null
539
+ > {
540
+ const isTask = source === "profile:task";
541
+ const profile = await getModelProfileByName(name);
542
+ if (!profile) {
543
+ return isTask
544
+ ? { modelUnsupported: `[model-profile-missing] Model profile "${name}" no longer exists.` }
545
+ : null;
546
+ }
547
+ if (profile.harness !== harness) {
548
+ if (!isTask) return null;
549
+ return {
550
+ modelUnsupported: `[model-harness-mismatch] Model profile "${name}" runs on the ${profile.harness} harness, not on the ${harness ?? "unknown"} harness of this worker. Re-create the task with a ${harness ?? "matching"} profile.`,
551
+ };
552
+ }
553
+ let resolution: TaskModelResolution = {
554
+ resolvedModel: profile.model,
555
+ modelSource: source,
556
+ modelAlias: null,
557
+ };
558
+ if (isAlias(profile.model)) {
559
+ const resolved = await resolveLatestAlias(profile.model);
560
+ if (!resolved) {
561
+ if (!isTask) return null;
562
+ return {
563
+ modelUnsupported: `[model-profile-unresolved] Model profile "${name}" names ${profile.model}, which resolves to no usable model.`,
564
+ };
565
+ }
566
+ resolution = {
567
+ resolvedModel: resolved,
568
+ modelSource: source,
569
+ modelAlias: profile.model.trim().toLowerCase(),
570
+ };
571
+ }
572
+ return { profile: { name: profile.name, effort: profile.effort }, resolution };
573
+ }
574
+
509
575
  /**
510
576
  * Resolve and record the model for a task the given agent is claiming. Runs
511
577
  * inside the claim transaction. Returns the fields to merge into the trigger.
@@ -513,10 +579,77 @@ export type ClaimModelFields = Partial<TaskModelResolution> & {
513
579
  * previous session reported into the column.
514
580
  */
515
581
  export async function recordClaimModelResolution(
516
- task: { id: string; model?: string | null; modelTier?: string | null },
582
+ task: {
583
+ id: string;
584
+ model?: string | null;
585
+ modelTier?: string | null;
586
+ profile?: string | null;
587
+ effort?: string | null;
588
+ vcsRepo?: string | null;
589
+ },
517
590
  agent: { id: string; harnessProvider?: ProviderName | null; provider?: ProviderName | null },
591
+ /**
592
+ * Whether the claiming worker's config read includes secret repo rows
593
+ * (`readsRepoSecrets`). A secret repo override it never receives cannot
594
+ * beat its default profile. Defaults to true (an operator-key worker).
595
+ */
596
+ { includeRepoSecrets = true }: { includeRepoSecrets?: boolean } = {},
518
597
  ): Promise<ClaimModelFields> {
519
598
  const harnessProvider = agent.harnessProvider ?? agent.provider ?? null;
599
+
600
+ if (!task.model?.trim()) {
601
+ const taskProfile = task.profile?.trim();
602
+ const agentDefault =
603
+ taskProfile || parseModelTier(task.modelTier)
604
+ ? null
605
+ : await getAgentDefaultProfileName(agent.id);
606
+ // The agent default is agent-scoped config: a repo-scoped MODEL_OVERRIDE or
607
+ // REASONING_EFFORT_OVERRIDE on the task's repo still beats it on that axis
608
+ // (repo > agent), as it beats the agent's own override in the worker env.
609
+ // A task's own profile is the creator's pick and beats repo config.
610
+ const repoKeys = agentDefault
611
+ ? await repoModelOverrideKeys(task.vcsRepo, { includeSecrets: includeRepoSecrets })
612
+ : new Set<string>();
613
+ const defaultProfile = repoKeys.has("MODEL_OVERRIDE") ? null : agentDefault;
614
+ const picked = taskProfile
615
+ ? await resolveClaimProfile(taskProfile, "profile:task", harnessProvider)
616
+ : defaultProfile
617
+ ? await resolveClaimProfile(defaultProfile, "profile:agent-default", harnessProvider)
618
+ : null;
619
+ if (picked && "modelUnsupported" in picked)
620
+ return { modelUnsupported: picked.modelUnsupported };
621
+ if (picked) {
622
+ const checked = await applyCliSupport(
623
+ picked.resolution,
624
+ harnessProvider,
625
+ await getAgentHarnessCliVersion(agent.id),
626
+ );
627
+ // Profiles never fail fast on the CLI check (only a raw task pin does), so this is a resolution.
628
+ const resolution = "unsupported" in checked ? picked.resolution : checked;
629
+ await getDbClient().run(
630
+ "UPDATE agent_tasks SET resolvedModel = ?, modelSource = ?, modelAlias = ?, resolvedProfile = ? WHERE id = ?",
631
+ [
632
+ resolution.resolvedModel,
633
+ resolution.modelSource,
634
+ resolution.modelAlias,
635
+ picked.profile.name,
636
+ task.id,
637
+ ],
638
+ );
639
+ return {
640
+ resolvedModel: resolution.resolvedModel,
641
+ modelSource: resolution.modelSource,
642
+ ...(resolution.modelAlias ? { modelAlias: resolution.modelAlias } : {}),
643
+ resolvedProfile: picked.profile.name,
644
+ // A task's own effort stays the pin; the profile's fills only an unset one,
645
+ // and an agent default's yields to the repo's effort override.
646
+ ...(!task.effort && picked.profile.effort && !repoKeys.has("REASONING_EFFORT_OVERRIDE")
647
+ ? { effort: picked.profile.effort }
648
+ : {}),
649
+ };
650
+ }
651
+ }
652
+
520
653
  const resolved = await resolveTaskModel({
521
654
  model: task.model,
522
655
  modelTier: task.modelTier,
@@ -527,8 +660,9 @@ export async function recordClaimModelResolution(
527
660
  // A re-claimed row keeps the claim record of its previous run; drop it so
528
661
  // the trigger and the row do not replay that run's model.
529
662
  await getDbClient().run(
530
- `UPDATE agent_tasks SET resolvedModel = NULL, modelSource = NULL, modelAlias = NULL
531
- WHERE id = ? AND (resolvedModel IS NOT NULL OR modelSource IS NOT NULL OR modelAlias IS NOT NULL)`,
663
+ `UPDATE agent_tasks SET resolvedModel = NULL, modelSource = NULL, modelAlias = NULL, resolvedProfile = NULL
664
+ WHERE id = ? AND (resolvedModel IS NOT NULL OR modelSource IS NOT NULL OR modelAlias IS NOT NULL
665
+ OR resolvedProfile IS NOT NULL)`,
532
666
  [task.id],
533
667
  );
534
668
  return {};
@@ -544,7 +678,7 @@ export async function recordClaimModelResolution(
544
678
  }
545
679
  const resolution = checked;
546
680
  await getDbClient().run(
547
- "UPDATE agent_tasks SET resolvedModel = ?, modelSource = ?, modelAlias = ? WHERE id = ?",
681
+ "UPDATE agent_tasks SET resolvedModel = ?, modelSource = ?, modelAlias = ?, resolvedProfile = NULL WHERE id = ?",
548
682
  [resolution.resolvedModel, resolution.modelSource, resolution.modelAlias, task.id],
549
683
  );
550
684
  return resolution.modelAlias
@@ -32,6 +32,7 @@ import {
32
32
  import { type TaskModelPinFields, taskModelPin } from "../tasks/model-pin";
33
33
  import { ampModelError, ampPinCatalogError, ampPinProviderError } from "../utils/amp-models";
34
34
  import { getAgentById, getAllAgents } from "./db";
35
+ import { getModelProfileByName } from "./db/model-profiles";
35
36
  import { loadModelsCatalog } from "./model-catalog-store";
36
37
  import { resolveLatestAlias } from "./model-tier-resolution";
37
38
 
@@ -100,9 +101,15 @@ export async function harnessModelErrorFor(
100
101
  * stays sync and catalog-free; this runs next to it.
101
102
  */
102
103
  export async function poolTaskRunsOnHarness(
103
- task: TaskModelPinFields | null | undefined,
104
+ task: (TaskModelPinFields & { profile?: string | null }) | null | undefined,
104
105
  harness: string | null | undefined,
105
106
  ): Promise<boolean> {
107
+ // A task's model profile runs on one harness only; other workers leave it in the pool.
108
+ const profileName = task?.profile?.trim();
109
+ if (profileName) {
110
+ const profile = await getModelProfileByName(profileName);
111
+ return !profile || !harness || profile.harness === harness;
112
+ }
106
113
  // Only the task's own pin restricts harnesses; a model a previous run
107
114
  // reported into `model` does not.
108
115
  const model = taskModelPin(task ?? {})?.trim();
@@ -232,6 +232,8 @@ export interface SwarmSdk {
232
232
  // --- config ---
233
233
  config_get(args?: { agentId?: string; repoId?: string; key?: string; includeSecrets?: boolean }): Promise<unknown>;
234
234
  config_list(args?: { scope?: "global" | "agent" | "repo"; scopeId?: string; key?: string; includeSecrets?: boolean }): Promise<unknown>;
235
+ // --- model profiles ---
236
+ modelProfile_list(args?: { harness?: string }): Promise<unknown>;
235
237
  // --- slack ---
236
238
  slack_read(args?: { inboxMessageId?: string; taskId?: string; channelId?: string; threadTs?: string; limit?: number; includeFiles?: boolean }): Promise<unknown>;
237
239
  slack_listChannels(args?: { types?: Array<"public" | "private" | "dm" | "mpim">; limit?: number }): Promise<unknown>;
@@ -439,6 +441,16 @@ export interface SwarmSdk {
439
441
  // --- human input ---
440
442
  request_humanInput(args: Record<string, unknown>): Promise<unknown>;
441
443
  approval_cancel(args: { requestId: string; reason?: string }): Promise<unknown>;
444
+
445
+ // --- decisions ---
446
+ systemOne_decide(args: {
447
+ provider?: "typesafe" | "openrouter" | "laya" | "openai" | "cloudflare";
448
+ model?: string;
449
+ state: string | string[] | Record<string, unknown>;
450
+ questions: Record<string, Record<string, unknown>>;
451
+ timeoutMs?: number;
452
+ maxRetries?: number;
453
+ }): Promise<unknown>;
442
454
  }
443
455
 
444
456
  export interface ScriptStdlib {
@@ -77,8 +77,12 @@ export function encodedForms(value: string): string[] {
77
77
  /** Register a stored secret value, plus its encoded forms, with the scrubber. */
78
78
  export function registerStoredSecret(value: string | null | undefined, name: string): void {
79
79
  if (!value) return;
80
- registerVolatileSecret(value, name);
81
- for (const form of encodedForms(value)) registerVolatileSecret(form, name);
80
+ // Config values are hydrated verbatim but consumed trimmed.
81
+ for (const candidate of new Set([value, value.trim()])) {
82
+ if (!candidate) continue;
83
+ registerVolatileSecret(candidate, name);
84
+ for (const form of encodedForms(candidate)) registerVolatileSecret(form, name);
85
+ }
82
86
  }
83
87
 
84
88
  export interface SecretRegistryLoadResult {
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Boot seed for the shipped model profiles: one `<harness>-<tier>` profile per
3
+ * harness and tier, a 1:1 copy of `DEFAULT_MODEL_TIER_MAP` (runbooks/model-tiers.md
4
+ * § Model profiles). A global `MODEL_TIER_<HARNESS>_<TIER>` row, when one exists,
5
+ * overrides the seed's model at seeding time. Seeds use the harness default
6
+ * route, no effort and no fallbacks.
7
+ *
8
+ * Idempotent: an existing name is never overwritten, so an operator's edit to a
9
+ * seed survives every boot. Harnesses with no portable tier mapping (acp) get
10
+ * no seeds.
11
+ */
12
+ import { DEFAULT_MODEL_TIER_MAP, MODEL_TIERS, ProviderNameSchema } from "../types";
13
+ import { createModelProfile } from "./db/model-profiles";
14
+ import { getDbClient } from "./db/runtime";
15
+ import { tierConfigKey } from "./model-tier-keys";
16
+
17
+ export async function seedModelProfiles(): Promise<number> {
18
+ let inserted = 0;
19
+ for (const harness of ProviderNameSchema.options) {
20
+ for (const tier of MODEL_TIERS) {
21
+ const fallback = DEFAULT_MODEL_TIER_MAP[harness]?.[tier];
22
+ if (!fallback) continue;
23
+ const configured = await getDbClient().get<{ value: string }>(
24
+ "SELECT value FROM swarm_config WHERE scope = 'global' AND scopeId IS NULL AND key = ? AND isSecret = 0",
25
+ [tierConfigKey(harness, tier)],
26
+ );
27
+ const created = await createModelProfile({
28
+ name: `${harness}-${tier}`,
29
+ harness,
30
+ model: configured?.value.trim() || fallback,
31
+ effort: null,
32
+ // Tier values are free-form today (a `latest:` alias, a gateway id); keep that.
33
+ allowCustomModel: true,
34
+ description: `The ${tier} tier on ${harness}, seeded from the tier map.`,
35
+ seededFrom: `tier:${tier}`,
36
+ });
37
+ if (created) inserted++;
38
+ }
39
+ }
40
+ return inserted;
41
+ }