@tencent-weixin/openclaw-weixin 2.3.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/index.js +16 -0
  2. package/dist/index.js.map +1 -0
  3. package/dist/src/api/api.js +374 -0
  4. package/dist/src/api/api.js.map +1 -0
  5. package/dist/src/api/config-cache.js +64 -0
  6. package/dist/src/api/config-cache.js.map +1 -0
  7. package/dist/src/api/session-guard.js +49 -0
  8. package/dist/src/api/session-guard.js.map +1 -0
  9. package/dist/src/api/types.js +35 -0
  10. package/dist/src/api/types.js.map +1 -0
  11. package/dist/src/auth/accounts.js +326 -0
  12. package/dist/src/auth/accounts.js.map +1 -0
  13. package/dist/src/auth/login-qr.js +332 -0
  14. package/dist/src/auth/login-qr.js.map +1 -0
  15. package/dist/src/auth/pairing.js +104 -0
  16. package/dist/src/auth/pairing.js.map +1 -0
  17. package/dist/src/cdn/aes-ecb.js +19 -0
  18. package/dist/src/cdn/aes-ecb.js.map +1 -0
  19. package/dist/src/cdn/cdn-upload.js +73 -0
  20. package/dist/src/cdn/cdn-upload.js.map +1 -0
  21. package/dist/src/cdn/cdn-url.js +14 -0
  22. package/dist/src/cdn/cdn-url.js.map +1 -0
  23. package/dist/src/cdn/pic-decrypt.js +89 -0
  24. package/dist/src/cdn/pic-decrypt.js.map +1 -0
  25. package/dist/src/cdn/upload.js +106 -0
  26. package/dist/src/cdn/upload.js.map +1 -0
  27. package/dist/src/channel.js +460 -0
  28. package/dist/src/channel.js.map +1 -0
  29. package/dist/src/compat.js +67 -0
  30. package/dist/src/compat.js.map +1 -0
  31. package/dist/src/config/config-schema.js +19 -0
  32. package/dist/src/config/config-schema.js.map +1 -0
  33. package/dist/src/media/media-download.js +95 -0
  34. package/dist/src/media/media-download.js.map +1 -0
  35. package/dist/src/media/mime.js +73 -0
  36. package/dist/src/media/mime.js.map +1 -0
  37. package/dist/src/media/silk-transcode.js +64 -0
  38. package/dist/src/media/silk-transcode.js.map +1 -0
  39. package/dist/src/media/voice-outbound.js +177 -0
  40. package/dist/src/media/voice-outbound.js.map +1 -0
  41. package/dist/src/messaging/abort-fence.js +70 -0
  42. package/dist/src/messaging/abort-fence.js.map +1 -0
  43. package/dist/src/messaging/buttons.js +117 -0
  44. package/dist/src/messaging/buttons.js.map +1 -0
  45. package/dist/src/messaging/debug-mode.js +63 -0
  46. package/dist/src/messaging/debug-mode.js.map +1 -0
  47. package/dist/src/messaging/error-notice.js +24 -0
  48. package/dist/src/messaging/error-notice.js.map +1 -0
  49. package/dist/src/messaging/inbound.js +201 -0
  50. package/dist/src/messaging/inbound.js.map +1 -0
  51. package/dist/src/messaging/lane-key.js +66 -0
  52. package/dist/src/messaging/lane-key.js.map +1 -0
  53. package/dist/src/messaging/markdown-filter.js +368 -0
  54. package/dist/src/messaging/markdown-filter.js.map +1 -0
  55. package/dist/src/messaging/merged-record.js +149 -0
  56. package/dist/src/messaging/merged-record.js.map +1 -0
  57. package/dist/src/messaging/model-buttons.js +182 -0
  58. package/dist/src/messaging/model-buttons.js.map +1 -0
  59. package/dist/src/messaging/model-callback-handler.js +133 -0
  60. package/dist/src/messaging/model-callback-handler.js.map +1 -0
  61. package/dist/src/messaging/outbound-hooks.js +56 -0
  62. package/dist/src/messaging/outbound-hooks.js.map +1 -0
  63. package/dist/src/messaging/process-message.js +381 -0
  64. package/dist/src/messaging/process-message.js.map +1 -0
  65. package/dist/src/messaging/send-media.js +54 -0
  66. package/dist/src/messaging/send-media.js.map +1 -0
  67. package/dist/src/messaging/send.js +182 -0
  68. package/dist/src/messaging/send.js.map +1 -0
  69. package/dist/src/messaging/slash-commands.js +70 -0
  70. package/dist/src/messaging/slash-commands.js.map +1 -0
  71. package/dist/src/monitor/lane-scheduler.js +46 -0
  72. package/dist/src/monitor/lane-scheduler.js.map +1 -0
  73. package/dist/src/monitor/monitor.js +143 -0
  74. package/dist/src/monitor/monitor.js.map +1 -0
  75. package/dist/src/runtime.js +54 -0
  76. package/dist/src/runtime.js.map +1 -0
  77. package/dist/src/storage/state-dir.js +9 -0
  78. package/dist/src/storage/state-dir.js.map +1 -0
  79. package/dist/src/storage/sync-buf.js +64 -0
  80. package/dist/src/storage/sync-buf.js.map +1 -0
  81. package/dist/src/streaming/stream-pipeline.js +431 -0
  82. package/dist/src/streaming/stream-pipeline.js.map +1 -0
  83. package/dist/src/streaming/stream-session.js +260 -0
  84. package/dist/src/streaming/stream-session.js.map +1 -0
  85. package/dist/src/streaming/stream.js +239 -0
  86. package/dist/src/streaming/stream.js.map +1 -0
  87. package/dist/src/util/logger.js +120 -0
  88. package/dist/src/util/logger.js.map +1 -0
  89. package/dist/src/util/markdown-fences.js +54 -0
  90. package/dist/src/util/markdown-fences.js.map +1 -0
  91. package/dist/src/util/random.js +16 -0
  92. package/dist/src/util/random.js.map +1 -0
  93. package/dist/src/util/redact.js +54 -0
  94. package/dist/src/util/redact.js.map +1 -0
  95. package/index.ts +0 -5
  96. package/openclaw.plugin.json +11 -1
  97. package/package.json +9 -2
  98. package/src/api/api.ts +2 -3
  99. package/src/auth/accounts.ts +0 -1
  100. package/src/channel.ts +13 -1
  101. package/src/monitor/monitor.ts +11 -10
  102. package/src/runtime.ts +0 -70
@@ -0,0 +1,95 @@
1
+ import { logger } from "../util/logger.js";
2
+ import { getMimeFromFilename } from "./mime.js";
3
+ import { downloadAndDecryptBuffer, downloadPlainCdnBuffer, } from "../cdn/pic-decrypt.js";
4
+ import { silkToWav } from "./silk-transcode.js";
5
+ import { MessageItemType } from "../api/types.js";
6
+ const WEIXIN_MEDIA_MAX_BYTES = 100 * 1024 * 1024;
7
+ /**
8
+ * Download and decrypt media from a single MessageItem.
9
+ * Returns the populated WeixinInboundMediaOpts fields; empty object on unsupported type or failure.
10
+ */
11
+ export async function downloadMediaFromItem(item, deps) {
12
+ const { cdnBaseUrl, saveMedia, log, errLog, label } = deps;
13
+ const result = {};
14
+ if (item.type === MessageItemType.IMAGE) {
15
+ const img = item.image_item;
16
+ if (!img?.media?.encrypt_query_param && !img?.media?.full_url)
17
+ return result;
18
+ const aesKeyBase64 = img.aeskey
19
+ ? Buffer.from(img.aeskey, "hex").toString("base64")
20
+ : img.media.aes_key;
21
+ logger.debug(`${label} image: encrypt_query_param=${(img.media.encrypt_query_param ?? "").slice(0, 40)}... hasAesKey=${Boolean(aesKeyBase64)} aeskeySource=${img.aeskey ? "image_item.aeskey" : "media.aes_key"} full_url=${Boolean(img.media.full_url)}`);
22
+ try {
23
+ const buf = aesKeyBase64
24
+ ? await downloadAndDecryptBuffer(img.media.encrypt_query_param ?? "", aesKeyBase64, cdnBaseUrl, `${label} image`, img.media.full_url)
25
+ : await downloadPlainCdnBuffer(img.media.encrypt_query_param ?? "", cdnBaseUrl, `${label} image-plain`, img.media.full_url);
26
+ const saved = await saveMedia(buf, undefined, "inbound", WEIXIN_MEDIA_MAX_BYTES);
27
+ result.decryptedPicPath = saved.path;
28
+ logger.debug(`${label} image saved: ${saved.path}`);
29
+ }
30
+ catch (err) {
31
+ logger.error(`${label} image download/decrypt failed: ${String(err)}`);
32
+ errLog(`weixin ${label} image download/decrypt failed: ${String(err)}`);
33
+ }
34
+ }
35
+ else if (item.type === MessageItemType.VOICE) {
36
+ const voice = item.voice_item;
37
+ if ((!voice?.media?.encrypt_query_param && !voice?.media?.full_url) || !voice?.media?.aes_key)
38
+ return result;
39
+ try {
40
+ const silkBuf = await downloadAndDecryptBuffer(voice.media.encrypt_query_param ?? "", voice.media.aes_key, cdnBaseUrl, `${label} voice`, voice.media.full_url);
41
+ logger.debug(`${label} voice: decrypted ${silkBuf.length} bytes, attempting silk transcode`);
42
+ const wavBuf = await silkToWav(silkBuf);
43
+ if (wavBuf) {
44
+ const saved = await saveMedia(wavBuf, "audio/wav", "inbound", WEIXIN_MEDIA_MAX_BYTES);
45
+ result.decryptedVoicePath = saved.path;
46
+ result.voiceMediaType = "audio/wav";
47
+ logger.debug(`${label} voice: saved WAV to ${saved.path}`);
48
+ }
49
+ else {
50
+ const saved = await saveMedia(silkBuf, "audio/silk", "inbound", WEIXIN_MEDIA_MAX_BYTES);
51
+ result.decryptedVoicePath = saved.path;
52
+ result.voiceMediaType = "audio/silk";
53
+ logger.debug(`${label} voice: silk transcode unavailable, saved raw SILK to ${saved.path}`);
54
+ }
55
+ }
56
+ catch (err) {
57
+ logger.error(`${label} voice download/transcode failed: ${String(err)}`);
58
+ errLog(`weixin ${label} voice download/transcode failed: ${String(err)}`);
59
+ }
60
+ }
61
+ else if (item.type === MessageItemType.FILE) {
62
+ const fileItem = item.file_item;
63
+ if ((!fileItem?.media?.encrypt_query_param && !fileItem?.media?.full_url) || !fileItem?.media?.aes_key)
64
+ return result;
65
+ try {
66
+ const buf = await downloadAndDecryptBuffer(fileItem.media.encrypt_query_param ?? "", fileItem.media.aes_key, cdnBaseUrl, `${label} file`, fileItem.media.full_url);
67
+ const mime = getMimeFromFilename(fileItem.file_name ?? "file.bin");
68
+ const saved = await saveMedia(buf, mime, "inbound", WEIXIN_MEDIA_MAX_BYTES, fileItem.file_name ?? undefined);
69
+ result.decryptedFilePath = saved.path;
70
+ result.fileMediaType = mime;
71
+ logger.debug(`${label} file: saved to ${saved.path} mime=${mime}`);
72
+ }
73
+ catch (err) {
74
+ logger.error(`${label} file download failed: ${String(err)}`);
75
+ errLog(`weixin ${label} file download failed: ${String(err)}`);
76
+ }
77
+ }
78
+ else if (item.type === MessageItemType.VIDEO) {
79
+ const videoItem = item.video_item;
80
+ if ((!videoItem?.media?.encrypt_query_param && !videoItem?.media?.full_url) || !videoItem?.media?.aes_key)
81
+ return result;
82
+ try {
83
+ const buf = await downloadAndDecryptBuffer(videoItem.media.encrypt_query_param ?? "", videoItem.media.aes_key, cdnBaseUrl, `${label} video`, videoItem.media.full_url);
84
+ const saved = await saveMedia(buf, "video/mp4", "inbound", WEIXIN_MEDIA_MAX_BYTES);
85
+ result.decryptedVideoPath = saved.path;
86
+ logger.debug(`${label} video: saved to ${saved.path}`);
87
+ }
88
+ catch (err) {
89
+ logger.error(`${label} video download failed: ${String(err)}`);
90
+ errLog(`weixin ${label} video download failed: ${String(err)}`);
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+ //# sourceMappingURL=media-download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-download.js","sourceRoot":"","sources":["../../../src/media/media-download.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import path from "node:path";
2
+ const EXTENSION_TO_MIME = {
3
+ ".pdf": "application/pdf",
4
+ ".doc": "application/msword",
5
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
6
+ ".xls": "application/vnd.ms-excel",
7
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
8
+ ".ppt": "application/vnd.ms-powerpoint",
9
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
10
+ ".txt": "text/plain",
11
+ ".csv": "text/csv",
12
+ ".zip": "application/zip",
13
+ ".tar": "application/x-tar",
14
+ ".gz": "application/gzip",
15
+ ".mp3": "audio/mpeg",
16
+ ".ogg": "audio/ogg",
17
+ ".wav": "audio/wav",
18
+ ".mp4": "video/mp4",
19
+ ".mov": "video/quicktime",
20
+ ".webm": "video/webm",
21
+ ".mkv": "video/x-matroska",
22
+ ".avi": "video/x-msvideo",
23
+ ".png": "image/png",
24
+ ".jpg": "image/jpeg",
25
+ ".jpeg": "image/jpeg",
26
+ ".gif": "image/gif",
27
+ ".webp": "image/webp",
28
+ ".bmp": "image/bmp",
29
+ };
30
+ const MIME_TO_EXTENSION = {
31
+ "image/jpeg": ".jpg",
32
+ "image/jpg": ".jpg",
33
+ "image/png": ".png",
34
+ "image/gif": ".gif",
35
+ "image/webp": ".webp",
36
+ "image/bmp": ".bmp",
37
+ "video/mp4": ".mp4",
38
+ "video/quicktime": ".mov",
39
+ "video/webm": ".webm",
40
+ "video/x-matroska": ".mkv",
41
+ "video/x-msvideo": ".avi",
42
+ "audio/mpeg": ".mp3",
43
+ "audio/ogg": ".ogg",
44
+ "audio/wav": ".wav",
45
+ "application/pdf": ".pdf",
46
+ "application/zip": ".zip",
47
+ "application/x-tar": ".tar",
48
+ "application/gzip": ".gz",
49
+ "text/plain": ".txt",
50
+ "text/csv": ".csv",
51
+ };
52
+ /** Get MIME type from filename extension. Returns "application/octet-stream" for unknown extensions. */
53
+ export function getMimeFromFilename(filename) {
54
+ const ext = path.extname(filename).toLowerCase();
55
+ return EXTENSION_TO_MIME[ext] ?? "application/octet-stream";
56
+ }
57
+ /** Get file extension from MIME type. Returns ".bin" for unknown types. */
58
+ export function getExtensionFromMime(mimeType) {
59
+ const ct = mimeType.split(";")[0].trim().toLowerCase();
60
+ return MIME_TO_EXTENSION[ct] ?? ".bin";
61
+ }
62
+ /** Get file extension from Content-Type header or URL path. Returns ".bin" for unknown. */
63
+ export function getExtensionFromContentTypeOrUrl(contentType, url) {
64
+ if (contentType) {
65
+ const ext = getExtensionFromMime(contentType);
66
+ if (ext !== ".bin")
67
+ return ext;
68
+ }
69
+ const ext = path.extname(new URL(url).pathname).toLowerCase();
70
+ const knownExts = new Set(Object.keys(EXTENSION_TO_MIME));
71
+ return knownExts.has(ext) ? ext : ".bin";
72
+ }
73
+ //# sourceMappingURL=mime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime.js","sourceRoot":"","sources":["../../../src/media/mime.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,iBAAiB,GAA2B;IAChD,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,oBAAoB;IAC5B,OAAO,EAAE,yEAAyE;IAClF,MAAM,EAAE,0BAA0B;IAClC,OAAO,EAAE,mEAAmE;IAC5E,MAAM,EAAE,+BAA+B;IACvC,OAAO,EAAE,2EAA2E;IACpF,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,mBAAmB;IAC3B,KAAK,EAAE,kBAAkB;IACzB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,iBAAiB;IACzB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,kBAAkB;IAC1B,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;CACpB,CAAC;AAEF,MAAM,iBAAiB,GAA2B;IAChD,YAAY,EAAE,MAAM;IACpB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,YAAY,EAAE,OAAO;IACrB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,iBAAiB,EAAE,MAAM;IACzB,YAAY,EAAE,OAAO;IACrB,kBAAkB,EAAE,MAAM;IAC1B,iBAAiB,EAAE,MAAM;IACzB,YAAY,EAAE,MAAM;IACpB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,iBAAiB,EAAE,MAAM;IACzB,iBAAiB,EAAE,MAAM;IACzB,mBAAmB,EAAE,MAAM;IAC3B,kBAAkB,EAAE,KAAK;IACzB,YAAY,EAAE,MAAM;IACpB,UAAU,EAAE,MAAM;CACnB,CAAC;AAEF,wGAAwG;AACxG,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,OAAO,iBAAiB,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;AAC9D,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvD,OAAO,iBAAiB,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC;AACzC,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,gCAAgC,CAAC,WAA0B,EAAE,GAAW;IACtF,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,GAAG,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;QAC9C,IAAI,GAAG,KAAK,MAAM;YAAE,OAAO,GAAG,CAAC;IACjC,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC1D,OAAO,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,64 @@
1
+ import { logger } from "../util/logger.js";
2
+ /** Default sample rate for Weixin voice messages. */
3
+ const SILK_SAMPLE_RATE = 24_000;
4
+ /**
5
+ * Wrap raw pcm_s16le bytes in a WAV container.
6
+ * Mono channel, 16-bit signed little-endian.
7
+ */
8
+ function pcmBytesToWav(pcm, sampleRate) {
9
+ const pcmBytes = pcm.byteLength;
10
+ const totalSize = 44 + pcmBytes;
11
+ const buf = Buffer.allocUnsafe(totalSize);
12
+ let offset = 0;
13
+ buf.write("RIFF", offset);
14
+ offset += 4;
15
+ buf.writeUInt32LE(totalSize - 8, offset);
16
+ offset += 4;
17
+ buf.write("WAVE", offset);
18
+ offset += 4;
19
+ buf.write("fmt ", offset);
20
+ offset += 4;
21
+ buf.writeUInt32LE(16, offset);
22
+ offset += 4; // fmt chunk size
23
+ buf.writeUInt16LE(1, offset);
24
+ offset += 2; // PCM format
25
+ buf.writeUInt16LE(1, offset);
26
+ offset += 2; // mono
27
+ buf.writeUInt32LE(sampleRate, offset);
28
+ offset += 4;
29
+ buf.writeUInt32LE(sampleRate * 2, offset);
30
+ offset += 4; // byte rate (mono 16-bit)
31
+ buf.writeUInt16LE(2, offset);
32
+ offset += 2; // block align
33
+ buf.writeUInt16LE(16, offset);
34
+ offset += 2; // bits per sample
35
+ buf.write("data", offset);
36
+ offset += 4;
37
+ buf.writeUInt32LE(pcmBytes, offset);
38
+ offset += 4;
39
+ Buffer.from(pcm.buffer, pcm.byteOffset, pcm.byteLength).copy(buf, offset);
40
+ return buf;
41
+ }
42
+ /**
43
+ * Try to transcode a SILK audio buffer to WAV using silk-wasm.
44
+ * silk-wasm's decode() returns { data: Uint8Array (pcm_s16le), duration: number }.
45
+ *
46
+ * Returns a WAV Buffer on success, or null if silk-wasm is unavailable or decoding fails.
47
+ * Callers should fall back to passing the raw SILK file when null is returned.
48
+ */
49
+ export async function silkToWav(silkBuf) {
50
+ try {
51
+ const { decode } = await import("silk-wasm");
52
+ logger.debug(`silkToWav: decoding ${silkBuf.length} bytes of SILK`);
53
+ const result = await decode(silkBuf, SILK_SAMPLE_RATE);
54
+ logger.debug(`silkToWav: decoded duration=${result.duration}ms pcmBytes=${result.data.byteLength}`);
55
+ const wav = pcmBytesToWav(result.data, SILK_SAMPLE_RATE);
56
+ logger.debug(`silkToWav: WAV size=${wav.length}`);
57
+ return wav;
58
+ }
59
+ catch (err) {
60
+ logger.warn(`silkToWav: transcode failed, will use raw silk err=${String(err)}`);
61
+ return null;
62
+ }
63
+ }
64
+ //# sourceMappingURL=silk-transcode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"silk-transcode.js","sourceRoot":"","sources":["../../../src/media/silk-transcode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,177 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { logger } from "../util/logger.js";
4
+ const OGG_CAPTURE = Buffer.from("OggS");
5
+ const OPUS_HEAD_MAGIC = Buffer.from("OpusHead");
6
+ const GP_UNKNOWN = 0xffffffffffffffffn;
7
+ /** Weixin `VoiceItem.encode_type`: 6=silk (README: inbound SILK). */
8
+ export const VOICE_ENCODE_SILK = 6;
9
+ /** Ogg container (used for Opus-in-Ogg outbound). */
10
+ export const VOICE_ENCODE_OGG_SPEEX = 8;
11
+ /**
12
+ * Extensions always treated as outbound voice (upload + VOICE item).
13
+ * `.ogg` is handled separately: only when the file is Ogg Opus (see {@link isWeixinVoiceOutboundPath}).
14
+ */
15
+ const VOICE_EXTENSIONS = new Set([".opus", ".silk", ".slk"]);
16
+ function readUint32LE(buf, o) {
17
+ return buf.readUInt32LE(o);
18
+ }
19
+ /**
20
+ * Parse one Ogg page starting at `start`. Returns next byte offset after this page, or null if invalid.
21
+ */
22
+ function skipOggPage(buf, start) {
23
+ if (start + 27 > buf.length)
24
+ return null;
25
+ if (!buf.subarray(start, start + 4).equals(OGG_CAPTURE))
26
+ return null;
27
+ const nsegs = buf[start + 26];
28
+ if (start + 27 + nsegs > buf.length)
29
+ return null;
30
+ let bodySize = 0;
31
+ for (let i = 0; i < nsegs; i++) {
32
+ bodySize += buf[start + 27 + i];
33
+ }
34
+ const end = start + 27 + nsegs + bodySize;
35
+ if (end > buf.length)
36
+ return null;
37
+ return end;
38
+ }
39
+ /**
40
+ * First Ogg page whose first packet payload starts with OpusHead defines the Opus logical stream serial.
41
+ */
42
+ function findOpusStreamSerial(buf) {
43
+ let off = 0;
44
+ while (off < buf.length) {
45
+ const idx = buf.indexOf(OGG_CAPTURE, off);
46
+ if (idx < 0)
47
+ return null;
48
+ const end = skipOggPage(buf, idx);
49
+ if (end === null) {
50
+ off = idx + 1;
51
+ continue;
52
+ }
53
+ const nsegs = buf[idx + 26];
54
+ const bodyStart = idx + 27 + nsegs;
55
+ const firstSegLen = nsegs > 0 ? buf[idx + 27] : 0;
56
+ if (firstSegLen >= OPUS_HEAD_MAGIC.length &&
57
+ bodyStart + firstSegLen <= buf.length) {
58
+ const payload = buf.subarray(bodyStart, bodyStart + firstSegLen);
59
+ if (payload.subarray(0, OPUS_HEAD_MAGIC.length).equals(OPUS_HEAD_MAGIC)) {
60
+ return readUint32LE(buf, idx + 14);
61
+ }
62
+ }
63
+ off = end;
64
+ }
65
+ return null;
66
+ }
67
+ function maxGranuleForSerial(buf, targetSerial) {
68
+ let off = 0;
69
+ let maxGp = 0n;
70
+ while (off < buf.length) {
71
+ const idx = buf.indexOf(OGG_CAPTURE, off);
72
+ if (idx < 0)
73
+ break;
74
+ const end = skipOggPage(buf, idx);
75
+ if (end === null) {
76
+ off = idx + 1;
77
+ continue;
78
+ }
79
+ const serial = readUint32LE(buf, idx + 14);
80
+ if (serial === targetSerial) {
81
+ const gp = buf.readBigUInt64LE(idx + 6);
82
+ if (gp !== GP_UNKNOWN && gp > maxGp)
83
+ maxGp = gp;
84
+ }
85
+ off = end;
86
+ }
87
+ return maxGp;
88
+ }
89
+ /**
90
+ * Opus granule position counts 48 kHz PCM samples (RFC 7845). Duration in ms = gp * 1000 / 48000.
91
+ */
92
+ export function playtimeMsFromOpusGranule(maxGp) {
93
+ if (maxGp <= 0n)
94
+ return 0;
95
+ const ms = (maxGp * 1000n) / 48000n;
96
+ return Number(ms);
97
+ }
98
+ export function parseOggOpusPlaytimeMs(buf) {
99
+ const serial = findOpusStreamSerial(buf);
100
+ if (serial === null)
101
+ return null;
102
+ const maxGp = maxGranuleForSerial(buf, serial);
103
+ if (maxGp <= 0n)
104
+ return null;
105
+ const ms = playtimeMsFromOpusGranule(maxGp);
106
+ return ms > 0 ? ms : null;
107
+ }
108
+ export function bufferLooksLikeOggOpus(buf) {
109
+ return findOpusStreamSerial(buf) !== null;
110
+ }
111
+ async function silkPlaytimeMsWithOptionalWasm(buf) {
112
+ try {
113
+ const { decode } = await import("silk-wasm");
114
+ const result = await decode(buf, 24_000);
115
+ const d = result.duration;
116
+ if (typeof d !== "number" || !Number.isFinite(d) || d <= 0)
117
+ return null;
118
+ return Math.round(d);
119
+ }
120
+ catch (err) {
121
+ logger.debug(`voice-outbound: silk-wasm decode unavailable or failed err=${String(err)}`);
122
+ return null;
123
+ }
124
+ }
125
+ export async function isWeixinVoiceOutboundPath(filePath) {
126
+ const ext = path.extname(filePath).toLowerCase();
127
+ if (VOICE_EXTENSIONS.has(ext))
128
+ return true;
129
+ if (ext !== ".ogg")
130
+ return false;
131
+ const head = Buffer.allocUnsafe(65536);
132
+ const fh = await fs.open(filePath, "r");
133
+ try {
134
+ const { bytesRead } = await fh.read(head, 0, head.length, 0);
135
+ if (bytesRead <= 0)
136
+ return false;
137
+ return bufferLooksLikeOggOpus(head.subarray(0, bytesRead));
138
+ }
139
+ finally {
140
+ await fh.close();
141
+ }
142
+ }
143
+ /**
144
+ * Compute voice playtime (ms) and encoding hints for Weixin VOICE item.
145
+ * Returns null if duration cannot be determined (caller should send as file).
146
+ */
147
+ export async function resolveWeixinOutboundVoiceMeta(filePath) {
148
+ const ext = path.extname(filePath).toLowerCase();
149
+ const buf = await fs.readFile(filePath);
150
+ if (ext === ".opus" || ext === ".ogg") {
151
+ const ms = parseOggOpusPlaytimeMs(buf);
152
+ if (ms == null || ms <= 0) {
153
+ logger.warn(`voice-outbound: cannot resolve playtime for ${ext} file — parseOggOpusPlaytimeMs returned ${ms} (not a valid Ogg Opus stream?) filePath=${filePath} size=${buf.length}`);
154
+ return null;
155
+ }
156
+ return {
157
+ playtimeMs: ms,
158
+ encode_type: VOICE_ENCODE_OGG_SPEEX,
159
+ sample_rate: 48_000,
160
+ };
161
+ }
162
+ if (ext === ".silk" || ext === ".slk") {
163
+ const ms = await silkPlaytimeMsWithOptionalWasm(buf);
164
+ if (ms == null || ms <= 0) {
165
+ logger.warn(`voice-outbound: cannot resolve playtime for ${ext} file — silk-wasm decode returned ${ms} (wasm missing, or invalid SILK bitstream?) filePath=${filePath} size=${buf.length}`);
166
+ return null;
167
+ }
168
+ return {
169
+ playtimeMs: ms,
170
+ encode_type: VOICE_ENCODE_SILK,
171
+ sample_rate: 24_000,
172
+ };
173
+ }
174
+ logger.warn(`voice-outbound: unsupported voice extension '${ext}' for filePath=${filePath}`);
175
+ return null;
176
+ }
177
+ //# sourceMappingURL=voice-outbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-outbound.js","sourceRoot":"","sources":["../../../src/media/voice-outbound.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Abort fence (generation barrier) for Weixin message dispatch.
3
+ *
4
+ * When the user sends an "abort" intent (e.g. "stop", "中止", "/abort") we
5
+ * cannot truly cancel the in-flight LLM/tool pipeline because the underlying
6
+ * SDK does not currently propagate an AbortSignal end-to-end. Instead, we
7
+ * adopt the same trick used by the Telegram channel
8
+ * (extensions/telegram/src/bot-message-dispatch.ts in the openclaw main repo):
9
+ *
10
+ * - Each dispatch picks up a fence `generation` at entry.
11
+ * - An abort message bumps the generation (`supersede=true`) and starts its
12
+ * own dispatch with the new generation.
13
+ * - All outbound side-effects (sendMessage, typing start/stop, debug timing,
14
+ * etc.) check `isDispatchSuperseded(...)` first; if the generation no longer
15
+ * matches, they become no-ops.
16
+ *
17
+ * Effect: the old long-running task keeps running in the background but its
18
+ * output is silently dropped, so the user perceives it as cancelled and the
19
+ * new turn begins immediately.
20
+ *
21
+ * The fence is keyed per chat (typically `accountId:userId` for Weixin DMs),
22
+ * so different users do not share state.
23
+ */
24
+ const fenceByKey = new Map();
25
+ /**
26
+ * Begin a new dispatch and return its generation. When `supersede` is true
27
+ * (i.e. this dispatch is itself an abort request), bump the generation first
28
+ * so all already-running dispatches on this key become superseded.
29
+ */
30
+ export function beginAbortFence(params) {
31
+ const existing = fenceByKey.get(params.key);
32
+ const state = existing ?? { generation: 0, activeDispatches: 0 };
33
+ if (params.supersede) {
34
+ state.generation += 1;
35
+ }
36
+ state.activeDispatches += 1;
37
+ fenceByKey.set(params.key, state);
38
+ return state.generation;
39
+ }
40
+ /**
41
+ * Returns true when the dispatch with `generation` has been superseded by a
42
+ * later abort on `key`. Cheap to call; check before any user-visible side
43
+ * effect.
44
+ */
45
+ export function isDispatchSuperseded(params) {
46
+ return (fenceByKey.get(params.key)?.generation ?? 0) !== params.generation;
47
+ }
48
+ /**
49
+ * Mark a dispatch finished. When the lane goes idle we drop the entry to
50
+ * avoid unbounded growth across many distinct users.
51
+ */
52
+ export function endAbortFence(key) {
53
+ const state = fenceByKey.get(key);
54
+ if (!state) {
55
+ return;
56
+ }
57
+ state.activeDispatches -= 1;
58
+ if (state.activeDispatches <= 0) {
59
+ fenceByKey.delete(key);
60
+ }
61
+ }
62
+ /** Number of tracked fence keys. Exposed for diagnostics and tests. */
63
+ export function getAbortFenceSizeForTests() {
64
+ return fenceByKey.size;
65
+ }
66
+ /** Clear all fence state. Tests only. */
67
+ export function resetAbortFenceForTests() {
68
+ fenceByKey.clear();
69
+ }
70
+ //# sourceMappingURL=abort-fence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abort-fence.js","sourceRoot":"","sources":["../../../src/messaging/abort-fence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,MAAM,UAAU,GAAG,IAAI,GAAG,EAA2B,CAAC;AAEtD;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAA2C;IACzE,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAoB,QAAQ,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC;IAClF,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACrB,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,KAAK,CAAC,gBAAgB,IAAI,CAAC,CAAC;IAC5B,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC,UAAU,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAA2C;IAC9E,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,UAAU,IAAI,CAAC,CAAC,KAAK,MAAM,CAAC,UAAU,CAAC;AAC7E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;IACT,CAAC;IACD,KAAK,CAAC,gBAAgB,IAAI,CAAC,CAAC;IAC5B,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,EAAE,CAAC;QAChC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;AACH,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,yBAAyB;IACvC,OAAO,UAAU,CAAC,IAAI,CAAC;AACzB,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,uBAAuB;IACrC,UAAU,CAAC,KAAK,EAAE,CAAC;AACrB,CAAC"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Shared button normalisation helpers used by both the channel action handler
3
+ * (channel.ts) and the inbound message dispatch path (process-message.ts).
4
+ */
5
+ import { logger } from "../util/logger.js";
6
+ import { findFenceSpanAt, parseFenceSpans } from "../util/markdown-fences.js";
7
+ /**
8
+ * Normalize a single button-like object to {text, callback_data, style?}.
9
+ * Handles field name variants: label→text, value/callbackData→callback_data.
10
+ */
11
+ export function normalizeButtonObject(b) {
12
+ const text = String(b.text ?? b.label ?? "");
13
+ const callbackData = String(b.callback_data ?? b.callbackData ?? b.value ?? b.action ?? b.data ?? "");
14
+ if (!text && !callbackData)
15
+ return null;
16
+ const style = typeof b.style === "string" && b.style ? b.style : undefined;
17
+ return style
18
+ ? { text, callback_data: callbackData, style }
19
+ : { text, callback_data: callbackData };
20
+ }
21
+ /**
22
+ * Normalize any button-like value to the canonical [[{text, callback_data}]] format.
23
+ * Handles all known AI malformations:
24
+ * - [[{text, callback_data}]] → already correct
25
+ * - [{text, callback_data}] → flat array, wrap into single row
26
+ * - {text, callback_data} → single button object
27
+ * - {buttons: [...]} → unwrap .buttons
28
+ * - [{type:"button", label, value}] → normalize fields
29
+ */
30
+ export function normalizeButtonsParam(raw) {
31
+ if (raw == null)
32
+ return undefined;
33
+ if (Array.isArray(raw)) {
34
+ if (raw.length === 0)
35
+ return undefined;
36
+ if (Array.isArray(raw[0])) {
37
+ return raw;
38
+ }
39
+ const flatButtons = raw.filter((b) => b != null && typeof b === "object" && !Array.isArray(b));
40
+ if (flatButtons.length === 0)
41
+ return undefined;
42
+ const row = flatButtons.map(normalizeButtonObject).filter(Boolean);
43
+ return row.length > 0 ? [row] : undefined;
44
+ }
45
+ if (typeof raw === "object") {
46
+ const obj = raw;
47
+ if (Array.isArray(obj.buttons)) {
48
+ return normalizeButtonsParam(obj.buttons);
49
+ }
50
+ const btn = normalizeButtonObject(obj);
51
+ return btn ? [[btn]] : undefined;
52
+ }
53
+ return undefined;
54
+ }
55
+ /**
56
+ * Parse and strip the inline WEIXIN_BUTTONS marker from outbound text.
57
+ *
58
+ * Agents writing plain-text responses (deliver / cron / streaming) can embed
59
+ * buttons by appending a line of the form:
60
+ *
61
+ * WEIXIN_BUTTONS:[{"label":"Yes","value":"yes"},{"label":"No","value":"no"}]
62
+ *
63
+ * Returns:
64
+ * - `cleanText`: the original text with the marker line removed and any
65
+ * resulting extra blank lines collapsed.
66
+ * - `buttons`: the normalised button array (canonical [[{text,callback_data}]]
67
+ * form) ready to pass to sendMessageWeixin / sendFinalMessageWithStreamId,
68
+ * or undefined when no valid marker is found.
69
+ */
70
+ const INLINE_BUTTONS_RE = /^WEIXIN_BUTTONS:(\[[\s\S]*?\])\s*$/gm;
71
+ export function parseInlineButtons(text) {
72
+ // Mirror openclaw core's MEDIA: handling: a marker that lives inside a
73
+ // fenced code block is treated as literal documentation, not a directive.
74
+ const fenceSpans = parseFenceSpans(text);
75
+ const re = new RegExp(INLINE_BUTTONS_RE.source, INLINE_BUTTONS_RE.flags);
76
+ let match;
77
+ while ((match = re.exec(text)) !== null) {
78
+ if (findFenceSpanAt(fenceSpans, match.index))
79
+ continue;
80
+ try {
81
+ const parsed = JSON.parse(match[1]);
82
+ const buttons = normalizeButtonsParam(parsed);
83
+ const cleanText = text
84
+ .replace(match[0], "")
85
+ .replace(/\n{3,}/g, "\n\n")
86
+ .trim();
87
+ logger.debug(`parseInlineButtons: extracted ${buttons?.length ?? 0} button rows from inline marker`);
88
+ return { cleanText, buttons };
89
+ }
90
+ catch {
91
+ logger.warn(`parseInlineButtons: JSON parse failed, keeping marker as-is`);
92
+ return { cleanText: text, buttons: undefined };
93
+ }
94
+ }
95
+ return { cleanText: text, buttons: undefined };
96
+ }
97
+ /**
98
+ * Unified outbound helper: strip any inline WEIXIN_BUTTONS marker from `text`
99
+ * and merge the resulting buttons with any explicitly-provided buttons.
100
+ *
101
+ * Explicit buttons always win over marker-parsed buttons — an agent that calls
102
+ * the `message` tool with a structured `buttons` param is making a more
103
+ * deliberate choice than one that embeds a marker in free-text.
104
+ *
105
+ * Every outbound entry point (sendText / sendMedia / sendPayload / handleAction
106
+ * / streaming deliver) should route text through this helper so the marker is
107
+ * never leaked to the wire regardless of which code path delivered it.
108
+ */
109
+ export function resolveOutboundButtons(text, explicitButtons) {
110
+ const explicitNormalised = explicitButtons != null ? normalizeButtonsParam(explicitButtons) : undefined;
111
+ const { cleanText, buttons: inlineButtons } = parseInlineButtons(text);
112
+ return {
113
+ text: cleanText,
114
+ buttons: explicitNormalised ?? inlineButtons,
115
+ };
116
+ }
117
+ //# sourceMappingURL=buttons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buttons.js","sourceRoot":"","sources":["../../../src/messaging/buttons.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE9E;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,CAA0B;IAE1B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,MAAM,CACzB,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,CACzE,CAAC;IACF,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,KAAK,GACT,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/D,OAAO,KAAK;QACV,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,KAAK,EAAE;QAC9C,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAY;IAChD,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,SAAS,CAAC;IAElC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QACvC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1B,OAAO,GAAG,CAAC;QACb,CAAC;QACD,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAC5B,CAAC,CAAC,EAAgC,EAAE,CAClC,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAC1D,CAAC;QACF,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QAC/C,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACnE,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5C,CAAC;IAED,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,GAA8B,CAAC;QAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/B,OAAO,qBAAqB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5C,CAAC;QACD,MAAM,GAAG,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACvC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnC,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,iBAAiB,GAAG,sCAAsC,CAAC;AAEjE,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAI7C,uEAAuE;IACvE,0EAA0E;IAC1E,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACzE,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACxC,IAAI,eAAe,CAAC,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC;YAAE,SAAS;QACvD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAE,CAAY,CAAC;YAChD,MAAM,OAAO,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;YAC9C,MAAM,SAAS,GAAG,IAAI;iBACnB,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;iBACrB,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC;iBAC1B,IAAI,EAAE,CAAC;YACV,MAAM,CAAC,KAAK,CACV,iCAAiC,OAAO,EAAE,MAAM,IAAI,CAAC,iCAAiC,CACvF,CAAC;YACF,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;YAC3E,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;QACjD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAY,EACZ,eAAyB;IAEzB,MAAM,kBAAkB,GACtB,eAAe,IAAI,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/E,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACvE,OAAO;QACL,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,kBAAkB,IAAI,aAAa;KAC7C,CAAC;AACJ,CAAC"}