digital-brain 1.1.20 → 1.1.26
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/docs/AUTOMATIONS.md +1 -0
- package/package.json +1 -1
- package/whatsapp-web/auto-reply.mjs +41 -9
package/docs/AUTOMATIONS.md
CHANGED
|
@@ -163,6 +163,7 @@ Guardrails:
|
|
|
163
163
|
- with `--provider codex-app`, requires a Codex desktop bridge automation/thread that writes response files
|
|
164
164
|
- requires `--allow "Name"` or `--contact "+15551234567"` unless `--allow-all` is explicitly passed
|
|
165
165
|
- single-threads reply generation so multiple incoming chats do not trigger overlapping sends
|
|
166
|
+
- debounces live messages per chat, default 12000ms, so message bursts get one combined reply; override with `--reply-debounce-ms <ms>`
|
|
166
167
|
- skips likely business, notification, OTP, and service chats unless `--include-businesses` is passed or the chat is explicitly allowlisted by name or contact number
|
|
167
168
|
- processes unread chats on startup unless `--no-process-unread` is passed
|
|
168
169
|
- skips groups unless `--include-groups` is passed
|
package/package.json
CHANGED
|
@@ -41,6 +41,7 @@ const processUnreadOnStart = !Boolean(args["no-process-unread"]);
|
|
|
41
41
|
const cooldownMinutes = numberArg("cooldown-minutes", 20);
|
|
42
42
|
const maxRepliesPerChat = numberArg("max-replies-per-chat", 0);
|
|
43
43
|
const maxContextChars = numberArg("max-context-chars", 12000);
|
|
44
|
+
const replyDebounceMs = numberArg("reply-debounce-ms", 12000);
|
|
44
45
|
const outboundLogMode = args["log-mode"] || config.outboundLogMode || "metadata";
|
|
45
46
|
const state = loadState();
|
|
46
47
|
const whitelist = loadWhitelist();
|
|
@@ -48,6 +49,7 @@ const hasStoredScope = Object.keys(whitelist.allowedChats || {}).length > 0;
|
|
|
48
49
|
const hasInitialScope = allowAll || allow.length > 0 || contactNumbers.length > 0 || hasStoredScope;
|
|
49
50
|
const interactiveTerminal = Boolean(input.isTTY && output.isTTY);
|
|
50
51
|
let queueTail = Promise.resolve();
|
|
52
|
+
const pendingLiveReplies = new Map();
|
|
51
53
|
const rl = interactiveTerminal ? readline.createInterface({ input, output }) : null;
|
|
52
54
|
|
|
53
55
|
fs.mkdirSync(outboundDir, { recursive: true });
|
|
@@ -89,6 +91,7 @@ client.on("ready", async () => {
|
|
|
89
91
|
if (provider === "codex-app") console.log(`Codex App bridge: ${codexAppBridgeDir}`);
|
|
90
92
|
if (!includeBusinesses) console.log("Likely business, notification, OTP, and service chats are skipped by default.");
|
|
91
93
|
console.log(maxRepliesPerChat > 0 ? `Reply cap: ${maxRepliesPerChat} per chat per run.` : "Reply cap: unlimited.");
|
|
94
|
+
console.log(`Live reply debounce: ${replyDebounceMs}ms.`);
|
|
92
95
|
try {
|
|
93
96
|
if (processUnreadOnStart) {
|
|
94
97
|
await processUnreadChats();
|
|
@@ -104,7 +107,7 @@ client.on("ready", async () => {
|
|
|
104
107
|
client.on("message", async (message) => {
|
|
105
108
|
try {
|
|
106
109
|
console.log(`Received WhatsApp message event: ${summarize(message.body || "[non-text message]")}`);
|
|
107
|
-
|
|
110
|
+
await scheduleLiveMessage(message);
|
|
108
111
|
} catch (error) {
|
|
109
112
|
console.error(`Auto-reply error: ${error.message}`);
|
|
110
113
|
}
|
|
@@ -190,6 +193,20 @@ function enqueueMessage(message, knownChat = null) {
|
|
|
190
193
|
return queueTail;
|
|
191
194
|
}
|
|
192
195
|
|
|
196
|
+
async function scheduleLiveMessage(message) {
|
|
197
|
+
if (message.fromMe || message.isStatus) return;
|
|
198
|
+
const chat = await message.getChat();
|
|
199
|
+
const key = chatKey(chat);
|
|
200
|
+
const existing = pendingLiveReplies.get(key);
|
|
201
|
+
if (existing?.timer) clearTimeout(existing.timer);
|
|
202
|
+
const timer = setTimeout(() => {
|
|
203
|
+
pendingLiveReplies.delete(key);
|
|
204
|
+
enqueueMessage(message, chat);
|
|
205
|
+
}, replyDebounceMs);
|
|
206
|
+
pendingLiveReplies.set(key, { message, chat, timer });
|
|
207
|
+
console.log(`Queued debounced reply for ${chatName(chat)} in ${replyDebounceMs}ms.`);
|
|
208
|
+
}
|
|
209
|
+
|
|
193
210
|
async function ensureChatApproved(chat, recentMessages) {
|
|
194
211
|
const name = chatName(chat);
|
|
195
212
|
if (isExplicitlyAllowed(chat) || isChatWhitelisted(chat)) return true;
|
|
@@ -274,7 +291,7 @@ async function handleMessage(message, knownChat = null) {
|
|
|
274
291
|
const reply = await generateReply(prompt);
|
|
275
292
|
console.log(`Generated reply for ${name} in ${Date.now() - startedAt}ms: ${summarize(reply || "[empty reply]")}`);
|
|
276
293
|
const finalReply = disclosure.required && !containsDisclosure(reply)
|
|
277
|
-
? `
|
|
294
|
+
? `btw ai is helping me reply rn. ${reply}`
|
|
278
295
|
: reply;
|
|
279
296
|
|
|
280
297
|
if (!finalReply.trim()) return;
|
|
@@ -292,6 +309,11 @@ async function handleMessage(message, knownChat = null) {
|
|
|
292
309
|
|
|
293
310
|
function buildPrompt({ chatName, incomingBody, recentMessages, disclosureRequired }) {
|
|
294
311
|
const memory = readMemoryContext(chatName);
|
|
312
|
+
const selfExamples = recentMessages
|
|
313
|
+
.filter((item) => item.fromMe && compact(item.body || ""))
|
|
314
|
+
.slice(-6)
|
|
315
|
+
.map((item) => `- ${compact(item.body || "")}`)
|
|
316
|
+
.join("\n") || "- No recent self examples in this chat.";
|
|
295
317
|
const transcript = recentMessages
|
|
296
318
|
.slice(-12)
|
|
297
319
|
.map((item) => `${item.fromMe ? "Me" : chatName}: ${compact(item.body || "")}`)
|
|
@@ -299,16 +321,23 @@ function buildPrompt({ chatName, incomingBody, recentMessages, disclosureRequire
|
|
|
299
321
|
return [
|
|
300
322
|
"You are helping the user reply on WhatsApp.",
|
|
301
323
|
"Write exactly one message to send as the user.",
|
|
302
|
-
"Be natural,
|
|
324
|
+
"Be natural, terse, and relationship-appropriate.",
|
|
325
|
+
"Default to 1-12 words for casual chats unless the incoming message clearly requires detail.",
|
|
303
326
|
"First infer the immediate intent of the current conversation from the recent chat. Continue that thread only.",
|
|
327
|
+
"If the other person sent multiple messages in a row, answer the combined latest intent once.",
|
|
328
|
+
"Do not repeat facts, plans, suggestions, or context that were already stated in the recent chat unless confirming them briefly.",
|
|
329
|
+
"If the chat includes a URL, do not pretend you opened or inspected it. React only to what the sender said, or ask if the user should check it.",
|
|
304
330
|
"Do not start with hey/hi unless the recent chat itself uses that greeting pattern.",
|
|
305
|
-
"
|
|
306
|
-
"
|
|
307
|
-
"
|
|
331
|
+
"Primary style source: the user's recent messages in this exact chat. Match their length, casing, bluntness, and punctuation from those examples.",
|
|
332
|
+
"Secondary style source: My Communication Style. Use it only to break ties, not to add extra slang.",
|
|
333
|
+
"Do not perform a persona. Do not intensify the tone beyond the user's examples.",
|
|
334
|
+
"Do not force bro, lol, haha, lmao, wild, rn, emojis, or question marks. Use them only if the user's recent examples in this chat use them naturally.",
|
|
335
|
+
"Avoid assistant-like niceness and filler such as sounds perfect, happy to, sure thing, smooth, quick, no worries, no demon stuff, digital prep chef, digital neil, spitting facts, living in the future, or let’s unless that exact energy is already in the chat.",
|
|
336
|
+
"If the recipient asks about AI, answer directly in the user's casual tone and do not overexplain.",
|
|
308
337
|
"Do not sound like customer support, corporate email, or a generic AI assistant.",
|
|
309
338
|
"Use the user's local memory context, but do not reveal private notes or say you read a vault.",
|
|
310
339
|
"Do not invent facts, commitments, times, or promises.",
|
|
311
|
-
disclosureRequired ? "This send requires AI disclosure.
|
|
340
|
+
disclosureRequired ? "This send requires AI disclosure. Use a short casual disclosure like: btw ai is helping me reply rn. Do not make it cute or apologetic." : "Do not mention AI unless the incoming message asks about it.",
|
|
312
341
|
"",
|
|
313
342
|
`Chat: ${chatName}`,
|
|
314
343
|
"",
|
|
@@ -318,6 +347,9 @@ function buildPrompt({ chatName, incomingBody, recentMessages, disclosureRequire
|
|
|
318
347
|
"Recent chat:",
|
|
319
348
|
transcript,
|
|
320
349
|
"",
|
|
350
|
+
"Recent examples of the user's own messages in this chat:",
|
|
351
|
+
selfExamples,
|
|
352
|
+
"",
|
|
321
353
|
`Incoming message: ${incomingBody}`,
|
|
322
354
|
"",
|
|
323
355
|
"Reply:",
|
|
@@ -371,8 +403,8 @@ async function generateOpenAiReply(prompt) {
|
|
|
371
403
|
body: JSON.stringify({
|
|
372
404
|
model,
|
|
373
405
|
input: prompt,
|
|
374
|
-
max_output_tokens:
|
|
375
|
-
temperature: 0.
|
|
406
|
+
max_output_tokens: 80,
|
|
407
|
+
temperature: 0.25,
|
|
376
408
|
}),
|
|
377
409
|
});
|
|
378
410
|
} catch (error) {
|