@desplega.ai/agent-swarm 2.7.4 → 2.7.5
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-g3ads55s.js +15 -0
- package/dist/{actions-gfrr9vb7.js → actions-zwfykqqk.js} +18 -18
- package/dist/{amp-adapter-0s58gm46.js → amp-adapter-z3s0ssdt.js} +3 -3
- package/dist/{app-stvq692g.js → app-vmp4fgep.js} +3 -3
- package/dist/{assistant-5s37fr9n.js → assistant-vwde653f.js} +28 -21
- package/dist/{boot-reembed-yzy1841n.js → boot-reembed-375t7tbk.js} +1 -1
- package/dist/{boot-reembed-cv82gz7y.js → boot-reembed-gq9rcpnj.js} +1 -1
- package/dist/{boot-scrub-sweep-8k4q95ap.js → boot-scrub-sweep-nd938kyf.js} +4 -4
- package/dist/{boot-seeds-ftt9htah.js → boot-seeds-aykx51v3.js} +4 -4
- package/dist/{boxd-515v1t8z.js → boxd-k1j26g3h.js} +1 -1
- package/dist/{claude-adapter-t00wrzeh.js → claude-adapter-8j4hm099.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-0wyynpn1.js} +3 -3
- package/dist/{claude-sdk-session-vj0c2azx.js → claude-sdk-session-q6wj5hmy.js} +9 -9
- package/dist/{cli-wndv9p7g.js → cli-0731hvbw.js} +6 -6
- package/dist/{cli-d2pzqej1.js → cli-0bxqnvq9.js} +719 -7
- package/dist/{cli-jkt52mgt.js → cli-0gey5fzy.js} +2 -2
- package/dist/{cli-1yekbyqm.js → cli-1338796d.js} +205 -48
- package/dist/{cli-1s1dqn1j.js → cli-1ayk1eas.js} +171 -123
- 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-kvtc7aez.js → cli-3h4nhjn8.js} +42 -14
- package/dist/{cli-655xm9ms.js → cli-583frbyr.js} +1 -1
- package/dist/{cli-4r12t6w6.js → cli-5c4hjzne.js} +101 -4
- package/dist/{cli-az0bpf8p.js → cli-5pag6rmn.js} +3 -3
- package/dist/{cli-36g23z21.js → cli-6s7vdcta.js} +4 -4
- package/dist/{cli-rtk9g7av.js → cli-73jzwksr.js} +6 -2
- package/dist/{cli-jg0nfda6.js → cli-75gkpjvw.js} +52 -21
- package/dist/{cli-a81qevyg.js → cli-7a42nfy0.js} +20 -36
- package/dist/{cli-0sbkss7h.js → cli-7wcyk2s5.js} +2 -2
- package/dist/{cli-41z8xwj0.js → cli-7wspm5gm.js} +14 -4
- package/dist/{cli-byqfbnjw.js → cli-82ae98pz.js} +9 -9
- package/dist/{cli-bq2pcphn.js → cli-8hs41zy2.js} +1 -1
- package/dist/{cli-vqgnyh76.js → cli-98rw5dt5.js} +15 -3
- package/dist/{cli-9cctx1tn.js → cli-a22h291q.js} +1 -1
- package/dist/{cli-j4kt1mpb.js → cli-a47xy8eq.js} +3 -3
- package/dist/{cli-ycm6180s.js → cli-a7v854rf.js} +5 -3
- package/dist/{cli-98pke4vk.js → cli-ae7mrdf9.js} +2 -2
- package/dist/{cli-ahd90bwt.js → cli-afrrppsw.js} +6 -6
- package/dist/{cli-8yy525ad.js → cli-bhjt02p8.js} +2 -2
- package/dist/{cli-v1yw3wha.js → cli-bnz7a7qf.js} +4 -4
- package/dist/{cli-j4kw3rsx.js → cli-cm5yqn35.js} +18 -0
- package/dist/{cli-h55y2bjc.js → cli-dhar9qay.js} +2 -2
- package/dist/{cli-5gr1g711.js → cli-fqzx7gwa.js} +2 -2
- 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-mvckssyq.js → cli-g61qxmge.js} +5 -5
- package/dist/{cli-aq58fk5m.js → cli-g83s9ewh.js} +7 -1
- package/dist/{cli-224epwwf.js → cli-hn9hxmz9.js} +3 -3
- package/dist/{cli-5hjced2v.js → cli-j3jvnpe8.js} +6 -6
- package/dist/{cli-dae4sbgy.js → cli-jm2vfpfd.js} +2 -2
- package/dist/{cli-zn15c46z.js → cli-mjzynrse.js} +1 -1
- package/dist/{cli-a1fhj4n9.js → cli-mmwm7zbf.js} +2 -2
- package/dist/{cli-cq1pf336.js → cli-mry0qzym.js} +5 -5
- package/dist/{cli-bwty35gx.js → cli-mthwvjre.js} +1 -1
- package/dist/{cli-s0exq74r.js → cli-nmbmy64z.js} +3 -3
- package/dist/{cli-wcmnan2g.js → cli-nn4d8dne.js} +5 -5
- package/dist/{cli-jg4p5t36.js → cli-nzxr556s.js} +121 -51
- package/dist/{cli-bcz074zx.js → cli-p599f1qm.js} +1 -1
- package/dist/{cli-k76tgq03.js → cli-pywf3eh5.js} +2 -2
- package/dist/{cli-wk9md5px.js → cli-qxnf8z2z.js} +2 -2
- package/dist/{cli-je8yeyc5.js → cli-rhw40jgb.js} +5 -5
- package/dist/{cli-avgcz4ra.js → cli-rm6hqj3z.js} +406 -35
- package/dist/{cli-0fgr1w9b.js → cli-rpm2sgv0.js} +2 -2
- package/dist/{cli-t282jgab.js → cli-rqkxrmgw.js} +12 -12
- package/dist/{cli-2qpvmmkw.js → cli-s0qnd4wv.js} +1 -1
- package/dist/{cli-pwyevs29.js → cli-spknvxbq.js} +22 -21
- package/dist/{cli-9y354g3j.js → cli-srye3vdt.js} +1 -1
- package/dist/{cli-pe09p6dv.js → cli-tqgg40ek.js} +1 -1
- package/dist/{cli-f255yaqn.js → cli-vbyfwh92.js} +3 -3
- package/dist/{cli-gcbdjt47.js → cli-vfym7wmz.js} +6 -6
- package/dist/{cli-edmm9d9y.js → cli-vqypxzh4.js} +7 -7
- package/dist/{cli-1gm8jcyr.js → cli-wvaawacd.js} +13 -13
- package/dist/{cli-eny54typ.js → cli-wx3rhnm0.js} +1 -1
- package/dist/{cli-b1afgtj3.js → cli-xbkdm710.js} +3 -3
- package/dist/{cli-mc97xy2s.js → cli-xtjhfmpc.js} +16 -4
- package/dist/{cli-eqx6k993.js → cli-y24sc29k.js} +25 -13
- package/dist/{cli-ktmvb20s.js → cli-z6jkx23q.js} +12 -12
- package/dist/{cli-7n5sbw0z.js → cli-z8eh3ex8.js} +5 -5
- package/dist/cli-zh3fp8v2.js +151 -0
- package/dist/{cli-13jvmew3.js → cli-zppwg9p9.js} +7 -7
- package/dist/cli.js +18 -18
- package/dist/{codex-adapter-g0gctw29.js → codex-adapter-q6k3gnyg.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-3mwmk3d9.js} +6 -6
- package/dist/{commands-1p07cvff.js → commands-evrenjn9.js} +2 -2
- package/dist/{controller-z0s7sv6r.js → controller-xjp8vswh.js} +4 -4
- package/dist/{controllers-connect-x9kvvhyk.js → controllers-connect-pp5jnxvc.js} +20 -20
- package/dist/{cursor-adapter-m5b98pvk.js → cursor-adapter-yf47w4t0.js} +2 -2
- package/dist/{db-rsh04kh3.js → db-rgfsh91f.js} +2 -2
- package/dist/{db-capk2raj.js → db-zxpw6aht.js} +3 -3
- package/dist/{devin-adapter-yxm87fe9.js → devin-adapter-r9phbnc9.js} +1 -1
- package/dist/{dispatcher-304q6fwa.js → dispatcher-smasgws7.js} +17 -17
- package/dist/{docker-7rxt0rry.js → docker-rrjhaqt9.js} +2 -2
- package/dist/{dsh-adapter-btewem0y.js → dsh-adapter-ftyy8dnp.js} +2 -2
- package/dist/{e2b-renc0k8q.js → e2b-qcaj675y.js} +2 -2
- package/dist/{e2b-7fc9b9bd.js → e2b-w1p0bnpm.js} +2 -2
- package/dist/{easter-egg-3d-fm1p3n4d.js → easter-egg-3d-yyts7p16.js} +1 -1
- package/dist/{embedding-space-s68v49ct.js → embedding-space-1z0mbg95.js} +6 -6
- package/dist/{embeddings-4ms3604w.js → embeddings-ygeg7tm7.js} +1 -1
- package/dist/{event-bus-dfjx2qdh.js → event-bus-g0ka3pbw.js} +1 -1
- package/dist/{execute-mcdgccjs.js → execute-14qhqp4d.js} +1 -1
- package/dist/{external-channel-9dxwfztn.js → external-channel-ttgrs3sx.js} +2 -2
- package/dist/fx-adapter-gt6pncj2.js +18 -0
- package/dist/{grok-adapter-05ecv4h0.js → grok-adapter-sm4m5k4g.js} +8 -8
- package/dist/handlers-vwmba1m2.js +31 -0
- package/dist/{heartbeat-3n94heh3.js → heartbeat-qahz4p62.js} +20 -20
- package/dist/{hook-zxwje86c.js → hook-4fd5wq5w.js} +4 -4
- package/dist/{hook-aadrn69x.js → hook-kj3d398m.js} +6 -6
- package/dist/{http-xzxk9mst.js → http-1ted0j1k.js} +100 -64
- package/dist/index-5r71rf98.js +32 -0
- package/dist/{index-d34f6r87.js → index-6rq3r5zb.js} +1 -1
- package/dist/{index-9j4zwcht.js → index-7v51awbn.js} +158 -672
- package/dist/index-anmwxs87.js +29 -0
- package/dist/{index-qeb82z37.js → index-q4b917zs.js} +1 -1
- package/dist/index-spp0ra5c.js +36 -0
- package/dist/{keepalive-w4aakbyk.js → keepalive-6cf311t5.js} +4 -4
- package/dist/lead-fjy418cw.js +69 -0
- package/dist/{maintenance-hbptpk6p.js → maintenance-a4bm1bmt.js} +1 -1
- package/dist/{oauth-refresh-sweep-217semrv.js → oauth-refresh-sweep-3h0dfw1k.js} +3 -3
- package/dist/{onboard-vz2s5df3.js → onboard-myn5hraa.js} +3 -3
- package/dist/{opencode-adapter-gtfd7d7v.js → opencode-adapter-p2nkb5am.js} +5 -5
- package/dist/{otel-impl-aefave37.js → otel-impl-a1a0ef6b.js} +2 -2
- package/dist/{pi-mono-adapter-f8h5yseh.js → pi-mono-adapter-rzmpb72a.js} +117 -23
- package/dist/{pricing-refresh-m1yhh9ya.js → pricing-refresh-83drrnxj.js} +3 -3
- package/dist/{rbac-roles-svj72cdf.js → rbac-roles-0fb6e7pj.js} +2 -2
- package/dist/{rbac-roles-rrw8twdz.js → rbac-roles-s8y6ecb5.js} +1 -1
- package/dist/{render-v2-amwbnfxh.js → render-v2-twmbgrz4.js} +3 -3
- package/dist/{resume-9cd6wf6z.js → resume-zfqc36xj.js} +7 -7
- package/dist/{runtime-fp4vrx7m.js → runtime-qwggdpqr.js} +1 -1
- package/dist/{session-access-21t6yjyg.js → session-access-sc9pn9da.js} +4 -4
- package/dist/{session-source-backfill-pxbzcsc6.js → session-source-backfill-qh07xr8g.js} +3 -3
- package/dist/{setup-4592tqz4.js → setup-4fr0m7a1.js} +1 -1
- package/dist/{virtual-modules-gzfnpmnz.js → virtual-modules-fh4ha15b.js} +2 -2
- package/dist/{work-objects-t9tcf174.js → work-objects-0wr8zxct.js} +20 -20
- package/dist/worker-0c5tyzqe.js +69 -0
- package/dist/{worker-follow-up-q32jh4yk.js → worker-follow-up-ktyyazyp.js} +17 -17
- package/dist/{x-b6wxntdt.js → x-vmmye1kb.js} +2 -2
- package/openapi.json +97 -32
- package/package.json +1 -1
- 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.ts +8 -4
- package/src/be/migrations/259_session_summaries_open_tasks.sql +111 -0
- package/src/be/model-validation.ts +5 -1
- package/src/be/pricing-normalize.ts +7 -2
- package/src/be/scripts/typecheck.ts +1 -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/steering.ts +50 -3
- package/src/be/swarm-config-guard.ts +1 -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/mcp-bridge.ts +4 -0
- 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/scheduler/scheduler.ts +36 -3
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +5 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +33 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +5 -0
- package/src/server.ts +7 -0
- package/src/slack/assistant.ts +17 -1
- package/src/slack/handlers.ts +18 -5
- package/src/slack/left-threads.ts +65 -0
- package/src/slack/steering.ts +3 -0
- package/src/slack/thread-buffer.ts +40 -16
- 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/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/explicit-model-validation.test.ts +99 -0
- 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/fx-adapter.test.ts +771 -0
- package/src/tests/grok-adapter.test.ts +36 -0
- package/src/tests/mcp-loopback-proxy.test.ts +193 -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 +3 -2
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/seed-model-profiles.test.ts +2 -2
- package/src/tests/seed-schedules.test.ts +138 -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 +431 -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/slack-leave-thread.ts +104 -0
- package/src/tools/steer-task.ts +3 -2
- package/src/tools/tool-config.ts +2 -1
- package/src/types.ts +19 -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/swarm-docs/content.md +1 -0
- package/templates/workflows/dreaming/content.md +5 -1
- package/dist/acp-adapter-96dc64m2.js +0 -15
- 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
|
@@ -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");
|