@desplega.ai/agent-swarm 2.7.5 → 2.7.7

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 (319) hide show
  1. package/dist/{acp-adapter-g3ads55s.js → acp-adapter-7b4vk3h5.js} +5 -4
  2. package/dist/{actions-zwfykqqk.js → actions-3tt153af.js} +18 -17
  3. package/dist/{amp-adapter-z3s0ssdt.js → amp-adapter-njpts0yr.js} +1 -1
  4. package/dist/{anthropic-messages-x4q6rfq5.js → anthropic-messages-xey2skwx.js} +7 -7
  5. package/dist/{app-vmp4fgep.js → app-nky4378f.js} +3 -3
  6. package/dist/{assistant-vwde653f.js → assistant-jnb669pa.js} +24 -22
  7. package/dist/{azure-openai-responses-s1w5n70z.js → azure-openai-responses-26t7j9ra.js} +5 -5
  8. package/dist/{boot-reembed-375t7tbk.js → boot-reembed-9mgvaztt.js} +1 -1
  9. package/dist/{boot-reembed-gq9rcpnj.js → boot-reembed-qs2djdej.js} +1 -1
  10. package/dist/{boot-scrub-sweep-nd938kyf.js → boot-scrub-sweep-yjpdj7s1.js} +3 -3
  11. package/dist/{boot-seeds-aykx51v3.js → boot-seeds-5cm6ambd.js} +4 -4
  12. package/dist/{boxd-k1j26g3h.js → boxd-nv507va8.js} +5 -5
  13. package/dist/{claude-adapter-8j4hm099.js → claude-adapter-fn287zmm.js} +5 -4
  14. package/dist/{claude-managed-setup-0wyynpn1.js → claude-managed-setup-wtdz2mj2.js} +1 -1
  15. package/dist/{claude-sdk-session-q6wj5hmy.js → claude-sdk-session-x91kqyhm.js} +9677 -9514
  16. package/dist/{cli-j3jvnpe8.js → cli-08bmz9z9.js} +1 -1
  17. package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
  18. package/dist/{cli-y24sc29k.js → cli-1c6tzt8g.js} +30 -8
  19. package/dist/{cli-mjzynrse.js → cli-35rsp8ye.js} +1 -1
  20. package/dist/{cli-rpm2sgv0.js → cli-3fg0bdcv.js} +19 -2
  21. package/dist/{cli-nn4d8dne.js → cli-5b456bpf.js} +2 -2
  22. package/dist/{cli-bhjt02p8.js → cli-5jnhv72d.js} +1 -1
  23. package/dist/{cli-fqzx7gwa.js → cli-5zx699j9.js} +2 -2
  24. package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
  25. package/dist/{cli-vqypxzh4.js → cli-6g0hb4cp.js} +8 -8
  26. package/dist/{cli-tqgg40ek.js → cli-6pfd54vx.js} +9 -8
  27. package/dist/{cli-bsn8nhrq.js → cli-6vcp76qk.js} +3 -3
  28. package/dist/{cli-zppwg9p9.js → cli-72v799xn.js} +1 -1
  29. package/dist/cli-82j36f2m.js +45 -0
  30. package/dist/{cli-hn9hxmz9.js → cli-8305gbq0.js} +29 -16
  31. package/dist/{cli-z8eh3ex8.js → cli-8sckt74b.js} +8 -8
  32. package/dist/{cli-bnz7a7qf.js → cli-95jzxcp3.js} +1 -1
  33. package/dist/{cli-0bxqnvq9.js → cli-adeegh7d.js} +29 -7
  34. package/dist/{cli-g61qxmge.js → cli-afkqkrtw.js} +3 -3
  35. package/dist/{cli-7a42nfy0.js → cli-ayayvfg4.js} +24 -4
  36. package/dist/{cli-bqt1mptt.js → cli-az48n0nr.js} +4 -4
  37. package/dist/{cli-z6jkx23q.js → cli-bfv7vdg6.js} +9 -9
  38. package/dist/{cli-pywf3eh5.js → cli-caj33vcq.js} +5 -5
  39. package/dist/{cli-5pag6rmn.js → cli-cb2a8pna.js} +1 -1
  40. package/dist/{cli-wvaawacd.js → cli-cppkf404.js} +5236 -72
  41. package/dist/{cli-mry0qzym.js → cli-ddaanqkd.js} +123 -10
  42. package/dist/{cli-0gey5fzy.js → cli-e0kmphd6.js} +2 -2
  43. package/dist/{cli-zh3fp8v2.js → cli-e46hg0xw.js} +1 -1
  44. package/dist/{cli-jm2vfpfd.js → cli-e5bzrn01.js} +1 -1
  45. package/dist/{cli-qxnf8z2z.js → cli-excnnjg2.js} +165 -153
  46. package/dist/{cli-71ptav7x.js → cli-ferkkrg9.js} +1 -1
  47. package/dist/{cli-vpf89f34.js → cli-fmjd0t1y.js} +3 -3
  48. package/dist/{cli-rm6hqj3z.js → cli-fyt2g4wd.js} +33 -139
  49. package/dist/cli-g08zdaec.js +55 -0
  50. package/dist/{cli-ehq736cn.js → cli-g7s47cc5.js} +4 -4
  51. package/dist/{cli-82ae98pz.js → cli-gz6q4eve.js} +3 -3
  52. package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
  53. package/dist/{cli-rqkxrmgw.js → cli-je518txm.js} +39 -14
  54. package/dist/{cli-8hs41zy2.js → cli-jtjtabxe.js} +1 -1
  55. package/dist/{cli-611a1294.js → cli-k0wwfqbg.js} +18 -1
  56. package/dist/{cli-nzxr556s.js → cli-k1nje3v3.js} +38 -43
  57. package/dist/{cli-3h4nhjn8.js → cli-k8196sh2.js} +9 -7
  58. package/dist/{cli-afrrppsw.js → cli-k899ehvh.js} +2 -2
  59. package/dist/{cli-0731hvbw.js → cli-m0da8j35.js} +23 -9
  60. package/dist/cli-m2z8jagn.js +12078 -0
  61. package/dist/{cli-8veytvbh.js → cli-mkk9rnzz.js} +5 -5
  62. package/dist/{cli-7wspm5gm.js → cli-n6bv12jn.js} +528 -6
  63. package/dist/{cli-spknvxbq.js → cli-nmzhp1b5.js} +14 -12
  64. package/dist/{cli-p599f1qm.js → cli-p82cnnjq.js} +1 -1
  65. package/dist/{cli-xtjhfmpc.js → cli-pc6n7fre.js} +1 -1
  66. package/dist/{cli-vbyfwh92.js → cli-pjctyea3.js} +2 -2
  67. package/dist/{cli-nmbmy64z.js → cli-pq73h5mz.js} +3 -3
  68. package/dist/{cli-aa6yxc7m.js → cli-pz4rk021.js} +1 -1
  69. package/dist/{cli-7wcyk2s5.js → cli-q3tkegby.js} +1 -1
  70. package/dist/cli-qghe0h66.js +67 -0
  71. package/dist/{cli-6s7vdcta.js → cli-qh4d4tvk.js} +4 -4
  72. package/dist/{cli-mmwm7zbf.js → cli-rtt8g4v9.js} +1 -1
  73. package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
  74. package/dist/{cli-1h3wcpbw.js → cli-snpj5zbj.js} +2 -2
  75. package/dist/{cli-a7v854rf.js → cli-tgrv9cjx.js} +263 -12
  76. package/dist/{cli-a47xy8eq.js → cli-vz22fyqv.js} +186 -54
  77. package/dist/{cli-1ayk1eas.js → cli-wk5yrfwd.js} +285 -42
  78. package/dist/{cli-1338796d.js → cli-ws19768n.js} +1755 -119
  79. package/dist/{cli-70r77n04.js → cli-xae4g51r.js} +1 -1
  80. package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
  81. package/dist/{cli-xbkdm710.js → cli-yndmfxb3.js} +3 -3
  82. package/dist/{cli-rhw40jgb.js → cli-yyj3j5m4.js} +2 -2
  83. package/dist/cli.js +26 -25
  84. package/dist/{cloudflare-workers-ai-system-one-bm60fjm0.js → cloudflare-workers-ai-system-one-ef2a980q.js} +2 -2
  85. package/dist/{codex-adapter-q6k3gnyg.js → codex-adapter-cf2yxe9m.js} +3 -2
  86. package/dist/{codex-hook-9efm2x1m.js → codex-hook-a0c6g6t6.js} +1 -1
  87. package/dist/{codex-session-runner-3mwmk3d9.js → codex-session-runner-6vfzdgbd.js} +3 -2
  88. package/dist/{commands-evrenjn9.js → commands-t3mfy7vn.js} +9 -3
  89. package/dist/{controller-xjp8vswh.js → controller-q7xrbaz0.js} +8 -3
  90. package/dist/{controllers-connect-pp5jnxvc.js → controllers-connect-c6nm70kg.js} +20 -19
  91. package/dist/{cursor-adapter-yf47w4t0.js → cursor-adapter-ftd81ga8.js} +1 -1
  92. package/dist/{db-zxpw6aht.js → db-dk572x25.js} +3 -3
  93. package/dist/{db-rgfsh91f.js → db-vemtpmcc.js} +14 -4
  94. package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
  95. package/dist/{dispatcher-smasgws7.js → dispatcher-kh7ecxh9.js} +17 -16
  96. package/dist/{docker-rrjhaqt9.js → docker-c1a3gqmf.js} +89 -22
  97. package/dist/{dsh-adapter-ftyy8dnp.js → dsh-adapter-v8628r89.js} +1 -1
  98. package/dist/{e2b-w1p0bnpm.js → e2b-fr12yccx.js} +5 -5
  99. package/dist/{e2b-qcaj675y.js → e2b-x6ywzak3.js} +3 -3
  100. package/dist/{easter-egg-3d-yyts7p16.js → easter-egg-3d-nenw4x63.js} +1 -1
  101. package/dist/{embedding-space-1z0mbg95.js → embedding-space-0jepb239.js} +6 -6
  102. package/dist/{embeddings-ygeg7tm7.js → embeddings-xxt49yxg.js} +1 -1
  103. package/dist/{execute-14qhqp4d.js → execute-1mhw6e4x.js} +1 -1
  104. package/dist/{external-channel-ttgrs3sx.js → external-channel-55v12e60.js} +2 -2
  105. package/dist/{fx-adapter-gt6pncj2.js → fx-adapter-bs8wqrk5.js} +6 -5
  106. package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-v94hr7h0.js} +5 -5
  107. package/dist/{google-vertex-jrtdh9gs.js → google-vertex-g7vr6cf8.js} +5 -5
  108. package/dist/{grok-adapter-sm4m5k4g.js → grok-adapter-gpjqmkcq.js} +6 -5
  109. package/dist/handlers-zxmp1ta4.js +32 -0
  110. package/dist/{heartbeat-qahz4p62.js → heartbeat-k42svnr0.js} +20 -19
  111. package/dist/{hook-4fd5wq5w.js → hook-114m7hap.js} +4 -3
  112. package/dist/{hook-kj3d398m.js → hook-6nphweh0.js} +24 -9
  113. package/dist/{http-1ted0j1k.js → http-r8cc4wz7.js} +1196 -211
  114. package/dist/index-a8phxqnk.js +37 -0
  115. package/dist/{index-q4b917zs.js → index-e3s8wmxy.js} +1 -1
  116. package/dist/{index-6rq3r5zb.js → index-fpk4bcd9.js} +1 -1
  117. package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
  118. package/dist/index-k6h00t4c.js +30 -0
  119. package/dist/{index-7v51awbn.js → index-nh3wyqdt.js} +1027 -577
  120. package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
  121. package/dist/index-xrz7te8h.js +33 -0
  122. package/dist/{keepalive-6cf311t5.js → keepalive-x060nt9p.js} +3 -3
  123. package/dist/lead-956sd2yc.js +71 -0
  124. package/dist/{maintenance-a4bm1bmt.js → maintenance-2xxfexhf.js} +1 -1
  125. package/dist/{mistral-conversations-4pmwj1c0.js → mistral-conversations-3gjpdb4b.js} +4 -4
  126. package/dist/{oauth-refresh-sweep-3h0dfw1k.js → oauth-refresh-sweep-fckc4ar5.js} +2 -2
  127. package/dist/{onboard-myn5hraa.js → onboard-zc48j237.js} +5 -5
  128. package/dist/{openai-codex-responses-44j573nm.js → openai-codex-responses-8pbxq9h5.js} +5 -5
  129. package/dist/{openai-completions-a4gee2a8.js → openai-completions-rm00s912.js} +4 -4
  130. package/dist/{openai-decisions-w4qkrcar.js → openai-decisions-8v957r2r.js} +1 -1
  131. package/dist/{openai-responses-rbp6h5kr.js → openai-responses-agxpf96k.js} +5 -5
  132. package/dist/{opencode-adapter-p2nkb5am.js → opencode-adapter-r690h1cr.js} +4 -3
  133. package/dist/{otel-impl-a1a0ef6b.js → otel-impl-r5xbh81q.js} +4 -4
  134. package/dist/{pi-mono-adapter-rzmpb72a.js → pi-mono-adapter-c7v8sdec.js} +7 -6
  135. package/dist/{pricing-refresh-83drrnxj.js → pricing-refresh-3pmre11v.js} +3 -3
  136. package/dist/{rbac-roles-s8y6ecb5.js → rbac-roles-3vbshwsb.js} +1 -1
  137. package/dist/{rbac-roles-0fb6e7pj.js → rbac-roles-910mmkgz.js} +2 -2
  138. package/dist/{render-v2-twmbgrz4.js → render-v2-pxykkbab.js} +3 -3
  139. package/dist/{resume-zfqc36xj.js → resume-adda0bwy.js} +6 -6
  140. package/dist/{runtime-qwggdpqr.js → runtime-fnbm7stk.js} +1 -1
  141. package/dist/{session-access-sc9pn9da.js → session-access-2fvamrdc.js} +4 -4
  142. package/dist/{session-source-backfill-qh07xr8g.js → session-source-backfill-08xvv0sm.js} +2 -2
  143. package/dist/{setup-4fr0m7a1.js → setup-1aqmvmh1.js} +3 -3
  144. package/dist/{typesafe-system-one-5mavm259.js → typesafe-system-one-0bmqcxfg.js} +2 -2
  145. package/dist/{virtual-modules-fh4ha15b.js → virtual-modules-d2pjk9j1.js} +5 -5
  146. package/dist/{work-objects-0wr8zxct.js → work-objects-9s0yrm5j.js} +20 -19
  147. package/dist/{worker-follow-up-ktyyazyp.js → worker-follow-up-3q78fqmr.js} +17 -16
  148. package/dist/worker-y15hy08h.js +71 -0
  149. package/openapi.json +289 -4
  150. package/package.json +9 -8
  151. package/src/apps/definition.ts +6 -5
  152. package/src/be/db/page-theme-tokens.ts +12 -0
  153. package/src/be/db/script-runs.ts +23 -0
  154. package/src/be/db/tasks/read.ts +131 -4
  155. package/src/be/db/tasks/write.ts +19 -0
  156. package/src/be/db-client.ts +114 -11
  157. package/src/be/db.ts +49 -6
  158. package/src/be/memory/providers/sqlite-store.ts +49 -12
  159. package/src/be/migrations/260_runtime_pool_cache.sql +20 -0
  160. package/src/be/migrations/262_session_visibility_indexes.sql +18 -0
  161. package/src/be/migrations/263_task_list_filter_index.sql +11 -0
  162. package/src/be/migrations/264_slack_thread_cover_index.sql +9 -0
  163. package/src/be/migrations/266_script_runs_source_task.sql +11 -0
  164. package/src/be/migrations/269_pages_design_system.sql +23 -0
  165. package/src/be/runtime-pools/cache-cleanup.ts +109 -0
  166. package/src/be/runtime-pools/db.ts +65 -6
  167. package/src/be/runtime-pools/remote-controller.ts +5 -0
  168. package/src/be/runtime-pools/supervisor.ts +33 -2
  169. package/src/be/scripts/typecheck.ts +22 -0
  170. package/src/be/seed/schedules-seeder.ts +0 -10
  171. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +2 -0
  172. package/src/be/seed-scripts/catalog/catalog-report.ts +2 -0
  173. package/src/be/seed-scripts/catalog/memory-eval.ts +2 -0
  174. package/src/be/seed-scripts/catalog/swarm-update-check.ts +150 -0
  175. package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
  176. package/src/be/seed-scripts/index.ts +9 -0
  177. package/src/be/seed-skills/bundled-files.generated.json +3 -3
  178. package/src/be/swarm-config-guard.ts +32 -0
  179. package/src/be/task-auto-title-listener.ts +127 -0
  180. package/src/commands/controller.ts +4 -0
  181. package/src/commands/entrypoint-api-readiness.ts +74 -0
  182. package/src/commands/onboard/agent-fs-version.ts +1 -1
  183. package/src/commands/profile-sync.ts +151 -16
  184. package/src/commands/runner.ts +15 -7
  185. package/src/hooks/hook.ts +20 -9
  186. package/src/http/index.ts +4 -1
  187. package/src/http/list-total-cache.ts +14 -7
  188. package/src/http/memory.ts +38 -9
  189. package/src/http/pages-public.ts +93 -129
  190. package/src/http/pages.ts +94 -21
  191. package/src/http/script-runs.ts +174 -5
  192. package/src/http/status.ts +10 -2
  193. package/src/http/tasks.ts +4 -2
  194. package/src/pages/browser-sdk.ts +48 -21
  195. package/src/pages/color-literals.ts +115 -0
  196. package/src/pages/page-head.ts +766 -0
  197. package/src/pages/version.ts +3 -0
  198. package/src/providers/amp-adapter.ts +1 -1
  199. package/src/providers/codex-adapter.ts +1 -0
  200. package/src/providers/codex-app-server.ts +27 -1
  201. package/src/providers/fx-adapter.ts +1 -1
  202. package/src/providers/pi-mono-extension.ts +30 -15
  203. package/src/runtime-providers/controller-protocol.ts +14 -1
  204. package/src/runtime-providers/controller.ts +5 -0
  205. package/src/runtime-providers/docker.ts +108 -23
  206. package/src/runtime-providers/types.ts +16 -0
  207. package/src/script-workflows/workflow-ctx.ts +8 -0
  208. package/src/scripts-runtime/types/stdlib.d.ts +22 -0
  209. package/src/scripts-runtime/types/swarm-sdk.d.ts +22 -0
  210. package/src/server-user.ts +1 -0
  211. package/src/slack/commands.ts +5 -1
  212. package/src/slack/handlers.ts +37 -2
  213. package/src/slack/left-threads.ts +27 -4
  214. package/src/tasks/follow-up-gate.ts +318 -0
  215. package/src/tasks/follow-up-redelegation-guard.ts +155 -0
  216. package/src/tasks/worker-follow-up.ts +21 -1
  217. package/src/tests/claude-artifacts-read-script.test.ts +209 -0
  218. package/src/tests/codex-app-server.test.ts +59 -0
  219. package/src/tests/codex-gateway-route.test.ts +50 -1
  220. package/src/tests/create-page-tool.test.ts +501 -0
  221. package/src/tests/db-client-busy-retry.test.ts +81 -0
  222. package/src/tests/db-client.test.ts +21 -0
  223. package/src/tests/db-task-read-contract.test.ts +9 -2
  224. package/src/tests/db-task-write-contract.test.ts +7 -1
  225. package/src/tests/db-tasks-read.test.ts +21 -0
  226. package/src/tests/entrypoint-api-readiness.test.ts +416 -210
  227. package/src/tests/explicit-model-validation.test.ts +14 -8
  228. package/src/tests/fixtures/pages/head-v1.html +56 -0
  229. package/src/tests/follow-up-gate-shadow.test.ts +513 -0
  230. package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
  231. package/src/tests/identity-field-budget.test.ts +1 -1
  232. package/src/tests/internal-ai/credentials.test.ts +62 -0
  233. package/src/tests/internal-ai/task-title.test.ts +283 -0
  234. package/src/tests/list-total-cache.test.ts +27 -2
  235. package/src/tests/memory-fts-populate.test.ts +11 -9
  236. package/src/tests/memory-retrieval-purge.test.ts +80 -0
  237. package/src/tests/merge-gate-detect-changes.test.ts +145 -0
  238. package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
  239. package/src/tests/page-proxy.test.ts +1 -1
  240. package/src/tests/page-session-dev-cookie.test.ts +188 -0
  241. package/src/tests/pages-frame-ancestors.test.ts +28 -1
  242. package/src/tests/pages-head-v2.test.ts +534 -0
  243. package/src/tests/pages-http.test.ts +330 -0
  244. package/src/tests/pages-public-html.test.ts +43 -0
  245. package/src/tests/pages-storage.test.ts +182 -0
  246. package/src/tests/pages-versioning.test.ts +72 -0
  247. package/src/tests/profile-sync-stale-writeback.test.ts +334 -0
  248. package/src/tests/profile-sync.test.ts +11 -15
  249. package/src/tests/query-audit-plans.test.ts +27 -0
  250. package/src/tests/runtime-cache-cleanup.test.ts +367 -0
  251. package/src/tests/runtime-e2b-pools.test.ts +41 -0
  252. package/src/tests/runtime-pools-db.test.ts +98 -0
  253. package/src/tests/runtime-pools-http.test.ts +29 -0
  254. package/src/tests/runtime-providers-docker.test.ts +253 -0
  255. package/src/tests/runtime-remote-controller.test.ts +66 -2
  256. package/src/tests/runtime-supervisor.test.ts +66 -2
  257. package/src/tests/scheduled-task-required-params-migration.test.ts +3 -1
  258. package/src/tests/script-connections.test.ts +13 -0
  259. package/src/tests/script-runs-http.test.ts +684 -2
  260. package/src/tests/seed-registry.test.ts +28 -6
  261. package/src/tests/seed-schedules.test.ts +61 -8
  262. package/src/tests/seed-scripts.test.ts +168 -2
  263. package/src/tests/slack-leave-thread.test.ts +145 -5
  264. package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
  265. package/src/tests/status.test.ts +11 -0
  266. package/src/tests/swarm-diff.test.ts +121 -2
  267. package/src/tests/task-auto-title-config-guard.test.ts +37 -0
  268. package/src/tests/task-auto-title-listener.test.ts +165 -0
  269. package/src/tests/tasks-readable-row.test.ts +155 -0
  270. package/src/tests/theme-import.test.ts +758 -0
  271. package/src/tests/themes-page-theme.test.ts +155 -0
  272. package/src/tests/themes-presets.test.ts +172 -0
  273. package/src/tests/workflow-script-principal.test.ts +2 -1
  274. package/src/themes/classics.ts +949 -0
  275. package/src/themes/import.ts +420 -0
  276. package/src/themes/page-fields.ts +40 -0
  277. package/src/themes/page-theme.ts +102 -0
  278. package/src/themes/presets.ts +561 -0
  279. package/src/themes/tokens.ts +218 -0
  280. package/src/tools/app-upsert.ts +1 -1
  281. package/src/tools/create-page.ts +118 -12
  282. package/src/tools/send-task.ts +35 -47
  283. package/src/tools/slack-leave-thread.ts +1 -1
  284. package/src/tools/task-dedup.ts +45 -21
  285. package/src/tools/utils.ts +33 -0
  286. package/src/types.ts +83 -3
  287. package/src/utils/constants.ts +19 -0
  288. package/src/utils/follow-up-gate-schedule-ids.ts +13 -0
  289. package/src/utils/internal-ai/credentials.ts +146 -93
  290. package/src/utils/internal-ai/index.ts +10 -0
  291. package/src/utils/internal-ai/models.ts +18 -0
  292. package/src/utils/internal-ai/task-title.ts +282 -0
  293. package/src/utils/page-session.ts +37 -0
  294. package/templates/schedules/daily-status-report/content.md +88 -2
  295. package/templates/schedules/daily-swarm-update-check/content.md +16 -40
  296. package/templates/skills/apps/SKILL.md +1 -1
  297. package/templates/skills/apps/content.md +1 -1
  298. package/templates/skills/composio-google-docs/content.md +10 -0
  299. package/templates/skills/heartbeat-runbook/content.md +1 -1
  300. package/templates/skills/pages/config.json +1 -1
  301. package/templates/skills/pages/content.md +539 -86
  302. package/templates/skills/pages/files/examples/annotated-pr.html +21 -12
  303. package/templates/skills/pages/files/examples/multiplayer-board.html +41 -41
  304. package/templates/skills/pages/files/examples/report-page.html +125 -124
  305. package/templates/skills/script-workflows/content.md +19 -1
  306. package/templates/skills/swarm-docs/content.md +1 -0
  307. package/templates/skills/swarm-scripts/SKILL.md +2 -0
  308. package/templates/skills/swarm-scripts/content.md +2 -0
  309. package/dist/cli-2pnswgh8.js +0 -189
  310. package/dist/cli-dznrgaep.js +0 -30
  311. package/dist/cli-w3hcrxht.js +0 -6985
  312. package/dist/handlers-vwmba1m2.js +0 -31
  313. package/dist/index-5r71rf98.js +0 -32
  314. package/dist/index-anmwxs87.js +0 -29
  315. package/dist/index-spp0ra5c.js +0 -36
  316. package/dist/lead-fjy418cw.js +0 -69
  317. package/dist/worker-0c5tyzqe.js +0 -69
  318. package/templates/schedules/daily-workflow-health-audit/config.json +0 -19
  319. package/templates/schedules/daily-workflow-health-audit/content.md +0 -191
@@ -34,11 +34,53 @@ const HIGH_SIMILARITY_THRESHOLD = 0.8;
34
34
  /** Lower threshold used when the tasks also target the same agent */
35
35
  const SAME_AGENT_SIMILARITY_THRESHOLD = 0.6;
36
36
 
37
+ /**
38
+ * Rows one duplicate check scans. The JS similarity match needs more than the
39
+ * query's default 50 (an older duplicate can sit behind newer unrelated tasks),
40
+ * but the scan must stay bounded: send-task runs it twice per call, once inside
41
+ * the write transaction.
42
+ */
43
+ export const DEDUP_SCAN_LIMIT = 500;
44
+
37
45
  export interface DedupMatch {
38
46
  task: AgentTask;
39
47
  reason: string;
40
48
  }
41
49
 
50
+ /**
51
+ * The description-similarity rules shared by the duplicate check and the
52
+ * follow-up re-delegation guard: a very similar description matches any
53
+ * target, a moderately similar one matches only the same target agent.
54
+ */
55
+ export function matchSimilarTask(
56
+ existing: AgentTask,
57
+ opts: { taskDescription: string; targetAgentId?: string },
58
+ ): DedupMatch | null {
59
+ const similarity = jaccardSimilarity(existing.task, opts.taskDescription);
60
+
61
+ // Very similar task description: likely duplicate regardless of target
62
+ if (similarity > HIGH_SIMILARITY_THRESHOLD) {
63
+ return {
64
+ task: existing,
65
+ reason: `similar task description (${(similarity * 100).toFixed(0)}% match)`,
66
+ };
67
+ }
68
+
69
+ // Same target agent + moderately similar task: lower threshold
70
+ if (
71
+ opts.targetAgentId &&
72
+ existing.agentId === opts.targetAgentId &&
73
+ similarity > SAME_AGENT_SIMILARITY_THRESHOLD
74
+ ) {
75
+ return {
76
+ task: existing,
77
+ reason: `similar task to same agent (${(similarity * 100).toFixed(0)}% match)`,
78
+ };
79
+ }
80
+
81
+ return null;
82
+ }
83
+
42
84
  /**
43
85
  * Check for duplicate tasks created recently.
44
86
  * Returns the first matching duplicate, or null if none found.
@@ -54,6 +96,7 @@ export async function findDuplicateTask(opts: {
54
96
  const recentTasks = await findRecentSimilarTasks({
55
97
  creatorAgentId: opts.creatorAgentId,
56
98
  windowMinutes: opts.windowMinutes ?? 10,
99
+ limit: DEDUP_SCAN_LIMIT,
57
100
  });
58
101
 
59
102
  for (const existing of recentTasks) {
@@ -70,27 +113,8 @@ export async function findDuplicateTask(opts: {
70
113
  };
71
114
  }
72
115
 
73
- const similarity = jaccardSimilarity(existing.task, opts.taskDescription);
74
-
75
- // 2. Very similar task description — likely duplicate regardless of target
76
- if (similarity > HIGH_SIMILARITY_THRESHOLD) {
77
- return {
78
- task: existing,
79
- reason: `similar task description (${(similarity * 100).toFixed(0)}% match)`,
80
- };
81
- }
82
-
83
- // 3. Same target agent + moderately similar task — lower threshold
84
- if (
85
- opts.targetAgentId &&
86
- existing.agentId === opts.targetAgentId &&
87
- similarity > SAME_AGENT_SIMILARITY_THRESHOLD
88
- ) {
89
- return {
90
- task: existing,
91
- reason: `similar task to same agent (${(similarity * 100).toFixed(0)}% match)`,
92
- };
93
- }
116
+ const match = matchSimilarTask(existing, opts);
117
+ if (match) return match;
94
118
  }
95
119
 
96
120
  return null;
@@ -22,6 +22,7 @@ import { upsertKv } from "../be/db";
22
22
  import { MCP_OVERFLOW_NAMESPACE, mcpOverflowNamespace } from "../kv-overflow";
23
23
  import { joinKvPath, type KvShapeEntry, splitKvPath, summarizeKvShape } from "../kv-view";
24
24
  import { withSpan } from "../otel";
25
+ import { COLOR_LITERAL_THRESHOLD } from "../pages/color-literals";
25
26
  import type { PermissionVerb } from "../rbac/permissions";
26
27
  import { SCRIPT_LONG_TIMEOUT_HINT_MS } from "../scripts-runtime/executors/types";
27
28
  import { scrubObject, scrubSecrets } from "../utils/secret-scrubber";
@@ -314,6 +315,37 @@ const workflowLongScriptTimeoutNudge = (r: SwarmToolResult): string | undefined
314
315
  return hint ? WORKFLOW_LONG_SCRIPT_TIMEOUT_NUDGE : undefined;
315
316
  };
316
317
 
318
+ /**
319
+ * One call can fall into more than one create_page trap, and a result carries
320
+ * one nudge, so this entry joins one sentence per trap that applies: a
321
+ * design version 1 page that uses the design system, theme tokens that set
322
+ * light colors only, and hardcoded colors. The tool computes the flags.
323
+ */
324
+ const createPageNudge = (r: SwarmToolResult): string | undefined => {
325
+ if (!r.ok) return undefined;
326
+ const data = r.data as
327
+ | {
328
+ designVersion?: unknown;
329
+ usesDesignSystem?: unknown;
330
+ lightOnlyColors?: unknown;
331
+ colorLiteralCount?: unknown;
332
+ }
333
+ | undefined;
334
+ const count = data?.colorLiteralCount;
335
+ const sentences = [
336
+ data?.designVersion === 1 && data.usesDesignSystem === true
337
+ ? "This page is on designVersion 1 (the original look), so design system classes, tokens and themes have no effect. Pass designVersion: 2."
338
+ : undefined,
339
+ data?.lightOnlyColors === true
340
+ ? "Your themeTokens set colors for light mode only. Dark mode keeps the preset's dark colors; add a dark map (or a .dark block) to restyle it too."
341
+ : undefined,
342
+ typeof count === "number" && count >= COLOR_LITERAL_THRESHOLD
343
+ ? `This page hardcodes ${count} color values. Use theme tokens (var(--color-primary), bg-primary, text-muted-foreground) so theme and dark-mode switches restyle it. Until then the page stays light in a dark dashboard.`
344
+ : undefined,
345
+ ].filter((sentence) => sentence !== undefined);
346
+ return sentences.length > 0 ? sentences.join(" ") : undefined;
347
+ };
348
+
317
349
  /**
318
350
  * Central conditional nudges, keyed by tool name. Applied by the finalize
319
351
  * pipeline when the tool did not set an explicit nudge. Keep entries to a
@@ -333,6 +365,7 @@ export const NUDGES: Record<string, (result: SwarmToolResult) => string | undefi
333
365
  "patch-workflow-node": workflowLongScriptTimeoutNudge,
334
366
  "slack-download-file": slackDownloadFileNudge,
335
367
  "slack-read": slackReadNudge,
368
+ create_page: createPageNudge,
336
369
  "script-search": (r) => {
337
370
  if (!r.ok) return undefined;
338
371
  // proxyScriptsApi wraps the parsed HTTP body as data = { status, data },
package/src/types.ts CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { normalizeAssetKey } from "./assets/key";
6
6
  import { isSafeBundlePath } from "./extensions/bundle-path";
7
+ import { THEME_IMPORT_FORMATS } from "./themes/import";
7
8
  import { MAX_PROFILE_FILE_LENGTH } from "./utils/constants";
8
9
  import { z } from "./utils/zod-openapi";
9
10
 
@@ -1695,6 +1696,8 @@ export const AgentLogEventTypeSchema = z.enum([
1695
1696
  "task_deferred_wait_woke",
1696
1697
  // The lead follow-up was skipped because the woken waiter already carries the result
1697
1698
  "task_follow_up_suppressed",
1699
+ // Shadow follow-up gate: what a verifier said about a gated run (never skips the follow-up)
1700
+ "task_follow_up_gate_shadow",
1698
1701
  // store-progress refused a completion once because its citations were inaccurate
1699
1702
  "task_citation_check_refused",
1700
1703
  "channel_message",
@@ -2212,6 +2215,39 @@ export const RuntimeControllerSchema = RuntimeControllerCreateSchema.omit({ secr
2212
2215
  .openapi("RuntimeController");
2213
2216
  export type RuntimeController = z.infer<typeof RuntimeControllerSchema>;
2214
2217
 
2218
+ export const RuntimePoolCacheSchema = z
2219
+ .object({
2220
+ mountPath: z
2221
+ .string()
2222
+ .min(2)
2223
+ .refine((path) => {
2224
+ const parts = path.split("/").filter(Boolean);
2225
+ const normalized = `/${parts.join("/")}`;
2226
+ const protectedPaths = [
2227
+ "/workspace/personal",
2228
+ "/workspace/shared",
2229
+ ...["/home/worker", "/root"].flatMap((home) =>
2230
+ [".ssh", ".codex", ".claude", ".claude.json", ".pi", ".config", ".local"].map(
2231
+ (directory) => `${home}/${directory}`,
2232
+ ),
2233
+ ),
2234
+ ];
2235
+ return (
2236
+ path.startsWith("/") &&
2237
+ parts.length > 0 &&
2238
+ !path.includes("\0") &&
2239
+ !parts.some((part) => part === "." || part === "..") &&
2240
+ !protectedPaths.some(
2241
+ (protectedPath) =>
2242
+ protectedPath === normalized ||
2243
+ protectedPath.startsWith(`${normalized}/`) ||
2244
+ normalized.startsWith(`${protectedPath}/`),
2245
+ )
2246
+ );
2247
+ }, "Cache mountPath must be absolute, without . or .., and must not cover worker homes, workspaces or credential directories"),
2248
+ })
2249
+ .strict();
2250
+
2215
2251
  export const RuntimePoolCreateSchema = z
2216
2252
  .object({
2217
2253
  name: z.string().min(1).max(200),
@@ -2226,6 +2262,7 @@ export const RuntimePoolCreateSchema = z
2226
2262
  delivery: DeliveryModeSchema.nullable().default(null),
2227
2263
  env: z.record(z.string(), z.string()).default({}),
2228
2264
  resources: RuntimeResourcesSchema.nullable().default(null),
2265
+ cache: RuntimePoolCacheSchema.nullable().default(null),
2229
2266
  ttlSec: z.number().int().positive().nullable().default(null),
2230
2267
  enabled: z.boolean().default(true),
2231
2268
  })
@@ -2239,6 +2276,7 @@ export const RuntimePoolUpdateSchema = RuntimePoolCreateSchema.partial().extend(
2239
2276
  delivery: DeliveryModeSchema.nullable().optional(),
2240
2277
  env: z.record(z.string(), z.string()).optional(),
2241
2278
  resources: RuntimeResourcesSchema.nullable().optional(),
2279
+ cache: RuntimePoolCacheSchema.nullable().optional(),
2242
2280
  ttlSec: z.number().int().positive().nullable().optional(),
2243
2281
  enabled: z.boolean().optional(),
2244
2282
  });
@@ -2738,10 +2776,36 @@ export type PageContentType = z.infer<typeof PageContentTypeSchema>;
2738
2776
  export const PageAuthModeSchema = z.enum(["public", "authed", "password"]);
2739
2777
  export type PageAuthMode = z.infer<typeof PageAuthModeSchema>;
2740
2778
 
2779
+ // Per-page theme overrides as stored: page token names (`--color-primary`,
2780
+ // `--radius-lg`) mapped to CSS values, per mode. Writes go through
2781
+ // `importThemeTokens` (src/themes/import.ts), which maps outside names onto the
2782
+ // token contract and checks every value, so the stored form is always normalized.
2783
+ export const PageThemeTokensSchema = z
2784
+ .object({
2785
+ light: z.record(z.string(), z.string()),
2786
+ dark: z.record(z.string(), z.string()),
2787
+ })
2788
+ .openapi("PageThemeTokens");
2789
+
2790
+ // Write-side `themeTokens` input, mirroring `ThemeImportInput` (src/themes/import.ts)
2791
+ // as one flat object: the default `tokens` format sends `light` / `dark` maps,
2792
+ // the `css` format sends a CSS variable block. `importThemeTokens` checks which
2793
+ // fields go with which format, so a mismatch gets a precise error message. The
2794
+ // `format` enum is the importer's own list, so a new importer needs no change
2795
+ // here unless it takes a new payload field.
2796
+ export const PageThemeTokensInputSchema = z.strictObject({
2797
+ format: z.enum(THEME_IMPORT_FORMATS).optional(),
2798
+ light: z.record(z.string(), z.string()).optional(),
2799
+ dark: z.record(z.string(), z.string()).optional(),
2800
+ css: z.string().optional(),
2801
+ });
2802
+ export type PageThemeTokensInput = z.infer<typeof PageThemeTokensInputSchema>;
2803
+
2741
2804
  // PageSnapshot captures the mutable content fields frozen per-version in
2742
2805
  // page_versions.snapshot. Omits id / agentId / slug / timestamps (these are
2743
2806
  // invariant across versions for a given page id; the slug is a parent-only
2744
- // identifier).
2807
+ // identifier). The design fields are optional because snapshots taken before
2808
+ // design version 2 do not carry them (they read as design version 1).
2745
2809
  export const PageSnapshotSchema = z
2746
2810
  .object({
2747
2811
  title: z.string(),
@@ -2751,6 +2815,9 @@ export const PageSnapshotSchema = z
2751
2815
  passwordHash: z.string().optional(),
2752
2816
  body: z.string(),
2753
2817
  needsCredentials: z.array(z.string()).optional(),
2818
+ theme: z.string().nullable().optional(),
2819
+ themeTokens: PageThemeTokensSchema.nullable().optional(),
2820
+ designVersion: z.number().int().min(1).optional(),
2754
2821
  })
2755
2822
  .openapi("PageSnapshot");
2756
2823
  export type PageSnapshot = z.infer<typeof PageSnapshotSchema>;
@@ -2768,6 +2835,9 @@ export const PageSchema = z
2768
2835
  passwordHash: z.string().optional(),
2769
2836
  body: z.string(),
2770
2837
  needsCredentials: z.array(z.string()).optional(),
2838
+ theme: z.string().nullable(),
2839
+ themeTokens: PageThemeTokensSchema.nullable(),
2840
+ designVersion: z.number().int().min(1),
2771
2841
  viewCount: z.number().int().min(0).default(0),
2772
2842
  createdAt: z.string(),
2773
2843
  updatedAt: z.string(),
@@ -2791,8 +2861,15 @@ export type WorkflowSummary = Omit<
2791
2861
  "definition" | "triggers" | "cooldown" | "input" | "triggerSchema"
2792
2862
  > & { nodeCount: number };
2793
2863
 
2794
- /** `/api/pages` list item — drops the (potentially huge) `body` and `passwordHash`. */
2795
- export type PageSummary = Omit<Page, "body" | "passwordHash">;
2864
+ /**
2865
+ * `/api/pages` list item: drops the (potentially huge) `body` and `passwordHash`,
2866
+ * plus the design fields (`theme`, `themeTokens`, `designVersion`). List views do
2867
+ * not render a page, so those only come with `?fields=full` or the get-by-id route.
2868
+ */
2869
+ export type PageSummary = Omit<
2870
+ Page,
2871
+ "body" | "passwordHash" | "theme" | "themeTokens" | "designVersion"
2872
+ >;
2796
2873
 
2797
2874
  /** `/api/schedules` list item — swaps the full `taskTemplate` for a short preview. */
2798
2875
  export type ScheduledTaskSummary = Omit<ScheduledTask, "taskTemplate"> & {
@@ -3114,6 +3191,8 @@ export const ScriptRunSchema = z
3114
3191
  lastHeartbeatAt: z.string().optional(),
3115
3192
  idempotencyKey: z.string().optional(),
3116
3193
  requestedByUserId: z.string().optional(),
3194
+ /** Task whose session launched the run; the follow-up re-delegation guard reads it. */
3195
+ sourceTaskId: z.string().optional(),
3117
3196
  })
3118
3197
  .openapi("ScriptRun");
3119
3198
  export type ScriptRun = z.infer<typeof ScriptRunSchema>;
@@ -3122,6 +3201,7 @@ export const ScriptRunListItemSchema = ScriptRunSchema.omit({
3122
3201
  source: true,
3123
3202
  args: true,
3124
3203
  output: true,
3204
+ sourceTaskId: true,
3125
3205
  });
3126
3206
  export type ScriptRunListItem = z.infer<typeof ScriptRunListItemSchema>;
3127
3207
 
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { isEnvFlagEnabled, parseEnvFlag } from "./env-flag";
6
+ import { isDevOrTestNodeEnv } from "./node-env";
6
7
 
7
8
  /** Maximum profile-file length in JavaScript characters (not UTF-8 bytes). */
8
9
  export const MAX_PROFILE_FILE_LENGTH = 256 * 1024;
@@ -45,6 +46,24 @@ export function getAppUrl(fallback = DEFAULT_APP_URL): string {
45
46
  return (getConfiguredAppUrls()[0] || fallback).replace(/\/+$/, "");
46
47
  }
47
48
 
49
+ /**
50
+ * Local dashboard origins (Vite ports and portless `*.localhost`) that may
51
+ * iframe pages without being listed in `APP_URL`. Returned only when NODE_ENV
52
+ * is explicitly development or test: deployed images leave NODE_ENV unset, and
53
+ * an unset value must not let a localhost origin frame production pages.
54
+ */
55
+ export function getLocalDevAppOrigins(): string[] {
56
+ if (!isDevOrTestNodeEnv()) return [];
57
+ return [
58
+ "http://localhost:5274",
59
+ "http://localhost:5175",
60
+ "http://127.0.0.1:5274",
61
+ "http://127.0.0.1:5175",
62
+ "https://*.localhost",
63
+ "http://*.localhost",
64
+ ];
65
+ }
66
+
48
67
  /**
49
68
  * Internal API/MCP base URL — how workers/agents and in-process callers reach
50
69
  * the API server. May be a private/cluster address (e.g. the Helm ClusterIP
@@ -0,0 +1,13 @@
1
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2
+
3
+ /** Split `FOLLOW_UP_GATE_SCHEDULE_IDS`, a comma-separated id list. Blank entries are dropped. */
4
+ export function parseFollowUpGateScheduleIds(raw: string | undefined): string[] {
5
+ return (raw ?? "")
6
+ .split(",")
7
+ .map((id) => id.trim())
8
+ .filter(Boolean);
9
+ }
10
+
11
+ export function isFollowUpGateScheduleId(id: string): boolean {
12
+ return UUID_RE.test(id);
13
+ }
@@ -108,6 +108,30 @@ export interface ResolveCredentialOptions {
108
108
  _persistCodexOAuth?: typeof persistCodexOAuth;
109
109
  }
110
110
 
111
+ /**
112
+ * Credential kinds in precedence order. `resolveCredentials` walks this list;
113
+ * `resolveCredential` returns the first hit.
114
+ */
115
+ export const CREDENTIAL_KIND_PRECEDENCE: readonly CredentialKind[] = [
116
+ "openrouter",
117
+ "anthropic",
118
+ "openai",
119
+ "openai-codex",
120
+ "claude-cli",
121
+ ];
122
+
123
+ export interface ResolveCredentialsOptions extends ResolveCredentialOptions {
124
+ /**
125
+ * Only resolve these kinds (in precedence order). Lets a caller probe one
126
+ * provider at a time, so the codex OAuth lookup (an HTTP call that can
127
+ * rotate a refresh token) runs only when that kind is actually needed.
128
+ * Default: every kind.
129
+ */
130
+ kinds?: readonly CredentialKind[];
131
+ /** Stop after this many credentials. Default: no limit. */
132
+ limit?: number;
133
+ }
134
+
111
135
  /**
112
136
  * Resolve a credential according to the documented precedence. Returns `null`
113
137
  * if no credential could be resolved — callers MUST treat null as a graceful
@@ -125,112 +149,141 @@ export interface ResolveCredentialOptions {
125
149
  export async function resolveCredential(
126
150
  opts: ResolveCredentialOptions = {},
127
151
  ): Promise<ResolvedCredential | null> {
152
+ const [first] = await resolveCredentials({ ...opts, limit: 1 });
153
+ return first ?? null;
154
+ }
155
+
156
+ /**
157
+ * Every credential the environment offers, in the precedence order documented
158
+ * on {@link resolveCredential}. Callers that can fall back across providers
159
+ * (task titles) walk the list; `resolveCredential` is the `limit: 1` case.
160
+ * The codex OAuth probe runs only when `openai-codex` is reached, so an env
161
+ * key ahead of it (or `kinds` that exclude it) never triggers the HTTP call.
162
+ */
163
+ export async function resolveCredentials(
164
+ opts: ResolveCredentialsOptions = {},
165
+ ): Promise<ResolvedCredential[]> {
128
166
  const env = opts.env ?? process.env;
129
167
  const getEnvKey = opts._getEnvApiKey ?? getEnvApiKey;
130
- const getCodex = opts._getValidCodexOAuth ?? getValidCodexOAuth;
131
- const getOAuth = opts._getOAuthApiKey ?? resolveOAuthApiKey;
132
- const persistCodex = opts._persistCodexOAuth ?? persistCodexOAuth;
168
+ const wanted = opts.kinds ?? CREDENTIAL_KIND_PRECEDENCE;
169
+ const limit = opts.limit ?? Number.POSITIVE_INFINITY;
170
+ const found: ResolvedCredential[] = [];
133
171
 
134
- // 1. OpenRouter.
135
- const openrouterKey = env.OPENROUTER_API_KEY ?? getEnvKey("openrouter");
136
- if (openrouterKey) {
137
- return {
138
- kind: "openrouter",
139
- apiKey: selectCredential(openrouterKey, undefined, "OPENROUTER_API_KEY").selected,
140
- modelDefault: resolveRaterModelString("openrouter"),
141
- };
172
+ for (const kind of CREDENTIAL_KIND_PRECEDENCE) {
173
+ if (found.length >= limit) break;
174
+ if (!wanted.includes(kind)) continue;
175
+ const cred = await resolveKind(kind, opts, env, getEnvKey);
176
+ if (cred) found.push(cred);
142
177
  }
178
+ return found;
179
+ }
143
180
 
144
- // 2. Anthropic. pi-ai sends this key to api.anthropic.com, so skip it when
145
- // ANTHROPIC_BASE_URL names a gateway: the key belongs to the gateway (#1800).
146
- const anthropicBaseUrl = env.ANTHROPIC_BASE_URL?.trim();
147
- const anthropicKeyIsForGateway =
148
- Boolean(anthropicBaseUrl) && !isFirstPartyAnthropicUrl(anthropicBaseUrl ?? "");
149
- const anthropicKey = anthropicKeyIsForGateway
150
- ? undefined
151
- : (env.ANTHROPIC_API_KEY ?? getEnvKey("anthropic"));
152
- if (anthropicKey) {
153
- return {
154
- kind: "anthropic",
155
- apiKey: selectCredential(anthropicKey, undefined, "ANTHROPIC_API_KEY").selected,
156
- modelDefault: resolveRaterModelString("anthropic"),
157
- };
181
+ async function resolveKind(
182
+ kind: CredentialKind,
183
+ opts: ResolveCredentialOptions,
184
+ env: NodeJS.ProcessEnv,
185
+ getEnvKey: typeof getEnvApiKey,
186
+ ): Promise<ResolvedCredential | null> {
187
+ switch (kind) {
188
+ case "openrouter": {
189
+ const key = env.OPENROUTER_API_KEY ?? getEnvKey("openrouter");
190
+ if (!key) return null;
191
+ return {
192
+ kind,
193
+ apiKey: selectCredential(key, undefined, "OPENROUTER_API_KEY").selected,
194
+ modelDefault: resolveRaterModelString(kind),
195
+ };
196
+ }
197
+ case "anthropic": {
198
+ // pi-ai sends this key to api.anthropic.com, so skip it when
199
+ // ANTHROPIC_BASE_URL names a gateway: the key belongs to the gateway (#1800).
200
+ const anthropicBaseUrl = env.ANTHROPIC_BASE_URL?.trim();
201
+ const keyIsForGateway =
202
+ Boolean(anthropicBaseUrl) && !isFirstPartyAnthropicUrl(anthropicBaseUrl ?? "");
203
+ const key = keyIsForGateway ? undefined : (env.ANTHROPIC_API_KEY ?? getEnvKey("anthropic"));
204
+ if (!key) return null;
205
+ return {
206
+ kind,
207
+ apiKey: selectCredential(key, undefined, "ANTHROPIC_API_KEY").selected,
208
+ modelDefault: resolveRaterModelString(kind),
209
+ };
210
+ }
211
+ case "openai": {
212
+ const key = env.OPENAI_API_KEY ?? getEnvKey("openai");
213
+ if (!key) return null;
214
+ return {
215
+ kind,
216
+ apiKey: selectCredential(key, undefined, "OPENAI_API_KEY").selected,
217
+ modelDefault: resolveRaterModelString(kind),
218
+ };
219
+ }
220
+ case "openai-codex":
221
+ // Only if we have apiUrl + apiKey to probe the config store.
222
+ if (!opts.apiUrl || !opts.apiKey) return null;
223
+ return resolveCodexOAuthCredential(opts, opts.apiUrl, opts.apiKey);
224
+ case "claude-cli":
225
+ // `AGENT_SWARM_CLAUDE_OAUTH_TOKEN` is the mirror set by claude-adapter.ts
226
+ // before spawning `claude` — the CLI strips `CLAUDE_CODE_OAUTH_TOKEN` from
227
+ // hook subprocesses (security), so the hook reads the mirror instead.
228
+ if (!env.AGENT_SWARM_CLAUDE_OAUTH_TOKEN && !env.CLAUDE_CODE_OAUTH_TOKEN) return null;
229
+ return { kind, modelDefault: resolveRaterModelString(kind) };
158
230
  }
231
+ }
159
232
 
160
- // 3. OpenAI.
161
- const openaiKey = env.OPENAI_API_KEY ?? getEnvKey("openai");
162
- if (openaiKey) {
163
- return {
164
- kind: "openai",
165
- apiKey: selectCredential(openaiKey, undefined, "OPENAI_API_KEY").selected,
166
- modelDefault: resolveRaterModelString("openai"),
233
+ async function resolveCodexOAuthCredential(
234
+ opts: ResolveCredentialOptions,
235
+ apiUrl: string,
236
+ apiKey: string,
237
+ ): Promise<ResolvedCredential | null> {
238
+ const getCodex = opts._getValidCodexOAuth ?? getValidCodexOAuth;
239
+ const getOAuth = opts._getOAuthApiKey ?? resolveOAuthApiKey;
240
+ const persistCodex = opts._persistCodexOAuth ?? persistCodexOAuth;
241
+ try {
242
+ const codexCreds = await getCodex(apiUrl, apiKey);
243
+ if (!codexCreds) return null;
244
+ // pi-ai expects a Record<providerID, OAuthCredentials>.
245
+ const credMap: Record<string, OAuthCredentials> = {
246
+ "openai-codex": {
247
+ access: codexCreds.access,
248
+ refresh: codexCreds.refresh,
249
+ expires: codexCreds.expires,
250
+ },
167
251
  };
168
- }
169
-
170
- // 4. Codex OAuth — only if we have apiUrl + apiKey to probe the config store.
171
- if (opts.apiUrl && opts.apiKey) {
172
- try {
173
- const codexCreds = await getCodex(opts.apiUrl, opts.apiKey);
174
- if (codexCreds) {
175
- // pi-ai expects a Record<providerID, OAuthCredentials>.
176
- const credMap: Record<string, OAuthCredentials> = {
177
- "openai-codex": {
178
- access: codexCreds.access,
179
- refresh: codexCreds.refresh,
180
- expires: codexCreds.expires,
181
- },
182
- };
183
- const oauthResult = await getOAuth("openai-codex", credMap);
184
- if (oauthResult) {
185
- // Persist any rotated refresh token — best-effort, must not block.
186
- // Wrap defensively here even though the production helper already
187
- // swallows; tests may inject a throwing hook, and persistence
188
- // failure must never prevent the current call from succeeding.
189
- if (oauthResult.newCredentials) {
190
- const updated = oauthResult.newCredentials;
191
- try {
192
- await persistCodex(opts.apiUrl, opts.apiKey, {
193
- access: String(updated.access),
194
- refresh: String(updated.refresh),
195
- expires: Number(updated.expires),
196
- accountId: codexCreds.accountId,
197
- });
198
- } catch (err) {
199
- console.error(
200
- `internal-ai: persistCodexOAuth failed (callerTag=${opts.callerTag ?? "<unset>"}):`,
201
- err,
202
- );
203
- }
204
- }
205
- return {
206
- kind: "openai-codex",
207
- apiKey: oauthResult.apiKey,
208
- modelDefault: resolveRaterModelString("openai-codex"),
209
- };
210
- }
252
+ const oauthResult = await getOAuth("openai-codex", credMap);
253
+ if (!oauthResult) return null;
254
+ // Persist any rotated refresh token — best-effort, must not block.
255
+ // Wrap defensively here even though the production helper already
256
+ // swallows; tests may inject a throwing hook, and persistence
257
+ // failure must never prevent the current call from succeeding.
258
+ if (oauthResult.newCredentials) {
259
+ const updated = oauthResult.newCredentials;
260
+ try {
261
+ await persistCodex(apiUrl, apiKey, {
262
+ access: String(updated.access),
263
+ refresh: String(updated.refresh),
264
+ expires: Number(updated.expires),
265
+ accountId: codexCreds.accountId,
266
+ });
267
+ } catch (err) {
268
+ console.error(
269
+ `internal-ai: persistCodexOAuth failed (callerTag=${opts.callerTag ?? "<unset>"}):`,
270
+ err,
271
+ );
211
272
  }
212
- } catch (err) {
213
- console.error(
214
- `internal-ai: codex OAuth probe failed (callerTag=${opts.callerTag ?? "<unset>"}):`,
215
- err,
216
- );
217
- // Fall through to claude-cli fallback below.
218
273
  }
219
- }
220
-
221
- // 5. CLAUDE_CODE_OAUTH_TOKEN → claude-cli fallback.
222
- // `AGENT_SWARM_CLAUDE_OAUTH_TOKEN` is the mirror set by claude-adapter.ts
223
- // before spawning `claude` — the CLI strips `CLAUDE_CODE_OAUTH_TOKEN` from
224
- // hook subprocesses (security), so the hook reads the mirror instead.
225
- if (env.AGENT_SWARM_CLAUDE_OAUTH_TOKEN || env.CLAUDE_CODE_OAUTH_TOKEN) {
226
274
  return {
227
- kind: "claude-cli",
228
- modelDefault: resolveRaterModelString("claude-cli"),
275
+ kind: "openai-codex",
276
+ apiKey: oauthResult.apiKey,
277
+ modelDefault: resolveRaterModelString("openai-codex"),
229
278
  };
279
+ } catch (err) {
280
+ console.error(
281
+ `internal-ai: codex OAuth probe failed (callerTag=${opts.callerTag ?? "<unset>"}):`,
282
+ err,
283
+ );
284
+ // The caller falls through to the claude-cli fallback.
285
+ return null;
230
286
  }
231
-
232
- // 6. No creds.
233
- return null;
234
287
  }
235
288
 
236
289
  // Re-export for convenience to keep imports flat.
@@ -19,11 +19,14 @@ export {
19
19
  completeStructuredWithModel,
20
20
  } from "./complete-structured.js";
21
21
  export {
22
+ CREDENTIAL_KIND_PRECEDENCE,
22
23
  type CredentialKind,
23
24
  DEFAULT_MODEL,
24
25
  type ResolveCredentialOptions,
26
+ type ResolveCredentialsOptions,
25
27
  type ResolvedCredential,
26
28
  resolveCredential,
29
+ resolveCredentials,
27
30
  } from "./credentials.js";
28
31
  export {
29
32
  MEMORY_RATER_DEFAULT_MODEL,
@@ -36,3 +39,10 @@ export {
36
39
  summarizeSession,
37
40
  summaryToolSchema,
38
41
  } from "./summarize-session.js";
42
+ export {
43
+ type GenerateTaskTitleOptions,
44
+ type GenerateTaskTitleResult,
45
+ generateTaskTitle,
46
+ TASK_TITLE_MODEL_CANDIDATES,
47
+ TASK_TITLE_MODEL_ENV,
48
+ } from "./task-title.js";
@@ -59,6 +59,24 @@ export function resolveRaterModelString(kind: CredentialKind): string {
59
59
  return process.env.MEMORY_RATER_MODEL ?? MEMORY_RATER_DEFAULT_MODEL[kind];
60
60
  }
61
61
 
62
+ const PROVIDER_TO_KIND: Record<string, CredentialKind> = {
63
+ openrouter: "openrouter",
64
+ anthropic: "anthropic",
65
+ openai: "openai",
66
+ "openai-codex": "openai-codex",
67
+ };
68
+
69
+ /**
70
+ * Credential kind a model string belongs to. `provider/model-id` maps through
71
+ * the provider prefix; a bare alias (`haiku`) is a `claude -p` model. `null`
72
+ * for a provider the internal-ai layer cannot call.
73
+ */
74
+ export function kindForModelString(modelStr: string): CredentialKind | null {
75
+ const slash = modelStr.indexOf("/");
76
+ if (slash < 0) return "claude-cli";
77
+ return PROVIDER_TO_KIND[modelStr.slice(0, slash)] ?? null;
78
+ }
79
+
62
80
  /**
63
81
  * Split a `provider/model-id` string on the FIRST `/` so that OpenRouter
64
82
  * compound IDs like `openrouter/deepseek/deepseek-v4.1-flash` parse as