@mentra/engine 3.1.0-dev.33 → 3.1.0-dev.38

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 (39) hide show
  1. package/build/engine.d.ts +2 -0
  2. package/build/engine.d.ts.map +1 -1
  3. package/build/facades/pairing.d.ts +4 -0
  4. package/build/facades/pairing.d.ts.map +1 -1
  5. package/build/facades/pairing.js +81 -7
  6. package/build/facades/pairing.js.map +1 -1
  7. package/build/generated/releaseMetadata.js +5 -5
  8. package/build/generated/releaseMetadata.js.map +1 -1
  9. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -1
  10. package/build/react/MentraLiveOtaFlow.js +17 -0
  11. package/build/react/MentraLiveOtaFlow.js.map +1 -1
  12. package/build/react/index.d.ts +1 -1
  13. package/build/react/index.d.ts.map +1 -1
  14. package/build/react/index.js.map +1 -1
  15. package/build/react/useMentraLiveOta.d.ts +8 -0
  16. package/build/react/useMentraLiveOta.d.ts.map +1 -1
  17. package/build/react/useMentraLiveOta.js +39 -7
  18. package/build/react/useMentraLiveOta.js.map +1 -1
  19. package/build/services/OtaAutoChain.d.ts +3 -1
  20. package/build/services/OtaAutoChain.d.ts.map +1 -1
  21. package/build/services/OtaAutoChain.js +3 -1
  22. package/build/services/OtaAutoChain.js.map +1 -1
  23. package/build/services/OtaUpdateCheckService.d.ts +3 -0
  24. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  25. package/build/services/OtaUpdateCheckService.js +12 -0
  26. package/build/services/OtaUpdateCheckService.js.map +1 -1
  27. package/build/services/otaReleaseVersion.d.ts +10 -0
  28. package/build/services/otaReleaseVersion.d.ts.map +1 -0
  29. package/build/services/otaReleaseVersion.js +14 -0
  30. package/build/services/otaReleaseVersion.js.map +1 -0
  31. package/package.json +6 -6
  32. package/src/facades/pairing.ts +88 -12
  33. package/src/generated/releaseMetadata.ts +5 -5
  34. package/src/react/MentraLiveOtaFlow.tsx +23 -0
  35. package/src/react/index.ts +1 -0
  36. package/src/react/useMentraLiveOta.ts +55 -7
  37. package/src/services/OtaAutoChain.ts +4 -0
  38. package/src/services/OtaUpdateCheckService.ts +15 -0
  39. package/src/services/otaReleaseVersion.ts +22 -0
@@ -1 +1 @@
1
- {"version":3,"file":"OtaUpdateCheckService.js","sourceRoot":"","sources":["../../src/services/OtaUpdateCheckService.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk, {type OtaUpdateInfo} from \"@mentra/bluetooth-sdk\"\nimport {getGlassesSystemTimeMs, isGlassesConnected, useGlassesStore, waitForGlassesState} from \"../stores/glasses\"\nimport {maybeFixGlassesClockFromVersionInfo} from \"./glassesClockSync\"\nimport {hasConfiguredModernOtaManifestPin, resolveOtaManifestUrl} from \"./otaManifestUrl\"\n\n/**\n * Phone-side mirror of ASG's `DOWNGRADE_FLOOR_VERSION_CODE`. Set to the versionCode of the first\n * release shipping the shared media root (the first downgrade-safe build). A non-positive value\n * means downgrades are disabled, so the phone must NOT offer them — offering a downgrade the\n * glasses' `DowngradeGate` will refuse leaves the version-change session with no install and the\n * glasses reporting a bare \"complete\", which would otherwise present as false success. Keep this\n * in lockstep with the ASG/recovery constant at release time.\n */\nexport const DOWNGRADE_FLOOR_VERSION_CODE = 0\n\nexport interface VersionInfo {\n versionCode: number\n versionName: string\n downloadUrl: string\n apkSize: number\n sha256: string\n releaseNotes: string\n isRequired?: boolean\n}\n\nexport interface MtkPatch {\n start_firmware: string\n end_firmware: string\n url: string\n sha256?: string\n}\n\nexport interface BesFirmware {\n version: string\n url: string\n sha256?: string\n}\n\nexport interface VersionJson {\n apps?: {\n [packageName: string]: VersionInfo\n }\n mtk_patches?: MtkPatch[]\n bes_firmware?: BesFirmware\n versionCode?: number\n versionName?: string\n downloadUrl?: string\n apkSize?: number\n sha256?: string\n releaseNotes?: string\n}\n\nexport interface OtaCheckResult {\n hasCheckCompleted: boolean\n updateAvailable: boolean\n latestVersionInfo: VersionInfo | null\n updates: string[]\n mtkPatch: MtkPatch | null\n besVersion: string | null\n /** True when the pending APK step installs an OLDER build than the glasses currently run. */\n isApkDowngrade: boolean\n /**\n * Raw manifest JSON (stringified) this check actually fetched, or null when the\n * fetch failed. Lets change-watchers baseline on exactly what the check saw,\n * instead of racing it with a second fetch of the same URL.\n */\n manifestBody: string | null\n /**\n * Why the check failed, when it did. \"network\" is retryable (connection/server\n * trouble); \"pin_unavailable\" is permanent for this app build (manifest 404/gone,\n * unparseable, or carrying no valid ASG pin) — the remedy is updating the app.\n */\n checkFailureReason?: \"network\" | \"pin_unavailable\"\n}\n\nexport type OtaCheckSkippedReason = \"disconnected\" | \"missing_build\" | \"dev_build\"\n\nexport interface OtaCheckCurrentGlassesResult extends OtaCheckResult {\n updateInfo: OtaUpdateInfo | null\n isRequired: boolean\n skippedReason?: OtaCheckSkippedReason\n manifestUrl?: string\n buildNumber?: string\n mtkFirmwareVersion?: string\n besFirmwareVersion?: string\n}\n\nexport interface OtaCheckCurrentGlassesOptions {\n waitForBuildNumberMs?: number\n waitForBesVersionMs?: number\n waitForMtkVersionMs?: number\n refreshVersionInfo?: boolean\n fixClockBeforeCheck?: boolean\n /** Downgrade floor override (defaults to DOWNGRADE_FLOOR_VERSION_CODE); for tests. */\n floorVersionCode?: number\n}\n\nfunction emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurrentGlassesResult {\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n updateInfo: null,\n isRequired: true,\n skippedReason,\n }\n}\n\nfunction glassesConnectedNow(): boolean {\n return isGlassesConnected(useGlassesStore.getState().connection)\n}\n\ntype ManifestFetchResult = {json: VersionJson | null; failureReason?: \"network\" | \"pin_unavailable\"}\n\nasync function fetchVersionInfoDetailed(url: string): Promise<ManifestFetchResult> {\n try {\n const response = await fetch(url)\n if (!response.ok) {\n console.warn(\"Failed to fetch version info:\", response.status)\n // 4xx: the manifest does not exist (or is gone) — permanent for this app\n // build, retrying cannot help. Everything else is transient server/network\n // trouble and retryable.\n return {\n json: null,\n failureReason: response.status >= 400 && response.status < 500 ? \"pin_unavailable\" : \"network\",\n }\n }\n try {\n return {json: await response.json()}\n } catch (parseError) {\n console.warn(\"OTA: manifest is not valid JSON:\", parseError)\n return {json: null, failureReason: \"pin_unavailable\"}\n }\n } catch (error) {\n console.warn(\"OTA: Error fetching version info:\", error)\n return {json: null, failureReason: \"network\"}\n }\n}\n\nexport async function fetchVersionInfo(url: string): Promise<VersionJson | null> {\n return (await fetchVersionInfoDetailed(url)).json\n}\n\nexport function checkVersionUpdateAvailable(\n currentBuildNumber: string | undefined,\n versionJson: VersionJson | null,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): boolean {\n return getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode) !== null\n}\n\n/**\n * Direction of the pending APK change, or null when the glasses already match the manifest.\n * Every apps-shaped manifest the phone drives is an exact pin, so both directions are\n * actionable (mirroring the glasses-side `DowngradeGate`); the ancient top-level manifest\n * shape stays upgrade-only, and zeroed rescue pins are never actionable.\n */\nexport function getApkUpdateDirection(\n currentBuildNumber: string | undefined,\n versionJson: VersionJson | null,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): \"upgrade\" | \"downgrade\" | null {\n if (!currentBuildNumber || !versionJson) {\n return null\n }\n\n const currentVersion = parseInt(currentBuildNumber, 10)\n if (isNaN(currentVersion)) {\n return null\n }\n\n let serverVersion: number | undefined\n let exactPin = false\n\n const appEntry = versionJson.apps?.[\"com.mentra.asg_client\"]\n if (appEntry) {\n serverVersion = appEntry.versionCode\n exactPin = true\n } else if (versionJson.versionCode) {\n serverVersion = versionJson.versionCode\n }\n\n if (!serverVersion || isNaN(serverVersion)) {\n return null\n }\n\n if (serverVersion > currentVersion) {\n return \"upgrade\"\n }\n // Downgrades require an exact pin AND a target at/above the enabled floor. A non-positive floor\n // disables downgrades entirely, matching ASG's fail-closed DowngradeGate — so the phone never\n // offers what the glasses would refuse.\n if (serverVersion < currentVersion && exactPin && floorVersionCode > 0 && serverVersion >= floorVersionCode) {\n return \"downgrade\"\n }\n return null\n}\n\nexport function getLatestVersionInfo(versionJson: VersionJson | null): VersionInfo | null {\n if (!versionJson) {\n return null\n }\n\n if (versionJson.apps?.[\"com.mentra.asg_client\"]) {\n return versionJson.apps[\"com.mentra.asg_client\"]\n }\n\n if (versionJson.versionCode) {\n return {\n versionCode: versionJson.versionCode,\n versionName: versionJson.versionName || \"\",\n downloadUrl: versionJson.downloadUrl || \"\",\n apkSize: versionJson.apkSize || 0,\n sha256: versionJson.sha256 || \"\",\n releaseNotes: versionJson.releaseNotes || \"\",\n }\n }\n\n return null\n}\n\nexport function findMatchingMtkPatch(\n patches: MtkPatch[] | undefined,\n currentVersion: string | undefined,\n): MtkPatch | null {\n if (!patches || !currentVersion) {\n return null\n }\n\n return (\n patches.find((patch) => {\n if (patch.start_firmware === currentVersion) {\n return true\n }\n const serverDate = patch.start_firmware.includes(\"_\")\n ? patch.start_firmware.split(\"_\").pop()\n : patch.start_firmware\n return serverDate === currentVersion\n }) || null\n )\n}\n\nexport function checkBesUpdate(besFirmware: BesFirmware | undefined, currentVersion: string | undefined): boolean {\n if (!besFirmware) {\n return false\n }\n\n if (!currentVersion) {\n console.log(`📱 BES current version unknown - assuming update needed (server: ${besFirmware.version})`)\n return true\n }\n return compareVersions(besFirmware.version, currentVersion) > 0\n}\n\nexport function compareVersions(version1: string, version2: string): number {\n if (version1.includes(\".\") && version2.includes(\".\")) {\n const parts1 = version1.split(\".\")\n const parts2 = version2.split(\".\")\n const maxLen = Math.max(parts1.length, parts2.length)\n\n for (let i = 0; i < maxLen; i++) {\n const v1 = i < parts1.length ? parseInt(parts1[i], 10) : 0\n const v2 = i < parts2.length ? parseInt(parts2[i], 10) : 0\n if (v1 !== v2) {\n return v1 - v2\n }\n }\n return 0\n }\n\n return version1.localeCompare(version2)\n}\n\nexport async function checkForOtaUpdate(\n otaVersionUrl: string,\n currentBuildNumber: string,\n currentMtkVersion?: string,\n currentBesVersion?: string,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): Promise<OtaCheckResult> {\n try {\n console.log(\"OTA: Checking for OTA update - URL: \" + otaVersionUrl + \", current build: \" + currentBuildNumber)\n const fetched = await fetchVersionInfoDetailed(otaVersionUrl)\n const versionJson = fetched.json\n if (!versionJson) {\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n checkFailureReason: fetched.failureReason ?? \"network\",\n }\n }\n const latestVersionInfo = getLatestVersionInfo(versionJson)\n\n // A manifest whose ASG pin is missing/zeroed cannot verify anything for modern\n // glasses (zeroed pins are only legitimate in the frozen legacy rescue manifests\n // that pre-39 glasses are checked against). Report the check as FAILED rather\n // than silently completing as \"up to date\" — an unverifiable state must never\n // present as a verified one.\n const currentVersionNumber = parseInt(currentBuildNumber, 10)\n const pinnedEntry = versionJson?.apps?.[\"com.mentra.asg_client\"]\n const pinnedVersion = pinnedEntry?.versionCode ?? versionJson?.versionCode\n if (\n versionJson &&\n Number.isFinite(currentVersionNumber) &&\n currentVersionNumber >= 39 &&\n !(typeof pinnedVersion === \"number\" && pinnedVersion > 0)\n ) {\n console.error(\n `OTA: manifest at ${otaVersionUrl} has no valid ASG pin (versionCode=${String(pinnedVersion)}) - treating check as failed`,\n )\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n checkFailureReason: \"pin_unavailable\",\n }\n }\n\n const apkDirection = getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode)\n const apkUpdateAvailable = apkDirection !== null\n console.log(\n `OTA: APK update available: ${apkUpdateAvailable} (current: ${currentBuildNumber}, direction: ${apkDirection ?? \"none\"})`,\n )\n\n const mtkPatch = findMatchingMtkPatch(versionJson?.mtk_patches, currentMtkVersion)\n const mtkUpdateAvailable = mtkPatch !== null\n if (!currentMtkVersion && versionJson?.mtk_patches?.length) {\n console.log(`OTA: MTK current version unknown - skipping MTK patch check`)\n }\n console.log(\n `OTA: MTK patch available: ${mtkUpdateAvailable ? \"yes\" : \"no\"} (current MTK: ${currentMtkVersion || \"unknown\"})`,\n )\n\n const besUpdateAvailable = checkBesUpdate(versionJson?.bes_firmware, currentBesVersion)\n console.log(`OTA: BES update available: ${besUpdateAvailable} (current BES: ${currentBesVersion || \"unknown\"})`)\n\n const updates: string[] = []\n if (apkUpdateAvailable) updates.push(\"apk\")\n if (mtkUpdateAvailable) updates.push(\"mtk\")\n if (besUpdateAvailable) updates.push(\"bes\")\n\n console.log(`OTA: OTA check result - updates available: ${updates.length > 0}, updates: [${updates.join(\", \")}]`)\n\n return {\n hasCheckCompleted: true,\n updateAvailable: updates.length > 0,\n latestVersionInfo,\n updates,\n mtkPatch,\n besVersion: versionJson?.bes_firmware?.version || null,\n isApkDowngrade: apkDirection === \"downgrade\",\n manifestBody: versionJson ? JSON.stringify(versionJson) : null,\n }\n } catch (error) {\n console.error(\"Error checking for OTA update:\", error)\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n checkFailureReason: \"network\",\n }\n }\n}\n\nexport async function checkCurrentGlassesForUpdate(\n options: OtaCheckCurrentGlassesOptions = {},\n): Promise<OtaCheckCurrentGlassesResult> {\n const {\n waitForBuildNumberMs = 0,\n waitForBesVersionMs = 5000,\n waitForMtkVersionMs = 2000,\n refreshVersionInfo = true,\n fixClockBeforeCheck = true,\n floorVersionCode = DOWNGRADE_FLOOR_VERSION_CODE,\n } = options\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n if (!hasConfiguredModernOtaManifestPin()) {\n console.log(\"OTA: check skipped - this app or Engine package has no embedded OTA manifest pin\")\n return emptyCheckResult(\"dev_build\")\n }\n\n if (refreshVersionInfo) {\n void BluetoothSdk.requestVersionInfo()\n .then((versionInfo) => {\n useGlassesStore.getState().setGlassesInfo(versionInfo)\n })\n .catch((error) => {\n console.warn(\"OTA: Failed to request version_info from glasses:\", error)\n })\n }\n\n let buildNumber = useGlassesStore.getState().buildNumber\n if (!buildNumber && waitForBuildNumberMs > 0) {\n await waitForGlassesState(\"buildNumber\", (value) => !!value, waitForBuildNumberMs)\n buildNumber = useGlassesStore.getState().buildNumber\n }\n\n if (!buildNumber || !(parseInt(buildNumber, 10) > 0)) {\n // Covers absent, non-numeric, and zero build numbers: a glasses version we\n // cannot parse means nothing is verifiable, so skip rather than ghost an\n // \"up to date\" result from a comparison that never really ran.\n return emptyCheckResult(\"missing_build\")\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n let besFirmwareVersion = useGlassesStore.getState().besFirmwareVersion\n if (!besFirmwareVersion && waitForBesVersionMs > 0) {\n console.log(\"OTA: BES version still unknown - waiting up to 5s for it to arrive...\")\n await waitForGlassesState(\"besFirmwareVersion\", (value) => !!value, waitForBesVersionMs)\n besFirmwareVersion = useGlassesStore.getState().besFirmwareVersion\n if (besFirmwareVersion) {\n console.log(`OTA: BES version arrived: ${besFirmwareVersion}`)\n } else {\n console.log(\"OTA: BES version still unknown after extended wait - will assume BES update if published\")\n }\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n let mtkFirmwareVersion = useGlassesStore.getState().mtkFirmwareVersion\n if (!mtkFirmwareVersion && waitForMtkVersionMs > 0) {\n await waitForGlassesState(\"mtkFirmwareVersion\", (value) => !!value, waitForMtkVersionMs)\n mtkFirmwareVersion = useGlassesStore.getState().mtkFirmwareVersion\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n if (fixClockBeforeCheck) {\n const systemTimeMs = getGlassesSystemTimeMs()\n await maybeFixGlassesClockFromVersionInfo(systemTimeMs > 0 ? systemTimeMs : undefined).catch((error) => {\n console.warn(\"OTA: clock fix attempt failed; continuing OTA check\", error)\n })\n }\n\n const manifestUrl = resolveOtaManifestUrl(useGlassesStore.getState().otaVersionUrl, buildNumber)\n if (!manifestUrl) {\n console.log(\"OTA: check skipped - no OTA manifest pin resolved\")\n return emptyCheckResult(\"dev_build\")\n }\n const result = await checkForOtaUpdate(\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n floorVersionCode,\n )\n\n if (!result.hasCheckCompleted) {\n return {\n ...result,\n updateInfo: null,\n isRequired: true,\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n }\n }\n\n const mtkUpdatedThisSession = useGlassesStore.getState().mtkUpdatedThisSession\n let filteredUpdates = result.updates\n if (mtkUpdatedThisSession && filteredUpdates.includes(\"mtk\")) {\n console.log(\"OTA: Filtering out MTK - already updated this session (pending reboot)\")\n filteredUpdates = filteredUpdates.filter((update) => update !== \"mtk\")\n }\n\n // Pre-migration parity: an update is only surfaced when the manifest also\n // carries APK version metadata. A firmware-only manifest (mtk_patches /\n // bes_firmware with no APK entry) is therefore dropped here — same as the\n // legacy host check. Loud-log it so the case is visible in the field;\n // surfacing firmware-only updates is a deliberate behavior change to make\n // separately, not silently inside the boundary migration.\n if (filteredUpdates.length > 0 && !result.latestVersionInfo) {\n console.warn(\n `OTA: manifest lists updates [${filteredUpdates.join(\", \")}] but has no APK version metadata - ` +\n \"reporting no update (legacy behavior)\",\n )\n }\n const updateAvailable = filteredUpdates.length > 0 && !!result.latestVersionInfo\n const isApkDowngrade = result.isApkDowngrade && filteredUpdates.includes(\"apk\")\n const updateInfo = updateAvailable\n ? {\n available: true,\n versionCode: result.latestVersionInfo?.versionCode || 0,\n versionName: result.latestVersionInfo?.versionName || \"\",\n updates: filteredUpdates,\n totalSize: 0,\n ...(filteredUpdates.includes(\"bes\") && result.besVersion ? {besVersion: result.besVersion} : {}),\n isDowngrade: isApkDowngrade,\n }\n : null\n\n useGlassesStore.getState().setOtaUpdateAvailable(updateInfo)\n\n return {\n ...result,\n updateAvailable,\n updates: filteredUpdates,\n isApkDowngrade,\n updateInfo,\n // Required-update policy: an EXPLICIT isRequired in the manifest is the\n // release-engineering escape hatch and wins in both directions. When absent,\n // upgrades keep the historical forced default, but downgrades are always\n // skippable — a version change that resets glasses settings must be\n // declinable unless someone explicitly forced it.\n isRequired:\n result.latestVersionInfo?.isRequired === true\n ? true\n : result.latestVersionInfo?.isRequired === false\n ? false\n : !isApkDowngrade,\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n }\n}\n"]}
1
+ {"version":3,"file":"OtaUpdateCheckService.js","sourceRoot":"","sources":["../../src/services/OtaUpdateCheckService.ts"],"names":[],"mappings":"AAAA,OAAO,YAAkC,MAAM,uBAAuB,CAAA;AACtE,OAAO,EAAC,uBAAuB,EAAC,MAAM,8BAA8B,CAAA;AACpE,OAAO,EAAC,sBAAsB,EAAE,kBAAkB,EAAE,eAAe,EAAE,mBAAmB,EAAC,MAAM,mBAAmB,CAAA;AAClH,OAAO,EAAC,mCAAmC,EAAC,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAAC,iCAAiC,EAAE,qBAAqB,EAAC,MAAM,kBAAkB,CAAA;AACzF,OAAO,EAAC,wBAAwB,EAAC,MAAM,qBAAqB,CAAA;AAE5D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAA;AAuF7C,SAAS,gBAAgB,CAAC,aAAqC;IAC7D,OAAO;QACL,iBAAiB,EAAE,KAAK;QACxB,eAAe,EAAE,KAAK;QACtB,iBAAiB,EAAE,IAAI;QACvB,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,IAAI;QAChB,cAAc,EAAE,KAAK;QACrB,YAAY,EAAE,IAAI;QAClB,cAAc,EAAE,IAAI;QACpB,UAAU,EAAE,IAAI;QAChB,UAAU,EAAE,IAAI;QAChB,aAAa;KACd,CAAA;AACH,CAAC;AAED,SAAS,mBAAmB;IAC1B,OAAO,kBAAkB,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,CAAA;AAClE,CAAC;AAID,KAAK,UAAU,wBAAwB,CAAC,GAAW;IACjD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;QACjC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,OAAO,CAAC,IAAI,CAAC,+BAA+B,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;YAC9D,yEAAyE;YACzE,2EAA2E;YAC3E,yBAAyB;YACzB,OAAO;gBACL,IAAI,EAAE,IAAI;gBACV,aAAa,EAAE,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS;aAC/F,CAAA;QACH,CAAC;QACD,IAAI,CAAC;YACH,OAAO,EAAC,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAC,CAAA;QACtC,CAAC;QAAC,OAAO,UAAU,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,kCAAkC,EAAE,UAAU,CAAC,CAAA;YAC5D,OAAO,EAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,iBAAiB,EAAC,CAAA;QACvD,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,mCAAmC,EAAE,KAAK,CAAC,CAAA;QACxD,OAAO,EAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAC,CAAA;IAC/C,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,OAAO,CAAC,MAAM,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;AACnD,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,kBAAsC,EACtC,WAA+B,EAC/B,mBAA2B,4BAA4B;IAEvD,OAAO,qBAAqB,CAAC,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,CAAC,KAAK,IAAI,CAAA;AAC1F,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,kBAAsC,EACtC,WAA+B,EAC/B,mBAA2B,4BAA4B;IAEvD,IAAI,CAAC,kBAAkB,IAAI,CAAC,WAAW,EAAE,CAAC;QACxC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,cAAc,GAAG,QAAQ,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAA;IACvD,IAAI,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,aAAiC,CAAA;IACrC,IAAI,QAAQ,GAAG,KAAK,CAAA;IAEpB,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC,uBAAuB,CAAC,CAAA;IAC5D,IAAI,QAAQ,EAAE,CAAC;QACb,aAAa,GAAG,QAAQ,CAAC,WAAW,CAAA;QACpC,QAAQ,GAAG,IAAI,CAAA;IACjB,CAAC;SAAM,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC;QACnC,aAAa,GAAG,WAAW,CAAC,WAAW,CAAA;IACzC,CAAC;IAED,IAAI,CAAC,aAAa,IAAI,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;QAC3C,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,aAAa,GAAG,cAAc,EAAE,CAAC;QACnC,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,gGAAgG;IAChG,8FAA8F;IAC9F,wCAAwC;IACxC,IAAI,aAAa,GAAG,cAAc,IAAI,QAAQ,IAAI,gBAAgB,GAAG,CAAC,IAAI,aAAa,IAAI,gBAAgB,EAAE,CAAC;QAC5G,OAAO,WAAW,CAAA;IACpB,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,WAA+B;IAClE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC,uBAAuB,CAAC,EAAE,CAAC;QAChD,OAAO,WAAW,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAA;IAClD,CAAC;IAED,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC;QAC5B,OAAO;YACL,WAAW,EAAE,WAAW,CAAC,WAAW;YACpC,WAAW,EAAE,WAAW,CAAC,WAAW,IAAI,EAAE;YAC1C,WAAW,EAAE,WAAW,CAAC,WAAW,IAAI,EAAE;YAC1C,OAAO,EAAE,WAAW,CAAC,OAAO,IAAI,CAAC;YACjC,MAAM,EAAE,WAAW,CAAC,MAAM,IAAI,EAAE;YAChC,YAAY,EAAE,WAAW,CAAC,YAAY,IAAI,EAAE;SAC7C,CAAA;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,OAA+B,EAC/B,cAAkC;IAElC,IAAI,CAAC,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;QAChC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,CACL,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QACrB,IAAI,KAAK,CAAC,cAAc,KAAK,cAAc,EAAE,CAAC;YAC5C,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC;YACnD,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE;YACvC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAA;QACxB,OAAO,UAAU,KAAK,cAAc,CAAA;IACtC,CAAC,CAAC,IAAI,IAAI,CACX,CAAA;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,WAAoC,EAAE,cAAkC;IACrG,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,oEAAoE,WAAW,CAAC,OAAO,GAAG,CAAC,CAAA;QACvG,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,eAAe,CAAC,WAAW,CAAC,OAAO,EAAE,cAAc,CAAC,GAAG,CAAC,CAAA;AACjE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,QAAgB;IAChE,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;QAErD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAChC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAC1D,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAC1D,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACd,OAAO,EAAE,GAAG,EAAE,CAAA;YAChB,CAAC;QACH,CAAC;QACD,OAAO,CAAC,CAAA;IACV,CAAC;IAED,OAAO,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;AACzC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,aAAqB,EACrB,kBAA0B,EAC1B,iBAA0B,EAC1B,iBAA0B,EAC1B,mBAA2B,4BAA4B;IAEvD,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,sCAAsC,GAAG,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,CAAC,CAAA;QAC9G,MAAM,OAAO,GAAG,MAAM,wBAAwB,CAAC,aAAa,CAAC,CAAA;QAC7D,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAA;QAChC,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO;gBACL,iBAAiB,EAAE,KAAK;gBACxB,eAAe,EAAE,KAAK;gBACtB,iBAAiB,EAAE,IAAI;gBACvB,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;gBACd,UAAU,EAAE,IAAI;gBAChB,cAAc,EAAE,KAAK;gBACrB,YAAY,EAAE,IAAI;gBAClB,cAAc,EAAE,IAAI;gBACpB,kBAAkB,EAAE,OAAO,CAAC,aAAa,IAAI,SAAS;aACvD,CAAA;QACH,CAAC;QACD,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAA;QAE3D,+EAA+E;QAC/E,iFAAiF;QACjF,8EAA8E;QAC9E,8EAA8E;QAC9E,6BAA6B;QAC7B,MAAM,oBAAoB,GAAG,QAAQ,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAA;QAC7D,MAAM,WAAW,GAAG,WAAW,EAAE,IAAI,EAAE,CAAC,uBAAuB,CAAC,CAAA;QAChE,MAAM,aAAa,GAAG,WAAW,EAAE,WAAW,IAAI,WAAW,EAAE,WAAW,CAAA;QAC1E,IACE,WAAW;YACX,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YACrC,oBAAoB,IAAI,EAAE;YAC1B,CAAC,CAAC,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,GAAG,CAAC,CAAC,EACzD,CAAC;YACD,OAAO,CAAC,KAAK,CACX,oBAAoB,aAAa,sCAAsC,MAAM,CAAC,aAAa,CAAC,8BAA8B,CAC3H,CAAA;YACD,OAAO;gBACL,iBAAiB,EAAE,KAAK;gBACxB,eAAe,EAAE,KAAK;gBACtB,iBAAiB,EAAE,IAAI;gBACvB,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;gBACd,UAAU,EAAE,IAAI;gBAChB,cAAc,EAAE,KAAK;gBACrB,YAAY,EAAE,IAAI;gBAClB,cAAc,EAAE,IAAI;gBACpB,kBAAkB,EAAE,iBAAiB;aACtC,CAAA;QACH,CAAC;QAED,MAAM,YAAY,GAAG,qBAAqB,CAAC,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,CAAC,CAAA;QAC7F,MAAM,kBAAkB,GAAG,YAAY,KAAK,IAAI,CAAA;QAChD,OAAO,CAAC,GAAG,CACT,8BAA8B,kBAAkB,cAAc,kBAAkB,gBAAgB,YAAY,IAAI,MAAM,GAAG,CAC1H,CAAA;QAED,MAAM,QAAQ,GAAG,oBAAoB,CAAC,WAAW,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAA;QAClF,MAAM,kBAAkB,GAAG,QAAQ,KAAK,IAAI,CAAA;QAC5C,IAAI,CAAC,iBAAiB,IAAI,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;YAC3D,OAAO,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAA;QAC5E,CAAC;QACD,OAAO,CAAC,GAAG,CACT,6BAA6B,kBAAkB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,kBAAkB,iBAAiB,IAAI,SAAS,GAAG,CAClH,CAAA;QAED,MAAM,kBAAkB,GAAG,cAAc,CAAC,WAAW,EAAE,YAAY,EAAE,iBAAiB,CAAC,CAAA;QACvF,OAAO,CAAC,GAAG,CAAC,8BAA8B,kBAAkB,kBAAkB,iBAAiB,IAAI,SAAS,GAAG,CAAC,CAAA;QAEhH,MAAM,OAAO,GAAa,EAAE,CAAA;QAC5B,IAAI,kBAAkB;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC3C,IAAI,kBAAkB;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC3C,IAAI,kBAAkB;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAE3C,OAAO,CAAC,GAAG,CAAC,8CAA8C,OAAO,CAAC,MAAM,GAAG,CAAC,eAAe,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAEjH,OAAO;YACL,iBAAiB,EAAE,IAAI;YACvB,eAAe,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC;YACnC,iBAAiB;YACjB,OAAO;YACP,QAAQ;YACR,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,IAAI,IAAI;YACtD,cAAc,EAAE,YAAY,KAAK,WAAW;YAC5C,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;YAC9D,cAAc,EAAE,wBAAwB,CAAC;gBACvC,sBAAsB,EAAE,WAAW,CAAC,cAAc;gBAClD,WAAW,EAAE,aAAa;gBAC1B,mBAAmB,EAAE,uBAAuB,CAAC,cAAc;gBAC3D,uBAAuB,EAAE,uBAAuB,CAAC,eAAe;aACjE,CAAC;SACH,CAAA;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAA;QACtD,OAAO;YACL,iBAAiB,EAAE,KAAK;YACxB,eAAe,EAAE,KAAK;YACtB,iBAAiB,EAAE,IAAI;YACvB,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,IAAI;YACd,UAAU,EAAE,IAAI;YAChB,cAAc,EAAE,KAAK;YACrB,YAAY,EAAE,IAAI;YAClB,cAAc,EAAE,IAAI;YACpB,kBAAkB,EAAE,SAAS;SAC9B,CAAA;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,UAAyC,EAAE;IAE3C,MAAM,EACJ,oBAAoB,GAAG,CAAC,EACxB,mBAAmB,GAAG,IAAI,EAC1B,mBAAmB,GAAG,IAAI,EAC1B,kBAAkB,GAAG,IAAI,EACzB,mBAAmB,GAAG,IAAI,EAC1B,gBAAgB,GAAG,4BAA4B,GAChD,GAAG,OAAO,CAAA;IAEX,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;QAC3B,OAAO,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACzC,CAAC;IAED,IAAI,CAAC,iCAAiC,EAAE,EAAE,CAAC;QACzC,OAAO,CAAC,GAAG,CAAC,kFAAkF,CAAC,CAAA;QAC/F,OAAO,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACtC,CAAC;IAED,IAAI,kBAAkB,EAAE,CAAC;QACvB,KAAK,YAAY,CAAC,kBAAkB,EAAE;aACnC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE;YACpB,eAAe,CAAC,QAAQ,EAAE,CAAC,cAAc,CAAC,WAAW,CAAC,CAAA;QACxD,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,OAAO,CAAC,IAAI,CAAC,mDAAmD,EAAE,KAAK,CAAC,CAAA;QAC1E,CAAC,CAAC,CAAA;IACN,CAAC;IAED,IAAI,WAAW,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAA;IACxD,IAAI,CAAC,WAAW,IAAI,oBAAoB,GAAG,CAAC,EAAE,CAAC;QAC7C,MAAM,mBAAmB,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAA;QAClF,WAAW,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAA;IACtD,CAAC;IAED,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACrD,2EAA2E;QAC3E,yEAAyE;QACzE,+DAA+D;QAC/D,OAAO,gBAAgB,CAAC,eAAe,CAAC,CAAA;IAC1C,CAAC;IAED,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;QAC3B,OAAO,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACzC,CAAC;IAED,IAAI,kBAAkB,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,kBAAkB,CAAA;IACtE,IAAI,CAAC,kBAAkB,IAAI,mBAAmB,GAAG,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,uEAAuE,CAAC,CAAA;QACpF,MAAM,mBAAmB,CAAC,oBAAoB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAA;QACxF,kBAAkB,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,kBAAkB,CAAA;QAClE,IAAI,kBAAkB,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,6BAA6B,kBAAkB,EAAE,CAAC,CAAA;QAChE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,0FAA0F,CAAC,CAAA;QACzG,CAAC;IACH,CAAC;IAED,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;QAC3B,OAAO,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACzC,CAAC;IAED,IAAI,kBAAkB,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,kBAAkB,CAAA;IACtE,IAAI,CAAC,kBAAkB,IAAI,mBAAmB,GAAG,CAAC,EAAE,CAAC;QACnD,MAAM,mBAAmB,CAAC,oBAAoB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAA;QACxF,kBAAkB,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,kBAAkB,CAAA;IACpE,CAAC;IAED,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;QAC3B,OAAO,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACzC,CAAC;IAED,IAAI,mBAAmB,EAAE,CAAC;QACxB,MAAM,YAAY,GAAG,sBAAsB,EAAE,CAAA;QAC7C,MAAM,mCAAmC,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACrG,OAAO,CAAC,IAAI,CAAC,qDAAqD,EAAE,KAAK,CAAC,CAAA;QAC5E,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,qBAAqB,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC,aAAa,EAAE,WAAW,CAAC,CAAA;IAChG,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAA;QAChE,OAAO,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACtC,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,iBAAiB,CACpC,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,CACjB,CAAA;IAED,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;QAC9B,OAAO;YACL,GAAG,MAAM;YACT,UAAU,EAAE,IAAI;YAChB,UAAU,EAAE,IAAI;YAChB,WAAW;YACX,WAAW;YACX,kBAAkB;YAClB,kBAAkB;SACnB,CAAA;IACH,CAAC;IAED,MAAM,qBAAqB,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,qBAAqB,CAAA;IAC9E,IAAI,eAAe,GAAG,MAAM,CAAC,OAAO,CAAA;IACpC,IAAI,qBAAqB,IAAI,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,wEAAwE,CAAC,CAAA;QACrF,eAAe,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,KAAK,CAAC,CAAA;IACxE,CAAC;IAED,0EAA0E;IAC1E,wEAAwE;IACxE,0EAA0E;IAC1E,sEAAsE;IACtE,0EAA0E;IAC1E,0DAA0D;IAC1D,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;QAC5D,OAAO,CAAC,IAAI,CACV,gCAAgC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,sCAAsC;YAC9F,uCAAuC,CAC1C,CAAA;IACH,CAAC;IACD,MAAM,eAAe,GAAG,eAAe,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAA;IAChF,MAAM,cAAc,GAAG,MAAM,CAAC,cAAc,IAAI,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC/E,MAAM,UAAU,GAAG,eAAe;QAChC,CAAC,CAAC;YACE,SAAS,EAAE,IAAI;YACf,WAAW,EAAE,MAAM,CAAC,iBAAiB,EAAE,WAAW,IAAI,CAAC;YACvD,WAAW,EAAE,MAAM,CAAC,iBAAiB,EAAE,WAAW,IAAI,EAAE;YACxD,OAAO,EAAE,eAAe;YACxB,SAAS,EAAE,CAAC;YACZ,GAAG,CAAC,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAC,UAAU,EAAE,MAAM,CAAC,UAAU,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAChG,WAAW,EAAE,cAAc;SAC5B;QACH,CAAC,CAAC,IAAI,CAAA;IAER,eAAe,CAAC,QAAQ,EAAE,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAA;IAE5D,OAAO;QACL,GAAG,MAAM;QACT,eAAe;QACf,OAAO,EAAE,eAAe;QACxB,cAAc;QACd,UAAU;QACV,wEAAwE;QACxE,6EAA6E;QAC7E,yEAAyE;QACzE,oEAAoE;QACpE,kDAAkD;QAClD,UAAU,EACR,MAAM,CAAC,iBAAiB,EAAE,UAAU,KAAK,IAAI;YAC3C,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,MAAM,CAAC,iBAAiB,EAAE,UAAU,KAAK,KAAK;gBAC9C,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,CAAC,cAAc;QACvB,WAAW;QACX,WAAW;QACX,kBAAkB;QAClB,kBAAkB;KACnB,CAAA;AACH,CAAC","sourcesContent":["import BluetoothSdk, {type OtaUpdateInfo} from \"@mentra/bluetooth-sdk\"\nimport {ENGINE_RELEASE_METADATA} from \"../generated/releaseMetadata\"\nimport {getGlassesSystemTimeMs, isGlassesConnected, useGlassesStore, waitForGlassesState} from \"../stores/glasses\"\nimport {maybeFixGlassesClockFromVersionInfo} from \"./glassesClockSync\"\nimport {hasConfiguredModernOtaManifestPin, resolveOtaManifestUrl} from \"./otaManifestUrl\"\nimport {resolveOtaReleaseVersion} from \"./otaReleaseVersion\"\n\n/**\n * Phone-side mirror of ASG's `DOWNGRADE_FLOOR_VERSION_CODE`. Set to the versionCode of the first\n * release shipping the shared media root (the first downgrade-safe build). A non-positive value\n * means downgrades are disabled, so the phone must NOT offer them — offering a downgrade the\n * glasses' `DowngradeGate` will refuse leaves the version-change session with no install and the\n * glasses reporting a bare \"complete\", which would otherwise present as false success. Keep this\n * in lockstep with the ASG/recovery constant at release time.\n */\nexport const DOWNGRADE_FLOOR_VERSION_CODE = 0\n\nexport interface VersionInfo {\n versionCode: number\n versionName: string\n downloadUrl: string\n apkSize: number\n sha256: string\n releaseNotes: string\n isRequired?: boolean\n}\n\nexport interface MtkPatch {\n start_firmware: string\n end_firmware: string\n url: string\n sha256?: string\n}\n\nexport interface BesFirmware {\n version: string\n url: string\n sha256?: string\n}\n\nexport interface VersionJson {\n releaseVersion?: string\n apps?: {\n [packageName: string]: VersionInfo\n }\n mtk_patches?: MtkPatch[]\n bes_firmware?: BesFirmware\n versionCode?: number\n versionName?: string\n downloadUrl?: string\n apkSize?: number\n sha256?: string\n releaseNotes?: string\n}\n\nexport interface OtaCheckResult {\n hasCheckCompleted: boolean\n updateAvailable: boolean\n latestVersionInfo: VersionInfo | null\n updates: string[]\n mtkPatch: MtkPatch | null\n besVersion: string | null\n /** True when the pending APK step installs an OLDER build than the glasses currently run. */\n isApkDowngrade: boolean\n /**\n * Raw manifest JSON (stringified) this check actually fetched, or null when the\n * fetch failed. Lets change-watchers baseline on exactly what the check saw,\n * instead of racing it with a second fetch of the same URL.\n */\n manifestBody: string | null\n /** Coordinated release identity for the selected OTA pin. */\n releaseVersion: string | null\n /**\n * Why the check failed, when it did. \"network\" is retryable (connection/server\n * trouble); \"pin_unavailable\" is permanent for this app build (manifest 404/gone,\n * unparseable, or carrying no valid ASG pin) — the remedy is updating the app.\n */\n checkFailureReason?: \"network\" | \"pin_unavailable\"\n}\n\nexport type OtaCheckSkippedReason = \"disconnected\" | \"missing_build\" | \"dev_build\"\n\nexport interface OtaCheckCurrentGlassesResult extends OtaCheckResult {\n updateInfo: OtaUpdateInfo | null\n isRequired: boolean\n skippedReason?: OtaCheckSkippedReason\n manifestUrl?: string\n buildNumber?: string\n mtkFirmwareVersion?: string\n besFirmwareVersion?: string\n}\n\nexport interface OtaCheckCurrentGlassesOptions {\n waitForBuildNumberMs?: number\n waitForBesVersionMs?: number\n waitForMtkVersionMs?: number\n refreshVersionInfo?: boolean\n fixClockBeforeCheck?: boolean\n /** Downgrade floor override (defaults to DOWNGRADE_FLOOR_VERSION_CODE); for tests. */\n floorVersionCode?: number\n}\n\nfunction emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurrentGlassesResult {\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n releaseVersion: null,\n updateInfo: null,\n isRequired: true,\n skippedReason,\n }\n}\n\nfunction glassesConnectedNow(): boolean {\n return isGlassesConnected(useGlassesStore.getState().connection)\n}\n\ntype ManifestFetchResult = {json: VersionJson | null; failureReason?: \"network\" | \"pin_unavailable\"}\n\nasync function fetchVersionInfoDetailed(url: string): Promise<ManifestFetchResult> {\n try {\n const response = await fetch(url)\n if (!response.ok) {\n console.warn(\"Failed to fetch version info:\", response.status)\n // 4xx: the manifest does not exist (or is gone) — permanent for this app\n // build, retrying cannot help. Everything else is transient server/network\n // trouble and retryable.\n return {\n json: null,\n failureReason: response.status >= 400 && response.status < 500 ? \"pin_unavailable\" : \"network\",\n }\n }\n try {\n return {json: await response.json()}\n } catch (parseError) {\n console.warn(\"OTA: manifest is not valid JSON:\", parseError)\n return {json: null, failureReason: \"pin_unavailable\"}\n }\n } catch (error) {\n console.warn(\"OTA: Error fetching version info:\", error)\n return {json: null, failureReason: \"network\"}\n }\n}\n\nexport async function fetchVersionInfo(url: string): Promise<VersionJson | null> {\n return (await fetchVersionInfoDetailed(url)).json\n}\n\nexport function checkVersionUpdateAvailable(\n currentBuildNumber: string | undefined,\n versionJson: VersionJson | null,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): boolean {\n return getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode) !== null\n}\n\n/**\n * Direction of the pending APK change, or null when the glasses already match the manifest.\n * Every apps-shaped manifest the phone drives is an exact pin, so both directions are\n * actionable (mirroring the glasses-side `DowngradeGate`); the ancient top-level manifest\n * shape stays upgrade-only, and zeroed rescue pins are never actionable.\n */\nexport function getApkUpdateDirection(\n currentBuildNumber: string | undefined,\n versionJson: VersionJson | null,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): \"upgrade\" | \"downgrade\" | null {\n if (!currentBuildNumber || !versionJson) {\n return null\n }\n\n const currentVersion = parseInt(currentBuildNumber, 10)\n if (isNaN(currentVersion)) {\n return null\n }\n\n let serverVersion: number | undefined\n let exactPin = false\n\n const appEntry = versionJson.apps?.[\"com.mentra.asg_client\"]\n if (appEntry) {\n serverVersion = appEntry.versionCode\n exactPin = true\n } else if (versionJson.versionCode) {\n serverVersion = versionJson.versionCode\n }\n\n if (!serverVersion || isNaN(serverVersion)) {\n return null\n }\n\n if (serverVersion > currentVersion) {\n return \"upgrade\"\n }\n // Downgrades require an exact pin AND a target at/above the enabled floor. A non-positive floor\n // disables downgrades entirely, matching ASG's fail-closed DowngradeGate — so the phone never\n // offers what the glasses would refuse.\n if (serverVersion < currentVersion && exactPin && floorVersionCode > 0 && serverVersion >= floorVersionCode) {\n return \"downgrade\"\n }\n return null\n}\n\nexport function getLatestVersionInfo(versionJson: VersionJson | null): VersionInfo | null {\n if (!versionJson) {\n return null\n }\n\n if (versionJson.apps?.[\"com.mentra.asg_client\"]) {\n return versionJson.apps[\"com.mentra.asg_client\"]\n }\n\n if (versionJson.versionCode) {\n return {\n versionCode: versionJson.versionCode,\n versionName: versionJson.versionName || \"\",\n downloadUrl: versionJson.downloadUrl || \"\",\n apkSize: versionJson.apkSize || 0,\n sha256: versionJson.sha256 || \"\",\n releaseNotes: versionJson.releaseNotes || \"\",\n }\n }\n\n return null\n}\n\nexport function findMatchingMtkPatch(\n patches: MtkPatch[] | undefined,\n currentVersion: string | undefined,\n): MtkPatch | null {\n if (!patches || !currentVersion) {\n return null\n }\n\n return (\n patches.find((patch) => {\n if (patch.start_firmware === currentVersion) {\n return true\n }\n const serverDate = patch.start_firmware.includes(\"_\")\n ? patch.start_firmware.split(\"_\").pop()\n : patch.start_firmware\n return serverDate === currentVersion\n }) || null\n )\n}\n\nexport function checkBesUpdate(besFirmware: BesFirmware | undefined, currentVersion: string | undefined): boolean {\n if (!besFirmware) {\n return false\n }\n\n if (!currentVersion) {\n console.log(`📱 BES current version unknown - assuming update needed (server: ${besFirmware.version})`)\n return true\n }\n return compareVersions(besFirmware.version, currentVersion) > 0\n}\n\nexport function compareVersions(version1: string, version2: string): number {\n if (version1.includes(\".\") && version2.includes(\".\")) {\n const parts1 = version1.split(\".\")\n const parts2 = version2.split(\".\")\n const maxLen = Math.max(parts1.length, parts2.length)\n\n for (let i = 0; i < maxLen; i++) {\n const v1 = i < parts1.length ? parseInt(parts1[i], 10) : 0\n const v2 = i < parts2.length ? parseInt(parts2[i], 10) : 0\n if (v1 !== v2) {\n return v1 - v2\n }\n }\n return 0\n }\n\n return version1.localeCompare(version2)\n}\n\nexport async function checkForOtaUpdate(\n otaVersionUrl: string,\n currentBuildNumber: string,\n currentMtkVersion?: string,\n currentBesVersion?: string,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): Promise<OtaCheckResult> {\n try {\n console.log(\"OTA: Checking for OTA update - URL: \" + otaVersionUrl + \", current build: \" + currentBuildNumber)\n const fetched = await fetchVersionInfoDetailed(otaVersionUrl)\n const versionJson = fetched.json\n if (!versionJson) {\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n releaseVersion: null,\n checkFailureReason: fetched.failureReason ?? \"network\",\n }\n }\n const latestVersionInfo = getLatestVersionInfo(versionJson)\n\n // A manifest whose ASG pin is missing/zeroed cannot verify anything for modern\n // glasses (zeroed pins are only legitimate in the frozen legacy rescue manifests\n // that pre-39 glasses are checked against). Report the check as FAILED rather\n // than silently completing as \"up to date\" — an unverifiable state must never\n // present as a verified one.\n const currentVersionNumber = parseInt(currentBuildNumber, 10)\n const pinnedEntry = versionJson?.apps?.[\"com.mentra.asg_client\"]\n const pinnedVersion = pinnedEntry?.versionCode ?? versionJson?.versionCode\n if (\n versionJson &&\n Number.isFinite(currentVersionNumber) &&\n currentVersionNumber >= 39 &&\n !(typeof pinnedVersion === \"number\" && pinnedVersion > 0)\n ) {\n console.error(\n `OTA: manifest at ${otaVersionUrl} has no valid ASG pin (versionCode=${String(pinnedVersion)}) - treating check as failed`,\n )\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n releaseVersion: null,\n checkFailureReason: \"pin_unavailable\",\n }\n }\n\n const apkDirection = getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode)\n const apkUpdateAvailable = apkDirection !== null\n console.log(\n `OTA: APK update available: ${apkUpdateAvailable} (current: ${currentBuildNumber}, direction: ${apkDirection ?? \"none\"})`,\n )\n\n const mtkPatch = findMatchingMtkPatch(versionJson?.mtk_patches, currentMtkVersion)\n const mtkUpdateAvailable = mtkPatch !== null\n if (!currentMtkVersion && versionJson?.mtk_patches?.length) {\n console.log(`OTA: MTK current version unknown - skipping MTK patch check`)\n }\n console.log(\n `OTA: MTK patch available: ${mtkUpdateAvailable ? \"yes\" : \"no\"} (current MTK: ${currentMtkVersion || \"unknown\"})`,\n )\n\n const besUpdateAvailable = checkBesUpdate(versionJson?.bes_firmware, currentBesVersion)\n console.log(`OTA: BES update available: ${besUpdateAvailable} (current BES: ${currentBesVersion || \"unknown\"})`)\n\n const updates: string[] = []\n if (apkUpdateAvailable) updates.push(\"apk\")\n if (mtkUpdateAvailable) updates.push(\"mtk\")\n if (besUpdateAvailable) updates.push(\"bes\")\n\n console.log(`OTA: OTA check result - updates available: ${updates.length > 0}, updates: [${updates.join(\", \")}]`)\n\n return {\n hasCheckCompleted: true,\n updateAvailable: updates.length > 0,\n latestVersionInfo,\n updates,\n mtkPatch,\n besVersion: versionJson?.bes_firmware?.version || null,\n isApkDowngrade: apkDirection === \"downgrade\",\n manifestBody: versionJson ? JSON.stringify(versionJson) : null,\n releaseVersion: resolveOtaReleaseVersion({\n manifestReleaseVersion: versionJson.releaseVersion,\n manifestUrl: otaVersionUrl,\n packagedManifestUrl: ENGINE_RELEASE_METADATA.otaManifestUrl,\n packagedReleaseIdentity: ENGINE_RELEASE_METADATA.releaseIdentity,\n }),\n }\n } catch (error) {\n console.error(\"Error checking for OTA update:\", error)\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n releaseVersion: null,\n checkFailureReason: \"network\",\n }\n }\n}\n\nexport async function checkCurrentGlassesForUpdate(\n options: OtaCheckCurrentGlassesOptions = {},\n): Promise<OtaCheckCurrentGlassesResult> {\n const {\n waitForBuildNumberMs = 0,\n waitForBesVersionMs = 5000,\n waitForMtkVersionMs = 2000,\n refreshVersionInfo = true,\n fixClockBeforeCheck = true,\n floorVersionCode = DOWNGRADE_FLOOR_VERSION_CODE,\n } = options\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n if (!hasConfiguredModernOtaManifestPin()) {\n console.log(\"OTA: check skipped - this app or Engine package has no embedded OTA manifest pin\")\n return emptyCheckResult(\"dev_build\")\n }\n\n if (refreshVersionInfo) {\n void BluetoothSdk.requestVersionInfo()\n .then((versionInfo) => {\n useGlassesStore.getState().setGlassesInfo(versionInfo)\n })\n .catch((error) => {\n console.warn(\"OTA: Failed to request version_info from glasses:\", error)\n })\n }\n\n let buildNumber = useGlassesStore.getState().buildNumber\n if (!buildNumber && waitForBuildNumberMs > 0) {\n await waitForGlassesState(\"buildNumber\", (value) => !!value, waitForBuildNumberMs)\n buildNumber = useGlassesStore.getState().buildNumber\n }\n\n if (!buildNumber || !(parseInt(buildNumber, 10) > 0)) {\n // Covers absent, non-numeric, and zero build numbers: a glasses version we\n // cannot parse means nothing is verifiable, so skip rather than ghost an\n // \"up to date\" result from a comparison that never really ran.\n return emptyCheckResult(\"missing_build\")\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n let besFirmwareVersion = useGlassesStore.getState().besFirmwareVersion\n if (!besFirmwareVersion && waitForBesVersionMs > 0) {\n console.log(\"OTA: BES version still unknown - waiting up to 5s for it to arrive...\")\n await waitForGlassesState(\"besFirmwareVersion\", (value) => !!value, waitForBesVersionMs)\n besFirmwareVersion = useGlassesStore.getState().besFirmwareVersion\n if (besFirmwareVersion) {\n console.log(`OTA: BES version arrived: ${besFirmwareVersion}`)\n } else {\n console.log(\"OTA: BES version still unknown after extended wait - will assume BES update if published\")\n }\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n let mtkFirmwareVersion = useGlassesStore.getState().mtkFirmwareVersion\n if (!mtkFirmwareVersion && waitForMtkVersionMs > 0) {\n await waitForGlassesState(\"mtkFirmwareVersion\", (value) => !!value, waitForMtkVersionMs)\n mtkFirmwareVersion = useGlassesStore.getState().mtkFirmwareVersion\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n if (fixClockBeforeCheck) {\n const systemTimeMs = getGlassesSystemTimeMs()\n await maybeFixGlassesClockFromVersionInfo(systemTimeMs > 0 ? systemTimeMs : undefined).catch((error) => {\n console.warn(\"OTA: clock fix attempt failed; continuing OTA check\", error)\n })\n }\n\n const manifestUrl = resolveOtaManifestUrl(useGlassesStore.getState().otaVersionUrl, buildNumber)\n if (!manifestUrl) {\n console.log(\"OTA: check skipped - no OTA manifest pin resolved\")\n return emptyCheckResult(\"dev_build\")\n }\n const result = await checkForOtaUpdate(\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n floorVersionCode,\n )\n\n if (!result.hasCheckCompleted) {\n return {\n ...result,\n updateInfo: null,\n isRequired: true,\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n }\n }\n\n const mtkUpdatedThisSession = useGlassesStore.getState().mtkUpdatedThisSession\n let filteredUpdates = result.updates\n if (mtkUpdatedThisSession && filteredUpdates.includes(\"mtk\")) {\n console.log(\"OTA: Filtering out MTK - already updated this session (pending reboot)\")\n filteredUpdates = filteredUpdates.filter((update) => update !== \"mtk\")\n }\n\n // Pre-migration parity: an update is only surfaced when the manifest also\n // carries APK version metadata. A firmware-only manifest (mtk_patches /\n // bes_firmware with no APK entry) is therefore dropped here — same as the\n // legacy host check. Loud-log it so the case is visible in the field;\n // surfacing firmware-only updates is a deliberate behavior change to make\n // separately, not silently inside the boundary migration.\n if (filteredUpdates.length > 0 && !result.latestVersionInfo) {\n console.warn(\n `OTA: manifest lists updates [${filteredUpdates.join(\", \")}] but has no APK version metadata - ` +\n \"reporting no update (legacy behavior)\",\n )\n }\n const updateAvailable = filteredUpdates.length > 0 && !!result.latestVersionInfo\n const isApkDowngrade = result.isApkDowngrade && filteredUpdates.includes(\"apk\")\n const updateInfo = updateAvailable\n ? {\n available: true,\n versionCode: result.latestVersionInfo?.versionCode || 0,\n versionName: result.latestVersionInfo?.versionName || \"\",\n updates: filteredUpdates,\n totalSize: 0,\n ...(filteredUpdates.includes(\"bes\") && result.besVersion ? {besVersion: result.besVersion} : {}),\n isDowngrade: isApkDowngrade,\n }\n : null\n\n useGlassesStore.getState().setOtaUpdateAvailable(updateInfo)\n\n return {\n ...result,\n updateAvailable,\n updates: filteredUpdates,\n isApkDowngrade,\n updateInfo,\n // Required-update policy: an EXPLICIT isRequired in the manifest is the\n // release-engineering escape hatch and wins in both directions. When absent,\n // upgrades keep the historical forced default, but downgrades are always\n // skippable — a version change that resets glasses settings must be\n // declinable unless someone explicitly forced it.\n isRequired:\n result.latestVersionInfo?.isRequired === true\n ? true\n : result.latestVersionInfo?.isRequired === false\n ? false\n : !isApkDowngrade,\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ type OtaReleaseVersionInput = {
2
+ manifestReleaseVersion?: string | null;
3
+ manifestUrl: string;
4
+ packagedManifestUrl?: string | null;
5
+ packagedReleaseIdentity?: string | null;
6
+ };
7
+ /** Resolve the label for the selected OTA pin, including stable promotion of exact beta OTA bytes. */
8
+ export declare function resolveOtaReleaseVersion(input: OtaReleaseVersionInput): string | null;
9
+ export {};
10
+ //# sourceMappingURL=otaReleaseVersion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otaReleaseVersion.d.ts","sourceRoot":"","sources":["../../src/services/otaReleaseVersion.ts"],"names":[],"mappings":"AAEA,KAAK,sBAAsB,GAAG;IAC5B,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxC,CAAA;AAOD,sGAAsG;AACtG,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI,CAMrF"}
@@ -0,0 +1,14 @@
1
+ const COORDINATED_RELEASE_VERSION = /^\d+\.\d+\.\d+(?:-(?:dev|beta)\.[1-9]\d*)?$/;
2
+ function validReleaseVersion(version) {
3
+ const trimmed = version?.trim();
4
+ return trimmed && COORDINATED_RELEASE_VERSION.test(trimmed) ? trimmed : null;
5
+ }
6
+ /** Resolve the label for the selected OTA pin, including stable promotion of exact beta OTA bytes. */
7
+ export function resolveOtaReleaseVersion(input) {
8
+ const packagedRelease = validReleaseVersion(input.packagedReleaseIdentity);
9
+ if (packagedRelease && input.packagedManifestUrl?.trim() === input.manifestUrl.trim()) {
10
+ return packagedRelease;
11
+ }
12
+ return validReleaseVersion(input.manifestReleaseVersion);
13
+ }
14
+ //# sourceMappingURL=otaReleaseVersion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otaReleaseVersion.js","sourceRoot":"","sources":["../../src/services/otaReleaseVersion.ts"],"names":[],"mappings":"AAAA,MAAM,2BAA2B,GAAG,6CAA6C,CAAA;AASjF,SAAS,mBAAmB,CAAC,OAAkC;IAC7D,MAAM,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,CAAA;IAC/B,OAAO,OAAO,IAAI,2BAA2B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;AAC9E,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,wBAAwB,CAAC,KAA6B;IACpE,MAAM,eAAe,GAAG,mBAAmB,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAA;IAC1E,IAAI,eAAe,IAAI,KAAK,CAAC,mBAAmB,EAAE,IAAI,EAAE,KAAK,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;QACtF,OAAO,eAAe,CAAA;IACxB,CAAC;IACD,OAAO,mBAAmB,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAA;AAC1D,CAAC","sourcesContent":["const COORDINATED_RELEASE_VERSION = /^\\d+\\.\\d+\\.\\d+(?:-(?:dev|beta)\\.[1-9]\\d*)?$/\n\ntype OtaReleaseVersionInput = {\n manifestReleaseVersion?: string | null\n manifestUrl: string\n packagedManifestUrl?: string | null\n packagedReleaseIdentity?: string | null\n}\n\nfunction validReleaseVersion(version: string | null | undefined): string | null {\n const trimmed = version?.trim()\n return trimmed && COORDINATED_RELEASE_VERSION.test(trimmed) ? trimmed : null\n}\n\n/** Resolve the label for the selected OTA pin, including stable promotion of exact beta OTA bytes. */\nexport function resolveOtaReleaseVersion(input: OtaReleaseVersionInput): string | null {\n const packagedRelease = validReleaseVersion(input.packagedReleaseIdentity)\n if (packagedRelease && input.packagedManifestUrl?.trim() === input.manifestUrl.trim()) {\n return packagedRelease\n }\n return validReleaseVersion(input.manifestReleaseVersion)\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/engine",
3
- "version": "3.1.0-dev.33",
3
+ "version": "3.1.0-dev.38",
4
4
  "description": "Mentra Engine — miniapp registry, WebView bridge, and miniapp store",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -97,11 +97,11 @@
97
97
  "registry": "https://registry.npmjs.org/"
98
98
  },
99
99
  "dependencies": {
100
- "@mentra/bluetooth-sdk": "3.1.0-dev.33",
101
- "@mentra/cloud-client": "3.1.0-dev.33",
102
- "@mentra/cloud-protocol": "3.1.0-dev.33",
103
- "@mentra/crust": "3.1.0-dev.33",
104
- "@mentra/miniapp": "3.1.0-dev.33",
100
+ "@mentra/bluetooth-sdk": "3.1.0-dev.38",
101
+ "@mentra/cloud-client": "3.1.0-dev.38",
102
+ "@mentra/cloud-protocol": "3.1.0-dev.38",
103
+ "@mentra/crust": "3.1.0-dev.38",
104
+ "@mentra/miniapp": "3.1.0-dev.38",
105
105
  "buffer": "^6.0.3",
106
106
  "events": "^3.3.0",
107
107
  "semver": "^7.7.4",
@@ -13,6 +13,7 @@ import BluetoothSdk from "@mentra/bluetooth-sdk/internal"
13
13
  import type {ConnectOptions, Device, DeviceModel, PairFailureEvent, GlassesNotReadyEvent} from "@mentra/bluetooth-sdk"
14
14
  import {useCoreStore} from "../stores/core"
15
15
  import {useGlassesStore} from "../stores/glasses"
16
+ import {SETTINGS, useSettingsStore} from "../stores/settings"
16
17
  import {hasDefaultDevice} from "../services/DeviceStoreHydration"
17
18
  import {
18
19
  markPendingSelection,
@@ -20,18 +21,14 @@ import {
20
21
  subscribePairingIdentity,
21
22
  type IdentitySnapshot,
22
23
  } from "../services/PairingIdentity"
23
- import {
24
- isGlassesConnected,
25
- isGlassesLinkLayerBusy,
26
- isGlassesReady,
27
- waitForGlassesReady,
28
- } from "../services/GlassesReadiness"
24
+ import {isGlassesConnected, isGlassesLinkLayerBusy, isGlassesReady} from "../services/GlassesReadiness"
29
25
  import {
30
26
  logAutomaticReportSubmissionStatus,
31
27
  toAutomaticReportSubmissionStatus,
32
28
  type AutomaticReportSubmissionStatus,
33
29
  } from "../services/AutomaticReportResult"
34
30
  import {pushAllBluetoothSettings} from "../services/GlassesSettingsSync"
31
+ import {ControllerTypes} from "../types"
35
32
  import {submitAutomaticReport} from "./reports"
36
33
 
37
34
  export type {IdentitySnapshot} from "../services/PairingIdentity"
@@ -60,6 +57,82 @@ function projectReadiness() {
60
57
  }
61
58
  }
62
59
 
60
+ function projectTargetReady(options: Pick<PairingReadyWaitOptions, "deviceModel" | "deviceName">): boolean {
61
+ const {deviceModel, deviceName} = options
62
+ const glasses = useGlassesStore.getState()
63
+
64
+ if (deviceModel === ControllerTypes.R1) {
65
+ if (!glasses.controllerConnected || !glasses.controllerFullyBooted) return false
66
+ const settings = useSettingsStore.getState()
67
+ const pairedModel = settings.getSetting(SETTINGS.default_controller.key)
68
+ const pairedName = settings.getSetting(SETTINGS.controller_device_name.key)
69
+ return (!deviceModel || pairedModel === deviceModel) && (!deviceName || pairedName === deviceName)
70
+ }
71
+
72
+ if (!isGlassesReady(glasses.connection)) return false
73
+ const identity = projectPairingIdentity()
74
+ if (!deviceModel && !deviceName) return true
75
+ return (
76
+ identity.kind === "paired" &&
77
+ (!deviceModel || identity.model === deviceModel) &&
78
+ (!deviceName || identity.name === deviceName)
79
+ )
80
+ }
81
+
82
+ function subscribeTargetReady(
83
+ options: Pick<PairingReadyWaitOptions, "deviceModel" | "deviceName">,
84
+ cb: (ready: boolean) => void,
85
+ ): () => void {
86
+ let last = projectTargetReady(options)
87
+ const notify = () => {
88
+ const ready = projectTargetReady(options)
89
+ if (ready === last) return
90
+ last = ready
91
+ cb(ready)
92
+ }
93
+ const unsubscribeGlasses = useGlassesStore.subscribe(notify)
94
+ const unsubscribeSettings = useSettingsStore.subscribe(notify)
95
+ return () => {
96
+ unsubscribeGlasses()
97
+ unsubscribeSettings()
98
+ }
99
+ }
100
+
101
+ async function waitForTargetReady(options: PairingReadyWaitOptions, timeoutMs: number): Promise<boolean> {
102
+ return new Promise((resolve) => {
103
+ if (options.signal?.aborted) {
104
+ resolve(false)
105
+ return
106
+ }
107
+ if (projectTargetReady(options)) {
108
+ resolve(true)
109
+ return
110
+ }
111
+
112
+ let settled = false
113
+ let timeout: ReturnType<typeof setTimeout> | null = null
114
+ let unsubscribe: (() => void) | null = null
115
+ let onAbort: (() => void) | null = null
116
+ const finish = (ready: boolean) => {
117
+ if (settled) return
118
+ settled = true
119
+ if (timeout) clearTimeout(timeout)
120
+ if (unsubscribe) unsubscribe()
121
+ if (onAbort && options.signal) options.signal.removeEventListener("abort", onAbort)
122
+ resolve(ready)
123
+ }
124
+
125
+ unsubscribe = subscribeTargetReady(options, (ready) => {
126
+ if (ready) finish(true)
127
+ })
128
+ if (options.signal) {
129
+ onAbort = () => finish(false)
130
+ options.signal.addEventListener("abort", onAbort)
131
+ }
132
+ timeout = setTimeout(() => finish(projectTargetReady(options)), timeoutMs)
133
+ })
134
+ }
135
+
63
136
  export type PairingReadinessSnapshot = ReturnType<typeof projectReadiness>
64
137
 
65
138
  export async function submitPairingBootTimeoutReport(params: {
@@ -108,12 +181,7 @@ async function waitForReadyDuringPairing(options: PairingReadyWaitOptions = {}):
108
181
  })
109
182
 
110
183
  try {
111
- const ready = await waitForGlassesReady({
112
- getConnection: () => useGlassesStore.getState().connection,
113
- subscribe: (listener) => useGlassesStore.subscribe((state) => state.connection, listener),
114
- timeoutMs,
115
- signal: options.signal,
116
- })
184
+ const ready = await waitForTargetReady(options, timeoutMs)
117
185
 
118
186
  if (!ready && !options.signal?.aborted) {
119
187
  void submitPairingBootTimeoutReport({
@@ -193,6 +261,14 @@ export const pairing = {
193
261
  cb(snap)
194
262
  })
195
263
  },
264
+ /** Whether the exact wearable or controller selected on the scan screen is ready. */
265
+ targetReady: (options: Pick<PairingReadyWaitOptions, "deviceModel" | "deviceName">): boolean =>
266
+ projectTargetReady(options),
267
+ /** Subscribe to readiness for the exact selected target, excluding stale devices. */
268
+ onTargetReady: (
269
+ options: Pick<PairingReadyWaitOptions, "deviceModel" | "deviceName">,
270
+ cb: (ready: boolean) => void,
271
+ ): (() => void) => subscribeTargetReady(options, cb),
196
272
 
197
273
  // --- pairing-identity lifecycle (the PairingIdentity read-model + the JS-owned
198
274
  // identity writes; promotion to `paired` only ever happens natively) ---
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
12
12
  export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
13
13
  "schemaVersion": 1,
14
14
  "familyBaseVersion": "3.1.0",
15
- "releaseIdentity": "3.1.0-dev.33",
16
- "releaseSetId": "mentra-3.1.0-dev.33",
17
- "sourceCommit": "b1b492d91b29548ebedb4d4b7e98faf4a02b2d36",
18
- "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.1.0/mentra-live-ota-3.1.0-dev.33.json",
19
- "otaManifestSha256": "d1dbac07a41c51e52f56fef3f62aa6f62b46a23bfc1870cbf9a87b9b3eaa3e27"
15
+ "releaseIdentity": "3.1.0-dev.38",
16
+ "releaseSetId": "mentra-3.1.0-dev.38",
17
+ "sourceCommit": "1982fb29e2859267a19a5e1a87820b6762234f7c",
18
+ "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.1.0/mentra-live-ota-3.1.0-dev.38.json",
19
+ "otaManifestSha256": "9f952e253b26d0abb3936819d22e14d7bac627926dda6946d9e1d9330aa774cc"
20
20
  })
@@ -72,6 +72,9 @@ const ENGLISH_COPY: Record<string, string> = {
72
72
  "ota:updateConnectWifi": "Connect your {{deviceName}} to WiFi to install the update.",
73
73
  "ota:updateDescription":
74
74
  "A new update is available for your glasses. We recommend updating now for the best experience.",
75
+ "ota:releaseTransition": "{{fromVersion}} → {{toVersion}}",
76
+ "ota:releaseTransitionUnknown": "Current version unknown → {{toVersion}}",
77
+ "ota:updatedToVersion": "Updated to {{version}}",
75
78
  "ota:downgradeAvailable": "{{deviceName}} Version Change Required",
76
79
  "ota:downgradeDescription":
77
80
  "This app requires an earlier glasses software version. Your photos and videos will be preserved, but glasses settings will be reset and restored automatically after the change.",
@@ -204,6 +207,16 @@ function OtaFlowContent({
204
207
  ) : null}
205
208
  </>
206
209
  }>
210
+ {state.releaseTransition ? (
211
+ <BodyText colors={colors}>
212
+ {state.releaseTransition.fromVersion
213
+ ? translate("ota:releaseTransition", {
214
+ fromVersion: state.releaseTransition.fromVersion,
215
+ toVersion: state.releaseTransition.toVersion,
216
+ })
217
+ : translate("ota:releaseTransitionUnknown", {toVersion: state.releaseTransition.toVersion})}
218
+ </BodyText>
219
+ ) : null}
207
220
  <BodyText colors={colors}>
208
221
  {state.screen === "wifi_required"
209
222
  ? translate("ota:updateConnectWifi", {deviceName})
@@ -233,6 +246,11 @@ function OtaFlowContent({
233
246
  icon="check"
234
247
  title={translate("ota:upToDate")}>
235
248
  <BodyText colors={colors}>{translate("ota:noUpdatesAvailable")}</BodyText>
249
+ {state.releaseTransition ? (
250
+ <BodyText colors={colors}>
251
+ {translate("ota:updatedToVersion", {version: state.releaseTransition.toVersion})}
252
+ </BodyText>
253
+ ) : null}
236
254
  <ChangelogList changelogs={state.changelogs} colors={colors} />
237
255
  </FlowPage>
238
256
  )
@@ -364,6 +382,11 @@ function OtaFlowContent({
364
382
  icon="check"
365
383
  title={title}>
366
384
  <BodyText colors={colors}>{message}</BodyText>
385
+ {state.releaseTransition ? (
386
+ <BodyText colors={colors}>
387
+ {translate("ota:updatedToVersion", {version: state.releaseTransition.toVersion})}
388
+ </BodyText>
389
+ ) : null}
367
390
  <ChangelogList changelogs={state.changelogs} colors={colors} />
368
391
  </FlowPage>
369
392
  )
@@ -12,6 +12,7 @@ export {
12
12
  type MentraLiveOtaErrorCode,
13
13
  type MentraLiveOtaHotspotPhase,
14
14
  type MentraLiveOtaInstallPhase,
15
+ type MentraLiveOtaReleaseTransition,
15
16
  type MentraLiveOtaScreen,
16
17
  type MentraLiveOtaState,
17
18
  type MentraLiveOtaStep,
@@ -10,6 +10,7 @@ import {
10
10
  otaAutoChainReconnectWaitRemaining,
11
11
  stopOtaAutoChain,
12
12
  tryAdvanceOtaAutoChain,
13
+ type OtaAutoChainReleaseRange,
13
14
  } from "../services/OtaAutoChain"
14
15
  import {
15
16
  BES_INSTALL_RESTART_MESSAGE,
@@ -58,6 +59,13 @@ export type MentraLiveOtaHotspotPhase = "idle" | "downloading" | "starting_hotsp
58
59
  export type MentraLiveOtaInstallPhase = "download" | "install"
59
60
  export type MentraLiveOtaStep = "apk" | "mtk" | "bes"
60
61
 
62
+ export type MentraLiveOtaReleaseTransition = {
63
+ /** Current glasses software label. Temporarily backed by the reported ASG app version. */
64
+ fromVersion: string | null
65
+ /** Exact coordinated release identity for the selected OTA pin. */
66
+ toVersion: string
67
+ }
68
+
61
69
  export type MentraLiveOtaState = {
62
70
  screen: MentraLiveOtaScreen
63
71
  connected: boolean
@@ -86,6 +94,8 @@ export type MentraLiveOtaState = {
86
94
  canDiscard: boolean
87
95
  canOpenWifiSetup: boolean
88
96
  continueDisabled: boolean
97
+ /** Release labels for the offered or just-completed coordinated update. */
98
+ releaseTransition: MentraLiveOtaReleaseTransition | null
89
99
  /** Release notes crossed by this update, newest first. Populated on completion. */
90
100
  changelogs: ReleaseChangelog[]
91
101
  }
@@ -119,8 +129,18 @@ type CheckState = "checking" | "update_available" | "no_update" | "dev_build" |
119
129
  const AUTO_CHAIN_NETWORK_RETRY_DELAY_MS = 5000
120
130
  const AUTO_CHAIN_COMPLETE_DELAY_MS = 750
121
131
 
122
- function targetVersion(result: OtaCheckCurrentGlassesResult): string | null {
123
- return result.updateInfo?.versionName ?? result.latestVersionInfo?.versionName ?? null
132
+ function releaseRangeTargetVersion(result: OtaCheckCurrentGlassesResult): string | null {
133
+ return result.releaseVersion ?? result.updateInfo?.versionName ?? result.latestVersionInfo?.versionName ?? null
134
+ }
135
+
136
+ function currentReleaseVersion(appVersion: string | null): string | null {
137
+ const version = appVersion?.trim()
138
+ return version || null
139
+ }
140
+
141
+ function releaseTransitionFromRange(range: OtaAutoChainReleaseRange | null): MentraLiveOtaReleaseTransition | null {
142
+ if (!range?.releaseVersion) return null
143
+ return {fromVersion: range.fromVersion, toVersion: range.releaseVersion}
124
144
  }
125
145
 
126
146
  function releaseChangelogsForActiveChain(): ReleaseChangelog[] {
@@ -191,6 +211,10 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
191
211
  const [isVersionChange, setIsVersionChange] = useState(false)
192
212
  const [errorKind, setErrorKind] = useState<"network" | "pin_unavailable">("network")
193
213
  const [updateFingerprint, setUpdateFingerprint] = useState<string | null>(null)
214
+ const [offeredReleaseTransition, setOfferedReleaseTransition] = useState<MentraLiveOtaReleaseTransition | null>(null)
215
+ const [completedReleaseTransition, setCompletedReleaseTransition] = useState<MentraLiveOtaReleaseTransition | null>(
216
+ null,
217
+ )
194
218
  const [completedChangelogs, setCompletedChangelogs] = useState<ReleaseChangelog[]>([])
195
219
  const [checkGeneration, setCheckGeneration] = useState(0)
196
220
  const performCheckGenerationRef = useRef(0)
@@ -336,6 +360,10 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
336
360
  setIsVersionChange(result.updateInfo.isDowngrade === true)
337
361
  const fingerprint = otaAutoChainFingerprint(result)
338
362
  setUpdateFingerprint(fingerprint)
363
+ if (!isOtaAutoChainActive()) {
364
+ const fromVersion = currentReleaseVersion(ota.snapshot().appVersion)
365
+ setOfferedReleaseTransition(result.releaseVersion ? {fromVersion, toVersion: result.releaseVersion} : null)
366
+ }
339
367
  if (isOtaAutoChainActive()) {
340
368
  const snapshot = ota.snapshot()
341
369
  if (!snapshot.wifiStatusKnown) return
@@ -345,7 +373,8 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
345
373
  const admission = tryAdvanceOtaAutoChain(
346
374
  fingerprint,
347
375
  result.updateInfo.isDowngrade === true,
348
- targetVersion(result),
376
+ releaseRangeTargetVersion(result),
377
+ result.releaseVersion,
349
378
  )
350
379
  if (admission.advance) {
351
380
  checkCompletedRef.current = true
@@ -359,7 +388,10 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
359
388
  setCheckState("update_available")
360
389
  return
361
390
  }
362
- if (isOtaAutoChainActive()) setCompletedChangelogs(releaseChangelogsForActiveChain())
391
+ if (isOtaAutoChainActive()) {
392
+ setCompletedChangelogs(releaseChangelogsForActiveChain())
393
+ setCompletedReleaseTransition(releaseTransitionFromRange(otaAutoChainReleaseRange()))
394
+ }
363
395
  stopOtaAutoChain()
364
396
  checkCompletedRef.current = true
365
397
  ota.clearUpdateAvailable()
@@ -425,6 +457,8 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
425
457
  }, [installSnapshot.connected, installSnapshot.displayState, page, returnToCheck])
426
458
 
427
459
  const check = useCallback(() => {
460
+ setOfferedReleaseTransition(null)
461
+ setCompletedReleaseTransition(null)
428
462
  setCompletedChangelogs([])
429
463
  setPage("check")
430
464
  setCheckState("checking")
@@ -451,13 +485,17 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
451
485
  ota.installSession.prepare(result)
452
486
  setCompletedChangelogs([])
453
487
  if (updateFingerprint) {
488
+ const fromVersion = offeredReleaseTransition
489
+ ? offeredReleaseTransition.fromVersion
490
+ : currentReleaseVersion(snapshot.appVersion)
454
491
  beginOtaAutoChain(updateFingerprint, isVersionChange, {
455
- fromVersion: snapshot.appVersion,
456
- toVersion: targetVersion(result),
492
+ fromVersion,
493
+ toVersion: releaseRangeTargetVersion(result),
494
+ releaseVersion: result.releaseVersion,
457
495
  })
458
496
  }
459
497
  navigateToProgress()
460
- }, [check, isVersionChange, navigateToProgress, page, updateFingerprint])
498
+ }, [check, isVersionChange, navigateToProgress, offeredReleaseTransition, page, updateFingerprint])
461
499
 
462
500
  const retryInstall = useCallback(() => {
463
501
  if (page !== "progress") return
@@ -525,6 +563,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
525
563
  canDiscard: false,
526
564
  canOpenWifiSetup: false,
527
565
  continueDisabled: false,
566
+ releaseTransition: null,
528
567
  changelogs: [],
529
568
  }
530
569
  }
@@ -579,6 +618,12 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
579
618
  canDiscard: false,
580
619
  canOpenWifiSetup: screen === "wifi_required",
581
620
  continueDisabled: false,
621
+ releaseTransition:
622
+ screen === "update_available" || screen === "wifi_required"
623
+ ? offeredReleaseTransition
624
+ : screen === "up_to_date"
625
+ ? completedReleaseTransition
626
+ : null,
582
627
  changelogs: screen === "up_to_date" ? completedChangelogs : [],
583
628
  }
584
629
  }
@@ -637,17 +682,20 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
637
682
  canDiscard: screen === "disconnected",
638
683
  canOpenWifiSetup: screen === "failed" && showChangeWifi,
639
684
  continueDisabled: installSnapshot.continueButtonDisabled,
685
+ releaseTransition: releaseTransitionFromRange(otaAutoChainReleaseRange()),
640
686
  changelogs,
641
687
  }
642
688
  }, [
643
689
  checkState,
644
690
  completedChangelogs,
691
+ completedReleaseTransition,
645
692
  errorKind,
646
693
  firmwareRestarting,
647
694
  installSnapshot,
648
695
  isUpdateRequired,
649
696
  isVersionChange,
650
697
  otaSnapshot,
698
+ offeredReleaseTransition,
651
699
  page,
652
700
  runtimeReady,
653
701
  ])
@@ -14,6 +14,8 @@ type OtaAutoChainSession = {
14
14
  export type OtaAutoChainReleaseRange = {
15
15
  fromVersion: string | null
16
16
  toVersion: string | null
17
+ /** Exact coordinated release identity for the OTA pin, absent for legacy manifests. */
18
+ releaseVersion?: string | null
17
19
  }
18
20
 
19
21
  export type OtaAutoChainAdvanceResult =
@@ -99,6 +101,7 @@ export function tryAdvanceOtaAutoChain(
99
101
  fingerprint: string,
100
102
  isDowngrade: boolean,
101
103
  targetVersion: string | null,
104
+ releaseVersion: string | null = null,
102
105
  ): OtaAutoChainAdvanceResult {
103
106
  if (!session) {
104
107
  return {advance: false, reason: "inactive"}
@@ -122,5 +125,6 @@ export function tryAdvanceOtaAutoChain(
122
125
  session.seenFingerprints.add(fingerprint)
123
126
  session.passCount += 1
124
127
  if (targetVersion) session.releaseRange.toVersion = targetVersion
128
+ if (releaseVersion) session.releaseRange.releaseVersion = releaseVersion
125
129
  return {advance: true, passCount: session.passCount}
126
130
  }