@stage-labs/metro 0.1.0-beta.15 → 0.1.0-beta.151

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 (327) hide show
  1. package/dist/api.js +9 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +182 -0
  4. package/dist/cli.js +86 -0
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +77 -0
  8. package/dist/mcp-config.js +25 -0
  9. package/dist/node-name.js +71 -0
  10. package/dist/onboarding.js +43 -0
  11. package/dist/provider-flags.js +9 -0
  12. package/dist/route.js +50 -0
  13. package/dist/runtime-install.js +97 -0
  14. package/dist/runtime.js +74 -0
  15. package/dist/serve.js +155 -0
  16. package/dist/service.js +242 -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 -28
  21. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  22. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
  23. package/runtime/marketplace/plugin/.mcp.json +1 -0
  24. package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
  25. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +40 -0
  26. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  27. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  28. package/runtime/marketplace/plugin/orchestrator.md +16 -0
  29. package/runtime/node_modules/@metro-labs/core/package.json +43 -0
  30. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
  31. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  32. package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
  33. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  34. package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
  35. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  36. package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
  37. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  38. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
  39. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
  40. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
  41. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
  46. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  47. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  48. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  49. package/runtime/node_modules/@metro-labs/daemon/package.json +47 -0
  50. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  51. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +39 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +225 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +364 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +185 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +40 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +200 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +30 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +380 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +354 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +263 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +118 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +313 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +157 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +69 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +304 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +299 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +108 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +59 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +101 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +247 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +182 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +148 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +201 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +257 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +353 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +232 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +242 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +186 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +370 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +210 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +58 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +65 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +80 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +48 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +117 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +97 -0
  175. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  176. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  177. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  191. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  192. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  193. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  194. package/runtime/node_modules/@metro-labs/http/package.json +29 -0
  195. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  196. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +106 -0
  197. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  198. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  199. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  200. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +150 -0
  201. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  202. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  203. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  204. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  205. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  206. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  207. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  208. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  209. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  210. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  211. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  212. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  213. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  214. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  215. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  216. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  217. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  218. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  219. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  220. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  221. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  222. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  223. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  224. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  225. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  226. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  227. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  228. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  229. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  230. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  231. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  232. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
  233. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +223 -0
  234. package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
  235. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
  236. package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
  237. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  238. package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
  239. package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
  240. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  241. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  242. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  243. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  244. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  245. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  246. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
  247. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  248. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  249. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
  250. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  251. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  252. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  253. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  254. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  255. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  256. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  257. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  258. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  259. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  260. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  261. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  262. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
  263. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  264. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  265. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  266. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  267. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  268. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  269. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  270. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  271. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  272. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  273. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  274. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  275. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  276. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  277. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  278. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  279. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  280. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  281. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  282. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  283. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  284. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  285. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  286. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  287. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  288. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  289. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  290. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  291. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  292. package/runtime/runtime.json +3 -0
  293. package/runtime/server.ts +1 -0
  294. package/runtime/stations.json +37 -0
  295. package/runtime/trains/.keep +0 -0
  296. package/LICENSE +0 -21
  297. package/README.md +0 -139
  298. package/dist/broker/claims.js +0 -144
  299. package/dist/broker/history-stream.js +0 -149
  300. package/dist/cli/config.js +0 -179
  301. package/dist/cli/index.js +0 -175
  302. package/dist/cli/messenger-api.js +0 -214
  303. package/dist/cli/messenger-transcribe.js +0 -43
  304. package/dist/cli/messenger-uploads.js +0 -116
  305. package/dist/cli/monitor-api.js +0 -205
  306. package/dist/cli/tail.js +0 -95
  307. package/dist/cli/util.js +0 -72
  308. package/dist/cli/webhook.js +0 -181
  309. package/dist/codex-rc/client.js +0 -225
  310. package/dist/codex-rc/protocol.js +0 -38
  311. package/dist/dispatcher/server.js +0 -122
  312. package/dist/dispatcher.js +0 -97
  313. package/dist/history.js +0 -143
  314. package/dist/ipc.js +0 -90
  315. package/dist/lines.js +0 -54
  316. package/dist/local-identity.js +0 -80
  317. package/dist/log.js +0 -15
  318. package/dist/paths.js +0 -114
  319. package/dist/trains/protocol.js +0 -99
  320. package/dist/trains/supervisor.js +0 -210
  321. package/dist/tunnel.js +0 -102
  322. package/docs/broker.md +0 -166
  323. package/docs/monitor.md +0 -241
  324. package/docs/uri-scheme.md +0 -127
  325. package/examples/README.md +0 -32
  326. package/examples/telegram.ts +0 -121
  327. package/skills/metro/SKILL.md +0 -110
@@ -0,0 +1,49 @@
1
+ import { errMsg, log, logFatalSync } from '@metro-labs/core/log';
2
+
3
+ export type CrashKind = 'unhandledRejection' | 'uncaughtException';
4
+
5
+ const seen: Record<CrashKind, number> = {
6
+ unhandledRejection: 0,
7
+ uncaughtException: 0,
8
+ };
9
+
10
+ let ready = false;
11
+ let installed = false;
12
+
13
+ export function markDaemonReady(): void {
14
+ ready = true;
15
+ }
16
+
17
+ export function crashCount(kind: CrashKind): number {
18
+ return seen[kind];
19
+ }
20
+
21
+ function fields(kind: CrashKind, err: unknown): Record<string, unknown> {
22
+ return {
23
+ crash: kind,
24
+ seen: seen[kind],
25
+ err: errMsg(err),
26
+ errType: err instanceof Error ? err.name : typeof err,
27
+ stack: err instanceof Error ? err.stack : undefined,
28
+ };
29
+ }
30
+
31
+ function reportCrash(kind: CrashKind, err: unknown): void {
32
+ seen[kind] += 1;
33
+ if (!ready) {
34
+ logFatalSync(fields(kind, err), 'daemon: failed before it was ready');
35
+ process.exit(1);
36
+ }
37
+ log.error(fields(kind, err), 'daemon: kept running through a crash');
38
+ }
39
+
40
+ export function installCrashGuard(): void {
41
+ if (installed) return;
42
+ installed = true;
43
+ process.on('unhandledRejection', (reason: unknown) => {
44
+ reportCrash('unhandledRejection', reason);
45
+ });
46
+ process.on('uncaughtException', (err: unknown) => {
47
+ reportCrash('uncaughtException', err);
48
+ });
49
+ }
@@ -0,0 +1,30 @@
1
+ import { localOwner, setLocalOwner } from '../agents/file-admin.js';
2
+ import { isOrganizationId } from '@metro-labs/http/workos-token';
3
+ import { log } from '@metro-labs/core/log';
4
+
5
+ const NO_ONE = 'nobody can sign in; restart with metro serve --owner <organization id>';
6
+
7
+ const WALLET = `the owner is a wallet address, and wallets cannot sign in any more; ${NO_ONE}`;
8
+
9
+ function keepsOrganization(wanted: string, stored: string | null): stored is string {
10
+ return wanted !== '' && stored !== null && isOrganizationId(stored) && !isOrganizationId(wanted);
11
+ }
12
+
13
+ function announce(owner: string | null): string | null {
14
+ if (owner === null) log.warn(`local daemon: no owner set, so ${NO_ONE}`);
15
+ else if (!isOrganizationId(owner)) log.warn(`local daemon: ${WALLET}`);
16
+ return owner;
17
+ }
18
+
19
+ export function applyLocalOwner(): string | null {
20
+ const wanted = process.env.METRO_OWNER?.trim() ?? '';
21
+ const stored = localOwner();
22
+ if (keepsOrganization(wanted, stored)) {
23
+ log.info({ owner: stored, ignored: wanted }, 'local daemon: this machine belongs to an organization, so the wallet in --owner is ignored');
24
+ return stored;
25
+ }
26
+ if (wanted === '') return announce(stored);
27
+ const owner = setLocalOwner(wanted);
28
+ log.info({ owner }, 'local daemon: owner set from METRO_OWNER');
29
+ return announce(owner);
30
+ }
@@ -0,0 +1,106 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import {
3
+ closeSync,
4
+ mkdirSync,
5
+ openSync,
6
+ readFileSync,
7
+ unlinkSync,
8
+ writeSync,
9
+ } from 'node:fs';
10
+ import { homedir } from 'node:os';
11
+ import { join } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { log } from '@metro-labs/core/log';
14
+
15
+ export const STATE_DIR =
16
+ process.env.METRO_STATE_DIR ?? join(homedir(), '.cache', 'metro');
17
+ mkdirSync(STATE_DIR, { recursive: true });
18
+
19
+
20
+ export function trainsDir(): string {
21
+ const env = process.env.METRO_TRAINS_DIR?.trim();
22
+ if (env !== undefined && env !== '') return env;
23
+ return fileURLToPath(new URL('../../trains', import.meta.url));
24
+ }
25
+
26
+ function readLockPid(lockFile: string): number {
27
+ try {
28
+ return Number(readFileSync(lockFile, 'utf8').trim());
29
+ } catch {
30
+ return NaN;
31
+ }
32
+ }
33
+
34
+ function namesMetro(text: string): boolean {
35
+ return text.includes('server.ts') || text.includes('metro');
36
+ }
37
+
38
+ function holderIsMetro(pid: number): boolean {
39
+ try {
40
+ return namesMetro(readFileSync(`/proc/${pid}/cmdline`, 'utf8'));
41
+ } catch {
42
+ return holderCommand(pid);
43
+ }
44
+ }
45
+
46
+ function holderCommand(pid: number): boolean {
47
+ try {
48
+ return namesMetro(
49
+ execFileSync('ps', ['-o', 'command=', '-p', String(pid)], {
50
+ encoding: 'utf8',
51
+ stdio: ['ignore', 'pipe', 'ignore'],
52
+ }),
53
+ );
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ export function lockHeldByLiveDaemon(pid: number): boolean {
60
+ if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
61
+ try {
62
+ process.kill(pid, 0);
63
+ } catch {
64
+ return false;
65
+ }
66
+ return holderIsMetro(pid);
67
+ }
68
+
69
+ function writeOwnLock(lockFile: string): void {
70
+ const fd = openSync(lockFile, 'wx');
71
+ writeSync(fd, String(process.pid));
72
+ closeSync(fd);
73
+ process.on('exit', () => {
74
+ try {
75
+ if (readFileSync(lockFile, 'utf8').trim() === String(process.pid))
76
+ unlinkSync(lockFile);
77
+ } catch {
78
+ }
79
+ });
80
+ }
81
+
82
+ export function acquireLock(lockFile: string): void {
83
+ for (let attempt = 0; attempt < 5; attempt++) {
84
+ try {
85
+ writeOwnLock(lockFile);
86
+ return;
87
+ } catch (err) {
88
+ if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;
89
+ const pid = readLockPid(lockFile);
90
+ if (lockHeldByLiveDaemon(pid)) {
91
+ log.info(
92
+ { pid },
93
+ 'a healthy `metro` daemon is already running; exiting (no second dispatcher)',
94
+ );
95
+ process.exit(0);
96
+ }
97
+ try {
98
+ unlinkSync(lockFile);
99
+ } catch {
100
+ }
101
+ }
102
+ }
103
+ throw new Error(
104
+ `metro: could not acquire dispatcher lock (${lockFile}) after retries`,
105
+ );
106
+ }
@@ -0,0 +1,25 @@
1
+ import { str } from '@metro-labs/core/str';
2
+
3
+ export type Addressed = 'direct' | 'mention' | 'reply';
4
+
5
+ interface AddressFacts {
6
+ isPrivate?: unknown;
7
+ mentionsSelf?: unknown;
8
+ replyToSelf?: unknown;
9
+ }
10
+
11
+ export function addressedBy(facts: AddressFacts, replyTo: string, sent: ReadonlySet<string>): Addressed | undefined {
12
+ if (facts.isPrivate === true) return 'direct';
13
+ if (facts.mentionsSelf === true) return 'mention';
14
+ if (facts.replyToSelf === true || (replyTo !== '' && sent.has(replyTo))) return 'reply';
15
+ return undefined;
16
+ }
17
+
18
+ export function replyMeta(ev: Record<string, unknown>, sent: ReadonlySet<string>): Record<string, string> {
19
+ const replyTo = str(ev.replyTo);
20
+ const addressed = addressedBy(ev, replyTo, sent);
21
+ return {
22
+ ...(replyTo ? { reply_to: replyTo } : {}),
23
+ ...(addressed === undefined ? {} : { addressed }),
24
+ };
25
+ }
@@ -0,0 +1,24 @@
1
+ import { accountFromLine, agentIdForAccount } from '../agents/map.js';
2
+
3
+ const accountStrippedLine = (line: string): string => {
4
+ const parts = line.split('/');
5
+ if (parts.length < 5) return line;
6
+ return [parts[0], parts[1], parts[2], ...parts.slice(4)].join('/');
7
+ };
8
+
9
+ export const dedupeOwner = (line: string): string => {
10
+ const acct = accountFromLine(line);
11
+ if (!acct) return 'unowned';
12
+ const agentId = agentIdForAccount(acct.station, acct.accountId);
13
+ return agentId === undefined
14
+ ? `account:${acct.accountId}`
15
+ : `agent:${agentId}`;
16
+ };
17
+
18
+ export const dedupeKey = (
19
+ station: string,
20
+ line: string,
21
+ kind: string,
22
+ messageId: string,
23
+ ): string =>
24
+ `${dedupeOwner(line)} ${station} ${accountStrippedLine(line)} ${kind} ${messageId}`;
@@ -0,0 +1,380 @@
1
+ import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { str } from '@metro-labs/core/str';
3
+ import { dedupeKey } from './dedupe.js';
4
+ import {
5
+ buildMediaFailureNote,
6
+ buildMediaNote,
7
+ type MediaNote,
8
+ type SavedMedia,
9
+ } from './media-note.js';
10
+ import { buildWebhookNote } from './webhook-note.js';
11
+ import { replyMeta } from './addressed.js';
12
+ import {
13
+ capSet,
14
+ displayNameMeta,
15
+ senderMeta,
16
+ tsMeta,
17
+ takeMediaCtx,
18
+ type MediaCtx,
19
+ type PendingAtt,
20
+ type PendingMsg,
21
+ } from './pending.js';
22
+
23
+ interface InboundDeps {
24
+ mcp: Server;
25
+ log: (...a: unknown[]) => void;
26
+ getStations: () => Set<string>;
27
+ senderAllowed: (from: string, line: string) => boolean;
28
+ }
29
+
30
+ const ATTACH_TIMEOUT_MS = 15_000;
31
+ const DEDUPE_TTL_MS = 30_000;
32
+ const DEDUPE_MAX = 2_000;
33
+ const ALLOWED_LINES_MAX = 2_000;
34
+ const PENDING_PERMISSIONS_MAX = 500;
35
+ const SENT_IDS_MAX = 2_000;
36
+
37
+ const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i;
38
+
39
+ const shortId = (id: string): string =>
40
+ id.length > 10 ? `${id.slice(0, 6)}…` : id;
41
+
42
+ export class InboundRelay {
43
+ private readonly deps: InboundDeps;
44
+ private readonly pendingAttachments = new Map<string, PendingMsg>();
45
+ private readonly seenEvents = new Map<string, number>();
46
+ private readonly allowedLines = new Set<string>();
47
+ private readonly pendingPermissions = new Set<string>();
48
+ private readonly sentIds = new Set<string>();
49
+ private lastLine: string | undefined;
50
+
51
+ constructor(deps: InboundDeps) {
52
+ this.deps = deps;
53
+ }
54
+
55
+ get knownLine(): string | undefined {
56
+ return this.lastLine;
57
+ }
58
+
59
+ registerPermission(requestId: string): void {
60
+ this.pendingPermissions.add(requestId);
61
+ capSet(this.pendingPermissions, PENDING_PERMISSIONS_MAX);
62
+ }
63
+
64
+ noteSent(messageId: string): void {
65
+ if (!messageId) return;
66
+ this.sentIds.add(messageId);
67
+ capSet(this.sentIds, SENT_IDS_MAX);
68
+ }
69
+
70
+ private notify(method: string, params: Record<string, unknown>): Promise<void> {
71
+ return this.deps.mcp.notification({ method, params });
72
+ }
73
+
74
+ private isDuplicate(
75
+ station: string,
76
+ line: string,
77
+ kind: string,
78
+ messageId: string,
79
+ ): boolean {
80
+ if (!messageId) return false;
81
+ const key = dedupeKey(station, line, kind, messageId);
82
+ const now = Date.now();
83
+ if (this.seenEvents.size >= DEDUPE_MAX) {
84
+ for (const [k, t] of this.seenEvents) {
85
+ if (now - t > DEDUPE_TTL_MS) this.seenEvents.delete(k);
86
+ }
87
+ }
88
+ const prev = this.seenEvents.get(key);
89
+ if (prev !== undefined && now - prev < DEDUPE_TTL_MS) return true;
90
+ this.seenEvents.set(key, now);
91
+ return false;
92
+ }
93
+
94
+ private surfaceNote(
95
+ ctx: MediaCtx,
96
+ p: SavedMedia,
97
+ note: MediaNote,
98
+ extra: Record<string, unknown>,
99
+ ): Promise<void> {
100
+ return this.notify('notifications/claude/channel', {
101
+ content: note.content,
102
+ meta: {
103
+ line: ctx.line,
104
+ from: ctx.from,
105
+ station: ctx.station,
106
+ ...senderMeta(ctx),
107
+ kind: note.kind,
108
+ mime: p.mime ?? '',
109
+ name: note.name,
110
+ ...extra,
111
+ },
112
+ });
113
+ }
114
+
115
+ private async surfaceMedia(ctx: MediaCtx, p: SavedMedia): Promise<void> {
116
+ const note = await buildMediaNote(p, ctx.text ?? '');
117
+ if (!note) return;
118
+ await this.surfaceNote(ctx, p, note, {
119
+ ...(p.url ? { url: p.url } : {}),
120
+ local_path: note.path,
121
+ });
122
+ }
123
+
124
+ private surfaceMediaFailure(ctx: MediaCtx, p: SavedMedia): Promise<void> {
125
+ return this.surfaceNote(ctx, p, buildMediaFailureNote(p, ctx.text ?? ''), {
126
+ attachment_error: p.reason ?? '',
127
+ });
128
+ }
129
+
130
+ private async flushPendingFallback(id: string): Promise<void> {
131
+ const e = this.pendingAttachments.get(id);
132
+ if (!e) return;
133
+ this.pendingAttachments.delete(id);
134
+ const missing = e.attachments.filter((_, i) => !e.saved.has(i));
135
+ if (!missing.length) return;
136
+ const names = missing
137
+ .map((a) => a.name ?? a.kind ?? 'attachment')
138
+ .join(', ');
139
+ await this.notify('notifications/claude/channel', {
140
+ content:
141
+ (e.text ? `${e.text}\n` : '') +
142
+ `[attachment(s) could not be fetched in time: ${names}]`,
143
+ meta: {
144
+ line: e.line,
145
+ from: e.from,
146
+ station: e.station,
147
+ ...tsMeta(e.ts),
148
+ message_id: e.messageId,
149
+ line_name: e.lineName,
150
+ from_name: e.fromName,
151
+ ...displayNameMeta(e.fromDisplayName),
152
+ },
153
+ });
154
+ }
155
+
156
+ private mediaCtxFor(
157
+ ev: Record<string, unknown>,
158
+ payload: SavedMedia,
159
+ ): MediaCtx | null {
160
+ const forId = str(payload.attachmentFor);
161
+ const buf = forId ? this.pendingAttachments.get(forId) : undefined;
162
+ if (buf) {
163
+ buf.saved.add(typeof payload.index === 'number' ? payload.index : 0);
164
+ const ctx = takeMediaCtx(buf);
165
+ if (buf.saved.size >= buf.attachments.length) {
166
+ clearTimeout(buf.timer);
167
+ this.pendingAttachments.delete(forId);
168
+ }
169
+ return ctx;
170
+ }
171
+ const line = str(ev.line);
172
+ if (!line || !this.allowedLines.has(line)) return null;
173
+ return {
174
+ line,
175
+ from: 'metro://attachment',
176
+ station: str(ev.station) || 'xmtp',
177
+ ts: str(ev.ts),
178
+ };
179
+ }
180
+
181
+ private async routeAttachment(ev: Record<string, unknown>): Promise<boolean> {
182
+ const p = ev.payload as SavedMedia | undefined;
183
+ if (!p) return false;
184
+ const type = p.contentType;
185
+ if (type !== 'attachmentSaved' && type !== 'attachmentFailed') return false;
186
+ const ctx = this.mediaCtxFor(ev, p);
187
+ if (!ctx) return true;
188
+ if (type === 'attachmentFailed') await this.surfaceMediaFailure(ctx, p);
189
+ else await this.surfaceMedia(ctx, p);
190
+ return true;
191
+ }
192
+
193
+ private bufferAttachments(
194
+ ev: Record<string, unknown>,
195
+ base: { line: string; from: string; station: string; text: string },
196
+ atts: PendingAtt[],
197
+ ): void {
198
+ const id = str(ev.id);
199
+ if (!id) return;
200
+ const existing = this.pendingAttachments.get(id);
201
+ if (existing) clearTimeout(existing.timer);
202
+ this.pendingAttachments.set(id, {
203
+ ...base,
204
+ ts: str(ev.ts),
205
+ messageId: str(ev.messageId),
206
+ lineName: str(ev.lineName),
207
+ fromName: str(ev.fromName),
208
+ fromDisplayName: str(ev.fromDisplayName),
209
+ attachments: atts.map((a) => ({ kind: a.kind, name: a.name })),
210
+ saved: new Set<number>(),
211
+ timer: setTimeout(() => {
212
+ this.flushPendingFallback(id).catch((err: unknown) => {
213
+ this.deps.log(
214
+ 'inbound: attachment fallback not delivered',
215
+ 'id',
216
+ id,
217
+ 'line',
218
+ base.line,
219
+ err,
220
+ );
221
+ });
222
+ }, ATTACH_TIMEOUT_MS),
223
+ });
224
+ }
225
+
226
+ private async handleReact(
227
+ ev: Record<string, unknown>,
228
+ base: { line: string; from: string; station: string; text: string },
229
+ ): Promise<void> {
230
+ const re = ev.event as { emoji?: unknown; targetId?: string };
231
+ const emoji = reactionEmoji(re.emoji);
232
+ const target = re.targetId ?? str(ev.messageId);
233
+ const removed =
234
+ (ev.payload as { removed?: boolean } | undefined)?.removed === true ||
235
+ / \(removed\)\]?$/.test(base.text);
236
+ const verb = removed ? 'removed from' : 'reacted to';
237
+ const label = removed ? emoji || 'reaction' : emoji || 'reacted';
238
+ const content = `${label} ${verb} message ${shortId(target)}`.trim();
239
+ await this.notify('notifications/claude/channel', {
240
+ content,
241
+ meta: {
242
+ line: base.line,
243
+ from: base.from,
244
+ station: base.station,
245
+ ...tsMeta(ev.ts),
246
+ message_id: str(ev.messageId),
247
+ line_name: str(ev.lineName),
248
+ from_name: str(ev.fromName),
249
+ ...displayNameMeta(ev.fromDisplayName),
250
+ reaction: emoji,
251
+ target_id: target,
252
+ },
253
+ });
254
+ }
255
+
256
+ private async handlePermissionReply(text: string): Promise<boolean> {
257
+ const m = PERMISSION_REPLY_RE.exec(text);
258
+ if (m?.[1] === undefined || m[2] === undefined || !this.pendingPermissions.size)
259
+ return false;
260
+ const id = m[2].toLowerCase();
261
+ if (!this.pendingPermissions.has(id)) return false;
262
+ this.pendingPermissions.delete(id);
263
+ await this.notify('notifications/claude/channel/permission', {
264
+ request_id: id,
265
+ behavior: m[1].toLowerCase().startsWith('y') ? 'allow' : 'deny',
266
+ });
267
+ return true;
268
+ }
269
+
270
+ private droppedSender(from: string, line: string): boolean {
271
+ if (
272
+ from.startsWith('metro://claude') ||
273
+ from === 'metro://user' ||
274
+ !from.startsWith('metro://')
275
+ )
276
+ return true;
277
+ if (!this.deps.senderAllowed(from, line)) {
278
+ this.deps.log('drop: sender not allowed', from);
279
+ return true;
280
+ }
281
+ return false;
282
+ }
283
+
284
+ private routable(
285
+ ev: Record<string, unknown>,
286
+ replay: boolean,
287
+ ): EventBase | null {
288
+ const rawType = ev.event ? (ev.event as { type?: string }).type : 'msg';
289
+ const evType = rawType === 'reply' ? 'msg' : rawType;
290
+ if (evType !== 'msg' && evType !== 'react' && evType !== 'system')
291
+ return null;
292
+ const station = str(ev.station);
293
+ if (!this.deps.getStations().has(station)) return null;
294
+ const from = str(ev.from);
295
+ const line = str(ev.line);
296
+ if (this.droppedSender(from, line)) return null;
297
+ const text = str(ev.text);
298
+ if (!replay && this.isDuplicate(station, line, evType, str(ev.messageId))) {
299
+ this.deps.log(
300
+ 'drop: duplicate (per-account) event',
301
+ evType,
302
+ station,
303
+ str(ev.messageId),
304
+ );
305
+ return null;
306
+ }
307
+ return { evType, station, from, line, text };
308
+ }
309
+
310
+ private async emitMessage(
311
+ ev: Record<string, unknown>,
312
+ base: EventBase,
313
+ ): Promise<void> {
314
+ if (base.evType === 'msg' && base.text.trim() === '') {
315
+ this.deps.log('drop: empty message', base.station, base.line, str(ev.messageId));
316
+ return;
317
+ }
318
+ if (base.evType === 'msg' && (await this.handlePermissionReply(base.text)))
319
+ return;
320
+ await this.notify('notifications/claude/channel', {
321
+ content:
322
+ base.evType === 'system'
323
+ ? buildWebhookNote(base.text, str(ev.lineName), ev.payload)
324
+ : base.text,
325
+ meta: {
326
+ line: base.line,
327
+ from: base.from,
328
+ station: base.station,
329
+ ...tsMeta(ev.ts),
330
+ message_id: str(ev.messageId),
331
+ line_name: str(ev.lineName),
332
+ from_name: str(ev.fromName),
333
+ ...displayNameMeta(ev.fromDisplayName),
334
+ ...replyMeta(ev, this.sentIds),
335
+ },
336
+ });
337
+ }
338
+
339
+ async handleEvent(
340
+ ev: Record<string, unknown>,
341
+ replay = false,
342
+ ): Promise<void> {
343
+ if (await this.routeAttachment(ev)) return;
344
+
345
+ const base = this.routable(ev, replay);
346
+ if (!base) return;
347
+ if (base.evType !== 'system') this.lastLine = base.line;
348
+ if (base.line) {
349
+ this.allowedLines.add(base.line);
350
+ capSet(this.allowedLines, ALLOWED_LINES_MAX);
351
+ }
352
+
353
+ const atts = (ev.payload as { attachments?: PendingAtt[] } | undefined)
354
+ ?.attachments;
355
+ if (Array.isArray(atts) && atts.length) {
356
+ this.bufferAttachments(ev, base, atts);
357
+ return;
358
+ }
359
+
360
+ if (base.evType === 'react') {
361
+ await this.handleReact(ev, base);
362
+ return;
363
+ }
364
+ await this.emitMessage(ev, base);
365
+ }
366
+ }
367
+
368
+ interface EventBase {
369
+ evType: string;
370
+ station: string;
371
+ from: string;
372
+ line: string;
373
+ text: string;
374
+ }
375
+
376
+ function reactionEmoji(raw: unknown): string {
377
+ if (typeof raw === 'string') return raw;
378
+ const obj = raw as { name?: string; reaction?: string } | undefined;
379
+ return obj?.name ?? obj?.reaction ?? '';
380
+ }
@@ -0,0 +1,85 @@
1
+ import { stat } from 'node:fs/promises';
2
+
3
+ const MAX_INLINE_BYTES = 4 * 1024 * 1024;
4
+
5
+ export interface SavedMedia {
6
+ contentType?: string;
7
+ attachmentFor?: string;
8
+ attachmentPath?: string;
9
+ localPath?: string;
10
+ url?: string;
11
+ mime?: string;
12
+ name?: string;
13
+ kind?: string;
14
+ index?: number;
15
+ reason?: string;
16
+ }
17
+
18
+ export interface MediaNote {
19
+ content: string;
20
+ kind: string;
21
+ name: string;
22
+ path: string;
23
+ }
24
+
25
+ export function mediaKind(mime?: string, name?: string): string {
26
+ const m = (mime ?? '').toLowerCase();
27
+ if (m.startsWith('image/')) return 'image';
28
+ if (m.startsWith('video/')) return 'video';
29
+ if (m.startsWith('audio/')) return 'audio';
30
+ const n = name ?? '';
31
+ if (/\.(png|jpe?g|gif|webp|heic)$/i.test(n)) return 'image';
32
+ if (/\.(mp4|mov|webm|m4v)$/i.test(n)) return 'video';
33
+ if (/\.(m4a|mp3|ogg|wav)$/i.test(n)) return 'audio';
34
+ return 'file';
35
+ }
36
+
37
+ async function fileSize(path: string): Promise<number> {
38
+ try {
39
+ return (await stat(path)).size;
40
+ } catch {
41
+ return 0;
42
+ }
43
+ }
44
+
45
+ function howToRead(url: string | undefined, tooBig: boolean): string {
46
+ if (!url)
47
+ return 'That path is on the daemon host, not on yours - this file is readable only by an agent running there.';
48
+ if (tooBig)
49
+ return 'Large file - fetch the Public URL only if you need the bytes, and do not inline it. The path is on the daemon host, not on yours.';
50
+ return 'Fetch the Public URL to view it. The path is on the daemon host, so the Read tool works only for an agent running there.';
51
+ }
52
+
53
+ export async function buildMediaNote(
54
+ p: SavedMedia,
55
+ caption: string,
56
+ ): Promise<MediaNote | null> {
57
+ const path = p.attachmentPath ?? p.localPath;
58
+ if (!path) return null;
59
+ const kind = p.kind ?? mediaKind(p.mime, p.name);
60
+ const name = p.name ?? path.split('/').pop() ?? 'attachment';
61
+ const size = await fileSize(path);
62
+ const sizeNote = size ? ` (${(size / 1024 / 1024).toFixed(2)} MB)` : '';
63
+ const content =
64
+ (caption ? `${caption}\n` : '') +
65
+ `[${kind} attachment received: ${name}${p.mime ? `, ${p.mime}` : ''}${sizeNote}]\n` +
66
+ (p.url ? `Public URL: ${p.url}\n` : '') +
67
+ `Daemon-host path: ${path}\n` +
68
+ howToRead(p.url, size > MAX_INLINE_BYTES);
69
+ return { content, kind, name, path };
70
+ }
71
+
72
+ export function buildMediaFailureNote(
73
+ p: SavedMedia,
74
+ caption: string,
75
+ ): MediaNote {
76
+ const kind = p.kind ?? mediaKind(p.mime, p.name);
77
+ const name = p.name ?? 'attachment';
78
+ const reason = p.reason ?? 'no reason reported';
79
+ const content =
80
+ (caption ? `${caption}\n` : '') +
81
+ `[${kind} attachment could not be fetched: ${name}]\n` +
82
+ `Reason: ${reason}\n` +
83
+ 'The sender still has the file; nothing was saved. Ask them to resend it, or say why it could not be taken.';
84
+ return { content, kind, name, path: '' };
85
+ }