@openclaw/whatsapp 2026.5.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.
Files changed (302) hide show
  1. package/dist/access-control-BQ3tCkH8.js +288 -0
  2. package/dist/account-config-CNQ33oav.js +47 -0
  3. package/dist/account-ids-DG18xddX.js +5 -0
  4. package/dist/accounts-BDqgpFEB.js +116 -0
  5. package/dist/action-runtime-DTGNgG-i.js +74 -0
  6. package/dist/action-runtime-api.js +2 -0
  7. package/dist/action-runtime.runtime.js +2 -0
  8. package/dist/active-listener-N7GFKsuN.js +11 -0
  9. package/dist/agent-tools-login-DpRxLNRB.js +89 -0
  10. package/dist/api.js +148 -0
  11. package/dist/audio-preflight.runtime-Bcdsl_ri.js +7 -0
  12. package/dist/auth-presence.js +32 -0
  13. package/dist/auth-store-BjCb50Ry.js +381 -0
  14. package/dist/channel-DQ-5Xoj9.js +542 -0
  15. package/dist/channel-config-api.js +2 -0
  16. package/dist/channel-plugin-api.js +2 -0
  17. package/dist/channel-react-action-Df20qAy9.js +42 -0
  18. package/dist/channel.runtime-DKXRnA8x.js +55 -0
  19. package/dist/channel.setup-DOHR2BGp.js +20 -0
  20. package/dist/command-policy-O8sRnqUV.js +8 -0
  21. package/dist/config-schema-Dmo66Ubb.js +27 -0
  22. package/dist/connection-controller-BiHAsTM8.js +596 -0
  23. package/dist/connection-controller-registry-D_iIGz5m.js +22 -0
  24. package/dist/constants-DLGCIbfl.js +4 -0
  25. package/dist/constants.js +2 -0
  26. package/dist/contract-api.js +19 -0
  27. package/dist/creds-files-_2V8jzQa.js +19 -0
  28. package/dist/directory-config-DVs4ffXu.js +37 -0
  29. package/dist/directory-contract-api.js +2 -0
  30. package/dist/doctor-A9tXm9Om.js +48 -0
  31. package/dist/doctor-contract-api.js +10 -0
  32. package/dist/group-session-contract-B8QBn0I2.js +15 -0
  33. package/dist/identity-QHAlV7Fu.js +75 -0
  34. package/dist/index.js +18 -0
  35. package/dist/legacy-session-surface-api.js +8 -0
  36. package/dist/legacy-state-migrations-api.js +2 -0
  37. package/dist/light-runtime-api.js +5 -0
  38. package/dist/login-DymdrKca.js +52 -0
  39. package/dist/login-qr-DIS0Iv-7.js +378 -0
  40. package/dist/login-qr-api.js +2 -0
  41. package/dist/login-qr-runtime.js +16 -0
  42. package/dist/monitor-C4Fhg_lD.js +3349 -0
  43. package/dist/normalize-CWYiGkXw.js +2 -0
  44. package/dist/normalize-target-BPVHNsM5.js +74 -0
  45. package/dist/outbound-adapter-BssVnZdz.js +29 -0
  46. package/dist/outbound-base-Bq1UYEK5.js +119 -0
  47. package/dist/outbound-media-contract-CGss_QHy.js +163 -0
  48. package/dist/outbound-payload-test-api.js +2 -0
  49. package/dist/quoted-message-TENQzyxV.js +108 -0
  50. package/dist/reaction-level-CRVEVAfi.js +16 -0
  51. package/dist/reply-resolver.runtime-BRCgoO4C.js +2 -0
  52. package/dist/resolve-outbound-target-CN-Wo6IY.js +39 -0
  53. package/dist/rolldown-runtime-RkAeH_Qm.js +27 -0
  54. package/dist/runtime-6fZ67abR.js +8 -0
  55. package/dist/runtime-api.js +15 -0
  56. package/dist/runtime-group-policy-Gp1lQ0at.js +11 -0
  57. package/dist/secret-contract-api.js +5 -0
  58. package/dist/security-contract-DqH8JDX6.js +26 -0
  59. package/dist/security-contract-api.js +2 -0
  60. package/dist/send-CIyLFxdZ.js +234 -0
  61. package/dist/session-contract-B0efQf9P.js +27 -0
  62. package/dist/session-errors-CSuKZPub.js +63 -0
  63. package/dist/session.runtime-C1eSE_KK.js +13 -0
  64. package/dist/setup-core-DZKvpzbH.js +46 -0
  65. package/dist/setup-entry.js +23 -0
  66. package/dist/setup-plugin-api.js +2 -0
  67. package/dist/setup-surface-ieFnIW4g.js +344 -0
  68. package/dist/shared-D_wzSVar.js +307 -0
  69. package/dist/state-migrations-DnCdvoYD.js +39 -0
  70. package/dist/targets.js +2 -0
  71. package/dist/test-api.js +3 -0
  72. package/dist/text-runtime-C1zNiZeO.js +116 -0
  73. package/package.json +14 -6
  74. package/action-runtime-api.ts +0 -1
  75. package/action-runtime.runtime.ts +0 -1
  76. package/api.ts +0 -213
  77. package/auth-presence.ts +0 -80
  78. package/channel-config-api.ts +0 -1
  79. package/channel-plugin-api.ts +0 -3
  80. package/config-api.ts +0 -4
  81. package/constants.ts +0 -1
  82. package/contract-api.ts +0 -29
  83. package/directory-contract-api.ts +0 -4
  84. package/doctor-contract-api.ts +0 -8
  85. package/index.test.ts +0 -13
  86. package/index.ts +0 -16
  87. package/legacy-session-surface-api.ts +0 -6
  88. package/legacy-state-migrations-api.ts +0 -1
  89. package/light-runtime-api.ts +0 -12
  90. package/login-qr-api.ts +0 -1
  91. package/login-qr-runtime.ts +0 -23
  92. package/outbound-payload-test-api.ts +0 -1
  93. package/runtime-api.ts +0 -84
  94. package/secret-contract-api.ts +0 -4
  95. package/security-contract-api.ts +0 -4
  96. package/setup-entry.test.ts +0 -21
  97. package/setup-entry.ts +0 -21
  98. package/setup-plugin-api.ts +0 -3
  99. package/src/account-config.ts +0 -77
  100. package/src/account-ids.ts +0 -13
  101. package/src/account-types.ts +0 -5
  102. package/src/accounts.test.ts +0 -182
  103. package/src/accounts.ts +0 -176
  104. package/src/accounts.whatsapp-auth.test.ts +0 -59
  105. package/src/action-runtime-target-auth.ts +0 -27
  106. package/src/action-runtime.test.ts +0 -330
  107. package/src/action-runtime.ts +0 -76
  108. package/src/active-listener.test.ts +0 -65
  109. package/src/active-listener.ts +0 -17
  110. package/src/agent-tools-login.test.ts +0 -81
  111. package/src/agent-tools-login.ts +0 -113
  112. package/src/approval-auth.test.ts +0 -24
  113. package/src/approval-auth.ts +0 -27
  114. package/src/auth-store.runtime.ts +0 -1
  115. package/src/auth-store.test.ts +0 -311
  116. package/src/auth-store.ts +0 -502
  117. package/src/auto-reply/config.runtime.ts +0 -16
  118. package/src/auto-reply/constants.ts +0 -1
  119. package/src/auto-reply/deliver-reply.test.ts +0 -843
  120. package/src/auto-reply/deliver-reply.ts +0 -279
  121. package/src/auto-reply/loggers.ts +0 -6
  122. package/src/auto-reply/mentions.ts +0 -131
  123. package/src/auto-reply/monitor/ack-reaction.test.ts +0 -170
  124. package/src/auto-reply/monitor/ack-reaction.ts +0 -99
  125. package/src/auto-reply/monitor/audio-preflight.runtime.ts +0 -9
  126. package/src/auto-reply/monitor/broadcast.ts +0 -153
  127. package/src/auto-reply/monitor/commands.ts +0 -19
  128. package/src/auto-reply/monitor/echo.ts +0 -64
  129. package/src/auto-reply/monitor/group-activation.runtime.ts +0 -1
  130. package/src/auto-reply/monitor/group-activation.test.ts +0 -189
  131. package/src/auto-reply/monitor/group-activation.ts +0 -73
  132. package/src/auto-reply/monitor/group-gating.audio-preflight.test.ts +0 -103
  133. package/src/auto-reply/monitor/group-gating.runtime.ts +0 -8
  134. package/src/auto-reply/monitor/group-gating.ts +0 -217
  135. package/src/auto-reply/monitor/group-members.test.ts +0 -56
  136. package/src/auto-reply/monitor/group-members.ts +0 -65
  137. package/src/auto-reply/monitor/inbound-context.test.ts +0 -97
  138. package/src/auto-reply/monitor/inbound-context.ts +0 -92
  139. package/src/auto-reply/monitor/inbound-dispatch.runtime.ts +0 -21
  140. package/src/auto-reply/monitor/inbound-dispatch.test.ts +0 -895
  141. package/src/auto-reply/monitor/inbound-dispatch.ts +0 -422
  142. package/src/auto-reply/monitor/last-route.test.ts +0 -37
  143. package/src/auto-reply/monitor/last-route.ts +0 -61
  144. package/src/auto-reply/monitor/message-line.runtime.ts +0 -38
  145. package/src/auto-reply/monitor/message-line.ts +0 -54
  146. package/src/auto-reply/monitor/on-message.audio-preflight.test.ts +0 -354
  147. package/src/auto-reply/monitor/on-message.ts +0 -291
  148. package/src/auto-reply/monitor/peer.ts +0 -17
  149. package/src/auto-reply/monitor/process-message.audio-preflight.test.ts +0 -420
  150. package/src/auto-reply/monitor/process-message.test.ts +0 -355
  151. package/src/auto-reply/monitor/process-message.ts +0 -531
  152. package/src/auto-reply/monitor/runtime-api.ts +0 -30
  153. package/src/auto-reply/monitor-state.test.ts +0 -64
  154. package/src/auto-reply/monitor-state.ts +0 -106
  155. package/src/auto-reply/monitor.ts +0 -600
  156. package/src/auto-reply/reply-resolver.runtime.ts +0 -1
  157. package/src/auto-reply/types.ts +0 -48
  158. package/src/auto-reply/util.ts +0 -63
  159. package/src/auto-reply/web-auto-reply-monitor.test.ts +0 -687
  160. package/src/auto-reply/web-auto-reply-utils.test.ts +0 -392
  161. package/src/auto-reply.broadcast-groups.combined.test.ts +0 -246
  162. package/src/auto-reply.broadcast-groups.test-harness.ts +0 -47
  163. package/src/auto-reply.impl.ts +0 -6
  164. package/src/auto-reply.test-harness.ts +0 -421
  165. package/src/auto-reply.ts +0 -1
  166. package/src/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.test.ts +0 -427
  167. package/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts +0 -908
  168. package/src/auto-reply.web-auto-reply.last-route.test.ts +0 -184
  169. package/src/channel-actions.runtime.ts +0 -7
  170. package/src/channel-actions.test.ts +0 -216
  171. package/src/channel-actions.ts +0 -84
  172. package/src/channel-outbound.test.ts +0 -131
  173. package/src/channel-outbound.ts +0 -36
  174. package/src/channel-react-action.runtime.ts +0 -7
  175. package/src/channel-react-action.test.ts +0 -294
  176. package/src/channel-react-action.ts +0 -84
  177. package/src/channel.runtime.ts +0 -117
  178. package/src/channel.setup.test.ts +0 -461
  179. package/src/channel.setup.ts +0 -28
  180. package/src/channel.ts +0 -339
  181. package/src/command-policy.ts +0 -7
  182. package/src/config-accessors.test.ts +0 -34
  183. package/src/config-accessors.ts +0 -22
  184. package/src/config-schema.test.ts +0 -121
  185. package/src/config-schema.ts +0 -6
  186. package/src/config-ui-hints.ts +0 -24
  187. package/src/connection-controller-registry.test.ts +0 -27
  188. package/src/connection-controller-registry.ts +0 -49
  189. package/src/connection-controller.test.ts +0 -282
  190. package/src/connection-controller.ts +0 -679
  191. package/src/creds-files.ts +0 -19
  192. package/src/creds-persistence.ts +0 -102
  193. package/src/directory-config.test.ts +0 -60
  194. package/src/directory-config.ts +0 -40
  195. package/src/directory-contract.test.ts +0 -56
  196. package/src/doctor-contract.ts +0 -11
  197. package/src/doctor.test.ts +0 -74
  198. package/src/doctor.ts +0 -56
  199. package/src/group-intro.ts +0 -15
  200. package/src/group-policy.test.ts +0 -36
  201. package/src/group-policy.ts +0 -40
  202. package/src/group-session-contract.ts +0 -20
  203. package/src/group-session-key.test.ts +0 -53
  204. package/src/group-session-key.ts +0 -41
  205. package/src/heartbeat.ts +0 -34
  206. package/src/identity.ts +0 -164
  207. package/src/inbound/access-control.test-harness.ts +0 -37
  208. package/src/inbound/access-control.test.ts +0 -375
  209. package/src/inbound/access-control.ts +0 -237
  210. package/src/inbound/dedupe.ts +0 -132
  211. package/src/inbound/extract.test.ts +0 -282
  212. package/src/inbound/extract.ts +0 -488
  213. package/src/inbound/lifecycle.ts +0 -39
  214. package/src/inbound/media.node.test.ts +0 -83
  215. package/src/inbound/media.ts +0 -102
  216. package/src/inbound/monitor.ts +0 -890
  217. package/src/inbound/runtime-api.ts +0 -7
  218. package/src/inbound/save-media.runtime.ts +0 -1
  219. package/src/inbound/send-api.test.ts +0 -319
  220. package/src/inbound/send-api.ts +0 -145
  221. package/src/inbound/send-result.ts +0 -61
  222. package/src/inbound/types.ts +0 -106
  223. package/src/inbound-context.contract.test.ts +0 -32
  224. package/src/inbound-policy.ts +0 -231
  225. package/src/inbound.media.test.ts +0 -320
  226. package/src/inbound.test.ts +0 -290
  227. package/src/inbound.ts +0 -9
  228. package/src/login-qr.test.ts +0 -483
  229. package/src/login-qr.ts +0 -542
  230. package/src/login.coverage.test.ts +0 -133
  231. package/src/login.test.ts +0 -89
  232. package/src/login.ts +0 -70
  233. package/src/logout.test.ts +0 -162
  234. package/src/media.test.ts +0 -480
  235. package/src/media.ts +0 -10
  236. package/src/monitor-inbox.allows-messages-from-senders-allowfrom-list.test-support.ts +0 -400
  237. package/src/monitor-inbox.append-upsert.test-support.ts +0 -133
  238. package/src/monitor-inbox.behavior.test.ts +0 -5
  239. package/src/monitor-inbox.blocks-messages-from-unauthorized-senders-not-allowfrom.test-support.ts +0 -362
  240. package/src/monitor-inbox.captures-media-path-image-messages.test-support.ts +0 -306
  241. package/src/monitor-inbox.streams-inbound-messages.test-support.ts +0 -729
  242. package/src/monitor-inbox.test-harness.ts +0 -304
  243. package/src/normalize-target.ts +0 -123
  244. package/src/normalize.ts +0 -7
  245. package/src/outbound-adapter.poll.test.ts +0 -62
  246. package/src/outbound-adapter.sendpayload.test.ts +0 -195
  247. package/src/outbound-adapter.ts +0 -36
  248. package/src/outbound-base.test.ts +0 -560
  249. package/src/outbound-base.ts +0 -244
  250. package/src/outbound-media-contract.ts +0 -278
  251. package/src/outbound-media.runtime.ts +0 -36
  252. package/src/outbound-payload.contract.test.ts +0 -61
  253. package/src/outbound-send-deps.ts +0 -1
  254. package/src/outbound-test-support.ts +0 -16
  255. package/src/pairing-security.test-harness.ts +0 -59
  256. package/src/qr-image.ts +0 -1
  257. package/src/qr-terminal.ts +0 -1
  258. package/src/quoted-message.test.ts +0 -83
  259. package/src/quoted-message.ts +0 -184
  260. package/src/reaction-level.test.ts +0 -94
  261. package/src/reaction-level.ts +0 -21
  262. package/src/reconnect.test.ts +0 -51
  263. package/src/reconnect.ts +0 -55
  264. package/src/resolve-outbound-target.test.ts +0 -288
  265. package/src/resolve-outbound-target.ts +0 -58
  266. package/src/resolve-target.test.ts +0 -120
  267. package/src/runtime-api.ts +0 -59
  268. package/src/runtime-group-policy.ts +0 -16
  269. package/src/runtime.ts +0 -9
  270. package/src/security-contract.ts +0 -47
  271. package/src/security-fix.ts +0 -71
  272. package/src/send.test.ts +0 -534
  273. package/src/send.ts +0 -292
  274. package/src/session-contract.test.ts +0 -31
  275. package/src/session-contract.ts +0 -43
  276. package/src/session-errors.ts +0 -125
  277. package/src/session-route.test.ts +0 -41
  278. package/src/session-route.ts +0 -32
  279. package/src/session.runtime.ts +0 -8
  280. package/src/session.test.ts +0 -559
  281. package/src/session.ts +0 -330
  282. package/src/setup-core.ts +0 -52
  283. package/src/setup-finalize.ts +0 -446
  284. package/src/setup-surface.test.ts +0 -367
  285. package/src/setup-surface.ts +0 -69
  286. package/src/setup-test-helpers.ts +0 -216
  287. package/src/shared.ts +0 -291
  288. package/src/socket-timing.test.ts +0 -49
  289. package/src/socket-timing.ts +0 -38
  290. package/src/state-migrations.ts +0 -54
  291. package/src/status-issues.test.ts +0 -137
  292. package/src/status-issues.ts +0 -185
  293. package/src/system-prompt.test.ts +0 -199
  294. package/src/system-prompt.ts +0 -31
  295. package/src/targets-runtime.ts +0 -180
  296. package/src/test-helpers.ts +0 -690
  297. package/src/text-runtime.test.ts +0 -162
  298. package/src/text-runtime.ts +0 -11
  299. package/src/vcard.ts +0 -84
  300. package/targets.ts +0 -5
  301. package/test-api.ts +0 -2
  302. package/tsconfig.json +0 -16
@@ -1,843 +0,0 @@
1
- import fsSync from "node:fs";
2
- import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
3
- import { sleep } from "openclaw/plugin-sdk/text-runtime";
4
- import { beforeAll, describe, expect, it, vi } from "vitest";
5
- import { loadWebMedia } from "../media.js";
6
- import { cacheInboundMessageMeta } from "../quoted-message.js";
7
- import type { WebInboundMsg } from "./types.js";
8
-
9
- const hoisted = vi.hoisted(() => ({
10
- runFfmpeg: vi.fn(),
11
- }));
12
-
13
- vi.mock("openclaw/plugin-sdk/media-runtime", async () => {
14
- const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/media-runtime")>(
15
- "openclaw/plugin-sdk/media-runtime",
16
- );
17
- return {
18
- ...actual,
19
- runFfmpeg: hoisted.runFfmpeg,
20
- };
21
- });
22
-
23
- vi.mock("openclaw/plugin-sdk/runtime-env", async () => {
24
- const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/runtime-env")>(
25
- "openclaw/plugin-sdk/runtime-env",
26
- );
27
- return {
28
- ...actual,
29
- shouldLogVerbose: vi.fn(() => true),
30
- logVerbose: vi.fn(),
31
- };
32
- });
33
-
34
- vi.mock("openclaw/plugin-sdk/text-runtime", async () => {
35
- const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/text-runtime")>(
36
- "openclaw/plugin-sdk/text-runtime",
37
- );
38
- return {
39
- ...actual,
40
- sleep: vi.fn(async () => {}),
41
- };
42
- });
43
-
44
- vi.mock("../media.js", () => ({
45
- loadWebMedia: vi.fn(),
46
- }));
47
-
48
- let deliverWebReply: typeof import("./deliver-reply.js").deliverWebReply;
49
- let whatsappOutbound: typeof import("../outbound-adapter.js").whatsappOutbound;
50
-
51
- function acceptedSendResult(kind: "media" | "text", id: string) {
52
- return {
53
- kind,
54
- messageId: id,
55
- messageIds: [id],
56
- keys: [{ id }],
57
- providerAccepted: true,
58
- };
59
- }
60
-
61
- function unacceptedSendResult(kind: "media" | "text") {
62
- return {
63
- kind,
64
- messageId: "unknown",
65
- messageIds: [],
66
- keys: [],
67
- providerAccepted: false,
68
- };
69
- }
70
-
71
- function makeMsg(): WebInboundMsg {
72
- return {
73
- from: "+10000000000",
74
- to: "+20000000000",
75
- accountId: "work",
76
- chatId: "15551234567@s.whatsapp.net",
77
- chatType: "group",
78
- id: "msg-1",
79
- body: "latest batch body",
80
- senderJid: "222@s.whatsapp.net",
81
- reply: vi.fn(async () => acceptedSendResult("text", "reply-sent-1")),
82
- sendMedia: vi.fn(async () => acceptedSendResult("media", "media-sent-1")),
83
- } as unknown as WebInboundMsg;
84
- }
85
-
86
- function mockLoadedImageMedia() {
87
- (
88
- loadWebMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void }
89
- ).mockResolvedValueOnce({
90
- buffer: Buffer.from("img"),
91
- contentType: "image/jpeg",
92
- kind: "image",
93
- });
94
- }
95
-
96
- function mockFirstSendMediaFailure(msg: WebInboundMsg, message: string) {
97
- (
98
- msg.sendMedia as unknown as { mockRejectedValueOnce: (v: unknown) => void }
99
- ).mockRejectedValueOnce(new Error(message));
100
- }
101
-
102
- function mockFirstReplyFailure(msg: WebInboundMsg, message: string) {
103
- (msg.reply as unknown as { mockRejectedValueOnce: (v: unknown) => void }).mockRejectedValueOnce(
104
- new Error(message),
105
- );
106
- }
107
-
108
- function mockFirstReplyFailureWithWrappedError(msg: WebInboundMsg, message: string) {
109
- (msg.reply as unknown as { mockRejectedValueOnce: (v: unknown) => void }).mockRejectedValueOnce({
110
- error: { message },
111
- });
112
- }
113
-
114
- function expectFirstSendMediaPayload(msg: WebInboundMsg) {
115
- const payload = vi.mocked(msg.sendMedia).mock.calls[0]?.[0];
116
- expect(payload).toBeDefined();
117
- return payload;
118
- }
119
-
120
- function mockSecondReplySuccess(msg: WebInboundMsg) {
121
- (msg.reply as unknown as { mockResolvedValueOnce: (v: unknown) => void }).mockResolvedValueOnce(
122
- acceptedSendResult("text", "reply-retry-2"),
123
- );
124
- }
125
-
126
- const replyLogger = {
127
- info: vi.fn(),
128
- warn: vi.fn(),
129
- };
130
-
131
- async function expectReplySuppressed(replyResult: { text: string; isReasoning?: boolean }) {
132
- const msg = makeMsg();
133
- await deliverWebReply({
134
- replyResult,
135
- msg,
136
- maxMediaBytes: 1024 * 1024,
137
- textLimit: 200,
138
- replyLogger,
139
- skipLog: true,
140
- });
141
- expect(msg.reply).not.toHaveBeenCalled();
142
- expect(msg.sendMedia).not.toHaveBeenCalled();
143
- }
144
-
145
- describe("deliverWebReply", () => {
146
- beforeAll(async () => {
147
- ({ deliverWebReply } = await import("./deliver-reply.js"));
148
- ({ whatsappOutbound } = await import("../outbound-adapter.js"));
149
- });
150
-
151
- it("suppresses payloads flagged as reasoning", async () => {
152
- await expectReplySuppressed({ text: "Reasoning:\n_hidden_", isReasoning: true });
153
- });
154
-
155
- it("suppresses payloads that start with reasoning prefix text", async () => {
156
- await expectReplySuppressed({ text: " \n Reasoning:\n_hidden_" });
157
- });
158
-
159
- it("suppresses payloads that start with a quoted reasoning prefix", async () => {
160
- await expectReplySuppressed({ text: " > Reasoning:\n> _hidden_" });
161
- });
162
-
163
- it("does not suppress messages that mention Reasoning: mid-text", async () => {
164
- const msg = makeMsg();
165
-
166
- await deliverWebReply({
167
- replyResult: { text: "Intro line\nReasoning: appears in content but is not a prefix" },
168
- msg,
169
- maxMediaBytes: 1024 * 1024,
170
- textLimit: 200,
171
- replyLogger,
172
- skipLog: true,
173
- });
174
-
175
- expect(msg.reply).toHaveBeenCalledTimes(1);
176
- expect(msg.reply).toHaveBeenCalledWith(
177
- "Intro line\nReasoning: appears in content but is not a prefix",
178
- undefined,
179
- );
180
- });
181
-
182
- it("sends chunked text replies and logs a summary", async () => {
183
- const msg = makeMsg();
184
-
185
- const delivery = await deliverWebReply({
186
- replyResult: { text: "aaaaaa" },
187
- msg,
188
- maxMediaBytes: 1024 * 1024,
189
- textLimit: 3,
190
- replyLogger,
191
- skipLog: true,
192
- });
193
-
194
- expect(msg.reply).toHaveBeenCalledTimes(2);
195
- expect(msg.reply).toHaveBeenNthCalledWith(1, "aaa", undefined);
196
- expect(msg.reply).toHaveBeenNthCalledWith(2, "aaa", undefined);
197
- expect(replyLogger.info).toHaveBeenCalledWith(expect.any(Object), "auto-reply sent (text)");
198
- expect(delivery.providerAccepted).toBe(true);
199
- expect(delivery.messageIds).toEqual(["reply-sent-1"]);
200
- });
201
-
202
- it("reports text replies that Baileys did not accept", async () => {
203
- const msg = makeMsg();
204
- vi.mocked(msg.reply).mockResolvedValueOnce(unacceptedSendResult("text"));
205
-
206
- const delivery = await deliverWebReply({
207
- replyResult: { text: "hello" },
208
- msg,
209
- maxMediaBytes: 1024 * 1024,
210
- textLimit: 200,
211
- replyLogger,
212
- skipLog: true,
213
- });
214
-
215
- expect(msg.reply).toHaveBeenCalledTimes(1);
216
- expect(delivery).toMatchObject({
217
- messageIds: [],
218
- providerAccepted: false,
219
- });
220
- expect(replyLogger.warn).toHaveBeenCalledWith(
221
- expect.any(Object),
222
- "auto-reply text was not accepted by WhatsApp provider",
223
- );
224
- });
225
-
226
- it("strips raw XML tool-call blocks before WhatsApp text delivery", async () => {
227
- const msg = makeMsg();
228
-
229
- await deliverWebReply({
230
- replyResult: {
231
- text: 'Before\n<function_calls><invoke name="web_search"><parameter name="query">x</parameter></invoke></function_calls>\nAfter',
232
- },
233
- msg,
234
- maxMediaBytes: 1024 * 1024,
235
- textLimit: 4000,
236
- replyLogger,
237
- skipLog: true,
238
- });
239
-
240
- expect(msg.reply).toHaveBeenCalledTimes(1);
241
- const sentText = vi.mocked(msg.reply).mock.calls[0]?.[0];
242
- expect(sentText).not.toContain("function_calls");
243
- expect(sentText).not.toContain("invoke");
244
- expect(sentText).toContain("Before");
245
- expect(sentText).toContain("After");
246
- });
247
-
248
- it("uses the same final sanitizer stack for auto-reply text delivery", async () => {
249
- const msg = makeMsg();
250
-
251
- await deliverWebReply({
252
- replyResult: {
253
- text: [
254
- "Before",
255
- "<function_calls>",
256
- ' <invoke name="send_message">',
257
- ' <parameter name="text"><b>hidden</b></parameter>',
258
- " </invoke>",
259
- "</function_calls>",
260
- "<div>After</div>",
261
- ].join("\n"),
262
- },
263
- msg,
264
- maxMediaBytes: 1024 * 1024,
265
- textLimit: 4000,
266
- replyLogger,
267
- skipLog: true,
268
- });
269
-
270
- expect(msg.reply).toHaveBeenCalledTimes(1);
271
- expect(vi.mocked(msg.reply).mock.calls[0]?.[0]).toBe("Before\n\nAfter\n");
272
- });
273
-
274
- it("strips legacy uppercase TOOL_CALL text before WhatsApp text delivery", async () => {
275
- const msg = makeMsg();
276
-
277
- await deliverWebReply({
278
- replyResult: {
279
- text: [
280
- "Before",
281
- '[TOOL_CALL]{tool => "web_search", args => {"query":"NET stock price"}}[/TOOL_CALL]',
282
- "After",
283
- ].join("\n"),
284
- },
285
- msg,
286
- maxMediaBytes: 1024 * 1024,
287
- textLimit: 4000,
288
- replyLogger,
289
- skipLog: true,
290
- });
291
-
292
- expect(msg.reply).toHaveBeenCalledTimes(1);
293
- expect(vi.mocked(msg.reply).mock.calls[0]?.[0]).toBe("Before\n\nAfter");
294
- });
295
-
296
- it("keeps quote threading on every text chunk for a threaded reply", async () => {
297
- const msg = makeMsg();
298
- cacheInboundMessageMeta("work", "15551234567@s.whatsapp.net", "reply-1", {
299
- participant: "111@s.whatsapp.net",
300
- body: "quoted body",
301
- fromMe: true,
302
- });
303
-
304
- await deliverWebReply({
305
- replyResult: { text: "aaaaaa", replyToId: "reply-1" },
306
- msg,
307
- maxMediaBytes: 1024 * 1024,
308
- textLimit: 3,
309
- replyLogger,
310
- skipLog: true,
311
- });
312
-
313
- expect(msg.reply).toHaveBeenCalledTimes(2);
314
- expect(msg.reply).toHaveBeenNthCalledWith(
315
- 1,
316
- "aaa",
317
- expect.objectContaining({
318
- quoted: expect.objectContaining({
319
- key: expect.objectContaining({
320
- id: "reply-1",
321
- fromMe: true,
322
- participant: "111@s.whatsapp.net",
323
- }),
324
- message: { conversation: "quoted body" },
325
- }),
326
- }),
327
- );
328
- expect(msg.reply).toHaveBeenNthCalledWith(
329
- 2,
330
- "aaa",
331
- expect.objectContaining({
332
- quoted: expect.objectContaining({
333
- key: expect.objectContaining({
334
- id: "reply-1",
335
- fromMe: true,
336
- participant: "111@s.whatsapp.net",
337
- }),
338
- message: { conversation: "quoted body" },
339
- }),
340
- }),
341
- );
342
- });
343
-
344
- it.each(["connection closed", "operation timed out"])(
345
- "retries text send on transient failure: %s",
346
- async (errorMessage) => {
347
- const msg = makeMsg();
348
- mockFirstReplyFailure(msg, errorMessage);
349
- mockSecondReplySuccess(msg);
350
-
351
- await deliverWebReply({
352
- replyResult: { text: "hi" },
353
- msg,
354
- maxMediaBytes: 1024 * 1024,
355
- textLimit: 200,
356
- replyLogger,
357
- skipLog: true,
358
- });
359
-
360
- expect(msg.reply).toHaveBeenCalledTimes(2);
361
- expect(sleep).toHaveBeenCalledWith(500);
362
- },
363
- );
364
-
365
- it("retries text send on wrapped transient failure", async () => {
366
- const msg = makeMsg();
367
- mockFirstReplyFailureWithWrappedError(msg, "connection closed");
368
- mockSecondReplySuccess(msg);
369
-
370
- await deliverWebReply({
371
- replyResult: { text: "hi" },
372
- msg,
373
- maxMediaBytes: 1024 * 1024,
374
- textLimit: 200,
375
- replyLogger,
376
- skipLog: true,
377
- });
378
-
379
- expect(msg.reply).toHaveBeenCalledTimes(2);
380
- expect(sleep).toHaveBeenCalledWith(500);
381
- });
382
-
383
- it("sends image media with caption and then remaining text", async () => {
384
- const msg = makeMsg();
385
- const mediaLocalRoots = ["/tmp/workspace-work"];
386
- mockLoadedImageMedia();
387
-
388
- await deliverWebReply({
389
- replyResult: { text: "aaaaaa", mediaUrl: "http://example.com/img.jpg" },
390
- msg,
391
- mediaLocalRoots,
392
- maxMediaBytes: 1024 * 1024,
393
- textLimit: 3,
394
- replyLogger,
395
- skipLog: true,
396
- });
397
-
398
- expect(loadWebMedia).toHaveBeenCalledWith("http://example.com/img.jpg", {
399
- maxBytes: 1024 * 1024,
400
- localRoots: mediaLocalRoots,
401
- });
402
-
403
- expect(msg.sendMedia).toHaveBeenCalledWith(
404
- expect.objectContaining({
405
- image: expect.any(Buffer),
406
- caption: "aaa",
407
- mimetype: "image/jpeg",
408
- }),
409
- undefined,
410
- );
411
- expect(msg.reply).toHaveBeenCalledWith("aaa", undefined);
412
- expect(replyLogger.info).toHaveBeenCalledWith(expect.any(Object), "auto-reply sent (media)");
413
- expect(logVerbose).toHaveBeenCalled();
414
- });
415
-
416
- it("preserves leading indentation after trimming only leading blank lines", async () => {
417
- const msg = makeMsg();
418
-
419
- await deliverWebReply({
420
- replyResult: { text: "\n \n indented block" },
421
- msg,
422
- maxMediaBytes: 1024 * 1024,
423
- textLimit: 200,
424
- replyLogger,
425
- skipLog: true,
426
- });
427
-
428
- expect(msg.reply).toHaveBeenCalledTimes(1);
429
- expect(msg.reply).toHaveBeenCalledWith(" indented block", undefined);
430
- });
431
-
432
- it("keeps quote threading on media and trailing text chunks for a threaded reply", async () => {
433
- const msg = makeMsg();
434
- mockLoadedImageMedia();
435
- cacheInboundMessageMeta("work", "15551234567@s.whatsapp.net", "reply-2", {
436
- participant: "111@s.whatsapp.net",
437
- body: "quoted media body",
438
- fromMe: true,
439
- });
440
-
441
- await deliverWebReply({
442
- replyResult: {
443
- text: "captiontrail",
444
- mediaUrl: "http://example.com/img.jpg",
445
- replyToId: "reply-2",
446
- },
447
- msg,
448
- maxMediaBytes: 1024 * 1024,
449
- textLimit: 7,
450
- replyLogger,
451
- skipLog: true,
452
- });
453
-
454
- expect(msg.sendMedia).toHaveBeenCalledWith(
455
- expect.objectContaining({
456
- image: expect.any(Buffer),
457
- caption: "caption",
458
- mimetype: "image/jpeg",
459
- }),
460
- expect.objectContaining({
461
- quoted: expect.objectContaining({
462
- key: expect.objectContaining({
463
- id: "reply-2",
464
- fromMe: true,
465
- participant: "111@s.whatsapp.net",
466
- }),
467
- message: { conversation: "quoted media body" },
468
- }),
469
- }),
470
- );
471
- expect(msg.reply).toHaveBeenCalledWith(
472
- "trail",
473
- expect.objectContaining({
474
- quoted: expect.objectContaining({
475
- key: expect.objectContaining({
476
- id: "reply-2",
477
- fromMe: true,
478
- participant: "111@s.whatsapp.net",
479
- }),
480
- message: { conversation: "quoted media body" },
481
- }),
482
- }),
483
- );
484
- });
485
-
486
- it("retries media send on transient failure", async () => {
487
- const msg = makeMsg();
488
- mockLoadedImageMedia();
489
- mockFirstSendMediaFailure(msg, "socket reset");
490
- (
491
- msg.sendMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void }
492
- ).mockResolvedValueOnce(acceptedSendResult("media", "media-retry-2"));
493
-
494
- await deliverWebReply({
495
- replyResult: { text: "caption", mediaUrl: "http://example.com/img.jpg" },
496
- msg,
497
- maxMediaBytes: 1024 * 1024,
498
- textLimit: 200,
499
- replyLogger,
500
- skipLog: true,
501
- });
502
-
503
- expect(msg.sendMedia).toHaveBeenCalledTimes(2);
504
- expect(sleep).toHaveBeenCalledWith(500);
505
- });
506
-
507
- it("falls back to text-only when the first media send fails", async () => {
508
- const msg = makeMsg();
509
- mockLoadedImageMedia();
510
- mockFirstSendMediaFailure(msg, "boom");
511
-
512
- await deliverWebReply({
513
- replyResult: { text: "caption", mediaUrl: "http://example.com/img.jpg" },
514
- msg,
515
- maxMediaBytes: 1024 * 1024,
516
- textLimit: 20,
517
- replyLogger,
518
- skipLog: true,
519
- });
520
-
521
- expect(msg.reply).toHaveBeenCalledTimes(1);
522
- expect(
523
- String((msg.reply as unknown as { mock: { calls: unknown[][] } }).mock.calls[0]?.[0]),
524
- ).toContain("⚠️ Media failed");
525
- expect(
526
- String((msg.reply as unknown as { mock: { calls: unknown[][] } }).mock.calls[0]?.[0]),
527
- ).not.toContain("boom");
528
- expect(replyLogger.warn).toHaveBeenCalledWith(
529
- expect.objectContaining({ mediaUrl: "http://example.com/img.jpg" }),
530
- "failed to send web media reply",
531
- );
532
- });
533
-
534
- it("still attempts later media after the first media fails", async () => {
535
- vi.clearAllMocks();
536
- const msg = makeMsg();
537
- (
538
- loadWebMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void }
539
- ).mockResolvedValueOnce({
540
- buffer: Buffer.from("bad"),
541
- contentType: "image/jpeg",
542
- kind: "image",
543
- });
544
- (
545
- loadWebMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void }
546
- ).mockResolvedValueOnce({
547
- buffer: Buffer.from("good"),
548
- contentType: "application/pdf",
549
- kind: "file",
550
- fileName: "good.pdf",
551
- });
552
- mockFirstSendMediaFailure(msg, "boom");
553
- (
554
- msg.sendMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void }
555
- ).mockResolvedValueOnce(acceptedSendResult("media", "media-second-1"));
556
-
557
- await deliverWebReply({
558
- replyResult: {
559
- text: "caption",
560
- mediaUrls: ["http://example.com/bad.jpg", "http://example.com/good.pdf"],
561
- },
562
- msg,
563
- maxMediaBytes: 1024 * 1024,
564
- textLimit: 200,
565
- replyLogger,
566
- skipLog: true,
567
- });
568
-
569
- expect(loadWebMedia).toHaveBeenNthCalledWith(1, "http://example.com/bad.jpg", {
570
- maxBytes: 1024 * 1024,
571
- localRoots: undefined,
572
- });
573
- expect(loadWebMedia).toHaveBeenNthCalledWith(2, "http://example.com/good.pdf", {
574
- maxBytes: 1024 * 1024,
575
- localRoots: undefined,
576
- });
577
- expect(msg.sendMedia).toHaveBeenCalledTimes(2);
578
- expect(msg.sendMedia).toHaveBeenNthCalledWith(
579
- 2,
580
- expect.objectContaining({
581
- document: expect.any(Buffer),
582
- fileName: "good.pdf",
583
- caption: undefined,
584
- mimetype: "application/pdf",
585
- }),
586
- undefined,
587
- );
588
- expect(msg.reply).toHaveBeenCalledTimes(1);
589
- expect(
590
- String((msg.reply as unknown as { mock: { calls: unknown[][] } }).mock.calls[0]?.[0]),
591
- ).toContain("⚠️ Media failed");
592
- expect(
593
- String((msg.reply as unknown as { mock: { calls: unknown[][] } }).mock.calls[0]?.[0]),
594
- ).not.toContain("boom");
595
- });
596
-
597
- it("sanitizes XML tool-call blocks for outbound sendPayload delivery", async () => {
598
- const sendWhatsApp = vi.fn(async (_to: string, _text: string) => ({
599
- messageId: "wa-1",
600
- toJid: "jid",
601
- }));
602
-
603
- await whatsappOutbound.sendPayload!({
604
- cfg: {},
605
- to: "5511999999999@c.us",
606
- text: "",
607
- payload: {
608
- text: 'Before\n<function_calls><invoke name="web_search"><parameter name="query">x</parameter></invoke></function_calls>\nAfter',
609
- },
610
- deps: { sendWhatsApp },
611
- });
612
-
613
- expect(sendWhatsApp).toHaveBeenCalledTimes(1);
614
- const sentText = sendWhatsApp.mock.calls[0]?.[1];
615
- expect(sentText).not.toContain("function_calls");
616
- expect(sentText).not.toContain("invoke");
617
- expect(sentText).toContain("Before");
618
- expect(sentText).toContain("After");
619
- });
620
-
621
- it("keeps payload and auto-reply media normalization in parity", async () => {
622
- const payload = {
623
- text: "\n\ncaption",
624
- mediaUrls: [" ", " /tmp/voice.ogg "],
625
- };
626
- const sendWhatsApp = vi.fn(async () => ({ messageId: "wa-1", toJid: "jid" }));
627
-
628
- await whatsappOutbound.sendPayload!({
629
- cfg: {},
630
- to: "5511999999999@c.us",
631
- text: "",
632
- payload,
633
- deps: { sendWhatsApp },
634
- });
635
-
636
- const msg = makeMsg();
637
- (
638
- loadWebMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void }
639
- ).mockResolvedValueOnce({
640
- buffer: Buffer.from("aud"),
641
- contentType: "audio/ogg",
642
- kind: "audio",
643
- });
644
-
645
- await deliverWebReply({
646
- replyResult: payload,
647
- msg,
648
- maxMediaBytes: 1024 * 1024,
649
- textLimit: 200,
650
- replyLogger,
651
- skipLog: true,
652
- });
653
-
654
- expect(sendWhatsApp).toHaveBeenCalledTimes(1);
655
- expect(sendWhatsApp).toHaveBeenCalledWith("5511999999999@c.us", "caption", {
656
- verbose: false,
657
- cfg: {},
658
- mediaUrl: "/tmp/voice.ogg",
659
- mediaLocalRoots: undefined,
660
- accountId: undefined,
661
- gifPlayback: undefined,
662
- });
663
- expect(loadWebMedia).toHaveBeenCalledWith("/tmp/voice.ogg", {
664
- maxBytes: 1024 * 1024,
665
- localRoots: undefined,
666
- });
667
- expect(msg.sendMedia).toHaveBeenCalledTimes(1);
668
- expect(msg.sendMedia).toHaveBeenCalledWith(
669
- expect.objectContaining({
670
- audio: expect.any(Buffer),
671
- ptt: true,
672
- mimetype: "audio/ogg; codecs=opus",
673
- }),
674
- undefined,
675
- );
676
- expect(expectFirstSendMediaPayload(msg)).not.toHaveProperty("caption");
677
- expect(msg.reply).toHaveBeenCalledWith("caption", undefined);
678
- });
679
-
680
- it("sends audio media as ptt voice note with visible text separately", async () => {
681
- const msg = makeMsg();
682
- (
683
- loadWebMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void }
684
- ).mockResolvedValueOnce({
685
- buffer: Buffer.from("aud"),
686
- contentType: "audio/ogg",
687
- kind: "audio",
688
- });
689
-
690
- await deliverWebReply({
691
- replyResult: { text: "cap", mediaUrl: "http://example.com/a.ogg" },
692
- msg,
693
- maxMediaBytes: 1024 * 1024,
694
- textLimit: 200,
695
- replyLogger,
696
- skipLog: true,
697
- });
698
-
699
- expect(msg.sendMedia).toHaveBeenCalledWith(
700
- expect.objectContaining({
701
- audio: expect.any(Buffer),
702
- ptt: true,
703
- mimetype: "audio/ogg; codecs=opus",
704
- }),
705
- undefined,
706
- );
707
- expect(expectFirstSendMediaPayload(msg)).not.toHaveProperty("caption");
708
- expect(msg.reply).toHaveBeenCalledWith("cap", undefined);
709
- });
710
-
711
- it("transcodes mp3 audio media before sending a ptt voice note", async () => {
712
- vi.clearAllMocks();
713
- hoisted.runFfmpeg.mockImplementation(async (args: string[]) => {
714
- fsSync.writeFileSync(args.at(-1) ?? "", Buffer.from("opus-output"));
715
- return "";
716
- });
717
- const msg = makeMsg();
718
- (
719
- loadWebMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void }
720
- ).mockResolvedValueOnce({
721
- buffer: Buffer.from("mp3"),
722
- contentType: "audio/mpeg",
723
- kind: "audio",
724
- fileName: "voice.mp3",
725
- });
726
-
727
- await deliverWebReply({
728
- replyResult: { text: "cap", mediaUrl: "http://example.com/a.mp3" },
729
- msg,
730
- maxMediaBytes: 1024 * 1024,
731
- textLimit: 200,
732
- replyLogger,
733
- skipLog: true,
734
- });
735
-
736
- expect(hoisted.runFfmpeg).toHaveBeenCalledWith(
737
- expect.arrayContaining(["-c:a", "libopus", "-ar", "48000", "-b:a", "64k"]),
738
- );
739
- expect(msg.sendMedia).toHaveBeenCalledWith(
740
- expect.objectContaining({
741
- audio: Buffer.from("opus-output"),
742
- ptt: true,
743
- mimetype: "audio/ogg; codecs=opus",
744
- }),
745
- undefined,
746
- );
747
- expect(expectFirstSendMediaPayload(msg)).not.toHaveProperty("caption");
748
- expect(msg.reply).toHaveBeenCalledWith("cap", undefined);
749
- });
750
-
751
- it("sends video media", async () => {
752
- const msg = makeMsg();
753
- (
754
- loadWebMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void }
755
- ).mockResolvedValueOnce({
756
- buffer: Buffer.from("vid"),
757
- contentType: "video/mp4",
758
- kind: "video",
759
- });
760
-
761
- await deliverWebReply({
762
- replyResult: { text: "cap", mediaUrl: "http://example.com/v.mp4" },
763
- msg,
764
- maxMediaBytes: 1024 * 1024,
765
- textLimit: 200,
766
- replyLogger,
767
- skipLog: true,
768
- });
769
-
770
- expect(msg.sendMedia).toHaveBeenCalledWith(
771
- expect.objectContaining({
772
- video: expect.any(Buffer),
773
- caption: "cap",
774
- mimetype: "video/mp4",
775
- }),
776
- undefined,
777
- );
778
- });
779
-
780
- it("sends non-audio/image/video media as document", async () => {
781
- const msg = makeMsg();
782
- (
783
- loadWebMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void }
784
- ).mockResolvedValueOnce({
785
- buffer: Buffer.from("bin"),
786
- contentType: undefined,
787
- kind: "file",
788
- fileName: "x.bin",
789
- });
790
-
791
- await deliverWebReply({
792
- replyResult: { text: "cap", mediaUrl: "http://example.com/x.bin" },
793
- msg,
794
- maxMediaBytes: 1024 * 1024,
795
- textLimit: 200,
796
- replyLogger,
797
- skipLog: true,
798
- });
799
-
800
- expect(msg.sendMedia).toHaveBeenCalledWith(
801
- expect.objectContaining({
802
- document: expect.any(Buffer),
803
- fileName: "x.bin",
804
- caption: "cap",
805
- mimetype: "application/octet-stream",
806
- }),
807
- undefined,
808
- );
809
- });
810
-
811
- it("strips URL query and fragment data from derived document file names", async () => {
812
- const msg = makeMsg();
813
- (
814
- loadWebMedia as unknown as { mockResolvedValueOnce: (v: unknown) => void }
815
- ).mockResolvedValueOnce({
816
- buffer: Buffer.from("pdf"),
817
- contentType: "application/pdf",
818
- kind: "file",
819
- });
820
-
821
- await deliverWebReply({
822
- replyResult: {
823
- text: "cap",
824
- mediaUrl: "https://example.com/report.pdf?X-Amz-Signature=secret#frag",
825
- },
826
- msg,
827
- maxMediaBytes: 1024 * 1024,
828
- textLimit: 200,
829
- replyLogger,
830
- skipLog: true,
831
- });
832
-
833
- expect(msg.sendMedia).toHaveBeenCalledWith(
834
- expect.objectContaining({
835
- document: expect.any(Buffer),
836
- fileName: "report.pdf",
837
- caption: "cap",
838
- mimetype: "application/pdf",
839
- }),
840
- undefined,
841
- );
842
- });
843
- });