@rubytech/create-maxy-code 0.1.266 → 0.1.268
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.
- package/dist/index.js +16 -0
- package/package.json +1 -1
- package/payload/platform/package-lock.json +16 -0
- package/payload/platform/package.json +3 -2
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +36 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts +19 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js +31 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js.map +1 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +40 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.js +36 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -0
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts +3 -0
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -0
- package/payload/platform/services/whatsapp-channel/dist/server.js +114 -0
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -0
- package/payload/platform/services/whatsapp-channel/package.json +20 -0
- package/payload/server/{chunk-SOLVVUST.js → chunk-W4EM7RK4.js} +2 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/server.js +345 -14
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Pure helpers for the WhatsApp channel server. No MCP SDK import here, so
|
|
2
|
+
// these can be unit-tested without the SDK installed and so the server's
|
|
3
|
+
// transport wiring (server.ts) stays separable from its message shaping.
|
|
4
|
+
/**
|
|
5
|
+
* Shape an inbound WhatsApp payload as a Claude Code channel notification.
|
|
6
|
+
* `content` becomes the `<channel>` body; each `meta` entry becomes a tag
|
|
7
|
+
* attribute, so keys must be identifier-safe ([A-Za-z0-9_]) — hyphenated
|
|
8
|
+
* keys are silently dropped by Claude Code.
|
|
9
|
+
*/
|
|
10
|
+
export function buildInboundNotification(p) {
|
|
11
|
+
return {
|
|
12
|
+
method: 'notifications/claude/channel',
|
|
13
|
+
params: {
|
|
14
|
+
content: p.text,
|
|
15
|
+
meta: { sender_id: p.senderId, wa_message_id: p.waMessageId },
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Defence in depth: this server serves exactly one sender. Even though the
|
|
21
|
+
* gateway only streams that sender's messages, refuse anything else before
|
|
22
|
+
* it reaches the session.
|
|
23
|
+
*/
|
|
24
|
+
export function acceptsSender(mySenderId, p) {
|
|
25
|
+
return p.senderId === mySenderId;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build the body for POST /wa-channel/reply. The reply tool exposes only
|
|
29
|
+
* `text` to the model; the senderId is the server's fixed sender (from spawn
|
|
30
|
+
* env), never trusted from the model, so a compromised turn cannot redirect a
|
|
31
|
+
* reply to a different WhatsApp recipient.
|
|
32
|
+
*/
|
|
33
|
+
export function buildReplyPostBody(mySenderId, text) {
|
|
34
|
+
return { senderId: mySenderId, text };
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=notification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification.js","sourceRoot":"","sources":["../src/notification.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AAWzE;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,CAAiB;IACxD,OAAO;QACL,MAAM,EAAE,8BAAuC;QAC/C,MAAM,EAAE;YACN,OAAO,EAAE,CAAC,CAAC,IAAI;YACf,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,CAAC,CAAC,WAAW,EAA4B;SACxF;KACF,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,CAAuB;IACvE,OAAO,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAA;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAkB,EAAE,IAAY;IACjE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAA;AACvC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Per-sender WhatsApp channel MCP server. Spawned as a stdio child of one
|
|
3
|
+
// `claude` session (so it dies when that session dies — no bound port, no
|
|
4
|
+
// orphan). It:
|
|
5
|
+
// - subscribes to the gateway for ITS sender's inbound (SSE) and emits each
|
|
6
|
+
// message as a notifications/claude/channel event;
|
|
7
|
+
// - exposes a `reply` tool that posts the agent's reply back to the gateway,
|
|
8
|
+
// which sends it to WhatsApp.
|
|
9
|
+
//
|
|
10
|
+
// Transport glue only — message shaping and the reply body are the unit-tested
|
|
11
|
+
// pure helpers in ./notification. Verified end-to-end at deploy (Task 7).
|
|
12
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
13
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
15
|
+
import { acceptsSender, buildInboundNotification, buildReplyPostBody } from './notification.js';
|
|
16
|
+
function requireEnv(name) {
|
|
17
|
+
const v = process.env[name];
|
|
18
|
+
if (!v)
|
|
19
|
+
throw new Error(`whatsapp-channel: missing required env ${name}`);
|
|
20
|
+
return v;
|
|
21
|
+
}
|
|
22
|
+
const SENDER_ID = requireEnv('WA_CHANNEL_SENDER_ID');
|
|
23
|
+
const GATEWAY_URL = requireEnv('WA_CHANNEL_GATEWAY_URL').replace(/\/$/, '');
|
|
24
|
+
// Co-visibility: the operator watches the same session in claude.ai/code,
|
|
25
|
+
// where the reply tool call shows but its text does not. Instruct the agent to
|
|
26
|
+
// also write its answer as normal assistant text so the operator can read it
|
|
27
|
+
// in the web view, AND relay that same text via the reply tool to WhatsApp.
|
|
28
|
+
const INSTRUCTIONS = `Messages from the WhatsApp sender arrive as <channel source="whatsapp" sender_id="...">. ` +
|
|
29
|
+
`Always do two things with your response, in this order: (1) write your answer as a normal ` +
|
|
30
|
+
`assistant message so it is visible in the session, then (2) call the reply tool with that ` +
|
|
31
|
+
`same text to send it to WhatsApp. Do not skip either step.`;
|
|
32
|
+
const mcp = new Server({ name: 'whatsapp', version: '0.1.0' }, {
|
|
33
|
+
capabilities: { experimental: { 'claude/channel': {} }, tools: {} },
|
|
34
|
+
instructions: INSTRUCTIONS,
|
|
35
|
+
});
|
|
36
|
+
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
37
|
+
tools: [
|
|
38
|
+
{
|
|
39
|
+
name: 'reply',
|
|
40
|
+
description: 'Send your reply text to the WhatsApp sender of this conversation.',
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: { text: { type: 'string', description: 'The message to send to WhatsApp' } },
|
|
44
|
+
required: ['text'],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
}));
|
|
49
|
+
mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
50
|
+
if (req.params.name !== 'reply')
|
|
51
|
+
throw new Error(`unknown tool: ${req.params.name}`);
|
|
52
|
+
const text = req.params.arguments?.text;
|
|
53
|
+
if (typeof text !== 'string')
|
|
54
|
+
throw new Error('reply: text must be a string');
|
|
55
|
+
const res = await fetch(`${GATEWAY_URL}/wa-channel/reply`, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'content-type': 'application/json' },
|
|
58
|
+
body: JSON.stringify(buildReplyPostBody(SENDER_ID, text)),
|
|
59
|
+
});
|
|
60
|
+
if (!res.ok)
|
|
61
|
+
throw new Error(`reply: gateway returned ${res.status}`);
|
|
62
|
+
return { content: [{ type: 'text', text: 'sent' }] };
|
|
63
|
+
});
|
|
64
|
+
// Subscribe to this sender's inbound stream and emit each message as an event.
|
|
65
|
+
async function subscribeInbound() {
|
|
66
|
+
const url = `${GATEWAY_URL}/wa-channel/inbound?senderId=${encodeURIComponent(SENDER_ID)}`;
|
|
67
|
+
const res = await fetch(url, { headers: { accept: 'text/event-stream' } });
|
|
68
|
+
if (!res.ok || !res.body)
|
|
69
|
+
throw new Error(`inbound subscribe failed: ${res.status}`);
|
|
70
|
+
const reader = res.body.getReader();
|
|
71
|
+
const decoder = new TextDecoder();
|
|
72
|
+
let buffer = '';
|
|
73
|
+
for (;;) {
|
|
74
|
+
const { value, done } = await reader.read();
|
|
75
|
+
if (done)
|
|
76
|
+
break;
|
|
77
|
+
buffer += decoder.decode(value, { stream: true });
|
|
78
|
+
const frames = buffer.split('\n\n');
|
|
79
|
+
buffer = frames.pop() ?? '';
|
|
80
|
+
for (const frame of frames) {
|
|
81
|
+
const dataLine = frame.split('\n').find((l) => l.startsWith('data:'));
|
|
82
|
+
if (!dataLine)
|
|
83
|
+
continue;
|
|
84
|
+
const data = dataLine.slice('data:'.length).trim();
|
|
85
|
+
if (!data)
|
|
86
|
+
continue; // ping
|
|
87
|
+
let payload;
|
|
88
|
+
try {
|
|
89
|
+
payload = JSON.parse(data);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!acceptsSender(SENDER_ID, payload))
|
|
95
|
+
continue;
|
|
96
|
+
await mcp.notification(buildInboundNotification(payload));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function main() {
|
|
101
|
+
await mcp.connect(new StdioServerTransport());
|
|
102
|
+
// Tell the gateway this sender's channel is live so any cold-start queue drains.
|
|
103
|
+
await fetch(`${GATEWAY_URL}/wa-channel/ready`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'content-type': 'application/json' },
|
|
106
|
+
body: JSON.stringify({ senderId: SENDER_ID }),
|
|
107
|
+
}).catch(() => { });
|
|
108
|
+
await subscribeInbound();
|
|
109
|
+
}
|
|
110
|
+
main().catch((err) => {
|
|
111
|
+
console.error(`[whatsapp-channel] fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maxy/whatsapp-channel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/server.js",
|
|
10
|
+
"test": "vitest run"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.7.0",
|
|
17
|
+
"@types/node": "^22.0.0",
|
|
18
|
+
"vitest": "^4.1.2"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/payload/server/server.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
COMMERCIAL_MODE,
|
|
10
10
|
GREETING_DIRECTIVE,
|
|
11
11
|
Hono,
|
|
12
|
+
HtmlEscapedCallbackPhase,
|
|
12
13
|
INSTALL_OWNER_FILE,
|
|
13
14
|
LOG_DIR,
|
|
14
15
|
MAXY_DIR,
|
|
@@ -71,6 +72,7 @@ import {
|
|
|
71
72
|
requirePortEnv,
|
|
72
73
|
resolveAccount,
|
|
73
74
|
resolveAgentConfig,
|
|
75
|
+
resolveCallback,
|
|
74
76
|
resolveClientIp,
|
|
75
77
|
resolveDefaultAgentSlug,
|
|
76
78
|
resolveUserAccounts,
|
|
@@ -93,7 +95,7 @@ import {
|
|
|
93
95
|
vncLog,
|
|
94
96
|
walkPremiumBundles,
|
|
95
97
|
writeAdminUserAndPerson
|
|
96
|
-
} from "./chunk-
|
|
98
|
+
} from "./chunk-W4EM7RK4.js";
|
|
97
99
|
import {
|
|
98
100
|
__commonJS,
|
|
99
101
|
__toESM
|
|
@@ -1105,24 +1107,24 @@ var pr54206Applied = () => {
|
|
|
1105
1107
|
return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
|
|
1106
1108
|
};
|
|
1107
1109
|
var useReadableToWeb = pr54206Applied();
|
|
1108
|
-
var createStreamBody = (
|
|
1110
|
+
var createStreamBody = (stream2) => {
|
|
1109
1111
|
if (useReadableToWeb) {
|
|
1110
|
-
return Readable.toWeb(
|
|
1112
|
+
return Readable.toWeb(stream2);
|
|
1111
1113
|
}
|
|
1112
1114
|
const body = new ReadableStream({
|
|
1113
1115
|
start(controller) {
|
|
1114
|
-
|
|
1116
|
+
stream2.on("data", (chunk) => {
|
|
1115
1117
|
controller.enqueue(chunk);
|
|
1116
1118
|
});
|
|
1117
|
-
|
|
1119
|
+
stream2.on("error", (err) => {
|
|
1118
1120
|
controller.error(err);
|
|
1119
1121
|
});
|
|
1120
|
-
|
|
1122
|
+
stream2.on("end", () => {
|
|
1121
1123
|
controller.close();
|
|
1122
1124
|
});
|
|
1123
1125
|
},
|
|
1124
1126
|
cancel() {
|
|
1125
|
-
|
|
1127
|
+
stream2.destroy();
|
|
1126
1128
|
}
|
|
1127
1129
|
});
|
|
1128
1130
|
return body;
|
|
@@ -1227,10 +1229,10 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
1227
1229
|
end = size - 1;
|
|
1228
1230
|
}
|
|
1229
1231
|
const chunksize = end - start + 1;
|
|
1230
|
-
const
|
|
1232
|
+
const stream2 = createReadStream(path2, { start, end });
|
|
1231
1233
|
c.header("Content-Length", chunksize.toString());
|
|
1232
1234
|
c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`);
|
|
1233
|
-
result = c.body(createStreamBody(
|
|
1235
|
+
result = c.body(createStreamBody(stream2), 206);
|
|
1234
1236
|
}
|
|
1235
1237
|
await options.onFound?.(path2, c);
|
|
1236
1238
|
return result;
|
|
@@ -2892,9 +2894,9 @@ function getDownloadableContent(message) {
|
|
|
2892
2894
|
if (msg.stickerMessage) return { downloadable: msg.stickerMessage, mediaType: "sticker" };
|
|
2893
2895
|
return void 0;
|
|
2894
2896
|
}
|
|
2895
|
-
async function streamToBuffer(
|
|
2897
|
+
async function streamToBuffer(stream2) {
|
|
2896
2898
|
const chunks = [];
|
|
2897
|
-
for await (const chunk of
|
|
2899
|
+
for await (const chunk of stream2) {
|
|
2898
2900
|
chunks.push(Buffer.from(chunk));
|
|
2899
2901
|
}
|
|
2900
2902
|
return Buffer.concat(chunks);
|
|
@@ -2925,8 +2927,8 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
2925
2927
|
const downloadable = getDownloadableContent(content);
|
|
2926
2928
|
if (downloadable) {
|
|
2927
2929
|
try {
|
|
2928
|
-
const
|
|
2929
|
-
buffer = await streamToBuffer(
|
|
2930
|
+
const stream2 = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
|
|
2931
|
+
buffer = await streamToBuffer(stream2);
|
|
2930
2932
|
} catch (fallbackErr) {
|
|
2931
2933
|
console.error(`${TAG8} direct download fallback failed: ${String(fallbackErr)}`);
|
|
2932
2934
|
}
|
|
@@ -4671,7 +4673,8 @@ async function managerRcSpawn(opts) {
|
|
|
4671
4673
|
initialMessage: opts.initialMessage,
|
|
4672
4674
|
closeAfterTurn: opts.closeAfterTurn,
|
|
4673
4675
|
sliceToken: opts.sliceToken,
|
|
4674
|
-
personId: opts.personId
|
|
4676
|
+
personId: opts.personId,
|
|
4677
|
+
waChannel: opts.waChannel
|
|
4675
4678
|
})
|
|
4676
4679
|
}).catch((err) => ({ __throw: err instanceof Error ? err.message : String(err) }));
|
|
4677
4680
|
if ("__throw" in res) {
|
|
@@ -15567,6 +15570,300 @@ function startReaper2() {
|
|
|
15567
15570
|
startReaper();
|
|
15568
15571
|
}
|
|
15569
15572
|
|
|
15573
|
+
// app/lib/whatsapp/gateway/inbound-hub.ts
|
|
15574
|
+
var InboundHub = class {
|
|
15575
|
+
senders = /* @__PURE__ */ new Map();
|
|
15576
|
+
state(senderId) {
|
|
15577
|
+
let s = this.senders.get(senderId);
|
|
15578
|
+
if (!s) {
|
|
15579
|
+
s = { subscriber: null, queue: [] };
|
|
15580
|
+
this.senders.set(senderId, s);
|
|
15581
|
+
}
|
|
15582
|
+
return s;
|
|
15583
|
+
}
|
|
15584
|
+
/** Route an inbound message to its sender. Delivers immediately if that
|
|
15585
|
+
* sender's channel server is attached; otherwise queues it (cold start). */
|
|
15586
|
+
deliver(payload) {
|
|
15587
|
+
const s = this.state(payload.senderId);
|
|
15588
|
+
if (s.subscriber) {
|
|
15589
|
+
s.subscriber(payload);
|
|
15590
|
+
} else {
|
|
15591
|
+
s.queue.push(payload);
|
|
15592
|
+
}
|
|
15593
|
+
}
|
|
15594
|
+
/** A sender's channel server connected. Drain any queued messages to it in
|
|
15595
|
+
* arrival order, then route live. Replaces any prior subscriber. */
|
|
15596
|
+
attach(senderId, subscriber) {
|
|
15597
|
+
const s = this.state(senderId);
|
|
15598
|
+
s.subscriber = subscriber;
|
|
15599
|
+
if (s.queue.length > 0) {
|
|
15600
|
+
const pending = s.queue;
|
|
15601
|
+
s.queue = [];
|
|
15602
|
+
for (const payload of pending) subscriber(payload);
|
|
15603
|
+
}
|
|
15604
|
+
}
|
|
15605
|
+
/** A sender's channel server disconnected (session ended/restarting).
|
|
15606
|
+
* Subsequent messages queue again until re-attach, so nothing is lost
|
|
15607
|
+
* across a restart. */
|
|
15608
|
+
detach(senderId) {
|
|
15609
|
+
const s = this.senders.get(senderId);
|
|
15610
|
+
if (s) s.subscriber = null;
|
|
15611
|
+
}
|
|
15612
|
+
/** Whether a sender currently has a live channel server. The gateway uses
|
|
15613
|
+
* this to decide whether an inbound needs a cold-start spawn/resume. */
|
|
15614
|
+
hasSubscriber(senderId) {
|
|
15615
|
+
return this.senders.get(senderId)?.subscriber != null;
|
|
15616
|
+
}
|
|
15617
|
+
};
|
|
15618
|
+
|
|
15619
|
+
// node_modules/hono/dist/utils/stream.js
|
|
15620
|
+
var StreamingApi = class {
|
|
15621
|
+
writer;
|
|
15622
|
+
encoder;
|
|
15623
|
+
writable;
|
|
15624
|
+
abortSubscribers = [];
|
|
15625
|
+
responseReadable;
|
|
15626
|
+
/**
|
|
15627
|
+
* Whether the stream has been aborted.
|
|
15628
|
+
*/
|
|
15629
|
+
aborted = false;
|
|
15630
|
+
/**
|
|
15631
|
+
* Whether the stream has been closed normally.
|
|
15632
|
+
*/
|
|
15633
|
+
closed = false;
|
|
15634
|
+
constructor(writable, _readable) {
|
|
15635
|
+
this.writable = writable;
|
|
15636
|
+
this.writer = writable.getWriter();
|
|
15637
|
+
this.encoder = new TextEncoder();
|
|
15638
|
+
const reader = _readable.getReader();
|
|
15639
|
+
this.abortSubscribers.push(async () => {
|
|
15640
|
+
await reader.cancel();
|
|
15641
|
+
});
|
|
15642
|
+
this.responseReadable = new ReadableStream({
|
|
15643
|
+
async pull(controller) {
|
|
15644
|
+
const { done, value } = await reader.read();
|
|
15645
|
+
done ? controller.close() : controller.enqueue(value);
|
|
15646
|
+
},
|
|
15647
|
+
cancel: () => {
|
|
15648
|
+
this.abort();
|
|
15649
|
+
}
|
|
15650
|
+
});
|
|
15651
|
+
}
|
|
15652
|
+
async write(input) {
|
|
15653
|
+
try {
|
|
15654
|
+
if (typeof input === "string") {
|
|
15655
|
+
input = this.encoder.encode(input);
|
|
15656
|
+
}
|
|
15657
|
+
await this.writer.write(input);
|
|
15658
|
+
} catch {
|
|
15659
|
+
}
|
|
15660
|
+
return this;
|
|
15661
|
+
}
|
|
15662
|
+
async writeln(input) {
|
|
15663
|
+
await this.write(input + "\n");
|
|
15664
|
+
return this;
|
|
15665
|
+
}
|
|
15666
|
+
sleep(ms) {
|
|
15667
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
15668
|
+
}
|
|
15669
|
+
async close() {
|
|
15670
|
+
try {
|
|
15671
|
+
await this.writer.close();
|
|
15672
|
+
} catch {
|
|
15673
|
+
}
|
|
15674
|
+
this.closed = true;
|
|
15675
|
+
}
|
|
15676
|
+
async pipe(body) {
|
|
15677
|
+
this.writer.releaseLock();
|
|
15678
|
+
await body.pipeTo(this.writable, { preventClose: true });
|
|
15679
|
+
this.writer = this.writable.getWriter();
|
|
15680
|
+
}
|
|
15681
|
+
onAbort(listener) {
|
|
15682
|
+
this.abortSubscribers.push(listener);
|
|
15683
|
+
}
|
|
15684
|
+
/**
|
|
15685
|
+
* Abort the stream.
|
|
15686
|
+
* You can call this method when stream is aborted by external event.
|
|
15687
|
+
*/
|
|
15688
|
+
abort() {
|
|
15689
|
+
if (!this.aborted) {
|
|
15690
|
+
this.aborted = true;
|
|
15691
|
+
this.abortSubscribers.forEach((subscriber) => subscriber());
|
|
15692
|
+
}
|
|
15693
|
+
}
|
|
15694
|
+
};
|
|
15695
|
+
|
|
15696
|
+
// node_modules/hono/dist/helper/streaming/utils.js
|
|
15697
|
+
var isOldBunVersion = () => {
|
|
15698
|
+
const version = typeof Bun !== "undefined" ? Bun.version : void 0;
|
|
15699
|
+
if (version === void 0) {
|
|
15700
|
+
return false;
|
|
15701
|
+
}
|
|
15702
|
+
const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
|
|
15703
|
+
isOldBunVersion = () => result;
|
|
15704
|
+
return result;
|
|
15705
|
+
};
|
|
15706
|
+
|
|
15707
|
+
// node_modules/hono/dist/helper/streaming/sse.js
|
|
15708
|
+
var SSEStreamingApi = class extends StreamingApi {
|
|
15709
|
+
constructor(writable, readable) {
|
|
15710
|
+
super(writable, readable);
|
|
15711
|
+
}
|
|
15712
|
+
async writeSSE(message) {
|
|
15713
|
+
const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
|
|
15714
|
+
const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
|
|
15715
|
+
return `data: ${line}`;
|
|
15716
|
+
}).join("\n");
|
|
15717
|
+
for (const key of ["event", "id", "retry"]) {
|
|
15718
|
+
if (message[key] && /[\r\n]/.test(message[key])) {
|
|
15719
|
+
throw new Error(`${key} must not contain "\\r" or "\\n"`);
|
|
15720
|
+
}
|
|
15721
|
+
}
|
|
15722
|
+
const sseData = [
|
|
15723
|
+
message.event && `event: ${message.event}`,
|
|
15724
|
+
dataLines,
|
|
15725
|
+
message.id && `id: ${message.id}`,
|
|
15726
|
+
message.retry && `retry: ${message.retry}`
|
|
15727
|
+
].filter(Boolean).join("\n") + "\n\n";
|
|
15728
|
+
await this.write(sseData);
|
|
15729
|
+
}
|
|
15730
|
+
};
|
|
15731
|
+
var run = async (stream2, cb, onError) => {
|
|
15732
|
+
try {
|
|
15733
|
+
await cb(stream2);
|
|
15734
|
+
} catch (e) {
|
|
15735
|
+
if (e instanceof Error && onError) {
|
|
15736
|
+
await onError(e, stream2);
|
|
15737
|
+
await stream2.writeSSE({
|
|
15738
|
+
event: "error",
|
|
15739
|
+
data: e.message
|
|
15740
|
+
});
|
|
15741
|
+
} else {
|
|
15742
|
+
console.error(e);
|
|
15743
|
+
}
|
|
15744
|
+
} finally {
|
|
15745
|
+
stream2.close();
|
|
15746
|
+
}
|
|
15747
|
+
};
|
|
15748
|
+
var contextStash = /* @__PURE__ */ new WeakMap();
|
|
15749
|
+
var streamSSE = (c, cb, onError) => {
|
|
15750
|
+
const { readable, writable } = new TransformStream();
|
|
15751
|
+
const stream2 = new SSEStreamingApi(writable, readable);
|
|
15752
|
+
if (isOldBunVersion()) {
|
|
15753
|
+
c.req.raw.signal.addEventListener("abort", () => {
|
|
15754
|
+
if (!stream2.closed) {
|
|
15755
|
+
stream2.abort();
|
|
15756
|
+
}
|
|
15757
|
+
});
|
|
15758
|
+
}
|
|
15759
|
+
contextStash.set(stream2.responseReadable, c);
|
|
15760
|
+
c.header("Transfer-Encoding", "chunked");
|
|
15761
|
+
c.header("Content-Type", "text/event-stream");
|
|
15762
|
+
c.header("Cache-Control", "no-cache");
|
|
15763
|
+
c.header("Connection", "keep-alive");
|
|
15764
|
+
run(stream2, cb, onError);
|
|
15765
|
+
return c.newResponse(stream2.responseReadable);
|
|
15766
|
+
};
|
|
15767
|
+
|
|
15768
|
+
// app/lib/whatsapp/gateway/routes.ts
|
|
15769
|
+
function createWaChannelRoutes(deps) {
|
|
15770
|
+
const app45 = new Hono();
|
|
15771
|
+
app45.get("/wa-channel/inbound", (c) => {
|
|
15772
|
+
const senderId = c.req.query("senderId");
|
|
15773
|
+
if (!senderId) return c.json({ error: "senderId required" }, 400);
|
|
15774
|
+
return streamSSE(c, async (stream2) => {
|
|
15775
|
+
deps.hub.attach(senderId, (payload) => {
|
|
15776
|
+
void stream2.writeSSE({ data: JSON.stringify(payload) });
|
|
15777
|
+
});
|
|
15778
|
+
console.error(`[whatsapp-native] op=channel-attached senderId=${senderId}`);
|
|
15779
|
+
stream2.onAbort(() => {
|
|
15780
|
+
deps.hub.detach(senderId);
|
|
15781
|
+
console.error(`[whatsapp-native] op=channel-detached senderId=${senderId}`);
|
|
15782
|
+
});
|
|
15783
|
+
while (!stream2.aborted) {
|
|
15784
|
+
await stream2.sleep(15e3);
|
|
15785
|
+
await stream2.writeSSE({ data: "", event: "ping" });
|
|
15786
|
+
}
|
|
15787
|
+
});
|
|
15788
|
+
});
|
|
15789
|
+
app45.post("/wa-channel/reply", async (c) => {
|
|
15790
|
+
const body = await c.req.json().catch(() => null);
|
|
15791
|
+
const senderId = body?.senderId;
|
|
15792
|
+
const text = body?.text;
|
|
15793
|
+
if (typeof senderId !== "string" || typeof text !== "string") {
|
|
15794
|
+
return c.json({ error: "senderId and text required" }, 400);
|
|
15795
|
+
}
|
|
15796
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
15797
|
+
try {
|
|
15798
|
+
await deps.sendOutbound(senderId, text);
|
|
15799
|
+
} catch (err) {
|
|
15800
|
+
console.error(
|
|
15801
|
+
`[whatsapp-native] op=reply-failed senderId=${senderId} bytes=${bytes} error=${err instanceof Error ? err.message : String(err)}`
|
|
15802
|
+
);
|
|
15803
|
+
return c.json({ error: "send-failed" }, 500);
|
|
15804
|
+
}
|
|
15805
|
+
console.error(`[whatsapp-native] op=reply-dispatch senderId=${senderId} bytes=${bytes}`);
|
|
15806
|
+
return c.json({ ok: true });
|
|
15807
|
+
});
|
|
15808
|
+
app45.post("/wa-channel/ready", async (c) => {
|
|
15809
|
+
const body = await c.req.json().catch(() => null);
|
|
15810
|
+
const senderId = body?.senderId;
|
|
15811
|
+
if (typeof senderId !== "string") {
|
|
15812
|
+
return c.json({ error: "senderId required" }, 400);
|
|
15813
|
+
}
|
|
15814
|
+
deps.onReady?.(senderId);
|
|
15815
|
+
return c.json({ ok: true });
|
|
15816
|
+
});
|
|
15817
|
+
return app45;
|
|
15818
|
+
}
|
|
15819
|
+
|
|
15820
|
+
// app/lib/whatsapp/gateway/wa-gateway.ts
|
|
15821
|
+
var WaGateway = class {
|
|
15822
|
+
constructor(deps) {
|
|
15823
|
+
this.deps = deps;
|
|
15824
|
+
}
|
|
15825
|
+
hub = new InboundHub();
|
|
15826
|
+
replies = /* @__PURE__ */ new Map();
|
|
15827
|
+
spawning = /* @__PURE__ */ new Set();
|
|
15828
|
+
seq = 0;
|
|
15829
|
+
/** Hono sub-app exposing the loopback channel routes; mount on the UI app. */
|
|
15830
|
+
routes() {
|
|
15831
|
+
return createWaChannelRoutes({
|
|
15832
|
+
hub: this.hub,
|
|
15833
|
+
sendOutbound: (senderId, text) => this.sendOutbound(senderId, text)
|
|
15834
|
+
});
|
|
15835
|
+
}
|
|
15836
|
+
/** Handle one inbound admin WhatsApp message. */
|
|
15837
|
+
async handleInbound(input) {
|
|
15838
|
+
const bytes = Buffer.byteLength(input.text, "utf8");
|
|
15839
|
+
const hadSubscriber = this.hub.hasSubscriber(input.senderId);
|
|
15840
|
+
this.replies.set(input.senderId, input.reply);
|
|
15841
|
+
this.hub.deliver({ senderId: input.senderId, text: input.text, waMessageId: `wa-${++this.seq}` });
|
|
15842
|
+
console.error(
|
|
15843
|
+
`[whatsapp-native] op=inbound senderId=${input.senderId} accountId=${input.accountId} bytes=${bytes} delivery=${hadSubscriber ? "immediate" : "queued"}`
|
|
15844
|
+
);
|
|
15845
|
+
if (!hadSubscriber && !this.spawning.has(input.senderId)) {
|
|
15846
|
+
this.spawning.add(input.senderId);
|
|
15847
|
+
console.error(`[whatsapp-native] op=spawn-trigger senderId=${input.senderId}`);
|
|
15848
|
+
try {
|
|
15849
|
+
await this.deps.ensureChannelSession({
|
|
15850
|
+
accountId: input.accountId,
|
|
15851
|
+
senderId: input.senderId,
|
|
15852
|
+
gatewayUrl: this.deps.gatewayUrl,
|
|
15853
|
+
serverPath: this.deps.serverPath
|
|
15854
|
+
});
|
|
15855
|
+
} finally {
|
|
15856
|
+
this.spawning.delete(input.senderId);
|
|
15857
|
+
}
|
|
15858
|
+
}
|
|
15859
|
+
}
|
|
15860
|
+
async sendOutbound(senderId, text) {
|
|
15861
|
+
const reply = this.replies.get(senderId);
|
|
15862
|
+
if (!reply) throw new Error(`wa-gateway: no reply route for sender ${senderId}`);
|
|
15863
|
+
await reply(text);
|
|
15864
|
+
}
|
|
15865
|
+
};
|
|
15866
|
+
|
|
15570
15867
|
// app/lib/admin-sse-registry.ts
|
|
15571
15868
|
var activeAdminSSEControllers = /* @__PURE__ */ new Set();
|
|
15572
15869
|
function broadcastAdminShutdown(reason) {
|
|
@@ -16524,6 +16821,23 @@ app44.all("*", (c) => {
|
|
|
16524
16821
|
});
|
|
16525
16822
|
var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
|
|
16526
16823
|
var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
16824
|
+
var waNativeGatewayUrl = `http://127.0.0.1:${port}`;
|
|
16825
|
+
var waChannelServerPath = process.env.MAXY_WA_CHANNEL_SERVER_PATH ?? resolve26(process.env.MAXY_PLATFORM_ROOT ?? join18(__dirname, ".."), "services/whatsapp-channel/dist/server.js");
|
|
16826
|
+
var waGateway = new WaGateway({
|
|
16827
|
+
gatewayUrl: waNativeGatewayUrl,
|
|
16828
|
+
serverPath: waChannelServerPath,
|
|
16829
|
+
ensureChannelSession: async ({ accountId, senderId, gatewayUrl, serverPath }) => {
|
|
16830
|
+
const result = await managerRcSpawn({
|
|
16831
|
+
sessionId: adminSessionIdFor(accountId, senderId),
|
|
16832
|
+
waChannel: { senderId, gatewayUrl, serverPath },
|
|
16833
|
+
logContext: `channel=whatsapp-native senderId=${senderId}`
|
|
16834
|
+
});
|
|
16835
|
+
if ("error" in result) {
|
|
16836
|
+
console.error(`[whatsapp-native] spawn-failed senderId=${senderId} error=${result.error} status=${result.status}`);
|
|
16837
|
+
}
|
|
16838
|
+
}
|
|
16839
|
+
});
|
|
16840
|
+
app44.route("/", waGateway.routes());
|
|
16527
16841
|
var httpServer = serve({ fetch: app44.fetch, port, hostname });
|
|
16528
16842
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
16529
16843
|
startAuthHealthHeartbeat();
|
|
@@ -16702,6 +17016,23 @@ init({
|
|
|
16702
17016
|
platformRoot: resolve26(process.env.MAXY_PLATFORM_ROOT ?? join18(__dirname, "..")),
|
|
16703
17017
|
accountConfig: bootAccountConfig,
|
|
16704
17018
|
onMessage: async (msg) => {
|
|
17019
|
+
if (process.env.MAXY_WA_NATIVE_CHANNEL === "1" && msg.agentType === "admin" && !msg.isOwnerMirror && msg.text) {
|
|
17020
|
+
try {
|
|
17021
|
+
void msg.composing().catch(() => {
|
|
17022
|
+
});
|
|
17023
|
+
await waGateway.handleInbound({
|
|
17024
|
+
accountId: msg.accountId,
|
|
17025
|
+
senderId: msg.senderPhone,
|
|
17026
|
+
text: msg.text,
|
|
17027
|
+
reply: msg.reply
|
|
17028
|
+
});
|
|
17029
|
+
} catch (err) {
|
|
17030
|
+
console.error(
|
|
17031
|
+
`[whatsapp-native] reject senderId=${msg.senderPhone} message=${err instanceof Error ? err.message : String(err)}`
|
|
17032
|
+
);
|
|
17033
|
+
}
|
|
17034
|
+
return;
|
|
17035
|
+
}
|
|
16705
17036
|
if ((msg.text || msg.mediaPath) && !msg.isOwnerMirror) {
|
|
16706
17037
|
try {
|
|
16707
17038
|
void msg.composing().catch(() => {
|