@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.
Files changed (349) hide show
  1. package/README.md +11 -2
  2. package/dist/{acp-adapter-m47zfx4a.js → acp-adapter-wnhq4ydy.js} +6 -6
  3. package/dist/{actions-ws7k3tvq.js → actions-45eyf0ns.js} +31 -29
  4. package/dist/{amp-adapter-nvv8k8j5.js → amp-adapter-f5dr6dm0.js} +6 -6
  5. package/dist/{anthropic-messages-13yn502s.js → anthropic-messages-etkbxhhy.js} +11 -11
  6. package/dist/{app-80w6qspx.js → app-qv86qhzz.js} +9 -9
  7. package/dist/{artifact-fey7dvj9.js → artifact-eczsknc3.js} +6 -3
  8. package/dist/{assistant-jtq439kw.js → assistant-10yaeebp.js} +34 -32
  9. package/dist/{azure-openai-responses-9st34pfz.js → azure-openai-responses-xap57x4b.js} +7 -7
  10. package/dist/{boot-reembed-feda8wn8.js → boot-reembed-p2wbqxm4.js} +8 -8
  11. package/dist/{boot-reembed-3nc0aaed.js → boot-reembed-wyb785et.js} +8 -8
  12. package/dist/{boot-scrub-sweep-grsa2er0.js → boot-scrub-sweep-daaqcs9t.js} +9 -9
  13. package/dist/{boot-seeds-gcfgq44f.js → boot-seeds-1pvzyx4h.js} +12 -12
  14. package/dist/{claude-adapter-pjv2q7t8.js → claude-adapter-v2py1c6g.js} +15 -15
  15. package/dist/{claude-managed-adapter-0wa8se82.js → claude-managed-adapter-kqqqhp2q.js} +2 -2
  16. package/dist/{claude-managed-setup-zxa9p9tx.js → claude-managed-setup-bnmjarjc.js} +11 -8
  17. package/dist/{claude-sdk-session-c9j6k9kx.js → claude-sdk-session-mk938tpt.js} +15 -15
  18. package/dist/cli-0p3enk35.js +188 -0
  19. package/dist/cli-1srmzpr3.js +16 -0
  20. package/dist/{cli-zzev32jw.js → cli-231av4y6.js} +6 -6
  21. package/dist/{cli-eqfbsxsn.js → cli-2aap68ns.js} +14 -11
  22. package/dist/{cli-mv2520nm.js → cli-2bw3w47j.js} +1 -1
  23. package/dist/{cli-km8559dm.js → cli-2k8gyaey.js} +18 -10
  24. package/dist/{cli-efnd2kce.js → cli-2rbt7at8.js} +6 -1
  25. package/dist/{cli-xvc3q9br.js → cli-451ks6aw.js} +1 -1
  26. package/dist/{cli-7vz49g7h.js → cli-48ve9gsj.js} +101 -4
  27. package/dist/{cli-zp38xvtm.js → cli-5rqrj7q0.js} +1 -1
  28. package/dist/{cli-km4x66j0.js → cli-5y67yrt6.js} +13 -13
  29. package/dist/{cli-3ry5gx4f.js → cli-6pxmb99j.js} +2 -2
  30. package/dist/cli-6zx22mv6.js +0 -0
  31. package/dist/{cli-zp67qg5x.js → cli-7a0sde2x.js} +72 -1
  32. package/dist/{cli-v1w7pbd8.js → cli-7jykv7ef.js} +1 -1
  33. package/dist/{cli-rfphkzh5.js → cli-7pz3b7x5.js} +24 -4
  34. package/dist/{cli-mb75wpxf.js → cli-81r1wjyv.js} +1 -1
  35. package/dist/cli-8ntwbxmm.js +1351 -0
  36. package/dist/{cli-f14fvzag.js → cli-a1b9b32n.js} +6 -1
  37. package/dist/{cli-rg1c8ccw.js → cli-amvnn9wz.js} +497 -73
  38. package/dist/{cli-fyeft04q.js → cli-apnwqe6h.js} +2 -2
  39. package/dist/{cli-m23r88re.js → cli-bkdbswyn.js} +3 -3
  40. package/dist/{cli-qg2zhjfc.js → cli-c9cnsnms.js} +8 -8
  41. package/dist/cli-cd0hv9nh.js +32 -0
  42. package/dist/{cli-wt7ccy4r.js → cli-d161g69q.js} +9 -1
  43. package/dist/{cli-rbr91whd.js → cli-d78fccnb.js} +9 -5
  44. package/dist/cli-db3m6zdt.js +362 -0
  45. package/dist/{cli-hf5s9pgf.js → cli-e3pmyt39.js} +1 -1
  46. package/dist/{cli-91r17hb1.js → cli-ehf1dfay.js} +3 -3
  47. package/dist/{cli-61tbnjpf.js → cli-f3vy7ncj.js} +767 -541
  48. package/dist/{cli-sajr098b.js → cli-f651ypy4.js} +3 -3
  49. package/dist/{cli-9k7mqz00.js → cli-ge593j7e.js} +30 -3
  50. package/dist/cli-h8w3fg83.js +17 -0
  51. package/dist/{cli-dv76vq5d.js → cli-hadkza7w.js} +685 -209
  52. package/dist/{cli-jkfxpg1b.js → cli-hrbedqp0.js} +2 -2
  53. package/dist/{cli-xvdgasgy.js → cli-hsem16hb.js} +114 -9
  54. package/dist/{cli-kp3vfknv.js → cli-j8xzym9k.js} +2 -2
  55. package/dist/{cli-swrz3mj8.js → cli-jn9wk8z8.js} +1 -1
  56. package/dist/{cli-a8vps458.js → cli-jr749y58.js} +17274 -17242
  57. package/dist/{cli-b7y8113e.js → cli-k2v5x13t.js} +1 -1
  58. package/dist/{cli-bqrs5ycj.js → cli-m4csbs67.js} +2 -2
  59. package/dist/{cli-yvxfaem2.js → cli-ndvfgmk8.js} +9 -5
  60. package/dist/{cli-r7eeddkq.js → cli-ph3145b7.js} +3 -3
  61. package/dist/cli-ppy4nnnh.js +168 -0
  62. package/dist/{cli-e5qk3m2a.js → cli-pw9x22wp.js} +19 -19
  63. package/dist/{cli-s0k2j7q9.js → cli-pwnxv8pw.js} +10 -10
  64. package/dist/{cli-jv36y8wz.js → cli-rrbctenq.js} +1 -1
  65. package/dist/{cli-vejds37t.js → cli-tb8p51kg.js} +3 -3
  66. package/dist/cli-tqzk05ty.js +832 -0
  67. package/dist/{cli-rh44xx46.js → cli-vtqrsgnm.js} +15 -15
  68. package/dist/{cli-7mhy3ths.js → cli-x61srfpp.js} +5 -5
  69. package/dist/{cli-n4z21pt8.js → cli-xy6sgy6p.js} +1 -1
  70. package/dist/{cli-w6xa5nf5.js → cli-y1j1ggbf.js} +8 -8
  71. package/dist/{cli-jvmtxqtc.js → cli-ywh2a87k.js} +7 -2
  72. package/dist/{cli-3qhyh31j.js → cli-ywm1azg9.js} +6 -0
  73. package/dist/{cli-8xjst8q2.js → cli-z9cvwx5m.js} +11 -11
  74. package/dist/{cli-ysnkvdx6.js → cli-zh93km01.js} +313 -91
  75. package/dist/{cli-z48ggfyq.js → cli-zxm9p3x4.js} +2 -2
  76. package/dist/cli.js +42 -24
  77. package/dist/{codex-adapter-0kpg09ca.js → codex-adapter-cg8vntj1.js} +13 -13
  78. package/dist/{codex-hook-295jpbfa.js → codex-hook-7ba15hjd.js} +5 -3
  79. package/dist/{codex-login-g1cyt5zm.js → codex-login-841kv5fk.js} +7 -5
  80. package/dist/{codex-session-runner-8n672hxh.js → codex-session-runner-jxs6n1qx.js} +13 -13
  81. package/dist/{commands-mhmg5n0x.js → commands-8zhezbzr.js} +10 -10
  82. package/dist/controller-hq5ggpcp.js +193 -0
  83. package/dist/controllers-connect-q0brsq11.js +265 -0
  84. package/dist/{cursor-adapter-wn8n5zzk.js → cursor-adapter-t4pr1m3c.js} +5 -5
  85. package/dist/db-d80sy99v.js +71 -0
  86. package/dist/{db-wa6s1mwv.js → db-ggc4b127.js} +8 -8
  87. package/dist/{devin-adapter-bffjj5n4.js → devin-adapter-xgtj680j.js} +2 -2
  88. package/dist/{dispatcher-nhs2pkr8.js → dispatcher-eascgc1z.js} +30 -28
  89. package/dist/docker-f503k9p0.js +201 -0
  90. package/dist/{dsh-adapter-5423p70m.js → dsh-adapter-2h8m4cab.js} +5 -5
  91. package/dist/e2b-66xra4az.js +202 -0
  92. package/dist/{e2b-nd50eccg.js → e2b-xwhyj8cw.js} +43 -527
  93. package/dist/{event-bus-vnfthafg.js → event-bus-vjtv82gm.js} +2 -2
  94. package/dist/{grok-adapter-28pkdt80.js → grok-adapter-8ram8zse.js} +7 -7
  95. package/dist/{handlers-5rtf9nng.js → handlers-hypxbg7e.js} +33 -31
  96. package/dist/heartbeat-ge72pt7q.js +124 -0
  97. package/dist/{hook-krjfkpzy.js → hook-grgtyrad.js} +20 -17
  98. package/dist/{hook-qkm73zk6.js → hook-xzwg1zwp.js} +13 -13
  99. package/dist/{http-a397xcn4.js → http-2nf2axqq.js} +1958 -205
  100. package/dist/{index-vvsq7v45.js → index-0h5dd9xg.js} +30 -28
  101. package/dist/{index-ac0v8v9d.js → index-27aez391.js} +8 -8
  102. package/dist/{index-rtxg15ah.js → index-57svck0z.js} +39 -29
  103. package/dist/{index-ytdhtdry.js → index-6zehd2p5.js} +31 -22
  104. package/dist/{index-kqre45fr.js → index-9egx96ey.js} +30 -28
  105. package/dist/{keepalive-psfh8ysx.js → keepalive-5pjbsz8f.js} +10 -10
  106. package/dist/{lead-m6rppjn7.js → lead-g19prqhd.js} +48 -46
  107. package/dist/{maintenance-41f837ke.js → maintenance-1brc7q4c.js} +8 -8
  108. package/dist/{node-8zkdq42h.js → node-c9we95xf.js} +9 -9
  109. package/dist/{oauth-refresh-sweep-6hr55hm8.js → oauth-refresh-sweep-rhe857nd.js} +9 -9
  110. package/dist/{onboard-ktrtd7cz.js → onboard-6bcg69eg.js} +25 -15
  111. package/dist/{openai-codex-responses-jzxkta9b.js → openai-codex-responses-pgvtddvv.js} +3 -3
  112. package/dist/{openai-responses-m3g43rrt.js → openai-responses-9dyzwzkr.js} +9 -9
  113. package/dist/{opencode-adapter-yjyqynzf.js → opencode-adapter-f1zby94p.js} +3 -3
  114. package/dist/{otel-impl-dq4f2k3m.js → otel-impl-y237rck3.js} +5 -5
  115. package/dist/{pi-mono-adapter-e5v9w0hv.js → pi-mono-adapter-fx6k5wwh.js} +38 -27
  116. package/dist/{pricing-refresh-kmk7j1ra.js → pricing-refresh-se1qg8wn.js} +10 -10
  117. package/dist/{rbac-roles-9w1dkkxd.js → rbac-roles-b6ca1n23.js} +11 -11
  118. package/dist/{rbac-roles-9ykx9tc5.js → rbac-roles-c58kydph.js} +10 -10
  119. package/dist/{render-v2-ycqm53np.js → render-v2-gqz7z5xt.js} +9 -9
  120. package/dist/{resume-cq2pkp8q.js → resume-yb3y3vda.js} +16 -16
  121. package/dist/{setup-0dcmdvbn.js → setup-1rbr9x3c.js} +7 -5
  122. package/dist/static-t1gr2cr7.js +30 -0
  123. package/dist/{virtual-modules-1x9k4jcs.js → virtual-modules-rdjmcg7q.js} +3 -3
  124. package/dist/{work-objects-x01x2fh8.js → work-objects-nzcp8srm.js} +35 -33
  125. package/dist/{worker-py1ssb1h.js → worker-wnz1h3g0.js} +48 -46
  126. package/dist/{x-ma4gnfgy.js → x-qfmygqqv.js} +2 -2
  127. package/openapi.json +3382 -167
  128. package/package.json +3 -3
  129. package/src/be/db/agents.ts +6 -0
  130. package/src/be/db/instance-signals.ts +30 -0
  131. package/src/be/db/tasks/read.ts +2 -0
  132. package/src/be/db/tasks/write.ts +39 -5
  133. package/src/be/db/workflow-runs.ts +22 -0
  134. package/src/be/db.ts +96 -40
  135. package/src/be/fleet-benchmark-stats.ts +39 -0
  136. package/src/be/migrations/205_favorites_per_dashboard_user.sql +29 -0
  137. package/src/be/migrations/206_runtime_pools.sql +62 -0
  138. package/src/be/migrations/207_runtime_session_tokens.sql +31 -0
  139. package/src/be/migrations/208_task_runtime_dispatch.sql +4 -0
  140. package/src/be/model-tier-resolution.ts +9 -2
  141. package/src/be/multi-runtime.ts +102 -13
  142. package/src/be/org-profile.ts +35 -0
  143. package/src/be/rbac-audit.ts +4 -0
  144. package/src/be/runtime-pools/controllers.ts +150 -0
  145. package/src/be/runtime-pools/db.ts +526 -0
  146. package/src/be/runtime-pools/demand.ts +65 -0
  147. package/src/be/runtime-pools/e2b-images.ts +109 -0
  148. package/src/be/runtime-pools/errors.ts +8 -0
  149. package/src/be/runtime-pools/lifecycle.ts +44 -0
  150. package/src/be/runtime-pools/reachability.ts +30 -0
  151. package/src/be/runtime-pools/recovery.ts +248 -0
  152. package/src/be/runtime-pools/remote-controller.ts +144 -0
  153. package/src/be/runtime-pools/routing.ts +304 -0
  154. package/src/be/runtime-pools/seed.ts +110 -0
  155. package/src/be/runtime-pools/supervisor.ts +981 -0
  156. package/src/be/runtime-pools/tokens.ts +57 -0
  157. package/src/be/scripts/typecheck.ts +48 -3
  158. package/src/be/secret-registry.ts +17 -1
  159. package/src/be/skill-usage.ts +467 -0
  160. package/src/be/swarm-config-guard.ts +59 -2
  161. package/src/be/telemetry-enable.ts +66 -0
  162. package/src/be/telemetry-ingest-anchor.ts +77 -0
  163. package/src/be/users.ts +173 -23
  164. package/src/be/worker-nudge-listener.ts +28 -0
  165. package/src/be/worker-nudges.ts +22 -0
  166. package/src/cli.tsx +22 -3
  167. package/src/commands/controller.ts +180 -0
  168. package/src/commands/onboard/non-interactive.ts +36 -12
  169. package/src/commands/onboard/types.ts +1 -1
  170. package/src/commands/onboard.tsx +3 -0
  171. package/src/commands/push-delivery.ts +230 -0
  172. package/src/commands/runner.ts +560 -276
  173. package/src/e2b/dispatch.ts +15 -1
  174. package/src/extensions/contract-types.generated.ts +1 -1
  175. package/src/fleet-benchmark.ts +157 -0
  176. package/src/heartbeat/heartbeat.ts +53 -135
  177. package/src/hooks/hook.ts +3 -2
  178. package/src/http/agents.ts +21 -0
  179. package/src/http/all-routes.ts +6 -0
  180. package/src/http/apps.ts +4 -2
  181. package/src/http/auth.ts +4 -7
  182. package/src/http/config.ts +58 -21
  183. package/src/http/controllers-connect.ts +206 -0
  184. package/src/http/core.ts +108 -14
  185. package/src/http/favorite-owner.ts +31 -1
  186. package/src/http/fleet-benchmark.ts +206 -0
  187. package/src/http/index.ts +38 -6
  188. package/src/http/mcp.ts +3 -8
  189. package/src/http/poll.ts +31 -2
  190. package/src/http/realtime.ts +1 -1
  191. package/src/http/request-principal.ts +2 -0
  192. package/src/http/runtime-pools.ts +527 -0
  193. package/src/http/session-data.ts +2 -0
  194. package/src/http/sessions.ts +100 -23
  195. package/src/http/skills.ts +173 -0
  196. package/src/http/status.ts +25 -1
  197. package/src/http/tasks.ts +14 -1
  198. package/src/http/telemetry-ui.ts +102 -0
  199. package/src/http/used-by.ts +140 -0
  200. package/src/http/utils.ts +7 -1
  201. package/src/http/workflows.ts +10 -2
  202. package/src/jira/app.ts +1 -2
  203. package/src/kv-reserved-namespaces.ts +1 -0
  204. package/src/linear/app.ts +1 -2
  205. package/src/providers/acp-target-catalog.ts +75 -1
  206. package/src/providers/acp-targets.ts +114 -0
  207. package/src/providers/amp-adapter.ts +1 -1
  208. package/src/providers/claude-adapter.ts +1 -1
  209. package/src/providers/codex-adapter.ts +5 -1
  210. package/src/providers/opencode-adapter.ts +2 -1
  211. package/src/providers/pi-mono-adapter.ts +41 -26
  212. package/src/providers/pricing-sources.md +2 -0
  213. package/src/providers/skill-invoke.ts +39 -3
  214. package/src/rbac/legacy-policy.ts +8 -0
  215. package/src/rbac/permissions.ts +16 -0
  216. package/src/rbac/types.ts +2 -0
  217. package/src/realtime/auth.ts +13 -0
  218. package/src/realtime/channels.ts +24 -0
  219. package/src/realtime/transport.ts +94 -15
  220. package/src/realtime/worker-client.ts +176 -0
  221. package/src/runtime-providers/controller-protocol.ts +97 -0
  222. package/src/runtime-providers/controller.ts +86 -0
  223. package/src/runtime-providers/docker.ts +235 -0
  224. package/src/runtime-providers/e2b.ts +217 -0
  225. package/src/runtime-providers/index.ts +37 -0
  226. package/src/runtime-providers/static.ts +26 -0
  227. package/src/runtime-providers/types.ts +92 -0
  228. package/src/runtime-providers/worker-api-url.ts +11 -0
  229. package/src/scripts-runtime/sdk-allowlist.ts +6 -0
  230. package/src/scripts-runtime/types/stdlib.d.ts +15 -0
  231. package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
  232. package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
  233. package/src/server.ts +12 -0
  234. package/src/slack/router.ts +7 -5
  235. package/src/tasks/worker-follow-up.ts +80 -52
  236. package/src/telemetry-context.ts +6 -0
  237. package/src/telemetry-contract/EVENTS.md +14 -1
  238. package/src/telemetry-contract/SOURCE.json +4 -4
  239. package/src/telemetry-contract/catalog.json +155 -0
  240. package/src/telemetry-contract/types.gen.ts +248 -4
  241. package/src/telemetry-ui-events.ts +130 -0
  242. package/src/telemetry.ts +43 -3
  243. package/src/tests/acp-adapter.test.ts +129 -0
  244. package/src/tests/claude-adapter.test.ts +6 -2
  245. package/src/tests/codex-adapter.test.ts +6 -2
  246. package/src/tests/config-internal-keys.test.ts +27 -0
  247. package/src/tests/config-session-auth.test.ts +19 -3
  248. package/src/tests/db-retention.test.ts +50 -10
  249. package/src/tests/docs-index-footprint.test.js +3 -3
  250. package/src/tests/e2b-dispatch.test.ts +28 -1
  251. package/src/tests/e2e-runtime-pools.test.ts +51 -0
  252. package/src/tests/entrypoint-acp-target-binary.test.ts +38 -1
  253. package/src/tests/favorites.test.ts +188 -0
  254. package/src/tests/fetch-resolved-env.test.ts +59 -1
  255. package/src/tests/fleet-benchmark.test.ts +453 -0
  256. package/src/tests/harness-model-support.test.ts +70 -2
  257. package/src/tests/heartbeat-orphan-resume.test.ts +24 -0
  258. package/src/tests/heartbeat-reboot-live.test.ts +31 -0
  259. package/src/tests/heartbeat-supersede-resume.test.ts +21 -0
  260. package/src/tests/hook-runtime-close.test.ts +64 -0
  261. package/src/tests/http-task-write-ownership.test.ts +8 -1
  262. package/src/tests/integration-disable-flags.test.ts +45 -0
  263. package/src/tests/migration-206-runtime-pools.test.ts +134 -0
  264. package/src/tests/migration-207-runtime-session-tokens.test.ts +45 -0
  265. package/src/tests/migration-208-task-runtime-dispatch.test.ts +30 -0
  266. package/src/tests/multi-runtime-registration.test.ts +219 -4
  267. package/src/tests/onboard-non-interactive.test.ts +104 -6
  268. package/src/tests/opencode-adapter.test.ts +6 -2
  269. package/src/tests/org-website.test.ts +115 -0
  270. package/src/tests/pi-mono-adapter.test.ts +74 -10
  271. package/src/tests/rbac-admission-e2e.test.ts +4 -4
  272. package/src/tests/rbac-engine.test.ts +8 -1
  273. package/src/tests/realtime-room-auth.test.ts +49 -0
  274. package/src/tests/repo-allow-merge-gate.test.ts +8 -1
  275. package/src/tests/runner-exit-after-tasks.test.ts +163 -0
  276. package/src/tests/runner-finish-retry.test.ts +466 -0
  277. package/src/tests/runner-harness-telemetry-env.test.ts +105 -0
  278. package/src/tests/runner-push-delivery.test.ts +670 -0
  279. package/src/tests/runner-slot-reconcile.test.ts +89 -1
  280. package/src/tests/runtime-child-token-cleanup.test.ts +172 -0
  281. package/src/tests/runtime-controller-token.test.ts +254 -0
  282. package/src/tests/runtime-demand.test.ts +119 -0
  283. package/src/tests/runtime-e2b-pools.test.ts +847 -0
  284. package/src/tests/runtime-pool-reachability.test.ts +328 -0
  285. package/src/tests/runtime-pool-routing.test.ts +817 -0
  286. package/src/tests/runtime-pools-db.test.ts +275 -0
  287. package/src/tests/runtime-pools-http.test.ts +506 -0
  288. package/src/tests/runtime-pools-ownership-guards.test.ts +235 -0
  289. package/src/tests/runtime-pools-seed.test.ts +203 -0
  290. package/src/tests/runtime-pools-test-utils.ts +128 -0
  291. package/src/tests/runtime-provider-registration.test.ts +133 -0
  292. package/src/tests/runtime-providers-docker.test.ts +400 -0
  293. package/src/tests/runtime-providers-e2b-transport.test.ts +50 -0
  294. package/src/tests/runtime-providers-e2b.test.ts +335 -0
  295. package/src/tests/runtime-recovery.test.ts +745 -0
  296. package/src/tests/runtime-remote-controller.test.ts +846 -0
  297. package/src/tests/runtime-supervisor-failures.test.ts +387 -0
  298. package/src/tests/runtime-supervisor.test.ts +1039 -0
  299. package/src/tests/runtime-token-auth.test.ts +897 -0
  300. package/src/tests/script-connections.test.ts +7 -0
  301. package/src/tests/secret-scrubber.test.ts +17 -2
  302. package/src/tests/session-costs-codex-recompute.test.ts +49 -0
  303. package/src/tests/session-tokens.test.ts +13 -3
  304. package/src/tests/skill-invoke.test.ts +142 -0
  305. package/src/tests/skill-usage-cohorts.test.ts +232 -0
  306. package/src/tests/skill-usage.test.ts +348 -0
  307. package/src/tests/slack-attachments-block.test.ts +23 -0
  308. package/src/tests/slack-render-v2.test.ts +6 -0
  309. package/src/tests/status-instance.test.ts +71 -0
  310. package/src/tests/task-supersede-resume.test.ts +87 -4
  311. package/src/tests/telemetry-enable.test.ts +113 -0
  312. package/src/tests/telemetry-ui-route.test.ts +195 -0
  313. package/src/tests/tool-annotations.test.ts +1 -1
  314. package/src/tests/used-by-route.test.ts +237 -0
  315. package/src/tests/worker-nudges.test.ts +653 -0
  316. package/src/tests/worker-realtime-client.test.ts +188 -0
  317. package/src/tests/workflow-sub-workflow.test.ts +22 -0
  318. package/src/tools/app-upsert.ts +1 -1
  319. package/src/tools/runtime-controller-list.ts +28 -0
  320. package/src/tools/runtime-pool-bind-agent.ts +29 -0
  321. package/src/tools/runtime-pool-common.ts +37 -0
  322. package/src/tools/runtime-pool-create-agent.ts +29 -0
  323. package/src/tools/runtime-pool-delete.ts +27 -0
  324. package/src/tools/runtime-pool-list.ts +27 -0
  325. package/src/tools/runtime-pool-upsert.ts +31 -0
  326. package/src/tools/script-common.ts +1 -1
  327. package/src/tools/send-task.ts +3 -2
  328. package/src/tools/tool-config.ts +6 -0
  329. package/src/types.ts +137 -2
  330. package/src/utils/config-secrets.ts +1 -0
  331. package/src/utils/lead-monitor-channels.ts +3 -0
  332. package/src/utils/org-website.ts +104 -0
  333. package/src/utils/request-auth-context.ts +10 -2
  334. package/src/utils/runtime-pools-flag.ts +11 -0
  335. package/src/utils/secret-scrubber.ts +9 -0
  336. package/src/utils/session-token-connections.ts +20 -0
  337. package/templates/skills/apps/content.md +1 -1
  338. package/templates/skills/code-quality/content.md +9 -1
  339. package/templates/skills/db-query-guidance/content.md +9 -0
  340. package/templates/skills/swarm-scripts/SKILL.md +1 -1
  341. package/templates/skills/swarm-scripts/content.md +1 -1
  342. package/templates/skills/work-on-task/SKILL.md +1 -1
  343. package/templates/skills/work-on-task/content.md +1 -1
  344. package/dist/{cli-qd97v6z2.js → cli-1qtgd1h1.js} +3 -3
  345. package/dist/{cli-cmxkbzzs.js → cli-j0w59p15.js} +6 -6
  346. package/dist/{google-generative-ai-b8zxrpax.js → google-generative-ai-pvhm0dq1.js} +1 -1
  347. package/dist/{google-vertex-nrdd697t.js → google-vertex-tek9sqe2.js} +4 -4
  348. package/dist/{mistral-conversations-059y51zt.js → mistral-conversations-rjxa9s1j.js} +3 -3
  349. package/dist/{openai-completions-663yncfq.js → openai-completions-44gb0zsk.js} +12 -12
@@ -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
  }
@@ -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
- const owner = await resolveHttpFavoriteOwner(req, myAgentId);
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
- resolveBySessionToken,
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
- if (!header?.startsWith("Bearer ")) return null;
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
- const principal = await resolveBySessionToken(bearer);
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;
@@ -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: reading unmasked secrets requires the lead agent)";
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 requestMayReadSecrets(req: IncomingMessage): Promise<boolean> {
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 true;
61
+ if (auth?.kind === "operator") return "all";
58
62
  if (auth?.kind === "agent") {
59
63
  const agent = await getAgentById(auth.agentId);
60
- return can({
61
- principal: { kind: "agent", agentId: auth.agentId, isLead: agent?.isLead ?? false },
62
- verb: "config.read.secrets",
63
- resource: { kind: "none" },
64
- source: "http",
65
- }).allow;
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 false;
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 true;
70
- if (!isRbacEnabled()) return true;
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(req: IncomingMessage, includeSecrets: boolean) {
77
- if (!includeSecrets || (await requestMayReadSecrets(req))) {
78
- return { effectiveIncludeSecrets: includeSecrets, secretsNote: "" };
79
- }
80
- return { effectiveIncludeSecrets: false, secretsNote: SECRETS_FORCE_MASK_NOTE };
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(parsed.query.agentId || undefined, parsed.query.repoId || undefined),
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
+ }