@desplega.ai/agent-swarm 2.3.0 → 2.4.1
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-zvzr538d.js} +9 -9
- package/dist/{actions-zgcxfspj.js → actions-jhtqdw69.js} +21 -19
- package/dist/{amp-adapter-jw9xfz42.js → amp-adapter-bd84rhs2.js} +4 -4
- package/dist/{app-4e1s4675.js → app-kreq1gwg.js} +3 -3
- package/dist/{assistant-v238n9dk.js → assistant-3k22sbsq.js} +23 -21
- package/dist/{boot-reembed-p666cw5n.js → boot-reembed-e53x6b9v.js} +1 -1
- package/dist/{boot-reembed-w6pm64j4.js → boot-reembed-f6z923ny.js} +1 -1
- package/dist/{boot-scrub-sweep-jcyjagn3.js → boot-scrub-sweep-0538vsb7.js} +4 -4
- package/dist/{boot-seeds-gbedmamp.js → boot-seeds-6wxqkfk2.js} +4 -4
- package/dist/{claude-adapter-q7wspzt8.js → claude-adapter-gfts5044.js} +9 -9
- package/dist/{claude-managed-adapter-n7ahjyx6.js → claude-managed-adapter-h2y889ge.js} +3 -3
- package/dist/{claude-managed-setup-wza784fb.js → claude-managed-setup-h00hsgtz.js} +2 -2
- package/dist/{claude-sdk-session-yf400ptr.js → claude-sdk-session-56besmfz.js} +13 -13
- package/dist/{cli-65q70v2v.js → cli-0bh477zn.js} +33 -6
- package/dist/{cli-4ync7m60.js → cli-0ebey9hg.js} +1 -1
- package/dist/{cli-665j3feb.js → cli-0xk505e8.js} +1 -1
- package/dist/{cli-7gh7020n.js → cli-1dmcq2eh.js} +1 -1
- package/dist/{cli-gzvb3d4a.js → cli-1jx0srjt.js} +3 -3
- package/dist/{cli-zj4d6afs.js → cli-1pa3rz69.js} +21 -6
- package/dist/{cli-7ayprxp7.js → cli-2dfbv8qr.js} +8 -0
- package/dist/{cli-30m5q8mc.js → cli-3kh4kpqy.js} +6 -6
- package/dist/{cli-qrwp40st.js → cli-3p8fqcnh.js} +2 -2
- package/dist/{cli-y3dvz6ge.js → cli-3r448dqn.js} +3 -3
- package/dist/{cli-a17tzmn0.js → cli-3tnpj2bh.js} +7 -7
- package/dist/{cli-ea7chd26.js → cli-471qk6s2.js} +93 -46
- package/dist/{cli-a49vpbf3.js → cli-5kqnemen.js} +1 -1
- package/dist/{cli-0rh20bp3.js → cli-6ncjvx55.js} +3 -3
- package/dist/{cli-fb6t92v8.js → cli-6x9ma5w5.js} +1 -1
- package/dist/{cli-k28p6xh7.js → cli-7225gwj7.js} +2 -2
- package/dist/{cli-8cvvytad.js → cli-8nyhf7za.js} +3 -3
- package/dist/{cli-e09hj50q.js → cli-8wkb09q5.js} +11 -11
- package/dist/{cli-j0v75y13.js → cli-a0kcty51.js} +4 -4
- package/dist/{cli-zes4tsbb.js → cli-a4gvwttf.js} +4 -4
- package/dist/{cli-jhp7w1fy.js → cli-a5ebc95n.js} +2 -2
- package/dist/{cli-5d1m1qca.js → cli-ash6xxk7.js} +2 -2
- package/dist/{cli-fp4xbw07.js → cli-axh29tyr.js} +14 -12
- package/dist/{cli-p22zdvnt.js → cli-bn0rewsy.js} +3 -3
- package/dist/{cli-hz64bcnj.js → cli-bn5kevnf.js} +11 -373
- package/dist/{cli-8jtrekpy.js → cli-d2mb5s8a.js} +7 -7
- package/dist/{cli-c5sbmw1v.js → cli-d82btycs.js} +94 -15
- package/dist/{cli-vjfpshn3.js → cli-ejs3m1sx.js} +1 -1
- package/dist/{cli-yv0vgscc.js → cli-fpmsqq59.js} +18 -2
- package/dist/cli-fz9kewbh.js +382 -0
- package/dist/{cli-4s1kqrqg.js → cli-g0hgtz4p.js} +6 -6
- package/dist/{cli-hn3pftj2.js → cli-gf0kfg81.js} +7 -7
- package/dist/{cli-d43xqzbe.js → cli-hqhzg1qq.js} +2 -2
- package/dist/{cli-azs9bhc5.js → cli-j4rqdf2n.js} +2 -2
- package/dist/{cli-wtg6edvd.js → cli-jh0qcrga.js} +1 -1
- package/dist/{cli-kqypty34.js → cli-k9z4zd4p.js} +6 -5
- package/dist/{cli-jhmjwqcg.js → cli-kmqgmcnb.js} +47 -14
- package/dist/{cli-8qsx817b.js → cli-kr2pmfqx.js} +583 -769
- package/dist/{cli-4g629sgr.js → cli-m2qgh5sv.js} +20 -1
- package/dist/{cli-9e9kjqsj.js → cli-mfe7f7b5.js} +2 -2
- package/dist/{cli-x3en6ct3.js → cli-nwyw3zhg.js} +1 -1
- package/dist/{cli-6vcyw6v7.js → cli-nx462e4v.js} +5 -3
- package/dist/{cli-tr24eeny.js → cli-p0rakbrk.js} +9 -9
- package/dist/{cli-4hmaheb6.js → cli-p6argxh9.js} +9 -9
- package/dist/{cli-3avfh5az.js → cli-pemywhfq.js} +2 -2
- package/dist/{cli-etvn5ej2.js → cli-pnp2j5zg.js} +2 -2
- package/dist/{cli-wk8ccrz6.js → cli-pw7v69vp.js} +2 -2
- package/dist/{cli-ka01m5rb.js → cli-rdq7tzwm.js} +17 -14
- package/dist/{cli-re4wkbzx.js → cli-rqqsfz30.js} +677 -104
- package/dist/{cli-xgn4rwyv.js → cli-s48yvzb8.js} +4 -4
- package/dist/{cli-zv4vy2b0.js → cli-s95dknxh.js} +1 -1
- package/dist/{cli-g6p7c2my.js → cli-sjyetj21.js} +2 -2
- package/dist/{cli-rdr166ht.js → cli-smvcw9z5.js} +1 -1
- package/dist/{cli-fhvqt2fp.js → cli-sv5gb0yb.js} +1 -1
- package/dist/{cli-yv3n83x8.js → cli-syhq378v.js} +2 -2
- package/dist/{cli-q1812jbw.js → cli-tbhwrxpt.js} +9 -9
- package/dist/{cli-w7z94e6c.js → cli-tx00c0h6.js} +4 -2
- package/dist/{cli-dxwh4j32.js → cli-vy0fay6k.js} +46 -37
- package/dist/{cli-ptkpaqfy.js → cli-wt645v7q.js} +1 -1
- package/dist/{cli-ppapy34w.js → cli-wxbn3yfx.js} +2 -2
- package/dist/{cli-cby329ny.js → cli-x5mq1fyq.js} +3 -3
- package/dist/{cli-rc3r0bnh.js → cli-xbzvet96.js} +6 -6
- package/dist/{cli-4mkvwgzr.js → cli-xf234ws8.js} +1 -1
- package/dist/{cli-6p7ddg72.js → cli-xftrvxag.js} +5 -5
- package/dist/cli-z071fb95.js +848 -0
- package/dist/{cli-1v8w782f.js → cli-z1ew8vbq.js} +14 -7
- package/dist/{cli-9td5ss65.js → cli-zfg9enh9.js} +3 -3
- package/dist/{cli-yxqag76b.js → cli-zw88zjba.js} +1 -1
- package/dist/cli.js +19 -19
- package/dist/{codex-adapter-nhjj2veb.js → codex-adapter-p6rmm7vv.js} +6 -6
- package/dist/{codex-hook-x6tm6y5m.js → codex-hook-x7nejyrr.js} +8 -5
- package/dist/{codex-login-35p4sk85.js → codex-login-m819c41r.js} +1 -1
- package/dist/{codex-session-runner-6hggrjbx.js → codex-session-runner-8vd5t9t0.js} +7 -7
- package/dist/{commands-mrw1ph2w.js → commands-7ems2p13.js} +2 -2
- package/dist/{controller-pt1zam5w.js → controller-m263fg43.js} +4 -4
- package/dist/{controllers-connect-ymw5541d.js → controllers-connect-bg33ae53.js} +28 -24
- package/dist/{cursor-adapter-cf4zh5x0.js → cursor-adapter-cz6nm6mj.js} +3 -3
- package/dist/{db-hy80219q.js → db-w505dwa8.js} +2 -2
- package/dist/{db-hhpy6m4x.js → db-ys2rbdft.js} +3 -2
- package/dist/{devin-adapter-7egzmt0k.js → devin-adapter-zgqadvnm.js} +1 -1
- package/dist/{dispatcher-6pdjtkr1.js → dispatcher-0s0mckqr.js} +20 -18
- package/dist/{docker-njavd3fb.js → docker-fkgtr75t.js} +2 -2
- package/dist/{dsh-adapter-fbx06re2.js → dsh-adapter-3n3net33.js} +3 -3
- package/dist/{e2b-5r6a5jeb.js → e2b-7am6tmq6.js} +2 -2
- package/dist/{e2b-pw1nvtmh.js → e2b-thdcxtjd.js} +2 -2
- package/dist/{easter-egg-3d-6hg2r942.js → easter-egg-3d-5cgynaja.js} +1 -1
- package/dist/{event-bus-101fenkh.js → event-bus-9vpk8gmq.js} +1 -1
- package/dist/{execute-bcqc69a5.js → execute-t4e6m3rw.js} +1 -1
- package/dist/{external-channel-j6vb6ya1.js → external-channel-5b23fv8z.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-q9fghe5q.js} +10 -10
- package/dist/handlers-mqrym347.js +39 -0
- package/dist/{heartbeat-h1w1ktce.js → heartbeat-tww6yx1w.js} +23 -21
- package/dist/{hook-ejja4bpe.js → hook-dx0pkvar.js} +16 -9
- package/dist/{hook-skrbja18.js → hook-pvmh9b6m.js} +6 -6
- package/dist/{http-mzh41mfj.js → http-k2ejbt05.js} +975 -292
- package/dist/{index-6rmnn89d.js → index-2z710v4x.js} +10 -10
- package/dist/{index-xmmkyx82.js → index-dgz61nff.js} +20 -18
- package/dist/{index-wgsw9xcz.js → index-g6tbb6ve.js} +1 -1
- package/dist/index-h2ek08d6.js +40 -0
- package/dist/index-pqfwh9nn.js +41 -0
- package/dist/{index-epr2gnbw.js → index-x5aktmp8.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-nhvpw10z.js} +4 -4
- package/dist/lead-jb6sf69z.js +78 -0
- package/dist/{maintenance-f3xhnjsm.js → maintenance-agakc519.js} +1 -1
- package/dist/{oauth-refresh-sweep-fpd6x6rw.js → oauth-refresh-sweep-4dcbhtn0.js} +3 -3
- package/dist/{onboard-196tjgyn.js → onboard-4n2kwa4v.js} +3 -3
- package/dist/{opencode-adapter-eeesgfqk.js → opencode-adapter-3s7ax02e.js} +7 -7
- package/dist/{otel-impl-g8fh4c21.js → otel-impl-tfa12080.js} +2 -2
- package/dist/{pi-mono-adapter-zasp3ze3.js → pi-mono-adapter-ytp7gz8b.js} +24 -13
- package/dist/{pricing-refresh-sdcf5yaz.js → pricing-refresh-3cqx3f3x.js} +3 -3
- package/dist/{rbac-roles-bsp5657v.js → rbac-roles-9fae5rnv.js} +5 -3
- package/dist/{rbac-roles-2x4g1wb5.js → rbac-roles-y6zbfzve.js} +2 -2
- package/dist/{render-v2-0pyb39bb.js → render-v2-emawjvc4.js} +3 -3
- package/dist/{resume-a86tkj6r.js → resume-zhgts7h7.js} +10 -8
- package/dist/{runtime-ar8310ba.js → runtime-xe3aqy4x.js} +1 -1
- package/dist/session-access-cm823e06.js +25 -0
- package/dist/{setup-ypbzcapd.js → setup-tkp99w1e.js} +1 -1
- package/dist/{virtual-modules-wzk6jf3a.js → virtual-modules-4r3zkc20.js} +2 -2
- package/dist/{work-objects-yjp8de12.js → work-objects-f722h263.js} +23 -21
- package/dist/worker-p7f336rq.js +78 -0
- package/dist/{x-8ewt8gxn.js → x-y45mthwn.js} +2 -2
- package/openapi.json +1086 -305
- package/package.json +2 -2
- package/src/apps/action-caller.ts +6 -0
- package/src/apps/action-runner.ts +146 -0
- package/src/apps/row-store.ts +4 -2
- package/src/apps/script-types.ts +17 -4
- 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 +106 -37
- package/src/be/events.ts +11 -0
- package/src/be/fleet-benchmark-stats.ts +10 -5
- package/src/be/migrations/230_session_shares.sql +23 -0
- package/src/be/models-catalog.ts +4 -0
- package/src/be/onboarding.ts +6 -0
- package/src/be/rbac-roles.ts +2 -0
- package/src/be/scripts/typecheck.ts +13 -0
- package/src/be/session-shares.ts +244 -0
- package/src/be/swarm-config-guard.ts +33 -3
- package/src/be/telemetry-enable.ts +1 -1
- package/src/commands/provider-credentials.ts +12 -2
- package/src/commands/runner.ts +14 -0
- package/src/heartbeat/heartbeat.ts +2 -2
- package/src/hooks/codex-hook.ts +6 -2
- package/src/hooks/hook.ts +7 -0
- package/src/hooks/pr-body-guard.ts +38 -11
- package/src/hooks/pr-room-guard.ts +88 -0
- package/src/http/active-sessions.ts +13 -2
- package/src/http/agents.ts +11 -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 +118 -190
- 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/fleet-benchmark.ts +79 -30
- package/src/http/fs.ts +38 -3
- package/src/http/global-config.ts +7 -0
- package/src/http/index.ts +3 -1
- package/src/http/memory.ts +3 -1
- package/src/http/ranking.ts +351 -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 +85 -8
- package/src/http/telemetry-ui.ts +1 -1
- package/src/http/used-by.ts +64 -20
- package/src/http/users.ts +2 -2
- package/src/prompts/session-templates.ts +3 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +15 -0
- package/src/providers/reasoning-effort.ts +2 -1
- package/src/providers/types.ts +2 -0
- package/src/ranking/telemetry-ranking.ts +119 -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/scripts-runtime/sdk-allowlist.ts +7 -0
- package/src/scripts-runtime/types/stdlib.d.ts +31 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +31 -0
- package/src/server.ts +4 -0
- package/src/slack/blocks.ts +18 -0
- package/src/slack/dm.ts +29 -0
- package/src/telemetry-context.ts +19 -1
- package/src/telemetry-contract/EVENTS.md +45 -8
- package/src/telemetry-contract/SOURCE.json +38 -4
- package/src/telemetry-contract/catalog.json +294 -5
- 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/no-claim.json +10 -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 +963 -58
- package/src/telemetry-identity.ts +47 -9
- package/src/telemetry-snapshot.ts +9 -1
- package/src/telemetry-ui-events.ts +9 -2
- package/src/telemetry.ts +40 -5
- package/src/tests/app-action-run-tool.test.ts +178 -0
- package/src/tests/app-row-tools.test.ts +360 -0
- package/src/tests/apps-rbac.test.ts +189 -2
- package/src/tests/apps-script-types.test.ts +16 -3
- package/src/tests/credential-check.test.ts +38 -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/fleet-benchmark.test.ts +122 -2
- package/src/tests/http-users.test.ts +62 -1
- package/src/tests/model-catalog-table.test.ts +63 -0
- package/src/tests/model-profiles-api.test.ts +19 -7
- package/src/tests/models-catalog.test.ts +50 -0
- package/src/tests/onboarding.test.ts +21 -0
- package/src/tests/org-identity.test.ts +44 -0
- package/src/tests/org-website.test.ts +62 -0
- package/src/tests/pi-mono-adapter.test.ts +23 -0
- package/src/tests/pr-body-leaks.test.ts +4 -0
- package/src/tests/pr-room-guard.test.ts +242 -0
- package/src/tests/ranking-listing.test.ts +97 -0
- package/src/tests/ranking-route.test.ts +321 -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/reasoning-effort.test.ts +39 -3
- package/src/tests/runner-harness-telemetry-env.test.ts +56 -1
- 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/swarm-tool-result-gate.test.ts +2 -4
- package/src/tests/telemetry-context.test.ts +128 -2
- package/src/tests/telemetry-ui-route.test.ts +123 -0
- package/src/tests/tool-annotations.test.ts +3 -2
- package/src/tests/used-by-route.test.ts +153 -9
- package/src/tests/workflow-agent-task-retry.test.ts +130 -4
- package/src/tests/workflow-wait-recovery.test.ts +116 -4
- package/src/tools/app-action-run.ts +63 -0
- package/src/tools/app-rows.ts +234 -0
- package/src/tools/model-catalog/index.ts +1 -1
- 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/tools/tool-config.ts +8 -0
- package/src/tools/utils.ts +1 -1
- package/src/tools/workflows/retry-workflow-run.ts +1 -1
- package/src/types.ts +52 -0
- package/src/utils/company-profile.ts +24 -0
- package/src/utils/org-identity.ts +33 -0
- package/src/utils/org-website.ts +58 -21
- package/src/workflows/recovery.ts +10 -22
- package/src/workflows/resume.ts +16 -0
- package/src/workflows/task-step-routing.ts +47 -7
- package/templates/skills/apps/SKILL.md +58 -2
- package/templates/skills/apps/content.md +58 -2
- package/dist/handlers-tb3xgjnm.js +0 -37
- package/dist/index-nyfcdpky.js +0 -39
- package/dist/index-vjn3wbzf.js +0 -38
- package/dist/lead-61kqp604.js +0 -76
- package/dist/worker-4ptnn1ge.js +0 -76
- package/dist/{cli-n5mrsg0f.js → cli-60cgh1rv.js} +3 -3
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* - `context.org_domain`: the email DOMAIN of the org's admin (never the email)
|
|
6
6
|
* - `context.user_role`: `admin` when the user's grants include everything, else `member`
|
|
7
|
+
* - `context.user_function`: a canonical job function from `users.metadata.jobFunction`
|
|
8
|
+
* (not `users.role`: the dashboard reads an empty or `admin` role as admin-like)
|
|
7
9
|
*
|
|
8
10
|
* This file must not import `./be/db` (`be/db.ts` imports the trigger module
|
|
9
11
|
* that imports this one). It reads through the DB client and lazy-imports RBAC.
|
|
@@ -11,26 +13,36 @@
|
|
|
11
13
|
import { getDbClient } from "./be/db/runtime";
|
|
12
14
|
import { setTelemetryOrgDomain } from "./telemetry";
|
|
13
15
|
import { emailDomain, type TelemetryUserRole } from "./telemetry-context";
|
|
16
|
+
import { USER_FUNCTIONS } from "./utils/company-profile";
|
|
14
17
|
|
|
15
18
|
const ROLE_TTL_MS = 60 * 60_000;
|
|
16
19
|
const ROLE_CACHE_LIMIT = 1_000;
|
|
17
20
|
const DOMAIN_RECOMPUTE_DEBOUNCE_MS = 2_000;
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
interface UserRoleEntry {
|
|
23
|
+
role: TelemetryUserRole;
|
|
24
|
+
userFunction?: (typeof USER_FUNCTIONS)[number];
|
|
25
|
+
at: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const roleCache = new Map<string, UserRoleEntry>();
|
|
20
29
|
const roleLoads = new Map<string, Promise<TelemetryUserRole | undefined>>();
|
|
21
30
|
|
|
22
|
-
async function loadUserRole(userId: string): Promise<
|
|
31
|
+
async function loadUserRole(userId: string): Promise<UserRoleEntry | undefined> {
|
|
23
32
|
try {
|
|
24
33
|
// Lazy: be/rbac-roles imports be/db, which (transitively) imports this file.
|
|
25
34
|
const { getUserGrant } = await import("./be/rbac-roles");
|
|
26
35
|
const grant = await getUserGrant(userId);
|
|
27
36
|
const role: TelemetryUserRole = grant.grantsAll ? "admin" : "member";
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
const user = await getDbClient().get<{ jobFunction: string | null }>(
|
|
38
|
+
"SELECT json_extract(metadata, '$.jobFunction') AS jobFunction FROM users WHERE id = ? AND json_valid(metadata)",
|
|
39
|
+
[userId],
|
|
40
|
+
);
|
|
41
|
+
return {
|
|
42
|
+
role,
|
|
43
|
+
userFunction: USER_FUNCTIONS.find((value) => value === user?.jobFunction),
|
|
44
|
+
at: Date.now(),
|
|
45
|
+
};
|
|
34
46
|
} catch {
|
|
35
47
|
return undefined;
|
|
36
48
|
}
|
|
@@ -42,7 +54,20 @@ export function primeUserRole(userId: string): Promise<TelemetryUserRole | undef
|
|
|
42
54
|
if (hit && Date.now() - hit.at < ROLE_TTL_MS) return Promise.resolve(hit.role);
|
|
43
55
|
let load = roleLoads.get(userId);
|
|
44
56
|
if (!load) {
|
|
45
|
-
load = loadUserRole(userId)
|
|
57
|
+
load = loadUserRole(userId)
|
|
58
|
+
.then((entry) => {
|
|
59
|
+
if (entry && roleLoads.get(userId) === load) {
|
|
60
|
+
if (roleCache.size >= ROLE_CACHE_LIMIT) {
|
|
61
|
+
const oldest = roleCache.keys().next().value;
|
|
62
|
+
if (oldest !== undefined) roleCache.delete(oldest);
|
|
63
|
+
}
|
|
64
|
+
roleCache.set(userId, entry);
|
|
65
|
+
}
|
|
66
|
+
return entry?.role;
|
|
67
|
+
})
|
|
68
|
+
.finally(() => {
|
|
69
|
+
if (roleLoads.get(userId) === load) roleLoads.delete(userId);
|
|
70
|
+
});
|
|
46
71
|
roleLoads.set(userId, load);
|
|
47
72
|
}
|
|
48
73
|
return load;
|
|
@@ -59,6 +84,19 @@ export function cachedUserRole(userId: string): TelemetryUserRole | undefined {
|
|
|
59
84
|
return hit?.role;
|
|
60
85
|
}
|
|
61
86
|
|
|
87
|
+
/** Resolve the job function from the same cache and lookup as the RBAC role. */
|
|
88
|
+
export function cachedUserFunction(userId: string): UserRoleEntry["userFunction"] {
|
|
89
|
+
const hit = roleCache.get(userId);
|
|
90
|
+
if (!hit || Date.now() - hit.at >= ROLE_TTL_MS) void primeUserRole(userId);
|
|
91
|
+
return hit?.userFunction;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Invalidate after a user mutation commits, including any lookup already in progress. */
|
|
95
|
+
export function invalidateTelemetryUserRole(userId: string): void {
|
|
96
|
+
roleCache.delete(userId);
|
|
97
|
+
roleLoads.delete(userId);
|
|
98
|
+
}
|
|
99
|
+
|
|
62
100
|
/**
|
|
63
101
|
* The email domain of the org's earliest-created active admin, falling back to
|
|
64
102
|
* the earliest active user with an email. A candidate whose address has no
|
|
@@ -9,9 +9,15 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { getDbClient } from "./be/db/runtime";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
isTelemetryReady,
|
|
14
|
+
setTelemetryFunctionResolver,
|
|
15
|
+
setTelemetryRoleResolver,
|
|
16
|
+
telemetry,
|
|
17
|
+
} from "./telemetry";
|
|
13
18
|
import type { PropsFor } from "./telemetry-contract";
|
|
14
19
|
import {
|
|
20
|
+
cachedUserFunction,
|
|
15
21
|
cachedUserRole,
|
|
16
22
|
configureOrgDomainPersistence,
|
|
17
23
|
recomputeOrgDomain,
|
|
@@ -169,6 +175,7 @@ export function startTelemetryTicker(deps: SnapshotDeps): void {
|
|
|
169
175
|
if (!isTelemetryReady()) return;
|
|
170
176
|
stopTelemetryTicker();
|
|
171
177
|
setTelemetryRoleResolver(cachedUserRole);
|
|
178
|
+
setTelemetryFunctionResolver(cachedUserFunction);
|
|
172
179
|
|
|
173
180
|
const tick = async () => {
|
|
174
181
|
try {
|
|
@@ -207,5 +214,6 @@ export function stopTelemetryTicker(): void {
|
|
|
207
214
|
tickTimer = null;
|
|
208
215
|
firstTimer = null;
|
|
209
216
|
setTelemetryRoleResolver(null);
|
|
217
|
+
setTelemetryFunctionResolver(null);
|
|
210
218
|
configureOrgDomainPersistence(null);
|
|
211
219
|
}
|
|
@@ -15,6 +15,7 @@ interface PropertySpec {
|
|
|
15
15
|
minimum?: number;
|
|
16
16
|
required?: boolean;
|
|
17
17
|
nullable?: boolean;
|
|
18
|
+
items?: PropertySpec;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
interface CatalogEntry {
|
|
@@ -62,8 +63,8 @@ export function uiEventEmitter(event: string): string | undefined {
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
function resolveSpec(spec: PropertySpec): PropertySpec {
|
|
65
|
-
|
|
66
|
-
return { ...
|
|
66
|
+
const resolved = spec.ref ? { ...file.definitions[spec.ref], ...spec } : spec;
|
|
67
|
+
return resolved.items ? { ...resolved, items: resolveSpec(resolved.items) } : resolved;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function typeMatches(type: string | undefined, value: unknown): boolean {
|
|
@@ -117,6 +118,12 @@ export function validateUiEvent(event: string, properties: Record<string, unknow
|
|
|
117
118
|
if (spec.enum && !spec.enum.includes(value as string)) {
|
|
118
119
|
problems.push(`"${key}" must be one of ${spec.enum.join(", ")}`);
|
|
119
120
|
}
|
|
121
|
+
if (Array.isArray(value) && spec.items?.enum) {
|
|
122
|
+
const allowed = spec.items.enum;
|
|
123
|
+
if (value.some((item) => !allowed.includes(item))) {
|
|
124
|
+
problems.push(`"${key}" items must be one of ${allowed.join(", ")}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
120
127
|
if (typeof spec.minimum === "number" && typeof value === "number" && value < spec.minimum) {
|
|
121
128
|
problems.push(`"${key}" must be at least ${spec.minimum}`);
|
|
122
129
|
}
|
package/src/telemetry.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
validOrgDomain,
|
|
23
23
|
validOrgId,
|
|
24
24
|
} from "./telemetry-context";
|
|
25
|
-
import type { PropsFor, TelemetryEventName } from "./telemetry-contract";
|
|
25
|
+
import type { EventContext, PropsFor, TelemetryEventName } from "./telemetry-contract";
|
|
26
26
|
import { isEnvFlagEnabled } from "./utils/env-flag";
|
|
27
27
|
import { getNodeEnv } from "./utils/node-env";
|
|
28
28
|
import { normalizeOrgWebsite } from "./utils/org-website";
|
|
@@ -68,6 +68,7 @@ let cachedOrgId: string | undefined;
|
|
|
68
68
|
/** Email domain of the org's admin, set by the API server (`setTelemetryOrgDomain`). */
|
|
69
69
|
let cachedOrgDomain: string | undefined;
|
|
70
70
|
let roleResolver: ((userId: string) => TelemetryUserRole | undefined) | null = null;
|
|
71
|
+
let functionResolver: ((userId: string) => EventContext["user_function"]) | null = null;
|
|
71
72
|
let identityReloader: (() => Promise<void>) | null = null;
|
|
72
73
|
let lastIdentityLoadAt = 0;
|
|
73
74
|
let source = "unknown";
|
|
@@ -510,6 +511,13 @@ export function setTelemetryRoleResolver(
|
|
|
510
511
|
roleResolver = resolver;
|
|
511
512
|
}
|
|
512
513
|
|
|
514
|
+
/** API server only: cache-backed lookup of the actor's job function. */
|
|
515
|
+
export function setTelemetryFunctionResolver(
|
|
516
|
+
resolver: ((userId: string) => EventContext["user_function"]) | null,
|
|
517
|
+
): void {
|
|
518
|
+
functionResolver = resolver;
|
|
519
|
+
}
|
|
520
|
+
|
|
513
521
|
/**
|
|
514
522
|
* True when `track()` would send: telemetry is on, an install ID exists and an
|
|
515
523
|
* org ID resolved. Emitters that record "already sent" state check this first.
|
|
@@ -630,6 +638,7 @@ function emit(
|
|
|
630
638
|
metadata?: Record<string, unknown>,
|
|
631
639
|
actor?: TelemetryActor,
|
|
632
640
|
emitter: string = source,
|
|
641
|
+
identity?: { orgName: string; orgWebsite: string },
|
|
633
642
|
): void {
|
|
634
643
|
if (!isTelemetryEnabled() || !installationId) return;
|
|
635
644
|
maybeRefreshOrgIdentity();
|
|
@@ -639,7 +648,9 @@ function emit(
|
|
|
639
648
|
const occurredAt = new Date().toISOString();
|
|
640
649
|
const sentFor = installationId;
|
|
641
650
|
const isCloud = cachedIsCloud || isCloudDeployment();
|
|
642
|
-
const orgIdentity =
|
|
651
|
+
const orgIdentity = identity
|
|
652
|
+
? { ...getOrgIdentity(), organization_name: identity.orgName }
|
|
653
|
+
: getOrgIdentity();
|
|
643
654
|
const userId = actor?.userId?.trim() || undefined;
|
|
644
655
|
const payload = {
|
|
645
656
|
schema_version: 2 as const,
|
|
@@ -653,7 +664,7 @@ function emit(
|
|
|
653
664
|
orgId,
|
|
654
665
|
orgName: orgIdentity.organization_name,
|
|
655
666
|
orgDomain: cachedOrgDomain,
|
|
656
|
-
orgWebsite: normalizeOrgWebsite(process.env.SWARM_ORG_WEBSITE),
|
|
667
|
+
orgWebsite: identity?.orgWebsite ?? normalizeOrgWebsite(process.env.SWARM_ORG_WEBSITE),
|
|
657
668
|
installationId,
|
|
658
669
|
actor: userId ? { userId, role: actor?.role ?? roleResolver?.(userId) ?? null } : undefined,
|
|
659
670
|
isCloud,
|
|
@@ -661,8 +672,10 @@ function emit(
|
|
|
661
672
|
swarmVersion: pkg.version,
|
|
662
673
|
installMethod: cachedInstallMethod,
|
|
663
674
|
installPreset: cachedInstallPreset,
|
|
664
|
-
|
|
665
|
-
|
|
675
|
+
acquisitionSource: process.env.SWARM_ORG_ACQUISITION_SOURCE,
|
|
676
|
+
teamSize: process.env.SWARM_ORG_TEAM_SIZE,
|
|
677
|
+
useCase: process.env.SWARM_ORG_USE_CASE,
|
|
678
|
+
userFunction: userId ? functionResolver?.(userId) : undefined,
|
|
666
679
|
}),
|
|
667
680
|
properties: normalizeProperties(event, {
|
|
668
681
|
...properties,
|
|
@@ -742,6 +755,7 @@ export function _resetTelemetryStateForTests(): void {
|
|
|
742
755
|
cachedOrgId = undefined;
|
|
743
756
|
cachedOrgDomain = undefined;
|
|
744
757
|
roleResolver = null;
|
|
758
|
+
functionResolver = null;
|
|
745
759
|
identityReloader = null;
|
|
746
760
|
lastIdentityLoadAt = 0;
|
|
747
761
|
}
|
|
@@ -820,6 +834,27 @@ export const telemetry = {
|
|
|
820
834
|
);
|
|
821
835
|
},
|
|
822
836
|
|
|
837
|
+
/**
|
|
838
|
+
* Public-ranking events. The API passes the org identity it just read from
|
|
839
|
+
* config, so a listing built right after an identity edit still matches the
|
|
840
|
+
* envelope's `org_name` / `org_website` (the proxy rejects a mismatch) before
|
|
841
|
+
* the debounced config reload updates `process.env`.
|
|
842
|
+
*/
|
|
843
|
+
ranking<S extends EventSuffix<"ranking">>(
|
|
844
|
+
event: S,
|
|
845
|
+
props: FamilyProps<"ranking", S>,
|
|
846
|
+
identity?: { orgName: string; orgWebsite: string },
|
|
847
|
+
): void {
|
|
848
|
+
emit(
|
|
849
|
+
`ranking.${event}`,
|
|
850
|
+
props as Record<string, unknown>,
|
|
851
|
+
undefined,
|
|
852
|
+
undefined,
|
|
853
|
+
source,
|
|
854
|
+
identity,
|
|
855
|
+
);
|
|
856
|
+
},
|
|
857
|
+
|
|
823
858
|
org<S extends EventSuffix<"org">>(event: S, props: FamilyProps<"org", S>): void {
|
|
824
859
|
emit(`org.${event}`, props as Record<string, unknown>, undefined);
|
|
825
860
|
},
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { afterAll, beforeAll, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { createServer as createHttpServer } from "node:http";
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { type AppRecord, createApp } from "../apps/store";
|
|
6
|
+
import { closeDb, createAgent, getTaskById, initDb } from "../be/db";
|
|
7
|
+
import { upsertScriptByName } from "../be/scripts/db";
|
|
8
|
+
import { handleCore } from "../http/core";
|
|
9
|
+
import { handleMcpBridge } from "../http/mcp-bridge";
|
|
10
|
+
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
11
|
+
import { LEGACY_POLICY, type LegacyRule } from "../rbac";
|
|
12
|
+
import { isMcpToolAllowedForScripts } from "../scripts-runtime/sdk-allowlist";
|
|
13
|
+
import { registerAppActionRunTool } from "../tools/app-action-run";
|
|
14
|
+
import { markExtensionRequestOrigin, markScriptSdkRequestOrigin } from "../tools/utils";
|
|
15
|
+
import { SKIP_SANDBOX_SPAWN_TESTS } from "./sandbox-spawn-test-helpers";
|
|
16
|
+
import { listenOnFreePort } from "./test-net";
|
|
17
|
+
import { CHILD_PROCESS_TEST_BUDGET_MS } from "./test-proc";
|
|
18
|
+
|
|
19
|
+
const dbPath = `/private/tmp/test-app-action-run-tool-${process.pid}.sqlite`;
|
|
20
|
+
const agentId = crypto.randomUUID();
|
|
21
|
+
const ownerId = crypto.randomUUID();
|
|
22
|
+
const apiKey = "example-app-action-run-key";
|
|
23
|
+
const previousKey = process.env.AGENT_SWARM_API_KEY;
|
|
24
|
+
const previousBase = process.env.MCP_BASE_URL;
|
|
25
|
+
const definition: AppRecord["definition"] = {
|
|
26
|
+
schemaVersion: 1,
|
|
27
|
+
models: { note: { columns: { title: { kind: "string" } } } },
|
|
28
|
+
actions: { triage: { kind: "task", prompt: "Triage the notes." } },
|
|
29
|
+
pages: { main: { root: "root", elements: { root: { type: "Container", props: {} } } } },
|
|
30
|
+
defaultPage: "main",
|
|
31
|
+
};
|
|
32
|
+
type ToolResult = {
|
|
33
|
+
isError?: boolean;
|
|
34
|
+
structuredContent: {
|
|
35
|
+
message: string;
|
|
36
|
+
data: { ok: boolean; taskId: string; status: string; result?: unknown };
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
let handler: (args: unknown, extra: unknown) => Promise<ToolResult>;
|
|
40
|
+
let appId: string;
|
|
41
|
+
|
|
42
|
+
async function removeDb() {
|
|
43
|
+
for (const suffix of ["", "-wal", "-shm"]) await unlink(dbPath + suffix).catch(() => undefined);
|
|
44
|
+
}
|
|
45
|
+
beforeAll(async () => {
|
|
46
|
+
process.env.AGENT_SWARM_API_KEY = apiKey;
|
|
47
|
+
await removeDb();
|
|
48
|
+
initDb(dbPath);
|
|
49
|
+
await createAgent({ id: agentId, name: "Action invoker", isLead: false, status: "idle" });
|
|
50
|
+
await createAgent({ id: ownerId, name: "Script owner", isLead: true, status: "idle" });
|
|
51
|
+
appId = (await createApp({ name: "Action tool", definition })).id;
|
|
52
|
+
const server = new McpServer({ name: "app-action-run-tool", version: "1.0.0" });
|
|
53
|
+
registerAppActionRunTool(server);
|
|
54
|
+
handler = (server as unknown as { _registeredTools: Record<string, { handler: typeof handler }> })
|
|
55
|
+
._registeredTools["app-action-run"]!.handler;
|
|
56
|
+
});
|
|
57
|
+
afterAll(async () => {
|
|
58
|
+
closeDb();
|
|
59
|
+
await removeDb();
|
|
60
|
+
if (previousKey === undefined) delete process.env.AGENT_SWARM_API_KEY;
|
|
61
|
+
else process.env.AGENT_SWARM_API_KEY = previousKey;
|
|
62
|
+
if (previousBase === undefined) delete process.env.MCP_BASE_URL;
|
|
63
|
+
else process.env.MCP_BASE_URL = previousBase;
|
|
64
|
+
});
|
|
65
|
+
function call(
|
|
66
|
+
action: string,
|
|
67
|
+
input: Record<string, unknown> = {},
|
|
68
|
+
origin?: "script-sdk" | "extension",
|
|
69
|
+
id = agentId,
|
|
70
|
+
target = appId,
|
|
71
|
+
) {
|
|
72
|
+
const extra = { sessionId: "action-tool", requestInfo: { headers: { "x-agent-id": id } } };
|
|
73
|
+
if (origin === "script-sdk") markScriptSdkRequestOrigin(extra);
|
|
74
|
+
if (origin === "extension") markExtensionRequestOrigin(extra);
|
|
75
|
+
return handler({ appId: target, action, input }, extra);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
test("runs task actions with the verified caller and the REST response shape", async () => {
|
|
79
|
+
const output = await call("triage", { caller: { kind: "guest", id: "forged" } });
|
|
80
|
+
expect(output.isError).not.toBe(true);
|
|
81
|
+
expect(output.structuredContent).toMatchObject({ ok: true, status: "pending" });
|
|
82
|
+
const task = await getTaskById(output.structuredContent.taskId);
|
|
83
|
+
expect(task?.task).toContain(`"kind":"agent","id":"${agentId}","name":"Action invoker"`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("refuses script and extension origins and stays outside the SDK allowlist", async () => {
|
|
87
|
+
for (const origin of ["script-sdk", "extension"] as const) {
|
|
88
|
+
const output = await call("triage", {}, origin);
|
|
89
|
+
expect(output.isError).toBe(true);
|
|
90
|
+
expect(output.structuredContent.message).toContain("cannot run app actions");
|
|
91
|
+
}
|
|
92
|
+
expect(isMcpToolAllowedForScripts("app-action-run")).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("rejects missing agents, apps, actions, and denied principals", async () => {
|
|
96
|
+
expect((await call("triage", {}, undefined, "")).isError).toBe(true);
|
|
97
|
+
expect((await call("triage", {}, undefined, agentId, "missing")).isError).toBe(true);
|
|
98
|
+
expect((await call("missing")).isError).toBe(true);
|
|
99
|
+
const policy = LEGACY_POLICY as unknown as Record<"app.use", LegacyRule>;
|
|
100
|
+
const original = policy["app.use"];
|
|
101
|
+
policy["app.use"] = { name: "deny-actions", denyReason: "denied actions", evaluate: () => false };
|
|
102
|
+
try {
|
|
103
|
+
expect((await call("triage")).isError).toBe(true);
|
|
104
|
+
} finally {
|
|
105
|
+
policy["app.use"] = original;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test.skipIf(SKIP_SANDBOX_SPAWN_TESTS)(
|
|
110
|
+
"runs script actions as the owner while preserving the invoking agent caller",
|
|
111
|
+
async () => {
|
|
112
|
+
const server = createHttpServer(async (req, res) => {
|
|
113
|
+
const id = req.headers["x-agent-id"] as string | undefined;
|
|
114
|
+
if (await handleCore(req, res, id, apiKey)) return;
|
|
115
|
+
if (
|
|
116
|
+
await handleMcpBridge(
|
|
117
|
+
req,
|
|
118
|
+
res,
|
|
119
|
+
getPathSegments(req.url ?? ""),
|
|
120
|
+
parseQueryParams(req.url ?? ""),
|
|
121
|
+
id,
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
return;
|
|
125
|
+
res.writeHead(404).end();
|
|
126
|
+
});
|
|
127
|
+
try {
|
|
128
|
+
const port = await listenOnFreePort(server);
|
|
129
|
+
process.env.MCP_BASE_URL = `http://localhost:${port}`;
|
|
130
|
+
const saved = await upsertScriptByName({
|
|
131
|
+
name: "action_tool_owner",
|
|
132
|
+
scope: "agent",
|
|
133
|
+
scopeId: ownerId,
|
|
134
|
+
agentId: ownerId,
|
|
135
|
+
source: `export default async function(args, ctx) {
|
|
136
|
+
const response = await ctx.swarm.app_row_create({ appId: args.app.id, model: "note", values: { title: "owner-write" } });
|
|
137
|
+
return { caller: args.caller, row: (response?.data ?? response).row };
|
|
138
|
+
}`,
|
|
139
|
+
description: "Owner action fixture",
|
|
140
|
+
intent: "Verify script owner and caller",
|
|
141
|
+
signatureJson: "{}",
|
|
142
|
+
typeChecked: true,
|
|
143
|
+
embeddingMode: "skip",
|
|
144
|
+
});
|
|
145
|
+
const app = await createApp({
|
|
146
|
+
name: "Script action",
|
|
147
|
+
definition: {
|
|
148
|
+
...definition,
|
|
149
|
+
actions: {
|
|
150
|
+
write: {
|
|
151
|
+
kind: "script",
|
|
152
|
+
scriptId: saved.script.id,
|
|
153
|
+
args: { caller: { kind: "guest", id: "default-forgery" } },
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
const output = await call(
|
|
159
|
+
"write",
|
|
160
|
+
{ caller: { kind: "operator", id: "forged" } },
|
|
161
|
+
undefined,
|
|
162
|
+
agentId,
|
|
163
|
+
app.id,
|
|
164
|
+
);
|
|
165
|
+
expect(output.isError).not.toBe(true);
|
|
166
|
+
expect(output.structuredContent).toMatchObject({
|
|
167
|
+
ok: true,
|
|
168
|
+
result: {
|
|
169
|
+
caller: { kind: "agent", id: agentId, name: "Action invoker" },
|
|
170
|
+
row: { title: "owner-write", createdBy: `agent:${ownerId}` },
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
} finally {
|
|
174
|
+
if (server.listening) await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
178
|
+
);
|