@openclaw/whatsapp 2026.5.2 → 2026.5.3-beta.2
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 -339
- 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 -319
- package/src/inbound/send-api.ts +0 -145
- 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 -123
- package/src/normalize.ts +0 -7
- 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 -288
- package/src/resolve-outbound-target.ts +0 -58
- package/src/resolve-target.test.ts +0 -120
- 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 -534
- package/src/send.ts +0 -292
- 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.test.ts +0 -41
- package/src/session-route.ts +0 -32
- 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,420 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
// Mock the lazy-loaded audio preflight runtime boundary
|
|
4
|
-
const transcribeFirstAudioMock = vi.fn();
|
|
5
|
-
const maybeSendAckReactionMock = vi.fn();
|
|
6
|
-
|
|
7
|
-
vi.mock("./audio-preflight.runtime.js", () => ({
|
|
8
|
-
transcribeFirstAudio: (...args: unknown[]) => transcribeFirstAudioMock(...args),
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
// Controllable shouldComputeCommandAuthorized for command-sync tests
|
|
12
|
-
let shouldComputeCommandResult = false;
|
|
13
|
-
let shouldComputeCommandBodies: string[] = [];
|
|
14
|
-
|
|
15
|
-
// Minimal mocks for process-message dependencies
|
|
16
|
-
vi.mock("../../accounts.js", () => ({
|
|
17
|
-
resolveWhatsAppAccount: () => ({
|
|
18
|
-
accountId: "default",
|
|
19
|
-
dmPolicy: "pairing",
|
|
20
|
-
groupPolicy: "allowlist",
|
|
21
|
-
allowFrom: [],
|
|
22
|
-
}),
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
vi.mock("../../identity.js", () => ({
|
|
26
|
-
getPrimaryIdentityId: () => undefined,
|
|
27
|
-
getSelfIdentity: () => ({ e164: "+15550000001" }),
|
|
28
|
-
getSenderIdentity: () => ({ e164: "+15550000002", name: "Alice" }),
|
|
29
|
-
}));
|
|
30
|
-
|
|
31
|
-
vi.mock("../../reconnect.js", () => ({
|
|
32
|
-
newConnectionId: () => "test-conn-id",
|
|
33
|
-
}));
|
|
34
|
-
|
|
35
|
-
vi.mock("../../session.js", () => ({
|
|
36
|
-
formatError: (err: unknown) => String(err),
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
vi.mock("../deliver-reply.js", () => ({
|
|
40
|
-
deliverWebReply: vi.fn(async () => {}),
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
vi.mock("../loggers.js", () => ({
|
|
44
|
-
whatsappInboundLog: { info: () => {}, debug: () => {} },
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
vi.mock("./ack-reaction.js", () => ({
|
|
48
|
-
maybeSendAckReaction: (...args: unknown[]) => maybeSendAckReactionMock(...args),
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
|
-
vi.mock("./inbound-context.js", () => ({
|
|
52
|
-
resolveVisibleWhatsAppGroupHistory: () => [],
|
|
53
|
-
resolveVisibleWhatsAppReplyContext: () => null,
|
|
54
|
-
}));
|
|
55
|
-
|
|
56
|
-
vi.mock("./last-route.js", () => ({
|
|
57
|
-
trackBackgroundTask: () => {},
|
|
58
|
-
updateLastRouteInBackground: () => {},
|
|
59
|
-
}));
|
|
60
|
-
|
|
61
|
-
vi.mock("./message-line.js", () => ({
|
|
62
|
-
buildInboundLine: (params: { msg: { body: string } }) => params.msg.body,
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
vi.mock("./runtime-api.js", () => ({
|
|
66
|
-
buildHistoryContextFromEntries: (_p: { currentMessage: string }) => _p.currentMessage,
|
|
67
|
-
createChannelReplyPipeline: () => ({ onModelSelected: undefined }),
|
|
68
|
-
formatInboundEnvelope: (p: { body: string }) => p.body,
|
|
69
|
-
logVerbose: () => {},
|
|
70
|
-
normalizeE164: (v: string) => v,
|
|
71
|
-
readStoreAllowFromForDmPolicy: async () => [],
|
|
72
|
-
recordSessionMetaFromInbound: async () => {},
|
|
73
|
-
resolveChannelContextVisibilityMode: () => "standard",
|
|
74
|
-
resolveInboundSessionEnvelopeContext: () => ({
|
|
75
|
-
storePath: "/tmp/sessions.json",
|
|
76
|
-
envelopeOptions: {},
|
|
77
|
-
previousTimestamp: undefined,
|
|
78
|
-
}),
|
|
79
|
-
resolvePinnedMainDmOwnerFromAllowlist: () => null,
|
|
80
|
-
resolveDmGroupAccessWithCommandGate: () => ({ commandAuthorized: true }),
|
|
81
|
-
shouldComputeCommandAuthorized: (body: string) => {
|
|
82
|
-
shouldComputeCommandBodies.push(body);
|
|
83
|
-
return shouldComputeCommandResult || body.startsWith("/");
|
|
84
|
-
},
|
|
85
|
-
shouldLogVerbose: () => false,
|
|
86
|
-
type: undefined,
|
|
87
|
-
}));
|
|
88
|
-
|
|
89
|
-
vi.mock("./inbound-dispatch.js", () => ({
|
|
90
|
-
buildWhatsAppInboundContext: (params: {
|
|
91
|
-
bodyForAgent?: string;
|
|
92
|
-
combinedBody: string;
|
|
93
|
-
commandAuthorized?: boolean;
|
|
94
|
-
commandBody?: string;
|
|
95
|
-
msg: { body: string; mediaPath?: string; mediaType?: string };
|
|
96
|
-
mediaTranscribedIndexes?: number[];
|
|
97
|
-
rawBody?: string;
|
|
98
|
-
transcript?: string;
|
|
99
|
-
}) => ({
|
|
100
|
-
Body: params.combinedBody,
|
|
101
|
-
BodyForAgent: params.bodyForAgent ?? params.msg.body,
|
|
102
|
-
CommandAuthorized: params.commandAuthorized,
|
|
103
|
-
CommandBody: params.commandBody ?? params.msg.body,
|
|
104
|
-
MediaPath: params.msg.mediaPath,
|
|
105
|
-
MediaType: params.msg.mediaType,
|
|
106
|
-
MediaTranscribedIndexes: params.mediaTranscribedIndexes,
|
|
107
|
-
RawBody: params.rawBody ?? params.msg.body,
|
|
108
|
-
Transcript: params.transcript,
|
|
109
|
-
}),
|
|
110
|
-
dispatchWhatsAppBufferedReply: vi.fn(async () => true),
|
|
111
|
-
resolveWhatsAppDmRouteTarget: () => "+15550000002",
|
|
112
|
-
resolveWhatsAppResponsePrefix: () => undefined,
|
|
113
|
-
updateWhatsAppMainLastRoute: () => {},
|
|
114
|
-
}));
|
|
115
|
-
|
|
116
|
-
import { dispatchWhatsAppBufferedReply } from "./inbound-dispatch.js";
|
|
117
|
-
import { processMessage } from "./process-message.js";
|
|
118
|
-
|
|
119
|
-
type WebInboundMsg = Parameters<typeof processMessage>[0]["msg"];
|
|
120
|
-
type TestRoute = Parameters<typeof processMessage>[0]["route"];
|
|
121
|
-
|
|
122
|
-
const flushMicrotasks = async () => {
|
|
123
|
-
await Promise.resolve();
|
|
124
|
-
await Promise.resolve();
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
function makeAudioMsg(overrides: Partial<WebInboundMsg> = {}): WebInboundMsg {
|
|
128
|
-
return {
|
|
129
|
-
id: "msg-1",
|
|
130
|
-
from: "+15550000002",
|
|
131
|
-
to: "+15550000001",
|
|
132
|
-
body: "<media:audio>",
|
|
133
|
-
chatType: "direct",
|
|
134
|
-
mediaType: "audio/ogg; codecs=opus",
|
|
135
|
-
mediaPath: "/tmp/voice.ogg",
|
|
136
|
-
timestamp: 1700000000,
|
|
137
|
-
accountId: "default",
|
|
138
|
-
...overrides,
|
|
139
|
-
} as WebInboundMsg;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function makeRoute(overrides: Partial<TestRoute> = {}): TestRoute {
|
|
143
|
-
return {
|
|
144
|
-
agentId: "main",
|
|
145
|
-
sessionKey: "agent:main:main",
|
|
146
|
-
mainSessionKey: "agent:main:main",
|
|
147
|
-
accountId: "default",
|
|
148
|
-
...overrides,
|
|
149
|
-
} as TestRoute;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function makeParams(msgOverrides: Partial<WebInboundMsg> = {}) {
|
|
153
|
-
return {
|
|
154
|
-
cfg: {
|
|
155
|
-
tools: { media: { audio: { enabled: true } } },
|
|
156
|
-
channels: { whatsapp: {} },
|
|
157
|
-
commands: { useAccessGroups: false },
|
|
158
|
-
} as never,
|
|
159
|
-
msg: makeAudioMsg(msgOverrides),
|
|
160
|
-
route: makeRoute(),
|
|
161
|
-
groupHistoryKey: "whatsapp:default:+15550000002",
|
|
162
|
-
groupHistories: new Map(),
|
|
163
|
-
groupMemberNames: new Map(),
|
|
164
|
-
connectionId: "conn-1",
|
|
165
|
-
verbose: false,
|
|
166
|
-
maxMediaBytes: 1024 * 1024,
|
|
167
|
-
replyResolver: vi.fn() as never,
|
|
168
|
-
replyLogger: {
|
|
169
|
-
info: () => {},
|
|
170
|
-
warn: () => {},
|
|
171
|
-
debug: () => {},
|
|
172
|
-
error: () => {},
|
|
173
|
-
} as never,
|
|
174
|
-
backgroundTasks: new Set<Promise<unknown>>(),
|
|
175
|
-
rememberSentText: () => {},
|
|
176
|
-
echoHas: () => false,
|
|
177
|
-
echoForget: () => {},
|
|
178
|
-
buildCombinedEchoKey: (p: { combinedBody: string }) => p.combinedBody,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function makeAckReactionHandle() {
|
|
183
|
-
return {
|
|
184
|
-
ackReactionPromise: Promise.resolve(true),
|
|
185
|
-
ackReactionValue: "👀",
|
|
186
|
-
remove: vi.fn(async () => undefined),
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function makeRemoveAckAfterReplyParams() {
|
|
191
|
-
return {
|
|
192
|
-
...makeParams(),
|
|
193
|
-
cfg: {
|
|
194
|
-
tools: { media: { audio: { enabled: true } } },
|
|
195
|
-
channels: { whatsapp: {} },
|
|
196
|
-
commands: { useAccessGroups: false },
|
|
197
|
-
messages: { removeAckAfterReply: true },
|
|
198
|
-
} as never,
|
|
199
|
-
preflightAudioTranscript: "pre-computed transcript from caller",
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
describe("processMessage audio preflight transcription", () => {
|
|
204
|
-
beforeEach(() => {
|
|
205
|
-
transcribeFirstAudioMock.mockReset();
|
|
206
|
-
maybeSendAckReactionMock.mockReset();
|
|
207
|
-
maybeSendAckReactionMock.mockResolvedValue(null);
|
|
208
|
-
shouldComputeCommandResult = false;
|
|
209
|
-
shouldComputeCommandBodies = [];
|
|
210
|
-
vi.mocked(dispatchWhatsAppBufferedReply).mockClear();
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("replaces <media:audio> body with transcript when transcription succeeds", async () => {
|
|
214
|
-
transcribeFirstAudioMock.mockResolvedValueOnce("okay let's test this voice message");
|
|
215
|
-
|
|
216
|
-
await processMessage(makeParams());
|
|
217
|
-
|
|
218
|
-
expect(transcribeFirstAudioMock).toHaveBeenCalledTimes(1);
|
|
219
|
-
expect(transcribeFirstAudioMock).toHaveBeenCalledWith(
|
|
220
|
-
expect.objectContaining({
|
|
221
|
-
ctx: expect.objectContaining({
|
|
222
|
-
MediaPaths: ["/tmp/voice.ogg"],
|
|
223
|
-
MediaTypes: ["audio/ogg; codecs=opus"],
|
|
224
|
-
}),
|
|
225
|
-
}),
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
const dispatchCall = vi.mocked(dispatchWhatsAppBufferedReply).mock.calls[0]?.[0];
|
|
229
|
-
expect(dispatchCall?.context).toMatchObject({
|
|
230
|
-
Body: "okay let's test this voice message",
|
|
231
|
-
BodyForAgent: "okay let's test this voice message",
|
|
232
|
-
CommandBody: "<media:audio>",
|
|
233
|
-
RawBody: "<media:audio>",
|
|
234
|
-
Transcript: "okay let's test this voice message",
|
|
235
|
-
MediaTranscribedIndexes: [0],
|
|
236
|
-
});
|
|
237
|
-
// mediaPath and mediaType must be preserved so inboundAudio detection (used by
|
|
238
|
-
// features like messages.tts.auto: "inbound") still recognises this as audio.
|
|
239
|
-
expect(dispatchCall?.context).toMatchObject({
|
|
240
|
-
MediaPath: "/tmp/voice.ogg",
|
|
241
|
-
MediaType: "audio/ogg; codecs=opus",
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it("falls back to <media:audio> placeholder when transcription fails", async () => {
|
|
246
|
-
transcribeFirstAudioMock.mockRejectedValueOnce(new Error("provider unavailable"));
|
|
247
|
-
|
|
248
|
-
await processMessage(makeParams());
|
|
249
|
-
|
|
250
|
-
expect(transcribeFirstAudioMock).toHaveBeenCalledTimes(1);
|
|
251
|
-
|
|
252
|
-
const dispatchCall = vi.mocked(dispatchWhatsAppBufferedReply).mock.calls[0]?.[0];
|
|
253
|
-
expect(dispatchCall?.context).toMatchObject({
|
|
254
|
-
Body: "<media:audio>",
|
|
255
|
-
BodyForAgent: "<media:audio>",
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it("falls back to <media:audio> placeholder when transcription returns undefined", async () => {
|
|
260
|
-
transcribeFirstAudioMock.mockResolvedValueOnce(undefined);
|
|
261
|
-
|
|
262
|
-
await processMessage(makeParams());
|
|
263
|
-
|
|
264
|
-
expect(transcribeFirstAudioMock).toHaveBeenCalledTimes(1);
|
|
265
|
-
|
|
266
|
-
const dispatchCall = vi.mocked(dispatchWhatsAppBufferedReply).mock.calls[0]?.[0];
|
|
267
|
-
expect(dispatchCall?.context).toMatchObject({
|
|
268
|
-
Body: "<media:audio>",
|
|
269
|
-
BodyForAgent: "<media:audio>",
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it("does not call transcribeFirstAudio when mediaType is not audio", async () => {
|
|
274
|
-
await processMessage(
|
|
275
|
-
makeParams({ body: "<media:image>", mediaType: "image/jpeg", mediaPath: "/tmp/img.jpg" }),
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
expect(transcribeFirstAudioMock).not.toHaveBeenCalled();
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it("does not call transcribeFirstAudio when body is not <media:audio>", async () => {
|
|
282
|
-
await processMessage(makeParams({ body: "hello there", mediaType: "audio/ogg; codecs=opus" }));
|
|
283
|
-
|
|
284
|
-
expect(transcribeFirstAudioMock).not.toHaveBeenCalled();
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it("does not call transcribeFirstAudio when mediaPath is absent", async () => {
|
|
288
|
-
await processMessage(makeParams({ mediaPath: undefined }));
|
|
289
|
-
|
|
290
|
-
expect(transcribeFirstAudioMock).not.toHaveBeenCalled();
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it("does not call transcribeFirstAudio when msg.mediaType is absent", async () => {
|
|
294
|
-
await processMessage(
|
|
295
|
-
makeParams({ mediaType: undefined, body: "<media:audio>", mediaPath: "/tmp/voice.ogg" }),
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
expect(transcribeFirstAudioMock).not.toHaveBeenCalled();
|
|
299
|
-
|
|
300
|
-
// Body passes through as-is without a mediaType to confirm audio
|
|
301
|
-
const dispatchCall = vi.mocked(dispatchWhatsAppBufferedReply).mock.calls[0]?.[0];
|
|
302
|
-
expect(dispatchCall?.context).toMatchObject({
|
|
303
|
-
Body: "<media:audio>",
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
it("does not use transcript body for command detection", async () => {
|
|
308
|
-
transcribeFirstAudioMock.mockResolvedValueOnce("/new start a new session");
|
|
309
|
-
|
|
310
|
-
await processMessage(makeParams());
|
|
311
|
-
|
|
312
|
-
expect(shouldComputeCommandBodies).toEqual(["<media:audio>"]);
|
|
313
|
-
|
|
314
|
-
const dispatchCall = vi.mocked(dispatchWhatsAppBufferedReply).mock.calls[0]?.[0];
|
|
315
|
-
expect(dispatchCall?.context).toMatchObject({
|
|
316
|
-
Body: "/new start a new session",
|
|
317
|
-
BodyForAgent: "/new start a new session",
|
|
318
|
-
CommandBody: "<media:audio>",
|
|
319
|
-
RawBody: "<media:audio>",
|
|
320
|
-
Transcript: "/new start a new session",
|
|
321
|
-
MediaTranscribedIndexes: [0],
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it("uses preflightAudioTranscript when provided, skipping transcribeFirstAudio", async () => {
|
|
326
|
-
// Simulate broadcast fan-out: caller pre-computed the transcript and passes it in.
|
|
327
|
-
// transcribeFirstAudio must NOT be called again inside processMessage.
|
|
328
|
-
await processMessage({
|
|
329
|
-
...makeParams(),
|
|
330
|
-
preflightAudioTranscript: "pre-computed transcript from fan-out caller",
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
expect(transcribeFirstAudioMock).not.toHaveBeenCalled();
|
|
334
|
-
|
|
335
|
-
const dispatchCall = vi.mocked(dispatchWhatsAppBufferedReply).mock.calls[0]?.[0];
|
|
336
|
-
expect(dispatchCall?.context).toMatchObject({
|
|
337
|
-
Body: "pre-computed transcript from fan-out caller",
|
|
338
|
-
BodyForAgent: "pre-computed transcript from fan-out caller",
|
|
339
|
-
CommandBody: "<media:audio>",
|
|
340
|
-
RawBody: "<media:audio>",
|
|
341
|
-
Transcript: "pre-computed transcript from fan-out caller",
|
|
342
|
-
MediaTranscribedIndexes: [0],
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it("does not send a duplicate ack when caller already sent it", async () => {
|
|
347
|
-
await processMessage({
|
|
348
|
-
...makeParams(),
|
|
349
|
-
preflightAudioTranscript: "pre-computed transcript from caller",
|
|
350
|
-
ackAlreadySent: true,
|
|
351
|
-
ackReaction: makeAckReactionHandle(),
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
expect(maybeSendAckReactionMock).not.toHaveBeenCalled();
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
it("removes caller-provided ack after a successful visible reply", async () => {
|
|
358
|
-
const ackReaction = makeAckReactionHandle();
|
|
359
|
-
|
|
360
|
-
await processMessage({
|
|
361
|
-
...makeRemoveAckAfterReplyParams(),
|
|
362
|
-
ackReaction,
|
|
363
|
-
});
|
|
364
|
-
await flushMicrotasks();
|
|
365
|
-
|
|
366
|
-
expect(ackReaction.remove).toHaveBeenCalledTimes(1);
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
it("removes internally sent ack after a successful visible reply", async () => {
|
|
370
|
-
const ackReaction = makeAckReactionHandle();
|
|
371
|
-
maybeSendAckReactionMock.mockResolvedValueOnce(ackReaction);
|
|
372
|
-
|
|
373
|
-
await processMessage(makeRemoveAckAfterReplyParams());
|
|
374
|
-
await flushMicrotasks();
|
|
375
|
-
|
|
376
|
-
expect(maybeSendAckReactionMock).toHaveBeenCalledTimes(1);
|
|
377
|
-
expect(ackReaction.remove).toHaveBeenCalledTimes(1);
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
it("keeps ack when no visible reply was delivered", async () => {
|
|
381
|
-
const ackReaction = makeAckReactionHandle();
|
|
382
|
-
maybeSendAckReactionMock.mockResolvedValueOnce(ackReaction);
|
|
383
|
-
vi.mocked(dispatchWhatsAppBufferedReply).mockResolvedValueOnce(false);
|
|
384
|
-
|
|
385
|
-
await processMessage(makeRemoveAckAfterReplyParams());
|
|
386
|
-
await flushMicrotasks();
|
|
387
|
-
|
|
388
|
-
expect(ackReaction.remove).not.toHaveBeenCalled();
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
it("keeps ack when the ack send failed", async () => {
|
|
392
|
-
const ackReaction = {
|
|
393
|
-
...makeAckReactionHandle(),
|
|
394
|
-
ackReactionPromise: Promise.resolve(false),
|
|
395
|
-
};
|
|
396
|
-
maybeSendAckReactionMock.mockResolvedValueOnce(ackReaction);
|
|
397
|
-
|
|
398
|
-
await processMessage(makeRemoveAckAfterReplyParams());
|
|
399
|
-
await flushMicrotasks();
|
|
400
|
-
|
|
401
|
-
expect(ackReaction.remove).not.toHaveBeenCalled();
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
it("skips internal STT when preflightAudioTranscript is null (failed preflight sentinel)", async () => {
|
|
405
|
-
// null = caller already attempted preflight but got nothing (provider unavailable,
|
|
406
|
-
// disabled, etc.). processMessage must NOT retry to avoid 1+N attempts in broadcast.
|
|
407
|
-
await processMessage({
|
|
408
|
-
...makeParams(),
|
|
409
|
-
preflightAudioTranscript: null,
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
expect(transcribeFirstAudioMock).not.toHaveBeenCalled();
|
|
413
|
-
|
|
414
|
-
// Body falls back to the original <media:audio> placeholder, not retried transcript.
|
|
415
|
-
const dispatchCall = vi.mocked(dispatchWhatsAppBufferedReply).mock.calls[0]?.[0];
|
|
416
|
-
expect(dispatchCall?.context).toMatchObject({
|
|
417
|
-
Body: "<media:audio>",
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
});
|