@openclaw/whatsapp 2026.7.2-beta.2 → 2026.7.2-beta.4

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 (147) hide show
  1. package/dist/{access-control-DazDX3RB.js → access-control-CRz_v6bR.js} +4 -5
  2. package/dist/{accounts-CKOFMPf-.js → accounts-CxPuEFi-.js} +44 -4
  3. package/dist/{action-runtime-Bf-12TIH.js → action-runtime-CHU7xb6o.js} +2 -2
  4. package/dist/action-runtime-api.js +1 -1
  5. package/dist/action-runtime.runtime.js +1 -1
  6. package/dist/{active-listener-5kHap439.js → active-listener-BEjWsCNM.js} +1 -1
  7. package/dist/api.js +12 -15
  8. package/dist/{approval-handler.runtime-CBiGQuZ5.js → approval-handler.runtime-C4ddB7Kc.js} +4 -4
  9. package/dist/{approval-reactions-BGQoz25s.js → approval-reactions-P2HXjmln.js} +3 -3
  10. package/dist/{auth-store-BQi9pZqQ.js → auth-store-DirYMTbo.js} +4 -3
  11. package/dist/call-tool-api.js +4 -4
  12. package/dist/{channel-DbadDUDD.js → channel-B9xjrOGG.js} +37 -19
  13. package/dist/channel-config-api.js +1 -1
  14. package/dist/channel-plugin-api.js +1 -1
  15. package/dist/{channel-react-action-PoRWfP9x.js → channel-react-action-CBW-PA8q.js} +10 -13
  16. package/dist/{channel.runtime-BDDy4Lhz.js → channel.runtime-DdKuhlNu.js} +5 -5
  17. package/dist/{channel.setup-BZ-po8fg.js → channel.setup-DXUCPHel.js} +3 -6
  18. package/dist/{config-schema-BZ8h1hWA.js → config-schema-C6IeKIC0.js} +6 -0
  19. package/dist/{connection-controller-C1rwfWb4.js → connection-controller-BVRExqrB.js} +186 -27
  20. package/dist/connection-controller-runtime-context-DG0tuYuw.js +23 -0
  21. package/dist/contract-api.js +1 -1
  22. package/dist/directory-config-BTbJkV1W.js +196 -0
  23. package/dist/directory-contract-api.js +1 -1
  24. package/dist/doctor-CwzVXKoE.js +10 -0
  25. package/dist/doctor-contract-api.js +35 -79
  26. package/dist/{group-session-key-CLTZeH_F.js → group-session-key-DJSdYfnb.js} +9 -4
  27. package/dist/{identity-D7L8LCuH.js → identity-BM4_wZeM.js} +2 -2
  28. package/dist/light-runtime-api.js +2 -2
  29. package/dist/{login-DGIoToX8.js → login-BKUm7KSk.js} +6 -7
  30. package/dist/{login-qr-X-CZHOk9.js → login-qr-DagJvMbg.js} +7 -8
  31. package/dist/login-qr-runtime.js +1 -1
  32. package/dist/{monitor-BZ8QvCeV.js → monitor-CBVNJ2lb.js} +623 -400
  33. package/dist/question-reactions-CaOrSYzU.js +142 -0
  34. package/dist/runtime-BLlToOi6.js +21 -0
  35. package/dist/runtime-api.js +10 -10
  36. package/dist/runtime-setter-api.js +1 -1
  37. package/dist/{send-D9Ydnn4o.js → send-DhJVN30g.js} +38 -85
  38. package/dist/{send-api-W33lI2nR.js → send-api-CpHwbor_.js} +38 -32
  39. package/dist/{setup-finalize-DBf1jjBj.js → setup-finalize-9XzKdFq6.js} +3 -3
  40. package/dist/setup-plugin-api.js +1 -1
  41. package/dist/{setup-surface-CiKGdNJv.js → setup-surface-Kv3mPki2.js} +3 -3
  42. package/dist/{setup-core-3aSOoK2T.js → shared-CuqHFnKd.js} +78 -56
  43. package/dist/{session-mxWFn0eS.js → socket-close-BoSHhQNJ.js} +226 -7
  44. package/dist/{targets-runtime-Bsh0HR9y.js → targets-runtime-CKFrhoX4.js} +142 -29
  45. package/dist/text-runtime-CU_IlbK5.js +4 -0
  46. package/node_modules/@protobufjs/utf8/CHANGELOG.md +8 -0
  47. package/node_modules/@protobufjs/utf8/index.js +29 -18
  48. package/node_modules/@protobufjs/utf8/package.json +2 -2
  49. package/node_modules/@protobufjs/utf8/tests/index.js +5 -0
  50. package/node_modules/@thi.ng/bitstream/package.json +4 -4
  51. package/node_modules/@thi.ng/errors/package.json +4 -4
  52. package/node_modules/@types/node/README.md +1 -1
  53. package/node_modules/@types/node/fs/promises.d.ts +16 -6
  54. package/node_modules/@types/node/package.json +2 -2
  55. package/node_modules/audio-type/package.json +3 -2
  56. package/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -1
  57. package/node_modules/lru-cache/dist/commonjs/browser/index.js +11 -4
  58. package/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -1
  59. package/node_modules/lru-cache/dist/commonjs/browser/index.min.js +1 -1
  60. package/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +3 -3
  61. package/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
  62. package/node_modules/lru-cache/dist/commonjs/index.js +11 -4
  63. package/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
  64. package/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
  65. package/node_modules/lru-cache/dist/commonjs/index.min.js.map +3 -3
  66. package/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -1
  67. package/node_modules/lru-cache/dist/commonjs/node/index.js +11 -4
  68. package/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -1
  69. package/node_modules/lru-cache/dist/commonjs/node/index.min.js +1 -1
  70. package/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +3 -3
  71. package/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -1
  72. package/node_modules/lru-cache/dist/esm/browser/index.js +11 -4
  73. package/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -1
  74. package/node_modules/lru-cache/dist/esm/browser/index.min.js +1 -1
  75. package/node_modules/lru-cache/dist/esm/browser/index.min.js.map +3 -3
  76. package/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
  77. package/node_modules/lru-cache/dist/esm/index.js +11 -4
  78. package/node_modules/lru-cache/dist/esm/index.js.map +1 -1
  79. package/node_modules/lru-cache/dist/esm/index.min.js +1 -1
  80. package/node_modules/lru-cache/dist/esm/index.min.js.map +3 -3
  81. package/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -1
  82. package/node_modules/lru-cache/dist/esm/node/index.js +11 -4
  83. package/node_modules/lru-cache/dist/esm/node/index.js.map +1 -1
  84. package/node_modules/lru-cache/dist/esm/node/index.min.js +1 -1
  85. package/node_modules/lru-cache/dist/esm/node/index.min.js.map +3 -3
  86. package/node_modules/lru-cache/package.json +1 -1
  87. package/node_modules/music-metadata/lib/common/Util.d.ts +1 -1
  88. package/node_modules/music-metadata/lib/common/Util.js +7 -1
  89. package/node_modules/music-metadata/lib/id3v2/FrameHeader.js +23 -3
  90. package/node_modules/music-metadata/lib/id3v2/FrameParser.js +2 -2
  91. package/node_modules/music-metadata/lib/id3v2/ID3v2Token.js +1 -1
  92. package/node_modules/music-metadata/lib/mp4/AtomToken.js +38 -13
  93. package/node_modules/music-metadata/lib/mpeg/MpegParser.js +1 -1
  94. package/node_modules/music-metadata/package.json +6 -6
  95. package/node_modules/p-queue/dist/index.js +17 -6
  96. package/node_modules/p-queue/package.json +1 -1
  97. package/node_modules/protobufjs/dist/light/protobuf.js +145 -188
  98. package/node_modules/protobufjs/dist/light/protobuf.js.map +1 -1
  99. package/node_modules/protobufjs/dist/light/protobuf.min.js +3 -3
  100. package/node_modules/protobufjs/dist/light/protobuf.min.js.map +1 -1
  101. package/node_modules/protobufjs/dist/minimal/protobuf.js +33 -76
  102. package/node_modules/protobufjs/dist/minimal/protobuf.js.map +1 -1
  103. package/node_modules/protobufjs/dist/minimal/protobuf.min.js +3 -3
  104. package/node_modules/protobufjs/dist/minimal/protobuf.min.js.map +1 -1
  105. package/node_modules/protobufjs/dist/protobuf.js +168 -208
  106. package/node_modules/protobufjs/dist/protobuf.js.map +1 -1
  107. package/node_modules/protobufjs/dist/protobuf.min.js +3 -3
  108. package/node_modules/protobufjs/dist/protobuf.min.js.map +1 -1
  109. package/node_modules/protobufjs/index.d.ts +0 -8
  110. package/node_modules/protobufjs/package.json +2 -3
  111. package/node_modules/protobufjs/src/parse.js +3 -0
  112. package/node_modules/protobufjs/src/util/minimal.js +0 -3
  113. package/node_modules/typebox/build/compile/validator.d.mts +1 -4
  114. package/node_modules/typebox/build/guard/guard.d.mts +1 -1
  115. package/node_modules/typebox/build/guard/guard.mjs +5 -2
  116. package/node_modules/typebox/build/guard/string.mjs +16 -4
  117. package/node_modules/typebox/build/schema/engine/_functions.mjs +34 -15
  118. package/node_modules/typebox/build/type/action/readonly_object.d.mts +5 -0
  119. package/node_modules/typebox/build/type/action/readonly_object.mjs +8 -0
  120. package/node_modules/typebox/build/typebox.d.mts +1 -1
  121. package/node_modules/typebox/build/typebox.mjs +1 -1
  122. package/node_modules/typebox/package.json +1 -1
  123. package/node_modules/typebox/readme.md +29 -32
  124. package/node_modules/ws/lib/receiver.js +15 -32
  125. package/node_modules/ws/lib/websocket-server.js +4 -4
  126. package/node_modules/ws/lib/websocket.js +4 -4
  127. package/node_modules/ws/package.json +5 -1
  128. package/npm-shrinkwrap.json +41 -45
  129. package/openclaw.plugin.json +139 -179
  130. package/package.json +21 -11
  131. package/dist/account-config-LiT9wWH9.js +0 -43
  132. package/dist/connection-controller-runtime-context-CbTsqmPT.js +0 -14
  133. package/dist/directory-config-CLBQrjtC.js +0 -37
  134. package/dist/doctor-By3SB4lm.js +0 -48
  135. package/dist/runtime-BfAdAEYT.js +0 -8
  136. package/dist/text-runtime-BTGkMN0o.js +0 -4
  137. package/node_modules/@protobufjs/inquire/CHANGELOG.md +0 -8
  138. package/node_modules/@protobufjs/inquire/LICENSE +0 -26
  139. package/node_modules/@protobufjs/inquire/README.md +0 -13
  140. package/node_modules/@protobufjs/inquire/index.d.ts +0 -10
  141. package/node_modules/@protobufjs/inquire/index.js +0 -38
  142. package/node_modules/@protobufjs/inquire/package.json +0 -21
  143. package/node_modules/@protobufjs/inquire/tests/data/array.js +0 -1
  144. package/node_modules/@protobufjs/inquire/tests/data/emptyArray.js +0 -1
  145. package/node_modules/@protobufjs/inquire/tests/data/emptyObject.js +0 -1
  146. package/node_modules/@protobufjs/inquire/tests/data/object.js +0 -1
  147. package/node_modules/@protobufjs/inquire/tests/index.js +0 -20
@@ -1,8 +1,8 @@
1
- import { n as getWhatsAppRuntime } from "./runtime-BfAdAEYT.js";
2
- import { t as WHATSAPP_CONNECTION_CONTROLLER_CAPABILITY } from "./connection-controller-runtime-context-CbTsqmPT.js";
3
- import { c as logoutWeb, f as readWebAuthExistsForDecision, r as WhatsAppAuthUnstableError } from "./auth-store-BQi9pZqQ.js";
4
- import { c as resolveComparableIdentity } from "./identity-D7L8LCuH.js";
5
- import { i as DEFAULT_WHATSAPP_SOCKET_TIMING, r as waitForWaConnection, t as createWaSocket } from "./session-mxWFn0eS.js";
1
+ import { r as getWhatsAppChannelRuntime } from "./runtime-BLlToOi6.js";
2
+ import { n as WHATSAPP_CONNECTION_OWNER_PENDING_CAPABILITY, t as WHATSAPP_CONNECTION_CONTROLLER_CAPABILITY } from "./connection-controller-runtime-context-DG0tuYuw.js";
3
+ import { c as logoutWeb, f as readWebAuthExistsForDecision, r as WhatsAppAuthUnstableError, w as waitForCredsSaveQueueWithTimeout } from "./auth-store-DirYMTbo.js";
4
+ import { c as resolveComparableIdentity } from "./identity-BM4_wZeM.js";
5
+ import { a as waitForWaConnection, m as acquireWhatsAppGatewayConnectionOwner, o as DEFAULT_WHATSAPP_SOCKET_TIMING, r as createWaSocket, t as closeWhatsAppSocketAndWait } from "./socket-close-BoSHhQNJ.js";
6
6
  import { n as getStatusCode, t as formatError } from "./session-errors-JuazczrA.js";
7
7
  import { computeBackoff, info, sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
8
8
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
@@ -18,16 +18,14 @@ const DEFAULT_RECONNECT_POLICY = {
18
18
  maxAttempts: 12
19
19
  };
20
20
  function resolveHeartbeatSeconds(cfg, overrideSeconds) {
21
- const candidate = overrideSeconds ?? cfg.web?.heartbeatSeconds;
21
+ const candidate = overrideSeconds;
22
22
  if (typeof candidate === "number" && candidate > 0) return candidate;
23
23
  return DEFAULT_HEARTBEAT_SECONDS;
24
24
  }
25
25
  function resolveReconnectPolicy(cfg, overrides) {
26
- const reconnectOverrides = cfg.web?.reconnect ?? {};
27
26
  const overrideConfig = overrides ?? {};
28
27
  const merged = {
29
28
  ...DEFAULT_RECONNECT_POLICY,
30
- ...reconnectOverrides,
31
29
  ...overrideConfig
32
30
  };
33
31
  merged.initialMs = Math.max(250, merged.initialMs);
@@ -89,17 +87,28 @@ function createLiveConnection(params) {
89
87
  openedAfterRecentInbound: params.openedAfterRecentInbound,
90
88
  backgroundTasks: /* @__PURE__ */ new Set(),
91
89
  closePromise,
92
- resolveClose: resolveClosePromise
90
+ resolveClose: resolveClosePromise,
91
+ socketClosed: false
93
92
  };
94
93
  }
94
+ async function closeWebSocketBestEffort(sock) {
95
+ try {
96
+ await sock.ws?.close?.();
97
+ } catch {}
98
+ }
95
99
  function closeWaSocket(sock) {
96
100
  try {
97
101
  if (typeof sock?.end === "function") {
98
- sock.end(/* @__PURE__ */ new Error("OpenClaw WhatsApp socket close"));
102
+ Promise.resolve(sock.end(/* @__PURE__ */ new Error("OpenClaw WhatsApp socket close"))).catch(async () => await closeWebSocketBestEffort(sock));
99
103
  return;
100
104
  }
101
- sock?.ws?.close?.();
102
- } catch {}
105
+ if (sock) closeWebSocketBestEffort(sock);
106
+ } catch {
107
+ if (sock) closeWebSocketBestEffort(sock);
108
+ }
109
+ }
110
+ function stoppedControllerError() {
111
+ return /* @__PURE__ */ new Error("WhatsApp connection controller is shutting down");
103
112
  }
104
113
  function closeWaSocketSoon(sock, delayMs = 500) {
105
114
  setTimeout(() => {
@@ -229,6 +238,15 @@ var WhatsAppConnectionController = class {
229
238
  this.disconnectRetryController = new AbortController();
230
239
  this.current = null;
231
240
  this.runtimeContextLease = null;
241
+ this.pendingOwnerContextLease = null;
242
+ this.connectionOwnerLease = null;
243
+ this.retainedOwnerReleaseLease = null;
244
+ this.connectionOwnerLeasePromise = null;
245
+ this.connectionSetupPromise = null;
246
+ this.pendingSocketCleanup = null;
247
+ this.shuttingDown = false;
248
+ this.ownerAcquireAbortController = new AbortController();
249
+ this.setupAbortController = new AbortController();
232
250
  this.reconnectAttempts = 0;
233
251
  this.lastHandledInboundAt = null;
234
252
  this.accountId = params.accountId;
@@ -249,11 +267,17 @@ var WhatsAppConnectionController = class {
249
267
  ...params.socketTiming
250
268
  };
251
269
  this.socketRef = { current: null };
252
- this.abortPromise = params.abortSignal && new Promise((resolve) => {
253
- params.abortSignal?.addEventListener("abort", () => resolve("aborted"), { once: true });
270
+ const abortSignal = params.abortSignal;
271
+ if (abortSignal) this.abortPromise = new Promise((resolve) => {
272
+ const stop = () => {
273
+ resolve("aborted");
274
+ this.stopDisconnectRetries();
275
+ this.ownerAcquireAbortController.abort(abortSignal.reason);
276
+ this.setupAbortController.abort(abortSignal.reason);
277
+ };
278
+ if (abortSignal.aborted) stop();
279
+ else abortSignal.addEventListener("abort", stop, { once: true });
254
280
  });
255
- if (params.abortSignal?.aborted) this.stopDisconnectRetries();
256
- else params.abortSignal?.addEventListener("abort", () => this.stopDisconnectRetries(), { once: true });
257
281
  }
258
282
  getActiveListener() {
259
283
  return this.current?.listener ?? null;
@@ -322,7 +346,23 @@ var WhatsAppConnectionController = class {
322
346
  this.current.unregisterUnhandled = unregister;
323
347
  }
324
348
  async openConnection(params) {
349
+ if (this.shuttingDown) throw stoppedControllerError();
350
+ if (this.connectionSetupPromise) throw new Error("WhatsApp connection setup is already in progress");
351
+ const setupPromise = this.openConnectionOwned(params);
352
+ this.connectionSetupPromise = setupPromise;
353
+ try {
354
+ return await setupPromise;
355
+ } finally {
356
+ if (this.connectionSetupPromise === setupPromise) this.connectionSetupPromise = null;
357
+ }
358
+ }
359
+ async openConnectionOwned(params) {
360
+ await this.ensureConnectionOwnership();
361
+ this.throwIfSetupStopped();
362
+ await this.finishPendingSocketCleanup();
363
+ this.throwIfSetupStopped();
325
364
  if (this.current) await this.closeCurrentConnection();
365
+ this.throwIfSetupStopped();
326
366
  let sock = null;
327
367
  let connection = null;
328
368
  try {
@@ -332,8 +372,9 @@ var WhatsAppConnectionController = class {
332
372
  ...params.getMessage ? { getMessage: params.getMessage } : {},
333
373
  ...params.cachedGroupMetadata ? { cachedGroupMetadata: params.cachedGroupMetadata } : {}
334
374
  });
335
- await waitForWaConnection(sock, { timeoutMs: this.socketTiming.connectTimeoutMs });
336
- const channelRuntime = getWhatsAppRuntime().channel;
375
+ this.throwIfSetupStopped();
376
+ await this.waitForSetupStep(waitForWaConnection(sock, { timeoutMs: this.socketTiming.connectTimeoutMs }));
377
+ this.throwIfSetupStopped();
337
378
  this.socketRef.current = sock;
338
379
  connection = createLiveConnection({
339
380
  connectionId: params.connectionId,
@@ -341,16 +382,24 @@ var WhatsAppConnectionController = class {
341
382
  listener: {},
342
383
  openedAfterRecentInbound: this.isOpeningAfterRecentInbound()
343
384
  });
344
- const listener = await params.createListener({
385
+ const listenerTask = params.createListener({
345
386
  sock,
346
387
  connection
347
388
  });
389
+ let listener;
390
+ try {
391
+ listener = await this.waitForSetupStep(listenerTask);
392
+ } catch (error) {
393
+ if (this.setupAbortController.signal.aborted) listenerTask.then((lateListener) => lateListener.close?.()).catch(() => {});
394
+ throw error;
395
+ }
348
396
  connection.listener = listener;
397
+ this.throwIfSetupStopped();
349
398
  this.current = connection;
350
399
  connection.unregisterTransportActivity = this.attachTransportActivityListener(sock);
351
400
  const previousRuntimeContextLease = this.runtimeContextLease;
352
401
  this.runtimeContextLease = registerChannelRuntimeContext({
353
- channelRuntime,
402
+ channelRuntime: getWhatsAppChannelRuntime(),
354
403
  channelId: "whatsapp",
355
404
  accountId: this.accountId,
356
405
  capability: WHATSAPP_CONNECTION_CONTROLLER_CAPABILITY,
@@ -358,16 +407,38 @@ var WhatsAppConnectionController = class {
358
407
  abortSignal: this.abortSignal
359
408
  });
360
409
  previousRuntimeContextLease?.dispose();
410
+ this.pendingOwnerContextLease?.dispose();
411
+ this.pendingOwnerContextLease = null;
361
412
  this.startTimers(connection, {
362
413
  onHeartbeat: params.onHeartbeat,
363
414
  onWatchdogTimeout: params.onWatchdogTimeout
364
415
  });
365
416
  return connection;
366
417
  } catch (err) {
367
- if (this.socketRef.current === sock) this.socketRef.current = null;
368
- closeWaSocket(sock);
369
- if (connection?.unregisterUnhandled) connection.unregisterUnhandled();
370
- connection?.unregisterTransportActivity?.();
418
+ try {
419
+ if (connection && this.current === connection) await this.closeCurrentConnection();
420
+ else {
421
+ try {
422
+ await connection?.listener.close?.();
423
+ } catch {}
424
+ if (this.socketRef.current === sock) this.socketRef.current = null;
425
+ if (sock) {
426
+ const cleanup = connection ?? {
427
+ sock,
428
+ socketClosed: false
429
+ };
430
+ await this.finishSocketCleanup(cleanup);
431
+ }
432
+ if (connection?.unregisterUnhandled) connection.unregisterUnhandled();
433
+ connection?.unregisterTransportActivity?.();
434
+ }
435
+ } catch (closeError) {
436
+ if (sock && (!connection || this.current !== connection)) this.pendingSocketCleanup = connection ?? {
437
+ sock,
438
+ socketClosed: false
439
+ };
440
+ throw new AggregateError([err, closeError], "WhatsApp connection setup and close failed", { cause: err });
441
+ }
371
442
  throw err;
372
443
  }
373
444
  }
@@ -460,6 +531,7 @@ var WhatsAppConnectionController = class {
460
531
  const connection = this.current;
461
532
  if (!connection) return;
462
533
  this.current = null;
534
+ this.pendingSocketCleanup = connection;
463
535
  if (this.socketRef.current === connection.sock) this.socketRef.current = null;
464
536
  connection.unregisterUnhandled?.();
465
537
  connection.unregisterTransportActivity?.();
@@ -472,16 +544,103 @@ var WhatsAppConnectionController = class {
472
544
  try {
473
545
  await connection.listener.close?.();
474
546
  } catch {}
475
- closeWaSocket(connection.sock);
547
+ await this.finishSocketCleanup(connection);
548
+ if (this.pendingSocketCleanup === connection) this.pendingSocketCleanup = null;
476
549
  }
477
550
  async waitBeforeRetry(delayMs) {
478
551
  await this.sleep(delayMs, this.abortSignal);
479
552
  }
480
553
  async shutdown() {
554
+ this.shuttingDown = true;
555
+ const stoppedError = stoppedControllerError();
556
+ this.ownerAcquireAbortController.abort(stoppedError);
557
+ this.setupAbortController.abort(stoppedError);
481
558
  this.stopDisconnectRetries();
559
+ await this.connectionSetupPromise?.catch(() => {});
560
+ await this.connectionOwnerLeasePromise?.catch(() => {});
482
561
  await this.closeCurrentConnection();
483
- this.runtimeContextLease?.dispose();
484
- this.runtimeContextLease = null;
562
+ await this.finishPendingSocketCleanup();
563
+ try {
564
+ this.runtimeContextLease?.dispose();
565
+ } finally {
566
+ this.runtimeContextLease = null;
567
+ try {
568
+ this.pendingOwnerContextLease?.dispose();
569
+ } finally {
570
+ this.pendingOwnerContextLease = null;
571
+ if (this.connectionOwnerLease) {
572
+ await this.connectionOwnerLease.release();
573
+ this.connectionOwnerLease = null;
574
+ }
575
+ if (this.retainedOwnerReleaseLease) {
576
+ await this.retainedOwnerReleaseLease.release();
577
+ this.retainedOwnerReleaseLease = null;
578
+ }
579
+ }
580
+ }
581
+ }
582
+ async finishSocketCleanup(cleanup) {
583
+ if (!cleanup.socketClosed) {
584
+ await closeWhatsAppSocketAndWait(cleanup.sock, "OpenClaw WhatsApp socket close");
585
+ cleanup.socketClosed = true;
586
+ }
587
+ if (await waitForCredsSaveQueueWithTimeout(this.authDir) === "timed_out") throw new Error("WhatsApp credential persistence did not drain before owner release");
588
+ }
589
+ async finishPendingSocketCleanup() {
590
+ const cleanup = this.pendingSocketCleanup;
591
+ if (!cleanup) return;
592
+ await this.finishSocketCleanup(cleanup);
593
+ if (this.pendingSocketCleanup === cleanup) this.pendingSocketCleanup = null;
594
+ }
595
+ throwIfSetupStopped() {
596
+ if (this.shuttingDown || this.setupAbortController.signal.aborted) throw stoppedControllerError();
597
+ }
598
+ async waitForSetupStep(task) {
599
+ this.throwIfSetupStopped();
600
+ const signal = this.setupAbortController.signal;
601
+ let onAbort;
602
+ const aborted = new Promise((_resolve, reject) => {
603
+ onAbort = () => reject(stoppedControllerError());
604
+ signal.addEventListener("abort", onAbort, { once: true });
605
+ });
606
+ try {
607
+ return await Promise.race([task, aborted]);
608
+ } finally {
609
+ if (onAbort) signal.removeEventListener("abort", onAbort);
610
+ }
611
+ }
612
+ async ensureConnectionOwnership() {
613
+ if (this.retainedOwnerReleaseLease) {
614
+ await this.retainedOwnerReleaseLease.release();
615
+ this.retainedOwnerReleaseLease = null;
616
+ }
617
+ if (this.connectionOwnerLease) return;
618
+ if (!this.connectionOwnerLeasePromise) this.connectionOwnerLeasePromise = (async () => {
619
+ const ownerLease = await acquireWhatsAppGatewayConnectionOwner(this.authDir, this.ownerAcquireAbortController.signal);
620
+ try {
621
+ if (this.shuttingDown) throw stoppedControllerError();
622
+ this.pendingOwnerContextLease = registerChannelRuntimeContext({
623
+ channelRuntime: getWhatsAppChannelRuntime(),
624
+ channelId: "whatsapp",
625
+ accountId: this.accountId,
626
+ capability: WHATSAPP_CONNECTION_OWNER_PENDING_CAPABILITY,
627
+ context: true,
628
+ abortSignal: this.abortSignal
629
+ });
630
+ this.connectionOwnerLease = ownerLease;
631
+ } catch (error) {
632
+ try {
633
+ await ownerLease.release();
634
+ } catch (releaseError) {
635
+ this.retainedOwnerReleaseLease = ownerLease;
636
+ throw new AggregateError([error, releaseError], "WhatsApp connection ownership setup and release failed", { cause: error });
637
+ }
638
+ throw error;
639
+ }
640
+ })().finally(() => {
641
+ this.connectionOwnerLeasePromise = null;
642
+ });
643
+ await this.connectionOwnerLeasePromise;
485
644
  }
486
645
  startTimers(connection, hooks) {
487
646
  if (!this.keepAlive) return;
@@ -0,0 +1,23 @@
1
+ import { t as getOptionalWhatsAppChannelRuntime } from "./runtime-BLlToOi6.js";
2
+ import { getChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
3
+ //#region extensions/whatsapp/src/connection-controller-runtime-context.ts
4
+ const WHATSAPP_CONNECTION_CONTROLLER_CAPABILITY = "connection-controller";
5
+ const WHATSAPP_CONNECTION_OWNER_PENDING_CAPABILITY = "connection-owner-pending";
6
+ function getWhatsAppConnectionController(accountId) {
7
+ return getChannelRuntimeContext({
8
+ channelRuntime: getOptionalWhatsAppChannelRuntime() ?? void 0,
9
+ channelId: "whatsapp",
10
+ accountId,
11
+ capability: "connection-controller"
12
+ }) ?? null;
13
+ }
14
+ function hasPendingWhatsAppConnectionOwner(accountId) {
15
+ return Boolean(getChannelRuntimeContext({
16
+ channelRuntime: getOptionalWhatsAppChannelRuntime() ?? void 0,
17
+ channelId: "whatsapp",
18
+ accountId,
19
+ capability: WHATSAPP_CONNECTION_OWNER_PENDING_CAPABILITY
20
+ }));
21
+ }
22
+ //#endregion
23
+ export { hasPendingWhatsAppConnectionOwner as i, WHATSAPP_CONNECTION_OWNER_PENDING_CAPABILITY as n, getWhatsAppConnectionController as r, WHATSAPP_CONNECTION_CONTROLLER_CAPABILITY as t };
@@ -2,7 +2,7 @@ import { c as normalizeWhatsAppTarget$1, t as isWhatsAppGroupJid$1 } from "./nor
2
2
  import { t as whatsappCommandPolicy$1 } from "./command-policy-BIOSHySD.js";
3
3
  import { t as resolveLegacyGroupSessionKey$1 } from "./group-session-contract-DDnZSsJ1.js";
4
4
  import { r as isLegacyGroupSessionKey$1, t as canonicalizeLegacySessionKey$1 } from "./session-contract-DMTm6Q_L.js";
5
- import { a as resolveWhatsAppRuntimeGroupPolicy$1, n as testing } from "./access-control-DazDX3RB.js";
5
+ import { a as resolveWhatsAppRuntimeGroupPolicy$1, n as testing } from "./access-control-CRz_v6bR.js";
6
6
  //#region extensions/whatsapp/contract-api.ts
7
7
  const canonicalizeLegacySessionKey = canonicalizeLegacySessionKey$1;
8
8
  const isLegacyGroupSessionKey = isLegacyGroupSessionKey$1;
@@ -0,0 +1,196 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { c as resolveMergedWhatsAppAccountConfig, o as resolveWhatsAppAuthDir } from "./accounts-CxPuEFi-.js";
3
+ import { c as normalizeWhatsAppTarget, t as isWhatsAppGroupJid } from "./normalize-target-DSZnHkKF.js";
4
+ import { i as hasPendingWhatsAppConnectionOwner, r as getWhatsAppConnectionController } from "./connection-controller-runtime-context-DG0tuYuw.js";
5
+ import "./normalize-C-Z16y7Q.js";
6
+ import { f as readWebAuthExistsForDecision, w as waitForCredsSaveQueueWithTimeout } from "./auth-store-DirYMTbo.js";
7
+ import { n as resolveWebAccountId } from "./active-listener-BEjWsCNM.js";
8
+ import { a as waitForWaConnection, h as acquireWhatsAppStandaloneConnectionOwner, n as createWaDirectorySocket, p as WhatsAppConnectionOwnerBusyError, t as closeWhatsAppSocketAndWait } from "./socket-close-BoSHhQNJ.js";
9
+ import { listResolvedDirectoryGroupEntriesFromMapKeys, listResolvedDirectoryUserEntriesFromAllowFrom } from "openclaw/plugin-sdk/directory-config-runtime";
10
+ //#region extensions/whatsapp/src/directory-config.ts
11
+ var directory_config_exports = /* @__PURE__ */ __exportAll({
12
+ WHATSAPP_DIRECTORY_UNAVAILABLE_CODE: () => WHATSAPP_DIRECTORY_UNAVAILABLE_CODE,
13
+ WhatsAppDirectoryUnavailableError: () => WhatsAppDirectoryUnavailableError,
14
+ listWhatsAppDirectoryGroupsFromConfig: () => listWhatsAppDirectoryGroupsFromConfig,
15
+ listWhatsAppDirectoryGroupsLive: () => listWhatsAppDirectoryGroupsLive,
16
+ listWhatsAppDirectoryPeersFromConfig: () => listWhatsAppDirectoryPeersFromConfig
17
+ });
18
+ function resolveWhatsAppDirectoryAccount(cfg, accountId) {
19
+ return resolveMergedWhatsAppAccountConfig({
20
+ cfg,
21
+ accountId
22
+ });
23
+ }
24
+ async function listWhatsAppDirectoryPeersFromConfig(params) {
25
+ return listResolvedDirectoryUserEntriesFromAllowFrom({
26
+ ...params,
27
+ resolveAccount: resolveWhatsAppDirectoryAccount,
28
+ resolveAllowFrom: (account) => account.allowFrom,
29
+ normalizeId: (entry) => {
30
+ const normalized = normalizeWhatsAppTarget(entry);
31
+ if (!normalized || isWhatsAppGroupJid(normalized)) return null;
32
+ return normalized;
33
+ }
34
+ });
35
+ }
36
+ async function listWhatsAppDirectoryGroupsFromConfig(params) {
37
+ return listResolvedDirectoryGroupEntriesFromMapKeys({
38
+ ...params,
39
+ resolveAccount: resolveWhatsAppDirectoryAccount,
40
+ resolveGroups: (account) => account.groups
41
+ });
42
+ }
43
+ const WHATSAPP_DIRECTORY_UNAVAILABLE_CODE = "whatsapp_directory_unavailable";
44
+ var WhatsAppDirectoryUnavailableError = class extends Error {
45
+ constructor(reason, message, options) {
46
+ super(message, options);
47
+ this.reason = reason;
48
+ this.code = WHATSAPP_DIRECTORY_UNAVAILABLE_CODE;
49
+ this.name = "WhatsAppDirectoryUnavailableError";
50
+ }
51
+ };
52
+ async function fetchLiveGroups(sock, params) {
53
+ const groups = await sock.groupFetchAllParticipating();
54
+ const query = params.query?.trim().toLowerCase() ?? "";
55
+ const limit = typeof params.limit === "number" && params.limit > 0 ? params.limit : void 0;
56
+ const entries = Object.entries(groups).map(([jid, metadata]) => ({
57
+ kind: "group",
58
+ id: jid,
59
+ name: metadata?.subject?.trim() || void 0
60
+ })).filter((entry) => {
61
+ if (!query) return true;
62
+ return entry.id.toLowerCase().includes(query) || entry.name?.toLowerCase().includes(query);
63
+ }).toSorted((left, right) => left.id.localeCompare(right.id));
64
+ return limit ? entries.slice(0, limit) : entries;
65
+ }
66
+ function unavailable(reason, message, cause) {
67
+ return new WhatsAppDirectoryUnavailableError(reason, message, cause ? { cause } : void 0);
68
+ }
69
+ const pendingStandaloneCleanups = /* @__PURE__ */ new Map();
70
+ const STANDALONE_CLEANUP_RETRY_MS = 1e3;
71
+ async function completeStandaloneCleanup(cleanup) {
72
+ if (cleanup.sock && !cleanup.socketClosed) {
73
+ await closeWhatsAppSocketAndWait(cleanup.sock, "OpenClaw WhatsApp standalone directory socket close");
74
+ cleanup.socketClosed = true;
75
+ }
76
+ if (cleanup.sock) {
77
+ if (await waitForCredsSaveQueueWithTimeout(cleanup.authDir) === "timed_out") throw new Error("WhatsApp credential persistence did not drain before socket release");
78
+ }
79
+ await cleanup.ownerLease.release();
80
+ if (pendingStandaloneCleanups.get(cleanup.authDir) === cleanup) pendingStandaloneCleanups.delete(cleanup.authDir);
81
+ if (cleanup.retryTimer) {
82
+ clearTimeout(cleanup.retryTimer);
83
+ cleanup.retryTimer = null;
84
+ }
85
+ }
86
+ function runStandaloneCleanup(cleanup) {
87
+ if (cleanup.inFlight) return cleanup.inFlight;
88
+ const task = completeStandaloneCleanup(cleanup).finally(() => {
89
+ if (cleanup.inFlight === task) cleanup.inFlight = null;
90
+ });
91
+ cleanup.inFlight = task;
92
+ return task;
93
+ }
94
+ function scheduleStandaloneCleanupRetry(cleanup) {
95
+ if (cleanup.retryTimer) return;
96
+ cleanup.retryTimer = setTimeout(() => {
97
+ cleanup.retryTimer = null;
98
+ runStandaloneCleanup(cleanup).catch(() => {
99
+ scheduleStandaloneCleanupRetry(cleanup);
100
+ });
101
+ }, STANDALONE_CLEANUP_RETRY_MS);
102
+ cleanup.retryTimer.unref?.();
103
+ }
104
+ function retainStandaloneCleanup(cleanup) {
105
+ pendingStandaloneCleanups.set(cleanup.authDir, cleanup);
106
+ scheduleStandaloneCleanupRetry(cleanup);
107
+ }
108
+ async function finishStandaloneCleanupOrThrow(cleanup, operationError) {
109
+ try {
110
+ await runStandaloneCleanup(cleanup);
111
+ } catch (cleanupError) {
112
+ retainStandaloneCleanup(cleanup);
113
+ throw cleanupUnavailable(operationError === void 0 ? cleanupError : new AggregateError([operationError, cleanupError], "WhatsApp live group lookup and cleanup failed", { cause: operationError }));
114
+ }
115
+ }
116
+ function cleanupUnavailable(error) {
117
+ return unavailable("cleanup_failed", "WhatsApp live group lookup could not safely close its standalone connection.", error);
118
+ }
119
+ async function finishPriorStandaloneCleanup(authDir) {
120
+ const cleanup = pendingStandaloneCleanups.get(authDir);
121
+ if (!cleanup) return;
122
+ try {
123
+ await runStandaloneCleanup(cleanup);
124
+ } catch (error) {
125
+ scheduleStandaloneCleanupRetry(cleanup);
126
+ throw cleanupUnavailable(error);
127
+ }
128
+ }
129
+ async function listGroupsThroughStandaloneOwner(params) {
130
+ const account = resolveWhatsAppDirectoryAccount(params.cfg, params.accountId);
131
+ const authDir = resolveWhatsAppAuthDir({
132
+ cfg: params.cfg,
133
+ accountId: account.accountId
134
+ }).authDir;
135
+ await finishPriorStandaloneCleanup(authDir);
136
+ let ownerLease;
137
+ try {
138
+ ownerLease = await acquireWhatsAppStandaloneConnectionOwner(authDir);
139
+ } catch (error) {
140
+ if (error instanceof WhatsAppConnectionOwnerBusyError) throw unavailable("connection_owner_busy", "WhatsApp live groups are unavailable because the account is owned by another process.", error);
141
+ throw unavailable("connection_failed", "WhatsApp live groups are unavailable because connection ownership failed.", error);
142
+ }
143
+ const cleanup = {
144
+ authDir,
145
+ inFlight: null,
146
+ ownerLease,
147
+ retryTimer: null,
148
+ sock: null,
149
+ socketClosed: true
150
+ };
151
+ let groups;
152
+ try {
153
+ let authState;
154
+ try {
155
+ authState = await readWebAuthExistsForDecision(authDir);
156
+ } catch (error) {
157
+ throw unavailable("auth_unstable", "WhatsApp live groups are unavailable because linked credentials could not be read.", error);
158
+ }
159
+ if (authState.outcome === "unstable") throw unavailable("auth_unstable", "WhatsApp live groups are unavailable while linked credentials are changing.");
160
+ if (!authState.exists) throw unavailable("not_linked", "WhatsApp live groups are unavailable because this account is not linked.");
161
+ try {
162
+ cleanup.sock = await createWaDirectorySocket(authDir);
163
+ cleanup.socketClosed = false;
164
+ await waitForWaConnection(cleanup.sock, { timeoutMs: 3e4 });
165
+ } catch (error) {
166
+ throw unavailable("connection_failed", "WhatsApp live groups are unavailable because the standalone connection failed.", error);
167
+ }
168
+ try {
169
+ groups = await fetchLiveGroups(cleanup.sock, params);
170
+ } catch (error) {
171
+ throw unavailable("lookup_failed", "WhatsApp live group lookup failed.", error);
172
+ }
173
+ } catch (error) {
174
+ await finishStandaloneCleanupOrThrow(cleanup, error);
175
+ throw error;
176
+ }
177
+ await finishStandaloneCleanupOrThrow(cleanup);
178
+ return groups;
179
+ }
180
+ async function listWhatsAppDirectoryGroupsLive(params) {
181
+ const accountId = resolveWebAccountId({
182
+ cfg: params.cfg,
183
+ accountId: params.accountId
184
+ });
185
+ const controller = getWhatsAppConnectionController(accountId);
186
+ if (!controller && !hasPendingWhatsAppConnectionOwner(accountId)) return await listGroupsThroughStandaloneOwner(params);
187
+ const sock = controller?.getCurrentSock();
188
+ if (!sock) throw unavailable("active_owner_unavailable", "WhatsApp live groups are unavailable while the gateway connection is offline.");
189
+ try {
190
+ return await fetchLiveGroups(sock, params);
191
+ } catch (error) {
192
+ throw unavailable("lookup_failed", "WhatsApp live group lookup failed.", error);
193
+ }
194
+ }
195
+ //#endregion
196
+ export { listWhatsAppDirectoryGroupsFromConfig as n, listWhatsAppDirectoryPeersFromConfig as r, directory_config_exports as t };
@@ -1,4 +1,4 @@
1
- import { n as listWhatsAppDirectoryGroupsFromConfig, r as listWhatsAppDirectoryPeersFromConfig } from "./directory-config-CLBQrjtC.js";
1
+ import { n as listWhatsAppDirectoryGroupsFromConfig, r as listWhatsAppDirectoryPeersFromConfig } from "./directory-config-BTbJkV1W.js";
2
2
  //#region extensions/whatsapp/directory-contract-api.ts
3
3
  const whatsappDirectoryContractPlugin = {
4
4
  id: "whatsapp",
@@ -0,0 +1,10 @@
1
+ //#region extensions/whatsapp/src/doctor.ts
2
+ function normalizeCompatibilityConfig({ cfg }) {
3
+ return {
4
+ config: cfg,
5
+ changes: []
6
+ };
7
+ }
8
+ const whatsappDoctor = { normalizeCompatibilityConfig };
9
+ //#endregion
10
+ export { whatsappDoctor as n, normalizeCompatibilityConfig as t };