@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,27 @@
1
+ const DEFAULT_SESSION_TTL_SEC = 30 * 24 * 3600;
2
+ const PREVIEW = /^([a-z0-9-]+--)?metro-ui\.netlify\.app$/;
3
+
4
+ export function sessionTtlFromEnv(): number {
5
+ const ttl = Number(process.env.METRO_SESSION_TTL_SEC);
6
+ return Number.isFinite(ttl) && ttl > 0 ? ttl : DEFAULT_SESSION_TTL_SEC;
7
+ }
8
+
9
+ export function allowedWebHost(host: string): boolean {
10
+ if (host === 'metro.box' || host === 'localhost' || host === '127.0.0.1')
11
+ return true;
12
+ return PREVIEW.test(host);
13
+ }
14
+
15
+ export function validateReturnTo(returnTo: string): boolean {
16
+ let url: URL;
17
+ try {
18
+ url = new URL(returnTo);
19
+ } catch {
20
+ return false;
21
+ }
22
+ const host = url.hostname;
23
+ if (url.protocol === 'http:' && (host === 'localhost' || host === '127.0.0.1'))
24
+ return true;
25
+ if (url.protocol !== 'https:') return false;
26
+ return allowedWebHost(host);
27
+ }
@@ -0,0 +1,218 @@
1
+ import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { ID_RE } from '../db/ids.js';
3
+
4
+ export class SessionError extends Error {}
5
+
6
+ const b64url = (buf: Buffer): string =>
7
+ buf
8
+ .toString('base64')
9
+ .replace(/\+/g, '-')
10
+ .replace(/\//g, '_')
11
+ .replace(/=+$/, '');
12
+
13
+ const b64urlJson = (v: unknown): string =>
14
+ b64url(Buffer.from(JSON.stringify(v)));
15
+
16
+ const decodeJson = (seg: string): unknown =>
17
+ JSON.parse(
18
+ Buffer.from(seg.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString(
19
+ 'utf8',
20
+ ),
21
+ );
22
+
23
+ const HEADER = b64urlJson({ alg: 'HS256', typ: 'JWT' });
24
+
25
+ function sign(payload: Record<string, unknown>, secret: string): string {
26
+ if (secret === '') throw new SessionError('missing signing secret');
27
+ const body = b64urlJson(payload);
28
+ const data = `${HEADER}.${body}`;
29
+ const sig = b64url(createHmac('sha256', secret).update(data).digest());
30
+ return `${data}.${sig}`;
31
+ }
32
+
33
+ function verify(token: string, secret: string): Record<string, unknown> {
34
+ if (secret === '') throw new SessionError('missing signing secret');
35
+ const parts = token.split('.');
36
+ if (parts.length !== 3) throw new SessionError('malformed token');
37
+ const data = `${parts[0]}.${parts[1]}`;
38
+ const expected = b64url(createHmac('sha256', secret).update(data).digest());
39
+ const a = Buffer.from(parts[2] ?? '');
40
+ const b = Buffer.from(expected);
41
+ if (a.length !== b.length || !timingSafeEqual(a, b))
42
+ throw new SessionError('bad signature');
43
+ let payload: unknown;
44
+ try {
45
+ payload = decodeJson(parts[1] ?? '');
46
+ } catch {
47
+ throw new SessionError('malformed payload');
48
+ }
49
+ if (typeof payload !== 'object' || payload === null)
50
+ throw new SessionError('malformed payload');
51
+ return payload as Record<string, unknown>;
52
+ }
53
+
54
+ const nowSec = (now?: number): number => Math.floor((now ?? Date.now()) / 1000);
55
+
56
+ export function newNonce(): string {
57
+ return b64url(randomBytes(16));
58
+ }
59
+
60
+ interface StateClaims {
61
+ return_to: string;
62
+ nonce: string;
63
+ }
64
+
65
+ export function signState(
66
+ claims: StateClaims,
67
+ secret: string,
68
+ opts: { ttlSec?: number; now?: number } = {},
69
+ ): string {
70
+ const iat = nowSec(opts.now);
71
+ return sign(
72
+ {
73
+ typ: 'state',
74
+ return_to: claims.return_to,
75
+ nonce: claims.nonce,
76
+ iat,
77
+ exp: iat + (opts.ttlSec ?? 600),
78
+ },
79
+ secret,
80
+ );
81
+ }
82
+
83
+ export function verifyState(
84
+ token: string,
85
+ secret: string,
86
+ now?: number,
87
+ ): StateClaims {
88
+ const p = verify(token, secret);
89
+ if (p.typ !== 'state') throw new SessionError('wrong token type');
90
+ if (typeof p.exp !== 'number' || p.exp < nowSec(now))
91
+ throw new SessionError('state expired');
92
+ if (typeof p.return_to !== 'string' || typeof p.nonce !== 'string')
93
+ throw new SessionError('malformed state');
94
+ return { return_to: p.return_to, nonce: p.nonce };
95
+ }
96
+
97
+ interface SessionClaims {
98
+ subject: string;
99
+ agentIds: string[];
100
+ }
101
+
102
+ export interface AgentClaims {
103
+ subject: string;
104
+ agentId: string;
105
+ }
106
+
107
+ export interface RunClaims {
108
+ subject: string;
109
+ agentId: string;
110
+ runtimeId: string;
111
+ }
112
+
113
+ export function signSession(
114
+ claims: SessionClaims,
115
+ secret: string,
116
+ opts: { ttlSec?: number; now?: number } = {},
117
+ ): string {
118
+ const iat = nowSec(opts.now);
119
+ return sign(
120
+ {
121
+ typ: 'session',
122
+ sub: claims.subject,
123
+ agent_ids: claims.agentIds,
124
+ iat,
125
+ exp: iat + (opts.ttlSec ?? 30 * 24 * 3600),
126
+ },
127
+ secret,
128
+ );
129
+ }
130
+
131
+ export function verifySession(
132
+ token: string,
133
+ secret: string,
134
+ now?: number,
135
+ ): SessionClaims {
136
+ const p = verify(token, secret);
137
+ if (p.typ !== 'session') throw new SessionError('wrong token type');
138
+ if (typeof p.exp !== 'number' || p.exp < nowSec(now))
139
+ throw new SessionError('session expired');
140
+ const ids = p.agent_ids;
141
+ if (
142
+ typeof p.sub !== 'string' ||
143
+ !Array.isArray(ids) ||
144
+ ids.some((a) => typeof a !== 'string' || !ID_RE.test(a))
145
+ )
146
+ throw new SessionError('malformed session');
147
+ return { subject: p.sub, agentIds: ids as string[] };
148
+ }
149
+
150
+ export function signAgentToken(
151
+ claims: AgentClaims,
152
+ secret: string,
153
+ opts: { ttlSec?: number; now?: number } = {},
154
+ ): string {
155
+ const iat = nowSec(opts.now);
156
+ return sign(
157
+ {
158
+ typ: 'agent',
159
+ sub: claims.subject,
160
+ agent: claims.agentId,
161
+ iat,
162
+ exp: iat + (opts.ttlSec ?? 30 * 24 * 3600),
163
+ },
164
+ secret,
165
+ );
166
+ }
167
+
168
+ export function verifyAgentToken(
169
+ token: string,
170
+ secret: string,
171
+ now?: number,
172
+ ): AgentClaims {
173
+ const p = verify(token, secret);
174
+ if (p.typ !== 'agent') throw new SessionError('wrong token type');
175
+ if (typeof p.exp !== 'number' || p.exp < nowSec(now))
176
+ throw new SessionError('agent token expired');
177
+ if (typeof p.sub !== 'string' || typeof p.agent !== 'string')
178
+ throw new SessionError('malformed agent token');
179
+ return { subject: p.sub, agentId: p.agent };
180
+ }
181
+
182
+ export function signRunToken(
183
+ claims: RunClaims,
184
+ secret: string,
185
+ opts: { ttlSec?: number; now?: number } = {},
186
+ ): string {
187
+ const iat = nowSec(opts.now);
188
+ return sign(
189
+ {
190
+ typ: 'run',
191
+ sub: claims.subject,
192
+ agent: claims.agentId,
193
+ rt: claims.runtimeId,
194
+ iat,
195
+ exp: iat + (opts.ttlSec ?? 365 * 24 * 3600),
196
+ },
197
+ secret,
198
+ );
199
+ }
200
+
201
+ export function verifyRunToken(
202
+ token: string,
203
+ secret: string,
204
+ now?: number,
205
+ ): RunClaims {
206
+ const p = verify(token, secret);
207
+ if (p.typ !== 'run') throw new SessionError('wrong token type');
208
+ if (typeof p.exp !== 'number' || p.exp < nowSec(now))
209
+ throw new SessionError('run token expired');
210
+ if (
211
+ typeof p.sub !== 'string' ||
212
+ typeof p.agent !== 'string' ||
213
+ typeof p.rt !== 'string' ||
214
+ !ID_RE.test(p.agent)
215
+ )
216
+ throw new SessionError('malformed run token');
217
+ return { subject: p.sub, agentId: p.agent, runtimeId: p.rt };
218
+ }
@@ -0,0 +1,82 @@
1
+ import { verifyMessage, type Hex } from 'viem';
2
+ import { parseSiweMessage, validateSiweMessage } from 'viem/siwe';
3
+ import { ApiError } from './api-error.js';
4
+ import { allowedWebHost } from './session-config.js';
5
+
6
+ export class SiweError extends ApiError {}
7
+
8
+ export interface SiweLogin {
9
+ message: string;
10
+ signature: string;
11
+ }
12
+
13
+ export interface SiweVerifyArgs {
14
+ address: `0x${string}`;
15
+ message: string;
16
+ signature: Hex;
17
+ }
18
+
19
+ export interface SiweDeps {
20
+ takeNonce: (nonce: string) => boolean;
21
+ now?: number;
22
+ verify?: (args: SiweVerifyArgs) => Promise<boolean>;
23
+ }
24
+
25
+ const HEX_SIGNATURE = /^0x[0-9a-fA-F]{130}$/;
26
+
27
+ export function allowedSiweDomain(domain: string): boolean {
28
+ return allowedWebHost(domain.replace(/:\d+$/, ''));
29
+ }
30
+
31
+ function hostOf(uri: string): string | null {
32
+ try {
33
+ return new URL(uri).host;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ function fieldsOrThrow(message: string): {
40
+ address: `0x${string}`;
41
+ domain: string;
42
+ nonce: string;
43
+ parsed: ReturnType<typeof parseSiweMessage>;
44
+ } {
45
+ const parsed = parseSiweMessage(message);
46
+ const { address, domain, nonce, uri, version } = parsed;
47
+ if (
48
+ address === undefined ||
49
+ domain === undefined ||
50
+ nonce === undefined ||
51
+ uri === undefined ||
52
+ version !== '1'
53
+ )
54
+ throw new SiweError('that is not a sign-in message', 400);
55
+ if (!allowedSiweDomain(domain))
56
+ throw new SiweError(`sign-in messages for ${domain} are not accepted here`, 400);
57
+ if (hostOf(uri) !== domain)
58
+ throw new SiweError('the sign-in message names a different site', 400);
59
+ return { address, domain, nonce, parsed };
60
+ }
61
+
62
+ export async function verifySiweLogin(
63
+ input: SiweLogin,
64
+ deps: SiweDeps,
65
+ ): Promise<string> {
66
+ if (!HEX_SIGNATURE.test(input.signature))
67
+ throw new SiweError('that is not an Ethereum signature', 400);
68
+ const { address, domain, nonce, parsed } = fieldsOrThrow(input.message);
69
+ const time = new Date(deps.now ?? Date.now());
70
+ if (!validateSiweMessage({ message: parsed, domain, nonce, time }))
71
+ throw new SiweError('this sign-in message is expired or does not match', 400);
72
+ if (!deps.takeNonce(nonce))
73
+ throw new SiweError('that sign-in has expired or was already used', 400);
74
+ const verify = deps.verify ?? verifyMessage;
75
+ const signature = input.signature as Hex;
76
+ if (!(await verify({ address, message: input.message, signature })))
77
+ throw new SiweError(
78
+ 'the signature does not match the address; smart-contract wallets are not supported yet',
79
+ 401,
80
+ );
81
+ return address.toLowerCase();
82
+ }
@@ -0,0 +1,43 @@
1
+ import { generateSiweNonce } from 'viem/siwe';
2
+
3
+ const TTL_MS = 10 * 60_000;
4
+ const SWEEP_MS = 60_000;
5
+ const MAX_LIVE = 500;
6
+
7
+ const live = new Map<string, number>();
8
+ let sweeper: ReturnType<typeof setInterval> | null = null;
9
+
10
+ function sweep(now = Date.now()): void {
11
+ for (const [nonce, expiresAt] of live) if (expiresAt <= now) live.delete(nonce);
12
+ }
13
+
14
+ function ensureSweeper(): void {
15
+ if (sweeper !== null) return;
16
+ sweeper = setInterval(sweep, SWEEP_MS);
17
+ sweeper.unref?.();
18
+ }
19
+
20
+ export function mintNonce(now = Date.now()): string {
21
+ sweep(now);
22
+ while (live.size >= MAX_LIVE) {
23
+ const oldest = live.keys().next();
24
+ if (oldest.done) break;
25
+ live.delete(oldest.value);
26
+ }
27
+ const nonce = generateSiweNonce();
28
+ live.set(nonce, now + TTL_MS);
29
+ ensureSweeper();
30
+ return nonce;
31
+ }
32
+
33
+ export function takeNonce(nonce: string, now = Date.now()): boolean {
34
+ const expiresAt = live.get(nonce);
35
+ if (expiresAt === undefined) return false;
36
+ live.delete(nonce);
37
+ return expiresAt > now;
38
+ }
39
+
40
+ export function nonceCount(): number {
41
+ sweep();
42
+ return live.size;
43
+ }
@@ -0,0 +1,89 @@
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
+ sendJson,
9
+ } from './api-http.js';
10
+ import { mintNonce, takeNonce } from './siwe-nonces.js';
11
+ import { verifySiweLogin } from './siwe-auth.js';
12
+ import { signSession } from './session.js';
13
+ import { sessionTtlFromEnv } from './session-config.js';
14
+ import { ensureUserWithProject } from '../db/projects.js';
15
+
16
+ const NONCE_PATH = '/auth/siwe/nonce';
17
+ const VERIFY_PATH = '/auth/siwe/verify';
18
+
19
+ export interface SiweRouteDeps {
20
+ ensureUser: (address: string) => Promise<string>;
21
+ }
22
+
23
+ const defaultDeps: SiweRouteDeps = { ensureUser: ensureUserWithProject };
24
+
25
+ async function handleVerify(
26
+ req: IncomingMessage,
27
+ res: ServerResponse,
28
+ secret: string,
29
+ deps: SiweRouteDeps,
30
+ ): Promise<void> {
31
+ const body = await readJsonBody(req);
32
+ const message = bodyField(body, 'message');
33
+ const signature = bodyField(body, 'signature');
34
+ if (typeof message !== 'string' || typeof signature !== 'string') {
35
+ sendJson(req, res, 400, { error: 'message and signature are required' });
36
+ return;
37
+ }
38
+ const address = await verifySiweLogin({ message, signature }, { takeNonce });
39
+ const userId = await deps.ensureUser(address);
40
+ log.info({ userId, address }, 'siwe: session issued');
41
+ const session = signSession({ subject: address, agentIds: [] }, secret, {
42
+ ttlSec: sessionTtlFromEnv(),
43
+ });
44
+ sendJson(req, res, 200, { session, address });
45
+ }
46
+
47
+ const ROUTES: Record<string, string> = {
48
+ [NONCE_PATH]: 'GET',
49
+ [VERIFY_PATH]: 'POST',
50
+ };
51
+
52
+ function refused(
53
+ req: IncomingMessage,
54
+ res: ServerResponse,
55
+ path: string,
56
+ ): boolean {
57
+ if (req.method === 'OPTIONS') {
58
+ res.writeHead(204, cors(req)).end();
59
+ return true;
60
+ }
61
+ if (req.method !== ROUTES[path]) {
62
+ sendJson(req, res, 405, { error: 'method not allowed' });
63
+ return true;
64
+ }
65
+ return false;
66
+ }
67
+
68
+ export function handleSiweAuthRequest(
69
+ req: IncomingMessage,
70
+ res: ServerResponse,
71
+ deps: SiweRouteDeps = defaultDeps,
72
+ ): boolean {
73
+ const path = (req.url ?? '').split('?')[0] ?? '';
74
+ if (ROUTES[path] === undefined) return false;
75
+ if (refused(req, res, path)) return true;
76
+ const secret = process.env.METRO_SESSION_SECRET?.trim() ?? '';
77
+ if (secret === '') {
78
+ sendJson(req, res, 503, { error: 'sign-in is not configured' });
79
+ return true;
80
+ }
81
+ if (path === NONCE_PATH) {
82
+ sendJson(req, res, 200, { nonce: mintNonce() });
83
+ return true;
84
+ }
85
+ handleVerify(req, res, secret, deps).catch((err: unknown) => {
86
+ apiFailure(req, res, err, 'siwe');
87
+ });
88
+ return true;
89
+ }
@@ -0,0 +1,97 @@
1
+ import { watch, type FSWatcher } from 'node:fs';
2
+ import { join, parse as parsePath } from 'node:path';
3
+ import { errMsg, log } from './log.js';
4
+ import type { Pending } from './protocol.js';
5
+
6
+ export const RESTART_BACKOFFS_MS = [1_000, 5_000, 30_000] as const;
7
+ export const MAX_CONSECUTIVE_FAILS = 5;
8
+ export const HOT_RELOAD_DEBOUNCE_MS = 300;
9
+ export const TRAIN_EXT = /\.(ts|js|mjs)$/;
10
+
11
+ export interface TrainState {
12
+ name: string;
13
+ path: string;
14
+ proc: ReturnType<typeof Bun.spawn> | null;
15
+ pending: Map<string, Pending>;
16
+ buf: string;
17
+ errBuf: string;
18
+ failCount: number;
19
+ restartTimer: ReturnType<typeof setTimeout> | null;
20
+ startedAt: string | null;
21
+ stopped: boolean;
22
+ }
23
+
24
+ export function startWatcher(
25
+ dir: string,
26
+ timers: Map<string, ReturnType<typeof setTimeout>>,
27
+ onChange: (name: string, path: string) => void,
28
+ ): FSWatcher | null {
29
+ try {
30
+ const w = watch(dir, (_event, filename) => {
31
+ if (!filename) return;
32
+ const base = filename;
33
+ if (!TRAIN_EXT.test(base) || base.startsWith('_') || base.startsWith('.'))
34
+ return;
35
+ const name = parsePath(base).name;
36
+ const existing = timers.get(name);
37
+ if (existing) clearTimeout(existing);
38
+ timers.set(
39
+ name,
40
+ setTimeout(() => {
41
+ timers.delete(name);
42
+ onChange(name, join(dir, base));
43
+ }, HOT_RELOAD_DEBOUNCE_MS),
44
+ );
45
+ });
46
+ w.on('error', (err) => {
47
+ log.warn({ err: errMsg(err) }, 'train hot-reload: watcher error');
48
+ });
49
+ log.info({ dir }, 'train hot-reload: watching');
50
+ return w;
51
+ } catch (err) {
52
+ log.warn(
53
+ { err: errMsg(err) },
54
+ 'train hot-reload: watch unavailable (on-demand restart only)',
55
+ );
56
+ return null;
57
+ }
58
+ }
59
+
60
+ export function killGracefully(
61
+ proc: ReturnType<typeof Bun.spawn> | null,
62
+ ): Promise<unknown> | null {
63
+ if (proc?.exitCode !== null) return null;
64
+ try {
65
+ proc.kill('SIGTERM');
66
+ } catch {
67
+ }
68
+ const grace = setTimeout(() => {
69
+ try {
70
+ proc.kill('SIGKILL');
71
+ } catch {
72
+ }
73
+ }, 2_000);
74
+ return proc.exited.finally(() => {
75
+ clearTimeout(grace);
76
+ });
77
+ }
78
+
79
+ export async function pumpStream(
80
+ stream: unknown,
81
+ name: string,
82
+ kind: 'stdout' | 'stderr',
83
+ onChunk: (s: string) => void,
84
+ ): Promise<void> {
85
+ if (!stream || typeof stream === 'number') return;
86
+ const reader = (stream as ReadableStream<Uint8Array>).getReader();
87
+ const dec = new TextDecoder();
88
+ try {
89
+ while (true) {
90
+ const { value, done } = await reader.read();
91
+ if (done) break;
92
+ onChunk(dec.decode(value, { stream: true }));
93
+ }
94
+ } catch (err) {
95
+ log.debug({ name, err: errMsg(err) }, `train: ${kind} pump ended`);
96
+ }
97
+ }