refactor!: rename chat providers to channels
This commit is contained in:
@@ -31,7 +31,7 @@ export type ResolvedWhatsAppAccount = {
|
||||
};
|
||||
|
||||
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
|
||||
const accounts = cfg.whatsapp?.accounts;
|
||||
const accounts = cfg.channels?.whatsapp?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") return [];
|
||||
return Object.keys(accounts).filter(Boolean);
|
||||
}
|
||||
@@ -52,7 +52,7 @@ function resolveAccountConfig(
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string,
|
||||
): WhatsAppAccountConfig | undefined {
|
||||
const accounts = cfg.whatsapp?.accounts;
|
||||
const accounts = cfg.channels?.whatsapp?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") return undefined;
|
||||
const entry = accounts[accountId] as WhatsAppAccountConfig | undefined;
|
||||
return entry;
|
||||
@@ -115,23 +115,31 @@ export function resolveWhatsAppAccount(params: {
|
||||
enabled,
|
||||
messagePrefix:
|
||||
accountCfg?.messagePrefix ??
|
||||
params.cfg.whatsapp?.messagePrefix ??
|
||||
params.cfg.channels?.whatsapp?.messagePrefix ??
|
||||
params.cfg.messages?.messagePrefix,
|
||||
authDir,
|
||||
isLegacyAuthDir: isLegacy,
|
||||
selfChatMode: accountCfg?.selfChatMode ?? params.cfg.whatsapp?.selfChatMode,
|
||||
dmPolicy: accountCfg?.dmPolicy ?? params.cfg.whatsapp?.dmPolicy,
|
||||
allowFrom: accountCfg?.allowFrom ?? params.cfg.whatsapp?.allowFrom,
|
||||
selfChatMode:
|
||||
accountCfg?.selfChatMode ?? params.cfg.channels?.whatsapp?.selfChatMode,
|
||||
dmPolicy: accountCfg?.dmPolicy ?? params.cfg.channels?.whatsapp?.dmPolicy,
|
||||
allowFrom:
|
||||
accountCfg?.allowFrom ?? params.cfg.channels?.whatsapp?.allowFrom,
|
||||
groupAllowFrom:
|
||||
accountCfg?.groupAllowFrom ?? params.cfg.whatsapp?.groupAllowFrom,
|
||||
groupPolicy: accountCfg?.groupPolicy ?? params.cfg.whatsapp?.groupPolicy,
|
||||
accountCfg?.groupAllowFrom ??
|
||||
params.cfg.channels?.whatsapp?.groupAllowFrom,
|
||||
groupPolicy:
|
||||
accountCfg?.groupPolicy ?? params.cfg.channels?.whatsapp?.groupPolicy,
|
||||
textChunkLimit:
|
||||
accountCfg?.textChunkLimit ?? params.cfg.whatsapp?.textChunkLimit,
|
||||
mediaMaxMb: accountCfg?.mediaMaxMb ?? params.cfg.whatsapp?.mediaMaxMb,
|
||||
accountCfg?.textChunkLimit ??
|
||||
params.cfg.channels?.whatsapp?.textChunkLimit,
|
||||
mediaMaxMb:
|
||||
accountCfg?.mediaMaxMb ?? params.cfg.channels?.whatsapp?.mediaMaxMb,
|
||||
blockStreaming:
|
||||
accountCfg?.blockStreaming ?? params.cfg.whatsapp?.blockStreaming,
|
||||
ackReaction: accountCfg?.ackReaction ?? params.cfg.whatsapp?.ackReaction,
|
||||
groups: accountCfg?.groups ?? params.cfg.whatsapp?.groups,
|
||||
accountCfg?.blockStreaming ??
|
||||
params.cfg.channels?.whatsapp?.blockStreaming,
|
||||
ackReaction:
|
||||
accountCfg?.ackReaction ?? params.cfg.channels?.whatsapp?.ackReaction,
|
||||
groups: accountCfg?.groups ?? params.cfg.channels?.whatsapp?.groups,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { info, success } from "../globals.js";
|
||||
import { getChildLogger } from "../logging.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||
import type { Provider } from "../utils.js";
|
||||
import type { WebChannel } from "../utils.js";
|
||||
import { jidToE164, resolveUserPath } from "../utils.js";
|
||||
|
||||
export function resolveDefaultWebAuthDir(): string {
|
||||
@@ -161,7 +161,7 @@ export function getWebAuthAgeMs(
|
||||
export function logWebSelfId(
|
||||
authDir: string = resolveDefaultWebAuthDir(),
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
includeProviderPrefix = false,
|
||||
includeChannelPrefix = false,
|
||||
) {
|
||||
// Human-friendly log of the currently linked personal web session.
|
||||
const { e164, jid } = readWebSelfId(authDir);
|
||||
@@ -169,19 +169,19 @@ export function logWebSelfId(
|
||||
e164 || jid
|
||||
? `${e164 ?? "unknown"}${jid ? ` (jid ${jid})` : ""}`
|
||||
: "unknown";
|
||||
const prefix = includeProviderPrefix ? "Web Provider: " : "";
|
||||
const prefix = includeChannelPrefix ? "Web Channel: " : "";
|
||||
runtime.log(info(`${prefix}${details}`));
|
||||
}
|
||||
|
||||
export async function pickProvider(
|
||||
pref: Provider | "auto",
|
||||
export async function pickWebChannel(
|
||||
pref: WebChannel | "auto",
|
||||
authDir: string = resolveDefaultWebAuthDir(),
|
||||
): Promise<Provider> {
|
||||
const choice: Provider = pref === "auto" ? "web" : pref;
|
||||
): Promise<WebChannel> {
|
||||
const choice: WebChannel = pref === "auto" ? "web" : pref;
|
||||
const hasWeb = await webAuthExists(authDir);
|
||||
if (!hasWeb) {
|
||||
throw new Error(
|
||||
"No WhatsApp Web session found. Run `clawdbot providers login --verbose` to link.",
|
||||
"No WhatsApp Web session found. Run `clawdbot channels login --channel whatsapp --verbose` to link.",
|
||||
);
|
||||
}
|
||||
return choice;
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
},
|
||||
groupActivation?: "always" | "mention",
|
||||
): boolean {
|
||||
const ackConfig = cfg.whatsapp?.ackReaction;
|
||||
const ackConfig = cfg.channels?.whatsapp?.ackReaction;
|
||||
const emoji = (ackConfig?.emoji ?? "").trim();
|
||||
const directEnabled = ackConfig?.direct ?? true;
|
||||
const groupMode = ackConfig?.group ?? "mentions";
|
||||
@@ -43,7 +43,7 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
describe("direct chat", () => {
|
||||
it("should react when direct=true", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀", direct: true } },
|
||||
channels: { whatsapp: { ackReaction: { emoji: "👀", direct: true } } },
|
||||
};
|
||||
expect(
|
||||
shouldSendReaction(cfg, {
|
||||
@@ -55,7 +55,7 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
|
||||
it("should not react when direct=false", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀", direct: false } },
|
||||
channels: { whatsapp: { ackReaction: { emoji: "👀", direct: false } } },
|
||||
};
|
||||
expect(
|
||||
shouldSendReaction(cfg, {
|
||||
@@ -67,7 +67,7 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
|
||||
it("should not react when emoji is empty", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "", direct: true } },
|
||||
channels: { whatsapp: { ackReaction: { emoji: "", direct: true } } },
|
||||
};
|
||||
expect(
|
||||
shouldSendReaction(cfg, {
|
||||
@@ -79,7 +79,7 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
|
||||
it("should not react when message id is missing", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀", direct: true } },
|
||||
channels: { whatsapp: { ackReaction: { emoji: "👀", direct: true } } },
|
||||
};
|
||||
expect(
|
||||
shouldSendReaction(cfg, {
|
||||
@@ -92,7 +92,9 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
describe("group chat - always mode", () => {
|
||||
it("should react to all messages when group=always", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "always" } },
|
||||
channels: {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "always" } },
|
||||
},
|
||||
};
|
||||
expect(
|
||||
shouldSendReaction(cfg, {
|
||||
@@ -105,7 +107,9 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
|
||||
it("should react even with mention when group=always", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "always" } },
|
||||
channels: {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "always" } },
|
||||
},
|
||||
};
|
||||
expect(
|
||||
shouldSendReaction(cfg, {
|
||||
@@ -120,7 +124,9 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
describe("group chat - mentions mode", () => {
|
||||
it("should react when mentioned", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "mentions" } },
|
||||
channels: {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "mentions" } },
|
||||
},
|
||||
};
|
||||
expect(
|
||||
shouldSendReaction(cfg, {
|
||||
@@ -133,7 +139,9 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
|
||||
it("should not react when not mentioned", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "mentions" } },
|
||||
channels: {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "mentions" } },
|
||||
},
|
||||
};
|
||||
expect(
|
||||
shouldSendReaction(
|
||||
@@ -150,7 +158,9 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
|
||||
it("should react to all messages when group activation is always", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "mentions" } },
|
||||
channels: {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "mentions" } },
|
||||
},
|
||||
};
|
||||
expect(
|
||||
shouldSendReaction(
|
||||
@@ -169,7 +179,9 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
describe("group chat - never mode", () => {
|
||||
it("should not react even with mention", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "never" } },
|
||||
channels: {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "never" } },
|
||||
},
|
||||
};
|
||||
expect(
|
||||
shouldSendReaction(cfg, {
|
||||
@@ -182,7 +194,9 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
|
||||
it("should not react without mention", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "never" } },
|
||||
channels: {
|
||||
whatsapp: { ackReaction: { emoji: "👀", group: "never" } },
|
||||
},
|
||||
};
|
||||
expect(
|
||||
shouldSendReaction(cfg, {
|
||||
@@ -197,8 +211,10 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
describe("combinations", () => {
|
||||
it("direct=false, group=always: only groups", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: {
|
||||
ackReaction: { emoji: "✅", direct: false, group: "always" },
|
||||
channels: {
|
||||
whatsapp: {
|
||||
ackReaction: { emoji: "✅", direct: false, group: "always" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -217,8 +233,10 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
|
||||
it("direct=true, group=never: only direct", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: {
|
||||
ackReaction: { emoji: "🤖", direct: true, group: "never" },
|
||||
channels: {
|
||||
whatsapp: {
|
||||
ackReaction: { emoji: "🤖", direct: true, group: "never" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -239,7 +257,7 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
describe("defaults", () => {
|
||||
it("should default direct=true", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀" } },
|
||||
channels: { whatsapp: { ackReaction: { emoji: "👀" } } },
|
||||
};
|
||||
expect(shouldSendReaction(cfg, { id: "m1", chatType: "direct" })).toBe(
|
||||
true,
|
||||
@@ -248,7 +266,7 @@ describe("WhatsApp ack reaction logic", () => {
|
||||
|
||||
it("should default group=mentions", () => {
|
||||
const cfg: ClawdbotConfig = {
|
||||
whatsapp: { ackReaction: { emoji: "👀" } },
|
||||
channels: { whatsapp: { ackReaction: { emoji: "👀" } } },
|
||||
};
|
||||
|
||||
expect(
|
||||
|
||||
@@ -23,7 +23,7 @@ import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { resetLogger, setLoggerOverride } from "../logging.js";
|
||||
import {
|
||||
HEARTBEAT_TOKEN,
|
||||
monitorWebProvider,
|
||||
monitorWebChannel,
|
||||
SILENT_REPLY_TOKEN,
|
||||
} from "./auto-reply.js";
|
||||
import {
|
||||
@@ -115,14 +115,12 @@ describe("partial reply gating", () => {
|
||||
const replyResolver = vi.fn().mockResolvedValue({ text: "final reply" });
|
||||
|
||||
const mockConfig: ClawdbotConfig = {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
};
|
||||
|
||||
setLoadConfigMock(mockConfig);
|
||||
|
||||
await monitorWebProvider(
|
||||
await monitorWebChannel(
|
||||
false,
|
||||
async ({ onMessage }) => {
|
||||
await onMessage({
|
||||
@@ -210,15 +208,13 @@ describe("partial reply gating", () => {
|
||||
const replyResolver = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
const mockConfig: ClawdbotConfig = {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
session: { store: store.storePath },
|
||||
};
|
||||
|
||||
setLoadConfigMock(mockConfig);
|
||||
|
||||
await monitorWebProvider(
|
||||
await monitorWebChannel(
|
||||
false,
|
||||
async ({ onMessage }) => {
|
||||
await onMessage({
|
||||
@@ -242,22 +238,19 @@ describe("partial reply gating", () => {
|
||||
|
||||
let stored: Record<
|
||||
string,
|
||||
{ lastProvider?: string; lastTo?: string }
|
||||
{ lastChannel?: string; lastTo?: string }
|
||||
> | null = null;
|
||||
for (let attempt = 0; attempt < 50; attempt += 1) {
|
||||
stored = JSON.parse(await fs.readFile(store.storePath, "utf8")) as Record<
|
||||
string,
|
||||
{ lastProvider?: string; lastTo?: string }
|
||||
{ lastChannel?: string; lastTo?: string }
|
||||
>;
|
||||
if (
|
||||
stored[mainSessionKey]?.lastProvider &&
|
||||
stored[mainSessionKey]?.lastTo
|
||||
)
|
||||
if (stored[mainSessionKey]?.lastChannel && stored[mainSessionKey]?.lastTo)
|
||||
break;
|
||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
||||
}
|
||||
if (!stored) throw new Error("store not loaded");
|
||||
expect(stored[mainSessionKey]?.lastProvider).toBe("whatsapp");
|
||||
expect(stored[mainSessionKey]?.lastChannel).toBe("whatsapp");
|
||||
expect(stored[mainSessionKey]?.lastTo).toBe("+1000");
|
||||
|
||||
resetLoadConfigMock();
|
||||
@@ -274,15 +267,13 @@ describe("partial reply gating", () => {
|
||||
const replyResolver = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
const mockConfig: ClawdbotConfig = {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
session: { store: store.storePath },
|
||||
};
|
||||
|
||||
setLoadConfigMock(mockConfig);
|
||||
|
||||
await monitorWebProvider(
|
||||
await monitorWebChannel(
|
||||
false,
|
||||
async ({ onMessage }) => {
|
||||
await onMessage({
|
||||
@@ -310,15 +301,15 @@ describe("partial reply gating", () => {
|
||||
|
||||
let stored: Record<
|
||||
string,
|
||||
{ lastProvider?: string; lastTo?: string; lastAccountId?: string }
|
||||
{ lastChannel?: string; lastTo?: string; lastAccountId?: string }
|
||||
> | null = null;
|
||||
for (let attempt = 0; attempt < 50; attempt += 1) {
|
||||
stored = JSON.parse(await fs.readFile(store.storePath, "utf8")) as Record<
|
||||
string,
|
||||
{ lastProvider?: string; lastTo?: string; lastAccountId?: string }
|
||||
{ lastChannel?: string; lastTo?: string; lastAccountId?: string }
|
||||
>;
|
||||
if (
|
||||
stored[groupSessionKey]?.lastProvider &&
|
||||
stored[groupSessionKey]?.lastChannel &&
|
||||
stored[groupSessionKey]?.lastTo &&
|
||||
stored[groupSessionKey]?.lastAccountId
|
||||
)
|
||||
@@ -326,7 +317,7 @@ describe("partial reply gating", () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
||||
}
|
||||
if (!stored) throw new Error("store not loaded");
|
||||
expect(stored[groupSessionKey]?.lastProvider).toBe("whatsapp");
|
||||
expect(stored[groupSessionKey]?.lastChannel).toBe("whatsapp");
|
||||
expect(stored[groupSessionKey]?.lastTo).toBe("123@g.us");
|
||||
expect(stored[groupSessionKey]?.lastAccountId).toBe("work");
|
||||
|
||||
@@ -394,14 +385,12 @@ describe("typing controller idle", () => {
|
||||
});
|
||||
|
||||
const mockConfig: ClawdbotConfig = {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
};
|
||||
|
||||
setLoadConfigMock(mockConfig);
|
||||
|
||||
await monitorWebProvider(
|
||||
await monitorWebChannel(
|
||||
false,
|
||||
async ({ onMessage }) => {
|
||||
await onMessage({
|
||||
@@ -459,7 +448,7 @@ describe("web auto-reply", () => {
|
||||
exit: vi.fn(),
|
||||
};
|
||||
const controller = new AbortController();
|
||||
const run = monitorWebProvider(
|
||||
const run = monitorWebChannel(
|
||||
false,
|
||||
listenerFactory,
|
||||
true,
|
||||
@@ -530,7 +519,7 @@ describe("web auto-reply", () => {
|
||||
exit: vi.fn(),
|
||||
};
|
||||
const controller = new AbortController();
|
||||
const run = monitorWebProvider(
|
||||
const run = monitorWebChannel(
|
||||
false,
|
||||
listenerFactory,
|
||||
true,
|
||||
@@ -589,7 +578,7 @@ describe("web auto-reply", () => {
|
||||
exit: vi.fn(),
|
||||
};
|
||||
|
||||
const run = monitorWebProvider(
|
||||
const run = monitorWebChannel(
|
||||
false,
|
||||
listenerFactory,
|
||||
true,
|
||||
@@ -653,7 +642,7 @@ describe("web auto-reply", () => {
|
||||
session: { store: store.storePath },
|
||||
}));
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
// Two messages from the same sender with fixed timestamps
|
||||
@@ -742,7 +731,7 @@ describe("web auto-reply", () => {
|
||||
status: 200,
|
||||
} as Response);
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
await capturedOnMessage?.({
|
||||
@@ -791,7 +780,7 @@ describe("web auto-reply", () => {
|
||||
headers: { get: () => "text/plain" },
|
||||
} as unknown as Response);
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -858,7 +847,7 @@ describe("web auto-reply", () => {
|
||||
status: 200,
|
||||
} as Response);
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -960,7 +949,7 @@ describe("web auto-reply", () => {
|
||||
status: 200,
|
||||
} as Response);
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1034,7 +1023,7 @@ describe("web auto-reply", () => {
|
||||
status: 200,
|
||||
} as Response);
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1089,7 +1078,7 @@ describe("web auto-reply", () => {
|
||||
status: 200,
|
||||
} as Response);
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1134,7 +1123,7 @@ describe("web auto-reply", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1211,15 +1200,17 @@ describe("web auto-reply", () => {
|
||||
);
|
||||
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
accounts: {
|
||||
default: { authDir },
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
accounts: {
|
||||
default: { authDir },
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1292,15 +1283,17 @@ describe("web auto-reply", () => {
|
||||
);
|
||||
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
accounts: {
|
||||
default: { authDir },
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
accounts: {
|
||||
default: { authDir },
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1345,7 +1338,7 @@ describe("web auto-reply", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1373,9 +1366,11 @@ describe("web auto-reply", () => {
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "ok" });
|
||||
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
groups: { "*": { requireMention: true } },
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
groupChat: { mentionPatterns: ["@global"] },
|
||||
@@ -1411,7 +1406,7 @@ describe("web auto-reply", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1456,9 +1451,11 @@ describe("web auto-reply", () => {
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "ok" });
|
||||
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
groups: { "*": { requireMention: false } },
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
groups: { "*": { requireMention: false } },
|
||||
},
|
||||
},
|
||||
messages: { groupChat: { mentionPatterns: ["@clawd"] } },
|
||||
}));
|
||||
@@ -1475,7 +1472,7 @@ describe("web auto-reply", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1505,9 +1502,11 @@ describe("web auto-reply", () => {
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "ok" });
|
||||
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
groups: { "999@g.us": { requireMention: false } },
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
groups: { "999@g.us": { requireMention: false } },
|
||||
},
|
||||
},
|
||||
messages: { groupChat: { mentionPatterns: ["@clawd"] } },
|
||||
}));
|
||||
@@ -1524,7 +1523,7 @@ describe("web auto-reply", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1556,11 +1555,13 @@ describe("web auto-reply", () => {
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "ok" });
|
||||
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123@g.us": { requireMention: false },
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
groups: {
|
||||
"*": { requireMention: true },
|
||||
"123@g.us": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
messages: { groupChat: { mentionPatterns: ["@clawd"] } },
|
||||
@@ -1578,7 +1579,7 @@ describe("web auto-reply", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1637,7 +1638,7 @@ describe("web auto-reply", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1694,10 +1695,12 @@ describe("web auto-reply", () => {
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "ok" });
|
||||
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
// Self-chat heuristic: allowFrom includes selfE164.
|
||||
allowFrom: ["+999"],
|
||||
groups: { "*": { requireMention: true } },
|
||||
channels: {
|
||||
whatsapp: {
|
||||
// Self-chat heuristic: allowFrom includes selfE164.
|
||||
allowFrom: ["+999"],
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
groupChat: {
|
||||
@@ -1718,7 +1721,7 @@ describe("web auto-reply", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
// WhatsApp @mention of the owner should NOT trigger the bot in self-chat mode.
|
||||
@@ -1784,7 +1787,7 @@ describe("web auto-reply", () => {
|
||||
return { close: vi.fn(), onClose };
|
||||
});
|
||||
|
||||
const run = monitorWebProvider(
|
||||
const run = monitorWebChannel(
|
||||
false,
|
||||
listenerFactory,
|
||||
true,
|
||||
@@ -1825,7 +1828,7 @@ describe("web auto-reply", () => {
|
||||
};
|
||||
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "auto" });
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1846,9 +1849,7 @@ describe("web auto-reply", () => {
|
||||
it("prefixes body with same-phone marker when from === to", async () => {
|
||||
// Enable messagePrefix for same-phone mode testing
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: "[same-phone]",
|
||||
responsePrefix: undefined,
|
||||
@@ -1869,7 +1870,7 @@ describe("web auto-reply", () => {
|
||||
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "reply" });
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1905,7 +1906,7 @@ describe("web auto-reply", () => {
|
||||
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "reply" });
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1939,7 +1940,7 @@ describe("web auto-reply", () => {
|
||||
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "reply" });
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -1970,9 +1971,7 @@ describe("web auto-reply", () => {
|
||||
|
||||
it("applies responsePrefix to regular replies", async () => {
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: "🦞",
|
||||
@@ -1994,7 +1993,7 @@ describe("web auto-reply", () => {
|
||||
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "hello there" });
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -2014,9 +2013,7 @@ describe("web auto-reply", () => {
|
||||
|
||||
it("does not deliver HEARTBEAT_OK responses", async () => {
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: "🦞",
|
||||
@@ -2039,7 +2036,7 @@ describe("web auto-reply", () => {
|
||||
// Resolver returns exact HEARTBEAT_OK
|
||||
const resolver = vi.fn().mockResolvedValue({ text: HEARTBEAT_TOKEN });
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -2058,9 +2055,7 @@ describe("web auto-reply", () => {
|
||||
|
||||
it("does not double-prefix if responsePrefix already present", async () => {
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: "🦞",
|
||||
@@ -2083,7 +2078,7 @@ describe("web auto-reply", () => {
|
||||
// Resolver returns text that already has prefix
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "🦞 already prefixed" });
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -2103,9 +2098,7 @@ describe("web auto-reply", () => {
|
||||
|
||||
it("sends tool summaries immediately with responsePrefix", async () => {
|
||||
setLoadConfigMock(() => ({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: "🦞",
|
||||
@@ -2138,7 +2131,7 @@ describe("web auto-reply", () => {
|
||||
},
|
||||
);
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -2175,7 +2168,7 @@ describe("web auto-reply", () => {
|
||||
{
|
||||
agentId: "rich",
|
||||
match: {
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
peer: { kind: "dm", id: "+1555" },
|
||||
},
|
||||
},
|
||||
@@ -2197,7 +2190,7 @@ describe("web auto-reply", () => {
|
||||
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "hello" });
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -2237,7 +2230,7 @@ describe("web auto-reply", () => {
|
||||
{
|
||||
agentId: "rich",
|
||||
match: {
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
peer: { kind: "dm", id: "+1555" },
|
||||
},
|
||||
},
|
||||
@@ -2259,7 +2252,7 @@ describe("web auto-reply", () => {
|
||||
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "hello there" });
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -2281,7 +2274,7 @@ describe("web auto-reply", () => {
|
||||
describe("broadcast groups", () => {
|
||||
it("broadcasts sequentially in configured order", async () => {
|
||||
setLoadConfigMock({
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
agents: {
|
||||
defaults: { maxConcurrent: 10 },
|
||||
list: [{ id: "alfred" }, { id: "baerbel" }],
|
||||
@@ -2313,7 +2306,7 @@ describe("broadcast groups", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -2338,7 +2331,7 @@ describe("broadcast groups", () => {
|
||||
|
||||
it("shares group history across broadcast agents and clears after replying", async () => {
|
||||
setLoadConfigMock({
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
agents: {
|
||||
defaults: { maxConcurrent: 10 },
|
||||
list: [{ id: "alfred" }, { id: "baerbel" }],
|
||||
@@ -2366,7 +2359,7 @@ describe("broadcast groups", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -2445,7 +2438,7 @@ describe("broadcast groups", () => {
|
||||
|
||||
it("broadcasts in parallel by default", async () => {
|
||||
setLoadConfigMock({
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
agents: {
|
||||
defaults: { maxConcurrent: 10 },
|
||||
list: [{ id: "alfred" }, { id: "baerbel" }],
|
||||
@@ -2488,7 +2481,7 @@ describe("broadcast groups", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
@@ -2511,7 +2504,7 @@ describe("broadcast groups", () => {
|
||||
|
||||
it("skips unknown broadcast agent ids when agents.list is present", async () => {
|
||||
setLoadConfigMock({
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
agents: {
|
||||
defaults: { maxConcurrent: 10 },
|
||||
list: [{ id: "alfred" }],
|
||||
@@ -2542,7 +2535,7 @@ describe("broadcast groups", () => {
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
await monitorWebChannel(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
|
||||
@@ -29,11 +29,13 @@ import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/pr
|
||||
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
||||
import { HEARTBEAT_TOKEN, SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
import type { ReplyPayload } from "../auto-reply/types.js";
|
||||
import { toLocationContext } from "../channels/location.js";
|
||||
import { resolveWhatsAppHeartbeatRecipients } from "../channels/plugins/whatsapp-heartbeat.js";
|
||||
import { waitForever } from "../cli/wait.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import {
|
||||
resolveProviderGroupPolicy,
|
||||
resolveProviderGroupRequireMention,
|
||||
resolveChannelGroupPolicy,
|
||||
resolveChannelGroupRequireMention,
|
||||
} from "../config/group-policy.js";
|
||||
import {
|
||||
DEFAULT_IDLE_MINUTES,
|
||||
@@ -50,8 +52,6 @@ import { emitHeartbeatEvent } from "../infra/heartbeat-events.js";
|
||||
import { enqueueSystemEvent } from "../infra/system-events.js";
|
||||
import { registerUnhandledRejectionHandler } from "../infra/unhandled-rejections.js";
|
||||
import { createSubsystemLogger, getChildLogger } from "../logging.js";
|
||||
import { toLocationContext } from "../providers/location.js";
|
||||
import { resolveWhatsAppHeartbeatRecipients } from "../providers/plugins/whatsapp-heartbeat.js";
|
||||
import {
|
||||
buildAgentSessionKey,
|
||||
resolveAgentRoute,
|
||||
@@ -80,7 +80,7 @@ import {
|
||||
} from "./reconnect.js";
|
||||
import { formatError, getWebAuthAgeMs, readWebSelfId } from "./session.js";
|
||||
|
||||
const whatsappLog = createSubsystemLogger("gateway/providers/whatsapp");
|
||||
const whatsappLog = createSubsystemLogger("gateway/channels/whatsapp");
|
||||
const whatsappInboundLog = whatsappLog.child("inbound");
|
||||
const whatsappOutboundLog = whatsappLog.child("outbound");
|
||||
const whatsappHeartbeatLog = whatsappLog.child("heartbeat");
|
||||
@@ -145,14 +145,14 @@ export type WebMonitorTuning = {
|
||||
reconnect?: Partial<ReconnectPolicy>;
|
||||
heartbeatSeconds?: number;
|
||||
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
||||
statusSink?: (status: WebProviderStatus) => void;
|
||||
statusSink?: (status: WebChannelStatus) => void;
|
||||
/** WhatsApp account id. Default: "default". */
|
||||
accountId?: string;
|
||||
};
|
||||
|
||||
export { HEARTBEAT_PROMPT, HEARTBEAT_TOKEN, SILENT_REPLY_TOKEN };
|
||||
|
||||
export type WebProviderStatus = {
|
||||
export type WebChannelStatus = {
|
||||
running: boolean;
|
||||
connected: boolean;
|
||||
reconnectAttempts: number;
|
||||
@@ -190,7 +190,7 @@ function buildMentionConfig(
|
||||
agentId?: string,
|
||||
): MentionConfig {
|
||||
const mentionRegexes = buildMentionRegexes(cfg, agentId);
|
||||
return { mentionRegexes, allowFrom: cfg.whatsapp?.allowFrom };
|
||||
return { mentionRegexes, allowFrom: cfg.channels?.whatsapp?.allowFrom };
|
||||
}
|
||||
|
||||
function resolveMentionTargets(
|
||||
@@ -726,7 +726,7 @@ async function deliverWebReply(params: {
|
||||
}
|
||||
}
|
||||
|
||||
export async function monitorWebProvider(
|
||||
export async function monitorWebChannel(
|
||||
verbose: boolean,
|
||||
listenerFactory: typeof monitorWebInbox | undefined = monitorWebInbox,
|
||||
keepAlive = true,
|
||||
@@ -739,7 +739,7 @@ export async function monitorWebProvider(
|
||||
const replyLogger = getChildLogger({ module: "web-auto-reply", runId });
|
||||
const heartbeatLogger = getChildLogger({ module: "web-heartbeat", runId });
|
||||
const reconnectLogger = getChildLogger({ module: "web-reconnect", runId });
|
||||
const status: WebProviderStatus = {
|
||||
const status: WebChannelStatus = {
|
||||
running: true,
|
||||
connected: false,
|
||||
reconnectAttempts: 0,
|
||||
@@ -765,17 +765,20 @@ export async function monitorWebProvider(
|
||||
});
|
||||
const cfg = {
|
||||
...baseCfg,
|
||||
whatsapp: {
|
||||
...baseCfg.whatsapp,
|
||||
ackReaction: account.ackReaction,
|
||||
messagePrefix: account.messagePrefix,
|
||||
allowFrom: account.allowFrom,
|
||||
groupAllowFrom: account.groupAllowFrom,
|
||||
groupPolicy: account.groupPolicy,
|
||||
textChunkLimit: account.textChunkLimit,
|
||||
mediaMaxMb: account.mediaMaxMb,
|
||||
blockStreaming: account.blockStreaming,
|
||||
groups: account.groups,
|
||||
channels: {
|
||||
...baseCfg.channels,
|
||||
whatsapp: {
|
||||
...baseCfg.channels?.whatsapp,
|
||||
ackReaction: account.ackReaction,
|
||||
messagePrefix: account.messagePrefix,
|
||||
allowFrom: account.allowFrom,
|
||||
groupAllowFrom: account.groupAllowFrom,
|
||||
groupPolicy: account.groupPolicy,
|
||||
textChunkLimit: account.textChunkLimit,
|
||||
mediaMaxMb: account.mediaMaxMb,
|
||||
blockStreaming: account.blockStreaming,
|
||||
groups: account.groups,
|
||||
},
|
||||
},
|
||||
} satisfies ReturnType<typeof loadConfig>;
|
||||
const configuredMaxMb = cfg.agents?.defaults?.mediaMaxMb;
|
||||
@@ -792,8 +795,8 @@ export async function monitorWebProvider(
|
||||
buildMentionConfig(cfg, agentId);
|
||||
const baseMentionConfig = resolveMentionConfig();
|
||||
const groupHistoryLimit =
|
||||
cfg.whatsapp?.accounts?.[tuning.accountId ?? ""]?.historyLimit ??
|
||||
cfg.whatsapp?.historyLimit ??
|
||||
cfg.channels?.whatsapp?.accounts?.[tuning.accountId ?? ""]?.historyLimit ??
|
||||
cfg.channels?.whatsapp?.historyLimit ??
|
||||
cfg.messages?.groupChat?.historyLimit ??
|
||||
DEFAULT_GROUP_HISTORY_LIMIT;
|
||||
const groupHistories = new Map<
|
||||
@@ -884,9 +887,9 @@ export async function monitorWebProvider(
|
||||
const resolveGroupPolicyFor = (conversationId: string) => {
|
||||
const groupId =
|
||||
resolveGroupResolution(conversationId)?.id ?? conversationId;
|
||||
return resolveProviderGroupPolicy({
|
||||
return resolveChannelGroupPolicy({
|
||||
cfg,
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
groupId,
|
||||
});
|
||||
};
|
||||
@@ -894,9 +897,9 @@ export async function monitorWebProvider(
|
||||
const resolveGroupRequireMentionFor = (conversationId: string) => {
|
||||
const groupId =
|
||||
resolveGroupResolution(conversationId)?.id ?? conversationId;
|
||||
return resolveProviderGroupRequireMention({
|
||||
return resolveChannelGroupRequireMention({
|
||||
cfg,
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
groupId,
|
||||
});
|
||||
};
|
||||
@@ -1046,10 +1049,10 @@ export async function monitorWebProvider(
|
||||
};
|
||||
|
||||
const buildLine = (msg: WebInboundMsg, agentId: string) => {
|
||||
// WhatsApp inbound prefix: whatsapp.messagePrefix > legacy messages.messagePrefix > identity/defaults
|
||||
// WhatsApp inbound prefix: channels.whatsapp.messagePrefix > legacy messages.messagePrefix > identity/defaults
|
||||
const messagePrefix = resolveMessagePrefix(cfg, agentId, {
|
||||
configured: cfg.whatsapp?.messagePrefix,
|
||||
hasAllowFrom: (cfg.whatsapp?.allowFrom?.length ?? 0) > 0,
|
||||
configured: cfg.channels?.whatsapp?.messagePrefix,
|
||||
hasAllowFrom: (cfg.channels?.whatsapp?.allowFrom?.length ?? 0) > 0,
|
||||
});
|
||||
const prefixStr = messagePrefix ? `${messagePrefix} ` : "";
|
||||
const senderLabel =
|
||||
@@ -1063,7 +1066,7 @@ export async function monitorWebProvider(
|
||||
|
||||
// Wrap with standardized envelope for the agent.
|
||||
return formatAgentEnvelope({
|
||||
provider: "WhatsApp",
|
||||
channel: "WhatsApp",
|
||||
from:
|
||||
msg.chatType === "group"
|
||||
? msg.from
|
||||
@@ -1108,7 +1111,7 @@ export async function monitorWebProvider(
|
||||
? `${m.body}\n[message_id: ${m.id}]`
|
||||
: m.body;
|
||||
return formatAgentEnvelope({
|
||||
provider: "WhatsApp",
|
||||
channel: "WhatsApp",
|
||||
from: conversationId,
|
||||
timestamp: m.timestamp,
|
||||
body: `${m.sender}: ${bodyWithId}`,
|
||||
@@ -1143,7 +1146,7 @@ export async function monitorWebProvider(
|
||||
|
||||
// Send ack reaction immediately upon message receipt (post-gating)
|
||||
if (msg.id) {
|
||||
const ackConfig = cfg.whatsapp?.ackReaction;
|
||||
const ackConfig = cfg.channels?.whatsapp?.ackReaction;
|
||||
const emoji = (ackConfig?.emoji ?? "").trim();
|
||||
const directEnabled = ackConfig?.direct ?? true;
|
||||
const groupMode = ackConfig?.group ?? "mentions";
|
||||
@@ -1239,7 +1242,7 @@ export async function monitorWebProvider(
|
||||
const task = updateLastRoute({
|
||||
storePath,
|
||||
sessionKey: route.mainSessionKey,
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
to,
|
||||
accountId: route.accountId,
|
||||
}).catch((err) => {
|
||||
@@ -1368,8 +1371,8 @@ export async function monitorWebProvider(
|
||||
},
|
||||
replyOptions: {
|
||||
disableBlockStreaming:
|
||||
typeof cfg.whatsapp?.blockStreaming === "boolean"
|
||||
? !cfg.whatsapp.blockStreaming
|
||||
typeof cfg.channels?.whatsapp?.blockStreaming === "boolean"
|
||||
? !cfg.channels.whatsapp.blockStreaming
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
@@ -1428,7 +1431,7 @@ export async function monitorWebProvider(
|
||||
agentId: normalizedAgentId,
|
||||
sessionKey: buildAgentSessionKey({
|
||||
agentId: normalizedAgentId,
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
peer: {
|
||||
kind: msg.chatType === "group" ? "group" : "dm",
|
||||
id: peerId,
|
||||
@@ -1501,7 +1504,7 @@ export async function monitorWebProvider(
|
||||
})();
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
accountId: msg.accountId,
|
||||
peer: {
|
||||
kind: msg.chatType === "group" ? "group" : "dm",
|
||||
@@ -1511,7 +1514,7 @@ export async function monitorWebProvider(
|
||||
const groupHistoryKey =
|
||||
msg.chatType === "group"
|
||||
? buildGroupHistoryKey({
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
accountId: route.accountId,
|
||||
peerKind: "group",
|
||||
peerId,
|
||||
@@ -1550,7 +1553,7 @@ export async function monitorWebProvider(
|
||||
const task = updateLastRoute({
|
||||
storePath,
|
||||
sessionKey: route.sessionKey,
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
to: conversationId,
|
||||
accountId: route.accountId,
|
||||
}).catch((err) => {
|
||||
@@ -1665,7 +1668,7 @@ export async function monitorWebProvider(
|
||||
const { e164: selfE164 } = readWebSelfId(account.authDir);
|
||||
const connectRoute = resolveAgentRoute({
|
||||
cfg,
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
accountId: account.accountId,
|
||||
});
|
||||
enqueueSystemEvent(
|
||||
|
||||
@@ -24,8 +24,10 @@ vi.mock("../config/config.js", async (importOriginal) => {
|
||||
return {
|
||||
...actual,
|
||||
loadConfig: vi.fn().mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"], // Allow all in tests
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["*"], // Allow all in tests
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -36,9 +38,9 @@ vi.mock("../config/config.js", async (importOriginal) => {
|
||||
});
|
||||
|
||||
vi.mock("../pairing/pairing-store.js", () => ({
|
||||
readProviderAllowFromStore: (...args: unknown[]) =>
|
||||
readChannelAllowFromStore: (...args: unknown[]) =>
|
||||
readAllowFromStoreMock(...args),
|
||||
upsertProviderPairingRequest: (...args: unknown[]) =>
|
||||
upsertChannelPairingRequest: (...args: unknown[]) =>
|
||||
upsertPairingRequestMock(...args),
|
||||
}));
|
||||
|
||||
|
||||
@@ -11,22 +11,21 @@ import {
|
||||
isJidGroup,
|
||||
normalizeMessageContent,
|
||||
} from "@whiskeysockets/baileys";
|
||||
|
||||
import {
|
||||
formatLocationText,
|
||||
type NormalizedLocation,
|
||||
} from "../channels/location.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { logVerbose, shouldLogVerbose } from "../globals.js";
|
||||
import { recordChannelActivity } from "../infra/channel-activity.js";
|
||||
import { createDedupeCache } from "../infra/dedupe.js";
|
||||
import { recordProviderActivity } from "../infra/provider-activity.js";
|
||||
import { createSubsystemLogger, getChildLogger } from "../logging.js";
|
||||
import { saveMediaBuffer } from "../media/store.js";
|
||||
import { buildPairingReply } from "../pairing/pairing-messages.js";
|
||||
import {
|
||||
readProviderAllowFromStore,
|
||||
upsertProviderPairingRequest,
|
||||
readChannelAllowFromStore,
|
||||
upsertChannelPairingRequest,
|
||||
} from "../pairing/pairing-store.js";
|
||||
import {
|
||||
formatLocationText,
|
||||
type NormalizedLocation,
|
||||
} from "../providers/location.js";
|
||||
import {
|
||||
isSelfChatMode,
|
||||
jidToE164,
|
||||
@@ -101,7 +100,7 @@ export async function monitorWebInbox(options: {
|
||||
}) {
|
||||
const inboundLogger = getChildLogger({ module: "web-inbound" });
|
||||
const inboundConsoleLog = createSubsystemLogger(
|
||||
"gateway/providers/whatsapp",
|
||||
"gateway/channels/whatsapp",
|
||||
).child("inbound");
|
||||
const sock = await createWaSocket(false, options.verbose, {
|
||||
authDir: options.authDir,
|
||||
@@ -174,8 +173,8 @@ export async function monitorWebInbox(options: {
|
||||
}) => {
|
||||
if (upsert.type !== "notify" && upsert.type !== "append") return;
|
||||
for (const msg of upsert.messages ?? []) {
|
||||
recordProviderActivity({
|
||||
provider: "whatsapp",
|
||||
recordChannelActivity({
|
||||
channel: "whatsapp",
|
||||
accountId: options.accountId,
|
||||
direction: "inbound",
|
||||
});
|
||||
@@ -215,9 +214,9 @@ export async function monitorWebInbox(options: {
|
||||
cfg,
|
||||
accountId: options.accountId,
|
||||
});
|
||||
const dmPolicy = cfg.whatsapp?.dmPolicy ?? "pairing";
|
||||
const dmPolicy = cfg.channels?.whatsapp?.dmPolicy ?? "pairing";
|
||||
const configuredAllowFrom = account.allowFrom;
|
||||
const storeAllowFrom = await readProviderAllowFromStore("whatsapp").catch(
|
||||
const storeAllowFrom = await readChannelAllowFromStore("whatsapp").catch(
|
||||
() => [],
|
||||
);
|
||||
// Without user config, default to self-only DM access so the owner can talk to themselves
|
||||
@@ -296,8 +295,8 @@ export async function monitorWebInbox(options: {
|
||||
normalizedAllowFrom.includes(candidate));
|
||||
if (!allowed) {
|
||||
if (dmPolicy === "pairing") {
|
||||
const { code, created } = await upsertProviderPairingRequest({
|
||||
provider: "whatsapp",
|
||||
const { code, created } = await upsertChannelPairingRequest({
|
||||
channel: "whatsapp",
|
||||
id: candidate,
|
||||
meta: {
|
||||
name: (msg.pushName ?? "").trim() || undefined,
|
||||
@@ -310,7 +309,7 @@ export async function monitorWebInbox(options: {
|
||||
try {
|
||||
await sock.sendMessage(remoteJid, {
|
||||
text: buildPairingReply({
|
||||
provider: "whatsapp",
|
||||
channel: "whatsapp",
|
||||
idLine: `Your WhatsApp phone number: ${candidate}`,
|
||||
code,
|
||||
}),
|
||||
@@ -583,8 +582,8 @@ export async function monitorWebInbox(options: {
|
||||
}
|
||||
const result = await sock.sendMessage(jid, payload);
|
||||
const accountId = sendOptions?.accountId ?? options.accountId;
|
||||
recordProviderActivity({
|
||||
provider: "whatsapp",
|
||||
recordChannelActivity({
|
||||
channel: "whatsapp",
|
||||
accountId,
|
||||
direction: "outbound",
|
||||
});
|
||||
@@ -606,8 +605,8 @@ export async function monitorWebInbox(options: {
|
||||
selectableCount: poll.maxSelections ?? 1,
|
||||
},
|
||||
});
|
||||
recordProviderActivity({
|
||||
provider: "whatsapp",
|
||||
recordChannelActivity({
|
||||
channel: "whatsapp",
|
||||
accountId: options.accountId,
|
||||
direction: "outbound",
|
||||
});
|
||||
|
||||
@@ -14,9 +14,11 @@ const authDir = path.join(os.tmpdir(), "wa-creds");
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: () =>
|
||||
({
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
default: { enabled: true, authDir },
|
||||
channels: {
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
default: { enabled: true, authDir },
|
||||
},
|
||||
},
|
||||
},
|
||||
}) as never,
|
||||
|
||||
@@ -10,8 +10,11 @@ vi.mock("../media/store.js", () => ({
|
||||
}));
|
||||
|
||||
const mockLoadConfig = vi.fn().mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"], // Allow all in tests by default
|
||||
channels: {
|
||||
whatsapp: {
|
||||
// Allow all in tests by default
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -33,9 +36,9 @@ vi.mock("../config/config.js", async (importOriginal) => {
|
||||
});
|
||||
|
||||
vi.mock("../pairing/pairing-store.js", () => ({
|
||||
readProviderAllowFromStore: (...args: unknown[]) =>
|
||||
readChannelAllowFromStore: (...args: unknown[]) =>
|
||||
readAllowFromStoreMock(...args),
|
||||
upsertProviderPairingRequest: (...args: unknown[]) =>
|
||||
upsertChannelPairingRequest: (...args: unknown[]) =>
|
||||
upsertPairingRequestMock(...args),
|
||||
}));
|
||||
|
||||
@@ -680,9 +683,12 @@ describe("web monitor inbox", () => {
|
||||
|
||||
it("still forwards group messages (with sender info) even when allowFrom is restrictive", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["+111"], // does not include +777
|
||||
groupPolicy: "open",
|
||||
channels: {
|
||||
whatsapp: {
|
||||
// does not include +777
|
||||
allowFrom: ["+111"],
|
||||
groupPolicy: "open",
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -736,8 +742,11 @@ describe("web monitor inbox", () => {
|
||||
// Test for auto-recovery fix: early allowFrom filtering prevents Bad MAC errors
|
||||
// from unauthorized senders corrupting sessions
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["+111"], // Only allow +111
|
||||
channels: {
|
||||
whatsapp: {
|
||||
// Only allow +111
|
||||
allowFrom: ["+111"],
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -782,9 +791,7 @@ describe("web monitor inbox", () => {
|
||||
|
||||
// Reset mock for other tests
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: undefined,
|
||||
@@ -796,9 +803,11 @@ describe("web monitor inbox", () => {
|
||||
|
||||
it("skips read receipts in self-chat mode", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
// Self-chat heuristic: allowFrom includes selfE164 (+123).
|
||||
allowFrom: ["+123"],
|
||||
channels: {
|
||||
whatsapp: {
|
||||
// Self-chat heuristic: allowFrom includes selfE164 (+123).
|
||||
allowFrom: ["+123"],
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -832,9 +841,7 @@ describe("web monitor inbox", () => {
|
||||
|
||||
// Reset mock for other tests
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: undefined,
|
||||
@@ -846,10 +853,7 @@ describe("web monitor inbox", () => {
|
||||
|
||||
it("lets group messages through even when sender not in allowFrom", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["+1234"],
|
||||
groupPolicy: "open",
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["+1234"], groupPolicy: "open" } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: undefined,
|
||||
@@ -888,10 +892,7 @@ describe("web monitor inbox", () => {
|
||||
|
||||
it("blocks all group messages when groupPolicy is 'disabled'", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["+1234"],
|
||||
groupPolicy: "disabled",
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["+1234"], groupPolicy: "disabled" } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: undefined,
|
||||
@@ -929,9 +930,11 @@ describe("web monitor inbox", () => {
|
||||
|
||||
it("blocks group messages from senders not in groupAllowFrom when groupPolicy is 'allowlist'", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
groupAllowFrom: ["+1234"], // Does not include +999
|
||||
groupPolicy: "allowlist",
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupAllowFrom: ["+1234"], // Does not include +999
|
||||
groupPolicy: "allowlist",
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -970,9 +973,11 @@ describe("web monitor inbox", () => {
|
||||
|
||||
it("allows group messages from senders in groupAllowFrom when groupPolicy is 'allowlist'", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
groupAllowFrom: ["+15551234567"], // Includes the sender
|
||||
groupPolicy: "allowlist",
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupAllowFrom: ["+15551234567"], // Includes the sender
|
||||
groupPolicy: "allowlist",
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -1014,9 +1019,11 @@ describe("web monitor inbox", () => {
|
||||
|
||||
it("allows all group senders with wildcard in groupPolicy allowlist", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
groupAllowFrom: ["*"], // Wildcard allows everyone
|
||||
groupPolicy: "allowlist",
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupAllowFrom: ["*"], // Wildcard allows everyone
|
||||
groupPolicy: "allowlist",
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -1057,8 +1064,10 @@ describe("web monitor inbox", () => {
|
||||
|
||||
it("blocks group messages when groupPolicy allowlist has no groupAllowFrom", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
groupPolicy: "allowlist",
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupPolicy: "allowlist",
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -1096,8 +1105,11 @@ describe("web monitor inbox", () => {
|
||||
|
||||
it("allows messages from senders in allowFrom list", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["+111", "+999"], // Allow +999
|
||||
channels: {
|
||||
whatsapp: {
|
||||
// Allow +999
|
||||
allowFrom: ["+111", "+999"],
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -1134,9 +1146,7 @@ describe("web monitor inbox", () => {
|
||||
|
||||
// Reset mock for other tests
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: undefined,
|
||||
@@ -1150,8 +1160,11 @@ describe("web monitor inbox", () => {
|
||||
// Same-phone mode: when from === selfJid, should always be allowed
|
||||
// This allows users to message themselves even with restrictive allowFrom
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["+111"], // Only allow +111, but self is +123
|
||||
channels: {
|
||||
whatsapp: {
|
||||
// Only allow +111, but self is +123
|
||||
allowFrom: ["+111"],
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -1185,9 +1198,7 @@ describe("web monitor inbox", () => {
|
||||
|
||||
// Reset mock for other tests
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: undefined,
|
||||
@@ -1285,9 +1296,7 @@ describe("web monitor inbox", () => {
|
||||
|
||||
// Reset mock for other tests
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: undefined,
|
||||
@@ -1299,9 +1308,11 @@ describe("web monitor inbox", () => {
|
||||
|
||||
it("skips pairing replies for outbound DMs in same-phone mode", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
dmPolicy: "pairing",
|
||||
selfChatMode: true,
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "pairing",
|
||||
selfChatMode: true,
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -1336,9 +1347,7 @@ describe("web monitor inbox", () => {
|
||||
expect(sock.sendMessage).not.toHaveBeenCalled();
|
||||
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: undefined,
|
||||
@@ -1350,9 +1359,11 @@ describe("web monitor inbox", () => {
|
||||
|
||||
it("skips pairing replies for outbound DMs when same-phone mode is disabled", async () => {
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
dmPolicy: "pairing",
|
||||
selfChatMode: false,
|
||||
channels: {
|
||||
whatsapp: {
|
||||
dmPolicy: "pairing",
|
||||
selfChatMode: false,
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
@@ -1387,9 +1398,7 @@ describe("web monitor inbox", () => {
|
||||
expect(sock.sendMessage).not.toHaveBeenCalled();
|
||||
|
||||
mockLoadConfig.mockReturnValue({
|
||||
whatsapp: {
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
responsePrefix: undefined,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from "./active-listener.js";
|
||||
import { loadWebMedia } from "./media.js";
|
||||
|
||||
const outboundLog = createSubsystemLogger("gateway/providers/whatsapp").child(
|
||||
const outboundLog = createSubsystemLogger("gateway/channels/whatsapp").child(
|
||||
"outbound",
|
||||
);
|
||||
|
||||
|
||||
@@ -89,9 +89,7 @@ describe("web session", () => {
|
||||
logWebSelfId("/tmp/wa-creds", runtime as never, true);
|
||||
|
||||
expect(runtime.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"Web Provider: +12345 (jid 12345@s.whatsapp.net)",
|
||||
),
|
||||
expect.stringContaining("Web Channel: +12345 (jid 12345@s.whatsapp.net)"),
|
||||
);
|
||||
existsSpy.mockRestore();
|
||||
readSpy.mockRestore();
|
||||
|
||||
@@ -24,7 +24,7 @@ export {
|
||||
getWebAuthAgeMs,
|
||||
logoutWeb,
|
||||
logWebSelfId,
|
||||
pickProvider,
|
||||
pickWebChannel,
|
||||
readWebSelfId,
|
||||
WA_WEB_AUTH_DIR,
|
||||
webAuthExists,
|
||||
|
||||
@@ -6,9 +6,11 @@ import { createMockBaileys } from "../../test/mocks/baileys.js";
|
||||
// Use globalThis to store the mock config so it survives vi.mock hoisting
|
||||
const CONFIG_KEY = Symbol.for("clawdbot:testConfigMock");
|
||||
const DEFAULT_CONFIG = {
|
||||
whatsapp: {
|
||||
// Tests can override; default remains open to avoid surprising fixtures
|
||||
allowFrom: ["*"],
|
||||
channels: {
|
||||
whatsapp: {
|
||||
// Tests can override; default remains open to avoid surprising fixtures
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
messagePrefix: undefined,
|
||||
|
||||
Reference in New Issue
Block a user