@lofa199419/waha-v2 2026.3.4 → 2026.3.6

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.
@@ -0,0 +1,55 @@
1
+ # WAHA v2 Session Handoff (2026-03-05)
2
+
3
+ ## 1) Scope completed
4
+ - Focused only on `extensions/waha-v2`.
5
+ - Objective was to make typing indicator + chunked sending behavior reliable and observable, then publish and verify on target host.
6
+
7
+ ## 2) Local code changes made
8
+
9
+ ### `src/client.ts`
10
+ - Improved typing presence calls (`startTyping` / `stopTyping`) so non-2xx HTTP responses are treated as failures.
11
+ - Added response-context error reporting for easier diagnosis.
12
+
13
+ ### `src/webhook.ts`
14
+ - Added debug logs around typing/chunk lifecycle to make runtime behavior visible.
15
+ - Ensured inter-chunk typing start failures do **not** break chunk sending.
16
+ - Updated behavior to trigger typing before first chunk and added a short first-chunk delay (`700ms`) to make typing indicator visible.
17
+
18
+ ## 3) Publish
19
+ - Published npm package: `@lofa199419/waha-v2@2026.3.5`.
20
+ - Registry was verified to reflect `2026.3.5`.
21
+
22
+ ## 4) Remote deployment performed
23
+ Target host used:
24
+ - `ubuntu@100.106.66.110`
25
+
26
+ State found before fix:
27
+ - Host still had `waha-v2@2026.3.4` in `~/.openclaw/extensions/waha-v2`.
28
+ - Typing config block not set.
29
+
30
+ Actions performed:
31
+ - Updated extension on host to `2026.3.5`.
32
+ - Set config values:
33
+ - `channels.waha-v2.typing.enabled=true`
34
+ - `channels.waha-v2.typing.chunking=true`
35
+ - `channels.waha-v2.typing.charsPerSecond=25`
36
+ - `channels.waha-v2.typing.maxChunkLength=220`
37
+ - Restarted gateway.
38
+
39
+ Post-update verification:
40
+ - Plugin list shows `waha-v2@2026.3.5`.
41
+ - Typing config present.
42
+ - WAHA channel/accounts probing healthy.
43
+
44
+ ## 5) Remaining validation step
45
+ - Final observational confirmation: send a multi-paragraph prompt and verify logs show typing/chunk markers during a real message.
46
+
47
+ Suggested log check pattern:
48
+ - `waha-v2: send chunked reply`
49
+ - `waha-v2: send chunk`
50
+ - `typing start ok`
51
+ - `typing stop ok`
52
+ - first/inter-chunk typing start success/failure markers
53
+
54
+ ## 6) Environment note
55
+ - On the remote host, `rg` was unavailable; use `grep`/`grep -E` for log filtering.
@@ -0,0 +1,84 @@
1
+ # WAHA v2 E2E Status Handoff (2026-03-05)
2
+
3
+ ## Scope
4
+ This note captures what was smoke tested live in this session, what is confirmed, what is partially confirmed, and what remains to verify later.
5
+
6
+ ## Environment used
7
+ - Repo: `/home/lofa/dev/openclaw`
8
+ - Extension focus: `extensions/waha-v2`
9
+ - Active account: `default`
10
+ - Main WAHA session: `finaltouch`
11
+
12
+ ## Code changes completed in this session
13
+
14
+ ### 1) Target/action routing hardening (core)
15
+ - For no-target actions, accidental `target` no longer hard-fails.
16
+ - File: `src/infra/outbound/channel-target.ts`
17
+ - WAHA group/contact action target modes fixed so `target` maps correctly.
18
+ - File: `src/infra/outbound/message-action-spec.ts`
19
+ - Regression tests added/updated.
20
+ - File: `src/infra/outbound/message-action-runner.test.ts`
21
+
22
+ ### 2) Request-code flow in WAHA v2 plugin
23
+ - `request-code` now enforces session prep + delay flow:
24
+ - Missing session: create -> start
25
+ - Existing active session: stop -> start (restart)
26
+ - Existing stopped/failed session: start
27
+ - Wait 5 seconds
28
+ - Request code
29
+ - File: `extensions/waha-v2/src/channel.ts`
30
+
31
+ ## Live smoke tests run and result
32
+
33
+ ### A) Messaging/chat/group/contact core read paths
34
+ - PASS: `list-chats`
35
+ - PASS: `get-chat-messages` (chat `154739484418169@lid`)
36
+ - PASS: `react` on message `false_154739484418169@lid_3A6FC76D1349CFCA7C1D` with `👍`
37
+ - PASS: `list-groups`
38
+ - PASS: `get-group`
39
+ - PASS: `get-group-participants`
40
+ - PASS: `get-group-invite-code`
41
+ - PASS: `list-contacts`
42
+ - PASS: `check-contact` for known number `573016924546`
43
+ - PASS: `get-contact` (known resolved contact id)
44
+
45
+ ### B) Session/pairing E2E
46
+ - PASS: `start-session` on primary (`finaltouch`)
47
+ - PASS: fresh session create/start/get-qr flow
48
+ - temporary session: `e2e-smoke-1772739741688`
49
+ - PASS: request-code sequence with 5s delay for number `905382188346`
50
+ - temporary session: `pair-code-1772740042905`
51
+ - returned code: `J7CR-WTK7`
52
+ - NOTE: `startSession` on a fresh non-existent session fails first with `Resource not found` (expected), then succeeds after create.
53
+
54
+ ## Not fully confirmed / lower confidence areas
55
+
56
+ ### WAHA server-side failures seen (likely backend/permission/input related, not mapping)
57
+ - `promote-group-admin` / `demote-group-admin` on real group + participant returned WAHA 500.
58
+ - mutation ops with fake/nonexistent ids returned WAHA 500 (expected pattern).
59
+ - `check-contact` for obviously unknown number returned WAHA 500 (not a clean false response).
60
+
61
+ ### Explicitly not required right now (per user)
62
+ - block/unblock round-trip verification
63
+
64
+ ## 100% verified for current priority (practical)
65
+ Priority list from user context:
66
+ - Start a Session -> verified
67
+ - Get QR Code for Pairing -> verified
68
+ - Request Pairing Code -> verified (with required wait behavior)
69
+ - Send a Message -> verified
70
+ - React to a Message -> verified
71
+ - List Conversations -> verified
72
+ - Check Contact Status -> verified on known-valid contact
73
+
74
+ ## Remaining to double-check later (if needed)
75
+ 1. `request-code` through the full OpenClaw message action runner path (not only direct wrapper script) after publishing latest plugin/core changes everywhere.
76
+ 2. clean handling expectations for unknown numbers in `check-contact` (whether WAHA should return false vs 500; depends on backend behavior).
77
+ 3. admin-role mutation behavior (`promote/demote`) with a dedicated controlled test group where bot permissions are guaranteed.
78
+
79
+ ## Operational notes
80
+ - Temporary sessions created during tests were stopped.
81
+ - Last active primary session remains `finaltouch`.
82
+ - Existing older summary file remains:
83
+ - `extensions/waha-v2/SESSION_HANDOFF_2026-03-05.md`
84
+
package/index.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
3
3
  import { wahaV2Plugin } from "./src/channel.js";
4
- import { setWahaV2Runtime } from "./src/runtime.js";
5
- import { handleWahaV2WebhookRequest } from "./src/webhook.js";
6
4
  import {
7
5
  handleWahaV2QrRoute,
8
6
  handleWahaV2RequestCodeRoute,
@@ -15,7 +13,9 @@ import {
15
13
  WAHA_V2_ROUTE_STATUS,
16
14
  WAHA_V2_ROUTE_WAIT,
17
15
  } from "./src/routes.js";
16
+ import { setWahaV2Runtime } from "./src/runtime.js";
18
17
  import { WAHA_V2_WEBHOOK_BASE } from "./src/types.js";
18
+ import { handleWahaV2WebhookRequest } from "./src/webhook.js";
19
19
 
20
20
  const plugin = {
21
21
  id: "waha-v2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lofa199419/waha-v2",
3
- "version": "2026.3.4",
3
+ "version": "2026.3.6",
4
4
  "private": false,
5
5
  "description": "OpenClaw WAHA v2 channel plugin — independent WhatsApp HTTP API integration",
6
6
  "type": "module",
@@ -13,7 +13,9 @@
13
13
  "openclaw": ">=2026.0.0"
14
14
  },
15
15
  "peerDependenciesMeta": {
16
- "openclaw": { "optional": true }
16
+ "openclaw": {
17
+ "optional": true
18
+ }
17
19
  },
18
20
  "openclaw": {
19
21
  "extensions": [
@@ -19,43 +19,74 @@ Use the `message` tool with `channel: "waha-v2"`. You have complete WhatsApp acc
19
19
 
20
20
  ## Session States
21
21
 
22
- | State | Meaning |
23
- |---|---|
24
- | `WORKING` / `CONNECTED` | Ready — go ahead |
25
- | `STOPPED` / `FAILED` | Call `start-session` first |
22
+ | State | Meaning |
23
+ | -------------------------- | ----------------------------------------- |
24
+ | `WORKING` / `CONNECTED` | Ready — go ahead |
25
+ | `STOPPED` / `FAILED` | Call `start-session` first |
26
26
  | `SCAN_QR_CODE` / `PAIRING` | Waiting for user to scan QR or enter code |
27
- | `CONNECTING` | Starting up — wait briefly |
27
+ | `CONNECTING` | Starting up — wait briefly |
28
28
 
29
29
  ---
30
30
 
31
31
  ## Sending Messages
32
32
 
33
33
  ### Text
34
+
34
35
  ```json
35
36
  { "action": "send", "channel": "waha-v2", "to": "15550001234@c.us", "message": "Hello!" }
36
37
  ```
37
38
 
38
39
  ### Media (image / video / audio / file)
40
+
39
41
  ```json
40
- { "action": "send", "channel": "waha-v2", "to": "15550001234@c.us", "media": "https://…/file.pdf", "message": "Your report" }
42
+ {
43
+ "action": "send",
44
+ "channel": "waha-v2",
45
+ "to": "15550001234@c.us",
46
+ "media": "https://…/file.pdf",
47
+ "message": "Your report"
48
+ }
41
49
  ```
42
50
 
43
51
  ### Poll
52
+
44
53
  ```json
45
- { "action": "poll", "channel": "waha-v2", "to": "15550001234@c.us", "pollQuestion": "Which day?", "pollOption": ["Mon","Wed","Fri"], "pollMulti": false }
54
+ {
55
+ "action": "poll",
56
+ "channel": "waha-v2",
57
+ "to": "15550001234@c.us",
58
+ "pollQuestion": "Which day?",
59
+ "pollOption": ["Mon", "Wed", "Fri"],
60
+ "pollMulti": false
61
+ }
46
62
  ```
47
63
 
48
64
  ### Location
65
+
49
66
  ```json
50
- { "action": "send-location", "channel": "waha-v2", "to": "15550001234@c.us", "latitude": 37.77, "longitude": -122.41, "title": "SF Office" }
67
+ {
68
+ "action": "send-location",
69
+ "channel": "waha-v2",
70
+ "to": "15550001234@c.us",
71
+ "latitude": 37.77,
72
+ "longitude": -122.41,
73
+ "title": "SF Office"
74
+ }
51
75
  ```
52
76
 
53
77
  ### Contact card
78
+
54
79
  ```json
55
- { "action": "send-contact", "channel": "waha-v2", "to": "15550001234@c.us", "contacts": [{ "vcard": "BEGIN:VCARD\nVERSION:3.0\nFN:Alice\nTEL:+14155559999\nEND:VCARD" }] }
80
+ {
81
+ "action": "send-contact",
82
+ "channel": "waha-v2",
83
+ "to": "15550001234@c.us",
84
+ "contacts": [{ "vcard": "BEGIN:VCARD\nVERSION:3.0\nFN:Alice\nTEL:+14155559999\nEND:VCARD" }]
85
+ }
56
86
  ```
57
87
 
58
88
  ### Forward a message
89
+
59
90
  ```json
60
91
  { "action": "forward-message", "channel": "waha-v2", "to": "15550001234@c.us", "messageId": "<id>" }
61
92
  ```
@@ -152,11 +183,13 @@ Use the `message` tool with `channel: "waha-v2"`. You have complete WhatsApp acc
152
183
  ## Session Management & Login Flow
153
184
 
154
185
  ### Full QR login sequence
186
+
155
187
  1. `start-session` — if `alreadyConnected: true`, done.
156
188
  2. `get-qr` — returns `{ data }` base64 PNG; show to user to scan in WhatsApp → Linked Devices.
157
189
  3. User scans, then call `start-session` again to confirm `connected: true`.
158
190
 
159
191
  ### Pairing code (no phone camera)
192
+
160
193
  1. `start-session`
161
194
  2. `request-code` with `phoneNumber: "15550001234"` — returns 8-digit code.
162
195
  3. User enters code in WhatsApp → Linked Devices → Link with phone number.
@@ -171,7 +204,13 @@ Use the `message` tool with `channel: "waha-v2"`. You have complete WhatsApp acc
171
204
  ## Multi-Account
172
205
 
173
206
  ```json
174
- { "action": "send", "channel": "waha-v2", "accountId": "secondary", "to": "15550001234@c.us", "message": "Hi" }
207
+ {
208
+ "action": "send",
209
+ "channel": "waha-v2",
210
+ "accountId": "secondary",
211
+ "to": "15550001234@c.us",
212
+ "message": "Hi"
213
+ }
175
214
  ```
176
215
 
177
216
  Each account gets its own webhook: `{webhookUrl}/{accountId}`.
package/src/accounts.ts CHANGED
@@ -60,6 +60,7 @@ export function resolveWahaV2Account(
60
60
  enabled: account.enabled !== false,
61
61
  dmPolicy: account.dmPolicy,
62
62
  allowFrom: account.allowFrom,
63
+ typing: account.typing,
63
64
  };
64
65
  }
65
66
 
@@ -102,7 +103,10 @@ export function setWahaV2AccountEnabled(
102
103
  // Single-account mode — set enabled on root.
103
104
  return setWahaV2ChannelConfig(cfg, { ...root, enabled });
104
105
  }
105
- const accounts = { ...(root.accounts ?? {}), [accountId]: { ...(root.accounts?.[accountId] ?? {}), enabled } };
106
+ const accounts = {
107
+ ...(root.accounts ?? {}),
108
+ [accountId]: { ...(root.accounts?.[accountId] ?? {}), enabled },
109
+ };
106
110
  return setWahaV2ChannelConfig(cfg, { ...root, accounts });
107
111
  }
108
112
 
@@ -113,5 +117,8 @@ export function deleteWahaV2Account(cfg: OpenClawConfig, accountId: string): Ope
113
117
  }
114
118
  const accounts = { ...root.accounts };
115
119
  delete accounts[accountId];
116
- return setWahaV2ChannelConfig(cfg, { ...root, accounts: Object.keys(accounts).length > 0 ? accounts : undefined });
120
+ return setWahaV2ChannelConfig(cfg, {
121
+ ...root,
122
+ accounts: Object.keys(accounts).length > 0 ? accounts : undefined,
123
+ });
117
124
  }
package/src/channel.ts CHANGED
@@ -8,12 +8,12 @@ import {
8
8
  setWahaV2AccountEnabled,
9
9
  setWahaV2ChannelConfig,
10
10
  } from "./accounts.js";
11
+ import { wahaV2ChannelConfigSchema } from "./config-schema.js";
11
12
  import { wahaV2Gateway } from "./gateway.js";
12
13
  import { acquireLoginLock, releaseLoginLock, waitForWahaV2Connected } from "./login.js";
13
14
  import { wahaV2Outbound } from "./outbound.js";
14
15
  import { probeWahaV2Session } from "./probe.js";
15
16
  import { getWahaV2Client } from "./runtime.js";
16
- import { wahaV2ChannelConfigSchema } from "./config-schema.js";
17
17
  import {
18
18
  WAHA_V2_CHANNEL_ID,
19
19
  WAHA_V2_DEFAULT_ACCOUNT_ID,
@@ -22,6 +22,48 @@ import {
22
22
  type WahaV2RootConfig,
23
23
  } from "./types.js";
24
24
 
25
+ function sleep(ms: number): Promise<void> {
26
+ return new Promise((resolve) => setTimeout(resolve, ms));
27
+ }
28
+
29
+ async function prepareSessionForRequestCode(params: {
30
+ client: NonNullable<ReturnType<typeof getWahaV2Client>>;
31
+ session: string;
32
+ }): Promise<{
33
+ sessionExisted: boolean;
34
+ previousStatus: string | null;
35
+ restarted: boolean;
36
+ }> {
37
+ const sessions = await params.client.listSessions(true).catch(() => []);
38
+ const current = sessions.find((entry) => String(entry?.name ?? "") === params.session);
39
+ const previousStatus = current && typeof current.status === "string" ? current.status : null;
40
+ const sessionExisted = Boolean(current);
41
+ let restarted = false;
42
+
43
+ if (!sessionExisted) {
44
+ await params.client.createSession(params.session);
45
+ await params.client.startSession(params.session);
46
+ return { sessionExisted, previousStatus, restarted };
47
+ }
48
+
49
+ const normalized = String(previousStatus ?? "").trim().toUpperCase();
50
+ const shouldRestart =
51
+ normalized.length > 0 &&
52
+ !["STOPPED", "FAILED", "NONE", "UNKNOWN", "DISCONNECTED"].includes(normalized);
53
+
54
+ if (shouldRestart) {
55
+ await params.client.stopSession(params.session).catch(() => {});
56
+ restarted = true;
57
+ }
58
+
59
+ await params.client.startSession(params.session).catch(async () => {
60
+ await params.client.createSession(params.session);
61
+ await params.client.startSession(params.session);
62
+ });
63
+
64
+ return { sessionExisted, previousStatus, restarted };
65
+ }
66
+
25
67
  /** Normalize allow-from entries to WhatsApp JID format. */
26
68
  function normalizeAllowEntry(entry: string): string {
27
69
  const trimmed = entry.trim();
@@ -142,8 +184,7 @@ export const wahaV2Plugin: ChannelPlugin<ResolvedWahaV2Account> = {
142
184
  allowFrom: account.allowFrom,
143
185
  }),
144
186
 
145
- resolveAllowFrom: ({ cfg, accountId }) =>
146
- resolveWahaV2Account(cfg, accountId).allowFrom,
187
+ resolveAllowFrom: ({ cfg, accountId }) => resolveWahaV2Account(cfg, accountId).allowFrom,
147
188
 
148
189
  formatAllowFrom: ({ allowFrom }) =>
149
190
  allowFrom.map((e) => normalizeAllowEntry(String(e))).filter(Boolean),
@@ -220,7 +261,13 @@ export const wahaV2Plugin: ChannelPlugin<ResolvedWahaV2Account> = {
220
261
  }
221
262
  // Contact lookup / block
222
263
  if (gate("contacts")) {
223
- result.push("list-contacts", "get-contact", "check-contact", "block-contact", "unblock-contact");
264
+ result.push(
265
+ "list-contacts",
266
+ "get-contact",
267
+ "check-contact",
268
+ "block-contact",
269
+ "unblock-contact",
270
+ );
224
271
  }
225
272
  // Group management
226
273
  if (gate("groups")) {
@@ -280,7 +327,9 @@ export const wahaV2Plugin: ChannelPlugin<ResolvedWahaV2Account> = {
280
327
  await client.startSession(session).catch(async () => {
281
328
  await client.createSession(session);
282
329
  });
283
- const waitResult = await waitForWahaV2Connected(client, session, 5_000).catch(() => ({ ok: false }));
330
+ const waitResult = await waitForWahaV2Connected(client, session, 5_000).catch(() => ({
331
+ ok: false,
332
+ }));
284
333
  const probe = await probeWahaV2Session(client, session).catch(() => ({ ok: false }));
285
334
  return jsonResult({
286
335
  ok: true,
@@ -312,8 +361,20 @@ export const wahaV2Plugin: ChannelPlugin<ResolvedWahaV2Account> = {
312
361
  if (!phoneNumber) throw new Error("waha-v2: request-code requires params.phoneNumber");
313
362
  acquireLoginLock(account.accountId);
314
363
  try {
364
+ const prep = await prepareSessionForRequestCode({ client, session });
365
+ // Give WAHA a short transition window before requesting pairing code.
366
+ await sleep(5_000);
315
367
  const result = await client.requestCode(session, phoneNumber);
316
- return jsonResult({ ok: true, action: "request-code", session, code: result?.code ?? null });
368
+ return jsonResult({
369
+ ok: true,
370
+ action: "request-code",
371
+ session,
372
+ sessionExisted: prep.sessionExisted,
373
+ previousStatus: prep.previousStatus,
374
+ restarted: prep.restarted,
375
+ waitedMs: 5_000,
376
+ code: result?.code ?? null,
377
+ });
317
378
  } finally {
318
379
  releaseLoginLock(account.accountId);
319
380
  }
@@ -367,7 +428,8 @@ export const wahaV2Plugin: ChannelPlugin<ResolvedWahaV2Account> = {
367
428
  if (action === "forward-message") {
368
429
  const chatId = String(p.to ?? p.chatId ?? "");
369
430
  const messageId = String(p.messageId ?? "");
370
- if (!chatId || !messageId) throw new Error("waha-v2: forward-message requires to and messageId");
431
+ if (!chatId || !messageId)
432
+ throw new Error("waha-v2: forward-message requires to and messageId");
371
433
  const result = await client.forwardMessage(session, chatId, messageId);
372
434
  return jsonResult({ ok: true, action: "forward-message", chatId, result });
373
435
  }
@@ -390,7 +452,8 @@ export const wahaV2Plugin: ChannelPlugin<ResolvedWahaV2Account> = {
390
452
  const chatId = String(p.to ?? p.chatId ?? "");
391
453
  const messageId = String(p.messageId ?? "");
392
454
  const text = String(p.text ?? "");
393
- if (!chatId || !messageId || !text) throw new Error("waha-v2: edit requires to, messageId, text");
455
+ if (!chatId || !messageId || !text)
456
+ throw new Error("waha-v2: edit requires to, messageId, text");
394
457
  await client.editMessage(session, chatId, messageId, text);
395
458
  return jsonResult({ ok: true, action: "edit", chatId, messageId });
396
459
  }
@@ -441,7 +504,8 @@ export const wahaV2Plugin: ChannelPlugin<ResolvedWahaV2Account> = {
441
504
  if (action === "get-message") {
442
505
  const chatId = String(p.to ?? p.chatId ?? "");
443
506
  const messageId = String(p.messageId ?? "");
444
- if (!chatId || !messageId) throw new Error("waha-v2: get-message requires to and messageId");
507
+ if (!chatId || !messageId)
508
+ throw new Error("waha-v2: get-message requires to and messageId");
445
509
  const message = await client.getChatMessage(session, chatId, messageId);
446
510
  return jsonResult({ ok: true, action: "get-message", chatId, messageId, message });
447
511
  }
@@ -570,16 +634,22 @@ export const wahaV2Plugin: ChannelPlugin<ResolvedWahaV2Account> = {
570
634
 
571
635
  if (action === "promote-group-admin") {
572
636
  const groupId = String(p.groupId ?? p.to ?? "");
573
- const participants = Array.isArray(p.participants) ? (p.participants as string[]) : [String(p.participant ?? "")].filter(Boolean);
574
- if (!groupId || participants.length === 0) throw new Error("waha-v2: promote-group-admin requires groupId and participants");
637
+ const participants = Array.isArray(p.participants)
638
+ ? (p.participants as string[])
639
+ : [String(p.participant ?? "")].filter(Boolean);
640
+ if (!groupId || participants.length === 0)
641
+ throw new Error("waha-v2: promote-group-admin requires groupId and participants");
575
642
  const result = await client.promoteGroupAdmin(session, groupId, participants);
576
643
  return jsonResult({ ok: true, action: "promote-group-admin", groupId, result });
577
644
  }
578
645
 
579
646
  if (action === "demote-group-admin") {
580
647
  const groupId = String(p.groupId ?? p.to ?? "");
581
- const participants = Array.isArray(p.participants) ? (p.participants as string[]) : [String(p.participant ?? "")].filter(Boolean);
582
- if (!groupId || participants.length === 0) throw new Error("waha-v2: demote-group-admin requires groupId and participants");
648
+ const participants = Array.isArray(p.participants)
649
+ ? (p.participants as string[])
650
+ : [String(p.participant ?? "")].filter(Boolean);
651
+ if (!groupId || participants.length === 0)
652
+ throw new Error("waha-v2: demote-group-admin requires groupId and participants");
583
653
  const result = await client.demoteGroupAdmin(session, groupId, participants);
584
654
  return jsonResult({ ok: true, action: "demote-group-admin", groupId, result });
585
655
  }
@@ -602,12 +672,14 @@ export const wahaV2Plugin: ChannelPlugin<ResolvedWahaV2Account> = {
602
672
  const groupId = String(p.groupId ?? p.to ?? "");
603
673
  if (action === "renameGroup") {
604
674
  const subject = String(p.name ?? p.subject ?? "");
605
- if (!groupId || !subject) throw new Error("waha-v2: renameGroup requires groupId and name");
675
+ if (!groupId || !subject)
676
+ throw new Error("waha-v2: renameGroup requires groupId and name");
606
677
  await client.updateGroupSubject(session, groupId, subject);
607
678
  return jsonResult({ ok: true, action: "renameGroup", groupId });
608
679
  }
609
680
  const description = String(p.description ?? "");
610
- if (!groupId || !description) throw new Error("waha-v2: update-group-description requires groupId and description");
681
+ if (!groupId || !description)
682
+ throw new Error("waha-v2: update-group-description requires groupId and description");
611
683
  await client.updateGroupDescription(session, groupId, description);
612
684
  return jsonResult({ ok: true, action: "update-group-description", groupId });
613
685
  }