@desplega.ai/agent-swarm 2.7.4 → 2.7.6
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/README.md +1 -1
- package/dist/acp-adapter-bp91d37f.js +15 -0
- package/dist/{actions-gfrr9vb7.js → actions-yj0eeaqh.js} +19 -18
- package/dist/{amp-adapter-0s58gm46.js → amp-adapter-4pg9h34q.js} +3 -3
- package/dist/{app-stvq692g.js → app-909e97cr.js} +3 -3
- package/dist/{assistant-5s37fr9n.js → assistant-qdyb5fyy.js} +32 -23
- package/dist/{boot-reembed-yzy1841n.js → boot-reembed-fk0s9z18.js} +1 -1
- package/dist/{boot-reembed-cv82gz7y.js → boot-reembed-gs1rzrnh.js} +1 -1
- package/dist/{boot-scrub-sweep-8k4q95ap.js → boot-scrub-sweep-4qfd4kfx.js} +4 -4
- package/dist/{boot-seeds-ftt9htah.js → boot-seeds-yr00k13d.js} +4 -4
- package/dist/{boxd-515v1t8z.js → boxd-vdmfssd9.js} +5 -5
- package/dist/{claude-adapter-t00wrzeh.js → claude-adapter-qta1f1xn.js} +7 -7
- package/dist/{claude-managed-adapter-g6vmjccb.js → claude-managed-adapter-dvs2xw5n.js} +2 -2
- package/dist/{claude-managed-setup-afpa82f7.js → claude-managed-setup-sc6j4dm0.js} +3 -3
- package/dist/{claude-sdk-session-vj0c2azx.js → claude-sdk-session-zgrz6yvc.js} +9175 -9076
- package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
- package/dist/{cli-edmm9d9y.js → cli-0at8w34w.js} +7 -7
- package/dist/{cli-a81qevyg.js → cli-0qatpy8q.js} +20 -36
- package/dist/{cli-bcz074zx.js → cli-0yv5z2pg.js} +1 -1
- package/dist/{cli-a4btqqft.js → cli-1b7btd38.js} +1 -1
- package/dist/{cli-a2jw6ee5.js → cli-1p89xny0.js} +1 -1
- package/dist/{cli-v726afdm.js → cli-2pnswgh8.js} +1 -1
- package/dist/{cli-jkt52mgt.js → cli-386tyxxg.js} +2 -2
- package/dist/{cli-az0bpf8p.js → cli-3ypk9vk1.js} +3 -3
- package/dist/{cli-wk9md5px.js → cli-436532ck.js} +4 -4
- package/dist/{cli-7n5sbw0z.js → cli-4necwtsv.js} +5 -5
- package/dist/cli-54t00gt8.js +151 -0
- package/dist/{cli-655xm9ms.js → cli-583frbyr.js} +1 -1
- package/dist/{cli-4r12t6w6.js → cli-5c4hjzne.js} +101 -4
- package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
- package/dist/{cli-rtk9g7av.js → cli-73jzwksr.js} +6 -2
- package/dist/{cli-jg0nfda6.js → cli-75gkpjvw.js} +52 -21
- package/dist/{cli-wcmnan2g.js → cli-89knwags.js} +5 -5
- package/dist/{cli-pwyevs29.js → cli-8bc1w0tw.js} +22 -21
- package/dist/{cli-t282jgab.js → cli-8snvgs5r.js} +41 -16
- package/dist/{cli-vqgnyh76.js → cli-98rw5dt5.js} +15 -3
- package/dist/{cli-9cctx1tn.js → cli-a22h291q.js} +1 -1
- package/dist/{cli-ycm6180s.js → cli-a7v854rf.js} +5 -3
- package/dist/{cli-98pke4vk.js → cli-ae7mrdf9.js} +2 -2
- package/dist/{cli-je8yeyc5.js → cli-bjb3z51z.js} +5 -5
- package/dist/{cli-mc97xy2s.js → cli-c60vetmm.js} +16 -4
- package/dist/{cli-j4kw3rsx.js → cli-cm5yqn35.js} +18 -0
- package/dist/{cli-wndv9p7g.js → cli-decfb6v8.js} +6 -6
- package/dist/{cli-h55y2bjc.js → cli-dhar9qay.js} +2 -2
- package/dist/{cli-8yy525ad.js → cli-drr5v6av.js} +2 -2
- package/dist/{cli-5gr1g711.js → cli-dxae2yg7.js} +2 -2
- package/dist/{cli-pe09p6dv.js → cli-e3r2neck.js} +8 -7
- package/dist/{cli-13jvmew3.js → cli-ekwks2vs.js} +7 -7
- package/dist/{cli-jg4p5t36.js → cli-es6va8jk.js} +121 -51
- package/dist/{cli-j4kt1mpb.js → cli-faxe6thg.js} +3 -3
- package/dist/{cli-tzrpv5sc.js → cli-ftyxyspd.js} +1 -1
- package/dist/cli-fw2w0kva.js +47 -0
- package/dist/{cli-2dfbv8qr.js → cli-fy6x31ps.js} +1 -0
- package/dist/{cli-aq58fk5m.js → cli-g83s9ewh.js} +7 -1
- package/dist/cli-gdhpcbhf.js +55 -0
- package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
- package/dist/{cli-8veytvbh.js → cli-he9wchme.js} +1 -1
- package/dist/{cli-k76tgq03.js → cli-hgaazk69.js} +2 -2
- package/dist/{cli-d2pzqej1.js → cli-hr2e2fft.js} +741 -7
- package/dist/{cli-dae4sbgy.js → cli-jm2vfpfd.js} +2 -2
- package/dist/{cli-1s1dqn1j.js → cli-k2gzwsj2.js} +297 -140
- package/dist/{cli-b1afgtj3.js → cli-kqdg1bsb.js} +3 -3
- package/dist/cli-m2z8jagn.js +12078 -0
- package/dist/{cli-mvckssyq.js → cli-m48g6p95.js} +5 -5
- package/dist/{cli-bwty35gx.js → cli-mthwvjre.js} +1 -1
- package/dist/{cli-41z8xwj0.js → cli-nhrvpaj7.js} +34 -8
- package/dist/{cli-ahd90bwt.js → cli-nwj0ee2w.js} +6 -6
- package/dist/{cli-kvtc7aez.js → cli-p5gp8pcx.js} +44 -14
- package/dist/{cli-avgcz4ra.js → cli-pd7vagab.js} +436 -171
- package/dist/{cli-zn15c46z.js → cli-q0k1w5zd.js} +1 -1
- package/dist/{cli-bq2pcphn.js → cli-rmmmrjkd.js} +1 -1
- package/dist/{cli-2qpvmmkw.js → cli-s0qnd4wv.js} +1 -1
- package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
- package/dist/{cli-1yekbyqm.js → cli-shpbx5ee.js} +283 -82
- package/dist/{cli-9y354g3j.js → cli-srye3vdt.js} +1 -1
- package/dist/{cli-byqfbnjw.js → cli-svj5090z.js} +9 -9
- package/dist/{cli-gcbdjt47.js → cli-vfym7wmz.js} +6 -6
- package/dist/{cli-v1yw3wha.js → cli-vvpx3gsx.js} +4 -4
- package/dist/{cli-a1fhj4n9.js → cli-w6dt9ged.js} +2 -2
- package/dist/{cli-1gm8jcyr.js → cli-waqbxg08.js} +5232 -68
- package/dist/{cli-5hjced2v.js → cli-wp9sp1qb.js} +6 -6
- package/dist/{cli-eny54typ.js → cli-wx3rhnm0.js} +1 -1
- package/dist/{cli-s0exq74r.js → cli-xbbndvjy.js} +3 -3
- package/dist/{cli-f255yaqn.js → cli-xds5f77d.js} +3 -3
- package/dist/{cli-0sbkss7h.js → cli-xnqgpshy.js} +2 -2
- package/dist/{cli-0fgr1w9b.js → cli-xnrwazav.js} +20 -3
- package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
- package/dist/{cli-cq1pf336.js → cli-yn4kxs06.js} +124 -11
- package/dist/{cli-224epwwf.js → cli-yx4kfngt.js} +3 -3
- package/dist/{cli-eqx6k993.js → cli-yy3rhsdw.js} +47 -13
- package/dist/{cli-36g23z21.js → cli-zbbwez0r.js} +4 -4
- package/dist/{cli-ktmvb20s.js → cli-zm72anhg.js} +12 -12
- package/dist/cli.js +26 -25
- package/dist/{codex-adapter-g0gctw29.js → codex-adapter-ayd1fb6h.js} +5 -5
- package/dist/{codex-hook-vrzz2xhr.js → codex-hook-9efm2x1m.js} +1 -1
- package/dist/{codex-login-0gg46ygn.js → codex-login-y0pmff1c.js} +1 -1
- package/dist/{codex-session-runner-dr2cxe1c.js → codex-session-runner-jesa32xc.js} +6 -6
- package/dist/{commands-1p07cvff.js → commands-hanewgf2.js} +9 -3
- package/dist/{controller-z0s7sv6r.js → controller-dt1n3c1q.js} +9 -4
- package/dist/{controllers-connect-x9kvvhyk.js → controllers-connect-xx8x64n5.js} +22 -21
- package/dist/{cursor-adapter-m5b98pvk.js → cursor-adapter-9s4dwwdw.js} +2 -2
- package/dist/{db-capk2raj.js → db-3vttcbv4.js} +3 -3
- package/dist/{db-rsh04kh3.js → db-9dv0g1ab.js} +12 -4
- package/dist/{devin-adapter-yxm87fe9.js → devin-adapter-r9phbnc9.js} +1 -1
- package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
- package/dist/{dispatcher-304q6fwa.js → dispatcher-tx9hd1sv.js} +18 -17
- package/dist/{docker-7rxt0rry.js → docker-c1a3gqmf.js} +91 -24
- package/dist/{dsh-adapter-btewem0y.js → dsh-adapter-007y4ejs.js} +2 -2
- package/dist/{e2b-renc0k8q.js → e2b-a19d28rz.js} +4 -4
- package/dist/{e2b-7fc9b9bd.js → e2b-we6hs6q5.js} +6 -6
- package/dist/{easter-egg-3d-fm1p3n4d.js → easter-egg-3d-rgbrqb8s.js} +1 -1
- package/dist/{embedding-space-s68v49ct.js → embedding-space-ny1e2940.js} +6 -6
- package/dist/{embeddings-4ms3604w.js → embeddings-2t386t13.js} +1 -1
- package/dist/{event-bus-dfjx2qdh.js → event-bus-g0ka3pbw.js} +1 -1
- package/dist/{execute-mcdgccjs.js → execute-pksh0ceh.js} +1 -1
- package/dist/{external-channel-9dxwfztn.js → external-channel-phgmhmd7.js} +2 -2
- package/dist/fx-adapter-fy8rr921.js +18 -0
- package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-8y3m3k4b.js} +1 -1
- package/dist/{google-vertex-jrtdh9gs.js → google-vertex-w3v98n7b.js} +1 -1
- package/dist/{grok-adapter-05ecv4h0.js → grok-adapter-q0abvpaj.js} +8 -8
- package/dist/handlers-by3sfxyx.js +32 -0
- package/dist/{heartbeat-3n94heh3.js → heartbeat-kadyek8p.js} +21 -20
- package/dist/{hook-aadrn69x.js → hook-5zs3msf6.js} +6 -6
- package/dist/{hook-zxwje86c.js → hook-9k551xnj.js} +4 -4
- package/dist/{http-xzxk9mst.js → http-mhz9vw1f.js} +272 -103
- package/dist/index-4f2pvxae.js +37 -0
- package/dist/index-98gz7tmp.js +30 -0
- package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
- package/dist/index-k08z2cak.js +33 -0
- package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
- package/dist/{index-d34f6r87.js → index-q361cg2f.js} +1 -1
- package/dist/{index-9j4zwcht.js → index-rvr00sg7.js} +168 -675
- package/dist/{index-qeb82z37.js → index-vdygbq10.js} +1 -1
- package/dist/{keepalive-w4aakbyk.js → keepalive-w56f32cg.js} +4 -4
- package/dist/lead-enq35nqp.js +70 -0
- package/dist/{maintenance-hbptpk6p.js → maintenance-7mdgxcea.js} +1 -1
- package/dist/{oauth-refresh-sweep-217semrv.js → oauth-refresh-sweep-29txy9vk.js} +3 -3
- package/dist/{onboard-vz2s5df3.js → onboard-kpsrkarj.js} +5 -5
- package/dist/{opencode-adapter-gtfd7d7v.js → opencode-adapter-j25vm3rr.js} +5 -5
- package/dist/{otel-impl-aefave37.js → otel-impl-6kkct7a8.js} +2 -2
- package/dist/{pi-mono-adapter-f8h5yseh.js → pi-mono-adapter-exhkzhn2.js} +117 -23
- package/dist/{pricing-refresh-m1yhh9ya.js → pricing-refresh-n2x7jh6k.js} +3 -3
- package/dist/{rbac-roles-rrw8twdz.js → rbac-roles-81ypx6es.js} +1 -1
- package/dist/{rbac-roles-svj72cdf.js → rbac-roles-b5htkjby.js} +2 -2
- package/dist/{render-v2-amwbnfxh.js → render-v2-qn6jmpk4.js} +3 -3
- package/dist/{resume-9cd6wf6z.js → resume-wd30wx23.js} +7 -7
- package/dist/{runtime-fp4vrx7m.js → runtime-4hsg7h60.js} +1 -1
- package/dist/{session-access-21t6yjyg.js → session-access-5qvfevhk.js} +4 -4
- package/dist/{session-source-backfill-pxbzcsc6.js → session-source-backfill-n1czavbr.js} +3 -3
- package/dist/{setup-4592tqz4.js → setup-dnb56qy5.js} +3 -3
- package/dist/{virtual-modules-gzfnpmnz.js → virtual-modules-76xxv9fs.js} +2 -2
- package/dist/{work-objects-t9tcf174.js → work-objects-4n21tfkm.js} +21 -20
- package/dist/{worker-follow-up-q32jh4yk.js → worker-follow-up-77hvzxww.js} +18 -17
- package/dist/worker-gg1576j3.js +70 -0
- package/dist/{x-b6wxntdt.js → x-vmmye1kb.js} +2 -2
- package/openapi.json +189 -32
- package/package.json +8 -7
- package/src/be/db/script-runs.ts +23 -0
- package/src/be/db/session-summaries.ts +8 -2
- package/src/be/db/session-tree.ts +174 -61
- package/src/be/db/session-visibility.ts +5 -4
- package/src/be/db/tasks/read.ts +122 -3
- package/src/be/db-client.ts +8 -5
- package/src/be/db.ts +23 -8
- package/src/be/memory/providers/sqlite-store.ts +49 -12
- package/src/be/migrations/259_session_summaries_open_tasks.sql +111 -0
- 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/model-validation.ts +5 -1
- package/src/be/pricing-normalize.ts +7 -2
- 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 +23 -0
- package/src/be/seed/schedules-seeder.ts +135 -9
- package/src/be/seed/workflows-seeder.ts +65 -1
- package/src/be/seed-pricing.ts +23 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
- package/src/be/steering.ts +50 -3
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/commands/controller.ts +4 -0
- package/src/commands/entrypoint-api-readiness.ts +74 -0
- package/src/commands/provider-credentials.ts +29 -2
- package/src/commands/runner.ts +86 -5
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +27 -1
- package/src/extensions/dispatcher.ts +4 -0
- package/src/http/agents.ts +1 -0
- package/src/http/index.ts +1 -1
- package/src/http/list-total-cache.ts +14 -7
- package/src/http/mcp-bridge.ts +4 -0
- package/src/http/memory.ts +38 -9
- package/src/http/script-runs.ts +174 -5
- package/src/http/status.ts +10 -2
- package/src/http/tasks.ts +4 -2
- package/src/integrations/chat-sdk/inbound.ts +15 -5
- package/src/integrations/chat-sdk/steering.ts +3 -1
- package/src/kv-reserved-namespaces.ts +2 -0
- package/src/prompts/base-prompt.ts +4 -1
- package/src/prompts/session-templates.ts +26 -0
- package/src/providers/acp-adapter.ts +190 -26
- package/src/providers/acp-targets.ts +306 -6
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +9 -44
- package/src/providers/codex-oauth/flow.ts +24 -3
- package/src/providers/codex-oauth/storage.ts +56 -17
- package/src/providers/codex-oauth/subscription.ts +67 -0
- package/src/providers/fx-adapter.ts +211 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/mcp-loopback-proxy.ts +94 -0
- package/src/providers/pi-mono-adapter.ts +98 -13
- package/src/providers/pricing-sources.md +8 -0
- package/src/providers/reasoning-effort.ts +17 -1
- package/src/providers/types.ts +2 -1
- 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/scheduler/scheduler.ts +36 -3
- package/src/script-workflows/workflow-ctx.ts +8 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +27 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +33 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -0
- package/src/server-user.ts +1 -0
- package/src/server.ts +7 -0
- package/src/slack/assistant.ts +17 -1
- package/src/slack/commands.ts +5 -1
- package/src/slack/handlers.ts +53 -5
- package/src/slack/left-threads.ts +88 -0
- package/src/slack/steering.ts +3 -0
- package/src/slack/thread-buffer.ts +40 -16
- package/src/tasks/follow-up-redelegation-guard.ts +155 -0
- package/src/tests/acp-adapter.test.ts +79 -0
- package/src/tests/base-prompt.test.ts +12 -0
- package/src/tests/chat-sdk-inbound.test.ts +30 -0
- package/src/tests/chatgpt-subscription-model-groups.test.ts +62 -0
- package/src/tests/chatgpt-subscription-pi.test.ts +170 -0
- package/src/tests/chatgpt-subscription-session-bench.test.ts +198 -0
- package/src/tests/claude-artifacts-read-script.test.ts +209 -0
- package/src/tests/codex-oauth-credential-info.test.ts +40 -0
- package/src/tests/codex-oauth-refresh-egress.test.ts +194 -0
- package/src/tests/context-window.test.ts +7 -0
- package/src/tests/db-client.test.ts +21 -0
- package/src/tests/db-task-read-contract.test.ts +8 -2
- package/src/tests/db-task-write-contract.test.ts +5 -1
- package/src/tests/entrypoint-api-readiness.test.ts +416 -210
- package/src/tests/explicit-model-validation.test.ts +113 -8
- package/src/tests/extensions-run-context.test.ts +11 -0
- package/src/tests/extensions-steering.test.ts +353 -0
- package/src/tests/extensions-validate.test.ts +5 -0
- package/src/tests/fixtures/extensions/steering-policy/hooks.ts +39 -0
- package/src/tests/fixtures/extensions/steering-policy/manifest.json +7 -0
- package/src/tests/fixtures/fake-fx.ts +167 -0
- package/src/tests/fixtures/fx/acp-responses-recorded.json +157 -0
- package/src/tests/fixtures/fx/acp-updates-recorded.jsonl +15 -0
- package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
- package/src/tests/fx-adapter.test.ts +771 -0
- package/src/tests/grok-adapter.test.ts +36 -0
- package/src/tests/list-total-cache.test.ts +27 -2
- package/src/tests/mcp-loopback-proxy.test.ts +193 -0
- package/src/tests/memory-fts-populate.test.ts +11 -9
- package/src/tests/memory-retrieval-purge.test.ts +80 -0
- package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
- package/src/tests/model-tier-resolution.test.ts +3 -3
- package/src/tests/pricing-refresh.test.ts +5 -5
- package/src/tests/prompt-template-session.test.ts +5 -3
- package/src/tests/provider-registration.test.ts +2 -1
- package/src/tests/query-audit-plans.test.ts +30 -2
- 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/script-connections.test.ts +13 -0
- package/src/tests/script-runs-http.test.ts +684 -2
- package/src/tests/seed-model-profiles.test.ts +2 -2
- package/src/tests/seed-schedules.test.ts +138 -0
- package/src/tests/seed-scripts.test.ts +26 -0
- package/src/tests/session-cost-recompute-unit.test.ts +39 -0
- package/src/tests/session-costs-golden.test.ts +26 -0
- package/src/tests/session-summaries-plans.test.ts +120 -0
- package/src/tests/session-summaries.test.ts +72 -12
- package/src/tests/sessions-tree.test.ts +11 -3
- package/src/tests/slack-leave-thread.test.ts +571 -0
- package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
- package/src/tests/status.test.ts +11 -0
- package/src/tests/tasks-readable-row.test.ts +155 -0
- package/src/tests/tool-annotations.test.ts +2 -1
- package/src/tests/workflow-engine-v2.test.ts +42 -0
- package/src/tests/workflow-script-principal.test.ts +207 -7
- package/src/tools/send-task.ts +35 -47
- package/src/tools/slack-leave-thread.ts +104 -0
- package/src/tools/steer-task.ts +3 -2
- package/src/tools/task-dedup.ts +45 -21
- package/src/tools/tool-config.ts +2 -1
- package/src/types.ts +57 -0
- package/src/utils/codex-auth-failure.ts +30 -0
- package/src/utils/context-window.ts +4 -1
- package/src/utils/credentials.ts +7 -0
- package/src/utils/fx-models.ts +43 -0
- package/src/workflows/cooldown.ts +3 -2
- package/src/workflows/engine.ts +24 -9
- package/templates/schedules/dreaming-daily/config.json +1 -1
- package/templates/schedules/dreaming-daily/content.md +6 -14
- package/templates/skills/composio-google-docs/content.md +10 -0
- package/templates/skills/script-workflows/content.md +19 -1
- package/templates/skills/swarm-docs/content.md +2 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -0
- package/templates/skills/swarm-scripts/content.md +2 -0
- package/templates/workflows/dreaming/content.md +5 -1
- package/dist/acp-adapter-96dc64m2.js +0 -15
- package/dist/cli-dznrgaep.js +0 -30
- package/dist/cli-w3hcrxht.js +0 -6985
- package/dist/handlers-jg7v74ty.js +0 -31
- package/dist/index-8c1zbz59.js +0 -29
- package/dist/index-8k8tmfxd.js +0 -32
- package/dist/index-hq617xdj.js +0 -36
- package/dist/lead-567kw7x3.js +0 -68
- package/dist/worker-fjdqra6q.js +0 -68
|
@@ -62,9 +62,9 @@ beforeEach(async () => {
|
|
|
62
62
|
|
|
63
63
|
describe("tier seeds", () => {
|
|
64
64
|
test("one profile per harness and tier, copied from the tier map; none for acp", async () => {
|
|
65
|
-
expect(await seedModelProfiles()).toBe(
|
|
65
|
+
expect(await seedModelProfiles()).toBe(44);
|
|
66
66
|
const profiles = await listModelProfiles();
|
|
67
|
-
expect(profiles).toHaveLength(
|
|
67
|
+
expect(profiles).toHaveLength(44);
|
|
68
68
|
expect(profiles.some((p) => p.harness === "acp")).toBe(false);
|
|
69
69
|
for (const profile of profiles) {
|
|
70
70
|
const tier = profile.seededFrom?.replace("tier:", "") as (typeof MODEL_TIERS)[number];
|
|
@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { unlink } from "node:fs/promises";
|
|
3
3
|
import {
|
|
4
4
|
closeDb,
|
|
5
|
+
createWorkflow,
|
|
5
6
|
getScheduledTaskByName,
|
|
6
7
|
getScheduledTasks,
|
|
7
8
|
initDb,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
loadSeedSchedules,
|
|
14
15
|
type ScheduleTemplateSource,
|
|
15
16
|
} from "../be/seed/schedules-seeder";
|
|
17
|
+
import { createWorkflowsSeeder } from "../be/seed/workflows-seeder";
|
|
16
18
|
|
|
17
19
|
const TEST_DB_PATH = "./test-seed-schedules.sqlite";
|
|
18
20
|
|
|
@@ -72,6 +74,31 @@ function zeroConfigScheduleSource(opts?: {
|
|
|
72
74
|
} satisfies ScheduleTemplateSource;
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
/** A schedule template with an explicit target block and an optional task prompt. */
|
|
78
|
+
function targetScheduleSource(target: Record<string, unknown>, prompt?: string) {
|
|
79
|
+
return {
|
|
80
|
+
config: JSON.stringify({
|
|
81
|
+
name: "test-target-schedule",
|
|
82
|
+
title: "Test target schedule",
|
|
83
|
+
description: "Seeded schedule with a target.",
|
|
84
|
+
placeholders: [],
|
|
85
|
+
requires: [],
|
|
86
|
+
runAllSeedersCandidate: true,
|
|
87
|
+
autoEnableCandidate: true,
|
|
88
|
+
}),
|
|
89
|
+
content: `# Test\n\n## Schedule\n\n\`\`\`json\n${JSON.stringify({
|
|
90
|
+
cron: "0 9 * * *",
|
|
91
|
+
timezone: "UTC",
|
|
92
|
+
enabled: true,
|
|
93
|
+
...target,
|
|
94
|
+
})}\n\`\`\`\n${prompt === undefined ? "" : `\n## Scheduled Task\n\n${prompt}`}`,
|
|
95
|
+
} satisfies ScheduleTemplateSource;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function makeWorkflow(name: string) {
|
|
99
|
+
return createWorkflow({ name, definition: { nodes: [] } });
|
|
100
|
+
}
|
|
101
|
+
|
|
75
102
|
const ORIGINAL_SEED_AUTOMATIONS_ENABLED = process.env.SEED_AUTOMATIONS_ENABLED;
|
|
76
103
|
|
|
77
104
|
beforeEach(async () => {
|
|
@@ -133,6 +160,8 @@ describe("schedules seeder", () => {
|
|
|
133
160
|
|
|
134
161
|
test("SEED_AUTOMATIONS_ENABLED=false still seeds all schedules disabled", async () => {
|
|
135
162
|
process.env.SEED_AUTOMATIONS_ENABLED = "false";
|
|
163
|
+
// dreaming-daily targets the seeded dreaming workflow.
|
|
164
|
+
await runSeeder(createWorkflowsSeeder(), { quiet: true });
|
|
136
165
|
const result = await runSeeder(createSchedulesSeeder(), { quiet: true });
|
|
137
166
|
expect(result).toMatchObject({ created: 12, failed: [] });
|
|
138
167
|
const schedules = await getScheduledTasks();
|
|
@@ -141,6 +170,8 @@ describe("schedules seeder", () => {
|
|
|
141
170
|
});
|
|
142
171
|
|
|
143
172
|
test("seeds a disabled schedule whose timezone still needs setup", async () => {
|
|
173
|
+
// dreaming-daily targets the seeded dreaming workflow.
|
|
174
|
+
await runSeeder(createWorkflowsSeeder(), { quiet: true });
|
|
144
175
|
const result = await runSeeder(createSchedulesSeeder(), { quiet: true });
|
|
145
176
|
expect(result).toMatchObject({ created: 12, failed: [] });
|
|
146
177
|
expect(await getScheduledTaskByName("weekly-dependabot-triage")).toMatchObject({
|
|
@@ -245,6 +276,113 @@ describe("schedules seeder", () => {
|
|
|
245
276
|
);
|
|
246
277
|
});
|
|
247
278
|
|
|
279
|
+
test("dreaming-daily is a workflow schedule with no task prompt", async () => {
|
|
280
|
+
await runSeeder(createWorkflowsSeeder(), { quiet: true });
|
|
281
|
+
const dreaming = loadSeedSchedules().find((schedule) => schedule.name === "dreaming-daily");
|
|
282
|
+
expect(dreaming).toMatchObject({
|
|
283
|
+
targetType: "workflow",
|
|
284
|
+
workflow: "dreaming",
|
|
285
|
+
taskTemplate: "",
|
|
286
|
+
});
|
|
287
|
+
// Agent-task templates carry no target keys, so their seed hash is unchanged.
|
|
288
|
+
const digest = loadSeedSchedules().find((schedule) => schedule.name === "daily-blocker-digest");
|
|
289
|
+
expect(digest && "targetType" in digest).toBe(false);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("seeds a workflow target by name and re-runs as a no-op", async () => {
|
|
293
|
+
const workflow = await makeWorkflow("test-target-workflow");
|
|
294
|
+
const seeder = createSchedulesSeeder([
|
|
295
|
+
targetScheduleSource({ targetType: "workflow", workflow: "test-target-workflow" }),
|
|
296
|
+
]);
|
|
297
|
+
expect(await runSeeder(seeder, { quiet: true })).toMatchObject({ created: 1, failed: [] });
|
|
298
|
+
const row = await getScheduledTaskByName("test-target-schedule");
|
|
299
|
+
expect(row).toMatchObject({ targetType: "workflow", workflowId: workflow.id });
|
|
300
|
+
expect(row?.taskTemplate).toBeUndefined();
|
|
301
|
+
expect(await runSeeder(seeder, { quiet: true })).toMatchObject({
|
|
302
|
+
skippedUnchanged: 1,
|
|
303
|
+
updated: 0,
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("seeds a script target and fails on a missing script", async () => {
|
|
308
|
+
const seeder = createSchedulesSeeder([
|
|
309
|
+
targetScheduleSource({ targetType: "script", scriptName: "no-such-script", scriptArgs: {} }),
|
|
310
|
+
]);
|
|
311
|
+
const result = await runSeeder(seeder, { quiet: true });
|
|
312
|
+
expect(result.created).toBe(0);
|
|
313
|
+
expect(result.failed[0]?.error).toContain('Global script "no-such-script" not found');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("fails a workflow target whose workflow does not exist, without creating a row", async () => {
|
|
317
|
+
const result = await runSeeder(
|
|
318
|
+
createSchedulesSeeder([
|
|
319
|
+
targetScheduleSource({ targetType: "workflow", workflow: "missing" }),
|
|
320
|
+
]),
|
|
321
|
+
{ quiet: true },
|
|
322
|
+
);
|
|
323
|
+
expect(result.created).toBe(0);
|
|
324
|
+
expect(result.failed[0]?.error).toContain('Workflow "missing" not found');
|
|
325
|
+
expect(await getScheduledTaskByName("test-target-schedule")).toBeNull();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("rejects target blocks that ship fields or prompts the target ignores", () => {
|
|
329
|
+
const cases: [Record<string, unknown>, string | undefined, string][] = [
|
|
330
|
+
[{ targetType: "workflow", workflow: "w" }, "Do it.", "ships a ## Scheduled Task prompt"],
|
|
331
|
+
[{ targetType: "script", scriptName: "s" }, "Do it.", "ships a ## Scheduled Task prompt"],
|
|
332
|
+
[{ targetType: "workflow" }, undefined, "has no workflow name"],
|
|
333
|
+
[{ targetType: "script" }, undefined, "has no scriptName"],
|
|
334
|
+
[{ targetType: "workflow", workflow: "w", scriptName: "s" }, undefined, "sets scriptName"],
|
|
335
|
+
[{ workflow: "w" }, "Do it.", "(targetType agent-task) sets workflow"],
|
|
336
|
+
[{ targetType: "lambda" }, undefined, 'unknown targetType "lambda"'],
|
|
337
|
+
];
|
|
338
|
+
for (const [target, prompt, message] of cases) {
|
|
339
|
+
expect(() => loadSeedSchedules([targetScheduleSource(target, prompt)])).toThrow(message);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("migrates a pristine agent-task schedule to a workflow target and clears its prompt", async () => {
|
|
344
|
+
const workflow = await makeWorkflow("test-target-workflow");
|
|
345
|
+
await runSeeder(createSchedulesSeeder([targetScheduleSource({}, "Trigger the workflow.")]), {
|
|
346
|
+
quiet: true,
|
|
347
|
+
});
|
|
348
|
+
expect(await getScheduledTaskByName("test-target-schedule")).toMatchObject({
|
|
349
|
+
targetType: "agent-task",
|
|
350
|
+
taskTemplate: "Trigger the workflow.",
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const result = await runSeeder(
|
|
354
|
+
createSchedulesSeeder([
|
|
355
|
+
targetScheduleSource({ targetType: "workflow", workflow: "test-target-workflow" }),
|
|
356
|
+
]),
|
|
357
|
+
{ quiet: true },
|
|
358
|
+
);
|
|
359
|
+
expect(result).toMatchObject({ updated: 1, failed: [] });
|
|
360
|
+
const row = await getScheduledTaskByName("test-target-schedule");
|
|
361
|
+
expect(row).toMatchObject({ targetType: "workflow", workflowId: workflow.id });
|
|
362
|
+
expect(row?.taskTemplate).toBeUndefined();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("preserves an operator-edited agent-task schedule when its template moves to a workflow", async () => {
|
|
366
|
+
await makeWorkflow("test-target-workflow");
|
|
367
|
+
await runSeeder(createSchedulesSeeder([targetScheduleSource({}, "Trigger the workflow.")]), {
|
|
368
|
+
quiet: true,
|
|
369
|
+
});
|
|
370
|
+
const seeded = await getScheduledTaskByName("test-target-schedule");
|
|
371
|
+
await updateScheduledTask(seeded!.id, { taskTemplate: "My own prompt." });
|
|
372
|
+
|
|
373
|
+
const result = await runSeeder(
|
|
374
|
+
createSchedulesSeeder([
|
|
375
|
+
targetScheduleSource({ targetType: "workflow", workflow: "test-target-workflow" }),
|
|
376
|
+
]),
|
|
377
|
+
{ quiet: true },
|
|
378
|
+
);
|
|
379
|
+
expect(result.skippedUserModified).toBe(1);
|
|
380
|
+
expect(await getScheduledTaskByName("test-target-schedule")).toMatchObject({
|
|
381
|
+
targetType: "agent-task",
|
|
382
|
+
taskTemplate: "My own prompt.",
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
248
386
|
test("reports an invalid cron without creating a row", async () => {
|
|
249
387
|
const result = await runSeeder(createSchedulesSeeder([scheduleSource("not a cron")]), {
|
|
250
388
|
quiet: true,
|
|
@@ -217,6 +217,32 @@ describe("seed-scripts catalog", () => {
|
|
|
217
217
|
).toHaveLength(200);
|
|
218
218
|
});
|
|
219
219
|
|
|
220
|
+
test.each([
|
|
221
|
+
["Incomplete response returned, reason: max_output_tokens", "output-limit"],
|
|
222
|
+
["Stopped: max_tokens", "output-limit"],
|
|
223
|
+
["Maximum output length exceeded", "output-limit"],
|
|
224
|
+
["Output token limit reached", "output-limit"],
|
|
225
|
+
["401 Unauthorized", "auth/credentials"],
|
|
226
|
+
["invalid token", "auth/credentials"],
|
|
227
|
+
["Resource not accessible by personal access token", "auth/credentials"],
|
|
228
|
+
["Processed 100 tokens", "other"],
|
|
229
|
+
["max_input_tokens exceeded", "other"],
|
|
230
|
+
])("task-failure-audit classifies %s as %s", async (failureReason, expectedKey) => {
|
|
231
|
+
const result = await taskFailureAudit(
|
|
232
|
+
{ publishPage: false },
|
|
233
|
+
{
|
|
234
|
+
swarm: {
|
|
235
|
+
async task_list() {
|
|
236
|
+
return { data: { tasks: [{ id: "failed-task", failureReason }] } };
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
expect(result.groups).toHaveLength(1);
|
|
243
|
+
expect(result.groups[0].key).toBe(expectedKey);
|
|
244
|
+
});
|
|
245
|
+
|
|
220
246
|
test("task-failure-audit projects failure reasons into schedule-group samples", async () => {
|
|
221
247
|
let projectionArgs: Record<string, unknown> | undefined;
|
|
222
248
|
const result = await taskFailureAudit(
|
|
@@ -261,3 +261,42 @@ describe("nan and helmcode sessions price at $0 from the seeded table", () => {
|
|
|
261
261
|
}
|
|
262
262
|
});
|
|
263
263
|
});
|
|
264
|
+
|
|
265
|
+
describe("pi on the ChatGPT subscription prices like codex on the same pool", () => {
|
|
266
|
+
const cache = loadModelsDevCache();
|
|
267
|
+
const rows = cache ? buildModelsDevSeedRows(cache) : [];
|
|
268
|
+
const book: RateBook = Object.fromEntries(
|
|
269
|
+
rows.map((row) => [`${row.provider}:${row.model}:${row.tokenClass}`, row.pricePerMillionUsd]),
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
test("openai-codex/<id> on pi records the codex <id> pricing-table cost", async () => {
|
|
273
|
+
// One request: 69,308 input tokens, 55,680 of them cached, 376 output.
|
|
274
|
+
// codex reports input including cache reads; pi-ai reports them disjoint.
|
|
275
|
+
const codex = await recomputeSessionCost(
|
|
276
|
+
{
|
|
277
|
+
...BASE,
|
|
278
|
+
provider: "codex",
|
|
279
|
+
model: "gpt-5.6-luna",
|
|
280
|
+
inputTokens: 69_308,
|
|
281
|
+
cacheReadTokens: 55_680,
|
|
282
|
+
outputTokens: 376,
|
|
283
|
+
},
|
|
284
|
+
lookupFrom(book),
|
|
285
|
+
);
|
|
286
|
+
const pi = await recomputeSessionCost(
|
|
287
|
+
{
|
|
288
|
+
...BASE,
|
|
289
|
+
provider: "pi",
|
|
290
|
+
model: "openai-codex/gpt-5.6-luna",
|
|
291
|
+
inputTokens: 69_308 - 55_680,
|
|
292
|
+
cacheReadTokens: 55_680,
|
|
293
|
+
outputTokens: 376,
|
|
294
|
+
},
|
|
295
|
+
lookupFrom(book),
|
|
296
|
+
);
|
|
297
|
+
expect(codex.costSource).toBe("pricing-table");
|
|
298
|
+
expect(pi.costSource).toBe("pricing-table");
|
|
299
|
+
expect(pi.totalCostUsd).toBeCloseTo(codex.totalCostUsd, 12);
|
|
300
|
+
expect(pi.totalCostUsd).toBeGreaterThan(0);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -361,6 +361,32 @@ describe("Phase 3 — session cost recompute golden fixtures", () => {
|
|
|
361
361
|
expectExact(body.cost.totalCostUsd, expected);
|
|
362
362
|
});
|
|
363
363
|
|
|
364
|
+
// Boot-seeded rows only: fx reports the swarm model string with its route
|
|
365
|
+
// prefix and no cache split, so input and output price at the table rate.
|
|
366
|
+
test("fx runs price from the seeded rates on each route", async () => {
|
|
367
|
+
const openRouter = await postCost({
|
|
368
|
+
sessionId: "fx-openrouter",
|
|
369
|
+
provider: "fx",
|
|
370
|
+
model: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
371
|
+
totalCostUsd: 0,
|
|
372
|
+
inputTokens: 58_620,
|
|
373
|
+
outputTokens: 1_183,
|
|
374
|
+
});
|
|
375
|
+
expect(openRouter.cost.costSource).toBe("pricing-table");
|
|
376
|
+
expectExact(openRouter.cost.totalCostUsd, (58_620 * 0.14 + 1_183 * 0.42) / 1_000_000);
|
|
377
|
+
|
|
378
|
+
const openAi = await postCost({
|
|
379
|
+
sessionId: "fx-openai",
|
|
380
|
+
provider: "fx",
|
|
381
|
+
model: "gpt-5-mini",
|
|
382
|
+
totalCostUsd: 0,
|
|
383
|
+
inputTokens: 9_286,
|
|
384
|
+
outputTokens: 506,
|
|
385
|
+
});
|
|
386
|
+
expect(openAi.cost.costSource).toBe("pricing-table");
|
|
387
|
+
expectExact(openAi.cost.totalCostUsd, (9_286 * 0.25 + 506 * 2) / 1_000_000);
|
|
388
|
+
});
|
|
389
|
+
|
|
364
390
|
test("legacy cache writes without a TTL split keep the 5m class", async () => {
|
|
365
391
|
await seedModelRates("claude", "claude-legacy-cache-write", {
|
|
366
392
|
input: 5,
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
closeDb,
|
|
5
|
+
countSessions,
|
|
6
|
+
createTaskExtended,
|
|
7
|
+
createUser,
|
|
8
|
+
getDbClient,
|
|
9
|
+
initDb,
|
|
10
|
+
listRecentSessions,
|
|
11
|
+
} from "../be/db";
|
|
12
|
+
import {
|
|
13
|
+
getSessionReadState,
|
|
14
|
+
getSessionTree,
|
|
15
|
+
legacySessionSummaryQuery,
|
|
16
|
+
triageSessionList,
|
|
17
|
+
} from "../be/db/session-tree";
|
|
18
|
+
|
|
19
|
+
const TEST_DB_PATH = "./test-session-summaries-plans.sqlite";
|
|
20
|
+
|
|
21
|
+
async function removeDbFiles(): Promise<void> {
|
|
22
|
+
for (const suffix of ["", "-wal", "-shm"])
|
|
23
|
+
await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => undefined);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Runs the real caller and returns the plans of every statement it sent.
|
|
27
|
+
async function plansOf(run: () => Promise<unknown>): Promise<string[]> {
|
|
28
|
+
const client = getDbClient();
|
|
29
|
+
const seen: { sql: string; params: unknown[] }[] = [];
|
|
30
|
+
const methods = ["query", "get", "run"] as const;
|
|
31
|
+
const originals = methods.map((name) => client[name].bind(client));
|
|
32
|
+
const spies = methods.map((name, i) =>
|
|
33
|
+
spyOn(client, name).mockImplementation(((sql: string, params?: unknown[]) => {
|
|
34
|
+
seen.push({ sql, params: params ?? [] });
|
|
35
|
+
return (originals[i] as (sql: string, params?: unknown[]) => unknown)(sql, params);
|
|
36
|
+
}) as never),
|
|
37
|
+
);
|
|
38
|
+
try {
|
|
39
|
+
await run();
|
|
40
|
+
} finally {
|
|
41
|
+
for (const spy of spies) spy.mockRestore();
|
|
42
|
+
}
|
|
43
|
+
expect(seen.length).toBeGreaterThan(0);
|
|
44
|
+
const plans: string[] = [];
|
|
45
|
+
for (const { sql, params } of seen) {
|
|
46
|
+
const rows = await client.query<{ detail: string }>(
|
|
47
|
+
`EXPLAIN QUERY PLAN ${sql}`,
|
|
48
|
+
params as (string | number | null)[],
|
|
49
|
+
);
|
|
50
|
+
plans.push(rows.map((row) => row.detail).join("\n"));
|
|
51
|
+
}
|
|
52
|
+
return plans;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let userId = "";
|
|
56
|
+
|
|
57
|
+
describe("session read plans without sqlite_stat1", () => {
|
|
58
|
+
beforeAll(async () => {
|
|
59
|
+
await removeDbFiles();
|
|
60
|
+
initDb(TEST_DB_PATH);
|
|
61
|
+
userId = (await createUser({ name: "plans viewer" })).id;
|
|
62
|
+
const root = await createTaskExtended("plans root", {
|
|
63
|
+
source: "ui",
|
|
64
|
+
requestedByUserId: userId,
|
|
65
|
+
});
|
|
66
|
+
await createTaskExtended("plans child", { parentTaskId: root.id });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterAll(async () => {
|
|
70
|
+
closeDb();
|
|
71
|
+
await removeDbFiles();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("the test DB has no planner statistics, like production", async () => {
|
|
75
|
+
const stat = await getDbClient().get<{ name: string }>(
|
|
76
|
+
"SELECT name FROM sqlite_master WHERE name = 'sqlite_stat1'",
|
|
77
|
+
);
|
|
78
|
+
expect(stat).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const viewer = () => ({ userId, scope: "all" as const });
|
|
82
|
+
const now = "2026-10-29T12:00:00.000Z";
|
|
83
|
+
const CASES: [string, () => Promise<unknown>][] = [
|
|
84
|
+
["list", () => listRecentSessions({ viewer: viewer(), now, slim: true, tree: true })],
|
|
85
|
+
["list inbox", () => listRecentSessions({ viewer: viewer(), now, triage: "inbox" })],
|
|
86
|
+
["list done", () => listRecentSessions({ viewer: viewer(), now, triage: "done" })],
|
|
87
|
+
["list group", () => listRecentSessions({ viewer: viewer(), now, groupKey: "shared/" })],
|
|
88
|
+
["list status", () => listRecentSessions({ viewer: viewer(), now, status: "needs" })],
|
|
89
|
+
["list with user", () => listRecentSessions({ viewer: viewer(), now, withUserId: userId })],
|
|
90
|
+
["count", () => countSessions({ viewer: viewer(), now, tree: true })],
|
|
91
|
+
["count inbox", () => countSessions({ viewer: viewer(), now, triage: "inbox" })],
|
|
92
|
+
["count source", () => countSessions({ source: ["slack"] })],
|
|
93
|
+
["tree", () => getSessionTree({ kind: "user", userId }, now)],
|
|
94
|
+
["tree mine", () => getSessionTree({ kind: "user", userId }, now, { mine: true })],
|
|
95
|
+
["read state", () => getSessionReadState(userId, userId, now)],
|
|
96
|
+
["triage list", () => triageSessionList(userId, { triage: "inbox", now }, "read")],
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
for (const [name, run] of CASES) {
|
|
100
|
+
test(`${name} reads the summaries without a chain walk or task scan`, async () => {
|
|
101
|
+
for (const detail of await plansOf(run)) {
|
|
102
|
+
expect(detail).not.toContain("RECURSIVE");
|
|
103
|
+
expect(detail).not.toMatch(/\bSCAN agent_tasks\b/);
|
|
104
|
+
expect(detail).not.toMatch(/\bSCAN (r|rt|t)\b/);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
test("the chain walk trips the same checks (control)", async () => {
|
|
110
|
+
const query = legacySessionSummaryQuery({ viewer: viewer(), now });
|
|
111
|
+
const [detail] = await plansOf(() => getDbClient().query(query.sql, query.params));
|
|
112
|
+
expect(detail).toMatch(/RECURSIVE|\bSCAN agent_tasks\b|\bSCAN (r|t)\b/);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("the list reads agent_tasks only for the page roots", async () => {
|
|
116
|
+
const [detail] = await plansOf(() => listRecentSessions({ viewer: viewer(), now, slim: true }));
|
|
117
|
+
expect(detail).toContain("SEARCH r USING INDEX sqlite_autoindex_agent_tasks_1 (id=?)");
|
|
118
|
+
expect(detail).toContain("SCAN session_summaries USING INDEX idx_session_summaries_activity");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -13,11 +13,17 @@ import {
|
|
|
13
13
|
upsertInboxState,
|
|
14
14
|
} from "../be/db";
|
|
15
15
|
import { findSessionSummaryDrift } from "../be/db/session-summaries";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
legacySessionSummaryQuery,
|
|
18
|
+
type SessionFilters,
|
|
19
|
+
sessionSummaryQuery,
|
|
20
|
+
} from "../be/db/session-tree";
|
|
17
21
|
import { deleteTask } from "../be/db/tasks/write";
|
|
18
22
|
|
|
19
23
|
const dbPath = join(tmpdir(), `session-summaries-${process.pid}-${Date.now()}.sqlite`);
|
|
20
|
-
const
|
|
24
|
+
const MIGRATIONS = ["253_session_summaries.sql", "259_session_summaries_open_tasks.sql"].map(
|
|
25
|
+
(name) => join(import.meta.dir, "../be/migrations", name),
|
|
26
|
+
);
|
|
21
27
|
const BASE = Date.parse("2026-10-01T00:00:00.000Z");
|
|
22
28
|
let users: string[] = [];
|
|
23
29
|
|
|
@@ -209,7 +215,7 @@ const SCHEDULE = [
|
|
|
209
215
|
/** Each Done-marked root's read state from the chain walk equals the stored rule. */
|
|
210
216
|
async function expectReopensDoneMatches() {
|
|
211
217
|
const viewer = users[0]!;
|
|
212
|
-
const legacy =
|
|
218
|
+
const legacy = legacySessionSummaryQuery({ viewer: { userId: viewer, scope: "all" } });
|
|
213
219
|
const rows = await client().query<{ root_id: string; readState: string; stored: string }>(
|
|
214
220
|
`SELECT a.root_id, a.readState,
|
|
215
221
|
CASE WHEN s.lastHumanAt > d.lastUpdatedAt THEN 'unread' ELSE 'done' END AS stored
|
|
@@ -226,6 +232,49 @@ async function expectReopensDoneMatches() {
|
|
|
226
232
|
return rows.length;
|
|
227
233
|
}
|
|
228
234
|
|
|
235
|
+
/** A random filter set, as the list, count and tree send them. */
|
|
236
|
+
function randomFilters(): SessionFilters {
|
|
237
|
+
const opts: SessionFilters = {
|
|
238
|
+
viewer: { userId: rand() < 0.15 ? null : pick(users), scope: pick(["all", "mine", "shared"]) },
|
|
239
|
+
now: at(),
|
|
240
|
+
};
|
|
241
|
+
if (rand() < 0.2) opts.source = [pick(["ui", "slack", "mcp", "api"])];
|
|
242
|
+
if (rand() < 0.2) opts.showSource = [pick(["ui", "slack", "api"])];
|
|
243
|
+
if (rand() < 0.2) opts.hideSource = [pick(["mcp", "system"])];
|
|
244
|
+
if (rand() < 0.4) opts.triage = pick(["inbox", "later", "done", "unread"] as const);
|
|
245
|
+
if (rand() < 0.3) opts.status = pick(["running", "failed", "needs"] as const);
|
|
246
|
+
if (rand() < 0.2) opts.withUserId = pick(users);
|
|
247
|
+
if (rand() < 0.2) opts.q = pick(["task", "ab", "zz"]);
|
|
248
|
+
if (rand() < 0.2) opts.noGroup = true;
|
|
249
|
+
else if (rand() < 0.1) opts.groupKey = "shared/";
|
|
250
|
+
if (rand() < 0.3) opts.tree = true;
|
|
251
|
+
if (rand() < 0.2) opts.mine = true;
|
|
252
|
+
return opts;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The summary query returns the chain walk's rows for random filters. */
|
|
256
|
+
async function expectSummaryQueryMatchesLegacy(sets: number) {
|
|
257
|
+
const columns = `root_id, chainCount, lastActivityAt, latestStatus, readState, snoozeUntil,
|
|
258
|
+
needsYou, pendingApproval > 0 AS pendingApproval, openTasks`;
|
|
259
|
+
// Skip roots whose newest timestamp ties across chain tasks: the walk's status
|
|
260
|
+
// is arbitrary there, and so are needsYou and the status filters.
|
|
261
|
+
const untied = `(SELECT COUNT(*) FROM session_members m JOIN agent_tasks t ON t.id = m.taskId
|
|
262
|
+
WHERE m.rootId = q.root_id AND t.lastUpdatedAt = q.lastActivityAt) = 1`;
|
|
263
|
+
let rows = 0;
|
|
264
|
+
for (let i = 0; i < sets; i++) {
|
|
265
|
+
const opts = randomFilters();
|
|
266
|
+
const read = async (query: { sql: string; params: unknown[] }) =>
|
|
267
|
+
await client().query<Record<string, unknown>>(
|
|
268
|
+
`SELECT ${columns} FROM (${query.sql}) q WHERE ${untied} ORDER BY root_id`,
|
|
269
|
+
query.params as (string | number | null)[],
|
|
270
|
+
);
|
|
271
|
+
const expected = await read(legacySessionSummaryQuery(opts));
|
|
272
|
+
expect({ opts, rows: await read(sessionSummaryQuery(opts)) }).toEqual({ opts, rows: expected });
|
|
273
|
+
rows += expected.length;
|
|
274
|
+
}
|
|
275
|
+
return rows;
|
|
276
|
+
}
|
|
277
|
+
|
|
229
278
|
beforeAll(async () => {
|
|
230
279
|
initDb(dbPath);
|
|
231
280
|
users = [];
|
|
@@ -245,8 +294,9 @@ describe("session_summaries", () => {
|
|
|
245
294
|
test("500 random writes keep the summaries equal to the chain walk", async () => {
|
|
246
295
|
const counts: Record<string, number> = {};
|
|
247
296
|
// The most chains, human events and pending approvals any checkpoint compared.
|
|
248
|
-
const seen = { chains: 0, humans: 0, pending: 0 };
|
|
297
|
+
const seen = { chains: 0, humans: 0, pending: 0, open: 0 };
|
|
249
298
|
let doneChecks = 0;
|
|
299
|
+
let summaryRows = 0;
|
|
250
300
|
for (let i = 1; i <= 500; i++) {
|
|
251
301
|
const op = pick(SCHEDULE);
|
|
252
302
|
counts[op] = (counts[op] ?? 0) + 1;
|
|
@@ -257,9 +307,11 @@ describe("session_summaries", () => {
|
|
|
257
307
|
drift: [],
|
|
258
308
|
});
|
|
259
309
|
doneChecks += await expectReopensDoneMatches();
|
|
310
|
+
summaryRows += await expectSummaryQueryMatchesLegacy(5);
|
|
260
311
|
const shape = (await client().get<typeof seen>(
|
|
261
312
|
`SELECT SUM(chainCount > 2) AS chains, SUM(lastHumanAt IS NOT NULL) AS humans,
|
|
262
|
-
SUM(pendingApprovalCount > 0) AS pending
|
|
313
|
+
SUM(pendingApprovalCount > 0) AS pending, SUM(openTaskCount > 1) AS open
|
|
314
|
+
FROM session_summaries`,
|
|
263
315
|
))!;
|
|
264
316
|
for (const key of Object.keys(seen) as (keyof typeof seen)[])
|
|
265
317
|
seen[key] = Math.max(seen[key], shape[key] ?? 0);
|
|
@@ -270,7 +322,9 @@ describe("session_summaries", () => {
|
|
|
270
322
|
expect(seen.chains).toBeGreaterThan(5);
|
|
271
323
|
expect(seen.humans).toBeGreaterThan(5);
|
|
272
324
|
expect(seen.pending).toBeGreaterThan(0);
|
|
325
|
+
expect(seen.open).toBeGreaterThan(0);
|
|
273
326
|
expect(doneChecks).toBeGreaterThan(0);
|
|
327
|
+
expect(summaryRows).toBeGreaterThan(100);
|
|
274
328
|
}, 60_000);
|
|
275
329
|
|
|
276
330
|
test("the drift check reports a corrupted summary and member (positive control)", async () => {
|
|
@@ -279,13 +333,13 @@ describe("session_summaries", () => {
|
|
|
279
333
|
expect(await findSessionSummaryDrift()).toEqual([]);
|
|
280
334
|
|
|
281
335
|
await client().run(
|
|
282
|
-
"UPDATE session_summaries SET chainCount = chainCount + 5, lastHumanAt = 'x' WHERE rootId = ?",
|
|
336
|
+
"UPDATE session_summaries SET chainCount = chainCount + 5, openTaskCount = 7, lastHumanAt = 'x' WHERE rootId = ?",
|
|
283
337
|
[root],
|
|
284
338
|
);
|
|
285
339
|
await client().run("DELETE FROM session_members WHERE taskId = ?", [child]);
|
|
286
340
|
const drift = await findSessionSummaryDrift();
|
|
287
341
|
expect(drift.map((d) => `${d.rootId}:${d.field}`).sort()).toEqual(
|
|
288
|
-
[`${root}:chainCount`, `${root}:lastHumanAt`, `${root}:member`].sort(),
|
|
342
|
+
[`${root}:chainCount`, `${root}:lastHumanAt`, `${root}:member`, `${root}:openTasks`].sort(),
|
|
289
343
|
);
|
|
290
344
|
|
|
291
345
|
await client().run("INSERT INTO session_members (taskId, rootId) VALUES (?, ?)", [child, root]);
|
|
@@ -356,18 +410,24 @@ describe("session_summaries", () => {
|
|
|
356
410
|
await client().run("UPDATE agent_tasks SET parentTaskId = ? WHERE id = ?", [root, child]);
|
|
357
411
|
});
|
|
358
412
|
|
|
359
|
-
test("the migration
|
|
413
|
+
test("the migration backfills rebuild the same summaries", async () => {
|
|
360
414
|
const before = await client().query<Record<string, unknown>>(
|
|
361
415
|
"SELECT * FROM session_summaries ORDER BY rootId",
|
|
362
416
|
);
|
|
363
417
|
expect(before.length).toBeGreaterThan(20);
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
418
|
+
const backfills = await Promise.all(
|
|
419
|
+
MIGRATIONS.map(async (path) => {
|
|
420
|
+
const sql = await Bun.file(path).text();
|
|
421
|
+
return sql.slice(sql.indexOf("-- Backfill:"));
|
|
422
|
+
}),
|
|
423
|
+
);
|
|
424
|
+
expect(backfills[0]).toContain("INSERT INTO session_members");
|
|
425
|
+
expect(backfills[1]).toContain("openTaskCount");
|
|
367
426
|
await client().transaction(async (tx) => {
|
|
368
427
|
await tx.run("DELETE FROM session_summaries");
|
|
369
428
|
await tx.run("DELETE FROM session_members");
|
|
370
|
-
for (const statement of
|
|
429
|
+
for (const statement of backfills
|
|
430
|
+
.join("\n")
|
|
371
431
|
.split(/;\s*\n/)
|
|
372
432
|
.filter((s) => s.replace(/--.*$/gm, "").trim()))
|
|
373
433
|
await tx.run(statement);
|
|
@@ -425,14 +425,22 @@ describe("nested source thread groups", () => {
|
|
|
425
425
|
}
|
|
426
426
|
const [sql, params] = call;
|
|
427
427
|
const plan = await client.query<{ detail: string }>(`EXPLAIN QUERY PLAN ${sql}`, params);
|
|
428
|
-
expect(plan.some((p) => p.detail.startsWith("SEARCH r USING INDEX"))).toBe(true);
|
|
429
428
|
expect(
|
|
430
429
|
plan.some((p) =>
|
|
431
|
-
|
|
430
|
+
/^SEARCH session_summaries USING INDEX idx_session_summaries_(key|thread) \(key>\? AND key<\?\)/.test(
|
|
431
|
+
p.detail,
|
|
432
|
+
),
|
|
432
433
|
),
|
|
433
434
|
).toBe(true);
|
|
434
435
|
expect(
|
|
435
|
-
plan.some((p) =>
|
|
436
|
+
plan.some((p) =>
|
|
437
|
+
p.detail.includes("SEARCH session_thread_root USING INDEX idx_session_summaries_key"),
|
|
438
|
+
),
|
|
439
|
+
).toBe(true);
|
|
440
|
+
expect(
|
|
441
|
+
plan.some((p) =>
|
|
442
|
+
/^SCAN (session_summaries|session_thread_root|agent_tasks)\b/.test(p.detail),
|
|
443
|
+
),
|
|
436
444
|
).toBe(false);
|
|
437
445
|
expect(sql).not.toContain('"key" LIKE');
|
|
438
446
|
expect(sql).not.toContain("INDEXED BY");
|