@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
@@ -13,10 +13,12 @@ import {
13
13
  getLatestScriptRunStepTaskByContextKey,
14
14
  getScriptRun,
15
15
  getScriptRunByIdempotencyKey,
16
+ getScriptRunFollowUpGuard,
16
17
  getScriptRunJournalStep,
17
18
  getTaskById,
18
19
  listScriptRunJournalSteps,
19
20
  listScriptRuns,
21
+ setScriptRunFollowUpGuard,
20
22
  updateScriptRun,
21
23
  updateScriptRunIfNotTerminal,
22
24
  updateScriptRunIfRunning,
@@ -24,6 +26,7 @@ import {
24
26
  } from "../be/db";
25
27
  import { explicitModelErrorForAgent } from "../be/model-validation";
26
28
  import { refuseSourceWithSecrets } from "../be/scripts/source-secrets";
29
+ import { can } from "../rbac";
27
30
  import { lintWorkflowLabels } from "../script-workflows/label-lint";
28
31
  import { scriptRunMaxAgentTasks, scriptRunMaxSteps } from "../script-workflows/limits";
29
32
  import {
@@ -32,6 +35,12 @@ import {
32
35
  terminateScriptRunProcess,
33
36
  } from "../script-workflows/supervisor";
34
37
  import {
38
+ evaluateFollowUpRedelegation,
39
+ type FollowUpRedelegationBlock,
40
+ followUpThreadForSourceTask,
41
+ } from "../tasks/follow-up-redelegation-guard";
42
+ import {
43
+ type AgentTask,
35
44
  AgentTaskStatusSchema,
36
45
  RoutingReasonSchema,
37
46
  ScriptRunJournalEntrySchema,
@@ -111,6 +120,8 @@ const agentTaskBodySchema = z.object({
111
120
  parentTaskId: z.string().uuid().optional(),
112
121
  requestedByUserId: z.string().optional(),
113
122
  outputSchema: z.record(z.string(), z.unknown()).optional(),
123
+ /** Dispatch even when the follow-up re-delegation guard blocks it (same flag as send-task). */
124
+ allowRedelegation: z.boolean().optional(),
114
125
  });
115
126
 
116
127
  const scriptRunCreatedSchema = z.object({
@@ -298,10 +309,70 @@ const agentTaskRoute = route({
298
309
  description: "Agent task created or still running",
299
310
  schema: z.object({ taskId: z.string(), status: AgentTaskStatusSchema }),
300
311
  },
312
+ 403: {
313
+ description:
314
+ "Script run belongs to another agent, or the run's agent may not create a child task under parentTaskId",
315
+ },
301
316
  404: { description: "Script run not found" },
317
+ 409: { description: "Agent task failed, was cancelled, or was superseded" },
318
+ 422: { description: "Blocked by the follow-up re-delegation guard" },
302
319
  },
303
320
  });
304
321
 
322
+ /**
323
+ * Send-task's task.parent.attach decision for a `ctx.step.agentTask` step: a
324
+ * step inherits its parent's contextKey (its Slack or chat thread) and
325
+ * requester, so the run's agent needs the right to hold a child under it.
326
+ * Returns the 403 message, or null when allowed. A parent id with no task row
327
+ * passes: a step under it inherits nothing, as on send-task.
328
+ */
329
+ async function stepParentRefusal(runAgentId: string, parentTaskId: string): Promise<string | null> {
330
+ const parentTask = await getTaskById(parentTaskId);
331
+ if (!parentTask) return null;
332
+ const runAgent = await getAgentById(runAgentId);
333
+ const decision = can({
334
+ principal: { kind: "agent", agentId: runAgentId, isLead: runAgent?.isLead ?? false },
335
+ verb: "task.parent.attach",
336
+ resource: {
337
+ kind: "task",
338
+ taskId: parentTask.id,
339
+ requestedByUserId: parentTask.requestedByUserId,
340
+ creatorAgentId: parentTask.creatorAgentId,
341
+ agentId: parentTask.agentId,
342
+ },
343
+ source: "http",
344
+ });
345
+ return decision.allow
346
+ ? null
347
+ : `You don't have context for parent task ${parentTaskId}. Only its assignee, its creator, or a lead can create a child task under it.`;
348
+ }
349
+
350
+ /**
351
+ * The launching session's task (X-Source-Task-Id), kept only when it belongs
352
+ * to the launching agent so a run cannot borrow another agent's session.
353
+ */
354
+ async function resolveRunSourceTaskId(
355
+ req: IncomingMessage,
356
+ agentId: string,
357
+ ): Promise<string | undefined> {
358
+ const raw = req.headers["x-source-task-id"];
359
+ const sourceTaskId = Array.isArray(raw) ? raw[0] : raw;
360
+ if (!sourceTaskId) return undefined;
361
+ const task = await getTaskById(sourceTaskId);
362
+ return task?.agentId === agentId ? task.id : undefined;
363
+ }
364
+
365
+ /** One `ctx.step.agentTask` dispatch: the step task, or the guard rule that blocked it. */
366
+ type StepDispatch =
367
+ | {
368
+ task: AgentTask;
369
+ /** True when this call inserted the step, false on a replay. */
370
+ created?: boolean;
371
+ blocked?: undefined;
372
+ override?: FollowUpRedelegationBlock["rule"];
373
+ }
374
+ | { task?: undefined; blocked: FollowUpRedelegationBlock; override?: undefined };
375
+
305
376
  async function requireAgent(res: ServerResponse, agentId: string | undefined) {
306
377
  if (!agentId) {
307
378
  jsonError(res, "X-Agent-ID required for script runs API", 400);
@@ -408,6 +479,7 @@ export async function handleScriptRuns(
408
479
  // Cap check and insert commit together: N concurrent POSTs otherwise all
409
480
  // read cap-1 active runs and all insert, exceeding the cap.
410
481
  const cap = scriptRunConcurrencyCap();
482
+ const sourceTaskId = await resolveRunSourceTaskId(req, agent.id);
411
483
  const creation = await getDbClient().transaction(async () => {
412
484
  if ((await countActiveScriptRuns()) >= cap) return null;
413
485
  return await createScriptRun({
@@ -418,6 +490,7 @@ export async function handleScriptRuns(
418
490
  scriptName: parsed.body.scriptName,
419
491
  idempotencyKey: parsed.body.idempotencyKey,
420
492
  requestedByUserId: parsed.body.requestedByUserId,
493
+ sourceTaskId,
421
494
  createdBy: parsed.body.requestedByUserId,
422
495
  });
423
496
  });
@@ -629,9 +702,29 @@ export async function handleScriptRuns(
629
702
  return true;
630
703
  }
631
704
 
705
+ // Only the run's own harness dispatches its steps (it sends the run's
706
+ // agent id). Step tasks carry that agent as creator, so another agent
707
+ // must not create them.
708
+ if (agentId !== run.agentId) {
709
+ jsonError(res, "Script run belongs to another agent", 403);
710
+ return true;
711
+ }
712
+
713
+ // The requested parent, checked before any lookup or insert so a refused
714
+ // call creates nothing.
715
+ if (parsed.body.parentTaskId) {
716
+ const refusal = await stepParentRefusal(run.agentId, parsed.body.parentTaskId);
717
+ if (refusal) {
718
+ jsonError(res, refusal, 403);
719
+ return true;
720
+ }
721
+ }
722
+
632
723
  const contextKey = `script-run:${run.id}:${parsed.body.stepKey}`;
633
724
  let task = await getLatestScriptRunStepTaskByContextKey(contextKey);
725
+ let dispatchedHere = false;
634
726
  if (!task) {
727
+ const taskDescription = parsed.body.template ?? parsed.body.task ?? parsed.body.stepKey;
635
728
  const modelError = await explicitModelErrorForAgent({
636
729
  model: parsed.body.model,
637
730
  allowCustomModel: parsed.body.allowCustomModel,
@@ -641,10 +734,47 @@ export async function handleScriptRuns(
641
734
  jsonError(res, modelError, 400);
642
735
  return true;
643
736
  }
737
+ const followUpThread = await followUpThreadForSourceTask(run.sourceTaskId);
738
+ // The replay lookup, the guard decision and the insert share one write
739
+ // transaction. It serializes this step against the run's other steps,
740
+ // other runs and send-task dispatches into the same thread.
741
+ let dispatched: StepDispatch;
644
742
  try {
645
- task = await createTaskExtended(
646
- parsed.body.template ?? parsed.body.task ?? parsed.body.stepKey,
647
- {
743
+ dispatched = await getDbClient().transaction(async (): Promise<StepDispatch> => {
744
+ const replayed = await getLatestScriptRunStepTaskByContextKey(contextKey);
745
+ if (replayed) return { task: replayed };
746
+
747
+ // Follow-up re-delegation guard, as in send-task. A run is one
748
+ // dispatch: its first new step decides for the whole run, and every
749
+ // later step follows that decision. Later steps never compare against
750
+ // the run's own completed steps, a long run cannot use up the thread's
751
+ // loop cap, and parallel steps are dispatched all or none.
752
+ let override: FollowUpRedelegationBlock["rule"] | undefined;
753
+ if (followUpThread) {
754
+ const decision = await getScriptRunFollowUpGuard(run.id);
755
+ if (decision !== "passed") {
756
+ const blocked: FollowUpRedelegationBlock | null =
757
+ decision === null
758
+ ? await evaluateFollowUpRedelegation({
759
+ ...followUpThread,
760
+ creatorAgentId: run.agentId,
761
+ taskDescription,
762
+ targetAgentId: parsed.body.agentId,
763
+ })
764
+ : {
765
+ rule: decision,
766
+ message: `Blocked by the follow-up re-delegation guard (${decision} rule): an earlier step of this script run was blocked, and a run counts as one dispatch. If it must run, pass allowRedelegation: true.`,
767
+ };
768
+ if (blocked && !parsed.body.allowRedelegation) {
769
+ if (decision === null) await setScriptRunFollowUpGuard(run.id, blocked.rule);
770
+ return { blocked };
771
+ }
772
+ override = blocked?.rule;
773
+ await setScriptRunFollowUpGuard(run.id, "passed");
774
+ }
775
+ }
776
+
777
+ const created = await createTaskExtended(taskDescription, {
648
778
  agentId: parsed.body.agentId,
649
779
  routingReason:
650
780
  parsed.body.routingReason ?? (parsed.body.agentId ? "human_pinned" : undefined),
@@ -660,6 +790,20 @@ export async function handleScriptRuns(
660
790
  offeredTo: parsed.body.offerMode ? parsed.body.agentId : undefined,
661
791
  taskType: "script-run-step",
662
792
  source: "mcp",
793
+ // Counts the run toward the launcher's follow-up loop cap.
794
+ creatorAgentId: run.agentId,
795
+ // With no parent, the step inherits the launching session's Slack
796
+ // thread, as a send-task from that session does.
797
+ sourceTaskId: run.sourceTaskId,
798
+ // A guarded run's steps are its dispatches into the follow-up's
799
+ // thread, whatever their parent, so the guard sees them there.
800
+ ...(followUpThread
801
+ ? {
802
+ slackChannelId: followUpThread.channelId,
803
+ slackThreadTs: followUpThread.threadTs,
804
+ slackUserId: followUpThread.userId,
805
+ }
806
+ : {}),
663
807
  dir: parsed.body.dir,
664
808
  vcsRepo: parsed.body.vcsRepo,
665
809
  model: parsed.body.model,
@@ -667,8 +811,9 @@ export async function handleScriptRuns(
667
811
  requestedByUserId: parsed.body.requestedByUserId ?? run.requestedByUserId,
668
812
  outputSchema: parsed.body.outputSchema,
669
813
  contextKey,
670
- },
671
- );
814
+ });
815
+ return { task: created, created: true, override };
816
+ });
672
817
  } catch (error) {
673
818
  if (error instanceof ExtensionAgentAssignmentError) {
674
819
  jsonError(res, error.message, 400);
@@ -676,6 +821,30 @@ export async function handleScriptRuns(
676
821
  }
677
822
  throw error;
678
823
  }
824
+ if (dispatched.override) {
825
+ console.log(
826
+ `[script-runs] follow-up re-delegation override: rule=${dispatched.override} runId=${run.id} creatorAgentId=${run.agentId} sourceTaskId=${run.sourceTaskId} thread=${followUpThread?.channelId}/${followUpThread?.threadTs}`,
827
+ );
828
+ }
829
+ if (!dispatched.task) {
830
+ json(res, { error: dispatched.blocked.message, rule: dispatched.blocked.rule }, 422);
831
+ return true;
832
+ }
833
+ task = dispatched.task;
834
+ dispatchedHere = dispatched.created === true;
835
+ }
836
+
837
+ // A replay answers from the selected step, whatever parent this call sent
838
+ // (or omitted), so authorize the parent the step actually holds. Every
839
+ // replay lookup above lands here before any output is returned. A step this
840
+ // call just dispatched was authorized above; refusing it now would leave
841
+ // the work running behind a 403.
842
+ if (!dispatchedHere && task.parentTaskId) {
843
+ const refusal = await stepParentRefusal(run.agentId, task.parentTaskId);
844
+ if (refusal) {
845
+ jsonError(res, refusal, 403);
846
+ return true;
847
+ }
679
848
  }
680
849
 
681
850
  const deadline = Date.now() + 30_000;
@@ -45,6 +45,7 @@ import { getSlackConnectionState } from "../slack/connection-state";
45
45
  import { isTelemetryEnabled } from "../telemetry";
46
46
  import { type AgentCredStatus, AutomationIntegrationIdSchema, ProviderNameSchema } from "../types";
47
47
  import { isRuntimePoolsEnabled } from "../utils/runtime-pools-flag";
48
+ import { resolveAppUrl } from "./config-values";
48
49
  import { route } from "./route-def";
49
50
  import { json, jsonError } from "./utils";
50
51
 
@@ -116,6 +117,12 @@ export const StatusIdentitySchema = z.object({
116
117
  * cloud installs can be sliced server-side.
117
118
  */
118
119
  org_id: z.string().nullable(),
120
+ /**
121
+ * The swarm's public dashboard URL (`APP_URL`, then `DASHBOARD_URL`, with
122
+ * swarm_config precedence). Null when neither is set. The dashboard reads
123
+ * links to it in agent output as its own. Absent on older APIs.
124
+ */
125
+ dashboard_url: z.string().nullable().optional(),
119
126
  });
120
127
  export type StatusIdentity = z.infer<typeof StatusIdentitySchema>;
121
128
 
@@ -334,7 +341,7 @@ export async function rollupCredStatusForProvider(provider: string): Promise<Cre
334
341
 
335
342
  // ─── Identity ────────────────────────────────────────────────────────────────
336
343
 
337
- function buildIdentity(): StatusIdentity {
344
+ function buildIdentity(dashboardUrl: string | null): StatusIdentity {
338
345
  const cloudRaw = process.env.SWARM_CLOUD;
339
346
  const hideRaw = process.env.SWARM_HIDE_CLOUD_PROMO;
340
347
  return {
@@ -345,6 +352,7 @@ function buildIdentity(): StatusIdentity {
345
352
  marketing_url: process.env.SWARM_MARKETING_URL?.trim() || null,
346
353
  hide_cloud_promo: hideRaw === "true" || hideRaw === "1",
347
354
  org_id: process.env.SWARM_ORG_ID?.trim() || null,
355
+ dashboard_url: dashboardUrl,
348
356
  };
349
357
  }
350
358
 
@@ -759,7 +767,7 @@ export async function buildStatusPayload(): Promise<StatusResponse> {
759
767
  const instance = await getInstanceFeedbackSignals();
760
768
  return {
761
769
  runtime_pools: { enabled: isRuntimePoolsEnabled(), supervisor: runtimeSupervisorStatus() },
762
- identity: buildIdentity(),
770
+ identity: buildIdentity(await resolveAppUrl()),
763
771
  setup,
764
772
  activity: { ...activity, leads_ready: readyLeads },
765
773
  agent_fs: {
package/src/http/tasks.ts CHANGED
@@ -84,7 +84,7 @@ import {
84
84
  import { ensure } from "../utils/business-use";
85
85
  import { getRequestAuth } from "../utils/request-auth-context";
86
86
  import { scrubSecrets } from "../utils/secret-scrubber";
87
- import { createListTotalCache } from "./list-total-cache";
87
+ import { createListTotalCache, LIST_TOTAL_TTL_MS } from "./list-total-cache";
88
88
  import {
89
89
  agentFirstPrincipal,
90
90
  authUserId,
@@ -224,7 +224,9 @@ const SupersedeTaskResponseSchema = z.object({
224
224
 
225
225
  // ─── Route Definitions ───────────────────────────────────────────────────────
226
226
 
227
- const taskTotals = createListTotalCache();
227
+ // The task list total scans every visible task, and only the pager shows it,
228
+ // so page 1 polls reuse a total younger than 15 s.
229
+ const taskTotals = createListTotalCache(LIST_TOTAL_TTL_MS, 15_000);
228
230
 
229
231
  const listTasks = route({
230
232
  method: "get",
@@ -377,11 +377,23 @@ export const SWARM_UI_JS = `
377
377
  closeCursorRoomIfUnused(record);
378
378
  }
379
379
 
380
+ // Colors and fonts read the page theme tokens (designVersion 2). Each
381
+ // fallback is the original literal, so a designVersion 1 page, which has no
382
+ // tokens, renders exactly as before.
380
383
  var SEV_COLOR = {
381
- error: '#ef4444',
382
- warn: '#f59e0b',
383
- info: '#3b82f6',
384
+ error: 'var(--color-status-error-strong, #ef4444)',
385
+ warn: 'var(--color-status-warning-strong, #f59e0b)',
386
+ info: 'var(--color-status-info-strong, #3b82f6)',
384
387
  };
388
+ var MUTED = 'var(--color-muted-foreground, #7c8aa6)';
389
+ var MONO = "var(--font-mono, 'Space Mono', ui-monospace, monospace)";
390
+
391
+ // A color at a lower alpha. Mixing with transparent keeps the color and
392
+ // scales its alpha, so tint('#ef4444', '13.33%') equals #ef444422 and
393
+ // tint('rgb(34,197,94)', '10%') equals rgba(34,197,94,0.10).
394
+ function tint(color, percent) {
395
+ return 'color-mix(in srgb, ' + color + ' ' + percent + ', transparent)';
396
+ }
385
397
 
386
398
  function esc(s) {
387
399
  return String(s == null ? '' : s)
@@ -416,7 +428,7 @@ export const SWARM_UI_JS = `
416
428
  '<span class="swarm-diff-annot no-print" '
417
429
  + 'style="display:inline-block;margin-left:8px;padding:1px 6px;'
418
430
  + 'border-radius:4px;font-size:11px;font-weight:600;'
419
- + 'background:' + color + '22;color:' + color + ';border:1px solid ' + color + '55;">'
431
+ + 'background:' + tint(color, '13.33%') + ';color:' + color + ';border:1px solid ' + tint(color, '33.33%') + ';">'
420
432
  + esc((ann && ann.severity ? ann.severity.toUpperCase() : 'INFO')) + ' · ' + esc(ann && ann.text || '')
421
433
  + '</span>'
422
434
  );
@@ -448,12 +460,12 @@ export const SWARM_UI_JS = `
448
460
  var text = line.text == null ? '' : line.text;
449
461
  var bg, oldCell, newCell, sign;
450
462
  if (type === 'add') {
451
- bg = 'rgba(34,197,94,0.10)';
463
+ bg = tint('var(--color-status-success, rgb(34,197,94))', '10%');
452
464
  oldCell = '';
453
465
  newCell = String(newN++);
454
466
  sign = '+';
455
467
  } else if (type === 'del') {
456
- bg = 'rgba(239,68,68,0.10)';
468
+ bg = tint('var(--color-status-error, rgb(239,68,68))', '10%');
457
469
  oldCell = String(oldN++);
458
470
  newCell = '';
459
471
  sign = '-';
@@ -465,15 +477,17 @@ export const SWARM_UI_JS = `
465
477
  }
466
478
 
467
479
  var annHtml = '';
468
- var anns = annByLine[Number(newCell)] || annByLine[Number(oldCell)] || [];
480
+ // Annotations name new-side lines. A deleted row has no new-side
481
+ // number, so only a deleted row matches on its old-side number.
482
+ var anns = annByLine[Number(type === 'del' ? oldCell : newCell)] || [];
469
483
  for (var k = 0; k < anns.length; k++) annHtml += renderAnnotation(anns[k]);
470
484
 
471
485
  rowsHtml += (
472
486
  '<tr style="background:' + bg + ';">'
473
- + '<td class="swarm-diff-gutter" style="user-select:none;text-align:right;padding:0 8px;color:#7c8aa6;font-size:12px;width:48px;">' + esc(oldCell) + '</td>'
474
- + '<td class="swarm-diff-gutter" style="user-select:none;text-align:right;padding:0 8px;color:#7c8aa6;font-size:12px;width:48px;">' + esc(newCell) + '</td>'
475
- + '<td class="swarm-diff-sign" style="user-select:none;text-align:center;padding:0 4px;color:#7c8aa6;font-size:12px;width:18px;">' + esc(sign) + '</td>'
476
- + '<td class="swarm-diff-code" style="padding:0 8px;white-space:pre-wrap;word-break:break-word;font-family:\\'Space Mono\\',ui-monospace,monospace;font-size:12px;">' + esc(text) + annHtml + '</td>'
487
+ + '<td class="swarm-diff-gutter" style="user-select:none;text-align:right;padding:0 8px;color:' + MUTED + ';font-size:12px;width:48px;">' + esc(oldCell) + '</td>'
488
+ + '<td class="swarm-diff-gutter" style="user-select:none;text-align:right;padding:0 8px;color:' + MUTED + ';font-size:12px;width:48px;">' + esc(newCell) + '</td>'
489
+ + '<td class="swarm-diff-sign" style="user-select:none;text-align:center;padding:0 4px;color:' + MUTED + ';font-size:12px;width:18px;">' + esc(sign) + '</td>'
490
+ + '<td class="swarm-diff-code" style="padding:0 8px;white-space:pre-wrap;word-break:break-word;font-family:' + MONO + ';font-size:12px;">' + esc(text) + annHtml + '</td>'
477
491
  + '</tr>'
478
492
  );
479
493
  }
@@ -485,8 +499,8 @@ export const SWARM_UI_JS = `
485
499
  );
486
500
 
487
501
  return (
488
- '<a id="' + esc(anchorId) + '" class="swarm-diff-anchor" data-hunk="' + esc(anchorSlug) + '"></a>'
489
- + '<div class="swarm-diff-hunk-header" style="padding:6px 12px;background:rgba(124,138,166,0.10);color:#7c8aa6;font-family:\\'Space Mono\\',ui-monospace,monospace;font-size:11px;border-top:1px solid var(--swarm-border,#22304a);">'
502
+ '<a id="' + esc(anchorId) + '" class="swarm-diff-anchor" data-hunk="' + esc(anchorSlug) + '" data-new-start="' + esc(newStart) + '"></a>'
503
+ + '<div class="swarm-diff-hunk-header" style="padding:6px 12px;background:' + tint('var(--color-muted-foreground, rgb(124,138,166))', '10%') + ';color:' + MUTED + ';font-family:' + MONO + ';font-size:11px;border-top:1px solid var(--swarm-border,#22304a);">'
490
504
  + esc(header)
491
505
  + '</div>'
492
506
  + '<table class="swarm-diff-table" style="width:100%;border-collapse:collapse;table-layout:fixed;">'
@@ -503,7 +517,7 @@ export const SWARM_UI_JS = `
503
517
 
504
518
  var shaLine = '';
505
519
  if (baseSha || headSha) {
506
- shaLine = '<span class="swarm-diff-sha" style="font-family:\\'Space Mono\\',ui-monospace,monospace;font-size:11px;color:#7c8aa6;">'
520
+ shaLine = '<span class="swarm-diff-sha" style="font-family:' + MONO + ';font-size:11px;color:' + MUTED + ';">'
507
521
  + esc(baseSha) + ' → ' + esc(headSha)
508
522
  + '</span>';
509
523
  }
@@ -515,8 +529,8 @@ export const SWARM_UI_JS = `
515
529
 
516
530
  rootEl.innerHTML = (
517
531
  '<div class="swarm-diff-root" style="border:1px solid var(--swarm-border,#22304a);border-radius:8px;background:var(--swarm-card,#121826);overflow:hidden;margin:12px 0;break-inside:avoid;">'
518
- + '<div class="swarm-diff-header" style="display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:rgba(59,130,246,0.10);border-bottom:1px solid var(--swarm-border,#22304a);">'
519
- + '<span class="swarm-diff-file" style="font-family:\\'Space Mono\\',ui-monospace,monospace;font-size:13px;font-weight:700;color:var(--swarm-text,#e6eaf2);">' + esc(file || '(untitled)') + '</span>'
532
+ + '<div class="swarm-diff-header" style="display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--color-surface, rgba(59,130,246,0.10));border-bottom:1px solid var(--swarm-border,#22304a);">'
533
+ + '<span class="swarm-diff-file" style="font-family:' + MONO + ';font-size:13px;font-weight:700;color:var(--swarm-text,#e6eaf2);">' + esc(file || '(untitled)') + '</span>'
520
534
  + shaLine
521
535
  + '</div>'
522
536
  + hunksHtml
@@ -580,21 +594,34 @@ export const SWARM_UI_JS = `
580
594
  var renderJumps = function() {
581
595
  var anchors = document.querySelectorAll('.swarm-diff-anchor[data-hunk]');
582
596
  if (!anchors.length) {
583
- self.innerHTML = '<span class="no-print" style="color:#7c8aa6;font-size:12px;">No hunks yet.</span>';
597
+ self.innerHTML = '<span class="no-print" style="color:' + MUTED + ';font-size:12px;">No hunks yet.</span>';
584
598
  return;
585
599
  }
586
- var items = '';
600
+ var labels = [];
601
+ var fileCounts = new Map();
587
602
  for (var i = 0; i < anchors.length; i++) {
588
603
  var a = anchors[i];
589
604
  var slug = a.getAttribute('data-hunk') || ('hunk-' + i);
590
605
  // Hunk title = nearest preceding diff's file attribute if available.
591
606
  var diff = a.closest && a.closest('swarm-diff');
592
607
  var file = (diff && diff.getAttribute('file')) || slug;
593
- items += '<li style="margin:0;padding:2px 0;"><a href="#' + esc(a.id) + '" style="color:#3b82f6;text-decoration:none;font-family:\\'Space Mono\\',ui-monospace,monospace;font-size:12px;">' + esc(file) + '</a></li>';
608
+ labels.push(file);
609
+ fileCounts.set(file, (fileCounts.get(file) || 0) + 1);
610
+ }
611
+ var items = '';
612
+ for (var n = 0; n < anchors.length; n++) {
613
+ // A file with more than one hunk gets each hunk's new-side start
614
+ // line (src/x.ts:58), so its links differ.
615
+ var label = labels[n];
616
+ var newStart = anchors[n].getAttribute('data-new-start');
617
+ if (fileCounts.get(label) > 1 && newStart) label += ':' + newStart;
618
+ // target=_self: the page head sets <base target="_blank">, which would
619
+ // open these in-page anchors in a new tab.
620
+ items += '<li style="margin:0;padding:2px 0;"><a href="#' + esc(anchors[n].id) + '" target="_self" style="color:var(--color-primary, #3b82f6);text-decoration:none;font-family:' + MONO + ';font-size:12px;">' + esc(label) + '</a></li>';
594
621
  }
595
622
  self.innerHTML = (
596
- '<nav class="swarm-diff-jumps no-print" style="padding:8px 12px;border:1px dashed var(--swarm-border,#22304a);border-radius:8px;background:rgba(124,138,166,0.05);">'
597
- + '<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.08em;color:#7c8aa6;margin-bottom:4px;">Jump to</div>'
623
+ '<nav class="swarm-diff-jumps no-print" style="padding:8px 12px;border:1px dashed var(--swarm-border,#22304a);border-radius:8px;background:' + tint('var(--color-muted-foreground, rgb(124,138,166))', '5%') + ';">'
624
+ + '<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.08em;color:' + MUTED + ';margin-bottom:4px;">Jump to</div>'
598
625
  + '<ul style="list-style:none;padding:0;margin:0;">' + items + '</ul>'
599
626
  + '</nav>'
600
627
  );
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Hardcoded colors in an HTML page body. A design version 2 page should take
3
+ * every color from a theme token. A body with `COLOR_LITERAL_THRESHOLD` or more
4
+ * hardcoded colors gets two things: `create_page` steers its author toward
5
+ * tokens (`NUDGES.create_page`), and `/p/:id` serves it light also when the
6
+ * viewer asks for dark mode, because its hardcoded light surfaces would put
7
+ * dark-mode text on them.
8
+ */
9
+
10
+ /** From this many hardcoded colors on, a page counts as hardcoded. */
11
+ export const COLOR_LITERAL_THRESHOLD = 8;
12
+
13
+ /**
14
+ * A `<style>` block (an unclosed one runs to the end of the body), a `style="..."`
15
+ * attribute, or a `class="..."` attribute. The tag's own attributes are capped at
16
+ * 200 characters: an unbounded `[^>]*` makes a body of many unterminated `<style`
17
+ * tags cost quadratic time.
18
+ */
19
+ const STYLE_BLOCK = /<style\b[^>]{0,200}>([\s\S]*?)(?:<\/style\s*>|$)/gi;
20
+ const STYLE_ATTRIBUTE = /\sstyle\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
21
+ const CLASS_ATTRIBUTE = /\sclass\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
22
+ /**
23
+ * In style regions: `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, and the rgb, rgba,
24
+ * hsl, hsla and oklch functions.
25
+ */
26
+ const COLOR_LITERAL = /#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3,4})\b|\b(?:rgba?|hsla?|oklch)\(/gi;
27
+ /**
28
+ * In style regions: the `white` and `black` keywords as values, not inside a name
29
+ * such as `white-space`, `.white` or `--black`. Lowercase only, so a font name such
30
+ * as "Archivo Black" does not count.
31
+ */
32
+ const NAMED_COLOR = /(?<![\w.#-])(?:white|black)(?![\w-])/g;
33
+ const PALETTE_UTILITIES = [
34
+ "bg",
35
+ "text",
36
+ "border",
37
+ "ring",
38
+ "outline",
39
+ "divide",
40
+ "fill",
41
+ "stroke",
42
+ "from",
43
+ "via",
44
+ "to",
45
+ "decoration",
46
+ "accent",
47
+ "caret",
48
+ "placeholder",
49
+ "shadow",
50
+ ];
51
+ const TAILWIND_PALETTE = [
52
+ "slate",
53
+ "gray",
54
+ "zinc",
55
+ "neutral",
56
+ "stone",
57
+ "red",
58
+ "orange",
59
+ "amber",
60
+ "yellow",
61
+ "lime",
62
+ "green",
63
+ "emerald",
64
+ "teal",
65
+ "cyan",
66
+ "sky",
67
+ "blue",
68
+ "indigo",
69
+ "violet",
70
+ "purple",
71
+ "fuchsia",
72
+ "pink",
73
+ "rose",
74
+ ];
75
+ /**
76
+ * In class attributes: Tailwind palette classes (`bg-blue-500`, `hover:text-gray-600/80`),
77
+ * `bg-white`, `bg-black`, `text-white`, `text-black`, and arbitrary color values
78
+ * (`bg-[#fff]`, `text-[rgb(0_0_0)]`, `-[hsl`, `-[oklch`).
79
+ */
80
+ const COLOR_CLASS = new RegExp(
81
+ `(?<![\\w-])(?:(?:${PALETTE_UTILITIES.join("|")})-(?:${TAILWIND_PALETTE.join("|")})-\\d+|(?:bg|text)-(?:white|black))(?![\\w-])|-\\[(?:#|rgb|hsl|oklch)`,
82
+ "g",
83
+ );
84
+
85
+ function attributeValues(html: string, attribute: RegExp): string[] {
86
+ return Array.from(html.matchAll(attribute), (match) => match[1] ?? match[2] ?? "");
87
+ }
88
+
89
+ /** The values of every `class="..."` attribute in an HTML body. */
90
+ export function classAttributeValues(html: string): string[] {
91
+ return attributeValues(html, CLASS_ATTRIBUTE);
92
+ }
93
+
94
+ function countMatches(regions: string[], pattern: RegExp): number {
95
+ return regions.reduce((total, region) => total + (region.match(pattern)?.length ?? 0), 0);
96
+ }
97
+
98
+ /**
99
+ * Counts the hardcoded colors of an HTML page body: color literals and the
100
+ * `white` / `black` keywords in its `<style>` blocks and `style="..."`
101
+ * attributes, and color classes in its `class="..."` attributes. A page on the
102
+ * design system should use theme tokens instead, so a high count means a theme
103
+ * switch will not restyle it.
104
+ */
105
+ export function countColorLiterals(html: string): number {
106
+ const styles = [
107
+ ...Array.from(html.matchAll(STYLE_BLOCK), (match) => match[1] ?? ""),
108
+ ...attributeValues(html, STYLE_ATTRIBUTE),
109
+ ];
110
+ return (
111
+ countMatches(styles, COLOR_LITERAL) +
112
+ countMatches(styles, NAMED_COLOR) +
113
+ countMatches(classAttributeValues(html), COLOR_CLASS)
114
+ );
115
+ }