@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
package/src/outbound-base.ts
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_ACCOUNT_ID,
|
|
3
|
-
listCombinedAccountIds,
|
|
4
|
-
normalizeOptionalAccountId,
|
|
5
|
-
resolveListedDefaultAccountId,
|
|
6
|
-
} from "openclaw/plugin-sdk/account-core";
|
|
7
|
-
import {
|
|
8
|
-
createAttachedChannelResultAdapter,
|
|
9
|
-
type ChannelOutboundAdapter,
|
|
10
|
-
} from "openclaw/plugin-sdk/channel-send-result";
|
|
11
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
12
|
-
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/outbound-send-deps";
|
|
13
|
-
import { sendTextMediaPayload } from "openclaw/plugin-sdk/reply-payload";
|
|
14
|
-
import {
|
|
15
|
-
normalizeWhatsAppOutboundPayload,
|
|
16
|
-
normalizeWhatsAppPayloadText,
|
|
17
|
-
} from "./outbound-media-contract.js";
|
|
18
|
-
import { WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS } from "./outbound-send-deps.js";
|
|
19
|
-
import { lookupInboundMessageMetaForTarget } from "./quoted-message.js";
|
|
20
|
-
import { toWhatsappJid } from "./text-runtime.js";
|
|
21
|
-
|
|
22
|
-
type WhatsAppChunker = NonNullable<ChannelOutboundAdapter["chunker"]>;
|
|
23
|
-
type WhatsAppSendTextOptions = {
|
|
24
|
-
verbose: boolean;
|
|
25
|
-
cfg: OpenClawConfig;
|
|
26
|
-
mediaUrl?: string;
|
|
27
|
-
mediaAccess?: {
|
|
28
|
-
localRoots?: readonly string[];
|
|
29
|
-
readFile?: (filePath: string) => Promise<Buffer>;
|
|
30
|
-
};
|
|
31
|
-
mediaLocalRoots?: readonly string[];
|
|
32
|
-
mediaReadFile?: (filePath: string) => Promise<Buffer>;
|
|
33
|
-
gifPlayback?: boolean;
|
|
34
|
-
audioAsVoice?: boolean;
|
|
35
|
-
accountId?: string;
|
|
36
|
-
quotedMessageKey?: {
|
|
37
|
-
id: string;
|
|
38
|
-
remoteJid: string;
|
|
39
|
-
fromMe: boolean;
|
|
40
|
-
participant?: string;
|
|
41
|
-
messageText?: string;
|
|
42
|
-
};
|
|
43
|
-
preserveLeadingWhitespace?: boolean;
|
|
44
|
-
};
|
|
45
|
-
type WhatsAppSendMessage = (
|
|
46
|
-
to: string,
|
|
47
|
-
body: string,
|
|
48
|
-
options: WhatsAppSendTextOptions,
|
|
49
|
-
) => Promise<{ messageId: string; toJid: string }>;
|
|
50
|
-
type WhatsAppSendPoll = (
|
|
51
|
-
to: string,
|
|
52
|
-
poll: Parameters<NonNullable<ChannelOutboundAdapter["sendPoll"]>>[0]["poll"],
|
|
53
|
-
options: { verbose: boolean; accountId?: string; cfg: OpenClawConfig },
|
|
54
|
-
) => Promise<{ messageId: string; toJid: string }>;
|
|
55
|
-
|
|
56
|
-
type CreateWhatsAppOutboundBaseParams = {
|
|
57
|
-
chunker: WhatsAppChunker;
|
|
58
|
-
sendMessageWhatsApp: WhatsAppSendMessage;
|
|
59
|
-
sendPollWhatsApp: WhatsAppSendPoll;
|
|
60
|
-
shouldLogVerbose: () => boolean;
|
|
61
|
-
resolveTarget: ChannelOutboundAdapter["resolveTarget"];
|
|
62
|
-
normalizeText?: (text: string | undefined) => string;
|
|
63
|
-
skipEmptyText?: boolean;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
function resolveQuoteLookupAccountId(cfg?: OpenClawConfig, accountId?: string | null): string {
|
|
67
|
-
const explicitAccountId = normalizeOptionalAccountId(accountId);
|
|
68
|
-
if (explicitAccountId) {
|
|
69
|
-
return explicitAccountId;
|
|
70
|
-
}
|
|
71
|
-
const channelCfg = cfg?.channels?.whatsapp;
|
|
72
|
-
const configuredIds = listCombinedAccountIds({
|
|
73
|
-
configuredAccountIds:
|
|
74
|
-
channelCfg?.accounts && typeof channelCfg.accounts === "object"
|
|
75
|
-
? Object.keys(channelCfg.accounts).filter(Boolean)
|
|
76
|
-
: [],
|
|
77
|
-
fallbackAccountIdWhenEmpty: DEFAULT_ACCOUNT_ID,
|
|
78
|
-
});
|
|
79
|
-
return resolveListedDefaultAccountId({
|
|
80
|
-
accountIds: configuredIds,
|
|
81
|
-
configuredDefaultAccountId: normalizeOptionalAccountId(channelCfg?.defaultAccount),
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
type WhatsAppOutboundBaseCore = Pick<
|
|
86
|
-
ChannelOutboundAdapter,
|
|
87
|
-
| "deliveryMode"
|
|
88
|
-
| "chunker"
|
|
89
|
-
| "chunkerMode"
|
|
90
|
-
| "textChunkLimit"
|
|
91
|
-
| "sanitizeText"
|
|
92
|
-
| "pollMaxOptions"
|
|
93
|
-
| "resolveTarget"
|
|
94
|
-
| "sendText"
|
|
95
|
-
| "sendMedia"
|
|
96
|
-
| "sendPoll"
|
|
97
|
-
>;
|
|
98
|
-
|
|
99
|
-
export function createWhatsAppOutboundBase({
|
|
100
|
-
chunker,
|
|
101
|
-
sendMessageWhatsApp,
|
|
102
|
-
sendPollWhatsApp,
|
|
103
|
-
shouldLogVerbose,
|
|
104
|
-
resolveTarget,
|
|
105
|
-
normalizeText = normalizeWhatsAppPayloadText,
|
|
106
|
-
skipEmptyText = true,
|
|
107
|
-
}: CreateWhatsAppOutboundBaseParams): Pick<
|
|
108
|
-
ChannelOutboundAdapter,
|
|
109
|
-
| "deliveryMode"
|
|
110
|
-
| "chunker"
|
|
111
|
-
| "chunkerMode"
|
|
112
|
-
| "textChunkLimit"
|
|
113
|
-
| "sanitizeText"
|
|
114
|
-
| "pollMaxOptions"
|
|
115
|
-
| "resolveTarget"
|
|
116
|
-
| "sendPayload"
|
|
117
|
-
| "sendText"
|
|
118
|
-
| "sendMedia"
|
|
119
|
-
| "sendPoll"
|
|
120
|
-
> {
|
|
121
|
-
const resolveQuotedMessageKey = (params: {
|
|
122
|
-
accountId: string;
|
|
123
|
-
to: string;
|
|
124
|
-
replyToId?: string | null;
|
|
125
|
-
}) => {
|
|
126
|
-
const replyToId = params.replyToId?.trim();
|
|
127
|
-
if (!replyToId) {
|
|
128
|
-
return undefined;
|
|
129
|
-
}
|
|
130
|
-
const targetJid = toWhatsappJid(params.to);
|
|
131
|
-
const cachedMeta = lookupInboundMessageMetaForTarget(params.accountId, targetJid, replyToId);
|
|
132
|
-
return {
|
|
133
|
-
id: replyToId,
|
|
134
|
-
remoteJid: cachedMeta?.remoteJid ?? targetJid,
|
|
135
|
-
fromMe: cachedMeta?.fromMe ?? false,
|
|
136
|
-
participant: cachedMeta?.participant,
|
|
137
|
-
messageText: cachedMeta?.body,
|
|
138
|
-
};
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const outbound: WhatsAppOutboundBaseCore = {
|
|
142
|
-
deliveryMode: "gateway",
|
|
143
|
-
chunker,
|
|
144
|
-
chunkerMode: "text",
|
|
145
|
-
textChunkLimit: 4000,
|
|
146
|
-
sanitizeText: ({ text }) => normalizeText(text),
|
|
147
|
-
pollMaxOptions: 12,
|
|
148
|
-
resolveTarget,
|
|
149
|
-
...createAttachedChannelResultAdapter({
|
|
150
|
-
channel: "whatsapp",
|
|
151
|
-
sendText: async ({ cfg, to, text, accountId, deps, gifPlayback, replyToId }) => {
|
|
152
|
-
const normalizedText = normalizeText(text);
|
|
153
|
-
if (skipEmptyText && !normalizedText) {
|
|
154
|
-
return { messageId: "" };
|
|
155
|
-
}
|
|
156
|
-
const send =
|
|
157
|
-
resolveOutboundSendDep<WhatsAppSendMessage>(deps, "whatsapp", {
|
|
158
|
-
legacyKeys: WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS,
|
|
159
|
-
}) ?? sendMessageWhatsApp;
|
|
160
|
-
const lookupAccountId = resolveQuoteLookupAccountId(cfg, accountId);
|
|
161
|
-
const quotedMessageKey = resolveQuotedMessageKey({
|
|
162
|
-
accountId: lookupAccountId,
|
|
163
|
-
to,
|
|
164
|
-
replyToId,
|
|
165
|
-
});
|
|
166
|
-
return await send(to, normalizedText, {
|
|
167
|
-
verbose: false,
|
|
168
|
-
cfg,
|
|
169
|
-
accountId: accountId ?? undefined,
|
|
170
|
-
gifPlayback,
|
|
171
|
-
quotedMessageKey,
|
|
172
|
-
});
|
|
173
|
-
},
|
|
174
|
-
sendMedia: async ({
|
|
175
|
-
cfg,
|
|
176
|
-
to,
|
|
177
|
-
text,
|
|
178
|
-
mediaUrl,
|
|
179
|
-
mediaAccess,
|
|
180
|
-
mediaLocalRoots,
|
|
181
|
-
mediaReadFile,
|
|
182
|
-
audioAsVoice,
|
|
183
|
-
accountId,
|
|
184
|
-
deps,
|
|
185
|
-
gifPlayback,
|
|
186
|
-
replyToId,
|
|
187
|
-
}) => {
|
|
188
|
-
const send =
|
|
189
|
-
resolveOutboundSendDep<WhatsAppSendMessage>(deps, "whatsapp", {
|
|
190
|
-
legacyKeys: WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS,
|
|
191
|
-
}) ?? sendMessageWhatsApp;
|
|
192
|
-
const lookupAccountId = resolveQuoteLookupAccountId(cfg, accountId);
|
|
193
|
-
const quotedMessageKey = resolveQuotedMessageKey({
|
|
194
|
-
accountId: lookupAccountId,
|
|
195
|
-
to,
|
|
196
|
-
replyToId,
|
|
197
|
-
});
|
|
198
|
-
return await send(to, normalizeText(text), {
|
|
199
|
-
verbose: false,
|
|
200
|
-
cfg,
|
|
201
|
-
mediaUrl,
|
|
202
|
-
mediaAccess,
|
|
203
|
-
mediaLocalRoots,
|
|
204
|
-
mediaReadFile,
|
|
205
|
-
...(audioAsVoice === undefined ? {} : { audioAsVoice }),
|
|
206
|
-
accountId: accountId ?? undefined,
|
|
207
|
-
gifPlayback,
|
|
208
|
-
quotedMessageKey,
|
|
209
|
-
});
|
|
210
|
-
},
|
|
211
|
-
sendPoll: async ({ cfg, to, poll, accountId }) =>
|
|
212
|
-
await sendPollWhatsApp(to, poll, {
|
|
213
|
-
verbose: shouldLogVerbose(),
|
|
214
|
-
accountId: accountId ?? undefined,
|
|
215
|
-
cfg,
|
|
216
|
-
}),
|
|
217
|
-
}),
|
|
218
|
-
};
|
|
219
|
-
return {
|
|
220
|
-
...outbound,
|
|
221
|
-
sendPayload: async (ctx) => {
|
|
222
|
-
if (ctx.payload.isError === true) {
|
|
223
|
-
return { channel: "whatsapp", messageId: "" };
|
|
224
|
-
}
|
|
225
|
-
const payload = normalizeWhatsAppOutboundPayload(ctx.payload, { normalizeText });
|
|
226
|
-
if (!payload.text && !(payload.mediaUrl || payload.mediaUrls?.length)) {
|
|
227
|
-
if (ctx.payload.interactive || ctx.payload.presentation || ctx.payload.channelData) {
|
|
228
|
-
throw new Error(
|
|
229
|
-
"WhatsApp sendPayload does not support structured-only payloads without text or media.",
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
return { channel: "whatsapp", messageId: "" };
|
|
233
|
-
}
|
|
234
|
-
return await sendTextMediaPayload({
|
|
235
|
-
channel: "whatsapp",
|
|
236
|
-
ctx: {
|
|
237
|
-
...ctx,
|
|
238
|
-
payload,
|
|
239
|
-
},
|
|
240
|
-
adapter: outbound,
|
|
241
|
-
});
|
|
242
|
-
},
|
|
243
|
-
};
|
|
244
|
-
}
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS, runFfmpeg } from "openclaw/plugin-sdk/media-runtime";
|
|
4
|
-
import { sanitizeForPlainText } from "openclaw/plugin-sdk/outbound-runtime";
|
|
5
|
-
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
|
6
|
-
import { formatError } from "./session-errors.js";
|
|
7
|
-
import {
|
|
8
|
-
sanitizeAssistantVisibleText,
|
|
9
|
-
sanitizeAssistantVisibleTextWithProfile,
|
|
10
|
-
stripToolCallXmlTags,
|
|
11
|
-
sleep,
|
|
12
|
-
} from "./text-runtime.js";
|
|
13
|
-
|
|
14
|
-
type WhatsAppOutboundPayloadLike = {
|
|
15
|
-
text?: string;
|
|
16
|
-
mediaUrl?: string;
|
|
17
|
-
mediaUrls?: readonly string[];
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
type WhatsAppLoadedMediaLike = {
|
|
21
|
-
buffer: Buffer;
|
|
22
|
-
contentType?: string;
|
|
23
|
-
kind?: string;
|
|
24
|
-
fileName?: string;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
type NormalizedWhatsAppOutboundPayload<T extends WhatsAppOutboundPayloadLike> = Omit<
|
|
28
|
-
T,
|
|
29
|
-
"text" | "mediaUrl" | "mediaUrls"
|
|
30
|
-
> & {
|
|
31
|
-
text: string;
|
|
32
|
-
mediaUrl?: string;
|
|
33
|
-
mediaUrls?: string[];
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type DeliverableWhatsAppOutboundPayload<T extends WhatsAppOutboundPayloadLike> = Omit<
|
|
37
|
-
NormalizedWhatsAppOutboundPayload<T>,
|
|
38
|
-
"text"
|
|
39
|
-
> & {
|
|
40
|
-
text?: string;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
type CanonicalWhatsAppLoadedMedia = {
|
|
44
|
-
buffer: Buffer;
|
|
45
|
-
kind: "image" | "audio" | "video" | "document";
|
|
46
|
-
mimetype: string;
|
|
47
|
-
fileName?: string;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const WHATSAPP_VOICE_FILE_NAME = "voice.ogg";
|
|
51
|
-
const WHATSAPP_VOICE_SAMPLE_RATE_HZ = 48_000;
|
|
52
|
-
const WHATSAPP_VOICE_BITRATE = "64k";
|
|
53
|
-
const WHATSAPP_VOICE_MIMETYPE = "audio/ogg; codecs=opus";
|
|
54
|
-
|
|
55
|
-
function stripWhatsAppPluralToolXml(text: string): string {
|
|
56
|
-
return stripToolCallXmlTags(text, { stripFunctionCallsXmlPayloads: true });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function finalizeWhatsAppVisibleText(text: string): string {
|
|
60
|
-
return sanitizeForPlainText(stripWhatsAppPluralToolXml(text));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function normalizeWhatsAppPayloadText(text: string | undefined): string {
|
|
64
|
-
return finalizeWhatsAppVisibleText(sanitizeAssistantVisibleText(text ?? "")).trimStart();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function stripLeadingBlankLines(text: string): string {
|
|
68
|
-
return text.replace(/^(?:[ \t]*\r?\n)+/, "");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function normalizeWhatsAppPayloadTextPreservingIndentation(
|
|
72
|
-
text: string | undefined,
|
|
73
|
-
): string {
|
|
74
|
-
const sanitized = sanitizeAssistantVisibleTextWithProfile(
|
|
75
|
-
stripLeadingBlankLines(text ?? ""),
|
|
76
|
-
"history",
|
|
77
|
-
);
|
|
78
|
-
const normalized = stripLeadingBlankLines(finalizeWhatsAppVisibleText(sanitized));
|
|
79
|
-
return normalized.trim() ? normalized : "";
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function resolveWhatsAppOutboundMediaUrls(
|
|
83
|
-
payload: Pick<WhatsAppOutboundPayloadLike, "mediaUrl" | "mediaUrls">,
|
|
84
|
-
): string[] {
|
|
85
|
-
const primaryMediaUrl = payload.mediaUrl?.trim();
|
|
86
|
-
const mediaUrls = (payload.mediaUrls ? [...payload.mediaUrls] : [])
|
|
87
|
-
.map((entry) => entry.trim())
|
|
88
|
-
.filter((entry): entry is string => Boolean(entry));
|
|
89
|
-
const orderedMediaUrls = [primaryMediaUrl, ...mediaUrls].filter((entry): entry is string =>
|
|
90
|
-
Boolean(entry),
|
|
91
|
-
);
|
|
92
|
-
return Array.from(new Set(orderedMediaUrls));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Keep new WhatsApp outbound-media behavior in this helper so payload, gateway, and auto-reply paths stay aligned.
|
|
96
|
-
export function normalizeWhatsAppOutboundPayload<T extends WhatsAppOutboundPayloadLike>(
|
|
97
|
-
payload: T,
|
|
98
|
-
options?: {
|
|
99
|
-
normalizeText?: (text: string | undefined) => string;
|
|
100
|
-
},
|
|
101
|
-
): NormalizedWhatsAppOutboundPayload<T> {
|
|
102
|
-
const mediaUrls = resolveWhatsAppOutboundMediaUrls(payload);
|
|
103
|
-
const normalizeText = options?.normalizeText ?? normalizeWhatsAppPayloadText;
|
|
104
|
-
return {
|
|
105
|
-
...payload,
|
|
106
|
-
text: normalizeText(payload.text),
|
|
107
|
-
mediaUrl: mediaUrls[0],
|
|
108
|
-
mediaUrls: mediaUrls.length > 0 ? mediaUrls : undefined,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function normalizeWhatsAppLoadedMedia(
|
|
113
|
-
media: WhatsAppLoadedMediaLike,
|
|
114
|
-
mediaUrl?: string,
|
|
115
|
-
): CanonicalWhatsAppLoadedMedia {
|
|
116
|
-
const kind =
|
|
117
|
-
media.kind === "image" || media.kind === "audio" || media.kind === "video"
|
|
118
|
-
? media.kind
|
|
119
|
-
: "document";
|
|
120
|
-
const mimetype =
|
|
121
|
-
kind === "audio" && isWhatsAppNativeVoiceAudio({ contentType: media.contentType, mediaUrl })
|
|
122
|
-
? WHATSAPP_VOICE_MIMETYPE
|
|
123
|
-
: (media.contentType ?? "application/octet-stream");
|
|
124
|
-
const fileName =
|
|
125
|
-
kind === "document"
|
|
126
|
-
? (media.fileName ?? deriveWhatsAppDocumentFileName(mediaUrl) ?? "file")
|
|
127
|
-
: undefined;
|
|
128
|
-
return {
|
|
129
|
-
buffer: media.buffer,
|
|
130
|
-
kind,
|
|
131
|
-
mimetype,
|
|
132
|
-
...(fileName ? { fileName } : {}),
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export async function prepareWhatsAppOutboundMedia(
|
|
137
|
-
media: WhatsAppLoadedMediaLike,
|
|
138
|
-
mediaUrl?: string,
|
|
139
|
-
): Promise<CanonicalWhatsAppLoadedMedia> {
|
|
140
|
-
const normalized = normalizeWhatsAppLoadedMedia(media, mediaUrl);
|
|
141
|
-
if (normalized.kind !== "audio") {
|
|
142
|
-
return normalized;
|
|
143
|
-
}
|
|
144
|
-
if (
|
|
145
|
-
isWhatsAppNativeVoiceAudio({
|
|
146
|
-
contentType: media.contentType,
|
|
147
|
-
fileName: media.fileName,
|
|
148
|
-
mediaUrl,
|
|
149
|
-
})
|
|
150
|
-
) {
|
|
151
|
-
return normalized;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const buffer = await transcodeToWhatsAppVoiceOpus({
|
|
155
|
-
buffer: media.buffer,
|
|
156
|
-
fileName: media.fileName ?? deriveWhatsAppDocumentFileName(mediaUrl) ?? "audio",
|
|
157
|
-
});
|
|
158
|
-
return {
|
|
159
|
-
buffer,
|
|
160
|
-
kind: "audio",
|
|
161
|
-
mimetype: WHATSAPP_VOICE_MIMETYPE,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function normalizeContentType(value: string | undefined): string {
|
|
166
|
-
return value?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function isWhatsAppNativeVoiceAudio(params: {
|
|
170
|
-
contentType?: string;
|
|
171
|
-
fileName?: string;
|
|
172
|
-
mediaUrl?: string;
|
|
173
|
-
}): boolean {
|
|
174
|
-
const contentType = normalizeContentType(params.contentType);
|
|
175
|
-
if (contentType === "audio/ogg" || contentType === "audio/opus") {
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
const fileName = params.fileName ?? deriveWhatsAppDocumentFileName(params.mediaUrl) ?? "";
|
|
179
|
-
const ext = path.extname(fileName).toLowerCase();
|
|
180
|
-
return ext === ".ogg" || ext === ".opus";
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async function transcodeToWhatsAppVoiceOpus(params: {
|
|
184
|
-
buffer: Buffer;
|
|
185
|
-
fileName: string;
|
|
186
|
-
}): Promise<Buffer> {
|
|
187
|
-
const tempRoot = resolvePreferredOpenClawTmpDir();
|
|
188
|
-
await fs.mkdir(tempRoot, { recursive: true, mode: 0o700 });
|
|
189
|
-
const tempDir = await fs.mkdtemp(path.join(tempRoot, "whatsapp-voice-"));
|
|
190
|
-
try {
|
|
191
|
-
const ext = path.extname(params.fileName).toLowerCase();
|
|
192
|
-
const inputExt = ext && ext.length <= 12 ? ext : ".audio";
|
|
193
|
-
const inputPath = path.join(tempDir, `input${inputExt}`);
|
|
194
|
-
const outputPath = path.join(tempDir, WHATSAPP_VOICE_FILE_NAME);
|
|
195
|
-
await fs.writeFile(inputPath, params.buffer, { mode: 0o600 });
|
|
196
|
-
await runFfmpeg([
|
|
197
|
-
"-hide_banner",
|
|
198
|
-
"-loglevel",
|
|
199
|
-
"error",
|
|
200
|
-
"-y",
|
|
201
|
-
"-i",
|
|
202
|
-
inputPath,
|
|
203
|
-
"-vn",
|
|
204
|
-
"-sn",
|
|
205
|
-
"-dn",
|
|
206
|
-
"-t",
|
|
207
|
-
String(MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS),
|
|
208
|
-
"-ar",
|
|
209
|
-
String(WHATSAPP_VOICE_SAMPLE_RATE_HZ),
|
|
210
|
-
"-ac",
|
|
211
|
-
"1",
|
|
212
|
-
"-c:a",
|
|
213
|
-
"libopus",
|
|
214
|
-
"-b:a",
|
|
215
|
-
WHATSAPP_VOICE_BITRATE,
|
|
216
|
-
outputPath,
|
|
217
|
-
]);
|
|
218
|
-
return await fs.readFile(outputPath);
|
|
219
|
-
} finally {
|
|
220
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function deriveWhatsAppDocumentFileName(mediaUrl: string | undefined): string | undefined {
|
|
225
|
-
if (!mediaUrl) {
|
|
226
|
-
return undefined;
|
|
227
|
-
}
|
|
228
|
-
try {
|
|
229
|
-
const parsed = new URL(mediaUrl);
|
|
230
|
-
const fileName = path.posix.basename(parsed.pathname);
|
|
231
|
-
return fileName ? decodeURIComponent(fileName) : undefined;
|
|
232
|
-
} catch {
|
|
233
|
-
const withoutQueryOrFragment = mediaUrl.split(/[?#]/, 1)[0] ?? "";
|
|
234
|
-
const fileName = withoutQueryOrFragment.split(/[\\/]/).pop();
|
|
235
|
-
return fileName || undefined;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function isRetryableWhatsAppOutboundError(error: unknown): boolean {
|
|
240
|
-
return /closed|reset|timed\s*out|disconnect/i.test(formatError(error));
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export async function sendWhatsAppOutboundWithRetry<T>(params: {
|
|
244
|
-
send: () => Promise<T>;
|
|
245
|
-
onRetry?: (params: {
|
|
246
|
-
attempt: number;
|
|
247
|
-
maxAttempts: number;
|
|
248
|
-
backoffMs: number;
|
|
249
|
-
error: unknown;
|
|
250
|
-
errorText: string;
|
|
251
|
-
}) => Promise<void> | void;
|
|
252
|
-
maxAttempts?: number;
|
|
253
|
-
}): Promise<T> {
|
|
254
|
-
const maxAttempts = params.maxAttempts ?? 3;
|
|
255
|
-
let lastError: unknown;
|
|
256
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
257
|
-
try {
|
|
258
|
-
return await params.send();
|
|
259
|
-
} catch (error) {
|
|
260
|
-
lastError = error;
|
|
261
|
-
const errorText = formatError(error);
|
|
262
|
-
const isLastAttempt = attempt === maxAttempts;
|
|
263
|
-
if (!isRetryableWhatsAppOutboundError(error) || isLastAttempt) {
|
|
264
|
-
throw error;
|
|
265
|
-
}
|
|
266
|
-
const backoffMs = 500 * attempt;
|
|
267
|
-
await params.onRetry?.({
|
|
268
|
-
attempt,
|
|
269
|
-
maxAttempts,
|
|
270
|
-
backoffMs,
|
|
271
|
-
error,
|
|
272
|
-
errorText,
|
|
273
|
-
});
|
|
274
|
-
await sleep(backoffMs);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
throw lastError;
|
|
278
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
|
|
2
|
-
|
|
3
|
-
export async function loadOutboundMediaFromUrl(
|
|
4
|
-
mediaUrl: string,
|
|
5
|
-
options: {
|
|
6
|
-
maxBytes?: number;
|
|
7
|
-
mediaAccess?: {
|
|
8
|
-
localRoots?: readonly string[];
|
|
9
|
-
readFile?: (filePath: string) => Promise<Buffer>;
|
|
10
|
-
};
|
|
11
|
-
mediaLocalRoots?: readonly string[];
|
|
12
|
-
mediaReadFile?: (filePath: string) => Promise<Buffer>;
|
|
13
|
-
} = {},
|
|
14
|
-
) {
|
|
15
|
-
const readFile = options.mediaAccess?.readFile ?? options.mediaReadFile;
|
|
16
|
-
const localRoots =
|
|
17
|
-
options.mediaAccess?.localRoots?.length && options.mediaAccess.localRoots.length > 0
|
|
18
|
-
? options.mediaAccess.localRoots
|
|
19
|
-
: options.mediaLocalRoots && options.mediaLocalRoots.length > 0
|
|
20
|
-
? options.mediaLocalRoots
|
|
21
|
-
: undefined;
|
|
22
|
-
return await loadWebMedia(
|
|
23
|
-
mediaUrl,
|
|
24
|
-
readFile
|
|
25
|
-
? {
|
|
26
|
-
...(options.maxBytes !== undefined ? { maxBytes: options.maxBytes } : {}),
|
|
27
|
-
localRoots: "any",
|
|
28
|
-
readFile,
|
|
29
|
-
hostReadCapability: true,
|
|
30
|
-
}
|
|
31
|
-
: {
|
|
32
|
-
...(options.maxBytes !== undefined ? { maxBytes: options.maxBytes } : {}),
|
|
33
|
-
...(localRoots ? { localRoots } : {}),
|
|
34
|
-
},
|
|
35
|
-
);
|
|
36
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
installChannelOutboundPayloadContractSuite,
|
|
3
|
-
primeChannelOutboundSendMock,
|
|
4
|
-
type OutboundPayloadHarnessParams,
|
|
5
|
-
} from "openclaw/plugin-sdk/channel-contract-testing";
|
|
6
|
-
import { describe, expect, it, vi } from "vitest";
|
|
7
|
-
import { whatsappOutbound } from "./outbound-adapter.js";
|
|
8
|
-
|
|
9
|
-
function createWhatsAppHarness(params: OutboundPayloadHarnessParams) {
|
|
10
|
-
const sendWhatsApp = vi.fn();
|
|
11
|
-
primeChannelOutboundSendMock(sendWhatsApp, { messageId: "wa-1" }, params.sendResults);
|
|
12
|
-
const ctx = {
|
|
13
|
-
cfg: {},
|
|
14
|
-
to: "5511999999999@c.us",
|
|
15
|
-
text: "",
|
|
16
|
-
payload: params.payload,
|
|
17
|
-
deps: {
|
|
18
|
-
whatsapp: sendWhatsApp,
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
return {
|
|
22
|
-
run: async () => await whatsappOutbound.sendPayload!(ctx),
|
|
23
|
-
sendMock: sendWhatsApp,
|
|
24
|
-
to: ctx.to,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe("WhatsApp outbound payload contract", () => {
|
|
29
|
-
installChannelOutboundPayloadContractSuite({
|
|
30
|
-
channel: "whatsapp",
|
|
31
|
-
chunking: { mode: "split", longTextLength: 5000, maxChunkLength: 4000 },
|
|
32
|
-
createHarness: createWhatsAppHarness,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("normalizes blank mediaUrls before contract delivery", async () => {
|
|
36
|
-
const sendWhatsApp = vi.fn();
|
|
37
|
-
primeChannelOutboundSendMock(sendWhatsApp, { messageId: "wa-1" });
|
|
38
|
-
|
|
39
|
-
await whatsappOutbound.sendPayload!({
|
|
40
|
-
cfg: {},
|
|
41
|
-
to: "5511999999999@c.us",
|
|
42
|
-
text: "",
|
|
43
|
-
payload: {
|
|
44
|
-
text: "\n\ncaption",
|
|
45
|
-
mediaUrls: [" ", " /tmp/voice.ogg "],
|
|
46
|
-
},
|
|
47
|
-
deps: {
|
|
48
|
-
whatsapp: sendWhatsApp,
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
expect(sendWhatsApp).toHaveBeenCalledTimes(1);
|
|
53
|
-
expect(sendWhatsApp).toHaveBeenCalledWith(
|
|
54
|
-
"5511999999999@c.us",
|
|
55
|
-
"caption",
|
|
56
|
-
expect.objectContaining({
|
|
57
|
-
mediaUrl: "/tmp/voice.ogg",
|
|
58
|
-
}),
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS = ["sendWhatsApp"] as const;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
2
|
-
|
|
3
|
-
export function createWhatsAppPollFixture() {
|
|
4
|
-
const cfg = { marker: "resolved-cfg" } as OpenClawConfig;
|
|
5
|
-
const poll = {
|
|
6
|
-
question: "Lunch?",
|
|
7
|
-
options: ["Pizza", "Sushi"],
|
|
8
|
-
maxSelections: 1,
|
|
9
|
-
};
|
|
10
|
-
return {
|
|
11
|
-
cfg,
|
|
12
|
-
poll,
|
|
13
|
-
to: "+1555",
|
|
14
|
-
accountId: "work",
|
|
15
|
-
};
|
|
16
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { vi, type Mock } from "vitest";
|
|
2
|
-
|
|
3
|
-
export type AsyncMock<TArgs extends unknown[] = unknown[], TResult = unknown> = {
|
|
4
|
-
(...args: TArgs): Promise<TResult>;
|
|
5
|
-
mockReset: () => AsyncMock<TArgs, TResult>;
|
|
6
|
-
mockResolvedValue: (value: TResult) => AsyncMock<TArgs, TResult>;
|
|
7
|
-
mockResolvedValueOnce: (value: TResult) => AsyncMock<TArgs, TResult>;
|
|
8
|
-
};
|
|
9
|
-
type UnknownMock = Mock<(...args: unknown[]) => unknown>;
|
|
10
|
-
|
|
11
|
-
export const loadConfigMock: UnknownMock = vi.fn();
|
|
12
|
-
export const readAllowFromStoreMock = vi.fn() as AsyncMock;
|
|
13
|
-
export const upsertPairingRequestMock = vi.fn() as AsyncMock;
|
|
14
|
-
|
|
15
|
-
export function resetPairingSecurityMocks(config: Record<string, unknown>) {
|
|
16
|
-
loadConfigMock.mockReset().mockReturnValue(config);
|
|
17
|
-
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
|
|
18
|
-
upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
vi.mock("openclaw/plugin-sdk/runtime-config-snapshot", async () => {
|
|
22
|
-
const actual = await vi.importActual<
|
|
23
|
-
typeof import("openclaw/plugin-sdk/runtime-config-snapshot")
|
|
24
|
-
>("openclaw/plugin-sdk/runtime-config-snapshot");
|
|
25
|
-
return {
|
|
26
|
-
...actual,
|
|
27
|
-
getRuntimeConfig: (...args: unknown[]) => loadConfigMock(...args),
|
|
28
|
-
};
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
vi.mock("openclaw/plugin-sdk/conversation-runtime", () => {
|
|
32
|
-
return {
|
|
33
|
-
upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
vi.mock("openclaw/plugin-sdk/security-runtime", async () => {
|
|
38
|
-
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/security-runtime")>(
|
|
39
|
-
"openclaw/plugin-sdk/security-runtime",
|
|
40
|
-
);
|
|
41
|
-
return {
|
|
42
|
-
...actual,
|
|
43
|
-
readStoreAllowFromForDmPolicy: async (params: {
|
|
44
|
-
provider: string;
|
|
45
|
-
accountId: string;
|
|
46
|
-
dmPolicy?: string | null;
|
|
47
|
-
shouldRead?: boolean | null;
|
|
48
|
-
}) => {
|
|
49
|
-
if (params.shouldRead === false || params.dmPolicy === "allowlist") {
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
return (await readAllowFromStoreMock(params.provider, params.accountId)) as string[];
|
|
54
|
-
} catch {
|
|
55
|
-
return [];
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
});
|
package/src/qr-image.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { renderQrPngBase64, renderQrPngDataUrl } from "openclaw/plugin-sdk/media-runtime";
|
package/src/qr-terminal.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { renderQrTerminal } from "openclaw/plugin-sdk/media-runtime";
|