@desplega.ai/agent-swarm 1.169.0 → 1.171.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 +11 -2
- package/dist/{acp-adapter-m47zfx4a.js → acp-adapter-wnhq4ydy.js} +6 -6
- package/dist/{actions-ws7k3tvq.js → actions-45eyf0ns.js} +31 -29
- package/dist/{amp-adapter-nvv8k8j5.js → amp-adapter-f5dr6dm0.js} +6 -6
- package/dist/{anthropic-messages-13yn502s.js → anthropic-messages-etkbxhhy.js} +11 -11
- package/dist/{app-80w6qspx.js → app-qv86qhzz.js} +9 -9
- package/dist/{artifact-fey7dvj9.js → artifact-eczsknc3.js} +6 -3
- package/dist/{assistant-jtq439kw.js → assistant-10yaeebp.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-p2wbqxm4.js} +8 -8
- package/dist/{boot-reembed-3nc0aaed.js → boot-reembed-wyb785et.js} +8 -8
- package/dist/{boot-scrub-sweep-grsa2er0.js → boot-scrub-sweep-daaqcs9t.js} +9 -9
- package/dist/{boot-seeds-gcfgq44f.js → boot-seeds-1pvzyx4h.js} +12 -12
- package/dist/{claude-adapter-pjv2q7t8.js → claude-adapter-v2py1c6g.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-mk938tpt.js} +15 -15
- 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-eqfbsxsn.js → cli-2aap68ns.js} +14 -11
- package/dist/{cli-mv2520nm.js → cli-2bw3w47j.js} +1 -1
- package/dist/{cli-km8559dm.js → cli-2k8gyaey.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-7vz49g7h.js → cli-48ve9gsj.js} +101 -4
- package/dist/{cli-zp38xvtm.js → cli-5rqrj7q0.js} +1 -1
- package/dist/{cli-km4x66j0.js → cli-5y67yrt6.js} +13 -13
- package/dist/{cli-3ry5gx4f.js → cli-6pxmb99j.js} +2 -2
- 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-8ntwbxmm.js +1351 -0
- package/dist/{cli-f14fvzag.js → cli-a1b9b32n.js} +6 -1
- package/dist/{cli-rg1c8ccw.js → cli-amvnn9wz.js} +497 -73
- package/dist/{cli-fyeft04q.js → cli-apnwqe6h.js} +2 -2
- package/dist/{cli-m23r88re.js → cli-bkdbswyn.js} +3 -3
- package/dist/{cli-qg2zhjfc.js → cli-c9cnsnms.js} +8 -8
- package/dist/cli-cd0hv9nh.js +32 -0
- package/dist/{cli-wt7ccy4r.js → cli-d161g69q.js} +9 -1
- package/dist/{cli-rbr91whd.js → cli-d78fccnb.js} +9 -5
- package/dist/cli-db3m6zdt.js +362 -0
- package/dist/{cli-hf5s9pgf.js → cli-e3pmyt39.js} +1 -1
- package/dist/{cli-91r17hb1.js → cli-ehf1dfay.js} +3 -3
- package/dist/{cli-61tbnjpf.js → cli-f3vy7ncj.js} +767 -541
- package/dist/{cli-sajr098b.js → cli-f651ypy4.js} +3 -3
- package/dist/{cli-9k7mqz00.js → cli-ge593j7e.js} +30 -3
- package/dist/cli-h8w3fg83.js +17 -0
- package/dist/{cli-dv76vq5d.js → cli-hadkza7w.js} +685 -209
- package/dist/{cli-jkfxpg1b.js → cli-hrbedqp0.js} +2 -2
- package/dist/{cli-xvdgasgy.js → cli-hsem16hb.js} +114 -9
- package/dist/{cli-kp3vfknv.js → cli-j8xzym9k.js} +2 -2
- package/dist/{cli-swrz3mj8.js → cli-jn9wk8z8.js} +1 -1
- package/dist/{cli-a8vps458.js → cli-jr749y58.js} +17274 -17242
- package/dist/{cli-b7y8113e.js → cli-k2v5x13t.js} +1 -1
- package/dist/{cli-bqrs5ycj.js → cli-m4csbs67.js} +2 -2
- package/dist/{cli-yvxfaem2.js → cli-ndvfgmk8.js} +9 -5
- package/dist/{cli-r7eeddkq.js → cli-ph3145b7.js} +3 -3
- package/dist/cli-ppy4nnnh.js +168 -0
- package/dist/{cli-e5qk3m2a.js → cli-pw9x22wp.js} +19 -19
- package/dist/{cli-s0k2j7q9.js → cli-pwnxv8pw.js} +10 -10
- package/dist/{cli-jv36y8wz.js → cli-rrbctenq.js} +1 -1
- package/dist/{cli-vejds37t.js → cli-tb8p51kg.js} +3 -3
- package/dist/cli-tqzk05ty.js +832 -0
- package/dist/{cli-rh44xx46.js → cli-vtqrsgnm.js} +15 -15
- package/dist/{cli-7mhy3ths.js → cli-x61srfpp.js} +5 -5
- package/dist/{cli-n4z21pt8.js → cli-xy6sgy6p.js} +1 -1
- 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-8xjst8q2.js → cli-z9cvwx5m.js} +11 -11
- package/dist/{cli-ysnkvdx6.js → cli-zh93km01.js} +313 -91
- package/dist/{cli-z48ggfyq.js → cli-zxm9p3x4.js} +2 -2
- package/dist/cli.js +42 -24
- package/dist/{codex-adapter-0kpg09ca.js → codex-adapter-cg8vntj1.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-jxs6n1qx.js} +13 -13
- package/dist/{commands-mhmg5n0x.js → commands-8zhezbzr.js} +10 -10
- package/dist/controller-hq5ggpcp.js +193 -0
- package/dist/controllers-connect-q0brsq11.js +265 -0
- package/dist/{cursor-adapter-wn8n5zzk.js → cursor-adapter-t4pr1m3c.js} +5 -5
- package/dist/db-d80sy99v.js +71 -0
- package/dist/{db-wa6s1mwv.js → db-ggc4b127.js} +8 -8
- package/dist/{devin-adapter-bffjj5n4.js → devin-adapter-xgtj680j.js} +2 -2
- package/dist/{dispatcher-nhs2pkr8.js → dispatcher-eascgc1z.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-8ram8zse.js} +7 -7
- package/dist/{handlers-5rtf9nng.js → handlers-hypxbg7e.js} +33 -31
- package/dist/heartbeat-ge72pt7q.js +124 -0
- package/dist/{hook-krjfkpzy.js → hook-grgtyrad.js} +20 -17
- package/dist/{hook-qkm73zk6.js → hook-xzwg1zwp.js} +13 -13
- package/dist/{http-a397xcn4.js → http-2nf2axqq.js} +1958 -205
- package/dist/{index-vvsq7v45.js → index-0h5dd9xg.js} +30 -28
- package/dist/{index-ac0v8v9d.js → index-27aez391.js} +8 -8
- package/dist/{index-rtxg15ah.js → index-57svck0z.js} +39 -29
- package/dist/{index-ytdhtdry.js → index-6zehd2p5.js} +31 -22
- package/dist/{index-kqre45fr.js → index-9egx96ey.js} +30 -28
- package/dist/{keepalive-psfh8ysx.js → keepalive-5pjbsz8f.js} +10 -10
- package/dist/{lead-m6rppjn7.js → lead-g19prqhd.js} +48 -46
- package/dist/{maintenance-41f837ke.js → maintenance-1brc7q4c.js} +8 -8
- package/dist/{node-8zkdq42h.js → node-c9we95xf.js} +9 -9
- package/dist/{oauth-refresh-sweep-6hr55hm8.js → oauth-refresh-sweep-rhe857nd.js} +9 -9
- package/dist/{onboard-ktrtd7cz.js → onboard-6bcg69eg.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-y237rck3.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-se1qg8wn.js} +10 -10
- package/dist/{rbac-roles-9w1dkkxd.js → rbac-roles-b6ca1n23.js} +11 -11
- package/dist/{rbac-roles-9ykx9tc5.js → rbac-roles-c58kydph.js} +10 -10
- package/dist/{render-v2-ycqm53np.js → render-v2-gqz7z5xt.js} +9 -9
- package/dist/{resume-cq2pkp8q.js → resume-yb3y3vda.js} +16 -16
- package/dist/{setup-0dcmdvbn.js → setup-1rbr9x3c.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-nzcp8srm.js} +35 -33
- package/dist/{worker-py1ssb1h.js → worker-wnz1h3g0.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/agents.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
setRuntimeCredentialReady,
|
|
45
45
|
upsertRuntimeInstance,
|
|
46
46
|
} from "../be/multi-runtime";
|
|
47
|
+
import { getPoolForAgent, resolvePoolDelivery } from "../be/runtime-pools/db";
|
|
47
48
|
import { ACP_TARGET_IDS } from "../providers/acp-target-catalog";
|
|
48
49
|
import { reasoningCapability } from "../providers/reasoning-effort";
|
|
49
50
|
import { ALL_CAPABILITIES, getEnabledCapabilities } from "../server";
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
AgentSchema,
|
|
58
59
|
AgentStatusSchema,
|
|
59
60
|
AgentWithTasksSchema,
|
|
61
|
+
DeliveryModeSchema,
|
|
60
62
|
type ProfileSyncConflict,
|
|
61
63
|
type ProviderName,
|
|
62
64
|
ProviderNameSchema,
|
|
@@ -77,7 +79,10 @@ import {
|
|
|
77
79
|
IDENTITY_FIELD_BUDGETS,
|
|
78
80
|
IdentityFieldBudgetError,
|
|
79
81
|
} from "../utils/identity-field-budget";
|
|
82
|
+
import { isLeadChannelMonitoringEnabled } from "../utils/lead-monitor-channels";
|
|
80
83
|
import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
|
|
84
|
+
import { getRequestAuth } from "../utils/request-auth-context";
|
|
85
|
+
import { isRuntimePoolsEnabled } from "../utils/runtime-pools-flag";
|
|
81
86
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
82
87
|
import { route, runtimeInstanceHeader } from "./route-def";
|
|
83
88
|
import { agentWithCapacity, json, jsonError } from "./utils";
|
|
@@ -116,6 +121,8 @@ const AgentWithCapacityAndTasksSchema = AgentWithTasksSchema.extend({
|
|
|
116
121
|
/** POST /api/agents response: the agent row plus the server's capability flags. */
|
|
117
122
|
const RegisterAgentResponseSchema = AgentSchema.extend({
|
|
118
123
|
enabledCapabilities: z.array(CapabilitySchema),
|
|
124
|
+
deliveryMode: DeliveryModeSchema,
|
|
125
|
+
pollRequired: z.boolean().optional(),
|
|
119
126
|
});
|
|
120
127
|
|
|
121
128
|
/** Shape shared by the single- and bulk- credential-status endpoints. */
|
|
@@ -634,6 +641,14 @@ export async function handleAgentRegister(
|
|
|
634
641
|
}
|
|
635
642
|
|
|
636
643
|
const agentId = myAgentId || crypto.randomUUID();
|
|
644
|
+
const auth = getRequestAuth(req);
|
|
645
|
+
if (auth?.kind === "agent") {
|
|
646
|
+
parsed.body = { ...parsed.body, isLead: (await getAgentById(auth.agentId))?.isLead ?? false };
|
|
647
|
+
if (auth.runtimeId && parsed.body.runtimeInstanceId !== auth.runtimeId) {
|
|
648
|
+
jsonError(res, "runtimeInstanceId does not match token", 403);
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
637
652
|
// Read once so one mode applies consistently even if a config reload
|
|
638
653
|
// flips the flag mid-request.
|
|
639
654
|
const multiRuntime = isMultiRuntimeEnabled();
|
|
@@ -802,9 +817,15 @@ export async function handleAgentRegister(
|
|
|
802
817
|
// transaction right after confirming the row exists — it cannot actually
|
|
803
818
|
// be null here. (Same value as before; this only affects the compile-time
|
|
804
819
|
// type used to check the response schema.)
|
|
820
|
+
const pollRequired = result.agent!.isLead && isLeadChannelMonitoringEnabled();
|
|
805
821
|
registerAgent.respond(res, result.created ? 201 : 200, {
|
|
806
822
|
...result.agent!,
|
|
807
823
|
enabledCapabilities: getEnabledCapabilities(),
|
|
824
|
+
pollRequired,
|
|
825
|
+
deliveryMode:
|
|
826
|
+
!pollRequired && isRuntimePoolsEnabled()
|
|
827
|
+
? resolvePoolDelivery((await getPoolForAgent(agentId)) ?? { delivery: null })
|
|
828
|
+
: "poll",
|
|
808
829
|
});
|
|
809
830
|
return true;
|
|
810
831
|
}
|
package/src/http/all-routes.ts
CHANGED
|
@@ -64,10 +64,16 @@ import "./stats";
|
|
|
64
64
|
import "./status";
|
|
65
65
|
import "./tasks";
|
|
66
66
|
import "./task-templates";
|
|
67
|
+
import "./telemetry-ui";
|
|
67
68
|
import "./trackers/jira";
|
|
68
69
|
import "./trackers/linear";
|
|
70
|
+
import "./used-by";
|
|
71
|
+
import "./fleet-benchmark";
|
|
69
72
|
import "./users";
|
|
70
73
|
import "./webhooks";
|
|
71
74
|
import "./workflow-events";
|
|
72
75
|
import "./workflows";
|
|
73
76
|
import "./x";
|
|
77
|
+
|
|
78
|
+
import "./runtime-pools";
|
|
79
|
+
import "./controllers-connect";
|
package/src/http/apps.ts
CHANGED
|
@@ -1698,7 +1698,9 @@ export async function handleApps(
|
|
|
1698
1698
|
}
|
|
1699
1699
|
if (definitionNeedsRepair(res, app)) return true;
|
|
1700
1700
|
const schema = app.definition.userConfig ?? {};
|
|
1701
|
-
|
|
1701
|
+
// App settings stay in the shared operator scope for dashboard tabs; only
|
|
1702
|
+
// favorites follow the picked dashboard user.
|
|
1703
|
+
const owner = await resolveHttpFavoriteOwner(req, myAgentId, { dashboardUser: false });
|
|
1702
1704
|
const stored = owner ? await getAppUserConfigValues(app.id, owner.scope) : {};
|
|
1703
1705
|
getUserConfigRoute.respond(res, 200, { values: mergeUserConfigValues(schema, stored), schema });
|
|
1704
1706
|
return true;
|
|
@@ -1730,7 +1732,7 @@ export async function handleApps(
|
|
|
1730
1732
|
jsonError(res, "app does not define userConfig", 400);
|
|
1731
1733
|
return true;
|
|
1732
1734
|
}
|
|
1733
|
-
const owner = await resolveHttpFavoriteOwner(req, myAgentId);
|
|
1735
|
+
const owner = await resolveHttpFavoriteOwner(req, myAgentId, { dashboardUser: false });
|
|
1734
1736
|
if (!owner) {
|
|
1735
1737
|
jsonError(res, "userConfig is per-user; agents have no user scope", 403);
|
|
1736
1738
|
return true;
|
package/src/http/auth.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { getPage } from "../be/db";
|
|
|
3
3
|
import {
|
|
4
4
|
findUserById,
|
|
5
5
|
fingerprintApiKey,
|
|
6
|
-
|
|
6
|
+
resolveSessionToken,
|
|
7
7
|
resolveUserByToken,
|
|
8
8
|
} from "../be/users";
|
|
9
9
|
import type { User } from "../types";
|
|
@@ -13,8 +13,7 @@ import type { HttpRequestAuth } from "../utils/request-auth-context";
|
|
|
13
13
|
function extractBearer(req: IncomingMessage): string | null {
|
|
14
14
|
const raw = req.headers.authorization;
|
|
15
15
|
const header = Array.isArray(raw) ? raw[0] : raw;
|
|
16
|
-
|
|
17
|
-
return header.slice("Bearer ".length).trim();
|
|
16
|
+
return header?.match(/^Bearer\s+(.+)$/i)?.[1]?.trim() ?? null;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
function singleHeader(req: IncomingMessage, name: string): string | null {
|
|
@@ -26,6 +25,7 @@ function singleHeader(req: IncomingMessage, name: string): string | null {
|
|
|
26
25
|
export async function resolveHttpRequestAuth(
|
|
27
26
|
req: IncomingMessage,
|
|
28
27
|
apiKey: string | undefined,
|
|
28
|
+
options: { touchSessionToken?: boolean } = {},
|
|
29
29
|
): Promise<HttpRequestAuth | null> {
|
|
30
30
|
const bearer = extractBearer(req);
|
|
31
31
|
if (!bearer) return null;
|
|
@@ -61,10 +61,7 @@ export async function resolveHttpRequestAuth(
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
if (bearer.startsWith("aseph_")) {
|
|
64
|
-
|
|
65
|
-
if (principal) {
|
|
66
|
-
return { kind: "agent", agentId: principal.agentId, taskId: principal.taskId };
|
|
67
|
-
}
|
|
64
|
+
return await resolveSessionToken(bearer, { touch: options.touchSessionToken });
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
return null;
|
package/src/http/config.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
resetAgentMaxTasksMirror,
|
|
15
15
|
upsertSwarmConfigWithPolicyMirror,
|
|
16
16
|
} from "../be/multi-runtime";
|
|
17
|
+
import { fillOrgLogoFromWebsite } from "../be/org-profile";
|
|
17
18
|
import { getUserGrant } from "../be/rbac-roles";
|
|
18
19
|
import {
|
|
19
20
|
internalConfigKeyError,
|
|
@@ -28,11 +29,11 @@ import { getRequestAuth } from "../utils/request-auth-context";
|
|
|
28
29
|
import { registerVolatileSecret } from "../utils/secret-scrubber";
|
|
29
30
|
import { reloadGlobalConfigsAndIntegrations, scheduleIntegrationsReload } from "./core";
|
|
30
31
|
import { route } from "./route-def";
|
|
31
|
-
import { jsonError } from "./utils";
|
|
32
|
+
import { duplicateGuardedQueryKey, jsonError } from "./utils";
|
|
32
33
|
|
|
33
34
|
const MAX_ENV_PRESENCE_KEYS = 200;
|
|
34
35
|
const SECRETS_FORCE_MASK_NOTE =
|
|
35
|
-
" (secret values masked:
|
|
36
|
+
" (secret values masked: this token cannot read secrets in this scope)";
|
|
36
37
|
|
|
37
38
|
// Config keys the API server owns for its own use and must NEVER hand out over
|
|
38
39
|
// HTTP — workers/entrypoints read config to build their env, and the agent-fs
|
|
@@ -52,32 +53,47 @@ function singleHeader(req: IncomingMessage, name: string): string | undefined {
|
|
|
52
53
|
return Array.isArray(raw) ? raw[0] : raw;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
async function
|
|
56
|
+
async function requestSecretReadScope(
|
|
57
|
+
req: IncomingMessage,
|
|
58
|
+
resolvedScope?: { agentId?: string; repoId?: string },
|
|
59
|
+
): Promise<"all" | "self" | "none"> {
|
|
56
60
|
const auth = getRequestAuth(req);
|
|
57
|
-
if (auth?.kind === "operator") return
|
|
61
|
+
if (auth?.kind === "operator") return "all";
|
|
58
62
|
if (auth?.kind === "agent") {
|
|
59
63
|
const agent = await getAgentById(auth.agentId);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
if (
|
|
65
|
+
can({
|
|
66
|
+
principal: { kind: "agent", agentId: auth.agentId, isLead: agent?.isLead ?? false },
|
|
67
|
+
verb: "config.read.secrets",
|
|
68
|
+
resource: { kind: "none" },
|
|
69
|
+
source: "http",
|
|
70
|
+
}).allow
|
|
71
|
+
)
|
|
72
|
+
return "all";
|
|
73
|
+
if (!auth.runtimeId || resolvedScope?.agentId !== auth.agentId) return "none";
|
|
74
|
+
return "self";
|
|
66
75
|
}
|
|
67
|
-
if (auth?.kind === "guest") return
|
|
76
|
+
if (auth?.kind === "guest") return "none";
|
|
68
77
|
// Preserve legacy reads without a user or session-token principal.
|
|
69
|
-
if (auth?.kind !== "user") return
|
|
70
|
-
if (!isRbacEnabled()) return
|
|
78
|
+
if (auth?.kind !== "user") return "all";
|
|
79
|
+
if (!isRbacEnabled()) return "all";
|
|
71
80
|
|
|
72
81
|
const grant = await getUserGrant(auth.userId);
|
|
73
|
-
return grant.grantsAll || grant.verbs.has("config.read.secrets");
|
|
82
|
+
return grant.grantsAll || grant.verbs.has("config.read.secrets") ? "all" : "none";
|
|
74
83
|
}
|
|
75
84
|
|
|
76
|
-
async function resolveSecretsRead(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
85
|
+
async function resolveSecretsRead(
|
|
86
|
+
req: IncomingMessage,
|
|
87
|
+
includeSecrets: boolean,
|
|
88
|
+
resolvedScope?: { agentId?: string; repoId?: string },
|
|
89
|
+
) {
|
|
90
|
+
const scope = await requestSecretReadScope(req, resolvedScope);
|
|
91
|
+
const auth = getRequestAuth(req);
|
|
92
|
+
return {
|
|
93
|
+
effectiveIncludeSecrets: includeSecrets && scope !== "none",
|
|
94
|
+
includeRepoSecrets: !(auth?.kind === "agent" && auth.runtimeId && scope !== "all"),
|
|
95
|
+
secretsNote: includeSecrets && scope === "none" ? SECRETS_FORCE_MASK_NOTE : "",
|
|
96
|
+
};
|
|
81
97
|
}
|
|
82
98
|
|
|
83
99
|
/**
|
|
@@ -293,16 +309,34 @@ export async function handleConfig(
|
|
|
293
309
|
pathSegments: string[],
|
|
294
310
|
queryParams: URLSearchParams,
|
|
295
311
|
): Promise<boolean> {
|
|
312
|
+
if (getRequestAuth(req)?.kind === "agent") {
|
|
313
|
+
const duplicate = duplicateGuardedQueryKey(queryParams);
|
|
314
|
+
if (duplicate) {
|
|
315
|
+
jsonError(res, `Duplicate query parameter: ${duplicate}`, 400);
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
296
319
|
if (getResolvedConfigRoute.match(req.method, pathSegments)) {
|
|
297
320
|
const parsed = await getResolvedConfigRoute.parse(req, res, pathSegments, queryParams);
|
|
298
321
|
if (!parsed) return true;
|
|
299
322
|
const includeSecrets = parsed.query.includeSecrets === "true";
|
|
300
|
-
const { effectiveIncludeSecrets, secretsNote } = await resolveSecretsRead(req, includeSecrets);
|
|
301
323
|
const key = parsed.query.key;
|
|
324
|
+
const auth = getRequestAuth(req);
|
|
325
|
+
const resolvedScope = {
|
|
326
|
+
agentId:
|
|
327
|
+
parsed.query.agentId ||
|
|
328
|
+
(auth?.kind === "agent" && auth.runtimeId ? auth.agentId : undefined),
|
|
329
|
+
repoId: parsed.query.repoId || undefined,
|
|
330
|
+
};
|
|
331
|
+
const { effectiveIncludeSecrets, includeRepoSecrets, secretsNote } = await resolveSecretsRead(
|
|
332
|
+
req,
|
|
333
|
+
includeSecrets,
|
|
334
|
+
resolvedScope,
|
|
335
|
+
);
|
|
302
336
|
// Filter by key before masking and before any secret is registered, so a
|
|
303
337
|
// caller asking for one key never receives the rest (matches get-config).
|
|
304
338
|
const configs = stripApiOnlyKeys(
|
|
305
|
-
await getResolvedConfig(
|
|
339
|
+
await getResolvedConfig(resolvedScope.agentId, resolvedScope.repoId, { includeRepoSecrets }),
|
|
306
340
|
).filter((c) => !key || c.key === key);
|
|
307
341
|
const result = effectiveIncludeSecrets ? configs : maskSecrets(configs);
|
|
308
342
|
if (effectiveIncludeSecrets) {
|
|
@@ -451,6 +485,9 @@ export async function handleConfig(
|
|
|
451
485
|
// (CLI, automation, dashboard) don't have to remember to hit /reload.
|
|
452
486
|
// Debounced to coalesce sequential single-key upserts from the dashboard
|
|
453
487
|
// batch save (no bulk endpoint exists).
|
|
488
|
+
if (scope === "global" && key === "SWARM_ORG_WEBSITE") {
|
|
489
|
+
await fillOrgLogoFromWebsite(String(value));
|
|
490
|
+
}
|
|
454
491
|
if (scope === "global") {
|
|
455
492
|
scheduleIntegrationsReload();
|
|
456
493
|
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { IncomingMessage, Server } from "node:http";
|
|
2
|
+
import type { Duplex } from "node:stream";
|
|
3
|
+
import { type WebSocket, WebSocketServer } from "ws";
|
|
4
|
+
import { getDbClient } from "../be/db";
|
|
5
|
+
import { ensureRuntimeOwnerId, reconcileRemoteProvisions } from "../be/runtime-pools/controllers";
|
|
6
|
+
import { getRuntimeController } from "../be/runtime-pools/db";
|
|
7
|
+
import {
|
|
8
|
+
installRemoteController,
|
|
9
|
+
RemoteController,
|
|
10
|
+
remoteControllerSession,
|
|
11
|
+
removeRemoteController,
|
|
12
|
+
} from "../be/runtime-pools/remote-controller";
|
|
13
|
+
import { reconcileControllerOrphans } from "../be/runtime-pools/supervisor";
|
|
14
|
+
import {
|
|
15
|
+
CONTROLLER_PING_INTERVAL_MS,
|
|
16
|
+
CONTROLLER_PROTOCOL_VERSION,
|
|
17
|
+
ControllerClientMessageSchema,
|
|
18
|
+
} from "../runtime-providers/controller-protocol";
|
|
19
|
+
import { getApiKey } from "../utils/api-key";
|
|
20
|
+
import type { HttpRequestAuth } from "../utils/request-auth-context";
|
|
21
|
+
import { isRuntimePoolsEnabled } from "../utils/runtime-pools-flag";
|
|
22
|
+
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
23
|
+
import { registerSessionTokenConnection } from "../utils/session-token-connections";
|
|
24
|
+
import { resolveHttpRequestAuth } from "./auth";
|
|
25
|
+
import { route } from "./route-def";
|
|
26
|
+
|
|
27
|
+
const connectRoute = route({
|
|
28
|
+
method: "get",
|
|
29
|
+
path: "/api/controllers/connect",
|
|
30
|
+
pattern: ["api", "controllers", "connect"],
|
|
31
|
+
summary: "Connect a remote runtime controller",
|
|
32
|
+
description:
|
|
33
|
+
"Requires a controller token and a WebSocket upgrade. Accepts one session per controller.",
|
|
34
|
+
tags: ["Runtime pools"],
|
|
35
|
+
responses: {
|
|
36
|
+
101: { description: "Controller socket connected" },
|
|
37
|
+
401: { description: "Controller token required" },
|
|
38
|
+
404: { description: "Runtime pools disabled" },
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
type ControllerAuth = Extract<HttpRequestAuth, { kind: "controller" }>;
|
|
43
|
+
class ControllerAuthError extends Error {}
|
|
44
|
+
|
|
45
|
+
async function reconcileHandles(controller: RemoteController): Promise<void> {
|
|
46
|
+
const ownerId = await ensureRuntimeOwnerId();
|
|
47
|
+
const handles = await controller.list(ownerId);
|
|
48
|
+
await reconcileRemoteProvisions(controller.id, handles, ownerId);
|
|
49
|
+
await reconcileControllerOrphans(controller.id, controller, ownerId, new Date(), handles);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function attachControllerTransport(
|
|
53
|
+
server: Server,
|
|
54
|
+
options: { commandTimeoutMs?: number; provisionTimeoutMs?: number } = {},
|
|
55
|
+
): () => void {
|
|
56
|
+
const wss = new WebSocketServer({ noServer: true, maxPayload: 1024 * 1024 });
|
|
57
|
+
let closing = false;
|
|
58
|
+
const onUpgrade = (req: IncomingMessage, socket: Duplex, head: Buffer) => {
|
|
59
|
+
if (!connectRoute.match(req.method, (req.url?.split("?")[0] ?? "").split("/").filter(Boolean)))
|
|
60
|
+
return;
|
|
61
|
+
if (closing || !isRuntimePoolsEnabled()) {
|
|
62
|
+
socket.end("HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
void resolveHttpRequestAuth(req, getApiKey(), { touchSessionToken: false })
|
|
66
|
+
.then((auth) => {
|
|
67
|
+
if (auth?.kind !== "controller") throw new Error("Controller token required");
|
|
68
|
+
if (closing || socket.destroyed) return;
|
|
69
|
+
wss.handleUpgrade(req, socket, head, (ws) => connect(ws, req, auth));
|
|
70
|
+
})
|
|
71
|
+
.catch(() => socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n"));
|
|
72
|
+
};
|
|
73
|
+
server.on("upgrade", onUpgrade);
|
|
74
|
+
|
|
75
|
+
function connect(ws: WebSocket, req: IncomingMessage, auth: ControllerAuth): void {
|
|
76
|
+
let session: RemoteController | undefined;
|
|
77
|
+
let closed = false;
|
|
78
|
+
let helloReceived = false;
|
|
79
|
+
let online = false;
|
|
80
|
+
let alive = true;
|
|
81
|
+
const unregister = registerSessionTokenConnection(auth.tokenId, () =>
|
|
82
|
+
ws.close(1008, "Controller token revoked"),
|
|
83
|
+
);
|
|
84
|
+
const reportError = (error: unknown) =>
|
|
85
|
+
console.error("[runtime-controller]", scrubSecrets(String(error)));
|
|
86
|
+
const closeOnError = (error: unknown) => {
|
|
87
|
+
const detail = scrubSecrets(String(error));
|
|
88
|
+
void getDbClient()
|
|
89
|
+
.run(
|
|
90
|
+
"UPDATE runtime_controllers SET last_error = ?, last_error_at = ?, status = CASE WHEN status = 'online' THEN status ELSE 'offline' END WHERE id = ?",
|
|
91
|
+
[detail, new Date().toISOString(), auth.controllerId],
|
|
92
|
+
)
|
|
93
|
+
.catch(reportError)
|
|
94
|
+
.finally(() =>
|
|
95
|
+
ws.close(
|
|
96
|
+
error instanceof ControllerAuthError ? 1008 : 1011,
|
|
97
|
+
error instanceof ControllerAuthError
|
|
98
|
+
? "Controller access rejected"
|
|
99
|
+
: "controller operation failed",
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
const checkAuth = async () => {
|
|
104
|
+
const current = await resolveHttpRequestAuth(req, getApiKey(), { touchSessionToken: false });
|
|
105
|
+
if (current?.kind !== "controller" || current.tokenId !== auth.tokenId)
|
|
106
|
+
throw new ControllerAuthError("Controller token revoked or expired");
|
|
107
|
+
if (!isRuntimePoolsEnabled()) throw new Error("Runtime pools disabled");
|
|
108
|
+
};
|
|
109
|
+
const hello = async (
|
|
110
|
+
message: Extract<ReturnType<typeof ControllerClientMessageSchema.parse>, { type: "hello" }>,
|
|
111
|
+
) => {
|
|
112
|
+
await checkAuth();
|
|
113
|
+
const row = await getRuntimeController(auth.controllerId);
|
|
114
|
+
if (!row || message.controllerId !== auth.controllerId || message.kind !== row.kind) {
|
|
115
|
+
throw new ControllerAuthError("Controller identity or kind does not match");
|
|
116
|
+
}
|
|
117
|
+
if (closed) return;
|
|
118
|
+
await getDbClient().run("UPDATE runtime_controllers SET status = 'unknown' WHERE id = ?", [
|
|
119
|
+
row.id,
|
|
120
|
+
]);
|
|
121
|
+
if (closed) return;
|
|
122
|
+
session = new RemoteController(
|
|
123
|
+
row.id,
|
|
124
|
+
ws,
|
|
125
|
+
message.traits,
|
|
126
|
+
row.config.WORKER_API_URL,
|
|
127
|
+
options,
|
|
128
|
+
);
|
|
129
|
+
installRemoteController(session);
|
|
130
|
+
await reconcileHandles(session);
|
|
131
|
+
await checkAuth();
|
|
132
|
+
if (closed || remoteControllerSession(row.id) !== session) return;
|
|
133
|
+
await getDbClient().run(
|
|
134
|
+
"UPDATE runtime_controllers SET status = 'online', last_seen_at = ?, updated_at = ?, last_error = NULL, last_error_at = NULL WHERE id = ?",
|
|
135
|
+
[new Date().toISOString(), new Date().toISOString(), row.id],
|
|
136
|
+
);
|
|
137
|
+
online = true;
|
|
138
|
+
};
|
|
139
|
+
ws.on("message", (data) => {
|
|
140
|
+
try {
|
|
141
|
+
const message = ControllerClientMessageSchema.parse(JSON.parse(data.toString()));
|
|
142
|
+
if (message.type === "result") {
|
|
143
|
+
session?.result(message);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (helloReceived) throw new Error("Controller already sent hello");
|
|
147
|
+
helloReceived = true;
|
|
148
|
+
void hello(message).catch(closeOnError);
|
|
149
|
+
} catch {
|
|
150
|
+
ws.close(1002, "Invalid controller message");
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
ws.on("pong", () => {
|
|
154
|
+
alive = true;
|
|
155
|
+
});
|
|
156
|
+
ws.on("error", () => ws.close(1011, "Controller connection failed"));
|
|
157
|
+
const timer = setInterval(() => {
|
|
158
|
+
if (!alive || !helloReceived) {
|
|
159
|
+
ws.terminate();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
alive = false;
|
|
163
|
+
ws.ping();
|
|
164
|
+
void (async () => {
|
|
165
|
+
await checkAuth();
|
|
166
|
+
if (online && session && remoteControllerSession(auth.controllerId) === session)
|
|
167
|
+
await getDbClient().run("UPDATE runtime_controllers SET last_seen_at = ? WHERE id = ?", [
|
|
168
|
+
new Date().toISOString(),
|
|
169
|
+
auth.controllerId,
|
|
170
|
+
]);
|
|
171
|
+
})().catch(closeOnError);
|
|
172
|
+
}, CONTROLLER_PING_INTERVAL_MS);
|
|
173
|
+
timer.unref();
|
|
174
|
+
ws.on("close", () => {
|
|
175
|
+
closed = true;
|
|
176
|
+
clearInterval(timer);
|
|
177
|
+
unregister();
|
|
178
|
+
session?.disconnect("Controller disconnected");
|
|
179
|
+
if (!session || !removeRemoteController(session)) return;
|
|
180
|
+
const disconnectedAt = new Date().toISOString();
|
|
181
|
+
void getDbClient()
|
|
182
|
+
.run(
|
|
183
|
+
online
|
|
184
|
+
? "UPDATE runtime_controllers SET status = 'offline', updated_at = ?, last_seen_at = ? WHERE id = ?"
|
|
185
|
+
: "UPDATE runtime_controllers SET status = 'offline' WHERE id = ?",
|
|
186
|
+
online ? [disconnectedAt, disconnectedAt, auth.controllerId] : [auth.controllerId],
|
|
187
|
+
)
|
|
188
|
+
.catch(reportError);
|
|
189
|
+
});
|
|
190
|
+
ws.send(
|
|
191
|
+
JSON.stringify({
|
|
192
|
+
type: "welcome",
|
|
193
|
+
controllerId: auth.controllerId,
|
|
194
|
+
version: CONTROLLER_PROTOCOL_VERSION,
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
void checkAuth().catch(closeOnError);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return () => {
|
|
201
|
+
closing = true;
|
|
202
|
+
server.off("upgrade", onUpgrade);
|
|
203
|
+
for (const ws of wss.clients) ws.terminate();
|
|
204
|
+
wss.close();
|
|
205
|
+
};
|
|
206
|
+
}
|