@desplega.ai/agent-swarm 2.2.0 → 2.4.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 +1 -0
- package/dist/{acp-adapter-k0vwfyvn.js → acp-adapter-kedstxx6.js} +6 -6
- package/dist/{actions-w4pnb8vz.js → actions-vget6r1k.js} +18 -16
- package/dist/{amp-adapter-0cmxb2t5.js → amp-adapter-xeb5mktv.js} +2 -2
- package/dist/{app-zneem7g9.js → app-mqap8t8m.js} +3 -3
- package/dist/{assistant-wbzd2rnr.js → assistant-h0gv97mv.js} +20 -18
- package/dist/{boot-reembed-4kx69j3g.js → boot-reembed-qr8a894z.js} +1 -1
- package/dist/{boot-reembed-db0wwgy1.js → boot-reembed-rj85jjhw.js} +1 -1
- package/dist/{boot-scrub-sweep-tfv4d3mm.js → boot-scrub-sweep-eyfvn6v9.js} +3 -3
- package/dist/{boot-seeds-w2hmkxn5.js → boot-seeds-xs9a8dx1.js} +4 -4
- package/dist/{claude-adapter-5qrc2paq.js → claude-adapter-bm23cye7.js} +5 -5
- package/dist/{claude-managed-adapter-n7ahjyx6.js → claude-managed-adapter-1mwrsm9d.js} +2 -2
- package/dist/{claude-managed-setup-7ra32em4.js → claude-managed-setup-gkfgks4j.js} +1 -1
- package/dist/{claude-sdk-session-cd02gc76.js → claude-sdk-session-apb4ypz3.js} +5 -5
- package/dist/cli-02vhhssk.js +848 -0
- package/dist/{cli-4ync7m60.js → cli-0ebey9hg.js} +1 -1
- package/dist/{cli-665j3feb.js → cli-0xk505e8.js} +1 -1
- package/dist/{cli-8c1m9mhy.js → cli-1cevz46c.js} +5 -5
- package/dist/{cli-gzvb3d4a.js → cli-1jx0srjt.js} +3 -3
- package/dist/{cli-zj4d6afs.js → cli-1pa3rz69.js} +21 -6
- package/dist/{cli-vmbakbq0.js → cli-2rwet3r7.js} +1 -1
- package/dist/{cli-azs9bhc5.js → cli-3wjs22x3.js} +2 -2
- package/dist/{cli-tba8fxf9.js → cli-4fhyq711.js} +45 -36
- package/dist/{cli-ak4c12mh.js → cli-5ff2sjyt.js} +1 -1
- package/dist/{cli-6q7pyd3k.js → cli-5s5hx57y.js} +5 -3
- package/dist/{cli-h23mr2s6.js → cli-6ftrrwm1.js} +9 -371
- package/dist/{cli-9ac0mkg5.js → cli-8b9wvjps.js} +1 -1
- package/dist/cli-8cxkw1f5.js +382 -0
- package/dist/{cli-xhgtay7w.js → cli-8rwvksbc.js} +2 -2
- package/dist/{cli-e09hj50q.js → cli-8wkb09q5.js} +11 -11
- package/dist/{cli-ksrphveb.js → cli-a1esmmee.js} +14 -2
- package/dist/{cli-xnp2vkx7.js → cli-avmqhatn.js} +7 -7
- package/dist/{cli-xx13gqf3.js → cli-b80a1100.js} +1 -1
- package/dist/{cli-z1hh99j0.js → cli-bfc3k1x5.js} +3 -3
- package/dist/{cli-0k9rcwfy.js → cli-bhwa8rzc.js} +33 -15
- package/dist/{cli-8cvvytad.js → cli-bn26xbcs.js} +2 -2
- package/dist/{cli-k28p6xh7.js → cli-c8fwpgqb.js} +1 -1
- package/dist/{cli-rc3r0bnh.js → cli-ccn0eybf.js} +1 -1
- package/dist/{cli-9td5ss65.js → cli-dsgj3e7a.js} +2 -2
- package/dist/{cli-jrk8mj0t.js → cli-ecshf504.js} +2 -2
- package/dist/{cli-raejy0mp.js → cli-eqgfs202.js} +1 -1
- package/dist/{cli-ppapy34w.js → cli-f3nx1h8z.js} +2 -2
- package/dist/{cli-c9vrwcpb.js → cli-ffbrkh1r.js} +74 -35
- package/dist/{cli-mnry16kh.js → cli-kk2r80n5.js} +4 -4
- package/dist/{cli-c0dpzwnx.js → cli-kz116kvw.js} +3 -3
- package/dist/{cli-e5y285rg.js → cli-mvg2hf8p.js} +4 -4
- package/dist/{cli-2jm992rm.js → cli-mz1ezzzh.js} +2 -2
- package/dist/{cli-dw0njd9q.js → cli-nme7wery.js} +6 -6
- package/dist/{cli-mvsfbbgq.js → cli-nrrg0fvy.js} +18 -4
- package/dist/{cli-8jtrekpy.js → cli-p1m2b6gc.js} +7 -7
- package/dist/{cli-4c7k75rq.js → cli-rg7ntypm.js} +378 -84
- package/dist/{cli-z3dk189k.js → cli-rsd7ga7v.js} +10 -8
- package/dist/{cli-etvn5ej2.js → cli-sppc0cmw.js} +1 -1
- package/dist/{cli-8w65vwee.js → cli-tg9ksbn8.js} +1 -1
- package/dist/{cli-th8krqbf.js → cli-tgvpmnwz.js} +2 -2
- package/dist/{cli-hwc4a9xm.js → cli-tj4j2ts4.js} +6 -6
- package/dist/{cli-jhhb2v9n.js → cli-ttaz6c7m.js} +2 -2
- package/dist/{cli-w7z94e6c.js → cli-tx00c0h6.js} +4 -2
- package/dist/{cli-4g629sgr.js → cli-wrv09v7q.js} +20 -1
- package/dist/{cli-cby329ny.js → cli-wyrr38xm.js} +2 -2
- package/dist/{cli-5ks6x36q.js → cli-x1p5dhq2.js} +3 -3
- package/dist/{cli-bddxamg7.js → cli-x719j3vs.js} +220 -629
- package/dist/{cli-ztcxv5nv.js → cli-xfe63zx3.js} +1 -1
- package/dist/{cli-q6gw45wj.js → cli-ywyrffff.js} +1 -1
- package/dist/{cli-p46xdr1m.js → cli-zvze9rbm.js} +1 -1
- package/dist/cli.js +15 -15
- package/dist/{codex-adapter-m0fx3v3s.js → codex-adapter-pp2gq8gr.js} +3 -3
- package/dist/{codex-hook-x6tm6y5m.js → codex-hook-qwzccd64.js} +1 -1
- package/dist/{codex-session-runner-j9m2gfyy.js → codex-session-runner-0vzn09t8.js} +3 -3
- package/dist/{commands-ktnqwrv4.js → commands-psqhkttb.js} +2 -2
- package/dist/{controller-nex2he8v.js → controller-t20vz2k8.js} +2 -2
- package/dist/{controllers-connect-6yhwqv9v.js → controllers-connect-gkxmqw9d.js} +24 -20
- package/dist/{cursor-adapter-02s5ekva.js → cursor-adapter-3v542qe5.js} +1 -1
- package/dist/{db-jvr86v7f.js → db-2xbyenjr.js} +2 -2
- package/dist/{db-19tw9r5s.js → db-nc1rd869.js} +3 -2
- package/dist/{dispatcher-d3mnsg2y.js → dispatcher-ewceb8ts.js} +17 -15
- package/dist/{dsh-adapter-63rvej88.js → dsh-adapter-75xwh78t.js} +1 -1
- package/dist/{easter-egg-3d-6hg2r942.js → easter-egg-3d-xsc47jwf.js} +1 -1
- package/dist/{execute-bcqc69a5.js → execute-gzmw1mnw.js} +1 -1
- package/dist/{external-channel-44cwer32.js → external-channel-a5dsmxn4.js} +2 -2
- package/dist/{google-generative-ai-phpmwxvg.js → google-generative-ai-bmq8gkf3.js} +1 -1
- package/dist/{google-vertex-es9ybgdy.js → google-vertex-b1rvp52s.js} +1 -1
- package/dist/{grok-adapter-ysp907f6.js → grok-adapter-vj88xr23.js} +7 -7
- package/dist/handlers-zbs90bny.js +39 -0
- package/dist/{heartbeat-2etacsqq.js → heartbeat-t2m9r3wa.js} +20 -18
- package/dist/{hook-mzxxs5n2.js → hook-8femx1bg.js} +4 -4
- package/dist/{hook-3kvmax6d.js → hook-xa677f8r.js} +5 -5
- package/dist/{http-fn978m0g.js → http-xpmmq028.js} +940 -269
- package/dist/{index-e4tsa1h7.js → index-8pwc1jc8.js} +17 -15
- package/dist/{index-w2wr89xd.js → index-cdzyfrsp.js} +1 -1
- package/dist/{index-1r8hw4vd.js → index-dg2tmj99.js} +17 -15
- package/dist/index-nxa6me0q.js +41 -0
- package/dist/{index-039x2sag.js → index-s7ckdajq.js} +42 -14
- package/dist/{index-wgsw9xcz.js → index-syztqdt6.js} +1 -1
- package/dist/{index-x0q2a4f9.js → index-ysd45kgc.js} +1 -1
- package/dist/{index-g909y089.js → index-zgvqzjsw.js} +8 -8
- package/dist/{keepalive-d4nzs76x.js → keepalive-3nr4st3r.js} +3 -3
- package/dist/{lead-8h5n9k5f.js → lead-td8h2sfn.js} +33 -31
- package/dist/{maintenance-ea6jfw42.js → maintenance-verwpe2c.js} +1 -1
- package/dist/{oauth-refresh-sweep-3f25djq3.js → oauth-refresh-sweep-9q2t0y3q.js} +2 -2
- package/dist/{onboard-pvk7hdb2.js → onboard-aytqedmq.js} +2 -2
- package/dist/{opencode-adapter-eeesgfqk.js → opencode-adapter-f4hryjny.js} +4 -4
- package/dist/{otel-impl-qb7nhgdp.js → otel-impl-hckw4wt1.js} +1 -1
- package/dist/{pi-mono-adapter-zasp3ze3.js → pi-mono-adapter-hxby0ce1.js} +6 -6
- package/dist/{pricing-refresh-6ehwkcmp.js → pricing-refresh-5nvme3sm.js} +3 -3
- package/dist/{rbac-roles-km0f9cmm.js → rbac-roles-pd8c26m1.js} +5 -3
- package/dist/{rbac-roles-9648nh00.js → rbac-roles-vbpy21mg.js} +2 -2
- package/dist/{render-v2-xkze1wef.js → render-v2-7nqgvk2q.js} +3 -3
- package/dist/{resume-tq0bbvt5.js → resume-b6djcj3c.js} +6 -6
- package/dist/{runtime-ar8310ba.js → runtime-txrxc9za.js} +1 -1
- package/dist/session-access-fc3f9y12.js +25 -0
- package/dist/{setup-rnrazxvd.js → setup-n39szm6b.js} +1 -1
- package/dist/{virtual-modules-wzk6jf3a.js → virtual-modules-wyp2k6z3.js} +2 -2
- package/dist/{work-objects-e75q2rqs.js → work-objects-jem2fre0.js} +20 -18
- package/dist/{worker-4ww4hjef.js → worker-wr6xgfr2.js} +33 -31
- package/openapi.json +1011 -115
- package/package.json +2 -2
- package/src/apps/action-caller.ts +6 -0
- package/src/apps/script-types.ts +16 -3
- package/src/be/asset-key-audit.ts +4 -13
- package/src/be/db/active-sessions.ts +5 -0
- package/src/be/db/session-visibility.ts +29 -0
- package/src/be/db/tasks/read.ts +61 -3
- package/src/be/db/tasks/write.ts +15 -6
- package/src/be/db.ts +149 -66
- package/src/be/events.ts +11 -0
- package/src/be/migrations/227_inbox_item_state_last_read_at.sql +1 -0
- package/src/be/migrations/230_session_shares.sql +23 -0
- package/src/be/rbac-roles.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +22 -4
- package/src/be/session-shares.ts +244 -0
- package/src/be/swarm-config-guard.ts +23 -2
- package/src/commands/runner.ts +11 -0
- package/src/heartbeat/heartbeat.ts +25 -7
- package/src/http/active-sessions.ts +13 -2
- package/src/http/agents.ts +10 -3
- package/src/http/all-routes.ts +1 -0
- package/src/http/api-keys.ts +3 -1
- package/src/http/approval-requests.ts +2 -1
- package/src/http/apps.ts +36 -11
- package/src/http/assets.ts +26 -4
- package/src/http/config.ts +3 -0
- package/src/http/context.ts +2 -0
- package/src/http/core.ts +39 -2
- package/src/http/events.ts +6 -2
- package/src/http/favorite-owner.ts +1 -1
- package/src/http/fs.ts +38 -3
- package/src/http/inbox-state.ts +58 -8
- package/src/http/index.ts +3 -1
- package/src/http/memory.ts +3 -1
- package/src/http/ranking.ts +367 -0
- package/src/http/realtime.ts +26 -21
- package/src/http/route-def.ts +77 -11
- package/src/http/script-runs.ts +2 -1
- package/src/http/scripts.ts +8 -0
- package/src/http/session-access.ts +179 -0
- package/src/http/session-data.ts +10 -2
- package/src/http/sessions.ts +212 -12
- package/src/http/stats.ts +7 -3
- package/src/http/tasks.ts +83 -7
- package/src/http/used-by.ts +11 -3
- package/src/kv-reserved-namespaces.ts +1 -0
- package/src/prompts/session-templates.ts +3 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/ranking/telemetry-ranking.ts +147 -0
- package/src/ranking/usage.ts +57 -0
- package/src/rbac/admission.ts +22 -7
- package/src/rbac/legacy-policy.ts +2 -0
- package/src/rbac/permissions.ts +9 -0
- package/src/realtime/auth.ts +69 -0
- package/src/realtime/tickets.ts +4 -1
- package/src/realtime/transport.ts +77 -9
- package/src/slack/blocks.ts +18 -0
- package/src/slack/dm.ts +29 -0
- package/src/telemetry-contract/EVENTS.md +3 -0
- package/src/telemetry-contract/SOURCE.json +37 -4
- package/src/telemetry-contract/catalog.json +69 -0
- package/src/telemetry-contract/ranking/fixtures/expected.json +30 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/bad-category.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/bad-claim.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/blank-description.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/blank-name.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/description-too-long.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/domain-uppercase.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/domain-with-scheme.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/domain-with-www.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/http-source-url.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/missing-description.json +10 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/missing-version.json +10 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/name-not-string.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/name-too-long.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/not-object.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/null.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/other-with-subcategory.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/oversize.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/short-claim.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/subcategory-other-category.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/trailing-data.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/unknown-field.json +12 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/unsupported-version.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/updated-at-not-date.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/updated-at-offset.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/wrong-case-field.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/category-without-subcategory.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/full.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/minimal.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/padded-text.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/unicode-name.json +11 -0
- package/src/telemetry-contract/ranking/listing.v1.schema.json +61 -0
- package/src/telemetry-contract/ranking/taxonomy.json +8 -0
- package/src/telemetry-contract/ranking-taxonomy.gen.ts +11 -0
- package/src/telemetry-contract/types.gen.ts +708 -1
- package/src/telemetry.ts +26 -2
- package/src/tests/apps-rbac.test.ts +189 -2
- package/src/tests/apps-script-types.test.ts +16 -3
- package/src/tests/asset-key-api.test.ts +128 -0
- package/src/tests/asset-key-invariants.test.ts +179 -3
- package/src/tests/asset-key-migration.test.ts +35 -0
- package/src/tests/check-pr-body.test.ts +23 -0
- package/src/tests/check-ranking-entry.test.ts +146 -0
- package/src/tests/db-extraction-contract.test.ts +1 -0
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/heartbeat-checklist.test.ts +88 -0
- package/src/tests/heartbeat-jev-gate.test.ts +394 -10
- package/src/tests/inbox-state.test.ts +167 -0
- package/src/tests/kv-tool.test.ts +15 -0
- package/src/tests/org-identity.test.ts +44 -0
- package/src/tests/ranking-listing.test.ts +112 -0
- package/src/tests/ranking-route.test.ts +319 -0
- package/src/tests/ranking-usage.test.ts +50 -0
- package/src/tests/rbac-admission-e2e.test.ts +8 -0
- package/src/tests/rbac-admission.test.ts +38 -0
- package/src/tests/rbac-charact-http.test.ts +4 -3
- package/src/tests/rbac-engine.test.ts +2 -0
- package/src/tests/rbac-roles.test.ts +2 -0
- package/src/tests/realtime-room-surfaces.test.ts +3 -1
- package/src/tests/realtime-session-join-race.test.ts +142 -0
- package/src/tests/realtime-tickets.test.ts +24 -17
- package/src/tests/realtime-transport.test.ts +91 -0
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/session-route-posture.test.ts +639 -0
- package/src/tests/session-sharing-routes.test.ts +282 -0
- package/src/tests/session-sharing.test.ts +526 -0
- package/src/tests/sessions-detail-batch.test.ts +7 -1
- package/src/tests/slack-tool-blocks.test.ts +101 -0
- package/src/tests/slack-update.test.ts +55 -0
- package/src/tests/slack-user-dm.test.ts +177 -0
- package/src/tests/used-by-route.test.ts +10 -0
- package/src/tools/slack-post.ts +18 -12
- package/src/tools/slack-read.ts +13 -1
- package/src/tools/slack-reply.ts +7 -10
- package/src/tools/slack-start-thread.ts +18 -11
- package/src/tools/slack-update.ts +2 -9
- package/src/types.ts +37 -0
- package/src/utils/org-identity.ts +33 -0
- package/templates/skills/apps/SKILL.md +17 -0
- package/templates/skills/apps/content.md +17 -0
- package/templates/skills/code-quality/content.md +2 -2
- package/templates/skills/pages/content.md +4 -0
- package/templates/skills/swarm-scripts/SKILL.md +6 -0
- package/templates/skills/swarm-scripts/content.md +6 -0
- package/dist/handlers-t5pbabwf.js +0 -37
- package/dist/index-qyjbb7br.js +0 -39
- package/dist/{cli-n5mrsg0f.js → cli-60cgh1rv.js} +3 -3
- /package/dist/{cli-j0v75y13.js → cli-y83zk5sb.js} +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
closeDb,
|
|
8
|
+
createAgent,
|
|
9
|
+
createLogEntry,
|
|
10
|
+
createSessionLogs,
|
|
11
|
+
createSteeringMessage,
|
|
12
|
+
createTaskExtended,
|
|
13
|
+
createUser,
|
|
14
|
+
getDbClient,
|
|
15
|
+
initDb,
|
|
16
|
+
insertActiveSession,
|
|
17
|
+
insertTaskAttachment,
|
|
18
|
+
} from "../be/db";
|
|
19
|
+
import { createEvent } from "../be/events";
|
|
20
|
+
import { setSessionShares } from "../be/session-shares";
|
|
21
|
+
import { mintToken } from "../be/users";
|
|
22
|
+
import { getFileStorageProvider, resetFileStorageProvider } from "../fs/registry";
|
|
23
|
+
import { handleActiveSessions } from "../http/active-sessions";
|
|
24
|
+
import { handleAgentsRest } from "../http/agents";
|
|
25
|
+
import { handleCore } from "../http/core";
|
|
26
|
+
import { handleEvents } from "../http/events";
|
|
27
|
+
import { handleFs } from "../http/fs";
|
|
28
|
+
import { handleSessionData } from "../http/session-data";
|
|
29
|
+
import { handleStats } from "../http/stats";
|
|
30
|
+
import { handleTasks } from "../http/tasks";
|
|
31
|
+
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
32
|
+
import { beginRequestAuthScope } from "../utils/request-auth-context";
|
|
33
|
+
import { listenOnFreePort } from "./test-net";
|
|
34
|
+
|
|
35
|
+
// Every route here returns task content. A minted `aswt_` token for a user with
|
|
36
|
+
// no share must not read a private session through any of them. A worker-only
|
|
37
|
+
// feed refuses every user token that names the agent in X-Agent-ID. The owner,
|
|
38
|
+
// a shared user and the agent itself (operator key + X-Agent-ID) keep reading.
|
|
39
|
+
|
|
40
|
+
const OPERATOR_KEY = "session-routes-operator-key";
|
|
41
|
+
const HANDLERS = [
|
|
42
|
+
handleTasks,
|
|
43
|
+
handleSessionData,
|
|
44
|
+
handleEvents,
|
|
45
|
+
handleFs,
|
|
46
|
+
handleActiveSessions,
|
|
47
|
+
handleStats,
|
|
48
|
+
handleAgentsRest,
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
type Who = "owner" | "sharee" | "viewer" | "agent";
|
|
52
|
+
|
|
53
|
+
const server = createServer(async (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
beginRequestAuthScope();
|
|
56
|
+
const agentId = req.headers["x-agent-id"] as string | undefined;
|
|
57
|
+
if (await handleCore(req, res, agentId, OPERATOR_KEY)) return;
|
|
58
|
+
const segments = getPathSegments(req.url ?? "");
|
|
59
|
+
const query = parseQueryParams(req.url ?? "");
|
|
60
|
+
for (const handler of HANDLERS) {
|
|
61
|
+
if (await handler(req, res, segments, query, agentId)) return;
|
|
62
|
+
}
|
|
63
|
+
res.writeHead(404);
|
|
64
|
+
res.end();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
res.writeHead(500);
|
|
67
|
+
res.end(String(error));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
let port: number;
|
|
72
|
+
let agent: { id: string };
|
|
73
|
+
let owner: { id: string };
|
|
74
|
+
let sharee: { id: string };
|
|
75
|
+
const tokens = {} as Record<Exclude<Who, "agent">, string>;
|
|
76
|
+
let filesDir: string;
|
|
77
|
+
|
|
78
|
+
beforeAll(async () => {
|
|
79
|
+
filesDir = await mkdtemp(join(tmpdir(), "session-routes-"));
|
|
80
|
+
process.env.AGENT_FS_LOCAL_DIR = filesDir;
|
|
81
|
+
delete process.env.AGENT_FS_API_URL;
|
|
82
|
+
delete process.env.API_AGENT_FS_API_KEY;
|
|
83
|
+
delete process.env.AGENT_FS_API_KEY;
|
|
84
|
+
resetFileStorageProvider();
|
|
85
|
+
initDb(":memory:");
|
|
86
|
+
agent = await createAgent({ name: "Worker", isLead: false, status: "idle" });
|
|
87
|
+
owner = await createUser({ name: "Owner" });
|
|
88
|
+
sharee = await createUser({ name: "Sharee" });
|
|
89
|
+
const viewer = await createUser({ name: "Viewer" });
|
|
90
|
+
const system = { kind: "system", id: "test" } as const;
|
|
91
|
+
tokens.owner = (await mintToken(owner.id, "test", system)).plaintext;
|
|
92
|
+
tokens.sharee = (await mintToken(sharee.id, "test", system)).plaintext;
|
|
93
|
+
tokens.viewer = (await mintToken(viewer.id, "test", system)).plaintext;
|
|
94
|
+
port = await listenOnFreePort(server, "127.0.0.1");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterAll(async () => {
|
|
98
|
+
server.closeAllConnections();
|
|
99
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
100
|
+
closeDb();
|
|
101
|
+
resetFileStorageProvider();
|
|
102
|
+
await rm(filesDir, { recursive: true, force: true });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/** An owned root and descendant on `agent`, shared read-only with `sharee` and nobody else. */
|
|
106
|
+
async function privateSession(label: string) {
|
|
107
|
+
const root = await createTaskExtended(`${label} root`, {
|
|
108
|
+
requestedByUserId: owner.id,
|
|
109
|
+
agentId: agent.id,
|
|
110
|
+
source: "ui",
|
|
111
|
+
});
|
|
112
|
+
const child = await createTaskExtended(`${label} child`, {
|
|
113
|
+
requestedByUserId: owner.id,
|
|
114
|
+
agentId: agent.id,
|
|
115
|
+
parentTaskId: root.id,
|
|
116
|
+
source: "ui",
|
|
117
|
+
});
|
|
118
|
+
await setSessionShares(
|
|
119
|
+
{ id: root.id, requestedByUserId: owner.id },
|
|
120
|
+
{ everyone: "none", users: [{ userId: sharee.id, access: "read" }] },
|
|
121
|
+
owner.id,
|
|
122
|
+
);
|
|
123
|
+
return { root, child };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function get(path: string, who: Who, withAgentHeader = false) {
|
|
127
|
+
const headers: Record<string, string> = {
|
|
128
|
+
Authorization: `Bearer ${who === "agent" ? OPERATOR_KEY : tokens[who]}`,
|
|
129
|
+
};
|
|
130
|
+
if (who === "agent" || withAgentHeader) headers["X-Agent-ID"] = agent.id;
|
|
131
|
+
const res = await fetch(`http://127.0.0.1:${port}${path}`, { headers });
|
|
132
|
+
return { status: res.status, body: await res.text() };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The unshared viewer gets `deniedStatus` without `secret`; the owner, the sharee and the agent read it. */
|
|
136
|
+
async function expectPrivate(
|
|
137
|
+
path: string,
|
|
138
|
+
secret: string,
|
|
139
|
+
{ deniedStatus }: { deniedStatus: 200 | 403 },
|
|
140
|
+
) {
|
|
141
|
+
const denied = await get(path, "viewer");
|
|
142
|
+
expect({ path, status: denied.status }).toEqual({ path, status: deniedStatus });
|
|
143
|
+
expect(denied.body).not.toContain(secret);
|
|
144
|
+
for (const who of ["owner", "sharee", "agent"] as const) {
|
|
145
|
+
const allowed = await get(path, who);
|
|
146
|
+
expect({ path, who, status: allowed.status }).toEqual({ path, who, status: 200 });
|
|
147
|
+
expect(allowed.body).toContain(secret);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** A worker-only feed: every user token naming the agent gets 403 without `secret`; the agent reads it. */
|
|
152
|
+
async function expectWorkerOnly(path: string, secret: string) {
|
|
153
|
+
for (const who of ["viewer", "sharee", "owner"] as const) {
|
|
154
|
+
const denied = await get(path, who, true);
|
|
155
|
+
expect({ path, who, status: denied.status }).toEqual({ path, who, status: 403 });
|
|
156
|
+
expect(denied.body).not.toContain(secret);
|
|
157
|
+
}
|
|
158
|
+
const allowed = await get(path, "agent");
|
|
159
|
+
expect({ path, status: allowed.status }).toEqual({ path, status: 200 });
|
|
160
|
+
expect(allowed.body).toContain(secret);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
describe("task-content routes respect session read access", () => {
|
|
164
|
+
test("B6 steering: task history, the per-task worker feed and the agent feed", async () => {
|
|
165
|
+
const { child } = await privateSession("steering");
|
|
166
|
+
const secret = "PRIVATE STEERING BODY";
|
|
167
|
+
await createSteeringMessage({
|
|
168
|
+
taskId: child.id,
|
|
169
|
+
body: secret,
|
|
170
|
+
mode: "queue",
|
|
171
|
+
source: "ui",
|
|
172
|
+
createdByKind: "user",
|
|
173
|
+
createdByUserId: owner.id,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
await expectPrivate(`/api/tasks/${child.id}/steering-messages`, secret, { deniedStatus: 403 });
|
|
177
|
+
await expectWorkerOnly(`/api/steering-messages?taskId=${child.id}`, secret);
|
|
178
|
+
await expectWorkerOnly("/api/steering-messages", secret);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("B7 session logs", async () => {
|
|
182
|
+
const { child } = await privateSession("logs");
|
|
183
|
+
const secret = "PRIVATE HARNESS LOG LINE";
|
|
184
|
+
await createSessionLogs({
|
|
185
|
+
taskId: child.id,
|
|
186
|
+
sessionId: "logs-session",
|
|
187
|
+
iteration: 1,
|
|
188
|
+
cli: "codex",
|
|
189
|
+
lines: [secret],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await expectPrivate(`/api/tasks/${child.id}/session-logs`, secret, { deniedStatus: 403 });
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("B8 attachments: list, metadata, raw bytes and signed URL", async () => {
|
|
196
|
+
const { child } = await privateSession("files");
|
|
197
|
+
const secret = "PRIVATE FILE BYTES";
|
|
198
|
+
const provider = getFileStorageProvider();
|
|
199
|
+
const file = await provider.upload(
|
|
200
|
+
{ taskId: child.id, name: "secret-notes.txt" },
|
|
201
|
+
Buffer.from(secret),
|
|
202
|
+
{ contentType: "text/plain" },
|
|
203
|
+
);
|
|
204
|
+
const attachment = await insertTaskAttachment({
|
|
205
|
+
taskId: child.id,
|
|
206
|
+
agentId: agent.id,
|
|
207
|
+
kind: "shared-fs",
|
|
208
|
+
name: "secret-notes.txt",
|
|
209
|
+
path: file.key,
|
|
210
|
+
providerId: provider.id,
|
|
211
|
+
providerKey: file.key,
|
|
212
|
+
});
|
|
213
|
+
const files = `/api/fs/tasks/${child.id}/files`;
|
|
214
|
+
|
|
215
|
+
await expectPrivate(files, "secret-notes.txt", { deniedStatus: 403 });
|
|
216
|
+
await expectPrivate(`${files}/${attachment.id}`, "secret-notes.txt", { deniedStatus: 403 });
|
|
217
|
+
await expectPrivate(`${files}/${attachment.id}/raw`, secret, { deniedStatus: 403 });
|
|
218
|
+
|
|
219
|
+
// The local provider cannot sign (501), so this only proves the viewer
|
|
220
|
+
// stops before the provider while the owner reaches it.
|
|
221
|
+
const signed = `${files}/${attachment.id}/signed-url`;
|
|
222
|
+
expect((await get(signed, "viewer")).status).toBe(403);
|
|
223
|
+
expect((await get(signed, "owner")).status).toBe(501);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("B9 events and audit logs, task-filtered and unfiltered", async () => {
|
|
227
|
+
const { root, child } = await privateSession("events");
|
|
228
|
+
const eventSecret = "PRIVATE EVENT PAYLOAD";
|
|
229
|
+
const progressSecret = "PRIVATE PROGRESS TEXT";
|
|
230
|
+
await createEvent({
|
|
231
|
+
taskId: child.id,
|
|
232
|
+
agentId: agent.id,
|
|
233
|
+
category: "tool",
|
|
234
|
+
event: "tool.start",
|
|
235
|
+
source: "worker",
|
|
236
|
+
data: { message: eventSecret },
|
|
237
|
+
});
|
|
238
|
+
await createLogEntry({
|
|
239
|
+
taskId: root.id,
|
|
240
|
+
agentId: agent.id,
|
|
241
|
+
eventType: "task_progress",
|
|
242
|
+
newValue: progressSecret,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await expectPrivate(`/api/events?taskId=${child.id}`, eventSecret, { deniedStatus: 200 });
|
|
246
|
+
await expectPrivate("/api/events", eventSecret, { deniedStatus: 200 });
|
|
247
|
+
await expectPrivate("/api/logs", progressSecret, { deniedStatus: 200 });
|
|
248
|
+
await expectPrivate(`/api/logs?agentId=${agent.id}`, progressSecret, { deniedStatus: 200 });
|
|
249
|
+
// Task-less rows belong to no session and stay visible.
|
|
250
|
+
expect((await get("/api/logs", "viewer")).body).toContain("agent_joined");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("B10 task feeds: active sessions, concurrent context, agent tasks, paused and cancelled", async () => {
|
|
254
|
+
const secret = "PRIVATE B10 TASK";
|
|
255
|
+
const { child } = await privateSession(secret);
|
|
256
|
+
const activeSecret = "PRIVATE ACTIVE DESCRIPTION";
|
|
257
|
+
await insertActiveSession({
|
|
258
|
+
agentId: agent.id,
|
|
259
|
+
taskId: child.id,
|
|
260
|
+
triggerType: "task",
|
|
261
|
+
taskDescription: activeSecret,
|
|
262
|
+
});
|
|
263
|
+
const setStatus = (status: string) =>
|
|
264
|
+
getDbClient().run(
|
|
265
|
+
"UPDATE agent_tasks SET status = ?, finishedAt = ?, failureReason = 'stopped' WHERE id = ?",
|
|
266
|
+
[status, new Date().toISOString(), child.id],
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
await setStatus("in_progress");
|
|
270
|
+
await expectPrivate("/api/active-sessions", activeSecret, { deniedStatus: 200 });
|
|
271
|
+
await expectPrivate("/api/concurrent-context", secret, { deniedStatus: 200 });
|
|
272
|
+
await expectPrivate("/api/agents?include=tasks", secret, { deniedStatus: 200 });
|
|
273
|
+
await expectPrivate(`/api/agents/${agent.id}?include=tasks`, secret, { deniedStatus: 200 });
|
|
274
|
+
|
|
275
|
+
await setStatus("paused");
|
|
276
|
+
await expectWorkerOnly("/api/paused-tasks", secret);
|
|
277
|
+
|
|
278
|
+
await setStatus("cancelled");
|
|
279
|
+
await expectWorkerOnly(`/cancelled-tasks?taskId=${child.id}`, secret);
|
|
280
|
+
await expectWorkerOnly("/cancelled-tasks", secret);
|
|
281
|
+
});
|
|
282
|
+
});
|