diff --git a/src/auto-reply/reply/inbound-sender-meta.ts b/src/auto-reply/reply/inbound-sender-meta.ts index 7895942ab..1417cb767 100644 --- a/src/auto-reply/reply/inbound-sender-meta.ts +++ b/src/auto-reply/reply/inbound-sender-meta.ts @@ -33,7 +33,8 @@ function hasSenderMetaLine(body: string, ctx: MsgContext): boolean { if (candidates.length === 0) return false; return candidates.some((candidate) => { const escaped = escapeRegExp(candidate); - const pattern = new RegExp(`(^|\\n)${escaped}:\\s`, "i"); + // Check for sender at start of line OR after envelope header bracket + const pattern = new RegExp(`(^|\\n|\\]\\s*)${escaped}:\\s`, "i"); return pattern.test(body); }); } diff --git a/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts b/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts index 1a385ff5d..13486e188 100644 --- a/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts +++ b/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts @@ -381,8 +381,9 @@ describe("monitorIMessageProvider", () => { expect(replyMock).toHaveBeenCalledOnce(); const ctx = replyMock.mock.calls[0]?.[0] as { Body?: string; ChatType?: string }; expect(ctx.ChatType).toBe("group"); - expect(String(ctx.Body ?? "")).toContain("[from:"); - expect(String(ctx.Body ?? "")).toContain("+15550002222"); + // Sender should appear as prefix in group messages (no redundant [from:] suffix) + expect(String(ctx.Body ?? "")).toContain("+15550002222:"); + expect(String(ctx.Body ?? "")).not.toContain("[from:"); expect(sendMock).toHaveBeenCalledWith( "chat_id:42", diff --git a/src/imessage/monitor/monitor-provider.ts b/src/imessage/monitor/monitor-provider.ts index 3e84e4e10..3970cd1b2 100644 --- a/src/imessage/monitor/monitor-provider.ts +++ b/src/imessage/monitor/monitor-provider.ts @@ -165,16 +165,8 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P async function handleMessageNow(message: IMessagePayload) { const senderRaw = message.sender ?? ""; const sender = senderRaw.trim(); - if (!sender) { - logVerbose(`imessage: skipping message (no sender), chat_id=${message.chat_id}`); - return; - } + if (!sender) return; const senderNormalized = normalizeIMessageHandle(sender); - if (!senderNormalized) { - logVerbose( - `imessage: sender normalized to empty, raw="${sender}", chat_id=${message.chat_id}`, - ); - } if (message.is_from_me) return; const chatId = message.chat_id ?? undefined; @@ -391,14 +383,13 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P } const chatTarget = formatIMessageChatTarget(chatId); + // For groups: use chat name or just "Group" (channel "iMessage" is already shown) + // For DMs: show sender, only add id: suffix if raw differs from normalized const fromLabel = isGroup - ? `${message.chat_name || "iMessage Group"} id:${chatId ?? "unknown"}` - : `${senderNormalized} id:${sender}`; - if (isGroup && !senderNormalized) { - logVerbose( - `imessage: group message missing normalized sender, raw="${sender}", chat_id=${chatId}`, - ); - } + ? `${message.chat_name || "Group"} id:${chatId ?? "unknown"}` + : senderNormalized === sender + ? senderNormalized + : `${senderNormalized} id:${sender}`; const body = formatInboundEnvelope({ channel: "iMessage", from: fromLabel, diff --git a/src/signal/monitor/event-handler.inbound-contract.test.ts b/src/signal/monitor/event-handler.inbound-contract.test.ts index 2e8dff16a..a91106b3c 100644 --- a/src/signal/monitor/event-handler.inbound-contract.test.ts +++ b/src/signal/monitor/event-handler.inbound-contract.test.ts @@ -60,7 +60,9 @@ describe("signal createSignalEventHandler inbound contract", () => { expect(capturedCtx).toBeTruthy(); expectInboundContextContract(capturedCtx!); - expect(String(capturedCtx?.Body ?? "")).toContain("[from:"); + // Sender should appear as prefix in group messages (no redundant [from:] suffix) expect(String(capturedCtx?.Body ?? "")).toContain("Alice"); + expect(String(capturedCtx?.Body ?? "")).toMatch(/Alice.*:/); + expect(String(capturedCtx?.Body ?? "")).not.toContain("[from:"); }); }); diff --git a/src/signal/monitor/event-handler.ts b/src/signal/monitor/event-handler.ts index 8a86ef945..42a0f2dab 100644 --- a/src/signal/monitor/event-handler.ts +++ b/src/signal/monitor/event-handler.ts @@ -65,9 +65,13 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { }; async function handleSignalInboundMessage(entry: SignalInboundEntry) { + // For groups: use group name or just "Group" (channel "Signal" is already shown) + // For DMs: show sender, only add id: suffix if display differs from name const fromLabel = entry.isGroup - ? `${entry.groupName ?? "Signal Group"} id:${entry.groupId}` - : `${entry.senderName} id:${entry.senderDisplay}`; + ? `${entry.groupName || "Group"} id:${entry.groupId}` + : entry.senderName === entry.senderDisplay + ? entry.senderName + : `${entry.senderName} id:${entry.senderDisplay}`; const body = formatInboundEnvelope({ channel: "Signal", from: fromLabel,