@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,244 @@
|
|
|
1
|
+
import { getDbClient } from "./db";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Session sharing store. A session is a root task (`parentTaskId IS NULL`) and
|
|
5
|
+
* its descendants; its owner is the root's `requestedByUserId`.
|
|
6
|
+
*
|
|
7
|
+
* Rows in `session_shares` are never deleted. Revoking sets `revokedAt`, and an
|
|
8
|
+
* access change revokes the active row and inserts a new one in the same
|
|
9
|
+
* transaction, so the rows for a root are its full share history.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type SessionShareAccess = "read" | "write";
|
|
13
|
+
export type SessionAccess = "owner" | SessionShareAccess | "none";
|
|
14
|
+
export type SessionEveryoneAccess = SessionShareAccess | "none";
|
|
15
|
+
|
|
16
|
+
export type SessionShareRow = {
|
|
17
|
+
id: string;
|
|
18
|
+
rootTaskId: string;
|
|
19
|
+
/** NULL = everyone. */
|
|
20
|
+
userId: string | null;
|
|
21
|
+
access: SessionShareAccess;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
revokedAt: string | null;
|
|
24
|
+
revokedBy: string | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type SessionRoot = { id: string; requestedByUserId: string | null };
|
|
28
|
+
|
|
29
|
+
export type SessionSharesState = {
|
|
30
|
+
everyone: SessionEveryoneAccess;
|
|
31
|
+
users: { userId: string; access: SessionShareAccess; createdAt: string }[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type DesiredSessionShares = {
|
|
35
|
+
everyone: SessionEveryoneAccess;
|
|
36
|
+
users: { userId: string; access: SessionShareAccess }[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const RANK: Record<SessionAccess, number> = { none: 0, read: 1, write: 2, owner: 3 };
|
|
40
|
+
|
|
41
|
+
/** True when `access` grants at least `needed`. */
|
|
42
|
+
export function hasSessionAccess(access: SessionAccess, needed: SessionAccess): boolean {
|
|
43
|
+
return RANK[access] >= RANK[needed];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The session root of any task: walk `parentTaskId` up to the topmost task
|
|
48
|
+
* that still exists. No depth cap, so a chain of any length resolves to its
|
|
49
|
+
* actual root; a corrupt parent cycle has no top and resolves to null.
|
|
50
|
+
*/
|
|
51
|
+
export async function getSessionRoot(taskId: string): Promise<SessionRoot | null> {
|
|
52
|
+
return (await getSessionRoots([taskId])).get(taskId) ?? null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const ROOT_LOOKUP_CHUNK = 500;
|
|
56
|
+
|
|
57
|
+
type AncestorRow = { id: string; parentTaskId: string | null; requestedByUserId: string | null };
|
|
58
|
+
|
|
59
|
+
/** `getSessionRoot` for many tasks; unknown tasks and tasks in a cycle are absent. */
|
|
60
|
+
export async function getSessionRoots(taskIds: string[]): Promise<Map<string, SessionRoot>> {
|
|
61
|
+
const ids = [...new Set(taskIds)];
|
|
62
|
+
// Every ancestor once (`UNION` drops revisits, which also ends a cycle),
|
|
63
|
+
// then resolve each chain in memory: linear in the tasks involved.
|
|
64
|
+
const tasks = new Map<string, AncestorRow>();
|
|
65
|
+
for (let start = 0; start < ids.length; start += ROOT_LOOKUP_CHUNK) {
|
|
66
|
+
const chunk = ids.slice(start, start + ROOT_LOOKUP_CHUNK).filter((id) => !tasks.has(id));
|
|
67
|
+
if (chunk.length === 0) continue;
|
|
68
|
+
const rows = await getDbClient().query<AncestorRow>(
|
|
69
|
+
`WITH RECURSIVE up(id) AS (
|
|
70
|
+
SELECT id FROM agent_tasks WHERE id IN (${chunk.map(() => "?").join(", ")})
|
|
71
|
+
UNION
|
|
72
|
+
SELECT t.parentTaskId FROM agent_tasks t JOIN up ON t.id = up.id
|
|
73
|
+
WHERE t.parentTaskId IS NOT NULL
|
|
74
|
+
)
|
|
75
|
+
SELECT t.id, t.parentTaskId, t.requestedByUserId
|
|
76
|
+
FROM up CROSS JOIN agent_tasks t ON t.id = up.id`,
|
|
77
|
+
chunk,
|
|
78
|
+
);
|
|
79
|
+
for (const row of rows) tasks.set(row.id, row);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const resolved = new Map<string, SessionRoot | null>();
|
|
83
|
+
const rootOf = (taskId: string): SessionRoot | null => {
|
|
84
|
+
const path: string[] = [];
|
|
85
|
+
const onPath = new Set<string>();
|
|
86
|
+
let current = tasks.get(taskId);
|
|
87
|
+
let root: SessionRoot | null = null;
|
|
88
|
+
while (current) {
|
|
89
|
+
const known = resolved.get(current.id);
|
|
90
|
+
if (known !== undefined) {
|
|
91
|
+
root = known;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
if (onPath.has(current.id)) break;
|
|
95
|
+
path.push(current.id);
|
|
96
|
+
onPath.add(current.id);
|
|
97
|
+
const parent = current.parentTaskId ? tasks.get(current.parentTaskId) : undefined;
|
|
98
|
+
if (!parent) {
|
|
99
|
+
root = { id: current.id, requestedByUserId: current.requestedByUserId };
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
current = parent;
|
|
103
|
+
}
|
|
104
|
+
for (const id of path) resolved.set(id, root);
|
|
105
|
+
return root;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const roots = new Map<string, SessionRoot>();
|
|
109
|
+
for (const id of ids) {
|
|
110
|
+
const root = rootOf(id);
|
|
111
|
+
if (root) roots.set(id, root);
|
|
112
|
+
}
|
|
113
|
+
return roots;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* What `actingUserId` may do in the session rooted at `root`.
|
|
118
|
+
*
|
|
119
|
+
* - `owner` for the root's requester, and for every user when the root has no
|
|
120
|
+
* owner (Slack and schedule sessions stay open, as before sharing existed).
|
|
121
|
+
* - Otherwise the best active share naming the user or everyone; `none` when
|
|
122
|
+
* there is none. A root with no rows is private to its owner.
|
|
123
|
+
*
|
|
124
|
+
* A null `actingUserId` is a viewer with no user (a guest page session): it
|
|
125
|
+
* gets ownerless roots and everyone shares only. Callers that act for no user
|
|
126
|
+
* (agents, operator-key calls with no picked dashboard user) skip this check
|
|
127
|
+
* entirely; see `resolveSessionActor`.
|
|
128
|
+
*/
|
|
129
|
+
export async function sessionAccessForRoot(
|
|
130
|
+
actingUserId: string | null,
|
|
131
|
+
root: SessionRoot,
|
|
132
|
+
): Promise<SessionAccess> {
|
|
133
|
+
if (!root.requestedByUserId) return "owner";
|
|
134
|
+
if (actingUserId && root.requestedByUserId === actingUserId) return "owner";
|
|
135
|
+
const rows = await getDbClient().query<{ access: SessionShareAccess }>(
|
|
136
|
+
`SELECT access FROM session_shares
|
|
137
|
+
WHERE rootTaskId = ? AND revokedAt IS NULL AND (userId IS NULL OR userId = ?)`,
|
|
138
|
+
[root.id, actingUserId],
|
|
139
|
+
);
|
|
140
|
+
if (rows.some((row) => row.access === "write")) return "write";
|
|
141
|
+
return rows.length > 0 ? "read" : "none";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** `sessionAccessForRoot` for any task in the session; `none` for an unknown task. */
|
|
145
|
+
export async function sessionAccess(
|
|
146
|
+
actingUserId: string | null,
|
|
147
|
+
taskId: string,
|
|
148
|
+
): Promise<SessionAccess> {
|
|
149
|
+
const root = await getSessionRoot(taskId);
|
|
150
|
+
return root ? sessionAccessForRoot(actingUserId, root) : "none";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function listActiveSessionShares(rootTaskId: string): Promise<SessionShareRow[]> {
|
|
154
|
+
return getDbClient().query<SessionShareRow>(
|
|
155
|
+
`SELECT id, rootTaskId, userId, access, createdAt, revokedAt, revokedBy
|
|
156
|
+
FROM session_shares
|
|
157
|
+
WHERE rootTaskId = ? AND revokedAt IS NULL
|
|
158
|
+
ORDER BY createdAt, rowid`,
|
|
159
|
+
[rootTaskId],
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Every row for a root, revoked ones included, oldest first. */
|
|
164
|
+
export async function listSessionShareHistory(rootTaskId: string): Promise<SessionShareRow[]> {
|
|
165
|
+
return getDbClient().query<SessionShareRow>(
|
|
166
|
+
`SELECT id, rootTaskId, userId, access, createdAt, revokedAt, revokedBy
|
|
167
|
+
FROM session_shares
|
|
168
|
+
WHERE rootTaskId = ?
|
|
169
|
+
ORDER BY createdAt, rowid`,
|
|
170
|
+
[rootTaskId],
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function toSessionSharesState(rows: SessionShareRow[]): SessionSharesState {
|
|
175
|
+
const everyone = rows.find((row) => row.userId === null);
|
|
176
|
+
return {
|
|
177
|
+
everyone: everyone?.access ?? "none",
|
|
178
|
+
users: rows
|
|
179
|
+
.filter((row) => row.userId !== null)
|
|
180
|
+
.map((row) => ({ userId: row.userId!, access: row.access, createdAt: row.createdAt })),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Make the active shares of `root` equal `desired`, in one write transaction.
|
|
186
|
+
* A target whose access changes has its active row revoked and a new row
|
|
187
|
+
* inserted; a target no longer listed is revoked; nothing is deleted or
|
|
188
|
+
* updated in place apart from revocation. The owner is never stored.
|
|
189
|
+
*/
|
|
190
|
+
export async function setSessionShares(
|
|
191
|
+
root: SessionRoot,
|
|
192
|
+
desired: DesiredSessionShares,
|
|
193
|
+
actorUserId: string | null,
|
|
194
|
+
): Promise<SessionSharesState> {
|
|
195
|
+
const want = new Map<string, SessionShareAccess>();
|
|
196
|
+
if (desired.everyone !== "none") want.set("*", desired.everyone);
|
|
197
|
+
for (const user of desired.users) {
|
|
198
|
+
if (user.userId !== root.requestedByUserId) want.set(user.userId, user.access);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return getDbClient().transaction(async (tx) => {
|
|
202
|
+
const now = new Date().toISOString();
|
|
203
|
+
const active = await tx.query<SessionShareRow>(
|
|
204
|
+
`SELECT id, rootTaskId, userId, access, createdAt, revokedAt, revokedBy
|
|
205
|
+
FROM session_shares WHERE rootTaskId = ? AND revokedAt IS NULL`,
|
|
206
|
+
[root.id],
|
|
207
|
+
);
|
|
208
|
+
const kept = new Set<string>();
|
|
209
|
+
for (const row of active) {
|
|
210
|
+
const target = row.userId ?? "*";
|
|
211
|
+
if (want.get(target) === row.access) {
|
|
212
|
+
kept.add(target);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
await tx.run(
|
|
216
|
+
"UPDATE session_shares SET revokedAt = ?, revokedBy = ?, updated_by = ? WHERE id = ?",
|
|
217
|
+
[now, actorUserId, actorUserId, row.id],
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
for (const [target, access] of want) {
|
|
221
|
+
if (kept.has(target)) continue;
|
|
222
|
+
await tx.run(
|
|
223
|
+
`INSERT INTO session_shares (id, rootTaskId, userId, access, createdAt, created_by, updated_by)
|
|
224
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
225
|
+
[
|
|
226
|
+
crypto.randomUUID(),
|
|
227
|
+
root.id,
|
|
228
|
+
target === "*" ? null : target,
|
|
229
|
+
access,
|
|
230
|
+
now,
|
|
231
|
+
actorUserId,
|
|
232
|
+
actorUserId,
|
|
233
|
+
],
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const rows = await tx.query<SessionShareRow>(
|
|
237
|
+
`SELECT id, rootTaskId, userId, access, createdAt, revokedAt, revokedBy
|
|
238
|
+
FROM session_shares WHERE rootTaskId = ? AND revokedAt IS NULL
|
|
239
|
+
ORDER BY createdAt, rowid`,
|
|
240
|
+
[root.id],
|
|
241
|
+
);
|
|
242
|
+
return toSessionSharesState(rows);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { validateChatSdkAdaptersValue } from "../integrations/chat-sdk/schema";
|
|
2
|
+
import { parseTelemetryRanking, TELEMETRY_RANKING_KEY } from "../ranking/telemetry-ranking";
|
|
2
3
|
import { validateQuickJSPoolSize } from "../scripts-runtime/executors/quickjs-pool-config";
|
|
3
4
|
import { normalizeSlackReactionShortcode } from "../slack/reaction-shortcode";
|
|
4
5
|
import { ProviderNameSchema } from "../types";
|
|
5
6
|
import { API_DRAIN_MAX_MS_LIMIT } from "../utils/api-drain";
|
|
7
|
+
import {
|
|
8
|
+
COMPANY_ACQUISITION_SOURCES,
|
|
9
|
+
COMPANY_TEAM_SIZES,
|
|
10
|
+
COMPANY_USE_CASES,
|
|
11
|
+
} from "../utils/company-profile";
|
|
6
12
|
import { envExportRestrictedError, isEnvExportRestrictedKey } from "../utils/config-env-export";
|
|
7
13
|
import { validateEgressAllowlist } from "../utils/egress-guard";
|
|
8
14
|
import { parseEnabledTools } from "../utils/enabled-tools";
|
|
@@ -69,15 +75,24 @@ export function isApiOnlyConfigKey(key: string): boolean {
|
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
/** Config rows owned by dedicated API surfaces, not the generic config API. */
|
|
72
|
-
export const INTERNAL_CONFIG_KEYS = new Set([
|
|
78
|
+
export const INTERNAL_CONFIG_KEYS = new Set([
|
|
79
|
+
"onboarding_state",
|
|
80
|
+
"runtime_owner_id",
|
|
81
|
+
TELEMETRY_RANKING_KEY,
|
|
82
|
+
]);
|
|
73
83
|
|
|
74
84
|
export function isInternalConfigKey(key: string): boolean {
|
|
75
85
|
return INTERNAL_CONFIG_KEYS.has(key.toLowerCase());
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
export function internalConfigKeyError(key: string): Error {
|
|
89
|
+
const lower = key.toLowerCase();
|
|
79
90
|
const owner =
|
|
80
|
-
|
|
91
|
+
lower === "runtime_owner_id"
|
|
92
|
+
? "the runtime supervisor"
|
|
93
|
+
: lower === TELEMETRY_RANKING_KEY
|
|
94
|
+
? "/api/ranking"
|
|
95
|
+
: "/api/onboarding";
|
|
81
96
|
return new Error(`Key '${key}' is managed by ${owner}`);
|
|
82
97
|
}
|
|
83
98
|
|
|
@@ -141,7 +156,8 @@ function booleanValidators(keys: string[]): Record<string, ConfigValidator> {
|
|
|
141
156
|
|
|
142
157
|
/** Build a single `{ KEY: validator }` entry restricted to `options`. */
|
|
143
158
|
function enumValidator(key: string, options: string[]): Record<string, ConfigValidator> {
|
|
144
|
-
const
|
|
159
|
+
const listed = options.filter(Boolean).join(", ");
|
|
160
|
+
const message = `Invalid ${key} value (must be one of: ${listed}${options.includes("") ? ", or empty" : ""})`;
|
|
145
161
|
return {
|
|
146
162
|
[key]: (value: unknown) => {
|
|
147
163
|
if (typeof value !== "string") return message;
|
|
@@ -301,6 +317,16 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
301
317
|
return "Invalid TASK_TOOL_MANIFESTS (expected JSON with taskTypes and/or schedules maps, each selecting at most 16 known swarm tools)";
|
|
302
318
|
}
|
|
303
319
|
},
|
|
320
|
+
// Owned by /api/ranking (an internal key); parsed here as defense in depth.
|
|
321
|
+
[TELEMETRY_RANKING_KEY.toUpperCase()]: (value) => {
|
|
322
|
+
try {
|
|
323
|
+
if (typeof value !== "string") throw new Error("Expected JSON string");
|
|
324
|
+
parseTelemetryRanking(value);
|
|
325
|
+
return null;
|
|
326
|
+
} catch {
|
|
327
|
+
return "Invalid telemetry_ranking (expected the v1 ranking listing JSON)";
|
|
328
|
+
}
|
|
329
|
+
},
|
|
304
330
|
// MCP tool allowlist: comma-separated names or a JSON array of strings.
|
|
305
331
|
// Unknown names are accepted here and warned about at session init.
|
|
306
332
|
SWARM_ENABLED_TOOLS: (value) => {
|
|
@@ -403,8 +429,12 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
403
429
|
? null
|
|
404
430
|
: "Invalid SWARM_ORG_WEBSITE (must be a public hostname such as example.com, without scheme, www, path or port)";
|
|
405
431
|
},
|
|
432
|
+
...enumValidator("SWARM_ORG_TEAM_SIZE", ["", ...COMPANY_TEAM_SIZES]),
|
|
433
|
+
...enumValidator("SWARM_ORG_USE_CASE", ["", ...COMPANY_USE_CASES]),
|
|
434
|
+
...enumValidator("SWARM_ORG_ACQUISITION_SOURCE", ["", ...COMPANY_ACQUISITION_SOURCES]),
|
|
406
435
|
...booleanValidators([
|
|
407
436
|
"SWARM_USED_BY_CONSENT",
|
|
437
|
+
"PUBLIC_RANKING_ENABLED",
|
|
408
438
|
"SWARM_DEV_MODE",
|
|
409
439
|
"MULTI_RUNTIME_ENABLED",
|
|
410
440
|
"STEERING_ENABLED",
|
|
@@ -13,7 +13,7 @@ import { telemetryIngestRecorder } from "./telemetry-ingest-anchor";
|
|
|
13
13
|
|
|
14
14
|
type GetConfig = (key: string) => Promise<string | undefined>;
|
|
15
15
|
type SetConfig = (key: string, value: string) => Promise<void>;
|
|
16
|
-
export type TelemetryEnabledVia = "banner" | "settings" | "env";
|
|
16
|
+
export type TelemetryEnabledVia = "banner" | "settings" | "benchmark" | "env";
|
|
17
17
|
|
|
18
18
|
let accessors: { getConfig: GetConfig; setConfig: SetConfig } | null = null;
|
|
19
19
|
let lastEnabled = false;
|
|
@@ -155,8 +155,13 @@ export const CREDENTIAL_PROVIDER_CHECKERS: Record<SupportedProvider, CredentialC
|
|
|
155
155
|
devin: (env) => checkDevinCredentials(env),
|
|
156
156
|
opencode: (env, opts) => checkOpencodeCredentials(env, opts),
|
|
157
157
|
pi: async (env, opts) => {
|
|
158
|
-
const { checkPiMonoCredentials } = await import(
|
|
159
|
-
|
|
158
|
+
const { checkPiMonoCredentials, listPiOpencodeGoModelIds } = await import(
|
|
159
|
+
"../providers/pi-mono-adapter"
|
|
160
|
+
);
|
|
161
|
+
return {
|
|
162
|
+
...(await checkPiMonoCredentials(env, opts)),
|
|
163
|
+
opencodeGoModels: listPiOpencodeGoModelIds(),
|
|
164
|
+
};
|
|
160
165
|
},
|
|
161
166
|
acp: (env) => {
|
|
162
167
|
const target = env.ACP_TARGET ?? "custom";
|
|
@@ -662,6 +667,10 @@ export async function buildCredStatusReport(
|
|
|
662
667
|
error: presence.ready ? undefined : (presence.hint ?? undefined),
|
|
663
668
|
}
|
|
664
669
|
: null;
|
|
670
|
+
const opencodeGo: AgentCredStatus["opencodeGo"] =
|
|
671
|
+
presence.opencodeGoModels !== undefined
|
|
672
|
+
? { probedAt: Date.now(), models: presence.opencodeGoModels }
|
|
673
|
+
: null;
|
|
665
674
|
return {
|
|
666
675
|
ready: presence.ready,
|
|
667
676
|
missing: presence.missing ?? [],
|
|
@@ -672,6 +681,7 @@ export async function buildCredStatusReport(
|
|
|
672
681
|
reportedAt: Date.now(),
|
|
673
682
|
reportKind: kind,
|
|
674
683
|
bedrock,
|
|
684
|
+
opencodeGo,
|
|
675
685
|
acp: null,
|
|
676
686
|
};
|
|
677
687
|
}
|
package/src/commands/runner.ts
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
skillFromToolArgs,
|
|
56
56
|
} from "../providers/skill-invoke.ts";
|
|
57
57
|
import type { SteerDeliveryResult } from "../providers/types.ts";
|
|
58
|
+
import { rankingUsageProps } from "../ranking/usage.ts";
|
|
58
59
|
import { WorkerRealtimeClient } from "../realtime/worker-client";
|
|
59
60
|
import { resumedTaskModelSelection, type TaskModelPinFields } from "../tasks/model-pin.ts";
|
|
60
61
|
import { initTelemetry, telemetry } from "../telemetry.ts";
|
|
@@ -1228,6 +1229,13 @@ export const RELOADABLE_ENV_KEYS: ReadonlySet<string> = new Set([
|
|
|
1228
1229
|
"TEMPLATE_REGISTRY_URL",
|
|
1229
1230
|
"SLACK_DISABLE",
|
|
1230
1231
|
"SWARM_ORG_NAME",
|
|
1232
|
+
// Gate the public-ranking `ranking.usage` emit, so clearing the website or
|
|
1233
|
+
// turning the setting off stops it on running workers.
|
|
1234
|
+
"SWARM_ORG_WEBSITE",
|
|
1235
|
+
"PUBLIC_RANKING_ENABLED",
|
|
1236
|
+
"SWARM_ORG_TEAM_SIZE",
|
|
1237
|
+
"SWARM_ORG_USE_CASE",
|
|
1238
|
+
"SWARM_ORG_ACQUISITION_SOURCE",
|
|
1231
1239
|
// pi reads these from process.env for its traits and its session.
|
|
1232
1240
|
"PI_TOOL_DEFERRAL",
|
|
1233
1241
|
"PI_CODEMODE",
|
|
@@ -4923,6 +4931,12 @@ async function spawnProviderProcess(
|
|
|
4923
4931
|
},
|
|
4924
4932
|
{ userId: opts.requestedByUserId ?? null },
|
|
4925
4933
|
);
|
|
4934
|
+
// Public ranking: token counts only, while the install is opted in.
|
|
4935
|
+
const rankingUsage = rankingUsageProps(result.cost, {
|
|
4936
|
+
model: result.cost.model,
|
|
4937
|
+
provider: result.cost.provider ?? opts.harnessProvider,
|
|
4938
|
+
});
|
|
4939
|
+
if (rankingUsage) telemetry.ranking("usage", rankingUsage);
|
|
4926
4940
|
}
|
|
4927
4941
|
|
|
4928
4942
|
// Post completion context usage snapshot
|
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
REBOOT_RETRY_PIN_TAG,
|
|
77
77
|
resolveLeadOnlyRecoveryAssignment,
|
|
78
78
|
} from "../tasks/worker-follow-up";
|
|
79
|
-
import { type AgentTask, TERMINAL_TASK_STATUSES } from "../types";
|
|
79
|
+
import { type AgentTask, SUPERSEDED_WORKFLOW_TASK_REASON, TERMINAL_TASK_STATUSES } from "../types";
|
|
80
80
|
import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
81
81
|
import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
|
|
82
82
|
import { DEFAULT_OPENROUTER_BASE_URL, getOpenRouterBaseUrl } from "../utils/openrouter-base-url";
|
|
@@ -679,7 +679,7 @@ function isRemediationAction(value: unknown): value is RemediationAction {
|
|
|
679
679
|
|
|
680
680
|
function decideRemediation(task: AgentTask, facts: RemediationFacts): RemediationDecision {
|
|
681
681
|
if (task.workflowRunStepId != null) {
|
|
682
|
-
return { action: "fail", reason:
|
|
682
|
+
return { action: "fail", reason: SUPERSEDED_WORKFLOW_TASK_REASON };
|
|
683
683
|
}
|
|
684
684
|
if (facts.skipAutoResume || facts.alreadyResumed) {
|
|
685
685
|
return { action: "fail", reason: facts.legacyFailReason };
|
package/src/hooks/codex-hook.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { getApiKey } from "../utils/api-key";
|
|
|
35
35
|
import { getMcpBaseUrl } from "../utils/constants";
|
|
36
36
|
import { isSteeringEnabled } from "../utils/steering-enabled";
|
|
37
37
|
import { guardGhPrBody, type PrBodyGuardDeps } from "./pr-body-guard";
|
|
38
|
+
import { guardGhPrRoom, type PrRoomGuardDeps } from "./pr-room-guard";
|
|
38
39
|
|
|
39
40
|
const FETCH_TIMEOUT_MS = 5_000;
|
|
40
41
|
|
|
@@ -169,13 +170,16 @@ export async function handleCodexHookEvent(
|
|
|
169
170
|
};
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
/** PreToolUse: the PR
|
|
173
|
+
/** PreToolUse: return the first PR guard block reason, or null to allow the call. */
|
|
173
174
|
export async function codexPreToolUseBlock(
|
|
174
175
|
msg: CodexHookMessage,
|
|
175
176
|
deps?: PrBodyGuardDeps,
|
|
177
|
+
roomDeps?: PrRoomGuardDeps,
|
|
176
178
|
): Promise<string | null> {
|
|
177
179
|
if (msg.hook_event_name !== "PreToolUse") return null;
|
|
178
|
-
|
|
180
|
+
const cwd = msg.cwd ?? process.cwd();
|
|
181
|
+
const block = await guardGhPrBody(msg.tool_input, cwd, deps);
|
|
182
|
+
return block ?? (await guardGhPrRoom(msg.tool_input, cwd, roomDeps));
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
/** stdin/stdout entry point used by the `codex-hook` CLI command. */
|
package/src/hooks/hook.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { getMcpBaseUrl, MAX_PROFILE_FILE_LENGTH } from "../utils/constants";
|
|
|
24
24
|
import { summarizeSession as runSummarize } from "../utils/internal-ai";
|
|
25
25
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
26
26
|
import { guardGhPrBody } from "./pr-body-guard";
|
|
27
|
+
import { guardGhPrRoom } from "./pr-room-guard";
|
|
27
28
|
import { checkToolLoop, clearToolHistory } from "./tool-loop-detection";
|
|
28
29
|
|
|
29
30
|
const SERVER_NAME = pkg.config?.name ?? "agent-swarm";
|
|
@@ -1189,6 +1190,12 @@ export async function handleHook(): Promise<void> {
|
|
|
1189
1190
|
process.exitCode = 2;
|
|
1190
1191
|
return;
|
|
1191
1192
|
}
|
|
1193
|
+
const prRoomBlock = await guardGhPrRoom(msg.tool_input, msg.cwd ?? process.cwd());
|
|
1194
|
+
if (prRoomBlock) {
|
|
1195
|
+
console.error(prRoomBlock);
|
|
1196
|
+
process.exitCode = 2;
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1192
1199
|
}
|
|
1193
1200
|
|
|
1194
1201
|
// Emitted last, so a later block stays the only stdout of the event.
|
|
@@ -40,22 +40,29 @@ import { isAbsolute, resolve } from "node:path";
|
|
|
40
40
|
import { findPrBodyLeaks, type LeakCategory } from "../utils/pr-body-leaks";
|
|
41
41
|
|
|
42
42
|
export type GhPrInvocation = {
|
|
43
|
+
action: "create" | "edit";
|
|
43
44
|
/** Working directory of the `gh` call after any `cd`; null when a `cd` target could not be resolved. */
|
|
44
45
|
cwd: string | null;
|
|
45
46
|
/** Explicit target repos: `--repo` / `-R`, a PR URL, or `GH_REPO`. Empty means the checkout at `cwd`. */
|
|
46
47
|
repos: string[];
|
|
48
|
+
/** Expanded targets for workflow guards. Null means an unknown shell value. */
|
|
49
|
+
resolvedRepos: Array<string | null>;
|
|
47
50
|
/** Also check the checkout: `GH_REPO` was assigned without `export`, so gh may not see it. */
|
|
48
51
|
alsoCheckout: boolean;
|
|
49
52
|
/** The `gh pr edit` target could not be classified (unset variable, `$(...)`, non-PR URL), so the PR's repo is unknown. */
|
|
50
53
|
unknownTarget: boolean;
|
|
51
54
|
/** Literal public text: `--title` and `--body` values, heredoc or here-string stdin. */
|
|
52
55
|
texts: string[];
|
|
56
|
+
/** Expanded body values and stdin, excluding the title. Null means an unknown shell value. */
|
|
57
|
+
bodyTexts: Array<string | null>;
|
|
53
58
|
/** Files that become the body, expanded: `--body-file PATH`, or `--body-file -` with `< PATH`. */
|
|
54
59
|
bodyFiles: string[];
|
|
55
60
|
/** Body file paths whose variables could not be resolved, as written. They count as unreadable. */
|
|
56
61
|
unresolvedBodyFiles: string[];
|
|
57
62
|
/** The body text could not be isolated (piped stdin, unquoted `$(...)`); scan the whole command. */
|
|
58
63
|
opaqueBody: boolean;
|
|
64
|
+
/** gh may build or change the body from a source the parser cannot read: `--fill*`, `--template`, `--editor`, `--web`, `--recover`. */
|
|
65
|
+
externalBody: boolean;
|
|
59
66
|
};
|
|
60
67
|
|
|
61
68
|
export type PrBodyGuardDeps = {
|
|
@@ -282,6 +289,9 @@ const SHORT_FLAGS: Record<string, string> = {
|
|
|
282
289
|
p: "project",
|
|
283
290
|
r: "reviewer",
|
|
284
291
|
T: "template",
|
|
292
|
+
e: "editor",
|
|
293
|
+
f: "fill",
|
|
294
|
+
w: "web",
|
|
285
295
|
};
|
|
286
296
|
|
|
287
297
|
type ParsedArgs = {
|
|
@@ -826,11 +836,14 @@ function parseWithState(
|
|
|
826
836
|
else bodyFiles.push(expanded);
|
|
827
837
|
}
|
|
828
838
|
found.push({
|
|
839
|
+
action,
|
|
829
840
|
cwd: wrapperFlags || dirUnknown ? null : state.dir,
|
|
830
841
|
repos: [...new Set(repos)],
|
|
842
|
+
resolvedRepos: repos.map(expand),
|
|
831
843
|
alsoCheckout,
|
|
832
844
|
unknownTarget,
|
|
833
845
|
texts: [...inline, ...(fromStdin ? stdin : [])],
|
|
846
|
+
bodyTexts: [...values("body"), ...(fromStdin ? stdin : [])].map(expand),
|
|
834
847
|
bodyFiles,
|
|
835
848
|
unresolvedBodyFiles,
|
|
836
849
|
// An unquoted `$(` splits at the paren, leaving a value that ends in `$`.
|
|
@@ -839,6 +852,9 @@ function parseWithState(
|
|
|
839
852
|
inline.some((value) => value.endsWith("$")) ||
|
|
840
853
|
(action === "edit" && target?.endsWith("$") === true) ||
|
|
841
854
|
(fromStdin && stdin.length === 0 && args.stdinFile === undefined),
|
|
855
|
+
externalBody:
|
|
856
|
+
values("fill", "fill-first", "fill-verbose", "template", "editor", "web", "recover")
|
|
857
|
+
.length > 0,
|
|
842
858
|
});
|
|
843
859
|
};
|
|
844
860
|
|
|
@@ -861,6 +877,25 @@ function parseWithState(
|
|
|
861
877
|
const BLOCK_ADVICE =
|
|
862
878
|
"Paraphrase the motivation ('a maintainer asked for X') and link only public sources (Fixes #N, a public PR or issue). Task, session, agent-fs and Slack permalinks go only under '## Swarm provenance'. Bare Slack ids, ts values and private-chat quotes are blocked everywhere.";
|
|
863
879
|
|
|
880
|
+
/** Read body files relative to the parsed checkout and retain the first unreadable path. */
|
|
881
|
+
export async function readGhPrBodyFiles(
|
|
882
|
+
call: GhPrInvocation,
|
|
883
|
+
readFile: PrBodyGuardDeps["readFile"],
|
|
884
|
+
): Promise<{ texts: string[]; unreadable: string | undefined }> {
|
|
885
|
+
const texts: string[] = [];
|
|
886
|
+
let unreadable = call.unresolvedBodyFiles[0];
|
|
887
|
+
for (const bodyFile of call.bodyFiles) {
|
|
888
|
+
try {
|
|
889
|
+
const base = isAbsolute(bodyFile) ? "/" : call.cwd;
|
|
890
|
+
if (base === null) throw new Error("unknown working directory");
|
|
891
|
+
texts.push(await readFile(resolve(base, bodyFile)));
|
|
892
|
+
} catch {
|
|
893
|
+
unreadable ??= bodyFile;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return { texts, unreadable };
|
|
897
|
+
}
|
|
898
|
+
|
|
864
899
|
/** Check a shell command. Returns a block reason, or null to allow. */
|
|
865
900
|
export async function checkGhPrCommand(
|
|
866
901
|
command: string,
|
|
@@ -873,17 +908,9 @@ export async function checkGhPrCommand(
|
|
|
873
908
|
for (const text of call.opaqueBody ? [...call.texts, command] : call.texts) {
|
|
874
909
|
for (const leak of findPrBodyLeaks(text)) leaks.add(leak);
|
|
875
910
|
}
|
|
876
|
-
|
|
877
|
-
for (const
|
|
878
|
-
|
|
879
|
-
const base = isAbsolute(bodyFile) ? "/" : call.cwd;
|
|
880
|
-
if (base === null) throw new Error("unknown working directory");
|
|
881
|
-
for (const leak of findPrBodyLeaks(await deps.readFile(resolve(base, bodyFile)))) {
|
|
882
|
-
leaks.add(leak);
|
|
883
|
-
}
|
|
884
|
-
} catch {
|
|
885
|
-
unreadable ??= bodyFile;
|
|
886
|
-
}
|
|
911
|
+
const { texts, unreadable } = await readGhPrBodyFiles(call, deps.readFile);
|
|
912
|
+
for (const text of texts) {
|
|
913
|
+
for (const leak of findPrBodyLeaks(text)) leaks.add(leak);
|
|
887
914
|
}
|
|
888
915
|
if (leaks.size === 0 && unreadable === undefined) continue;
|
|
889
916
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { parseGhPrCommands, readGhPrBodyFiles, shellCommandOf } from "./pr-body-guard";
|
|
2
|
+
|
|
3
|
+
export type PrRoomGuardDeps = {
|
|
4
|
+
env: Record<string, string | undefined>;
|
|
5
|
+
readFile: (path: string) => Promise<string>;
|
|
6
|
+
remoteOrigin: (cwd: string) => Promise<string>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const BLOCK_REASON =
|
|
10
|
+
'This repo uses PR rooms. Create the room first with the pr-room skill (app action createRoom). Then pass the returned prBody with --body-file. The body must contain the line "PR room: prr-...".';
|
|
11
|
+
const ROOM_LINE = /^PR room: prr-[a-z2-7]{10}\b/m;
|
|
12
|
+
const REMOTE_LOOKUP_TIMEOUT_MS = 5_000;
|
|
13
|
+
|
|
14
|
+
/** Read the `remote.origin.url` of a checkout. A lookup that does not finish in `timeoutMs` is killed and rejects. */
|
|
15
|
+
export async function gitRemoteOrigin(
|
|
16
|
+
cwd: string,
|
|
17
|
+
timeoutMs = REMOTE_LOOKUP_TIMEOUT_MS,
|
|
18
|
+
): Promise<string> {
|
|
19
|
+
const proc = Bun.spawn(["git", "-C", cwd, "config", "--get", "remote.origin.url"], {
|
|
20
|
+
stdout: "pipe",
|
|
21
|
+
stderr: "ignore",
|
|
22
|
+
stdin: "ignore",
|
|
23
|
+
timeout: timeoutMs,
|
|
24
|
+
killSignal: "SIGKILL",
|
|
25
|
+
});
|
|
26
|
+
const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
27
|
+
if (code !== 0) throw new Error("Cannot resolve checkout remote");
|
|
28
|
+
return out.trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const defaultDeps = (): PrRoomGuardDeps => ({
|
|
32
|
+
env: process.env,
|
|
33
|
+
readFile: (path) => Bun.file(path).text(),
|
|
34
|
+
remoteOrigin: (cwd) => gitRemoteOrigin(cwd),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/** Extract owner/repo from an HTTPS, SSH URL, or SCP-style SSH remote. */
|
|
38
|
+
function repoFromRemote(remote: string): string | undefined {
|
|
39
|
+
const match =
|
|
40
|
+
/^(?:https?:\/\/[^/]+\/|ssh:\/\/[^/]+\/|[^\s/@:]+@[^\s/:]+:)([^/\s]+)\/([^/\s]+?)\/?$/.exec(
|
|
41
|
+
remote.trim(),
|
|
42
|
+
);
|
|
43
|
+
return match ? `${match[1]}/${match[2]?.replace(/\.git$/, "")}`.toLowerCase() : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Require a room marker for configured repositories. Unresolved or ambiguous inputs allow the command. */
|
|
47
|
+
export async function guardGhPrRoom(
|
|
48
|
+
toolInput: unknown,
|
|
49
|
+
cwd: string,
|
|
50
|
+
deps: PrRoomGuardDeps = defaultDeps(),
|
|
51
|
+
): Promise<string | null> {
|
|
52
|
+
try {
|
|
53
|
+
const repos = new Set(
|
|
54
|
+
(deps.env.PR_ROOM_REPOS ?? "")
|
|
55
|
+
.split(",")
|
|
56
|
+
.map((repo) => repo.trim().toLowerCase())
|
|
57
|
+
.filter(Boolean),
|
|
58
|
+
);
|
|
59
|
+
if (repos.size === 0) return null;
|
|
60
|
+
const command = shellCommandOf(toolInput);
|
|
61
|
+
if (command === null) return null;
|
|
62
|
+
for (const call of parseGhPrCommands(command, cwd, deps.env)) {
|
|
63
|
+
if (call.action !== "create" || call.unknownTarget || call.cwd === null) continue;
|
|
64
|
+
// The body comes from a pipe, a command substitution, gh itself, or an unknown variable.
|
|
65
|
+
if (call.opaqueBody || call.externalBody || call.bodyTexts.includes(null)) continue;
|
|
66
|
+
const targets = [...call.resolvedRepos];
|
|
67
|
+
if (targets.includes(null)) continue;
|
|
68
|
+
if (targets.length === 0 || call.alsoCheckout) {
|
|
69
|
+
try {
|
|
70
|
+
const repo = repoFromRemote(await deps.remoteOrigin(call.cwd));
|
|
71
|
+
if (!repo) continue;
|
|
72
|
+
targets.push(repo);
|
|
73
|
+
} catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// An unexported `GH_REPO=x` may not reach gh: block only when every candidate is listed.
|
|
78
|
+
if (!targets.every((repo) => repo !== null && repos.has(repo.trim().toLowerCase()))) continue;
|
|
79
|
+
const body = await readGhPrBodyFiles(call, deps.readFile);
|
|
80
|
+
if (body.unreadable !== undefined) continue;
|
|
81
|
+
const texts = [...call.bodyTexts, ...body.texts];
|
|
82
|
+
if (!texts.some((text) => text !== null && ROOM_LINE.test(text))) return BLOCK_REASON;
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// A workflow reminder must not prevent commands when resolution fails.
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|