@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
|
@@ -28,6 +28,16 @@ const EXAMPLE_HUNK = {
|
|
|
28
28
|
],
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
/** The rendered diff rows: old-side and new-side gutter numbers plus the row HTML. */
|
|
32
|
+
function diffRows(html: string): { old: string; new: string; html: string }[] {
|
|
33
|
+
return [...html.matchAll(/<tr\b[^>]*>([\s\S]*?)<\/tr>/g)].map(([, row]) => {
|
|
34
|
+
const [oldCell = "", newCell = ""] = [
|
|
35
|
+
...row!.matchAll(/<td class="swarm-diff-gutter"[^>]*>([^<]*)<\/td>/g),
|
|
36
|
+
].map(([, cell]) => cell!);
|
|
37
|
+
return { old: oldCell, new: newCell, html: row! };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
type StubInstance = {
|
|
32
42
|
innerHTML: string;
|
|
33
43
|
textContent: string;
|
|
@@ -55,12 +65,15 @@ function makeRig(
|
|
|
55
65
|
): {
|
|
56
66
|
Ctor: new () => StubInstance;
|
|
57
67
|
CursorsCtor: new () => StubInstance;
|
|
68
|
+
JumpsCtor: new () => StubInstance;
|
|
58
69
|
SdkCtor?: new () => { room: (name?: string, options?: unknown) => Promise<unknown> };
|
|
59
70
|
body: StubInstance;
|
|
60
71
|
setText: (el: StubInstance, attrs: Record<string, string>, text: string) => void;
|
|
61
72
|
fireDocumentEvent: (name: string, event: unknown) => void;
|
|
62
73
|
fireNextFrame: () => void;
|
|
63
74
|
flushMicrotasks: () => Promise<void>;
|
|
75
|
+
/** Sets what `document.querySelectorAll` returns (the jump list's anchors). */
|
|
76
|
+
setAnchors: (anchors: unknown[]) => void;
|
|
64
77
|
} {
|
|
65
78
|
const registry = new Map<string, new () => StubInstance>();
|
|
66
79
|
const documentListeners = new Map<string, ((event: unknown) => void)[]>();
|
|
@@ -143,12 +156,13 @@ function makeRig(
|
|
|
143
156
|
|
|
144
157
|
// Provide `document` stubs the jump-list path uses.
|
|
145
158
|
const body = new StubHTMLElement();
|
|
159
|
+
let anchors: unknown[] = [];
|
|
146
160
|
const doc = {
|
|
147
161
|
readyState,
|
|
148
162
|
documentElement: { scrollWidth: 1000, scrollHeight: 500, clientWidth: 1000, clientHeight: 500 },
|
|
149
163
|
body,
|
|
150
164
|
createElement: () => new StubHTMLElement(),
|
|
151
|
-
querySelectorAll: () =>
|
|
165
|
+
querySelectorAll: () => anchors,
|
|
152
166
|
addEventListener: (name: string, handler: (event: unknown) => void) => {
|
|
153
167
|
const handlers = documentListeners.get(name) ?? [];
|
|
154
168
|
handlers.push(handler);
|
|
@@ -176,7 +190,7 @@ function makeRig(
|
|
|
176
190
|
"CustomEvent",
|
|
177
191
|
"queueMicrotask",
|
|
178
192
|
"console",
|
|
179
|
-
`${SWARM_UI_JS}\nreturn { diff: window.customElements.get('swarm-diff'), cursors: window.customElements.get('swarm-cursors'), sdk: window.SwarmSDK };`,
|
|
193
|
+
`${SWARM_UI_JS}\nreturn { diff: window.customElements.get('swarm-diff'), cursors: window.customElements.get('swarm-cursors'), jumps: window.customElements.get('swarm-diff-jumps'), sdk: window.SwarmSDK };`,
|
|
180
194
|
);
|
|
181
195
|
const constructors = factory(
|
|
182
196
|
win,
|
|
@@ -189,16 +203,20 @@ function makeRig(
|
|
|
189
203
|
) as {
|
|
190
204
|
diff?: new () => StubInstance;
|
|
191
205
|
cursors?: new () => StubInstance;
|
|
206
|
+
jumps?: new () => StubInstance;
|
|
192
207
|
sdk?: new () => { room: (name?: string, options?: unknown) => Promise<unknown> };
|
|
193
208
|
};
|
|
194
209
|
const Ctor = constructors.diff;
|
|
195
210
|
if (!Ctor) throw new Error("custom element did not register");
|
|
196
211
|
const CursorsCtor = constructors.cursors;
|
|
197
212
|
if (!CursorsCtor) throw new Error("swarm-cursors custom element did not register");
|
|
213
|
+
const JumpsCtor = constructors.jumps;
|
|
214
|
+
if (!JumpsCtor) throw new Error("swarm-diff-jumps custom element did not register");
|
|
198
215
|
|
|
199
216
|
return {
|
|
200
217
|
Ctor,
|
|
201
218
|
CursorsCtor,
|
|
219
|
+
JumpsCtor,
|
|
202
220
|
SdkCtor: constructors.sdk,
|
|
203
221
|
body,
|
|
204
222
|
setText(el, attrs, text) {
|
|
@@ -220,6 +238,9 @@ function makeRig(
|
|
|
220
238
|
// Drain the chained async room-open and render callbacks.
|
|
221
239
|
for (let i = 0; i < 5; i++) await Promise.resolve();
|
|
222
240
|
},
|
|
241
|
+
setAnchors(list) {
|
|
242
|
+
anchors = list;
|
|
243
|
+
},
|
|
223
244
|
};
|
|
224
245
|
}
|
|
225
246
|
|
|
@@ -517,6 +538,58 @@ describe("<swarm-diff> render", () => {
|
|
|
517
538
|
expect(html).toContain("Avoid raw console.log");
|
|
518
539
|
});
|
|
519
540
|
|
|
541
|
+
test("shows an annotation once, on its new-side line, not on a context row with that old-side number", async () => {
|
|
542
|
+
// Three added lines shift the new side: the context row `});` is old 63,
|
|
543
|
+
// new 66, and the annotation names new line 63.
|
|
544
|
+
const shifted = {
|
|
545
|
+
hunks: [
|
|
546
|
+
{
|
|
547
|
+
old_start: 60,
|
|
548
|
+
old_lines: 4,
|
|
549
|
+
new_start: 60,
|
|
550
|
+
new_lines: 7,
|
|
551
|
+
lines: [
|
|
552
|
+
{ type: "context", text: "start();" },
|
|
553
|
+
{ type: "add", text: "one();" },
|
|
554
|
+
{ type: "add", text: "two();" },
|
|
555
|
+
{ type: "add", text: "three();" },
|
|
556
|
+
{ type: "context", text: "four();" },
|
|
557
|
+
{ type: "context", text: "five();" },
|
|
558
|
+
{ type: "context", text: "});" },
|
|
559
|
+
],
|
|
560
|
+
annotations: [{ line: 63, severity: "warn", text: "Note on new line 63" }],
|
|
561
|
+
},
|
|
562
|
+
],
|
|
563
|
+
};
|
|
564
|
+
const html = await renderViaStub(JSON.stringify(shifted), { file: "src/a.ts" });
|
|
565
|
+
const rows = diffRows(html);
|
|
566
|
+
expect(rows.find((row) => row.html.includes("});"))).toMatchObject({ old: "63", new: "66" });
|
|
567
|
+
const annotated = rows.filter((row) => row.html.includes("Note on new line 63"));
|
|
568
|
+
expect(annotated.map((row) => [row.old, row.new])).toEqual([["", "63"]]);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test("matches a deleted row's annotation on its old-side number", async () => {
|
|
572
|
+
const deleted = {
|
|
573
|
+
hunks: [
|
|
574
|
+
{
|
|
575
|
+
old_start: 10,
|
|
576
|
+
old_lines: 3,
|
|
577
|
+
new_start: 20,
|
|
578
|
+
new_lines: 2,
|
|
579
|
+
lines: [
|
|
580
|
+
{ type: "context", text: "keep();" },
|
|
581
|
+
{ type: "del", text: "drop();" },
|
|
582
|
+
{ type: "context", text: "keepToo();" },
|
|
583
|
+
],
|
|
584
|
+
annotations: [{ line: 11, severity: "error", text: "Why drop this?" }],
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
};
|
|
588
|
+
const html = await renderViaStub(JSON.stringify(deleted), { file: "src/b.ts" });
|
|
589
|
+
const annotated = diffRows(html).filter((row) => row.html.includes("Why drop this?"));
|
|
590
|
+
expect(annotated.map((row) => [row.old, row.new])).toEqual([["11", ""]]);
|
|
591
|
+
});
|
|
592
|
+
|
|
520
593
|
test("handles empty/missing JSON body gracefully (no rows, no throw)", async () => {
|
|
521
594
|
const html = await renderViaStub("", { file: "empty.ts" });
|
|
522
595
|
// Should still render an outer container with the file name.
|
|
@@ -545,6 +618,52 @@ describe("<swarm-diff> render", () => {
|
|
|
545
618
|
});
|
|
546
619
|
});
|
|
547
620
|
|
|
621
|
+
describe("<swarm-diff-jumps> labels", () => {
|
|
622
|
+
const hunk = (oldStart: number, newStart: number) => ({
|
|
623
|
+
old_start: oldStart,
|
|
624
|
+
old_lines: 1,
|
|
625
|
+
new_start: newStart,
|
|
626
|
+
new_lines: 1,
|
|
627
|
+
lines: [{ type: "context", text: "x();" }],
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test("a file with more than one hunk shows each hunk's new-side start line", async () => {
|
|
631
|
+
const rig = makeRig();
|
|
632
|
+
const render = async (file: string, hunks: unknown[]) => {
|
|
633
|
+
const el = new rig.Ctor();
|
|
634
|
+
rig.setText(el, { file }, JSON.stringify({ hunks }));
|
|
635
|
+
el.connectedCallback?.();
|
|
636
|
+
await rig.flushMicrotasks();
|
|
637
|
+
return el;
|
|
638
|
+
};
|
|
639
|
+
// The anchors the jump list finds in the document, built from the rendered HTML.
|
|
640
|
+
const anchorsOf = (diff: StubInstance) =>
|
|
641
|
+
[
|
|
642
|
+
...diff.innerHTML.matchAll(
|
|
643
|
+
/<a id="([^"]*)" class="swarm-diff-anchor" data-hunk="([^"]*)" data-new-start="([^"]*)"><\/a>/g,
|
|
644
|
+
),
|
|
645
|
+
].map(([, id, slug, newStart]) => {
|
|
646
|
+
const attrs: Record<string, string> = { "data-hunk": slug!, "data-new-start": newStart! };
|
|
647
|
+
return { id, getAttribute: (name: string) => attrs[name] ?? null, closest: () => diff };
|
|
648
|
+
});
|
|
649
|
+
const x = await render("src/x.ts", [hunk(10, 58), hunk(100, 120)]);
|
|
650
|
+
const y = await render("src/y.ts", [hunk(5, 5)]);
|
|
651
|
+
rig.setAnchors([...anchorsOf(x), ...anchorsOf(y)]);
|
|
652
|
+
|
|
653
|
+
const jumps = new rig.JumpsCtor();
|
|
654
|
+
jumps.connectedCallback?.();
|
|
655
|
+
await rig.flushMicrotasks();
|
|
656
|
+
const links = [
|
|
657
|
+
...jumps.innerHTML.matchAll(/<a href="#([^"]*)" target="_self"[^>]*>([^<]*)<\/a>/g),
|
|
658
|
+
].map(([, href, label]) => [href, label]);
|
|
659
|
+
expect(links).toEqual([
|
|
660
|
+
["swarm-diff-src-x-ts-10", "src/x.ts:58"],
|
|
661
|
+
["swarm-diff-src-x-ts-100", "src/x.ts:120"],
|
|
662
|
+
["swarm-diff-src-y-ts-5", "src/y.ts"],
|
|
663
|
+
]);
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
|
|
548
667
|
describe("<swarm-diff> parse-order regression (Bug #479-1)", () => {
|
|
549
668
|
// Real browsers fire connectedCallback when the parser sees the opening
|
|
550
669
|
// tag, BEFORE the JSON text children are parsed. The element MUST defer
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { validateConfigValue } from "../be/swarm-config-guard";
|
|
3
|
+
|
|
4
|
+
describe("task auto-title config validation", () => {
|
|
5
|
+
test("TASK_AUTO_TITLE_ENABLED accepts boolean literals only", () => {
|
|
6
|
+
expect(validateConfigValue("TASK_AUTO_TITLE_ENABLED", "true")).toBeNull();
|
|
7
|
+
expect(validateConfigValue("TASK_AUTO_TITLE_ENABLED", "false")).toBeNull();
|
|
8
|
+
expect(validateConfigValue("TASK_AUTO_TITLE_ENABLED", "maybe")).toMatch(
|
|
9
|
+
/TASK_AUTO_TITLE_ENABLED/,
|
|
10
|
+
);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("TASK_AUTO_TITLE_MODEL accepts provider/model-id for a callable provider, a claude alias, or empty", () => {
|
|
14
|
+
for (const value of [
|
|
15
|
+
"openrouter/deepseek/deepseek-v4.1-flash",
|
|
16
|
+
"anthropic/claude-haiku-5-5",
|
|
17
|
+
"openai/gpt-5-nano",
|
|
18
|
+
"openai-codex/gpt-6-luna",
|
|
19
|
+
"haiku",
|
|
20
|
+
"",
|
|
21
|
+
" anthropic/claude-haiku-5-5 ",
|
|
22
|
+
]) {
|
|
23
|
+
expect(validateConfigValue("TASK_AUTO_TITLE_MODEL", value)).toBeNull();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("TASK_AUTO_TITLE_MODEL rejects unsupported providers and malformed strings", () => {
|
|
28
|
+
expect(validateConfigValue("TASK_AUTO_TITLE_MODEL", "google/gemini-2.5-flash-lite")).toMatch(
|
|
29
|
+
/provider must be/,
|
|
30
|
+
);
|
|
31
|
+
expect(validateConfigValue("TASK_AUTO_TITLE_MODEL", "openai/")).toMatch(/provider\/model-id/);
|
|
32
|
+
expect(validateConfigValue("TASK_AUTO_TITLE_MODEL", "openai/gpt 5")).toMatch(
|
|
33
|
+
/provider\/model-id/,
|
|
34
|
+
);
|
|
35
|
+
expect(validateConfigValue("TASK_AUTO_TITLE_MODEL", 42)).toMatch(/must be a string/);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
closeDb,
|
|
5
|
+
createAgent,
|
|
6
|
+
createTaskExtended,
|
|
7
|
+
getTaskById,
|
|
8
|
+
initDb,
|
|
9
|
+
updateTaskTitle,
|
|
10
|
+
} from "../be/db";
|
|
11
|
+
import { setTaskTitleIfUnset } from "../be/db/tasks/write";
|
|
12
|
+
import {
|
|
13
|
+
isTaskAutoTitleEnabled,
|
|
14
|
+
startTaskAutoTitleListener,
|
|
15
|
+
type TaskAutoTitleListener,
|
|
16
|
+
} from "../be/task-auto-title-listener";
|
|
17
|
+
import type { generateTaskTitle } from "../utils/internal-ai/task-title";
|
|
18
|
+
import { workflowEventBus } from "../workflows/event-bus";
|
|
19
|
+
|
|
20
|
+
const TEST_DB_PATH = "./test-task-auto-title-listener.sqlite";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `createTaskExtended` emits `task.created` from an after-commit hook that
|
|
24
|
+
* imports the event bus asynchronously, so the event lands after the call
|
|
25
|
+
* returns. Create the task through this helper to await that emission.
|
|
26
|
+
*/
|
|
27
|
+
async function createTaskAndAwaitEvent(task: string): Promise<string> {
|
|
28
|
+
const created = new Promise<void>((resolve) => {
|
|
29
|
+
const handler = () => {
|
|
30
|
+
workflowEventBus.off("task.created", handler);
|
|
31
|
+
resolve();
|
|
32
|
+
};
|
|
33
|
+
workflowEventBus.on("task.created", handler);
|
|
34
|
+
});
|
|
35
|
+
const row = await createTaskExtended(task, { agentId, source: "api" });
|
|
36
|
+
await created;
|
|
37
|
+
return row.id;
|
|
38
|
+
}
|
|
39
|
+
const LONG_TASK =
|
|
40
|
+
"Investigate why the nightly Playwright suite times out on the sessions page after the sidebar redesign and fix the flake.";
|
|
41
|
+
|
|
42
|
+
let agentId: string;
|
|
43
|
+
let listener: TaskAutoTitleListener | null = null;
|
|
44
|
+
|
|
45
|
+
type Generate = typeof generateTaskTitle;
|
|
46
|
+
|
|
47
|
+
/** Generator stub that returns `title` and records every prompt it saw. */
|
|
48
|
+
function generator(title: string | null, seen: string[] = []) {
|
|
49
|
+
const generate: Generate = async (opts) => {
|
|
50
|
+
seen.push(opts.task);
|
|
51
|
+
return title
|
|
52
|
+
? { ok: true, title, model: "test/model", kind: "openrouter" }
|
|
53
|
+
: { ok: false, reason: "all-models-failed" };
|
|
54
|
+
};
|
|
55
|
+
return { generate, seen };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
beforeAll(async () => {
|
|
59
|
+
initDb(TEST_DB_PATH);
|
|
60
|
+
agentId = (await createAgent({ name: "auto-title-worker", isLead: false, status: "idle" })).id;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
listener?.stop();
|
|
65
|
+
listener = null;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterAll(async () => {
|
|
69
|
+
closeDb();
|
|
70
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
71
|
+
try {
|
|
72
|
+
await unlink(`${TEST_DB_PATH}${suffix}`);
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("isTaskAutoTitleEnabled", () => {
|
|
78
|
+
test("defaults on and honours false-ish values", () => {
|
|
79
|
+
expect(isTaskAutoTitleEnabled({})).toBe(true);
|
|
80
|
+
expect(isTaskAutoTitleEnabled({ TASK_AUTO_TITLE_ENABLED: "" })).toBe(true);
|
|
81
|
+
expect(isTaskAutoTitleEnabled({ TASK_AUTO_TITLE_ENABLED: "false" })).toBe(false);
|
|
82
|
+
expect(isTaskAutoTitleEnabled({ TASK_AUTO_TITLE_ENABLED: "0" })).toBe(false);
|
|
83
|
+
expect(isTaskAutoTitleEnabled({ TASK_AUTO_TITLE_ENABLED: "true" })).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("setTaskTitleIfUnset", () => {
|
|
88
|
+
test("writes once and never replaces an existing title", async () => {
|
|
89
|
+
const task = await createTaskExtended(LONG_TASK, { agentId, source: "api" });
|
|
90
|
+
expect(task.title).toBeUndefined();
|
|
91
|
+
|
|
92
|
+
const first = await setTaskTitleIfUnset(task.id, " Generated title ");
|
|
93
|
+
expect(first?.title).toBe("Generated title");
|
|
94
|
+
|
|
95
|
+
const second = await setTaskTitleIfUnset(task.id, "Another title");
|
|
96
|
+
expect(second).toBeNull();
|
|
97
|
+
expect((await getTaskById(task.id))?.title).toBe("Generated title");
|
|
98
|
+
|
|
99
|
+
expect(await setTaskTitleIfUnset(task.id, " ")).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("startTaskAutoTitleListener", () => {
|
|
104
|
+
test("titles a new task after commit", async () => {
|
|
105
|
+
const { generate, seen } = generator("Fix sessions page Playwright flake");
|
|
106
|
+
listener = startTaskAutoTitleListener({ env: {}, generate });
|
|
107
|
+
|
|
108
|
+
const taskId = await createTaskAndAwaitEvent(LONG_TASK);
|
|
109
|
+
await listener.idle();
|
|
110
|
+
|
|
111
|
+
expect(seen).toEqual([LONG_TASK]);
|
|
112
|
+
expect((await getTaskById(taskId))?.title).toBe("Fix sessions page Playwright flake");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("keeps a title a person set while the model was running", async () => {
|
|
116
|
+
let release: () => void = () => {};
|
|
117
|
+
const gate = new Promise<void>((resolve) => {
|
|
118
|
+
release = resolve;
|
|
119
|
+
});
|
|
120
|
+
const generate: Generate = async () => {
|
|
121
|
+
await gate;
|
|
122
|
+
return { ok: true, title: "Model title", model: "test/model", kind: "openrouter" };
|
|
123
|
+
};
|
|
124
|
+
listener = startTaskAutoTitleListener({ env: {}, generate });
|
|
125
|
+
|
|
126
|
+
const taskId = await createTaskAndAwaitEvent(LONG_TASK);
|
|
127
|
+
await updateTaskTitle(taskId, "Human title");
|
|
128
|
+
release();
|
|
129
|
+
await listener.idle();
|
|
130
|
+
|
|
131
|
+
expect((await getTaskById(taskId))?.title).toBe("Human title");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("leaves the task untouched when generation fails", async () => {
|
|
135
|
+
const { generate, seen } = generator(null);
|
|
136
|
+
listener = startTaskAutoTitleListener({ env: {}, generate });
|
|
137
|
+
|
|
138
|
+
const taskId = await createTaskAndAwaitEvent(LONG_TASK);
|
|
139
|
+
await listener.idle();
|
|
140
|
+
|
|
141
|
+
expect(seen.length).toBe(1);
|
|
142
|
+
expect((await getTaskById(taskId))?.title).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("does nothing when TASK_AUTO_TITLE_ENABLED is false", async () => {
|
|
146
|
+
const { generate, seen } = generator("Should not land");
|
|
147
|
+
listener = startTaskAutoTitleListener({ env: { TASK_AUTO_TITLE_ENABLED: "false" }, generate });
|
|
148
|
+
|
|
149
|
+
const taskId = await createTaskAndAwaitEvent(LONG_TASK);
|
|
150
|
+
await listener.idle();
|
|
151
|
+
|
|
152
|
+
expect(seen).toEqual([]);
|
|
153
|
+
expect((await getTaskById(taskId))?.title).toBeUndefined();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("stop() detaches from the event bus", async () => {
|
|
157
|
+
const { generate, seen } = generator("Late title");
|
|
158
|
+
const handle = startTaskAutoTitleListener({ env: {}, generate });
|
|
159
|
+
handle.stop();
|
|
160
|
+
|
|
161
|
+
await createTaskAndAwaitEvent(LONG_TASK);
|
|
162
|
+
await handle.idle();
|
|
163
|
+
expect(seen).toEqual([]);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { closeDb, getAllTasks, getDb, getDbClient, initDb } from "../be/db";
|
|
4
|
+
import { readableSessionCondition } from "../be/db/tasks/read";
|
|
5
|
+
|
|
6
|
+
const TEST_DB_PATH = "./test-tasks-readable-row.sqlite";
|
|
7
|
+
const AT = "2026-10-01T00:00:00.000Z";
|
|
8
|
+
|
|
9
|
+
async function removeDbFiles(): Promise<void> {
|
|
10
|
+
for (const suffix of ["", "-wal", "-shm"])
|
|
11
|
+
await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => undefined);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function task(
|
|
15
|
+
id: string,
|
|
16
|
+
opts: { parent?: string; owner?: string; scheduleId?: string } = {},
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
await getDbClient().run(
|
|
19
|
+
`INSERT INTO agent_tasks (id, task, status, source, parentTaskId, requestedByUserId, scheduleId,
|
|
20
|
+
createdAt, lastUpdatedAt)
|
|
21
|
+
VALUES (?, ?, 'completed', 'api', ?, ?, ?, ?, ?)`,
|
|
22
|
+
[id, `task ${id}`, opts.parent ?? null, opts.owner ?? null, opts.scheduleId ?? null, AT, AT],
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("readableSessionRowCondition", () => {
|
|
27
|
+
beforeAll(async () => {
|
|
28
|
+
await removeDbFiles();
|
|
29
|
+
initDb(TEST_DB_PATH);
|
|
30
|
+
for (const id of ["u1", "u2", "u3"]) {
|
|
31
|
+
await getDbClient().run(
|
|
32
|
+
"INSERT INTO users (id, name, role, status, createdAt, lastUpdatedAt) VALUES (?, ?, 'member', 'active', ?, ?)",
|
|
33
|
+
[id, id, AT, AT],
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
// u1's session, three deep.
|
|
37
|
+
await task("a", { owner: "u1" });
|
|
38
|
+
await task("a1", { parent: "a", owner: "u1" });
|
|
39
|
+
await task("a2", { parent: "a1" });
|
|
40
|
+
// u2's session, with a child that names u1.
|
|
41
|
+
await task("b", { owner: "u2" });
|
|
42
|
+
await task("b1", { parent: "b", owner: "u1" });
|
|
43
|
+
// u2's schedule run: everyone reads it.
|
|
44
|
+
await task("c", { owner: "u2", scheduleId: "schedule-1" });
|
|
45
|
+
await task("c1", { parent: "c" });
|
|
46
|
+
// u2's session shared with u1, and one shared with everyone.
|
|
47
|
+
await task("d", { owner: "u2" });
|
|
48
|
+
await task("d1", { parent: "d" });
|
|
49
|
+
await task("e", { owner: "u2" });
|
|
50
|
+
await getDbClient().run(
|
|
51
|
+
`INSERT INTO session_shares (id, rootTaskId, userId, access, createdAt)
|
|
52
|
+
VALUES ('share-d', 'd', 'u1', 'read', ?), ('share-e', 'e', NULL, 'read', ?)`,
|
|
53
|
+
[AT, AT],
|
|
54
|
+
);
|
|
55
|
+
// A revoked share hides again.
|
|
56
|
+
await task("f", { owner: "u2" });
|
|
57
|
+
await getDbClient().run(
|
|
58
|
+
`INSERT INTO session_shares (id, rootTaskId, userId, access, createdAt, revokedAt)
|
|
59
|
+
VALUES ('share-f', 'f', 'u1', 'read', ?, ?)`,
|
|
60
|
+
[AT, AT],
|
|
61
|
+
);
|
|
62
|
+
// A session nobody owns.
|
|
63
|
+
await task("g");
|
|
64
|
+
await task("g1", { parent: "g", owner: "u2" });
|
|
65
|
+
// Orphan subtrees: the top's parent is gone, so the top is the root.
|
|
66
|
+
await task("h", { owner: "u2" });
|
|
67
|
+
await task("h1", { parent: "h" });
|
|
68
|
+
await task("h2", { parent: "h1", owner: "u1" });
|
|
69
|
+
await task("i", { owner: "u1" });
|
|
70
|
+
await task("i1", { parent: "i", owner: "u2" });
|
|
71
|
+
await task("i2", { parent: "i1" });
|
|
72
|
+
// Chains deeper than the walk's hop limit fall back to the set form.
|
|
73
|
+
for (const [root, owner] of [
|
|
74
|
+
["deep-u1", "u1"],
|
|
75
|
+
["deep-u2", "u2"],
|
|
76
|
+
] as const) {
|
|
77
|
+
await task(root, { owner });
|
|
78
|
+
let parent: string = root;
|
|
79
|
+
for (let i = 1; i <= 70; i++) {
|
|
80
|
+
await task(`${root}-${i}`, { parent });
|
|
81
|
+
parent = `${root}-${i}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
getDb().run("PRAGMA foreign_keys = OFF");
|
|
85
|
+
getDb().run("DELETE FROM agent_tasks WHERE id IN ('h', 'i')");
|
|
86
|
+
getDb().run("PRAGMA foreign_keys = ON");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
afterAll(async () => {
|
|
90
|
+
closeDb();
|
|
91
|
+
await removeDbFiles();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
for (const userId of ["u1", "u2", "u3", null]) {
|
|
95
|
+
test(`the page shows the rows the set form shows, for ${userId ?? "a guest"}`, async () => {
|
|
96
|
+
const set = readableSessionCondition(userId);
|
|
97
|
+
const expected = (
|
|
98
|
+
await getDbClient().query<{ id: string }>(
|
|
99
|
+
`SELECT id FROM agent_tasks WHERE ${set.sql} ORDER BY id`,
|
|
100
|
+
set.params,
|
|
101
|
+
)
|
|
102
|
+
).map((row) => row.id);
|
|
103
|
+
const page = await getAllTasks({ readableBy: { userId }, limit: 1000 });
|
|
104
|
+
expect(page.map((t) => t.id).sort()).toEqual(expected);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
test("u1 sees exactly the expected sessions", async () => {
|
|
109
|
+
const page = await getAllTasks({ readableBy: { userId: "u1" }, limit: 1000 });
|
|
110
|
+
const deep = Array.from({ length: 70 }, (_, i) => `deep-u1-${i + 1}`);
|
|
111
|
+
expect(page.map((t) => t.id).sort()).toEqual(
|
|
112
|
+
[
|
|
113
|
+
"a",
|
|
114
|
+
"a1",
|
|
115
|
+
"a2",
|
|
116
|
+
"c",
|
|
117
|
+
"c1",
|
|
118
|
+
"d",
|
|
119
|
+
"d1",
|
|
120
|
+
"e",
|
|
121
|
+
"g",
|
|
122
|
+
"g1",
|
|
123
|
+
"h1",
|
|
124
|
+
"h2",
|
|
125
|
+
"deep-u1",
|
|
126
|
+
...deep,
|
|
127
|
+
].sort(),
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("the page walks up from each row it reads", async () => {
|
|
132
|
+
const query = spyOn(getDbClient(), "query");
|
|
133
|
+
let sql = "";
|
|
134
|
+
let params: unknown[] = [];
|
|
135
|
+
try {
|
|
136
|
+
await getAllTasks({ status: "completed", readableBy: { userId: "u1" } });
|
|
137
|
+
[sql, params = []] = query.mock.calls.find(([s]) => s.includes("WITH page AS"))! as [
|
|
138
|
+
string,
|
|
139
|
+
unknown[]?,
|
|
140
|
+
];
|
|
141
|
+
} finally {
|
|
142
|
+
query.mockRestore();
|
|
143
|
+
}
|
|
144
|
+
const plan = getDb()
|
|
145
|
+
.query<{ detail: string }, any[]>(`EXPLAIN QUERY PLAN ${sql}`)
|
|
146
|
+
.all(...(params as any[]))
|
|
147
|
+
.map((row) => row.detail)
|
|
148
|
+
.join("\n");
|
|
149
|
+
// The walk is a correlated lookup per row. The set form appears only as
|
|
150
|
+
// the fallback branch, which SQLite builds when a walk finds no root.
|
|
151
|
+
expect(plan).toContain("CORRELATED SCALAR SUBQUERY");
|
|
152
|
+
expect(plan).toContain("SEARCH u USING INDEX sqlite_autoindex_agent_tasks_1 (id=?)");
|
|
153
|
+
expect(plan).toContain("SEARCH p USING INDEX sqlite_autoindex_agent_tasks_1 (id=?)");
|
|
154
|
+
});
|
|
155
|
+
});
|