@stage-labs/metro 0.1.0-beta.4 → 0.1.0-beta.41

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 (249) hide show
  1. package/dist/api.js +131 -0
  2. package/dist/bedrock-eventstream.js +120 -0
  3. package/dist/bedrock-proxy.js +260 -0
  4. package/dist/bedrock-upstream.js +72 -0
  5. package/dist/bedrock.js +106 -0
  6. package/dist/claude.js +15 -0
  7. package/dist/cli.js +181 -207
  8. package/dist/control.js +173 -0
  9. package/dist/plugin.js +82 -0
  10. package/dist/prompt.js +61 -0
  11. package/dist/runtime.js +117 -0
  12. package/dist/serve.js +77 -0
  13. package/dist/store.js +93 -0
  14. package/dist/tail.js +120 -0
  15. package/dist/update.js +33 -0
  16. package/dist/version.js +97 -0
  17. package/package.json +34 -21
  18. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  19. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +119 -0
  20. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  21. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  22. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  23. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +210 -0
  24. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  25. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  26. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  27. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  28. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  29. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  30. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  31. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  32. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  33. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  34. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  35. package/runtime/node_modules/@metro-labs/mcp/package.json +67 -0
  36. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  37. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +367 -0
  38. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  39. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  40. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +334 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-connector-api.ts +150 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-import.ts +76 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-pair.ts +20 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +153 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +133 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +278 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +383 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +143 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +33 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +315 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +100 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +57 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +333 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +117 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/hosted-connectors.ts +118 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +369 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +86 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/import-api.ts +67 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-connectors-api.ts +56 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +187 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-secret.ts +21 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +31 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +162 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +120 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +58 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +145 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +214 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +224 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/daemon/relay.ts +303 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +139 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +105 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +27 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +63 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-config.ts +27 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +218 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-auth.ts +82 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-nonces.ts +43 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-routes.ts +89 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +320 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +257 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +295 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +91 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +214 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-connectors.ts +143 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +37 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +147 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-relay.ts +126 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +326 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +319 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +152 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +373 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +309 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +24 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +105 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +118 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +45 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +72 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +18 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +141 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +203 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +192 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +73 -0
  133. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  134. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  135. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +211 -0
  136. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  137. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +212 -0
  138. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +397 -0
  139. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  140. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +240 -0
  141. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  142. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +80 -0
  143. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  144. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  145. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  146. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  147. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +182 -0
  148. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  149. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  150. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  151. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  152. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  153. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  154. package/runtime/node_modules/@metro-labs/telegram/package.json +28 -0
  155. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +81 -0
  156. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  157. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  158. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  159. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +141 -0
  160. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  161. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  162. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  163. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  164. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  165. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  166. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  167. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  168. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  169. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  170. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  171. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  172. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  173. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  174. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  175. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  176. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +172 -0
  177. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
  178. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  179. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  180. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  181. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +48 -0
  182. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  183. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  184. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  185. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +40 -0
  186. package/runtime/node_modules/@metro-labs/whatsapp/package.json +29 -0
  187. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +67 -0
  188. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  189. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  190. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  191. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  192. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +318 -0
  193. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  194. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +132 -0
  195. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  196. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  197. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  198. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  199. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  200. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  201. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  202. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  203. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +141 -0
  204. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  205. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  206. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  208. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  209. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +113 -0
  210. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  211. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  212. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  213. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  214. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  215. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  216. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  217. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  218. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  219. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +333 -0
  220. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +78 -0
  221. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  222. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  223. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  224. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  225. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  226. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  227. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  228. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +39 -0
  234. package/runtime/runtime.json +3 -0
  235. package/runtime/trains/.keep +0 -0
  236. package/LICENSE +0 -21
  237. package/README.md +0 -117
  238. package/dist/agents/claude.js +0 -207
  239. package/dist/agents/codex.js +0 -207
  240. package/dist/agents/types.js +0 -2
  241. package/dist/channels/discord.js +0 -104
  242. package/dist/channels/telegram.js +0 -189
  243. package/dist/helpers/scope-cache.js +0 -65
  244. package/dist/helpers/streaming.js +0 -209
  245. package/dist/helpers/telegram-format.js +0 -39
  246. package/dist/helpers/turn.js +0 -40
  247. package/dist/log.js +0 -10
  248. package/dist/orchestrator.js +0 -208
  249. package/dist/paths.js +0 -68
@@ -0,0 +1,303 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { RelayTarget } from '../db/connector-relay.js';
3
+ import { agentIdentity, assertLease, type Fence } from './api-http.js';
4
+ import { ApiError } from './api-error.js';
5
+ import { errMsg, log } from './log.js';
6
+
7
+ export interface RelayApiDeps {
8
+ target: (
9
+ agentId: string,
10
+ connectorId: string,
11
+ force: boolean,
12
+ ) => Promise<RelayTarget>;
13
+ fence: Fence;
14
+ }
15
+
16
+ const ID_PATH_RE = /^\/relay\/([A-Za-z0-9][A-Za-z0-9_-]{10})$/;
17
+ const METHODS = new Set(['POST', 'GET', 'DELETE']);
18
+ const RELAY_BODY_MAX = 8 * 1024 * 1024;
19
+ const FIRST_BYTE_MS = 120_000;
20
+ const PASS_REQ = [
21
+ 'accept',
22
+ 'content-type',
23
+ 'mcp-session-id',
24
+ 'mcp-protocol-version',
25
+ 'last-event-id',
26
+ ] as const;
27
+ const PASS_RES = [
28
+ 'content-type',
29
+ 'mcp-session-id',
30
+ 'mcp-protocol-version',
31
+ 'cache-control',
32
+ ] as const;
33
+
34
+ const keepaliveMs = (): number =>
35
+ Number(process.env.METRO_RELAY_KEEPALIVE_MS) || 25_000;
36
+
37
+ const reconnectUrl = (id: string): string =>
38
+ `https://metro.box/#/connector/${id}`;
39
+
40
+ function answer(
41
+ res: ServerResponse,
42
+ status: number,
43
+ body: Record<string, unknown>,
44
+ ): void {
45
+ if (res.headersSent) return;
46
+ res.writeHead(status, { 'content-type': 'application/json' });
47
+ res.end(JSON.stringify(body));
48
+ }
49
+
50
+ const signinAnswer = (res: ServerResponse, id: string): void => {
51
+ answer(res, 424, {
52
+ error: 'this connector needs signing in again',
53
+ reconnect: reconnectUrl(id),
54
+ });
55
+ };
56
+
57
+ function closeIfBodyUnread(req: IncomingMessage, res: ServerResponse): void {
58
+ if (req.readableEnded) return;
59
+ const drop = (): void => {
60
+ req.socket?.destroy();
61
+ };
62
+ if (res.writableFinished) drop();
63
+ else res.once('finish', drop);
64
+ }
65
+
66
+ async function readCapped(req: IncomingMessage): Promise<Uint8Array<ArrayBuffer>> {
67
+ const declared = Number(req.headers['content-length']);
68
+ if (Number.isFinite(declared) && declared > RELAY_BODY_MAX)
69
+ throw new ApiError('relay body exceeds 8 MiB', 413);
70
+ const chunks: Buffer[] = [];
71
+ let total = 0;
72
+ for await (const chunk of req) {
73
+ const buf = chunk as Buffer;
74
+ total += buf.length;
75
+ if (total > RELAY_BODY_MAX)
76
+ throw new ApiError('relay body exceeds 8 MiB', 413);
77
+ chunks.push(buf);
78
+ }
79
+ const merged = Buffer.concat(chunks);
80
+ const out = new Uint8Array(new ArrayBuffer(merged.byteLength));
81
+ out.set(merged);
82
+ return out;
83
+ }
84
+
85
+ function upstreamHeaders(
86
+ req: IncomingMessage,
87
+ injected: Record<string, string>,
88
+ ): Record<string, string> {
89
+ const out: Record<string, string> = {};
90
+ for (const name of PASS_REQ) {
91
+ const value = req.headers[name];
92
+ if (typeof value === 'string') out[name] = value;
93
+ }
94
+ return { ...out, ...injected };
95
+ }
96
+
97
+ async function forward(
98
+ req: IncomingMessage,
99
+ target: { url: string; headers: Record<string, string> },
100
+ body: Uint8Array<ArrayBuffer> | null,
101
+ signal: AbortSignal,
102
+ ): Promise<Response> {
103
+ const firstByte = setTimeout(() => {
104
+ log.warn({ url: target.url }, 'relay: upstream sent no response in time');
105
+ }, FIRST_BYTE_MS);
106
+ firstByte.unref?.();
107
+ try {
108
+ return await fetch(target.url, {
109
+ method: req.method,
110
+ headers: upstreamHeaders(req, target.headers),
111
+ ...(body === null ? {} : { body }),
112
+ signal,
113
+ redirect: 'manual',
114
+ });
115
+ } finally {
116
+ clearTimeout(firstByte);
117
+ }
118
+ }
119
+
120
+ interface Keepalive {
121
+ touch: () => void;
122
+ stop: () => void;
123
+ }
124
+
125
+ function startKeepalive(res: ServerResponse): Keepalive {
126
+ let timer: ReturnType<typeof setTimeout> | undefined;
127
+ const arm = (): void => {
128
+ timer = setTimeout(() => {
129
+ try {
130
+ res.write(': keepalive\n\n');
131
+ } catch {
132
+ log.debug('relay: keepalive write failed');
133
+ }
134
+ arm();
135
+ }, keepaliveMs());
136
+ timer.unref?.();
137
+ };
138
+ arm();
139
+ return {
140
+ touch: (): void => {
141
+ if (timer) clearTimeout(timer);
142
+ arm();
143
+ },
144
+ stop: (): void => {
145
+ if (timer) clearTimeout(timer);
146
+ },
147
+ };
148
+ }
149
+
150
+ async function pumpBody(
151
+ res: ServerResponse,
152
+ body: ReadableStream<Uint8Array>,
153
+ touch: () => void,
154
+ ): Promise<void> {
155
+ const reader = body.getReader();
156
+ for (;;) {
157
+ const { done, value } = await reader.read();
158
+ if (done) return;
159
+ touch();
160
+ const ok = res.write(value);
161
+ if (!ok)
162
+ await new Promise((resolve) => {
163
+ res.once('drain', resolve);
164
+ });
165
+ }
166
+ }
167
+
168
+ async function pipe(res: ServerResponse, upstream: Response): Promise<void> {
169
+ const headers: Record<string, string> = {};
170
+ for (const name of PASS_RES) {
171
+ const value = upstream.headers.get(name);
172
+ if (value !== null) headers[name] = value;
173
+ }
174
+ const sse = (headers['content-type'] ?? '').includes('text/event-stream');
175
+ if (sse) headers['x-accel-buffering'] = 'no';
176
+ res.writeHead(upstream.status, headers);
177
+ if (upstream.body === null) {
178
+ res.end();
179
+ return;
180
+ }
181
+ const keepalive = sse ? startKeepalive(res) : null;
182
+ try {
183
+ await pumpBody(res, upstream.body, () => keepalive?.touch());
184
+ } finally {
185
+ keepalive?.stop();
186
+ }
187
+ res.end();
188
+ }
189
+
190
+ const authFailed = (status: number): boolean =>
191
+ status === 401 || status === 403;
192
+
193
+ type Exchanged =
194
+ | { kind: 'response'; upstream: Response }
195
+ | { kind: 'missing' }
196
+ | { kind: 'signin' };
197
+
198
+ async function exchange(
199
+ req: IncomingMessage,
200
+ agentId: string,
201
+ connectorId: string,
202
+ deps: RelayApiDeps,
203
+ body: Uint8Array<ArrayBuffer> | null,
204
+ signal: AbortSignal,
205
+ ): Promise<Exchanged> {
206
+ const target = await deps.target(agentId, connectorId, false);
207
+ if (target.kind !== 'ok') return { kind: target.kind };
208
+ let upstream = await forward(req, target, body, signal);
209
+ if (!authFailed(upstream.status)) return { kind: 'response', upstream };
210
+ await upstream.body?.cancel();
211
+ const fresh = await deps.target(agentId, connectorId, true);
212
+ if (fresh.kind !== 'ok') return { kind: 'signin' };
213
+ upstream = await forward(req, fresh, body, signal);
214
+ if (!authFailed(upstream.status)) return { kind: 'response', upstream };
215
+ await upstream.body?.cancel();
216
+ return { kind: 'signin' };
217
+ }
218
+
219
+ async function relayExchange(
220
+ req: IncomingMessage,
221
+ res: ServerResponse,
222
+ agentId: string,
223
+ connectorId: string,
224
+ deps: RelayApiDeps,
225
+ ): Promise<void> {
226
+ const control = new AbortController();
227
+ const bail = (): void => {
228
+ if (!res.writableEnded) control.abort();
229
+ };
230
+ res.once('close', bail);
231
+ res.socket?.once('close', bail);
232
+ res.once('finish', () => {
233
+ res.socket?.removeListener('close', bail);
234
+ });
235
+ const body = req.method === 'POST' ? await readCapped(req) : null;
236
+ const out = await exchange(
237
+ req,
238
+ agentId,
239
+ connectorId,
240
+ deps,
241
+ body,
242
+ control.signal,
243
+ );
244
+ if (out.kind === 'missing') {
245
+ answer(res, 404, { error: 'no such connector' });
246
+ return;
247
+ }
248
+ if (out.kind === 'signin') {
249
+ signinAnswer(res, connectorId);
250
+ return;
251
+ }
252
+ if (out.upstream.status >= 300 && out.upstream.status < 400) {
253
+ await out.upstream.body?.cancel();
254
+ answer(res, 502, { error: 'the connector redirected; metro does not follow' });
255
+ return;
256
+ }
257
+ await pipe(res, out.upstream);
258
+ }
259
+
260
+ function dispatch(
261
+ req: IncomingMessage,
262
+ res: ServerResponse,
263
+ connectorId: string,
264
+ deps: RelayApiDeps,
265
+ ): void {
266
+ const who = agentIdentity(req);
267
+ if (who === null) {
268
+ answer(res, 401, { error: 'unauthorized' });
269
+ return;
270
+ }
271
+ assertLease(who, deps.fence)
272
+ .then(() => relayExchange(req, res, who.agentId, connectorId, deps))
273
+ .catch((err: unknown) => {
274
+ if (err instanceof ApiError) {
275
+ answer(res, err.status, { error: err.message });
276
+ } else {
277
+ log.warn({ err: errMsg(err), connector: connectorId }, 'relay: failed');
278
+ answer(res, 502, { error: 'metro could not reach the connector' });
279
+ if (!res.writableEnded) res.end();
280
+ }
281
+ closeIfBodyUnread(req, res);
282
+ });
283
+ }
284
+
285
+ export function handleRelayRequest(
286
+ req: IncomingMessage,
287
+ res: ServerResponse,
288
+ deps: RelayApiDeps,
289
+ ): boolean {
290
+ const path = (req.url ?? '').split('?')[0] ?? '';
291
+ if (path !== '/relay' && !path.startsWith('/relay/')) return false;
292
+ const match = ID_PATH_RE.exec(path);
293
+ if (match?.[1] === undefined) {
294
+ answer(res, 404, { error: 'no such connector' });
295
+ return true;
296
+ }
297
+ if (!METHODS.has(req.method ?? '')) {
298
+ answer(res, 405, { error: 'method not allowed' });
299
+ return true;
300
+ }
301
+ dispatch(req, res, match[1], deps);
302
+ return true;
303
+ }
@@ -0,0 +1,139 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { log } from './log.js';
3
+ import {
4
+ apiFailure,
5
+ bodyField,
6
+ cors,
7
+ readJsonBody,
8
+ runIdentity,
9
+ sendJson,
10
+ } from './api-http.js';
11
+ import { AGENT_CODE_RE, takeAgentCode } from './agent-pair.js';
12
+ import { sessionTtlFromEnv } from './session-config.js';
13
+ import { signRunToken } from './session.js';
14
+ import type { LoadedAgent } from '../db/materialize.js';
15
+ import type { RuntimeLease } from '../db/runtimes.js';
16
+
17
+ const CLAIM_PATH = '/api/run/claim';
18
+ const CONFIG_PATH = '/api/run/config';
19
+
20
+ const ALLOWED: Record<string, string> = {
21
+ [CLAIM_PATH]: 'POST',
22
+ [CONFIG_PATH]: 'GET',
23
+ };
24
+
25
+ export interface RunApiDeps {
26
+ claimRuntime: (agentId: string, label: string) => Promise<RuntimeLease>;
27
+ fenceRuntime: (runtimeId: string, agentId: string) => Promise<void>;
28
+ touchRuntime: (runtimeId: string) => Promise<void>;
29
+ loadAgent: (agentId: string) => Promise<LoadedAgent>;
30
+ blockedStations: (agentId: string) => Promise<string[]>;
31
+ }
32
+
33
+ function secretOrNull(): string | null {
34
+ const secret = process.env.METRO_SESSION_SECRET?.trim() ?? '';
35
+ return secret === '' ? null : secret;
36
+ }
37
+
38
+ function labelOf(body: unknown): string {
39
+ const raw = bodyField(body, 'label');
40
+ const label = typeof raw === 'string' ? raw.trim().slice(0, 64) : '';
41
+ return label === '' ? 'unnamed machine' : label;
42
+ }
43
+
44
+ async function handleClaim(
45
+ req: IncomingMessage,
46
+ res: ServerResponse,
47
+ deps: RunApiDeps,
48
+ ): Promise<void> {
49
+ const secret = secretOrNull();
50
+ if (secret === null) {
51
+ sendJson(req, res, 503, { error: 'sign-in is not configured' });
52
+ return;
53
+ }
54
+ const body = await readJsonBody(req);
55
+ const raw = bodyField(body, 'code');
56
+ const code = typeof raw === 'string' ? raw.trim() : '';
57
+ if (!AGENT_CODE_RE.test(code)) {
58
+ sendJson(req, res, 400, {
59
+ error:
60
+ 'that does not look like an agent code — metro start wants the ma_… code from the agent page',
61
+ });
62
+ return;
63
+ }
64
+ const taken = takeAgentCode(code);
65
+ if (taken === undefined) {
66
+ sendJson(req, res, 400, {
67
+ error: 'that code has expired or was already used',
68
+ });
69
+ return;
70
+ }
71
+ const blocked = await deps.blockedStations(taken.agentId);
72
+ if (blocked.length > 0) {
73
+ sendJson(req, res, 409, {
74
+ error:
75
+ `this agent holds ${blocked.join(', ')}, which only runs on metro because a webhook url has to be publicly reachable. Detach it, or move it to a second agent that stays on metro, then start this one.`,
76
+ });
77
+ return;
78
+ }
79
+ const lease = await deps.claimRuntime(taken.agentId, labelOf(body));
80
+ const token = signRunToken(
81
+ { subject: taken.subject, agentId: lease.agentId, runtimeId: lease.runtimeId },
82
+ secret,
83
+ { ttlSec: sessionTtlFromEnv() },
84
+ );
85
+ log.info(
86
+ { agent: lease.agentId, runtime: lease.runtimeId, label: lease.label },
87
+ 'run: agent claimed by a local runtime',
88
+ );
89
+ sendJson(req, res, 200, {
90
+ token,
91
+ agent: lease.agentId,
92
+ label: lease.label,
93
+ });
94
+ }
95
+
96
+ async function handleConfig(
97
+ req: IncomingMessage,
98
+ res: ServerResponse,
99
+ deps: RunApiDeps,
100
+ ): Promise<void> {
101
+ const who = runIdentity(req);
102
+ if (who === null) {
103
+ sendJson(req, res, 401, { error: 'unauthorized' });
104
+ return;
105
+ }
106
+ await deps.fenceRuntime(who.runtimeId, who.agentId);
107
+ await deps.touchRuntime(who.runtimeId);
108
+ sendJson(req, res, 200, { agent: await deps.loadAgent(who.agentId) });
109
+ }
110
+
111
+ export function handleRunApiRequest(
112
+ req: IncomingMessage,
113
+ res: ServerResponse,
114
+ deps: RunApiDeps,
115
+ ): boolean {
116
+ const path = (req.url ?? '').split('?')[0] ?? '';
117
+ const method = ALLOWED[path];
118
+ if (method === undefined) {
119
+ if (!path.startsWith('/api/run/') && path !== '/api/run') return false;
120
+ sendJson(req, res, 404, { error: 'no such route' });
121
+ return true;
122
+ }
123
+ if (req.method === 'OPTIONS') {
124
+ res.writeHead(204, cors(req)).end();
125
+ return true;
126
+ }
127
+ if (req.method !== method) {
128
+ sendJson(req, res, 405, { error: 'method not allowed' });
129
+ return true;
130
+ }
131
+ const work =
132
+ path === CLAIM_PATH
133
+ ? handleClaim(req, res, deps)
134
+ : handleConfig(req, res, deps);
135
+ work.catch((err: unknown) => {
136
+ apiFailure(req, res, err, 'run-api');
137
+ });
138
+ return true;
139
+ }
@@ -0,0 +1,105 @@
1
+ import { errMsg, log } from './log.js';
2
+ import type { LoadedAgent, StationSource } from '../db/materialize.js';
3
+
4
+ const POLL_MS = 30_000;
5
+ const BACKOFF_MS = 5 * 60_000;
6
+ const FETCH_TIMEOUT_MS = 15_000;
7
+
8
+ export class RuntimeRevoked extends Error {}
9
+
10
+ const LOOPBACK = new Set(['127.0.0.1', '::1', 'localhost']);
11
+
12
+ export function carriesSecretsSafely(base: string): boolean {
13
+ let url: URL;
14
+ try {
15
+ url = new URL(base);
16
+ } catch {
17
+ return false;
18
+ }
19
+ if (url.protocol === 'https:') return true;
20
+ return url.protocol === 'http:' && LOOPBACK.has(url.hostname);
21
+ }
22
+
23
+ export interface RuntimeConfig {
24
+ url: string;
25
+ token: string;
26
+ }
27
+
28
+ export function runtimeConfigFromEnv(): RuntimeConfig | null {
29
+ const token = process.env.METRO_RUN_TOKEN?.trim() ?? '';
30
+ if (token === '') return null;
31
+ const raw = (process.env.METRO_URL?.trim() ?? '').replace(/\/+$/, '');
32
+ const url = raw === '' ? 'https://mcp.metro.box' : raw;
33
+ if (!carriesSecretsSafely(url))
34
+ throw new Error(
35
+ `refusing to fetch station credentials from ${url} in the clear — ` +
36
+ 'use https, or a loopback address',
37
+ );
38
+ return { url, token };
39
+ }
40
+
41
+ export function httpSource(cfg: RuntimeConfig): StationSource {
42
+ return async () => {
43
+ const res = await fetch(`${cfg.url}/api/run/config`, {
44
+ headers: { authorization: `Bearer ${cfg.token}` },
45
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
46
+ redirect: 'manual',
47
+ });
48
+ if (res.status === 401 || res.status === 403 || res.status === 409)
49
+ throw new RuntimeRevoked(
50
+ `metro no longer recognises this machine as the holder (${String(res.status)})`,
51
+ );
52
+ if (!res.ok)
53
+ throw new Error(`metro answered ${String(res.status)} for the run config`);
54
+ const body = (await res.json()) as { agent?: LoadedAgent };
55
+ if (!body.agent) throw new Error('metro returned no agent');
56
+ return [body.agent];
57
+ };
58
+ }
59
+
60
+ export interface PollerDeps {
61
+ sync: () => Promise<void>;
62
+ stopAll: () => Promise<void>;
63
+ everyMs?: number;
64
+ backoffMs?: number;
65
+ }
66
+
67
+ export function startRuntimePoller(deps: PollerDeps): () => void {
68
+ const everyMs = deps.everyMs ?? POLL_MS;
69
+ const backoffMs = deps.backoffMs ?? BACKOFF_MS;
70
+ let stopped = false;
71
+ let timer: ReturnType<typeof setTimeout> | null = null;
72
+
73
+ const arm = (ms: number): void => {
74
+ if (stopped) return;
75
+ timer = setTimeout(() => {
76
+ tick().catch((err: unknown) => {
77
+ log.error({ err: errMsg(err) }, 'runtime: poller failed');
78
+ });
79
+ }, ms);
80
+ timer.unref?.();
81
+ };
82
+
83
+ async function tick(): Promise<void> {
84
+ if (stopped) return;
85
+ try {
86
+ await deps.sync();
87
+ arm(everyMs);
88
+ } catch (err) {
89
+ if (err instanceof RuntimeRevoked) {
90
+ stopped = true;
91
+ log.error({ reason: err.message }, 'runtime: revoked — stopping stations');
92
+ await deps.stopAll();
93
+ return;
94
+ }
95
+ log.warn({ err: errMsg(err) }, 'runtime: could not reach metro; retrying');
96
+ arm(backoffMs);
97
+ }
98
+ }
99
+
100
+ arm(everyMs);
101
+ return () => {
102
+ stopped = true;
103
+ if (timer !== null) clearTimeout(timer);
104
+ };
105
+ }
@@ -0,0 +1,57 @@
1
+ import {
2
+ chmodSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ renameSync,
7
+ statSync,
8
+ writeFileSync,
9
+ } from 'node:fs';
10
+ import { dirname } from 'node:path';
11
+ import { errMsg, log } from './log.js';
12
+
13
+ export function ensureSecureDir(dir: string): void {
14
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
15
+ try {
16
+ chmodSync(dir, 0o700);
17
+ } catch {
18
+ }
19
+ }
20
+
21
+ export function chmodIfExists(path: string, mode = 0o600): void {
22
+ if (!existsSync(path)) return;
23
+ try {
24
+ if ((statSync(path).mode & 0o777) !== mode) chmodSync(path, mode);
25
+ } catch {
26
+ }
27
+ }
28
+
29
+ export function writeSecure(path: string, data: string): void {
30
+ ensureSecureDir(dirname(path));
31
+ const tmp = `${path}.tmp-${process.pid}`;
32
+ writeFileSync(tmp, data, { mode: 0o600 });
33
+ try {
34
+ chmodSync(tmp, 0o600);
35
+ } catch {
36
+ }
37
+ renameSync(tmp, path);
38
+ chmodIfExists(path, 0o600);
39
+ }
40
+
41
+ export function readJson<T>(
42
+ path: string,
43
+ fallback: T,
44
+ opts?: { warn?: string },
45
+ ): T {
46
+ if (!existsSync(path)) return fallback;
47
+ try {
48
+ return JSON.parse(readFileSync(path, 'utf8')) as T;
49
+ } catch (err) {
50
+ if (opts?.warn) log.warn({ err: errMsg(err), path }, opts.warn);
51
+ return fallback;
52
+ }
53
+ }
54
+
55
+ export function writeJson(path: string, value: unknown): void {
56
+ writeFileSync(path, JSON.stringify(value, null, 2));
57
+ }
@@ -0,0 +1,27 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { apiSession, cors, sendJson } from './api-http.js';
3
+
4
+ const PATH = '/api/session';
5
+
6
+ export function handleSessionApiRequest(
7
+ req: IncomingMessage,
8
+ res: ServerResponse,
9
+ ): boolean {
10
+ const path = (req.url ?? '').split('?')[0] ?? '';
11
+ if (path !== PATH) return false;
12
+ if (req.method === 'OPTIONS') {
13
+ res.writeHead(204, cors(req)).end();
14
+ return true;
15
+ }
16
+ if (req.method !== 'GET') {
17
+ sendJson(req, res, 405, { error: 'method not allowed' });
18
+ return true;
19
+ }
20
+ const session = apiSession(req);
21
+ if (!session) {
22
+ sendJson(req, res, 401, { error: 'unauthorized' });
23
+ return true;
24
+ }
25
+ sendJson(req, res, 200, { subject: session.subject });
26
+ return true;
27
+ }
@@ -0,0 +1,63 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { handleSessionApiRequest } from './session-api.js';
3
+ import { handleAgentApiRequest, type AgentApiDeps } from './agent-api.js';
4
+ import {
5
+ handleConnectorApiRequest,
6
+ type ConnectorApiDeps,
7
+ } from './connector-api.js';
8
+ import { handleCliPairRequest } from './cli-pair-api.js';
9
+ import { handleRunApiRequest, type RunApiDeps } from './run-api.js';
10
+ import type { RelayApiDeps } from './relay.js';
11
+ import {
12
+ handleProjectApiRequest,
13
+ type ProjectApiDeps,
14
+ } from './project-api.js';
15
+ import {
16
+ handleAgentConnectorRequest,
17
+ type AgentConnectorApiDeps,
18
+ } from './agent-connector-api.js';
19
+ import type { SiweRouteDeps } from './siwe-routes.js';
20
+ import { handleImportRequest, type ImportApiDeps } from './import-api.js';
21
+ import {
22
+ handleLocalConnectorsRequest,
23
+ type LocalConnectorsDeps,
24
+ } from './local-connectors-api.js';
25
+ import type { ModeInfo } from './mode-api.js';
26
+
27
+ export interface SessionApis {
28
+ agentApi?: AgentApiDeps;
29
+ agentConnectorApi?: AgentConnectorApiDeps;
30
+ importApi?: ImportApiDeps;
31
+ localConnectors?: LocalConnectorsDeps;
32
+ connectorApi?: ConnectorApiDeps;
33
+ projectApi?: ProjectApiDeps;
34
+ runApi?: RunApiDeps;
35
+ relayApi?: RelayApiDeps;
36
+ siwe?: SiweRouteDeps;
37
+ mode?: () => ModeInfo;
38
+ }
39
+
40
+ export function handleSessionApis(
41
+ req: IncomingMessage,
42
+ res: ServerResponse,
43
+ apis: SessionApis,
44
+ ): boolean {
45
+ const { agentApi, agentConnectorApi, connectorApi, projectApi, runApi, importApi } = apis;
46
+ const { localConnectors } = apis;
47
+ const routes: (() => boolean)[] = [() => handleSessionApiRequest(req, res)];
48
+ if (runApi) routes.push(() => handleRunApiRequest(req, res, runApi));
49
+ if (projectApi)
50
+ routes.push(() => handleProjectApiRequest(req, res, projectApi));
51
+ if (connectorApi)
52
+ routes.push(
53
+ () => handleCliPairRequest(req, res, connectorApi),
54
+ () => handleConnectorApiRequest(req, res, connectorApi),
55
+ );
56
+ if (importApi) routes.push(() => handleImportRequest(req, res, importApi));
57
+ if (localConnectors)
58
+ routes.push(() => handleLocalConnectorsRequest(req, res, localConnectors));
59
+ if (agentConnectorApi)
60
+ routes.push(() => handleAgentConnectorRequest(req, res, agentConnectorApi));
61
+ if (agentApi) routes.push(() => handleAgentApiRequest(req, res, agentApi));
62
+ return routes.some((run) => run());
63
+ }