@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
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { IncomingMessage } from "node:http";
|
|
2
|
+
import { getSteeringMessageById } from "../be/db";
|
|
3
|
+
import { getActiveSessionById } from "../be/db/active-sessions";
|
|
4
|
+
import { getUserGrant } from "../be/rbac-roles";
|
|
5
|
+
import {
|
|
6
|
+
getSessionRoot,
|
|
7
|
+
hasSessionAccess,
|
|
8
|
+
type SessionAccess,
|
|
9
|
+
sessionAccess,
|
|
10
|
+
sessionAccessForRoot,
|
|
11
|
+
} from "../be/session-shares";
|
|
12
|
+
import { findUserById } from "../be/users";
|
|
13
|
+
import { isRbacEnabled } from "../rbac";
|
|
14
|
+
import { getRequestAuth } from "../utils/request-auth-context";
|
|
15
|
+
import { dashboardUserIdHeader } from "./favorite-owner";
|
|
16
|
+
import { type RouteDef, routePathParams } from "./route-def";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Who a request acts for when it touches a session.
|
|
20
|
+
*
|
|
21
|
+
* - `bypass`: no session check. Agents (an `aseph_` token, or the operator key
|
|
22
|
+
* with `X-Agent-ID`) continue chains through `POST /api/tasks` all the time,
|
|
23
|
+
* and an operator-key call with no picked dashboard user is a script or the
|
|
24
|
+
* CLI. Both keep the access they had before sharing existed.
|
|
25
|
+
* - `user`: an `aswt_` user token, or an operator-key dashboard tab naming an
|
|
26
|
+
* active user in `X-Swarm-User-Id`. On the shared key that identity is
|
|
27
|
+
* honor-system, the same trust the dashboard already gives favorites.
|
|
28
|
+
* `userId: null` is a guest page session, which has no user at all.
|
|
29
|
+
*/
|
|
30
|
+
export type SessionActor = { kind: "bypass" } | { kind: "user"; userId: string | null };
|
|
31
|
+
|
|
32
|
+
const BYPASS: SessionActor = { kind: "bypass" };
|
|
33
|
+
|
|
34
|
+
export async function resolveSessionActor(
|
|
35
|
+
req: IncomingMessage,
|
|
36
|
+
myAgentId: string | undefined,
|
|
37
|
+
): Promise<SessionActor> {
|
|
38
|
+
const auth = getRequestAuth(req);
|
|
39
|
+
if (auth?.kind === "user") return { kind: "user", userId: auth.userId };
|
|
40
|
+
if (auth?.kind === "guest") return { kind: "user", userId: null };
|
|
41
|
+
if (auth?.kind === "operator" && !myAgentId && !auth.page) {
|
|
42
|
+
const picked = dashboardUserIdHeader(req);
|
|
43
|
+
if (!picked) return BYPASS;
|
|
44
|
+
const user = await findUserById(picked);
|
|
45
|
+
return user?.status === "active" ? { kind: "user", userId: user.id } : BYPASS;
|
|
46
|
+
}
|
|
47
|
+
return BYPASS;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The actor's access to the session holding `taskId`; `owner` for a bypass actor. */
|
|
51
|
+
export async function sessionAccessFor(
|
|
52
|
+
actor: SessionActor,
|
|
53
|
+
taskId: string,
|
|
54
|
+
): Promise<SessionAccess> {
|
|
55
|
+
return actor.kind === "bypass" ? "owner" : sessionAccess(actor.userId, taskId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const SESSION_READ_DENIED =
|
|
59
|
+
"You don't have access to this session. Ask the owner to share it.";
|
|
60
|
+
|
|
61
|
+
/** True unless the request acts for a user who lacks read access to the task's session. */
|
|
62
|
+
export async function canReadSession(
|
|
63
|
+
req: IncomingMessage,
|
|
64
|
+
myAgentId: string | undefined,
|
|
65
|
+
taskId: string,
|
|
66
|
+
): Promise<boolean> {
|
|
67
|
+
const access = await sessionAccessFor(await resolveSessionActor(req, myAgentId), taskId);
|
|
68
|
+
return hasSessionAccess(access, "read");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The SQL list filter (`readableSessionCondition`) for an actor; none for a bypass actor. */
|
|
72
|
+
export function readableByFor(actor: SessionActor): { userId: string | null } | undefined {
|
|
73
|
+
return actor.kind === "user" ? { userId: actor.userId } : undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const SESSION_WRITE_DENIED =
|
|
77
|
+
"You can't write in this session. Ask the owner to share it with edit access.";
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The denial for an actor lacking `needed` access to the session holding
|
|
81
|
+
* `taskId`, or null. An unknown task passes, so the handler still answers 404.
|
|
82
|
+
*/
|
|
83
|
+
async function denialFor(
|
|
84
|
+
actor: SessionActor,
|
|
85
|
+
taskId: string,
|
|
86
|
+
needed: "read" | "write",
|
|
87
|
+
): Promise<string | null> {
|
|
88
|
+
if (actor.kind === "bypass") return null;
|
|
89
|
+
const root = await getSessionRoot(taskId);
|
|
90
|
+
if (!root) return null;
|
|
91
|
+
if (hasSessionAccess(await sessionAccessForRoot(actor.userId, root), needed)) return null;
|
|
92
|
+
return needed === "read" ? SESSION_READ_DENIED : SESSION_WRITE_DENIED;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const SESSION_AGENT_WIDE_DENIED =
|
|
96
|
+
"This changes sessions across a whole agent. Only the agent or the operator can call it.";
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Enforces a route's path session posture (`taskParam`, `steeringParam`,
|
|
100
|
+
* `activeSessionParam`, `agentWide`) before any handler runs: a GET needs read
|
|
101
|
+
* access, every other method write access.
|
|
102
|
+
*/
|
|
103
|
+
export async function pathSessionDenial(
|
|
104
|
+
req: IncomingMessage,
|
|
105
|
+
myAgentId: string | undefined,
|
|
106
|
+
def: RouteDef,
|
|
107
|
+
pathSegments: string[],
|
|
108
|
+
): Promise<string | null> {
|
|
109
|
+
const posture = def.session;
|
|
110
|
+
if (!posture || "bodyTaskIds" in posture || "none" in posture) return null;
|
|
111
|
+
const actor = await resolveSessionActor(req, myAgentId);
|
|
112
|
+
if (actor.kind === "bypass") return null;
|
|
113
|
+
if ("agentWide" in posture) return SESSION_AGENT_WIDE_DENIED;
|
|
114
|
+
const needed = def.method === "get" ? "read" : "write";
|
|
115
|
+
const params = routePathParams(def, pathSegments);
|
|
116
|
+
let taskId: string | undefined;
|
|
117
|
+
if ("taskParam" in posture) {
|
|
118
|
+
taskId = params[posture.taskParam];
|
|
119
|
+
} else if ("steeringParam" in posture) {
|
|
120
|
+
const message = await getSteeringMessageById(params[posture.steeringParam] ?? "");
|
|
121
|
+
taskId = message?.taskId;
|
|
122
|
+
} else {
|
|
123
|
+
const row = await getActiveSessionById(params[posture.activeSessionParam] ?? "");
|
|
124
|
+
// An unknown row passes so the handler answers; a row with no task is the agent's own.
|
|
125
|
+
if (!row) return null;
|
|
126
|
+
if (!row.taskId) return needed === "read" ? SESSION_READ_DENIED : SESSION_WRITE_DENIED;
|
|
127
|
+
taskId = row.taskId;
|
|
128
|
+
}
|
|
129
|
+
if (!taskId) return null;
|
|
130
|
+
return denialFor(actor, taskId, needed);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Enforces a `bodyTaskIds` posture: every task the body names needs write access. */
|
|
134
|
+
export async function bodySessionDenial(
|
|
135
|
+
req: IncomingMessage,
|
|
136
|
+
taskIds: readonly unknown[],
|
|
137
|
+
): Promise<string | null> {
|
|
138
|
+
const ids = [...new Set(taskIds.filter((id): id is string => typeof id === "string" && !!id))];
|
|
139
|
+
if (ids.length === 0) return null;
|
|
140
|
+
const actor = await resolveSessionActor(req, headerAgentId(req));
|
|
141
|
+
for (const id of ids) {
|
|
142
|
+
const denial = await denialFor(actor, id, "write");
|
|
143
|
+
if (denial) return denial;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function headerAgentId(req: IncomingMessage): string | undefined {
|
|
149
|
+
const header = req.headers["x-agent-id"];
|
|
150
|
+
return Array.isArray(header) ? header[0] : header;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const USER_AGENT_HEADER_DENIED =
|
|
154
|
+
"A user token cannot act as an agent. Drop X-Agent-ID, or call with the agent's own credential.";
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Refuses a user or guest token that claims agent identity: any X-Agent-ID,
|
|
158
|
+
* and the agent MCP transport at all (`/mcp-user` is the user transport).
|
|
159
|
+
* Runs once in `handleCore`, before any handler reads or changes state. A
|
|
160
|
+
* route that sets `auth.userNamesAgent` (the dashboard script playground)
|
|
161
|
+
* admits the pair for a user holding `agent.act.any`, an authorization of the
|
|
162
|
+
* user, never of the named agent.
|
|
163
|
+
*/
|
|
164
|
+
export async function userAgentIdentityDenial(
|
|
165
|
+
req: IncomingMessage,
|
|
166
|
+
def: RouteDef | undefined,
|
|
167
|
+
pathSegments: string[],
|
|
168
|
+
): Promise<string | null> {
|
|
169
|
+
const auth = getRequestAuth(req);
|
|
170
|
+
if (auth?.kind !== "user" && auth?.kind !== "guest") return null;
|
|
171
|
+
if (pathSegments.length === 1 && pathSegments[0] === "mcp") return USER_AGENT_HEADER_DENIED;
|
|
172
|
+
if (req.headers["x-agent-id"] === undefined) return null;
|
|
173
|
+
if (auth.kind === "user" && def?.auth?.userNamesAgent) {
|
|
174
|
+
if (!isRbacEnabled()) return null;
|
|
175
|
+
const grant = await getUserGrant(auth.userId);
|
|
176
|
+
if (grant.grantsAll || grant.verbs.has("agent.act.any")) return null;
|
|
177
|
+
}
|
|
178
|
+
return USER_AGENT_HEADER_DENIED;
|
|
179
|
+
}
|
package/src/http/session-data.ts
CHANGED
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
SessionCostSchema,
|
|
25
25
|
SessionLogSchema,
|
|
26
26
|
} from "../types";
|
|
27
|
-
import { route } from "./route-def";
|
|
27
|
+
import { bodyTask, route } from "./route-def";
|
|
28
|
+
import { canReadSession, SESSION_READ_DENIED } from "./session-access";
|
|
28
29
|
import { recomputeSessionCost } from "./session-cost-recompute";
|
|
29
30
|
import { cachedUsageReport } from "./usage-cache";
|
|
30
31
|
import { jsonError } from "./utils";
|
|
@@ -127,6 +128,7 @@ const createSessionLogsRoute = route({
|
|
|
127
128
|
method: "post",
|
|
128
129
|
path: "/api/session-logs",
|
|
129
130
|
pattern: ["api", "session-logs"],
|
|
131
|
+
session: bodyTask("taskId"),
|
|
130
132
|
summary: "Store session logs",
|
|
131
133
|
tags: ["Session Data"],
|
|
132
134
|
body: z.object({
|
|
@@ -149,6 +151,7 @@ const getSessionLogsByTask = route({
|
|
|
149
151
|
method: "get",
|
|
150
152
|
path: "/api/tasks/{taskId}/session-logs",
|
|
151
153
|
pattern: ["api", "tasks", null, "session-logs"],
|
|
154
|
+
session: { taskParam: "taskId" },
|
|
152
155
|
summary: "Get session logs for a task",
|
|
153
156
|
tags: ["Session Data"],
|
|
154
157
|
params: z.object({ taskId: z.string() }),
|
|
@@ -172,6 +175,7 @@ const createSessionCostRoute = route({
|
|
|
172
175
|
method: "post",
|
|
173
176
|
path: "/api/session-costs",
|
|
174
177
|
pattern: ["api", "session-costs"],
|
|
178
|
+
session: bodyTask("taskId"),
|
|
175
179
|
summary: "Store session cost record",
|
|
176
180
|
tags: ["Session Data"],
|
|
177
181
|
body: z.object({
|
|
@@ -310,7 +314,7 @@ export async function handleSessionData(
|
|
|
310
314
|
res: ServerResponse,
|
|
311
315
|
pathSegments: string[],
|
|
312
316
|
queryParams: URLSearchParams,
|
|
313
|
-
|
|
317
|
+
myAgentId: string | undefined,
|
|
314
318
|
): Promise<boolean> {
|
|
315
319
|
if (createSessionLogsRoute.match(req.method, pathSegments)) {
|
|
316
320
|
const parsed = await createSessionLogsRoute.parse(req, res, pathSegments, queryParams);
|
|
@@ -353,6 +357,10 @@ export async function handleSessionData(
|
|
|
353
357
|
jsonError(res, "Task not found", 404);
|
|
354
358
|
return true;
|
|
355
359
|
}
|
|
360
|
+
if (!(await canReadSession(req, myAgentId, task.id))) {
|
|
361
|
+
jsonError(res, SESSION_READ_DENIED, 403);
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
356
364
|
const logs = await getSessionLogsByTaskId(parsed.params.taskId, parsed.query?.limit);
|
|
357
365
|
getSessionLogsByTask.respond(res, 200, { logs });
|
|
358
366
|
return true;
|
package/src/http/sessions.ts
CHANGED
|
@@ -7,18 +7,40 @@ import {
|
|
|
7
7
|
getTaskById,
|
|
8
8
|
listRecentSessions,
|
|
9
9
|
} from "../be/db";
|
|
10
|
+
import {
|
|
11
|
+
getSessionRoot,
|
|
12
|
+
listActiveSessionShares,
|
|
13
|
+
type SessionAccess,
|
|
14
|
+
sessionAccessForRoot,
|
|
15
|
+
setSessionShares,
|
|
16
|
+
toSessionSharesState,
|
|
17
|
+
} from "../be/session-shares";
|
|
10
18
|
import { getTaskSteeringFieldsForTasks } from "../be/steering";
|
|
11
19
|
import { getTaskCitationsForTasks, TaskCitationSchema } from "../be/task-citations";
|
|
12
20
|
import {
|
|
21
|
+
findUserById,
|
|
13
22
|
InvalidRuntimeTokenError,
|
|
14
23
|
mintRuntimeToken,
|
|
15
24
|
mintSessionToken,
|
|
16
25
|
revokeSessionToken,
|
|
17
26
|
} from "../be/users";
|
|
18
|
-
import {
|
|
27
|
+
import { can } from "../rbac";
|
|
28
|
+
import { sessionAccessTopic } from "../realtime/auth";
|
|
29
|
+
import { realtimeBus } from "../realtime/bus";
|
|
30
|
+
import {
|
|
31
|
+
type AgentTask,
|
|
32
|
+
AgentTaskSchema,
|
|
33
|
+
AgentTaskStatusSchema,
|
|
34
|
+
SessionAccessSchema,
|
|
35
|
+
SessionOwnerSchema,
|
|
36
|
+
SessionSharesSchema,
|
|
37
|
+
SessionSharesUpdateSchema,
|
|
38
|
+
SteerModeSchema,
|
|
39
|
+
} from "../types";
|
|
19
40
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
20
41
|
import { isRuntimePoolsEnabled } from "../utils/runtime-pools-flag";
|
|
21
42
|
import { route } from "./route-def";
|
|
43
|
+
import { resolveSessionActor, type SessionActor } from "./session-access";
|
|
22
44
|
import { jsonError } from "./utils";
|
|
23
45
|
|
|
24
46
|
// ─── Response Schemas ────────────────────────────────────────────────────────
|
|
@@ -91,6 +113,9 @@ const mintSessionTokenRoute = route({
|
|
|
91
113
|
method: "post",
|
|
92
114
|
path: "/api/sessions/tokens",
|
|
93
115
|
pattern: ["api", "sessions", "tokens"],
|
|
116
|
+
session: {
|
|
117
|
+
none: "user tokens cannot mint: the handler admits only the operator key or the task's own runtime",
|
|
118
|
+
},
|
|
94
119
|
summary: "Mint an ephemeral task or runtime token",
|
|
95
120
|
description:
|
|
96
121
|
"Operators can mint task or runtime tokens. Runtime tokens can mint task tokens for their own assigned tasks. Child tokens expire with their parent runtime.",
|
|
@@ -175,6 +200,14 @@ const listSessions = route({
|
|
|
175
200
|
contextKeyPrefix: z.string().min(1).optional(),
|
|
176
201
|
/** `full` restores the legacy shape (full root `AgentTask`); default is slim. */
|
|
177
202
|
fields: z.enum(["full", "slim"]).optional(),
|
|
203
|
+
/**
|
|
204
|
+
* `mine`: sessions the caller owns. `shared`: sessions someone else owns
|
|
205
|
+
* with an active share to the caller or to everyone. Omit for every
|
|
206
|
+
* session the caller can open. Callers that act for no user (agents, the
|
|
207
|
+
* operator key with no picked dashboard user) see every session, and
|
|
208
|
+
* an empty list for either scope.
|
|
209
|
+
*/
|
|
210
|
+
scope: z.enum(["mine", "shared"]).optional(),
|
|
178
211
|
}),
|
|
179
212
|
responses: {
|
|
180
213
|
200: {
|
|
@@ -195,23 +228,78 @@ const getSession = route({
|
|
|
195
228
|
method: "get",
|
|
196
229
|
path: "/api/sessions/{rootTaskId}",
|
|
197
230
|
pattern: ["api", "sessions", null],
|
|
231
|
+
session: { taskParam: "rootTaskId" },
|
|
198
232
|
summary: "Get a session — root task + the entire descendant chain",
|
|
199
233
|
tags: ["Sessions"],
|
|
200
234
|
params: z.object({ rootTaskId: z.string() }),
|
|
201
235
|
responses: {
|
|
202
236
|
200: {
|
|
203
|
-
description:
|
|
237
|
+
description:
|
|
238
|
+
"Root task + chain (ordered by createdAt), plus the caller's `access` and the session `owner`",
|
|
204
239
|
schema: z.object({
|
|
205
240
|
root: TaskWithSteeringSchema,
|
|
206
241
|
chain: z.array(TaskWithSteeringSchema),
|
|
242
|
+
access: SessionAccessSchema,
|
|
243
|
+
owner: SessionOwnerSchema.nullable(),
|
|
207
244
|
}),
|
|
208
245
|
},
|
|
209
246
|
401: { description: "Unauthorized" },
|
|
247
|
+
403: { description: "The caller has no access to this session" },
|
|
248
|
+
404: { description: "Root task not found" },
|
|
249
|
+
},
|
|
250
|
+
auth: { apiKey: true },
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const getSessionShares = route({
|
|
254
|
+
method: "get",
|
|
255
|
+
path: "/api/sessions/{rootTaskId}/shares",
|
|
256
|
+
pattern: ["api", "sessions", null, "shares"],
|
|
257
|
+
session: { taskParam: "rootTaskId" },
|
|
258
|
+
summary: "List a session's active shares",
|
|
259
|
+
description:
|
|
260
|
+
"Active shares only. `everyone` is the access every user has; `users` are per-person shares. A session with no shares is private to its owner.",
|
|
261
|
+
tags: ["Sessions"],
|
|
262
|
+
params: z.object({ rootTaskId: z.string() }),
|
|
263
|
+
responses: {
|
|
264
|
+
200: { description: "Active shares", schema: SessionSharesSchema },
|
|
265
|
+
401: { description: "Unauthorized" },
|
|
266
|
+
403: { description: "The caller has no access to this session" },
|
|
267
|
+
404: { description: "Root task not found" },
|
|
268
|
+
},
|
|
269
|
+
auth: { apiKey: true },
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const putSessionShares = route({
|
|
273
|
+
method: "put",
|
|
274
|
+
path: "/api/sessions/{rootTaskId}/shares",
|
|
275
|
+
pattern: ["api", "sessions", null, "shares"],
|
|
276
|
+
session: { taskParam: "rootTaskId" },
|
|
277
|
+
summary: "Set who can view or edit a session",
|
|
278
|
+
description:
|
|
279
|
+
"Owner only. The body is the full desired state: users left out lose access. Changed targets have their active share revoked and a new one inserted in one transaction; shares are never deleted.",
|
|
280
|
+
tags: ["Sessions"],
|
|
281
|
+
params: z.object({ rootTaskId: z.string() }),
|
|
282
|
+
body: SessionSharesUpdateSchema,
|
|
283
|
+
responses: {
|
|
284
|
+
200: { description: "Active shares after the change", schema: SessionSharesSchema },
|
|
285
|
+
400: { description: "Validation error or unknown user" },
|
|
286
|
+
401: { description: "Unauthorized" },
|
|
287
|
+
403: { description: "Only the session owner can share it" },
|
|
210
288
|
404: { description: "Root task not found" },
|
|
289
|
+
409: { description: "The session has no owner, so it is already open to everyone" },
|
|
211
290
|
},
|
|
212
291
|
auth: { apiKey: true },
|
|
292
|
+
rbac: { permission: "session.share" },
|
|
213
293
|
});
|
|
214
294
|
|
|
295
|
+
/** The actor's access to the session rooted at `root`; `owner` when the actor bypasses checks. */
|
|
296
|
+
async function accessForRoot(
|
|
297
|
+
actor: SessionActor,
|
|
298
|
+
root: { id: string; requestedByUserId: string | null },
|
|
299
|
+
): Promise<SessionAccess> {
|
|
300
|
+
return actor.kind === "bypass" ? "owner" : sessionAccessForRoot(actor.userId, root);
|
|
301
|
+
}
|
|
302
|
+
|
|
215
303
|
// ─── Handler ─────────────────────────────────────────────────────────────────
|
|
216
304
|
|
|
217
305
|
export async function handleSessions(
|
|
@@ -219,6 +307,7 @@ export async function handleSessions(
|
|
|
219
307
|
res: ServerResponse,
|
|
220
308
|
pathSegments: string[],
|
|
221
309
|
queryParams: URLSearchParams,
|
|
310
|
+
myAgentId?: string,
|
|
222
311
|
): Promise<boolean> {
|
|
223
312
|
if (mintSessionTokenRoute.match(req.method, pathSegments)) {
|
|
224
313
|
const auth = getRequestAuth(req);
|
|
@@ -312,13 +401,30 @@ export async function handleSessions(
|
|
|
312
401
|
.map((s) => s.trim())
|
|
313
402
|
.filter(Boolean)
|
|
314
403
|
: undefined;
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
404
|
+
const actor = await resolveSessionActor(req, myAgentId);
|
|
405
|
+
if (actor.kind === "bypass" && parsed.query.scope) {
|
|
406
|
+
// No user to own or receive a share.
|
|
407
|
+
listSessions.respond(res, 200, {
|
|
408
|
+
sessions: [],
|
|
409
|
+
total: 0,
|
|
410
|
+
limit: parsed.query.limit ?? 25,
|
|
411
|
+
offset: parsed.query.offset ?? 0,
|
|
412
|
+
});
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
const filterOpts = {
|
|
318
416
|
source: sources,
|
|
319
417
|
q: parsed.query.q,
|
|
320
418
|
requestedByUserId: parsed.query.requestedByUserId,
|
|
321
419
|
contextKeyPrefix: parsed.query.contextKeyPrefix,
|
|
420
|
+
...(actor.kind === "user"
|
|
421
|
+
? { viewer: { userId: actor.userId, scope: parsed.query.scope ?? "all" } as const }
|
|
422
|
+
: {}),
|
|
423
|
+
};
|
|
424
|
+
const baseOpts = {
|
|
425
|
+
...filterOpts,
|
|
426
|
+
limit: parsed.query.limit,
|
|
427
|
+
offset: parsed.query.offset,
|
|
322
428
|
};
|
|
323
429
|
// List responses default to slim (root is a task summary); `?fields=full` restores it.
|
|
324
430
|
const sessions =
|
|
@@ -327,12 +433,7 @@ export async function handleSessions(
|
|
|
327
433
|
: await listRecentSessions({ ...baseOpts, slim: true });
|
|
328
434
|
// Filter-aware total: same WHERE as the list query, so the UI pager
|
|
329
435
|
// reflects the filtered result set.
|
|
330
|
-
const total = await countSessions(
|
|
331
|
-
source: sources,
|
|
332
|
-
q: parsed.query.q,
|
|
333
|
-
requestedByUserId: parsed.query.requestedByUserId,
|
|
334
|
-
contextKeyPrefix: parsed.query.contextKeyPrefix,
|
|
335
|
-
});
|
|
436
|
+
const total = await countSessions(filterOpts);
|
|
336
437
|
listSessions.respond(res, 200, {
|
|
337
438
|
sessions,
|
|
338
439
|
total,
|
|
@@ -350,6 +451,25 @@ export async function handleSessions(
|
|
|
350
451
|
jsonError(res, "Root task not found", 404);
|
|
351
452
|
return true;
|
|
352
453
|
}
|
|
454
|
+
// The route accepts any task id; access follows the topmost root.
|
|
455
|
+
const sessionRoot = (await getSessionRoot(root.id)) ?? {
|
|
456
|
+
id: root.id,
|
|
457
|
+
requestedByUserId: root.requestedByUserId ?? null,
|
|
458
|
+
};
|
|
459
|
+
const access = await accessForRoot(await resolveSessionActor(req, myAgentId), sessionRoot);
|
|
460
|
+
if (access === "none") {
|
|
461
|
+
jsonError(res, "You don't have access to this session. Ask the owner to share it.", 403);
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
const ownerUser = sessionRoot.requestedByUserId
|
|
465
|
+
? await findUserById(sessionRoot.requestedByUserId)
|
|
466
|
+
: null;
|
|
467
|
+
const owner = sessionRoot.requestedByUserId
|
|
468
|
+
? {
|
|
469
|
+
id: sessionRoot.requestedByUserId,
|
|
470
|
+
name: ownerUser?.name ?? sessionRoot.requestedByUserId,
|
|
471
|
+
}
|
|
472
|
+
: null;
|
|
353
473
|
const chain = await getRootTaskChain(parsed.params.rootTaskId);
|
|
354
474
|
// Fixed query count regardless of chain length: one agents lookup and one
|
|
355
475
|
// citations lookup for the whole chain (chunked past 500 ids).
|
|
@@ -363,7 +483,87 @@ export async function handleSessions(
|
|
|
363
483
|
...steering.get(task.id)!,
|
|
364
484
|
citations: citations.get(task.id)!,
|
|
365
485
|
});
|
|
366
|
-
getSession.respond(res, 200, {
|
|
486
|
+
getSession.respond(res, 200, {
|
|
487
|
+
root: decorate(root),
|
|
488
|
+
chain: chain.map(decorate),
|
|
489
|
+
access,
|
|
490
|
+
owner,
|
|
491
|
+
});
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (getSessionShares.match(req.method, pathSegments)) {
|
|
496
|
+
const parsed = await getSessionShares.parse(req, res, pathSegments, queryParams);
|
|
497
|
+
if (!parsed) return true;
|
|
498
|
+
const root = await getSessionRoot(parsed.params.rootTaskId);
|
|
499
|
+
if (!root) {
|
|
500
|
+
jsonError(res, "Root task not found", 404);
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
const access = await accessForRoot(await resolveSessionActor(req, myAgentId), root);
|
|
504
|
+
if (access === "none") {
|
|
505
|
+
jsonError(res, "You don't have access to this session. Ask the owner to share it.", 403);
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
getSessionShares.respond(
|
|
509
|
+
res,
|
|
510
|
+
200,
|
|
511
|
+
toSessionSharesState(await listActiveSessionShares(root.id)),
|
|
512
|
+
);
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (putSessionShares.match(req.method, pathSegments)) {
|
|
517
|
+
const parsed = await putSessionShares.parse(req, res, pathSegments, queryParams);
|
|
518
|
+
if (!parsed) return true;
|
|
519
|
+
const auth = getRequestAuth(req);
|
|
520
|
+
const actor = await resolveSessionActor(req, myAgentId);
|
|
521
|
+
// Sharing is a dashboard action: a person, or the operator key itself.
|
|
522
|
+
const principal =
|
|
523
|
+
auth?.kind === "user"
|
|
524
|
+
? ({ kind: "user", userId: auth.userId } as const)
|
|
525
|
+
: auth?.kind === "operator" && !myAgentId
|
|
526
|
+
? ({ kind: "operator" } as const)
|
|
527
|
+
: null;
|
|
528
|
+
if (
|
|
529
|
+
!principal ||
|
|
530
|
+
!can({ principal, verb: "session.share", resource: { kind: "none" }, source: "http" }).allow
|
|
531
|
+
) {
|
|
532
|
+
jsonError(res, "Only the session owner can share it", 403);
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
const root = await getSessionRoot(parsed.params.rootTaskId);
|
|
536
|
+
if (!root) {
|
|
537
|
+
jsonError(res, "Root task not found", 404);
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
if (!root.requestedByUserId) {
|
|
541
|
+
jsonError(res, "This session has no owner, so it is already open to everyone", 409);
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
if (actor.kind === "user" && actor.userId !== root.requestedByUserId) {
|
|
545
|
+
jsonError(res, "Only the session owner can share it", 403);
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
const userIds = [...new Set(parsed.body.users.map((user) => user.userId))];
|
|
549
|
+
if (userIds.length !== parsed.body.users.length) {
|
|
550
|
+
jsonError(res, "Each user may appear only once", 400);
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
for (const userId of userIds) {
|
|
554
|
+
if (!(await findUserById(userId))) {
|
|
555
|
+
jsonError(res, `Unknown user ${userId}`, 400);
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const state = await setSessionShares(
|
|
560
|
+
root,
|
|
561
|
+
parsed.body,
|
|
562
|
+
actor.kind === "user" ? actor.userId : null,
|
|
563
|
+
);
|
|
564
|
+
// Committed: presence sockets re-check access, so a revoked viewer leaves.
|
|
565
|
+
realtimeBus.publish(sessionAccessTopic(root.id), null);
|
|
566
|
+
putSessionShares.respond(res, 200, state);
|
|
367
567
|
return true;
|
|
368
568
|
}
|
|
369
569
|
|
package/src/http/stats.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
|
|
|
19
19
|
import { resolveHttpFavoriteOwner } from "./favorite-owner";
|
|
20
20
|
import { route } from "./route-def";
|
|
21
21
|
import { scheduleSummaryWithFavoriteSchema } from "./schedules";
|
|
22
|
+
import { readableByFor, resolveSessionActor } from "./session-access";
|
|
22
23
|
|
|
23
24
|
// ─── Response schemas ────────────────────────────────────────────────────────
|
|
24
25
|
|
|
@@ -222,11 +223,12 @@ export async function handleStats(
|
|
|
222
223
|
if (!parsed) return true;
|
|
223
224
|
const limit = parsed.query.limit ?? 100;
|
|
224
225
|
const agentId = parsed.query.agentId;
|
|
226
|
+
const readableBy = readableByFor(await resolveSessionActor(req, myAgentId));
|
|
225
227
|
let logs: AgentLog[] = [];
|
|
226
228
|
if (agentId) {
|
|
227
|
-
logs = (await getLogsByAgentId(agentId)).slice(0, limit);
|
|
229
|
+
logs = (await getLogsByAgentId(agentId, readableBy)).slice(0, limit);
|
|
228
230
|
} else {
|
|
229
|
-
logs = await getAllLogs(limit);
|
|
231
|
+
logs = await getAllLogs(limit, readableBy);
|
|
230
232
|
}
|
|
231
233
|
listLogs.respond(res, 200, { logs });
|
|
232
234
|
return true;
|
|
@@ -306,7 +308,9 @@ export async function handleStats(
|
|
|
306
308
|
}
|
|
307
309
|
|
|
308
310
|
if (getConcurrentContextRoute.match(req.method, pathSegments)) {
|
|
309
|
-
const context = await getConcurrentContext(
|
|
311
|
+
const context = await getConcurrentContext(
|
|
312
|
+
readableByFor(await resolveSessionActor(req, myAgentId)),
|
|
313
|
+
);
|
|
310
314
|
getConcurrentContextRoute.respond(res, 200, context);
|
|
311
315
|
return true;
|
|
312
316
|
}
|