@tencent-weixin/openclaw-weixin 2.4.4 → 2.4.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/CHANGELOG.zh_CN.md +34 -0
  3. package/dist/src/api/api.js +35 -1
  4. package/dist/src/api/api.js.map +1 -1
  5. package/dist/src/api/session-guard.js +3 -3
  6. package/dist/src/api/session-guard.js.map +1 -1
  7. package/dist/src/api/types.js.map +1 -1
  8. package/dist/src/cdn/cdn-upload.js +3 -2
  9. package/dist/src/cdn/cdn-upload.js.map +1 -1
  10. package/dist/src/cdn/upload.js +12 -3
  11. package/dist/src/cdn/upload.js.map +1 -1
  12. package/dist/src/monitor/monitor.js +8 -8
  13. package/dist/src/monitor/monitor.js.map +1 -1
  14. package/openclaw.plugin.json +1 -1
  15. package/package.json +6 -5
  16. package/src/api/api.ts +50 -1
  17. package/src/api/session-guard.ts +3 -3
  18. package/src/api/types.ts +2 -1
  19. package/src/cdn/cdn-upload.ts +8 -2
  20. package/src/cdn/upload.ts +14 -3
  21. package/src/monitor/monitor.ts +9 -11
  22. package/dist/src/media/voice-outbound.js +0 -274
  23. package/dist/src/media/voice-outbound.js.map +0 -1
  24. package/dist/src/messaging/abort-fence.js +0 -70
  25. package/dist/src/messaging/abort-fence.js.map +0 -1
  26. package/dist/src/messaging/batch-session.js +0 -271
  27. package/dist/src/messaging/batch-session.js.map +0 -1
  28. package/dist/src/messaging/buttons.js +0 -117
  29. package/dist/src/messaging/buttons.js.map +0 -1
  30. package/dist/src/messaging/lane-key.js +0 -66
  31. package/dist/src/messaging/lane-key.js.map +0 -1
  32. package/dist/src/messaging/merged-record.js +0 -149
  33. package/dist/src/messaging/merged-record.js.map +0 -1
  34. package/dist/src/messaging/model-buttons.js +0 -182
  35. package/dist/src/messaging/model-buttons.js.map +0 -1
  36. package/dist/src/messaging/model-callback-handler.js +0 -133
  37. package/dist/src/messaging/model-callback-handler.js.map +0 -1
  38. package/dist/src/messaging/run-context.js +0 -9
  39. package/dist/src/messaging/run-context.js.map +0 -1
  40. package/dist/src/messaging/run-report-session.js +0 -123
  41. package/dist/src/messaging/run-report-session.js.map +0 -1
  42. package/dist/src/monitor/lane-scheduler.js +0 -46
  43. package/dist/src/monitor/lane-scheduler.js.map +0 -1
  44. package/dist/src/runtime.js +0 -54
  45. package/dist/src/runtime.js.map +0 -1
  46. package/dist/src/streaming/batch-session.js +0 -271
  47. package/dist/src/streaming/batch-session.js.map +0 -1
  48. package/dist/src/streaming/stream-piece.js +0 -54
  49. package/dist/src/streaming/stream-piece.js.map +0 -1
  50. package/dist/src/streaming/stream-pipeline.js +0 -431
  51. package/dist/src/streaming/stream-pipeline.js.map +0 -1
  52. package/dist/src/streaming/stream-session.js +0 -260
  53. package/dist/src/streaming/stream-session.js.map +0 -1
  54. package/dist/src/streaming/stream.js +0 -239
  55. package/dist/src/streaming/stream.js.map +0 -1
  56. package/dist/src/util/markdown-fences.js +0 -54
  57. package/dist/src/util/markdown-fences.js.map +0 -1
package/src/cdn/upload.ts CHANGED
@@ -7,6 +7,7 @@ import type { WeixinApiOptions } from "../api/api.js";
7
7
  import { aesEcbPaddedSize } from "./aes-ecb.js";
8
8
  import { uploadBufferToCdn } from "./cdn-upload.js";
9
9
  import { logger } from "../util/logger.js";
10
+ import { redactUrl } from "../util/redact.js";
10
11
  import { getExtensionFromContentTypeOrUrl } from "../media/mime.js";
11
12
  import { tempFileName } from "../util/random.js";
12
13
  import { UploadMediaType } from "../api/types.js";
@@ -28,10 +29,20 @@ export type UploadedFileInfo = {
28
29
  * Returns the local file path; extension is inferred from Content-Type / URL.
29
30
  */
30
31
  export async function downloadRemoteImageToTemp(url: string, destDir: string): Promise<string> {
31
- logger.debug(`downloadRemoteImageToTemp: fetching url=${url}`);
32
- const res = await fetch(url);
32
+ logger.debug(`downloadRemoteImageToTemp: fetching url=${redactUrl(url)}`);
33
+ let res: Response;
34
+ try {
35
+ res = await fetch(url);
36
+ } catch (err) {
37
+ const cause =
38
+ (err as NodeJS.ErrnoException).cause ?? (err as NodeJS.ErrnoException).code ?? "";
39
+ logger.error(
40
+ `downloadRemoteImageToTemp: fetch network error url=${redactUrl(url)} error=${String(err)}${cause ? ` cause=${cause}` : ""}`,
41
+ );
42
+ throw err;
43
+ }
33
44
  if (!res.ok) {
34
- const msg = `remote media download failed: ${res.status} ${res.statusText} url=${url}`;
45
+ const msg = `remote media download failed: ${res.status} ${res.statusText} url=${redactUrl(url)}`;
35
46
  logger.error(`downloadRemoteImageToTemp: ${msg}`);
36
47
  throw new Error(msg);
37
48
  }
@@ -1,9 +1,9 @@
1
1
  import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/channel-contract";
2
2
  import type { PluginRuntime } from "openclaw/plugin-sdk/core";
3
3
 
4
- import { getUpdates } from "../api/api.js";
4
+ import { getUpdates, classifyFetchError } from "../api/api.js";
5
5
  import { WeixinConfigManager } from "../api/config-cache.js";
6
- import { SESSION_EXPIRED_ERRCODE, pauseSession, getRemainingPauseMs } from "../api/session-guard.js";
6
+ import { STALE_TOKEN_ERRCODE, pauseSession, getRemainingPauseMs } from "../api/session-guard.js";
7
7
  import { processOneMessage } from "../messaging/process-message.js";
8
8
  import { getSyncBufFilePath, loadGetUpdatesBuf, saveGetUpdatesBuf } from "../storage/sync-buf.js";
9
9
  import { logger } from "../util/logger.js";
@@ -112,17 +112,14 @@ export async function monitorWeixinProvider(opts: MonitorWeixinOpts): Promise<vo
112
112
  (resp.ret !== undefined && resp.ret !== 0) ||
113
113
  (resp.errcode !== undefined && resp.errcode !== 0);
114
114
  if (isApiError) {
115
- const isSessionExpired =
116
- resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
115
+ const isStaleToken =
116
+ resp.errcode === STALE_TOKEN_ERRCODE || resp.ret === STALE_TOKEN_ERRCODE;
117
117
 
118
- if (isSessionExpired) {
118
+ if (isStaleToken) {
119
119
  pauseSession(accountId);
120
120
  const pauseMs = getRemainingPauseMs(accountId);
121
- errLog(
122
- `weixin getUpdates: session expired (errcode ${SESSION_EXPIRED_ERRCODE}), pausing bot for ${Math.ceil(pauseMs / 60_000)} min`,
123
- );
124
121
  aLog.error(
125
- `getUpdates: session expired (errcode=${resp.errcode} ret=${resp.ret}), pausing all requests for ${Math.ceil(pauseMs / 60_000)} min`,
122
+ `getUpdates: token for ${accountId} is stale, pausing all requests for ${Math.ceil(pauseMs / 60_000)} min`,
126
123
  );
127
124
  consecutiveFailures = 0;
128
125
  await sleep(pauseMs, abortSignal);
@@ -190,10 +187,11 @@ export async function monitorWeixinProvider(opts: MonitorWeixinOpts): Promise<vo
190
187
  return;
191
188
  }
192
189
  consecutiveFailures += 1;
190
+ const classified = classifyFetchError(err);
193
191
  errLog(
194
- `weixin getUpdates error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${String(err)}`,
192
+ `weixin getUpdates error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${String(err)} type=${classified.type} description=${classified.description}${classified.code ? ` code=${classified.code}` : ""}`,
195
193
  );
196
- aLog.error(`getUpdates error: ${String(err)}, stack=${(err as Error).stack}`);
194
+ aLog.error(`getUpdates error: ${String(err)}, type=${classified.type} code=${classified.code ?? "none"}, stack=${(err as Error).stack}`);
197
195
  if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
198
196
  errLog(
199
197
  `weixin getUpdates: ${MAX_CONSECUTIVE_FAILURES} consecutive failures, backing off 30s`,
@@ -1,274 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import fs from "node:fs/promises";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { promisify } from "node:util";
6
- import { logger } from "../util/logger.js";
7
- const execFileAsync = promisify(execFile);
8
- const OGG_CAPTURE = Buffer.from("OggS");
9
- const OPUS_HEAD_MAGIC = Buffer.from("OpusHead");
10
- const GP_UNKNOWN = 0xffffffffffffffffn;
11
- /** Weixin `VoiceItem.encode_type`: 6=silk (README: inbound SILK). */
12
- export const VOICE_ENCODE_SILK = 6;
13
- /** Ogg container (used for Opus-in-Ogg outbound). */
14
- export const VOICE_ENCODE_OGG_SPEEX = 8;
15
- /**
16
- * Extensions always treated as outbound voice (upload + VOICE item).
17
- * `.ogg` is handled separately: only when the file is Ogg Opus (see {@link isWeixinVoiceOutboundPath}).
18
- */
19
- const VOICE_EXTENSIONS = new Set([".opus", ".silk", ".slk"]);
20
- const AUDIO_AS_VOICE_EXTENSIONS = new Set([
21
- ".aac",
22
- ".amr",
23
- ".m4a",
24
- ".mp3",
25
- ".oga",
26
- ".ogg",
27
- ".opus",
28
- ".silk",
29
- ".slk",
30
- ".wav",
31
- ]);
32
- function readUint32LE(buf, o) {
33
- return buf.readUInt32LE(o);
34
- }
35
- /**
36
- * Parse one Ogg page starting at `start`. Returns next byte offset after this page, or null if invalid.
37
- */
38
- function skipOggPage(buf, start) {
39
- if (start + 27 > buf.length)
40
- return null;
41
- if (!buf.subarray(start, start + 4).equals(OGG_CAPTURE))
42
- return null;
43
- const nsegs = buf[start + 26];
44
- if (start + 27 + nsegs > buf.length)
45
- return null;
46
- let bodySize = 0;
47
- for (let i = 0; i < nsegs; i++) {
48
- bodySize += buf[start + 27 + i];
49
- }
50
- const end = start + 27 + nsegs + bodySize;
51
- if (end > buf.length)
52
- return null;
53
- return end;
54
- }
55
- /**
56
- * First Ogg page whose first packet payload starts with OpusHead defines the Opus logical stream serial.
57
- */
58
- function findOpusStreamSerial(buf) {
59
- let off = 0;
60
- while (off < buf.length) {
61
- const idx = buf.indexOf(OGG_CAPTURE, off);
62
- if (idx < 0)
63
- return null;
64
- const end = skipOggPage(buf, idx);
65
- if (end === null) {
66
- off = idx + 1;
67
- continue;
68
- }
69
- const nsegs = buf[idx + 26];
70
- const bodyStart = idx + 27 + nsegs;
71
- const firstSegLen = nsegs > 0 ? buf[idx + 27] : 0;
72
- if (firstSegLen >= OPUS_HEAD_MAGIC.length &&
73
- bodyStart + firstSegLen <= buf.length) {
74
- const payload = buf.subarray(bodyStart, bodyStart + firstSegLen);
75
- if (payload.subarray(0, OPUS_HEAD_MAGIC.length).equals(OPUS_HEAD_MAGIC)) {
76
- return readUint32LE(buf, idx + 14);
77
- }
78
- }
79
- off = end;
80
- }
81
- return null;
82
- }
83
- function maxGranuleForSerial(buf, targetSerial) {
84
- let off = 0;
85
- let maxGp = 0n;
86
- while (off < buf.length) {
87
- const idx = buf.indexOf(OGG_CAPTURE, off);
88
- if (idx < 0)
89
- break;
90
- const end = skipOggPage(buf, idx);
91
- if (end === null) {
92
- off = idx + 1;
93
- continue;
94
- }
95
- const serial = readUint32LE(buf, idx + 14);
96
- if (serial === targetSerial) {
97
- const gp = buf.readBigUInt64LE(idx + 6);
98
- if (gp !== GP_UNKNOWN && gp > maxGp)
99
- maxGp = gp;
100
- }
101
- off = end;
102
- }
103
- return maxGp;
104
- }
105
- /**
106
- * Opus granule position counts 48 kHz PCM samples (RFC 7845). Duration in ms = gp * 1000 / 48000.
107
- */
108
- export function playtimeMsFromOpusGranule(maxGp) {
109
- if (maxGp <= 0n)
110
- return 0;
111
- const ms = (maxGp * 1000n) / 48000n;
112
- return Number(ms);
113
- }
114
- export function parseOggOpusPlaytimeMs(buf) {
115
- const serial = findOpusStreamSerial(buf);
116
- if (serial === null)
117
- return null;
118
- const maxGp = maxGranuleForSerial(buf, serial);
119
- if (maxGp <= 0n)
120
- return null;
121
- const ms = playtimeMsFromOpusGranule(maxGp);
122
- return ms > 0 ? ms : null;
123
- }
124
- export function bufferLooksLikeOggOpus(buf) {
125
- return findOpusStreamSerial(buf) !== null;
126
- }
127
- async function silkPlaytimeMsWithOptionalWasm(buf) {
128
- try {
129
- const { decode } = await import("silk-wasm");
130
- const result = await decode(buf, 24_000);
131
- const d = result.duration;
132
- if (typeof d !== "number" || !Number.isFinite(d) || d <= 0)
133
- return null;
134
- return Math.round(d);
135
- }
136
- catch (err) {
137
- logger.debug(`voice-outbound: silk-wasm decode unavailable or failed err=${String(err)}`);
138
- return null;
139
- }
140
- }
141
- async function ffprobePlaytimeMs(filePath) {
142
- try {
143
- const { stdout } = await execFileAsync("ffprobe", [
144
- "-v",
145
- "error",
146
- "-show_entries",
147
- "format=duration",
148
- "-of",
149
- "default=noprint_wrappers=1:nokey=1",
150
- filePath,
151
- ]);
152
- const seconds = Number.parseFloat(stdout.trim());
153
- if (!Number.isFinite(seconds) || seconds <= 0)
154
- return null;
155
- return Math.round(seconds * 1000);
156
- }
157
- catch (err) {
158
- logger.debug(`voice-outbound: ffprobe duration unavailable err=${String(err)}`);
159
- return null;
160
- }
161
- }
162
- const WEIXIN_VOICE_TRANSCODE_SAMPLE_RATE = 48_000;
163
- const WEIXIN_VOICE_TRANSCODE_BITRATE = "64k";
164
- const WEIXIN_VOICE_TRANSCODE_MAX_SECS = 60;
165
- /**
166
- * Transcode a generic audio file to Ogg/Opus for Weixin voice messages using ffmpeg.
167
- * Returns the transcoded buffer on success, or null if ffmpeg is unavailable or fails.
168
- * The caller should check the returned buffer with resolveWeixinOutboundVoiceMeta
169
- * before uploading; if that also fails, degrade to a file attachment.
170
- */
171
- export async function transcodeToWeixinVoiceOpus(filePath) {
172
- const tmpPath = path.join(os.tmpdir(), `weixin-voice-${Date.now()}-${Math.random().toString(36).slice(2)}.ogg`);
173
- try {
174
- await execFileAsync("ffmpeg", [
175
- "-hide_banner",
176
- "-loglevel",
177
- "error",
178
- "-y",
179
- "-i",
180
- filePath,
181
- "-vn",
182
- "-sn",
183
- "-dn",
184
- "-t",
185
- String(WEIXIN_VOICE_TRANSCODE_MAX_SECS),
186
- "-ar",
187
- String(WEIXIN_VOICE_TRANSCODE_SAMPLE_RATE),
188
- "-ac",
189
- "1",
190
- "-c:a",
191
- "libopus",
192
- "-b:a",
193
- WEIXIN_VOICE_TRANSCODE_BITRATE,
194
- "-f",
195
- "ogg",
196
- tmpPath,
197
- ]);
198
- return await fs.readFile(tmpPath);
199
- }
200
- catch (err) {
201
- logger.warn(`voice-outbound: transcodeToWeixinVoiceOpus failed filePath=${filePath} err=${String(err)}`);
202
- return null;
203
- }
204
- finally {
205
- await fs.unlink(tmpPath).catch(() => { });
206
- }
207
- }
208
- export function isWeixinAudioAsVoiceOutboundPath(filePath) {
209
- const ext = path.extname(filePath).toLowerCase();
210
- return AUDIO_AS_VOICE_EXTENSIONS.has(ext);
211
- }
212
- export async function isWeixinVoiceOutboundPath(filePath) {
213
- const ext = path.extname(filePath).toLowerCase();
214
- if (VOICE_EXTENSIONS.has(ext))
215
- return true;
216
- if (ext !== ".ogg")
217
- return false;
218
- const head = Buffer.allocUnsafe(65536);
219
- const fh = await fs.open(filePath, "r");
220
- try {
221
- const { bytesRead } = await fh.read(head, 0, head.length, 0);
222
- if (bytesRead <= 0)
223
- return false;
224
- return bufferLooksLikeOggOpus(head.subarray(0, bytesRead));
225
- }
226
- finally {
227
- await fh.close();
228
- }
229
- }
230
- /**
231
- * Compute voice playtime (ms) and encoding hints for Weixin VOICE item.
232
- * Returns null if duration cannot be determined (caller should send as file).
233
- */
234
- export async function resolveWeixinOutboundVoiceMeta(filePath, opts = {}) {
235
- const ext = path.extname(filePath).toLowerCase();
236
- const buf = await fs.readFile(filePath);
237
- if (ext === ".opus" || ext === ".ogg") {
238
- const ms = parseOggOpusPlaytimeMs(buf);
239
- if (ms == null || ms <= 0) {
240
- logger.warn(`voice-outbound: cannot resolve playtime for ${ext} file — parseOggOpusPlaytimeMs returned ${ms} (not a valid Ogg Opus stream?) filePath=${filePath} size=${buf.length}`);
241
- return null;
242
- }
243
- return {
244
- playtimeMs: ms,
245
- encode_type: VOICE_ENCODE_OGG_SPEEX,
246
- sample_rate: 48_000,
247
- };
248
- }
249
- if (ext === ".silk" || ext === ".slk") {
250
- const ms = await silkPlaytimeMsWithOptionalWasm(buf);
251
- if (ms == null || ms <= 0) {
252
- 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}`);
253
- return null;
254
- }
255
- return {
256
- playtimeMs: ms,
257
- encode_type: VOICE_ENCODE_SILK,
258
- sample_rate: 24_000,
259
- };
260
- }
261
- if (opts.allowGenericAudio && AUDIO_AS_VOICE_EXTENSIONS.has(ext)) {
262
- const ms = await ffprobePlaytimeMs(filePath);
263
- if (ms == null || ms <= 0) {
264
- logger.warn(`voice-outbound: cannot resolve playtime for audioAsVoice ${ext} file via ffprobe filePath=${filePath} size=${buf.length}`);
265
- return null;
266
- }
267
- return {
268
- playtimeMs: ms,
269
- };
270
- }
271
- logger.warn(`voice-outbound: unsupported voice extension '${ext}' for filePath=${filePath}`);
272
- return null;
273
- }
274
- //# sourceMappingURL=voice-outbound.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"voice-outbound.js","sourceRoot":"","sources":["../../../src/media/voice-outbound.ts"],"names":[],"mappings":"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"}
@@ -1,70 +0,0 @@
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
@@ -1 +0,0 @@
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"}