feat: add is_bot_message column and support dedicated phone numbers (#235)

* feat: add is_bot_message column and support dedicated phone numbers

Replace fragile content-prefix bot detection with an explicit
is_bot_message database column. The old prefix check (content NOT LIKE
'Andy:%') is kept as a backstop for pre-migration messages.

- Add is_bot_message column with automatic backfill migration
- Add ASSISTANT_HAS_OWN_NUMBER env var to skip name prefix when the
  assistant has its own WhatsApp number
- Move prefix logic into WhatsApp channel (no longer a router concern)
- Remove prefixAssistantName from Channel interface
- Load .env via dotenv so launchd-managed processes pick up config
- WhatsApp bot detection: fromMe for own number, prefix match for shared

Based on #160 and #173.

Co-Authored-By: Stefan Gasser <stefan@stefangasser.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract shared .env parser and remove dotenv dependency

Extract .env parsing into src/env.ts, used by both config.ts and
container-runner.ts. Reads only requested keys without loading secrets
into process.env, avoiding leaking API keys to child processes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Stefan Gasser <stefan@stefangasser.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-02-15 15:31:57 +02:00
committed by GitHub
parent c8ab3d95e1
commit 9261a25531
13 changed files with 202 additions and 140 deletions

View File

@@ -124,11 +124,7 @@ async function processGroupMessages(chatJid: string): Promise<boolean> {
const isMainGroup = group.folder === MAIN_GROUP_FOLDER;
const sinceTimestamp = lastAgentTimestamp[chatJid] || '';
const missedMessages = getMessagesSince(
chatJid,
sinceTimestamp,
ASSISTANT_NAME,
);
const missedMessages = getMessagesSince(chatJid, sinceTimestamp, ASSISTANT_NAME);
if (missedMessages.length === 0) return true;
@@ -177,7 +173,7 @@ async function processGroupMessages(chatJid: string): Promise<boolean> {
const text = raw.replace(/<internal>[\s\S]*?<\/internal>/g, '').trim();
logger.info({ group: group.name }, `Agent output: ${raw.slice(0, 200)}`);
if (text) {
await whatsapp.sendMessage(chatJid, `${ASSISTANT_NAME}: ${text}`);
await whatsapp.sendMessage(chatJid, text);
outputSentToUser = true;
}
// Only reset idle timer on actual results, not session-update markers (result: null)
@@ -300,11 +296,7 @@ async function startMessageLoop(): Promise<void> {
while (true) {
try {
const jids = Object.keys(registeredGroups);
const { messages, newTimestamp } = getNewMessages(
jids,
lastTimestamp,
ASSISTANT_NAME,
);
const { messages, newTimestamp } = getNewMessages(jids, lastTimestamp, ASSISTANT_NAME);
if (messages.length > 0) {
logger.info({ count: messages.length }, 'New messages');
@@ -488,7 +480,7 @@ async function main(): Promise<void> {
queue,
onProcess: (groupJid, proc, containerName, groupFolder) => queue.registerProcess(groupJid, proc, containerName, groupFolder),
sendMessage: async (jid, rawText) => {
const text = formatOutbound(whatsapp, rawText);
const text = formatOutbound(rawText);
if (text) await whatsapp.sendMessage(jid, text);
},
});