From e881b3c5debdfeadb1b6d669f35c668624d77b08 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 2 Dec 2025 06:52:56 +0000 Subject: [PATCH 1/2] Document exclamation mark escaping workaround for Claude Code Add symlink CLAUDE.md -> AGENTS.md for Claude Code compatibility. --- AGENTS.md | 16 ++++++++++++++++ CLAUDE.md | 1 + 2 files changed, 17 insertions(+) create mode 120000 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index 6c364db09..1212fa1c9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,3 +36,19 @@ ## Agent-Specific Notes - If the relay is running in tmux (`warelay-relay`), restart it after code changes: kill pane/session and run `pnpm warelay relay --verbose` inside tmux. Check tmux before editing; keep the watcher healthy if you start it. - Also read the shared guardrails at `~/Projects/oracle/AGENTS.md` and `~/Projects/agent-scripts/AGENTS.MD` before making changes; align with any cross-repo rules noted there. + +## Exclamation Mark Escaping Workaround +The Claude Code Bash tool escapes `!` to `\!` in command arguments. When using `warelay send` with messages containing exclamation marks, use heredoc syntax: + +```bash +# WRONG - will send "Hello\!" with backslash +warelay send --provider web --to "+1234" --message 'Hello!' + +# CORRECT - use heredoc to avoid escaping +warelay send --provider web --to "+1234" --message "$(cat <<'EOF' +Hello! +EOF +)" +``` + +This is a Claude Code quirk, not a warelay bug. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 96152f6577ee348853ee8504f6fd6422ecb4a266 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 2 Dec 2025 06:58:17 +0000 Subject: [PATCH 2/2] Add typing indicator after IPC send After sending via IPC, automatically show "composing" indicator so user knows more messages may be coming from the running session. --- CHANGELOG.md | 1 + src/web/auto-reply.ts | 8 +++++++- src/web/inbound.ts | 8 ++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73f6caaa1..eff96cb5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Changes - **IPC server for relay:** The web relay now starts a Unix socket server at `~/.warelay/relay.sock`. Commands like `warelay send --provider web` automatically connect via IPC when the relay is running, falling back to direct connection otherwise. +- **Typing indicator after IPC send:** After sending a message via IPC (e.g., `warelay send`), the relay now automatically shows the typing indicator ("composing") to signal that more messages may be coming. - **Auto-recovery from stuck WhatsApp sessions:** Added watchdog timer that detects when WhatsApp event emitter stops firing (e.g., after Bad MAC decryption errors) and automatically restarts the connection after 30 minutes of no message activity. Heartbeat logging now includes `minutesSinceLastMessage` and warns when >30 minutes without messages. The 30-minute timeout is intentionally longer than typical `heartbeatMinutes` configs to avoid false positives. - **Early allowFrom filtering:** Unauthorized senders are now blocked in `inbound.ts` BEFORE encryption/decryption attempts, preventing Bad MAC errors from corrupting session state. Previously, messages from unauthorized senders would trigger decryption failures that could silently kill the event emitter. - **Test isolation improvements:** Mock `loadConfig()` in all test files to prevent loading real user config (with emojis/prefixes) during tests. Default test config now has no prefixes/timestamps for cleaner assertions. diff --git a/src/web/auto-reply.ts b/src/web/auto-reply.ts index 9020a21fd..cc965dab0 100644 --- a/src/web/auto-reply.ts +++ b/src/web/auto-reply.ts @@ -702,7 +702,7 @@ export async function monitorWebProvider( // Start IPC server so `warelay send` can use this connection // instead of creating a new one (which would corrupt Signal session) - if ("sendMessage" in listener) { + if ("sendMessage" in listener && "sendComposingTo" in listener) { startIpcServer(async (to, message, mediaUrl) => { let mediaBuffer: Buffer | undefined; let mediaType: string | undefined; @@ -721,6 +721,12 @@ export async function monitorWebProvider( } } logInfo(`📤 IPC send to ${to}: ${message.substring(0, 50)}...`, runtime); + // Show typing indicator after send so user knows more may be coming + try { + await listener.sendComposingTo(to); + } catch { + // Ignore typing indicator errors - not critical + } return result; }); } diff --git a/src/web/inbound.ts b/src/web/inbound.ts index bafcac10d..9d6a4df6a 100644 --- a/src/web/inbound.ts +++ b/src/web/inbound.ts @@ -257,6 +257,14 @@ export async function monitorWebInbox(options: { const result = await sock.sendMessage(jid, payload); return { messageId: result?.key?.id ?? "unknown" }; }, + /** + * Send typing indicator ("composing") to a chat. + * Used after IPC send to show more messages are coming. + */ + sendComposingTo: async (to: string): Promise => { + const jid = `${to.replace(/^\+/, "")}@s.whatsapp.net`; + await sock.sendPresenceUpdate("composing", jid); + }, } as const; }