@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
@@ -10,7 +10,7 @@
10
10
  import { pathToFileURL } from "node:url";
11
11
  import { toJSONSchema } from "zod";
12
12
 
13
- async function createShims(tmpdir: string): Promise<void> {
13
+ async function createShims(moduleDir: string): Promise<void> {
14
14
  const runtimeDir = process.env.SCRIPT_RUNTIME_DIR;
15
15
  const shims: [string, URL][] = [
16
16
  [
@@ -35,7 +35,7 @@ async function createShims(tmpdir: string): Promise<void> {
35
35
  ]),
36
36
  ];
37
37
  for (const [name, url] of shims) {
38
- const dir = `${tmpdir}/node_modules/${name}`;
38
+ const dir = `${moduleDir}/node_modules/${name}`;
39
39
  await Bun.$`mkdir -p ${dir}`.quiet();
40
40
  await Bun.write(`${dir}/package.json`, JSON.stringify({ type: "module", main: "index.ts" }));
41
41
  await Bun.write(`${dir}/index.ts`, `export * from ${JSON.stringify(url.href)};\n`);
@@ -52,14 +52,15 @@ if (!sourceFile || !resultFile || !tmpdir) {
52
52
  }
53
53
 
54
54
  try {
55
- await createShims(tmpdir);
55
+ // Subdirectory, not the tmpdir itself: this harness runs with cwd = tmpdir,
56
+ // and Bun (>= 1.3.12) snapshots the cwd listing at startup — a file or
57
+ // node_modules written into cwd after launch is invisible to the module
58
+ // resolver. See the same fix in eval-harness.ts.
59
+ const moduleDir = `${tmpdir}/user-module`;
60
+ await createShims(moduleDir);
56
61
 
57
62
  const source = await Bun.file(sourceFile).text();
58
- // Subdirectory, not the tmpdir itself: this harness runs with cwd = tmpdir,
59
- // and Bun (>= 1.3.12) snapshots the cwd listing at startup — a file written
60
- // into cwd after launch is invisible to the module resolver. See the same
61
- // fix in eval-harness.ts.
62
- const userModulePath = `${tmpdir}/user-module/user-script.ts`;
63
+ const userModulePath = `${moduleDir}/user-script.ts`;
63
64
  await Bun.write(userModulePath, source);
64
65
 
65
66
  let mod: Record<string, unknown>;
@@ -0,0 +1,464 @@
1
+ import ts from "typescript";
2
+
3
+ /**
4
+ * Flags script source that would fail, or silently differ, under the quickjs
5
+ * executor. It is advisory: callers attach the findings as warnings and never
6
+ * reject on them.
7
+ */
8
+ export type QuickJSCompatRule =
9
+ | "bunGlobal"
10
+ | "process"
11
+ | "fsStdlib"
12
+ | "require"
13
+ | "workspaceRw"
14
+ | "unsupportedGlobal"
15
+ | "cryptoSubtle"
16
+ | "responseBody"
17
+ | "localeFormat"
18
+ | "hostCredential"
19
+ | "privateHost";
20
+
21
+ export type QuickJSCompatFinding = {
22
+ rule: QuickJSCompatRule;
23
+ message: string;
24
+ /** 1-based line in the script source, when the finding has one. */
25
+ line?: number;
26
+ };
27
+
28
+ export type QuickJSCompatReport = {
29
+ compatible: boolean;
30
+ findings: QuickJSCompatFinding[];
31
+ };
32
+
33
+ // Globals that Bun provides and the quickjs sandbox does not (measured against
34
+ // the QuickJS build plus quickjs-prelude.ts and quickjs-web-shims.ts).
35
+ const UNSUPPORTED_GLOBALS = new Set([
36
+ "AbortController",
37
+ "AbortSignal",
38
+ "Blob",
39
+ "Buffer",
40
+ "Event",
41
+ "EventTarget",
42
+ "File",
43
+ "FormData",
44
+ "Intl",
45
+ "ReadableStream",
46
+ "Request",
47
+ "TransformStream",
48
+ "WebSocket",
49
+ "WritableStream",
50
+ "clearImmediate",
51
+ "navigator",
52
+ "performance",
53
+ "queueMicrotask",
54
+ "setImmediate",
55
+ "structuredClone",
56
+ ]);
57
+ const RESPONSE_BODY_METHODS = new Set(["arrayBuffer", "blob", "formData", "bytes"]);
58
+ const LOCALE_METHODS = new Set(["toLocaleString", "toLocaleDateString", "toLocaleTimeString"]);
59
+ const FS_STDLIB = new Set(["glob", "grep"]);
60
+ // ctx.swarm.config values that quickjs keeps host-side (see quickjs-runner.ts).
61
+ const HOST_ONLY_CONFIG = new Set(["apiKey", "runtimeInstanceId"]);
62
+ const MAX_FINDINGS = 20;
63
+
64
+ const SCRIPT_FILE = "user-script.ts";
65
+
66
+ /** The initializer of the `const` binding a name reads; null for anything else. */
67
+ type ConstInitializer = (node: ts.Identifier) => ts.Expression | null;
68
+ // Follows at most this many consts, as in `const API = "..."; const url = `${API}/x``.
69
+ const MAX_CONST_DEPTH = 4;
70
+
71
+ /** True inside a `declare` statement, namespace or global block. */
72
+ function isAmbient(node: ts.Node): boolean {
73
+ for (let current: ts.Node | undefined = node; current; current = current.parent) {
74
+ if (
75
+ ts.canHaveModifiers(current) &&
76
+ ts.getModifiers(current)?.some((modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword)
77
+ ) {
78
+ return true;
79
+ }
80
+ }
81
+ return false;
82
+ }
83
+
84
+ /**
85
+ * Parses and binds the script with the TypeScript checker (no lib, no imports),
86
+ * so a name resolves by lexical scope: a `Buffer` parameter in one function does
87
+ * not hide a global `Buffer` read in another. A name declared only with
88
+ * `declare` (ambient) still counts as a global, because nothing defines it at run time.
89
+ */
90
+ function createScope(source: string): {
91
+ sourceFile: ts.SourceFile;
92
+ isLocal: (node: ts.Identifier) => boolean;
93
+ constInitializer: ConstInitializer;
94
+ } {
95
+ const sourceFile = ts.createSourceFile(SCRIPT_FILE, source, ts.ScriptTarget.Latest, true);
96
+ const host: ts.CompilerHost = {
97
+ getSourceFile: (name) => (name === SCRIPT_FILE ? sourceFile : undefined),
98
+ getDefaultLibFileName: () => "lib.d.ts",
99
+ writeFile: () => {},
100
+ getCurrentDirectory: () => "/",
101
+ getCanonicalFileName: (name) => name,
102
+ useCaseSensitiveFileNames: () => true,
103
+ getNewLine: () => "\n",
104
+ fileExists: (name) => name === SCRIPT_FILE,
105
+ readFile: () => undefined,
106
+ };
107
+ const program = ts.createProgram({
108
+ rootNames: [SCRIPT_FILE],
109
+ options: { noLib: true, noResolve: true, types: [], target: ts.ScriptTarget.Latest },
110
+ host,
111
+ });
112
+ const checker = program.getTypeChecker();
113
+ const isLocal = (node: ts.Identifier) => {
114
+ const symbol =
115
+ ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node
116
+ ? checker.getShorthandAssignmentValueSymbol(node.parent)
117
+ : checker.getSymbolAtLocation(node);
118
+ return Boolean(
119
+ symbol?.declarations?.some(
120
+ (declaration) => declaration.getSourceFile() === sourceFile && !isAmbient(declaration),
121
+ ),
122
+ );
123
+ };
124
+ const constInitializer: ConstInitializer = (node) => {
125
+ const declaration = checker.getSymbolAtLocation(node)?.valueDeclaration;
126
+ if (!declaration || !ts.isVariableDeclaration(declaration) || !declaration.initializer) {
127
+ return null;
128
+ }
129
+ return ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const
130
+ ? declaration.initializer
131
+ : null;
132
+ };
133
+ return { sourceFile, isLocal, constInitializer };
134
+ }
135
+
136
+ /** True when `node` is read as a value, not as a property name or declaration. */
137
+ function isValueReference(node: ts.Identifier): boolean {
138
+ const parent = node.parent;
139
+ if (ts.isPropertyAccessExpression(parent) && parent.name === node) return false;
140
+ if (ts.isShorthandPropertyAssignment(parent)) return true;
141
+ if ("name" in parent && (parent as { name?: ts.Node }).name === node) return false;
142
+ if (ts.isTypeOfExpression(parent)) return false;
143
+ return true;
144
+ }
145
+
146
+ function isTypeOnly(node: ts.Node): boolean {
147
+ return (
148
+ ts.isTypeNode(node) ||
149
+ ts.isInterfaceDeclaration(node) ||
150
+ ts.isTypeAliasDeclaration(node) ||
151
+ (ts.isImportDeclaration(node) && Boolean(node.importClause?.isTypeOnly))
152
+ );
153
+ }
154
+
155
+ function stdlibImportNames(node: ts.ImportDeclaration): string[] {
156
+ if (!ts.isStringLiteral(node.moduleSpecifier) || node.moduleSpecifier.text !== "stdlib")
157
+ return [];
158
+ const bindings = node.importClause?.namedBindings;
159
+ if (!bindings || !ts.isNamedImports(bindings)) return [];
160
+ return bindings.elements
161
+ .map((element) => (element.propertyName ?? element.name).text)
162
+ .filter((name) => FS_STDLIB.has(name));
163
+ }
164
+
165
+ /** Strips parentheses, `as`, `!` and `satisfies`: `(globalThis as any)` is `globalThis`. */
166
+ function unwrap(node: ts.Expression): ts.Expression {
167
+ let current = node;
168
+ while (
169
+ ts.isParenthesizedExpression(current) ||
170
+ ts.isAsExpression(current) ||
171
+ ts.isNonNullExpression(current) ||
172
+ ts.isTypeAssertionExpression(current) ||
173
+ ts.isSatisfiesExpression(current)
174
+ ) {
175
+ current = current.expression;
176
+ }
177
+ return current;
178
+ }
179
+
180
+ function isGlobalThis(node: ts.Expression): boolean {
181
+ const inner = unwrap(node);
182
+ return ts.isIdentifier(inner) && inner.text === "globalThis";
183
+ }
184
+
185
+ /** `globalThis.X` or `X` (free), as the global's name; otherwise null. */
186
+ function globalName(node: ts.Expression, isLocal: (node: ts.Identifier) => boolean): string | null {
187
+ const inner = unwrap(node);
188
+ if (ts.isIdentifier(inner)) return isLocal(inner) ? null : inner.text;
189
+ if (ts.isPropertyAccessExpression(inner) && isGlobalThis(inner.expression)) {
190
+ return inner.name.text;
191
+ }
192
+ return null;
193
+ }
194
+
195
+ function isPrivateIPv4([a = 0, b = 0, c = 0]: number[]): boolean {
196
+ return (
197
+ a === 0 ||
198
+ a === 10 ||
199
+ a === 127 ||
200
+ (a === 100 && b >= 64 && b <= 127) ||
201
+ (a === 169 && b === 254) ||
202
+ (a === 172 && b >= 16 && b <= 31) ||
203
+ (a === 192 && b === 168) ||
204
+ (a === 192 && b === 0 && c === 0) ||
205
+ (a === 198 && (b === 18 || b === 19)) ||
206
+ a >= 224
207
+ );
208
+ }
209
+
210
+ // `text` is a WHATWG-serialized IPv6 address without brackets: hex groups, at most one "::".
211
+ function isPrivateIPv6(text: string): boolean {
212
+ const [head = "", tail = ""] = text.split("::");
213
+ const left = head ? head.split(":") : [];
214
+ const right = text.includes("::") && tail ? tail.split(":") : [];
215
+ const fill = text.includes("::") ? 8 - left.length - right.length : 0;
216
+ const g = [...left, ...Array<string>(fill).fill("0"), ...right].map((part) => parseInt(part, 16));
217
+ const g0 = g[0] ?? 0;
218
+ if (g.slice(0, 6).every((part) => part === 0)) return true; // ::/96: ::, ::1
219
+ if (g.slice(0, 5).every((part) => part === 0) && g[5] === 0xffff) {
220
+ const [g6 = 0, g7 = 0] = g.slice(6);
221
+ return isPrivateIPv4([g6 >> 8, g6 & 255, g7 >> 8]); // IPv4-mapped
222
+ }
223
+ return (
224
+ (g0 & 0xfe00) === 0xfc00 || // fc00::/7 unique local
225
+ (g0 & 0xffc0) === 0xfe80 || // fe80::/10 link-local
226
+ (g0 & 0xffc0) === 0xfec0 || // fec0::/10 site-local
227
+ (g0 & 0xff00) === 0xff00 // ff00::/8 multicast
228
+ );
229
+ }
230
+
231
+ /**
232
+ * How the default egress policy sees a parsed URL hostname, statically:
233
+ * "private" for loopback names and private, link-local or reserved IP literals,
234
+ * "single-label" for a name with no dot (usually an internal service name that
235
+ * resolves to a private address). Null for anything else, including public names
236
+ * that resolve privately: only the runtime check sees DNS.
237
+ */
238
+ function staticHostClass(hostname: string): "private" | "single-label" | null {
239
+ const host = hostname.toLowerCase().replace(/\.$/, "");
240
+ if (host === "localhost" || host.endsWith(".localhost")) return "private";
241
+ if (host.startsWith("[")) return isPrivateIPv6(host.slice(1, -1)) ? "private" : null;
242
+ const v4 = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(host);
243
+ if (v4) return isPrivateIPv4(v4.slice(1).map(Number)) ? "private" : null;
244
+ return host !== "" && !host.includes(".") ? "single-label" : null;
245
+ }
246
+
247
+ /** The fetch-like call this callee names: fetch, globalThis.fetch, ctx.stdlib.fetch or fetchJson. */
248
+ function fetchCallName(callee: ts.Expression): string | null {
249
+ const inner = unwrap(callee);
250
+ if (ts.isIdentifier(inner)) {
251
+ return inner.text === "fetch" || inner.text === "fetchJson" ? inner.text : null;
252
+ }
253
+ if (!ts.isPropertyAccessExpression(inner)) return null;
254
+ const method = inner.name.text;
255
+ if (method !== "fetch" && method !== "fetchJson") return null;
256
+ const owner = unwrap(inner.expression);
257
+ if (method === "fetch" && isGlobalThis(owner)) return "fetch";
258
+ const ownerName = ts.isIdentifier(owner)
259
+ ? owner.text
260
+ : ts.isPropertyAccessExpression(owner)
261
+ ? owner.name.text
262
+ : null;
263
+ return ownerName === "stdlib" ? `ctx.stdlib.${method}` : null;
264
+ }
265
+
266
+ /**
267
+ * The fixed start of a URL argument: a literal, a const holding one, or a
268
+ * template or `+` concatenation that starts with them, through up to
269
+ * MAX_CONST_DEPTH consts. `cut` is true when the
270
+ * text stops at a dynamic part that could still extend the host, as in
271
+ * `http://intra${suffix}`; a dynamic part right after a const value cannot.
272
+ */
273
+ function staticPrefix(
274
+ node: ts.Expression,
275
+ resolve: ConstInitializer,
276
+ depth = 0,
277
+ ): { text: string; cut: boolean } | null {
278
+ const inner = unwrap(node);
279
+ if (ts.isStringLiteral(inner) || ts.isNoSubstitutionTemplateLiteral(inner)) {
280
+ return { text: inner.text, cut: false };
281
+ }
282
+ if (ts.isIdentifier(inner)) {
283
+ const initializer = depth < MAX_CONST_DEPTH ? resolve(inner) : null;
284
+ return initializer ? staticPrefix(initializer, resolve, depth + 1) : null;
285
+ }
286
+ if (ts.isTemplateExpression(inner)) {
287
+ let text = inner.head.text;
288
+ let boundary = false;
289
+ for (const span of inner.templateSpans) {
290
+ const part = staticPrefix(span.expression, resolve, depth);
291
+ if (part === null) return { text, cut: !boundary || text === "" };
292
+ if (part.cut) return { text: text + part.text, cut: true };
293
+ text += part.text + span.literal.text;
294
+ boundary = span.literal.text === "";
295
+ }
296
+ return { text, cut: false };
297
+ }
298
+ if (ts.isBinaryExpression(inner) && inner.operatorToken.kind === ts.SyntaxKind.PlusToken) {
299
+ const left = staticPrefix(inner.left, resolve, depth);
300
+ if (left === null || left.cut) return left;
301
+ const right = staticPrefix(inner.right, resolve, depth);
302
+ if (right === null) return { text: left.text, cut: !ts.isIdentifier(unwrap(inner.left)) };
303
+ return { text: left.text + right.text, cut: right.cut };
304
+ }
305
+ return null;
306
+ }
307
+
308
+ /** The URL argument's host when the source fixes it, parsed with the host URL parser. */
309
+ function literalUrl(arg: ts.Expression | undefined, resolve: ConstInitializer): URL | null {
310
+ if (!arg) return null;
311
+ const prefix = staticPrefix(arg, resolve);
312
+ if (!prefix) return null;
313
+ // A cut prefix fixes the host only once the authority has ended.
314
+ if (prefix.cut && !/^[a-z][a-z0-9+.-]*:\/\/[^/?#]*[/?#]/i.test(prefix.text)) return null;
315
+ try {
316
+ return new URL(prefix.text);
317
+ } catch {
318
+ return null;
319
+ }
320
+ }
321
+
322
+ export function checkQuickJSCompat(
323
+ source: string,
324
+ options: { fsMode?: string } = {},
325
+ ): QuickJSCompatReport {
326
+ const findings: QuickJSCompatFinding[] = [];
327
+ const seen = new Set<string>();
328
+ const { sourceFile, isLocal, constInitializer } = createScope(source);
329
+ const add = (rule: QuickJSCompatRule, message: string, node?: ts.Node) => {
330
+ if (seen.has(`${rule}:${message}`)) return;
331
+ seen.add(`${rule}:${message}`);
332
+ const line = node
333
+ ? sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1
334
+ : undefined;
335
+ findings.push(line === undefined ? { rule, message } : { rule, message, line });
336
+ };
337
+
338
+ if (options.fsMode === "workspace-rw") {
339
+ add("workspaceRw", "fsMode workspace-rw is not available under quickjs");
340
+ }
341
+
342
+ const reportGlobal = (name: string, node: ts.Node) => {
343
+ if (name === "Bun") add("bunGlobal", "Bun.* is not available under quickjs", node);
344
+ else if (name === "process") add("process", "process is not available under quickjs", node);
345
+ else if (UNSUPPORTED_GLOBALS.has(name)) {
346
+ add("unsupportedGlobal", `${name} is not available under quickjs`, node);
347
+ }
348
+ };
349
+
350
+ const visit = (node: ts.Node) => {
351
+ if (isTypeOnly(node)) return;
352
+
353
+ if (
354
+ ts.isIdentifier(node) &&
355
+ (node.text === "Bun" || node.text === "process" || UNSUPPORTED_GLOBALS.has(node.text)) &&
356
+ isValueReference(node) &&
357
+ !isLocal(node)
358
+ ) {
359
+ reportGlobal(node.text, node);
360
+ }
361
+
362
+ if (ts.isPropertyAccessExpression(node)) {
363
+ const member = node.name.text;
364
+ const owner = globalName(node.expression, isLocal);
365
+ if (isGlobalThis(node.expression) && !ts.isTypeOfExpression(node.parent)) {
366
+ reportGlobal(member, node);
367
+ }
368
+ const parent = unwrap(node.expression);
369
+ if (
370
+ HOST_ONLY_CONFIG.has(member) &&
371
+ ts.isPropertyAccessExpression(parent) &&
372
+ parent.name.text === "config"
373
+ ) {
374
+ add(
375
+ "hostCredential",
376
+ `ctx.swarm.config.${member} is host-only under quickjs; ctx.swarm calls use it for you`,
377
+ node,
378
+ );
379
+ }
380
+ if (member === "subtle" && owner === "crypto") {
381
+ add("cryptoSubtle", "crypto.subtle is not available under quickjs", node);
382
+ }
383
+ if (
384
+ FS_STDLIB.has(member) &&
385
+ ts.isPropertyAccessExpression(unwrap(node.expression)) &&
386
+ (unwrap(node.expression) as ts.PropertyAccessExpression).name.text === "stdlib"
387
+ ) {
388
+ add("fsStdlib", `ctx.stdlib.${member} needs filesystem access, which quickjs lacks`, node);
389
+ }
390
+ }
391
+
392
+ if (ts.isImportDeclaration(node)) {
393
+ for (const name of stdlibImportNames(node)) {
394
+ add("fsStdlib", `stdlib ${name} needs filesystem access, which quickjs lacks`, node);
395
+ }
396
+ }
397
+
398
+ if (
399
+ ts.isVariableDeclaration(node) &&
400
+ ts.isObjectBindingPattern(node.name) &&
401
+ node.initializer &&
402
+ ts.isPropertyAccessExpression(node.initializer) &&
403
+ node.initializer.name.text === "stdlib"
404
+ ) {
405
+ for (const element of node.name.elements) {
406
+ const name = (element.propertyName ?? element.name).getText(sourceFile);
407
+ if (FS_STDLIB.has(name)) {
408
+ add("fsStdlib", `ctx.stdlib.${name} needs filesystem access, which quickjs lacks`, node);
409
+ }
410
+ }
411
+ }
412
+
413
+ if (ts.isCallExpression(node)) {
414
+ const callee = node.expression;
415
+ const fetchName = fetchCallName(callee);
416
+ const url = fetchName ? literalUrl(node.arguments[0], constInitializer) : null;
417
+ const hostClass =
418
+ url && (url.protocol === "http:" || url.protocol === "https:")
419
+ ? staticHostClass(url.hostname)
420
+ : null;
421
+ if (url && hostClass) {
422
+ add(
423
+ "privateHost",
424
+ `${fetchName} to ${url.host} (${hostClass} host) depends on the egress policy: quickjs refuses private hosts unless the operator allows them`,
425
+ node,
426
+ );
427
+ }
428
+ if (ts.isIdentifier(callee) && callee.text === "require" && !isLocal(callee)) {
429
+ add("require", "require() is not supported; use an import", node);
430
+ }
431
+ if (ts.isPropertyAccessExpression(callee)) {
432
+ const method = callee.name.text;
433
+ if (RESPONSE_BODY_METHODS.has(method) && node.arguments.length === 0) {
434
+ add(
435
+ "responseBody",
436
+ `.${method}() on a fetch Response is not available under quickjs (text() and json() are)`,
437
+ node,
438
+ );
439
+ }
440
+ if (LOCALE_METHODS.has(method) && node.arguments.length > 0) {
441
+ add(
442
+ "localeFormat",
443
+ `${method}(locale, options) ignores its arguments under quickjs (no Intl)`,
444
+ node,
445
+ );
446
+ }
447
+ }
448
+ }
449
+
450
+ ts.forEachChild(node, visit);
451
+ };
452
+ visit(sourceFile);
453
+
454
+ return { compatible: findings.length === 0, findings: findings.slice(0, MAX_FINDINGS) };
455
+ }
456
+
457
+ /** One human-readable line per finding, for `warnings` arrays. */
458
+ export function quickJSCompatWarnings(report: QuickJSCompatReport, label?: string): string[] {
459
+ const prefix = label ? `${label}: ` : "";
460
+ return report.findings.map(
461
+ (finding) =>
462
+ `${prefix}quickjs compat (${finding.rule}${finding.line ? `, line ${finding.line}` : ""}): ${finding.message}`,
463
+ );
464
+ }
@@ -89,6 +89,7 @@ export const SDK_TOOL_NAME_MAP = {
89
89
  // ── model catalog ──
90
90
  modelCatalog_refresh: "model-catalog-refresh", // external: fetches models.dev
91
91
  modelCatalog_overlayUpsert: "model-catalog-overlay-upsert",
92
+ modelProfile_list: "list-model-profiles",
92
93
 
93
94
  // ── slack ──
94
95
  slack_read: "slack-read",
@@ -189,6 +190,9 @@ export const SDK_TOOL_NAME_MAP = {
189
190
  // ── human input ──
190
191
  request_humanInput: "request-human-input",
191
192
  approval_cancel: "cancel-approval-request", // destructive
193
+
194
+ // ── decisions ──
195
+ systemOne_decide: "system-one-decide",
192
196
  } as const;
193
197
 
194
198
  export const SDK_ALLOWLIST = Object.keys(SDK_TOOL_NAME_MAP) as Array<
@@ -329,6 +329,8 @@ declare module "swarm-sdk" {
329
329
  key?: string;
330
330
  includeSecrets?: boolean;
331
331
  }): Promise<unknown>;
332
+ // --- model profiles ---
333
+ modelProfile_list(args?: { harness?: string }): Promise<unknown>;
332
334
  // --- slack ---
333
335
  slack_read(args?: {
334
336
  inboxMessageId?: string;
@@ -678,6 +680,16 @@ declare module "swarm-sdk" {
678
680
  // --- human input ---
679
681
  request_humanInput(args: Record<string, unknown>): Promise<unknown>;
680
682
  approval_cancel(args: { requestId: string; reason?: string }): Promise<unknown>;
683
+
684
+ // --- decisions ---
685
+ systemOne_decide(args: {
686
+ provider?: "typesafe" | "openrouter" | "laya" | "openai" | "cloudflare";
687
+ model?: string;
688
+ state: string | string[] | Record<string, unknown>;
689
+ questions: Record<string, Record<string, unknown>>;
690
+ timeoutMs?: number;
691
+ maxRetries?: number;
692
+ }): Promise<unknown>;
681
693
  }
682
694
 
683
695
  export interface ScriptStdlib {
@@ -60,6 +60,7 @@ declare module "swarm-extension" {
60
60
  model?: string | undefined;
61
61
  modelTier?: "smol" | "regular" | "smart" | "ultra" | undefined;
62
62
  effort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
63
+ profile?: string | undefined;
63
64
  scheduleId?: string | undefined;
64
65
  workflowRunId?: string | undefined;
65
66
  workflowRunStepId?: string | undefined;
@@ -194,6 +195,8 @@ declare module "swarm-extension" {
194
195
  modelSource?: string | undefined;
195
196
  modelAlias?: string | undefined;
196
197
  resolvedEffort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
198
+ profile?: string | undefined;
199
+ resolvedProfile?: string | undefined;
197
200
  scheduleId?: string | undefined;
198
201
  workflowRunId?: string | null | undefined;
199
202
  workflowRunStepId?: string | null | undefined;
@@ -311,6 +311,8 @@ declare module "swarm-sdk" {
311
311
  key?: string;
312
312
  includeSecrets?: boolean;
313
313
  }): Promise<unknown>;
314
+ // --- model profiles ---
315
+ modelProfile_list(args?: { harness?: string }): Promise<unknown>;
314
316
  // --- slack ---
315
317
  slack_read(args?: {
316
318
  inboxMessageId?: string;
@@ -660,6 +662,16 @@ declare module "swarm-sdk" {
660
662
  // --- human input ---
661
663
  request_humanInput(args: Record<string, unknown>): Promise<unknown>;
662
664
  approval_cancel(args: { requestId: string; reason?: string }): Promise<unknown>;
665
+
666
+ // --- decisions ---
667
+ systemOne_decide(args: {
668
+ provider?: "typesafe" | "openrouter" | "laya" | "openai" | "cloudflare";
669
+ model?: string;
670
+ state: string | string[] | Record<string, unknown>;
671
+ questions: Record<string, Record<string, unknown>>;
672
+ timeoutMs?: number;
673
+ maxRetries?: number;
674
+ }): Promise<unknown>;
663
675
  }
664
676
 
665
677
  export interface ScriptStdlib {
package/src/server.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import pkg from "../package.json";
3
- import { ensurePricingSeeded, ensureRbacSeeded } from "./be/boot-seeds";
3
+ import { ensureModelProfilesSeeded, ensurePricingSeeded, ensureRbacSeeded } from "./be/boot-seeds";
4
4
  import { initDb } from "./be/db";
5
5
  import { startPricingRefreshLoop } from "./be/pricing-refresh";
6
6
  import { isSteeringEnabled } from "./be/steering";
@@ -81,6 +81,7 @@ import {
81
81
  registerModelCatalogOverlayUpsertTool,
82
82
  registerModelCatalogRefreshTool,
83
83
  } from "./tools/model-catalog";
84
+ import { registerListModelProfilesTool } from "./tools/model-profiles/list-model-profiles";
84
85
  import { registerMyAgentInfoTool } from "./tools/my-agent-info";
85
86
  import { registerGetOauthAccessTokenTool } from "./tools/oauth-access-token";
86
87
  import { registerPollTaskTool } from "./tools/poll-task";
@@ -176,6 +177,7 @@ import {
176
177
  } from "./tools/swarm-config";
177
178
  import { registerSwarmXTool } from "./tools/swarm-x";
178
179
  // Task pool capability
180
+ import { registerSystemOneDecideTool } from "./tools/system-one-decide";
179
181
  import { registerTaskActionTool } from "./tools/task-action";
180
182
  // Tracker capability
181
183
  import {
@@ -414,6 +416,11 @@ export async function createServer(
414
416
  // manual-override constants for runtime-fee / ACU pricing.
415
417
  ensurePricingSeeded();
416
418
  startPricingRefreshLoop();
419
+ try {
420
+ await ensureModelProfilesSeeded();
421
+ } catch (err) {
422
+ console.error("[server] Failed to seed model profiles:", err);
423
+ }
417
424
 
418
425
  try {
419
426
  ensureRbacSeeded();
@@ -477,6 +484,7 @@ export async function createServer(
477
484
  registerGetTasksTool(server);
478
485
  registerGetMetricsTool(server);
479
486
  registerSendTaskTool(server);
487
+ registerListModelProfilesTool(server);
480
488
  registerGetTaskDetailsTool(server);
481
489
  registerStoreProgressTool(server);
482
490
  registerMyAgentInfoTool(server);
@@ -611,6 +619,7 @@ export async function createServer(
611
619
  registerGetWorkflowRunTool(server);
612
620
  registerRetryWorkflowRunTool(server);
613
621
  registerCancelWorkflowRunTool(server);
622
+ registerSystemOneDecideTool(server);
614
623
  registerRequestHumanInputTool(server);
615
624
  registerCancelApprovalRequestTool(server);
616
625
  }
@@ -8,6 +8,7 @@
8
8
  import type { AgentTaskStatus, TaskAttachment } from "../types";
9
9
  import { getAppUrl } from "../utils/constants";
10
10
  import { taskAttachmentDisplayUrl } from "../utils/task-attachment-links";
11
+ import { completedStatusIcon } from "./reaction-shortcode";
11
12
 
12
13
  // Slack limits section text to 3000 chars; we use 2900 for safety.
13
14
  export const MAX_SECTION_LENGTH = 2900;
@@ -294,7 +295,7 @@ export function buildCompletedBlocks(opts: {
294
295
  trailer?: string;
295
296
  }): SlackBlock[] {
296
297
  const taskLink = getTaskLink(opts.taskId);
297
- let line = `✅ *${opts.agentName}* (${taskLink})`;
298
+ let line = `${completedStatusIcon()} *${opts.agentName}* (${taskLink})`;
298
299
  if (opts.duration) line += ` · ${opts.duration}`;
299
300
 
300
301
  const blocks: SlackBlock[] = [sectionBlock(line)];
@@ -479,7 +480,8 @@ export function isTreeOutputTruncated(text: string): boolean {
479
480
  * Render a single node line: icon + bold name + task link + optional duration.
480
481
  */
481
482
  function renderNodeLine(node: TreeNode): string {
482
- const icon = STATUS_ICON[node.status] ?? "•";
483
+ const icon =
484
+ node.status === "completed" ? completedStatusIcon() : (STATUS_ICON[node.status] ?? "•");
483
485
  const taskLink = getTaskLink(node.taskId);
484
486
  let line = `${icon} *${node.agentName}* (${taskLink})`;
485
487
  if (node.duration) line += ` · ${node.duration}`;
@@ -43,3 +43,17 @@ export function reactionName(event: SlackReactionEvent): string {
43
43
  const normalized = normalizeSlackReactionShortcode(raw);
44
44
  return normalized ?? SLACK_REACTION_DEFAULTS[event];
45
45
  }
46
+
47
+ export const SLACK_STATUS_ICON_COMPLETED_KEY = "SLACK_STATUS_ICON_COMPLETED";
48
+ export const SLACK_STATUS_ICON_COMPLETED_DEFAULT = "✅";
49
+
50
+ /**
51
+ * The "done" glyph in thread text (task tree, thread header, outcome cards).
52
+ * Read per call so a global config write, which reloads `process.env`, takes
53
+ * effect without a restart. Unlike a reaction there is no `invalid_name`
54
+ * fallback in text: a name the workspace lacks renders as literal `:name:`.
55
+ */
56
+ export function completedStatusIcon(): string {
57
+ const normalized = normalizeSlackReactionShortcode(process.env[SLACK_STATUS_ICON_COMPLETED_KEY]);
58
+ return normalized ? `:${normalized}:` : SLACK_STATUS_ICON_COMPLETED_DEFAULT;
59
+ }