@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
|
@@ -61,6 +61,259 @@ afterEach(async () => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
describe("Docker runtime provider", () => {
|
|
64
|
+
for (const failureAt of ["create", "start"] as const) {
|
|
65
|
+
test(`cache mount failure during container ${failureAt} retries once without cache`, async () => {
|
|
66
|
+
const bodies: Array<{ HostConfig: { Mounts?: unknown }; Env: string[] }> = [];
|
|
67
|
+
const secret = "mount-failure-private-token";
|
|
68
|
+
handler = (url, init) => {
|
|
69
|
+
if (url.pathname.endsWith("/volumes/create"))
|
|
70
|
+
return Response.json(JSON.parse(String(init.body)));
|
|
71
|
+
if (url.pathname.endsWith("/containers/create")) {
|
|
72
|
+
bodies.push(JSON.parse(String(init.body)));
|
|
73
|
+
if (bodies.length === 1 && failureAt === "create")
|
|
74
|
+
return Response.json({ message: `invalid mount config ${secret}` }, { status: 500 });
|
|
75
|
+
return Response.json({ Id: `container-${bodies.length}` });
|
|
76
|
+
}
|
|
77
|
+
if (url.pathname.endsWith("/container-1/start") && failureAt === "start")
|
|
78
|
+
return Response.json(
|
|
79
|
+
{ message: `error while mounting volume ${secret}` },
|
|
80
|
+
{ status: 500 },
|
|
81
|
+
);
|
|
82
|
+
return new Response(null, { status: 204 });
|
|
83
|
+
};
|
|
84
|
+
const result = await provider.provision(target, {
|
|
85
|
+
...request,
|
|
86
|
+
cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
|
|
87
|
+
env: { API_KEY: secret },
|
|
88
|
+
});
|
|
89
|
+
expect(result.externalId).toBe("container-2");
|
|
90
|
+
expect(result.meta?.cacheWarning).toContain("mount");
|
|
91
|
+
expect(result.meta?.cacheWarning).not.toContain(secret);
|
|
92
|
+
expect(bodies).toHaveLength(2);
|
|
93
|
+
expect(bodies[0]?.HostConfig.Mounts).toBeDefined();
|
|
94
|
+
expect(bodies[1]?.HostConfig.Mounts).toBeUndefined();
|
|
95
|
+
expect(bodies[1]?.Env).toEqual([`API_KEY=${secret}`]);
|
|
96
|
+
const operations = calls.map(({ url, init }) => `${init.method} ${url.pathname}`);
|
|
97
|
+
if (failureAt === "start")
|
|
98
|
+
expect(operations.indexOf("DELETE /v1.47/containers/container-1")).toBeLessThan(
|
|
99
|
+
operations.lastIndexOf("POST /v1.47/containers/create"),
|
|
100
|
+
);
|
|
101
|
+
expect(operations.at(-1)).toBe("POST /v1.47/containers/container-2/start");
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
test("unrelated container errors never trigger a cache fallback", async () => {
|
|
106
|
+
handler = (url, init) =>
|
|
107
|
+
url.pathname.endsWith("/volumes/create")
|
|
108
|
+
? Response.json(JSON.parse(String(init.body)))
|
|
109
|
+
: Response.json({ message: "network not found" }, { status: 500 });
|
|
110
|
+
await expect(
|
|
111
|
+
provider.provision(target, {
|
|
112
|
+
...request,
|
|
113
|
+
cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
|
|
114
|
+
}),
|
|
115
|
+
).rejects.toThrow("network not found");
|
|
116
|
+
expect(calls.filter(({ url }) => url.pathname.endsWith("/containers/create"))).toHaveLength(1);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("a failed cleanup after a mount start error prevents a duplicate container attempt", async () => {
|
|
120
|
+
handler = (url, init) => {
|
|
121
|
+
if (url.pathname.endsWith("/volumes/create"))
|
|
122
|
+
return Response.json(JSON.parse(String(init.body)));
|
|
123
|
+
if (url.pathname.endsWith("/containers/create"))
|
|
124
|
+
return Response.json({ Id: handle.externalId });
|
|
125
|
+
return Response.json(
|
|
126
|
+
{ message: init.method === "DELETE" ? "cleanup unavailable" : "volume mount failed" },
|
|
127
|
+
{ status: 500 },
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
await expect(
|
|
131
|
+
provider.provision(target, {
|
|
132
|
+
...request,
|
|
133
|
+
cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
|
|
134
|
+
}),
|
|
135
|
+
).rejects.toThrow("failed-start cleanup failed");
|
|
136
|
+
expect(calls.filter(({ url }) => url.pathname.endsWith("/containers/create"))).toHaveLength(1);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("cache-free retry errors propagate without a third attempt", async () => {
|
|
140
|
+
let creates = 0;
|
|
141
|
+
handler = (url, init) =>
|
|
142
|
+
url.pathname.endsWith("/volumes/create")
|
|
143
|
+
? Response.json(JSON.parse(String(init.body)))
|
|
144
|
+
: Response.json(
|
|
145
|
+
{ message: ++creates === 1 ? "volume mount failed" : "daemon unavailable" },
|
|
146
|
+
{ status: 500 },
|
|
147
|
+
);
|
|
148
|
+
await expect(
|
|
149
|
+
provider.provision(target, {
|
|
150
|
+
...request,
|
|
151
|
+
cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
|
|
152
|
+
}),
|
|
153
|
+
).rejects.toThrow("daemon unavailable");
|
|
154
|
+
expect(creates).toBe(2);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("cache release verifies ownership and never forces removal", async () => {
|
|
158
|
+
const cache = { key: "swarm-cache-pool-1", poolId: request.poolId, ownerId: request.ownerId };
|
|
159
|
+
handler = (_url, init) =>
|
|
160
|
+
init.method === "GET"
|
|
161
|
+
? Response.json({
|
|
162
|
+
Name: cache.key,
|
|
163
|
+
Labels: {
|
|
164
|
+
[RUNTIME_LABELS.owner]: cache.ownerId,
|
|
165
|
+
[RUNTIME_LABELS.poolId]: cache.poolId,
|
|
166
|
+
"swarm.cache": "true",
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
: new Response(null, { status: 204 });
|
|
170
|
+
await provider.releaseCache(target, cache);
|
|
171
|
+
expect(calls.map(({ init }) => init.method)).toEqual(["GET", "DELETE"]);
|
|
172
|
+
expect(calls[1]?.url.searchParams.has("force")).toBe(false);
|
|
173
|
+
calls = [];
|
|
174
|
+
handler = () => Response.json({ Name: cache.key, Labels: {} });
|
|
175
|
+
await expect(provider.releaseCache(target, cache)).rejects.toThrow("not owned");
|
|
176
|
+
expect(calls).toHaveLength(1);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("cache release tolerates missing volumes and defers in-use volumes", async () => {
|
|
180
|
+
const cache = { key: "swarm-cache-pool-1", poolId: request.poolId, ownerId: request.ownerId };
|
|
181
|
+
handler = () => new Response("missing", { status: 404 });
|
|
182
|
+
await provider.releaseCache(target, cache);
|
|
183
|
+
handler = (_url, init) =>
|
|
184
|
+
init.method === "GET"
|
|
185
|
+
? Response.json({
|
|
186
|
+
Name: cache.key,
|
|
187
|
+
Labels: {
|
|
188
|
+
[RUNTIME_LABELS.owner]: cache.ownerId,
|
|
189
|
+
[RUNTIME_LABELS.poolId]: cache.poolId,
|
|
190
|
+
"swarm.cache": "true",
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
: new Response("volume is in use", { status: 409 });
|
|
194
|
+
await expect(provider.releaseCache(target, cache)).rejects.toThrow("volume is in use");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("cache volumes are shared across runtimes and survive container termination", async () => {
|
|
198
|
+
const volumes = new Map<
|
|
199
|
+
string,
|
|
200
|
+
{ Name: string; Labels: Record<string, string>; content: string }
|
|
201
|
+
>();
|
|
202
|
+
const mounts: unknown[] = [];
|
|
203
|
+
handler = (url, init) => {
|
|
204
|
+
const body = init.body ? JSON.parse(String(init.body)) : undefined;
|
|
205
|
+
if (url.pathname.endsWith("/volumes/create")) {
|
|
206
|
+
if (!volumes.has(body.Name)) volumes.set(body.Name, { ...body, content: "" });
|
|
207
|
+
return Response.json(volumes.get(body.Name));
|
|
208
|
+
}
|
|
209
|
+
if (url.pathname.endsWith("/containers/create")) {
|
|
210
|
+
mounts.push(body.HostConfig.Mounts);
|
|
211
|
+
return Response.json({ Id: crypto.randomUUID() });
|
|
212
|
+
}
|
|
213
|
+
return new Response(null, { status: 204 });
|
|
214
|
+
};
|
|
215
|
+
const cache = { key: "swarm-cache-pool-1", mountPath: "/cache" };
|
|
216
|
+
const first = await provider.provision(target, { ...request, cache });
|
|
217
|
+
volumes.get(cache.key)!.content = "cached git objects";
|
|
218
|
+
await provider.terminate(target, first);
|
|
219
|
+
const second = await provider.provision(target, {
|
|
220
|
+
...request,
|
|
221
|
+
runtimeId: crypto.randomUUID(),
|
|
222
|
+
cache,
|
|
223
|
+
});
|
|
224
|
+
await provider.terminate(target, second);
|
|
225
|
+
expect(volumes.size).toBe(1);
|
|
226
|
+
expect(volumes.get(cache.key)?.content).toBe("cached git objects");
|
|
227
|
+
expect(volumes.get(cache.key)?.Labels).toEqual({
|
|
228
|
+
[RUNTIME_LABELS.owner]: request.ownerId,
|
|
229
|
+
[RUNTIME_LABELS.poolId]: request.poolId,
|
|
230
|
+
"swarm.cache": "true",
|
|
231
|
+
});
|
|
232
|
+
expect(mounts).toEqual(
|
|
233
|
+
Array(2).fill([{ Type: "volume", Source: cache.key, Target: "/cache" }]),
|
|
234
|
+
);
|
|
235
|
+
expect(
|
|
236
|
+
calls
|
|
237
|
+
.filter(({ init }) => init.method === "DELETE")
|
|
238
|
+
.every(
|
|
239
|
+
({ url }) =>
|
|
240
|
+
url.pathname.includes("/containers/") && url.searchParams.get("v") === "true",
|
|
241
|
+
),
|
|
242
|
+
).toBe(true);
|
|
243
|
+
expect(provider.traits).toMatchObject({ persistentCache: true, ephemeralDisk: true });
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("cache provisioning fails open with a scrubbed warning and no mount", async () => {
|
|
247
|
+
const secret = "cache-volume-short-secret";
|
|
248
|
+
handler = (url) =>
|
|
249
|
+
url.pathname.endsWith("/volumes/create")
|
|
250
|
+
? new Response(`volume unavailable ${secret}`, { status: 500 })
|
|
251
|
+
: url.pathname.endsWith("/containers/create")
|
|
252
|
+
? Response.json({ Id: handle.externalId })
|
|
253
|
+
: new Response(null, { status: 204 });
|
|
254
|
+
const result = await provider.provision(target, {
|
|
255
|
+
...request,
|
|
256
|
+
cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
|
|
257
|
+
env: { API_KEY: secret },
|
|
258
|
+
});
|
|
259
|
+
expect(result.meta?.cacheWarning).toContain("volume unavailable");
|
|
260
|
+
expect(result.meta?.cacheWarning).not.toContain(secret);
|
|
261
|
+
const body = JSON.parse(
|
|
262
|
+
String(calls.find(({ url }) => url.pathname.endsWith("/containers/create"))?.init.body),
|
|
263
|
+
);
|
|
264
|
+
expect(body.HostConfig.Mounts).toBeUndefined();
|
|
265
|
+
expect(calls.at(-1)?.url.pathname).toEndWith("/containers/container-1/start");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
for (const mismatched of ["owner", "poolId", "cache"] as const) {
|
|
269
|
+
test(`cache volumes with a mismatched ${mismatched} label are never mounted or relabeled`, async () => {
|
|
270
|
+
const labels = {
|
|
271
|
+
[RUNTIME_LABELS.owner]: request.ownerId,
|
|
272
|
+
[RUNTIME_LABELS.poolId]: request.poolId,
|
|
273
|
+
"swarm.cache": "true",
|
|
274
|
+
};
|
|
275
|
+
labels[mismatched === "cache" ? "swarm.cache" : RUNTIME_LABELS[mismatched]] = "foreign";
|
|
276
|
+
handler = (url) =>
|
|
277
|
+
url.pathname.endsWith("/volumes/create")
|
|
278
|
+
? Response.json({ Name: "swarm-cache-pool-1", Labels: labels })
|
|
279
|
+
: url.pathname.endsWith("/containers/create")
|
|
280
|
+
? Response.json({ Id: handle.externalId })
|
|
281
|
+
: new Response(null, { status: 204 });
|
|
282
|
+
const result = await provider.provision(target, {
|
|
283
|
+
...request,
|
|
284
|
+
cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
|
|
285
|
+
});
|
|
286
|
+
expect(result.meta?.cacheWarning).toContain("not owned");
|
|
287
|
+
expect(JSON.parse(String(calls[1]?.init.body)).HostConfig.Mounts).toBeUndefined();
|
|
288
|
+
expect(calls.some(({ init }) => init.method === "DELETE")).toBe(false);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
test("cache mounts survive the missing-image retry", async () => {
|
|
293
|
+
let creates = 0;
|
|
294
|
+
handler = (url, init) => {
|
|
295
|
+
if (url.pathname.endsWith("/volumes/create"))
|
|
296
|
+
return Response.json(JSON.parse(String(init.body)));
|
|
297
|
+
if (url.pathname.endsWith("/containers/create")) {
|
|
298
|
+
expect(JSON.parse(String(init.body)).HostConfig.Mounts).toEqual([
|
|
299
|
+
{ Type: "volume", Source: "swarm-cache-pool-1", Target: "/cache" },
|
|
300
|
+
]);
|
|
301
|
+
return ++creates === 1
|
|
302
|
+
? new Response("No such image", { status: 404 })
|
|
303
|
+
: Response.json({ Id: handle.externalId });
|
|
304
|
+
}
|
|
305
|
+
return url.pathname.endsWith("/images/create")
|
|
306
|
+
? new Response('{"status":"complete"}\n')
|
|
307
|
+
: new Response(null, { status: 204 });
|
|
308
|
+
};
|
|
309
|
+
await provider.provision(target, {
|
|
310
|
+
...request,
|
|
311
|
+
cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
|
|
312
|
+
});
|
|
313
|
+
expect(creates).toBe(2);
|
|
314
|
+
expect(calls.filter(({ url }) => url.pathname.endsWith("/volumes/create"))).toHaveLength(1);
|
|
315
|
+
});
|
|
316
|
+
|
|
64
317
|
for (const message of [
|
|
65
318
|
"client version 1.44 is too old. Minimum supported API version is 1.47",
|
|
66
319
|
"API version is unsupported",
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
markRuntimeTerminated,
|
|
18
18
|
upsertRuntimeInstance,
|
|
19
19
|
} from "../be/multi-runtime";
|
|
20
|
+
import { reconcileCacheReleases } from "../be/runtime-pools/cache-cleanup";
|
|
20
21
|
import {
|
|
21
22
|
clearRuntimeOwnerIdCache,
|
|
22
23
|
ensureRuntimeOwnerId,
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
getRuntimeController,
|
|
31
32
|
getRuntimePool,
|
|
32
33
|
updateRuntimeController,
|
|
34
|
+
updateRuntimePool,
|
|
33
35
|
} from "../be/runtime-pools/db";
|
|
34
36
|
import { remoteControllerSession } from "../be/runtime-pools/remote-controller";
|
|
35
37
|
import {
|
|
@@ -50,15 +52,29 @@ import type {
|
|
|
50
52
|
RuntimeTargetConfig,
|
|
51
53
|
} from "../runtime-providers";
|
|
52
54
|
import { ControllerOfflineError } from "../runtime-providers/controller";
|
|
53
|
-
import { RUNTIME_LABELS } from "../runtime-providers/types";
|
|
55
|
+
import { RUNTIME_LABELS, type RuntimeCacheReference } from "../runtime-providers/types";
|
|
54
56
|
import { listenOnFreePort } from "./test-net";
|
|
55
57
|
import { CHILD_PROCESS_TEST_BUDGET_MS, runChild } from "./test-proc";
|
|
56
58
|
|
|
57
59
|
class FakeProvider implements RuntimeProvider {
|
|
58
60
|
readonly kind = "docker" as const;
|
|
59
|
-
readonly traits = {
|
|
61
|
+
readonly traits: RuntimeProvider["traits"] = {
|
|
62
|
+
canProvision: true,
|
|
63
|
+
ephemeralDisk: true,
|
|
64
|
+
supportsPause: false,
|
|
65
|
+
};
|
|
60
66
|
requests: ProvisionRequest[] = [];
|
|
61
67
|
terminated: string[] = [];
|
|
68
|
+
releasedCaches: RuntimeCacheReference[] = [];
|
|
69
|
+
caches = new Set<string>();
|
|
70
|
+
cacheReleaseGate?: Promise<void>;
|
|
71
|
+
completedReleases = 0;
|
|
72
|
+
async releaseCache(_target: RuntimeTargetConfig, cache: RuntimeCacheReference) {
|
|
73
|
+
this.releasedCaches.push(cache);
|
|
74
|
+
await this.cacheReleaseGate;
|
|
75
|
+
this.caches.delete(cache.key);
|
|
76
|
+
this.completedReleases++;
|
|
77
|
+
}
|
|
62
78
|
owners: string[] = [];
|
|
63
79
|
runtimes = new Map<string, { handle: RuntimeHandle; ownerId: string; status: RuntimeStatus }>();
|
|
64
80
|
gate?: Promise<void>;
|
|
@@ -67,6 +83,7 @@ class FakeProvider implements RuntimeProvider {
|
|
|
67
83
|
}
|
|
68
84
|
async provision(_target: RuntimeTargetConfig, req: ProvisionRequest) {
|
|
69
85
|
this.requests.push(req);
|
|
86
|
+
if (req.cache) this.caches.add(req.cache.key);
|
|
70
87
|
await this.gate;
|
|
71
88
|
const handle: RuntimeHandle = {
|
|
72
89
|
kind: this.kind,
|
|
@@ -133,6 +150,7 @@ beforeEach(async () => {
|
|
|
133
150
|
process.env.RUNTIME_CIRCUIT_THRESHOLD = "3";
|
|
134
151
|
process.env.RUNTIME_PROVISION_MAX_ATTEMPTS = "1";
|
|
135
152
|
for (const table of [
|
|
153
|
+
"runtime_cache_releases",
|
|
136
154
|
"active_sessions",
|
|
137
155
|
"runtime_instances",
|
|
138
156
|
"session_tokens",
|
|
@@ -205,22 +223,68 @@ function request(ownerId: string): ProvisionRequest {
|
|
|
205
223
|
}
|
|
206
224
|
|
|
207
225
|
test("hello sets online and all controller commands make round trips through the CLI client", async () => {
|
|
226
|
+
fake.traits.persistentCache = true;
|
|
208
227
|
await connect();
|
|
209
228
|
const row = await getRuntimeController(controllerId);
|
|
210
229
|
expect(row?.lastSeenAt).toBeTruthy();
|
|
211
230
|
const commands = await getControllerCommands(controllerId);
|
|
212
231
|
expect(commands.workerApiUrl).toBe("http://remote-worker.invalid");
|
|
213
232
|
const req = request(await ensureRuntimeOwnerId());
|
|
233
|
+
expect(commands.traits.persistentCache).toBe(true);
|
|
234
|
+
req.cache = { key: `swarm-cache-${req.poolId}`, mountPath: "/cache" };
|
|
214
235
|
const handle = await commands.provision(req);
|
|
236
|
+
expect(fake.requests[0]?.cache).toEqual(req.cache);
|
|
215
237
|
expect(handle.controllerId).toBe(controllerId);
|
|
216
238
|
expect(await commands.status([handle])).toEqual({ [handle.externalId]: "running" });
|
|
217
239
|
expect(await commands.list(req.ownerId)).toEqual([handle]);
|
|
218
240
|
expect(await commands.list("another-swarm")).toEqual([]);
|
|
219
241
|
await commands.terminate(handle, "test complete");
|
|
242
|
+
const cache = { key: req.cache.key, poolId: req.poolId, ownerId: req.ownerId };
|
|
243
|
+
await commands.releaseCache!(cache);
|
|
244
|
+
expect(fake.releasedCaches).toEqual([cache]);
|
|
220
245
|
expect(await commands.status([handle])).toEqual({ [handle.externalId]: "exited" });
|
|
221
246
|
expect(JSON.stringify(logSpy.mock.calls)).not.toContain(token);
|
|
222
247
|
});
|
|
223
248
|
|
|
249
|
+
test("a timed-out remote cache release cannot delete a re-enabled cache", async () => {
|
|
250
|
+
fake.traits.persistentCache = true;
|
|
251
|
+
await connect();
|
|
252
|
+
const pool = await createRuntimePool({
|
|
253
|
+
name: "cache-timeout",
|
|
254
|
+
kind: "docker",
|
|
255
|
+
placements: [controllerId],
|
|
256
|
+
image: "worker:test",
|
|
257
|
+
cache: { mountPath: "/cache" },
|
|
258
|
+
});
|
|
259
|
+
const agent = await createPoolAgent(pool.id, { name: "cache-worker" });
|
|
260
|
+
const commands = await getControllerCommands(controllerId);
|
|
261
|
+
const owner = await ensureRuntimeOwnerId();
|
|
262
|
+
await provisionRuntime(pool, agent, commands, owner);
|
|
263
|
+
const oldKey = fake.requests[0]!.cache!.key;
|
|
264
|
+
const runtime = (await listRuntimeInstancesForPool(pool.id))[0]!;
|
|
265
|
+
await commands.terminate(runtime.externalHandle as unknown as RuntimeHandle, "test");
|
|
266
|
+
await markRuntimeTerminated(runtime.id, "stopped");
|
|
267
|
+
await updateRuntimePool(pool.id, { cache: null });
|
|
268
|
+
const finish = Promise.withResolvers<void>();
|
|
269
|
+
fake.cacheReleaseGate = finish.promise;
|
|
270
|
+
try {
|
|
271
|
+
await reconcileCacheReleases(async () => commands, new AbortController().signal);
|
|
272
|
+
expect(fake.completedReleases).toBe(0);
|
|
273
|
+
const enabled = await updateRuntimePool(pool.id, { cache: { mountPath: "/cache" } });
|
|
274
|
+
await provisionRuntime(enabled, agent, commands, owner);
|
|
275
|
+
const newKey = fake.requests.at(-1)!.cache!.key;
|
|
276
|
+
finish.resolve();
|
|
277
|
+
await until(async () => fake.completedReleases === 1);
|
|
278
|
+
expect(fake.caches.has(oldKey)).toBe(false);
|
|
279
|
+
expect(fake.caches.has(newKey)).toBe(true);
|
|
280
|
+
expect(newKey).not.toBe(oldKey);
|
|
281
|
+
await reconcileCacheReleases(async () => commands, new AbortController().signal);
|
|
282
|
+
} finally {
|
|
283
|
+
finish.resolve();
|
|
284
|
+
await until(async () => fake.completedReleases >= 1);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
224
288
|
test("remote E2B provisioning, renewal, pause, and resume use the controller session", async () => {
|
|
225
289
|
await updateRuntimeController(controllerId, {
|
|
226
290
|
kind: "e2b",
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
getRuntimePool,
|
|
32
32
|
} from "../be/runtime-pools/db";
|
|
33
33
|
import {
|
|
34
|
+
provisionRuntime,
|
|
34
35
|
reconcileControllerOrphans,
|
|
35
36
|
reconcileOnce,
|
|
36
37
|
runtimeSupervisorStatus,
|
|
@@ -53,13 +54,19 @@ import { registerVolatileSecret, scrubSecrets } from "../utils/secret-scrubber";
|
|
|
53
54
|
|
|
54
55
|
class FakeRuntimeProvider implements RuntimeProvider {
|
|
55
56
|
readonly kind = "docker" as const;
|
|
56
|
-
readonly traits = {
|
|
57
|
+
readonly traits: RuntimeProvider["traits"] = {
|
|
58
|
+
canProvision: true,
|
|
59
|
+
ephemeralDisk: true,
|
|
60
|
+
supportsPause: false,
|
|
61
|
+
persistentCache: true,
|
|
62
|
+
};
|
|
57
63
|
requests: ProvisionRequest[] = [];
|
|
58
64
|
targets: RuntimeTargetConfig[] = [];
|
|
59
65
|
terminated: string[] = [];
|
|
60
66
|
owners: string[] = [];
|
|
61
67
|
runtimes = new Map<string, { handle: RuntimeHandle; owner: string; status: RuntimeStatus }>();
|
|
62
68
|
failure: string | null = null;
|
|
69
|
+
cacheWarning: string | null = null;
|
|
63
70
|
gate: Promise<void> | null = null;
|
|
64
71
|
terminateFailures = new Map<string, string>();
|
|
65
72
|
statusFailures = new Set<string>();
|
|
@@ -74,7 +81,11 @@ class FakeRuntimeProvider implements RuntimeProvider {
|
|
|
74
81
|
if (this.failure) throw new Error(this.failure);
|
|
75
82
|
if (this.provisionFailures.has(req.agentId))
|
|
76
83
|
throw new Error(this.provisionFailures.get(req.agentId));
|
|
77
|
-
const handle: RuntimeHandle = {
|
|
84
|
+
const handle: RuntimeHandle = {
|
|
85
|
+
kind: "docker",
|
|
86
|
+
externalId: crypto.randomUUID(),
|
|
87
|
+
...(this.cacheWarning ? { meta: { cacheWarning: this.cacheWarning } } : {}),
|
|
88
|
+
};
|
|
78
89
|
this.runtimes.set(handle.externalId, { handle, owner: req.ownerId, status: "running" });
|
|
79
90
|
return handle;
|
|
80
91
|
}
|
|
@@ -184,6 +195,59 @@ async function activate(runtimeId: string, agentId: string, reportedSlots = 1) {
|
|
|
184
195
|
await getDbClient().run("UPDATE agents SET status = 'idle' WHERE id = ?", [agentId]);
|
|
185
196
|
}
|
|
186
197
|
|
|
198
|
+
test("cache identity is stable across provisions of one pool and scoped away from other pools", async () => {
|
|
199
|
+
const { pool, agent } = await wake({ cache: { mountPath: "/cache" } });
|
|
200
|
+
const controller = await getControllerCommands(controllerId);
|
|
201
|
+
await provisionRuntime(pool, agent, controller, await ensureRuntimeOwnerId());
|
|
202
|
+
const other = await setup({ cache: { mountPath: "/cache" } });
|
|
203
|
+
await provisionRuntime(other.pool, other.agent, controller, await ensureRuntimeOwnerId());
|
|
204
|
+
const [first, second, third] = fake.requests.map((req) => req.cache!);
|
|
205
|
+
expect(first).toEqual({
|
|
206
|
+
key: expect.stringContaining(`swarm-cache-${pool.id}-`),
|
|
207
|
+
mountPath: "/cache",
|
|
208
|
+
});
|
|
209
|
+
expect(second).toEqual(first);
|
|
210
|
+
expect(third.key).toStartWith(`swarm-cache-${other.pool.id}-`);
|
|
211
|
+
expect(third.key).not.toBe(first.key);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("an older controller boots without cache and surfaces a warning without failing the provision", async () => {
|
|
215
|
+
const { pool, agent } = await setup({ cache: { mountPath: "/cache" } });
|
|
216
|
+
delete fake.traits.persistentCache;
|
|
217
|
+
await provisionRuntime(
|
|
218
|
+
pool,
|
|
219
|
+
agent,
|
|
220
|
+
await getControllerCommands(controllerId),
|
|
221
|
+
await ensureRuntimeOwnerId(),
|
|
222
|
+
);
|
|
223
|
+
expect(fake.requests[0]?.cache).toBeUndefined();
|
|
224
|
+
expect(await getRuntimePool(pool.id)).toMatchObject({
|
|
225
|
+
consecutiveFailures: 0,
|
|
226
|
+
lastError: expect.stringContaining("Controller does not support persistent caches"),
|
|
227
|
+
});
|
|
228
|
+
expect((await getAgentById(agent.id))?.runtimeProvisionAttempts).toBe(0);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("cache failure warnings clear after recovery without hiding unrelated provision errors", async () => {
|
|
232
|
+
fake.cacheWarning = "cache unavailable";
|
|
233
|
+
const { pool, agent } = await wake({ cache: { mountPath: "/cache" } });
|
|
234
|
+
expect(await getRuntimePool(pool.id)).toMatchObject({
|
|
235
|
+
consecutiveFailures: 0,
|
|
236
|
+
lastError: "Persistent cache: cache unavailable",
|
|
237
|
+
});
|
|
238
|
+
fake.cacheWarning = null;
|
|
239
|
+
const commands = await getControllerCommands(controllerId);
|
|
240
|
+
await provisionRuntime(pool, agent, commands, await ensureRuntimeOwnerId());
|
|
241
|
+
expect((await getRuntimePool(pool.id))?.lastError).toBeNull();
|
|
242
|
+
await getDbClient().run(
|
|
243
|
+
"UPDATE runtime_pools SET last_error = 'Existing provision error' WHERE id = ?",
|
|
244
|
+
[pool.id],
|
|
245
|
+
);
|
|
246
|
+
fake.cacheWarning = "another cache failure";
|
|
247
|
+
await provisionRuntime(pool, agent, commands, await ensureRuntimeOwnerId());
|
|
248
|
+
expect((await getRuntimePool(pool.id))?.lastError).toBe("Existing provision error");
|
|
249
|
+
});
|
|
250
|
+
|
|
187
251
|
test("pending task provisions once while registration is pending, with scoped env and owner labels", async () => {
|
|
188
252
|
const { pool, agent, runtime } = await wake({
|
|
189
253
|
env: {
|
|
@@ -104,7 +104,9 @@ describe("scheduled task required params repair", () => {
|
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
test("keeps every requirement on genuinely tokenized fresh templates", async () => {
|
|
107
|
-
|
|
107
|
+
// Migrations 141 and 151 still name these, but no template ships for them.
|
|
108
|
+
const unshipped = ["dream-daily", "daily-workflow-health-audit"];
|
|
109
|
+
for (const name of canonicalNames.filter((name) => !unshipped.includes(name))) {
|
|
108
110
|
const template = new URL(`../../templates/schedules/${name}/content.md`, import.meta.url);
|
|
109
111
|
insert(name, await Bun.file(template).text());
|
|
110
112
|
}
|
|
@@ -667,6 +667,19 @@ describe("script connections", () => {
|
|
|
667
667
|
markMigrationApplied(database, "256_agent_slack_icon.sql");
|
|
668
668
|
// 259 alters session_summaries, which 253 (skipped above) creates.
|
|
669
669
|
markMigrationApplied(database, "259_session_summaries_open_tasks.sql");
|
|
670
|
+
// 260 alters runtime_pools and creates runtime_cache_releases. This
|
|
671
|
+
// migration-112-only fixture has neither runtime_pools nor runtime_controllers.
|
|
672
|
+
markMigrationApplied(database, "260_runtime_pool_cache.sql");
|
|
673
|
+
// 262 indexes agent_tasks columns that this fixture's id-only agent_tasks lacks.
|
|
674
|
+
markMigrationApplied(database, "262_session_visibility_indexes.sql");
|
|
675
|
+
// 263 indexes agent_tasks columns that this fixture's id-only agent_tasks lacks.
|
|
676
|
+
markMigrationApplied(database, "263_task_list_filter_index.sql");
|
|
677
|
+
// 264 indexes agent_tasks columns that this fixture's id-only agent_tasks lacks.
|
|
678
|
+
markMigrationApplied(database, "264_slack_thread_cover_index.sql");
|
|
679
|
+
// 266 alters script_runs, which this migration-112-only fixture does not create.
|
|
680
|
+
markMigrationApplied(database, "266_script_runs_source_task.sql");
|
|
681
|
+
// 269 alters pages, which this migration-112-only fixture does not create.
|
|
682
|
+
markMigrationApplied(database, "269_pages_design_system.sql");
|
|
670
683
|
|
|
671
684
|
const id = crypto.randomUUID();
|
|
672
685
|
const now = new Date().toISOString();
|