@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
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Page theme token contract: the dashboard token names that HTML pages
3
+ * (design version 2) expose.
4
+ *
5
+ * A page's look comes only from these custom properties. Presets, per-page
6
+ * overrides and imported themes all resolve to this set, so one switch
7
+ * restyles the whole page. Sidebar, action, peer, type-scale and easing
8
+ * tokens are dashboard-only and stay out of the contract.
9
+ */
10
+
11
+ /** Color tokens: one value per mode. */
12
+ export const PAGE_THEME_COLOR_TOKENS = [
13
+ "--color-background",
14
+ "--color-foreground",
15
+ "--color-card",
16
+ "--color-card-foreground",
17
+ "--color-surface",
18
+ "--color-popover",
19
+ "--color-popover-foreground",
20
+ "--color-primary",
21
+ "--color-primary-foreground",
22
+ "--color-secondary",
23
+ "--color-secondary-foreground",
24
+ "--color-muted",
25
+ "--color-muted-foreground",
26
+ "--color-accent",
27
+ "--color-accent-foreground",
28
+ "--color-destructive",
29
+ "--color-destructive-foreground",
30
+ "--color-border",
31
+ "--color-border-subtle",
32
+ "--color-input",
33
+ "--color-ring",
34
+ "--color-chart-1",
35
+ "--color-chart-2",
36
+ "--color-chart-3",
37
+ "--color-chart-4",
38
+ "--color-chart-5",
39
+ "--color-status-success",
40
+ "--color-status-success-strong",
41
+ "--color-status-success-foreground",
42
+ "--color-status-active",
43
+ "--color-status-active-strong",
44
+ "--color-status-active-foreground",
45
+ "--color-status-error",
46
+ "--color-status-error-strong",
47
+ "--color-status-error-foreground",
48
+ "--color-status-info",
49
+ "--color-status-info-strong",
50
+ "--color-status-info-foreground",
51
+ "--color-status-pending",
52
+ "--color-status-pending-strong",
53
+ "--color-status-pending-foreground",
54
+ "--color-status-warning",
55
+ "--color-status-warning-strong",
56
+ "--color-status-warning-foreground",
57
+ "--color-status-paused",
58
+ "--color-status-paused-strong",
59
+ "--color-status-paused-foreground",
60
+ "--color-status-neutral",
61
+ "--color-status-neutral-strong",
62
+ "--color-status-neutral-foreground",
63
+ "--color-status-success-solid",
64
+ "--color-status-active-solid",
65
+ ] as const;
66
+
67
+ /** Font and radius tokens: one value for both modes. */
68
+ export const PAGE_THEME_SHARED_TOKENS = [
69
+ "--font-sans",
70
+ "--font-mono",
71
+ "--radius-sm",
72
+ "--radius-md",
73
+ "--radius-lg",
74
+ "--radius-xl",
75
+ ] as const;
76
+
77
+ export const PAGE_THEME_TOKENS = [...PAGE_THEME_COLOR_TOKENS, ...PAGE_THEME_SHARED_TOKENS] as const;
78
+
79
+ export type PageThemeToken = (typeof PAGE_THEME_TOKENS)[number];
80
+ export type PageThemeTokenValues = Partial<Record<PageThemeToken, string>>;
81
+ export type PageThemeTokens = { light: PageThemeTokenValues; dark: PageThemeTokenValues };
82
+
83
+ type PageThemeColorToken = (typeof PAGE_THEME_COLOR_TOKENS)[number];
84
+ type PageThemeSharedToken = (typeof PAGE_THEME_SHARED_TOKENS)[number];
85
+
86
+ /** Hive values from the light `@theme` block of `apps/ui/src/styles/globals.css`. */
87
+ const HIVE_SHARED_TOKENS: Record<PageThemeSharedToken, string> = {
88
+ "--font-sans": '"Space Grotesk", sans-serif',
89
+ "--font-mono": '"Space Mono", monospace',
90
+ "--radius-sm": "0.375rem",
91
+ "--radius-md": "0.5rem",
92
+ "--radius-lg": "0.625rem",
93
+ "--radius-xl": "0.625rem",
94
+ };
95
+
96
+ const HIVE_LIGHT_COLORS: Record<PageThemeColorToken, string> = {
97
+ "--color-background": "oklch(1 0 0)",
98
+ "--color-foreground": "oklch(0.141 0.005 285.823)",
99
+ "--color-card": "oklch(1 0 0)",
100
+ "--color-card-foreground": "oklch(0.141 0.005 285.823)",
101
+ "--color-surface": "oklch(0.985 0.0015 286)",
102
+ "--color-popover": "oklch(1 0 0)",
103
+ "--color-popover-foreground": "oklch(0.141 0.005 285.823)",
104
+ "--color-primary": "oklch(0.555 0.163 48.998)",
105
+ "--color-primary-foreground": "oklch(0.985 0 0)",
106
+ "--color-secondary": "oklch(0.967 0.001 286.375)",
107
+ "--color-secondary-foreground": "oklch(0.21 0.006 285.885)",
108
+ "--color-muted": "oklch(0.967 0.001 286.375)",
109
+ "--color-muted-foreground": "oklch(0.552 0.016 285.938)",
110
+ "--color-accent": "oklch(0.943 0.003 286.375)",
111
+ "--color-accent-foreground": "oklch(0.21 0.006 285.885)",
112
+ "--color-destructive": "oklch(0.577 0.245 27.325)",
113
+ "--color-destructive-foreground": "oklch(0.985 0 0)",
114
+ "--color-border": "oklch(0.92 0.004 286.32)",
115
+ "--color-border-subtle": "oklch(0.945 0.003 286)",
116
+ "--color-input": "oklch(0.92 0.004 286.32)",
117
+ "--color-ring": "oklch(0.555 0.163 48.998)",
118
+ "--color-chart-1": "oklch(0.646 0.222 41.116)",
119
+ "--color-chart-2": "oklch(0.6 0.118 184.704)",
120
+ "--color-chart-3": "oklch(0.398 0.07 227.392)",
121
+ "--color-chart-4": "oklch(0.828 0.189 84.429)",
122
+ "--color-chart-5": "oklch(0.769 0.188 70.08)",
123
+ "--color-status-success": "oklch(0.74 0.1 163)",
124
+ "--color-status-success-strong": "oklch(0.5 0.09 163)",
125
+ "--color-status-success-foreground": "oklch(0.21 0.006 285.885)",
126
+ "--color-status-active": "oklch(0.81 0.11 75)",
127
+ "--color-status-active-strong": "oklch(0.55 0.1 68)",
128
+ "--color-status-active-foreground": "oklch(0.21 0.006 285.885)",
129
+ "--color-status-error": "oklch(0.72 0.12 25)",
130
+ "--color-status-error-strong": "oklch(0.51 0.14 25)",
131
+ "--color-status-error-foreground": "oklch(0.21 0.006 285.885)",
132
+ "--color-status-info": "oklch(0.74 0.09 235)",
133
+ "--color-status-info-strong": "oklch(0.5 0.09 240)",
134
+ "--color-status-info-foreground": "oklch(0.21 0.006 285.885)",
135
+ "--color-status-pending": "oklch(0.84 0.1 95)",
136
+ "--color-status-pending-strong": "oklch(0.55 0.09 90)",
137
+ "--color-status-pending-foreground": "oklch(0.21 0.006 285.885)",
138
+ "--color-status-warning": "oklch(0.76 0.11 55)",
139
+ "--color-status-warning-strong": "oklch(0.53 0.11 50)",
140
+ "--color-status-warning-foreground": "oklch(0.21 0.006 285.885)",
141
+ "--color-status-paused": "oklch(0.71 0.1 262)",
142
+ "--color-status-paused-strong": "oklch(0.5 0.1 262)",
143
+ "--color-status-paused-foreground": "oklch(0.21 0.006 285.885)",
144
+ "--color-status-neutral": "oklch(0.62 0.014 286)",
145
+ "--color-status-neutral-strong": "oklch(0.48 0.014 286)",
146
+ "--color-status-neutral-foreground": "oklch(0.21 0.006 285.885)",
147
+ "--color-status-success-solid": "oklch(0.627 0.194 149.2)",
148
+ "--color-status-active-solid": "oklch(0.666 0.179 58.318)",
149
+ };
150
+
151
+ const HIVE_DARK_COLORS: Record<PageThemeColorToken, string> = {
152
+ "--color-background": "oklch(0.141 0.005 285.823)",
153
+ "--color-foreground": "oklch(0.985 0 0)",
154
+ "--color-card": "oklch(0.21 0.006 285.885)",
155
+ "--color-card-foreground": "oklch(0.985 0 0)",
156
+ "--color-surface": "oklch(0.185 0.006 286)",
157
+ "--color-popover": "oklch(0.21 0.006 285.885)",
158
+ "--color-popover-foreground": "oklch(0.985 0 0)",
159
+ "--color-primary": "oklch(0.769 0.188 70.08)",
160
+ "--color-primary-foreground": "oklch(0.21 0.006 285.885)",
161
+ "--color-secondary": "oklch(0.274 0.006 286.033)",
162
+ "--color-secondary-foreground": "oklch(0.985 0 0)",
163
+ "--color-muted": "oklch(0.274 0.006 286.033)",
164
+ "--color-muted-foreground": "oklch(0.705 0.015 286.067)",
165
+ "--color-accent": "oklch(0.274 0.006 286.033)",
166
+ "--color-accent-foreground": "oklch(0.985 0 0)",
167
+ "--color-destructive": "oklch(0.704 0.191 22.216)",
168
+ "--color-destructive-foreground": "oklch(0.985 0 0)",
169
+ "--color-border": "oklch(1 0 0 / 10%)",
170
+ "--color-border-subtle": "oklch(1 0 0 / 8%)",
171
+ "--color-input": "oklch(1 0 0 / 15%)",
172
+ "--color-ring": "oklch(0.769 0.188 70.08)",
173
+ "--color-chart-1": "oklch(0.488 0.243 264.376)",
174
+ "--color-chart-2": "oklch(0.696 0.17 162.48)",
175
+ "--color-chart-3": "oklch(0.769 0.188 70.08)",
176
+ "--color-chart-4": "oklch(0.627 0.265 303.9)",
177
+ "--color-chart-5": "oklch(0.645 0.246 16.439)",
178
+ "--color-status-success": "oklch(0.78 0.1 163)",
179
+ "--color-status-success-strong": "oklch(0.78 0.1 163)",
180
+ "--color-status-success-foreground": "oklch(0.21 0.006 285.885)",
181
+ "--color-status-active": "oklch(0.84 0.1 80)",
182
+ "--color-status-active-strong": "oklch(0.84 0.1 80)",
183
+ "--color-status-active-foreground": "oklch(0.21 0.006 285.885)",
184
+ "--color-status-error": "oklch(0.74 0.11 22)",
185
+ "--color-status-error-strong": "oklch(0.74 0.11 22)",
186
+ "--color-status-error-foreground": "oklch(0.21 0.006 285.885)",
187
+ "--color-status-info": "oklch(0.78 0.09 235)",
188
+ "--color-status-info-strong": "oklch(0.78 0.09 235)",
189
+ "--color-status-info-foreground": "oklch(0.21 0.006 285.885)",
190
+ "--color-status-pending": "oklch(0.86 0.09 95)",
191
+ "--color-status-pending-strong": "oklch(0.86 0.09 95)",
192
+ "--color-status-pending-foreground": "oklch(0.21 0.006 285.885)",
193
+ "--color-status-warning": "oklch(0.79 0.1 55)",
194
+ "--color-status-warning-strong": "oklch(0.79 0.1 55)",
195
+ "--color-status-warning-foreground": "oklch(0.21 0.006 285.885)",
196
+ "--color-status-paused": "oklch(0.74 0.09 260)",
197
+ "--color-status-paused-strong": "oklch(0.74 0.09 260)",
198
+ "--color-status-paused-foreground": "oklch(0.21 0.006 285.885)",
199
+ "--color-status-neutral": "oklch(0.72 0.012 286)",
200
+ "--color-status-neutral-strong": "oklch(0.72 0.012 286)",
201
+ "--color-status-neutral-foreground": "oklch(0.21 0.006 285.885)",
202
+ "--color-status-success-solid": "oklch(0.723 0.219 149.6)",
203
+ "--color-status-active-solid": "oklch(0.769 0.188 70.08)",
204
+ };
205
+
206
+ /**
207
+ * Full Hive values for every contract token, copied from the light `@theme`
208
+ * and `.dark` blocks of `apps/ui/src/styles/globals.css` (keep in sync;
209
+ * `themes-presets.test.ts` fails on drift). The shared font and radius
210
+ * tokens come from the light block and apply to both modes.
211
+ */
212
+ export const HIVE_PAGE_TOKENS: {
213
+ light: Record<PageThemeToken, string>;
214
+ dark: Record<PageThemeToken, string>;
215
+ } = {
216
+ light: { ...HIVE_LIGHT_COLORS, ...HIVE_SHARED_TOKENS },
217
+ dark: { ...HIVE_DARK_COLORS, ...HIVE_SHARED_TOKENS },
218
+ };
@@ -24,7 +24,7 @@ export const registerAppUpsertTool = (server: McpServer) => {
24
24
  {
25
25
  title: "Create or update an app",
26
26
  description:
27
- "Stores a schema-backed app definition with models, queries/actions, pages, reusable elements, optional userConfig fields, and an optional top-level theme (a dashboard preset slug for the app's canvas — hive (stock; omit to inherit the viewer's theme), meadow, iris, rose, cobalt, ember, carbon, plus the classic presets github, vscode, material, solarized, tokyo, monokai, gruvbox, catppuccin; viewers can override it per-user; unknown slugs degrade to the viewer's dashboard theme), then returns its dashboard URL. userConfig is versioned schema only; each user's values are stored separately, survive rollback, and schema changes are always compatible. Pages may bind a declared field read-only at exactly /user/<field>; pure and bound reusable elements must receive that value through a prop. Elements are private by default: pure elements read declared props, allow $item/$index inside repeats, may expose one leaf ElementSlot, and cannot invoke actions; bound elements may use the defining app's queries/actions, while exported bound elements cannot navigate. Prop kinds include enum with a required non-empty enum values array. Pages or elements reuse them with literal ElementRef targets, and cross-app refs require export: true. Zero-model pure-UI apps are valid. Pass appId to update; breaking a referenced export is blocked by the compatibility gate unless forceElementBreak explicitly names it.",
27
+ "Stores a schema-backed app definition with models, queries/actions, pages, reusable elements, optional userConfig fields, and an optional top-level theme (a dashboard preset slug for the app's canvas: hive (stock; omit to inherit the viewer's theme), meadow, iris, rose, cobalt, ember, carbon, paper (the warm editorial paper look), plus the classic presets github, vscode, material, solarized, tokyo, monokai, gruvbox, catppuccin; viewers can override it per-user; unknown slugs degrade to the viewer's dashboard theme), then returns its dashboard URL. userConfig is versioned schema only; each user's values are stored separately, survive rollback, and schema changes are always compatible. Pages may bind a declared field read-only at exactly /user/<field>; pure and bound reusable elements must receive that value through a prop. Elements are private by default: pure elements read declared props, allow $item/$index inside repeats, may expose one leaf ElementSlot, and cannot invoke actions; bound elements may use the defining app's queries/actions, while exported bound elements cannot navigate. Prop kinds include enum with a required non-empty enum values array. Pages or elements reuse them with literal ElementRef targets, and cross-app refs require export: true. Zero-model pure-UI apps are valid. Pass appId to update; breaking a referenced export is blocked by the compatibility gate unless forceElementBreak explicitly names it.",
28
28
  annotations: { destructiveHint: false },
29
29
  inputSchema: z.object({
30
30
  name: z.string().min(1).describe("Human-readable app name."),
@@ -12,6 +12,10 @@
12
12
  * the title slugifies to empty (e.g. all symbols), fall back to the
13
13
  * generated page id.
14
14
  *
15
+ * Design fields: `theme`, `themeTokens` and `designVersion` set how an HTML
16
+ * page is styled. A new page gets designVersion 2. On an upsert, an omitted
17
+ * field keeps the stored value, and `null` clears `theme` / `themeTokens`.
18
+ *
15
19
  * Architecture note: this tool runs on the API server (per
16
20
  * `src/server.ts:155`+ pattern) and accesses `src/be/db` directly. The
17
21
  * worker-side DB-boundary invariant doesn't apply because `src/tools/*`
@@ -23,9 +27,18 @@ import { authorizeAssetKeyWrite } from "@/be/asset-key-auth";
23
27
  import { resolveTaskAuditUserId } from "@/be/audit-user";
24
28
  import { createPage, getPage, getPageBySlug, updatePage } from "@/be/db";
25
29
  import { getPageEditCounter } from "@/be/db/page-versions";
30
+ import { classAttributeValues, countColorLiterals } from "@/pages/color-literals";
26
31
  import { snapshotPage } from "@/pages/version";
32
+ import { readThemeFields } from "@/themes/page-fields";
33
+ import { PAGE_THEME_COLOR_TOKENS } from "@/themes/tokens";
27
34
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
28
- import { AssetKeySchema, PageAuthModeSchema, PageContentTypeSchema } from "@/types";
35
+ import {
36
+ AssetKeySchema,
37
+ type Page,
38
+ PageAuthModeSchema,
39
+ PageContentTypeSchema,
40
+ PageThemeTokensInputSchema,
41
+ } from "@/types";
29
42
  import { getAppUrl, getPublicMcpBaseUrl } from "@/utils/constants";
30
43
 
31
44
  /** Same slugifier used by the HTTP createPage handler. */
@@ -46,6 +59,26 @@ function getAppBaseUrl(): string {
46
59
  return getAppUrl();
47
60
  }
48
61
 
62
+ /** A `swarm-` primitive class inside one class attribute value. */
63
+ const SWARM_CLASS = /(?:^|\s)swarm-/;
64
+ const COLOR_TOKEN_REFERENCE = /var\(\s*--color-/i;
65
+ const PAGE_COLOR_TOKENS: ReadonlySet<string> = new Set<string>(PAGE_THEME_COLOR_TOKENS);
66
+
67
+ /** The body uses a `swarm-` class or a `var(--color-...)` token, which only design version 2 styles. */
68
+ function bodyUsesDesignSystem(html: string): boolean {
69
+ return (
70
+ COLOR_TOKEN_REFERENCE.test(html) ||
71
+ classAttributeValues(html).some((value) => SWARM_CLASS.test(value))
72
+ );
73
+ }
74
+
75
+ /** The stored overrides set light colors and no dark colors, so dark mode keeps the preset's colors. */
76
+ function hasLightOnlyColors(tokens: Page["themeTokens"]): boolean {
77
+ if (!tokens) return false;
78
+ const isColor = (name: string) => PAGE_COLOR_TOKENS.has(name);
79
+ return Object.keys(tokens.light).some(isColor) && !Object.keys(tokens.dark).some(isColor);
80
+ }
81
+
49
82
  export const registerCreatePageTool = (server: McpServer) => {
50
83
  createToolRegistrar(server)(
51
84
  "create_page",
@@ -56,7 +89,15 @@ export const registerCreatePageTool = (server: McpServer) => {
56
89
  "Calls are upsert-by-(agent, slug): if you previously created a page " +
57
90
  "with the same slug, its prior state is snapshotted and the row is " +
58
91
  "updated. Use this for static reports, dashboards, or JSON action " +
59
- "specs that don't need a long-lived process.",
92
+ "specs that don't need a long-lived process. " +
93
+ "HTML pages use the swarm design system (designVersion 2): theme " +
94
+ "tokens such as var(--color-primary), the swarm-* primitives, and " +
95
+ "Tailwind v4 utilities such as bg-card and text-muted-foreground. " +
96
+ "Set `theme` to a preset id or pass `themeTokens` (a token map, or a " +
97
+ 'CSS variable block with format "css") to restyle the whole page. ' +
98
+ "Do not hardcode colors: inside the dashboard the page follows the " +
99
+ "viewer's theme and dark mode. Pages created before designVersion 2 " +
100
+ "keep the old look; pass designVersion: 2 when you rewrite one.",
60
101
  annotations: { destructiveHint: false },
61
102
  inputSchema: z.object({
62
103
  key: AssetKeySchema.optional().describe(
@@ -99,6 +140,27 @@ export const registerCreatePageTool = (server: McpServer) => {
99
140
  .describe(
100
141
  "Declared credential needs for JSON pages (renderer ignores for v1 — reserved for follow-up).",
101
142
  ),
143
+ theme: z
144
+ .string()
145
+ .nullable()
146
+ .optional()
147
+ .describe(
148
+ "Theme preset id that restyles the whole page, for example paper, github or hive. An unknown id returns an error that lists every preset. Omit to follow the viewer's theme (on update, to keep the stored one). Pass null to clear it.",
149
+ ),
150
+ themeTokens: PageThemeTokensInputSchema.nullable()
151
+ .optional()
152
+ .describe(
153
+ 'Per-page token overrides, applied on top of the preset. Send { light, dark } maps of token name to CSS value (for example { light: { "--color-primary": "oklch(0.55 0.2 260)" } }), or { format: "css", css } with a CSS variable block (:root, .dark, @theme) as shadcn, tweakcn and Tailwind v4 export it. Stored normalized. Names outside the token contract, and values that reference a variable outside it, are ignored and reported in the result. Replaces the stored overrides. Omit to keep them on update. Pass null to clear them.',
154
+ ),
155
+ designVersion: z
156
+ .number()
157
+ .int()
158
+ .min(1)
159
+ .max(2)
160
+ .optional()
161
+ .describe(
162
+ "Design system version. 2 is the swarm design system and the default for new pages. 1 is the original look that older pages keep. Omit on update to keep the stored value.",
163
+ ),
102
164
  }),
103
165
  outputSchema: swarmToolOutputSchema({
104
166
  yourAgentId: z.string().optional(),
@@ -107,6 +169,11 @@ export const registerCreatePageTool = (server: McpServer) => {
107
169
  version: z.number().optional(),
108
170
  app_url: z.string().optional(),
109
171
  api_url: z.string().optional(),
172
+ designVersion: z.number().optional(),
173
+ ignoredTokens: z.array(z.string()).optional(),
174
+ colorLiteralCount: z.number().optional(),
175
+ usesDesignSystem: z.boolean().optional(),
176
+ lightOnlyColors: z.boolean().optional(),
110
177
  }),
111
178
  },
112
179
  async (input, requestInfo, _meta) => {
@@ -115,6 +182,9 @@ export const registerCreatePageTool = (server: McpServer) => {
115
182
  return toolErr(msg);
116
183
  }
117
184
 
185
+ const themeFields = readThemeFields(input);
186
+ if ("error" in themeFields) return toolErr(themeFields.error);
187
+
118
188
  const slug = input.slug ?? slugify(input.title);
119
189
  const finalSlug = slug || "page"; // Fallback if title slugifies to empty.
120
190
 
@@ -167,6 +237,10 @@ export const registerCreatePageTool = (server: McpServer) => {
167
237
  passwordHash: passwordHash ?? null,
168
238
  body: input.body,
169
239
  needsCredentials: needsCredentialsNames ?? null,
240
+ // Omitted design fields keep the stored values.
241
+ theme: themeFields.theme,
242
+ themeTokens: themeFields.themeTokens,
243
+ designVersion: input.designVersion,
170
244
  });
171
245
  if (!updated) {
172
246
  const msg = `Failed to update existing page ${existing.id}.`;
@@ -188,6 +262,9 @@ export const registerCreatePageTool = (server: McpServer) => {
188
262
  passwordHash,
189
263
  body: input.body,
190
264
  needsCredentials: needsCredentialsNames,
265
+ theme: themeFields.theme,
266
+ themeTokens: themeFields.themeTokens,
267
+ designVersion: input.designVersion,
191
268
  });
192
269
  id = created.id;
193
270
  } catch (err) {
@@ -209,17 +286,46 @@ export const registerCreatePageTool = (server: McpServer) => {
209
286
  const appUrl = `${getAppBaseUrl()}/pages/${id}`;
210
287
  const version = await getPageEditCounter(id);
211
288
 
212
- return toolOk(`Page "${input.title}" saved (slug=${finalSlug}, version=${version}).`, {
213
- details: `API: ${apiUrl}\n App: ${appUrl}`,
214
- data: {
215
- yourAgentId: requestInfo.agentId,
216
- id,
217
- key: fresh.key,
218
- version,
219
- app_url: appUrl,
220
- api_url: apiUrl,
289
+ // Judged on the saved page, so an upsert that keeps an older design
290
+ // version is judged by the version it keeps. The NUDGES entry for this
291
+ // tool reads these flags. Only HTML pages get the design system head.
292
+ const designFlags =
293
+ fresh.contentType === "text/html"
294
+ ? {
295
+ usesDesignSystem:
296
+ typeof input.theme === "string" ||
297
+ Boolean(input.themeTokens) ||
298
+ bodyUsesDesignSystem(fresh.body),
299
+ lightOnlyColors: hasLightOnlyColors(fresh.themeTokens),
300
+ ...(fresh.designVersion >= 2
301
+ ? { colorLiteralCount: countColorLiterals(fresh.body) }
302
+ : {}),
303
+ }
304
+ : {};
305
+ const { ignored } = themeFields;
306
+
307
+ return toolOk(
308
+ `Page "${input.title}" saved (slug=${finalSlug}, version=${version}, designVersion=${fresh.designVersion}).` +
309
+ (ignored.length > 0
310
+ ? " Some theme tokens were ignored (outside the page token contract or referencing a variable outside it)."
311
+ : ""),
312
+ {
313
+ details:
314
+ `API: ${apiUrl}\n App: ${appUrl}` +
315
+ (ignored.length > 0 ? `\n Ignored theme tokens: ${ignored.join(", ")}` : ""),
316
+ data: {
317
+ yourAgentId: requestInfo.agentId,
318
+ id,
319
+ key: fresh.key,
320
+ version,
321
+ app_url: appUrl,
322
+ api_url: apiUrl,
323
+ designVersion: fresh.designVersion,
324
+ ...(ignored.length > 0 ? { ignoredTokens: ignored } : {}),
325
+ ...designFlags,
326
+ },
221
327
  },
222
- });
328
+ );
223
329
  },
224
330
  );
225
331
  };
@@ -5,9 +5,7 @@ import { resolveTaskAuditUserId } from "@/be/audit-user";
5
5
  import {
6
6
  createTaskExtended,
7
7
  extensionAgentAssignmentError,
8
- findCompletedTaskInThread,
9
8
  findExistingLinearTrackerContextWork,
10
- findRecentCancelledTaskInThread,
11
9
  getActiveTaskCount,
12
10
  getAgentById,
13
11
  getDbClient,
@@ -23,6 +21,10 @@ import { repointTrackerSyncBySwarmId } from "@/be/db-queries/tracker";
23
21
  import { explicitModelErrorForAgent } from "@/be/model-validation";
24
22
  import { applyPreTaskCreate } from "@/extensions/apply-task-create";
25
23
  import { can } from "@/rbac";
24
+ import {
25
+ evaluateFollowUpRedelegation,
26
+ followUpThreadForSourceTask,
27
+ } from "@/tasks/follow-up-redelegation-guard";
26
28
  import { checkSlackRoutingCoherence } from "@/tasks/slack-routing";
27
29
  import { findDuplicateTask } from "@/tools/task-dedup";
28
30
  import { ownerCtx, type ToolCtx } from "@/tools/task-tool-ctx";
@@ -163,7 +165,13 @@ export const sendTaskInputSchema = z
163
165
  .boolean()
164
166
  .default(false)
165
167
  .describe(
166
- "If true, skip duplicate detection and create the task even if a similar one exists.",
168
+ "If true, skip duplicate detection and create the task even if a similar one exists. Does not bypass the follow-up re-delegation guard; see allowRedelegation.",
169
+ ),
170
+ allowRedelegation: z
171
+ .boolean()
172
+ .default(false)
173
+ .describe(
174
+ "Only matters when you call send-task from a follow-up task in a Slack thread. Such a dispatch is blocked when it repeats a task already completed in that thread (same-work rule, unless a task there was cancelled or failed after that completion), or when you already dispatched 10 tasks into the thread in the last 6 hours (loop-cap rule). Set true to dispatch anyway; the override is logged. Use it only for deliberate re-runs or long chains, never to retry a block you have not read.",
167
175
  ),
168
176
  slackChannelId: z
169
177
  .string()
@@ -292,6 +300,7 @@ export async function sendTaskHandler(
292
300
  effort,
293
301
  profile,
294
302
  allowDuplicate,
303
+ allowRedelegation,
295
304
  slackChannelId,
296
305
  slackThreadTs,
297
306
  slackUserId,
@@ -316,6 +325,7 @@ export async function sendTaskHandler(
316
325
  const requestedLeadOnly = ctx.kind === "user" ? false : leadOnly;
317
326
  const requestedCapabilities = ctx.kind === "user" ? undefined : requiredCapabilities;
318
327
  const requestedAllowDuplicate = ctx.kind === "user" ? false : allowDuplicate;
328
+ const requestedAllowRedelegation = ctx.kind === "user" ? false : allowRedelegation;
319
329
  const requestedOverrideSlackContext = ctx.kind === "user" ? false : overrideSlackContext;
320
330
 
321
331
  // Defense in depth for direct TypeScript callers that bypass MCP schema parsing.
@@ -572,7 +582,9 @@ export async function sendTaskHandler(
572
582
  // Every read in this handler releases the FIFO lock, so two concurrent
573
583
  // send-task calls with the same creator and text otherwise both pass the
574
584
  // guards and both create a task (no UNIQUE index arbitrates them).
575
- const evaluateDedupGuards = async (): Promise<{
585
+ const evaluateDedupGuards = async (
586
+ logOverride: boolean,
587
+ ): Promise<{
576
588
  ok: boolean;
577
589
  message: string;
578
590
  task?: AgentTask;
@@ -602,59 +614,35 @@ export async function sendTaskHandler(
602
614
  }
603
615
  }
604
616
 
605
- // Guard: prevent re-delegation from follow-up tasks
606
- // When the source task is a "follow-up" (worker completed/failed notification),
607
- // check if there are completed tasks in the same Slack thread recently.
608
- // This prevents the cycle: worker completes → follow-up → Lead re-delegates → repeat.
609
- //
610
- // Exception: if a MORE RECENT task in the same thread was cancelled (exit 130,
611
- // status='cancelled', or status='failed' with failureReason containing
612
- // "cancelled"), bypass the guard. A cancellation means the work was
613
- // interrupted — re-dispatch is the correct response, not a deduped no-op.
614
- // Without this bypass, a cancelled worker permanently jams the thread
615
- // against re-delegation when an earlier completed sibling exists.
617
+ // Guard: prevent re-delegation from follow-up tasks (same-work and
618
+ // loop-cap rules, see src/tasks/follow-up-redelegation-guard.ts).
619
+ // allowRedelegation bypasses both and is logged.
616
620
  //
617
621
  // NOTE: `taskType === "resume"` (created by createResumeFollowUp on
618
622
  // supersede) is intentionally NOT in this guard — a resume IS the legitimate
619
623
  // re-dispatch and bypassing the check is correct. Do not add "resume" here.
620
- if (sourceTaskId) {
621
- const sourceTask = await getTaskById(sourceTaskId);
622
- if (
623
- sourceTask?.taskType === "follow-up" &&
624
- sourceTask.slackThreadTs &&
625
- sourceTask.slackChannelId
626
- ) {
627
- const recentCompleted = await findCompletedTaskInThread(
628
- sourceTask.slackChannelId,
629
- sourceTask.slackThreadTs,
630
- 2880, // 48 hours in minutes
624
+ const followUpThread = creatorAgentId ? await followUpThreadForSourceTask(sourceTaskId) : null;
625
+ if (followUpThread && creatorAgentId) {
626
+ const blocked = await evaluateFollowUpRedelegation({
627
+ ...followUpThread,
628
+ creatorAgentId,
629
+ taskDescription,
630
+ targetAgentId: taskOptions.agentId ?? taskOptions.offeredTo,
631
+ });
632
+ if (blocked && !requestedAllowRedelegation) {
633
+ return { ok: false, message: blocked.message };
634
+ }
635
+ if (blocked && logOverride) {
636
+ console.log(
637
+ `[send-task] follow-up re-delegation override: rule=${blocked.rule} creatorAgentId=${creatorAgentId} sourceTaskId=${sourceTaskId} thread=${followUpThread.channelId}/${followUpThread.threadTs}`,
631
638
  );
632
- if (recentCompleted) {
633
- const recentCancelled = await findRecentCancelledTaskInThread(
634
- sourceTask.slackChannelId,
635
- sourceTask.slackThreadTs,
636
- 2880,
637
- );
638
- const cancelledMoreRecent =
639
- recentCancelled &&
640
- new Date(recentCancelled.lastUpdatedAt).getTime() >
641
- new Date(recentCompleted.lastUpdatedAt).getTime();
642
- if (!cancelledMoreRecent) {
643
- return {
644
- ok: false,
645
- message: `Blocked: re-delegation from follow-up task in a thread that already has completed work (task ${recentCompleted.id.slice(0, 8)}). The original request was already handled.`,
646
- };
647
- }
648
- // else: fall through — the cancellation is more recent than the
649
- // completion, so re-delegation is legitimate.
650
- }
651
639
  }
652
640
  }
653
641
 
654
642
  return null;
655
643
  };
656
644
 
657
- const guard = await evaluateDedupGuards();
645
+ const guard = await evaluateDedupGuards(true);
658
646
  if (guard) {
659
647
  const guardData = {
660
648
  yourAgentId: creatorAgentId,
@@ -669,7 +657,7 @@ export async function sendTaskHandler(
669
657
  // Authoritative re-check: the reads above are separated from the INSERT by
670
658
  // the whole handler, so only a guard inside this transaction can see a
671
659
  // concurrent send-task's committed task.
672
- const raced = await evaluateDedupGuards();
660
+ const raced = await evaluateDedupGuards(false);
673
661
  if (raced) return { success: raced.ok, message: raced.message, task: raced.task };
674
662
 
675
663
  // This transaction already checked the tracker key, excluding the caller's
@@ -94,7 +94,7 @@ export const registerSlackLeaveThreadTool = (server: McpServer) => {
94
94
  return toolErr("No Slack thread context available.");
95
95
  }
96
96
 
97
- await leaveSlackThread(slackChannelId, slackThreadTs, agent.id);
97
+ await leaveSlackThread(slackChannelId, slackThreadTs, { agentId: agent.id });
98
98
  return toolOk(
99
99
  `Left Slack thread ${slackChannelId}/${slackThreadTs}. Messages there are ignored until someone @mentions the bot.`,
100
100
  { data: { channelId: slackChannelId, threadTs: slackThreadTs } },