@mentra/engine 3.1.0-dev.82 → 3.1.0-dev.89

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.
@@ -30,6 +30,8 @@ export declare const PING_INTERVAL_MS = 10000;
30
30
  export declare const QUERY_REPLY_TIMEOUT_MS = 6000;
31
31
  /** 15s lockout on the Continue button after a BES restart, to prevent an accidental tap. */
32
32
  export declare const BES_CONTINUE_LOCKOUT_MS = 15000;
33
+ /** Fail closed if a BES reboot never reconnects; normal restarts complete in a fraction of this window. */
34
+ export declare const BES_RESTART_TIMEOUT_MS = 120000;
33
35
  /** The uniform pad progress-legacy.tsx added to every duration for old builds. */
34
36
  export declare const LEGACY_EXTRA_TIMEOUT_MS = 20000;
35
37
  export declare const LEGACY_RETRY_INTERVAL_MS: number;
@@ -39,6 +41,7 @@ export declare const LEGACY_MTK_INSTALL_TIMEOUT_MS: number;
39
41
  export declare const LEGACY_GLOBAL_OTA_TIMEOUT_MS: number;
40
42
  export declare const LEGACY_PING_INTERVAL_MS: number;
41
43
  export declare const LEGACY_BES_CONTINUE_LOCKOUT_MS: number;
44
+ export declare const LEGACY_BES_RESTART_TIMEOUT_MS: number;
42
45
  /**
43
46
  * After a legacy apk `install FINISHED` observed in-flight, the legacy screen held the
44
47
  * completed state for 12s (+20s pad) so the ASG process restart settles (and fresh
@@ -82,6 +85,7 @@ export declare function selectOtaProtocolProfile(otaStatus: OtaStatus | null, ot
82
85
  export declare const OtaProgressMessages: {
83
86
  readonly stalledOrStuck: "Update may have failed. Ensure glasses have internet access and try again.";
84
87
  readonly globalTimeout: "Update took too long. Please try again.";
88
+ readonly restartTimeout: "Glasses did not finish restarting. Restart them before trying the update again.";
85
89
  readonly sendOtaStartFailed: "Failed to communicate with glasses.";
86
90
  readonly hotspotArtifactDownloadFailed: "Could not download the update to your phone. Check phone internet and try again.";
87
91
  readonly hotspotArtifactVerifyFailed: "The update package could not be verified. Please try again later.";
@@ -1 +1 @@
1
- {"version":3,"file":"otaInstallPolicy.d.ts","sourceRoot":"","sources":["../../src/services/otaInstallPolicy.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,OAAO,KAAK,EAAC,WAAW,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAA;AAEjE;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,KAAK,CAAA;AAE1C,eAAO,MAAM,WAAW,IAAI,CAAA;AAC5B,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,OAAO,CAAA;AACrC,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,SAAU,CAAA;AAC1C,kDAAkD;AAClD,eAAO,MAAM,yBAAyB,QAAS,CAAA;AAC/C,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,SAAU,CAAA;AAC7C,mCAAmC;AACnC,eAAO,MAAM,qBAAqB,QAAiB,CAAA;AACnD,+BAA+B;AAC/B,eAAO,MAAM,gBAAgB,QAAS,CAAA;AACtC;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,OAAO,CAAA;AAC1C,4FAA4F;AAC5F,eAAO,MAAM,uBAAuB,QAAS,CAAA;AAW7C,kFAAkF;AAClF,eAAO,MAAM,uBAAuB,QAAS,CAAA;AAC7C,eAAO,MAAM,wBAAwB,QAA8C,CAAA;AACnF,eAAO,MAAM,0BAA0B,QAAgD,CAAA;AACvF,eAAO,MAAM,gCAAgC,QAAsD,CAAA;AACnG,eAAO,MAAM,6BAA6B,QAAmD,CAAA;AAC7F,eAAO,MAAM,4BAA4B,QAAkD,CAAA;AAC3F,eAAO,MAAM,uBAAuB,QAA6C,CAAA;AACjF,eAAO,MAAM,8BAA8B,QAAoD,CAAA;AAC/F;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,QAAmC,CAAA;AAC/E;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,QAAmC,CAAA;AAC1E,eAAO,MAAM,sBAAsB,QAAmC,CAAA;AACtE,eAAO,MAAM,iCAAiC,KAAK,CAAA;AACnD,eAAO,MAAM,iCAAiC,KAAK,CAAA;AACnD,eAAO,MAAM,4BAA4B,KAAK,CAAA;AAC9C,eAAO,MAAM,0BAA0B,KAAK,CAAA;AAE5C,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,CAAA;AAErD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,SAAS,GAAG,IAAI,EAC3B,WAAW,EAAE,WAAW,GAAG,IAAI,EAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,OAAO,CAKT;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,SAAS,GAAG,IAAI,EAC3B,WAAW,EAAE,WAAW,GAAG,IAAI,EAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,kBAAkB,GAAG,IAAI,CAQ3B;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;CAUtB,CAAA"}
1
+ {"version":3,"file":"otaInstallPolicy.d.ts","sourceRoot":"","sources":["../../src/services/otaInstallPolicy.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,OAAO,KAAK,EAAC,WAAW,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAA;AAEjE;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,KAAK,CAAA;AAE1C,eAAO,MAAM,WAAW,IAAI,CAAA;AAC5B,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,OAAO,CAAA;AACrC,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,SAAU,CAAA;AAC1C,kDAAkD;AAClD,eAAO,MAAM,yBAAyB,QAAS,CAAA;AAC/C,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,SAAU,CAAA;AAC7C,mCAAmC;AACnC,eAAO,MAAM,qBAAqB,QAAiB,CAAA;AACnD,+BAA+B;AAC/B,eAAO,MAAM,gBAAgB,QAAS,CAAA;AACtC;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,OAAO,CAAA;AAC1C,4FAA4F;AAC5F,eAAO,MAAM,uBAAuB,QAAS,CAAA;AAC7C,2GAA2G;AAC3G,eAAO,MAAM,sBAAsB,SAAU,CAAA;AAW7C,kFAAkF;AAClF,eAAO,MAAM,uBAAuB,QAAS,CAAA;AAC7C,eAAO,MAAM,wBAAwB,QAA8C,CAAA;AACnF,eAAO,MAAM,0BAA0B,QAAgD,CAAA;AACvF,eAAO,MAAM,gCAAgC,QAAsD,CAAA;AACnG,eAAO,MAAM,6BAA6B,QAAmD,CAAA;AAC7F,eAAO,MAAM,4BAA4B,QAAkD,CAAA;AAC3F,eAAO,MAAM,uBAAuB,QAA6C,CAAA;AACjF,eAAO,MAAM,8BAA8B,QAAoD,CAAA;AAC/F,eAAO,MAAM,6BAA6B,QAAmD,CAAA;AAC7F;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,QAAmC,CAAA;AAC/E;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,QAAmC,CAAA;AAC1E,eAAO,MAAM,sBAAsB,QAAmC,CAAA;AACtE,eAAO,MAAM,iCAAiC,KAAK,CAAA;AACnD,eAAO,MAAM,iCAAiC,KAAK,CAAA;AACnD,eAAO,MAAM,4BAA4B,KAAK,CAAA;AAC9C,eAAO,MAAM,0BAA0B,KAAK,CAAA;AAE5C,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,CAAA;AAErD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,SAAS,GAAG,IAAI,EAC3B,WAAW,EAAE,WAAW,GAAG,IAAI,EAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,OAAO,CAKT;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,SAAS,GAAG,IAAI,EAC3B,WAAW,EAAE,WAAW,GAAG,IAAI,EAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,kBAAkB,GAAG,IAAI,CAQ3B;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;CAWtB,CAAA"}
@@ -28,6 +28,8 @@ export const PING_INTERVAL_MS = 10_000;
28
28
  export const QUERY_REPLY_TIMEOUT_MS = 6000;
29
29
  /** 15s lockout on the Continue button after a BES restart, to prevent an accidental tap. */
30
30
  export const BES_CONTINUE_LOCKOUT_MS = 15_000;
31
+ /** Fail closed if a BES reboot never reconnects; normal restarts complete in a fraction of this window. */
32
+ export const BES_RESTART_TIMEOUT_MS = 120_000;
31
33
  // --- Legacy old-build (< MINIMUM_OTA_STATUS_BUILD) OTA policy (WP 8C) ---
32
34
  //
33
35
  // Ported verbatim from mobile/src/app/ota/progress-legacy.tsx, which padded EVERY
@@ -45,6 +47,7 @@ export const LEGACY_MTK_INSTALL_TIMEOUT_MS = MTK_INSTALL_TIMEOUT_MS + LEGACY_EXT
45
47
  export const LEGACY_GLOBAL_OTA_TIMEOUT_MS = GLOBAL_OTA_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS;
46
48
  export const LEGACY_PING_INTERVAL_MS = PING_INTERVAL_MS + LEGACY_EXTRA_TIMEOUT_MS;
47
49
  export const LEGACY_BES_CONTINUE_LOCKOUT_MS = BES_CONTINUE_LOCKOUT_MS + LEGACY_EXTRA_TIMEOUT_MS;
50
+ export const LEGACY_BES_RESTART_TIMEOUT_MS = BES_RESTART_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS;
48
51
  /**
49
52
  * After a legacy apk `install FINISHED` observed in-flight, the legacy screen held the
50
53
  * completed state for 12s (+20s pad) so the ASG process restart settles (and fresh
@@ -106,6 +109,7 @@ export function selectOtaProtocolProfile(otaStatus, otaProgress, buildNumber) {
106
109
  export const OtaProgressMessages = {
107
110
  stalledOrStuck: "Update may have failed. Ensure glasses have internet access and try again.",
108
111
  globalTimeout: "Update took too long. Please try again.",
112
+ restartTimeout: "Glasses did not finish restarting. Restart them before trying the update again.",
109
113
  sendOtaStartFailed: "Failed to communicate with glasses.",
110
114
  hotspotArtifactDownloadFailed: "Could not download the update to your phone. Check phone internet and try again.",
111
115
  hotspotArtifactVerifyFailed: "The update package could not be verified. Please try again later.",
@@ -1 +1 @@
1
- {"version":3,"file":"otaInstallPolicy.js","sourceRoot":"","sources":["../../src/services/otaInstallPolicy.ts"],"names":[],"mappings":"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","sourcesContent":["/** Shared OTA progress watchdog timings (see progress-legacy.tsx / progress.tsx). */\nimport type {OtaProgress, OtaStatus} from \"@mentra/bluetooth-sdk\"\n\n/**\n * First glasses `build_number` (ASG `versionCode`) that uses `progress.tsx` with unified\n * `ota_status` session UI. Strictly lower builds use `/ota/progress-legacy` (routed from\n * `check-for-updates.tsx` before navigation), and get the LEGACY_* padded policy below\n * in the unified OtaInstallCoordinator (WP 8C). Change only here.\n */\nexport const MINIMUM_OTA_STATUS_BUILD = 37\n\nexport const MAX_RETRIES = 3\n/** Wait between ended native ota_start requests; never overlaps a pending request. */\nexport const RETRY_INTERVAL_MS = 5000\n/** APK/BES and general install when progress events are expected */\nexport const PROGRESS_TIMEOUT_MS = 120_000\n/** Fail if still at 0% in starting/downloading */\nexport const DOWNLOAD_STUCK_TIMEOUT_MS = 70_000\n/** MTK system install often goes quiet for long periods */\nexport const MTK_INSTALL_TIMEOUT_MS = 300_000\n/** Whole multi-step session cap */\nexport const GLOBAL_OTA_TIMEOUT_MS = 20 * 60 * 1000\n/** BLE keepalive during OTA */\nexport const PING_INTERVAL_MS = 10_000\n/**\n * If we sent ota_query_status (e.g. on reconnect) and the glasses haven't\n * replied with an ota_status by then, fall back to ota_start. This recovers\n * from the case where the glasses process restarted between mount and\n * reconnect — the session is gone, ota_query_status returns nothing, and\n * the user would otherwise sit on the spinner forever.\n */\nexport const QUERY_REPLY_TIMEOUT_MS = 6000\n/** 15s lockout on the Continue button after a BES restart, to prevent an accidental tap. */\nexport const BES_CONTINUE_LOCKOUT_MS = 15_000\n\n// --- Legacy old-build (< MINIMUM_OTA_STATUS_BUILD) OTA policy (WP 8C) ---\n//\n// Ported verbatim from mobile/src/app/ota/progress-legacy.tsx, which padded EVERY\n// watchdog/timer by +20s for old ASG builds (\"Legacy OTA: +20s on every watchdog /\n// timer duration\"). Old builds respond slower on every path, so where the legacy\n// screen used a longer duration the coordinator applies the longer value whenever\n// the session is legacy-shaped (see isLegacyShapedOtaStatus / the coordinator's\n// session-shape check). Builds >= 37 keep the unpadded values above.\n\n/** The uniform pad progress-legacy.tsx added to every duration for old builds. */\nexport const LEGACY_EXTRA_TIMEOUT_MS = 20_000\nexport const LEGACY_RETRY_INTERVAL_MS = RETRY_INTERVAL_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_PROGRESS_TIMEOUT_MS = PROGRESS_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_DOWNLOAD_STUCK_TIMEOUT_MS = DOWNLOAD_STUCK_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_MTK_INSTALL_TIMEOUT_MS = MTK_INSTALL_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_GLOBAL_OTA_TIMEOUT_MS = GLOBAL_OTA_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_PING_INTERVAL_MS = PING_INTERVAL_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_BES_CONTINUE_LOCKOUT_MS = BES_CONTINUE_LOCKOUT_MS + LEGACY_EXTRA_TIMEOUT_MS\n/**\n * After a legacy apk `install FINISHED` observed in-flight, the legacy screen held the\n * completed state for 12s (+20s pad) so the ASG process restart settles (and fresh\n * version_info lands) before the user can continue.\n */\nexport const LEGACY_APK_COMPLETION_SETTLE_MS = 12_000 + LEGACY_EXTRA_TIMEOUT_MS\n/**\n * Legacy MTK install stall simulation (display-only): the MTK system install typically\n * stalls around 49-50%. After LEGACY_MTK_STALL_DETECT_MS without a new event in the\n * [45, 55) zone, the legacy screen simulated +1% every LEGACY_MTK_SIM_TICK_MS starting\n * at max(stall+1, 51) and capped at 60%, purely to keep the user informed.\n */\nexport const LEGACY_MTK_STALL_DETECT_MS = 20_000 + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_MTK_SIM_TICK_MS = 15_000 + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_MTK_STALL_ZONE_MIN_PERCENT = 45\nexport const LEGACY_MTK_STALL_ZONE_MAX_PERCENT = 55\nexport const LEGACY_MTK_SIM_FLOOR_PERCENT = 51\nexport const LEGACY_MTK_SIM_CAP_PERCENT = 60\n\nexport type OtaProtocolProfile = \"legacy\" | \"unified\"\n\n/**\n * Legacy-shaped ota_status: old (< 37) ASG builds send `ota_progress`, which the SDK\n * (Android MentraLive.kt / iOS MentraLive.swift) maps to a unified ota_status with\n * sessionId \"\" and totalSteps 1, and FINISHED→\"complete\" regardless of phase. A real\n * (>= 37) session always carries a sessionId; an \"idle\" reply also has sessionId \"\" but\n * proves the glasses speak the unified protocol, so it is NOT legacy-shaped.\n */\nexport function isLegacyShapedOtaStatus(otaStatus: OtaStatus | null): boolean {\n return !!otaStatus && otaStatus.sessionId === \"\" && otaStatus.status !== \"idle\"\n}\n\n/**\n * Is the current install session legacy-shaped? Event shape wins when events exist\n * (per the WP 8C rule: key off legacy-shaped inputs, not a build flag, where possible);\n * before any event arrives only the glasses build number can tell.\n */\nexport function isLegacyShapedOtaSession(\n otaStatus: OtaStatus | null,\n otaProgress: OtaProgress | null,\n buildNumber: string | null | undefined,\n): boolean {\n if (otaStatus) return isLegacyShapedOtaStatus(otaStatus)\n if (otaProgress) return true\n const build = Number.parseInt(buildNumber ?? \"\", 10)\n return Number.isFinite(build) && build > 0 && build < MINIMUM_OTA_STATUS_BUILD\n}\n\n/**\n * Select the compatibility profile once at install-screen attach. The old split\n * routes made this decision from the pre-update build and kept it for the whole\n * mounted flow, even when an APK step restarted into a newer ASG build. Returning\n * null leaves the coordinator undecided until the first meaningful OTA event.\n */\nexport function selectOtaProtocolProfile(\n otaStatus: OtaStatus | null,\n otaProgress: OtaProgress | null,\n buildNumber: string | null | undefined,\n): OtaProtocolProfile | null {\n const build = Number.parseInt(buildNumber ?? \"\", 10)\n if (Number.isFinite(build) && build > 0) {\n return build < MINIMUM_OTA_STATUS_BUILD ? \"legacy\" : \"unified\"\n }\n if (otaStatus) return isLegacyShapedOtaStatus(otaStatus) ? \"legacy\" : \"unified\"\n if (otaProgress) return \"legacy\"\n return null\n}\n\nexport const OtaProgressMessages = {\n stalledOrStuck: \"Update may have failed. Ensure glasses have internet access and try again.\",\n globalTimeout: \"Update took too long. Please try again.\",\n sendOtaStartFailed: \"Failed to communicate with glasses.\",\n hotspotArtifactDownloadFailed: \"Could not download the update to your phone. Check phone internet and try again.\",\n hotspotArtifactVerifyFailed: \"The update package could not be verified. Please try again later.\",\n hotspotWifiPermissionDenied: \"Allow nearby devices access so the phone can connect to the glasses hotspot.\",\n hotspotStartFailed: \"Could not start the glasses hotspot. Please try again.\",\n hotspotJoinFailed: \"Could not connect the phone to the glasses hotspot. Please try again.\",\n hotspotServerFailed: \"Could not make the update available to the glasses. Please try again.\",\n} as const\n"]}
1
+ {"version":3,"file":"otaInstallPolicy.js","sourceRoot":"","sources":["../../src/services/otaInstallPolicy.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAA;AAE1C,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAA;AAC5B,sFAAsF;AACtF,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAA;AACrC,oEAAoE;AACpE,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAA;AAC1C,kDAAkD;AAClD,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAA;AAC/C,2DAA2D;AAC3D,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAA;AAC7C,mCAAmC;AACnC,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AACnD,+BAA+B;AAC/B,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAA;AACtC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAA;AAC1C,4FAA4F;AAC5F,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAA;AAC7C,2GAA2G;AAC3G,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAA;AAE7C,2EAA2E;AAC3E,EAAE;AACF,kFAAkF;AAClF,mFAAmF;AACnF,iFAAiF;AACjF,kFAAkF;AAClF,gFAAgF;AAChF,qEAAqE;AAErE,kFAAkF;AAClF,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAA;AAC7C,MAAM,CAAC,MAAM,wBAAwB,GAAG,iBAAiB,GAAG,uBAAuB,CAAA;AACnF,MAAM,CAAC,MAAM,0BAA0B,GAAG,mBAAmB,GAAG,uBAAuB,CAAA;AACvF,MAAM,CAAC,MAAM,gCAAgC,GAAG,yBAAyB,GAAG,uBAAuB,CAAA;AACnG,MAAM,CAAC,MAAM,6BAA6B,GAAG,sBAAsB,GAAG,uBAAuB,CAAA;AAC7F,MAAM,CAAC,MAAM,4BAA4B,GAAG,qBAAqB,GAAG,uBAAuB,CAAA;AAC3F,MAAM,CAAC,MAAM,uBAAuB,GAAG,gBAAgB,GAAG,uBAAuB,CAAA;AACjF,MAAM,CAAC,MAAM,8BAA8B,GAAG,uBAAuB,GAAG,uBAAuB,CAAA;AAC/F,MAAM,CAAC,MAAM,6BAA6B,GAAG,sBAAsB,GAAG,uBAAuB,CAAA;AAC7F;;;;GAIG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,MAAM,GAAG,uBAAuB,CAAA;AAC/E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAM,GAAG,uBAAuB,CAAA;AAC1E,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,GAAG,uBAAuB,CAAA;AACtE,MAAM,CAAC,MAAM,iCAAiC,GAAG,EAAE,CAAA;AACnD,MAAM,CAAC,MAAM,iCAAiC,GAAG,EAAE,CAAA;AACnD,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAA;AAC9C,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAA;AAI5C;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,SAA2B;IACjE,OAAO,CAAC,CAAC,SAAS,IAAI,SAAS,CAAC,SAAS,KAAK,EAAE,IAAI,SAAS,CAAC,MAAM,KAAK,MAAM,CAAA;AACjF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,SAA2B,EAC3B,WAA+B,EAC/B,WAAsC;IAEtC,IAAI,SAAS;QAAE,OAAO,uBAAuB,CAAC,SAAS,CAAC,CAAA;IACxD,IAAI,WAAW;QAAE,OAAO,IAAI,CAAA;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;IACpD,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,wBAAwB,CAAA;AAChF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CACtC,SAA2B,EAC3B,WAA+B,EAC/B,WAAsC;IAEtC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;IACpD,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACxC,OAAO,KAAK,GAAG,wBAAwB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;IAChE,CAAC;IACD,IAAI,SAAS;QAAE,OAAO,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;IAC/E,IAAI,WAAW;QAAE,OAAO,QAAQ,CAAA;IAChC,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,cAAc,EAAE,4EAA4E;IAC5F,aAAa,EAAE,yCAAyC;IACxD,cAAc,EAAE,iFAAiF;IACjG,kBAAkB,EAAE,qCAAqC;IACzD,6BAA6B,EAAE,kFAAkF;IACjH,2BAA2B,EAAE,mEAAmE;IAChG,2BAA2B,EAAE,8EAA8E;IAC3G,kBAAkB,EAAE,wDAAwD;IAC5E,iBAAiB,EAAE,uEAAuE;IAC1F,mBAAmB,EAAE,uEAAuE;CACpF,CAAA","sourcesContent":["/** Shared OTA progress watchdog timings (see progress-legacy.tsx / progress.tsx). */\nimport type {OtaProgress, OtaStatus} from \"@mentra/bluetooth-sdk\"\n\n/**\n * First glasses `build_number` (ASG `versionCode`) that uses `progress.tsx` with unified\n * `ota_status` session UI. Strictly lower builds use `/ota/progress-legacy` (routed from\n * `check-for-updates.tsx` before navigation), and get the LEGACY_* padded policy below\n * in the unified OtaInstallCoordinator (WP 8C). Change only here.\n */\nexport const MINIMUM_OTA_STATUS_BUILD = 37\n\nexport const MAX_RETRIES = 3\n/** Wait between ended native ota_start requests; never overlaps a pending request. */\nexport const RETRY_INTERVAL_MS = 5000\n/** APK/BES and general install when progress events are expected */\nexport const PROGRESS_TIMEOUT_MS = 120_000\n/** Fail if still at 0% in starting/downloading */\nexport const DOWNLOAD_STUCK_TIMEOUT_MS = 70_000\n/** MTK system install often goes quiet for long periods */\nexport const MTK_INSTALL_TIMEOUT_MS = 300_000\n/** Whole multi-step session cap */\nexport const GLOBAL_OTA_TIMEOUT_MS = 20 * 60 * 1000\n/** BLE keepalive during OTA */\nexport const PING_INTERVAL_MS = 10_000\n/**\n * If we sent ota_query_status (e.g. on reconnect) and the glasses haven't\n * replied with an ota_status by then, fall back to ota_start. This recovers\n * from the case where the glasses process restarted between mount and\n * reconnect — the session is gone, ota_query_status returns nothing, and\n * the user would otherwise sit on the spinner forever.\n */\nexport const QUERY_REPLY_TIMEOUT_MS = 6000\n/** 15s lockout on the Continue button after a BES restart, to prevent an accidental tap. */\nexport const BES_CONTINUE_LOCKOUT_MS = 15_000\n/** Fail closed if a BES reboot never reconnects; normal restarts complete in a fraction of this window. */\nexport const BES_RESTART_TIMEOUT_MS = 120_000\n\n// --- Legacy old-build (< MINIMUM_OTA_STATUS_BUILD) OTA policy (WP 8C) ---\n//\n// Ported verbatim from mobile/src/app/ota/progress-legacy.tsx, which padded EVERY\n// watchdog/timer by +20s for old ASG builds (\"Legacy OTA: +20s on every watchdog /\n// timer duration\"). Old builds respond slower on every path, so where the legacy\n// screen used a longer duration the coordinator applies the longer value whenever\n// the session is legacy-shaped (see isLegacyShapedOtaStatus / the coordinator's\n// session-shape check). Builds >= 37 keep the unpadded values above.\n\n/** The uniform pad progress-legacy.tsx added to every duration for old builds. */\nexport const LEGACY_EXTRA_TIMEOUT_MS = 20_000\nexport const LEGACY_RETRY_INTERVAL_MS = RETRY_INTERVAL_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_PROGRESS_TIMEOUT_MS = PROGRESS_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_DOWNLOAD_STUCK_TIMEOUT_MS = DOWNLOAD_STUCK_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_MTK_INSTALL_TIMEOUT_MS = MTK_INSTALL_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_GLOBAL_OTA_TIMEOUT_MS = GLOBAL_OTA_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_PING_INTERVAL_MS = PING_INTERVAL_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_BES_CONTINUE_LOCKOUT_MS = BES_CONTINUE_LOCKOUT_MS + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_BES_RESTART_TIMEOUT_MS = BES_RESTART_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS\n/**\n * After a legacy apk `install FINISHED` observed in-flight, the legacy screen held the\n * completed state for 12s (+20s pad) so the ASG process restart settles (and fresh\n * version_info lands) before the user can continue.\n */\nexport const LEGACY_APK_COMPLETION_SETTLE_MS = 12_000 + LEGACY_EXTRA_TIMEOUT_MS\n/**\n * Legacy MTK install stall simulation (display-only): the MTK system install typically\n * stalls around 49-50%. After LEGACY_MTK_STALL_DETECT_MS without a new event in the\n * [45, 55) zone, the legacy screen simulated +1% every LEGACY_MTK_SIM_TICK_MS starting\n * at max(stall+1, 51) and capped at 60%, purely to keep the user informed.\n */\nexport const LEGACY_MTK_STALL_DETECT_MS = 20_000 + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_MTK_SIM_TICK_MS = 15_000 + LEGACY_EXTRA_TIMEOUT_MS\nexport const LEGACY_MTK_STALL_ZONE_MIN_PERCENT = 45\nexport const LEGACY_MTK_STALL_ZONE_MAX_PERCENT = 55\nexport const LEGACY_MTK_SIM_FLOOR_PERCENT = 51\nexport const LEGACY_MTK_SIM_CAP_PERCENT = 60\n\nexport type OtaProtocolProfile = \"legacy\" | \"unified\"\n\n/**\n * Legacy-shaped ota_status: old (< 37) ASG builds send `ota_progress`, which the SDK\n * (Android MentraLive.kt / iOS MentraLive.swift) maps to a unified ota_status with\n * sessionId \"\" and totalSteps 1, and FINISHED→\"complete\" regardless of phase. A real\n * (>= 37) session always carries a sessionId; an \"idle\" reply also has sessionId \"\" but\n * proves the glasses speak the unified protocol, so it is NOT legacy-shaped.\n */\nexport function isLegacyShapedOtaStatus(otaStatus: OtaStatus | null): boolean {\n return !!otaStatus && otaStatus.sessionId === \"\" && otaStatus.status !== \"idle\"\n}\n\n/**\n * Is the current install session legacy-shaped? Event shape wins when events exist\n * (per the WP 8C rule: key off legacy-shaped inputs, not a build flag, where possible);\n * before any event arrives only the glasses build number can tell.\n */\nexport function isLegacyShapedOtaSession(\n otaStatus: OtaStatus | null,\n otaProgress: OtaProgress | null,\n buildNumber: string | null | undefined,\n): boolean {\n if (otaStatus) return isLegacyShapedOtaStatus(otaStatus)\n if (otaProgress) return true\n const build = Number.parseInt(buildNumber ?? \"\", 10)\n return Number.isFinite(build) && build > 0 && build < MINIMUM_OTA_STATUS_BUILD\n}\n\n/**\n * Select the compatibility profile once at install-screen attach. The old split\n * routes made this decision from the pre-update build and kept it for the whole\n * mounted flow, even when an APK step restarted into a newer ASG build. Returning\n * null leaves the coordinator undecided until the first meaningful OTA event.\n */\nexport function selectOtaProtocolProfile(\n otaStatus: OtaStatus | null,\n otaProgress: OtaProgress | null,\n buildNumber: string | null | undefined,\n): OtaProtocolProfile | null {\n const build = Number.parseInt(buildNumber ?? \"\", 10)\n if (Number.isFinite(build) && build > 0) {\n return build < MINIMUM_OTA_STATUS_BUILD ? \"legacy\" : \"unified\"\n }\n if (otaStatus) return isLegacyShapedOtaStatus(otaStatus) ? \"legacy\" : \"unified\"\n if (otaProgress) return \"legacy\"\n return null\n}\n\nexport const OtaProgressMessages = {\n stalledOrStuck: \"Update may have failed. Ensure glasses have internet access and try again.\",\n globalTimeout: \"Update took too long. Please try again.\",\n restartTimeout: \"Glasses did not finish restarting. Restart them before trying the update again.\",\n sendOtaStartFailed: \"Failed to communicate with glasses.\",\n hotspotArtifactDownloadFailed: \"Could not download the update to your phone. Check phone internet and try again.\",\n hotspotArtifactVerifyFailed: \"The update package could not be verified. Please try again later.\",\n hotspotWifiPermissionDenied: \"Allow nearby devices access so the phone can connect to the glasses hotspot.\",\n hotspotStartFailed: \"Could not start the glasses hotspot. Please try again.\",\n hotspotJoinFailed: \"Could not connect the phone to the glasses hotspot. Please try again.\",\n hotspotServerFailed: \"Could not make the update available to the glasses. Please try again.\",\n} as const\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/engine",
3
- "version": "3.1.0-dev.82",
3
+ "version": "3.1.0-dev.89",
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.82",
101
- "@mentra/cloud-client": "3.1.0-dev.82",
102
- "@mentra/cloud-protocol": "3.1.0-dev.82",
103
- "@mentra/crust": "3.1.0-dev.82",
104
- "@mentra/miniapp": "3.1.0-dev.82",
100
+ "@mentra/bluetooth-sdk": "3.1.0-dev.89",
101
+ "@mentra/cloud-client": "3.1.0-dev.89",
102
+ "@mentra/cloud-protocol": "3.1.0-dev.89",
103
+ "@mentra/crust": "3.1.0-dev.89",
104
+ "@mentra/miniapp": "3.1.0-dev.89",
105
105
  "buffer": "^6.0.3",
106
106
  "events": "^3.3.0",
107
107
  "react-native-marked": "8.1.1",
@@ -17,7 +17,7 @@ import type {
17
17
  OtaUpdateInfo,
18
18
  ReleaseChangelog,
19
19
  } from "@mentra/bluetooth-sdk"
20
- import {isGlassesConnected, useGlassesStore} from "../stores/glasses"
20
+ import {isGlassesConnected, isGlassesReady, useGlassesStore} from "../stores/glasses"
21
21
  import {resolveOtaManifestUrl} from "../services/otaManifestUrl"
22
22
  import {otaInstallCoordinator, type OtaInstallSnapshot} from "../services/OtaInstallCoordinator"
23
23
  import {startGlassesStatusProjection} from "../services/GlassesStatusProjection"
@@ -32,6 +32,7 @@ function projectSnapshot() {
32
32
  const s = useGlassesStore.getState()
33
33
  return {
34
34
  connected: isGlassesConnected(s.connection),
35
+ ready: isGlassesReady(s.connection),
35
36
  buildNumber: s.buildNumber || null,
36
37
  appVersion: s.appVersion || null,
37
38
  mtkFirmwareVersion: s.mtkFirmwareVersion || null,
@@ -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.82",
16
- "releaseSetId": "mentra-3.1.0-dev.82",
17
- "sourceCommit": "b6cdc2075f5040f2ae11dee48e495b8e959e5d92",
18
- "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.1.0/mentra-live-ota-3.1.0-dev.82.json",
19
- "otaManifestSha256": "18ea5f71d6f37722ecb5441a5c8c9b62baea616d76a0c2720a0a6e824b7e3d48"
15
+ "releaseIdentity": "3.1.0-dev.89",
16
+ "releaseSetId": "mentra-3.1.0-dev.89",
17
+ "sourceCommit": "c698555446537e7bf08f843d2dff48909cf41fb9",
18
+ "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.1.0/mentra-live-ota-3.1.0-dev.89.json",
19
+ "otaManifestSha256": "5289e9228991bd812802f575b7828e8271a8aeb5310dcf0085db4d75cdf70018"
20
20
  })
package/src/index.ts CHANGED
@@ -89,6 +89,7 @@ export {
89
89
  GLOBAL_OTA_TIMEOUT_MS,
90
90
  PING_INTERVAL_MS,
91
91
  QUERY_REPLY_TIMEOUT_MS,
92
+ BES_RESTART_TIMEOUT_MS,
92
93
  OtaProgressMessages,
93
94
  } from "./services/otaInstallPolicy"
94
95
  export {deriveDisplayState} from "./services/otaDisplayState"
@@ -113,6 +113,10 @@ const ENGLISH_COPY: Record<string, string> = {
113
113
  "ota:versionChangeRestarting": "Installing a different version…",
114
114
  "ota:versionChangeVerifying": "Verifying your glasses…",
115
115
  "ota:versionChangeKeepNearby": "Keep your glasses nearby and connected. They will restart on their own.",
116
+ "ota:restartingGlasses": "Restarting {{deviceName}}…",
117
+ "ota:restartingGlassesMessage":
118
+ "The update is installed. Keep your glasses nearby and leave this screen open while they finish starting.",
119
+ "ota:restartingGlassesAutomatic": "We'll continue automatically when they're ready.",
116
120
  "ota:versionChangeComplete": "Version Change Complete",
117
121
  "ota:versionChangeCompleteMessage":
118
122
  "Your glasses are now on the required version. Their settings were reset and are being restored automatically.",
@@ -414,14 +418,11 @@ function OtaFlowContent({
414
418
 
415
419
  if (state.screen === "restarting") {
416
420
  return (
417
- <FlowPage
418
- actions={
419
- <FlowButton colors={colors} disabled={state.continueDisabled} label="Continue" onPress={controller.finish} />
420
- }
421
- colors={colors}
422
- icon="check"
423
- title="Update Installed"
424
- />
421
+ <FlowPage colors={colors} icon="download" title={translate("ota:restartingGlasses", {deviceName})}>
422
+ <ActivityIndicator size="large" color={colors.foreground} />
423
+ <BodyText colors={colors}>{translate("ota:restartingGlassesMessage")}</BodyText>
424
+ <BodyText colors={colors}>{translate("ota:restartingGlassesAutomatic")}</BodyText>
425
+ </FlowPage>
425
426
  )
426
427
  }
427
428
 
@@ -310,18 +310,18 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
310
310
 
311
311
  const performCheck = async () => {
312
312
  if (checkCompletedRef.current) return
313
+ if (isOtaAutoChainActive() && !otaSnapshot.ready) {
314
+ const remainingMs = otaAutoChainReconnectWaitRemaining()
315
+ if (remainingMs === null) return
316
+ reconnectTimeout = setTimeout(() => {
317
+ if (cancelled || myGeneration !== performCheckGenerationRef.current || ota.snapshot().ready) return
318
+ stopOtaAutoChain()
319
+ checkCompletedRef.current = true
320
+ setCheckState("error")
321
+ }, remainingMs)
322
+ return
323
+ }
313
324
  if (!otaSnapshot.connected) {
314
- if (isOtaAutoChainActive()) {
315
- const remainingMs = otaAutoChainReconnectWaitRemaining()
316
- if (remainingMs === null) return
317
- reconnectTimeout = setTimeout(() => {
318
- if (cancelled || myGeneration !== performCheckGenerationRef.current || ota.snapshot().connected) return
319
- stopOtaAutoChain()
320
- checkCompletedRef.current = true
321
- setCheckState("error")
322
- }, remainingMs)
323
- return
324
- }
325
325
  if (checkStartedRef.current) {
326
326
  stopOtaAutoChain()
327
327
  checkCompletedRef.current = true
@@ -430,7 +430,15 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
430
430
  cancelled = true
431
431
  if (reconnectTimeout) clearTimeout(reconnectTimeout)
432
432
  }
433
- }, [checkGeneration, continueApprovedChain, otaSnapshot.connected, otaSnapshot.wifiStatusKnown, page, runtimeReady])
433
+ }, [
434
+ checkGeneration,
435
+ continueApprovedChain,
436
+ otaSnapshot.connected,
437
+ otaSnapshot.ready,
438
+ otaSnapshot.wifiStatusKnown,
439
+ page,
440
+ runtimeReady,
441
+ ])
434
442
 
435
443
  useEffect(() => {
436
444
  if (
@@ -570,7 +578,11 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
570
578
  onFinishedRef.current?.()
571
579
  return
572
580
  }
573
- if (installSnapshot.displayState === "restarting") stopOtaAutoChain()
581
+ const restartStillInProgress =
582
+ installSnapshot.displayState === "restarting" ||
583
+ installSnapshot.versionChangePhase === "restarting" ||
584
+ installSnapshot.versionChangePhase === "verifying"
585
+ if (restartStillInProgress) return
574
586
  const requiresGlassesReboot = shouldRequireGlassesRebootForBesFailure(
575
587
  installSnapshot.otaStatus,
576
588
  installSnapshot.otaProgress,
@@ -748,7 +760,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
748
760
  error,
749
761
  canInstall: false,
750
762
  canRetry: screen === "failed" && !requiresGlassesReboot,
751
- canFinish: screen === "complete" || screen === "restarting" || (screen === "failed" && requiresGlassesReboot),
763
+ canFinish: screen === "complete" || (screen === "failed" && requiresGlassesReboot),
752
764
  canDismiss: false,
753
765
  canDiscard: screen === "disconnected",
754
766
  canOpenWifiSetup: screen === "failed" && showChangeWifi,
@@ -26,10 +26,12 @@ import type {OtaCheckCurrentGlassesResult} from "./OtaUpdateCheckService"
26
26
  import {deriveDisplayState, type DisplayState} from "./otaDisplayState"
27
27
  import {
28
28
  BES_CONTINUE_LOCKOUT_MS,
29
+ BES_RESTART_TIMEOUT_MS,
29
30
  DOWNLOAD_STUCK_TIMEOUT_MS,
30
31
  GLOBAL_OTA_TIMEOUT_MS,
31
32
  LEGACY_APK_COMPLETION_SETTLE_MS,
32
33
  LEGACY_BES_CONTINUE_LOCKOUT_MS,
34
+ LEGACY_BES_RESTART_TIMEOUT_MS,
33
35
  LEGACY_DOWNLOAD_STUCK_TIMEOUT_MS,
34
36
  LEGACY_GLOBAL_OTA_TIMEOUT_MS,
35
37
  LEGACY_MTK_INSTALL_TIMEOUT_MS,
@@ -245,6 +247,7 @@ class OtaInstallCoordinator {
245
247
  private pingInterval: ReturnType<typeof setInterval> | null = null
246
248
  private queryReplyTimeout: ReturnType<typeof setTimeout> | null = null
247
249
  private continueLockoutTimer: ReturnType<typeof setTimeout> | null = null
250
+ private restartRecoveryTimeout: ReturnType<typeof setTimeout> | null = null
248
251
  private legacyApkSettleTimer: ReturnType<typeof setTimeout> | null = null
249
252
  private mtkStallDetectTimer: ReturnType<typeof setTimeout> | null = null
250
253
  private mtkSimTickTimer: ReturnType<typeof setInterval> | null = null
@@ -1000,12 +1003,22 @@ class OtaInstallCoordinator {
1000
1003
  // (15s unified, 35s for legacy-shaped sessions — WP 8C-f).
1001
1004
  if (displayStateChanged && displayState === "restarting") {
1002
1005
  this.clearContinueLockoutTimer()
1006
+ this.clearRestartRecoveryTimeout()
1003
1007
  const lockoutMs = legacySession ? LEGACY_BES_CONTINUE_LOCKOUT_MS : BES_CONTINUE_LOCKOUT_MS
1008
+ const restartTimeoutMs = legacySession ? LEGACY_BES_RESTART_TIMEOUT_MS : BES_RESTART_TIMEOUT_MS
1004
1009
  this.setContinueButtonDisabled(true)
1005
1010
  this.continueLockoutTimer = setTimeout(() => {
1006
1011
  this.continueLockoutTimer = null
1007
1012
  this.setContinueButtonDisabled(false)
1008
1013
  }, lockoutMs)
1014
+ this.restartRecoveryTimeout = setTimeout(() => {
1015
+ this.restartRecoveryTimeout = null
1016
+ if (this.computeDisplayStateNow() !== "restarting") return
1017
+ console.log(`[OTA_PROGRESS] watchdog: glasses did not finish restarting in ${restartTimeoutMs}ms`)
1018
+ this.setErrorMsg(OtaProgressMessages.restartTimeout)
1019
+ }, restartTimeoutMs)
1020
+ } else if (displayStateChanged) {
1021
+ this.clearRestartRecoveryTimeout()
1009
1022
  }
1010
1023
 
1011
1024
  // Legacy MTK install stall simulation (WP 8C-e): display-only. The simulation
@@ -1668,6 +1681,13 @@ class OtaInstallCoordinator {
1668
1681
  }
1669
1682
  }
1670
1683
 
1684
+ private clearRestartRecoveryTimeout(): void {
1685
+ if (this.restartRecoveryTimeout) {
1686
+ clearTimeout(this.restartRecoveryTimeout)
1687
+ this.restartRecoveryTimeout = null
1688
+ }
1689
+ }
1690
+
1671
1691
  private clearLegacyApkSettleTimer(): void {
1672
1692
  if (this.legacyApkSettleTimer) {
1673
1693
  clearTimeout(this.legacyApkSettleTimer)
@@ -1706,6 +1726,7 @@ class OtaInstallCoordinator {
1706
1726
  this.clearPingInterval()
1707
1727
  this.clearLegacyApkSettleTimer()
1708
1728
  this.clearMtkSimulationTimers()
1729
+ this.clearRestartRecoveryTimeout()
1709
1730
  }
1710
1731
  }
1711
1732
 
@@ -32,6 +32,8 @@ export const PING_INTERVAL_MS = 10_000
32
32
  export const QUERY_REPLY_TIMEOUT_MS = 6000
33
33
  /** 15s lockout on the Continue button after a BES restart, to prevent an accidental tap. */
34
34
  export const BES_CONTINUE_LOCKOUT_MS = 15_000
35
+ /** Fail closed if a BES reboot never reconnects; normal restarts complete in a fraction of this window. */
36
+ export const BES_RESTART_TIMEOUT_MS = 120_000
35
37
 
36
38
  // --- Legacy old-build (< MINIMUM_OTA_STATUS_BUILD) OTA policy (WP 8C) ---
37
39
  //
@@ -51,6 +53,7 @@ export const LEGACY_MTK_INSTALL_TIMEOUT_MS = MTK_INSTALL_TIMEOUT_MS + LEGACY_EXT
51
53
  export const LEGACY_GLOBAL_OTA_TIMEOUT_MS = GLOBAL_OTA_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS
52
54
  export const LEGACY_PING_INTERVAL_MS = PING_INTERVAL_MS + LEGACY_EXTRA_TIMEOUT_MS
53
55
  export const LEGACY_BES_CONTINUE_LOCKOUT_MS = BES_CONTINUE_LOCKOUT_MS + LEGACY_EXTRA_TIMEOUT_MS
56
+ export const LEGACY_BES_RESTART_TIMEOUT_MS = BES_RESTART_TIMEOUT_MS + LEGACY_EXTRA_TIMEOUT_MS
54
57
  /**
55
58
  * After a legacy apk `install FINISHED` observed in-flight, the legacy screen held the
56
59
  * completed state for 12s (+20s pad) so the ASG process restart settles (and fresh
@@ -122,6 +125,7 @@ export function selectOtaProtocolProfile(
122
125
  export const OtaProgressMessages = {
123
126
  stalledOrStuck: "Update may have failed. Ensure glasses have internet access and try again.",
124
127
  globalTimeout: "Update took too long. Please try again.",
128
+ restartTimeout: "Glasses did not finish restarting. Restart them before trying the update again.",
125
129
  sendOtaStartFailed: "Failed to communicate with glasses.",
126
130
  hotspotArtifactDownloadFailed: "Could not download the update to your phone. Check phone internet and try again.",
127
131
  hotspotArtifactVerifyFailed: "The update package could not be verified. Please try again later.",