@stage-labs/metro 0.1.0-beta.2 → 0.1.0-beta.21

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 (225) hide show
  1. package/dist/api.js +103 -0
  2. package/dist/cli.js +134 -565
  3. package/dist/control.js +102 -0
  4. package/dist/prompt.js +10 -0
  5. package/dist/runtime.js +94 -0
  6. package/dist/store.js +70 -0
  7. package/dist/update.js +33 -0
  8. package/dist/version.js +97 -0
  9. package/package.json +34 -22
  10. package/runtime/node_modules/@metro-labs/discord/package.json +28 -0
  11. package/runtime/node_modules/@metro-labs/discord/src/accounts.ts +119 -0
  12. package/runtime/node_modules/@metro-labs/discord/src/actions.ts +388 -0
  13. package/runtime/node_modules/@metro-labs/discord/src/api-base.ts +1 -0
  14. package/runtime/node_modules/@metro-labs/discord/src/attachments.ts +29 -0
  15. package/runtime/node_modules/@metro-labs/discord/src/format.ts +210 -0
  16. package/runtime/node_modules/@metro-labs/discord/src/group-actions.ts +50 -0
  17. package/runtime/node_modules/@metro-labs/discord/src/group.ts +115 -0
  18. package/runtime/node_modules/@metro-labs/discord/src/index.ts +127 -0
  19. package/runtime/node_modules/@metro-labs/discord/src/members.ts +215 -0
  20. package/runtime/node_modules/@metro-labs/discord/src/send-files.ts +43 -0
  21. package/runtime/node_modules/@metro-labs/discord/src/station.ts +19 -0
  22. package/runtime/node_modules/@metro-labs/discord/src/verify.ts +82 -0
  23. package/runtime/node_modules/@metro-labs/discord/src/voice-speak.ts +195 -0
  24. package/runtime/node_modules/@metro-labs/discord/src/voice-transcribe.ts +292 -0
  25. package/runtime/node_modules/@metro-labs/discord/src/voice.ts +197 -0
  26. package/runtime/node_modules/@metro-labs/discord/src/wire.ts +5 -0
  27. package/runtime/node_modules/@metro-labs/mcp/package.json +66 -0
  28. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  29. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +367 -0
  30. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  31. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  32. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  33. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  34. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  35. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +335 -0
  36. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  37. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +129 -0
  38. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  39. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  40. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +124 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +278 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +322 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +111 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +20 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +202 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +338 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +31 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +100 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +57 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +333 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +117 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +164 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +208 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +357 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +79 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +162 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +120 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +58 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +134 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +214 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +224 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +125 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +18 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +105 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +27 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +41 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +218 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +323 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +169 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +295 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +91 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +215 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +71 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +37 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +218 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +147 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +329 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +19 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +364 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +300 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +31 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +105 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +131 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +45 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +57 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +18 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +145 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +203 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +192 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +73 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +211 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +212 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +397 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +240 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +80 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +182 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  133. package/runtime/node_modules/@metro-labs/telegram/package.json +25 -0
  134. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +120 -0
  135. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +295 -0
  136. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +128 -0
  137. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +172 -0
  138. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +103 -0
  139. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +166 -0
  140. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +47 -0
  141. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +17 -0
  142. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +48 -0
  143. package/runtime/node_modules/@metro-labs/telegram/src/verify.ts +38 -0
  144. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +3 -0
  145. package/runtime/node_modules/@metro-labs/telegram-user/package.json +28 -0
  146. package/runtime/node_modules/@metro-labs/telegram-user/src/accounts.ts +81 -0
  147. package/runtime/node_modules/@metro-labs/telegram-user/src/actions.ts +254 -0
  148. package/runtime/node_modules/@metro-labs/telegram-user/src/attachments.ts +87 -0
  149. package/runtime/node_modules/@metro-labs/telegram-user/src/client.ts +77 -0
  150. package/runtime/node_modules/@metro-labs/telegram-user/src/format.ts +141 -0
  151. package/runtime/node_modules/@metro-labs/telegram-user/src/group.ts +261 -0
  152. package/runtime/node_modules/@metro-labs/telegram-user/src/history.ts +52 -0
  153. package/runtime/node_modules/@metro-labs/telegram-user/src/inbound.ts +78 -0
  154. package/runtime/node_modules/@metro-labs/telegram-user/src/index.ts +46 -0
  155. package/runtime/node_modules/@metro-labs/telegram-user/src/login.ts +166 -0
  156. package/runtime/node_modules/@metro-labs/telegram-user/src/media-actions.ts +80 -0
  157. package/runtime/node_modules/@metro-labs/telegram-user/src/members.ts +75 -0
  158. package/runtime/node_modules/@metro-labs/telegram-user/src/normalize.ts +22 -0
  159. package/runtime/node_modules/@metro-labs/telegram-user/src/reactions.ts +167 -0
  160. package/runtime/node_modules/@metro-labs/telegram-user/src/station.ts +24 -0
  161. package/runtime/node_modules/@metro-labs/telegram-user/src/types.ts +7 -0
  162. package/runtime/node_modules/@metro-labs/telegram-user/src/wire.ts +1 -0
  163. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  164. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +40 -0
  165. package/runtime/node_modules/@metro-labs/whatsapp/package.json +29 -0
  166. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +67 -0
  167. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  168. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  169. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  170. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  171. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +318 -0
  172. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  173. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +132 -0
  174. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  175. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  176. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  177. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  178. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  179. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  180. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  181. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  182. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +141 -0
  183. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  184. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  185. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  186. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  187. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  188. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +113 -0
  189. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  190. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  191. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  192. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  193. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  194. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  195. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  196. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  197. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  198. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +333 -0
  199. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +78 -0
  200. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  201. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  202. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  203. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  204. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  205. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  206. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  207. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  208. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  209. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  210. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  211. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  212. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +39 -0
  213. package/runtime/runtime.json +3 -0
  214. package/runtime/trains/.keep +0 -0
  215. package/LICENSE +0 -21
  216. package/README.md +0 -78
  217. package/dist/channels/discord.js +0 -148
  218. package/dist/channels/telegram.js +0 -137
  219. package/dist/lib/address.js +0 -21
  220. package/dist/lib/codex-rc.js +0 -274
  221. package/dist/lib/dotenv.js +0 -31
  222. package/dist/log.js +0 -12
  223. package/dist/paths.js +0 -45
  224. package/dist/tail.js +0 -161
  225. package/skills/metro/SKILL.md +0 -122
@@ -0,0 +1,318 @@
1
+ import makeWASocket, {
2
+ Browsers,
3
+ DisconnectReason,
4
+ fetchLatestWaWebVersion,
5
+ type proto,
6
+ type WAMessage,
7
+ type WAMessageKey,
8
+ type WASocket,
9
+ } from 'baileys';
10
+ import { TrainError } from '@metro-labs/mcp/train-error';
11
+ import { errMsg } from '@metro-labs/mcp/log';
12
+ import type { WhatsAppAccount } from './types.js';
13
+ import type { InboundMessage, ReactionInput } from './format.js';
14
+ import { toInbound, toReaction, type ReactionEvent } from './parse.js';
15
+ import { baileysLogger } from './logger.js';
16
+ import { useAccountAuthState } from './auth-state.js';
17
+ import { knownKey, makeKeyCache, targetKey, type KeyCache } from './keys.js';
18
+ import { makeOutbox, type Outbox } from './outbox.js';
19
+ import { deliveryNotes, describeNote } from './delivery.js';
20
+ import {
21
+ ACK_WAIT_MS,
22
+ bindAcks,
23
+ makeAckWatch,
24
+ rejection,
25
+ type AckWatch,
26
+ } from './ack.js';
27
+
28
+ export interface InboundHandlers {
29
+ onMessage(m: InboundMessage, raw: WAMessage): void;
30
+ onReaction(r: ReactionInput): void;
31
+ }
32
+
33
+ export interface WAMedia {
34
+ kind: string;
35
+ path: string;
36
+ mime: string;
37
+ name: string;
38
+ caption?: string;
39
+ }
40
+
41
+ export interface WAClient {
42
+ account: WhatsAppAccount;
43
+ self(): string | null;
44
+ start(handlers: InboundHandlers): Promise<void>;
45
+ sendText(jid: string, text: string, quotedId?: string): Promise<string>;
46
+ sendMedia(jid: string, media: WAMedia, quotedId?: string): Promise<string>;
47
+ sendReaction(jid: string, messageId: string, emoji: string): Promise<void>;
48
+ editMessage(jid: string, messageId: string, text: string): Promise<void>;
49
+ deleteMessage(jid: string, messageId: string): Promise<void>;
50
+ reuploadMedia(m: WAMessage): Promise<WAMessage>;
51
+ disconnect(): Promise<void>;
52
+ }
53
+
54
+ interface State {
55
+ account: WhatsAppAccount;
56
+ handlers?: InboundHandlers;
57
+ sock?: WASocket;
58
+ closed: boolean;
59
+ openResolve?: () => void;
60
+ openPromise: Promise<void>;
61
+ keys: KeyCache;
62
+ outbox: Outbox;
63
+ acks: AckWatch;
64
+ }
65
+
66
+ type SendOpts = Parameters<WASocket['sendMessage']>[2];
67
+ type SendContent = Parameters<WASocket['sendMessage']>[1];
68
+
69
+ function resetGate(st: State): void {
70
+ st.openPromise = new Promise<void>((resolve) => {
71
+ st.openResolve = resolve;
72
+ });
73
+ }
74
+
75
+ function bindInbound(st: State, sock: WASocket): void {
76
+ sock.ev.on('messages.upsert', ({ messages, type }) => {
77
+ for (const m of messages) st.keys.remember(m.key);
78
+ if (type !== 'notify' || !st.handlers) return;
79
+ for (const m of messages) {
80
+ if (m.key.fromMe) continue;
81
+ const inbound = toInbound(st.account.id, m);
82
+ if (inbound) st.handlers.onMessage(inbound, m);
83
+ }
84
+ });
85
+ sock.ev.on('messages.reaction', (events: ReactionEvent[]) => {
86
+ if (!st.handlers) return;
87
+ for (const event of events) {
88
+ const reaction = toReaction(st.account.id, event);
89
+ if (reaction) st.handlers.onReaction(reaction);
90
+ }
91
+ });
92
+ }
93
+
94
+ function bindDelivery(st: State, sock: WASocket): void {
95
+ sock.ev.on('messages.update', (updates) => {
96
+ for (const note of deliveryNotes(updates))
97
+ process.stderr.write(
98
+ `whatsapp[${st.account.id}] send ${note.messageId} to ${note.jid}: ${describeNote(note)}\n`,
99
+ );
100
+ });
101
+ bindAcks(sock, st.acks, (ack) => {
102
+ process.stderr.write(
103
+ `whatsapp[${st.account.id}] send ${ack.messageId} to ${ack.jid} REFUSED by WhatsApp: ack error ${ack.error ?? '?'}\n`,
104
+ );
105
+ });
106
+ }
107
+
108
+ function servedFromOutbox(
109
+ st: State,
110
+ key: WAMessageKey,
111
+ ): proto.IMessage | undefined {
112
+ const message = st.outbox.lookup(key);
113
+ if (key.fromMe === true)
114
+ process.stderr.write(
115
+ message
116
+ ? `whatsapp[${st.account.id}] asked to send ${key.id} to ${key.remoteJid} again — served from the outbox\n`
117
+ : `whatsapp[${st.account.id}] asked to send ${key.id} to ${key.remoteJid} again — NOT in the outbox, so that message can never arrive\n`,
118
+ );
119
+ return message;
120
+ }
121
+
122
+ const TERMINAL: Record<number, string> = {
123
+ [DisconnectReason.loggedOut]: ' — re-pair required',
124
+ [DisconnectReason.connectionReplaced]:
125
+ ' — another metro took this device; not reconnecting (two sockets on one ' +
126
+ 'identity evict each other and risk a 463 timelock)',
127
+ };
128
+
129
+ export function terminalReason(code: number | undefined): string | undefined {
130
+ return code === undefined ? undefined : TERMINAL[code];
131
+ }
132
+
133
+ function onClose(st: State, code: number | undefined): void {
134
+ const terminal = terminalReason(code);
135
+ if (st.closed || terminal !== undefined) {
136
+ process.stderr.write(
137
+ `whatsapp[${st.account.id}] disconnected (code=${code ?? '?'})${terminal ?? ''}\n`,
138
+ );
139
+ return;
140
+ }
141
+ resetGate(st);
142
+ process.stderr.write(`whatsapp[${st.account.id}] reconnecting\n`);
143
+ void connect(st).catch((e: unknown) => {
144
+ process.stderr.write(
145
+ `whatsapp[${st.account.id}] reconnect failed: ${errMsg(e)}\n`,
146
+ );
147
+ });
148
+ }
149
+
150
+ function bindConnection(st: State, sock: WASocket): void {
151
+ sock.ev.on('connection.update', (update) => {
152
+ const { connection, lastDisconnect } = update;
153
+ if (connection === 'open') {
154
+ process.stderr.write(`whatsapp[${st.account.id}] connected\n`);
155
+ st.openResolve?.();
156
+ return;
157
+ }
158
+ if (connection !== 'close') return;
159
+ const code = (
160
+ lastDisconnect?.error as { output?: { statusCode?: number } } | undefined
161
+ )?.output?.statusCode;
162
+ onClose(st, code);
163
+ });
164
+ }
165
+
166
+ async function connect(st: State): Promise<void> {
167
+ const { state, saveCreds } = useAccountAuthState(
168
+ st.account.credentials,
169
+ st.account.id,
170
+ );
171
+ const { version, error } = await fetchLatestWaWebVersion({});
172
+ if (error) {
173
+ throw new TrainError(
174
+ 'whatsapp_connect',
175
+ `failed to fetch WhatsApp web version: ${errMsg(error)}`,
176
+ );
177
+ }
178
+ const sock = makeWASocket({
179
+ version,
180
+ auth: state,
181
+ browser: Browsers.macOS('Safari'),
182
+ markOnlineOnConnect: false,
183
+ syncFullHistory: false,
184
+ logger: baileysLogger(st.account.id),
185
+ getMessage: (key) => Promise.resolve(servedFromOutbox(st, key)),
186
+ });
187
+ st.sock = sock;
188
+ sock.ev.on('creds.update', () => void saveCreds());
189
+ bindConnection(st, sock);
190
+ bindInbound(st, sock);
191
+ bindDelivery(st, sock);
192
+ }
193
+
194
+ async function ready(st: State): Promise<WASocket> {
195
+ await st.openPromise;
196
+ if (!st.sock) throw new TrainError('whatsapp_call', 'socket not connected');
197
+ return st.sock;
198
+ }
199
+
200
+ async function send(
201
+ st: State,
202
+ jid: string,
203
+ content: SendContent,
204
+ opts?: SendOpts,
205
+ ): Promise<string> {
206
+ const sock = await ready(st);
207
+ const sent = await sock.sendMessage(jid, content, opts);
208
+ st.keys.remember(sent?.key);
209
+ st.outbox.remember(sent?.key, sent?.message);
210
+ const messageId = sent?.key.id;
211
+ if (!messageId)
212
+ throw new TrainError(
213
+ 'whatsapp_call',
214
+ `WhatsApp accepted no message for ${jid}, so there is nothing that can have arrived`,
215
+ );
216
+ const ack = await st.acks.wait(messageId, ACK_WAIT_MS);
217
+ const refused = ack ? rejection(ack) : undefined;
218
+ if (refused) throw refused;
219
+ return messageId;
220
+ }
221
+
222
+ function quotedOpts(st: State, jid: string, quotedId: string): SendOpts {
223
+ return {
224
+ quoted: {
225
+ key: knownKey(st.keys, jid, quotedId, false),
226
+ message: { conversation: '' },
227
+ },
228
+ };
229
+ }
230
+
231
+ function mediaContent(m: WAMedia): SendContent {
232
+ const source = { url: m.path };
233
+ const caption = m.caption ? { caption: m.caption } : {};
234
+ if (m.kind === 'image') return { image: source, ...caption };
235
+ if (m.kind === 'video')
236
+ return { video: source, mimetype: m.mime, ...caption };
237
+ if (m.kind === 'audio')
238
+ return { audio: source, mimetype: m.mime, ptt: false };
239
+ return {
240
+ document: source,
241
+ mimetype: m.mime,
242
+ fileName: m.name,
243
+ ...caption,
244
+ };
245
+ }
246
+
247
+ export function createClient(account: WhatsAppAccount): WAClient {
248
+ const st: State = {
249
+ account,
250
+ closed: false,
251
+ openPromise: Promise.resolve(),
252
+ keys: makeKeyCache(),
253
+ outbox: makeOutbox(),
254
+ acks: makeAckWatch(),
255
+ };
256
+ resetGate(st);
257
+ return {
258
+ account,
259
+ self() {
260
+ const jid = st.sock?.user?.id;
261
+ if (jid === undefined) return null;
262
+ return jid.split(':')[0]?.split('@')[0] ?? null;
263
+ },
264
+ async start(handlers) {
265
+ st.handlers = handlers;
266
+ try {
267
+ await connect(st);
268
+ } catch (e) {
269
+ process.stderr.write(
270
+ `whatsapp[${account.id}] connect failed: ${errMsg(e)}\n`,
271
+ );
272
+ }
273
+ },
274
+ sendText(jid, text, quotedId) {
275
+ return send(
276
+ st,
277
+ jid,
278
+ { text },
279
+ quotedId ? quotedOpts(st, jid, quotedId) : undefined,
280
+ );
281
+ },
282
+ sendMedia(jid, media, quotedId) {
283
+ return send(
284
+ st,
285
+ jid,
286
+ mediaContent(media),
287
+ quotedId ? quotedOpts(st, jid, quotedId) : undefined,
288
+ );
289
+ },
290
+ async sendReaction(jid, messageId, emoji) {
291
+ const target = targetKey(st.keys, jid, messageId, 'react to');
292
+ await send(st, jid, { react: { text: emoji, key: target } });
293
+ },
294
+ async editMessage(jid, messageId, text) {
295
+ await send(st, jid, {
296
+ text,
297
+ edit: knownKey(st.keys, jid, messageId, true),
298
+ });
299
+ },
300
+ async deleteMessage(jid, messageId) {
301
+ await send(st, jid, {
302
+ delete: knownKey(st.keys, jid, messageId, true),
303
+ });
304
+ },
305
+ async reuploadMedia(m) {
306
+ const sock = await ready(st);
307
+ return sock.updateMediaMessage(m);
308
+ },
309
+ async disconnect() {
310
+ st.closed = true;
311
+ try {
312
+ await st.sock?.end(undefined);
313
+ } catch {
314
+ st.sock = undefined;
315
+ }
316
+ },
317
+ };
318
+ }
@@ -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
+ }