@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
@@ -112,10 +112,15 @@ afterAll(async () => {
112
112
  await removeDbFiles(TEST_DB_PATH);
113
113
  });
114
114
 
115
- async function api(method: string, path: string, body: unknown) {
115
+ async function api(
116
+ method: string,
117
+ path: string,
118
+ body: unknown,
119
+ headers: Record<string, string> = {},
120
+ ) {
116
121
  const res = await fetch(`${baseUrl}${path}`, {
117
122
  method,
118
- headers: { "content-type": "application/json" },
123
+ headers: { "content-type": "application/json", ...headers },
119
124
  body: JSON.stringify(body),
120
125
  });
121
126
  const text = await res.text();
@@ -612,12 +617,13 @@ describe("harness compatibility at every create entry point", () => {
612
617
  test("internal script-run agent-task route: a cross-harness model is a 400", async () => {
613
618
  const runId = crypto.randomUUID();
614
619
  await createScriptRun({ id: runId, agentId: workerId, source: "inline", args: {} });
615
- const res = await api("POST", `/api/internal/script-runs/${runId}/agent-task`, {
616
- stepKey: "step-1",
617
- task: "script step",
618
- agentId: codexId,
619
- model: "claude-opus-5-5",
620
- });
620
+ // The run's own harness calls this route with the run's agent id.
621
+ const res = await api(
622
+ "POST",
623
+ `/api/internal/script-runs/${runId}/agent-task`,
624
+ { stepKey: "step-1", task: "script step", agentId: codexId, model: "claude-opus-5-5" },
625
+ { "X-Agent-ID": workerId },
626
+ );
621
627
  expect(res.status).toBe(400);
622
628
  expect(String(res.body.error)).toContain("does not run on the codex harness");
623
629
  });
@@ -0,0 +1,56 @@
1
+ <base target="_blank">
2
+ <link rel="preconnect" href="https://fonts.googleapis.com">
3
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
4
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
5
+ <script src="https://cdn.tailwindcss.com"></script>
6
+ <script>
7
+ if (window.tailwind) {
8
+ window.tailwind.config = { corePlugins: { preflight: false } };
9
+ }
10
+ </script>
11
+ <style>
12
+ *, ::before, ::after { box-sizing: border-box; border: 0 solid #e5e7eb; }
13
+ hr { border-top-width: 1px; }
14
+ img, video { display: block; max-width: 100%; height: auto; }
15
+ button, input, optgroup, select, textarea, ::file-selector-button { font: inherit; }
16
+ :root {
17
+ color-scheme: light;
18
+ --swarm-bg: #fbfbfa;
19
+ --swarm-card: #ffffff;
20
+ --swarm-border: #e5e7eb;
21
+ --swarm-text: #111827;
22
+ --swarm-muted: #6b7280;
23
+ --swarm-primary: #3b82f6;
24
+ }
25
+ html, body { background: var(--swarm-bg); color: var(--swarm-text); }
26
+ body {
27
+ font-family: "Space Grotesk", system-ui, sans-serif;
28
+ margin: 0;
29
+ padding: 24px;
30
+ line-height: 1.5;
31
+ }
32
+ code, pre, kbd, samp { font-family: "Space Mono", ui-monospace, monospace; }
33
+ a { color: var(--swarm-primary); }
34
+ ::selection { background: var(--swarm-primary); color: #fff; }
35
+ @media print {
36
+ /* Override theme variables so built-in primitives (swarm-diff, swarm-card)
37
+ that read var(--swarm-card) / var(--swarm-border) etc. inline-styled
38
+ backgrounds also flip to light. Without this, diff cards stay dark navy
39
+ on print. */
40
+ :root {
41
+ --swarm-bg: #ffffff;
42
+ --swarm-card: #ffffff;
43
+ --swarm-border: #cccccc;
44
+ --swarm-text: #000000;
45
+ --swarm-muted: #555555;
46
+ }
47
+ html, body { background: white !important; color: black !important; }
48
+ a { color: black !important; text-decoration: underline; }
49
+ /* Hide any swarm chrome the agent (or built-in primitives) tagged with
50
+ .no-print. Use this class on annotation badges, jump-list nav, anything
51
+ that shouldn't appear in the PDF export. */
52
+ .no-print { display: none !important; }
53
+ /* Avoid page-break inside cards / diff blocks. */
54
+ .swarm-card, swarm-diff { break-inside: avoid; }
55
+ }
56
+ </style>
@@ -0,0 +1,513 @@
1
+ import { afterAll, afterEach, beforeAll, describe, expect, spyOn, test } from "bun:test";
2
+ import {
3
+ closeDb,
4
+ createAgent,
5
+ createTaskExtended,
6
+ getDbClient,
7
+ getLogsByTaskIdAndEventType,
8
+ getTaskById,
9
+ initDb,
10
+ startTask,
11
+ } from "../be/db";
12
+ import { upsertScriptByName } from "../be/scripts/db";
13
+ import * as savedScriptRunner from "../be/scripts/run-saved";
14
+ import { validateConfigValue } from "../be/swarm-config-guard";
15
+ import type { RunScriptOutput } from "../scripts-runtime/loader";
16
+ import {
17
+ classifyFollowUpGate,
18
+ drainFollowUpGateShadowForTest,
19
+ type FollowUpGateDeps,
20
+ readFollowUpGateConfig,
21
+ setFollowUpGateDepsForTests,
22
+ setFollowUpGateTimeoutMsForTests,
23
+ } from "../tasks/follow-up-gate";
24
+ import { createWorkerTaskFollowUp } from "../tasks/worker-follow-up";
25
+ import type { AgentTask } from "../types";
26
+
27
+ const GATED = "11111111-1111-4111-8111-111111111111";
28
+ const NOT_GATED = "22222222-2222-4222-8222-222222222222";
29
+ const ENV_KEYS = [
30
+ "FOLLOW_UP_GATE_MODE",
31
+ "FOLLOW_UP_GATE_SCHEDULE_IDS",
32
+ "FOLLOW_UP_GATE_VERIFIER_SCRIPT",
33
+ ] as const;
34
+
35
+ const VERIFIER = { name: "test-verifier", version: 3, contentHash: "abc123" };
36
+
37
+ let leadId: string;
38
+ let workerId: string;
39
+
40
+ beforeAll(async () => {
41
+ initDb(":memory:");
42
+ leadId = (
43
+ await createAgent({
44
+ name: "Lead",
45
+ isLead: true,
46
+ status: "busy",
47
+ capabilities: [],
48
+ maxTasks: 50,
49
+ })
50
+ ).id;
51
+ workerId = (
52
+ await createAgent({
53
+ name: "Worker",
54
+ isLead: false,
55
+ status: "busy",
56
+ capabilities: [],
57
+ maxTasks: 50,
58
+ })
59
+ ).id;
60
+ });
61
+
62
+ afterEach(async () => {
63
+ await drainFollowUpGateShadowForTest();
64
+ setFollowUpGateDepsForTests(null);
65
+ setFollowUpGateTimeoutMsForTests(null);
66
+ for (const key of ENV_KEYS) delete process.env[key];
67
+ });
68
+
69
+ afterAll(() => closeDb());
70
+
71
+ function shadowEnv(overrides: Partial<Record<(typeof ENV_KEYS)[number], string>> = {}) {
72
+ process.env.FOLLOW_UP_GATE_MODE = "shadow";
73
+ process.env.FOLLOW_UP_GATE_SCHEDULE_IDS = GATED;
74
+ process.env.FOLLOW_UP_GATE_VERIFIER_SCRIPT = VERIFIER.name;
75
+ for (const [key, value] of Object.entries(overrides)) process.env[key] = value;
76
+ }
77
+
78
+ /** Deps that record calls; `run` decides the verifier result. */
79
+ function stubDeps(run: FollowUpGateDeps["runVerifier"]) {
80
+ const calls = { getVerifier: 0, runVerifier: 0 };
81
+ const deps: FollowUpGateDeps = {
82
+ getVerifier: async () => {
83
+ calls.getVerifier++;
84
+ return VERIFIER;
85
+ },
86
+ runVerifier: async (...args) => {
87
+ calls.runVerifier++;
88
+ return run(...args);
89
+ },
90
+ };
91
+ setFollowUpGateDepsForTests(deps);
92
+ return calls;
93
+ }
94
+
95
+ async function workerTask(options: Parameters<typeof createTaskExtended>[1] = {}) {
96
+ const task = await createTaskExtended("scheduled work", {
97
+ agentId: workerId,
98
+ scheduleId: GATED,
99
+ ...options,
100
+ });
101
+ await startTask(task.id);
102
+ return (await getTaskById(task.id)) as AgentTask;
103
+ }
104
+
105
+ async function shadowRows(taskId: string) {
106
+ await drainFollowUpGateShadowForTest();
107
+ const rows = await getLogsByTaskIdAndEventType(taskId, "task_follow_up_gate_shadow");
108
+ return rows.map((row) => ({ ...row, meta: JSON.parse(row.metadata ?? "{}") }));
109
+ }
110
+
111
+ async function finish(task: AgentTask, status: "completed" | "failed" = "completed") {
112
+ const followUp = await createWorkerTaskFollowUp({ task, status, output: "done" });
113
+ expect(followUp).not.toBeNull();
114
+ expect(followUp?.parentTaskId).toBe(task.id);
115
+ expect(await getTaskById(followUp!.id)).not.toBeNull();
116
+ return followUp!;
117
+ }
118
+
119
+ describe("classifyFollowUpGate", () => {
120
+ const shadow = readFollowUpGateConfig({
121
+ FOLLOW_UP_GATE_MODE: "shadow",
122
+ FOLLOW_UP_GATE_SCHEDULE_IDS: `${GATED}, not-a-uuid`,
123
+ });
124
+ const classify = (
125
+ task: Pick<AgentTask, "scheduleId" | "followUpConfig">,
126
+ status: "completed" | "failed" = "completed",
127
+ config = shadow,
128
+ ) => classifyFollowUpGate({ task, status, config });
129
+
130
+ test("a completed run of a gated schedule is eligible", () => {
131
+ expect(classify({ scheduleId: GATED })).toEqual({ eligible: true });
132
+ expect(classify({ scheduleId: GATED.toUpperCase() })).toEqual({ eligible: true });
133
+ });
134
+
135
+ test("a failed run is ineligible", () => {
136
+ expect(classify({ scheduleId: GATED }, "failed")).toEqual({
137
+ eligible: false,
138
+ reason: "status_failed",
139
+ });
140
+ });
141
+
142
+ test("follow-up instructions make a run ineligible", () => {
143
+ for (const followUpConfig of [{ onCompleted: "post it" }, { onFailed: "retry it" }]) {
144
+ expect(classify({ scheduleId: GATED, followUpConfig })).toEqual({
145
+ eligible: false,
146
+ reason: "follow_up_instructions",
147
+ });
148
+ }
149
+ });
150
+
151
+ test("a task with no schedule is not scheduled", () => {
152
+ expect(classify({})).toEqual({ eligible: false, reason: "not_scheduled" });
153
+ });
154
+
155
+ test("a schedule off the list is not gated", () => {
156
+ expect(classify({ scheduleId: NOT_GATED })).toEqual({
157
+ eligible: false,
158
+ reason: "schedule_not_gated",
159
+ });
160
+ });
161
+
162
+ test("mode off and an unknown mode read as off", () => {
163
+ for (const mode of [undefined, "off", "enforce", "SHADOWY"]) {
164
+ const config = readFollowUpGateConfig({
165
+ FOLLOW_UP_GATE_MODE: mode,
166
+ FOLLOW_UP_GATE_SCHEDULE_IDS: GATED,
167
+ });
168
+ expect(classify({ scheduleId: GATED }, "completed", config)).toEqual({
169
+ eligible: false,
170
+ reason: "mode_off",
171
+ });
172
+ }
173
+ });
174
+
175
+ test("malformed ids in the list are ignored", () => {
176
+ expect([...shadow.scheduleIds]).toEqual([GATED]);
177
+ });
178
+ });
179
+
180
+ describe("follow-up gate config validation", () => {
181
+ test("mode accepts off and shadow and rejects enforce", () => {
182
+ expect(validateConfigValue("FOLLOW_UP_GATE_MODE", "off")).toBeNull();
183
+ expect(validateConfigValue("FOLLOW_UP_GATE_MODE", "shadow")).toBeNull();
184
+ expect(validateConfigValue("FOLLOW_UP_GATE_MODE", "enforce")).toContain("FOLLOW_UP_GATE_MODE");
185
+ });
186
+
187
+ test("schedule ids accept empty and UUID lists and reject other ids", () => {
188
+ expect(validateConfigValue("FOLLOW_UP_GATE_SCHEDULE_IDS", "")).toBeNull();
189
+ expect(validateConfigValue("FOLLOW_UP_GATE_SCHEDULE_IDS", `${GATED}, ${NOT_GATED}`)).toBeNull();
190
+ expect(validateConfigValue("FOLLOW_UP_GATE_SCHEDULE_IDS", `${GATED},daily-report`)).toContain(
191
+ "FOLLOW_UP_GATE_SCHEDULE_IDS",
192
+ );
193
+ });
194
+ });
195
+
196
+ describe("follow-up gate shadow runner", () => {
197
+ test("mode off never looks up the verifier and writes no rows", async () => {
198
+ const calls = stubDeps(async () => ({ ok: true, controls: { ok: true } }));
199
+ process.env.FOLLOW_UP_GATE_SCHEDULE_IDS = GATED;
200
+ process.env.FOLLOW_UP_GATE_VERIFIER_SCRIPT = VERIFIER.name;
201
+ const task = await workerTask();
202
+ const followUp = await finish(task);
203
+ expect(followUp.taskType).toBe("follow-up");
204
+ expect(followUp.agentId).toBe(leadId);
205
+ expect(await shadowRows(task.id)).toHaveLength(0);
206
+ expect(calls).toEqual({ getVerifier: 0, runVerifier: 0 });
207
+ });
208
+
209
+ test("mode off registers no post-commit hook; shadow registers exactly one", async () => {
210
+ stubDeps(async () => ({ ok: true, controls: { ok: true } }));
211
+ const afterCommit = spyOn(getDbClient(), "afterCommit");
212
+ try {
213
+ const hooksFor = async () => {
214
+ const task = await workerTask();
215
+ const before = afterCommit.mock.calls.length;
216
+ await finish(task);
217
+ const count = afterCommit.mock.calls.length - before;
218
+ await drainFollowUpGateShadowForTest();
219
+ return count;
220
+ };
221
+ const off = await hooksFor();
222
+ shadowEnv();
223
+ expect(await hooksFor()).toBe(off + 1);
224
+ } finally {
225
+ afterCommit.mockRestore();
226
+ }
227
+ });
228
+
229
+ test("ok:true with passing controls records would_skip", async () => {
230
+ shadowEnv();
231
+ const seen: unknown[] = [];
232
+ stubDeps(async (script, input, agentId) => {
233
+ seen.push({ script: script.name, input, agentId });
234
+ return {
235
+ ok: true,
236
+ check: "page",
237
+ detail: "fresh",
238
+ controls: { ok: true },
239
+ unverified: ["archive"],
240
+ };
241
+ });
242
+ const task = await workerTask();
243
+ const followUp = await finish(task);
244
+ const rows = await shadowRows(task.id);
245
+ expect(rows).toHaveLength(1);
246
+ expect(rows[0]!.agentId).toBe(leadId);
247
+ expect(rows[0]!.meta).toMatchObject({
248
+ v: 1,
249
+ mode: "shadow",
250
+ followUpTaskId: followUp.id,
251
+ scheduleId: GATED,
252
+ status: "completed",
253
+ decision: "would_skip",
254
+ reason: null,
255
+ verdict: { ok: true, check: "page", detail: "fresh", controlsOk: true },
256
+ unverified: ["archive"],
257
+ verifier: VERIFIER,
258
+ });
259
+ expect(rows[0]!.meta.latencyMs).toBeGreaterThanOrEqual(0);
260
+ expect(seen).toEqual([{ script: VERIFIER.name, input: { taskId: task.id }, agentId: leadId }]);
261
+ });
262
+
263
+ test("ok:false, ok:null and failing controls record would_keep", async () => {
264
+ shadowEnv();
265
+ for (const result of [
266
+ { ok: false, controls: { ok: true } },
267
+ { ok: null, controls: { ok: true } },
268
+ { ok: true, controls: { ok: false } },
269
+ { ok: true },
270
+ ]) {
271
+ stubDeps(async () => result);
272
+ const task = await workerTask();
273
+ await finish(task);
274
+ const rows = await shadowRows(task.id);
275
+ expect(rows.map((row) => row.meta.decision)).toEqual(["would_keep"]);
276
+ }
277
+ });
278
+
279
+ test("a verifier throw records a scrubbed error", async () => {
280
+ shadowEnv();
281
+ stubDeps(async () => {
282
+ throw new Error("boom");
283
+ });
284
+ const task = await workerTask();
285
+ await finish(task);
286
+ const rows = await shadowRows(task.id);
287
+ expect(rows).toHaveLength(1);
288
+ expect(rows[0]!.meta).toMatchObject({
289
+ decision: "error",
290
+ reason: "verifier_error",
291
+ error: "boom",
292
+ verifier: VERIFIER,
293
+ });
294
+ });
295
+
296
+ test("a non-object result records verifier_error", async () => {
297
+ shadowEnv();
298
+ stubDeps(async () => undefined);
299
+ const task = await workerTask();
300
+ await finish(task);
301
+ const rows = await shadowRows(task.id);
302
+ expect(rows[0]!.meta).toMatchObject({ decision: "error", reason: "verifier_error" });
303
+ });
304
+
305
+ test("a verifier past the deadline records timeout and is aborted", async () => {
306
+ shadowEnv();
307
+ setFollowUpGateTimeoutMsForTests(20);
308
+ let aborted = false;
309
+ stubDeps(
310
+ (_script, _input, _agentId, signal) =>
311
+ new Promise((resolve) => {
312
+ signal.addEventListener("abort", () => {
313
+ aborted = true;
314
+ resolve({ ok: true, controls: { ok: true } });
315
+ });
316
+ }),
317
+ );
318
+ const task = await workerTask();
319
+ await finish(task);
320
+ const rows = await shadowRows(task.id);
321
+ expect(rows).toHaveLength(1);
322
+ expect(rows[0]!.meta).toMatchObject({ decision: "error", reason: "timeout" });
323
+ expect(aborted).toBe(true);
324
+ });
325
+
326
+ test("the follow-up returns before the verifier settles", async () => {
327
+ shadowEnv();
328
+ let release!: (value: unknown) => void;
329
+ let started = false;
330
+ stubDeps(() => {
331
+ started = true;
332
+ return new Promise((resolve) => {
333
+ release = resolve;
334
+ });
335
+ });
336
+ const task = await workerTask();
337
+ const followUp = await finish(task);
338
+ expect(followUp.id).toBeTruthy();
339
+ expect(await getLogsByTaskIdAndEventType(task.id, "task_follow_up_gate_shadow")).toHaveLength(
340
+ 0,
341
+ );
342
+ while (!started) await Bun.sleep(1);
343
+ release({ ok: true, controls: { ok: true } });
344
+ const rows = await shadowRows(task.id);
345
+ expect(rows.map((row) => row.meta.decision)).toEqual(["would_skip"]);
346
+ });
347
+
348
+ test("a third concurrent run hits the concurrency cap", async () => {
349
+ shadowEnv();
350
+ const releases: Array<(value: unknown) => void> = [];
351
+ const calls = stubDeps(
352
+ () =>
353
+ new Promise((resolve) => {
354
+ releases.push(resolve);
355
+ }),
356
+ );
357
+ const tasks = [await workerTask(), await workerTask(), await workerTask()];
358
+ for (const task of tasks) await finish(task);
359
+ const allRows = async () =>
360
+ (await Promise.all(tasks.map((task) => shadowRows(task.id)))).flat();
361
+ while (releases.length < 2) await Bun.sleep(1);
362
+ // Let the third hook run while the first two are still blocked.
363
+ let rows = await getLogsByTaskIdAndEventType(tasks[2]!.id, "task_follow_up_gate_shadow");
364
+ while (rows.length === 0) {
365
+ await Bun.sleep(1);
366
+ rows = await getLogsByTaskIdAndEventType(tasks[2]!.id, "task_follow_up_gate_shadow");
367
+ }
368
+ for (const release of releases) release({ ok: true, controls: { ok: true } });
369
+ const all = await allRows();
370
+ expect(all.map((row) => row.meta.decision).sort()).toEqual([
371
+ "error",
372
+ "would_skip",
373
+ "would_skip",
374
+ ]);
375
+ expect(all.find((row) => row.meta.decision === "error")!.meta.reason).toBe("concurrency_cap");
376
+ expect(calls.runVerifier).toBe(2);
377
+ });
378
+
379
+ test("a gated failed run records ineligible without running the verifier", async () => {
380
+ shadowEnv();
381
+ const calls = stubDeps(async () => ({ ok: true, controls: { ok: true } }));
382
+ const task = await workerTask();
383
+ const followUp = await createWorkerTaskFollowUp({ task, status: "failed", failureReason: "x" });
384
+ expect(followUp).not.toBeNull();
385
+ const rows = await shadowRows(task.id);
386
+ expect(rows).toHaveLength(1);
387
+ expect(rows[0]!.meta).toMatchObject({
388
+ decision: "ineligible",
389
+ reason: "status_failed",
390
+ status: "failed",
391
+ followUpTaskId: followUp!.id,
392
+ verdict: null,
393
+ verifier: null,
394
+ });
395
+ expect(calls).toEqual({ getVerifier: 0, runVerifier: 0 });
396
+ });
397
+
398
+ test("a run with follow-up instructions records ineligible", async () => {
399
+ shadowEnv();
400
+ const calls = stubDeps(async () => ({ ok: true, controls: { ok: true } }));
401
+ const task = await workerTask({ followUpConfig: { onCompleted: "post the report" } });
402
+ await finish(task);
403
+ const rows = await shadowRows(task.id);
404
+ expect(rows.map((row) => row.meta.reason)).toEqual(["follow_up_instructions"]);
405
+ expect(calls.runVerifier).toBe(0);
406
+ });
407
+
408
+ test("ungated and unscheduled runs write nothing", async () => {
409
+ shadowEnv();
410
+ const calls = stubDeps(async () => ({ ok: true, controls: { ok: true } }));
411
+ const ungated = await workerTask({ scheduleId: NOT_GATED });
412
+ const unscheduled = await workerTask({ scheduleId: undefined });
413
+ await finish(ungated);
414
+ await finish(unscheduled);
415
+ expect(await shadowRows(ungated.id)).toHaveLength(0);
416
+ expect(await shadowRows(unscheduled.id)).toHaveLength(0);
417
+ expect(calls).toEqual({ getVerifier: 0, runVerifier: 0 });
418
+ });
419
+
420
+ test("no verifier configured records no_verifier", async () => {
421
+ shadowEnv({ FOLLOW_UP_GATE_VERIFIER_SCRIPT: "" });
422
+ const calls = stubDeps(async () => ({ ok: true, controls: { ok: true } }));
423
+ const task = await workerTask();
424
+ await finish(task);
425
+ const rows = await shadowRows(task.id);
426
+ expect(rows[0]!.meta).toMatchObject({ decision: "error", reason: "no_verifier" });
427
+ expect(calls.getVerifier).toBe(0);
428
+ });
429
+
430
+ test("a missing verifier script records no_verifier", async () => {
431
+ shadowEnv();
432
+ setFollowUpGateDepsForTests({
433
+ getVerifier: async () => null,
434
+ runVerifier: async () => {
435
+ throw new Error("must not run");
436
+ },
437
+ });
438
+ const task = await workerTask();
439
+ await finish(task);
440
+ const rows = await shadowRows(task.id);
441
+ expect(rows[0]!.meta).toMatchObject({
442
+ decision: "error",
443
+ reason: "no_verifier",
444
+ verifier: null,
445
+ });
446
+ });
447
+
448
+ describe("default verifier lookup and runner", () => {
449
+ const runOutput = (overrides: Partial<RunScriptOutput>) => ({
450
+ exitCode: 0,
451
+ result: undefined as unknown,
452
+ stdout: "",
453
+ stderr: "",
454
+ durationMs: 1,
455
+ truncated: { stdout: false, stderr: false },
456
+ ...overrides,
457
+ });
458
+
459
+ beforeAll(async () => {
460
+ await upsertScriptByName({
461
+ name: "gate-fixture",
462
+ scope: "global",
463
+ source: "export default () => ({ ok: true, controls: { ok: true } });",
464
+ description: "Follow-up gate fixture",
465
+ intent: "Exercise the default verifier path",
466
+ signatureJson: JSON.stringify({ args: { type: "object" }, result: { type: "object" } }),
467
+ typeChecked: true,
468
+ embeddingMode: "skip",
469
+ });
470
+ });
471
+
472
+ test("runs the global script as the Lead and records its version and hash", async () => {
473
+ shadowEnv({ FOLLOW_UP_GATE_VERIFIER_SCRIPT: "gate-fixture" });
474
+ const saved = spyOn(savedScriptRunner, "runSavedScriptAsAgent").mockResolvedValue(
475
+ runOutput({ result: { ok: true, controls: { ok: true } } }),
476
+ );
477
+ try {
478
+ const task = await workerTask();
479
+ await finish(task);
480
+ const rows = await shadowRows(task.id);
481
+ expect(rows[0]!.meta.decision).toBe("would_skip");
482
+ expect(rows[0]!.meta.verifier.name).toBe("gate-fixture");
483
+ expect(rows[0]!.meta.verifier.version).toBeGreaterThanOrEqual(1);
484
+ expect(rows[0]!.meta.verifier.contentHash).toMatch(/^[0-9a-f]{64}$/);
485
+ const call = saved.mock.calls[0]![0];
486
+ expect(call.agentId).toBe(leadId);
487
+ expect(call.input).toEqual({ taskId: task.id });
488
+ expect(call.timeoutMs).toBe(60_000);
489
+ } finally {
490
+ saved.mockRestore();
491
+ }
492
+ });
493
+
494
+ test("a non-zero exit records verifier_error", async () => {
495
+ shadowEnv({ FOLLOW_UP_GATE_VERIFIER_SCRIPT: "gate-fixture" });
496
+ const saved = spyOn(savedScriptRunner, "runSavedScriptAsAgent").mockResolvedValue(
497
+ runOutput({ exitCode: 1, error: "script failed" }),
498
+ );
499
+ try {
500
+ const task = await workerTask();
501
+ await finish(task);
502
+ const rows = await shadowRows(task.id);
503
+ expect(rows[0]!.meta).toMatchObject({
504
+ decision: "error",
505
+ reason: "verifier_error",
506
+ error: "script failed",
507
+ });
508
+ } finally {
509
+ saved.mockRestore();
510
+ }
511
+ });
512
+ });
513
+ });