@stage-labs/metro 0.1.0-beta.7 → 0.1.0-beta.70

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 (266) hide show
  1. package/dist/api.js +14 -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 +101 -0
  8. package/dist/control.js +74 -0
  9. package/dist/hold.js +197 -0
  10. package/dist/local.js +93 -0
  11. package/dist/node-name.js +51 -0
  12. package/dist/plugin.js +80 -0
  13. package/dist/runtime-install.js +84 -0
  14. package/dist/runtime.js +70 -0
  15. package/dist/serve.js +147 -0
  16. package/dist/service.js +224 -0
  17. package/dist/tail.js +119 -0
  18. package/dist/update.js +45 -0
  19. package/dist/version.js +97 -0
  20. package/package.json +14 -25
  21. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  22. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  23. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  24. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  25. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  26. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  27. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  28. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  29. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  30. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  31. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  32. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  33. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  34. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  35. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  36. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  37. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  38. package/runtime/node_modules/@metro-labs/mcp/package.json +64 -0
  39. package/runtime/node_modules/@metro-labs/mcp/src/channels/addressed.ts +25 -0
  40. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +377 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +302 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +95 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +128 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +280 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +204 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/bundle-api.ts +111 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-api.ts +97 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-files.ts +329 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +39 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +307 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +98 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +70 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +330 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/control-api.ts +64 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +120 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +384 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-key.ts +33 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-registry.ts +13 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-routes.ts +54 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +7 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/is-record.ts +2 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-cli-api.ts +63 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +184 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-owner.ts +14 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/machine-api.ts +61 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +32 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +159 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +117 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +109 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +226 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/daemon/relay.ts +295 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/daemon/return-to.ts +21 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-deps.ts +82 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/daemon/server-types.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/daemon/servers-api.ts +80 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +29 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +61 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/daemon/signed-identity.ts +36 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +320 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-api.ts +71 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-tickets.ts +40 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-ws.ts +114 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +352 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/daemon/update-api.ts +116 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +294 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-api.ts +78 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-types.ts +12 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/daemon/version.ts +2 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +19 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/db/address.ts +6 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +43 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +32 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +144 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-relay.ts +39 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +69 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +329 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +155 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/db/local-connectors.ts +277 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +253 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +33 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/db/servers.ts +83 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/db/stations.ts +12 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/db/vault.ts +109 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +141 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +216 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  133. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  134. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  135. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  136. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +191 -0
  137. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  138. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  139. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  140. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +47 -0
  141. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  142. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  143. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  145. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +215 -0
  146. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +400 -0
  147. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  148. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +237 -0
  149. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  150. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +60 -0
  151. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  152. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  153. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  154. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  155. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +184 -0
  156. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  157. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  158. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  159. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  160. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  161. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  162. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  163. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  164. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  165. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  166. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  167. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  168. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  169. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  170. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  171. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  172. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  173. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  174. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  175. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  176. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  177. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  178. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  179. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  180. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  181. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  182. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  183. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  184. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  185. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  186. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  187. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  188. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  189. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  190. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  191. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  192. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  193. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  194. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  195. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  196. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  197. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  198. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  199. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  200. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +327 -0
  201. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  202. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  203. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  204. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  205. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  206. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  208. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  209. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  210. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  211. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  212. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  213. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  214. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  215. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  216. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  217. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  218. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  219. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  220. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  221. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  222. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  223. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  224. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  225. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  226. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  227. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  228. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  234. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  235. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  236. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  237. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  238. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  239. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  240. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  241. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  242. package/runtime/runtime.json +3 -0
  243. package/runtime/stations.json +36 -0
  244. package/runtime/trains/.keep +0 -0
  245. package/LICENSE +0 -21
  246. package/README.md +0 -193
  247. package/dist/cache.js +0 -75
  248. package/dist/cli/actions.js +0 -139
  249. package/dist/cli/config.js +0 -185
  250. package/dist/cli/index.js +0 -159
  251. package/dist/cli/skill.js +0 -62
  252. package/dist/cli/util.js +0 -72
  253. package/dist/codex-rc.js +0 -236
  254. package/dist/dispatcher.js +0 -95
  255. package/dist/history.js +0 -84
  256. package/dist/ipc.js +0 -72
  257. package/dist/log.js +0 -15
  258. package/dist/paths.js +0 -68
  259. package/dist/stations/discord.js +0 -200
  260. package/dist/stations/index.js +0 -73
  261. package/dist/stations/telegram-md.js +0 -34
  262. package/dist/stations/telegram-upload.js +0 -113
  263. package/dist/stations/telegram.js +0 -213
  264. package/docs/agents.md +0 -185
  265. package/docs/uri-scheme.md +0 -71
  266. package/skills/metro/SKILL.md +0 -241
@@ -0,0 +1,109 @@
1
+ import { and, asc, eq } from 'drizzle-orm';
2
+ import { ApiError } from '../daemon/api-error.js';
3
+ import { isRecord } from '../daemon/is-record.js';
4
+ import { getDb } from './client.js';
5
+ import { AGENT_NAME_RE, parseId } from './ids.js';
6
+ import { vaults } from './schema.js';
7
+ import { STATIONS } from './stations.js';
8
+ import { normalizeAddress } from './address.js';
9
+
10
+ export class VaultError extends ApiError {}
11
+
12
+ import { ENVELOPE_MAX, type VaultBundle, type VaultEntry } from '../daemon/vault-types.js';
13
+
14
+ export interface VaultInput {
15
+ name: string;
16
+ stations: string[];
17
+ envelope: Record<string, unknown>;
18
+ }
19
+ const STATION_NAMES = new Set<string>(STATIONS);
20
+
21
+ const missing = (): VaultError => new VaultError('no such bundle', 404);
22
+
23
+ function ownerOf(subject: string): string {
24
+ const address = normalizeAddress(subject);
25
+ if (address === null) throw new VaultError('the vault needs a vault identity', 403);
26
+ return address;
27
+ }
28
+
29
+ function stationsOf(raw: unknown): string[] {
30
+ if (!Array.isArray(raw)) throw new VaultError('stations must be a list of station names', 400);
31
+ const list = raw.map((s: unknown) => (typeof s === 'string' ? s : ''));
32
+ const bad = list.find((s) => !STATION_NAMES.has(s));
33
+ if (bad !== undefined) throw new VaultError(`'${bad}' is not a station`, 400);
34
+ return [...new Set(list)];
35
+ }
36
+
37
+ function envelopeOf(raw: unknown, id: string): Record<string, unknown> {
38
+ if (!isRecord(raw) || raw.v !== 1 || raw.agentId !== id)
39
+ throw new VaultError('envelope must be a v1 envelope for this agent', 400);
40
+ const key = raw.key;
41
+ if (!isRecord(key) || normalizeAddress(typeof key.recipient === 'string' ? key.recipient : '') === null)
42
+ throw new VaultError('envelope must name the wallet it was sealed to', 400);
43
+ if (typeof raw.ciphertext !== 'string' || typeof raw.nonce !== 'string')
44
+ throw new VaultError('envelope carries no ciphertext', 400);
45
+ if (JSON.stringify(raw).length > ENVELOPE_MAX)
46
+ throw new VaultError(`envelope is over ${String(ENVELOPE_MAX)} bytes`, 413);
47
+ return raw;
48
+ }
49
+
50
+ export function parseVaultInput(subject: string, id: string, body: unknown): VaultInput & { owner: string } {
51
+ const owner = ownerOf(subject);
52
+ if (parseId(id) === null) throw missing();
53
+ if (!isRecord(body)) throw new VaultError('body must be an object', 400);
54
+ const name = typeof body.name === 'string' ? body.name.trim() : '';
55
+ if (!AGENT_NAME_RE.test(name)) throw new VaultError('name is not an agent name', 400);
56
+ return { owner, name, stations: stationsOf(body.stations), envelope: envelopeOf(body.envelope, id) };
57
+ }
58
+
59
+ const entryOf = (row: { id: string; name: string; stations: unknown; syncedAt: string }): VaultEntry => ({
60
+ id: row.id,
61
+ name: row.name,
62
+ stations: Array.isArray(row.stations) ? row.stations.filter((s): s is string => typeof s === 'string') : [],
63
+ syncedAt: row.syncedAt,
64
+ });
65
+
66
+ export async function listVaultForOwner(subject: string): Promise<VaultEntry[]> {
67
+ const owner = ownerOf(subject);
68
+ const rows = await getDb()
69
+ .select({ id: vaults.id, name: vaults.name, stations: vaults.stations, syncedAt: vaults.syncedAt })
70
+ .from(vaults)
71
+ .where(eq(vaults.owner, owner))
72
+ .orderBy(asc(vaults.name));
73
+ return rows.map(entryOf);
74
+ }
75
+
76
+ export async function putVaultForOwner(subject: string, id: string, body: unknown): Promise<VaultEntry> {
77
+ const input = parseVaultInput(subject, id, body);
78
+ const syncedAt = new Date().toISOString();
79
+ const values = { id, owner: input.owner, name: input.name, stations: input.stations, envelope: input.envelope, syncedAt };
80
+ const db = getDb();
81
+ const held = await db.select({ owner: vaults.owner }).from(vaults).where(eq(vaults.id, id));
82
+ if (held[0] !== undefined && held[0].owner !== input.owner) throw missing();
83
+ await db
84
+ .insert(vaults)
85
+ .values(values)
86
+ .onConflictDoUpdate({ target: vaults.id, set: { name: input.name, stations: input.stations, envelope: input.envelope, syncedAt } });
87
+ return entryOf(values);
88
+ }
89
+
90
+ export async function getVaultForOwner(subject: string, id: string): Promise<VaultBundle> {
91
+ const owner = ownerOf(subject);
92
+ if (parseId(id) === null) throw missing();
93
+ const rows = await getDb().select().from(vaults).where(and(eq(vaults.id, id), eq(vaults.owner, owner)));
94
+ const row = rows[0];
95
+ if (row === undefined) throw missing();
96
+ return { ...entryOf(row), envelope: isRecord(row.envelope) ? row.envelope : {} };
97
+ }
98
+
99
+ export async function deleteVaultForOwner(subject: string, id: string): Promise<{ id: string; name: string }> {
100
+ const owner = ownerOf(subject);
101
+ if (parseId(id) === null) throw missing();
102
+ const gone = await getDb()
103
+ .delete(vaults)
104
+ .where(and(eq(vaults.id, id), eq(vaults.owner, owner)))
105
+ .returning({ id: vaults.id, name: vaults.name });
106
+ const row = gone[0];
107
+ if (row === undefined) throw missing();
108
+ return row;
109
+ }
@@ -0,0 +1,141 @@
1
+ import { forwardTrainCall } from '../daemon/train-call.js';
2
+ import {
3
+ accountStationNames,
4
+ stationByName,
5
+ } from '../stations/registry.js';
6
+ import { listEndpoints } from '../daemon/tunnel.js';
7
+ import { hookUrl } from '../stations/attach.js';
8
+ import { agentIdForAccount, knownAccounts, type KnownAccount } from '../db/agent-map.js';
9
+ import { stationRunsHere } from '../db/materialize.js';
10
+ import { type StationName } from '../db/stations.js';
11
+
12
+ const accountId = (acc: unknown): string | undefined => {
13
+ const id = (acc as { id?: unknown }).id;
14
+ return typeof id === 'string' ? id : undefined;
15
+ };
16
+
17
+ const asRecord = (acc: unknown): Record<string, unknown> | undefined =>
18
+ typeof acc === 'object' && acc !== null && !Array.isArray(acc)
19
+ ? (acc as Record<string, unknown>)
20
+ : undefined;
21
+
22
+ function withAgentId(station: string, acc: unknown): unknown {
23
+ const rec = asRecord(acc);
24
+ if (rec === undefined) return acc;
25
+ const id = typeof rec.id === 'string' ? rec.id : undefined;
26
+ if (id === undefined) return acc;
27
+ const agentId = agentIdForAccount(station, id);
28
+ return agentId === undefined ? acc : { ...rec, agentId };
29
+ }
30
+
31
+ export function attachAgentIds(
32
+ byStation: Record<string, unknown[]>,
33
+ ): Record<string, unknown[]> {
34
+ const out: Record<string, unknown[]> = {};
35
+ for (const [station, list] of Object.entries(byStation))
36
+ out[station] = list.map((acc) => withAgentId(station, acc));
37
+ return out;
38
+ }
39
+
40
+ export function scopeAccountsByAgent(
41
+ byStation: Record<string, unknown[]>,
42
+ allowed: Set<string>,
43
+ ): Record<string, unknown[]> {
44
+ const out: Record<string, unknown[]> = {};
45
+ for (const [station, list] of Object.entries(byStation)) {
46
+ out[station] = list.filter((acc) => {
47
+ const id = accountId(acc);
48
+ if (id === undefined) return false;
49
+ const agentId = agentIdForAccount(station, id);
50
+ return agentId !== undefined && allowed.has(agentId);
51
+ });
52
+ }
53
+ return out;
54
+ }
55
+
56
+ const hasTrain = (station: string): boolean =>
57
+ stationByName(station)?.hasTrain !== false;
58
+
59
+ function inCoreAccounts(station: string): unknown[] {
60
+ if (station !== 'webhook') return [];
61
+ return listEndpoints().flatMap((e) =>
62
+ e.secret === undefined || e.webhookId === undefined
63
+ ? []
64
+ : [
65
+ {
66
+ id: e.id,
67
+ handle: `/api/webhooks/${e.webhookId}`,
68
+ endpoint: hookUrl(e.webhookId, e.secret),
69
+ },
70
+ ],
71
+ );
72
+ }
73
+
74
+ export interface ScopedAccounts {
75
+ accounts: Record<string, unknown[]>;
76
+ unavailable: string[];
77
+ }
78
+
79
+ async function liveAccounts(
80
+ station: string,
81
+ ): Promise<{ rows: unknown[]; reachable: boolean }> {
82
+ if (!hasTrain(station))
83
+ return { rows: inCoreAccounts(station), reachable: true };
84
+ if (!stationRunsHere(station as StationName))
85
+ return { rows: [], reachable: true };
86
+ try {
87
+ const resp = await forwardTrainCall(station, 'accounts', {});
88
+ const list = (resp.result as { accounts?: unknown[] } | undefined)?.accounts;
89
+ return { rows: Array.isArray(list) ? list : [], reachable: true };
90
+ } catch {
91
+ return { rows: [], reachable: false };
92
+ }
93
+ }
94
+
95
+ function mergeKnown(
96
+ station: string,
97
+ live: unknown[],
98
+ known: KnownAccount[],
99
+ ): unknown[] {
100
+ const seen = new Set(
101
+ live.flatMap((row) => {
102
+ const id = accountId(row);
103
+ return id === undefined ? [] : [id];
104
+ }),
105
+ );
106
+ const missing = known
107
+ .filter((r) => r.station === station && !seen.has(r.id))
108
+ .map((r) => ({ id: r.id }));
109
+ return [...live, ...missing];
110
+ }
111
+
112
+ async function loadStations(): Promise<ScopedAccounts> {
113
+ const unavailable: string[] = [];
114
+ const accounts: Record<string, unknown[]> = {};
115
+ const known = knownAccounts();
116
+ await Promise.all(
117
+ accountStationNames().map(async (station) => {
118
+ const { rows, reachable } = await liveAccounts(station);
119
+ if (!reachable) unavailable.push(station);
120
+ accounts[station] = hasTrain(station) ? mergeKnown(station, rows, known) : rows;
121
+ }),
122
+ );
123
+ return { accounts, unavailable };
124
+ }
125
+
126
+ export async function gatherAccounts(
127
+ allowedAgents?: Set<string>,
128
+ ): Promise<Record<string, unknown[]>> {
129
+ const { accounts } = await loadStations();
130
+ return allowedAgents ? scopeAccountsByAgent(accounts, allowedAgents) : accounts;
131
+ }
132
+
133
+ export async function gatherAccountsForAgents(
134
+ allowed: Set<string>,
135
+ ): Promise<ScopedAccounts> {
136
+ const { accounts, unavailable } = await loadStations();
137
+ return {
138
+ accounts: attachAgentIds(scopeAccountsByAgent(accounts, allowed)),
139
+ unavailable,
140
+ };
141
+ }
@@ -0,0 +1,216 @@
1
+ import { stationForLine } from '../stations/registry.js';
2
+ import { kindOf, toCanonical } from '../stations/attachments.js';
3
+ import {
4
+ cleanupAttachments,
5
+ resolveAttachments,
6
+ type ResolvedAttachment,
7
+ } from '../stations/attach-resolve.js';
8
+ import type { CanonicalAttachment, ToolResult } from '../stations/types.js';
9
+ import { errResult, makeCtx, ok, okJson, toErr } from './ctx.js';
10
+ import { allowedAgents, currentIdentity } from './request-identity.js';
11
+ import { str } from './str.js';
12
+
13
+ type Station = NonNullable<ReturnType<typeof stationForLine>>;
14
+
15
+ interface MessageArgs {
16
+ line: string;
17
+ a: Record<string, unknown>;
18
+ ctx: ReturnType<typeof makeCtx>;
19
+ onSent?: (messageId: string) => void;
20
+ station: Station;
21
+ }
22
+
23
+ const messageIdOf = (response: { result: unknown }): string | undefined => {
24
+ const id = (response.result as { messageId?: unknown } | null)?.messageId;
25
+ return typeof id === 'string' && id !== '' ? id : undefined;
26
+ };
27
+
28
+ const withId = (text: string, messageId: string | undefined): string =>
29
+ messageId === undefined ? text : `${text} — message_id: ${messageId}`;
30
+
31
+ interface Sent {
32
+ labels: string[];
33
+ messageId?: string;
34
+ }
35
+
36
+ const deliveredLabels = (response: { result: unknown }): string[] => {
37
+ const list = (response.result as { attachments?: unknown } | null)
38
+ ?.attachments;
39
+ return Array.isArray(list)
40
+ ? list.filter((x): x is string => typeof x === 'string')
41
+ : [];
42
+ };
43
+
44
+ function assertDelivered(
45
+ station: Station,
46
+ delivered: string[],
47
+ atts: ResolvedAttachment[],
48
+ ): void {
49
+ if (delivered.length < atts.length)
50
+ throw new Error(
51
+ `${station.name} delivered ${delivered.length} of ${atts.length} attachment(s); ` +
52
+ 'the message text may still have been sent',
53
+ );
54
+ }
55
+
56
+ async function sendNative(
57
+ m: MessageArgs,
58
+ text: string | undefined,
59
+ replyTo: string | undefined,
60
+ atts: ResolvedAttachment[],
61
+ ): Promise<Sent> {
62
+ const { line, ctx, station } = m;
63
+ const labels: string[] = [];
64
+ let messageId: string | undefined;
65
+ if (text) {
66
+ const response = await ctx.call(
67
+ 'send',
68
+ replyTo ? { line, text, replyTo } : { line, text },
69
+ );
70
+ messageId = messageIdOf(response);
71
+ labels.push('text');
72
+ }
73
+ if (!atts.length) return { labels, messageId };
74
+ const delivered = station.sendAttachments
75
+ ? await station.sendAttachments(line, atts, ctx)
76
+ : [];
77
+ assertDelivered(station, delivered, atts);
78
+ labels.push(...delivered);
79
+ return { labels, messageId };
80
+ }
81
+
82
+ async function sendForwarded(
83
+ m: MessageArgs,
84
+ text: string | undefined,
85
+ replyTo: string | undefined,
86
+ atts: ResolvedAttachment[],
87
+ ): Promise<Sent> {
88
+ const { line, ctx, station } = m;
89
+ const args: Record<string, unknown> = { line };
90
+ if (text) args.text = text;
91
+ if (replyTo) args.replyTo = replyTo;
92
+ if (atts.length) args.attachments = atts.map(toCanonical);
93
+ const response = await ctx.call('send', args);
94
+ const messageId = messageIdOf(response);
95
+ const labels: string[] = [];
96
+ if (text) labels.push('text');
97
+ if (!atts.length) return { labels, messageId };
98
+ const delivered = deliveredLabels(response);
99
+ assertDelivered(station, delivered, atts);
100
+ labels.push(...delivered);
101
+ return { labels, messageId };
102
+ }
103
+
104
+ const unsupported = (station: Station, atts: CanonicalAttachment[]): string =>
105
+ `${station.name} cannot send attachments (${atts
106
+ .map((a) => kindOf(a.mime ?? '', a.path ?? a.url ?? a.name ?? ''))
107
+ .join(', ')}); send a link in \`text\` instead`;
108
+
109
+ function noteSent(m: MessageArgs, messageId: string | undefined): void {
110
+ if (messageId !== undefined) m.onSent?.(messageId);
111
+ }
112
+
113
+ async function handleSend(m: MessageArgs): Promise<ToolResult> {
114
+ const text = m.a.text as string | undefined;
115
+ const replyTo = m.a.reply_to as string | undefined;
116
+ const requested = (m.a.attachments as CanonicalAttachment[] | undefined) ?? [];
117
+ if (!text && !requested.length)
118
+ return errResult('send requires `text` or `attachments`');
119
+ if (requested.length && m.station.attachmentMode === 'none')
120
+ return errResult(unsupported(m.station, requested));
121
+ const atts = await resolveAttachments(requested, {
122
+ allowed: allowedAgents(currentIdentity()),
123
+ });
124
+ try {
125
+ const native =
126
+ m.station.attachmentMode === 'native' &&
127
+ typeof m.station.sendAttachments === 'function';
128
+ const sent = native
129
+ ? await sendNative(m, text, replyTo, atts)
130
+ : await sendForwarded(m, text, replyTo, atts);
131
+ if (!sent.labels.length)
132
+ return errResult('send requires `text` or `attachments`');
133
+ noteSent(m, sent.messageId);
134
+ return ok(withId(`sent: ${sent.labels.join(', ')}`, sent.messageId));
135
+ } finally {
136
+ await cleanupAttachments(atts);
137
+ }
138
+ }
139
+
140
+ async function handleRead({ line, a, ctx }: MessageArgs): Promise<ToolResult> {
141
+ const args: Record<string, unknown> = { line };
142
+ if (typeof a.limit === 'number') args.limit = a.limit;
143
+ if (a.before) args.before = str(a.before);
144
+ if (a.since) args.since = str(a.since);
145
+ return okJson(await ctx.call('read', args));
146
+ }
147
+
148
+ type MessageHandler = (m: MessageArgs) => Promise<ToolResult>;
149
+
150
+ interface VerbSpec {
151
+ args: readonly (readonly [string, string])[];
152
+ success: string;
153
+ }
154
+
155
+ const MESSAGE_VERBS: Record<string, VerbSpec> = {
156
+ reply: { args: [['message_id', 'replyTo'], ['text', 'text']], success: 'replied' },
157
+ react: { args: [['message_id', 'messageId'], ['emoji', 'emoji']], success: 'reacted' },
158
+ unreact: {
159
+ args: [['message_id', 'messageId'], ['emoji', 'emoji']],
160
+ success: 'reaction removed',
161
+ },
162
+ edit: { args: [['message_id', 'messageId'], ['text', 'text']], success: 'edited' },
163
+ delete: { args: [['message_id', 'messageId']], success: 'deleted' },
164
+ };
165
+
166
+ function makeVerbHandler(verb: string, spec: VerbSpec): MessageHandler {
167
+ return async ({ line, a, ctx, onSent }) => {
168
+ const payload: Record<string, unknown> = { line };
169
+ for (const [snake, camel] of spec.args) {
170
+ const value = str(a[snake]);
171
+ if (!value) {
172
+ const fields = spec.args.map(([snakeName]) => `\`${snakeName}\``).join(' and ');
173
+ return errResult(`${verb} requires ${fields}`);
174
+ }
175
+ payload[camel] = value;
176
+ }
177
+ const response = await ctx.call(verb, payload);
178
+ const messageId = messageIdOf(response);
179
+ if (verb === 'reply' && messageId !== undefined) onSent?.(messageId);
180
+ return ok(withId(spec.success, messageId));
181
+ };
182
+ }
183
+
184
+ const MESSAGE_HANDLERS: Record<string, MessageHandler> = {
185
+ send: handleSend,
186
+ read: handleRead,
187
+ ...Object.fromEntries(
188
+ Object.entries(MESSAGE_VERBS).map(([verb, spec]) => [verb, makeVerbHandler(verb, spec)]),
189
+ ),
190
+ };
191
+
192
+ export interface ToolHooks {
193
+ onSent?: (messageId: string) => void;
194
+ }
195
+
196
+ export async function dispatchMessageTool(
197
+ name: string,
198
+ a: Record<string, unknown>,
199
+ hooks: ToolHooks = {},
200
+ ): Promise<ToolResult> {
201
+ const line = str(a.line);
202
+ if (!line) return errResult(`${name} requires \`line\``);
203
+ const station = stationForLine(line);
204
+ if (!station || station.messageVerbs.size === 0) {
205
+ return errResult(
206
+ `${station?.name ?? 'these'} lines do not support outbound messaging (send/reply/react/unreact/edit/delete/read).`,
207
+ );
208
+ }
209
+ const handler = MESSAGE_HANDLERS[name];
210
+ if (!handler) return errResult(`unknown tool: ${name}`);
211
+ try {
212
+ return await handler({ line, a, ctx: makeCtx(station.name), station, onSent: hooks.onSent });
213
+ } catch (e) {
214
+ return toErr(name, e);
215
+ }
216
+ }
@@ -0,0 +1,29 @@
1
+ import { eventInScope } from '../db/agent-scope.js';
2
+ import { allowedAgents, type RequestIdentity } from './request-identity.js';
3
+ import { sessionScopeKey } from './session-route.js';
4
+
5
+ export class ChannelOwner {
6
+ private stream: RequestIdentity | undefined;
7
+
8
+ bindStream(identity: RequestIdentity): void {
9
+ this.stream = identity;
10
+ }
11
+
12
+ releaseStream(): void {
13
+ this.stream = undefined;
14
+ }
15
+
16
+ scope(): Set<string> {
17
+ return allowedAgents(this.stream);
18
+ }
19
+
20
+ streamBelongsTo(identity: RequestIdentity): boolean {
21
+ if (this.stream === undefined) return false;
22
+ return sessionScopeKey(this.stream) === sessionScopeKey(identity);
23
+ }
24
+
25
+ inScope(line: string): boolean {
26
+ if (this.stream === undefined) return false;
27
+ return eventInScope(this.scope(), line);
28
+ }
29
+ }
@@ -0,0 +1,62 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { forwardTrainCall } from '../daemon/train-call.js';
3
+ import { errMsg, log } from '../daemon/log.js';
4
+ import { TrainError } from '../daemon/train-error.js';
5
+ import { type ToolContext, type ToolResult } from '../stations/types.js';
6
+
7
+ export async function metroCall(
8
+ train: string,
9
+ action: string,
10
+ args: Record<string, unknown>,
11
+ ): Promise<{ result: unknown }> {
12
+ const line = typeof args.line === 'string' ? args.line : undefined;
13
+ log.info({ train, action, line }, 'metro: dispatching train call');
14
+ let response;
15
+ try {
16
+ response = await forwardTrainCall(train, action, args);
17
+ } catch (err) {
18
+ log.warn(
19
+ { train, action, line, err: errMsg(err) },
20
+ 'metro: train call failed to dispatch',
21
+ );
22
+ throw new TrainError(
23
+ 'metro_call_failed',
24
+ `metro ${action} ${train}: ${errMsg(err)}`,
25
+ );
26
+ }
27
+ if (response.error) {
28
+ log.warn(
29
+ { train, action, line, err: response.error },
30
+ 'metro: train call returned error',
31
+ );
32
+ throw new TrainError(
33
+ 'metro_call_error',
34
+ `metro ${action} ${train}: ${response.error}`,
35
+ );
36
+ }
37
+ log.info({ train, action, line }, 'metro: train call ok');
38
+ return { result: response.result ?? null };
39
+ }
40
+
41
+ export const ok = (text: string): ToolResult => ({
42
+ content: [{ type: 'text', text }],
43
+ });
44
+ export const okJson = (v: unknown): ToolResult => ({
45
+ content: [{ type: 'text', text: JSON.stringify(v, null, 2) }],
46
+ });
47
+ export const errResult = (text: string): ToolResult => ({
48
+ content: [{ type: 'text', text }],
49
+ isError: true,
50
+ });
51
+ export const toErr = (name: string, e: unknown): ToolResult =>
52
+ e instanceof TrainError
53
+ ? errResult(e.message)
54
+ : errResult(`metro ${name} failed: ${String(e)}`);
55
+
56
+ export const makeCtx = (station: string): ToolContext => ({
57
+ call: (action, args) => metroCall(station, action, args),
58
+ ok,
59
+ okJson,
60
+ err: errResult,
61
+ readFile: (path) => readFile(path),
62
+ });
@@ -0,0 +1,91 @@
1
+ import type {
2
+ EventStore,
3
+ EventId,
4
+ StreamId,
5
+ } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
6
+ import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
7
+ import { frameInScope, frameLine } from './frame-scope.js';
8
+
9
+ const EVENT_STORE_MAX = 500;
10
+
11
+ interface StoredEvent {
12
+ eventId: EventId;
13
+ streamId: StreamId;
14
+ message: JSONRPCMessage;
15
+ line: string | undefined;
16
+ owner: Set<string>;
17
+ }
18
+
19
+ export interface EventStoreDeps {
20
+ scopeOf: () => Set<string>;
21
+ max?: number;
22
+ }
23
+
24
+ export interface ScopedReplay {
25
+ send: (eventId: EventId, message: JSONRPCMessage) => Promise<void>;
26
+ scope: Set<string> | undefined;
27
+ onWithheld?: (eventId: EventId, line: string | undefined) => void;
28
+ }
29
+
30
+ const SEP = '_';
31
+
32
+ const encodeEventId = (streamId: StreamId, seq: number): EventId =>
33
+ `${streamId}${SEP}${seq}`;
34
+
35
+ const decodeStreamId = (eventId: EventId): StreamId | undefined => {
36
+ const idx = eventId.lastIndexOf(SEP);
37
+ if (idx <= 0) return undefined;
38
+ return eventId.slice(0, idx);
39
+ };
40
+
41
+ export class BoundedEventStore implements EventStore {
42
+ private readonly max: number;
43
+ private readonly scopeOf: () => Set<string>;
44
+ private readonly events: StoredEvent[] = [];
45
+ private seq = 0;
46
+
47
+ constructor(deps: EventStoreDeps) {
48
+ this.scopeOf = deps.scopeOf;
49
+ this.max = deps.max ?? EVENT_STORE_MAX;
50
+ }
51
+
52
+ storeEvent(streamId: StreamId, message: JSONRPCMessage): Promise<EventId> {
53
+ const eventId = encodeEventId(streamId, ++this.seq);
54
+ this.events.push({
55
+ eventId,
56
+ streamId,
57
+ message,
58
+ line: frameLine(message),
59
+ owner: new Set(this.scopeOf()),
60
+ });
61
+ if (this.events.length > this.max) this.events.shift();
62
+ return Promise.resolve(eventId);
63
+ }
64
+
65
+ getStreamIdForEventId(eventId: EventId): Promise<StreamId | undefined> {
66
+ return Promise.resolve(decodeStreamId(eventId));
67
+ }
68
+
69
+ async replayEventsAfter(
70
+ lastEventId: EventId,
71
+ { send, scope, onWithheld }: ScopedReplay,
72
+ ): Promise<StreamId> {
73
+ const streamId = decodeStreamId(lastEventId);
74
+ if (streamId === undefined) return '';
75
+ const allowed = scope ?? new Set<string>();
76
+ let seen = false;
77
+ for (const e of this.events) {
78
+ if (e.streamId !== streamId) continue;
79
+ if (!seen) {
80
+ if (e.eventId === lastEventId) seen = true;
81
+ continue;
82
+ }
83
+ if (!frameInScope(allowed, e)) {
84
+ onWithheld?.(e.eventId, e.line);
85
+ continue;
86
+ }
87
+ await send(e.eventId, e.message);
88
+ }
89
+ return streamId;
90
+ }
91
+ }
@@ -0,0 +1,21 @@
1
+ import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
2
+ import { eventInScope } from '../db/agent-scope.js';
3
+ import { str } from './str.js';
4
+
5
+ export interface FrameOrigin {
6
+ line: string | undefined;
7
+ owner: Set<string>;
8
+ }
9
+
10
+ export function frameLine(message: JSONRPCMessage): string | undefined {
11
+ const params: unknown = (message as { params?: unknown }).params;
12
+ if (typeof params !== 'object' || params === null) return undefined;
13
+ const meta: unknown = (params as { meta?: unknown }).meta;
14
+ if (typeof meta !== 'object' || meta === null) return undefined;
15
+ return str((meta as { line?: unknown }).line) || undefined;
16
+ }
17
+
18
+ export function frameInScope(scope: Set<string>, origin: FrameOrigin): boolean {
19
+ if (origin.line !== undefined) return eventInScope(scope, origin.line);
20
+ return origin.owner.size > 0 && [...origin.owner].every((id) => scope.has(id));
21
+ }