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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/dist/api.js +131 -0
  2. package/dist/bedrock-eventstream.js +120 -0
  3. package/dist/bedrock-proxy.js +260 -0
  4. package/dist/bedrock-upstream.js +72 -0
  5. package/dist/bedrock.js +106 -0
  6. package/dist/claude.js +15 -0
  7. package/dist/cli.js +181 -207
  8. package/dist/control.js +173 -0
  9. package/dist/plugin.js +82 -0
  10. package/dist/prompt.js +61 -0
  11. package/dist/runtime.js +117 -0
  12. package/dist/serve.js +77 -0
  13. package/dist/store.js +93 -0
  14. package/dist/tail.js +120 -0
  15. package/dist/update.js +33 -0
  16. package/dist/version.js +97 -0
  17. package/package.json +34 -21
  18. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  19. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +119 -0
  20. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  21. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  22. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  23. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +210 -0
  24. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  25. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  26. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  27. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  28. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  29. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  30. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  31. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  32. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  33. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  34. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  35. package/runtime/node_modules/@metro-labs/mcp/package.json +67 -0
  36. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  37. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +367 -0
  38. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  39. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  40. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +334 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-connector-api.ts +150 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-import.ts +76 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-pair.ts +20 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +153 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +133 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +278 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +383 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +143 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +33 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +315 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +100 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +57 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +333 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +117 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/hosted-connectors.ts +118 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +369 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +86 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/import-api.ts +67 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-connectors-api.ts +56 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +187 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-secret.ts +21 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +31 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +162 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +120 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +58 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +145 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +214 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +224 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/daemon/relay.ts +303 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +139 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +105 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +27 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +63 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-config.ts +27 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +218 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-auth.ts +82 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-nonces.ts +43 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-routes.ts +89 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +320 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +257 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +295 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +91 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +214 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-connectors.ts +143 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +37 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +147 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-relay.ts +126 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +326 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +319 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +152 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +373 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +309 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +24 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +105 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +118 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +45 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +72 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +18 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +141 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +203 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +192 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +73 -0
  133. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  134. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  135. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +211 -0
  136. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  137. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +212 -0
  138. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +397 -0
  139. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  140. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +240 -0
  141. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  142. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +80 -0
  143. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  144. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  145. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  146. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  147. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +182 -0
  148. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  149. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  150. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  151. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  152. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  153. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  154. package/runtime/node_modules/@metro-labs/telegram/package.json +28 -0
  155. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +81 -0
  156. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  157. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  158. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  159. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +141 -0
  160. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  161. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  162. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  163. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  164. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  165. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  166. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  167. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  168. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  169. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  170. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  171. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  172. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  173. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  174. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  175. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  176. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +172 -0
  177. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
  178. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  179. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  180. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  181. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +48 -0
  182. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  183. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  184. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  185. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +40 -0
  186. package/runtime/node_modules/@metro-labs/whatsapp/package.json +29 -0
  187. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +67 -0
  188. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  189. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  190. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  191. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  192. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +318 -0
  193. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  194. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +132 -0
  195. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  196. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  197. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  198. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  199. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  200. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  201. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  202. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  203. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +141 -0
  204. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  205. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  206. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  208. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  209. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +113 -0
  210. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  211. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  212. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  213. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  214. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  215. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  216. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  217. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  218. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  219. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +333 -0
  220. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +78 -0
  221. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  222. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  223. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  224. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  225. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  226. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  227. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  228. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +39 -0
  234. package/runtime/runtime.json +3 -0
  235. package/runtime/trains/.keep +0 -0
  236. package/LICENSE +0 -21
  237. package/README.md +0 -117
  238. package/dist/agents/claude.js +0 -207
  239. package/dist/agents/codex.js +0 -207
  240. package/dist/agents/types.js +0 -2
  241. package/dist/channels/discord.js +0 -104
  242. package/dist/channels/telegram.js +0 -189
  243. package/dist/helpers/scope-cache.js +0 -65
  244. package/dist/helpers/streaming.js +0 -209
  245. package/dist/helpers/telegram-format.js +0 -39
  246. package/dist/helpers/turn.js +0 -40
  247. package/dist/log.js +0 -10
  248. package/dist/orchestrator.js +0 -208
  249. package/dist/paths.js +0 -68
@@ -0,0 +1,49 @@
1
+ import { proto, type WAMessageUpdate } from 'baileys';
2
+
3
+ const STATUS_NAMES: Record<number, string> = {
4
+ [proto.WebMessageInfo.Status.ERROR]: 'error',
5
+ [proto.WebMessageInfo.Status.PENDING]: 'pending',
6
+ [proto.WebMessageInfo.Status.SERVER_ACK]: 'server-ack',
7
+ [proto.WebMessageInfo.Status.DELIVERY_ACK]: 'delivered',
8
+ [proto.WebMessageInfo.Status.READ]: 'read',
9
+ [proto.WebMessageInfo.Status.PLAYED]: 'played',
10
+ };
11
+
12
+ export interface DeliveryNote {
13
+ messageId: string;
14
+ jid: string;
15
+ status: string;
16
+ reachedRecipient: boolean;
17
+ reason?: string;
18
+ }
19
+
20
+ function reasonOf(params: unknown): string | undefined {
21
+ if (!Array.isArray(params)) return undefined;
22
+ const parts: string[] = [];
23
+ for (const part of params)
24
+ if (typeof part === 'string' && part.trim() !== '') parts.push(part.trim());
25
+ return parts.length ? parts.join(': ') : undefined;
26
+ }
27
+
28
+ export function deliveryNotes(updates: WAMessageUpdate[]): DeliveryNote[] {
29
+ const notes: DeliveryNote[] = [];
30
+ for (const { key, update } of updates) {
31
+ const status = update.status;
32
+ if (typeof status !== 'number' || key.fromMe !== true) continue;
33
+ const jid = key.remoteJid;
34
+ const messageId = key.id;
35
+ if (!jid || !messageId) continue;
36
+ const reason = reasonOf(update.messageStubParameters);
37
+ notes.push({
38
+ messageId,
39
+ jid,
40
+ status: STATUS_NAMES[status] ?? String(status),
41
+ reachedRecipient: status >= proto.WebMessageInfo.Status.DELIVERY_ACK,
42
+ ...(reason === undefined ? {} : { reason }),
43
+ });
44
+ }
45
+ return notes;
46
+ }
47
+
48
+ export const describeNote = (note: DeliveryNote): string =>
49
+ note.reason === undefined ? note.status : `${note.status} (${note.reason})`;
@@ -0,0 +1,132 @@
1
+ import type { SavedAttachment } from '@metro-labs/mcp/stations/attachments';
2
+ import { lineOf } from './accounts.js';
3
+ import { mintId, SELF_URI } from './wire.js';
4
+ import type { WAMediaRef } from './media.js';
5
+
6
+ export interface InboundMessage {
7
+ accountId: string;
8
+ chatJid: string;
9
+ senderJid: string;
10
+ messageId: string;
11
+ text: string;
12
+ date: Date;
13
+ isPrivate: boolean;
14
+ pushName?: string;
15
+ media?: WAMediaRef;
16
+ }
17
+
18
+ export interface ReactionInput {
19
+ accountId: string;
20
+ chatJid: string;
21
+ senderJid: string;
22
+ messageId: string;
23
+ emoji: string;
24
+ date: Date;
25
+ isPrivate: boolean;
26
+ pushName?: string;
27
+ removed?: boolean;
28
+ }
29
+
30
+ function attachmentView(ref: WAMediaRef): Record<string, unknown> {
31
+ return {
32
+ kind: ref.kind,
33
+ ...(ref.name ? { name: ref.name } : {}),
34
+ ...(ref.mime ? { mime: ref.mime } : {}),
35
+ ...(ref.bytes === undefined ? {} : { size: ref.bytes }),
36
+ };
37
+ }
38
+
39
+ export function envelope(m: InboundMessage): Record<string, unknown> {
40
+ return {
41
+ kind: 'inbound',
42
+ id: mintId(),
43
+ ts: m.date.toISOString(),
44
+ station: 'whatsapp',
45
+ line: lineOf(m.accountId, m.chatJid),
46
+ from: `metro://whatsapp/${m.accountId}/user/${m.senderJid}`,
47
+ ...(m.pushName ? { from_name: m.pushName, from_display_name: m.pushName } : {}),
48
+ message_id: m.messageId,
49
+ text: m.text,
50
+ is_private: m.isPrivate,
51
+ payload: {
52
+ account: m.accountId,
53
+ message_id: m.messageId,
54
+ ...(m.media ? { attachments: [attachmentView(m.media)] } : {}),
55
+ },
56
+ };
57
+ }
58
+
59
+ function mediaEvent(
60
+ m: InboundMessage,
61
+ text: string,
62
+ payload: Record<string, unknown>,
63
+ ): Record<string, unknown> {
64
+ return {
65
+ kind: 'inbound',
66
+ id: mintId(),
67
+ ts: new Date().toISOString(),
68
+ station: 'whatsapp',
69
+ line: lineOf(m.accountId, m.chatJid),
70
+ from: SELF_URI,
71
+ text,
72
+ payload: { account: m.accountId, ...payload },
73
+ };
74
+ }
75
+
76
+ export function attachmentSavedEnvelope(
77
+ m: InboundMessage,
78
+ sourceId: string,
79
+ ref: WAMediaRef,
80
+ saved: SavedAttachment,
81
+ index: number,
82
+ ): Record<string, unknown> {
83
+ return mediaEvent(m, `📎 saved: ${saved.path}`, {
84
+ contentType: 'attachmentSaved',
85
+ attachmentFor: sourceId,
86
+ index,
87
+ kind: ref.kind,
88
+ attachmentPath: saved.path,
89
+ localPath: saved.path,
90
+ mime: saved.mime,
91
+ name: saved.name,
92
+ });
93
+ }
94
+
95
+ export function attachmentFailedEnvelope(
96
+ m: InboundMessage,
97
+ sourceId: string,
98
+ ref: WAMediaRef,
99
+ index: number,
100
+ reason: string,
101
+ ): Record<string, unknown> {
102
+ return mediaEvent(m, `📎 not fetched: ${reason}`, {
103
+ contentType: 'attachmentFailed',
104
+ attachmentFor: sourceId,
105
+ index,
106
+ kind: ref.kind,
107
+ name: ref.name,
108
+ mime: ref.mime,
109
+ reason,
110
+ });
111
+ }
112
+
113
+ export function reactionEnvelope(r: ReactionInput): Record<string, unknown> {
114
+ return {
115
+ kind: 'react',
116
+ id: mintId(),
117
+ ts: r.date.toISOString(),
118
+ station: 'whatsapp',
119
+ line: lineOf(r.accountId, r.chatJid),
120
+ from: `metro://whatsapp/${r.accountId}/user/${r.senderJid}`,
121
+ ...(r.pushName ? { from_name: r.pushName } : {}),
122
+ message_id: r.messageId,
123
+ emoji: r.emoji,
124
+ event: { type: 'react', emoji: r.emoji, targetId: r.messageId },
125
+ is_private: r.isPrivate,
126
+ payload: {
127
+ account: r.accountId,
128
+ message_id: r.messageId,
129
+ removed: r.removed === true,
130
+ },
131
+ };
132
+ }
@@ -0,0 +1,57 @@
1
+ import type { WAMessage } from 'baileys';
2
+ import { errMsg } from '@metro-labs/mcp/log';
3
+ import { emit } from './wire.js';
4
+ import {
5
+ attachmentFailedEnvelope,
6
+ attachmentSavedEnvelope,
7
+ envelope,
8
+ reactionEnvelope,
9
+ type InboundMessage,
10
+ } from './format.js';
11
+ import { saveWhatsAppMedia } from './attachments.js';
12
+ import type { WAClient } from './client.js';
13
+
14
+ async function saveMedia(
15
+ client: WAClient,
16
+ m: InboundMessage,
17
+ raw: WAMessage,
18
+ sourceId: string,
19
+ ): Promise<void> {
20
+ const ref = m.media;
21
+ if (!ref) return;
22
+ try {
23
+ const saved = await saveWhatsAppMedia(
24
+ m.accountId,
25
+ raw,
26
+ ref,
27
+ m.messageId,
28
+ 0,
29
+ (msg) => client.reuploadMedia(msg),
30
+ );
31
+ emit(attachmentSavedEnvelope(m, sourceId, ref, saved, 0));
32
+ } catch (err) {
33
+ const reason = errMsg(err);
34
+ process.stderr.write(
35
+ `whatsapp[${m.accountId}] ${ref.kind} download failed for ${m.messageId}: ${reason}\n`,
36
+ );
37
+ emit(attachmentFailedEnvelope(m, sourceId, ref, 0, reason));
38
+ }
39
+ }
40
+
41
+ export async function startInbound(client: WAClient): Promise<void> {
42
+ await client.start({
43
+ onMessage: (m, raw) => {
44
+ const env = envelope(m);
45
+ emit(env);
46
+ if (!m.media) return;
47
+ saveMedia(client, m, raw, String(env.id)).catch((err: unknown) => {
48
+ process.stderr.write(
49
+ `whatsapp[${m.accountId}] media event not emitted: ${errMsg(err)}\n`,
50
+ );
51
+ });
52
+ },
53
+ onReaction: (r) => {
54
+ emit(reactionEnvelope(r));
55
+ },
56
+ });
57
+ }
@@ -0,0 +1,40 @@
1
+ import { TrainError } from '@metro-labs/mcp/train-error';
2
+ import { errMsg } from '@metro-labs/mcp/log';
3
+ import { readCalls } from '@metro-labs/mcp/trains/protocol';
4
+ import { accounts, loadAccounts, accountFor, lineOf, targetOf } from './accounts.js';
5
+ import { createClient, type WAClient } from './client.js';
6
+ import { startInbound } from './inbound.js';
7
+ import { makeHandleCall } from './actions.js';
8
+
9
+ const clients = new Map<string, WAClient>();
10
+
11
+ function clientFor(accountId: string): WAClient {
12
+ let client = clients.get(accountId);
13
+ if (!client) {
14
+ const account = accounts.get(accountId);
15
+ if (!account)
16
+ throw new TrainError('not_implemented', `unknown account '${accountId}'`);
17
+ client = createClient(account);
18
+ clients.set(accountId, client);
19
+ }
20
+ return client;
21
+ }
22
+
23
+ const handleCall = makeHandleCall(clientFor);
24
+
25
+ readCalls('whatsapp', handleCall);
26
+
27
+ function boot(): void {
28
+ for (const cfg of loadAccounts()) accounts.set(cfg.id, cfg);
29
+ for (const id of accounts.keys())
30
+ startInbound(clientFor(id)).catch((err: unknown) => {
31
+ process.stderr.write(`whatsapp[${id}] inbound failed: ${errMsg(err)}\n`);
32
+ });
33
+ process.stderr.write(
34
+ `whatsapp train ready (inbound+outbound) — ${accounts.size} account(s): ${[...accounts.keys()].join(', ')}\n`,
35
+ );
36
+ }
37
+
38
+ boot();
39
+
40
+ export { clients, accounts, clientFor, accountFor, lineOf, targetOf };
@@ -0,0 +1,59 @@
1
+ import { TrainError } from '@metro-labs/mcp/train-error';
2
+ import type { WAMessageKey } from 'baileys';
3
+ import { isGroupJid } from './parse.js';
4
+
5
+ const MAX_TRACKED_KEYS = 4000;
6
+
7
+ export interface KeyCache {
8
+ remember(key: WAMessageKey | null | undefined): void;
9
+ lookup(jid: string, id: string): WAMessageKey | undefined;
10
+ }
11
+
12
+ const slotOf = (jid: string, id: string): string => `${jid}|${id}`;
13
+
14
+ export function makeKeyCache(max: number = MAX_TRACKED_KEYS): KeyCache {
15
+ const seen = new Map<string, WAMessageKey>();
16
+ return {
17
+ remember(key) {
18
+ const id = key?.id;
19
+ const jid = key?.remoteJid;
20
+ if (!key || !id || !jid) return;
21
+ const slot = slotOf(jid, id);
22
+ seen.delete(slot);
23
+ seen.set(slot, key);
24
+ while (seen.size > max) {
25
+ const oldest = seen.keys().next().value;
26
+ if (oldest === undefined) break;
27
+ seen.delete(oldest);
28
+ }
29
+ },
30
+ lookup(jid, id) {
31
+ return seen.get(slotOf(jid, id));
32
+ },
33
+ };
34
+ }
35
+
36
+ export function knownKey(
37
+ cache: KeyCache,
38
+ jid: string,
39
+ id: string,
40
+ fromMe: boolean,
41
+ ): WAMessageKey {
42
+ return cache.lookup(jid, id) ?? { remoteJid: jid, id, fromMe };
43
+ }
44
+
45
+ export function targetKey(
46
+ cache: KeyCache,
47
+ jid: string,
48
+ id: string,
49
+ verb: string,
50
+ ): WAMessageKey {
51
+ const known = cache.lookup(jid, id);
52
+ if (known) return known;
53
+ if (isGroupJid(jid))
54
+ throw new TrainError(
55
+ 'whatsapp_unknown_message',
56
+ `cannot ${verb} message '${id}' in group ${jid}: this connection never saw that message, so the group key naming its original sender cannot be built — WhatsApp would accept the send and display nothing`,
57
+ );
58
+ return { remoteJid: jid, id, fromMe: false };
59
+ }
@@ -0,0 +1,60 @@
1
+ import type makeWASocket from 'baileys';
2
+
3
+ type BaileysLogger = NonNullable<
4
+ NonNullable<Parameters<typeof makeWASocket>[0]>['logger']
5
+ >;
6
+
7
+ const MAX_DETAIL = 400;
8
+
9
+ const LEVELS = ['trace', 'debug', 'info', 'warn', 'error'] as const;
10
+
11
+ type Level = (typeof LEVELS)[number];
12
+
13
+ const DEFAULT_LEVEL: Level = 'warn';
14
+
15
+ const SILENT = 'silent';
16
+
17
+ const oneLine = (s: string): string => s.replace(/\s+/g, ' ').trim();
18
+
19
+ function logLevel(): Level | typeof SILENT {
20
+ const raw = process.env.METRO_WHATSAPP_LOG_LEVEL?.trim().toLowerCase();
21
+ if (raw === SILENT) return SILENT;
22
+ return LEVELS.find((l) => l === raw) ?? DEFAULT_LEVEL;
23
+ }
24
+
25
+ function detail(obj: unknown): string {
26
+ if (obj === undefined || obj === null) return '';
27
+ if (obj instanceof Error) return ` ${oneLine(obj.message)}`;
28
+ if (typeof obj === 'string') return ` ${oneLine(obj)}`;
29
+ try {
30
+ return ` ${oneLine(JSON.stringify(obj) ?? '').slice(0, MAX_DETAIL)}`;
31
+ } catch {
32
+ return '';
33
+ }
34
+ }
35
+
36
+ export function baileysLogger(accountId: string): BaileysLogger {
37
+ const level = logLevel();
38
+ const noop = (): void => undefined;
39
+ const write =
40
+ (at: Level) =>
41
+ (obj: unknown, msg?: string): void => {
42
+ process.stderr.write(
43
+ `whatsapp[${accountId}] baileys ${at}: ${oneLine(msg ?? '')}${detail(obj)}\n`,
44
+ );
45
+ };
46
+ const method = (at: Level): ((obj: unknown, msg?: string) => void) =>
47
+ level !== SILENT && LEVELS.indexOf(at) >= LEVELS.indexOf(level)
48
+ ? write(at)
49
+ : noop;
50
+ const logger: BaileysLogger = {
51
+ level,
52
+ child: () => logger,
53
+ trace: method('trace'),
54
+ debug: method('debug'),
55
+ info: method('info'),
56
+ warn: method('warn'),
57
+ error: method('error'),
58
+ };
59
+ return logger;
60
+ }
@@ -0,0 +1,176 @@
1
+ import makeWASocket, {
2
+ Browsers,
3
+ DisconnectReason,
4
+ fetchLatestWaWebVersion,
5
+ type WASocket,
6
+ } from 'baileys';
7
+ import { errMsg, log } from '@metro-labs/mcp/log';
8
+ import { inMemoryAuthState } from './auth-state.js';
9
+
10
+ export class WhatsappLoginError extends Error {}
11
+
12
+ export interface WhatsappLoginEvents {
13
+ onQr: (qr: string) => void;
14
+ onPairingCode: (code: string) => void;
15
+ onPaired: (result: {
16
+ config: { phone: string; credentials: unknown };
17
+ identity: { phone: string };
18
+ }) => void;
19
+ onFailed: (message: string) => void;
20
+ }
21
+
22
+ const PHONE_RE = /^[0-9]{6,15}$/;
23
+ const RESTART_REQUIRED = 515;
24
+ const MAX_REOPENS = 6;
25
+
26
+ export function normalizePhone(raw: unknown): string {
27
+ const phone = typeof raw === 'string' ? raw.replace(/[^0-9]/g, '') : '';
28
+ if (phone !== '' && !PHONE_RE.test(phone))
29
+ throw new WhatsappLoginError(
30
+ 'phone must be an international number in digits, for example 447700900123',
31
+ );
32
+ return phone;
33
+ }
34
+
35
+ function closeReason(err: unknown): number | undefined {
36
+ return (err as { output?: { statusCode?: number } } | undefined)?.output
37
+ ?.statusCode;
38
+ }
39
+
40
+ export class WhatsappLogin {
41
+ private auth = inMemoryAuthState();
42
+ private sock: WASocket | null = null;
43
+ private pairingRequested = false;
44
+ private settled = false;
45
+ private reopens = 0;
46
+
47
+ constructor(
48
+ private phone: string,
49
+ private events: WhatsappLoginEvents,
50
+ ) {}
51
+
52
+ get mode(): 'pairing-code' | 'qr' {
53
+ return this.phone === '' ? 'qr' : 'pairing-code';
54
+ }
55
+
56
+ async start(): Promise<void> {
57
+ const { version, error } = await fetchLatestWaWebVersion({});
58
+ if (error)
59
+ throw new WhatsappLoginError(
60
+ 'could not reach WhatsApp to start the pairing',
61
+ );
62
+ this.open(version);
63
+ }
64
+
65
+ private open(version: [number, number, number]): void {
66
+ if (this.settled) return;
67
+ const sock = makeWASocket({
68
+ version,
69
+ auth: this.auth.state,
70
+ browser: Browsers.macOS('Safari'),
71
+ markOnlineOnConnect: false,
72
+ syncFullHistory: false,
73
+ printQRInTerminal: false,
74
+ });
75
+ this.sock = sock;
76
+ sock.ev.on('connection.update', (update) => {
77
+ const { connection, qr, lastDisconnect } = update;
78
+ if (typeof qr === 'string') this.onQr(sock, qr);
79
+ if (connection === 'open') this.finish();
80
+ if (connection === 'close')
81
+ this.onClose(version, closeReason(lastDisconnect?.error));
82
+ });
83
+ }
84
+
85
+ private onQr(sock: WASocket, qr: string): void {
86
+ if (this.settled) return;
87
+ if (this.mode === 'qr') {
88
+ this.events.onQr(qr);
89
+ return;
90
+ }
91
+ if (this.pairingRequested || this.auth.state.creds.registered) return;
92
+ this.pairingRequested = true;
93
+ void sock
94
+ .requestPairingCode(this.phone)
95
+ .then((code) => {
96
+ if (!this.settled) this.events.onPairingCode(code);
97
+ })
98
+ .catch(() => {
99
+ this.fail('WhatsApp refused to issue a pairing code for that number');
100
+ });
101
+ }
102
+
103
+ private onClose(
104
+ version: [number, number, number],
105
+ statusCode: number | undefined,
106
+ ): void {
107
+ if (this.settled) return;
108
+ if (statusCode === DisconnectReason.loggedOut) {
109
+ this.fail('WhatsApp ended the pairing, start again');
110
+ return;
111
+ }
112
+ this.reopens += 1;
113
+ if (this.reopens > MAX_REOPENS && statusCode !== RESTART_REQUIRED) {
114
+ this.fail('WhatsApp kept dropping the pairing connection, start again');
115
+ return;
116
+ }
117
+ this.open(version);
118
+ }
119
+
120
+ private pairedPhone(): string {
121
+ const linked = this.auth.state.creds.me?.id.split(':')[0] ?? '';
122
+ return PHONE_RE.test(linked) ? linked : this.phone;
123
+ }
124
+
125
+ private finish(): void {
126
+ if (this.settled) return;
127
+ const phone = this.pairedPhone();
128
+ if (phone === '') {
129
+ this.fail(
130
+ 'WhatsApp paired but did not say which number it linked, so Metro has nothing to store',
131
+ );
132
+ return;
133
+ }
134
+ const credentials = this.auth.serialize();
135
+ this.settled = true;
136
+ this.closeSocket()
137
+ .then(() => {
138
+ this.events.onPaired({
139
+ config: { phone, credentials },
140
+ identity: { phone },
141
+ });
142
+ })
143
+ .catch((err: unknown) => {
144
+ log.warn({ err: errMsg(err) }, 'whatsapp login: pairing report failed');
145
+ });
146
+ }
147
+
148
+ private fail(message: string): void {
149
+ if (this.settled) return;
150
+ this.settled = true;
151
+ this.closeSocket()
152
+ .then(() => {
153
+ this.events.onFailed(message);
154
+ })
155
+ .catch((err: unknown) => {
156
+ log.warn({ err: errMsg(err) }, 'whatsapp login: failure report failed');
157
+ });
158
+ }
159
+
160
+ private async closeSocket(): Promise<void> {
161
+ const sock = this.sock;
162
+ this.sock = null;
163
+ if (!sock) return;
164
+ try {
165
+ sock.ev.removeAllListeners('connection.update');
166
+ await sock.end(undefined);
167
+ } catch {
168
+ log.warn({}, 'whatsapp login: pairing socket did not close cleanly');
169
+ }
170
+ }
171
+
172
+ async cancel(): Promise<void> {
173
+ this.settled = true;
174
+ await this.closeSocket();
175
+ }
176
+ }
@@ -0,0 +1,84 @@
1
+ import type { proto } from 'baileys';
2
+ import { extFromMime } from '@metro-labs/mcp/stations/attachments';
3
+
4
+ export type WAMediaKind =
5
+ | 'image'
6
+ | 'video'
7
+ | 'voice'
8
+ | 'audio'
9
+ | 'document'
10
+ | 'sticker';
11
+
12
+ export interface WAMediaRef {
13
+ kind: WAMediaKind;
14
+ mime?: string;
15
+ name?: string;
16
+ bytes?: number;
17
+ }
18
+
19
+ interface Long {
20
+ toNumber(): number;
21
+ }
22
+
23
+ interface MediaNode {
24
+ mimetype?: string | null;
25
+ fileName?: string | null;
26
+ fileLength?: number | Long | null;
27
+ ptt?: boolean | null;
28
+ }
29
+
30
+ const MEDIA_NODES: [keyof proto.IMessage, WAMediaKind][] = [
31
+ ['imageMessage', 'image'],
32
+ ['videoMessage', 'video'],
33
+ ['audioMessage', 'audio'],
34
+ ['documentMessage', 'document'],
35
+ ['stickerMessage', 'sticker'],
36
+ ];
37
+
38
+ const EXT_BY_KIND: Record<WAMediaKind, string> = {
39
+ image: 'jpg',
40
+ video: 'mp4',
41
+ voice: 'ogg',
42
+ audio: 'mp3',
43
+ document: 'bin',
44
+ sticker: 'webp',
45
+ };
46
+
47
+ function toBytes(v: number | Long | null | undefined): number | undefined {
48
+ if (typeof v === 'number') return v;
49
+ if (v && typeof v.toNumber === 'function') return v.toNumber();
50
+ return undefined;
51
+ }
52
+
53
+ function baseMime(v: string | null | undefined): string | undefined {
54
+ const first = typeof v === 'string' ? (v.split(';')[0] ?? '').trim() : '';
55
+ return first || undefined;
56
+ }
57
+
58
+ function fallbackName(kind: WAMediaKind, mime: string | undefined): string {
59
+ const ext = extFromMime(mime) ?? EXT_BY_KIND[kind];
60
+ return kind === 'voice' ? `voice-message.${ext}` : `${kind}.${ext}`;
61
+ }
62
+
63
+ export function mediaRefIn(inner: proto.IMessage): WAMediaRef | undefined {
64
+ for (const [key, base] of MEDIA_NODES) {
65
+ const node = inner[key] as MediaNode | null | undefined;
66
+ if (!node) continue;
67
+ const kind = base === 'audio' && node.ptt === true ? 'voice' : base;
68
+ const mime = baseMime(node.mimetype);
69
+ const bytes = toBytes(node.fileLength);
70
+ return {
71
+ kind,
72
+ ...(mime ? { mime } : {}),
73
+ name: node.fileName ?? fallbackName(kind, mime),
74
+ ...(bytes === undefined ? {} : { bytes }),
75
+ };
76
+ }
77
+ return undefined;
78
+ }
79
+
80
+ export function mediaTag(ref: WAMediaRef | undefined): string | undefined {
81
+ if (!ref) return undefined;
82
+ if (ref.kind === 'document') return `[document: ${ref.name ?? 'document'}]`;
83
+ return `[${ref.kind}]`;
84
+ }
@@ -0,0 +1,22 @@
1
+ import type { Normalized } from '@metro-labs/mcp/stations/messaging-normalize';
2
+
3
+ type Args = Record<string, unknown>;
4
+
5
+ export function normalizeWhatsApp(action: string, env: Args): Normalized {
6
+ if (action === 'reply') {
7
+ return {
8
+ action: 'send',
9
+ args: {
10
+ line: env.line,
11
+ text: env.text,
12
+ replyTo: env.replyTo ?? env.messageId,
13
+ account: env.account,
14
+ attachments: env.attachments,
15
+ },
16
+ };
17
+ }
18
+ if (action === 'unreact') {
19
+ return { action: 'react', args: { ...env, emoji: '' } };
20
+ }
21
+ return { action, args: env };
22
+ }