{"version":3,"file":"agent/ui-observer.mjs","sources":["../../../src/agent/ui-observer.ts"],"sourcesContent":["import { imageInfoOfBase64, resizeImgBase64 } from '@midscene/shared/img';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\nimport type { DeviceFrameRef, DeviceFrameSource } from '../device';\nimport { ScreenshotItem } from '../screenshot-item';\nimport type { AgentAssertOpt, ServiceExtractOption, UIContext } from '../types';\n\nconst debug = getDebug('ui-observer');\nconst warnObserver = getDebug('ui-observer', { console: true });\n\n// Guardrails from the performance research: cap sampling at 5fps and bound the\n// frame buffer. All buffered frames (up to maxFrames) are sent to the\n// model so transient UI in long windows is not missed by down-sampling.\nconst DEFAULT_INTERVAL_MS = 1000;\nconst MIN_INTERVAL_MS = 200;\nconst DEFAULT_MAX_FRAMES = 30;\n// How long start() waits for a cold stream's first frame before proceeding.\nconst FIRST_FRAME_TIMEOUT_MS = 3000;\n// Default watchdog: auto-stop an observer that was never explicitly stopped.\nconst DEFAULT_WATCHDOG_MS = 5 * 60 * 1000;\n// Soft cap on frames sent to the model per assertion. We still send all frames\n// (no silent dropping), but warn the user when this is exceeded.\nconst MAX_FRAMES_TO_MODEL = 50;\n\nexport interface UIObserverOption {\n  /** Sampling interval between frames in ms. Default 1000, min 200 (5fps). */\n  intervalMs?: number;\n  /**\n   * Maximum number of frames to keep in the buffer. When full the buffer is\n   * thinned (change-point frames preserved, static intervals halved) so the\n   * whole window keeps temporal coverage. Default 30.\n   */\n  maxFrames?: number;\n  /**\n   * Auto-stop the observer after this many ms if stop() was never called.\n   * Prevents resource leaks from forgotten observers. Default 5min. Set 0 to\n   * disable.\n   */\n  watchdogMs?: number;\n}\n\ninterface UIObserverDeps {\n  /**\n   * Open the device's continuous frame source, if it has one. The observer\n   * falls back to plain screenshots when this returns undefined or throws.\n   */\n  openFrameSource: () => Promise<DeviceFrameSource | undefined>;\n  /** Fallback single-frame capture (already a data URL). */\n  screenshot: () => Promise<string>;\n  /** Capture the final full-quality UIContext (used as the representative). */\n  captureRepresentative: () => Promise<UIContext>;\n  /** Run an assert against a pre-built multi-frame UIContext. */\n  runAssert: (\n    assertion: string,\n    uiContext: UIContext,\n    msg?: string,\n    opt?: AgentAssertOpt & ServiceExtractOption,\n  ) => Promise<\n    undefined | { pass: boolean; thought?: string; message?: string }\n  >;\n  /** Run a boolean query against a pre-built multi-frame UIContext. */\n  runBoolean: (\n    prompt: string,\n    uiContext: UIContext,\n    opt?: ServiceExtractOption,\n  ) => Promise<boolean>;\n  /** Called when stop() completes, so the agent can clear its active-observer reference. */\n  onStopped?: () => void;\n  /** Screenshot shrink factor applied to fallback frames. Default 1 (no shrink). */\n  screenshotShrinkFactor?: number;\n}\n\n/**\n * Observes the screen over an explicit window so a later assertion can judge\n * everything that happened while other agent calls ran — including transient\n * UI that appears mid-action:\n *\n * ```ts\n * const observer = await agent.startObserving();\n * await agent.aiAct('submit the form');\n * await observer.stop();\n * await observer.aiAssert('a success toast appeared during the process');\n * ```\n *\n * Sampling is deliberately cheap: when the device exposes a continuous frame\n * source (scrcpy on Android, WDA MJPEG on iOS, CDP screencast on web), each\n * tick only grabs an opaque frame handle; any decode cost is paid ONCE at the\n * end, for all buffered frames actually sent to the model. Devices without a\n * frame source fall back to plain screenshots per tick. To avoid missing\n * short-lived transient UI in long observation windows, every buffered frame\n * is sent to the model — control cost via `intervalMs` and `maxFrames`.\n */\nexport class UIObserver {\n  private frames: DeviceFrameRef[] = [];\n  private source: DeviceFrameSource | null = null;\n  private usingFallback = false;\n  private stopped = false;\n  private loopPromise: Promise<void> | null = null;\n  private representative: UIContext | null = null;\n  private watchdogTimer: ReturnType<typeof setTimeout> | null = null;\n  /** Cross-assertion decode cache: keyed by frame.ref, avoids re-decoding on Android. */\n  private decodedCache = new Map<unknown, string>();\n  /** Background pre-decode started in stop(), awaited by buildObservedUIContext(). */\n  private preDecodePromise: Promise<void> | null = null;\n  private readonly intervalMs: number;\n  private readonly maxFrames: number;\n  private readonly watchdogMs: number;\n  private readonly screenshotShrinkFactor: number;\n\n  constructor(\n    private readonly deps: UIObserverDeps,\n    opt?: UIObserverOption,\n  ) {\n    this.intervalMs = Math.max(\n      MIN_INTERVAL_MS,\n      opt?.intervalMs ?? DEFAULT_INTERVAL_MS,\n    );\n    this.maxFrames = Math.max(2, opt?.maxFrames ?? DEFAULT_MAX_FRAMES);\n    this.watchdogMs = opt?.watchdogMs ?? DEFAULT_WATCHDOG_MS;\n    this.screenshotShrinkFactor = deps.screenshotShrinkFactor ?? 1;\n  }\n\n  /** Number of frames currently buffered. */\n  get frameCount(): number {\n    return this.frames.length;\n  }\n\n  /**\n   * Open the frame source (or arm the screenshot fallback), capture the first\n   * baseline frame, then start the background sampling loop. Awaiting this\n   * guarantees at least one pre-action frame exists.\n   */\n  async start(): Promise<void> {\n    assert(!this.loopPromise && !this.stopped, 'observer has already started');\n    try {\n      this.source = (await this.deps.openFrameSource()) ?? null;\n    } catch (error) {\n      debug(`frame source unavailable, using screenshot fallback: ${error}`);\n      this.source = null;\n    }\n    this.usingFallback = !this.source;\n    if (this.usingFallback) {\n      debug('no continuous frame source; sampling via plain screenshots');\n    } else {\n      // A freshly opened stream may not have produced a frame yet. Wait\n      // briefly so the \"one baseline frame before your next action\" guarantee\n      // holds even on cold streams; if none arrives, continue — frames will\n      // land on later ticks.\n      const waitStart = Date.now();\n      while (\n        !this.source!.latest() &&\n        Date.now() - waitStart < FIRST_FRAME_TIMEOUT_MS\n      ) {\n        await new Promise((resolve) => setTimeout(resolve, 50));\n      }\n      if (!this.source!.latest()) {\n        debug(\n          `no first frame within ${FIRST_FRAME_TIMEOUT_MS}ms; starting anyway`,\n        );\n      }\n    }\n    await this.captureOnce();\n    this.loopPromise = this.runLoop();\n\n    // Watchdog: auto-stop if the user forgets to call stop().\n    if (this.watchdogMs > 0) {\n      this.watchdogTimer = setTimeout(() => {\n        warnObserver(\n          `UIObserver auto-stopped after ${this.watchdogMs}ms. Call observer.stop() explicitly to avoid this.`,\n        );\n        debug(`watchdog fired after ${this.watchdogMs}ms, auto-stopping`);\n        this.stop().catch(() => {});\n      }, this.watchdogMs);\n      if (\n        typeof (this.watchdogTimer as { unref?: () => void }).unref ===\n        'function'\n      ) {\n        (this.watchdogTimer as { unref: () => void }).unref();\n      }\n    }\n  }\n\n  /**\n   * Stop sampling, kick off background pre-decode, capture the representative,\n   * and release the frame source. Guarantees that the frame source is released\n   * and the agent's active-observer reference is cleared even if intermediate\n   * steps (pre-decode, representative capture) throw.\n   */\n  async stop(): Promise<void> {\n    if (this.stopped) return;\n    this.stopped = true;\n\n    // Clear the watchdog.\n    if (this.watchdogTimer) {\n      clearTimeout(this.watchdogTimer);\n      this.watchdogTimer = null;\n    }\n\n    await this.loopPromise;\n\n    try {\n      // Kick off pre-decode in the background so aiAssert() doesn't have to\n      // wait for decode at call time. This runs concurrently with\n      // captureRepresentative().\n      if (this.source && this.frames.length > 0) {\n        const uniqueRefs = this.dedupeRefs(this.frames);\n        this.preDecodePromise = this.source\n          .decode(uniqueRefs)\n          .then(async (results) => {\n            const shrunk = await this.shrinkAllIfNeeded(results);\n            uniqueRefs.forEach((ref, i) => {\n              this.decodedCache.set(ref.ref, shrunk[i]);\n            });\n            debug(`pre-decoded ${uniqueRefs.length} frames`);\n          })\n          .catch((error) => {\n            debug(`pre-decode failed, will retry at assert time: ${error}`);\n          });\n      }\n\n      // Capture representative (DOM, viewport, etc.) — runs in parallel with\n      // pre-decode when possible.\n      const representativePromise = this.deps.captureRepresentative();\n\n      const [, representative] = await Promise.all([\n        this.preDecodePromise,\n        representativePromise,\n      ]);\n\n      // Temporal alignment: if the last sampled frame is already decoded, use\n      // its image as the representative screenshot so the sequence tail is\n      // consistent with what was actually on screen during sampling.\n      if (this.source && this.frames.length > 0) {\n        const lastFrame = this.frames[this.frames.length - 1];\n        const lastDecoded = this.decodedCache.get(lastFrame.ref);\n        if (lastDecoded) {\n          representative.screenshot = ScreenshotItem.create(\n            lastDecoded,\n            lastFrame.capturedAt,\n          );\n          debug('representative screenshot aligned with last sampled frame');\n        }\n      }\n\n      this.representative = representative;\n    } finally {\n      // Always release the frame source and notify the agent — even if\n      // pre-decode or representative capture threw. Keep the source reference\n      // so buildObservedUIContext() can still call decode() if pre-decode\n      // failed (decode is independent of the stream subscription).\n      if (this.source) {\n        try {\n          await this.source.stop();\n        } catch (error) {\n          debug(`error stopping frame source: ${error}`);\n        }\n      }\n\n      debug(\n        `observation stopped with ${this.frames.length} buffered frames (+1 representative)`,\n      );\n\n      // Notify the agent that this observer is no longer active.\n      this.deps.onStopped?.();\n    }\n  }\n\n  /**\n   * Assert against the observed window. All buffered frames (plus the final\n   * representative) are decoded and sent to the model. To control cost for\n   * long windows, increase `intervalMs` or decrease `maxFrames`.\n   * Throws when the assertion fails, mirroring `agent.aiAssert`.\n   */\n  async aiAssert(\n    assertion: string,\n    msg?: string,\n    opt?: AgentAssertOpt & ServiceExtractOption,\n  ): Promise<\n    undefined | { pass: boolean; thought?: string; message?: string }\n  > {\n    const uiContext = await this.buildObservedUIContext();\n    return this.deps.runAssert(assertion, uiContext, msg, opt);\n  }\n\n  /** Boolean query over the observed window (same frame semantics as aiAssert). */\n  async aiBoolean(\n    prompt: string,\n    opt?: ServiceExtractOption,\n  ): Promise<boolean> {\n    const uiContext = await this.buildObservedUIContext();\n    return this.deps.runBoolean(prompt, uiContext, opt);\n  }\n\n  private async buildObservedUIContext(): Promise<UIContext> {\n    assert(\n      this.stopped && this.representative,\n      'call observer.stop() before asserting on the observed window',\n    );\n    const representative = this.representative!;\n\n    // If pre-decode is still running (e.g. user called aiAssert very quickly\n    // after stop), wait for it to complete.\n    if (this.preDecodePromise) {\n      await this.preDecodePromise;\n      this.preDecodePromise = null;\n    }\n\n    // Send ALL buffered frames to the model so transient UI in long windows\n    // is not missed by down-sampling. Cost is controlled by intervalMs and\n    // maxFrames instead. Decode each UNIQUE frame once, using the\n    // cross-assertion cache.\n    const sampled = this.frames;\n    const uniqueRefs = this.dedupeRefs(sampled);\n\n    // Find which unique refs are not yet in the decode cache.\n    const uncachedRefs = uniqueRefs.filter(\n      (r) => !this.decodedCache.has(r.ref),\n    );\n    if (uncachedRefs.length > 0) {\n      const results = this.source\n        ? await this.shrinkAllIfNeeded(await this.source.decode(uncachedRefs))\n        : uncachedRefs.map((f) => f.ref as string);\n      assert(\n        results.length === uncachedRefs.length,\n        'frame source decode() must return one image per frame handle',\n      );\n      uncachedRefs.forEach((ref, i) => {\n        this.decodedCache.set(ref.ref, results[i]);\n      });\n      debug(\n        `decoded ${uncachedRefs.length} new frames (${uniqueRefs.length - uncachedRefs.length} from cache)`,\n      );\n    }\n\n    // Build the index map for ordered reconstruction.\n    const indexByRef = new Map<unknown, number>();\n    uniqueRefs.forEach((ref, i) => indexByRef.set(ref.ref, i));\n\n    // Reconstruct the full ordered sequence from the cache.\n    const sequence = sampled.map((frame) =>\n      ScreenshotItem.create(\n        this.decodedCache.get(frame.ref)!,\n        frame.capturedAt,\n      ),\n    );\n\n    const totalFrames = sequence.length + 1; // +1 for representative\n    if (totalFrames > MAX_FRAMES_TO_MODEL) {\n      warnObserver(\n        `WARNING: sending ${totalFrames} frames to the model (soft limit ${MAX_FRAMES_TO_MODEL}). Consider increasing intervalMs or decreasing maxFrames to reduce token cost.`,\n      );\n    }\n\n    debug(\n      `observed context: ${sequence.length}+1 frames ` +\n        `(buffered: ${this.frames.length}, unique: ${uniqueRefs.length}, ` +\n        `newly decoded: ${uncachedRefs.length})`,\n    );\n    return {\n      ...representative,\n      screenshotSequence: [...sequence, representative.screenshot],\n    };\n  }\n\n  private async captureOnce(): Promise<void> {\n    try {\n      if (this.source) {\n        const frame = this.source.latest();\n        if (frame) this.pushFrame(frame);\n        return;\n      }\n      let base64 = await this.deps.screenshot();\n      // Apply shrink factor to fallback screenshots so they match the\n      // representative frame size and don't inflate token cost.\n      if (this.screenshotShrinkFactor > 1) {\n        const { width, height } = await imageInfoOfBase64(base64);\n        base64 = await resizeImgBase64(base64, {\n          width: Math.round(width / this.screenshotShrinkFactor),\n          height: Math.round(height / this.screenshotShrinkFactor),\n        });\n      }\n      this.pushFrame({ ref: base64, capturedAt: Date.now() });\n    } catch (error) {\n      debug(`frame capture failed, skipping tick: ${error}`);\n    }\n  }\n\n  /**\n   * Apply screenshotShrinkFactor to an array of decoded base64 images in\n   * parallel. Returns the input unchanged when shrink factor is 1. Source\n   * frames come at device-native resolution; shrinking them matches the\n   * representative frame size so the sequence sent to the model has\n   * consistent resolution and token cost.\n   */\n  private async shrinkAllIfNeeded(base64s: string[]): Promise<string[]> {\n    if (this.screenshotShrinkFactor <= 1) return base64s;\n    const factor = this.screenshotShrinkFactor;\n    return Promise.all(\n      base64s.map(async (b64) => {\n        const { width, height } = await imageInfoOfBase64(b64);\n        return resizeImgBase64(b64, {\n          width: Math.round(width / factor),\n          height: Math.round(height / factor),\n        });\n      }),\n    );\n  }\n\n  private async runLoop(): Promise<void> {\n    while (!this.stopped) {\n      const tickStart = Date.now();\n      await this.captureOnce();\n      // Sleep out the remainder of the interval in short slices so stop()\n      // takes effect promptly.\n      while (!this.stopped && Date.now() - tickStart < this.intervalMs) {\n        await new Promise((resolve) => setTimeout(resolve, 50));\n      }\n    }\n  }\n\n  private pushFrame(frame: DeviceFrameRef): void {\n    if (this.frames.length >= this.maxFrames) {\n      this.frames = this.thinBuffer(this.frames);\n      debug(`frame buffer thinned to ${this.frames.length} frames`);\n    }\n    this.frames.push(frame);\n  }\n\n  /**\n   * Smart thinning: preserve all \"change point\" frames — frames where the\n   * screen content differs from the previous frame (detected by ref identity).\n   * Between change points, keep every other static frame so temporal coverage\n   * is maintained without bloating the buffer. This ensures a brief toast\n   * that produces a new keyframe is never thinned out.\n   *\n   * If smart thinning alone cannot reduce below maxFrames (e.g. the\n   * screen is constantly changing and every frame is a change point), a\n   * second pass of uniform sampling enforces the hard cap while keeping\n   * temporal coverage.\n   */\n  private thinBuffer(frames: DeviceFrameRef[]): DeviceFrameRef[] {\n    if (frames.length <= 1) return frames;\n\n    // Step 1: identify change points.\n    const isChangePoint = new Array(frames.length).fill(false);\n    isChangePoint[0] = true; // always keep the first frame\n    for (let i = 1; i < frames.length; i++) {\n      if (frames[i].ref !== frames[i - 1].ref) {\n        isChangePoint[i] = true;\n      }\n    }\n    // Always keep the last frame too (closest to when stop() fires).\n    isChangePoint[frames.length - 1] = true;\n\n    // Step 2: keep change points plus every other static frame.\n    let result: DeviceFrameRef[] = [];\n    let staticCounter = 0;\n    for (let i = 0; i < frames.length; i++) {\n      if (isChangePoint[i]) {\n        result.push(frames[i]);\n        staticCounter = 0;\n      } else if (staticCounter % 2 === 0) {\n        result.push(frames[i]);\n        staticCounter++;\n      } else {\n        staticCounter++;\n      }\n    }\n\n    // Step 3: hard cap — if still over maxFrames, uniformly sample\n    // down to the limit. This handles the all-change-points case (animation,\n    // video, scrolling) where Step 2 is effectively a no-op.\n    if (result.length > this.maxFrames) {\n      const step = result.length / this.maxFrames;\n      const sampled: DeviceFrameRef[] = [];\n      for (let i = 0; i < this.maxFrames; i++) {\n        sampled.push(result[Math.floor(i * step)]);\n      }\n      // Always keep the last frame — it's the closest to stop().\n      sampled[this.maxFrames - 1] = result[result.length - 1];\n      debug(\n        `hard cap: uniformly sampled ${this.maxFrames} frames from ${result.length} change-point frames`,\n      );\n      result = sampled;\n    }\n\n    return result;\n  }\n\n  /** Deduplicate frame refs by identity, preserving first-seen order. */\n  private dedupeRefs(frames: DeviceFrameRef[]): DeviceFrameRef[] {\n    const seen = new Set<unknown>();\n    const result: DeviceFrameRef[] = [];\n    for (const frame of frames) {\n      if (!seen.has(frame.ref)) {\n        seen.add(frame.ref);\n        result.push(frame);\n      }\n    }\n    return result;\n  }\n}\n"],"names":["debug","getDebug","warnObserver","DEFAULT_INTERVAL_MS","MIN_INTERVAL_MS","DEFAULT_MAX_FRAMES","FIRST_FRAME_TIMEOUT_MS","DEFAULT_WATCHDOG_MS","MAX_FRAMES_TO_MODEL","UIObserver","assert","error","waitStart","Date","Promise","resolve","setTimeout","clearTimeout","uniqueRefs","results","shrunk","ref","i","representativePromise","representative","lastFrame","lastDecoded","ScreenshotItem","assertion","msg","opt","uiContext","prompt","sampled","uncachedRefs","r","f","indexByRef","Map","sequence","frame","totalFrames","base64","width","height","imageInfoOfBase64","resizeImgBase64","Math","base64s","factor","b64","tickStart","frames","isChangePoint","Array","result","staticCounter","step","seen","Set","deps"],"mappings":";;;;;;;;;;;;;;AAOA,MAAMA,QAAQC,SAAS;AACvB,MAAMC,eAAeD,SAAS,eAAe;IAAE,SAAS;AAAK;AAK7D,MAAME,sBAAsB;AAC5B,MAAMC,kBAAkB;AACxB,MAAMC,qBAAqB;AAE3B,MAAMC,yBAAyB;AAE/B,MAAMC,sBAAsB;AAG5B,MAAMC,sBAAsB;AAsErB,MAAMC;IA+BX,IAAI,aAAqB;QACvB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM;IAC3B;IAOA,MAAM,QAAuB;QAC3BC,OAAO,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;QAC3C,IAAI;YACF,IAAI,CAAC,MAAM,GAAI,MAAM,IAAI,CAAC,IAAI,CAAC,eAAe,MAAO;QACvD,EAAE,OAAOC,OAAO;YACdX,MAAM,CAAC,qDAAqD,EAAEW,OAAO;YACrE,IAAI,CAAC,MAAM,GAAG;QAChB;QACA,IAAI,CAAC,aAAa,GAAG,CAAC,IAAI,CAAC,MAAM;QACjC,IAAI,IAAI,CAAC,aAAa,EACpBX,MAAM;aACD;YAKL,MAAMY,YAAYC,KAAK,GAAG;YAC1B,MACE,CAAC,IAAI,CAAC,MAAM,CAAE,MAAM,MACpBA,KAAK,GAAG,KAAKD,YAAYN,uBAEzB,MAAM,IAAIQ,QAAQ,CAACC,UAAYC,WAAWD,SAAS;YAErD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAE,MAAM,IACtBf,MACE,CAAC,sBAAsB,EAAEM,uBAAuB,mBAAmB,CAAC;QAG1E;QACA,MAAM,IAAI,CAAC,WAAW;QACtB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO;QAG/B,IAAI,IAAI,CAAC,UAAU,GAAG,GAAG;YACvB,IAAI,CAAC,aAAa,GAAGU,WAAW;gBAC9Bd,aACE,CAAC,8BAA8B,EAAE,IAAI,CAAC,UAAU,CAAC,kDAAkD,CAAC;gBAEtGF,MAAM,CAAC,qBAAqB,EAAE,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC;gBAChE,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,KAAO;YAC3B,GAAG,IAAI,CAAC,UAAU;YAClB,IACE,AACA,cADA,OAAQ,IAAI,CAAC,aAAa,CAA4B,KAAK,EAG1D,IAAI,CAAC,aAAa,CAA2B,KAAK;QAEvD;IACF;IAQA,MAAM,OAAsB;QAC1B,IAAI,IAAI,CAAC,OAAO,EAAE;QAClB,IAAI,CAAC,OAAO,GAAG;QAGf,IAAI,IAAI,CAAC,aAAa,EAAE;YACtBiB,aAAa,IAAI,CAAC,aAAa;YAC/B,IAAI,CAAC,aAAa,GAAG;QACvB;QAEA,MAAM,IAAI,CAAC,WAAW;QAEtB,IAAI;YAIF,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,GAAG;gBACzC,MAAMC,aAAa,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM;gBAC9C,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAChC,MAAM,CAACA,YACP,IAAI,CAAC,OAAOC;oBACX,MAAMC,SAAS,MAAM,IAAI,CAAC,iBAAiB,CAACD;oBAC5CD,WAAW,OAAO,CAAC,CAACG,KAAKC;wBACvB,IAAI,CAAC,YAAY,CAAC,GAAG,CAACD,IAAI,GAAG,EAAED,MAAM,CAACE,EAAE;oBAC1C;oBACAtB,MAAM,CAAC,YAAY,EAAEkB,WAAW,MAAM,CAAC,OAAO,CAAC;gBACjD,GACC,KAAK,CAAC,CAACP;oBACNX,MAAM,CAAC,8CAA8C,EAAEW,OAAO;gBAChE;YACJ;YAIA,MAAMY,wBAAwB,IAAI,CAAC,IAAI,CAAC,qBAAqB;YAE7D,MAAM,GAAGC,eAAe,GAAG,MAAMV,QAAQ,GAAG,CAAC;gBAC3C,IAAI,CAAC,gBAAgB;gBACrBS;aACD;YAKD,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,GAAG;gBACzC,MAAME,YAAY,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,EAAE;gBACrD,MAAMC,cAAc,IAAI,CAAC,YAAY,CAAC,GAAG,CAACD,UAAU,GAAG;gBACvD,IAAIC,aAAa;oBACfF,eAAe,UAAU,GAAGG,eAAe,MAAM,CAC/CD,aACAD,UAAU,UAAU;oBAEtBzB,MAAM;gBACR;YACF;YAEA,IAAI,CAAC,cAAc,GAAGwB;QACxB,SAAU;YAKR,IAAI,IAAI,CAAC,MAAM,EACb,IAAI;gBACF,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI;YACxB,EAAE,OAAOb,OAAO;gBACdX,MAAM,CAAC,6BAA6B,EAAEW,OAAO;YAC/C;YAGFX,MACE,CAAC,yBAAyB,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,oCAAoC,CAAC;YAItF,IAAI,CAAC,IAAI,CAAC,SAAS;QACrB;IACF;IAQA,MAAM,SACJ4B,SAAiB,EACjBC,GAAY,EACZC,GAA2C,EAG3C;QACA,MAAMC,YAAY,MAAM,IAAI,CAAC,sBAAsB;QACnD,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAACH,WAAWG,WAAWF,KAAKC;IACxD;IAGA,MAAM,UACJE,MAAc,EACdF,GAA0B,EACR;QAClB,MAAMC,YAAY,MAAM,IAAI,CAAC,sBAAsB;QACnD,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAACC,QAAQD,WAAWD;IACjD;IAEA,MAAc,yBAA6C;QACzDpB,OACE,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,cAAc,EACnC;QAEF,MAAMc,iBAAiB,IAAI,CAAC,cAAc;QAI1C,IAAI,IAAI,CAAC,gBAAgB,EAAE;YACzB,MAAM,IAAI,CAAC,gBAAgB;YAC3B,IAAI,CAAC,gBAAgB,GAAG;QAC1B;QAMA,MAAMS,UAAU,IAAI,CAAC,MAAM;QAC3B,MAAMf,aAAa,IAAI,CAAC,UAAU,CAACe;QAGnC,MAAMC,eAAehB,WAAW,MAAM,CACpC,CAACiB,IAAM,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAACA,EAAE,GAAG;QAErC,IAAID,aAAa,MAAM,GAAG,GAAG;YAC3B,MAAMf,UAAU,IAAI,CAAC,MAAM,GACvB,MAAM,IAAI,CAAC,iBAAiB,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAACe,iBACtDA,aAAa,GAAG,CAAC,CAACE,IAAMA,EAAE,GAAG;YACjC1B,OACES,QAAQ,MAAM,KAAKe,aAAa,MAAM,EACtC;YAEFA,aAAa,OAAO,CAAC,CAACb,KAAKC;gBACzB,IAAI,CAAC,YAAY,CAAC,GAAG,CAACD,IAAI,GAAG,EAAEF,OAAO,CAACG,EAAE;YAC3C;YACAtB,MACE,CAAC,QAAQ,EAAEkC,aAAa,MAAM,CAAC,aAAa,EAAEhB,WAAW,MAAM,GAAGgB,aAAa,MAAM,CAAC,YAAY,CAAC;QAEvG;QAGA,MAAMG,aAAa,IAAIC;QACvBpB,WAAW,OAAO,CAAC,CAACG,KAAKC,IAAMe,WAAW,GAAG,CAAChB,IAAI,GAAG,EAAEC;QAGvD,MAAMiB,WAAWN,QAAQ,GAAG,CAAC,CAACO,QAC5Bb,eAAe,MAAM,CACnB,IAAI,CAAC,YAAY,CAAC,GAAG,CAACa,MAAM,GAAG,GAC/BA,MAAM,UAAU;QAIpB,MAAMC,cAAcF,SAAS,MAAM,GAAG;QACtC,IAAIE,cAAcjC,qBAChBN,aACE,CAAC,iBAAiB,EAAEuC,YAAY,iCAAiC,EAAEjC,oBAAoB,+EAA+E,CAAC;QAI3KR,MACE,CAAC,kBAAkB,EAAEuC,SAAS,MAAM,CACjC,qBAAW,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAErB,WAAW,MAAM,CAC7D,iBAAe,EAAEgB,aAAa,MAAM,CAAC,CAAC,CAFO;QAIlD,OAAO;YACL,GAAGV,cAAc;YACjB,oBAAoB;mBAAIe;gBAAUf,eAAe,UAAU;aAAC;QAC9D;IACF;IAEA,MAAc,cAA6B;QACzC,IAAI;YACF,IAAI,IAAI,CAAC,MAAM,EAAE;gBACf,MAAMgB,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM;gBAChC,IAAIA,OAAO,IAAI,CAAC,SAAS,CAACA;gBAC1B;YACF;YACA,IAAIE,SAAS,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU;YAGvC,IAAI,IAAI,CAAC,sBAAsB,GAAG,GAAG;gBACnC,MAAM,EAAEC,KAAK,EAAEC,MAAM,EAAE,GAAG,MAAMC,kBAAkBH;gBAClDA,SAAS,MAAMI,gBAAgBJ,QAAQ;oBACrC,OAAOK,KAAK,KAAK,CAACJ,QAAQ,IAAI,CAAC,sBAAsB;oBACrD,QAAQI,KAAK,KAAK,CAACH,SAAS,IAAI,CAAC,sBAAsB;gBACzD;YACF;YACA,IAAI,CAAC,SAAS,CAAC;gBAAE,KAAKF;gBAAQ,YAAY7B,KAAK,GAAG;YAAG;QACvD,EAAE,OAAOF,OAAO;YACdX,MAAM,CAAC,qCAAqC,EAAEW,OAAO;QACvD;IACF;IASA,MAAc,kBAAkBqC,OAAiB,EAAqB;QACpE,IAAI,IAAI,CAAC,sBAAsB,IAAI,GAAG,OAAOA;QAC7C,MAAMC,SAAS,IAAI,CAAC,sBAAsB;QAC1C,OAAOnC,QAAQ,GAAG,CAChBkC,QAAQ,GAAG,CAAC,OAAOE;YACjB,MAAM,EAAEP,KAAK,EAAEC,MAAM,EAAE,GAAG,MAAMC,kBAAkBK;YAClD,OAAOJ,gBAAgBI,KAAK;gBAC1B,OAAOH,KAAK,KAAK,CAACJ,QAAQM;gBAC1B,QAAQF,KAAK,KAAK,CAACH,SAASK;YAC9B;QACF;IAEJ;IAEA,MAAc,UAAyB;QACrC,MAAO,CAAC,IAAI,CAAC,OAAO,CAAE;YACpB,MAAME,YAAYtC,KAAK,GAAG;YAC1B,MAAM,IAAI,CAAC,WAAW;YAGtB,MAAO,CAAC,IAAI,CAAC,OAAO,IAAIA,KAAK,GAAG,KAAKsC,YAAY,IAAI,CAAC,UAAU,CAC9D,MAAM,IAAIrC,QAAQ,CAACC,UAAYC,WAAWD,SAAS;QAEvD;IACF;IAEQ,UAAUyB,KAAqB,EAAQ;QAC7C,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE;YACxC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM;YACzCxC,MAAM,CAAC,wBAAwB,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;QAC9D;QACA,IAAI,CAAC,MAAM,CAAC,IAAI,CAACwC;IACnB;IAcQ,WAAWY,MAAwB,EAAoB;QAC7D,IAAIA,OAAO,MAAM,IAAI,GAAG,OAAOA;QAG/B,MAAMC,gBAAgB,IAAIC,MAAMF,OAAO,MAAM,EAAE,IAAI,CAAC;QACpDC,aAAa,CAAC,EAAE,GAAG;QACnB,IAAK,IAAI/B,IAAI,GAAGA,IAAI8B,OAAO,MAAM,EAAE9B,IACjC,IAAI8B,MAAM,CAAC9B,EAAE,CAAC,GAAG,KAAK8B,MAAM,CAAC9B,IAAI,EAAE,CAAC,GAAG,EACrC+B,aAAa,CAAC/B,EAAE,GAAG;QAIvB+B,aAAa,CAACD,OAAO,MAAM,GAAG,EAAE,GAAG;QAGnC,IAAIG,SAA2B,EAAE;QACjC,IAAIC,gBAAgB;QACpB,IAAK,IAAIlC,IAAI,GAAGA,IAAI8B,OAAO,MAAM,EAAE9B,IACjC,IAAI+B,aAAa,CAAC/B,EAAE,EAAE;YACpBiC,OAAO,IAAI,CAACH,MAAM,CAAC9B,EAAE;YACrBkC,gBAAgB;QAClB,OAAO,IAAIA,gBAAgB,MAAM,GAAG;YAClCD,OAAO,IAAI,CAACH,MAAM,CAAC9B,EAAE;YACrBkC;QACF,OACEA;QAOJ,IAAID,OAAO,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE;YAClC,MAAME,OAAOF,OAAO,MAAM,GAAG,IAAI,CAAC,SAAS;YAC3C,MAAMtB,UAA4B,EAAE;YACpC,IAAK,IAAIX,IAAI,GAAGA,IAAI,IAAI,CAAC,SAAS,EAAEA,IAClCW,QAAQ,IAAI,CAACsB,MAAM,CAACR,KAAK,KAAK,CAACzB,IAAImC,MAAM;YAG3CxB,OAAO,CAAC,IAAI,CAAC,SAAS,GAAG,EAAE,GAAGsB,MAAM,CAACA,OAAO,MAAM,GAAG,EAAE;YACvDvD,MACE,CAAC,4BAA4B,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,EAAEuD,OAAO,MAAM,CAAC,oBAAoB,CAAC;YAElGA,SAAStB;QACX;QAEA,OAAOsB;IACT;IAGQ,WAAWH,MAAwB,EAAoB;QAC7D,MAAMM,OAAO,IAAIC;QACjB,MAAMJ,SAA2B,EAAE;QACnC,KAAK,MAAMf,SAASY,OAClB,IAAI,CAACM,KAAK,GAAG,CAAClB,MAAM,GAAG,GAAG;YACxBkB,KAAK,GAAG,CAAClB,MAAM,GAAG;YAClBe,OAAO,IAAI,CAACf;QACd;QAEF,OAAOe;IACT;IAvYA,YACmBK,IAAoB,EACrC9B,GAAsB,CACtB;;QAnBF,uBAAQ,UAAR;QACA,uBAAQ,UAAR;QACA,uBAAQ,iBAAR;QACA,uBAAQ,WAAR;QACA,uBAAQ,eAAR;QACA,uBAAQ,kBAAR;QACA,uBAAQ,iBAAR;QAEA,uBAAQ,gBAAR;QAEA,uBAAQ,oBAAR;QACA,uBAAiB,cAAjB;QACA,uBAAiB,aAAjB;QACA,uBAAiB,cAAjB;QACA,uBAAiB,0BAAjB;aAGmB8B,IAAI,GAAJA;aAjBX,MAAM,GAAqB,EAAE;aAC7B,MAAM,GAA6B;aACnC,aAAa,GAAG;aAChB,OAAO,GAAG;aACV,WAAW,GAAyB;aACpC,cAAc,GAAqB;aACnC,aAAa,GAAyC;aAEtD,YAAY,GAAG,IAAItB;aAEnB,gBAAgB,GAAyB;QAU/C,IAAI,CAAC,UAAU,GAAGS,KAAK,GAAG,CACxB3C,iBACA0B,KAAK,cAAc3B;QAErB,IAAI,CAAC,SAAS,GAAG4C,KAAK,GAAG,CAAC,GAAGjB,KAAK,aAAazB;QAC/C,IAAI,CAAC,UAAU,GAAGyB,KAAK,cAAcvB;QACrC,IAAI,CAAC,sBAAsB,GAAGqD,KAAK,sBAAsB,IAAI;IAC/D;AA6XF"}