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

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 +91 -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,334 @@
1
+ import { webhookPort } from './tunnel.js';
2
+ import type { IncomingMessage, ServerResponse } from 'node:http';
3
+ import { errMsg, log } from './log.js';
4
+ import { publicBaseOrDefault } from './attach-serve.js';
5
+ import {
6
+ apiFailure,
7
+ apiSession,
8
+ projectParam,
9
+ cors,
10
+ readJsonBody,
11
+ sendJson,
12
+ type ApiSession,
13
+ } from './api-http.js';
14
+ import {
15
+ accountRoute,
16
+ accountRouteAllows,
17
+ ATTACHABLE,
18
+ handleAccountRoute,
19
+ type AccountApiDeps,
20
+ type AccountRoute,
21
+ } from './account-api.js';
22
+ import {
23
+ parseAgentId,
24
+ type AgentSummary,
25
+ type CreatedAgent,
26
+ type DeletedAgent,
27
+ type ResetAgentKey,
28
+ } from '../db/agent-admin.js';
29
+
30
+ const PREFIX = '/api/agents';
31
+ const SERVER_NAME = 'metro';
32
+
33
+ export interface AgentApiDeps extends AccountApiDeps {
34
+ listAgents: (subject: string, project: string) => Promise<AgentSummary[]>;
35
+ createAgent: (
36
+ subject: string,
37
+ project: string,
38
+ name: string,
39
+ ) => Promise<CreatedAgent>;
40
+ deleteAgent: (
41
+ subject: string,
42
+ id: string,
43
+ ) => Promise<DeletedAgent>;
44
+ resetKey: (
45
+ subject: string,
46
+ id: string,
47
+ ) => Promise<ResetAgentKey>;
48
+ gatherAccounts: (allowed: Set<string>) => Promise<{
49
+ accounts: Record<string, unknown[]>;
50
+ unavailable: string[];
51
+ }>;
52
+ capabilities: () => Record<string, string[]>;
53
+ attachable?: string[];
54
+ liveness: () => Map<string, { connected: boolean; lastSeenAt: number }>;
55
+ releaseRuntime: (subject: string, agentId: string) => Promise<void>;
56
+ runtimes: (agentIds: string[]) => Promise<Map<string, string>>;
57
+ connectorIds: (agentIds: string[]) => Promise<Map<string, string[]>>;
58
+ }
59
+
60
+ type Routable =
61
+ | { kind: 'collection' }
62
+ | { kind: 'agent'; id: string }
63
+ | { kind: 'key'; id: string }
64
+ | { kind: 'runtime'; id: string }
65
+ | { kind: 'accounts'; id: string; route: AccountRoute };
66
+
67
+ type Target = Routable | { kind: 'unknown' } | null;
68
+
69
+ function subTarget(id: string, rest: string[]): Target {
70
+ if (rest.length === 0) return { kind: 'agent', id };
71
+ if (rest.length === 1 && rest[0] === 'key') return { kind: 'key', id };
72
+ if (rest.length === 1 && rest[0] === 'runtime')
73
+ return { kind: 'runtime', id };
74
+ if (rest[0] !== 'accounts') return { kind: 'unknown' };
75
+ const route = accountRoute(rest.slice(1));
76
+ return route === null ? { kind: 'unknown' } : { kind: 'accounts', id, route };
77
+ }
78
+
79
+ export function target(path: string): Target {
80
+ if (path === PREFIX || path === `${PREFIX}/`) return { kind: 'collection' };
81
+ if (!path.startsWith(`${PREFIX}/`)) return null;
82
+ const segments = path.slice(PREFIX.length + 1).split('/').filter(Boolean);
83
+ const head = segments[0];
84
+ if (head === undefined) return { kind: 'collection' };
85
+ const id = parseAgentId(head);
86
+ return id === null ? { kind: 'unknown' } : subTarget(id, segments.slice(1));
87
+ }
88
+
89
+ export function mcpEndpoint(): string {
90
+ return `${publicBaseOrDefault()}/mcp`;
91
+ }
92
+
93
+ const localMcpEndpoint = (): string => `http://127.0.0.1:${String(webhookPort())}/mcp`;
94
+
95
+ export function mcpAddCommand(key: string): string {
96
+ const url = `${localMcpEndpoint()}?token=${key}`;
97
+ return `claude mcp add --transport http ${SERVER_NAME} "${url}"`;
98
+ }
99
+
100
+ interface KeyPayload {
101
+ key: string | null;
102
+ endpoint: string | null;
103
+ command: string | null;
104
+ }
105
+
106
+ function credentials(key: string): KeyPayload {
107
+ return {
108
+ key,
109
+ endpoint: `${localMcpEndpoint()}?token=${key}`,
110
+ command: mcpAddCommand(key),
111
+ };
112
+ }
113
+
114
+ function keyPayload(agent: AgentSummary): KeyPayload {
115
+ const value = agent.owned ? agent.key : null;
116
+ if (value === null) return { key: null, endpoint: null, command: null };
117
+ return credentials(value);
118
+ }
119
+
120
+ function livenessPayload(
121
+ agent: AgentSummary,
122
+ live: Map<string, { connected: boolean; lastSeenAt: number }>,
123
+ ): Record<string, unknown> {
124
+ const found = agent.owned ? live.get(agent.id) : undefined;
125
+ if (found === undefined) return { connected: false, last_seen: null };
126
+ return {
127
+ connected: found.connected,
128
+ last_seen: new Date(found.lastSeenAt).toISOString(),
129
+ };
130
+ }
131
+
132
+ function agentPayload(
133
+ agent: AgentSummary,
134
+ live: Map<string, { connected: boolean; lastSeenAt: number }>,
135
+ runtimes: Map<string, string>,
136
+ connectors: Map<string, string[]>,
137
+ ): Record<string, unknown> {
138
+ return {
139
+ id: agent.id,
140
+ name: agent.name,
141
+ owned: agent.owned,
142
+ runtime: agent.owned ? (runtimes.get(agent.id) ?? null) : null,
143
+ connector_ids: connectors.get(agent.id) ?? [],
144
+ ...livenessPayload(agent, live),
145
+ ...keyPayload(agent),
146
+ };
147
+ }
148
+
149
+ function wantsAccounts(req: IncomingMessage): boolean {
150
+ const query = (req.url ?? '').split('?')[1] ?? '';
151
+ return new URLSearchParams(query).get('accounts') === '1';
152
+ }
153
+
154
+ async function handleList(
155
+ req: IncomingMessage,
156
+ res: ServerResponse,
157
+ deps: AgentApiDeps,
158
+ session: ApiSession,
159
+ ): Promise<void> {
160
+ const project = projectParam(req);
161
+ if (project === null) {
162
+ sendJson(req, res, 400, { error: 'a project is required' });
163
+ return;
164
+ }
165
+ const list = await deps.listAgents(session.subject, project);
166
+ const live = deps.liveness();
167
+ const held = await deps.runtimes(
168
+ list.filter((a) => a.owned).map((a) => a.id),
169
+ );
170
+ const connectors = await deps.connectorIds(list.map((a) => a.id));
171
+ const base = {
172
+ subject: session.subject,
173
+ endpoint: mcpEndpoint(),
174
+ agents: list.map((a) => agentPayload(a, live, held, connectors)),
175
+ capabilities: deps.capabilities(),
176
+ attachable: deps.attachable ?? ATTACHABLE,
177
+ };
178
+ if (!wantsAccounts(req)) {
179
+ sendJson(req, res, 200, base);
180
+ return;
181
+ }
182
+ const { accounts, unavailable } = await deps.gatherAccounts(
183
+ new Set(list.map((a) => a.id)),
184
+ );
185
+ sendJson(req, res, 200, { ...base, accounts, unavailable });
186
+ }
187
+
188
+ async function handleRuntime(
189
+ req: IncomingMessage,
190
+ res: ServerResponse,
191
+ deps: AgentApiDeps,
192
+ session: ApiSession,
193
+ id: string,
194
+ ): Promise<void> {
195
+ await deps.releaseRuntime(session.subject, id);
196
+ log.info({ agent: id }, 'agent-api: runtime released');
197
+ sendJson(req, res, 200, { agent: id, runtime: null });
198
+ }
199
+
200
+ async function handleCreate(
201
+ req: IncomingMessage,
202
+ res: ServerResponse,
203
+ deps: AgentApiDeps,
204
+ session: ApiSession,
205
+ ): Promise<void> {
206
+ const body = await readJsonBody(req);
207
+ const name = (body as { name?: unknown }).name;
208
+ const project = projectParam(req);
209
+ if (project === null) {
210
+ sendJson(req, res, 400, { error: 'a project is required' });
211
+ return;
212
+ }
213
+ const created = await deps.createAgent(session.subject, project, name as string);
214
+ log.info(
215
+ { agent: created.name, id: created.id, owner: session.subject },
216
+ 'agent-api: created agent',
217
+ );
218
+ sendJson(req, res, 201, {
219
+ id: created.id,
220
+ name: created.name,
221
+ ...credentials(created.key),
222
+ });
223
+ }
224
+
225
+ async function handleResetKey(
226
+ req: IncomingMessage,
227
+ res: ServerResponse,
228
+ deps: AgentApiDeps,
229
+ session: ApiSession,
230
+ id: string,
231
+ ): Promise<void> {
232
+ const reset = await deps.resetKey(session.subject, id);
233
+ log.info(
234
+ { agent: reset.name, id: reset.id, owner: session.subject },
235
+ 'agent-api: reset agent key',
236
+ );
237
+ sendJson(req, res, 200, {
238
+ id: reset.id,
239
+ name: reset.name,
240
+ reset: true,
241
+ ...credentials(reset.key),
242
+ });
243
+ }
244
+
245
+ async function handleDelete(
246
+ req: IncomingMessage,
247
+ res: ServerResponse,
248
+ deps: AgentApiDeps,
249
+ session: ApiSession,
250
+ id: string,
251
+ ): Promise<void> {
252
+ const gone = await deps.deleteAgent(session.subject, id);
253
+ log.info(
254
+ { agent: gone.name, id: gone.id, owner: session.subject },
255
+ 'agent-api: deleted agent',
256
+ );
257
+ sendJson(req, res, 200, { id: gone.id, name: gone.name, deleted: true });
258
+ }
259
+
260
+ type AgentTarget = Exclude<Routable, { kind: 'accounts' }>;
261
+
262
+ async function routeAgent(
263
+ req: IncomingMessage,
264
+ res: ServerResponse,
265
+ deps: AgentApiDeps,
266
+ session: ApiSession,
267
+ tgt: AgentTarget,
268
+ ): Promise<void> {
269
+ try {
270
+ if (tgt.kind === 'key') await handleResetKey(req, res, deps, session, tgt.id);
271
+ else if (tgt.kind === 'runtime')
272
+ await handleRuntime(req, res, deps, session, tgt.id);
273
+ else if (tgt.kind === 'agent')
274
+ await handleDelete(req, res, deps, session, tgt.id);
275
+ else if (req.method === 'GET') await handleList(req, res, deps, session);
276
+ else await handleCreate(req, res, deps, session);
277
+ } catch (err) {
278
+ apiFailure(req, res, err);
279
+ }
280
+ }
281
+
282
+ const ALLOWED: Record<AgentTarget['kind'], string[]> = {
283
+ collection: ['GET', 'POST'],
284
+ agent: ['DELETE'],
285
+ key: ['POST'],
286
+ runtime: ['DELETE'],
287
+ };
288
+
289
+ function methodAllowed(tgt: Routable, method: string | undefined): boolean {
290
+ if (tgt.kind === 'accounts') return accountRouteAllows(tgt.route, method);
291
+ return ALLOWED[tgt.kind].includes(method ?? '');
292
+ }
293
+
294
+ function dispatch(
295
+ req: IncomingMessage,
296
+ res: ServerResponse,
297
+ deps: AgentApiDeps,
298
+ tgt: Routable,
299
+ ): Promise<void> {
300
+ const session = apiSession(req);
301
+ if (!session) {
302
+ sendJson(req, res, 401, { error: 'unauthorized' });
303
+ return Promise.resolve();
304
+ }
305
+ if (tgt.kind === 'accounts')
306
+ return handleAccountRoute(req, res, deps, session, tgt.id, tgt.route);
307
+ return routeAgent(req, res, deps, session, tgt);
308
+ }
309
+
310
+ export function handleAgentApiRequest(
311
+ req: IncomingMessage,
312
+ res: ServerResponse,
313
+ deps: AgentApiDeps,
314
+ ): boolean {
315
+ const tgt = target((req.url ?? '').split('?')[0] ?? '');
316
+ if (tgt === null) return false;
317
+ if (req.method === 'OPTIONS') {
318
+ res.writeHead(204, cors(req)).end();
319
+ return true;
320
+ }
321
+ if (tgt.kind === 'unknown') {
322
+ sendJson(req, res, 404, { error: 'no such agent' });
323
+ return true;
324
+ }
325
+ if (!methodAllowed(tgt, req.method)) {
326
+ sendJson(req, res, 405, { error: 'method not allowed' });
327
+ return true;
328
+ }
329
+ dispatch(req, res, deps, tgt).catch((err: unknown) => {
330
+ log.warn({ err: errMsg(err) }, 'agent-api: unhandled error');
331
+ if (!res.headersSent) sendJson(req, res, 500, { error: 'agent api failed' });
332
+ });
333
+ return true;
334
+ }
@@ -0,0 +1,150 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { errMsg, log } from './log.js';
3
+ import {
4
+ apiFailure,
5
+ apiSession,
6
+ bodyField,
7
+ cors,
8
+ readJsonBody,
9
+ sendJson,
10
+ type ApiSession,
11
+ } from './api-http.js';
12
+ import { parseId } from '../db/ids.js';
13
+ import type { AgentConnectors } from '../db/agent-connectors.js';
14
+
15
+ const PREFIX = '/api/agents';
16
+
17
+ export interface AgentConnectorApiDeps {
18
+ agentConnectors: (subject: string, agentId: string) => Promise<AgentConnectors>;
19
+ addConnector: (
20
+ subject: string,
21
+ agentId: string,
22
+ connectorId: string,
23
+ ) => Promise<AgentConnectors>;
24
+ removeConnector: (
25
+ subject: string,
26
+ agentId: string,
27
+ connectorId: string,
28
+ ) => Promise<AgentConnectors>;
29
+ mintCode: (
30
+ subject: string,
31
+ agentId: string,
32
+ ) => Promise<{ code: string; expiresAt: number; agent: string }>;
33
+ }
34
+
35
+ type Routable =
36
+ | { kind: 'connectors'; id: string }
37
+ | { kind: 'connector'; id: string; connectorId: string }
38
+ | { kind: 'code'; id: string };
39
+
40
+ type Target = Routable | { kind: 'unknown' } | null;
41
+
42
+ const ALLOWED: Record<Routable['kind'], string[]> = {
43
+ connectors: ['GET', 'POST'],
44
+ connector: ['DELETE'],
45
+ code: ['POST'],
46
+ };
47
+
48
+ function connectorTarget(id: string, rest: string[]): Target {
49
+ if (rest.length === 1) return { kind: 'connectors', id };
50
+ if (rest.length > 2) return { kind: 'unknown' };
51
+ const connectorId = parseId(rest[1] ?? '');
52
+ return connectorId === null
53
+ ? { kind: 'unknown' }
54
+ : { kind: 'connector', id, connectorId };
55
+ }
56
+
57
+ export function target(path: string): Target {
58
+ if (!path.startsWith(`${PREFIX}/`)) return null;
59
+ const segments = path.slice(PREFIX.length + 1).split('/').filter(Boolean);
60
+ const id = parseId(segments[0] ?? '');
61
+ const head = segments[1];
62
+ if (id === null || head === undefined) return null;
63
+ if (head === 'connectors') return connectorTarget(id, segments.slice(1));
64
+ if (head === 'code' && segments.length === 2) return { kind: 'code', id };
65
+ return null;
66
+ }
67
+
68
+ async function handleConnectors(
69
+ req: IncomingMessage,
70
+ res: ServerResponse,
71
+ deps: AgentConnectorApiDeps,
72
+ session: ApiSession,
73
+ id: string,
74
+ ): Promise<void> {
75
+ if (req.method === 'GET') {
76
+ sendJson(req, res, 200, await deps.agentConnectors(session.subject, id));
77
+ return;
78
+ }
79
+ const connectorId = bodyField(await readJsonBody(req), 'connectorId');
80
+ if (typeof connectorId !== 'string' || parseId(connectorId) === null) {
81
+ sendJson(req, res, 400, { error: 'connectorId is required' });
82
+ return;
83
+ }
84
+ sendJson(req, res, 200, await deps.addConnector(session.subject, id, connectorId));
85
+ }
86
+
87
+ async function handleCode(
88
+ req: IncomingMessage,
89
+ res: ServerResponse,
90
+ deps: AgentConnectorApiDeps,
91
+ session: ApiSession,
92
+ id: string,
93
+ ): Promise<void> {
94
+ const minted = await deps.mintCode(session.subject, id);
95
+ log.info({ agent: id, subject: session.subject }, 'agent-api: minted pairing code');
96
+ sendJson(req, res, 201, minted);
97
+ }
98
+
99
+ async function route(
100
+ req: IncomingMessage,
101
+ res: ServerResponse,
102
+ deps: AgentConnectorApiDeps,
103
+ session: ApiSession,
104
+ tgt: Routable,
105
+ ): Promise<void> {
106
+ try {
107
+ if (tgt.kind === 'connectors') await handleConnectors(req, res, deps, session, tgt.id);
108
+ else if (tgt.kind === 'code') await handleCode(req, res, deps, session, tgt.id);
109
+ else
110
+ sendJson(
111
+ req,
112
+ res,
113
+ 200,
114
+ await deps.removeConnector(session.subject, tgt.id, tgt.connectorId),
115
+ );
116
+ } catch (err) {
117
+ apiFailure(req, res, err, 'agent-api');
118
+ }
119
+ }
120
+
121
+ export function handleAgentConnectorRequest(
122
+ req: IncomingMessage,
123
+ res: ServerResponse,
124
+ deps: AgentConnectorApiDeps,
125
+ ): boolean {
126
+ const tgt = target((req.url ?? '').split('?')[0] ?? '');
127
+ if (tgt === null) return false;
128
+ if (req.method === 'OPTIONS') {
129
+ res.writeHead(204, cors(req)).end();
130
+ return true;
131
+ }
132
+ if (tgt.kind === 'unknown') {
133
+ sendJson(req, res, 404, { error: 'no such connector' });
134
+ return true;
135
+ }
136
+ if (!ALLOWED[tgt.kind].includes(req.method ?? '')) {
137
+ sendJson(req, res, 405, { error: 'method not allowed' });
138
+ return true;
139
+ }
140
+ const session = apiSession(req);
141
+ if (!session) {
142
+ sendJson(req, res, 401, { error: 'unauthorized' });
143
+ return true;
144
+ }
145
+ route(req, res, deps, session, tgt).catch((err: unknown) => {
146
+ log.warn({ err: errMsg(err) }, 'agent-api: unhandled error');
147
+ if (!res.headersSent) sendJson(req, res, 500, { error: 'agent api failed' });
148
+ });
149
+ return true;
150
+ }
@@ -0,0 +1,76 @@
1
+ import { mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { ApiError } from './api-error.js';
4
+ import { configDir } from './paths.js';
5
+ import { writeSecure } from './secure-fs.js';
6
+ import { carriesSecretsSafely, httpSource } from './runtime-source.js';
7
+ import type { LoadedAgent } from '../db/materialize.js';
8
+
9
+ const CLAIM_TIMEOUT_MS = 15_000;
10
+ const CODE_RE = /^ma_[A-Za-z0-9_-]{16}$/;
11
+
12
+ export function metroBaseUrl(): string {
13
+ const raw = (process.env.METRO_URL?.trim() ?? '').replace(/\/+$/, '');
14
+ const url = raw === '' ? 'https://mcp.metro.box' : raw;
15
+ if (!carriesSecretsSafely(url))
16
+ throw new ApiError(`refusing to read station credentials from ${url} in the clear`, 400);
17
+ return url;
18
+ }
19
+
20
+ export function parsePairingCode(raw: unknown): string {
21
+ const code = typeof raw === 'string' ? raw.trim() : '';
22
+ if (!CODE_RE.test(code))
23
+ throw new ApiError('paste the pairing code from the agent\'s page on metro.box', 400);
24
+ return code;
25
+ }
26
+
27
+ async function claim(base: string, code: string, label: string): Promise<string> {
28
+ let res: Response;
29
+ try {
30
+ res = await fetch(`${base}/api/run/claim`, {
31
+ method: 'POST',
32
+ headers: { 'content-type': 'application/json' },
33
+ body: JSON.stringify({ code, label }),
34
+ signal: AbortSignal.timeout(CLAIM_TIMEOUT_MS),
35
+ redirect: 'manual',
36
+ });
37
+ } catch {
38
+ throw new ApiError(`could not reach ${base}`, 502);
39
+ }
40
+ const body: unknown = await res.json().catch(() => null);
41
+ const field = (key: string): unknown =>
42
+ typeof body === 'object' && body !== null ? (body as Record<string, unknown>)[key] : undefined;
43
+ if (!res.ok) {
44
+ const reason = field('error');
45
+ throw new ApiError(
46
+ typeof reason === 'string' ? reason : `metro answered ${String(res.status)} for the code`,
47
+ res.status === 404 ? 400 : res.status,
48
+ );
49
+ }
50
+ const token = field('token');
51
+ if (typeof token !== 'string' || token === '')
52
+ throw new ApiError('metro returned no runtime token', 502);
53
+ return token;
54
+ }
55
+
56
+ export function storeRunToken(agentId: string, token: string, url: string): string {
57
+ const dir = configDir();
58
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
59
+ const path = join(dir, `runtime-${agentId}.json`);
60
+ writeSecure(path, `${JSON.stringify({ token, url }, null, 2)}\n`);
61
+ return path;
62
+ }
63
+
64
+ export async function fetchAgentWithCode(
65
+ code: string,
66
+ label: string,
67
+ base = metroBaseUrl(),
68
+ ): Promise<LoadedAgent> {
69
+ const token = await claim(base, code, label);
70
+ const agents = await httpSource({ url: base, token })();
71
+ const agent = agents[0];
72
+ if (agent === undefined || agents.length !== 1)
73
+ throw new ApiError('metro returned no agent for that code', 502);
74
+ storeRunToken(agent.id, token, base);
75
+ return agent;
76
+ }
@@ -0,0 +1,20 @@
1
+ import { makeCodeStore } from './pair-codes.js';
2
+
3
+ export interface AgentCode {
4
+ subject: string;
5
+ agentId: string;
6
+ }
7
+
8
+ const store = makeCodeStore<AgentCode>('ma');
9
+
10
+ export const AGENT_CODE_RE = store.pattern;
11
+
12
+ export const mintAgentCode = (
13
+ entry: AgentCode,
14
+ now?: number,
15
+ ): { code: string; expiresAt: number } => store.mint(entry, now);
16
+
17
+ export const takeAgentCode = (code: string, now?: number): AgentCode | undefined =>
18
+ store.take(code, now);
19
+
20
+ export const agentCodeCount = (): number => store.count();
@@ -0,0 +1,8 @@
1
+ export class ApiError extends Error {
2
+ constructor(
3
+ message: string,
4
+ readonly status: number,
5
+ ) {
6
+ super(message);
7
+ }
8
+ }