Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions services/imessage-bridge/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5726,6 +5726,16 @@ export class IMessageSession {
const sms = await this.sender.sendSMS(to, text);
if (sms.ok) {
this.logger.info({ to, lastService: st?.service, lastError: st?.error }, "pre-routed via SMS service (Text Message Forwarding) — iMessage not working for this contact");
// Audit the send HERE too: this branch returns before the outbound
// audit at the end of send(), so an RCS/SMS-only contact's replies
// were invisible to it. Caught by the first real end-to-end test —
// the reply landed, and the log that exists to measure reply quality
// had no record of it. Same shape as the main site so the two are
// greppable together.
this.logger.info(
{ to, textPreview: text.slice(0, 160), len: text.length, botSelf: isBotSelfMessage(text), via: "sms-preroute" },
"outbound sent",
);
this.recordBridgeSend(text);
this.enqueueOutboundEcho(to, text);
this.broadcast({ type: "agent_reply", data: { to, text, timestamp: Date.now() } });
Expand Down
22 changes: 22 additions & 0 deletions services/imessage-bridge/src/silent-drop-audit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,25 @@ describe("no reply-suppressing path returns without a trace", () => {
expect(src).toMatch(/this\.reportSilentDrops\(\);/);
});
});

describe("every send path reaches the outbound audit log", () => {
const src = readFileSync(new URL("./session.ts", import.meta.url), "utf8");

it("the SMS pre-route branch audits before it returns", () => {
// Found by the first real end-to-end test: an RCS-only contact got a real
// reply via Text Message Forwarding, but the branch returns early — before
// the audit at the end of send() — so the log built to measure reply
// quality had no record of the one reply that proved the path works.
const branch = src.slice(src.indexOf("pre-routed via SMS service"));
const upToReturn = branch.slice(0, branch.indexOf("return { ok: true }"));
expect(upToReturn).toMatch(/"outbound sent"/);
});

it("send() has an audit line for each early return", () => {
const fn = src.slice(src.indexOf("pre-routed via SMS service") - 4000);
const body = fn.slice(0, fn.indexOf("private recordBridgeSend"));
const audits = (body.match(/"outbound sent"/g) || []).length;
// One for the SMS pre-route branch, one for the normal path.
expect(audits).toBeGreaterThanOrEqual(2);
});
});
17 changes: 17 additions & 0 deletions services/whatsapp-bridge/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3486,6 +3486,16 @@ export class WhatsAppSession {
}
const sent = await this.socket.sendMessage(own, { text });
if (sent?.key?.id) this.bridgeSentIds.set(sent.key.id, Date.now());
// Audit here too: sendSelf talks to the socket DIRECTLY rather than going
// through sendMessage, so owner-facing traffic (briefs, digests, drafts,
// drop notices) was absent from the audit log. Same omission as the
// iMessage SMS pre-route branch — a send path that bypasses the one place
// the audit lives. botSelf is nearly always true here, which is exactly
// what makes the "real reply vs machinery" ratio meaningful.
this.logger.info(
{ to: own, textPreview: text.slice(0, 160), len: text.length, botSelf: isBotSelfMessage(text), via: "sendSelf" },
"outbound sent",
);
// ponytail: owner briefs are serial, so a single last-key field is enough
// for handleCenterCommand to remember/edit the Brief it just sent.
this.lastSelfSentKey = sent?.key ?? undefined;
Expand Down Expand Up @@ -7198,6 +7208,13 @@ export class WhatsAppSession {
if (!own || !this.socket) return;
try {
const sent = await this.socket.sendMessage(own, { text });
// Audit: confirmToSelf is the owner's MAIN reply channel on WhatsApp and
// it talks to the socket directly, like sendSelf. Without this the audit
// log sees almost no owner-side WhatsApp traffic at all.
this.logger.info(
{ to: own, textPreview: text.slice(0, 160), len: text.length, botSelf: isBotSelfMessage(text), via: "confirmToSelf" },
"outbound sent",
);
if (sent?.key?.id) {
this.bridgeSentIds.set(sent.key.id, Date.now());
// Best-effort retry-tracking: confirmToSelf is called from many
Expand Down
Loading