@desplega.ai/agent-swarm 2.3.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-v85a2bqy.js → acp-adapter-kedstxx6.js} +6 -6
- package/dist/{actions-zgcxfspj.js → actions-vget6r1k.js} +18 -16
- package/dist/{amp-adapter-jw9xfz42.js → amp-adapter-xeb5mktv.js} +2 -2
- package/dist/{app-4e1s4675.js → app-mqap8t8m.js} +3 -3
- package/dist/{assistant-v238n9dk.js → assistant-h0gv97mv.js} +20 -18
- package/dist/{boot-reembed-p666cw5n.js → boot-reembed-qr8a894z.js} +1 -1
- package/dist/{boot-reembed-w6pm64j4.js → boot-reembed-rj85jjhw.js} +1 -1
- package/dist/{boot-scrub-sweep-jcyjagn3.js → boot-scrub-sweep-eyfvn6v9.js} +3 -3
- package/dist/{boot-seeds-gbedmamp.js → boot-seeds-xs9a8dx1.js} +4 -4
- package/dist/{claude-adapter-q7wspzt8.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-wza784fb.js → claude-managed-setup-gkfgks4j.js} +1 -1
- package/dist/{claude-sdk-session-yf400ptr.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-30m5q8mc.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-fhvqt2fp.js → cli-2rwet3r7.js} +1 -1
- package/dist/{cli-azs9bhc5.js → cli-3wjs22x3.js} +2 -2
- package/dist/{cli-dxwh4j32.js → cli-4fhyq711.js} +45 -36
- package/dist/{cli-yv3n83x8.js → cli-5ff2sjyt.js} +1 -1
- package/dist/{cli-6vcyw6v7.js → cli-5s5hx57y.js} +5 -3
- package/dist/{cli-hz64bcnj.js → cli-6ftrrwm1.js} +9 -371
- package/dist/{cli-zes4tsbb.js → cli-8b9wvjps.js} +1 -1
- package/dist/cli-8cxkw1f5.js +382 -0
- package/dist/{cli-4s1kqrqg.js → cli-8rwvksbc.js} +2 -2
- package/dist/{cli-e09hj50q.js → cli-8wkb09q5.js} +11 -11
- package/dist/{cli-yv0vgscc.js → cli-a1esmmee.js} +13 -2
- package/dist/{cli-4hmaheb6.js → cli-avmqhatn.js} +7 -7
- package/dist/{cli-g6p7c2my.js → cli-b80a1100.js} +1 -1
- package/dist/{cli-tr24eeny.js → cli-bfc3k1x5.js} +3 -3
- package/dist/{cli-ka01m5rb.js → cli-bhwa8rzc.js} +13 -11
- 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-a17tzmn0.js → cli-ecshf504.js} +2 -2
- package/dist/{cli-d43xqzbe.js → cli-eqgfs202.js} +1 -1
- package/dist/{cli-ppapy34w.js → cli-f3nx1h8z.js} +2 -2
- package/dist/{cli-ea7chd26.js → cli-ffbrkh1r.js} +74 -35
- package/dist/{cli-xgn4rwyv.js → cli-kk2r80n5.js} +4 -4
- package/dist/{cli-hn3pftj2.js → cli-kz116kvw.js} +3 -3
- package/dist/{cli-1v8w782f.js → cli-mvg2hf8p.js} +4 -4
- package/dist/{cli-5d1m1qca.js → cli-mz1ezzzh.js} +1 -1
- package/dist/{cli-jhmjwqcg.js → cli-nme7wery.js} +6 -6
- package/dist/{cli-65q70v2v.js → cli-nrrg0fvy.js} +18 -4
- package/dist/{cli-8jtrekpy.js → cli-p1m2b6gc.js} +7 -7
- package/dist/{cli-re4wkbzx.js → cli-rg7ntypm.js} +328 -47
- package/dist/{cli-fp4xbw07.js → cli-rsd7ga7v.js} +10 -8
- package/dist/{cli-etvn5ej2.js → cli-sppc0cmw.js} +1 -1
- package/dist/{cli-qrwp40st.js → cli-tg9ksbn8.js} +1 -1
- package/dist/{cli-6p7ddg72.js → cli-tgvpmnwz.js} +2 -2
- package/dist/{cli-q1812jbw.js → cli-tj4j2ts4.js} +6 -6
- package/dist/{cli-kqypty34.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-y3dvz6ge.js → cli-x1p5dhq2.js} +3 -3
- package/dist/{cli-8qsx817b.js → cli-x719j3vs.js} +219 -629
- package/dist/{cli-9e9kjqsj.js → cli-xfe63zx3.js} +1 -1
- package/dist/{cli-wtg6edvd.js → cli-ywyrffff.js} +1 -1
- package/dist/{cli-0rh20bp3.js → cli-zvze9rbm.js} +1 -1
- package/dist/cli.js +14 -14
- package/dist/{codex-adapter-nhjj2veb.js → codex-adapter-pp2gq8gr.js} +3 -3
- package/dist/{codex-hook-x6tm6y5m.js → codex-hook-qwzccd64.js} +1 -1
- package/dist/{codex-session-runner-6hggrjbx.js → codex-session-runner-0vzn09t8.js} +3 -3
- package/dist/{commands-mrw1ph2w.js → commands-psqhkttb.js} +2 -2
- package/dist/{controller-pt1zam5w.js → controller-t20vz2k8.js} +2 -2
- package/dist/{controllers-connect-ymw5541d.js → controllers-connect-gkxmqw9d.js} +24 -20
- package/dist/{cursor-adapter-cf4zh5x0.js → cursor-adapter-3v542qe5.js} +1 -1
- package/dist/{db-hy80219q.js → db-2xbyenjr.js} +2 -2
- package/dist/{db-hhpy6m4x.js → db-nc1rd869.js} +3 -2
- package/dist/{dispatcher-6pdjtkr1.js → dispatcher-ewceb8ts.js} +17 -15
- package/dist/{dsh-adapter-fbx06re2.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-j6vb6ya1.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-1193sprp.js → grok-adapter-vj88xr23.js} +7 -7
- package/dist/handlers-zbs90bny.js +39 -0
- package/dist/{heartbeat-h1w1ktce.js → heartbeat-t2m9r3wa.js} +20 -18
- package/dist/{hook-skrbja18.js → hook-8femx1bg.js} +4 -4
- package/dist/{hook-ejja4bpe.js → hook-xa677f8r.js} +5 -5
- package/dist/{http-mzh41mfj.js → http-xpmmq028.js} +868 -253
- package/dist/{index-vjn3wbzf.js → index-8pwc1jc8.js} +17 -15
- package/dist/{index-epr2gnbw.js → index-cdzyfrsp.js} +1 -1
- package/dist/{index-xmmkyx82.js → index-dg2tmj99.js} +17 -15
- package/dist/index-nxa6me0q.js +41 -0
- package/dist/{index-6rmnn89d.js → index-s7ckdajq.js} +9 -9
- 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-4djvbm2j.js → keepalive-3nr4st3r.js} +3 -3
- package/dist/{lead-61kqp604.js → lead-td8h2sfn.js} +33 -31
- package/dist/{maintenance-f3xhnjsm.js → maintenance-verwpe2c.js} +1 -1
- package/dist/{oauth-refresh-sweep-fpd6x6rw.js → oauth-refresh-sweep-9q2t0y3q.js} +2 -2
- package/dist/{onboard-196tjgyn.js → onboard-aytqedmq.js} +2 -2
- package/dist/{opencode-adapter-eeesgfqk.js → opencode-adapter-f4hryjny.js} +4 -4
- package/dist/{otel-impl-g8fh4c21.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-sdcf5yaz.js → pricing-refresh-5nvme3sm.js} +3 -3
- package/dist/{rbac-roles-bsp5657v.js → rbac-roles-pd8c26m1.js} +5 -3
- package/dist/{rbac-roles-2x4g1wb5.js → rbac-roles-vbpy21mg.js} +2 -2
- package/dist/{render-v2-0pyb39bb.js → render-v2-7nqgvk2q.js} +3 -3
- package/dist/{resume-a86tkj6r.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-ypbzcapd.js → setup-n39szm6b.js} +1 -1
- package/dist/{virtual-modules-wzk6jf3a.js → virtual-modules-wyp2k6z3.js} +2 -2
- package/dist/{work-objects-yjp8de12.js → work-objects-jem2fre0.js} +20 -18
- package/dist/{worker-4ptnn1ge.js → worker-wr6xgfr2.js} +33 -31
- package/openapi.json +844 -110
- package/package.json +1 -1
- package/src/apps/action-caller.ts +6 -0
- package/src/apps/script-types.ts +16 -3
- 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 +92 -33
- package/src/be/events.ts +11 -0
- package/src/be/migrations/230_session_shares.sql +23 -0
- package/src/be/rbac-roles.ts +2 -0
- 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/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/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/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/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/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/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/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 +35 -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/dist/handlers-tb3xgjnm.js +0 -37
- package/dist/index-nyfcdpky.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,848 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getRequestAuth,
|
|
3
|
+
init_request_auth_context,
|
|
4
|
+
getDbClient2,
|
|
5
|
+
getActiveTaskCount2,
|
|
6
|
+
getTaskById2,
|
|
7
|
+
getAgentCurrentTask2,
|
|
8
|
+
getActiveSessionById,
|
|
9
|
+
init_active_sessions,
|
|
10
|
+
getSteeringMessageById2,
|
|
11
|
+
init_db3
|
|
12
|
+
} from "./cli-rg7ntypm.js";
|
|
13
|
+
import {
|
|
14
|
+
getUserGrant2,
|
|
15
|
+
init_rbac_roles
|
|
16
|
+
} from "./cli-5s5hx57y.js";
|
|
17
|
+
import {
|
|
18
|
+
findUserById,
|
|
19
|
+
findUserByExternalId,
|
|
20
|
+
init_users
|
|
21
|
+
} from "./cli-8cxkw1f5.js";
|
|
22
|
+
import {
|
|
23
|
+
isRbacEnabled,
|
|
24
|
+
init_rbac
|
|
25
|
+
} from "./cli-1pa3rz69.js";
|
|
26
|
+
import {
|
|
27
|
+
init_zod
|
|
28
|
+
} from "./cli-4chz1hye.js";
|
|
29
|
+
import {
|
|
30
|
+
ZodError,
|
|
31
|
+
string2,
|
|
32
|
+
object2
|
|
33
|
+
} from "./cli-d9f7gzeq.js";
|
|
34
|
+
import {
|
|
35
|
+
getNodeEnv,
|
|
36
|
+
init_node_env
|
|
37
|
+
} from "./cli-rgeadyvq.js";
|
|
38
|
+
import {
|
|
39
|
+
isEnvFlagEnabled,
|
|
40
|
+
init_env_flag
|
|
41
|
+
} from "./cli-0hyhdkdk.js";
|
|
42
|
+
import {
|
|
43
|
+
scrubSecrets,
|
|
44
|
+
init_secret_scrubber
|
|
45
|
+
} from "./cli-4mkvwgzr.js";
|
|
46
|
+
|
|
47
|
+
// src/http/route-def.ts
|
|
48
|
+
init_zod();
|
|
49
|
+
init_node_env();
|
|
50
|
+
init_secret_scrubber();
|
|
51
|
+
|
|
52
|
+
// src/http/utils.ts
|
|
53
|
+
init_db3();
|
|
54
|
+
init_env_flag();
|
|
55
|
+
init_node_env();
|
|
56
|
+
init_secret_scrubber();
|
|
57
|
+
function duplicateGuardedQueryKey(query) {
|
|
58
|
+
return ["agentId", "repoId", "includeSecrets", "resolveSecrets"].find((key) => query.getAll(key).length > 1);
|
|
59
|
+
}
|
|
60
|
+
var DEFAULT_ALLOWED_ORIGINS = [
|
|
61
|
+
"https://*.agent-swarm.dev",
|
|
62
|
+
"https://*.agent-swarm.cloud",
|
|
63
|
+
"http://localhost:5274",
|
|
64
|
+
"http://127.0.0.1:5274",
|
|
65
|
+
"http://[::1]:5274",
|
|
66
|
+
"https://ui.swarm.localhost:1355"
|
|
67
|
+
];
|
|
68
|
+
var warnedAboutAllowAnyOrigin = false;
|
|
69
|
+
function warnIfCorsAllowsAnyOrigin() {
|
|
70
|
+
if (isEnvFlagEnabled("CORS_ALLOW_ANY_ORIGIN", false) && !warnedAboutAllowAnyOrigin) {
|
|
71
|
+
warnedAboutAllowAnyOrigin = true;
|
|
72
|
+
console.warn("[CORS] CORS_ALLOW_ANY_ORIGIN=true: any request origin can receive non-credentialed responses; cookie-authenticated responses still require CORS_ALLOWED_ORIGINS. Set CORS_ALLOWED_ORIGINS and disable CORS_ALLOW_ANY_ORIGIN to restrict access.");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getAllowedOrigins() {
|
|
76
|
+
const raw = process.env.CORS_ALLOWED_ORIGINS;
|
|
77
|
+
if (!raw || !raw.trim())
|
|
78
|
+
return DEFAULT_ALLOWED_ORIGINS;
|
|
79
|
+
return raw.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
80
|
+
}
|
|
81
|
+
function isDevLoopbackOrigin(origin) {
|
|
82
|
+
if (getNodeEnv() !== "development")
|
|
83
|
+
return false;
|
|
84
|
+
return /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d{1,5})?$/.test(origin);
|
|
85
|
+
}
|
|
86
|
+
function originAllowedForCredentials(origin) {
|
|
87
|
+
if (isDevLoopbackOrigin(origin))
|
|
88
|
+
return true;
|
|
89
|
+
return getAllowedOrigins().some((entry) => {
|
|
90
|
+
if (!entry.includes("*"))
|
|
91
|
+
return entry === origin;
|
|
92
|
+
const pattern = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/\*\.([a-zA-Z0-9.-]+)(:\d+)?$/.exec(entry);
|
|
93
|
+
const candidate = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([a-zA-Z0-9.-]+)(:\d+)?$/.exec(origin);
|
|
94
|
+
if (!pattern || !candidate)
|
|
95
|
+
return false;
|
|
96
|
+
if (pattern[1] !== candidate[1] || pattern[3] !== candidate[3])
|
|
97
|
+
return false;
|
|
98
|
+
const suffix = pattern[2].toLowerCase();
|
|
99
|
+
const hostname = candidate[2].toLowerCase();
|
|
100
|
+
const validLabel = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
101
|
+
if (![...suffix.split("."), ...hostname.split(".")].every((label) => validLabel.test(label))) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return hostname.endsWith(`.${suffix}`);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function isOriginAllowedForCredentials(origin) {
|
|
108
|
+
const allowed = originAllowedForCredentials(origin);
|
|
109
|
+
if (!allowed) {
|
|
110
|
+
console.warn(`[CORS] Denied credentialed origin ${scrubSecrets(JSON.stringify(origin.slice(0, 512)))}; add the trusted SPA origin to CORS_ALLOWED_ORIGINS (custom values replace hosted/dev defaults).`);
|
|
111
|
+
}
|
|
112
|
+
return allowed;
|
|
113
|
+
}
|
|
114
|
+
function allowedCredentialRedirect(raw) {
|
|
115
|
+
if (!raw)
|
|
116
|
+
return;
|
|
117
|
+
try {
|
|
118
|
+
const redirect = new URL(raw);
|
|
119
|
+
if (redirect.protocol !== "http:" && redirect.protocol !== "https:")
|
|
120
|
+
return;
|
|
121
|
+
return originAllowedForCredentials(redirect.origin) ? redirect.toString() : undefined;
|
|
122
|
+
} catch {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function setCorsHeaders(req, res) {
|
|
127
|
+
const rawOrigin = req.headers.origin;
|
|
128
|
+
const origin = Array.isArray(rawOrigin) ? rawOrigin[0] : rawOrigin;
|
|
129
|
+
const allowCredentials = origin ? isOriginAllowedForCredentials(origin) : false;
|
|
130
|
+
const allowAnyOrigin = isEnvFlagEnabled("CORS_ALLOW_ANY_ORIGIN", false);
|
|
131
|
+
if (allowAnyOrigin)
|
|
132
|
+
warnIfCorsAllowsAnyOrigin();
|
|
133
|
+
if (origin && !allowCredentials && !allowAnyOrigin) {
|
|
134
|
+
res.setHeader("Vary", "Origin");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (origin) {
|
|
138
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
139
|
+
res.setHeader("Vary", "Origin");
|
|
140
|
+
if (allowCredentials)
|
|
141
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
142
|
+
const reqHeaders = req.headers["access-control-request-headers"];
|
|
143
|
+
const askedHeaders = Array.isArray(reqHeaders) ? reqHeaders.join(", ") : reqHeaders;
|
|
144
|
+
res.setHeader("Access-Control-Allow-Headers", askedHeaders ?? "Authorization, Content-Type, X-Agent-ID, X-Requested-With, X-Swarm-User-Id");
|
|
145
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
|
|
146
|
+
res.setHeader("Access-Control-Expose-Headers", "Content-Type, Content-Length, ETag, Location");
|
|
147
|
+
} else {
|
|
148
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
149
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
|
|
150
|
+
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
151
|
+
res.setHeader("Access-Control-Expose-Headers", "*");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function parseQueryParams(url) {
|
|
155
|
+
const queryIndex = url.indexOf("?");
|
|
156
|
+
if (queryIndex === -1)
|
|
157
|
+
return new URLSearchParams;
|
|
158
|
+
return new URLSearchParams(url.slice(queryIndex + 1));
|
|
159
|
+
}
|
|
160
|
+
function getPathSegments(url) {
|
|
161
|
+
const pathEnd = url.indexOf("?");
|
|
162
|
+
const path = pathEnd === -1 ? url : url.slice(0, pathEnd);
|
|
163
|
+
return path.split("/").filter(Boolean);
|
|
164
|
+
}
|
|
165
|
+
function abortedStatusCodeAttribute(headersSent, statusCode) {
|
|
166
|
+
return headersSent ? statusCode : undefined;
|
|
167
|
+
}
|
|
168
|
+
function wireHttpSpanLifecycle(res, span, getStatusCode, startTime) {
|
|
169
|
+
let spanEnded = false;
|
|
170
|
+
res.on("finish", () => {
|
|
171
|
+
if (spanEnded)
|
|
172
|
+
return;
|
|
173
|
+
spanEnded = true;
|
|
174
|
+
const statusCode = getStatusCode();
|
|
175
|
+
span.setAttributes({
|
|
176
|
+
"http.response.status_code": statusCode,
|
|
177
|
+
"agentswarm.http.duration_ms": Math.round((performance.now() - startTime) * 10) / 10
|
|
178
|
+
});
|
|
179
|
+
if (statusCode >= 500) {
|
|
180
|
+
span.setStatus({ code: 2, message: `HTTP ${statusCode}` });
|
|
181
|
+
}
|
|
182
|
+
span.end();
|
|
183
|
+
});
|
|
184
|
+
res.on("error", (err) => {
|
|
185
|
+
if (spanEnded)
|
|
186
|
+
return;
|
|
187
|
+
spanEnded = true;
|
|
188
|
+
if (err)
|
|
189
|
+
span.recordException(err);
|
|
190
|
+
span.setStatus({ code: 2, message: err?.message });
|
|
191
|
+
span.end();
|
|
192
|
+
});
|
|
193
|
+
res.on("close", () => {
|
|
194
|
+
if (spanEnded)
|
|
195
|
+
return;
|
|
196
|
+
spanEnded = true;
|
|
197
|
+
span.setAttributes({
|
|
198
|
+
"http.response.status_code": abortedStatusCodeAttribute(res.headersSent, getStatusCode()),
|
|
199
|
+
"agentswarm.http.duration_ms": Math.round((performance.now() - startTime) * 10) / 10,
|
|
200
|
+
"agentswarm.http.aborted": true
|
|
201
|
+
});
|
|
202
|
+
span.end();
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function safeRequestUrlForLog(rawUrl) {
|
|
206
|
+
if (!rawUrl)
|
|
207
|
+
return "";
|
|
208
|
+
try {
|
|
209
|
+
const url = new URL(rawUrl, "http://localhost");
|
|
210
|
+
const params = Array.from(url.searchParams.keys());
|
|
211
|
+
if (params.length === 0)
|
|
212
|
+
return url.pathname;
|
|
213
|
+
const redactedQuery = params.map((key) => `${key}=[REDACTED]`).join("&");
|
|
214
|
+
return `${url.pathname}?${redactedQuery}`;
|
|
215
|
+
} catch {
|
|
216
|
+
const pathOnly = rawUrl.split("?")[0] || rawUrl;
|
|
217
|
+
return scrubSecrets(pathOnly);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function agentWithCapacity(agent) {
|
|
221
|
+
const activeCount = await getActiveTaskCount2(agent.id);
|
|
222
|
+
const max = agent.maxTasks ?? 1;
|
|
223
|
+
return {
|
|
224
|
+
...agent,
|
|
225
|
+
capacity: {
|
|
226
|
+
current: activeCount,
|
|
227
|
+
max,
|
|
228
|
+
available: Math.max(0, max - activeCount)
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
class RequestBodyTooLargeError extends Error {
|
|
234
|
+
maxBytes;
|
|
235
|
+
constructor(maxBytes) {
|
|
236
|
+
super(`Payload too large (max ${maxBytes} bytes)`);
|
|
237
|
+
this.name = "RequestBodyTooLargeError";
|
|
238
|
+
this.maxBytes = maxBytes;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function requestContentLength(req) {
|
|
242
|
+
const raw = req.headers["content-length"];
|
|
243
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
244
|
+
if (!value)
|
|
245
|
+
return;
|
|
246
|
+
const parsed = Number(value);
|
|
247
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
|
|
248
|
+
}
|
|
249
|
+
async function parseBody(req, maxBytes = Number.POSITIVE_INFINITY) {
|
|
250
|
+
const contentLength = requestContentLength(req);
|
|
251
|
+
if (contentLength !== undefined && contentLength > maxBytes) {
|
|
252
|
+
req.resume();
|
|
253
|
+
throw new RequestBodyTooLargeError(maxBytes);
|
|
254
|
+
}
|
|
255
|
+
return await new Promise((resolve, reject) => {
|
|
256
|
+
const chunks = [];
|
|
257
|
+
let totalBytes = 0;
|
|
258
|
+
let settled = false;
|
|
259
|
+
const cleanup = () => {
|
|
260
|
+
req.off("data", onData);
|
|
261
|
+
req.off("end", onEnd);
|
|
262
|
+
req.off("error", onError);
|
|
263
|
+
req.off("aborted", onAborted);
|
|
264
|
+
req.off("close", onClose);
|
|
265
|
+
};
|
|
266
|
+
const rejectTooLarge = () => {
|
|
267
|
+
if (settled)
|
|
268
|
+
return;
|
|
269
|
+
settled = true;
|
|
270
|
+
reject(new RequestBodyTooLargeError(maxBytes));
|
|
271
|
+
req.resume();
|
|
272
|
+
};
|
|
273
|
+
const onData = (chunk) => {
|
|
274
|
+
if (settled)
|
|
275
|
+
return;
|
|
276
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
277
|
+
totalBytes += buffer.byteLength;
|
|
278
|
+
if (totalBytes > maxBytes) {
|
|
279
|
+
rejectTooLarge();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
chunks.push(buffer);
|
|
283
|
+
};
|
|
284
|
+
const onEnd = () => {
|
|
285
|
+
if (settled) {
|
|
286
|
+
cleanup();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
settled = true;
|
|
290
|
+
cleanup();
|
|
291
|
+
const raw = Buffer.concat(chunks).toString();
|
|
292
|
+
if (raw.trim() === "") {
|
|
293
|
+
resolve({});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
resolve(JSON.parse(raw));
|
|
298
|
+
} catch (error) {
|
|
299
|
+
reject(error);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
const onError = (error) => {
|
|
303
|
+
cleanup();
|
|
304
|
+
if (!settled) {
|
|
305
|
+
settled = true;
|
|
306
|
+
reject(error);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
const onAborted = () => onError(new Error("Request body was aborted"));
|
|
310
|
+
const onClose = () => {
|
|
311
|
+
if (!req.readableEnded)
|
|
312
|
+
onError(new Error("Request body closed before completion"));
|
|
313
|
+
};
|
|
314
|
+
req.on("data", onData);
|
|
315
|
+
req.on("end", onEnd);
|
|
316
|
+
req.on("error", onError);
|
|
317
|
+
req.on("aborted", onAborted);
|
|
318
|
+
req.on("close", onClose);
|
|
319
|
+
req.resume();
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
var BODY_TOO_LARGE = Symbol("body-too-large");
|
|
323
|
+
function enforceContentLengthCap(req, res, maxBytes) {
|
|
324
|
+
const raw = req.headers["content-length"];
|
|
325
|
+
const val = Array.isArray(raw) ? raw[0] : raw;
|
|
326
|
+
if (!val)
|
|
327
|
+
return null;
|
|
328
|
+
const n = Number(val);
|
|
329
|
+
if (!Number.isFinite(n) || n < 0)
|
|
330
|
+
return null;
|
|
331
|
+
if (n > maxBytes) {
|
|
332
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
333
|
+
res.end(JSON.stringify({ error: `Payload too large (max ${maxBytes} bytes)` }));
|
|
334
|
+
return BODY_TOO_LARGE;
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
function json(res, data, status = 200) {
|
|
339
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
340
|
+
res.end(JSON.stringify(data));
|
|
341
|
+
}
|
|
342
|
+
function jsonError(res, error, status = 400) {
|
|
343
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
344
|
+
res.end(JSON.stringify({ error: scrubSecrets(error) }));
|
|
345
|
+
}
|
|
346
|
+
function writeUnhandledError(res, err, req) {
|
|
347
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
348
|
+
console.error(`[HTTP] ❌ ${req?.method ?? ""} ${safeRequestUrlForLog(req?.url)} → ${scrubSecrets(message)}`);
|
|
349
|
+
if (!res.headersSent) {
|
|
350
|
+
jsonError(res, message, 500);
|
|
351
|
+
} else if (!res.writableEnded) {
|
|
352
|
+
res.end();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function triggerSchemaErrorResponse(res, message, details) {
|
|
356
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
357
|
+
res.end(JSON.stringify({ error: "TriggerSchemaError", message, details }));
|
|
358
|
+
}
|
|
359
|
+
function deriveApiBaseUrl(req) {
|
|
360
|
+
const publicBase = process.env.PUBLIC_MCP_BASE_URL?.trim();
|
|
361
|
+
if (publicBase)
|
|
362
|
+
return publicBase.replace(/\/+$/, "");
|
|
363
|
+
const envBase = process.env.MCP_BASE_URL?.trim();
|
|
364
|
+
if (envBase)
|
|
365
|
+
return envBase.replace(/\/+$/, "");
|
|
366
|
+
const fwdProtoRaw = req.headers["x-forwarded-proto"];
|
|
367
|
+
const fwdHostRaw = req.headers["x-forwarded-host"];
|
|
368
|
+
const fwdProto = Array.isArray(fwdProtoRaw) ? fwdProtoRaw[0] : fwdProtoRaw;
|
|
369
|
+
const fwdHost = Array.isArray(fwdHostRaw) ? fwdHostRaw[0] : fwdHostRaw;
|
|
370
|
+
const proto = fwdProto?.split(",")[0]?.trim() || "http";
|
|
371
|
+
const host = fwdHost?.split(",")[0]?.trim() || req.headers.host;
|
|
372
|
+
if (host)
|
|
373
|
+
return `${proto}://${host}`;
|
|
374
|
+
return `http://localhost:${process.env.PORT || "3013"}`;
|
|
375
|
+
}
|
|
376
|
+
function httpServerSemconvAttributes(req) {
|
|
377
|
+
const headerValue = (v) => Array.isArray(v) ? v[0] : v;
|
|
378
|
+
const firstHop = (v) => headerValue(v)?.split(",")[0]?.trim() || undefined;
|
|
379
|
+
const fwdProto = firstHop(req.headers["x-forwarded-proto"]);
|
|
380
|
+
const host = firstHop(req.headers["x-forwarded-host"]) ?? headerValue(req.headers.host)?.trim();
|
|
381
|
+
return {
|
|
382
|
+
"server.address": host ? host.replace(/:\d+$/, "") || undefined : undefined,
|
|
383
|
+
"url.scheme": fwdProto ?? "http",
|
|
384
|
+
"network.protocol.version": req.httpVersion || undefined,
|
|
385
|
+
"user_agent.original": headerValue(req.headers["user-agent"])
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function matchRoute(method, pathSegments, expectedMethod, pattern, exact = false) {
|
|
389
|
+
if (method !== expectedMethod)
|
|
390
|
+
return false;
|
|
391
|
+
for (let i = 0;i < pattern.length; i++) {
|
|
392
|
+
const seg = pattern[i];
|
|
393
|
+
if (seg === null) {
|
|
394
|
+
if (!pathSegments[i])
|
|
395
|
+
return false;
|
|
396
|
+
} else {
|
|
397
|
+
if (pathSegments[i] !== seg)
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (exact && pathSegments[pattern.length])
|
|
402
|
+
return false;
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/http/route-def.ts
|
|
407
|
+
var runtimeInstanceHeader = (operation) => object2({
|
|
408
|
+
"X-Runtime-Instance-ID": string2().optional().describe("Identifies the concrete runtime instance (worker process) making the call, " + `as generated at its boot. Required to ${operation} when multi-runtime mode ` + "(MULTI_RUNTIME_ENABLED) is on; ignored otherwise.")
|
|
409
|
+
});
|
|
410
|
+
function bodyTask(key) {
|
|
411
|
+
return { bodyTaskIds: (body) => [body?.[key]] };
|
|
412
|
+
}
|
|
413
|
+
function bodyListTask(listKey, key) {
|
|
414
|
+
return {
|
|
415
|
+
bodyTaskIds: (body) => {
|
|
416
|
+
const items = body?.[listKey];
|
|
417
|
+
return Array.isArray(items) ? items.map((item) => item?.[key]) : [];
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function routePathParams(def, pathSegments) {
|
|
422
|
+
const params = {};
|
|
423
|
+
const paramNames = def.path.match(/\{(\w+)\}/g)?.map((p) => p.slice(1, -1)) ?? [];
|
|
424
|
+
for (let i = 0;i < def.pattern.length; i++) {
|
|
425
|
+
if (def.pattern[i] === null && paramNames.length > 0) {
|
|
426
|
+
params[paramNames.shift()] = pathSegments[i] ?? "";
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return params;
|
|
430
|
+
}
|
|
431
|
+
var routeRegistry = [];
|
|
432
|
+
function isPublicRoute(method, pathSegments) {
|
|
433
|
+
for (const def of routeRegistry) {
|
|
434
|
+
if (def.auth?.apiKey === false) {
|
|
435
|
+
if (matchRoute(method, pathSegments, def.method.toUpperCase(), def.pattern, def.exact ?? true)) {
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
function findRoute(method, pathSegments) {
|
|
443
|
+
for (const def of routeRegistry) {
|
|
444
|
+
if (matchRoute(method, pathSegments, def.method.toUpperCase(), def.pattern, def.exact ?? true)) {
|
|
445
|
+
return def;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
var CORE_ROUTE_TEMPLATES = {
|
|
451
|
+
health: { path: "/health" },
|
|
452
|
+
"openapi.json": { path: "/openapi.json" },
|
|
453
|
+
docs: { path: "/docs", toleratesTrailingSlash: true },
|
|
454
|
+
me: { path: "/me", methods: ["GET"], toleratesQueryString: true },
|
|
455
|
+
"cancelled-tasks": {
|
|
456
|
+
path: "/cancelled-tasks",
|
|
457
|
+
methods: ["GET"],
|
|
458
|
+
toleratesQueryString: true
|
|
459
|
+
},
|
|
460
|
+
"internal/reload-config": { path: "/internal/reload-config", methods: ["POST"] },
|
|
461
|
+
mcp: { path: "/mcp", methods: ["GET", "POST", "DELETE"] },
|
|
462
|
+
"mcp-user": { path: "/mcp-user", methods: ["GET", "POST", "DELETE"] }
|
|
463
|
+
};
|
|
464
|
+
function describeRequestRoute(method, pathSegments, hasQueryString = false, hasTrailingSlash = false) {
|
|
465
|
+
const m = (method ?? "").toUpperCase() || "UNKNOWN";
|
|
466
|
+
const matched = findRoute(method, pathSegments);
|
|
467
|
+
if (matched)
|
|
468
|
+
return { spanName: `${m} ${matched.path}`, httpRoute: matched.path };
|
|
469
|
+
const coreTemplate = CORE_ROUTE_TEMPLATES[pathSegments.join("/")];
|
|
470
|
+
if (coreTemplate && (!coreTemplate.methods || coreTemplate.methods.includes(m)) && (!hasQueryString || coreTemplate.toleratesQueryString) && (!hasTrailingSlash || coreTemplate.toleratesTrailingSlash)) {
|
|
471
|
+
return { spanName: `${m} ${coreTemplate.path}`, httpRoute: coreTemplate.path };
|
|
472
|
+
}
|
|
473
|
+
const first = pathSegments[0];
|
|
474
|
+
return { spanName: first ? `${m} /${first}` : m };
|
|
475
|
+
}
|
|
476
|
+
var VALIDATE_RESPONSES = getNodeEnv() === "test" || getNodeEnv() === "development" || process.env.VALIDATE_HTTP_RESPONSES === "true";
|
|
477
|
+
function route(def) {
|
|
478
|
+
routeRegistry.push(def);
|
|
479
|
+
return {
|
|
480
|
+
def,
|
|
481
|
+
respond(res, code, data) {
|
|
482
|
+
if (VALIDATE_RESPONSES) {
|
|
483
|
+
const schema = def.responses[code]?.schema;
|
|
484
|
+
if (schema) {
|
|
485
|
+
const result = schema.safeParse(data);
|
|
486
|
+
if (!result.success) {
|
|
487
|
+
const detail = `Response schema violation: ${def.method.toUpperCase()} ${def.path} ${code} — ` + result.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join("; ");
|
|
488
|
+
if (getNodeEnv() === "test") {
|
|
489
|
+
throw new Error(detail);
|
|
490
|
+
}
|
|
491
|
+
console.error(`[route-def] ${scrubSecrets(detail)}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
res.writeHead(code, { "Content-Type": "application/json" });
|
|
496
|
+
res.end(JSON.stringify(data));
|
|
497
|
+
},
|
|
498
|
+
match(method, pathSegments) {
|
|
499
|
+
return matchRoute(method, pathSegments, def.method.toUpperCase(), def.pattern, def.exact ?? true);
|
|
500
|
+
},
|
|
501
|
+
async parse(req, res, pathSegments, queryParams) {
|
|
502
|
+
try {
|
|
503
|
+
const rawBody = def.body ? await parseBody(req, def.maxBodyBytes) : undefined;
|
|
504
|
+
if (def.session && "bodyTaskIds" in def.session) {
|
|
505
|
+
const { bodySessionDenial } = await import("./session-access-fc3f9y12.js");
|
|
506
|
+
const denial = await bodySessionDenial(req, def.session.bodyTaskIds(rawBody));
|
|
507
|
+
if (denial) {
|
|
508
|
+
jsonError(res, denial, 403);
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const params = def.params ? def.params.parse(routePathParams(def, pathSegments)) : undefined;
|
|
513
|
+
const query = def.query ? def.query.parse(Object.fromEntries(queryParams)) : undefined;
|
|
514
|
+
const body = def.body ? def.body.parse(rawBody) : undefined;
|
|
515
|
+
return { params, query, body };
|
|
516
|
+
} catch (err) {
|
|
517
|
+
if (err instanceof ZodError) {
|
|
518
|
+
jsonError(res, `Validation error: ${err.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")}`, 400);
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
if (err instanceof RequestBodyTooLargeError) {
|
|
522
|
+
jsonError(res, err.message, 413);
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
throw err;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/http/session-access.ts
|
|
532
|
+
init_db3();
|
|
533
|
+
init_active_sessions();
|
|
534
|
+
init_rbac_roles();
|
|
535
|
+
|
|
536
|
+
// src/be/session-shares.ts
|
|
537
|
+
init_db3();
|
|
538
|
+
var RANK = { none: 0, read: 1, write: 2, owner: 3 };
|
|
539
|
+
function hasSessionAccess(access, needed) {
|
|
540
|
+
return RANK[access] >= RANK[needed];
|
|
541
|
+
}
|
|
542
|
+
async function getSessionRoot(taskId) {
|
|
543
|
+
return (await getSessionRoots([taskId])).get(taskId) ?? null;
|
|
544
|
+
}
|
|
545
|
+
var ROOT_LOOKUP_CHUNK = 500;
|
|
546
|
+
async function getSessionRoots(taskIds) {
|
|
547
|
+
const ids = [...new Set(taskIds)];
|
|
548
|
+
const tasks = new Map;
|
|
549
|
+
for (let start = 0;start < ids.length; start += ROOT_LOOKUP_CHUNK) {
|
|
550
|
+
const chunk = ids.slice(start, start + ROOT_LOOKUP_CHUNK).filter((id) => !tasks.has(id));
|
|
551
|
+
if (chunk.length === 0)
|
|
552
|
+
continue;
|
|
553
|
+
const rows = await getDbClient2().query(`WITH RECURSIVE up(id) AS (
|
|
554
|
+
SELECT id FROM agent_tasks WHERE id IN (${chunk.map(() => "?").join(", ")})
|
|
555
|
+
UNION
|
|
556
|
+
SELECT t.parentTaskId FROM agent_tasks t JOIN up ON t.id = up.id
|
|
557
|
+
WHERE t.parentTaskId IS NOT NULL
|
|
558
|
+
)
|
|
559
|
+
SELECT t.id, t.parentTaskId, t.requestedByUserId
|
|
560
|
+
FROM up CROSS JOIN agent_tasks t ON t.id = up.id`, chunk);
|
|
561
|
+
for (const row of rows)
|
|
562
|
+
tasks.set(row.id, row);
|
|
563
|
+
}
|
|
564
|
+
const resolved = new Map;
|
|
565
|
+
const rootOf = (taskId) => {
|
|
566
|
+
const path = [];
|
|
567
|
+
const onPath = new Set;
|
|
568
|
+
let current = tasks.get(taskId);
|
|
569
|
+
let root = null;
|
|
570
|
+
while (current) {
|
|
571
|
+
const known = resolved.get(current.id);
|
|
572
|
+
if (known !== undefined) {
|
|
573
|
+
root = known;
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
if (onPath.has(current.id))
|
|
577
|
+
break;
|
|
578
|
+
path.push(current.id);
|
|
579
|
+
onPath.add(current.id);
|
|
580
|
+
const parent = current.parentTaskId ? tasks.get(current.parentTaskId) : undefined;
|
|
581
|
+
if (!parent) {
|
|
582
|
+
root = { id: current.id, requestedByUserId: current.requestedByUserId };
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
current = parent;
|
|
586
|
+
}
|
|
587
|
+
for (const id of path)
|
|
588
|
+
resolved.set(id, root);
|
|
589
|
+
return root;
|
|
590
|
+
};
|
|
591
|
+
const roots = new Map;
|
|
592
|
+
for (const id of ids) {
|
|
593
|
+
const root = rootOf(id);
|
|
594
|
+
if (root)
|
|
595
|
+
roots.set(id, root);
|
|
596
|
+
}
|
|
597
|
+
return roots;
|
|
598
|
+
}
|
|
599
|
+
async function sessionAccessForRoot(actingUserId, root) {
|
|
600
|
+
if (!root.requestedByUserId)
|
|
601
|
+
return "owner";
|
|
602
|
+
if (actingUserId && root.requestedByUserId === actingUserId)
|
|
603
|
+
return "owner";
|
|
604
|
+
const rows = await getDbClient2().query(`SELECT access FROM session_shares
|
|
605
|
+
WHERE rootTaskId = ? AND revokedAt IS NULL AND (userId IS NULL OR userId = ?)`, [root.id, actingUserId]);
|
|
606
|
+
if (rows.some((row) => row.access === "write"))
|
|
607
|
+
return "write";
|
|
608
|
+
return rows.length > 0 ? "read" : "none";
|
|
609
|
+
}
|
|
610
|
+
async function sessionAccess(actingUserId, taskId) {
|
|
611
|
+
const root = await getSessionRoot(taskId);
|
|
612
|
+
return root ? sessionAccessForRoot(actingUserId, root) : "none";
|
|
613
|
+
}
|
|
614
|
+
async function listActiveSessionShares(rootTaskId) {
|
|
615
|
+
return getDbClient2().query(`SELECT id, rootTaskId, userId, access, createdAt, revokedAt, revokedBy
|
|
616
|
+
FROM session_shares
|
|
617
|
+
WHERE rootTaskId = ? AND revokedAt IS NULL
|
|
618
|
+
ORDER BY createdAt, rowid`, [rootTaskId]);
|
|
619
|
+
}
|
|
620
|
+
function toSessionSharesState(rows) {
|
|
621
|
+
const everyone = rows.find((row) => row.userId === null);
|
|
622
|
+
return {
|
|
623
|
+
everyone: everyone?.access ?? "none",
|
|
624
|
+
users: rows.filter((row) => row.userId !== null).map((row) => ({ userId: row.userId, access: row.access, createdAt: row.createdAt }))
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
async function setSessionShares(root, desired, actorUserId) {
|
|
628
|
+
const want = new Map;
|
|
629
|
+
if (desired.everyone !== "none")
|
|
630
|
+
want.set("*", desired.everyone);
|
|
631
|
+
for (const user of desired.users) {
|
|
632
|
+
if (user.userId !== root.requestedByUserId)
|
|
633
|
+
want.set(user.userId, user.access);
|
|
634
|
+
}
|
|
635
|
+
return getDbClient2().transaction(async (tx) => {
|
|
636
|
+
const now = new Date().toISOString();
|
|
637
|
+
const active = await tx.query(`SELECT id, rootTaskId, userId, access, createdAt, revokedAt, revokedBy
|
|
638
|
+
FROM session_shares WHERE rootTaskId = ? AND revokedAt IS NULL`, [root.id]);
|
|
639
|
+
const kept = new Set;
|
|
640
|
+
for (const row of active) {
|
|
641
|
+
const target = row.userId ?? "*";
|
|
642
|
+
if (want.get(target) === row.access) {
|
|
643
|
+
kept.add(target);
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
await tx.run("UPDATE session_shares SET revokedAt = ?, revokedBy = ?, updated_by = ? WHERE id = ?", [now, actorUserId, actorUserId, row.id]);
|
|
647
|
+
}
|
|
648
|
+
for (const [target, access] of want) {
|
|
649
|
+
if (kept.has(target))
|
|
650
|
+
continue;
|
|
651
|
+
await tx.run(`INSERT INTO session_shares (id, rootTaskId, userId, access, createdAt, created_by, updated_by)
|
|
652
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
|
|
653
|
+
crypto.randomUUID(),
|
|
654
|
+
root.id,
|
|
655
|
+
target === "*" ? null : target,
|
|
656
|
+
access,
|
|
657
|
+
now,
|
|
658
|
+
actorUserId,
|
|
659
|
+
actorUserId
|
|
660
|
+
]);
|
|
661
|
+
}
|
|
662
|
+
const rows = await tx.query(`SELECT id, rootTaskId, userId, access, createdAt, revokedAt, revokedBy
|
|
663
|
+
FROM session_shares WHERE rootTaskId = ? AND revokedAt IS NULL
|
|
664
|
+
ORDER BY createdAt, rowid`, [root.id]);
|
|
665
|
+
return toSessionSharesState(rows);
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/http/session-access.ts
|
|
670
|
+
init_users();
|
|
671
|
+
init_rbac();
|
|
672
|
+
init_request_auth_context();
|
|
673
|
+
|
|
674
|
+
// src/be/audit-user.ts
|
|
675
|
+
init_request_auth_context();
|
|
676
|
+
init_db3();
|
|
677
|
+
init_users();
|
|
678
|
+
async function resolveTaskAuditUserId(sourceTaskId, callerAgentId) {
|
|
679
|
+
if (!callerAgentId)
|
|
680
|
+
return null;
|
|
681
|
+
let resolvedSourceTaskId = sourceTaskId;
|
|
682
|
+
if (!resolvedSourceTaskId) {
|
|
683
|
+
const currentTask = await getAgentCurrentTask2(callerAgentId);
|
|
684
|
+
if (currentTask)
|
|
685
|
+
resolvedSourceTaskId = currentTask.id;
|
|
686
|
+
}
|
|
687
|
+
if (!resolvedSourceTaskId)
|
|
688
|
+
return null;
|
|
689
|
+
const task = await getTaskById2(resolvedSourceTaskId);
|
|
690
|
+
if (!task)
|
|
691
|
+
return null;
|
|
692
|
+
if (task.agentId !== callerAgentId)
|
|
693
|
+
return null;
|
|
694
|
+
if (task.requestedByUserId)
|
|
695
|
+
return task.requestedByUserId;
|
|
696
|
+
if (task.slackUserId) {
|
|
697
|
+
const user = await findUserByExternalId("slack", task.slackUserId);
|
|
698
|
+
if (user)
|
|
699
|
+
return user.id;
|
|
700
|
+
}
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
async function resolveHttpAuditUserId(req, callerAgentId) {
|
|
704
|
+
const auth = getRequestAuth(req);
|
|
705
|
+
if (auth?.kind === "user")
|
|
706
|
+
return auth.userId;
|
|
707
|
+
const header = req.headers["x-source-task-id"];
|
|
708
|
+
const sourceTaskId = Array.isArray(header) ? header[0] : header;
|
|
709
|
+
return resolveTaskAuditUserId(sourceTaskId, callerAgentId);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// src/http/favorite-owner.ts
|
|
713
|
+
init_users();
|
|
714
|
+
init_request_auth_context();
|
|
715
|
+
var DASHBOARD_USER_HEADER = "x-swarm-user-id";
|
|
716
|
+
function dashboardUserIdHeader(req) {
|
|
717
|
+
const raw = req.headers[DASHBOARD_USER_HEADER];
|
|
718
|
+
const value = (Array.isArray(raw) ? raw[0] : raw)?.trim();
|
|
719
|
+
return value ? value : null;
|
|
720
|
+
}
|
|
721
|
+
async function resolveHttpFavoriteOwner(req, callerAgentId, opts = {}) {
|
|
722
|
+
const auth = getRequestAuth(req);
|
|
723
|
+
if (auth?.kind === "user") {
|
|
724
|
+
return { scope: `user:${auth.userId}`, userId: auth.userId, actorId: auth.userId };
|
|
725
|
+
}
|
|
726
|
+
if (auth?.kind === "operator" && !callerAgentId) {
|
|
727
|
+
const pickedUserId = opts.dashboardUser === false || auth.page ? null : dashboardUserIdHeader(req);
|
|
728
|
+
if (pickedUserId) {
|
|
729
|
+
const user = await findUserById(pickedUserId);
|
|
730
|
+
if (user?.status === "active") {
|
|
731
|
+
return { scope: `user:${user.id}`, userId: user.id, actorId: user.id };
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return {
|
|
735
|
+
scope: "operator",
|
|
736
|
+
userId: null,
|
|
737
|
+
actorId: auth.fingerprint
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const userId = await resolveHttpAuditUserId(req, callerAgentId);
|
|
741
|
+
return userId ? { scope: `user:${userId}`, userId, actorId: userId } : null;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/http/session-access.ts
|
|
745
|
+
var BYPASS = { kind: "bypass" };
|
|
746
|
+
async function resolveSessionActor2(req, myAgentId) {
|
|
747
|
+
const auth = getRequestAuth(req);
|
|
748
|
+
if (auth?.kind === "user")
|
|
749
|
+
return { kind: "user", userId: auth.userId };
|
|
750
|
+
if (auth?.kind === "guest")
|
|
751
|
+
return { kind: "user", userId: null };
|
|
752
|
+
if (auth?.kind === "operator" && !myAgentId && !auth.page) {
|
|
753
|
+
const picked = dashboardUserIdHeader(req);
|
|
754
|
+
if (!picked)
|
|
755
|
+
return BYPASS;
|
|
756
|
+
const user = await findUserById(picked);
|
|
757
|
+
return user?.status === "active" ? { kind: "user", userId: user.id } : BYPASS;
|
|
758
|
+
}
|
|
759
|
+
return BYPASS;
|
|
760
|
+
}
|
|
761
|
+
async function sessionAccessFor2(actor, taskId) {
|
|
762
|
+
return actor.kind === "bypass" ? "owner" : sessionAccess(actor.userId, taskId);
|
|
763
|
+
}
|
|
764
|
+
var SESSION_READ_DENIED2 = "You don't have access to this session. Ask the owner to share it.";
|
|
765
|
+
async function canReadSession2(req, myAgentId, taskId) {
|
|
766
|
+
const access = await sessionAccessFor2(await resolveSessionActor2(req, myAgentId), taskId);
|
|
767
|
+
return hasSessionAccess(access, "read");
|
|
768
|
+
}
|
|
769
|
+
function readableByFor2(actor) {
|
|
770
|
+
return actor.kind === "user" ? { userId: actor.userId } : undefined;
|
|
771
|
+
}
|
|
772
|
+
var SESSION_WRITE_DENIED2 = "You can't write in this session. Ask the owner to share it with edit access.";
|
|
773
|
+
async function denialFor(actor, taskId, needed) {
|
|
774
|
+
if (actor.kind === "bypass")
|
|
775
|
+
return null;
|
|
776
|
+
const root = await getSessionRoot(taskId);
|
|
777
|
+
if (!root)
|
|
778
|
+
return null;
|
|
779
|
+
if (hasSessionAccess(await sessionAccessForRoot(actor.userId, root), needed))
|
|
780
|
+
return null;
|
|
781
|
+
return needed === "read" ? SESSION_READ_DENIED2 : SESSION_WRITE_DENIED2;
|
|
782
|
+
}
|
|
783
|
+
var SESSION_AGENT_WIDE_DENIED = "This changes sessions across a whole agent. Only the agent or the operator can call it.";
|
|
784
|
+
async function pathSessionDenial2(req, myAgentId, def, pathSegments) {
|
|
785
|
+
const posture = def.session;
|
|
786
|
+
if (!posture || "bodyTaskIds" in posture || "none" in posture)
|
|
787
|
+
return null;
|
|
788
|
+
const actor = await resolveSessionActor2(req, myAgentId);
|
|
789
|
+
if (actor.kind === "bypass")
|
|
790
|
+
return null;
|
|
791
|
+
if ("agentWide" in posture)
|
|
792
|
+
return SESSION_AGENT_WIDE_DENIED;
|
|
793
|
+
const needed = def.method === "get" ? "read" : "write";
|
|
794
|
+
const params = routePathParams(def, pathSegments);
|
|
795
|
+
let taskId;
|
|
796
|
+
if ("taskParam" in posture) {
|
|
797
|
+
taskId = params[posture.taskParam];
|
|
798
|
+
} else if ("steeringParam" in posture) {
|
|
799
|
+
const message = await getSteeringMessageById2(params[posture.steeringParam] ?? "");
|
|
800
|
+
taskId = message?.taskId;
|
|
801
|
+
} else {
|
|
802
|
+
const row = await getActiveSessionById(params[posture.activeSessionParam] ?? "");
|
|
803
|
+
if (!row)
|
|
804
|
+
return null;
|
|
805
|
+
if (!row.taskId)
|
|
806
|
+
return needed === "read" ? SESSION_READ_DENIED2 : SESSION_WRITE_DENIED2;
|
|
807
|
+
taskId = row.taskId;
|
|
808
|
+
}
|
|
809
|
+
if (!taskId)
|
|
810
|
+
return null;
|
|
811
|
+
return denialFor(actor, taskId, needed);
|
|
812
|
+
}
|
|
813
|
+
async function bodySessionDenial2(req, taskIds) {
|
|
814
|
+
const ids = [...new Set(taskIds.filter((id) => typeof id === "string" && !!id))];
|
|
815
|
+
if (ids.length === 0)
|
|
816
|
+
return null;
|
|
817
|
+
const actor = await resolveSessionActor2(req, headerAgentId(req));
|
|
818
|
+
for (const id of ids) {
|
|
819
|
+
const denial = await denialFor(actor, id, "write");
|
|
820
|
+
if (denial)
|
|
821
|
+
return denial;
|
|
822
|
+
}
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
function headerAgentId(req) {
|
|
826
|
+
const header = req.headers["x-agent-id"];
|
|
827
|
+
return Array.isArray(header) ? header[0] : header;
|
|
828
|
+
}
|
|
829
|
+
var USER_AGENT_HEADER_DENIED = "A user token cannot act as an agent. Drop X-Agent-ID, or call with the agent's own credential.";
|
|
830
|
+
async function userAgentIdentityDenial2(req, def, pathSegments) {
|
|
831
|
+
const auth = getRequestAuth(req);
|
|
832
|
+
if (auth?.kind !== "user" && auth?.kind !== "guest")
|
|
833
|
+
return null;
|
|
834
|
+
if (pathSegments.length === 1 && pathSegments[0] === "mcp")
|
|
835
|
+
return USER_AGENT_HEADER_DENIED;
|
|
836
|
+
if (req.headers["x-agent-id"] === undefined)
|
|
837
|
+
return null;
|
|
838
|
+
if (auth.kind === "user" && def?.auth?.userNamesAgent) {
|
|
839
|
+
if (!isRbacEnabled())
|
|
840
|
+
return null;
|
|
841
|
+
const grant = await getUserGrant2(auth.userId);
|
|
842
|
+
if (grant.grantsAll || grant.verbs.has("agent.act.any"))
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
return USER_AGENT_HEADER_DENIED;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
export { duplicateGuardedQueryKey, warnIfCorsAllowsAnyOrigin, isOriginAllowedForCredentials, allowedCredentialRedirect, setCorsHeaders, parseQueryParams, getPathSegments, wireHttpSpanLifecycle, safeRequestUrlForLog, agentWithCapacity, parseBody, BODY_TOO_LARGE, enforceContentLengthCap, json, jsonError, writeUnhandledError, triggerSchemaErrorResponse, deriveApiBaseUrl, httpServerSemconvAttributes, hasSessionAccess, getSessionRoot, sessionAccessForRoot, listActiveSessionShares, toSessionSharesState, setSessionShares, resolveTaskAuditUserId, resolveHttpAuditUserId, dashboardUserIdHeader, resolveHttpFavoriteOwner, resolveSessionActor2, sessionAccessFor2, SESSION_READ_DENIED2, canReadSession2, readableByFor2, SESSION_WRITE_DENIED2, pathSessionDenial2, bodySessionDenial2, userAgentIdentityDenial2, runtimeInstanceHeader, bodyTask, bodyListTask, routeRegistry, isPublicRoute, findRoute, describeRequestRoute, route };
|