@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.
- package/dist/{acp-adapter-g3ads55s.js → acp-adapter-7b4vk3h5.js} +5 -4
- package/dist/{actions-zwfykqqk.js → actions-3tt153af.js} +18 -17
- package/dist/{amp-adapter-z3s0ssdt.js → amp-adapter-njpts0yr.js} +1 -1
- package/dist/{anthropic-messages-x4q6rfq5.js → anthropic-messages-xey2skwx.js} +7 -7
- package/dist/{app-vmp4fgep.js → app-nky4378f.js} +3 -3
- package/dist/{assistant-vwde653f.js → assistant-jnb669pa.js} +24 -22
- package/dist/{azure-openai-responses-s1w5n70z.js → azure-openai-responses-26t7j9ra.js} +5 -5
- package/dist/{boot-reembed-375t7tbk.js → boot-reembed-9mgvaztt.js} +1 -1
- package/dist/{boot-reembed-gq9rcpnj.js → boot-reembed-qs2djdej.js} +1 -1
- package/dist/{boot-scrub-sweep-nd938kyf.js → boot-scrub-sweep-yjpdj7s1.js} +3 -3
- package/dist/{boot-seeds-aykx51v3.js → boot-seeds-5cm6ambd.js} +4 -4
- package/dist/{boxd-k1j26g3h.js → boxd-nv507va8.js} +5 -5
- package/dist/{claude-adapter-8j4hm099.js → claude-adapter-fn287zmm.js} +5 -4
- package/dist/{claude-managed-setup-0wyynpn1.js → claude-managed-setup-wtdz2mj2.js} +1 -1
- package/dist/{claude-sdk-session-q6wj5hmy.js → claude-sdk-session-x91kqyhm.js} +9677 -9514
- package/dist/{cli-j3jvnpe8.js → cli-08bmz9z9.js} +1 -1
- package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
- package/dist/{cli-y24sc29k.js → cli-1c6tzt8g.js} +30 -8
- package/dist/{cli-mjzynrse.js → cli-35rsp8ye.js} +1 -1
- package/dist/{cli-rpm2sgv0.js → cli-3fg0bdcv.js} +19 -2
- package/dist/{cli-nn4d8dne.js → cli-5b456bpf.js} +2 -2
- package/dist/{cli-bhjt02p8.js → cli-5jnhv72d.js} +1 -1
- package/dist/{cli-fqzx7gwa.js → cli-5zx699j9.js} +2 -2
- package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
- package/dist/{cli-vqypxzh4.js → cli-6g0hb4cp.js} +8 -8
- package/dist/{cli-tqgg40ek.js → cli-6pfd54vx.js} +9 -8
- package/dist/{cli-bsn8nhrq.js → cli-6vcp76qk.js} +3 -3
- package/dist/{cli-zppwg9p9.js → cli-72v799xn.js} +1 -1
- package/dist/cli-82j36f2m.js +45 -0
- package/dist/{cli-hn9hxmz9.js → cli-8305gbq0.js} +29 -16
- package/dist/{cli-z8eh3ex8.js → cli-8sckt74b.js} +8 -8
- package/dist/{cli-bnz7a7qf.js → cli-95jzxcp3.js} +1 -1
- package/dist/{cli-0bxqnvq9.js → cli-adeegh7d.js} +29 -7
- package/dist/{cli-g61qxmge.js → cli-afkqkrtw.js} +3 -3
- package/dist/{cli-7a42nfy0.js → cli-ayayvfg4.js} +24 -4
- package/dist/{cli-bqt1mptt.js → cli-az48n0nr.js} +4 -4
- package/dist/{cli-z6jkx23q.js → cli-bfv7vdg6.js} +9 -9
- package/dist/{cli-pywf3eh5.js → cli-caj33vcq.js} +5 -5
- package/dist/{cli-5pag6rmn.js → cli-cb2a8pna.js} +1 -1
- package/dist/{cli-wvaawacd.js → cli-cppkf404.js} +5236 -72
- package/dist/{cli-mry0qzym.js → cli-ddaanqkd.js} +123 -10
- package/dist/{cli-0gey5fzy.js → cli-e0kmphd6.js} +2 -2
- package/dist/{cli-zh3fp8v2.js → cli-e46hg0xw.js} +1 -1
- package/dist/{cli-jm2vfpfd.js → cli-e5bzrn01.js} +1 -1
- package/dist/{cli-qxnf8z2z.js → cli-excnnjg2.js} +165 -153
- package/dist/{cli-71ptav7x.js → cli-ferkkrg9.js} +1 -1
- package/dist/{cli-vpf89f34.js → cli-fmjd0t1y.js} +3 -3
- package/dist/{cli-rm6hqj3z.js → cli-fyt2g4wd.js} +33 -139
- package/dist/cli-g08zdaec.js +55 -0
- package/dist/{cli-ehq736cn.js → cli-g7s47cc5.js} +4 -4
- package/dist/{cli-82ae98pz.js → cli-gz6q4eve.js} +3 -3
- package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
- package/dist/{cli-rqkxrmgw.js → cli-je518txm.js} +39 -14
- package/dist/{cli-8hs41zy2.js → cli-jtjtabxe.js} +1 -1
- package/dist/{cli-611a1294.js → cli-k0wwfqbg.js} +18 -1
- package/dist/{cli-nzxr556s.js → cli-k1nje3v3.js} +38 -43
- package/dist/{cli-3h4nhjn8.js → cli-k8196sh2.js} +9 -7
- package/dist/{cli-afrrppsw.js → cli-k899ehvh.js} +2 -2
- package/dist/{cli-0731hvbw.js → cli-m0da8j35.js} +23 -9
- package/dist/cli-m2z8jagn.js +12078 -0
- package/dist/{cli-8veytvbh.js → cli-mkk9rnzz.js} +5 -5
- package/dist/{cli-7wspm5gm.js → cli-n6bv12jn.js} +528 -6
- package/dist/{cli-spknvxbq.js → cli-nmzhp1b5.js} +14 -12
- package/dist/{cli-p599f1qm.js → cli-p82cnnjq.js} +1 -1
- package/dist/{cli-xtjhfmpc.js → cli-pc6n7fre.js} +1 -1
- package/dist/{cli-vbyfwh92.js → cli-pjctyea3.js} +2 -2
- package/dist/{cli-nmbmy64z.js → cli-pq73h5mz.js} +3 -3
- package/dist/{cli-aa6yxc7m.js → cli-pz4rk021.js} +1 -1
- package/dist/{cli-7wcyk2s5.js → cli-q3tkegby.js} +1 -1
- package/dist/cli-qghe0h66.js +67 -0
- package/dist/{cli-6s7vdcta.js → cli-qh4d4tvk.js} +4 -4
- package/dist/{cli-mmwm7zbf.js → cli-rtt8g4v9.js} +1 -1
- package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
- package/dist/{cli-1h3wcpbw.js → cli-snpj5zbj.js} +2 -2
- package/dist/{cli-a7v854rf.js → cli-tgrv9cjx.js} +263 -12
- package/dist/{cli-a47xy8eq.js → cli-vz22fyqv.js} +186 -54
- package/dist/{cli-1ayk1eas.js → cli-wk5yrfwd.js} +285 -42
- package/dist/{cli-1338796d.js → cli-ws19768n.js} +1755 -119
- package/dist/{cli-70r77n04.js → cli-xae4g51r.js} +1 -1
- package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
- package/dist/{cli-xbkdm710.js → cli-yndmfxb3.js} +3 -3
- package/dist/{cli-rhw40jgb.js → cli-yyj3j5m4.js} +2 -2
- package/dist/cli.js +26 -25
- package/dist/{cloudflare-workers-ai-system-one-bm60fjm0.js → cloudflare-workers-ai-system-one-ef2a980q.js} +2 -2
- package/dist/{codex-adapter-q6k3gnyg.js → codex-adapter-cf2yxe9m.js} +3 -2
- package/dist/{codex-hook-9efm2x1m.js → codex-hook-a0c6g6t6.js} +1 -1
- package/dist/{codex-session-runner-3mwmk3d9.js → codex-session-runner-6vfzdgbd.js} +3 -2
- package/dist/{commands-evrenjn9.js → commands-t3mfy7vn.js} +9 -3
- package/dist/{controller-xjp8vswh.js → controller-q7xrbaz0.js} +8 -3
- package/dist/{controllers-connect-pp5jnxvc.js → controllers-connect-c6nm70kg.js} +20 -19
- package/dist/{cursor-adapter-yf47w4t0.js → cursor-adapter-ftd81ga8.js} +1 -1
- package/dist/{db-zxpw6aht.js → db-dk572x25.js} +3 -3
- package/dist/{db-rgfsh91f.js → db-vemtpmcc.js} +14 -4
- package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
- package/dist/{dispatcher-smasgws7.js → dispatcher-kh7ecxh9.js} +17 -16
- package/dist/{docker-rrjhaqt9.js → docker-c1a3gqmf.js} +89 -22
- package/dist/{dsh-adapter-ftyy8dnp.js → dsh-adapter-v8628r89.js} +1 -1
- package/dist/{e2b-w1p0bnpm.js → e2b-fr12yccx.js} +5 -5
- package/dist/{e2b-qcaj675y.js → e2b-x6ywzak3.js} +3 -3
- package/dist/{easter-egg-3d-yyts7p16.js → easter-egg-3d-nenw4x63.js} +1 -1
- package/dist/{embedding-space-1z0mbg95.js → embedding-space-0jepb239.js} +6 -6
- package/dist/{embeddings-ygeg7tm7.js → embeddings-xxt49yxg.js} +1 -1
- package/dist/{execute-14qhqp4d.js → execute-1mhw6e4x.js} +1 -1
- package/dist/{external-channel-ttgrs3sx.js → external-channel-55v12e60.js} +2 -2
- package/dist/{fx-adapter-gt6pncj2.js → fx-adapter-bs8wqrk5.js} +6 -5
- package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-v94hr7h0.js} +5 -5
- package/dist/{google-vertex-jrtdh9gs.js → google-vertex-g7vr6cf8.js} +5 -5
- package/dist/{grok-adapter-sm4m5k4g.js → grok-adapter-gpjqmkcq.js} +6 -5
- package/dist/handlers-zxmp1ta4.js +32 -0
- package/dist/{heartbeat-qahz4p62.js → heartbeat-k42svnr0.js} +20 -19
- package/dist/{hook-4fd5wq5w.js → hook-114m7hap.js} +4 -3
- package/dist/{hook-kj3d398m.js → hook-6nphweh0.js} +24 -9
- package/dist/{http-1ted0j1k.js → http-r8cc4wz7.js} +1196 -211
- package/dist/index-a8phxqnk.js +37 -0
- package/dist/{index-q4b917zs.js → index-e3s8wmxy.js} +1 -1
- package/dist/{index-6rq3r5zb.js → index-fpk4bcd9.js} +1 -1
- package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
- package/dist/index-k6h00t4c.js +30 -0
- package/dist/{index-7v51awbn.js → index-nh3wyqdt.js} +1027 -577
- package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
- package/dist/index-xrz7te8h.js +33 -0
- package/dist/{keepalive-6cf311t5.js → keepalive-x060nt9p.js} +3 -3
- package/dist/lead-956sd2yc.js +71 -0
- package/dist/{maintenance-a4bm1bmt.js → maintenance-2xxfexhf.js} +1 -1
- package/dist/{mistral-conversations-4pmwj1c0.js → mistral-conversations-3gjpdb4b.js} +4 -4
- package/dist/{oauth-refresh-sweep-3h0dfw1k.js → oauth-refresh-sweep-fckc4ar5.js} +2 -2
- package/dist/{onboard-myn5hraa.js → onboard-zc48j237.js} +5 -5
- package/dist/{openai-codex-responses-44j573nm.js → openai-codex-responses-8pbxq9h5.js} +5 -5
- package/dist/{openai-completions-a4gee2a8.js → openai-completions-rm00s912.js} +4 -4
- package/dist/{openai-decisions-w4qkrcar.js → openai-decisions-8v957r2r.js} +1 -1
- package/dist/{openai-responses-rbp6h5kr.js → openai-responses-agxpf96k.js} +5 -5
- package/dist/{opencode-adapter-p2nkb5am.js → opencode-adapter-r690h1cr.js} +4 -3
- package/dist/{otel-impl-a1a0ef6b.js → otel-impl-r5xbh81q.js} +4 -4
- package/dist/{pi-mono-adapter-rzmpb72a.js → pi-mono-adapter-c7v8sdec.js} +7 -6
- package/dist/{pricing-refresh-83drrnxj.js → pricing-refresh-3pmre11v.js} +3 -3
- package/dist/{rbac-roles-s8y6ecb5.js → rbac-roles-3vbshwsb.js} +1 -1
- package/dist/{rbac-roles-0fb6e7pj.js → rbac-roles-910mmkgz.js} +2 -2
- package/dist/{render-v2-twmbgrz4.js → render-v2-pxykkbab.js} +3 -3
- package/dist/{resume-zfqc36xj.js → resume-adda0bwy.js} +6 -6
- package/dist/{runtime-qwggdpqr.js → runtime-fnbm7stk.js} +1 -1
- package/dist/{session-access-sc9pn9da.js → session-access-2fvamrdc.js} +4 -4
- package/dist/{session-source-backfill-qh07xr8g.js → session-source-backfill-08xvv0sm.js} +2 -2
- package/dist/{setup-4fr0m7a1.js → setup-1aqmvmh1.js} +3 -3
- package/dist/{typesafe-system-one-5mavm259.js → typesafe-system-one-0bmqcxfg.js} +2 -2
- package/dist/{virtual-modules-fh4ha15b.js → virtual-modules-d2pjk9j1.js} +5 -5
- package/dist/{work-objects-0wr8zxct.js → work-objects-9s0yrm5j.js} +20 -19
- package/dist/{worker-follow-up-ktyyazyp.js → worker-follow-up-3q78fqmr.js} +17 -16
- package/dist/worker-y15hy08h.js +71 -0
- package/openapi.json +289 -4
- package/package.json +9 -8
- package/src/apps/definition.ts +6 -5
- package/src/be/db/page-theme-tokens.ts +12 -0
- package/src/be/db/script-runs.ts +23 -0
- package/src/be/db/tasks/read.ts +131 -4
- package/src/be/db/tasks/write.ts +19 -0
- package/src/be/db-client.ts +114 -11
- package/src/be/db.ts +49 -6
- package/src/be/memory/providers/sqlite-store.ts +49 -12
- package/src/be/migrations/260_runtime_pool_cache.sql +20 -0
- package/src/be/migrations/262_session_visibility_indexes.sql +18 -0
- package/src/be/migrations/263_task_list_filter_index.sql +11 -0
- package/src/be/migrations/264_slack_thread_cover_index.sql +9 -0
- package/src/be/migrations/266_script_runs_source_task.sql +11 -0
- package/src/be/migrations/269_pages_design_system.sql +23 -0
- package/src/be/runtime-pools/cache-cleanup.ts +109 -0
- package/src/be/runtime-pools/db.ts +65 -6
- package/src/be/runtime-pools/remote-controller.ts +5 -0
- package/src/be/runtime-pools/supervisor.ts +33 -2
- package/src/be/scripts/typecheck.ts +22 -0
- package/src/be/seed/schedules-seeder.ts +0 -10
- package/src/be/seed-scripts/catalog/catalog-report.inline.ts +2 -0
- package/src/be/seed-scripts/catalog/catalog-report.ts +2 -0
- package/src/be/seed-scripts/catalog/memory-eval.ts +2 -0
- package/src/be/seed-scripts/catalog/swarm-update-check.ts +150 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
- package/src/be/seed-scripts/index.ts +9 -0
- package/src/be/seed-skills/bundled-files.generated.json +3 -3
- package/src/be/swarm-config-guard.ts +32 -0
- package/src/be/task-auto-title-listener.ts +127 -0
- package/src/commands/controller.ts +4 -0
- package/src/commands/entrypoint-api-readiness.ts +74 -0
- package/src/commands/onboard/agent-fs-version.ts +1 -1
- package/src/commands/profile-sync.ts +151 -16
- package/src/commands/runner.ts +15 -7
- package/src/hooks/hook.ts +20 -9
- package/src/http/index.ts +4 -1
- package/src/http/list-total-cache.ts +14 -7
- package/src/http/memory.ts +38 -9
- package/src/http/pages-public.ts +93 -129
- package/src/http/pages.ts +94 -21
- package/src/http/script-runs.ts +174 -5
- package/src/http/status.ts +10 -2
- package/src/http/tasks.ts +4 -2
- package/src/pages/browser-sdk.ts +48 -21
- package/src/pages/color-literals.ts +115 -0
- package/src/pages/page-head.ts +766 -0
- package/src/pages/version.ts +3 -0
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +1 -0
- package/src/providers/codex-app-server.ts +27 -1
- package/src/providers/fx-adapter.ts +1 -1
- package/src/providers/pi-mono-extension.ts +30 -15
- package/src/runtime-providers/controller-protocol.ts +14 -1
- package/src/runtime-providers/controller.ts +5 -0
- package/src/runtime-providers/docker.ts +108 -23
- package/src/runtime-providers/types.ts +16 -0
- package/src/script-workflows/workflow-ctx.ts +8 -0
- package/src/scripts-runtime/types/stdlib.d.ts +22 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +22 -0
- package/src/server-user.ts +1 -0
- package/src/slack/commands.ts +5 -1
- package/src/slack/handlers.ts +37 -2
- package/src/slack/left-threads.ts +27 -4
- package/src/tasks/follow-up-gate.ts +318 -0
- package/src/tasks/follow-up-redelegation-guard.ts +155 -0
- package/src/tasks/worker-follow-up.ts +21 -1
- package/src/tests/claude-artifacts-read-script.test.ts +209 -0
- package/src/tests/codex-app-server.test.ts +59 -0
- package/src/tests/codex-gateway-route.test.ts +50 -1
- package/src/tests/create-page-tool.test.ts +501 -0
- package/src/tests/db-client-busy-retry.test.ts +81 -0
- package/src/tests/db-client.test.ts +21 -0
- package/src/tests/db-task-read-contract.test.ts +9 -2
- package/src/tests/db-task-write-contract.test.ts +7 -1
- package/src/tests/db-tasks-read.test.ts +21 -0
- package/src/tests/entrypoint-api-readiness.test.ts +416 -210
- package/src/tests/explicit-model-validation.test.ts +14 -8
- package/src/tests/fixtures/pages/head-v1.html +56 -0
- package/src/tests/follow-up-gate-shadow.test.ts +513 -0
- package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
- package/src/tests/identity-field-budget.test.ts +1 -1
- package/src/tests/internal-ai/credentials.test.ts +62 -0
- package/src/tests/internal-ai/task-title.test.ts +283 -0
- package/src/tests/list-total-cache.test.ts +27 -2
- package/src/tests/memory-fts-populate.test.ts +11 -9
- package/src/tests/memory-retrieval-purge.test.ts +80 -0
- package/src/tests/merge-gate-detect-changes.test.ts +145 -0
- package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/page-session-dev-cookie.test.ts +188 -0
- package/src/tests/pages-frame-ancestors.test.ts +28 -1
- package/src/tests/pages-head-v2.test.ts +534 -0
- package/src/tests/pages-http.test.ts +330 -0
- package/src/tests/pages-public-html.test.ts +43 -0
- package/src/tests/pages-storage.test.ts +182 -0
- package/src/tests/pages-versioning.test.ts +72 -0
- package/src/tests/profile-sync-stale-writeback.test.ts +334 -0
- package/src/tests/profile-sync.test.ts +11 -15
- package/src/tests/query-audit-plans.test.ts +27 -0
- package/src/tests/runtime-cache-cleanup.test.ts +367 -0
- package/src/tests/runtime-e2b-pools.test.ts +41 -0
- package/src/tests/runtime-pools-db.test.ts +98 -0
- package/src/tests/runtime-pools-http.test.ts +29 -0
- package/src/tests/runtime-providers-docker.test.ts +253 -0
- package/src/tests/runtime-remote-controller.test.ts +66 -2
- package/src/tests/runtime-supervisor.test.ts +66 -2
- package/src/tests/scheduled-task-required-params-migration.test.ts +3 -1
- package/src/tests/script-connections.test.ts +13 -0
- package/src/tests/script-runs-http.test.ts +684 -2
- package/src/tests/seed-registry.test.ts +28 -6
- package/src/tests/seed-schedules.test.ts +61 -8
- package/src/tests/seed-scripts.test.ts +168 -2
- package/src/tests/slack-leave-thread.test.ts +145 -5
- package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
- package/src/tests/status.test.ts +11 -0
- package/src/tests/swarm-diff.test.ts +121 -2
- package/src/tests/task-auto-title-config-guard.test.ts +37 -0
- package/src/tests/task-auto-title-listener.test.ts +165 -0
- package/src/tests/tasks-readable-row.test.ts +155 -0
- package/src/tests/theme-import.test.ts +758 -0
- package/src/tests/themes-page-theme.test.ts +155 -0
- package/src/tests/themes-presets.test.ts +172 -0
- package/src/tests/workflow-script-principal.test.ts +2 -1
- package/src/themes/classics.ts +949 -0
- package/src/themes/import.ts +420 -0
- package/src/themes/page-fields.ts +40 -0
- package/src/themes/page-theme.ts +102 -0
- package/src/themes/presets.ts +561 -0
- package/src/themes/tokens.ts +218 -0
- package/src/tools/app-upsert.ts +1 -1
- package/src/tools/create-page.ts +118 -12
- package/src/tools/send-task.ts +35 -47
- package/src/tools/slack-leave-thread.ts +1 -1
- package/src/tools/task-dedup.ts +45 -21
- package/src/tools/utils.ts +33 -0
- package/src/types.ts +83 -3
- package/src/utils/constants.ts +19 -0
- package/src/utils/follow-up-gate-schedule-ids.ts +13 -0
- package/src/utils/internal-ai/credentials.ts +146 -93
- package/src/utils/internal-ai/index.ts +10 -0
- package/src/utils/internal-ai/models.ts +18 -0
- package/src/utils/internal-ai/task-title.ts +282 -0
- package/src/utils/page-session.ts +37 -0
- package/templates/schedules/daily-status-report/content.md +88 -2
- package/templates/schedules/daily-swarm-update-check/content.md +16 -40
- package/templates/skills/apps/SKILL.md +1 -1
- package/templates/skills/apps/content.md +1 -1
- package/templates/skills/composio-google-docs/content.md +10 -0
- package/templates/skills/heartbeat-runbook/content.md +1 -1
- package/templates/skills/pages/config.json +1 -1
- package/templates/skills/pages/content.md +539 -86
- package/templates/skills/pages/files/examples/annotated-pr.html +21 -12
- package/templates/skills/pages/files/examples/multiplayer-board.html +41 -41
- package/templates/skills/pages/files/examples/report-page.html +125 -124
- package/templates/skills/script-workflows/content.md +19 -1
- package/templates/skills/swarm-docs/content.md +1 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -0
- package/templates/skills/swarm-scripts/content.md +2 -0
- package/dist/cli-2pnswgh8.js +0 -189
- package/dist/cli-dznrgaep.js +0 -30
- package/dist/cli-w3hcrxht.js +0 -6985
- package/dist/handlers-vwmba1m2.js +0 -31
- package/dist/index-5r71rf98.js +0 -32
- package/dist/index-anmwxs87.js +0 -29
- package/dist/index-spp0ra5c.js +0 -36
- package/dist/lead-fjy418cw.js +0 -69
- package/dist/worker-0c5tyzqe.js +0 -69
- package/templates/schedules/daily-workflow-health-audit/config.json +0 -19
- package/templates/schedules/daily-workflow-health-audit/content.md +0 -191
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import { unlink } from "node:fs/promises";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
closeDb,
|
|
5
|
+
getScheduledTaskByName,
|
|
6
|
+
getScheduledTasks,
|
|
7
|
+
initDb,
|
|
8
|
+
listWorkflows,
|
|
9
|
+
} from "../be/db";
|
|
10
|
+
import { getScript } from "../be/scripts/db";
|
|
4
11
|
import { runSeeder, SEEDERS } from "../be/seed";
|
|
5
12
|
import { schedulesSeeder } from "../be/seed/schedules-seeder";
|
|
6
13
|
import { workflowsSeeder } from "../be/seed/workflows-seeder";
|
|
14
|
+
import { scriptsSeeder } from "../be/seed-scripts";
|
|
7
15
|
|
|
8
16
|
const TEST_DB_PATH = "./test-seed-registry.sqlite";
|
|
9
17
|
|
|
@@ -58,15 +66,29 @@ describe("seeder registry", () => {
|
|
|
58
66
|
}
|
|
59
67
|
});
|
|
60
68
|
|
|
61
|
-
test("seeds all
|
|
69
|
+
test("seeds all 22 automation templates on a fresh database and re-runs as a no-op", async () => {
|
|
62
70
|
expect(await listWorkflows()).toHaveLength(0);
|
|
63
71
|
expect(await getScheduledTasks()).toHaveLength(0);
|
|
64
72
|
|
|
73
|
+
// Boot order: scripts seed before schedules, so a script-target schedule
|
|
74
|
+
// (daily-swarm-update-check) resolves its catalog script on a fresh install.
|
|
75
|
+
const order = SEEDERS.map((seeder) => seeder.kind);
|
|
76
|
+
expect(order.indexOf("script")).toBeLessThan(order.indexOf("schedule"));
|
|
77
|
+
expect(await getScript({ name: "swarm-update-check", scope: "global" })).toBeNull();
|
|
78
|
+
const scriptResult = await runSeeder(scriptsSeeder, {
|
|
79
|
+
quiet: true,
|
|
80
|
+
scriptEmbeddingMode: "skip",
|
|
81
|
+
});
|
|
65
82
|
const workflowResult = await runSeeder(workflowsSeeder, { quiet: true });
|
|
66
83
|
const scheduleResult = await runSeeder(schedulesSeeder, { quiet: true });
|
|
67
84
|
|
|
85
|
+
expect(scriptResult.failed).toEqual([]);
|
|
68
86
|
expect(workflowResult).toMatchObject({ created: 11, failed: [] });
|
|
69
|
-
expect(scheduleResult).toMatchObject({ created:
|
|
87
|
+
expect(scheduleResult).toMatchObject({ created: 11, failed: [] });
|
|
88
|
+
expect(await getScheduledTaskByName("daily-swarm-update-check")).toMatchObject({
|
|
89
|
+
targetType: "script",
|
|
90
|
+
scriptName: "swarm-update-check",
|
|
91
|
+
});
|
|
70
92
|
expect((await listWorkflows()).map((workflow) => workflow.name).sort()).toEqual(
|
|
71
93
|
workflowsSeeder.items().map((item) => item.key),
|
|
72
94
|
);
|
|
@@ -83,10 +105,10 @@ describe("seeder registry", () => {
|
|
|
83
105
|
expect(await runSeeder(schedulesSeeder, { quiet: true })).toMatchObject({
|
|
84
106
|
created: 0,
|
|
85
107
|
updated: 0,
|
|
86
|
-
skippedUnchanged:
|
|
108
|
+
skippedUnchanged: 11,
|
|
87
109
|
failed: [],
|
|
88
110
|
});
|
|
89
111
|
expect(await listWorkflows()).toHaveLength(11);
|
|
90
|
-
expect(await getScheduledTasks()).toHaveLength(
|
|
91
|
-
});
|
|
112
|
+
expect(await getScheduledTasks()).toHaveLength(11);
|
|
113
|
+
}, 60_000); // typechecks every catalog script with tsc
|
|
92
114
|
});
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
initDb,
|
|
9
9
|
updateScheduledTask,
|
|
10
10
|
} from "../be/db";
|
|
11
|
+
import { upsertScriptByName } from "../be/scripts/db";
|
|
11
12
|
import { runSeeder } from "../be/seed";
|
|
12
13
|
import {
|
|
13
14
|
createSchedulesSeeder,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
type ScheduleTemplateSource,
|
|
16
17
|
} from "../be/seed/schedules-seeder";
|
|
17
18
|
import { createWorkflowsSeeder } from "../be/seed/workflows-seeder";
|
|
19
|
+
import { SEED_SCRIPTS } from "../be/seed-scripts";
|
|
18
20
|
|
|
19
21
|
const TEST_DB_PATH = "./test-seed-schedules.sqlite";
|
|
20
22
|
|
|
@@ -95,6 +97,23 @@ function targetScheduleSource(target: Record<string, unknown>, prompt?: string)
|
|
|
95
97
|
} satisfies ScheduleTemplateSource;
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
/** Stand-in for the catalog script daily-swarm-update-check targets; boot seeds the real one first. */
|
|
101
|
+
async function seedUpdateCheckScript() {
|
|
102
|
+
await upsertScriptByName({
|
|
103
|
+
name: "swarm-update-check",
|
|
104
|
+
scope: "global",
|
|
105
|
+
scopeId: null,
|
|
106
|
+
source: "export default async function main() { return {}; }",
|
|
107
|
+
description: "test stand-in",
|
|
108
|
+
intent: "seed-schedules test fixture",
|
|
109
|
+
signatureJson: "{}",
|
|
110
|
+
fsMode: "none",
|
|
111
|
+
agentId: null,
|
|
112
|
+
isScratch: false,
|
|
113
|
+
typeChecked: true,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
98
117
|
async function makeWorkflow(name: string) {
|
|
99
118
|
return createWorkflow({ name, definition: { nodes: [] } });
|
|
100
119
|
}
|
|
@@ -118,9 +137,9 @@ afterEach(async () => {
|
|
|
118
137
|
});
|
|
119
138
|
|
|
120
139
|
describe("schedules seeder", () => {
|
|
121
|
-
test("loads all
|
|
140
|
+
test("loads all eleven schedule templates, including the unfenced schedule block", () => {
|
|
122
141
|
const schedules = loadSeedSchedules();
|
|
123
|
-
expect(schedules).toHaveLength(
|
|
142
|
+
expect(schedules).toHaveLength(11);
|
|
124
143
|
// Zero-config, requires:[], placeholders:[], template enabled:true -> auto-enabled
|
|
125
144
|
// under the default (unset -> on) SEED_AUTOMATIONS_ENABLED switch.
|
|
126
145
|
expect(schedules.find((schedule) => schedule.name === "daily-status-report")).toMatchObject({
|
|
@@ -135,6 +154,11 @@ describe("schedules seeder", () => {
|
|
|
135
154
|
enabled: true,
|
|
136
155
|
requiredParams: [],
|
|
137
156
|
requires: [],
|
|
157
|
+
// A script fire: no agent task, no prompt.
|
|
158
|
+
targetType: "script",
|
|
159
|
+
scriptName: "swarm-update-check",
|
|
160
|
+
scriptArgs: {},
|
|
161
|
+
taskTemplate: "",
|
|
138
162
|
});
|
|
139
163
|
expect(
|
|
140
164
|
schedules.find((schedule) => schedule.name === "weekly-harness-upgrade-check"),
|
|
@@ -149,9 +173,10 @@ describe("schedules seeder", () => {
|
|
|
149
173
|
"daily-blocker-digest",
|
|
150
174
|
"daily-status-report",
|
|
151
175
|
"daily-swarm-update-check",
|
|
152
|
-
"daily-workflow-health-audit",
|
|
153
176
|
"dreaming-daily",
|
|
154
177
|
]);
|
|
178
|
+
// The status report absorbed the workflow-health audit's six queries.
|
|
179
|
+
expect(schedules.map((schedule) => schedule.name)).not.toContain("daily-workflow-health-audit");
|
|
155
180
|
// dreaming replaces Daily Evolution, which seeds off even though it is zero-config.
|
|
156
181
|
expect(
|
|
157
182
|
schedules.find((schedule) => schedule.name === "daily-compounding-reflection"),
|
|
@@ -160,20 +185,22 @@ describe("schedules seeder", () => {
|
|
|
160
185
|
|
|
161
186
|
test("SEED_AUTOMATIONS_ENABLED=false still seeds all schedules disabled", async () => {
|
|
162
187
|
process.env.SEED_AUTOMATIONS_ENABLED = "false";
|
|
163
|
-
// dreaming-daily targets the seeded dreaming workflow.
|
|
188
|
+
// dreaming-daily targets the seeded dreaming workflow, daily-swarm-update-check a catalog script.
|
|
164
189
|
await runSeeder(createWorkflowsSeeder(), { quiet: true });
|
|
190
|
+
await seedUpdateCheckScript();
|
|
165
191
|
const result = await runSeeder(createSchedulesSeeder(), { quiet: true });
|
|
166
|
-
expect(result).toMatchObject({ created:
|
|
192
|
+
expect(result).toMatchObject({ created: 11, failed: [] });
|
|
167
193
|
const schedules = await getScheduledTasks();
|
|
168
|
-
expect(schedules).toHaveLength(
|
|
194
|
+
expect(schedules).toHaveLength(11);
|
|
169
195
|
expect(schedules.every((schedule) => schedule.enabled === false)).toBe(true);
|
|
170
196
|
});
|
|
171
197
|
|
|
172
198
|
test("seeds a disabled schedule whose timezone still needs setup", async () => {
|
|
173
|
-
// dreaming-daily targets the seeded dreaming workflow.
|
|
199
|
+
// dreaming-daily targets the seeded dreaming workflow, daily-swarm-update-check a catalog script.
|
|
174
200
|
await runSeeder(createWorkflowsSeeder(), { quiet: true });
|
|
201
|
+
await seedUpdateCheckScript();
|
|
175
202
|
const result = await runSeeder(createSchedulesSeeder(), { quiet: true });
|
|
176
|
-
expect(result).toMatchObject({ created:
|
|
203
|
+
expect(result).toMatchObject({ created: 11, failed: [] });
|
|
177
204
|
expect(await getScheduledTaskByName("weekly-dependabot-triage")).toMatchObject({
|
|
178
205
|
cronExpression: "40 3 * * 0",
|
|
179
206
|
timezone: "{{TIMEZONE}}",
|
|
@@ -313,6 +340,32 @@ describe("schedules seeder", () => {
|
|
|
313
340
|
expect(result.failed[0]?.error).toContain('Global script "no-such-script" not found');
|
|
314
341
|
});
|
|
315
342
|
|
|
343
|
+
test("daily-swarm-update-check runs the catalog's swarm-update-check script, no agent task", async () => {
|
|
344
|
+
// The scripts catalog ships the target, so boot's script seeder creates it first.
|
|
345
|
+
expect(SEED_SCRIPTS.map((script) => script.name)).toContain("swarm-update-check");
|
|
346
|
+
const updateCheck = loadSeedSchedules().filter(
|
|
347
|
+
(schedule) => schedule.name === "daily-swarm-update-check",
|
|
348
|
+
);
|
|
349
|
+
expect(updateCheck).toHaveLength(1);
|
|
350
|
+
|
|
351
|
+
// Control: without the script the item fails instead of seeding an agent task.
|
|
352
|
+
const seeder = createSchedulesSeeder();
|
|
353
|
+
const before = await runSeeder(seeder, { quiet: true });
|
|
354
|
+
expect(before.failed.map((failure) => failure.key)).toContain("daily-swarm-update-check");
|
|
355
|
+
expect(await getScheduledTaskByName("daily-swarm-update-check")).toBeNull();
|
|
356
|
+
|
|
357
|
+
await seedUpdateCheckScript();
|
|
358
|
+
await runSeeder(seeder, { quiet: true });
|
|
359
|
+
const row = await getScheduledTaskByName("daily-swarm-update-check");
|
|
360
|
+
expect(row).toMatchObject({
|
|
361
|
+
targetType: "script",
|
|
362
|
+
scriptName: "swarm-update-check",
|
|
363
|
+
scriptArgs: {},
|
|
364
|
+
enabled: true,
|
|
365
|
+
});
|
|
366
|
+
expect(row?.taskTemplate).toBeUndefined();
|
|
367
|
+
});
|
|
368
|
+
|
|
316
369
|
test("fails a workflow target whose workflow does not exist, without creating a row", async () => {
|
|
317
370
|
const result = await runSeeder(
|
|
318
371
|
createSchedulesSeeder([
|
|
@@ -20,6 +20,10 @@ import opsCatalogAudit, {
|
|
|
20
20
|
renderPage as renderOpsCatalogAuditPage,
|
|
21
21
|
} from "../be/seed-scripts/catalog/ops-catalog-audit";
|
|
22
22
|
import scheduleHealth from "../be/seed-scripts/catalog/schedule-health";
|
|
23
|
+
import swarmUpdateCheck, {
|
|
24
|
+
compareVersions,
|
|
25
|
+
parseVersion,
|
|
26
|
+
} from "../be/seed-scripts/catalog/swarm-update-check";
|
|
23
27
|
import taskContextGathering from "../be/seed-scripts/catalog/task-context-gathering";
|
|
24
28
|
import taskFailureAudit from "../be/seed-scripts/catalog/task-failure-audit";
|
|
25
29
|
import { extractScriptSignature } from "../scripts-runtime/extract-signature";
|
|
@@ -62,8 +66,8 @@ afterAll(async () => {
|
|
|
62
66
|
});
|
|
63
67
|
|
|
64
68
|
describe("seed-scripts catalog", () => {
|
|
65
|
-
test("manifest holds
|
|
66
|
-
expect(SEED_SCRIPTS.length).toBe(
|
|
69
|
+
test("manifest holds 40 unique, well-described scripts", () => {
|
|
70
|
+
expect(SEED_SCRIPTS.length).toBe(40);
|
|
67
71
|
const names = SEED_SCRIPTS.map((s) => s.name);
|
|
68
72
|
expect(new Set(names).size).toBe(names.length);
|
|
69
73
|
for (const s of SEED_SCRIPTS) {
|
|
@@ -217,6 +221,32 @@ describe("seed-scripts catalog", () => {
|
|
|
217
221
|
).toHaveLength(200);
|
|
218
222
|
});
|
|
219
223
|
|
|
224
|
+
test.each([
|
|
225
|
+
["Incomplete response returned, reason: max_output_tokens", "output-limit"],
|
|
226
|
+
["Stopped: max_tokens", "output-limit"],
|
|
227
|
+
["Maximum output length exceeded", "output-limit"],
|
|
228
|
+
["Output token limit reached", "output-limit"],
|
|
229
|
+
["401 Unauthorized", "auth/credentials"],
|
|
230
|
+
["invalid token", "auth/credentials"],
|
|
231
|
+
["Resource not accessible by personal access token", "auth/credentials"],
|
|
232
|
+
["Processed 100 tokens", "other"],
|
|
233
|
+
["max_input_tokens exceeded", "other"],
|
|
234
|
+
])("task-failure-audit classifies %s as %s", async (failureReason, expectedKey) => {
|
|
235
|
+
const result = await taskFailureAudit(
|
|
236
|
+
{ publishPage: false },
|
|
237
|
+
{
|
|
238
|
+
swarm: {
|
|
239
|
+
async task_list() {
|
|
240
|
+
return { data: { tasks: [{ id: "failed-task", failureReason }] } };
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
expect(result.groups).toHaveLength(1);
|
|
247
|
+
expect(result.groups[0].key).toBe(expectedKey);
|
|
248
|
+
});
|
|
249
|
+
|
|
220
250
|
test("task-failure-audit projects failure reasons into schedule-group samples", async () => {
|
|
221
251
|
let projectionArgs: Record<string, unknown> | undefined;
|
|
222
252
|
const result = await taskFailureAudit(
|
|
@@ -1974,3 +2004,139 @@ describe("dreaming-heartbeat check refuses a worker-chosen draftPath", () => {
|
|
|
1974
2004
|
expect(out.reasons.join(" ")).not.toContain("draftPath");
|
|
1975
2005
|
});
|
|
1976
2006
|
});
|
|
2007
|
+
|
|
2008
|
+
describe("swarm-update-check", () => {
|
|
2009
|
+
const RELEASES = "https://api.github.com/repos/desplega-ai/agent-swarm/releases/latest";
|
|
2010
|
+
|
|
2011
|
+
/** Fake API + GitHub: `/health` answers `running`, the release endpoint `release`. */
|
|
2012
|
+
function fakeCtx(opts: {
|
|
2013
|
+
running?: string | null;
|
|
2014
|
+
release?: { status: number; body?: unknown } | "network-error";
|
|
2015
|
+
agents?: unknown[];
|
|
2016
|
+
}) {
|
|
2017
|
+
const fetches: { url: string; retries?: number }[] = [];
|
|
2018
|
+
const sent: any[] = [];
|
|
2019
|
+
const stdlib = {
|
|
2020
|
+
Redacted: { value: (v: unknown) => v },
|
|
2021
|
+
fetch: async (url: string, init: { retries?: number } = {}) => {
|
|
2022
|
+
fetches.push({ url, retries: init.retries });
|
|
2023
|
+
if (url === "http://api.test/health") {
|
|
2024
|
+
return new Response(
|
|
2025
|
+
JSON.stringify(opts.running === null ? {} : { version: opts.running }),
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
if (url === RELEASES) {
|
|
2029
|
+
const release = opts.release ?? { status: 200, body: {} };
|
|
2030
|
+
if (release === "network-error") throw new Error("getaddrinfo ENOTFOUND");
|
|
2031
|
+
return new Response(JSON.stringify(release.body ?? {}), { status: release.status });
|
|
2032
|
+
}
|
|
2033
|
+
return new Response("{}", { status: 404 });
|
|
2034
|
+
},
|
|
2035
|
+
};
|
|
2036
|
+
const swarm = {
|
|
2037
|
+
config: { mcpBaseUrl: "http://api.test/" },
|
|
2038
|
+
async swarm_get() {
|
|
2039
|
+
return {
|
|
2040
|
+
success: true,
|
|
2041
|
+
status: 200,
|
|
2042
|
+
data: {
|
|
2043
|
+
agents: opts.agents ?? [
|
|
2044
|
+
{ id: "worker-1", isLead: false },
|
|
2045
|
+
{ id: "lead-1", isLead: true },
|
|
2046
|
+
],
|
|
2047
|
+
},
|
|
2048
|
+
};
|
|
2049
|
+
},
|
|
2050
|
+
async task_send(args: unknown) {
|
|
2051
|
+
sent.push(args);
|
|
2052
|
+
return { success: true, status: 200, data: { success: true, task: { id: "notify-1" } } };
|
|
2053
|
+
},
|
|
2054
|
+
};
|
|
2055
|
+
return { ctx: { stdlib, swarm }, fetches, sent };
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
const release = (tag: string) => ({
|
|
2059
|
+
status: 200,
|
|
2060
|
+
body: {
|
|
2061
|
+
tag_name: tag,
|
|
2062
|
+
html_url: `https://github.com/desplega-ai/agent-swarm/releases/tag/${tag}`,
|
|
2063
|
+
},
|
|
2064
|
+
});
|
|
2065
|
+
|
|
2066
|
+
test("compares versions numerically, never as strings", () => {
|
|
2067
|
+
expect(compareVersions(parseVersion("1.10.0")!, parseVersion("1.9.0")!)).toBeGreaterThan(0);
|
|
2068
|
+
expect(compareVersions(parseVersion("v2.0.0")!, parseVersion("2.0.0")!)).toBe(0);
|
|
2069
|
+
expect(parseVersion("1.2")).toBeNull();
|
|
2070
|
+
expect(parseVersion(undefined)).toBeNull();
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2073
|
+
test("a newer release notifies the lead once, with the versions and release URL", async () => {
|
|
2074
|
+
const { ctx, sent } = fakeCtx({ running: "1.9.0", release: release("v1.10.0") });
|
|
2075
|
+
const result: any = await swarmUpdateCheck({}, ctx);
|
|
2076
|
+
expect(result).toMatchObject({
|
|
2077
|
+
checked: true,
|
|
2078
|
+
status: "update-available",
|
|
2079
|
+
running: "1.9.0",
|
|
2080
|
+
latest: "1.10.0",
|
|
2081
|
+
notified: true,
|
|
2082
|
+
leadTaskId: "notify-1",
|
|
2083
|
+
});
|
|
2084
|
+
expect(sent).toHaveLength(1);
|
|
2085
|
+
expect(sent[0]).toMatchObject({ agentId: "lead-1", routingReason: "skill" });
|
|
2086
|
+
expect(sent[0].task).toContain("running 1.9.0, latest 1.10.0");
|
|
2087
|
+
expect(sent[0].task).toContain(
|
|
2088
|
+
"https://github.com/desplega-ai/agent-swarm/releases/tag/v1.10.0",
|
|
2089
|
+
);
|
|
2090
|
+
expect(sent[0].task).toContain("admin delivery channel");
|
|
2091
|
+
});
|
|
2092
|
+
|
|
2093
|
+
test("control: up to date and older-than-running releases notify nobody", async () => {
|
|
2094
|
+
for (const tag of ["v1.10.0", "v1.9.9"]) {
|
|
2095
|
+
const { ctx, sent } = fakeCtx({ running: "1.10.0", release: release(tag) });
|
|
2096
|
+
const result: any = await swarmUpdateCheck({}, ctx);
|
|
2097
|
+
expect(result).toMatchObject({ checked: true, status: "up-to-date" });
|
|
2098
|
+
expect(sent).toHaveLength(0);
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
|
|
2102
|
+
test("a missing running version is unknown, not up to date, and skips GitHub", async () => {
|
|
2103
|
+
const { ctx, sent, fetches } = fakeCtx({ running: null, release: release("v9.0.0") });
|
|
2104
|
+
const result: any = await swarmUpdateCheck({}, ctx);
|
|
2105
|
+
expect(result).toMatchObject({ checked: false, status: "unknown" });
|
|
2106
|
+
expect(result.message).toContain("Unable to determine running version");
|
|
2107
|
+
expect(fetches.map((f) => f.url)).not.toContain(RELEASES);
|
|
2108
|
+
expect(sent).toHaveLength(0);
|
|
2109
|
+
});
|
|
2110
|
+
|
|
2111
|
+
test("a failed GitHub call is reported after one attempt and notifies nobody", async () => {
|
|
2112
|
+
for (const failure of [
|
|
2113
|
+
{ status: 403, body: { message: "rate limited" } },
|
|
2114
|
+
"network-error" as const,
|
|
2115
|
+
]) {
|
|
2116
|
+
const { ctx, sent, fetches } = fakeCtx({ running: "1.0.0", release: failure });
|
|
2117
|
+
const result: any = await swarmUpdateCheck({}, ctx);
|
|
2118
|
+
expect(result).toMatchObject({ checked: false, status: "github-unreachable" });
|
|
2119
|
+
expect(result.message).toContain("Could not reach GitHub releases API");
|
|
2120
|
+
const github = fetches.filter((f) => f.url === RELEASES);
|
|
2121
|
+
expect(github).toHaveLength(1);
|
|
2122
|
+
expect(github[0]?.retries).toBe(1);
|
|
2123
|
+
expect(sent).toHaveLength(0);
|
|
2124
|
+
}
|
|
2125
|
+
});
|
|
2126
|
+
|
|
2127
|
+
test("dryRun reports the update without notifying", async () => {
|
|
2128
|
+
const { ctx, sent } = fakeCtx({ running: "1.0.0", release: release("v1.0.1") });
|
|
2129
|
+
const result: any = await swarmUpdateCheck({ dryRun: true }, ctx);
|
|
2130
|
+
expect(result).toMatchObject({ status: "update-available", notified: false });
|
|
2131
|
+
expect(sent).toHaveLength(0);
|
|
2132
|
+
});
|
|
2133
|
+
|
|
2134
|
+
test("an update with no lead to notify fails the run", async () => {
|
|
2135
|
+
const { ctx } = fakeCtx({
|
|
2136
|
+
running: "1.0.0",
|
|
2137
|
+
release: release("v1.0.1"),
|
|
2138
|
+
agents: [{ id: "worker-1", isLead: false }],
|
|
2139
|
+
});
|
|
2140
|
+
await expect(swarmUpdateCheck({}, ctx)).rejects.toThrow("no lead to notify");
|
|
2141
|
+
});
|
|
2142
|
+
});
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
createTaskExtended,
|
|
10
10
|
createWorkflow,
|
|
11
11
|
deleteSwarmConfig,
|
|
12
|
+
getKv,
|
|
12
13
|
getSlackTasksInThread,
|
|
13
14
|
initDb,
|
|
14
15
|
updateWorkflow,
|
|
@@ -19,9 +20,14 @@ import { invokeToolInProcess, resetBridgeServerForTesting } from "../http/mcp-br
|
|
|
19
20
|
import { handleTasks } from "../http/tasks";
|
|
20
21
|
import { reservedNamespaceError } from "../kv-reserved-namespaces";
|
|
21
22
|
import { createServer as createMcpServer } from "../server";
|
|
23
|
+
import { registerCommandHandler } from "../slack/commands";
|
|
22
24
|
import { registerMessageHandler, resetSlackHandlerCachesForTesting } from "../slack/handlers";
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
+
import {
|
|
26
|
+
isSlackThreadLeft,
|
|
27
|
+
SLACK_LEFT_THREAD_CONFIRMATION,
|
|
28
|
+
SLACK_LEFT_THREAD_KV_NAMESPACE,
|
|
29
|
+
} from "../slack/left-threads";
|
|
30
|
+
import { bufferThreadMessage, getBufferMessageCount, instantFlush } from "../slack/thread-buffer";
|
|
25
31
|
import { slackContextKey } from "../tasks/context-key";
|
|
26
32
|
import { registerSlackLeaveThreadTool } from "../tools/slack-leave-thread";
|
|
27
33
|
import type { Agent, AgentTask } from "../types";
|
|
@@ -45,6 +51,8 @@ const previousEnv = {
|
|
|
45
51
|
|
|
46
52
|
type Handler = (args: Record<string, unknown>) => Promise<void>;
|
|
47
53
|
const handlers = new Map<string, Handler>();
|
|
54
|
+
const commands = new Map<string, Handler>();
|
|
55
|
+
const ephemerals: Array<Record<string, unknown>> = [];
|
|
48
56
|
|
|
49
57
|
const client = {
|
|
50
58
|
auth: { test: async () => ({ user_id: BOT_USER_ID, bot_id: "B_LEAVE_BOT" }) },
|
|
@@ -52,7 +60,13 @@ const client = {
|
|
|
52
60
|
info: async () => ({ ok: true, channel: {} }),
|
|
53
61
|
replies: async () => ({ ok: true, messages: [] }),
|
|
54
62
|
},
|
|
55
|
-
chat: {
|
|
63
|
+
chat: {
|
|
64
|
+
postMessage: async () => ({ ok: true, ts: "1.1" }),
|
|
65
|
+
postEphemeral: async (args: Record<string, unknown>) => {
|
|
66
|
+
ephemerals.push(args);
|
|
67
|
+
return { ok: true };
|
|
68
|
+
},
|
|
69
|
+
},
|
|
56
70
|
reactions: { add: async () => ({ ok: true }) },
|
|
57
71
|
users: { info: async () => ({ ok: true, user: { profile: {} } }) },
|
|
58
72
|
};
|
|
@@ -99,10 +113,10 @@ let lead: Agent;
|
|
|
99
113
|
let worker: Agent;
|
|
100
114
|
let otherWorker: Agent;
|
|
101
115
|
|
|
102
|
-
async function seedThread(channel: string, threadTs: string) {
|
|
116
|
+
async function seedThread(channel: string, threadTs: string, opts: { unassigned?: boolean } = {}) {
|
|
103
117
|
// As the Slack handlers create it: only Slack ingress writes slackTriggerMessageTs.
|
|
104
118
|
return createTaskExtended("original ask", {
|
|
105
|
-
agentId: worker.id,
|
|
119
|
+
agentId: opts.unassigned ? undefined : worker.id,
|
|
106
120
|
source: "slack",
|
|
107
121
|
slackChannelId: channel,
|
|
108
122
|
slackThreadTs: threadTs,
|
|
@@ -158,6 +172,11 @@ beforeAll(async () => {
|
|
|
158
172
|
handlers.set(type, handler);
|
|
159
173
|
},
|
|
160
174
|
} as never);
|
|
175
|
+
registerCommandHandler({
|
|
176
|
+
command: (name: string, handler: Handler) => {
|
|
177
|
+
commands.set(name, handler);
|
|
178
|
+
},
|
|
179
|
+
} as never);
|
|
161
180
|
|
|
162
181
|
routeServer = createServer(async (req, res) => {
|
|
163
182
|
setRequestAuth(req, { kind: "operator", fingerprint: "test-key" });
|
|
@@ -192,6 +211,7 @@ afterAll(async () => {
|
|
|
192
211
|
|
|
193
212
|
beforeEach(() => {
|
|
194
213
|
resetSlackHandlerCachesForTesting();
|
|
214
|
+
ephemerals.length = 0;
|
|
195
215
|
});
|
|
196
216
|
|
|
197
217
|
describe("slack-leave-thread", () => {
|
|
@@ -429,3 +449,123 @@ describe("SLACK_LEAVE_THREAD_DISABLE", () => {
|
|
|
429
449
|
}
|
|
430
450
|
});
|
|
431
451
|
});
|
|
452
|
+
|
|
453
|
+
describe("!leave", () => {
|
|
454
|
+
test("leaves the thread in code: no task, no workflow event, an ephemeral confirmation", async () => {
|
|
455
|
+
const emitted: unknown[] = [];
|
|
456
|
+
const listener = (payload: unknown) => emitted.push(payload);
|
|
457
|
+
workflowEventBus.on("slack.message", listener);
|
|
458
|
+
try {
|
|
459
|
+
await seedThread("C_BANG_A", "3000.0001");
|
|
460
|
+
expect(await sendThreadReply("C_BANG_A", "3000.0001", "!leave")).toHaveLength(1);
|
|
461
|
+
expect(emitted).toHaveLength(0);
|
|
462
|
+
} finally {
|
|
463
|
+
workflowEventBus.off("slack.message", listener);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
expect(await isSlackThreadLeft("C_BANG_A", "3000.0001")).toBe(true);
|
|
467
|
+
const entry = await getKv(SLACK_LEFT_THREAD_KV_NAMESPACE, "C_BANG_A:3000.0001");
|
|
468
|
+
expect(entry?.value).toMatchObject({ slackUserId: HUMAN });
|
|
469
|
+
expect(entry?.value).not.toHaveProperty("agentId");
|
|
470
|
+
expect(ephemerals).toEqual([
|
|
471
|
+
{
|
|
472
|
+
channel: "C_BANG_A",
|
|
473
|
+
user: HUMAN,
|
|
474
|
+
thread_ts: "3000.0001",
|
|
475
|
+
text: SLACK_LEFT_THREAD_CONFIRMATION,
|
|
476
|
+
},
|
|
477
|
+
]);
|
|
478
|
+
|
|
479
|
+
expect(await sendThreadReply("C_BANG_A", "3000.0001", "chatting among ourselves")).toHaveLength(
|
|
480
|
+
1,
|
|
481
|
+
);
|
|
482
|
+
expect(
|
|
483
|
+
await sendThreadReply("C_BANG_A", "3000.0001", `<@${BOT_USER_ID}> back please`),
|
|
484
|
+
).toHaveLength(2);
|
|
485
|
+
expect(await isSlackThreadLeft("C_BANG_A", "3000.0001")).toBe(false);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test("an @mention before !leave leaves instead of rejoining, also in a left thread", async () => {
|
|
489
|
+
await seedThread("C_BANG_B", "3100.0001");
|
|
490
|
+
await sendThreadReply("C_BANG_B", "3100.0001", "!leave");
|
|
491
|
+
expect(await sendThreadReply("C_BANG_B", "3100.0001", `<@${BOT_USER_ID}> !leave`)).toHaveLength(
|
|
492
|
+
1,
|
|
493
|
+
);
|
|
494
|
+
expect(await isSlackThreadLeft("C_BANG_B", "3100.0001")).toBe(true);
|
|
495
|
+
expect(ephemerals).toHaveLength(2);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test("with ADDITIVE_SLACK the !leave message is never buffered", async () => {
|
|
499
|
+
process.env.ADDITIVE_SLACK = "true";
|
|
500
|
+
try {
|
|
501
|
+
await seedThread("C_BANG_C", "3200.0001");
|
|
502
|
+
await sendThreadReply("C_BANG_C", "3200.0001", "!leave");
|
|
503
|
+
expect(getBufferMessageCount("C_BANG_C:3200.0001")).toBe(0);
|
|
504
|
+
expect(await isSlackThreadLeft("C_BANG_C", "3200.0001")).toBe(true);
|
|
505
|
+
} finally {
|
|
506
|
+
process.env.ADDITIVE_SLACK = "false";
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test.each([
|
|
511
|
+
["!leave", "3600.0001"],
|
|
512
|
+
[`<@${BOT_USER_ID}> !leave`, "3600.0002"],
|
|
513
|
+
])("leaves a human-rooted thread whose only task is unassigned: %s", async (text, threadTs) => {
|
|
514
|
+
const emitted: unknown[] = [];
|
|
515
|
+
const listener = (payload: unknown) => emitted.push(payload);
|
|
516
|
+
workflowEventBus.on("slack.message", listener);
|
|
517
|
+
try {
|
|
518
|
+
const seeded = await seedThread("C_BANG_G", threadTs, { unassigned: true });
|
|
519
|
+
expect(seeded.agentId).toBeNull();
|
|
520
|
+
expect(await sendThreadReply("C_BANG_G", threadTs, text)).toHaveLength(1);
|
|
521
|
+
expect(emitted).toHaveLength(0);
|
|
522
|
+
} finally {
|
|
523
|
+
workflowEventBus.off("slack.message", listener);
|
|
524
|
+
}
|
|
525
|
+
expect(await isSlackThreadLeft("C_BANG_G", threadTs)).toBe(true);
|
|
526
|
+
expect(ephemerals).toHaveLength(1);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test("a task in another thread of the channel does not make !leave a command", async () => {
|
|
530
|
+
await seedThread("C_BANG_H", "3700.0001", { unassigned: true });
|
|
531
|
+
await sendThreadReply("C_BANG_H", "3700.0002", "!leave");
|
|
532
|
+
expect(await isSlackThreadLeft("C_BANG_H", "3700.0002")).toBe(false);
|
|
533
|
+
expect(ephemerals).toHaveLength(0);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test("is not a command outside a swarm thread, when addressed elsewhere, or with extra text", async () => {
|
|
537
|
+
await sendThreadReply("C_BANG_D", "3300.0001", "!leave");
|
|
538
|
+
expect(await isSlackThreadLeft("C_BANG_D", "3300.0001")).toBe(false);
|
|
539
|
+
|
|
540
|
+
await seedThread("C_BANG_E", "3400.0001");
|
|
541
|
+
await sendThreadReply("C_BANG_E", "3400.0001", "<@U_SOMEONE_ELSE> !leave");
|
|
542
|
+
await sendThreadReply("C_BANG_E", "3400.0001", "!leave it to me");
|
|
543
|
+
expect(await isSlackThreadLeft("C_BANG_E", "3400.0001")).toBe(false);
|
|
544
|
+
expect(ephemerals).toHaveLength(0);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test("SLACK_LEAVE_THREAD_DISABLE makes !leave an ordinary message and hides it from help", async () => {
|
|
548
|
+
async function helpText(): Promise<string> {
|
|
549
|
+
let text = "";
|
|
550
|
+
await commands.get("/agent-swarm-help")!({
|
|
551
|
+
ack: async () => {},
|
|
552
|
+
respond: async (reply: { blocks: Array<{ text: { text: string } }> }) => {
|
|
553
|
+
text = reply.blocks.map((b) => b.text.text).join("\n");
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
return text;
|
|
557
|
+
}
|
|
558
|
+
expect(await helpText()).toContain("`!leave` in a thread");
|
|
559
|
+
|
|
560
|
+
process.env.SLACK_LEAVE_THREAD_DISABLE = "true";
|
|
561
|
+
try {
|
|
562
|
+
await seedThread("C_BANG_F", "3500.0001");
|
|
563
|
+
expect(await sendThreadReply("C_BANG_F", "3500.0001", "!leave")).toHaveLength(2);
|
|
564
|
+
expect(await isSlackThreadLeft("C_BANG_F", "3500.0001")).toBe(false);
|
|
565
|
+
expect(ephemerals).toHaveLength(0);
|
|
566
|
+
expect(await helpText()).not.toContain("!leave");
|
|
567
|
+
} finally {
|
|
568
|
+
delete process.env.SLACK_LEAVE_THREAD_DISABLE;
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
1
2
|
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
3
|
import {
|
|
3
4
|
closeDb,
|
|
@@ -6,6 +7,7 @@ import {
|
|
|
6
7
|
getDb,
|
|
7
8
|
getDbClient,
|
|
8
9
|
getResolvableDeferralOutcomes,
|
|
10
|
+
getSlackTreeMessages,
|
|
9
11
|
initDb,
|
|
10
12
|
recordSlackMessage,
|
|
11
13
|
} from "../be/db";
|
|
@@ -93,12 +95,32 @@ describe("slack render-v2 watcher queries", () => {
|
|
|
93
95
|
closeDb();
|
|
94
96
|
});
|
|
95
97
|
|
|
98
|
+
test("the tree sweep tests thread tasks in the covering index, not task rows", async () => {
|
|
99
|
+
const sql = await capturedSql(() => getSlackTreeMessages());
|
|
100
|
+
const detail = plan(sql);
|
|
101
|
+
const uses = "SEARCH task USING COVERING INDEX idx_agent_tasks_slack_thread_cover";
|
|
102
|
+
expect(detail.split(uses).length - 1).toBe(3);
|
|
103
|
+
// Control: without migration 264 the same tests read the task rows.
|
|
104
|
+
const copy = Database.deserialize(getDb().serialize());
|
|
105
|
+
try {
|
|
106
|
+
copy.run("DROP INDEX idx_agent_tasks_slack_thread_cover");
|
|
107
|
+
const reverted = copy
|
|
108
|
+
.query<{ detail: string }, []>(`EXPLAIN QUERY PLAN ${sql}`)
|
|
109
|
+
.all()
|
|
110
|
+
.map((row) => row.detail)
|
|
111
|
+
.join("\n");
|
|
112
|
+
expect(reverted).not.toContain(uses);
|
|
113
|
+
} finally {
|
|
114
|
+
copy.close();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
96
118
|
test("deferral resolution drives from the chain, not from every task", async () => {
|
|
97
119
|
const sql = await capturedSql(() => getResolvableDeferralOutcomes());
|
|
98
120
|
const detail = plan(sql, [50]);
|
|
99
|
-
//
|
|
121
|
+
// Any parentTaskId-led index is a seek on the chain's children.
|
|
100
122
|
expect(detail).toMatch(
|
|
101
|
-
/SEARCH wake USING INDEX idx_agent_tasks_parentTaskId(_source)? \(parentTaskId=\?\)/,
|
|
123
|
+
/SEARCH wake USING INDEX idx_agent_tasks_parentTaskId(_source|_id)? \(parentTaskId=\?\)/,
|
|
102
124
|
);
|
|
103
125
|
expect(detail).not.toContain("idx_agent_tasks_taskType");
|
|
104
126
|
expect(detail).not.toContain("idx_agent_tasks_status_last_updated");
|
package/src/tests/status.test.ts
CHANGED
|
@@ -87,6 +87,8 @@ const ENV_KEYS_TO_RESET = [
|
|
|
87
87
|
"SWARM_BRAND_COLOR",
|
|
88
88
|
"SWARM_MARKETING_URL",
|
|
89
89
|
"SWARM_HIDE_CLOUD_PROMO",
|
|
90
|
+
"APP_URL",
|
|
91
|
+
"DASHBOARD_URL",
|
|
90
92
|
"HARNESS_PROVIDER",
|
|
91
93
|
"ANTHROPIC_API_KEY",
|
|
92
94
|
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
@@ -191,6 +193,7 @@ describe("buildStatusPayload — identity", () => {
|
|
|
191
193
|
marketing_url: null,
|
|
192
194
|
hide_cloud_promo: false,
|
|
193
195
|
org_id: null,
|
|
196
|
+
dashboard_url: null,
|
|
194
197
|
});
|
|
195
198
|
});
|
|
196
199
|
|
|
@@ -212,9 +215,17 @@ describe("buildStatusPayload — identity", () => {
|
|
|
212
215
|
marketing_url: "https://swarm.acme.example",
|
|
213
216
|
hide_cloud_promo: true,
|
|
214
217
|
org_id: "org_acme_123",
|
|
218
|
+
dashboard_url: null,
|
|
215
219
|
});
|
|
216
220
|
});
|
|
217
221
|
|
|
222
|
+
test("reports the configured dashboard URL: the first APP_URL entry, else DASHBOARD_URL", async () => {
|
|
223
|
+
process.env.DASHBOARD_URL = "https://old.example.com";
|
|
224
|
+
expect((await buildStatusPayload()).identity.dashboard_url).toBe("https://old.example.com");
|
|
225
|
+
process.env.APP_URL = "https://swarm.example.com/, https://other.example.com";
|
|
226
|
+
expect((await buildStatusPayload()).identity.dashboard_url).toBe("https://swarm.example.com");
|
|
227
|
+
});
|
|
228
|
+
|
|
218
229
|
test("treats SWARM_CLOUD=1 the same as 'true'", async () => {
|
|
219
230
|
process.env.SWARM_CLOUD = "1";
|
|
220
231
|
const payload = await buildStatusPayload();
|