@mentra/engine 3.2.0-dev.200 → 3.2.0-dev.206

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 (31) hide show
  1. package/build/generated/releaseMetadata.js +5 -5
  2. package/build/generated/releaseMetadata.js.map +1 -1
  3. package/build/services/DeviceEventRouter.d.ts.map +1 -1
  4. package/build/services/DeviceEventRouter.js +3 -7
  5. package/build/services/DeviceEventRouter.js.map +1 -1
  6. package/build/services/PhoneStreamCoordinator.d.ts +7 -20
  7. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  8. package/build/services/PhoneStreamCoordinator.js +16 -99
  9. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  10. package/build/services/asg/galleryNotices.d.ts +1 -1
  11. package/build/services/asg/galleryNotices.d.ts.map +1 -1
  12. package/build/services/asg/galleryNotices.js.map +1 -1
  13. package/build/services/asg/gallerySyncService.d.ts +14 -0
  14. package/build/services/asg/gallerySyncService.d.ts.map +1 -1
  15. package/build/services/asg/gallerySyncService.js +232 -96
  16. package/build/services/asg/gallerySyncService.js.map +1 -1
  17. package/build/services/slimStreamStatus.d.ts.map +1 -1
  18. package/build/services/slimStreamStatus.js +16 -0
  19. package/build/services/slimStreamStatus.js.map +1 -1
  20. package/package.json +7 -7
  21. package/src/generated/releaseMetadata.ts +5 -5
  22. package/src/services/DeviceEventRouter.ts +5 -9
  23. package/src/services/PhoneStreamCoordinator.ts +23 -128
  24. package/src/services/asg/galleryNotices.ts +1 -0
  25. package/src/services/asg/gallerySyncService.ts +260 -112
  26. package/src/services/slimStreamStatus.ts +10 -1
  27. package/build/services/StreamLifecycleController.d.ts +0 -85
  28. package/build/services/StreamLifecycleController.d.ts.map +0 -1
  29. package/build/services/StreamLifecycleController.js +0 -173
  30. package/build/services/StreamLifecycleController.js.map +0 -1
  31. package/src/services/StreamLifecycleController.ts +0 -243
@@ -1,10 +1,10 @@
1
1
  export const ENGINE_RELEASE_METADATA = Object.freeze({
2
2
  "schemaVersion": 1,
3
3
  "familyBaseVersion": "3.2.0",
4
- "releaseIdentity": "3.2.0-dev.200",
5
- "releaseSetId": "mentra-3.2.0-dev.200",
6
- "sourceCommit": "1cdd911f596eba7b2b2f0e9ba5655c7ff16b5362",
7
- "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.200.json",
8
- "otaManifestSha256": "f0b5cca2fa4d1d43acf5933fb4de629819d36a145a162c763f4faa27a7c8ff79"
4
+ "releaseIdentity": "3.2.0-dev.206",
5
+ "releaseSetId": "mentra-3.2.0-dev.206",
6
+ "sourceCommit": "3c22b92a5a53ce608c087503417ca5b3d16cc926",
7
+ "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.206.json",
8
+ "otaManifestSha256": "7bc5e09f2757b2c11da58c93459bb0a554f406dd0ae5b50b58db28b64b92c838"
9
9
  });
10
10
  //# sourceMappingURL=releaseMetadata.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,sBAAsB;IACtC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,wHAAwH;IAC1I,mBAAmB,EAAE,kEAAkE;CACxF,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"3.2.0\",\n \"releaseIdentity\": \"3.2.0-dev.200\",\n \"releaseSetId\": \"mentra-3.2.0-dev.200\",\n \"sourceCommit\": \"1cdd911f596eba7b2b2f0e9ba5655c7ff16b5362\",\n \"otaManifestUrl\": \"https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.200.json\",\n \"otaManifestSha256\": \"f0b5cca2fa4d1d43acf5933fb4de629819d36a145a162c763f4faa27a7c8ff79\"\n})\n"]}
1
+ {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,sBAAsB;IACtC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,wHAAwH;IAC1I,mBAAmB,EAAE,kEAAkE;CACxF,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"3.2.0\",\n \"releaseIdentity\": \"3.2.0-dev.206\",\n \"releaseSetId\": \"mentra-3.2.0-dev.206\",\n \"sourceCommit\": \"3c22b92a5a53ce608c087503417ca5b3d16cc926\",\n \"otaManifestUrl\": \"https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.206.json\",\n \"otaManifestSha256\": \"7bc5e09f2757b2c11da58c93459bb0a554f406dd0ae5b50b58db28b64b92c838\"\n})\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"DeviceEventRouter.d.ts","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"AAmCA,wBAAgB,sBAAsB,IAAI,IAAI,CA8M7C;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C"}
1
+ {"version":3,"file":"DeviceEventRouter.d.ts","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"AAmCA,wBAAgB,sBAAsB,IAAI,IAAI,CA0M7C;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C"}
@@ -128,18 +128,14 @@ export function startDeviceEventRouter() {
128
128
  }
129
129
  }
130
130
  }));
131
- // Phone-owned stream status / keep-alive → the stream coordinator. The Cloud V1 relay
131
+ // Phone-owned stream status → the stream coordinator. The Cloud V1 relay
132
132
  // for non-owned streams was removed with Cloud V1 app end-of-life.
133
133
  subs.push(BluetoothSdk.addListener("stream_status", (event) => {
134
- if (event.streamId && phoneStreamCoordinator.owns(event.streamId)) {
134
+ if ((event.streamId && phoneStreamCoordinator.owns(event.streamId)) ||
135
+ (!event.streamId && event.kind === "snapshot" && event.terminal === true)) {
135
136
  phoneStreamCoordinator.handleGlassesStatus(event);
136
137
  }
137
138
  }));
138
- subs.push(BluetoothSdk.addListener("keep_alive_ack", (event) => {
139
- if (event.streamId && phoneStreamCoordinator.owns(event.streamId)) {
140
- phoneStreamCoordinator.handleKeepAliveAck(event);
141
- }
142
- }));
143
139
  // --- device input → local miniapps (forwardEvent is subscriber-gated; a no-op when
144
140
  // no miniapp listens). The v1 SocketComms cloud legs for these were removed with
145
141
  // the Cloud V1 ripout. ---
@@ -1 +1 @@
1
- {"version":3,"file":"DeviceEventRouter.js","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Device-event router — engine-owned. Subscribes to the inbound native BLE/device\n * events and routes them into the engine runtime: device stores, the process event\n * bus, the photo/stream coordinators, and local miniapps (via forwardEvent).\n *\n * Why this exists: engine owns the stores, coordinators, miniapp runtime, and facades,\n * but it never *subscribed to the device* for most events — the host MantleManager was\n * still the event router the whole runtime secretly depended on. So a bare OEM that\n * imported engine + called engine.start() got a connected runtime with almost no device\n * data flowing in (no miniapp input, dead gallery sync, starved coordinators). This\n * service moves those inbound bridges into engine so ANY host gets them.\n *\n * Scope: the engine legs only. The Cloud V1 relays that used to live beside them in\n * MantleManager (touch→cloud, the cloud-SDK stream/photo legs, etc.) were deleted with\n * Cloud V1 app end-of-life.\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport {shallow} from \"zustand/shallow\"\n\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport localSttFallbackCoordinator from \"./LocalSttFallbackCoordinator\"\nimport {phonePhotoCoordinator} from \"./PhonePhotoCoordinator\"\nimport {phoneStreamCoordinator} from \"./PhoneStreamCoordinator\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore} from \"../stores/settings\"\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {retirePendingSelectionOnPromotion} from \"./PairingIdentity\"\nimport GlobalEventEmitter from \"../utils/GlobalEventEmitter\"\nimport {asgCameraApi} from \"./asg/asgCameraApi\"\n\nlet subs: Array<{remove: () => void}> = []\n\nexport function startDeviceEventRouter(): void {\n if (subs.length) return\n\n // --- device state → engine stores ---\n\n // Standalone WiFi status → glasses store.\n subs.push(\n BluetoothSdk.addListener(\"wifi_status_change\", (event) => {\n const {type: _type, ...wifi} = event\n useGlassesStore.getState().setGlassesInfo({wifi})\n }),\n )\n\n // Forward glasses Wi-Fi to miniapps (session.glasses.onWifi) from the STORE — the\n // single source of truth — so every path converges here: wifi_status_change,\n // onGlassesStatus, and BLE disconnect. Effective connectivity requires the glasses\n // to be connected AND on Wi-Fi, so a disconnect correctly flips `connected` false.\n // `linkConnected` carries the BLE leg on its own so a miniapp can tell \"glasses\n // out of Bluetooth range\" from \"glasses on Bluetooth but off Wi-Fi\" — the two\n // need opposite recoveries (wait vs. open Wi-Fi setup).\n subs.push({\n remove: useGlassesStore.subscribe(\n (s) => {\n const linkConnected = isGlassesConnected(s.connection)\n const connected = linkConnected && s.wifi.state === \"connected\"\n // ssid/localIp describe how to reach the glasses over Wi-Fi, so they are\n // only meaningful under effective connectivity. Gate them on `connected`\n // (not raw wifi.state) so a BLE drop with a stale \"connected\" Wi-Fi leg\n // never hands miniapps an unreachable address.\n return {\n connected,\n linkConnected,\n ssid: linkConnected && s.wifi.state === \"connected\" ? s.wifi.ssid : undefined,\n localIp: linkConnected && s.wifi.state === \"connected\" ? s.wifi.localIp : undefined,\n }\n },\n (wifi) => localMiniappRuntime.forwardEvent(\"glasses_wifi\", wifi),\n {equalityFn: shallow},\n ),\n })\n\n // Incremental battery status → glasses store + local miniapps.\n subs.push(\n BluetoothSdk.addListener(\"battery_status\", (event) => {\n const state = useGlassesStore.getState()\n state.setBatteryInfo(event.level, event.charging, state.caseBatteryLevel, state.caseCharging)\n localMiniappRuntime.forwardEvent(\"glasses_battery_update\", {\n type: \"glasses_battery_update\",\n level: event.level,\n charging: event.charging,\n timestamp: event.timestamp ?? Date.now(),\n })\n }),\n )\n\n // Hotspot status → glasses store + event bus. engine's own gallerySyncService listens\n // on the bus for these, so without this bridge engine's gallery sync is dead.\n subs.push(\n BluetoothSdk.addListener(\"hotspot_status_change\", (event) => {\n const enabled = event.state === \"enabled\"\n const ssid = enabled ? event.ssid : \"\"\n const password = enabled ? event.password : \"\"\n const localIp = enabled ? event.localIp : \"\"\n useGlassesStore.getState().setHotspotInfo(enabled, ssid, password, localIp)\n // Always retarget the ASG camera client: on disable (or an event with no\n // IP) fall back to the localhost default so later calls fail fast instead\n // of hitting the previous network's stale glasses IP.\n asgCameraApi.setServer(localIp || \"localhost\", 8089)\n GlobalEventEmitter.emit(\"hotspot_status_change\", {enabled, ssid, password, local_ip: localIp})\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"hotspot_error\", (event) => {\n GlobalEventEmitter.emit(\"hotspot_error\", {error_message: event.errorMessage, timestamp: event.timestamp})\n }),\n )\n\n // Glasses gallery content counts → event bus (consumed by gallerySyncService).\n subs.push(\n BluetoothSdk.addListener(\"gallery_status\", (event) => {\n GlobalEventEmitter.emit(\"gallery_status\", {\n photos: event.photos,\n videos: event.videos,\n total: event.total,\n has_content: event.hasContent,\n camera_busy: event.cameraBusy,\n })\n }),\n )\n\n // Hardware-originated setting changes (user changes a setting ON the glasses) → store.\n // The inbound complement to GlassesSettingsSync (which only pushes store→device).\n subs.push(\n BluetoothSdk.addListener(\"save_setting\", async (event) => {\n const settings = useSettingsStore.getState()\n // Damp the relay: native re-echoes some settings unconditionally (e.g.\n // the identity block at every handleDeviceReady) — a same-value echo\n // must not become a store write (persistence churn + change-push noise).\n if (settings.getSetting(event.key) !== event.value) {\n await settings.setSetting(event.key, event.value)\n }\n // Two-phase identity: a promoted default retires the pending selection\n // marker. The rule lives with PairingIdentity (a no-op for other keys).\n await retirePendingSelectionOnPromotion(event.key, event.value)\n }),\n )\n\n // --- coordinators (owns()-gated) ---\n\n // Phone-owned photo errors settle the in-flight long-poll fast (vs. timeout).\n // Non-owned photo responses used to forward to the Cloud V1 photo pipeline via\n // restComms; that relay was removed with Cloud V1 app end-of-life, so they drop.\n // Wire v2 sends a short 4-hex BLE requestId; the coordinator maps it back to\n // the cloud requestId (resolveCloudRequestId) before ownership checks.\n subs.push(\n BluetoothSdk.addListener(\"photo_response\", (event) => {\n const cloudRequestId = event.requestId ? phonePhotoCoordinator.resolveCloudRequestId(event.requestId) : \"\"\n if (cloudRequestId && phonePhotoCoordinator.owns(cloudRequestId)) {\n if (event.state === \"error\") {\n phonePhotoCoordinator.handlePhotoError(\n event.requestId ?? cloudRequestId,\n event.errorCode ?? \"GLASSES_ERROR\",\n event.errorMessage ?? \"Glasses reported an error\",\n )\n }\n }\n }),\n )\n\n // Phone-owned stream status / keep-alive → the stream coordinator. The Cloud V1 relay\n // for non-owned streams was removed with Cloud V1 app end-of-life.\n subs.push(\n BluetoothSdk.addListener(\"stream_status\", (event) => {\n if (event.streamId && phoneStreamCoordinator.owns(event.streamId)) {\n phoneStreamCoordinator.handleGlassesStatus(event)\n }\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"keep_alive_ack\", (event) => {\n if (event.streamId && phoneStreamCoordinator.owns(event.streamId)) {\n phoneStreamCoordinator.handleKeepAliveAck(event)\n }\n }),\n )\n\n // --- device input → local miniapps (forwardEvent is subscriber-gated; a no-op when\n // no miniapp listens). The v1 SocketComms cloud legs for these were removed with\n // the Cloud V1 ripout. ---\n\n subs.push(\n BluetoothSdk.addListener(\"button_press\", (event) => {\n localMiniappRuntime.forwardEvent(\"button_press\", event)\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"touch_event\", (event) => {\n localMiniappRuntime.forwardEvent(\"touch_event\", event)\n }),\n )\n // G2 IMU accelerometer — payload already matches the miniapp AccelData shape.\n subs.push(\n BluetoothSdk.addListener(\"accel_event\", (event) => {\n localMiniappRuntime.forwardEvent(\"accel_event\", {\n x: event.x,\n y: event.y,\n z: event.z,\n timestamp: typeof event.timestamp === \"number\" ? event.timestamp : Date.now(),\n })\n }),\n )\n // Head position — translate native {up:boolean} → SDK {position:\"up\"|\"down\"}.\n subs.push(\n BluetoothSdk.addListener(\"head_up\", (event) => {\n localMiniappRuntime.forwardEvent(\"head_up\", {position: event.up ? \"up\" : \"down\", timestamp: Date.now()})\n }),\n )\n // On-device STT transcripts → local miniapps only while the local engine is\n // active (cloud fallback or an explicit forceLocal subscription). The runtime\n // filters mixed local/cloud delivery per miniapp to avoid double-delivery.\n // forwardEvent is subscriber-gated — a no-op when no miniapp listens.\n // (Was MantleManager.handle_local_transcription; its offline-captions display branch\n // went away with the pseudo captions renderer.)\n subs.push(\n BluetoothSdk.addListener(\"local_transcription\", (event) => {\n if (!localSttFallbackCoordinator.isActive()) return\n const lang = event.transcribeLanguage ?? localSttFallbackCoordinator.getActiveLanguage() ?? \"en-US\"\n localMiniappRuntime.forwardEvent(`transcription:${lang}`, event, \"local\")\n }),\n )\n\n // --- glasses swipe-menu app launcher (G2) ---\n subs.push(\n BluetoothSdk.addListener(\"miniapp_selected\", (event) => {\n const packageName = event.packageName as string\n if (!packageName) return\n const app = useAppStatusStore.getState().apps.find((a) => a.packageName === packageName)\n if (!app) return\n // Toggle: stop if running, else start.\n if (app.running) {\n useAppStatusStore.getState().stop(packageName)\n } else {\n useAppStatusStore.getState().start(app, {skipNavigation: true})\n }\n }),\n )\n}\n\nexport function stopDeviceEventRouter(): void {\n for (const sub of subs) sub.remove()\n subs = []\n}\n"]}
1
+ {"version":3,"file":"DeviceEventRouter.js","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Device-event router — engine-owned. Subscribes to the inbound native BLE/device\n * events and routes them into the engine runtime: device stores, the process event\n * bus, the photo/stream coordinators, and local miniapps (via forwardEvent).\n *\n * Why this exists: engine owns the stores, coordinators, miniapp runtime, and facades,\n * but it never *subscribed to the device* for most events — the host MantleManager was\n * still the event router the whole runtime secretly depended on. So a bare OEM that\n * imported engine + called engine.start() got a connected runtime with almost no device\n * data flowing in (no miniapp input, dead gallery sync, starved coordinators). This\n * service moves those inbound bridges into engine so ANY host gets them.\n *\n * Scope: the engine legs only. The Cloud V1 relays that used to live beside them in\n * MantleManager (touch→cloud, the cloud-SDK stream/photo legs, etc.) were deleted with\n * Cloud V1 app end-of-life.\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport {shallow} from \"zustand/shallow\"\n\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport localSttFallbackCoordinator from \"./LocalSttFallbackCoordinator\"\nimport {phonePhotoCoordinator} from \"./PhonePhotoCoordinator\"\nimport {phoneStreamCoordinator} from \"./PhoneStreamCoordinator\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore} from \"../stores/settings\"\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {retirePendingSelectionOnPromotion} from \"./PairingIdentity\"\nimport GlobalEventEmitter from \"../utils/GlobalEventEmitter\"\nimport {asgCameraApi} from \"./asg/asgCameraApi\"\n\nlet subs: Array<{remove: () => void}> = []\n\nexport function startDeviceEventRouter(): void {\n if (subs.length) return\n\n // --- device state → engine stores ---\n\n // Standalone WiFi status → glasses store.\n subs.push(\n BluetoothSdk.addListener(\"wifi_status_change\", (event) => {\n const {type: _type, ...wifi} = event\n useGlassesStore.getState().setGlassesInfo({wifi})\n }),\n )\n\n // Forward glasses Wi-Fi to miniapps (session.glasses.onWifi) from the STORE — the\n // single source of truth — so every path converges here: wifi_status_change,\n // onGlassesStatus, and BLE disconnect. Effective connectivity requires the glasses\n // to be connected AND on Wi-Fi, so a disconnect correctly flips `connected` false.\n // `linkConnected` carries the BLE leg on its own so a miniapp can tell \"glasses\n // out of Bluetooth range\" from \"glasses on Bluetooth but off Wi-Fi\" — the two\n // need opposite recoveries (wait vs. open Wi-Fi setup).\n subs.push({\n remove: useGlassesStore.subscribe(\n (s) => {\n const linkConnected = isGlassesConnected(s.connection)\n const connected = linkConnected && s.wifi.state === \"connected\"\n // ssid/localIp describe how to reach the glasses over Wi-Fi, so they are\n // only meaningful under effective connectivity. Gate them on `connected`\n // (not raw wifi.state) so a BLE drop with a stale \"connected\" Wi-Fi leg\n // never hands miniapps an unreachable address.\n return {\n connected,\n linkConnected,\n ssid: linkConnected && s.wifi.state === \"connected\" ? s.wifi.ssid : undefined,\n localIp: linkConnected && s.wifi.state === \"connected\" ? s.wifi.localIp : undefined,\n }\n },\n (wifi) => localMiniappRuntime.forwardEvent(\"glasses_wifi\", wifi),\n {equalityFn: shallow},\n ),\n })\n\n // Incremental battery status → glasses store + local miniapps.\n subs.push(\n BluetoothSdk.addListener(\"battery_status\", (event) => {\n const state = useGlassesStore.getState()\n state.setBatteryInfo(event.level, event.charging, state.caseBatteryLevel, state.caseCharging)\n localMiniappRuntime.forwardEvent(\"glasses_battery_update\", {\n type: \"glasses_battery_update\",\n level: event.level,\n charging: event.charging,\n timestamp: event.timestamp ?? Date.now(),\n })\n }),\n )\n\n // Hotspot status → glasses store + event bus. engine's own gallerySyncService listens\n // on the bus for these, so without this bridge engine's gallery sync is dead.\n subs.push(\n BluetoothSdk.addListener(\"hotspot_status_change\", (event) => {\n const enabled = event.state === \"enabled\"\n const ssid = enabled ? event.ssid : \"\"\n const password = enabled ? event.password : \"\"\n const localIp = enabled ? event.localIp : \"\"\n useGlassesStore.getState().setHotspotInfo(enabled, ssid, password, localIp)\n // Always retarget the ASG camera client: on disable (or an event with no\n // IP) fall back to the localhost default so later calls fail fast instead\n // of hitting the previous network's stale glasses IP.\n asgCameraApi.setServer(localIp || \"localhost\", 8089)\n GlobalEventEmitter.emit(\"hotspot_status_change\", {enabled, ssid, password, local_ip: localIp})\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"hotspot_error\", (event) => {\n GlobalEventEmitter.emit(\"hotspot_error\", {error_message: event.errorMessage, timestamp: event.timestamp})\n }),\n )\n\n // Glasses gallery content counts → event bus (consumed by gallerySyncService).\n subs.push(\n BluetoothSdk.addListener(\"gallery_status\", (event) => {\n GlobalEventEmitter.emit(\"gallery_status\", {\n photos: event.photos,\n videos: event.videos,\n total: event.total,\n has_content: event.hasContent,\n camera_busy: event.cameraBusy,\n })\n }),\n )\n\n // Hardware-originated setting changes (user changes a setting ON the glasses) → store.\n // The inbound complement to GlassesSettingsSync (which only pushes store→device).\n subs.push(\n BluetoothSdk.addListener(\"save_setting\", async (event) => {\n const settings = useSettingsStore.getState()\n // Damp the relay: native re-echoes some settings unconditionally (e.g.\n // the identity block at every handleDeviceReady) — a same-value echo\n // must not become a store write (persistence churn + change-push noise).\n if (settings.getSetting(event.key) !== event.value) {\n await settings.setSetting(event.key, event.value)\n }\n // Two-phase identity: a promoted default retires the pending selection\n // marker. The rule lives with PairingIdentity (a no-op for other keys).\n await retirePendingSelectionOnPromotion(event.key, event.value)\n }),\n )\n\n // --- coordinators (owns()-gated) ---\n\n // Phone-owned photo errors settle the in-flight long-poll fast (vs. timeout).\n // Non-owned photo responses used to forward to the Cloud V1 photo pipeline via\n // restComms; that relay was removed with Cloud V1 app end-of-life, so they drop.\n // Wire v2 sends a short 4-hex BLE requestId; the coordinator maps it back to\n // the cloud requestId (resolveCloudRequestId) before ownership checks.\n subs.push(\n BluetoothSdk.addListener(\"photo_response\", (event) => {\n const cloudRequestId = event.requestId ? phonePhotoCoordinator.resolveCloudRequestId(event.requestId) : \"\"\n if (cloudRequestId && phonePhotoCoordinator.owns(cloudRequestId)) {\n if (event.state === \"error\") {\n phonePhotoCoordinator.handlePhotoError(\n event.requestId ?? cloudRequestId,\n event.errorCode ?? \"GLASSES_ERROR\",\n event.errorMessage ?? \"Glasses reported an error\",\n )\n }\n }\n }),\n )\n\n // Phone-owned stream status → the stream coordinator. The Cloud V1 relay\n // for non-owned streams was removed with Cloud V1 app end-of-life.\n subs.push(\n BluetoothSdk.addListener(\"stream_status\", (event) => {\n if (\n (event.streamId && phoneStreamCoordinator.owns(event.streamId)) ||\n (!event.streamId && event.kind === \"snapshot\" && event.terminal === true)\n ) {\n phoneStreamCoordinator.handleGlassesStatus(event)\n }\n }),\n )\n\n // --- device input → local miniapps (forwardEvent is subscriber-gated; a no-op when\n // no miniapp listens). The v1 SocketComms cloud legs for these were removed with\n // the Cloud V1 ripout. ---\n\n subs.push(\n BluetoothSdk.addListener(\"button_press\", (event) => {\n localMiniappRuntime.forwardEvent(\"button_press\", event)\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"touch_event\", (event) => {\n localMiniappRuntime.forwardEvent(\"touch_event\", event)\n }),\n )\n // G2 IMU accelerometer — payload already matches the miniapp AccelData shape.\n subs.push(\n BluetoothSdk.addListener(\"accel_event\", (event) => {\n localMiniappRuntime.forwardEvent(\"accel_event\", {\n x: event.x,\n y: event.y,\n z: event.z,\n timestamp: typeof event.timestamp === \"number\" ? event.timestamp : Date.now(),\n })\n }),\n )\n // Head position — translate native {up:boolean} → SDK {position:\"up\"|\"down\"}.\n subs.push(\n BluetoothSdk.addListener(\"head_up\", (event) => {\n localMiniappRuntime.forwardEvent(\"head_up\", {position: event.up ? \"up\" : \"down\", timestamp: Date.now()})\n }),\n )\n // On-device STT transcripts → local miniapps only while the local engine is\n // active (cloud fallback or an explicit forceLocal subscription). The runtime\n // filters mixed local/cloud delivery per miniapp to avoid double-delivery.\n // forwardEvent is subscriber-gated — a no-op when no miniapp listens.\n // (Was MantleManager.handle_local_transcription; its offline-captions display branch\n // went away with the pseudo captions renderer.)\n subs.push(\n BluetoothSdk.addListener(\"local_transcription\", (event) => {\n if (!localSttFallbackCoordinator.isActive()) return\n const lang = event.transcribeLanguage ?? localSttFallbackCoordinator.getActiveLanguage() ?? \"en-US\"\n localMiniappRuntime.forwardEvent(`transcription:${lang}`, event, \"local\")\n }),\n )\n\n // --- glasses swipe-menu app launcher (G2) ---\n subs.push(\n BluetoothSdk.addListener(\"miniapp_selected\", (event) => {\n const packageName = event.packageName as string\n if (!packageName) return\n const app = useAppStatusStore.getState().apps.find((a) => a.packageName === packageName)\n if (!app) return\n // Toggle: stop if running, else start.\n if (app.running) {\n useAppStatusStore.getState().stop(packageName)\n } else {\n useAppStatusStore.getState().start(app, {skipNavigation: true})\n }\n }),\n )\n}\n\nexport function stopDeviceEventRouter(): void {\n for (const sub of subs) sub.remove()\n subs = []\n}\n"]}
@@ -5,7 +5,6 @@
5
5
  * miniapp → SDK → LocalMiniappRuntime → coordinator
6
6
  * coordinator → BluetoothSdk (BLE → glasses publisher)
7
7
  * coordinator ↔ cloudStreamApi (managed only, cloud-v2 runtime provisioning)
8
- * coordinator ↔ StreamLifecycleController (keep-alive heartbeat)
9
8
  * coordinator → status listeners → routed back to miniapp(s)
10
9
  *
11
10
  * Single-stream constraint:
@@ -28,33 +27,24 @@
28
27
  * the legacy path.
29
28
  *
30
29
  * BLE link loss is a SUSPENDED state, not a failure:
31
- * The glasses publisher keeps pushing over Wi-Fi when the phone's Bluetooth
32
- * link drops; only our keep-alives stop reaching it. While the store says
33
- * the glasses are disconnected we pause the heartbeat (so we don't count
34
- * misses we caused ourselves), fan out `suspended`, and give the link
35
- * `glassesGraceMs` to come back. On reconnect we resume, heartbeat at once,
36
- * and fan out `resumed` with the SAME streamId — no re-provision. The grace
37
- * ends early when Cloudflare reports the publisher gone for two probes in
38
- * a row (glasses powered off, not a BLE hiccup). If the grace expires the
39
- * stream is torn down with reason `glasses_disconnected`, and the BLE stop
40
- * the glasses never received is sent on the next reconnect.
30
+ * ASG owns the ten-second phone/controller liveness deadline. The coordinator
31
+ * observes link suspension, retains cloud resources briefly for reconciliation,
32
+ * and never uses missing JavaScript heartbeats as evidence of publisher failure.
33
+ * An explicit stop that could not reach BLE is sent on the next reconnect.
41
34
  */
42
- import type { KeepAliveAckEvent, StreamResolvedConfig, StreamStartRequest, StreamStatusEvent } from "@mentra/bluetooth-sdk/internal";
35
+ import type { StreamResolvedConfig, StreamStartRequest, StreamStatusEvent } from "@mentra/bluetooth-sdk/internal";
43
36
  import { type RestreamDestinationInput } from "./cloudStreamApi";
44
37
  /**
45
38
  * Default cadence + thresholds. Exposed via {@link CoordinatorTimings} so tests
46
39
  * can shorten them; production code should never override these.
47
40
  */
48
41
  declare const DEFAULT_TIMINGS: {
49
- readonly keepAliveIntervalMs: 15000;
50
- readonly ackTimeoutMs: 10000;
51
- readonly maxMissedAcks: 3;
52
42
  readonly cloudflareStatusPollMs: 5000;
53
43
  readonly cloudflareStartupPollInitialMs: 500;
54
44
  readonly hlsReadinessInitialDelayMs: 5000;
55
45
  readonly hlsReadinessPollMs: 2000;
56
46
  readonly hlsReadinessMaxAttempts: 30;
57
- readonly glassesGraceMs: 45000;
47
+ readonly glassesGraceMs: 15000;
58
48
  readonly suspendedPublisherGoneProbes: 2;
59
49
  };
60
50
  type TimingConfig = {
@@ -140,7 +130,6 @@ export declare class StreamConflictError extends Error {
140
130
  }
141
131
  export declare class PhoneStreamCoordinator {
142
132
  private current;
143
- private lifecycle;
144
133
  private statusSubscriber;
145
134
  private idCounter;
146
135
  private readonly timings;
@@ -206,8 +195,6 @@ export declare class PhoneStreamCoordinator {
206
195
  * and the registry says it's phone-owned.
207
196
  */
208
197
  handleGlassesStatus(event: StreamStatusEvent): void;
209
- /** Called by MantleManager when a phone-owned keep_alive_ack arrives. */
210
- handleKeepAliveAck(event: KeepAliveAckEvent): void;
211
198
  private attachLink;
212
199
  private detachLinkIfIdle;
213
200
  private handleLinkChange;
@@ -222,7 +209,7 @@ export declare class PhoneStreamCoordinator {
222
209
  private failSuspended;
223
210
  private flushPendingBleStop;
224
211
  private mintId;
225
- /** BLE keep-alives. StreamLifecycleController uses BgTimer so they survive MentraOS backgrounding. */
212
+ /** Observe link/status; native SDK and ASG own controller liveness. */
226
213
  private startLifecycle;
227
214
  private startCloudflareStatusPoll;
228
215
  private startHlsReadinessPoll;
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneStreamCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/PhoneStreamCoordinator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PhoneStreamCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/PhoneStreamCoordinator.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,6 @@
5
5
  * miniapp → SDK → LocalMiniappRuntime → coordinator
6
6
  * coordinator → BluetoothSdk (BLE → glasses publisher)
7
7
  * coordinator ↔ cloudStreamApi (managed only, cloud-v2 runtime provisioning)
8
- * coordinator ↔ StreamLifecycleController (keep-alive heartbeat)
9
8
  * coordinator → status listeners → routed back to miniapp(s)
10
9
  *
11
10
  * Single-stream constraint:
@@ -28,21 +27,14 @@
28
27
  * the legacy path.
29
28
  *
30
29
  * BLE link loss is a SUSPENDED state, not a failure:
31
- * The glasses publisher keeps pushing over Wi-Fi when the phone's Bluetooth
32
- * link drops; only our keep-alives stop reaching it. While the store says
33
- * the glasses are disconnected we pause the heartbeat (so we don't count
34
- * misses we caused ourselves), fan out `suspended`, and give the link
35
- * `glassesGraceMs` to come back. On reconnect we resume, heartbeat at once,
36
- * and fan out `resumed` with the SAME streamId — no re-provision. The grace
37
- * ends early when Cloudflare reports the publisher gone for two probes in
38
- * a row (glasses powered off, not a BLE hiccup). If the grace expires the
39
- * stream is torn down with reason `glasses_disconnected`, and the BLE stop
40
- * the glasses never received is sent on the next reconnect.
30
+ * ASG owns the ten-second phone/controller liveness deadline. The coordinator
31
+ * observes link suspension, retains cloud resources briefly for reconciliation,
32
+ * and never uses missing JavaScript heartbeats as evidence of publisher failure.
33
+ * An explicit stop that could not reach BLE is sent on the next reconnect.
41
34
  */
42
35
  import BluetoothSdk from "@mentra/bluetooth-sdk/internal";
43
36
  import { isGlassesConnected } from "./GlassesReadiness";
44
37
  import { useGlassesStore } from "../stores/glasses";
45
- import { StreamLifecycleController } from "./StreamLifecycleController";
46
38
  import { slimStreamStatusEvent, streamStatusSignature } from "./slimStreamStatus";
47
39
  import { getManagedStreamStatus, provisionManagedStream, teardownManagedStream, } from "./cloudStreamApi";
48
40
  /**
@@ -50,9 +42,6 @@ import { getManagedStreamStatus, provisionManagedStream, teardownManagedStream,
50
42
  * can shorten them; production code should never override these.
51
43
  */
52
44
  const DEFAULT_TIMINGS = {
53
- keepAliveIntervalMs: 15_000,
54
- ackTimeoutMs: 10_000,
55
- maxMissedAcks: 3,
56
45
  cloudflareStatusPollMs: 5_000,
57
46
  // During WHIP startup, probe quickly so readiness is not quantized to the
58
47
  // steady-state 5s monitoring cadence. The delay backs off on each miss.
@@ -61,10 +50,8 @@ const DEFAULT_TIMINGS = {
61
50
  hlsReadinessInitialDelayMs: 5_000,
62
51
  hlsReadinessPollMs: 2_000,
63
52
  hlsReadinessMaxAttempts: 30,
64
- // How long a stream survives a BLE link drop before we give up on it. Must
65
- // stay under the glasses publisher's own 60s no-keep-alive watchdog with
66
- // margin for the resume heartbeat's round trip.
67
- glassesGraceMs: 45_000,
53
+ // Allow ASG's ten-second stop plus a short delivery/reconciliation margin.
54
+ glassesGraceMs: 15_000,
68
55
  // Consecutive Cloudflare "publisher disconnected" probes while suspended
69
56
  // before we conclude the glasses are off (not just out of BLE range).
70
57
  suspendedPublisherGoneProbes: 2,
@@ -79,18 +66,6 @@ export const LINK_STATUS = {
79
66
  resumed: "resumed",
80
67
  reason: "glasses_disconnected",
81
68
  };
82
- // Console-backed minimal logger; replaces pino on the phone.
83
- const consoleLogger = {
84
- child: (bindings) => ({
85
- ...consoleLogger,
86
- debug: (...args) => console.debug("[STREAM]", bindings, ...args),
87
- warn: (...args) => console.warn("[STREAM]", bindings, ...args),
88
- error: (...args) => console.error("[STREAM]", bindings, ...args),
89
- }),
90
- debug: (...args) => console.debug("[STREAM]", ...args),
91
- warn: (...args) => console.warn("[STREAM]", ...args),
92
- error: (...args) => console.error("[STREAM]", ...args),
93
- };
94
69
  export class StreamConflictError extends Error {
95
70
  code;
96
71
  stage;
@@ -109,7 +84,6 @@ export class StreamConflictError extends Error {
109
84
  }
110
85
  export class PhoneStreamCoordinator {
111
86
  current = null;
112
- lifecycle = null;
113
87
  statusSubscriber = null;
114
88
  idCounter = 0;
115
89
  timings;
@@ -235,7 +209,7 @@ export class PhoneStreamCoordinator {
235
209
  // the lock sees the in-progress entry and rejects with the conflict.
236
210
  this.current = entry;
237
211
  try {
238
- const event = await BluetoothSdk.startExternallyManagedStream({
212
+ const event = await BluetoothSdk.startStream({
239
213
  type: "start_stream",
240
214
  streamUrl: opts.streamUrl,
241
215
  streamId,
@@ -285,9 +259,7 @@ export class PhoneStreamCoordinator {
285
259
  throw new StreamConflictError("STREAM_DESTINATIONS_LOCKED", "Restream destinations cannot be modified on an already-running managed stream.");
286
260
  }
287
261
  existing.subscribers.add(packageName);
288
- const immediate = existing.hlsReady
289
- ? managedStartResult(existing)
290
- : null;
262
+ const immediate = existing.hlsReady ? managedStartResult(existing) : null;
291
263
  return { kind: "join", entry: existing, immediate };
292
264
  }
293
265
  // Fresh provision — claim slot BEFORE awaiting Cloudflare so a
@@ -322,7 +294,7 @@ export class PhoneStreamCoordinator {
322
294
  elapsedMs: Date.now() - startupStartedAtMs,
323
295
  });
324
296
  try {
325
- const event = await BluetoothSdk.startExternallyManagedStream({
297
+ const event = await BluetoothSdk.startStream({
326
298
  type: "start_stream",
327
299
  streamUrl: ingestUrl,
328
300
  streamId,
@@ -403,7 +375,6 @@ export class PhoneStreamCoordinator {
403
375
  return;
404
376
  if (event.streamId && event.streamId !== this.current.streamId)
405
377
  return;
406
- this.lifecycle?.recordActivity();
407
378
  const includeResolvedConfig = !this.resolvedConfigForwarded && !!event.resolvedConfig;
408
379
  if (includeResolvedConfig)
409
380
  this.resolvedConfigForwarded = true;
@@ -423,14 +394,13 @@ export class PhoneStreamCoordinator {
423
394
  // glasses publisher auto-recovers (error → reconnecting → reconnected),
424
395
  // and tearing down on the first hiccup deletes the live input out from
425
396
  // under a publisher that comes right back (it then retries into a dead
426
- // input forever). A publisher that errors and never recovers is reaped by
427
- // the keep-alive ack timeout. Queue the teardown through the transition
428
- // lock so it serializes with any start/stop currently in flight.
397
+ // input forever). ASG explicitly marks terminal failures; serialize teardown
398
+ // with any start/stop currently in flight.
429
399
  const isStopped = (event.kind === "lifecycle" && event.status === "stopped") ||
430
400
  (event.kind === "snapshot" && event.status === "stopped");
431
401
  const isGiveUp = event.kind === "reconnect" && event.status === "reconnect_failed";
432
- if (isGiveUp || isStopped) {
433
- const reason = isGiveUp ? "glasses_gave_up" : "glasses_stopped";
402
+ if (event.terminal === true || isGiveUp || isStopped) {
403
+ const reason = isGiveUp ? "glasses_gave_up" : event.status === "error" ? "glasses_error" : "glasses_stopped";
434
404
  const targetStreamId = this.current.streamId;
435
405
  void this.runExclusive(async () => {
436
406
  // The stream we wanted to tear down may already be gone (e.g. another
@@ -441,12 +411,6 @@ export class PhoneStreamCoordinator {
441
411
  });
442
412
  }
443
413
  }
444
- /** Called by MantleManager when a phone-owned keep_alive_ack arrives. */
445
- handleKeepAliveAck(event) {
446
- if (!event.ackId)
447
- return;
448
- this.lifecycle?.handleAck(event.ackId);
449
- }
450
414
  // ===========================================================================
451
415
  // BLE link suspension
452
416
  // ===========================================================================
@@ -479,9 +443,6 @@ export class PhoneStreamCoordinator {
479
443
  if (!entry)
480
444
  return;
481
445
  const since = Date.now();
482
- // Pause the heartbeat: misses now would be our own fault, not the
483
- // publisher's, and counting them would reap a stream that is still live.
484
- this.lifecycle?.setActive(false);
485
446
  const graceTimer = setTimeout(() => this.onGraceExpired(entry.streamId), this.timings.glassesGraceMs);
486
447
  this.suspended = { since, graceTimer, publisherGoneProbes: 0 };
487
448
  console.warn("[STREAM] BLE link lost; stream suspended", {
@@ -504,10 +465,6 @@ export class PhoneStreamCoordinator {
504
465
  this.suspended = null;
505
466
  const suspendedMs = Date.now() - suspended.since;
506
467
  console.info("[STREAM] BLE link back; stream resumed", { streamId: entry.streamId, suspendedMs });
507
- // Resume and heartbeat immediately: the glasses watchdog has been running
508
- // the whole time, so the first keep-alive must not wait another interval.
509
- this.lifecycle?.setActive(true);
510
- this.lifecycle?.tickNow();
511
468
  // A resumed session is a fresh status baseline for subscribers.
512
469
  this.lastFanoutSignature = null;
513
470
  this.fanout({
@@ -557,43 +514,10 @@ export class PhoneStreamCoordinator {
557
514
  this.idCounter += 1;
558
515
  return `phone-${prefix}-${Date.now().toString(36)}-${this.idCounter}`;
559
516
  }
560
- /** BLE keep-alives. StreamLifecycleController uses BgTimer so they survive MentraOS backgrounding. */
561
- startLifecycle(streamId) {
562
- // A new stream supersedes any deferred stop for the previous one.
517
+ /** Observe link/status; native SDK and ASG own controller liveness. */
518
+ startLifecycle(_streamId) {
563
519
  this.pendingBleStop = null;
564
520
  this.attachLink();
565
- this.lifecycle?.dispose();
566
- const ctrl = new StreamLifecycleController({
567
- logger: consoleLogger,
568
- streamId,
569
- keepAliveIntervalMs: this.timings.keepAliveIntervalMs,
570
- ackTimeoutMs: this.timings.ackTimeoutMs,
571
- maxMissedAcks: this.timings.maxMissedAcks,
572
- }, {
573
- sendKeepAlive: async (ackId) => {
574
- await BluetoothSdk.sendExternallyManagedStreamKeepAlive({
575
- type: "keep_stream_alive",
576
- streamId,
577
- ackId,
578
- });
579
- },
580
- onTimeout: async () => {
581
- this.fanout({
582
- streamId,
583
- source: "coordinator",
584
- status: "error",
585
- data: { reason: "keep_alive_timeout" },
586
- });
587
- await this.runExclusive(async () => {
588
- // The stream this timeout was bound to may already be gone.
589
- if (this.current?.streamId !== streamId)
590
- return;
591
- await this.teardownLocked("keep_alive_timeout");
592
- });
593
- },
594
- });
595
- ctrl.setActive(true);
596
- this.lifecycle = ctrl;
597
521
  }
598
522
  startCloudflareStatusPoll(entry) {
599
523
  // Keep the existing ~60s readiness budget while decoupling it from probe
@@ -800,9 +724,7 @@ export class PhoneStreamCoordinator {
800
724
  fanout(update) {
801
725
  if (!this.current || !this.statusSubscriber)
802
726
  return;
803
- const targets = this.current.kind === "managed"
804
- ? Array.from(this.current.subscribers)
805
- : [this.current.packageName];
727
+ const targets = this.current.kind === "managed" ? Array.from(this.current.subscribers) : [this.current.packageName];
806
728
  for (const pkg of targets) {
807
729
  try {
808
730
  this.statusSubscriber(pkg, update);
@@ -830,11 +752,6 @@ export class PhoneStreamCoordinator {
830
752
  clearTimeout(this.suspended.graceTimer);
831
753
  this.suspended = null;
832
754
  }
833
- // Dispose the lifecycle controller immediately so it doesn't fire one
834
- // more keep-alive against a stream we're tearing down. The transition
835
- // lock guarantees no new lifecycle is started concurrently.
836
- this.lifecycle?.dispose();
837
- this.lifecycle = null;
838
755
  // With the link down a BLE write can only fail (and hold the transition
839
756
  // lock for the native timeout). Defer it to the next reconnect instead.
840
757
  const linkUp = this.linkSource.isConnected();