@openclaw/whatsapp 2026.5.2-beta.2 → 2026.5.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-control-BQ3tCkH8.js +288 -0
- package/dist/account-config-CNQ33oav.js +47 -0
- package/dist/account-ids-DG18xddX.js +5 -0
- package/dist/accounts-BDqgpFEB.js +116 -0
- package/dist/action-runtime-DTGNgG-i.js +74 -0
- package/dist/action-runtime-api.js +2 -0
- package/dist/action-runtime.runtime.js +2 -0
- package/dist/active-listener-N7GFKsuN.js +11 -0
- package/dist/agent-tools-login-DpRxLNRB.js +89 -0
- package/dist/api.js +148 -0
- package/dist/audio-preflight.runtime-Bcdsl_ri.js +7 -0
- package/dist/auth-presence.js +32 -0
- package/dist/auth-store-BjCb50Ry.js +381 -0
- package/dist/channel-DQ-5Xoj9.js +542 -0
- package/dist/channel-config-api.js +2 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel-react-action-Df20qAy9.js +42 -0
- package/dist/channel.runtime-DKXRnA8x.js +55 -0
- package/dist/channel.setup-DOHR2BGp.js +20 -0
- package/dist/command-policy-O8sRnqUV.js +8 -0
- package/dist/config-schema-Dmo66Ubb.js +27 -0
- package/dist/connection-controller-BiHAsTM8.js +596 -0
- package/dist/connection-controller-registry-D_iIGz5m.js +22 -0
- package/dist/constants-DLGCIbfl.js +4 -0
- package/dist/constants.js +2 -0
- package/dist/contract-api.js +19 -0
- package/dist/creds-files-_2V8jzQa.js +19 -0
- package/dist/directory-config-DVs4ffXu.js +37 -0
- package/dist/directory-contract-api.js +2 -0
- package/dist/doctor-A9tXm9Om.js +48 -0
- package/dist/doctor-contract-api.js +10 -0
- package/dist/group-session-contract-B8QBn0I2.js +15 -0
- package/dist/identity-QHAlV7Fu.js +75 -0
- package/dist/index.js +18 -0
- package/dist/legacy-session-surface-api.js +8 -0
- package/dist/legacy-state-migrations-api.js +2 -0
- package/dist/light-runtime-api.js +5 -0
- package/dist/login-DymdrKca.js +52 -0
- package/dist/login-qr-DIS0Iv-7.js +378 -0
- package/dist/login-qr-api.js +2 -0
- package/dist/login-qr-runtime.js +16 -0
- package/dist/monitor-C4Fhg_lD.js +3349 -0
- package/dist/normalize-CWYiGkXw.js +2 -0
- package/dist/normalize-target-BPVHNsM5.js +74 -0
- package/dist/outbound-adapter-BssVnZdz.js +29 -0
- package/dist/outbound-base-Bq1UYEK5.js +119 -0
- package/dist/outbound-media-contract-CGss_QHy.js +163 -0
- package/dist/outbound-payload-test-api.js +2 -0
- package/dist/quoted-message-TENQzyxV.js +108 -0
- package/dist/reaction-level-CRVEVAfi.js +16 -0
- package/dist/reply-resolver.runtime-BRCgoO4C.js +2 -0
- package/dist/resolve-outbound-target-CN-Wo6IY.js +39 -0
- package/dist/rolldown-runtime-RkAeH_Qm.js +27 -0
- package/dist/runtime-6fZ67abR.js +8 -0
- package/dist/runtime-api.js +15 -0
- package/dist/runtime-group-policy-Gp1lQ0at.js +11 -0
- package/dist/secret-contract-api.js +5 -0
- package/dist/security-contract-DqH8JDX6.js +26 -0
- package/dist/security-contract-api.js +2 -0
- package/dist/send-CIyLFxdZ.js +234 -0
- package/dist/session-contract-B0efQf9P.js +27 -0
- package/dist/session-errors-CSuKZPub.js +63 -0
- package/dist/session.runtime-C1eSE_KK.js +13 -0
- package/dist/setup-core-DZKvpzbH.js +46 -0
- package/dist/setup-entry.js +23 -0
- package/dist/setup-plugin-api.js +2 -0
- package/dist/setup-surface-ieFnIW4g.js +344 -0
- package/dist/shared-D_wzSVar.js +307 -0
- package/dist/state-migrations-DnCdvoYD.js +39 -0
- package/dist/targets.js +2 -0
- package/dist/test-api.js +3 -0
- package/dist/text-runtime-C1zNiZeO.js +116 -0
- package/package.json +14 -6
- package/action-runtime-api.ts +0 -1
- package/action-runtime.runtime.ts +0 -1
- package/api.ts +0 -213
- package/auth-presence.ts +0 -80
- package/channel-config-api.ts +0 -1
- package/channel-plugin-api.ts +0 -3
- package/config-api.ts +0 -4
- package/constants.ts +0 -1
- package/contract-api.ts +0 -29
- package/directory-contract-api.ts +0 -4
- package/doctor-contract-api.ts +0 -8
- package/index.test.ts +0 -13
- package/index.ts +0 -16
- package/legacy-session-surface-api.ts +0 -6
- package/legacy-state-migrations-api.ts +0 -1
- package/light-runtime-api.ts +0 -12
- package/login-qr-api.ts +0 -1
- package/login-qr-runtime.ts +0 -23
- package/outbound-payload-test-api.ts +0 -1
- package/runtime-api.ts +0 -84
- package/secret-contract-api.ts +0 -4
- package/security-contract-api.ts +0 -4
- package/setup-entry.test.ts +0 -21
- package/setup-entry.ts +0 -21
- package/setup-plugin-api.ts +0 -3
- package/src/account-config.ts +0 -77
- package/src/account-ids.ts +0 -13
- package/src/account-types.ts +0 -5
- package/src/accounts.test.ts +0 -182
- package/src/accounts.ts +0 -176
- package/src/accounts.whatsapp-auth.test.ts +0 -59
- package/src/action-runtime-target-auth.ts +0 -27
- package/src/action-runtime.test.ts +0 -330
- package/src/action-runtime.ts +0 -76
- package/src/active-listener.test.ts +0 -65
- package/src/active-listener.ts +0 -17
- package/src/agent-tools-login.test.ts +0 -81
- package/src/agent-tools-login.ts +0 -113
- package/src/approval-auth.test.ts +0 -24
- package/src/approval-auth.ts +0 -27
- package/src/auth-store.runtime.ts +0 -1
- package/src/auth-store.test.ts +0 -311
- package/src/auth-store.ts +0 -502
- package/src/auto-reply/config.runtime.ts +0 -16
- package/src/auto-reply/constants.ts +0 -1
- package/src/auto-reply/deliver-reply.test.ts +0 -843
- package/src/auto-reply/deliver-reply.ts +0 -279
- package/src/auto-reply/loggers.ts +0 -6
- package/src/auto-reply/mentions.ts +0 -131
- package/src/auto-reply/monitor/ack-reaction.test.ts +0 -170
- package/src/auto-reply/monitor/ack-reaction.ts +0 -99
- package/src/auto-reply/monitor/audio-preflight.runtime.ts +0 -9
- package/src/auto-reply/monitor/broadcast.ts +0 -153
- package/src/auto-reply/monitor/commands.ts +0 -19
- package/src/auto-reply/monitor/echo.ts +0 -64
- package/src/auto-reply/monitor/group-activation.runtime.ts +0 -1
- package/src/auto-reply/monitor/group-activation.test.ts +0 -189
- package/src/auto-reply/monitor/group-activation.ts +0 -73
- package/src/auto-reply/monitor/group-gating.audio-preflight.test.ts +0 -103
- package/src/auto-reply/monitor/group-gating.runtime.ts +0 -8
- package/src/auto-reply/monitor/group-gating.ts +0 -217
- package/src/auto-reply/monitor/group-members.test.ts +0 -56
- package/src/auto-reply/monitor/group-members.ts +0 -65
- package/src/auto-reply/monitor/inbound-context.test.ts +0 -97
- package/src/auto-reply/monitor/inbound-context.ts +0 -92
- package/src/auto-reply/monitor/inbound-dispatch.runtime.ts +0 -21
- package/src/auto-reply/monitor/inbound-dispatch.test.ts +0 -895
- package/src/auto-reply/monitor/inbound-dispatch.ts +0 -422
- package/src/auto-reply/monitor/last-route.test.ts +0 -37
- package/src/auto-reply/monitor/last-route.ts +0 -61
- package/src/auto-reply/monitor/message-line.runtime.ts +0 -38
- package/src/auto-reply/monitor/message-line.ts +0 -54
- package/src/auto-reply/monitor/on-message.audio-preflight.test.ts +0 -354
- package/src/auto-reply/monitor/on-message.ts +0 -291
- package/src/auto-reply/monitor/peer.ts +0 -17
- package/src/auto-reply/monitor/process-message.audio-preflight.test.ts +0 -420
- package/src/auto-reply/monitor/process-message.test.ts +0 -355
- package/src/auto-reply/monitor/process-message.ts +0 -531
- package/src/auto-reply/monitor/runtime-api.ts +0 -30
- package/src/auto-reply/monitor-state.test.ts +0 -64
- package/src/auto-reply/monitor-state.ts +0 -106
- package/src/auto-reply/monitor.ts +0 -600
- package/src/auto-reply/reply-resolver.runtime.ts +0 -1
- package/src/auto-reply/types.ts +0 -48
- package/src/auto-reply/util.ts +0 -63
- package/src/auto-reply/web-auto-reply-monitor.test.ts +0 -687
- package/src/auto-reply/web-auto-reply-utils.test.ts +0 -392
- package/src/auto-reply.broadcast-groups.combined.test.ts +0 -246
- package/src/auto-reply.broadcast-groups.test-harness.ts +0 -47
- package/src/auto-reply.impl.ts +0 -6
- package/src/auto-reply.test-harness.ts +0 -421
- package/src/auto-reply.ts +0 -1
- package/src/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.test.ts +0 -427
- package/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts +0 -908
- package/src/auto-reply.web-auto-reply.last-route.test.ts +0 -184
- package/src/channel-actions.runtime.ts +0 -7
- package/src/channel-actions.test.ts +0 -216
- package/src/channel-actions.ts +0 -84
- package/src/channel-outbound.test.ts +0 -131
- package/src/channel-outbound.ts +0 -36
- package/src/channel-react-action.runtime.ts +0 -7
- package/src/channel-react-action.test.ts +0 -294
- package/src/channel-react-action.ts +0 -84
- package/src/channel.runtime.ts +0 -117
- package/src/channel.setup.test.ts +0 -461
- package/src/channel.setup.ts +0 -28
- package/src/channel.ts +0 -334
- package/src/command-policy.ts +0 -7
- package/src/config-accessors.test.ts +0 -34
- package/src/config-accessors.ts +0 -22
- package/src/config-schema.test.ts +0 -121
- package/src/config-schema.ts +0 -6
- package/src/config-ui-hints.ts +0 -24
- package/src/connection-controller-registry.test.ts +0 -27
- package/src/connection-controller-registry.ts +0 -49
- package/src/connection-controller.test.ts +0 -282
- package/src/connection-controller.ts +0 -679
- package/src/creds-files.ts +0 -19
- package/src/creds-persistence.ts +0 -102
- package/src/directory-config.test.ts +0 -60
- package/src/directory-config.ts +0 -40
- package/src/directory-contract.test.ts +0 -56
- package/src/doctor-contract.ts +0 -11
- package/src/doctor.test.ts +0 -74
- package/src/doctor.ts +0 -56
- package/src/group-intro.ts +0 -15
- package/src/group-policy.test.ts +0 -36
- package/src/group-policy.ts +0 -40
- package/src/group-session-contract.ts +0 -20
- package/src/group-session-key.test.ts +0 -53
- package/src/group-session-key.ts +0 -41
- package/src/heartbeat.ts +0 -34
- package/src/identity.ts +0 -164
- package/src/inbound/access-control.test-harness.ts +0 -37
- package/src/inbound/access-control.test.ts +0 -375
- package/src/inbound/access-control.ts +0 -237
- package/src/inbound/dedupe.ts +0 -132
- package/src/inbound/extract.test.ts +0 -282
- package/src/inbound/extract.ts +0 -488
- package/src/inbound/lifecycle.ts +0 -39
- package/src/inbound/media.node.test.ts +0 -83
- package/src/inbound/media.ts +0 -102
- package/src/inbound/monitor.ts +0 -890
- package/src/inbound/runtime-api.ts +0 -7
- package/src/inbound/save-media.runtime.ts +0 -1
- package/src/inbound/send-api.test.ts +0 -307
- package/src/inbound/send-api.ts +0 -141
- package/src/inbound/send-result.ts +0 -61
- package/src/inbound/types.ts +0 -106
- package/src/inbound-context.contract.test.ts +0 -32
- package/src/inbound-policy.ts +0 -231
- package/src/inbound.media.test.ts +0 -320
- package/src/inbound.test.ts +0 -290
- package/src/inbound.ts +0 -9
- package/src/login-qr.test.ts +0 -483
- package/src/login-qr.ts +0 -542
- package/src/login.coverage.test.ts +0 -133
- package/src/login.test.ts +0 -89
- package/src/login.ts +0 -70
- package/src/logout.test.ts +0 -162
- package/src/media.test.ts +0 -480
- package/src/media.ts +0 -10
- package/src/monitor-inbox.allows-messages-from-senders-allowfrom-list.test-support.ts +0 -400
- package/src/monitor-inbox.append-upsert.test-support.ts +0 -133
- package/src/monitor-inbox.behavior.test.ts +0 -5
- package/src/monitor-inbox.blocks-messages-from-unauthorized-senders-not-allowfrom.test-support.ts +0 -362
- package/src/monitor-inbox.captures-media-path-image-messages.test-support.ts +0 -306
- package/src/monitor-inbox.streams-inbound-messages.test-support.ts +0 -729
- package/src/monitor-inbox.test-harness.ts +0 -304
- package/src/normalize-target.ts +0 -112
- package/src/normalize.ts +0 -6
- package/src/outbound-adapter.poll.test.ts +0 -62
- package/src/outbound-adapter.sendpayload.test.ts +0 -195
- package/src/outbound-adapter.ts +0 -36
- package/src/outbound-base.test.ts +0 -560
- package/src/outbound-base.ts +0 -244
- package/src/outbound-media-contract.ts +0 -278
- package/src/outbound-media.runtime.ts +0 -36
- package/src/outbound-payload.contract.test.ts +0 -61
- package/src/outbound-send-deps.ts +0 -1
- package/src/outbound-test-support.ts +0 -16
- package/src/pairing-security.test-harness.ts +0 -59
- package/src/qr-image.ts +0 -1
- package/src/qr-terminal.ts +0 -1
- package/src/quoted-message.test.ts +0 -83
- package/src/quoted-message.ts +0 -184
- package/src/reaction-level.test.ts +0 -94
- package/src/reaction-level.ts +0 -21
- package/src/reconnect.test.ts +0 -51
- package/src/reconnect.ts +0 -55
- package/src/resolve-outbound-target.test.ts +0 -265
- package/src/resolve-outbound-target.ts +0 -54
- package/src/resolve-target.test.ts +0 -97
- package/src/runtime-api.ts +0 -59
- package/src/runtime-group-policy.ts +0 -16
- package/src/runtime.ts +0 -9
- package/src/security-contract.ts +0 -47
- package/src/security-fix.ts +0 -71
- package/src/send.test.ts +0 -515
- package/src/send.ts +0 -287
- package/src/session-contract.test.ts +0 -31
- package/src/session-contract.ts +0 -43
- package/src/session-errors.ts +0 -125
- package/src/session-route.ts +0 -26
- package/src/session.runtime.ts +0 -8
- package/src/session.test.ts +0 -559
- package/src/session.ts +0 -330
- package/src/setup-core.ts +0 -52
- package/src/setup-finalize.ts +0 -446
- package/src/setup-surface.test.ts +0 -367
- package/src/setup-surface.ts +0 -69
- package/src/setup-test-helpers.ts +0 -216
- package/src/shared.ts +0 -291
- package/src/socket-timing.test.ts +0 -49
- package/src/socket-timing.ts +0 -38
- package/src/state-migrations.ts +0 -54
- package/src/status-issues.test.ts +0 -137
- package/src/status-issues.ts +0 -185
- package/src/system-prompt.test.ts +0 -199
- package/src/system-prompt.ts +0 -31
- package/src/targets-runtime.ts +0 -180
- package/src/test-helpers.ts +0 -690
- package/src/text-runtime.test.ts +0 -162
- package/src/text-runtime.ts +0 -11
- package/src/vcard.ts +0 -84
- package/targets.ts +0 -5
- package/test-api.ts +0 -2
- package/tsconfig.json +0 -16
|
@@ -1,895 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
-
import type { WhatsAppSendResult } from "../../inbound/send-result.js";
|
|
3
|
-
|
|
4
|
-
let capturedDispatchParams: unknown;
|
|
5
|
-
|
|
6
|
-
type CapturedReplyPayload = {
|
|
7
|
-
text?: string;
|
|
8
|
-
isReasoning?: boolean;
|
|
9
|
-
isCompactionNotice?: boolean;
|
|
10
|
-
isError?: boolean;
|
|
11
|
-
mediaUrl?: string;
|
|
12
|
-
mediaUrls?: string[];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const { dispatchReplyWithBufferedBlockDispatcherMock } = vi.hoisted(() => ({
|
|
16
|
-
dispatchReplyWithBufferedBlockDispatcherMock: vi.fn(async (params: { ctx: unknown }) => {
|
|
17
|
-
capturedDispatchParams = params;
|
|
18
|
-
return { queuedFinal: false, counts: { tool: 0, block: 0, final: 0 } };
|
|
19
|
-
}),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
vi.mock("./runtime-api.js", () => ({
|
|
23
|
-
dispatchReplyWithBufferedBlockDispatcher: dispatchReplyWithBufferedBlockDispatcherMock,
|
|
24
|
-
finalizeInboundContext: <T extends Record<string, unknown>>(ctx: T) => ({
|
|
25
|
-
...ctx,
|
|
26
|
-
BodyForCommands:
|
|
27
|
-
typeof ctx.CommandBody === "string"
|
|
28
|
-
? ctx.CommandBody
|
|
29
|
-
: typeof ctx.BodyForAgent === "string"
|
|
30
|
-
? ctx.BodyForAgent
|
|
31
|
-
: "",
|
|
32
|
-
}),
|
|
33
|
-
getAgentScopedMediaLocalRoots: () => [],
|
|
34
|
-
jidToE164: (value: string) => {
|
|
35
|
-
const phone = value.split("@")[0]?.replace(/[^\d]/g, "");
|
|
36
|
-
return phone ? `+${phone}` : null;
|
|
37
|
-
},
|
|
38
|
-
logVerbose: () => {},
|
|
39
|
-
resolveChunkMode: () => "length",
|
|
40
|
-
resolveIdentityNamePrefix: (cfg: {
|
|
41
|
-
agents?: { list?: Array<{ id?: string; default?: boolean; identity?: { name?: string } }> };
|
|
42
|
-
}) => {
|
|
43
|
-
const agent = cfg.agents?.list?.find((entry) => entry.default) ?? cfg.agents?.list?.[0];
|
|
44
|
-
const name = agent?.identity?.name?.trim();
|
|
45
|
-
return name ? `[${name}]` : undefined;
|
|
46
|
-
},
|
|
47
|
-
resolveInboundLastRouteSessionKey: (params: { sessionKey: string }) => params.sessionKey,
|
|
48
|
-
resolveMarkdownTableMode: () => undefined,
|
|
49
|
-
resolveSendableOutboundReplyParts: (payload: {
|
|
50
|
-
text?: string;
|
|
51
|
-
mediaUrls?: string[];
|
|
52
|
-
mediaUrl?: string;
|
|
53
|
-
}) => {
|
|
54
|
-
const urls = [
|
|
55
|
-
...(Array.isArray(payload.mediaUrls) ? payload.mediaUrls : []),
|
|
56
|
-
...(payload.mediaUrl ? [payload.mediaUrl] : []),
|
|
57
|
-
];
|
|
58
|
-
return {
|
|
59
|
-
text: payload.text ?? "",
|
|
60
|
-
hasMedia: urls.length > 0,
|
|
61
|
-
};
|
|
62
|
-
},
|
|
63
|
-
resolveTextChunkLimit: () => 4000,
|
|
64
|
-
shouldLogVerbose: () => false,
|
|
65
|
-
toLocationContext: () => ({}),
|
|
66
|
-
}));
|
|
67
|
-
|
|
68
|
-
import {
|
|
69
|
-
buildWhatsAppInboundContext,
|
|
70
|
-
dispatchWhatsAppBufferedReply,
|
|
71
|
-
resolveWhatsAppDmRouteTarget,
|
|
72
|
-
resolveWhatsAppResponsePrefix,
|
|
73
|
-
updateWhatsAppMainLastRoute,
|
|
74
|
-
} from "./inbound-dispatch.js";
|
|
75
|
-
|
|
76
|
-
type TestRoute = Parameters<typeof buildWhatsAppInboundContext>[0]["route"];
|
|
77
|
-
type TestMsg = Parameters<typeof buildWhatsAppInboundContext>[0]["msg"];
|
|
78
|
-
|
|
79
|
-
function acceptedSendResult(kind: "media" | "text", id: string): WhatsAppSendResult {
|
|
80
|
-
return {
|
|
81
|
-
kind,
|
|
82
|
-
messageId: id,
|
|
83
|
-
messageIds: [id],
|
|
84
|
-
keys: [{ id }],
|
|
85
|
-
providerAccepted: true,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function makeRoute(overrides: Partial<TestRoute> = {}): TestRoute {
|
|
90
|
-
return {
|
|
91
|
-
agentId: "main",
|
|
92
|
-
channel: "whatsapp",
|
|
93
|
-
accountId: "default",
|
|
94
|
-
sessionKey: "agent:main:whatsapp:direct:+1000",
|
|
95
|
-
mainSessionKey: "agent:main:whatsapp:direct:+1000",
|
|
96
|
-
lastRoutePolicy: "main",
|
|
97
|
-
matchedBy: "default",
|
|
98
|
-
...overrides,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function makeMsg(overrides: Partial<TestMsg> = {}): TestMsg {
|
|
103
|
-
return {
|
|
104
|
-
id: "msg1",
|
|
105
|
-
from: "+1000",
|
|
106
|
-
to: "+2000",
|
|
107
|
-
conversationId: "+1000",
|
|
108
|
-
accountId: "default",
|
|
109
|
-
chatId: "+1000",
|
|
110
|
-
chatType: "direct",
|
|
111
|
-
body: "hi",
|
|
112
|
-
sendComposing: async () => {},
|
|
113
|
-
reply: async () => acceptedSendResult("text", "r1"),
|
|
114
|
-
sendMedia: async () => acceptedSendResult("media", "m1"),
|
|
115
|
-
...overrides,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function getCapturedDeliver() {
|
|
120
|
-
return (
|
|
121
|
-
capturedDispatchParams as {
|
|
122
|
-
dispatcherOptions?: {
|
|
123
|
-
deliver?: (
|
|
124
|
-
payload: CapturedReplyPayload,
|
|
125
|
-
info: { kind: "tool" | "block" | "final" },
|
|
126
|
-
) => Promise<void>;
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
)?.dispatcherOptions?.deliver;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function getCapturedOnError() {
|
|
133
|
-
return (
|
|
134
|
-
capturedDispatchParams as {
|
|
135
|
-
dispatcherOptions?: {
|
|
136
|
-
onError?: (err: unknown, info: { kind: "tool" | "block" | "final" }) => void;
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
)?.dispatcherOptions?.onError;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
type BufferedReplyParams = Parameters<typeof dispatchWhatsAppBufferedReply>[0];
|
|
143
|
-
|
|
144
|
-
function makeReplyLogger(): BufferedReplyParams["replyLogger"] {
|
|
145
|
-
return {
|
|
146
|
-
info: () => {},
|
|
147
|
-
warn: () => {},
|
|
148
|
-
error: () => {},
|
|
149
|
-
debug: () => {},
|
|
150
|
-
} as never;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function acceptedDeliveryResult() {
|
|
154
|
-
return {
|
|
155
|
-
results: [
|
|
156
|
-
{
|
|
157
|
-
kind: "text" as const,
|
|
158
|
-
messageId: "wa-sent-1",
|
|
159
|
-
messageIds: ["wa-sent-1"],
|
|
160
|
-
keys: [{ id: "wa-sent-1" }],
|
|
161
|
-
providerAccepted: true,
|
|
162
|
-
},
|
|
163
|
-
],
|
|
164
|
-
messageIds: ["wa-sent-1"],
|
|
165
|
-
providerAccepted: true,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function unacceptedDeliveryResult() {
|
|
170
|
-
return {
|
|
171
|
-
results: [],
|
|
172
|
-
messageIds: [],
|
|
173
|
-
providerAccepted: false,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async function dispatchBufferedReply(overrides: Partial<BufferedReplyParams> = {}) {
|
|
178
|
-
const params: BufferedReplyParams = {
|
|
179
|
-
cfg: { channels: { whatsapp: { blockStreaming: true } } } as never,
|
|
180
|
-
connectionId: "conn",
|
|
181
|
-
context: { Body: "hi" },
|
|
182
|
-
conversationId: "+1000",
|
|
183
|
-
deliverReply: async () => acceptedDeliveryResult(),
|
|
184
|
-
groupHistories: new Map(),
|
|
185
|
-
groupHistoryKey: "+1000",
|
|
186
|
-
maxMediaBytes: 1,
|
|
187
|
-
msg: makeMsg(),
|
|
188
|
-
rememberSentText: () => {},
|
|
189
|
-
replyLogger: makeReplyLogger(),
|
|
190
|
-
replyPipeline: {} as never,
|
|
191
|
-
replyResolver: (async () => undefined) as never,
|
|
192
|
-
route: makeRoute(),
|
|
193
|
-
shouldClearGroupHistory: false,
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
return dispatchWhatsAppBufferedReply({ ...params, ...overrides });
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
describe("whatsapp inbound dispatch", () => {
|
|
200
|
-
beforeEach(() => {
|
|
201
|
-
capturedDispatchParams = undefined;
|
|
202
|
-
dispatchReplyWithBufferedBlockDispatcherMock.mockClear();
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("builds a finalized inbound context payload", () => {
|
|
206
|
-
const ctx = buildWhatsAppInboundContext({
|
|
207
|
-
combinedBody: "Alice: hi",
|
|
208
|
-
conversationId: "123@g.us",
|
|
209
|
-
groupHistory: [],
|
|
210
|
-
groupMemberRoster: new Map(),
|
|
211
|
-
msg: makeMsg({
|
|
212
|
-
from: "123@g.us",
|
|
213
|
-
chatType: "group",
|
|
214
|
-
timestamp: 1737158400000,
|
|
215
|
-
senderName: "Alice",
|
|
216
|
-
senderJid: "alice@s.whatsapp.net",
|
|
217
|
-
senderE164: "+15550002222",
|
|
218
|
-
groupSubject: "Test Group",
|
|
219
|
-
groupParticipants: [],
|
|
220
|
-
}),
|
|
221
|
-
route: makeRoute({ sessionKey: "agent:main:whatsapp:group:123@g.us" }),
|
|
222
|
-
sender: {
|
|
223
|
-
name: "Alice",
|
|
224
|
-
e164: "+15550002222",
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
expect(ctx).toMatchObject({
|
|
229
|
-
Body: "Alice: hi",
|
|
230
|
-
BodyForAgent: "hi",
|
|
231
|
-
BodyForCommands: "hi",
|
|
232
|
-
RawBody: "hi",
|
|
233
|
-
CommandBody: "hi",
|
|
234
|
-
Timestamp: 1737158400000,
|
|
235
|
-
SenderId: "+15550002222",
|
|
236
|
-
SenderE164: "+15550002222",
|
|
237
|
-
OriginatingChannel: "whatsapp",
|
|
238
|
-
OriginatingTo: "123@g.us",
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("keeps agent and command bodies independently overridable", () => {
|
|
243
|
-
const ctx = buildWhatsAppInboundContext({
|
|
244
|
-
bodyForAgent: "spoken transcript",
|
|
245
|
-
combinedBody: "spoken transcript",
|
|
246
|
-
commandBody: "<media:audio>",
|
|
247
|
-
conversationId: "+1000",
|
|
248
|
-
msg: makeMsg({
|
|
249
|
-
body: "<media:audio>",
|
|
250
|
-
mediaPath: "/tmp/voice.ogg",
|
|
251
|
-
mediaType: "audio/ogg; codecs=opus",
|
|
252
|
-
}),
|
|
253
|
-
rawBody: "<media:audio>",
|
|
254
|
-
route: makeRoute(),
|
|
255
|
-
sender: {
|
|
256
|
-
e164: "+1000",
|
|
257
|
-
},
|
|
258
|
-
transcript: "spoken transcript",
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
expect(ctx).toMatchObject({
|
|
262
|
-
Body: "spoken transcript",
|
|
263
|
-
BodyForAgent: "spoken transcript",
|
|
264
|
-
BodyForCommands: "<media:audio>",
|
|
265
|
-
CommandBody: "<media:audio>",
|
|
266
|
-
RawBody: "<media:audio>",
|
|
267
|
-
Transcript: "spoken transcript",
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("falls back SenderId to SenderE164 when sender id is missing", () => {
|
|
272
|
-
const ctx = buildWhatsAppInboundContext({
|
|
273
|
-
combinedBody: "hi",
|
|
274
|
-
conversationId: "+1000",
|
|
275
|
-
msg: makeMsg({
|
|
276
|
-
senderJid: "",
|
|
277
|
-
senderE164: "+1000",
|
|
278
|
-
}),
|
|
279
|
-
route: makeRoute(),
|
|
280
|
-
sender: {
|
|
281
|
-
e164: "+1000",
|
|
282
|
-
},
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
expect(ctx.SenderId).toBe("+1000");
|
|
286
|
-
expect(ctx.SenderE164).toBe("+1000");
|
|
287
|
-
expect(ctx.To).toBe("+2000");
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it("passes groupSystemPrompt into GroupSystemPrompt for group chats", () => {
|
|
291
|
-
const ctx = buildWhatsAppInboundContext({
|
|
292
|
-
combinedBody: "hi",
|
|
293
|
-
conversationId: "123@g.us",
|
|
294
|
-
groupSystemPrompt: "Specific group prompt",
|
|
295
|
-
msg: makeMsg({ from: "123@g.us", chatType: "group", groupParticipants: [] }),
|
|
296
|
-
route: makeRoute({ sessionKey: "agent:main:whatsapp:group:123@g.us" }),
|
|
297
|
-
sender: { e164: "+15550002222" },
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
expect(ctx.GroupSystemPrompt).toBe("Specific group prompt");
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it("passes groupSystemPrompt into GroupSystemPrompt for direct chats", () => {
|
|
304
|
-
const ctx = buildWhatsAppInboundContext({
|
|
305
|
-
combinedBody: "hi",
|
|
306
|
-
conversationId: "+1555",
|
|
307
|
-
groupSystemPrompt: "Specific direct prompt",
|
|
308
|
-
msg: makeMsg({ from: "+1555", chatType: "direct" }),
|
|
309
|
-
route: makeRoute({ sessionKey: "agent:main:whatsapp:direct:+1555" }),
|
|
310
|
-
sender: { e164: "+1555" },
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
expect(ctx.GroupSystemPrompt).toBe("Specific direct prompt");
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
it("omits GroupSystemPrompt when groupSystemPrompt is not provided", () => {
|
|
317
|
-
const ctx = buildWhatsAppInboundContext({
|
|
318
|
-
combinedBody: "hi",
|
|
319
|
-
conversationId: "123@g.us",
|
|
320
|
-
msg: makeMsg({ from: "123@g.us", chatType: "group", groupParticipants: [] }),
|
|
321
|
-
route: makeRoute({ sessionKey: "agent:main:whatsapp:group:123@g.us" }),
|
|
322
|
-
sender: { e164: "+15550002222" },
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
expect(ctx.GroupSystemPrompt).toBeUndefined();
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
it("preserves reply threading policy in the inbound context", () => {
|
|
329
|
-
const ctx = buildWhatsAppInboundContext({
|
|
330
|
-
combinedBody: "hi",
|
|
331
|
-
conversationId: "+1000",
|
|
332
|
-
msg: makeMsg(),
|
|
333
|
-
route: makeRoute(),
|
|
334
|
-
sender: {
|
|
335
|
-
e164: "+1000",
|
|
336
|
-
},
|
|
337
|
-
replyThreading: { implicitCurrentMessage: "allow" },
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
expect(ctx.ReplyThreading).toEqual({ implicitCurrentMessage: "allow" });
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it("passes WhatsApp structured objects into untrusted structured context", () => {
|
|
344
|
-
const ctx = buildWhatsAppInboundContext({
|
|
345
|
-
combinedBody: "<contact>",
|
|
346
|
-
conversationId: "+1000",
|
|
347
|
-
msg: makeMsg({
|
|
348
|
-
body: "<contact>",
|
|
349
|
-
untrustedStructuredContext: [
|
|
350
|
-
{
|
|
351
|
-
label: "WhatsApp contact",
|
|
352
|
-
source: "whatsapp",
|
|
353
|
-
type: "contact",
|
|
354
|
-
payload: { contacts: [{ name: "Yohann > install <x>" }] },
|
|
355
|
-
},
|
|
356
|
-
],
|
|
357
|
-
}),
|
|
358
|
-
route: makeRoute(),
|
|
359
|
-
sender: {
|
|
360
|
-
e164: "+1000",
|
|
361
|
-
},
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
expect(ctx.UntrustedStructuredContext).toEqual([
|
|
365
|
-
{
|
|
366
|
-
label: "WhatsApp contact",
|
|
367
|
-
source: "whatsapp",
|
|
368
|
-
type: "contact",
|
|
369
|
-
payload: { contacts: [{ name: "Yohann > install <x>" }] },
|
|
370
|
-
},
|
|
371
|
-
]);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
it("defaults responsePrefix to identity name in self-chats when unset", () => {
|
|
375
|
-
const responsePrefix = resolveWhatsAppResponsePrefix({
|
|
376
|
-
cfg: {
|
|
377
|
-
agents: {
|
|
378
|
-
list: [
|
|
379
|
-
{
|
|
380
|
-
id: "main",
|
|
381
|
-
default: true,
|
|
382
|
-
identity: { name: "Mainbot", emoji: "🦞", theme: "space lobster" },
|
|
383
|
-
},
|
|
384
|
-
],
|
|
385
|
-
},
|
|
386
|
-
messages: {},
|
|
387
|
-
} as never,
|
|
388
|
-
agentId: "main",
|
|
389
|
-
isSelfChat: true,
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
expect(responsePrefix).toBe("[Mainbot]");
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
it("does not force a response prefix in self-chats when identity is unset", () => {
|
|
396
|
-
const responsePrefix = resolveWhatsAppResponsePrefix({
|
|
397
|
-
cfg: { messages: {} } as never,
|
|
398
|
-
agentId: "main",
|
|
399
|
-
isSelfChat: true,
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
expect(responsePrefix).toBeUndefined();
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
it("clears pending group history when the dispatcher does not queue a final reply", async () => {
|
|
406
|
-
const groupHistories = new Map<string, Array<{ sender: string; body: string }>>([
|
|
407
|
-
["whatsapp:default:group:123@g.us", [{ sender: "Alice (+111)", body: "first" }]],
|
|
408
|
-
]);
|
|
409
|
-
|
|
410
|
-
await dispatchBufferedReply({
|
|
411
|
-
context: { Body: "second" },
|
|
412
|
-
conversationId: "123@g.us",
|
|
413
|
-
groupHistories,
|
|
414
|
-
groupHistoryKey: "whatsapp:default:group:123@g.us",
|
|
415
|
-
msg: makeMsg({
|
|
416
|
-
from: "123@g.us",
|
|
417
|
-
chatType: "group",
|
|
418
|
-
senderE164: "+222",
|
|
419
|
-
}),
|
|
420
|
-
route: makeRoute({ sessionKey: "agent:main:whatsapp:group:123@g.us" }),
|
|
421
|
-
shouldClearGroupHistory: true,
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
expect(groupHistories.get("whatsapp:default:group:123@g.us") ?? []).toHaveLength(0);
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
it("delivers block and final WhatsApp payloads; suppresses text-only tool payloads but delivers media", async () => {
|
|
428
|
-
const deliverReply = vi.fn(async () => acceptedDeliveryResult());
|
|
429
|
-
const rememberSentText = vi.fn();
|
|
430
|
-
|
|
431
|
-
await dispatchBufferedReply({
|
|
432
|
-
deliverReply,
|
|
433
|
-
rememberSentText,
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
const deliver = getCapturedDeliver();
|
|
437
|
-
expect(deliver).toBeTypeOf("function");
|
|
438
|
-
|
|
439
|
-
await deliver?.({ text: "tool payload" }, { kind: "tool" });
|
|
440
|
-
expect(deliverReply).not.toHaveBeenCalled();
|
|
441
|
-
expect(rememberSentText).not.toHaveBeenCalled();
|
|
442
|
-
|
|
443
|
-
await deliver?.(
|
|
444
|
-
{ text: "tool image", mediaUrls: ["/tmp/generated.jpg"] },
|
|
445
|
-
{
|
|
446
|
-
kind: "tool",
|
|
447
|
-
},
|
|
448
|
-
);
|
|
449
|
-
expect(deliverReply).toHaveBeenCalledTimes(1);
|
|
450
|
-
expect(rememberSentText).toHaveBeenCalledTimes(1);
|
|
451
|
-
expect(deliverReply).toHaveBeenLastCalledWith(
|
|
452
|
-
expect.objectContaining({
|
|
453
|
-
replyResult: expect.objectContaining({
|
|
454
|
-
mediaUrls: ["/tmp/generated.jpg"],
|
|
455
|
-
text: undefined,
|
|
456
|
-
}),
|
|
457
|
-
}),
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
await deliver?.(
|
|
461
|
-
{ text: "generated image", mediaUrls: ["/tmp/generated.jpg"] },
|
|
462
|
-
{
|
|
463
|
-
kind: "block",
|
|
464
|
-
},
|
|
465
|
-
);
|
|
466
|
-
expect(deliverReply).toHaveBeenCalledTimes(2);
|
|
467
|
-
expect(rememberSentText).toHaveBeenCalledTimes(2);
|
|
468
|
-
expect(deliverReply).toHaveBeenLastCalledWith(
|
|
469
|
-
expect.objectContaining({
|
|
470
|
-
replyResult: expect.objectContaining({
|
|
471
|
-
mediaUrls: ["/tmp/generated.jpg"],
|
|
472
|
-
text: "generated image",
|
|
473
|
-
}),
|
|
474
|
-
}),
|
|
475
|
-
);
|
|
476
|
-
|
|
477
|
-
await deliver?.({ text: "block payload" }, { kind: "block" });
|
|
478
|
-
await deliver?.({ text: "final payload" }, { kind: "final" });
|
|
479
|
-
expect(deliverReply).toHaveBeenCalledTimes(4);
|
|
480
|
-
expect(rememberSentText).toHaveBeenCalledTimes(4);
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
it("normalizes WhatsApp payload text before delivery and echo bookkeeping", async () => {
|
|
484
|
-
const deliverReply = vi.fn(async () => acceptedDeliveryResult());
|
|
485
|
-
const rememberSentText = vi.fn();
|
|
486
|
-
|
|
487
|
-
await dispatchBufferedReply({
|
|
488
|
-
deliverReply,
|
|
489
|
-
rememberSentText,
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
const deliver = getCapturedDeliver();
|
|
493
|
-
expect(deliver).toBeTypeOf("function");
|
|
494
|
-
|
|
495
|
-
await deliver?.(
|
|
496
|
-
{
|
|
497
|
-
text: 'Before\n<function_calls><invoke name="web_search"><parameter name="query">x</parameter></invoke></function_calls>\nAfter',
|
|
498
|
-
},
|
|
499
|
-
{ kind: "final" },
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
expect(deliverReply).toHaveBeenCalledWith(
|
|
503
|
-
expect.objectContaining({
|
|
504
|
-
replyResult: expect.objectContaining({ text: "Before\n\nAfter" }),
|
|
505
|
-
}),
|
|
506
|
-
);
|
|
507
|
-
expect(rememberSentText).toHaveBeenCalledWith(
|
|
508
|
-
"Before\n\nAfter",
|
|
509
|
-
expect.objectContaining({
|
|
510
|
-
combinedBody: "hi",
|
|
511
|
-
combinedBodySessionKey: "agent:main:whatsapp:direct:+1000",
|
|
512
|
-
}),
|
|
513
|
-
);
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
it("suppresses reasoning and compaction payloads before WhatsApp delivery", async () => {
|
|
517
|
-
const deliverReply = vi.fn(async () => acceptedDeliveryResult());
|
|
518
|
-
const rememberSentText = vi.fn();
|
|
519
|
-
|
|
520
|
-
await dispatchBufferedReply({
|
|
521
|
-
deliverReply,
|
|
522
|
-
rememberSentText,
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
const deliver = getCapturedDeliver();
|
|
526
|
-
expect(deliver).toBeTypeOf("function");
|
|
527
|
-
|
|
528
|
-
await deliver?.({ text: "Reasoning:\n_hidden_", isReasoning: true }, { kind: "block" });
|
|
529
|
-
await deliver?.(
|
|
530
|
-
{ text: "🧹 Compacting context...", isCompactionNotice: true },
|
|
531
|
-
{ kind: "block" },
|
|
532
|
-
);
|
|
533
|
-
expect(deliverReply).not.toHaveBeenCalled();
|
|
534
|
-
expect(rememberSentText).not.toHaveBeenCalled();
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
it("suppresses payloads that normalize to no visible WhatsApp content", async () => {
|
|
538
|
-
const deliverReply = vi.fn(async () => acceptedDeliveryResult());
|
|
539
|
-
const rememberSentText = vi.fn();
|
|
540
|
-
|
|
541
|
-
await dispatchBufferedReply({
|
|
542
|
-
deliverReply,
|
|
543
|
-
rememberSentText,
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
const deliver = getCapturedDeliver();
|
|
547
|
-
expect(deliver).toBeTypeOf("function");
|
|
548
|
-
|
|
549
|
-
await deliver?.(
|
|
550
|
-
{
|
|
551
|
-
text: '<function_calls><invoke name="web_search"><parameter name="query">x</parameter></invoke></function_calls>',
|
|
552
|
-
},
|
|
553
|
-
{ kind: "final" },
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
expect(deliverReply).not.toHaveBeenCalled();
|
|
557
|
-
expect(rememberSentText).not.toHaveBeenCalled();
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
it("suppresses error payload text", async () => {
|
|
561
|
-
const deliverReply = vi.fn(async () => acceptedDeliveryResult());
|
|
562
|
-
const rememberSentText = vi.fn();
|
|
563
|
-
|
|
564
|
-
await dispatchBufferedReply({ deliverReply, rememberSentText });
|
|
565
|
-
|
|
566
|
-
const deliver = getCapturedDeliver();
|
|
567
|
-
expect(deliver).toBeTypeOf("function");
|
|
568
|
-
|
|
569
|
-
await deliver?.({ text: "provider exploded", isError: true }, { kind: "final" });
|
|
570
|
-
|
|
571
|
-
expect(deliverReply).not.toHaveBeenCalled();
|
|
572
|
-
expect(rememberSentText).not.toHaveBeenCalled();
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
it("maps WhatsApp blockStreaming=true to disableBlockStreaming=false", async () => {
|
|
576
|
-
await dispatchBufferedReply();
|
|
577
|
-
|
|
578
|
-
expect(
|
|
579
|
-
(
|
|
580
|
-
capturedDispatchParams as {
|
|
581
|
-
replyOptions?: { disableBlockStreaming?: boolean };
|
|
582
|
-
}
|
|
583
|
-
)?.replyOptions?.disableBlockStreaming,
|
|
584
|
-
).toBe(false);
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
it("maps WhatsApp blockStreaming=false to disableBlockStreaming=true", async () => {
|
|
588
|
-
await dispatchBufferedReply({
|
|
589
|
-
cfg: { channels: { whatsapp: { blockStreaming: false } } } as never,
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
expect(
|
|
593
|
-
(
|
|
594
|
-
capturedDispatchParams as {
|
|
595
|
-
replyOptions?: { disableBlockStreaming?: boolean };
|
|
596
|
-
}
|
|
597
|
-
)?.replyOptions?.disableBlockStreaming,
|
|
598
|
-
).toBe(true);
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
it("leaves disableBlockStreaming undefined when WhatsApp blockStreaming is unset", async () => {
|
|
602
|
-
await dispatchBufferedReply({
|
|
603
|
-
cfg: { channels: { whatsapp: {} } } as never,
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
expect(
|
|
607
|
-
(
|
|
608
|
-
capturedDispatchParams as {
|
|
609
|
-
replyOptions?: { disableBlockStreaming?: boolean };
|
|
610
|
-
}
|
|
611
|
-
)?.replyOptions?.disableBlockStreaming,
|
|
612
|
-
).toBeUndefined();
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
it("treats block-only turns as visible replies instead of silent turns", async () => {
|
|
616
|
-
const deliverReply = vi.fn(async () => acceptedDeliveryResult());
|
|
617
|
-
const rememberSentText = vi.fn();
|
|
618
|
-
dispatchReplyWithBufferedBlockDispatcherMock.mockImplementationOnce(
|
|
619
|
-
async (params: {
|
|
620
|
-
ctx: unknown;
|
|
621
|
-
dispatcherOptions?: {
|
|
622
|
-
deliver?: (
|
|
623
|
-
payload: { text?: string },
|
|
624
|
-
info: { kind: "tool" | "block" | "final" },
|
|
625
|
-
) => Promise<void>;
|
|
626
|
-
};
|
|
627
|
-
}) => {
|
|
628
|
-
capturedDispatchParams = params;
|
|
629
|
-
await params.dispatcherOptions?.deliver?.({ text: "partial block" }, { kind: "block" });
|
|
630
|
-
return { queuedFinal: false, counts: { tool: 0, block: 1, final: 0 } };
|
|
631
|
-
},
|
|
632
|
-
);
|
|
633
|
-
|
|
634
|
-
await expect(
|
|
635
|
-
dispatchBufferedReply({
|
|
636
|
-
deliverReply,
|
|
637
|
-
rememberSentText,
|
|
638
|
-
}),
|
|
639
|
-
).resolves.toBe(true);
|
|
640
|
-
|
|
641
|
-
expect(deliverReply).toHaveBeenCalledTimes(1);
|
|
642
|
-
expect(rememberSentText).toHaveBeenCalledTimes(1);
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
it("does not treat generated WhatsApp text as sent when the provider did not accept it", async () => {
|
|
646
|
-
const deliverReply = vi.fn(async () => unacceptedDeliveryResult());
|
|
647
|
-
const rememberSentText = vi.fn();
|
|
648
|
-
const replyLogger = {
|
|
649
|
-
info: vi.fn(),
|
|
650
|
-
warn: vi.fn(),
|
|
651
|
-
error: vi.fn(),
|
|
652
|
-
debug: vi.fn(),
|
|
653
|
-
} as unknown as BufferedReplyParams["replyLogger"];
|
|
654
|
-
dispatchReplyWithBufferedBlockDispatcherMock.mockImplementationOnce(
|
|
655
|
-
async (params: {
|
|
656
|
-
ctx: unknown;
|
|
657
|
-
dispatcherOptions?: {
|
|
658
|
-
deliver?: (
|
|
659
|
-
payload: { text?: string },
|
|
660
|
-
info: { kind: "tool" | "block" | "final" },
|
|
661
|
-
) => Promise<void>;
|
|
662
|
-
};
|
|
663
|
-
}) => {
|
|
664
|
-
capturedDispatchParams = params;
|
|
665
|
-
await params.dispatcherOptions?.deliver?.({ text: "final text" }, { kind: "final" });
|
|
666
|
-
return { queuedFinal: false, counts: { tool: 0, block: 0, final: 1 } };
|
|
667
|
-
},
|
|
668
|
-
);
|
|
669
|
-
|
|
670
|
-
await expect(
|
|
671
|
-
dispatchBufferedReply({
|
|
672
|
-
deliverReply,
|
|
673
|
-
rememberSentText,
|
|
674
|
-
replyLogger,
|
|
675
|
-
}),
|
|
676
|
-
).resolves.toBe(false);
|
|
677
|
-
|
|
678
|
-
expect(deliverReply).toHaveBeenCalledTimes(1);
|
|
679
|
-
expect(rememberSentText).not.toHaveBeenCalled();
|
|
680
|
-
expect(replyLogger.warn).toHaveBeenCalledWith(
|
|
681
|
-
expect.objectContaining({
|
|
682
|
-
replyKind: "final",
|
|
683
|
-
conversationId: "+1000",
|
|
684
|
-
}),
|
|
685
|
-
"auto-reply was not accepted by WhatsApp provider",
|
|
686
|
-
);
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
it("returns true for tool-only media turns after delivering media", async () => {
|
|
690
|
-
const deliverReply = vi.fn(async () => acceptedDeliveryResult());
|
|
691
|
-
const rememberSentText = vi.fn();
|
|
692
|
-
dispatchReplyWithBufferedBlockDispatcherMock.mockImplementationOnce(
|
|
693
|
-
async (params: {
|
|
694
|
-
ctx: unknown;
|
|
695
|
-
dispatcherOptions?: {
|
|
696
|
-
deliver?: (
|
|
697
|
-
payload: CapturedReplyPayload,
|
|
698
|
-
info: { kind: "tool" | "block" | "final" },
|
|
699
|
-
) => Promise<void>;
|
|
700
|
-
};
|
|
701
|
-
}) => {
|
|
702
|
-
capturedDispatchParams = params;
|
|
703
|
-
await params.dispatcherOptions?.deliver?.(
|
|
704
|
-
{ text: "tool image", mediaUrls: ["/tmp/generated.jpg"] },
|
|
705
|
-
{ kind: "tool" },
|
|
706
|
-
);
|
|
707
|
-
return { queuedFinal: false, counts: { tool: 1, block: 0, final: 0 } };
|
|
708
|
-
},
|
|
709
|
-
);
|
|
710
|
-
|
|
711
|
-
await expect(
|
|
712
|
-
dispatchWhatsAppBufferedReply({
|
|
713
|
-
cfg: { channels: { whatsapp: { blockStreaming: true } } } as never,
|
|
714
|
-
connectionId: "conn",
|
|
715
|
-
context: { Body: "hi" },
|
|
716
|
-
conversationId: "+1000",
|
|
717
|
-
deliverReply,
|
|
718
|
-
groupHistories: new Map(),
|
|
719
|
-
groupHistoryKey: "+1000",
|
|
720
|
-
maxMediaBytes: 1,
|
|
721
|
-
msg: makeMsg(),
|
|
722
|
-
rememberSentText,
|
|
723
|
-
replyLogger: {
|
|
724
|
-
info: () => {},
|
|
725
|
-
warn: () => {},
|
|
726
|
-
error: () => {},
|
|
727
|
-
debug: () => {},
|
|
728
|
-
} as never,
|
|
729
|
-
replyPipeline: {},
|
|
730
|
-
replyResolver: (async () => undefined) as never,
|
|
731
|
-
route: makeRoute(),
|
|
732
|
-
shouldClearGroupHistory: false,
|
|
733
|
-
}),
|
|
734
|
-
).resolves.toBe(true);
|
|
735
|
-
|
|
736
|
-
expect(deliverReply).toHaveBeenCalledTimes(1);
|
|
737
|
-
expect(deliverReply).toHaveBeenCalledWith(
|
|
738
|
-
expect.objectContaining({
|
|
739
|
-
replyResult: expect.objectContaining({
|
|
740
|
-
mediaUrls: ["/tmp/generated.jpg"],
|
|
741
|
-
text: undefined,
|
|
742
|
-
}),
|
|
743
|
-
}),
|
|
744
|
-
);
|
|
745
|
-
expect(rememberSentText).toHaveBeenCalledWith(undefined, expect.any(Object));
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
it("passes sendComposing through as the reply typing callback", async () => {
|
|
749
|
-
const sendComposing = vi.fn(async () => undefined);
|
|
750
|
-
|
|
751
|
-
await dispatchBufferedReply({
|
|
752
|
-
msg: makeMsg({ sendComposing }),
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
expect(
|
|
756
|
-
(
|
|
757
|
-
capturedDispatchParams as {
|
|
758
|
-
dispatcherOptions?: { onReplyStart?: unknown };
|
|
759
|
-
}
|
|
760
|
-
)?.dispatcherOptions?.onReplyStart,
|
|
761
|
-
).toBe(sendComposing);
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
it("logs delivery failures from the shared dispatcher with WhatsApp context", async () => {
|
|
765
|
-
const replyLogger = {
|
|
766
|
-
info: vi.fn(),
|
|
767
|
-
warn: vi.fn(),
|
|
768
|
-
error: vi.fn(),
|
|
769
|
-
debug: vi.fn(),
|
|
770
|
-
} as unknown as BufferedReplyParams["replyLogger"];
|
|
771
|
-
const error = new Error("send failed");
|
|
772
|
-
|
|
773
|
-
await dispatchBufferedReply({
|
|
774
|
-
connectionId: "conn-1",
|
|
775
|
-
conversationId: "+15550001000",
|
|
776
|
-
msg: makeMsg({
|
|
777
|
-
id: "msg-1",
|
|
778
|
-
from: "+15550001000",
|
|
779
|
-
to: "+15550002000",
|
|
780
|
-
chatId: "15550001000@s.whatsapp.net",
|
|
781
|
-
}),
|
|
782
|
-
replyLogger,
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
getCapturedOnError()?.(error, { kind: "final" });
|
|
786
|
-
|
|
787
|
-
expect(replyLogger.error).toHaveBeenCalledWith(
|
|
788
|
-
{
|
|
789
|
-
err: error,
|
|
790
|
-
replyKind: "final",
|
|
791
|
-
correlationId: "msg-1",
|
|
792
|
-
connectionId: "conn-1",
|
|
793
|
-
conversationId: "+15550001000",
|
|
794
|
-
chatId: "15550001000@s.whatsapp.net",
|
|
795
|
-
to: "+15550001000",
|
|
796
|
-
from: "+15550002000",
|
|
797
|
-
},
|
|
798
|
-
"auto-reply delivery failed",
|
|
799
|
-
);
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
it("updates main last route for DM when session key matches main session key", () => {
|
|
803
|
-
const updateLastRoute = vi.fn();
|
|
804
|
-
|
|
805
|
-
updateWhatsAppMainLastRoute({
|
|
806
|
-
backgroundTasks: new Set(),
|
|
807
|
-
cfg: {} as never,
|
|
808
|
-
ctx: { Body: "hello" },
|
|
809
|
-
dmRouteTarget: "+1000",
|
|
810
|
-
pinnedMainDmRecipient: null,
|
|
811
|
-
route: makeRoute(),
|
|
812
|
-
updateLastRoute,
|
|
813
|
-
warn: () => {},
|
|
814
|
-
});
|
|
815
|
-
|
|
816
|
-
expect(updateLastRoute).toHaveBeenCalledTimes(1);
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
it("does not update main last route for isolated DM scope sessions", () => {
|
|
820
|
-
const updateLastRoute = vi.fn();
|
|
821
|
-
|
|
822
|
-
updateWhatsAppMainLastRoute({
|
|
823
|
-
backgroundTasks: new Set(),
|
|
824
|
-
cfg: {} as never,
|
|
825
|
-
ctx: { Body: "hello" },
|
|
826
|
-
dmRouteTarget: "+3000",
|
|
827
|
-
pinnedMainDmRecipient: null,
|
|
828
|
-
route: makeRoute({
|
|
829
|
-
sessionKey: "agent:main:whatsapp:dm:+1000:peer:+3000",
|
|
830
|
-
mainSessionKey: "agent:main:whatsapp:direct:+1000",
|
|
831
|
-
}),
|
|
832
|
-
updateLastRoute,
|
|
833
|
-
warn: () => {},
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
expect(updateLastRoute).not.toHaveBeenCalled();
|
|
837
|
-
});
|
|
838
|
-
|
|
839
|
-
it("does not update main last route for non-owner sender when main DM scope is pinned", () => {
|
|
840
|
-
const updateLastRoute = vi.fn();
|
|
841
|
-
|
|
842
|
-
updateWhatsAppMainLastRoute({
|
|
843
|
-
backgroundTasks: new Set(),
|
|
844
|
-
cfg: {} as never,
|
|
845
|
-
ctx: { Body: "hello" },
|
|
846
|
-
dmRouteTarget: "+3000",
|
|
847
|
-
pinnedMainDmRecipient: "+1000",
|
|
848
|
-
route: makeRoute({
|
|
849
|
-
sessionKey: "agent:main:main",
|
|
850
|
-
mainSessionKey: "agent:main:main",
|
|
851
|
-
}),
|
|
852
|
-
updateLastRoute,
|
|
853
|
-
warn: () => {},
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
expect(updateLastRoute).not.toHaveBeenCalled();
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
it("updates main last route for owner sender when main DM scope is pinned", () => {
|
|
860
|
-
const updateLastRoute = vi.fn();
|
|
861
|
-
|
|
862
|
-
updateWhatsAppMainLastRoute({
|
|
863
|
-
backgroundTasks: new Set(),
|
|
864
|
-
cfg: {} as never,
|
|
865
|
-
ctx: { Body: "hello" },
|
|
866
|
-
dmRouteTarget: "+1000",
|
|
867
|
-
pinnedMainDmRecipient: "+1000",
|
|
868
|
-
route: makeRoute({
|
|
869
|
-
sessionKey: "agent:main:main",
|
|
870
|
-
mainSessionKey: "agent:main:main",
|
|
871
|
-
}),
|
|
872
|
-
updateLastRoute,
|
|
873
|
-
warn: () => {},
|
|
874
|
-
});
|
|
875
|
-
|
|
876
|
-
expect(updateLastRoute).toHaveBeenCalledTimes(1);
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
it("resolves DM route targets from the sender first and the chat JID second", () => {
|
|
880
|
-
expect(
|
|
881
|
-
resolveWhatsAppDmRouteTarget({
|
|
882
|
-
msg: makeMsg({ from: "15550003333@s.whatsapp.net" }),
|
|
883
|
-
senderE164: "+15550002222",
|
|
884
|
-
normalizeE164: (value) => value,
|
|
885
|
-
}),
|
|
886
|
-
).toBe("+15550002222");
|
|
887
|
-
|
|
888
|
-
expect(
|
|
889
|
-
resolveWhatsAppDmRouteTarget({
|
|
890
|
-
msg: makeMsg({ from: "15550003333@s.whatsapp.net" }),
|
|
891
|
-
normalizeE164: () => null,
|
|
892
|
-
}),
|
|
893
|
-
).toBe("+15550003333");
|
|
894
|
-
});
|
|
895
|
-
});
|