@diegoaltoworks/chatter 0.45.0 → 0.48.0
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/README.md +19 -5
- package/dist/auth/apikeys.d.ts.map +1 -1
- package/dist/auth/secretStrength.d.ts +8 -0
- package/dist/auth/secretStrength.d.ts.map +1 -0
- package/dist/bin/create-apikey.js +22 -3
- package/dist/bin/wa-pair.js +59 -11
- package/dist/channels/gates.d.ts +9 -0
- package/dist/channels/gates.d.ts.map +1 -1
- package/dist/channels/index.d.ts +1 -0
- package/dist/channels/index.d.ts.map +1 -1
- package/dist/channels/index.js +252 -8
- package/dist/channels/index.mjs +250 -8
- package/dist/channels/pipeline.d.ts +106 -0
- package/dist/channels/pipeline.d.ts.map +1 -0
- package/dist/channels/senders.d.ts +2 -1
- package/dist/channels/senders.d.ts.map +1 -1
- package/dist/channels/whatsapp/channel.d.ts +4 -0
- package/dist/channels/whatsapp/channel.d.ts.map +1 -1
- package/dist/channels/whatsapp/crypto.d.ts +17 -3
- package/dist/channels/whatsapp/crypto.d.ts.map +1 -1
- package/dist/channels/whatsapp/inbound.d.ts +34 -4
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.d.ts +2 -1
- package/dist/channels/whatsapp/index.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +395 -159
- package/dist/channels/whatsapp/index.mjs +394 -160
- package/dist/channels/whatsapp/router.d.ts +107 -0
- package/dist/channels/whatsapp/router.d.ts.map +1 -0
- package/dist/core/answer.d.ts +5 -2
- package/dist/core/answer.d.ts.map +1 -1
- package/dist/core/logger.d.ts +23 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/messages.d.ts +8 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/pipeline.d.ts +2 -0
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/retrieval.d.ts +4 -0
- package/dist/core/retrieval.d.ts.map +1 -1
- package/dist/core/serve-static.d.ts +38 -0
- package/dist/core/serve-static.d.ts.map +1 -0
- package/dist/flows/engine.d.ts +3 -0
- package/dist/flows/engine.d.ts.map +1 -1
- package/dist/flows/index.d.ts +1 -0
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +91 -25
- package/dist/flows/index.mjs +91 -26
- package/dist/flows/intent.d.ts +2 -1
- package/dist/flows/intent.d.ts.map +1 -1
- package/dist/flows/loader.d.ts +5 -3
- package/dist/flows/loader.d.ts.map +1 -1
- package/dist/flows/types.d.ts +9 -0
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/history/index.d.ts +17 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/index.js +100 -0
- package/dist/history/index.mjs +73 -0
- package/dist/history/tursoStore.d.ts +26 -0
- package/dist/history/tursoStore.d.ts.map +1 -0
- package/dist/history/types.d.ts +18 -0
- package/dist/history/types.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1202 -32462
- package/dist/index.mjs +1118 -32414
- package/dist/mcp-server/cost-tracker.d.ts +11 -6
- package/dist/mcp-server/cost-tracker.d.ts.map +1 -1
- package/dist/mcp-server/logger.d.ts +5 -2
- package/dist/mcp-server/logger.d.ts.map +1 -1
- package/dist/mcp-server/sdk.d.ts +23 -0
- package/dist/mcp-server/sdk.d.ts.map +1 -0
- package/dist/mcp-server/tool-input.d.ts +12 -0
- package/dist/mcp-server/tool-input.d.ts.map +1 -0
- package/dist/mcp-server/types.d.ts +10 -3
- package/dist/mcp-server/types.d.ts.map +1 -1
- package/dist/mcp-server.d.ts +1 -2
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +192 -111
- package/dist/mcp-server.mjs +179 -98
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/bodyLimit.d.ts +11 -0
- package/dist/middleware/bodyLimit.d.ts.map +1 -0
- package/dist/middleware/clientKey.d.ts +15 -0
- package/dist/middleware/clientKey.d.ts.map +1 -0
- package/dist/middleware/cors.d.ts.map +1 -1
- package/dist/middleware/jwt.d.ts +0 -3
- package/dist/middleware/jwt.d.ts.map +1 -1
- package/dist/middleware/originMatch.d.ts +27 -0
- package/dist/middleware/originMatch.d.ts.map +1 -0
- package/dist/middleware/ratelimit.d.ts +0 -6
- package/dist/middleware/ratelimit.d.ts.map +1 -1
- package/dist/middleware/referrer.d.ts +5 -1
- package/dist/middleware/referrer.d.ts.map +1 -1
- package/dist/middleware/securityHeaders.d.ts +20 -0
- package/dist/middleware/securityHeaders.d.ts.map +1 -0
- package/dist/middleware/session.d.ts +2 -1
- package/dist/middleware/session.d.ts.map +1 -1
- package/dist/middleware/windowBucket.d.ts +19 -0
- package/dist/middleware/windowBucket.d.ts.map +1 -0
- package/dist/personas/index.js +20 -1
- package/dist/personas/index.mjs +20 -1
- package/dist/personas/resolver.d.ts +3 -0
- package/dist/personas/resolver.d.ts.map +1 -1
- package/dist/routes/demo.d.ts.map +1 -1
- package/dist/routes/openai.d.ts.map +1 -1
- package/dist/routes/private.d.ts.map +1 -1
- package/dist/routes/public.d.ts.map +1 -1
- package/dist/scheduler/compose.d.ts +2 -1
- package/dist/scheduler/compose.d.ts.map +1 -1
- package/dist/scheduler/index.js +29 -9
- package/dist/scheduler/index.mjs +29 -9
- package/dist/scheduler/scheduler.d.ts +3 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/tick.d.ts +2 -0
- package/dist/scheduler/tick.d.ts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +726 -538
- package/dist/server.mjs +708 -527
- package/dist/types.d.ts +87 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +30 -9
package/dist/channels/index.mjs
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
function matches(re, text) {
|
|
3
3
|
return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
|
|
4
4
|
}
|
|
5
|
+
function isBlockedByAllowlist(msg, config) {
|
|
6
|
+
return !msg.fromBot && msg.text.trim().length > 0 && !msg.isDirectMessage && config.allowedChats.length > 0 && !config.allowedChats.includes(msg.chatId);
|
|
7
|
+
}
|
|
5
8
|
function decideChannelAction(msg, config) {
|
|
6
9
|
if (msg.fromBot || !msg.text.trim()) return "ignore";
|
|
7
10
|
if (!msg.isDirectMessage) {
|
|
8
|
-
if (
|
|
9
|
-
return "ignore";
|
|
10
|
-
}
|
|
11
|
+
if (isBlockedByAllowlist(msg, config)) return "ignore";
|
|
11
12
|
if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
|
|
12
13
|
if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
|
|
13
14
|
if (config.mutedChats.has(msg.chatId)) return "ignore";
|
|
@@ -44,13 +45,252 @@ function isEffectivelyFromSelf(msg, registry) {
|
|
|
44
45
|
return msg.fromBot || isFromAnySession(msg.senderId, registry);
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
// src/core/guardrails.ts
|
|
49
|
+
function detectLeakage(text) {
|
|
50
|
+
const flags = [
|
|
51
|
+
/system prompt/i,
|
|
52
|
+
/hidden instruction/i,
|
|
53
|
+
/here (are|is) (my|the) rules/i,
|
|
54
|
+
/BEGIN SYSTEM PROMPT/i,
|
|
55
|
+
/tool instructions/i
|
|
56
|
+
];
|
|
57
|
+
return flags.some((rx) => rx.test(text));
|
|
58
|
+
}
|
|
59
|
+
function scrubOutput(text) {
|
|
60
|
+
return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
|
|
61
|
+
}
|
|
62
|
+
function guardOutput(text) {
|
|
63
|
+
if (detectLeakage(text)) {
|
|
64
|
+
return "Sorry, I can't share internal instructions. How else can I help?";
|
|
65
|
+
}
|
|
66
|
+
return scrubOutput(text);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/core/llm.ts
|
|
70
|
+
var DEFAULT_MODEL = "gpt-4o";
|
|
71
|
+
async function completeOnce({
|
|
72
|
+
client,
|
|
73
|
+
system,
|
|
74
|
+
messages,
|
|
75
|
+
temperature = 0.2,
|
|
76
|
+
model = DEFAULT_MODEL
|
|
77
|
+
}) {
|
|
78
|
+
const res = await client.chat.completions.create({
|
|
79
|
+
model,
|
|
80
|
+
temperature,
|
|
81
|
+
messages: [{ role: "system", content: system }, ...messages]
|
|
82
|
+
});
|
|
83
|
+
const text = res.choices[0]?.message?.content ?? "";
|
|
84
|
+
return {
|
|
85
|
+
content: guardOutput(text),
|
|
86
|
+
usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/core/answer.ts
|
|
91
|
+
var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
|
|
92
|
+
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
|
|
93
|
+
const result = await answerFn({ system, messages, mode, sender, conversationId });
|
|
94
|
+
const raw = typeof result === "string" ? result : result?.content;
|
|
95
|
+
const usage = typeof result === "string" ? void 0 : result?.usage;
|
|
96
|
+
return {
|
|
97
|
+
content: guardOutput(typeof raw === "string" ? raw : ""),
|
|
98
|
+
usage: usage ?? noUsage()
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function answerOnce({
|
|
102
|
+
answerFn,
|
|
103
|
+
client,
|
|
104
|
+
system,
|
|
105
|
+
messages,
|
|
106
|
+
mode,
|
|
107
|
+
sender,
|
|
108
|
+
conversationId,
|
|
109
|
+
temperature,
|
|
110
|
+
model
|
|
111
|
+
}) {
|
|
112
|
+
if (answerFn) {
|
|
113
|
+
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
|
|
114
|
+
}
|
|
115
|
+
return completeOnce({ client, system, messages, temperature, model });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/core/buckets.ts
|
|
119
|
+
function defaultBuckets(mode) {
|
|
120
|
+
return ["base", mode];
|
|
121
|
+
}
|
|
122
|
+
function isIdentified(sender) {
|
|
123
|
+
return typeof sender === "string" && sender.trim().length > 0;
|
|
124
|
+
}
|
|
125
|
+
function normalize(buckets) {
|
|
126
|
+
const out = [];
|
|
127
|
+
for (const raw of buckets) {
|
|
128
|
+
if (typeof raw !== "string") continue;
|
|
129
|
+
const bucket = raw.trim();
|
|
130
|
+
if (bucket && !out.includes(bucket)) out.push(bucket);
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
async function resolveBuckets({
|
|
135
|
+
mode,
|
|
136
|
+
sender,
|
|
137
|
+
bucketsFor
|
|
138
|
+
}) {
|
|
139
|
+
if (!bucketsFor) return void 0;
|
|
140
|
+
const identified = isIdentified(sender);
|
|
141
|
+
const requested = await bucketsFor(identified ? { mode, sender } : { mode });
|
|
142
|
+
if (!Array.isArray(requested)) return void 0;
|
|
143
|
+
const cleaned = normalize(requested);
|
|
144
|
+
if (identified) return cleaned;
|
|
145
|
+
const ceiling = new Set(defaultBuckets(mode));
|
|
146
|
+
return cleaned.filter((bucket) => ceiling.has(bucket));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/core/messages.ts
|
|
150
|
+
function lastUserMessage(messages) {
|
|
151
|
+
return [...messages].reverse().find((m) => m.role === "user");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/core/pipeline.ts
|
|
155
|
+
var MODE_SETTINGS = {
|
|
156
|
+
public: { topK: 6, label: "Context" },
|
|
157
|
+
private: { topK: 8, label: "Internal Context" }
|
|
158
|
+
};
|
|
159
|
+
async function prepareChat({
|
|
160
|
+
store,
|
|
161
|
+
prompts,
|
|
162
|
+
mode,
|
|
163
|
+
messages,
|
|
164
|
+
personaLayer,
|
|
165
|
+
channelHint,
|
|
166
|
+
buckets
|
|
167
|
+
}) {
|
|
168
|
+
const lastUserMsg = lastUserMessage(messages);
|
|
169
|
+
if (!lastUserMsg) {
|
|
170
|
+
throw new Error("no user message found in conversation");
|
|
171
|
+
}
|
|
172
|
+
const { topK, label } = MODE_SETTINGS[mode];
|
|
173
|
+
const ctx = await store.query(lastUserMsg.content, topK, buckets ?? defaultBuckets(mode));
|
|
174
|
+
const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
|
|
175
|
+
const hint = channelHint?.trim();
|
|
176
|
+
const system = [
|
|
177
|
+
prompts.baseSystemRules,
|
|
178
|
+
persona,
|
|
179
|
+
...hint ? [hint] : [],
|
|
180
|
+
`${label}:
|
|
181
|
+
${ctx.join("\n\n")}`
|
|
182
|
+
].join("\n\n");
|
|
183
|
+
return { system, messages, context: ctx };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/channels/pipeline.ts
|
|
187
|
+
var DEFAULT_HISTORY_LIMIT = 20;
|
|
188
|
+
var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
|
|
189
|
+
var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
|
|
190
|
+
async function resolvePersonaLayer(config, ctx) {
|
|
191
|
+
if (!config.personaResolver) return void 0;
|
|
192
|
+
try {
|
|
193
|
+
return await config.personaResolver(ctx);
|
|
194
|
+
} catch {
|
|
195
|
+
return void 0;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function createInboundPipeline(deps, config) {
|
|
199
|
+
const mutedChats = /* @__PURE__ */ new Set();
|
|
200
|
+
const now = config.now ?? Date.now;
|
|
201
|
+
const group = config.groupRateLimit ?? DEFAULT_GROUP_RATE_LIMIT;
|
|
202
|
+
const dm = config.dmRateLimit ?? DEFAULT_DM_RATE_LIMIT;
|
|
203
|
+
const underGroupRateLimit = createSlidingWindowRateLimiter(group.max, group.windowMs, now);
|
|
204
|
+
const underDmRateLimit = createSlidingWindowRateLimiter(dm.max, dm.windowMs, now);
|
|
205
|
+
const mode = "public";
|
|
206
|
+
return async function handleInboundMessage(msg, turn) {
|
|
207
|
+
const allowedChats = config.allowedChats ?? [];
|
|
208
|
+
const action = decideChannelAction(msg, {
|
|
209
|
+
allowedChats,
|
|
210
|
+
mutedChats,
|
|
211
|
+
muteRegex: config.muteRegex,
|
|
212
|
+
unmuteRegex: config.unmuteRegex
|
|
213
|
+
});
|
|
214
|
+
if (action === "mute") {
|
|
215
|
+
mutedChats.add(msg.chatId);
|
|
216
|
+
if (config.muteReply) await turn.reply.sendGateReply(msg.chatId, config.muteReply);
|
|
217
|
+
return { action: "mute" };
|
|
218
|
+
}
|
|
219
|
+
if (action === "unmute") {
|
|
220
|
+
mutedChats.delete(msg.chatId);
|
|
221
|
+
if (config.unmuteReply) await turn.reply.sendGateReply(msg.chatId, config.unmuteReply);
|
|
222
|
+
return { action: "unmute" };
|
|
223
|
+
}
|
|
224
|
+
if (action === "ignore") {
|
|
225
|
+
return { action: "ignore" };
|
|
226
|
+
}
|
|
227
|
+
if (!underReplyRateLimit(msg, { dm: underDmRateLimit, group: underGroupRateLimit })) {
|
|
228
|
+
return { action: "rate-limited" };
|
|
229
|
+
}
|
|
230
|
+
const sender = typeof turn.sender === "function" ? await turn.sender() : turn.sender ?? msg.senderId;
|
|
231
|
+
const conversationId = turn.conversationId ?? msg.chatId;
|
|
232
|
+
if (turn.intercept && await turn.intercept(sender)) {
|
|
233
|
+
return { action: "intercepted" };
|
|
234
|
+
}
|
|
235
|
+
const personaLayer = await resolvePersonaLayer(config, { sender, text: msg.text });
|
|
236
|
+
const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
|
|
237
|
+
const priorTurns = config.history ? await config.history.store.load(
|
|
238
|
+
conversationId,
|
|
239
|
+
config.history.limit ?? DEFAULT_HISTORY_LIMIT
|
|
240
|
+
) : [];
|
|
241
|
+
const messages = [...priorTurns, { role: "user", content: msg.text }];
|
|
242
|
+
await config.history?.store.append(conversationId, { role: "user", content: msg.text });
|
|
243
|
+
const { system } = await prepareChat({
|
|
244
|
+
store: deps.store,
|
|
245
|
+
prompts: deps.prompts,
|
|
246
|
+
mode,
|
|
247
|
+
messages,
|
|
248
|
+
personaLayer,
|
|
249
|
+
channelHint: config.channelHint,
|
|
250
|
+
buckets
|
|
251
|
+
});
|
|
252
|
+
const { content } = await answerOnce({
|
|
253
|
+
answerFn: config.answerFn,
|
|
254
|
+
client: deps.client,
|
|
255
|
+
system,
|
|
256
|
+
messages,
|
|
257
|
+
mode,
|
|
258
|
+
sender,
|
|
259
|
+
conversationId,
|
|
260
|
+
model: config.model
|
|
261
|
+
});
|
|
262
|
+
if (!content) {
|
|
263
|
+
return { action: "ignore" };
|
|
264
|
+
}
|
|
265
|
+
await config.history?.store.append(conversationId, { role: "assistant", content });
|
|
266
|
+
await turn.reply.sendAnswer(msg.chatId, content);
|
|
267
|
+
return { action: "reply", content };
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/core/logger.ts
|
|
272
|
+
var LEVEL_ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
273
|
+
function createConsoleLogger(level = "info") {
|
|
274
|
+
const threshold = LEVEL_ORDER[level];
|
|
275
|
+
const write = (at) => (...args) => {
|
|
276
|
+
if (LEVEL_ORDER[at] < threshold) return;
|
|
277
|
+
console.error(`[${at}]`, ...args);
|
|
278
|
+
};
|
|
279
|
+
return {
|
|
280
|
+
debug: write("debug"),
|
|
281
|
+
info: write("info"),
|
|
282
|
+
warn: write("warn"),
|
|
283
|
+
error: write("error")
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
47
287
|
// src/channels/senders.ts
|
|
48
|
-
function createSenderRegistry() {
|
|
288
|
+
function createSenderRegistry(logger = createConsoleLogger()) {
|
|
49
289
|
const senders = /* @__PURE__ */ new Map();
|
|
50
290
|
return {
|
|
51
291
|
register(name, sender) {
|
|
52
292
|
if (senders.has(name)) {
|
|
53
|
-
|
|
293
|
+
logger.warn(`Channel sender "${name}" is already registered; overwriting.`);
|
|
54
294
|
}
|
|
55
295
|
senders.set(name, sender);
|
|
56
296
|
},
|
|
@@ -67,7 +307,7 @@ function createSenderRegistry() {
|
|
|
67
307
|
await sender.sendText(chatId, text);
|
|
68
308
|
return true;
|
|
69
309
|
} catch (error) {
|
|
70
|
-
|
|
310
|
+
logger.warn(`Channel sender "${name}" sendText failed:`, error);
|
|
71
311
|
return false;
|
|
72
312
|
}
|
|
73
313
|
},
|
|
@@ -78,7 +318,7 @@ function createSenderRegistry() {
|
|
|
78
318
|
await sender.sendVoice(chatId, payload);
|
|
79
319
|
return true;
|
|
80
320
|
} catch (error) {
|
|
81
|
-
|
|
321
|
+
logger.warn(`Channel sender "${name}" sendVoice failed:`, error);
|
|
82
322
|
return false;
|
|
83
323
|
}
|
|
84
324
|
},
|
|
@@ -89,16 +329,18 @@ function createSenderRegistry() {
|
|
|
89
329
|
await sender.sendMedia(chatId, payload);
|
|
90
330
|
return true;
|
|
91
331
|
} catch (error) {
|
|
92
|
-
|
|
332
|
+
logger.warn(`Channel sender "${name}" sendMedia failed:`, error);
|
|
93
333
|
return false;
|
|
94
334
|
}
|
|
95
335
|
}
|
|
96
336
|
};
|
|
97
337
|
}
|
|
98
338
|
export {
|
|
339
|
+
createInboundPipeline,
|
|
99
340
|
createSenderRegistry,
|
|
100
341
|
createSlidingWindowRateLimiter,
|
|
101
342
|
decideChannelAction,
|
|
343
|
+
isBlockedByAllowlist,
|
|
102
344
|
isEffectivelyFromSelf,
|
|
103
345
|
isFromAnySession,
|
|
104
346
|
underReplyRateLimit
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel-agnostic inbound turn — the part of a channel's message handling
|
|
3
|
+
* that has nothing to do with its wire format.
|
|
4
|
+
*
|
|
5
|
+
* A transport (WhatsApp, or any future channel) still owns everything about
|
|
6
|
+
* its own shape: parsing mentions, resolving a sender's real identity,
|
|
7
|
+
* loop-guarding its own traffic, and any transport-specific short-circuit
|
|
8
|
+
* (an image request, a slash command). Once that work has produced a
|
|
9
|
+
* {@link ChannelMessage}, though, every channel needs the exact same next
|
|
10
|
+
* steps — decide reply/ignore/mute/unmute, rate-limit, resolve a persona and
|
|
11
|
+
* retrieval buckets, load/append conversation history, answer through
|
|
12
|
+
* `prepareChat`/`answerOnce`, and deliver the result — and this is that,
|
|
13
|
+
* extracted once so a second channel (Telegram, SMS, ...) never has to
|
|
14
|
+
* re-derive it. See docs/build-a-channel.md for a worked example.
|
|
15
|
+
*/
|
|
16
|
+
import type OpenAI from "openai";
|
|
17
|
+
import type { AnswerFn } from "../core/answer";
|
|
18
|
+
import type { BucketsFor } from "../core/buckets";
|
|
19
|
+
import type { PromptLoader } from "../core/prompts";
|
|
20
|
+
import type { VectorStore } from "../core/retrieval";
|
|
21
|
+
import type { HistoryStore } from "../history/types";
|
|
22
|
+
import { type ChannelMessage } from "./gates";
|
|
23
|
+
/** Where a pipeline turn's output goes. Distinct from `ChannelSender` (`./senders`) — a transport implementing this may thread/quote the chat answer without doing the same for a plain gate acknowledgement. */
|
|
24
|
+
export interface InboundReplySender {
|
|
25
|
+
/** Deliver the chat answer for `chatId`. */
|
|
26
|
+
sendAnswer(chatId: string, text: string): Promise<void>;
|
|
27
|
+
/** Deliver a mute/unmute acknowledgement for `chatId` — never the chat answer. */
|
|
28
|
+
sendGateReply(chatId: string, text: string): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export interface InboundPipelineDeps {
|
|
31
|
+
client: OpenAI;
|
|
32
|
+
store: VectorStore;
|
|
33
|
+
prompts: PromptLoader;
|
|
34
|
+
}
|
|
35
|
+
export interface InboundPipelineConfig {
|
|
36
|
+
answerFn?: AnswerFn;
|
|
37
|
+
bucketsFor?: BucketsFor;
|
|
38
|
+
model?: string;
|
|
39
|
+
/** Extra system-prompt section describing the delivery channel; passed through to `prepareChat`. */
|
|
40
|
+
channelHint?: string;
|
|
41
|
+
/** A throw/rejection is treated as "no persona" for that turn rather than failing the whole reply. */
|
|
42
|
+
personaResolver?: (ctx: {
|
|
43
|
+
sender: string;
|
|
44
|
+
text: string;
|
|
45
|
+
}) => string | undefined | Promise<string | undefined>;
|
|
46
|
+
/** Off by default — a channel stays single-turn until a store is configured. */
|
|
47
|
+
history?: {
|
|
48
|
+
store: HistoryStore;
|
|
49
|
+
/** Most recent turns to load per reply. @default 20 */
|
|
50
|
+
limit?: number;
|
|
51
|
+
};
|
|
52
|
+
/** Group chats eligible for a reply. Empty (default) = every group. Has no effect on DMs, which always reply. */
|
|
53
|
+
allowedChats?: string[];
|
|
54
|
+
muteRegex?: RegExp;
|
|
55
|
+
unmuteRegex?: RegExp;
|
|
56
|
+
/** Neutral, overridable acknowledgements — this module ships no bot personality; unset = silent mute/unmute. */
|
|
57
|
+
muteReply?: string;
|
|
58
|
+
unmuteReply?: string;
|
|
59
|
+
dmRateLimit?: {
|
|
60
|
+
max: number;
|
|
61
|
+
windowMs: number;
|
|
62
|
+
};
|
|
63
|
+
groupRateLimit?: {
|
|
64
|
+
max: number;
|
|
65
|
+
windowMs: number;
|
|
66
|
+
};
|
|
67
|
+
now?: () => number;
|
|
68
|
+
}
|
|
69
|
+
export interface InboundTurn {
|
|
70
|
+
/** Delivers this turn's gate ack / chat answer — see {@link InboundReplySender}. */
|
|
71
|
+
reply: InboundReplySender;
|
|
72
|
+
/**
|
|
73
|
+
* Identity handed to `bucketsFor`/`personaResolver`/`answerFn` — a
|
|
74
|
+
* transport's resolved sender (e.g. a phone number), which may differ
|
|
75
|
+
* from `ChannelMessage.senderId`'s raw wire identity. Accepted as a thunk
|
|
76
|
+
* so a costly resolution only runs once gates and rate-limiting pass.
|
|
77
|
+
* Omitted -> `msg.senderId`.
|
|
78
|
+
*/
|
|
79
|
+
sender?: string | (() => string | Promise<string>);
|
|
80
|
+
/** Thread key for history/`answerFn`. Omitted -> `msg.chatId`. */
|
|
81
|
+
conversationId?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Consulted after gates and rate-limiting pass, before persona/buckets/
|
|
84
|
+
* history/answer run — a transport-specific feature (WhatsApp's
|
|
85
|
+
* image-request routing, say) that fully owns the reply for messages it
|
|
86
|
+
* claims. `true` stops the turn here; `false`/`undefined` falls through to
|
|
87
|
+
* the normal chat turn.
|
|
88
|
+
*/
|
|
89
|
+
intercept?: (sender: string) => boolean | undefined | Promise<boolean | undefined>;
|
|
90
|
+
}
|
|
91
|
+
export type InboundPipelineOutcome = {
|
|
92
|
+
action: "ignore" | "mute" | "unmute" | "rate-limited" | "intercepted";
|
|
93
|
+
} | {
|
|
94
|
+
action: "reply";
|
|
95
|
+
content: string;
|
|
96
|
+
};
|
|
97
|
+
/** The reusable handler `createInboundPipeline` returns — one call per inbound message. */
|
|
98
|
+
export type InboundPipeline = (msg: ChannelMessage, turn: InboundTurn) => Promise<InboundPipelineOutcome>;
|
|
99
|
+
/**
|
|
100
|
+
* Builds the reusable per-session (or per-channel) inbound handler: gates
|
|
101
|
+
* and rate limiters live in its closure, exactly like a hand-rolled
|
|
102
|
+
* transport handler's, so a channel can create one pipeline and feed it
|
|
103
|
+
* every message it receives.
|
|
104
|
+
*/
|
|
105
|
+
export declare function createInboundPipeline(deps: InboundPipelineDeps, config: InboundPipelineConfig): InboundPipeline;
|
|
106
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/channels/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* `false` instead of throwing when the name is unknown or the underlying
|
|
7
7
|
* send fails, so a disconnected channel can never crash a caller.
|
|
8
8
|
*/
|
|
9
|
+
import { type Logger } from "../core/logger";
|
|
9
10
|
/** What a channel implements to receive outbound sends. Voice/media payload shapes are transport-defined. */
|
|
10
11
|
export interface ChannelSender {
|
|
11
12
|
sendText(chatId: string, text: string): Promise<void>;
|
|
@@ -26,5 +27,5 @@ export interface ChannelSenderRegistry {
|
|
|
26
27
|
sendMedia(name: string, chatId: string, payload: unknown): Promise<boolean>;
|
|
27
28
|
}
|
|
28
29
|
/** One registry per server: channels register on start, callers send by name. */
|
|
29
|
-
export declare function createSenderRegistry(): ChannelSenderRegistry;
|
|
30
|
+
export declare function createSenderRegistry(logger?: Logger): ChannelSenderRegistry;
|
|
30
31
|
//# sourceMappingURL=senders.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"senders.d.ts","sourceRoot":"","sources":["../../src/channels/senders.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,6GAA6G;AAC7G,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,sFAAsF;IACtF,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,sFAAsF;IACtF,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACpD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,iGAAiG;IACjG,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,4FAA4F;IAC5F,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5E,4FAA4F;IAC5F,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7E;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"senders.d.ts","sourceRoot":"","sources":["../../src/channels/senders.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAElE,6GAA6G;AAC7G,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,sFAAsF;IACtF,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,sFAAsF;IACtF,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACpD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,iGAAiG;IACjG,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,4FAA4F;IAC5F,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5E,4FAA4F;IAC5F,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7E;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAClC,MAAM,GAAE,MAA8B,GACrC,qBAAqB,CAkDvB"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* this channel as opt-in, never a default.
|
|
17
17
|
*/
|
|
18
18
|
import type { WAMessage, WASocket } from "@whiskeysockets/baileys";
|
|
19
|
+
import { type Logger } from "../../core/logger";
|
|
19
20
|
import type { Channel } from "../index";
|
|
20
21
|
import { type Baileys } from "./baileys";
|
|
21
22
|
import { type WaLeaseStore } from "./lease";
|
|
@@ -49,6 +50,8 @@ export interface WhatsAppChannelConfig {
|
|
|
49
50
|
staleMs?: number;
|
|
50
51
|
heartbeatMs?: number;
|
|
51
52
|
waitMs?: number;
|
|
53
|
+
/** Logger for connection/lease lifecycle events. Default: a console logger writing to stderr. */
|
|
54
|
+
logger?: Logger;
|
|
52
55
|
}
|
|
53
56
|
/** A live, leased session: what `stop()` (or losing the lease) must tear down. */
|
|
54
57
|
export interface WaSessionHandle {
|
|
@@ -78,6 +81,7 @@ export interface LeaseGatedConnectDeps {
|
|
|
78
81
|
heartbeatMs?: number;
|
|
79
82
|
waitMs?: number;
|
|
80
83
|
schedule?: (fn: () => void, ms: number) => void;
|
|
84
|
+
logger?: Logger;
|
|
81
85
|
}
|
|
82
86
|
/**
|
|
83
87
|
* Acquire `sessionId`'s lease before connecting — two instances must never
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnE,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAIxC,OAAO,EAAE,KAAK,OAAO,EAAmD,MAAM,WAAW,CAAC;AAC1F,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,SAAS,CAAC;AAKjB;gFACgF;AAChF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,SAAS,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,uJAAuJ;IACvJ,aAAa,EAAE,MAAM,CAAC;IACtB,qLAAqL;IACrL,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,iIAAiI;IACjI,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE;QAAE,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAA;KAAE,CAAC;IACvC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ9F;AAYD,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,YAAY,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,8GAA8G;IAC9G,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA4Df;AAED,4IAA4I;AAC5I,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAsM5E"}
|
|
@@ -3,8 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The stored auth state grants full account access, so it never touches the
|
|
5
5
|
* database in plaintext. The key derives from a caller-supplied secret (any
|
|
6
|
-
* strong passphrase, kept stable across restarts and deploys)
|
|
6
|
+
* strong passphrase, kept stable across restarts and deploys) via scrypt with
|
|
7
|
+
* a fresh salt per ciphertext, so cracking one row's key doesn't help crack
|
|
8
|
+
* another's, and offline guessing against the passphrase is far more
|
|
9
|
+
* expensive than the plain sha256 hash this format replaces. scrypt runs via
|
|
10
|
+
* the async, thread-pooled API — Baileys fans read/write out per key id (see
|
|
11
|
+
* `authState.ts`), sometimes dozens per event, and the sync variant would
|
|
12
|
+
* stall the whole process's event loop for the duration of each one.
|
|
13
|
+
*
|
|
14
|
+
* Payload layout: `version(1) | salt(16) | iv(12) | tag(16) | ciphertext`,
|
|
15
|
+
* base64-encoded. Rows written before this format (`iv(12) | tag(16) |
|
|
16
|
+
* ciphertext`, sha256-derived key, no version byte) still decrypt: every
|
|
17
|
+
* caller re-encrypts on write (see `authState.ts`), so a legacy row migrates
|
|
18
|
+
* to the new format the next time something rewrites it — a row Baileys never
|
|
19
|
+
* touches again stays in the legacy format indefinitely, which is fine since
|
|
20
|
+
* it still decrypts.
|
|
7
21
|
*/
|
|
8
|
-
export declare function encrypt(plaintext: string, secret: string): string
|
|
9
|
-
export declare function decrypt(payload: string, secret: string): string
|
|
22
|
+
export declare function encrypt(plaintext: string, secret: string): Promise<string>;
|
|
23
|
+
export declare function decrypt(payload: string, secret: string): Promise<string>;
|
|
10
24
|
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/crypto.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAqBH,wBAAsB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYhF;AAqBD,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc9E"}
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
* answerFn: deps.config.answerFn,
|
|
38
38
|
* bucketsFor: deps.config.bucketsFor,
|
|
39
39
|
* registry: new Map(),
|
|
40
|
+
* logger: deps.logger,
|
|
40
41
|
* });
|
|
41
42
|
* },
|
|
42
43
|
* });
|
|
@@ -44,11 +45,13 @@
|
|
|
44
45
|
*/
|
|
45
46
|
import type { WAMessage, WASocket } from "@whiskeysockets/baileys";
|
|
46
47
|
import type OpenAI from "openai";
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
48
|
+
import type { AnswerFn } from "../../core/answer";
|
|
49
|
+
import type { BucketsFor } from "../../core/buckets";
|
|
50
|
+
import { type Logger } from "../../core/logger";
|
|
49
51
|
import type { PromptLoader } from "../../core/prompts";
|
|
50
52
|
import type { VectorStore } from "../../core/retrieval";
|
|
51
|
-
import {
|
|
53
|
+
import type { HistoryStore } from "../../history/types";
|
|
54
|
+
import { type ChannelMessage, type SessionIdentityRegistry } from "../gates";
|
|
52
55
|
import type { WhatsAppMessageEvent } from "./channel";
|
|
53
56
|
import type { WhatsAppImageHandler } from "./images";
|
|
54
57
|
export declare function isGroupJid(jid: string): boolean;
|
|
@@ -105,6 +108,20 @@ export declare function extractText(message: WAMessage): string;
|
|
|
105
108
|
* bot would go silent on being addressed rather than answer.
|
|
106
109
|
*/
|
|
107
110
|
export declare function stripOwnMentions(text: string, ownIds: string[]): string;
|
|
111
|
+
export interface ResolvedWaMessage {
|
|
112
|
+
msg: ChannelMessage;
|
|
113
|
+
/** This session's own identities (phone + LID, normalized), as just written to `registry`. */
|
|
114
|
+
ownIds: string[];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Turns a raw Baileys message into the {@link ChannelMessage} shape every
|
|
118
|
+
* consumer works from — own-identity resolution, the loop guard, and
|
|
119
|
+
* mention/reply-to detection — done ONCE per message. `createWhatsAppInboundHandler`
|
|
120
|
+
* and `./router`'s `createWhatsAppMessageRouter` both call this rather than
|
|
121
|
+
* each re-deriving it, so registering more detectors on the router never
|
|
122
|
+
* multiplies this work.
|
|
123
|
+
*/
|
|
124
|
+
export declare function resolveWaMessage(event: WhatsAppMessageEvent, registry: SessionIdentityRegistry): ResolvedWaMessage;
|
|
108
125
|
export interface WhatsAppInboundConfig {
|
|
109
126
|
client: OpenAI;
|
|
110
127
|
store: VectorStore;
|
|
@@ -156,8 +173,21 @@ export interface WhatsAppInboundConfig {
|
|
|
156
173
|
* `./images` is configured.
|
|
157
174
|
*/
|
|
158
175
|
images?: WhatsAppImageHandler;
|
|
176
|
+
/**
|
|
177
|
+
* Optional conversation history (see `./history`). Off by default —
|
|
178
|
+
* WhatsApp stays single-turn, unchanged, until a store is configured. When
|
|
179
|
+
* set, prior turns for the chat are loaded before answering and the new
|
|
180
|
+
* turn (user message, then reply) is appended after.
|
|
181
|
+
*/
|
|
182
|
+
history?: {
|
|
183
|
+
store: HistoryStore;
|
|
184
|
+
/** Most recent turns to load per reply. @default 20 */
|
|
185
|
+
limit?: number;
|
|
186
|
+
};
|
|
159
187
|
now?: () => number;
|
|
188
|
+
/** Logger for gate/dispatch diagnostics. Default: a console logger writing to stderr. */
|
|
189
|
+
logger?: Logger;
|
|
160
190
|
}
|
|
161
|
-
/** Builds the `onMessage` handler for `createWhatsAppChannel` (see `./channel`) — see the module docstring for how to wire `ServerDependencies` into it. */
|
|
191
|
+
/** Builds the `onMessage` handler for `createWhatsAppChannel` (see `./channel`) — see the module docstring for how to wire `ServerDependencies` into it. Everything past turning a raw Baileys message into a `ChannelMessage` runs through `./channels`' `createInboundPipeline`, shared with every other channel. */
|
|
162
192
|
export declare function createWhatsAppInboundHandler(config: WhatsAppInboundConfig): (event: WhatsAppMessageEvent) => Promise<void>;
|
|
163
193
|
//# sourceMappingURL=inbound.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,6 +30,7 @@ export { loadBaileys } from "./baileys";
|
|
|
30
30
|
export { acquireSessionLease, createWhatsAppChannel, type LeaseGatedConnectDeps, reconnectDelayMs, senderNameFor, shutdownWaSessions, type WaSessionHandle, type WhatsAppChannelConfig, type WhatsAppMessageEvent, } from "./channel";
|
|
31
31
|
export { decrypt, encrypt } from "./crypto";
|
|
32
32
|
export { createWhatsAppImageHandler, type DrawRequestDetectorConfig, drawSubject, isDrawRequest, type WhatsAppImageHandler, type WhatsAppImageHandlerDeps, type WhatsAppImageRequestContext, type WhatsAppImageStrings, } from "./images";
|
|
33
|
-
export { createWhatsAppInboundHandler, extractText, isGroupJid, jidsMatch, jidToPhoneNumber, messageContext, senderPhoneFor, stripOwnMentions, type WaMessageContext, type WhatsAppInboundConfig, } from "./inbound";
|
|
33
|
+
export { createWhatsAppInboundHandler, extractText, isGroupJid, jidsMatch, jidToPhoneNumber, messageContext, type ResolvedWaMessage, resolveWaMessage, senderPhoneFor, stripOwnMentions, type WaMessageContext, type WhatsAppInboundConfig, } from "./inbound";
|
|
34
34
|
export { canAcquireLease, createTursoWaLeaseStore, LEASE_HEARTBEAT_MS, LEASE_STALE_MS, LEASE_WAIT_MS, type WaLeaseRow, type WaLeaseStore, } from "./lease";
|
|
35
|
+
export { createWhatsAppMessageRouter, type MessageDetector, type ParallelDetector, type ReplaceDetector, type WaDetectorContext, type WhatsAppMessageRouterConfig, } from "./router";
|
|
35
36
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,WAAW,EACX,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,WAAW,EACX,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,2BAA2B,EAC3B,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,GACjC,MAAM,UAAU,CAAC"}
|