@desplega.ai/agent-swarm 1.169.0 → 1.171.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/{acp-adapter-m47zfx4a.js → acp-adapter-1z9p3fvx.js} +6 -6
- package/dist/{actions-ws7k3tvq.js → actions-rc2yjk8a.js} +31 -29
- package/dist/{amp-adapter-nvv8k8j5.js → amp-adapter-ypyf5s1x.js} +6 -6
- package/dist/{anthropic-messages-13yn502s.js → anthropic-messages-etkbxhhy.js} +11 -11
- package/dist/{app-80w6qspx.js → app-04ab2s0z.js} +9 -9
- package/dist/{artifact-fey7dvj9.js → artifact-eczsknc3.js} +6 -3
- package/dist/{assistant-jtq439kw.js → assistant-vhmjd8wf.js} +34 -32
- package/dist/{azure-openai-responses-9st34pfz.js → azure-openai-responses-xap57x4b.js} +7 -7
- package/dist/{boot-reembed-feda8wn8.js → boot-reembed-3p0m259x.js} +8 -8
- package/dist/{boot-reembed-3nc0aaed.js → boot-reembed-wew3gwk8.js} +8 -8
- package/dist/{boot-scrub-sweep-grsa2er0.js → boot-scrub-sweep-5ytwtq24.js} +9 -9
- package/dist/{boot-seeds-gcfgq44f.js → boot-seeds-s8p2xry8.js} +12 -12
- package/dist/{claude-adapter-pjv2q7t8.js → claude-adapter-akrzg6jh.js} +15 -15
- package/dist/{claude-managed-adapter-0wa8se82.js → claude-managed-adapter-kqqqhp2q.js} +2 -2
- package/dist/{claude-managed-setup-zxa9p9tx.js → claude-managed-setup-bnmjarjc.js} +11 -8
- package/dist/{claude-sdk-session-c9j6k9kx.js → claude-sdk-session-exq1jadx.js} +15 -15
- package/dist/{cli-a8vps458.js → cli-01qndrvw.js} +17274 -17242
- package/dist/{cli-rg1c8ccw.js → cli-0d2rqhvq.js} +497 -73
- package/dist/cli-0p3enk35.js +188 -0
- package/dist/cli-1srmzpr3.js +16 -0
- package/dist/{cli-zzev32jw.js → cli-231av4y6.js} +6 -6
- package/dist/{cli-kp3vfknv.js → cli-280gnz4v.js} +2 -2
- package/dist/{cli-mv2520nm.js → cli-2bw3w47j.js} +1 -1
- package/dist/{cli-km8559dm.js → cli-2qbs2tvf.js} +18 -10
- package/dist/{cli-efnd2kce.js → cli-2rbt7at8.js} +6 -1
- package/dist/{cli-xvc3q9br.js → cli-451ks6aw.js} +1 -1
- package/dist/{cli-jkfxpg1b.js → cli-4bm25xtw.js} +2 -2
- package/dist/{cli-zp38xvtm.js → cli-5rqrj7q0.js} +1 -1
- package/dist/{cli-km4x66j0.js → cli-5y67yrt6.js} +13 -13
- package/dist/{cli-qg2zhjfc.js → cli-62bwwc2q.js} +8 -8
- package/dist/{cli-s0k2j7q9.js → cli-6xxxb8qt.js} +10 -10
- package/dist/cli-6zx22mv6.js +0 -0
- package/dist/{cli-zp67qg5x.js → cli-7a0sde2x.js} +72 -1
- package/dist/{cli-v1w7pbd8.js → cli-7jykv7ef.js} +1 -1
- package/dist/{cli-rfphkzh5.js → cli-7pz3b7x5.js} +24 -4
- package/dist/{cli-mb75wpxf.js → cli-81r1wjyv.js} +1 -1
- package/dist/{cli-rbr91whd.js → cli-8mr695sb.js} +9 -5
- package/dist/{cli-f14fvzag.js → cli-a1b9b32n.js} +6 -1
- package/dist/cli-cd0hv9nh.js +32 -0
- package/dist/{cli-dv76vq5d.js → cli-ctret352.js} +685 -209
- package/dist/{cli-wt7ccy4r.js → cli-d161g69q.js} +9 -1
- package/dist/cli-db3m6zdt.js +362 -0
- package/dist/{cli-hf5s9pgf.js → cli-e3pmyt39.js} +1 -1
- package/dist/{cli-ysnkvdx6.js → cli-ec7hrgqp.js} +313 -91
- package/dist/{cli-m23r88re.js → cli-erbp6b4g.js} +3 -3
- package/dist/{cli-9k7mqz00.js → cli-ge593j7e.js} +30 -3
- package/dist/cli-h8w3fg83.js +17 -0
- package/dist/{cli-xvdgasgy.js → cli-hsem16hb.js} +114 -9
- package/dist/{cli-yvxfaem2.js → cli-hsk5gd15.js} +9 -5
- package/dist/{cli-7vz49g7h.js → cli-jxsgrnmq.js} +101 -4
- package/dist/{cli-b7y8113e.js → cli-k2v5x13t.js} +1 -1
- package/dist/{cli-bqrs5ycj.js → cli-m4csbs67.js} +2 -2
- package/dist/{cli-91r17hb1.js → cli-mzj3aqac.js} +3 -3
- package/dist/{cli-swrz3mj8.js → cli-n805bb98.js} +1 -1
- package/dist/{cli-sajr098b.js → cli-n8ybnv1k.js} +3 -3
- package/dist/{cli-e5qk3m2a.js → cli-nkjwe4z7.js} +19 -19
- package/dist/{cli-3ry5gx4f.js → cli-p2pjfq1r.js} +2 -2
- package/dist/{cli-61tbnjpf.js → cli-p8f7dqgt.js} +767 -541
- package/dist/{cli-r7eeddkq.js → cli-ph3145b7.js} +3 -3
- package/dist/cli-ppy4nnnh.js +168 -0
- package/dist/cli-r455yd58.js +832 -0
- package/dist/{cli-jv36y8wz.js → cli-rrbctenq.js} +1 -1
- package/dist/{cli-8xjst8q2.js → cli-s88my3br.js} +11 -11
- package/dist/{cli-vejds37t.js → cli-srmej5tp.js} +3 -3
- package/dist/{cli-eqfbsxsn.js → cli-tg8q0b5y.js} +14 -11
- package/dist/cli-w0zx9ya1.js +1351 -0
- package/dist/{cli-rh44xx46.js → cli-wfvedq5w.js} +15 -15
- package/dist/{cli-n4z21pt8.js → cli-x207g4w1.js} +1 -1
- package/dist/{cli-fyeft04q.js → cli-x3fy2tgp.js} +2 -2
- package/dist/{cli-7mhy3ths.js → cli-x61srfpp.js} +5 -5
- package/dist/{cli-w6xa5nf5.js → cli-y1j1ggbf.js} +8 -8
- package/dist/{cli-jvmtxqtc.js → cli-ywh2a87k.js} +7 -2
- package/dist/{cli-3qhyh31j.js → cli-ywm1azg9.js} +6 -0
- package/dist/{cli-z48ggfyq.js → cli-zxm9p3x4.js} +2 -2
- package/dist/cli.js +42 -24
- package/dist/{codex-adapter-0kpg09ca.js → codex-adapter-9ag75whm.js} +13 -13
- package/dist/{codex-hook-295jpbfa.js → codex-hook-7ba15hjd.js} +5 -3
- package/dist/{codex-login-g1cyt5zm.js → codex-login-841kv5fk.js} +7 -5
- package/dist/{codex-session-runner-8n672hxh.js → codex-session-runner-9fp7y4cf.js} +13 -13
- package/dist/{commands-mhmg5n0x.js → commands-d7eah0y9.js} +10 -10
- package/dist/controller-hq5ggpcp.js +193 -0
- package/dist/controllers-connect-8t0k65x2.js +265 -0
- package/dist/{cursor-adapter-wn8n5zzk.js → cursor-adapter-t4pr1m3c.js} +5 -5
- package/dist/db-2rmd23wq.js +71 -0
- package/dist/{db-wa6s1mwv.js → db-sp39qv72.js} +8 -8
- package/dist/{devin-adapter-bffjj5n4.js → devin-adapter-xgtj680j.js} +2 -2
- package/dist/{dispatcher-nhs2pkr8.js → dispatcher-g7fsybsd.js} +30 -28
- package/dist/docker-f503k9p0.js +201 -0
- package/dist/{dsh-adapter-5423p70m.js → dsh-adapter-2h8m4cab.js} +5 -5
- package/dist/e2b-66xra4az.js +202 -0
- package/dist/{e2b-nd50eccg.js → e2b-xwhyj8cw.js} +43 -527
- package/dist/{event-bus-vnfthafg.js → event-bus-vjtv82gm.js} +2 -2
- package/dist/{grok-adapter-28pkdt80.js → grok-adapter-1err1me7.js} +7 -7
- package/dist/{handlers-5rtf9nng.js → handlers-c7yggm37.js} +33 -31
- package/dist/heartbeat-dkm924fk.js +124 -0
- package/dist/{hook-qkm73zk6.js → hook-g8e7sfnf.js} +13 -13
- package/dist/{hook-krjfkpzy.js → hook-wgx4exw6.js} +20 -17
- package/dist/{http-a397xcn4.js → http-1d23k928.js} +1958 -205
- package/dist/{index-kqre45fr.js → index-gm10tmye.js} +30 -28
- package/dist/{index-rtxg15ah.js → index-q7cj9bn2.js} +39 -29
- package/dist/{index-vvsq7v45.js → index-va4zbvzp.js} +30 -28
- package/dist/{index-ac0v8v9d.js → index-vertaxs1.js} +8 -8
- package/dist/{index-ytdhtdry.js → index-y5vvczrs.js} +31 -22
- package/dist/{keepalive-psfh8ysx.js → keepalive-1jc5e7ct.js} +10 -10
- package/dist/{lead-m6rppjn7.js → lead-yxw2f1sg.js} +48 -46
- package/dist/{maintenance-41f837ke.js → maintenance-h7w2m10y.js} +8 -8
- package/dist/{node-8zkdq42h.js → node-c9we95xf.js} +9 -9
- package/dist/{oauth-refresh-sweep-6hr55hm8.js → oauth-refresh-sweep-wh9ypd1b.js} +9 -9
- package/dist/{onboard-ktrtd7cz.js → onboard-dfr9j762.js} +25 -15
- package/dist/{openai-codex-responses-jzxkta9b.js → openai-codex-responses-pgvtddvv.js} +3 -3
- package/dist/{openai-responses-m3g43rrt.js → openai-responses-9dyzwzkr.js} +9 -9
- package/dist/{opencode-adapter-yjyqynzf.js → opencode-adapter-f1zby94p.js} +3 -3
- package/dist/{otel-impl-dq4f2k3m.js → otel-impl-and31m5s.js} +5 -5
- package/dist/{pi-mono-adapter-e5v9w0hv.js → pi-mono-adapter-fx6k5wwh.js} +38 -27
- package/dist/{pricing-refresh-kmk7j1ra.js → pricing-refresh-23zr6j3w.js} +10 -10
- package/dist/{rbac-roles-9ykx9tc5.js → rbac-roles-nc7k9d0t.js} +10 -10
- package/dist/{rbac-roles-9w1dkkxd.js → rbac-roles-wnx35x4x.js} +11 -11
- package/dist/{render-v2-ycqm53np.js → render-v2-81cg1y52.js} +9 -9
- package/dist/{resume-cq2pkp8q.js → resume-d4b6j2n2.js} +16 -16
- package/dist/{setup-0dcmdvbn.js → setup-g68r1w28.js} +7 -5
- package/dist/static-t1gr2cr7.js +30 -0
- package/dist/{virtual-modules-1x9k4jcs.js → virtual-modules-rdjmcg7q.js} +3 -3
- package/dist/{work-objects-x01x2fh8.js → work-objects-0zzdnbt9.js} +35 -33
- package/dist/{worker-py1ssb1h.js → worker-p1g8qm6h.js} +48 -46
- package/dist/{x-ma4gnfgy.js → x-qfmygqqv.js} +2 -2
- package/openapi.json +3382 -167
- package/package.json +3 -3
- package/src/be/db/agents.ts +6 -0
- package/src/be/db/instance-signals.ts +30 -0
- package/src/be/db/tasks/read.ts +2 -0
- package/src/be/db/tasks/write.ts +39 -5
- package/src/be/db/workflow-runs.ts +22 -0
- package/src/be/db.ts +96 -40
- package/src/be/fleet-benchmark-stats.ts +39 -0
- package/src/be/migrations/205_favorites_per_dashboard_user.sql +29 -0
- package/src/be/migrations/206_runtime_pools.sql +62 -0
- package/src/be/migrations/207_runtime_session_tokens.sql +31 -0
- package/src/be/migrations/208_task_runtime_dispatch.sql +4 -0
- package/src/be/model-tier-resolution.ts +9 -2
- package/src/be/multi-runtime.ts +102 -13
- package/src/be/org-profile.ts +35 -0
- package/src/be/rbac-audit.ts +4 -0
- package/src/be/runtime-pools/controllers.ts +150 -0
- package/src/be/runtime-pools/db.ts +526 -0
- package/src/be/runtime-pools/demand.ts +65 -0
- package/src/be/runtime-pools/e2b-images.ts +109 -0
- package/src/be/runtime-pools/errors.ts +8 -0
- package/src/be/runtime-pools/lifecycle.ts +44 -0
- package/src/be/runtime-pools/reachability.ts +30 -0
- package/src/be/runtime-pools/recovery.ts +248 -0
- package/src/be/runtime-pools/remote-controller.ts +144 -0
- package/src/be/runtime-pools/routing.ts +304 -0
- package/src/be/runtime-pools/seed.ts +110 -0
- package/src/be/runtime-pools/supervisor.ts +981 -0
- package/src/be/runtime-pools/tokens.ts +57 -0
- package/src/be/scripts/typecheck.ts +48 -3
- package/src/be/secret-registry.ts +17 -1
- package/src/be/skill-usage.ts +467 -0
- package/src/be/swarm-config-guard.ts +59 -2
- package/src/be/telemetry-enable.ts +66 -0
- package/src/be/telemetry-ingest-anchor.ts +77 -0
- package/src/be/users.ts +173 -23
- package/src/be/worker-nudge-listener.ts +28 -0
- package/src/be/worker-nudges.ts +22 -0
- package/src/cli.tsx +22 -3
- package/src/commands/controller.ts +180 -0
- package/src/commands/onboard/non-interactive.ts +36 -12
- package/src/commands/onboard/types.ts +1 -1
- package/src/commands/onboard.tsx +3 -0
- package/src/commands/push-delivery.ts +230 -0
- package/src/commands/runner.ts +560 -276
- package/src/e2b/dispatch.ts +15 -1
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/fleet-benchmark.ts +157 -0
- package/src/heartbeat/heartbeat.ts +53 -135
- package/src/hooks/hook.ts +3 -2
- package/src/http/agents.ts +21 -0
- package/src/http/all-routes.ts +6 -0
- package/src/http/apps.ts +4 -2
- package/src/http/auth.ts +4 -7
- package/src/http/config.ts +58 -21
- package/src/http/controllers-connect.ts +206 -0
- package/src/http/core.ts +108 -14
- package/src/http/favorite-owner.ts +31 -1
- package/src/http/fleet-benchmark.ts +206 -0
- package/src/http/index.ts +38 -6
- package/src/http/mcp.ts +3 -8
- package/src/http/poll.ts +31 -2
- package/src/http/realtime.ts +1 -1
- package/src/http/request-principal.ts +2 -0
- package/src/http/runtime-pools.ts +527 -0
- package/src/http/session-data.ts +2 -0
- package/src/http/sessions.ts +100 -23
- package/src/http/skills.ts +173 -0
- package/src/http/status.ts +25 -1
- package/src/http/tasks.ts +14 -1
- package/src/http/telemetry-ui.ts +102 -0
- package/src/http/used-by.ts +140 -0
- package/src/http/utils.ts +7 -1
- package/src/http/workflows.ts +10 -2
- package/src/jira/app.ts +1 -2
- package/src/kv-reserved-namespaces.ts +1 -0
- package/src/linear/app.ts +1 -2
- package/src/providers/acp-target-catalog.ts +75 -1
- package/src/providers/acp-targets.ts +114 -0
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +5 -1
- package/src/providers/opencode-adapter.ts +2 -1
- package/src/providers/pi-mono-adapter.ts +41 -26
- package/src/providers/pricing-sources.md +2 -0
- package/src/providers/skill-invoke.ts +39 -3
- package/src/rbac/legacy-policy.ts +8 -0
- package/src/rbac/permissions.ts +16 -0
- package/src/rbac/types.ts +2 -0
- package/src/realtime/auth.ts +13 -0
- package/src/realtime/channels.ts +24 -0
- package/src/realtime/transport.ts +94 -15
- package/src/realtime/worker-client.ts +176 -0
- package/src/runtime-providers/controller-protocol.ts +97 -0
- package/src/runtime-providers/controller.ts +86 -0
- package/src/runtime-providers/docker.ts +235 -0
- package/src/runtime-providers/e2b.ts +217 -0
- package/src/runtime-providers/index.ts +37 -0
- package/src/runtime-providers/static.ts +26 -0
- package/src/runtime-providers/types.ts +92 -0
- package/src/runtime-providers/worker-api-url.ts +11 -0
- package/src/scripts-runtime/sdk-allowlist.ts +6 -0
- package/src/scripts-runtime/types/stdlib.d.ts +15 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
- package/src/server.ts +12 -0
- package/src/slack/router.ts +7 -5
- package/src/tasks/worker-follow-up.ts +80 -52
- package/src/telemetry-context.ts +6 -0
- package/src/telemetry-contract/EVENTS.md +14 -1
- package/src/telemetry-contract/SOURCE.json +4 -4
- package/src/telemetry-contract/catalog.json +155 -0
- package/src/telemetry-contract/types.gen.ts +248 -4
- package/src/telemetry-ui-events.ts +130 -0
- package/src/telemetry.ts +43 -3
- package/src/tests/acp-adapter.test.ts +129 -0
- package/src/tests/claude-adapter.test.ts +6 -2
- package/src/tests/codex-adapter.test.ts +6 -2
- package/src/tests/config-internal-keys.test.ts +27 -0
- package/src/tests/config-session-auth.test.ts +19 -3
- package/src/tests/db-retention.test.ts +50 -10
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/e2b-dispatch.test.ts +28 -1
- package/src/tests/e2e-runtime-pools.test.ts +51 -0
- package/src/tests/entrypoint-acp-target-binary.test.ts +38 -1
- package/src/tests/favorites.test.ts +188 -0
- package/src/tests/fetch-resolved-env.test.ts +59 -1
- package/src/tests/fleet-benchmark.test.ts +453 -0
- package/src/tests/harness-model-support.test.ts +70 -2
- package/src/tests/heartbeat-orphan-resume.test.ts +24 -0
- package/src/tests/heartbeat-reboot-live.test.ts +31 -0
- package/src/tests/heartbeat-supersede-resume.test.ts +21 -0
- package/src/tests/hook-runtime-close.test.ts +64 -0
- package/src/tests/http-task-write-ownership.test.ts +8 -1
- package/src/tests/integration-disable-flags.test.ts +45 -0
- package/src/tests/migration-206-runtime-pools.test.ts +134 -0
- package/src/tests/migration-207-runtime-session-tokens.test.ts +45 -0
- package/src/tests/migration-208-task-runtime-dispatch.test.ts +30 -0
- package/src/tests/multi-runtime-registration.test.ts +219 -4
- package/src/tests/onboard-non-interactive.test.ts +104 -6
- package/src/tests/opencode-adapter.test.ts +6 -2
- package/src/tests/org-website.test.ts +115 -0
- package/src/tests/pi-mono-adapter.test.ts +74 -10
- package/src/tests/rbac-admission-e2e.test.ts +4 -4
- package/src/tests/rbac-engine.test.ts +8 -1
- package/src/tests/realtime-room-auth.test.ts +49 -0
- package/src/tests/repo-allow-merge-gate.test.ts +8 -1
- package/src/tests/runner-exit-after-tasks.test.ts +163 -0
- package/src/tests/runner-finish-retry.test.ts +466 -0
- package/src/tests/runner-harness-telemetry-env.test.ts +105 -0
- package/src/tests/runner-push-delivery.test.ts +670 -0
- package/src/tests/runner-slot-reconcile.test.ts +89 -1
- package/src/tests/runtime-child-token-cleanup.test.ts +172 -0
- package/src/tests/runtime-controller-token.test.ts +254 -0
- package/src/tests/runtime-demand.test.ts +119 -0
- package/src/tests/runtime-e2b-pools.test.ts +847 -0
- package/src/tests/runtime-pool-reachability.test.ts +328 -0
- package/src/tests/runtime-pool-routing.test.ts +817 -0
- package/src/tests/runtime-pools-db.test.ts +275 -0
- package/src/tests/runtime-pools-http.test.ts +506 -0
- package/src/tests/runtime-pools-ownership-guards.test.ts +235 -0
- package/src/tests/runtime-pools-seed.test.ts +203 -0
- package/src/tests/runtime-pools-test-utils.ts +128 -0
- package/src/tests/runtime-provider-registration.test.ts +133 -0
- package/src/tests/runtime-providers-docker.test.ts +400 -0
- package/src/tests/runtime-providers-e2b-transport.test.ts +50 -0
- package/src/tests/runtime-providers-e2b.test.ts +335 -0
- package/src/tests/runtime-recovery.test.ts +745 -0
- package/src/tests/runtime-remote-controller.test.ts +846 -0
- package/src/tests/runtime-supervisor-failures.test.ts +387 -0
- package/src/tests/runtime-supervisor.test.ts +1039 -0
- package/src/tests/runtime-token-auth.test.ts +897 -0
- package/src/tests/script-connections.test.ts +7 -0
- package/src/tests/secret-scrubber.test.ts +17 -2
- package/src/tests/session-costs-codex-recompute.test.ts +49 -0
- package/src/tests/session-tokens.test.ts +13 -3
- package/src/tests/skill-invoke.test.ts +142 -0
- package/src/tests/skill-usage-cohorts.test.ts +232 -0
- package/src/tests/skill-usage.test.ts +348 -0
- package/src/tests/slack-attachments-block.test.ts +23 -0
- package/src/tests/slack-render-v2.test.ts +6 -0
- package/src/tests/status-instance.test.ts +71 -0
- package/src/tests/task-supersede-resume.test.ts +87 -4
- package/src/tests/telemetry-enable.test.ts +113 -0
- package/src/tests/telemetry-ui-route.test.ts +195 -0
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/used-by-route.test.ts +237 -0
- package/src/tests/worker-nudges.test.ts +653 -0
- package/src/tests/worker-realtime-client.test.ts +188 -0
- package/src/tests/workflow-sub-workflow.test.ts +22 -0
- package/src/tools/app-upsert.ts +1 -1
- package/src/tools/runtime-controller-list.ts +28 -0
- package/src/tools/runtime-pool-bind-agent.ts +29 -0
- package/src/tools/runtime-pool-common.ts +37 -0
- package/src/tools/runtime-pool-create-agent.ts +29 -0
- package/src/tools/runtime-pool-delete.ts +27 -0
- package/src/tools/runtime-pool-list.ts +27 -0
- package/src/tools/runtime-pool-upsert.ts +31 -0
- package/src/tools/script-common.ts +1 -1
- package/src/tools/send-task.ts +3 -2
- package/src/tools/tool-config.ts +6 -0
- package/src/types.ts +137 -2
- package/src/utils/config-secrets.ts +1 -0
- package/src/utils/lead-monitor-channels.ts +3 -0
- package/src/utils/org-website.ts +104 -0
- package/src/utils/request-auth-context.ts +10 -2
- package/src/utils/runtime-pools-flag.ts +11 -0
- package/src/utils/secret-scrubber.ts +9 -0
- package/src/utils/session-token-connections.ts +20 -0
- package/templates/skills/apps/content.md +1 -1
- package/templates/skills/code-quality/content.md +9 -1
- package/templates/skills/db-query-guidance/content.md +9 -0
- package/templates/skills/swarm-scripts/SKILL.md +1 -1
- package/templates/skills/swarm-scripts/content.md +1 -1
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/dist/{cli-qd97v6z2.js → cli-1qtgd1h1.js} +3 -3
- package/dist/{cli-cmxkbzzs.js → cli-j0w59p15.js} +6 -6
- package/dist/{google-generative-ai-b8zxrpax.js → google-generative-ai-pvhm0dq1.js} +1 -1
- package/dist/{google-vertex-nrdd697t.js → google-vertex-tek9sqe2.js} +4 -4
- package/dist/{mistral-conversations-059y51zt.js → mistral-conversations-rjxa9s1j.js} +3 -3
- package/dist/{openai-completions-663yncfq.js → openai-completions-44gb0zsk.js} +12 -12
package/src/http/core.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
import { initAgentMail, resetAgentMail } from "../agentmail";
|
|
3
4
|
import {
|
|
4
5
|
initAzureDevOps,
|
|
@@ -17,17 +18,20 @@ import {
|
|
|
17
18
|
updateAgentStatus,
|
|
18
19
|
} from "../be/db";
|
|
19
20
|
import {
|
|
21
|
+
getRuntimeInstanceById,
|
|
20
22
|
markRuntimeInstanceOffline,
|
|
23
|
+
markRuntimeTerminated,
|
|
21
24
|
reconcileAgentStatusFromRuntimes,
|
|
22
25
|
touchRuntimeInstance,
|
|
23
26
|
} from "../be/multi-runtime";
|
|
24
27
|
import { enqueueAdmissionRow } from "../be/rbac-audit";
|
|
25
28
|
import { getUserGrant } from "../be/rbac-roles";
|
|
29
|
+
import { syncTelemetryEnabled } from "../be/telemetry-enable";
|
|
26
30
|
import { initGitHub, resetGitHub } from "../github";
|
|
27
31
|
import { initGitLab, isGitLabEnabled, resetGitLab } from "../gitlab";
|
|
28
32
|
import { initJira, resetJira } from "../jira";
|
|
29
33
|
import { initLinear, resetLinear } from "../linear";
|
|
30
|
-
import { decideAdmission, isRbacEnabled } from "../rbac";
|
|
34
|
+
import { can, decideAdmission, isRbacEnabled } from "../rbac";
|
|
31
35
|
import { startSlackApp, stopSlackApp } from "../slack";
|
|
32
36
|
import type { AgentStatus } from "../types";
|
|
33
37
|
import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
|
|
@@ -40,7 +44,13 @@ import { refreshSecretScrubberCache, registerSensitiveKeyName } from "../utils/s
|
|
|
40
44
|
import { resolveHttpRequestAuth } from "./auth";
|
|
41
45
|
import { generateOpenApiSpec, SCALAR_HTML } from "./openapi";
|
|
42
46
|
import { findRoute, isPublicRoute, route, runtimeInstanceHeader } from "./route-def";
|
|
43
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
agentWithCapacity,
|
|
49
|
+
duplicateGuardedQueryKey,
|
|
50
|
+
getPathSegments,
|
|
51
|
+
jsonError,
|
|
52
|
+
parseQueryParams,
|
|
53
|
+
} from "./utils";
|
|
44
54
|
|
|
45
55
|
/**
|
|
46
56
|
* Load global swarm_config entries into process.env.
|
|
@@ -159,6 +169,12 @@ async function reloadGlobalConfigsAndIntegrationsInner(): Promise<ReloadConfigRe
|
|
|
159
169
|
|
|
160
170
|
const updated = await loadGlobalConfigsIntoEnv(true);
|
|
161
171
|
|
|
172
|
+
// ANONYMIZED_TELEMETRY may have just flipped on: mint the install ID now
|
|
173
|
+
// instead of dropping every event until a restart.
|
|
174
|
+
await syncTelemetryEnabled("settings").catch((err) => {
|
|
175
|
+
console.error("[config-reload] telemetry re-init failed (non-fatal):", err);
|
|
176
|
+
});
|
|
177
|
+
|
|
162
178
|
// Retire the old transport before any other integration can fail during
|
|
163
179
|
// reload. HTTP/invalid selection must never leave the previous socket live.
|
|
164
180
|
await stopSlackApp();
|
|
@@ -380,17 +396,18 @@ const closeRoute = route({
|
|
|
380
396
|
method: "post",
|
|
381
397
|
path: "/close",
|
|
382
398
|
pattern: ["close"],
|
|
383
|
-
summary: "
|
|
399
|
+
summary: "End a session or terminate an agent runtime",
|
|
384
400
|
description:
|
|
385
|
-
`
|
|
386
|
-
"is
|
|
387
|
-
"
|
|
401
|
+
`Closes the calling session. ${RUNTIME_HEADER_DOC} Pooled runtimes remain active unless ` +
|
|
402
|
+
"runtimeShutdown is true. With multi-runtime mode on, the header is required and static " +
|
|
403
|
+
"runtimes retire independently. With the flag off, static agents are marked offline.",
|
|
388
404
|
tags: ["Core"],
|
|
389
405
|
auth: { apiKey: true, agentId: true },
|
|
390
406
|
headers: runtimeInstanceHeader("retire a runtime"),
|
|
407
|
+
body: z.object({ runtimeShutdown: z.boolean().optional() }).optional(),
|
|
391
408
|
rbac: { ungated: "self-scoped: an agent retires its own runtime" },
|
|
392
409
|
responses: {
|
|
393
|
-
204: { description: "
|
|
410
|
+
204: { description: "Session closed or runtime retired" },
|
|
394
411
|
400: {
|
|
395
412
|
description: "Missing X-Agent-ID, or missing X-Runtime-Instance-ID in multi-runtime mode",
|
|
396
413
|
},
|
|
@@ -409,6 +426,14 @@ export async function handleCore(
|
|
|
409
426
|
// request pipeline does afterwards, including audit columns in the DB layer.
|
|
410
427
|
beginRequestAuthScope();
|
|
411
428
|
|
|
429
|
+
// Controller credentials authorize only their separate WebSocket transport.
|
|
430
|
+
let auth = await resolveHttpRequestAuth(req, apiKey);
|
|
431
|
+
if (auth?.kind === "controller") {
|
|
432
|
+
setRequestAuth(req, auth);
|
|
433
|
+
jsonError(res, "Controller tokens can only connect to the controller socket", 403);
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
|
|
412
437
|
// Handle preflight
|
|
413
438
|
if (req.method === "OPTIONS") {
|
|
414
439
|
res.writeHead(204);
|
|
@@ -455,14 +480,12 @@ export async function handleCore(
|
|
|
455
480
|
// either the global swarm key or an active user-bound `aswt_` token.
|
|
456
481
|
const pathSegments = getPathSegments(req.url || "");
|
|
457
482
|
const isUserMcpRoute = req.url === "/mcp-user";
|
|
458
|
-
let auth = null as Awaited<ReturnType<typeof resolveHttpRequestAuth>>;
|
|
459
483
|
// `/mcp-user` runs its own `aswt_`-token auth in `handleMcpUser`; the swarm
|
|
460
484
|
// API key must not gate it.
|
|
461
485
|
if (isUserMcpRoute || isPublicRoute(req.method, pathSegments)) {
|
|
486
|
+
auth = null;
|
|
462
487
|
setRequestAuth(req, null);
|
|
463
488
|
} else {
|
|
464
|
-
auth = await resolveHttpRequestAuth(req, apiKey);
|
|
465
|
-
|
|
466
489
|
if (!auth) {
|
|
467
490
|
setRequestAuth(req, null);
|
|
468
491
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
@@ -472,6 +495,65 @@ export async function handleCore(
|
|
|
472
495
|
setRequestAuth(req, auth);
|
|
473
496
|
}
|
|
474
497
|
|
|
498
|
+
if (auth?.kind === "agent") {
|
|
499
|
+
const query = parseQueryParams(req.url || "");
|
|
500
|
+
const duplicate = duplicateGuardedQueryKey(query);
|
|
501
|
+
if (duplicate) {
|
|
502
|
+
jsonError(res, `Duplicate query parameter: ${duplicate}`, 400);
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
if (req.headers["x-page-id"] !== undefined) {
|
|
506
|
+
jsonError(res, "Agent tokens cannot send X-Page-Id", 403);
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
const header = req.headers["x-agent-id"];
|
|
510
|
+
const agentId = Array.isArray(header) ? header[0] : header;
|
|
511
|
+
if (agentId !== undefined && agentId !== auth.agentId) {
|
|
512
|
+
jsonError(res, "X-Agent-ID does not match token", 403);
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
req.headers["x-agent-id"] = auth.agentId;
|
|
516
|
+
myAgentId = auth.agentId;
|
|
517
|
+
if (auth.runtimeId) {
|
|
518
|
+
const header = req.headers["x-runtime-instance-id"];
|
|
519
|
+
const runtimeId = Array.isArray(header) ? header[0] : header;
|
|
520
|
+
if (runtimeId !== undefined && runtimeId !== auth.runtimeId) {
|
|
521
|
+
jsonError(res, "X-Runtime-Instance-ID does not match token", 403);
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
req.headers["x-runtime-instance-id"] = auth.runtimeId;
|
|
525
|
+
const sourceHeader = req.headers["x-source-task-id"];
|
|
526
|
+
const taskId = Array.isArray(sourceHeader) ? sourceHeader[0] : sourceHeader;
|
|
527
|
+
if (taskId !== undefined && (await getTaskById(taskId))?.agentId !== auth.agentId) {
|
|
528
|
+
jsonError(res, "X-Source-Task-Id does not belong to token agent", 403);
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const targetAgentId = pathSegments[2];
|
|
533
|
+
if (
|
|
534
|
+
pathSegments[0] === "api" &&
|
|
535
|
+
pathSegments[1] === "agents" &&
|
|
536
|
+
targetAgentId &&
|
|
537
|
+
targetAgentId !== auth.agentId &&
|
|
538
|
+
(req.method !== "GET" ||
|
|
539
|
+
query.get("resolveSecrets") === "true" ||
|
|
540
|
+
query.get("includeSecrets") === "true")
|
|
541
|
+
) {
|
|
542
|
+
const agent = await getAgentById(auth.agentId);
|
|
543
|
+
if (
|
|
544
|
+
!can({
|
|
545
|
+
principal: { kind: "agent", agentId: auth.agentId, isLead: agent?.isLead ?? false },
|
|
546
|
+
verb: "agent.act.any",
|
|
547
|
+
resource: { kind: "agent", agentId: targetAgentId },
|
|
548
|
+
source: "http",
|
|
549
|
+
}).allow
|
|
550
|
+
) {
|
|
551
|
+
jsonError(res, "Token cannot access another agent's scope", 403);
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
475
557
|
if (auth?.kind === "user" && isRbacEnabled()) {
|
|
476
558
|
const grant = await getUserGrant(auth.userId);
|
|
477
559
|
if (!grant.grantsAll) {
|
|
@@ -682,6 +764,14 @@ export async function handleCore(
|
|
|
682
764
|
return true;
|
|
683
765
|
}
|
|
684
766
|
|
|
767
|
+
const parsed = await closeRoute.parse(
|
|
768
|
+
req,
|
|
769
|
+
res,
|
|
770
|
+
getPathSegments(req.url || ""),
|
|
771
|
+
new URLSearchParams(),
|
|
772
|
+
);
|
|
773
|
+
if (!parsed) return true;
|
|
774
|
+
|
|
685
775
|
const found = await getDbClient().transaction(async () => {
|
|
686
776
|
const agent = await getAgentById(myAgentId);
|
|
687
777
|
|
|
@@ -689,10 +779,14 @@ export async function handleCore(
|
|
|
689
779
|
return false;
|
|
690
780
|
}
|
|
691
781
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
//
|
|
695
|
-
|
|
782
|
+
const runtime = runtimeInstanceId ? await getRuntimeInstanceById(runtimeInstanceId) : null;
|
|
783
|
+
if (runtime?.agentId === agent.id && runtime.poolId) {
|
|
784
|
+
// Session hooks do not end the runtime that owns their sessions.
|
|
785
|
+
if (parsed.body?.runtimeShutdown) {
|
|
786
|
+
await markRuntimeTerminated(runtime.id, "Runner shutdown");
|
|
787
|
+
}
|
|
788
|
+
} else if (multiRuntime && runtimeInstanceId) {
|
|
789
|
+
// Static runtimes retain independent close behavior.
|
|
696
790
|
await markRuntimeInstanceOffline(runtimeInstanceId, agent.id);
|
|
697
791
|
await reconcileAgentStatusFromRuntimes(agent.id);
|
|
698
792
|
} else {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage } from "node:http";
|
|
2
2
|
import { resolveHttpAuditUserId } from "../be/audit-user";
|
|
3
|
+
import { findUserById } from "../be/users";
|
|
3
4
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
4
5
|
|
|
5
6
|
export type FavoriteOwner = {
|
|
@@ -8,6 +9,19 @@ export type FavoriteOwner = {
|
|
|
8
9
|
actorId: string;
|
|
9
10
|
};
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Header the dashboard sends with the identity picked in its user picker.
|
|
14
|
+
* The dashboard authenticates with the shared operator key, so without it
|
|
15
|
+
* every person using the dashboard would share one favorite set.
|
|
16
|
+
*/
|
|
17
|
+
export const DASHBOARD_USER_HEADER = "x-swarm-user-id";
|
|
18
|
+
|
|
19
|
+
function dashboardUserIdHeader(req: IncomingMessage): string | null {
|
|
20
|
+
const raw = req.headers[DASHBOARD_USER_HEADER];
|
|
21
|
+
const value = (Array.isArray(raw) ? raw[0] : raw)?.trim();
|
|
22
|
+
return value ? value : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
11
25
|
/**
|
|
12
26
|
* Resolve the authenticated principal that owns favorite state.
|
|
13
27
|
*
|
|
@@ -16,20 +30,36 @@ export type FavoriteOwner = {
|
|
|
16
30
|
* clients authenticate with user-bound `aswt_` tokens. Agent traffic must be
|
|
17
31
|
* resolved through its owned task before the transport-level operator auth is
|
|
18
32
|
* allowed to select the dashboard's shared scope.
|
|
33
|
+
*
|
|
34
|
+
* An operator-key dashboard request names its picked user in
|
|
35
|
+
* `X-Swarm-User-Id`; an active user there owns the favorites. The operator key
|
|
36
|
+
* is fully trusted, so the header needs no further proof. A user token or a
|
|
37
|
+
* page session never honors the header. `dashboardUser: false` keeps the
|
|
38
|
+
* shared operator scope for callers whose stored state predates the header.
|
|
19
39
|
*/
|
|
20
40
|
export async function resolveHttpFavoriteOwner(
|
|
21
41
|
req: IncomingMessage,
|
|
22
42
|
callerAgentId: string | undefined,
|
|
43
|
+
opts: { dashboardUser?: boolean } = {},
|
|
23
44
|
): Promise<FavoriteOwner | null> {
|
|
24
45
|
const auth = getRequestAuth(req);
|
|
25
46
|
if (auth?.kind === "user") {
|
|
26
47
|
return { scope: `user:${auth.userId}`, userId: auth.userId, actorId: auth.userId };
|
|
27
48
|
}
|
|
28
49
|
if (auth?.kind === "operator" && !callerAgentId) {
|
|
50
|
+
const pickedUserId =
|
|
51
|
+
opts.dashboardUser === false || auth.page ? null : dashboardUserIdHeader(req);
|
|
52
|
+
if (pickedUserId) {
|
|
53
|
+
const user = await findUserById(pickedUserId);
|
|
54
|
+
if (user?.status === "active") {
|
|
55
|
+
return { scope: `user:${user.id}`, userId: user.id, actorId: user.id };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
29
58
|
return {
|
|
30
59
|
// A deployment has one configured operator key. Keep its dashboard
|
|
31
60
|
// favorites stable across key rotation; the fingerprint remains the
|
|
32
|
-
// audit actor, not the storage scope.
|
|
61
|
+
// audit actor, not the storage scope. Tabs with no picked identity
|
|
62
|
+
// land here.
|
|
33
63
|
scope: "operator",
|
|
34
64
|
userId: null,
|
|
35
65
|
actorId: auth.fingerprint,
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getSwarmConfigs } from "../be/db";
|
|
4
|
+
import { getLocalFleetStats } from "../be/fleet-benchmark-stats";
|
|
5
|
+
import {
|
|
6
|
+
compareWithFleet,
|
|
7
|
+
FLEET_MIN_FINISHED_TASKS,
|
|
8
|
+
type FleetBenchmarks,
|
|
9
|
+
fleetBucketFor,
|
|
10
|
+
type LocalFleetStats,
|
|
11
|
+
localSuccessRate,
|
|
12
|
+
localTasksPerWeek,
|
|
13
|
+
} from "../fleet-benchmark";
|
|
14
|
+
import { isTelemetryEnabled } from "../telemetry";
|
|
15
|
+
import { rejectGuest } from "./request-principal";
|
|
16
|
+
import { route } from "./route-def";
|
|
17
|
+
import { telemetryProxyEndpoint } from "./used-by";
|
|
18
|
+
|
|
19
|
+
/** The fleet changes slowly; one proxy call a day per install is plenty. */
|
|
20
|
+
const FLEET_TTL_MS = 24 * 60 * 60 * 1000;
|
|
21
|
+
/** After a failed call, wait before asking the proxy again. */
|
|
22
|
+
const FLEET_FAILURE_TTL_MS = 60 * 60 * 1000;
|
|
23
|
+
/** Local counts scan agent_tasks; the card does not need them fresher. */
|
|
24
|
+
const LOCAL_TTL_MS = 10 * 60 * 1000;
|
|
25
|
+
const TIMEOUT_MS = 10_000;
|
|
26
|
+
|
|
27
|
+
const FleetStateSchema = z.enum([
|
|
28
|
+
"ready",
|
|
29
|
+
"telemetry_off",
|
|
30
|
+
"no_org_name",
|
|
31
|
+
"not_enough_tasks",
|
|
32
|
+
"unavailable",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const FleetBenchmarkResponseSchema = z.object({
|
|
36
|
+
state: FleetStateSchema,
|
|
37
|
+
local: z
|
|
38
|
+
.object({
|
|
39
|
+
tasksPerWeek: z.number(),
|
|
40
|
+
taskSuccessRate: z.number().nullable(),
|
|
41
|
+
finishedTasks: z.number().int(),
|
|
42
|
+
agents: z.number().int(),
|
|
43
|
+
bucket: z.enum(["1", "2-5", "6+"]).nullable(),
|
|
44
|
+
})
|
|
45
|
+
.nullable(),
|
|
46
|
+
minFinishedTasks: z.number().int(),
|
|
47
|
+
generatedAt: z.string().nullable(),
|
|
48
|
+
comparisons: z.array(
|
|
49
|
+
z.object({
|
|
50
|
+
key: z.enum(["tasksPerWeek", "taskSuccessRate"]),
|
|
51
|
+
value: z.number(),
|
|
52
|
+
p50: z.number(),
|
|
53
|
+
band: z.enum(["top_10", "top_25", "above_median", "below_median", "bottom_25"]),
|
|
54
|
+
peers: z.enum(["1", "2-5", "6+", "all"]),
|
|
55
|
+
installs: z.number().int(),
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export type FleetBenchmarkResponse = z.infer<typeof FleetBenchmarkResponseSchema>;
|
|
61
|
+
|
|
62
|
+
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
const fleetBenchmark = route({
|
|
65
|
+
method: "get",
|
|
66
|
+
path: "/api/fleet-benchmark",
|
|
67
|
+
pattern: ["api", "fleet-benchmark"],
|
|
68
|
+
summary: "Compare this swarm with similar swarms",
|
|
69
|
+
description:
|
|
70
|
+
"Places this swarm's tasks per week and task success rate (last 30 days) in the fleet percentiles published by the telemetry proxy, against swarms with the same agent count when enough exist. Needs telemetry on and an organization name; otherwise `state` says what is missing and the proxy is not called. `unavailable` also covers an install whose first event the proxy saw cannot be established locally (until 30 days after the first accepted event). The fleet numbers are cached for a day.",
|
|
71
|
+
tags: ["Telemetry"],
|
|
72
|
+
responses: {
|
|
73
|
+
200: {
|
|
74
|
+
description: "Benchmark, or the reason it is not shown",
|
|
75
|
+
schema: FleetBenchmarkResponseSchema,
|
|
76
|
+
},
|
|
77
|
+
401: { description: "Unauthorized" },
|
|
78
|
+
403: { description: "Guest page sessions cannot read the benchmark" },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ─── Caches ──────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
let fleetCache: { value: FleetBenchmarks | null; until: number } | null = null;
|
|
85
|
+
let localCache: { value: LocalFleetStats | null; until: number } | null = null;
|
|
86
|
+
|
|
87
|
+
const ProxyMetricSchema = z.object({
|
|
88
|
+
n: z.number(),
|
|
89
|
+
p25: z.number().optional(),
|
|
90
|
+
p50: z.number().optional(),
|
|
91
|
+
p75: z.number().optional(),
|
|
92
|
+
p90: z.number().optional(),
|
|
93
|
+
omitted: z.string().optional(),
|
|
94
|
+
});
|
|
95
|
+
const ProxyBlockSchema = z.object({
|
|
96
|
+
installs: z.number(),
|
|
97
|
+
tasksPerWeek: ProxyMetricSchema,
|
|
98
|
+
taskSuccessRate: ProxyMetricSchema,
|
|
99
|
+
});
|
|
100
|
+
/**
|
|
101
|
+
* Everything `compareWithFleet` reads from `GET /v1/fleet/benchmarks`. A body
|
|
102
|
+
* that fails it counts as a failed call, so a malformed 200 is never cached.
|
|
103
|
+
*/
|
|
104
|
+
const ProxyFleetBenchmarksSchema = z.object({
|
|
105
|
+
window: z.string(),
|
|
106
|
+
generatedAt: z.string(),
|
|
107
|
+
minInstallsPerBucket: z.number(),
|
|
108
|
+
minFinishedTasks: z.number(),
|
|
109
|
+
overall: ProxyBlockSchema,
|
|
110
|
+
byAgentCount: z.object({
|
|
111
|
+
"1": ProxyBlockSchema.optional(),
|
|
112
|
+
"2-5": ProxyBlockSchema.optional(),
|
|
113
|
+
"6+": ProxyBlockSchema.optional(),
|
|
114
|
+
}),
|
|
115
|
+
}) satisfies z.ZodType<FleetBenchmarks>;
|
|
116
|
+
|
|
117
|
+
/** The proxy's fleet percentiles. The request carries no install ID. */
|
|
118
|
+
async function fleetBenchmarks(now: number): Promise<FleetBenchmarks | null> {
|
|
119
|
+
if (fleetCache && fleetCache.until > now) return fleetCache.value;
|
|
120
|
+
let value: FleetBenchmarks | null = null;
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetch(
|
|
123
|
+
`${await telemetryProxyEndpoint("/v1/fleet/benchmarks")}?window=30d`,
|
|
124
|
+
{
|
|
125
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
if (response.ok) {
|
|
129
|
+
const parsed = ProxyFleetBenchmarksSchema.safeParse(await response.json());
|
|
130
|
+
if (parsed.success) value = parsed.data;
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
// Unreachable proxy: the card says the comparison is unavailable.
|
|
134
|
+
}
|
|
135
|
+
fleetCache = { value, until: now + (value ? FLEET_TTL_MS : FLEET_FAILURE_TTL_MS) };
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function localStats(now: Date): Promise<LocalFleetStats | null> {
|
|
140
|
+
if (localCache && localCache.until > now.getTime()) return localCache.value;
|
|
141
|
+
const value = await getLocalFleetStats(now);
|
|
142
|
+
localCache = { value, until: now.getTime() + LOCAL_TTL_MS };
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function orgName(): Promise<string | undefined> {
|
|
147
|
+
const stored = (
|
|
148
|
+
await getSwarmConfigs({ scope: "global", key: "SWARM_ORG_NAME" })
|
|
149
|
+
)?.[0]?.value?.trim();
|
|
150
|
+
return stored || process.env.SWARM_ORG_NAME?.trim() || undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function buildFleetBenchmarkResponse(
|
|
154
|
+
now = new Date(),
|
|
155
|
+
): Promise<FleetBenchmarkResponse> {
|
|
156
|
+
const empty = {
|
|
157
|
+
local: null,
|
|
158
|
+
minFinishedTasks: FLEET_MIN_FINISHED_TASKS,
|
|
159
|
+
generatedAt: null,
|
|
160
|
+
comparisons: [],
|
|
161
|
+
};
|
|
162
|
+
// Telemetry off means no call to our proxy at all, not even an anonymous one.
|
|
163
|
+
if (!isTelemetryEnabled()) return { state: "telemetry_off", ...empty };
|
|
164
|
+
if (!(await orgName())) return { state: "no_org_name", ...empty };
|
|
165
|
+
|
|
166
|
+
const stats = await localStats(now);
|
|
167
|
+
// Where the proxy started counting this install is unknown: no comparison.
|
|
168
|
+
if (!stats) return { state: "unavailable", ...empty };
|
|
169
|
+
const finishedTasks = stats.tasksCompleted + stats.tasksFailed;
|
|
170
|
+
const local = {
|
|
171
|
+
tasksPerWeek: localTasksPerWeek(stats, now),
|
|
172
|
+
taskSuccessRate: localSuccessRate(stats),
|
|
173
|
+
finishedTasks,
|
|
174
|
+
agents: stats.agents,
|
|
175
|
+
bucket: fleetBucketFor(stats.agents),
|
|
176
|
+
};
|
|
177
|
+
if (finishedTasks < FLEET_MIN_FINISHED_TASKS)
|
|
178
|
+
return { ...empty, state: "not_enough_tasks", local };
|
|
179
|
+
|
|
180
|
+
const fleet = await fleetBenchmarks(now.getTime());
|
|
181
|
+
const comparisons = fleet ? compareWithFleet(stats, fleet, now) : [];
|
|
182
|
+
if (!fleet || comparisons.length === 0) return { ...empty, state: "unavailable", local };
|
|
183
|
+
return {
|
|
184
|
+
state: "ready",
|
|
185
|
+
local,
|
|
186
|
+
minFinishedTasks: FLEET_MIN_FINISHED_TASKS,
|
|
187
|
+
generatedAt: fleet.generatedAt,
|
|
188
|
+
comparisons,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── Handler ─────────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
export async function handleFleetBenchmark(
|
|
195
|
+
req: IncomingMessage,
|
|
196
|
+
res: ServerResponse,
|
|
197
|
+
pathSegments: string[],
|
|
198
|
+
queryParams: URLSearchParams,
|
|
199
|
+
): Promise<boolean> {
|
|
200
|
+
if (!fleetBenchmark.match(req.method, pathSegments)) return false;
|
|
201
|
+
if (rejectGuest(req, res)) return true;
|
|
202
|
+
const parsed = await fleetBenchmark.parse(req, res, pathSegments, queryParams);
|
|
203
|
+
if (!parsed) return true;
|
|
204
|
+
fleetBenchmark.respond(res, 200, await buildFleetBenchmarkResponse());
|
|
205
|
+
return true;
|
|
206
|
+
}
|
package/src/http/index.ts
CHANGED
|
@@ -25,9 +25,14 @@ import {
|
|
|
25
25
|
stopAuditGc,
|
|
26
26
|
stopAuditWriter,
|
|
27
27
|
} from "../be/rbac-audit";
|
|
28
|
+
import { startPoolRouting, stopPoolRouting } from "../be/runtime-pools/routing";
|
|
29
|
+
import { seedRuntimePools } from "../be/runtime-pools/seed";
|
|
30
|
+
import { startRuntimeSupervisor, stopRuntimeSupervisor } from "../be/runtime-pools/supervisor";
|
|
28
31
|
import { startScratchScriptGc, stopScratchScriptGc } from "../be/scripts/retention";
|
|
29
32
|
import { loadSecretRegistry } from "../be/secret-registry";
|
|
30
33
|
import { seedLegacyCapabilitiesConfig } from "../be/seed-capabilities";
|
|
34
|
+
import { initApiTelemetry } from "../be/telemetry-enable";
|
|
35
|
+
import { startWorkerNudgeListener } from "../be/worker-nudge-listener";
|
|
31
36
|
import {
|
|
32
37
|
loadEnabledExtensions,
|
|
33
38
|
setExtensionLoopbackBaseUrl,
|
|
@@ -53,7 +58,7 @@ import { attachRealtimeTransport } from "../realtime/transport";
|
|
|
53
58
|
import { startScriptRunSupervisor, stopScriptRunSupervisor } from "../script-workflows/supervisor";
|
|
54
59
|
import { getServerSessionsProcessed } from "../server-runtime-counters";
|
|
55
60
|
import { startSlackApp, stopSlackApp } from "../slack";
|
|
56
|
-
import {
|
|
61
|
+
import { telemetry } from "../telemetry";
|
|
57
62
|
import { startTelemetryTicker } from "../telemetry-snapshot";
|
|
58
63
|
import { API_DRAINING_HEADER } from "../utils/api-drain";
|
|
59
64
|
import { getApiKey } from "../utils/api-key";
|
|
@@ -80,6 +85,7 @@ import { handleEcosystem } from "./ecosystem";
|
|
|
80
85
|
import { handleEvents } from "./events";
|
|
81
86
|
import { handleExtensions } from "./extensions";
|
|
82
87
|
import { handleFavorites } from "./favorites";
|
|
88
|
+
import { handleFleetBenchmark } from "./fleet-benchmark";
|
|
83
89
|
import { handleFs } from "./fs";
|
|
84
90
|
import { handleHeartbeat } from "./heartbeat";
|
|
85
91
|
import { handleInboxState } from "./inbox-state";
|
|
@@ -113,6 +119,7 @@ import { handleRealtimeAsset, handleRealtimeTicket } from "./realtime";
|
|
|
113
119
|
import { handleRepos } from "./repos";
|
|
114
120
|
import { handleRooms } from "./rooms";
|
|
115
121
|
import { describeRequestRoute } from "./route-def";
|
|
122
|
+
import { handleRuntimePools } from "./runtime-pools";
|
|
116
123
|
import { handleSchedules } from "./schedules";
|
|
117
124
|
import { handleScriptConnectionProxy } from "./script-connection-proxy";
|
|
118
125
|
import { handleScriptConnections } from "./script-connections";
|
|
@@ -126,7 +133,9 @@ import { handleStats } from "./stats";
|
|
|
126
133
|
import { handleStatus } from "./status";
|
|
127
134
|
import { handleTaskTemplates } from "./task-templates";
|
|
128
135
|
import { handleTasks } from "./tasks";
|
|
136
|
+
import { handleTelemetryUi } from "./telemetry-ui";
|
|
129
137
|
import { handleTrackers } from "./trackers";
|
|
138
|
+
import { handleUsedBy } from "./used-by";
|
|
130
139
|
import { handleUsers } from "./users";
|
|
131
140
|
import {
|
|
132
141
|
getPathSegments,
|
|
@@ -329,7 +338,7 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
329
338
|
if (isApiDraining()) res.setHeader(API_DRAINING_HEADER, "1");
|
|
330
339
|
|
|
331
340
|
const queryParams = parseQueryParams(req.url || "");
|
|
332
|
-
|
|
341
|
+
let myAgentId = req.headers["x-agent-id"] as string | undefined;
|
|
333
342
|
|
|
334
343
|
// ── Route handlers (order matters — first match wins) ──
|
|
335
344
|
const handlers: (() => Promise<boolean>)[] = [
|
|
@@ -389,11 +398,15 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
389
398
|
() => handleApiKeys(req, res, pathSegments, queryParams),
|
|
390
399
|
() => handleHeartbeat(req, res, pathSegments),
|
|
391
400
|
() => handleEvents(req, res, pathSegments, queryParams, myAgentId),
|
|
401
|
+
() => handleRuntimePools(req, res, pathSegments, queryParams, myAgentId),
|
|
392
402
|
() => handleExtensions(req, res, pathSegments, queryParams, myAgentId),
|
|
393
403
|
() => handleFavorites(req, res, pathSegments, queryParams, myAgentId),
|
|
394
404
|
() => handleUsers(req, res, pathSegments, queryParams),
|
|
395
405
|
() => handleSessions(req, res, pathSegments, queryParams),
|
|
396
406
|
() => handleInboxState(req, res, pathSegments, queryParams),
|
|
407
|
+
() => handleTelemetryUi(req, res, pathSegments, queryParams, myAgentId),
|
|
408
|
+
() => handleUsedBy(req, res, pathSegments, queryParams, myAgentId),
|
|
409
|
+
() => handleFleetBenchmark(req, res, pathSegments, queryParams),
|
|
397
410
|
() => handleTaskTemplates(req, res, pathSegments, queryParams),
|
|
398
411
|
() => handleMcp(req, res, transports, transportActivity, mcpSessionAgents),
|
|
399
412
|
() => handleMcpUser(req, res, transportsUser, sessionUsers, transportActivityUser),
|
|
@@ -406,6 +419,7 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
406
419
|
// the async listener as an unhandled rejection and the client got a
|
|
407
420
|
// dropped connection instead of a 500.
|
|
408
421
|
if (await handleCore(req, res, myAgentId, apiKey)) return;
|
|
422
|
+
myAgentId = req.headers["x-agent-id"] as string | undefined;
|
|
409
423
|
|
|
410
424
|
for (const handler of handlers) {
|
|
411
425
|
if (await handler()) return;
|
|
@@ -433,6 +447,9 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
433
447
|
|
|
434
448
|
// Store references in globalThis for hot reload persistence
|
|
435
449
|
const detachRealtime = attachRealtimeTransport(httpServer);
|
|
450
|
+
const { attachControllerTransport } = await import("./controllers-connect");
|
|
451
|
+
const detachControllers = attachControllerTransport(httpServer);
|
|
452
|
+
const stopWorkerNudges = startWorkerNudgeListener();
|
|
436
453
|
const stopRoomDeletion = realtimeBus.subscribe("room:namespace-deleted", (namespace) => {
|
|
437
454
|
void Promise.resolve(removeNamespaceRooms(String(namespace))).catch((error) =>
|
|
438
455
|
console.error("[rooms] Deletion failed:", scrubSecrets(String(error))),
|
|
@@ -440,6 +457,8 @@ const stopRoomDeletion = realtimeBus.subscribe("room:namespace-deleted", (namesp
|
|
|
440
457
|
});
|
|
441
458
|
globalState.__closeRealtime = () => {
|
|
442
459
|
detachRealtime();
|
|
460
|
+
detachControllers();
|
|
461
|
+
stopWorkerNudges();
|
|
443
462
|
stopRoomDeletion();
|
|
444
463
|
};
|
|
445
464
|
globalState.__httpServer = httpServer;
|
|
@@ -468,6 +487,8 @@ async function shutdown() {
|
|
|
468
487
|
|
|
469
488
|
// Stop heartbeat triage
|
|
470
489
|
stopHeartbeat();
|
|
490
|
+
stopPoolRouting();
|
|
491
|
+
await stopRuntimeSupervisor();
|
|
471
492
|
|
|
472
493
|
// Stop the out-of-band queue alarm before disconnecting its Slack notifier.
|
|
473
494
|
stopQueueStallAlarm();
|
|
@@ -599,7 +620,7 @@ warnIfCorsAllowsAnyOrigin();
|
|
|
599
620
|
try {
|
|
600
621
|
const loaded = await loadSecretRegistry();
|
|
601
622
|
console.log(
|
|
602
|
-
`[secret-registry] registered config=${loaded.config} oauth=${loaded.oauth} scriptApi=${loaded.scriptApi} failed=${loaded.failed}`,
|
|
623
|
+
`[secret-registry] registered config=${loaded.config} oauth=${loaded.oauth} scriptApi=${loaded.scriptApi} runtimeController=${loaded.runtimeController} failed=${loaded.failed}`,
|
|
603
624
|
);
|
|
604
625
|
} catch (err) {
|
|
605
626
|
console.error(
|
|
@@ -608,6 +629,15 @@ try {
|
|
|
608
629
|
);
|
|
609
630
|
}
|
|
610
631
|
|
|
632
|
+
try {
|
|
633
|
+
await seedRuntimePools();
|
|
634
|
+
} catch (err) {
|
|
635
|
+
console.error(
|
|
636
|
+
"[startup] Failed to seed runtime pools:",
|
|
637
|
+
scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
611
641
|
// Upgrade seed: explicit CAPABILITIES env values that predate capability
|
|
612
642
|
// gating get the previously always-registered groups backfilled into a
|
|
613
643
|
// global swarm_config row (operator-editable; skipped when a row exists).
|
|
@@ -712,9 +742,8 @@ httpServer
|
|
|
712
742
|
const telemetrySetConfig = async (key: string, value: string) => {
|
|
713
743
|
await upsertSwarmConfig({ scope: "global", key, value });
|
|
714
744
|
};
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
});
|
|
745
|
+
// Also keeps the accessors so turning telemetry on at runtime re-inits it.
|
|
746
|
+
await initApiTelemetry(telemetryGetConfig, telemetrySetConfig);
|
|
718
747
|
telemetry.server("started", { port });
|
|
719
748
|
// Org email domain (hourly + on user changes) and the daily org.snapshot.
|
|
720
749
|
startTelemetryTicker({ getConfig: telemetryGetConfig, setConfig: telemetrySetConfig });
|
|
@@ -780,6 +809,9 @@ httpServer
|
|
|
780
809
|
startHeartbeat(heartbeatMs);
|
|
781
810
|
}
|
|
782
811
|
|
|
812
|
+
startPoolRouting();
|
|
813
|
+
startRuntimeSupervisor();
|
|
814
|
+
|
|
783
815
|
// Start OAuth token keepalive (proactive refresh to prevent expiry)
|
|
784
816
|
if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
|
|
785
817
|
const { startOAuthKeepalive } = await import("../oauth/keepalive");
|
package/src/http/mcp.ts
CHANGED
|
@@ -92,20 +92,15 @@ function forbidden(res: ServerResponse, message: string): true {
|
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
94
|
* When a request is authenticated via an aseph_ session token (kind: "agent"),
|
|
95
|
-
* the X-
|
|
96
|
-
*
|
|
97
|
-
* agent or task.
|
|
95
|
+
* the X-Source-Task-Id header must match a task-bound token.
|
|
96
|
+
* Central HTTP authentication enforces the agent and runtime headers.
|
|
98
97
|
*/
|
|
99
98
|
function validateAgentTokenIdentity(req: IncomingMessage, res: ServerResponse): true | undefined {
|
|
100
99
|
const auth = getRequestAuth(req);
|
|
101
|
-
if (!auth || auth.kind !== "agent") return undefined;
|
|
100
|
+
if (!auth || auth.kind !== "agent" || auth.taskId === null) return undefined;
|
|
102
101
|
|
|
103
|
-
const agentId = headerValue(req.headers["x-agent-id"]);
|
|
104
102
|
const taskId = headerValue(req.headers["x-source-task-id"]);
|
|
105
103
|
|
|
106
|
-
if (!agentId || agentId !== auth.agentId) {
|
|
107
|
-
return forbidden(res, "X-Agent-ID does not match session token");
|
|
108
|
-
}
|
|
109
104
|
if (!taskId || taskId !== auth.taskId) {
|
|
110
105
|
return forbidden(res, "X-Source-Task-Id does not match session token");
|
|
111
106
|
}
|