@desplega.ai/agent-swarm 2.5.4 → 2.6.0
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 +2 -2
- package/dist/{acp-adapter-ek38cwsv.js → acp-adapter-dgfakfpw.js} +9 -9
- package/dist/{actions-92mepq03.js → actions-fbqma8aa.js} +21 -21
- package/dist/{amp-adapter-9gaxg015.js → amp-adapter-en0jy6hs.js} +4 -4
- package/dist/{app-ctn8gsgx.js → app-k8a5cgpp.js} +3 -3
- package/dist/{assistant-zyr5kz47.js → assistant-a16433gr.js} +29 -25
- package/dist/{boot-reembed-dwzfttdr.js → boot-reembed-6c1sjfnr.js} +1 -1
- package/dist/{boot-reembed-w75maga2.js → boot-reembed-f4nn561n.js} +1 -1
- package/dist/{boot-scrub-sweep-e9kq43kf.js → boot-scrub-sweep-d4w9gajf.js} +4 -4
- package/dist/{boot-seeds-x28v3fhb.js → boot-seeds-9bmkspyw.js} +4 -4
- package/dist/{claude-adapter-f0afqnne.js → claude-adapter-nys1kjxa.js} +9 -9
- package/dist/{claude-managed-adapter-x81a7r65.js → claude-managed-adapter-55207973.js} +2 -2
- package/dist/{claude-managed-setup-bqzwjd9x.js → claude-managed-setup-3cm35gey.js} +10 -3
- package/dist/{claude-sdk-session-7b7whkaq.js → claude-sdk-session-nw5yd8cp.js} +11 -11
- package/dist/{cli-c61mbdds.js → cli-03gg9pd1.js} +4 -4
- package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
- package/dist/cli-1tem6ns9.js +894 -0
- package/dist/{cli-jbnn4vxb.js → cli-24j1jeeb.js} +3 -3
- package/dist/{cli-pzatvw94.js → cli-2sjhpsns.js} +2 -2
- package/dist/{cli-dew6x255.js → cli-39aet655.js} +2 -2
- package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
- package/dist/{cli-x0f2v69c.js → cli-3aqd4jaa.js} +12 -7
- package/dist/{cli-gbfxp48k.js → cli-3fwqy71j.js} +15 -15
- package/dist/{cli-7p4jxgtt.js → cli-3gfvmzht.js} +6 -6
- package/dist/{cli-03vpq45j.js → cli-3ndmpxxv.js} +2 -2
- package/dist/{cli-asq5ydm4.js → cli-4hxrf0p4.js} +131 -50
- package/dist/{cli-mz9afxxf.js → cli-4rf0f5vg.js} +15 -3
- package/dist/{cli-k4tjsdx9.js → cli-5rj83bam.js} +272 -195
- package/dist/{cli-sbwsxvtd.js → cli-5sckpqcx.js} +2 -2
- package/dist/{cli-cz5nmdxt.js → cli-683y0b3a.js} +2 -2
- package/dist/{cli-ethgy7ha.js → cli-6y51ryva.js} +2 -2
- package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
- package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
- package/dist/{cli-gx4rm0qx.js → cli-7enjwya9.js} +1 -1
- package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
- package/dist/{cli-hh5we352.js → cli-9abdgn5t.js} +7 -7
- package/dist/{cli-ff1tm9n9.js → cli-a2rr0fmg.js} +1 -1
- package/dist/{cli-c952c7ac.js → cli-a34pr3w1.js} +2 -2
- package/dist/{cli-s682zgqz.js → cli-abkm71vr.js} +1 -1
- package/dist/{cli-mmhj8vd7.js → cli-ag2yrnt5.js} +11 -2
- package/dist/{cli-bvpgr9ny.js → cli-aj8djyqt.js} +5 -5
- package/dist/{cli-gwhjmh8g.js → cli-art7c1zh.js} +5 -5
- package/dist/{cli-5bnsc0e1.js → cli-az25fn5f.js} +4 -4
- package/dist/{cli-9s05422w.js → cli-bqztfyve.js} +1 -1
- package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
- package/dist/{cli-1r5ad7j5.js → cli-c2habvsf.js} +12 -12
- package/dist/{cli-64mh9mf3.js → cli-ca6banwz.js} +69 -17
- package/dist/{cli-25n3mv5d.js → cli-cz37jqss.js} +5 -5
- package/dist/{cli-6n8axjv1.js → cli-d2dpf95n.js} +2 -2
- package/dist/{cli-gn5af0x5.js → cli-emhjcj2y.js} +11 -11
- package/dist/{cli-6a1xg1p7.js → cli-esakngh8.js} +2 -2
- package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
- package/dist/{cli-4b2z6n0m.js → cli-ffvsj2qw.js} +17 -8
- package/dist/{cli-abs8ndtr.js → cli-g20jjwt4.js} +26 -26
- package/dist/{cli-jb30dzvk.js → cli-g8ka5j4j.js} +446 -164
- package/dist/{cli-19202kew.js → cli-gf95q3bm.js} +5 -5
- package/dist/{cli-eftzyh9y.js → cli-gnsb1zxa.js} +2 -2
- package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
- package/dist/{cli-e42vss7b.js → cli-hfeb68z4.js} +5 -5
- package/dist/{cli-g9vd9ece.js → cli-k21gx5ht.js} +14 -16
- package/dist/{cli-tsma7p8v.js → cli-kf4jree9.js} +2 -2
- package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
- package/dist/{cli-wx2nj19m.js → cli-m5a9m0ak.js} +6 -6
- package/dist/{cli-621ce7kw.js → cli-m70bajzr.js} +3 -3
- package/dist/{cli-9j129brq.js → cli-mtnj4fwa.js} +6 -6
- package/dist/{cli-xzfq4at3.js → cli-mvjz5an3.js} +15 -11
- package/dist/{cli-277c31dw.js → cli-n7asmce6.js} +225 -3
- package/dist/{cli-xtrz6bzs.js → cli-nn6vx2mq.js} +1 -1
- package/dist/{cli-2655k16q.js → cli-nq677csy.js} +1 -1
- package/dist/{cli-0shx9j93.js → cli-pfk92eqz.js} +2 -2
- package/dist/{cli-wghjkr0e.js → cli-sqma6vw1.js} +10 -7
- package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
- package/dist/{cli-qqzmjxkf.js → cli-v49fkghp.js} +11 -6
- package/dist/{cli-x77gen2p.js → cli-w2656ka1.js} +7 -7
- package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
- package/dist/{cli-m4ecdxvz.js → cli-zqne1qcj.js} +7 -7
- package/dist/cli.js +17 -17
- package/dist/{codex-adapter-wch12q99.js → codex-adapter-d95q4qsn.js} +7 -7
- package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
- package/dist/{codex-session-runner-41pfcr95.js → codex-session-runner-gdwd8h0c.js} +8 -8
- package/dist/{commands-9849dkj6.js → commands-jqmgk54b.js} +2 -2
- package/dist/{controller-ja0sd95p.js → controller-z3jq589f.js} +4 -4
- package/dist/{controllers-connect-xp05dj6x.js → controllers-connect-r2za39ef.js} +23 -23
- package/dist/{cursor-adapter-623qxqew.js → cursor-adapter-4mr4jgkw.js} +2 -2
- package/dist/{db-47vj40dy.js → db-ppv2tze7.js} +3 -3
- package/dist/{db-w759qmx0.js → db-w36wwb5g.js} +4 -2
- package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
- package/dist/{dispatcher-7b8e4z2j.js → dispatcher-pv5h77vf.js} +20 -20
- package/dist/{docker-s84hjs91.js → docker-fdzyewpz.js} +2 -2
- package/dist/{dsh-adapter-2ezw3xea.js → dsh-adapter-d4pdpb3r.js} +2 -2
- package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
- package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
- package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-4sp4dvmc.js} +1 -1
- package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
- package/dist/{execute-0xpz8950.js → execute-jev839y1.js} +1 -1
- package/dist/{external-channel-e2s028da.js → external-channel-y6nkzhnn.js} +2 -2
- package/dist/{grok-adapter-ys7gaqee.js → grok-adapter-t08b9ntm.js} +10 -10
- package/dist/handlers-eqsnmv95.js +41 -0
- package/dist/{heartbeat-w3mntghj.js → heartbeat-vn31xwdk.js} +23 -23
- package/dist/{hook-g7kvv6x9.js → hook-6d0zbvvt.js} +6 -6
- package/dist/{hook-3jjywnpb.js → hook-cpjs8q51.js} +8 -8
- package/dist/{http-tkp7d4kb.js → http-9c6medda.js} +856 -99
- package/dist/{index-xdaahthb.js → index-38t35h0n.js} +20 -20
- package/dist/{index-3n8m9ea2.js → index-g6k9tctj.js} +1 -1
- package/dist/{index-1ef1myq2.js → index-k0yt1tvq.js} +20 -20
- package/dist/{index-31ceyf42.js → index-pkma1yf8.js} +1 -1
- package/dist/{index-q7m26kvs.js → index-qhjf2htm.js} +13 -10
- package/dist/index-yqgbfgaj.js +43 -0
- package/dist/{keepalive-v4ta8wgg.js → keepalive-em0117bp.js} +4 -4
- package/dist/lead-23e7z59t.js +79 -0
- package/dist/{maintenance-84n1ex21.js → maintenance-kbme3qy9.js} +1 -1
- package/dist/{oauth-refresh-sweep-vjnnandw.js → oauth-refresh-sweep-xysj4yse.js} +3 -3
- package/dist/{onboard-sfmpe4np.js → onboard-p6q0vz97.js} +3 -3
- package/dist/{opencode-adapter-jmpst4q4.js → opencode-adapter-esv3k9yn.js} +7 -7
- package/dist/{otel-impl-v6cc0zve.js → otel-impl-54qhqh1z.js} +2 -2
- package/dist/{pi-mono-adapter-fk8fkcf6.js → pi-mono-adapter-mymbwhj0.js} +10 -10
- package/dist/{pricing-refresh-fsk1e1zc.js → pricing-refresh-yb75wv33.js} +3 -3
- package/dist/{rbac-roles-prmkdnkw.js → rbac-roles-1vp2mw4p.js} +2 -2
- package/dist/{rbac-roles-zxmxqa39.js → rbac-roles-dexhc9x8.js} +1 -1
- package/dist/render-v2-tjay5hhf.js +16 -0
- package/dist/{resume-frdtgfa0.js → resume-8ayf37g5.js} +9 -9
- package/dist/{runtime-bt5pkpm1.js → runtime-hqe5cqcd.js} +1 -1
- package/dist/{session-access-fpez4y5q.js → session-access-fdhp987s.js} +4 -4
- package/dist/session-source-backfill-qgtc86z1.js +110 -0
- package/dist/{setup-mq6m3m8q.js → setup-4rrmqcq3.js} +1 -1
- package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-52km00d6.js} +2 -2
- package/dist/{work-objects-kfahhjsq.js → work-objects-p28fgd70.js} +23 -23
- package/dist/worker-e3w7vye1.js +79 -0
- package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
- package/openapi.json +1274 -32
- package/package.json +1 -1
- package/src/assets/source-key.ts +95 -0
- package/src/be/db/api-keys.ts +80 -0
- package/src/be/db/session-labels.ts +28 -0
- package/src/be/db/session-moves.ts +5 -6
- package/src/be/db/session-tree.ts +153 -14
- package/src/be/db/tasks/read.ts +11 -0
- package/src/be/db/tasks/write.ts +6 -1
- package/src/be/db/workflow-runs.ts +183 -1
- package/src/be/db-retention.ts +61 -6
- package/src/be/db.ts +59 -114
- package/src/be/memory/providers/openai-embedding.ts +5 -1
- package/src/be/migrations/244_api_key_validation.sql +4 -0
- package/src/be/onboarding.ts +4 -0
- package/src/be/scripts/typecheck.ts +1 -1
- package/src/be/session-source-backfill.ts +163 -0
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/comb/review-batch.ts +28 -1
- package/src/comb/templates.ts +7 -2
- package/src/commands/claude-managed-setup.ts +7 -1
- package/src/commands/credential-refresh.ts +76 -1
- package/src/commands/credential-wait.ts +2 -2
- package/src/commands/provider-credentials.ts +210 -170
- package/src/commands/runner.ts +25 -4
- package/src/extensions/apply-task-create.ts +8 -1
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +5 -0
- package/src/http/all-routes.ts +2 -0
- package/src/http/api-keys.ts +53 -0
- package/src/http/assets.ts +15 -0
- package/src/http/codex-oauth-accounts.ts +191 -0
- package/src/http/comb.ts +15 -2
- package/src/http/config.ts +1 -1
- package/src/http/core.ts +6 -0
- package/src/http/fleet-benchmark.ts +5 -2
- package/src/http/index.ts +11 -0
- package/src/http/integrations.ts +5 -1
- package/src/http/kv.ts +8 -3
- package/src/http/onboarding.ts +6 -1
- package/src/http/pool-keys.ts +354 -0
- package/src/http/sessions.ts +37 -12
- package/src/http/stats.ts +1 -0
- package/src/http/status.ts +26 -12
- package/src/http/tasks.ts +3 -0
- package/src/http/used-by.ts +7 -2
- package/src/http/workflows.ts +78 -9
- package/src/integrations/chat-sdk/inbound.ts +27 -0
- package/src/kv-reserved-namespaces.ts +19 -0
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +5 -1
- package/src/providers/claude-managed-adapter.ts +16 -9
- package/src/providers/codex-adapter.ts +71 -11
- package/src/scheduler/scheduler.ts +3 -2
- package/src/scripts-runtime/swarm-sdk.ts +7 -1
- package/src/scripts-runtime/types/stdlib.d.ts +1 -1
- package/src/scripts-runtime/types/swarm-extension.d.ts +17 -8
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -1
- package/src/slack/assistant.ts +7 -0
- package/src/slack/channel-labels.ts +74 -0
- package/src/slack/handlers.ts +8 -0
- package/src/slack/inbound-files.ts +5 -2
- package/src/slack/thread-buffer.ts +4 -0
- package/src/tasks/sibling-awareness.ts +12 -3
- package/src/telemetry-context.ts +5 -6
- package/src/telemetry-identity.ts +3 -3
- package/src/tests/api-keys-validation.test.ts +170 -0
- package/src/tests/claude-managed-adapter.test.ts +46 -0
- package/src/tests/claude-routes-e2e.test.ts +7 -2
- package/src/tests/codex-credential-pools.test.ts +120 -0
- package/src/tests/codex-gateway-route.test.ts +165 -0
- package/src/tests/codex-oauth-accounts.test.ts +357 -0
- package/src/tests/comb-review-batch.test.ts +63 -2
- package/src/tests/config-pool-keys.test.ts +533 -0
- package/src/tests/credential-check.test.ts +290 -2
- package/src/tests/credential-refresh.test.ts +172 -1
- package/src/tests/db-retention.test.ts +9 -3
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -3
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/extensions-pre-task.test.ts +53 -1
- package/src/tests/fixtures/extensions/record-task-create/hooks.ts +9 -0
- package/src/tests/fixtures/extensions/record-task-create/manifest.json +7 -0
- package/src/tests/fleet-benchmark.test.ts +8 -0
- package/src/tests/get-workflow-run-tool.test.ts +98 -0
- package/src/tests/integrations-http.test.ts +32 -2
- package/src/tests/internal-ai/complete-structured.test.ts +37 -1
- package/src/tests/internal-ai/credentials.test.ts +23 -0
- package/src/tests/kv-http.test.ts +32 -0
- package/src/tests/kv-tool.test.ts +25 -0
- package/src/tests/multi-runtime-registration.test.ts +12 -12
- package/src/tests/onboarding.test.ts +108 -1
- package/src/tests/openai-embedding-credentials.test.ts +35 -0
- package/src/tests/openrouter-attribution-call-sites.test.ts +2 -2
- package/src/tests/org-website.test.ts +36 -0
- package/src/tests/provider-key-probes.test.ts +181 -0
- package/src/tests/rbac-e2e-helpers.test.ts +88 -1
- package/src/tests/rbac-e2e-helpers.ts +36 -11
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/script-runs-retention.test.ts +212 -0
- package/src/tests/sdk-allowlist.test.ts +39 -0
- package/src/tests/session-source-backfill.test.ts +429 -0
- package/src/tests/session-source-keys.test.ts +236 -0
- package/src/tests/session-source-labels.test.ts +419 -0
- package/src/tests/sessions-detail-batch.test.ts +2 -0
- package/src/tests/sessions-move.test.ts +131 -0
- package/src/tests/sessions-tree.test.ts +299 -2
- package/src/tests/slack-inbound-files.test.ts +103 -23
- package/src/tests/status.test.ts +101 -18
- package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
- package/src/tests/test-net.ts +31 -0
- package/src/tests/transcription.test.ts +8 -0
- package/src/tests/used-by-route.test.ts +11 -0
- package/src/tests/workflow-cascade-cancel.test.ts +172 -0
- package/src/tests/workflow-http-v2.test.ts +131 -0
- package/src/tests/workflow-step-order.test.ts +92 -0
- package/src/tests/workflow-sub-workflow.test.ts +30 -0
- package/src/tools/get-tasks.ts +8 -0
- package/src/tools/kv/kv-read-auth.ts +4 -2
- package/src/tools/task-action.ts +1 -1
- package/src/tools/workflows/get-workflow-run.ts +14 -4
- package/src/types.ts +24 -0
- package/src/utils/company-profile.ts +51 -1
- package/src/utils/credentials.ts +5 -2
- package/src/utils/internal-ai/complete-structured.ts +5 -0
- package/src/utils/internal-ai/credentials.ts +4 -3
- package/src/utils/provider-key-probes.ts +328 -0
- package/src/utils/transcription.ts +4 -1
- package/src/workflows/executors/sub-workflow.ts +8 -1
- package/templates/ai-toolbox.manifest.json +2 -2
- package/templates/skills/researching/content.md +2 -0
- package/templates/skills/swarm-scripts/SKILL.md +1 -0
- package/templates/skills/swarm-scripts/content.md +1 -0
- package/dist/cli-7421jzsw.js +0 -84
- package/dist/cli-e45e4m6b.js +0 -413
- package/dist/cli-zw88zjba.js +0 -307
- package/dist/handlers-wy1dr6ja.js +0 -41
- package/dist/index-nmm79qgr.js +0 -43
- package/dist/lead-sawnyqxk.js +0 -79
- package/dist/render-v2-pjwxvnkw.js +0 -12
- package/dist/worker-v5e5rxj3.js +0 -79
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
2
|
import { createServer, type Server } from "node:http";
|
|
3
3
|
import { keyPrefixRange, sessionGroupKey } from "../assets/key";
|
|
4
|
+
import {
|
|
5
|
+
isSourceThreadKey,
|
|
6
|
+
NESTED_THREAD_MIN_ROOTS,
|
|
7
|
+
sessionThreadGroupKey,
|
|
8
|
+
} from "../assets/source-key";
|
|
4
9
|
import {
|
|
5
10
|
closeDb,
|
|
6
11
|
countSessions,
|
|
@@ -14,10 +19,12 @@ import {
|
|
|
14
19
|
listRecentSessions,
|
|
15
20
|
upsertInboxState,
|
|
16
21
|
} from "../be/db";
|
|
17
|
-
import {
|
|
22
|
+
import { rememberSessionLabel } from "../be/db/session-labels";
|
|
23
|
+
import { getSessionTree, type SessionFilters, threadRootsSql } from "../be/db/session-tree";
|
|
18
24
|
import { setSessionShares } from "../be/session-shares";
|
|
19
25
|
import { handleSessions } from "../http/sessions";
|
|
20
26
|
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
27
|
+
import { chatSdkContextKey } from "../tasks/context-key";
|
|
21
28
|
import { beginRequestAuthScope, setRequestAuth } from "../utils/request-auth-context";
|
|
22
29
|
import { listenOnFreePort } from "./test-net";
|
|
23
30
|
|
|
@@ -65,6 +72,7 @@ beforeEach(async () => {
|
|
|
65
72
|
await getDbClient().run("DELETE FROM inbox_item_state");
|
|
66
73
|
await getDbClient().run("DELETE FROM session_shares");
|
|
67
74
|
await getDbClient().run("DELETE FROM agent_tasks");
|
|
75
|
+
await getDbClient().run("DELETE FROM kv_entries WHERE namespace = 'session-labels'");
|
|
68
76
|
});
|
|
69
77
|
afterAll(async () => {
|
|
70
78
|
server.closeAllConnections();
|
|
@@ -170,7 +178,9 @@ describe("session groups and prefix bounds", () => {
|
|
|
170
178
|
const query = spyOn(client, "query");
|
|
171
179
|
let call: (typeof query.mock.calls)[number];
|
|
172
180
|
try {
|
|
173
|
-
await listRecentSessions(
|
|
181
|
+
await listRecentSessions(
|
|
182
|
+
filters({ groupKey: "shared/a/", showSource: ["slack"], hideSource: ["ui"] }),
|
|
183
|
+
);
|
|
174
184
|
call = query.mock.calls[0]!;
|
|
175
185
|
} finally {
|
|
176
186
|
query.mockRestore();
|
|
@@ -191,6 +201,232 @@ describe("session groups and prefix bounds", () => {
|
|
|
191
201
|
});
|
|
192
202
|
});
|
|
193
203
|
|
|
204
|
+
describe("nested source thread groups", () => {
|
|
205
|
+
const channel = "shared/slack/c1/";
|
|
206
|
+
const thread = `${channel}thread:1.2/`;
|
|
207
|
+
const detailGroupKey = async (id: string) => {
|
|
208
|
+
const res = await fetch(`${baseUrl}/api/sessions/${id}`, {
|
|
209
|
+
headers: { "x-swarm-user-id": owner.id },
|
|
210
|
+
});
|
|
211
|
+
expect(res.status).toBe(200);
|
|
212
|
+
return ((await res.json()) as { groupKey: string }).groupKey;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
test("two owned roots nest, lists agree, and a move collapses the group", async () => {
|
|
216
|
+
const first = await task(thread, { source: "slack" });
|
|
217
|
+
const second = await task(thread, { source: "mcp" }, NOW);
|
|
218
|
+
await getDbClient().run("UPDATE agent_tasks SET createdAt = ?, title = ? WHERE id = ?", [
|
|
219
|
+
OLD,
|
|
220
|
+
"First conversation",
|
|
221
|
+
first.id,
|
|
222
|
+
]);
|
|
223
|
+
await getDbClient().run("UPDATE agent_tasks SET status = 'in_progress' WHERE id = ?", [
|
|
224
|
+
second.id,
|
|
225
|
+
]);
|
|
226
|
+
await pending(second.id);
|
|
227
|
+
const single = await task(`${channel}thread:3/`);
|
|
228
|
+
const tree = await getSessionTree(actor(), NOW);
|
|
229
|
+
expect(tree.groups).toEqual([
|
|
230
|
+
{ key: channel, lastActivityAt: RECENT, total: 1, unread: 1, running: 0, needsYou: 0 },
|
|
231
|
+
{ key: thread, lastActivityAt: NOW, total: 2, unread: 2, running: 1, needsYou: 1 },
|
|
232
|
+
]);
|
|
233
|
+
expect(tree.threadLabels).toEqual({ [thread]: "First conversation" });
|
|
234
|
+
expect(tree.sourceGroups).toEqual([channel, thread]);
|
|
235
|
+
expect(await ids({ groupKey: channel })).toEqual([single.id]);
|
|
236
|
+
expect(await ids({ groupKey: thread })).toEqual([first.id, second.id].sort());
|
|
237
|
+
expect(await countSessions(filters({ groupKey: channel }))).toBe(1);
|
|
238
|
+
expect(await countSessions(filters({ groupKey: thread }))).toBe(2);
|
|
239
|
+
const rows = await listRecentSessions(filters());
|
|
240
|
+
expect(rows.find((r) => r.root.id === first.id)?.groupKey).toBe(thread);
|
|
241
|
+
expect(rows.find((r) => r.root.id === single.id)?.groupKey).toBe(channel);
|
|
242
|
+
// The session pane reveals the detail's group: the same rule as the list rows.
|
|
243
|
+
expect(await detailGroupKey(first.id)).toBe(thread);
|
|
244
|
+
expect(await detailGroupKey(single.id)).toBe(channel);
|
|
245
|
+
|
|
246
|
+
const moved = await fetch(`${baseUrl}/api/session-moves`, {
|
|
247
|
+
method: "POST",
|
|
248
|
+
headers: { "x-swarm-user-id": owner.id, "content-type": "application/json" },
|
|
249
|
+
body: JSON.stringify({ rootTaskIds: [second.id], groupKey: "shared/research/" }),
|
|
250
|
+
});
|
|
251
|
+
expect(moved.status).toBe(200);
|
|
252
|
+
expect(
|
|
253
|
+
(await getSessionTree(actor(), NOW)).groups.find((g) => g.key === thread),
|
|
254
|
+
).toBeUndefined();
|
|
255
|
+
expect(await ids({ groupKey: channel })).toEqual([first.id, single.id].sort());
|
|
256
|
+
expect(
|
|
257
|
+
(await listRecentSessions(filters())).find((r) => r.root.id === first.id)?.groupKey,
|
|
258
|
+
).toBe(channel);
|
|
259
|
+
expect(await detailGroupKey(first.id)).toBe(channel);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("one owned root stays a channel row despite children and ownerless roots", async () => {
|
|
263
|
+
const root = await task(thread);
|
|
264
|
+
await task(thread, { parentTaskId: root.id });
|
|
265
|
+
await task(thread, { requestedByUserId: undefined });
|
|
266
|
+
const tree = await getSessionTree(actor(), NOW);
|
|
267
|
+
expect(tree.groups.map((g) => [g.key, g.total])).toEqual([[channel, 1]]);
|
|
268
|
+
expect(tree.threadLabels).toEqual({});
|
|
269
|
+
expect((await listRecentSessions(filters({ tree: true })))[0].groupKey).toBe(channel);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("nesting counts private roots but labels use only roots visible to each user", async () => {
|
|
273
|
+
const visible = await task(thread, { source: "slack" });
|
|
274
|
+
const hidden = await task(thread, { requestedByUserId: other.id });
|
|
275
|
+
await getDbClient().run("UPDATE agent_tasks SET title = ? WHERE id = ?", [
|
|
276
|
+
"Visible title",
|
|
277
|
+
visible.id,
|
|
278
|
+
]);
|
|
279
|
+
await getDbClient().run("UPDATE agent_tasks SET createdAt = ?, title = ? WHERE id = ?", [
|
|
280
|
+
OLD,
|
|
281
|
+
"Hidden oldest",
|
|
282
|
+
hidden.id,
|
|
283
|
+
]);
|
|
284
|
+
const tree = await getSessionTree(actor(), NOW, { mine: true, showSource: ["slack"] });
|
|
285
|
+
expect(tree.groups.map((g) => [g.key, g.total])).toEqual([[thread, 1]]);
|
|
286
|
+
expect(tree.threadLabels[thread]).toBe("Visible title");
|
|
287
|
+
expect(await ids({ groupKey: channel })).toEqual([]);
|
|
288
|
+
expect(await ids({ groupKey: thread })).toEqual([visible.id]);
|
|
289
|
+
const filtered = await getSessionTree(actor(), NOW, { showSource: ["ui"] });
|
|
290
|
+
expect(filtered.groups).toEqual([]);
|
|
291
|
+
expect(filtered.sourceGroups).toEqual([channel, thread]);
|
|
292
|
+
expect(filtered.threadLabels[thread]).toBe("Visible title");
|
|
293
|
+
const otherTree = await getSessionTree({ kind: "user", userId: other.id }, NOW);
|
|
294
|
+
expect(otherTree.groups.map((g) => [g.key, g.total])).toEqual([[thread, 1]]);
|
|
295
|
+
expect(otherTree.threadLabels[thread]).toBe("Hidden oldest");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("the route merges title fallbacks and cached labels, including hidden thread groups", async () => {
|
|
299
|
+
const root = await task(thread);
|
|
300
|
+
await task(thread);
|
|
301
|
+
const text = "Oldest task text. ".repeat(8);
|
|
302
|
+
await getDbClient().run(
|
|
303
|
+
"UPDATE agent_tasks SET task = ?, title = '', createdAt = ? WHERE id = ?",
|
|
304
|
+
[text, OLD, root.id],
|
|
305
|
+
);
|
|
306
|
+
const headers = { "x-swarm-user-id": owner.id };
|
|
307
|
+
const read = async () => {
|
|
308
|
+
const res = await fetch(`${baseUrl}/api/session-tree?show=all&showSource=ui`, { headers });
|
|
309
|
+
expect(res.status).toBe(200);
|
|
310
|
+
return (await res.json()) as { labels: Record<string, string>; sourceGroups?: string[] };
|
|
311
|
+
};
|
|
312
|
+
await rememberSessionLabel(channel, "#engineering");
|
|
313
|
+
expect((await read()).labels).toEqual({
|
|
314
|
+
[channel]: "#engineering",
|
|
315
|
+
[thread]: text.trim(),
|
|
316
|
+
});
|
|
317
|
+
await rememberSessionLabel(thread, "Cached thread label");
|
|
318
|
+
const result = await read();
|
|
319
|
+
expect(result.labels[thread]).toBe("Cached thread label");
|
|
320
|
+
expect(result.sourceGroups).toBeUndefined();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("a title fallback skips the thread context and keeps the list preview shape", async () => {
|
|
324
|
+
const root = await task(thread);
|
|
325
|
+
await task(thread);
|
|
326
|
+
const label = async (prompt: string) => {
|
|
327
|
+
await getDbClient().run(
|
|
328
|
+
"UPDATE agent_tasks SET task = ?, title = '', createdAt = ? WHERE id = ?",
|
|
329
|
+
[prompt, OLD, root.id],
|
|
330
|
+
);
|
|
331
|
+
return (await getSessionTree(actor(), NOW)).threadLabels[thread];
|
|
332
|
+
};
|
|
333
|
+
const ask = "<@U1|Lena>: Fix the release notes";
|
|
334
|
+
expect(await label(`<thread_context>\nT: earlier message\n</thread_context>\n\n${ask}`)).toBe(
|
|
335
|
+
ask,
|
|
336
|
+
);
|
|
337
|
+
// A Chat SDK prompt puts its header line before the block.
|
|
338
|
+
const chat = await label(
|
|
339
|
+
"[chat] from: Lena\n\n<thread_context>\nLena: hi\n</thread_context>\n\nShip it",
|
|
340
|
+
);
|
|
341
|
+
expect(chat).toStartWith("[chat] from: Lena");
|
|
342
|
+
expect(chat).toEndWith("Ship it");
|
|
343
|
+
expect(chat).not.toContain("Lena: hi");
|
|
344
|
+
// A tag inside a line is text, not a block.
|
|
345
|
+
expect(await label("Explain the <thread_context> tag")).toBe(
|
|
346
|
+
"Explain the <thread_context> tag",
|
|
347
|
+
);
|
|
348
|
+
// 300 characters and "…": the list preview the UI repairs (`isListPreview`).
|
|
349
|
+
expect(await label(`<thread_context>\nT: hi\n</thread_context>\n\n${"a".repeat(400)}`)).toBe(
|
|
350
|
+
`${"a".repeat(300)}…`,
|
|
351
|
+
);
|
|
352
|
+
// A prompt that is only thread context has no label: the UI shows `Thread · <ts>`.
|
|
353
|
+
expect(await label("<thread_context>\nT: hi\n</thread_context>")).toBe("");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test("only source thread leaves nest, including Telegram and Discord", async () => {
|
|
357
|
+
for (const [key, expectedGroup] of [
|
|
358
|
+
["shared/telegram/-100/thread:42/", "shared/telegram/-100/thread:42/"],
|
|
359
|
+
["shared/discord/77/thread:99/", "shared/discord/77/thread:99/"],
|
|
360
|
+
["shared/slack/c1/thread:parent/thread:🚀/", "shared/slack/c1/thread:parent/thread:🚀/"],
|
|
361
|
+
["shared/slack/c1/thread:parent/child/", "shared/slack/c1/thread:parent/child/"],
|
|
362
|
+
["shared/slack/c1/task:leaf/", channel],
|
|
363
|
+
[`personal/${owner.id}/thread:1/`, `personal/${owner.id}/`],
|
|
364
|
+
["shared/notes/thread:1/", "shared/notes/"],
|
|
365
|
+
]) {
|
|
366
|
+
await getDbClient().run("DELETE FROM agent_tasks");
|
|
367
|
+
const root = await task(key);
|
|
368
|
+
await task(key);
|
|
369
|
+
expect(sessionThreadGroupKey(key, NESTED_THREAD_MIN_ROOTS)).toBe(expectedGroup);
|
|
370
|
+
expect(sessionThreadGroupKey(key, NESTED_THREAD_MIN_ROOTS - 1)).toBe(sessionGroupKey(key));
|
|
371
|
+
const threadRoots = threadRootsSql();
|
|
372
|
+
const count = await getDbClient().get<{ roots: number }>(
|
|
373
|
+
`SELECT ${threadRoots.sql} AS roots FROM agent_tasks r WHERE r.id = ?`,
|
|
374
|
+
[...threadRoots.params, root.id],
|
|
375
|
+
);
|
|
376
|
+
expect(count?.roots).toBe(isSourceThreadKey(key) ? NESTED_THREAD_MIN_ROOTS : 0);
|
|
377
|
+
expect((await listRecentSessions(filters({ rootId: root.id })))[0].groupKey).toBe(
|
|
378
|
+
expectedGroup,
|
|
379
|
+
);
|
|
380
|
+
const tree = await getSessionTree(actor(), NOW);
|
|
381
|
+
const noGroup = expectedGroup === `personal/${owner.id}/`;
|
|
382
|
+
expect(tree.noGroup.total).toBe(noGroup ? 2 : 0);
|
|
383
|
+
expect(tree.groups).toEqual(
|
|
384
|
+
noGroup
|
|
385
|
+
? []
|
|
386
|
+
: [
|
|
387
|
+
{
|
|
388
|
+
key: expectedGroup,
|
|
389
|
+
lastActivityAt: RECENT,
|
|
390
|
+
total: 2,
|
|
391
|
+
unread: 2,
|
|
392
|
+
running: 0,
|
|
393
|
+
needsYou: 0,
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test("channel and thread queries retain indexed key lookup without LIKE", async () => {
|
|
401
|
+
await task(thread);
|
|
402
|
+
await task(thread);
|
|
403
|
+
for (const groupKey of [channel, thread]) {
|
|
404
|
+
const client = getDbClient();
|
|
405
|
+
const query = spyOn(client, "query");
|
|
406
|
+
let call: (typeof query.mock.calls)[number];
|
|
407
|
+
try {
|
|
408
|
+
await listRecentSessions(filters({ groupKey }));
|
|
409
|
+
call = query.mock.calls[0]!;
|
|
410
|
+
} finally {
|
|
411
|
+
query.mockRestore();
|
|
412
|
+
}
|
|
413
|
+
const [sql, params] = call;
|
|
414
|
+
const plan = await client.query<{ detail: string }>(`EXPLAIN QUERY PLAN ${sql}`, params);
|
|
415
|
+
expect(plan.some((p) => p.detail.startsWith("SEARCH r USING INDEX"))).toBe(true);
|
|
416
|
+
expect(
|
|
417
|
+
plan.some((p) =>
|
|
418
|
+
p.detail.includes("SEARCH session_thread_root USING INDEX idx_agent_tasks_asset_key"),
|
|
419
|
+
),
|
|
420
|
+
).toBe(true);
|
|
421
|
+
expect(
|
|
422
|
+
plan.some((p) => p.detail === "SCAN r" || p.detail === "SCAN session_thread_root"),
|
|
423
|
+
).toBe(false);
|
|
424
|
+
expect(sql).not.toContain('"key" LIKE');
|
|
425
|
+
expect(sql).not.toContain("INDEXED BY");
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
194
430
|
describe("chain-wide read state", () => {
|
|
195
431
|
test("every CASE branch uses the fixed clock and chain activity", async () => {
|
|
196
432
|
const cases = [
|
|
@@ -740,6 +976,67 @@ describe("source filter and list triage", () => {
|
|
|
740
976
|
body: JSON.stringify(body),
|
|
741
977
|
});
|
|
742
978
|
|
|
979
|
+
test("effective sources agree across lists, tree facets and triage", async () => {
|
|
980
|
+
const slack = await task("shared/slack/c1/thread:1/", { source: "slack" });
|
|
981
|
+
const mcpSlack = await task("shared/slack/c1/thread:2/", { source: "mcp" });
|
|
982
|
+
const telegram = await task("shared/telegram/-100/thread:42/", {
|
|
983
|
+
source: "chat",
|
|
984
|
+
contextKey: chatSdkContextKey("telegram:-100:42"),
|
|
985
|
+
});
|
|
986
|
+
const discord = await task("shared/discord/c1/task:root/", { source: "chat" });
|
|
987
|
+
const ui = await task("shared/", { source: "ui" });
|
|
988
|
+
const mcp = await task("shared/research/", { source: "mcp" });
|
|
989
|
+
const chat = await task("shared/research/", { source: "chat" });
|
|
990
|
+
const facets = (await treeOf("show=all")).sources;
|
|
991
|
+
expect(facets).toEqual([
|
|
992
|
+
{ source: "slack", total: 2 },
|
|
993
|
+
{ source: "chat", total: 1 },
|
|
994
|
+
{ source: "discord", total: 1 },
|
|
995
|
+
{ source: "mcp", total: 1 },
|
|
996
|
+
{ source: "telegram", total: 1 },
|
|
997
|
+
{ source: "ui", total: 1 },
|
|
998
|
+
]);
|
|
999
|
+
const cases = [
|
|
1000
|
+
["slack", [slack.id, mcpSlack.id]],
|
|
1001
|
+
["telegram", [telegram.id]],
|
|
1002
|
+
["discord", [discord.id]],
|
|
1003
|
+
["mcp", [mcp.id]],
|
|
1004
|
+
["ui", [ui.id]],
|
|
1005
|
+
["chat", [chat.id]],
|
|
1006
|
+
] as const;
|
|
1007
|
+
for (const [source, expected] of cases) {
|
|
1008
|
+
const query = `show=all&showSource=${source}`;
|
|
1009
|
+
const list = await listOf(`triage=inbox&${query}`);
|
|
1010
|
+
expect(list.sessions.map((row) => row.root.id).sort()).toEqual([...expected].sort());
|
|
1011
|
+
expect(list.total).toBe(expected.length);
|
|
1012
|
+
const tree = await treeOf(query);
|
|
1013
|
+
expect(tree.inbox.total).toBe(expected.length);
|
|
1014
|
+
expect(tree.sources).toEqual(facets);
|
|
1015
|
+
const res = await triage({
|
|
1016
|
+
action: "done",
|
|
1017
|
+
triage: "inbox",
|
|
1018
|
+
show: "all",
|
|
1019
|
+
showSource: [source],
|
|
1020
|
+
});
|
|
1021
|
+
expect(res.status).toBe(200);
|
|
1022
|
+
expect(await res.json()).toEqual({ updated: expected.length });
|
|
1023
|
+
const done = await listOf("triage=done&show=all");
|
|
1024
|
+
expect(done.sessions.map((row) => row.root.id).sort()).toEqual([...expected].sort());
|
|
1025
|
+
expect((await treeOf(query)).done.total).toBe(expected.length);
|
|
1026
|
+
const reset = await triage({ action: "inbox", triage: "done", show: "all" });
|
|
1027
|
+
expect(await reset.json()).toEqual({ updated: expected.length });
|
|
1028
|
+
}
|
|
1029
|
+
const hidden = await listOf("triage=inbox&show=all&hideSource=slack");
|
|
1030
|
+
expect(hidden.total).toBe(5);
|
|
1031
|
+
expect(hidden.sessions.map((row) => row.root.id)).not.toContain(mcpSlack.id);
|
|
1032
|
+
const hiddenTree = await treeOf("show=all&hideSource=slack");
|
|
1033
|
+
expect(hiddenTree.inbox.total).toBe(5);
|
|
1034
|
+
expect(hiddenTree.sources).toEqual(facets);
|
|
1035
|
+
expect(await ids({ source: ["mcp"] })).toEqual([mcp.id, mcpSlack.id].sort());
|
|
1036
|
+
expect(await ids({ showSource: ["slack"], hideSource: ["slack"] })).toEqual([]);
|
|
1037
|
+
expect((await treeOf("show=all&showSource=slack&hideSource=slack")).inbox.total).toBe(0);
|
|
1038
|
+
});
|
|
1039
|
+
|
|
743
1040
|
test("hideSource leaves a source out of sections and lists, and sources still count it", async () => {
|
|
744
1041
|
const slackA = await task("shared/", { source: "slack" });
|
|
745
1042
|
const slackB = await task("shared/a/", { source: "slack" });
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
createAgent,
|
|
26
26
|
createTaskExtended,
|
|
27
27
|
getDbClient,
|
|
28
|
+
getKv,
|
|
28
29
|
getLogsByTaskId,
|
|
29
30
|
getSlackTasksInThread,
|
|
30
31
|
getSteeringMessagesForTask,
|
|
@@ -34,7 +35,9 @@ import {
|
|
|
34
35
|
promoteAbandonedDraftTasks,
|
|
35
36
|
startTask,
|
|
36
37
|
} from "../be/db";
|
|
38
|
+
import { installExtension } from "../be/extensions/db";
|
|
37
39
|
import { MAX_TASK_ATTACHMENT_BYTES } from "../be/task-attachment-store";
|
|
40
|
+
import { disableExtension, enableExtension } from "../extensions/lifecycle";
|
|
38
41
|
import { getFileStorageProvider, resetFileStorageProviderForTests } from "../fs/registry";
|
|
39
42
|
import * as slackAppModule from "../slack/app";
|
|
40
43
|
import { createAssistant } from "../slack/assistant";
|
|
@@ -49,6 +52,7 @@ import {
|
|
|
49
52
|
setDraftLeaseRefreshMsForTests,
|
|
50
53
|
} from "../slack/inbound-files";
|
|
51
54
|
import { getBufferMessageCount, instantFlush } from "../slack/thread-buffer";
|
|
55
|
+
import { loadBundleFixture } from "./fixtures/extensions/load";
|
|
52
56
|
|
|
53
57
|
const TEST_DB_PATH = "./test-slack-inbound-files.sqlite";
|
|
54
58
|
const BOT_TOKEN = "example-slack-inbound-files-token";
|
|
@@ -651,7 +655,7 @@ describe("Slack ingress with files", () => {
|
|
|
651
655
|
"SELECT id FROM agent_tasks WHERE slackTriggerMessageTs = ?",
|
|
652
656
|
[ts],
|
|
653
657
|
);
|
|
654
|
-
return { client, task: row ? await getTaskById(row.id) : null };
|
|
658
|
+
return { client, task: row ? await getTaskById(row.id) : null, ts };
|
|
655
659
|
}
|
|
656
660
|
|
|
657
661
|
test("an image with no text creates a task that carries the image (case 1)", async () => {
|
|
@@ -953,31 +957,107 @@ describe("Slack ingress with files", () => {
|
|
|
953
957
|
});
|
|
954
958
|
});
|
|
955
959
|
|
|
956
|
-
|
|
960
|
+
for (const [route, withFiles] of [
|
|
961
|
+
["lead", false],
|
|
962
|
+
["lead", true],
|
|
963
|
+
["no-lead", false],
|
|
964
|
+
["no-lead", true],
|
|
965
|
+
["follow-up", false],
|
|
966
|
+
["follow-up", true],
|
|
967
|
+
] as const) {
|
|
968
|
+
test(`Assistant DM metadata reaches hooks for ${route} with files=${withFiles}`, async () => {
|
|
969
|
+
const agent =
|
|
970
|
+
route === "no-lead"
|
|
971
|
+
? undefined
|
|
972
|
+
: await createAgent({
|
|
973
|
+
name: route === "follow-up" ? "worker" : "lead",
|
|
974
|
+
isLead: route === "lead",
|
|
975
|
+
status: route === "follow-up" ? "busy" : "idle",
|
|
976
|
+
});
|
|
977
|
+
const channelId = "D0ASSISTMETADATA";
|
|
978
|
+
const threadTs = "1800000000.000001";
|
|
979
|
+
const earlier =
|
|
980
|
+
route === "follow-up"
|
|
981
|
+
? await createTaskExtended("earlier work", {
|
|
982
|
+
agentId: agent!.id,
|
|
983
|
+
source: "slack",
|
|
984
|
+
slackChannelId: channelId,
|
|
985
|
+
slackThreadTs: threadTs,
|
|
986
|
+
})
|
|
987
|
+
: undefined;
|
|
988
|
+
if (earlier) await startTask(earlier.id);
|
|
989
|
+
const installed = await installExtension(await loadBundleFixture("record-task-create"));
|
|
990
|
+
const recorder = await enableExtension(installed.extension.id);
|
|
991
|
+
const text = "<@U_SWARM_BOT> inspect this\n keep the spacing";
|
|
992
|
+
try {
|
|
993
|
+
const { task, ts } = await sendAssistantDm({
|
|
994
|
+
channel: channelId,
|
|
995
|
+
thread_ts: threadTs,
|
|
996
|
+
text,
|
|
997
|
+
files: withFiles ? [slackFile()] : [],
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
expect(task).not.toBeNull();
|
|
1001
|
+
expect(task!.agentId ?? undefined).toBe(agent?.id);
|
|
1002
|
+
if (earlier) expect(task!.parentTaskId).toBe(earlier.id);
|
|
1003
|
+
expect(await readStoredBytes(task!.id)).toEqual(withFiles ? [PNG_BYTES] : []);
|
|
1004
|
+
const event = (await getKv(`task:agent:${recorder.agentId}`, "ext:record-task-create:last"))
|
|
1005
|
+
?.value;
|
|
1006
|
+
expect(event).toMatchObject({
|
|
1007
|
+
origin: "slack",
|
|
1008
|
+
slack: {
|
|
1009
|
+
trigger: "mention",
|
|
1010
|
+
messages: [{ ts, userId: "U_HUMAN", text }],
|
|
1011
|
+
},
|
|
1012
|
+
});
|
|
1013
|
+
} finally {
|
|
1014
|
+
await disableExtension(recorder.id);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
test.each([
|
|
1020
|
+
false,
|
|
1021
|
+
true,
|
|
1022
|
+
])("Slack event metadata accompanies a channel mention with files=%s", async (withFiles) => {
|
|
957
1023
|
await createAgent({ name: "lead", isLead: true, status: "idle" });
|
|
1024
|
+
const installed = await installExtension(await loadBundleFixture("record-task-create"));
|
|
1025
|
+
const recorder = await enableExtension(installed.extension.id);
|
|
958
1026
|
seq += 1;
|
|
959
1027
|
const ts = `1900000000.${String(seq).padStart(6, "0")}`;
|
|
1028
|
+
try {
|
|
1029
|
+
await channelMessage({
|
|
1030
|
+
event: {
|
|
1031
|
+
type: "message",
|
|
1032
|
+
subtype: "file_share",
|
|
1033
|
+
channel: "C0CHANNEL1",
|
|
1034
|
+
ts,
|
|
1035
|
+
user: "U_HUMAN",
|
|
1036
|
+
text: "<@U_SWARM_BOT> what is this?",
|
|
1037
|
+
files: withFiles ? [slackFile()] : [],
|
|
1038
|
+
},
|
|
1039
|
+
body: { event_id: `evt_inbound_files_${seq}` },
|
|
1040
|
+
client: slackClient(),
|
|
1041
|
+
say: mock(async () => ({ ts: "1900000000.999999" })),
|
|
1042
|
+
});
|
|
960
1043
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
);
|
|
980
|
-
expect(row).not.toBeNull();
|
|
981
|
-
expect(await readStoredBytes(row!.id)).toEqual([PNG_BYTES]);
|
|
1044
|
+
const row = await getDbClient().get<{ id: string }>(
|
|
1045
|
+
"SELECT id FROM agent_tasks WHERE slackTriggerMessageTs = ?",
|
|
1046
|
+
[ts],
|
|
1047
|
+
);
|
|
1048
|
+
expect(row).not.toBeNull();
|
|
1049
|
+
expect(await readStoredBytes(row!.id)).toEqual(withFiles ? [PNG_BYTES] : []);
|
|
1050
|
+
const event = (await getKv(`task:agent:${recorder.agentId}`, "ext:record-task-create:last"))
|
|
1051
|
+
?.value;
|
|
1052
|
+
expect(event).toMatchObject({
|
|
1053
|
+
origin: "slack",
|
|
1054
|
+
slack: {
|
|
1055
|
+
trigger: "mention",
|
|
1056
|
+
messages: [{ ts, userId: "U_HUMAN", text: "<@U_SWARM_BOT> what is this?" }],
|
|
1057
|
+
},
|
|
1058
|
+
});
|
|
1059
|
+
} finally {
|
|
1060
|
+
await disableExtension(recorder.id);
|
|
1061
|
+
}
|
|
982
1062
|
});
|
|
983
1063
|
});
|