{"version":3,"file":"agent/task-cache.mjs","sources":["../../../src/agent/task-cache.ts"],"sourcesContent":["import assert from 'node:assert';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { isDeepStrictEqual } from 'node:util';\nimport type { TUserPrompt } from '@/ai-model';\nimport type { ElementCacheFeature } from '@/types';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n  MIDSCENE_CACHE_MAX_FILENAME_LENGTH,\n  globalConfigManager,\n} from '@midscene/shared/env';\nimport { getDebug } from '@midscene/shared/logger';\nimport { ifInBrowser, ifInWorker } from '@midscene/shared/utils';\nimport { generateHashId } from '@midscene/shared/utils';\nimport { replaceIllegalPathCharsAndSpace } from '@midscene/shared/utils';\nimport yaml from 'js-yaml';\nimport semver from 'semver';\nimport { getMidsceneVersion } from './utils';\n\nconst DEFAULT_CACHE_MAX_FILENAME_LENGTH = 200;\n\nexport const debug = getDebug('cache');\nconst warn = getDebug('cache', { console: true });\n\nexport interface PlanningCache {\n  type: 'plan';\n  prompt: TUserPrompt;\n  yamlWorkflow: string;\n}\n\nexport interface LocateCache {\n  type: 'locate';\n  prompt: TUserPrompt;\n  cache?: ElementCacheFeature;\n  /** @deprecated kept for backward compatibility */\n  xpaths?: string[];\n}\n\nexport interface MatchCacheResult<T extends PlanningCache | LocateCache> {\n  cacheContent: T;\n  cacheUsable: boolean;\n  updateFn: (cb: (cache: T) => void) => void;\n}\n\nexport type CacheFileContent = {\n  midsceneVersion: string;\n  cacheId: string;\n  caches: Array<PlanningCache | LocateCache>;\n};\n\nconst lowestSupportedMidsceneVersion = '0.16.10';\nexport const cacheFileExt = '.cache.yaml';\n\nexport class TaskCache {\n  cacheId: string;\n\n  cacheFilePath?: string;\n\n  cache: CacheFileContent;\n\n  isCacheResultUsed: boolean; // a flag to indicate if the cache result should be used\n  cacheOriginalLength: number;\n\n  readOnlyMode: boolean; // a flag to indicate if the cache is in read-only mode\n\n  writeOnlyMode: boolean; // a flag to indicate if the cache is in write-only mode\n\n  private matchedCacheIndices: Set<string> = new Set(); // Track matched records\n\n  // Per `${type}:${promptStr}`, the in-memory indices of cache entries consumed\n  // by matchCache this run (most recent last). markLocateCacheStale() drains\n  // from here into staleCacheIndices when the consumed entry's element is\n  // rejected by a failed action.\n  private consumedCacheIndices: Map<string, number[]> = new Map();\n\n  // Per `${type}:${promptStr}`, indices of consumed entries whose backing\n  // element was rejected (the action using it failed and the run replanned).\n  // Only these are replaced in place on the re-locate; every other write\n  // appends, so legitimately repeated prompts keep one entry per occurrence.\n  private staleCacheIndices: Map<string, number[]> = new Map();\n\n  constructor(\n    cacheId: string,\n    isCacheResultUsed: boolean,\n    cacheFilePath?: string,\n    options: {\n      readOnly?: boolean;\n      writeOnly?: boolean;\n      cacheDir?: string;\n    } = {},\n  ) {\n    assert(cacheId, 'cacheId is required');\n    if (\n      options.cacheDir !== undefined &&\n      (typeof options.cacheDir !== 'string' || !options.cacheDir.trim())\n    ) {\n      throw new Error('cacheDir must be a non-empty string when provided');\n    }\n    const cacheDir = options.cacheDir?.trim();\n    let safeCacheId = replaceIllegalPathCharsAndSpace(cacheId);\n    const cacheMaxFilenameLength =\n      globalConfigManager.getEnvConfigValueAsNumber(\n        MIDSCENE_CACHE_MAX_FILENAME_LENGTH,\n      ) ?? DEFAULT_CACHE_MAX_FILENAME_LENGTH;\n    if (Buffer.byteLength(safeCacheId, 'utf8') > cacheMaxFilenameLength) {\n      const prefix = safeCacheId.slice(0, 32);\n      const hash = generateHashId(undefined, safeCacheId);\n      safeCacheId = `${prefix}-${hash}`;\n    }\n    this.cacheId = safeCacheId;\n\n    this.cacheFilePath =\n      ifInBrowser || ifInWorker\n        ? undefined\n        : cacheFilePath ||\n          join(\n            cacheDir || getMidsceneRunSubDir('cache'),\n            `${this.cacheId}${cacheFileExt}`,\n          );\n    const readOnlyMode = Boolean(options?.readOnly);\n    const writeOnlyMode = Boolean(options?.writeOnly);\n\n    if (readOnlyMode && writeOnlyMode) {\n      throw new Error('TaskCache cannot be both read-only and write-only');\n    }\n\n    this.isCacheResultUsed = writeOnlyMode ? false : isCacheResultUsed;\n    this.readOnlyMode = readOnlyMode;\n    this.writeOnlyMode = writeOnlyMode;\n\n    let cacheContent;\n    if (this.cacheFilePath && !this.writeOnlyMode) {\n      cacheContent = this.loadCacheFromFile();\n    }\n    if (!cacheContent) {\n      cacheContent = {\n        midsceneVersion: getMidsceneVersion(),\n        cacheId: this.cacheId,\n        caches: [],\n      };\n    }\n    this.cache = cacheContent;\n    this.cacheOriginalLength = this.isCacheResultUsed\n      ? this.cache.caches.length\n      : 0;\n  }\n\n  matchCache(\n    prompt: TUserPrompt,\n    type: 'plan' | 'locate',\n  ): MatchCacheResult<PlanningCache | LocateCache> | undefined {\n    if (!this.isCacheResultUsed) {\n      return undefined;\n    }\n    // Find the first unused matching cache\n    const promptStr = this.promptKey(prompt);\n    for (let i = 0; i < this.cacheOriginalLength; i++) {\n      const item = this.cache.caches[i];\n      const key = `${type}:${promptStr}:${i}`;\n      if (\n        item.type === type &&\n        isDeepStrictEqual(item.prompt, prompt) &&\n        !this.matchedCacheIndices.has(key)\n      ) {\n        if (item.type === 'locate') {\n          const locateItem = item as LocateCache;\n          if (!locateItem.cache && Array.isArray(locateItem.xpaths)) {\n            locateItem.cache = { xpaths: locateItem.xpaths };\n          }\n          if ('xpaths' in locateItem) {\n            locateItem.xpaths = undefined;\n          }\n        }\n        this.matchedCacheIndices.add(key);\n        const consumeKey = `${type}:${promptStr}`;\n        const consumed = this.consumedCacheIndices.get(consumeKey) ?? [];\n        consumed.push(i);\n        this.consumedCacheIndices.set(consumeKey, consumed);\n        debug(\n          'cache found and marked as used, type: %s, prompt: %s, index: %d',\n          type,\n          prompt,\n          i,\n        );\n        return {\n          cacheContent: item,\n          cacheUsable: true,\n          updateFn: (cb: (cache: PlanningCache | LocateCache) => void) => {\n            debug(\n              'will call updateFn to update cache, type: %s, prompt: %s, index: %d',\n              type,\n              prompt,\n              i,\n            );\n            cb(item);\n\n            if (this.readOnlyMode) {\n              debug(\n                'read-only mode, cache updated in memory but not flushed to file',\n              );\n              return;\n            }\n\n            debug(\n              'cache updated, will flush to file, type: %s, prompt: %s, index: %d',\n              type,\n              prompt,\n              i,\n            );\n            this.flushCacheToFile();\n          },\n        };\n      }\n    }\n    debug('no unused cache found, type: %s, prompt: %s', type, prompt);\n    return undefined;\n  }\n\n  matchPlanCache(\n    prompt: TUserPrompt,\n  ): MatchCacheResult<PlanningCache> | undefined {\n    const result = this.matchCache(prompt, 'plan') as\n      | MatchCacheResult<PlanningCache>\n      | undefined;\n    if (!result) return undefined;\n    // Guard against stale cache files written before the write-side fix\n    const yamlWorkflow = result.cacheContent.yamlWorkflow;\n    if (!yamlWorkflow?.trim()) {\n      debug(\n        'plan cache matched but yamlWorkflow is empty, treat as cache miss',\n      );\n      return {\n        ...result,\n        cacheUsable: false,\n      };\n    }\n    try {\n      const parsed = yaml.load(yamlWorkflow) as any;\n      const hasNonEmptyFlow = parsed?.tasks?.some(\n        (task: any) => Array.isArray(task.flow) && task.flow.length > 0,\n      );\n      if (!hasNonEmptyFlow) {\n        debug('plan cache matched but flow is empty, treat as cache miss');\n        return {\n          ...result,\n          cacheUsable: false,\n        };\n      }\n    } catch {\n      debug(\n        'plan cache matched but yamlWorkflow is invalid, treat as cache miss',\n      );\n      return {\n        ...result,\n        cacheUsable: false,\n      };\n    }\n    return result;\n  }\n\n  matchLocateCache(\n    prompt: TUserPrompt,\n  ): MatchCacheResult<LocateCache> | undefined {\n    return this.matchCache(prompt, 'locate') as\n      | MatchCacheResult<LocateCache>\n      | undefined;\n  }\n\n  appendCache(cache: PlanningCache | LocateCache) {\n    debug('will append cache', cache);\n    this.cache.caches.push(cache);\n\n    if (this.readOnlyMode) {\n      debug('read-only mode, cache appended to memory but not flushed to file');\n      return;\n    }\n\n    this.flushCacheToFile();\n  }\n\n  loadCacheFromFile() {\n    const cacheFile = this.cacheFilePath;\n    assert(cacheFile, 'cache file path is required');\n\n    if (!existsSync(cacheFile)) {\n      debug('no cache file found, path: %s', cacheFile);\n      return undefined;\n    }\n\n    // detect old cache file\n    const jsonTypeCacheFile = cacheFile.replace(cacheFileExt, '.json');\n    if (existsSync(jsonTypeCacheFile) && this.isCacheResultUsed) {\n      console.warn(\n        `An outdated cache file from an earlier version of Midscene has been detected. Since version 0.17, we have implemented an improved caching strategy. Please delete the old file located at: ${jsonTypeCacheFile}.`,\n      );\n      return undefined;\n    }\n\n    try {\n      const data = readFileSync(cacheFile, 'utf8');\n      const jsonData = yaml.load(data) as CacheFileContent;\n\n      const version = getMidsceneVersion();\n      if (!version) {\n        debug('no midscene version info, will not read cache from file');\n        return undefined;\n      }\n\n      if (\n        semver.lt(jsonData.midsceneVersion, lowestSupportedMidsceneVersion) &&\n        !jsonData.midsceneVersion.includes('beta') // for internal test\n      ) {\n        console.warn(\n          `You are using an old version of Midscene cache file, and we cannot match any info from it. Starting from Midscene v0.17, we changed our strategy to use xpath for cache info, providing better performance.\\nPlease delete the existing cache and rebuild it. Sorry for the inconvenience.\\ncache file: ${cacheFile}`,\n        );\n        return undefined;\n      }\n\n      debug(\n        'cache loaded from file, path: %s, cache version: %s, record length: %s',\n        cacheFile,\n        jsonData.midsceneVersion,\n        jsonData.caches.length,\n      );\n      jsonData.midsceneVersion = getMidsceneVersion(); // update the version\n      return jsonData;\n    } catch (err) {\n      debug(\n        'cache file exists but load failed, path: %s, error: %s',\n        cacheFile,\n        err,\n      );\n      return undefined;\n    }\n  }\n\n  flushCacheToFile(options?: { cleanUnused?: boolean }) {\n    const version = getMidsceneVersion();\n    if (!version) {\n      debug('no midscene version info, will not write cache to file');\n      return;\n    }\n\n    if (!this.cacheFilePath) {\n      debug('no cache file path, will not write cache to file');\n      return;\n    }\n\n    // Clean unused caches if requested\n    if (options?.cleanUnused) {\n      // Skip cleaning in write-only mode or when cache is not used\n      if (this.isCacheResultUsed) {\n        const originalLength = this.cache.caches.length;\n\n        // Collect indices of used caches\n        const usedIndices = new Set<number>();\n        for (const key of this.matchedCacheIndices) {\n          // key format: \"type:prompt:index\"\n          const parts = key.split(':');\n          const index = Number.parseInt(parts[parts.length - 1], 10);\n          if (!Number.isNaN(index)) {\n            usedIndices.add(index);\n          }\n        }\n\n        // Filter: keep used caches and newly added caches\n        this.cache.caches = this.cache.caches.filter((_, index) => {\n          const isUsed = usedIndices.has(index);\n          const isNew = index >= this.cacheOriginalLength;\n          return isUsed || isNew;\n        });\n\n        const removedCount = originalLength - this.cache.caches.length;\n        if (removedCount > 0) {\n          debug('cleaned %d unused cache record(s)', removedCount);\n        } else {\n          debug('no unused cache to clean');\n        }\n      } else {\n        debug('skip cleaning: cache is not used for reading');\n      }\n    }\n\n    try {\n      const dir = dirname(this.cacheFilePath);\n      if (!existsSync(dir)) {\n        mkdirSync(dir, { recursive: true });\n        debug('created cache directory: %s', dir);\n      }\n\n      // Sort caches to ensure plan entries come before locate entries for better readability\n      // Create a sorted copy for writing to disk while keeping in-memory order unchanged\n      const sortedCaches = [...this.cache.caches].sort((a, b) => {\n        if (a.type === 'plan' && b.type === 'locate') return -1;\n        if (a.type === 'locate' && b.type === 'plan') return 1;\n        return 0;\n      });\n\n      const cacheToWrite = {\n        ...this.cache,\n        caches: sortedCaches,\n      };\n\n      const yamlData = yaml.dump(cacheToWrite, { lineWidth: -1 });\n      writeFileSync(this.cacheFilePath, yamlData);\n      debug('cache flushed to file: %s', this.cacheFilePath);\n    } catch (err) {\n      warn(\n        `write cache to file failed, path: ${this.cacheFilePath}, error: ${err}`,\n      );\n    }\n  }\n\n  // Single source of truth for turning a prompt into a stable string key.\n  // matchCache and updateOrAppendCacheRecord must agree on this, otherwise a\n  // consumed entry can never be found again for in-place replacement.\n  private promptKey(prompt: TUserPrompt): string {\n    return typeof prompt === 'string' ? prompt : JSON.stringify(prompt);\n  }\n\n  // Copy the mutable payload of `newRecord` into an existing cache entry.\n  // Shared by the live-record update path and replaceCacheRecord so the field\n  // list lives in exactly one place. Caller guarantees same `type`.\n  private applyRecordInto(\n    target: PlanningCache | LocateCache,\n    newRecord: PlanningCache | LocateCache,\n  ) {\n    if (newRecord.type === 'plan') {\n      (target as PlanningCache).yamlWorkflow = newRecord.yamlWorkflow;\n    } else {\n      const locateCache = target as LocateCache;\n      locateCache.cache = newRecord.cache;\n      if ('xpaths' in locateCache) {\n        locateCache.xpaths = undefined;\n      }\n    }\n  }\n\n  updateOrAppendCacheRecord(\n    newRecord: PlanningCache | LocateCache,\n    cachedRecord?: MatchCacheResult<PlanningCache | LocateCache>,\n  ) {\n    if (cachedRecord) {\n      // update existing record\n      cachedRecord.updateFn((cache) => {\n        this.applyRecordInto(cache, newRecord);\n      });\n    } else {\n      // No live MatchCacheResult was passed. This is either a genuine first-time\n      // miss or a replanning re-locate after the previously matched entry was\n      // rejected. Only replace an entry that was explicitly marked stale (its\n      // element caused a failed action); otherwise append. Without this gate a\n      // legitimately repeated prompt — located more times than it has cached\n      // entries — would overwrite a still-valid entry instead of appending.\n      const consumeKey = `${newRecord.type}:${this.promptKey(newRecord.prompt)}`;\n      const staleIndex = this.staleCacheIndices.get(consumeKey)?.pop();\n      if (staleIndex !== undefined && this.cache.caches[staleIndex]) {\n        debug(\n          'replacing stale cache entry in place, type: %s, prompt: %s, index: %d',\n          newRecord.type,\n          newRecord.prompt,\n          staleIndex,\n        );\n        this.replaceCacheRecord(staleIndex, newRecord);\n      } else {\n        this.appendCache(newRecord);\n      }\n    }\n  }\n\n  /**\n   * Mark the most recently consumed locate cache entry for `prompt` as stale.\n   * Call this when an action that used the cache-hit element failed and the run\n   * is about to replan: the subsequent re-locate then replaces this entry in\n   * place instead of appending a duplicate, which would otherwise be matched\n   * first on the next run and re-trigger replanning forever (#2529).\n   *\n   * No-op when nothing was consumed for the prompt, so a plain first-time miss\n   * (and any repeated prompt that never failed) still appends normally.\n   */\n  markLocateCacheStale(prompt: TUserPrompt) {\n    const consumeKey = `locate:${this.promptKey(prompt)}`;\n    const index = this.consumedCacheIndices.get(consumeKey)?.pop();\n    if (index === undefined) {\n      return;\n    }\n    const stale = this.staleCacheIndices.get(consumeKey) ?? [];\n    stale.push(index);\n    this.staleCacheIndices.set(consumeKey, stale);\n    debug(\n      'marked locate cache entry as stale, prompt: %s, index: %d',\n      prompt,\n      index,\n    );\n  }\n\n  private replaceCacheRecord(\n    index: number,\n    newRecord: PlanningCache | LocateCache,\n  ) {\n    const target = this.cache.caches[index];\n    // Consumed indices are recorded per `${type}:${prompt}` in matchCache, which\n    // only stores entries whose `item.type === type`, so the target must share\n    // newRecord's type. Assert it to make the invariant explicit and fail fast.\n    assert(\n      target.type === newRecord.type,\n      `cache record type mismatch on replace: expected ${newRecord.type}, got ${target.type}`,\n    );\n    this.applyRecordInto(target, newRecord);\n\n    if (this.readOnlyMode) {\n      debug('read-only mode, cache replaced in memory but not flushed to file');\n      return;\n    }\n\n    this.flushCacheToFile();\n  }\n}\n"],"names":["DEFAULT_CACHE_MAX_FILENAME_LENGTH","debug","getDebug","warn","lowestSupportedMidsceneVersion","cacheFileExt","TaskCache","prompt","type","promptStr","i","item","key","isDeepStrictEqual","locateItem","Array","undefined","consumeKey","consumed","cb","result","yamlWorkflow","parsed","yaml","hasNonEmptyFlow","task","cache","cacheFile","assert","existsSync","jsonTypeCacheFile","console","data","readFileSync","jsonData","version","getMidsceneVersion","semver","err","options","originalLength","usedIndices","Set","parts","index","Number","_","isUsed","isNew","removedCount","dir","dirname","mkdirSync","sortedCaches","a","b","cacheToWrite","yamlData","writeFileSync","JSON","target","newRecord","locateCache","cachedRecord","staleIndex","stale","cacheId","isCacheResultUsed","cacheFilePath","Map","Error","cacheDir","safeCacheId","replaceIllegalPathCharsAndSpace","cacheMaxFilenameLength","globalConfigManager","MIDSCENE_CACHE_MAX_FILENAME_LENGTH","Buffer","prefix","hash","generateHashId","ifInBrowser","ifInWorker","join","getMidsceneRunSubDir","readOnlyMode","Boolean","writeOnlyMode","cacheContent"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAmBA,MAAMA,oCAAoC;AAEnC,MAAMC,QAAQC,SAAS;AAC9B,MAAMC,OAAOD,SAAS,SAAS;IAAE,SAAS;AAAK;AA4B/C,MAAME,iCAAiC;AAChC,MAAMC,eAAe;AAErB,MAAMC;IA8FX,WACEC,MAAmB,EACnBC,IAAuB,EACoC;QAC3D,IAAI,CAAC,IAAI,CAAC,iBAAiB,EACzB;QAGF,MAAMC,YAAY,IAAI,CAAC,SAAS,CAACF;QACjC,IAAK,IAAIG,IAAI,GAAGA,IAAI,IAAI,CAAC,mBAAmB,EAAEA,IAAK;YACjD,MAAMC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAACD,EAAE;YACjC,MAAME,MAAM,GAAGJ,KAAK,CAAC,EAAEC,UAAU,CAAC,EAAEC,GAAG;YACvC,IACEC,KAAK,IAAI,KAAKH,QACdK,kBAAkBF,KAAK,MAAM,EAAEJ,WAC/B,CAAC,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAACK,MAC9B;gBACA,IAAID,AAAc,aAAdA,KAAK,IAAI,EAAe;oBAC1B,MAAMG,aAAaH;oBACnB,IAAI,CAACG,WAAW,KAAK,IAAIC,MAAM,OAAO,CAACD,WAAW,MAAM,GACtDA,WAAW,KAAK,GAAG;wBAAE,QAAQA,WAAW,MAAM;oBAAC;oBAEjD,IAAI,YAAYA,YACdA,WAAW,MAAM,GAAGE;gBAExB;gBACA,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAACJ;gBAC7B,MAAMK,aAAa,GAAGT,KAAK,CAAC,EAAEC,WAAW;gBACzC,MAAMS,WAAW,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAACD,eAAe,EAAE;gBAChEC,SAAS,IAAI,CAACR;gBACd,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAACO,YAAYC;gBAC1CjB,MACE,mEACAO,MACAD,QACAG;gBAEF,OAAO;oBACL,cAAcC;oBACd,aAAa;oBACb,UAAU,CAACQ;wBACTlB,MACE,uEACAO,MACAD,QACAG;wBAEFS,GAAGR;wBAEH,IAAI,IAAI,CAAC,YAAY,EAAE,YACrBV,MACE;wBAKJA,MACE,sEACAO,MACAD,QACAG;wBAEF,IAAI,CAAC,gBAAgB;oBACvB;gBACF;YACF;QACF;QACAT,MAAM,+CAA+CO,MAAMD;IAE7D;IAEA,eACEA,MAAmB,EAC0B;QAC7C,MAAMa,SAAS,IAAI,CAAC,UAAU,CAACb,QAAQ;QAGvC,IAAI,CAACa,QAAQ;QAEb,MAAMC,eAAeD,OAAO,YAAY,CAAC,YAAY;QACrD,IAAI,CAACC,cAAc,QAAQ;YACzBpB,MACE;YAEF,OAAO;gBACL,GAAGmB,MAAM;gBACT,aAAa;YACf;QACF;QACA,IAAI;YACF,MAAME,SAASC,QAAAA,IAAS,CAACF;YACzB,MAAMG,kBAAkBF,QAAQ,OAAO,KACrC,CAACG,OAAcV,MAAM,OAAO,CAACU,KAAK,IAAI,KAAKA,KAAK,IAAI,CAAC,MAAM,GAAG;YAEhE,IAAI,CAACD,iBAAiB;gBACpBvB,MAAM;gBACN,OAAO;oBACL,GAAGmB,MAAM;oBACT,aAAa;gBACf;YACF;QACF,EAAE,OAAM;YACNnB,MACE;YAEF,OAAO;gBACL,GAAGmB,MAAM;gBACT,aAAa;YACf;QACF;QACA,OAAOA;IACT;IAEA,iBACEb,MAAmB,EACwB;QAC3C,OAAO,IAAI,CAAC,UAAU,CAACA,QAAQ;IAGjC;IAEA,YAAYmB,KAAkC,EAAE;QAC9CzB,MAAM,qBAAqByB;QAC3B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAACA;QAEvB,IAAI,IAAI,CAAC,YAAY,EAAE,YACrBzB,MAAM;QAIR,IAAI,CAAC,gBAAgB;IACvB;IAEA,oBAAoB;QAClB,MAAM0B,YAAY,IAAI,CAAC,aAAa;QACpCC,YAAOD,WAAW;QAElB,IAAI,CAACE,WAAWF,YAAY,YAC1B1B,MAAM,iCAAiC0B;QAKzC,MAAMG,oBAAoBH,UAAU,OAAO,CAACtB,cAAc;QAC1D,IAAIwB,WAAWC,sBAAsB,IAAI,CAAC,iBAAiB,EAAE,YAC3DC,QAAQ,IAAI,CACV,CAAC,2LAA2L,EAAED,kBAAkB,CAAC,CAAC;QAKtN,IAAI;YACF,MAAME,OAAOC,aAAaN,WAAW;YACrC,MAAMO,WAAWX,QAAAA,IAAS,CAACS;YAE3B,MAAMG,UAAUC;YAChB,IAAI,CAACD,SAAS,YACZlC,MAAM;YAIR,IACEoC,OAAO,EAAE,CAACH,SAAS,eAAe,EAAE9B,mCACpC,CAAC8B,SAAS,eAAe,CAAC,QAAQ,CAAC,SACnC,YACAH,QAAQ,IAAI,CACV,CAAC,wSAAwS,EAAEJ,WAAW;YAK1T1B,MACE,0EACA0B,WACAO,SAAS,eAAe,EACxBA,SAAS,MAAM,CAAC,MAAM;YAExBA,SAAS,eAAe,GAAGE;YAC3B,OAAOF;QACT,EAAE,OAAOI,KAAK;YACZrC,MACE,0DACA0B,WACAW;YAEF;QACF;IACF;IAEA,iBAAiBC,OAAmC,EAAE;QACpD,MAAMJ,UAAUC;QAChB,IAAI,CAACD,SAAS,YACZlC,MAAM;QAIR,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,YACvBA,MAAM;QAKR,IAAIsC,SAAS,aAEX,IAAI,IAAI,CAAC,iBAAiB,EAAE;YAC1B,MAAMC,iBAAiB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM;YAG/C,MAAMC,cAAc,IAAIC;YACxB,KAAK,MAAM9B,OAAO,IAAI,CAAC,mBAAmB,CAAE;gBAE1C,MAAM+B,QAAQ/B,IAAI,KAAK,CAAC;gBACxB,MAAMgC,QAAQC,OAAO,QAAQ,CAACF,KAAK,CAACA,MAAM,MAAM,GAAG,EAAE,EAAE;gBACvD,IAAI,CAACE,OAAO,KAAK,CAACD,QAChBH,YAAY,GAAG,CAACG;YAEpB;YAGA,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAACE,GAAGF;gBAC/C,MAAMG,SAASN,YAAY,GAAG,CAACG;gBAC/B,MAAMI,QAAQJ,SAAS,IAAI,CAAC,mBAAmB;gBAC/C,OAAOG,UAAUC;YACnB;YAEA,MAAMC,eAAeT,iBAAiB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM;YAC1DS,eAAe,IACjBhD,MAAM,qCAAqCgD,gBAE3ChD,MAAM;QAEV,OACEA,MAAM;QAIV,IAAI;YACF,MAAMiD,MAAMC,QAAQ,IAAI,CAAC,aAAa;YACtC,IAAI,CAACtB,WAAWqB,MAAM;gBACpBE,UAAUF,KAAK;oBAAE,WAAW;gBAAK;gBACjCjD,MAAM,+BAA+BiD;YACvC;YAIA,MAAMG,eAAe;mBAAI,IAAI,CAAC,KAAK,CAAC,MAAM;aAAC,CAAC,IAAI,CAAC,CAACC,GAAGC;gBACnD,IAAID,AAAW,WAAXA,EAAE,IAAI,IAAeC,AAAW,aAAXA,EAAE,IAAI,EAAe,OAAO;gBACrD,IAAID,AAAW,aAAXA,EAAE,IAAI,IAAiBC,AAAW,WAAXA,EAAE,IAAI,EAAa,OAAO;gBACrD,OAAO;YACT;YAEA,MAAMC,eAAe;gBACnB,GAAG,IAAI,CAAC,KAAK;gBACb,QAAQH;YACV;YAEA,MAAMI,WAAWlC,QAAAA,IAAS,CAACiC,cAAc;gBAAE,WAAW;YAAG;YACzDE,cAAc,IAAI,CAAC,aAAa,EAAED;YAClCxD,MAAM,6BAA6B,IAAI,CAAC,aAAa;QACvD,EAAE,OAAOqC,KAAK;YACZnC,KACE,CAAC,kCAAkC,EAAE,IAAI,CAAC,aAAa,CAAC,SAAS,EAAEmC,KAAK;QAE5E;IACF;IAKQ,UAAU/B,MAAmB,EAAU;QAC7C,OAAO,AAAkB,YAAlB,OAAOA,SAAsBA,SAASoD,KAAK,SAAS,CAACpD;IAC9D;IAKQ,gBACNqD,MAAmC,EACnCC,SAAsC,EACtC;QACA,IAAIA,AAAmB,WAAnBA,UAAU,IAAI,EACfD,OAAyB,YAAY,GAAGC,UAAU,YAAY;aAC1D;YACL,MAAMC,cAAcF;YACpBE,YAAY,KAAK,GAAGD,UAAU,KAAK;YACnC,IAAI,YAAYC,aACdA,YAAY,MAAM,GAAG9C;QAEzB;IACF;IAEA,0BACE6C,SAAsC,EACtCE,YAA4D,EAC5D;QACA,IAAIA,cAEFA,aAAa,QAAQ,CAAC,CAACrC;YACrB,IAAI,CAAC,eAAe,CAACA,OAAOmC;QAC9B;aACK;YAOL,MAAM5C,aAAa,GAAG4C,UAAU,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAACA,UAAU,MAAM,GAAG;YAC1E,MAAMG,aAAa,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC/C,aAAa;YAC3D,IAAI+C,AAAehD,WAAfgD,cAA4B,IAAI,CAAC,KAAK,CAAC,MAAM,CAACA,WAAW,EAAE;gBAC7D/D,MACE,yEACA4D,UAAU,IAAI,EACdA,UAAU,MAAM,EAChBG;gBAEF,IAAI,CAAC,kBAAkB,CAACA,YAAYH;YACtC,OACE,IAAI,CAAC,WAAW,CAACA;QAErB;IACF;IAYA,qBAAqBtD,MAAmB,EAAE;QACxC,MAAMU,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAACV,SAAS;QACrD,MAAMqC,QAAQ,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC3B,aAAa;QACzD,IAAI2B,AAAU5B,WAAV4B,OACF;QAEF,MAAMqB,QAAQ,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAChD,eAAe,EAAE;QAC1DgD,MAAM,IAAI,CAACrB;QACX,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC3B,YAAYgD;QACvChE,MACE,6DACAM,QACAqC;IAEJ;IAEQ,mBACNA,KAAa,EACbiB,SAAsC,EACtC;QACA,MAAMD,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM,CAAChB,MAAM;QAIvChB,YACEgC,OAAO,IAAI,KAAKC,UAAU,IAAI,EAC9B,CAAC,gDAAgD,EAAEA,UAAU,IAAI,CAAC,MAAM,EAAED,OAAO,IAAI,EAAE;QAEzF,IAAI,CAAC,eAAe,CAACA,QAAQC;QAE7B,IAAI,IAAI,CAAC,YAAY,EAAE,YACrB5D,MAAM;QAIR,IAAI,CAAC,gBAAgB;IACvB;IAnbA,YACEiE,OAAe,EACfC,iBAA0B,EAC1BC,aAAsB,EACtB7B,UAII,CAAC,CAAC,CACN;QApCF;QAEA;QAEA;QAEA;QACA;QAEA;QAEA;QAEA,uBAAQ,uBAAmC,IAAIG;QAM/C,uBAAQ,wBAA8C,IAAI2B;QAM1D,uBAAQ,qBAA2C,IAAIA;QAYrDzC,YAAOsC,SAAS;QAChB,IACE3B,AAAqBvB,WAArBuB,QAAQ,QAAQ,IACf,CAA4B,YAA5B,OAAOA,QAAQ,QAAQ,IAAiB,CAACA,QAAQ,QAAQ,CAAC,IAAI,EAAC,GAEhE,MAAM,IAAI+B,MAAM;QAElB,MAAMC,WAAWhC,QAAQ,QAAQ,EAAE;QACnC,IAAIiC,cAAcC,gCAAgCP;QAClD,MAAMQ,yBACJC,oBAAoB,yBAAyB,CAC3CC,uCACG5E;QACP,IAAI6E,OAAO,UAAU,CAACL,aAAa,UAAUE,wBAAwB;YACnE,MAAMI,SAASN,YAAY,KAAK,CAAC,GAAG;YACpC,MAAMO,OAAOC,eAAehE,QAAWwD;YACvCA,cAAc,GAAGM,OAAO,CAAC,EAAEC,MAAM;QACnC;QACA,IAAI,CAAC,OAAO,GAAGP;QAEf,IAAI,CAAC,aAAa,GAChBS,eAAeC,aACXlE,SACAoD,iBACAe,KACEZ,YAAYa,qBAAqB,UACjC,GAAG,IAAI,CAAC,OAAO,GAAG/E,cAAc;QAExC,MAAMgF,eAAeC,QAAQ/C,SAAS;QACtC,MAAMgD,gBAAgBD,QAAQ/C,SAAS;QAEvC,IAAI8C,gBAAgBE,eAClB,MAAM,IAAIjB,MAAM;QAGlB,IAAI,CAAC,iBAAiB,GAAGiB,gBAAgB,QAAQpB;QACjD,IAAI,CAAC,YAAY,GAAGkB;QACpB,IAAI,CAAC,aAAa,GAAGE;QAErB,IAAIC;QACJ,IAAI,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,aAAa,EAC3CA,eAAe,IAAI,CAAC,iBAAiB;QAEvC,IAAI,CAACA,cACHA,eAAe;YACb,iBAAiBpD;YACjB,SAAS,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE;QACZ;QAEF,IAAI,CAAC,KAAK,GAAGoD;QACb,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,iBAAiB,GAC7C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GACxB;IACN;AAoXF"}