@glassly/engine 0.4.0 → 0.4.1-dev.1137

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 (230) hide show
  1. package/build/engine.d.ts +3 -1
  2. package/build/engine.d.ts.map +1 -1
  3. package/build/engine.js +6 -4
  4. package/build/engine.js.map +1 -1
  5. package/build/facades/display.d.ts +1 -1
  6. package/build/facades/display.d.ts.map +1 -1
  7. package/build/facades/display.js +1 -1
  8. package/build/facades/display.js.map +1 -1
  9. package/build/facades/glasses.js +1 -1
  10. package/build/facades/glasses.js.map +1 -1
  11. package/build/facades/permissions.d.ts.map +1 -1
  12. package/build/facades/permissions.js +0 -2
  13. package/build/facades/permissions.js.map +1 -1
  14. package/build/facades/phoneNotifications.d.ts +7 -0
  15. package/build/facades/phoneNotifications.d.ts.map +1 -1
  16. package/build/facades/phoneNotifications.js +6 -0
  17. package/build/facades/phoneNotifications.js.map +1 -1
  18. package/build/facades/providerKeys.d.ts +1 -1
  19. package/build/facades/providerKeys.js +1 -1
  20. package/build/facades/providerKeys.js.map +1 -1
  21. package/build/facades/reports.d.ts +15 -0
  22. package/build/facades/reports.d.ts.map +1 -1
  23. package/build/facades/reports.js +37 -0
  24. package/build/facades/reports.js.map +1 -1
  25. package/build/facades/settings.js +1 -1
  26. package/build/facades/settings.js.map +1 -1
  27. package/build/generated/releaseMetadata.js +4 -4
  28. package/build/generated/releaseMetadata.js.map +1 -1
  29. package/build/index.d.ts +2 -1
  30. package/build/index.d.ts.map +1 -1
  31. package/build/index.js +1 -1
  32. package/build/index.js.map +1 -1
  33. package/build/react/useMentraLiveOta.d.ts.map +1 -1
  34. package/build/react/useMentraLiveOta.js.map +1 -1
  35. package/build/runtime/config.d.ts.map +1 -1
  36. package/build/runtime/config.js.map +1 -1
  37. package/build/services/AiTierResolution.d.ts +1 -1
  38. package/build/services/AiTierResolution.d.ts.map +1 -1
  39. package/build/services/AiTierResolution.js.map +1 -1
  40. package/build/services/AppRegistry.d.ts +12 -1
  41. package/build/services/AppRegistry.d.ts.map +1 -1
  42. package/build/services/AppRegistry.js +33 -0
  43. package/build/services/AppRegistry.js.map +1 -1
  44. package/build/services/AutomaticReportResult.d.ts.map +1 -1
  45. package/build/services/AutomaticReportResult.js.map +1 -1
  46. package/build/services/CloudClientService.d.ts +7 -5
  47. package/build/services/CloudClientService.d.ts.map +1 -1
  48. package/build/services/CloudClientService.js +17 -14
  49. package/build/services/CloudClientService.js.map +1 -1
  50. package/build/services/DisplayProcessor.d.ts.map +1 -1
  51. package/build/services/DisplayProcessor.js +2 -1
  52. package/build/services/DisplayProcessor.js.map +1 -1
  53. package/build/services/GestureDotsService.js +1 -1
  54. package/build/services/GestureDotsService.js.map +1 -1
  55. package/build/services/HotspotOtaTransport.d.ts.map +1 -1
  56. package/build/services/HotspotOtaTransport.js +0 -1
  57. package/build/services/HotspotOtaTransport.js.map +1 -1
  58. package/build/services/JSRouter.js +2 -2
  59. package/build/services/JSRouter.js.map +1 -1
  60. package/build/services/LocalLlmModelManager.d.ts.map +1 -1
  61. package/build/services/LocalLlmModelManager.js +4 -3
  62. package/build/services/LocalLlmModelManager.js.map +1 -1
  63. package/build/services/LocalLlmProvider.d.ts.map +1 -1
  64. package/build/services/LocalLlmProvider.js +5 -6
  65. package/build/services/LocalLlmProvider.js.map +1 -1
  66. package/build/services/LocalMiniappRuntime.d.ts +10 -3
  67. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  68. package/build/services/LocalMiniappRuntime.js +142 -30
  69. package/build/services/LocalMiniappRuntime.js.map +1 -1
  70. package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
  71. package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
  72. package/build/services/NativeNavigationService.d.ts.map +1 -1
  73. package/build/services/NativeNavigationService.js.map +1 -1
  74. package/build/services/NavigationHandlers.d.ts +1 -1
  75. package/build/services/NavigationHandlers.js +2 -2
  76. package/build/services/NavigationHandlers.js.map +1 -1
  77. package/build/services/NavigationService.d.ts +2 -2
  78. package/build/services/NavigationService.d.ts.map +1 -1
  79. package/build/services/NavigationService.js +11 -11
  80. package/build/services/NavigationService.js.map +1 -1
  81. package/build/services/PairingIdentity.d.ts.map +1 -1
  82. package/build/services/PairingIdentity.js.map +1 -1
  83. package/build/services/PhoneCalendarService.d.ts.map +1 -1
  84. package/build/services/PhoneCalendarService.js.map +1 -1
  85. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  86. package/build/services/PhoneNotificationsSync.js +2 -1
  87. package/build/services/PhoneNotificationsSync.js.map +1 -1
  88. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  89. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  90. package/build/services/ProviderKeyValidation.d.ts +1 -1
  91. package/build/services/ProviderKeyValidation.js +1 -1
  92. package/build/services/ProviderKeyValidation.js.map +1 -1
  93. package/build/services/STTModelManager.d.ts.map +1 -1
  94. package/build/services/STTModelManager.js +1 -6
  95. package/build/services/STTModelManager.js.map +1 -1
  96. package/build/services/SupportProfileSync.d.ts.map +1 -1
  97. package/build/services/SupportProfileSync.js +3 -0
  98. package/build/services/SupportProfileSync.js.map +1 -1
  99. package/build/services/TranscriptionRouting.d.ts +3 -0
  100. package/build/services/TranscriptionRouting.d.ts.map +1 -1
  101. package/build/services/TranscriptionRouting.js +14 -0
  102. package/build/services/TranscriptionRouting.js.map +1 -1
  103. package/build/services/UIRouter.d.ts.map +1 -1
  104. package/build/services/UIRouter.js.map +1 -1
  105. package/build/services/controllers.d.ts.map +1 -1
  106. package/build/services/controllers.js +1 -0
  107. package/build/services/controllers.js.map +1 -1
  108. package/build/services/effectiveCapabilities.js +3 -2
  109. package/build/services/effectiveCapabilities.js.map +1 -1
  110. package/build/services/gestureDotGlyphs.d.ts.map +1 -1
  111. package/build/services/gestureDotGlyphs.js.map +1 -1
  112. package/build/services/manifestActions.d.ts.map +1 -1
  113. package/build/services/manifestActions.js.map +1 -1
  114. package/build/services/neuralBand/MetaAuth.d.ts +1 -0
  115. package/build/services/neuralBand/MetaAuth.d.ts.map +1 -1
  116. package/build/services/neuralBand/MetaAuth.js +36 -8
  117. package/build/services/neuralBand/MetaAuth.js.map +1 -1
  118. package/build/services/notificationAppPayload.d.ts +14 -0
  119. package/build/services/notificationAppPayload.d.ts.map +1 -0
  120. package/build/services/notificationAppPayload.js +15 -0
  121. package/build/services/notificationAppPayload.js.map +1 -0
  122. package/build/services/notifications/miniappNotifications.d.ts +31 -0
  123. package/build/services/notifications/miniappNotifications.d.ts.map +1 -0
  124. package/build/services/notifications/miniappNotifications.js +24 -0
  125. package/build/services/notifications/miniappNotifications.js.map +1 -0
  126. package/build/services/pages/pageEligibility.d.ts +1 -2
  127. package/build/services/pages/pageEligibility.d.ts.map +1 -1
  128. package/build/services/pages/pageEligibility.js +4 -2
  129. package/build/services/pages/pageEligibility.js.map +1 -1
  130. package/build/services/settingsBridge.d.ts.map +1 -1
  131. package/build/services/settingsBridge.js +0 -2
  132. package/build/services/settingsBridge.js.map +1 -1
  133. package/build/services/uiShim.d.ts.map +1 -1
  134. package/build/services/uiShim.js +14 -0
  135. package/build/services/uiShim.js.map +1 -1
  136. package/build/stores/appVisibility.d.ts.map +1 -1
  137. package/build/stores/appVisibility.js.map +1 -1
  138. package/build/stores/gallerySync.d.ts.map +1 -1
  139. package/build/stores/gallerySync.js.map +1 -1
  140. package/build/stores/glasses.d.ts.map +1 -1
  141. package/build/stores/glasses.js +1 -0
  142. package/build/stores/glasses.js.map +1 -1
  143. package/build/stores/settings.d.ts.map +1 -1
  144. package/build/stores/settings.js +27 -28
  145. package/build/stores/settings.js.map +1 -1
  146. package/build/types/capabilities/even-realities-g1.d.ts.map +1 -1
  147. package/build/types/capabilities/even-realities-g1.js +4 -0
  148. package/build/types/capabilities/even-realities-g1.js.map +1 -1
  149. package/build/types/capabilities/nimo.d.ts.map +1 -1
  150. package/build/types/capabilities/nimo.js +21 -7
  151. package/build/types/capabilities/nimo.js.map +1 -1
  152. package/build/types/enums.d.ts +2 -1
  153. package/build/types/enums.d.ts.map +1 -1
  154. package/build/types/enums.js +1 -0
  155. package/build/types/enums.js.map +1 -1
  156. package/build/utils/display/index.d.ts +1 -0
  157. package/build/utils/display/index.d.ts.map +1 -1
  158. package/build/utils/display/index.js +2 -2
  159. package/build/utils/display/index.js.map +1 -1
  160. package/build/utils/display/profiles/index.d.ts +1 -0
  161. package/build/utils/display/profiles/index.d.ts.map +1 -1
  162. package/build/utils/display/profiles/index.js +1 -0
  163. package/build/utils/display/profiles/index.js.map +1 -1
  164. package/build/utils/display/profiles/nimo.d.ts +2 -0
  165. package/build/utils/display/profiles/nimo.d.ts.map +1 -0
  166. package/build/utils/display/profiles/nimo.js +3 -0
  167. package/build/utils/display/profiles/nimo.js.map +1 -0
  168. package/build/utils/display/scene/svgPath.js.map +1 -1
  169. package/build/utils/memory/search.d.ts.map +1 -1
  170. package/build/utils/memory/search.js +17 -7
  171. package/build/utils/memory/search.js.map +1 -1
  172. package/package.json +15 -15
  173. package/src/engine.ts +9 -7
  174. package/src/facades/display.ts +1 -1
  175. package/src/facades/glasses.ts +1 -1
  176. package/src/facades/permissions.ts +0 -2
  177. package/src/facades/phoneNotifications.ts +10 -0
  178. package/src/facades/providerKeys.ts +1 -1
  179. package/src/facades/reports.ts +41 -0
  180. package/src/facades/settings.ts +1 -1
  181. package/src/generated/releaseMetadata.ts +4 -4
  182. package/src/index.ts +2 -0
  183. package/src/react/useMentraLiveOta.ts +1 -4
  184. package/src/runtime/config.ts +1 -6
  185. package/src/services/AiTierResolution.ts +1 -1
  186. package/src/services/AppRegistry.ts +38 -1
  187. package/src/services/AutomaticReportResult.ts +1 -3
  188. package/src/services/CloudClientService.ts +17 -11
  189. package/src/services/DisplayProcessor.ts +2 -0
  190. package/src/services/GestureDotsService.ts +1 -1
  191. package/src/services/HotspotOtaTransport.ts +1 -5
  192. package/src/services/JSRouter.ts +2 -2
  193. package/src/services/LocalLlmModelManager.ts +4 -3
  194. package/src/services/LocalLlmProvider.ts +6 -7
  195. package/src/services/LocalMiniappRuntime.ts +170 -40
  196. package/src/services/LocalSttFallbackCoordinator.ts +1 -5
  197. package/src/services/NativeNavigationService.ts +1 -2
  198. package/src/services/NavigationHandlers.ts +2 -2
  199. package/src/services/NavigationService.ts +12 -11
  200. package/src/services/PairingIdentity.ts +1 -3
  201. package/src/services/PhoneCalendarService.ts +1 -3
  202. package/src/services/PhoneNotificationsSync.ts +2 -1
  203. package/src/services/PhoneStreamCoordinator.ts +1 -2
  204. package/src/services/ProviderKeyValidation.ts +1 -1
  205. package/src/services/STTModelManager.ts +1 -6
  206. package/src/services/SupportProfileSync.ts +2 -0
  207. package/src/services/TranscriptionRouting.ts +18 -0
  208. package/src/services/UIRouter.ts +3 -9
  209. package/src/services/controllers.ts +1 -0
  210. package/src/services/effectiveCapabilities.ts +3 -2
  211. package/src/services/gestureDotGlyphs.ts +10 -5
  212. package/src/services/manifestActions.ts +2 -1
  213. package/src/services/neuralBand/MetaAuth.ts +37 -8
  214. package/src/services/notificationAppPayload.ts +15 -0
  215. package/src/services/notifications/miniappNotifications.ts +53 -0
  216. package/src/services/pages/pageEligibility.ts +3 -2
  217. package/src/services/settingsBridge.ts +1 -4
  218. package/src/services/uiShim.ts +14 -0
  219. package/src/stores/appVisibility.ts +2 -5
  220. package/src/stores/gallerySync.ts +1 -7
  221. package/src/stores/glasses.ts +1 -0
  222. package/src/stores/settings.ts +27 -28
  223. package/src/types/capabilities/even-realities-g1.ts +5 -0
  224. package/src/types/capabilities/nimo.ts +23 -7
  225. package/src/types/enums.ts +1 -0
  226. package/src/utils/display/index.ts +2 -2
  227. package/src/utils/display/profiles/index.ts +2 -0
  228. package/src/utils/display/profiles/nimo.ts +2 -0
  229. package/src/utils/display/scene/svgPath.ts +1 -1
  230. package/src/utils/memory/search.ts +17 -7
@@ -23,6 +23,9 @@ import {
23
23
  nativeNotificationsSupported,
24
24
  uploadPhoneNotification,
25
25
  } from "../services/PhoneNotificationsSync"
26
+ import {onMiniappNotification, type MiniappNotificationEvent} from "../services/notifications/miniappNotifications"
27
+
28
+ export type {MiniappNotificationEvent} from "../services/notifications/miniappNotifications"
26
29
 
27
30
  export const phoneNotifications = {
28
31
  /**
@@ -65,6 +68,13 @@ export const phoneNotifications = {
65
68
  * surface runs and draw only when the answer is `host`.
66
69
  */
67
70
  presentationOwner: currentPresentationOwner,
71
+ /**
72
+ * Notifications miniapps post and dismiss. Hosts feed them to the same
73
+ * fan-out as the phone's, marked `system: false` with `source` set.
74
+ */
75
+ onMiniappNotification: (listener: (event: MiniappNotificationEvent) => void): (() => void) =>
76
+ onMiniappNotification(listener),
77
+
68
78
  /** Hand a captured notification to firmware that presents uploaded content (owner `firmware`, Android). */
69
79
  uploadToGlasses: (event: PhoneNotificationEvent): Promise<void> => uploadPhoneNotification(event),
70
80
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * providerKeys facade — `engine.providerKeys.{anthropic,gemini,openai,deepseek,elevenlabs}`:
3
3
  * validate-before-save probes for the user-supplied AI/voice provider keys
4
- * (Settings > API Keys). Same per-provider shape as `engine.maps.mapbox` /
4
+ * (Settings > Privacy > API Keys). Same per-provider shape as `engine.maps.mapbox` /
5
5
  * `engine.speech.sonioxDirect` so the Keys screen treats providers uniformly.
6
6
  */
7
7
  import {
@@ -169,10 +169,51 @@ async function submitReportInternal(input: InternalSubmitReportInput): Promise<R
169
169
  return {status: "submitted", reportId, reportStatus}
170
170
  }
171
171
 
172
+ /** A debug-console log upload: the report it went to, and whether it replaced an earlier upload. */
173
+ export type LogUploadResult =
174
+ | {status: "created" | "updated"; reportId: string}
175
+ | {status: "busy"}
176
+ | {status: "failed"; error: string}
177
+
178
+ /** Uploads within this window after a report's creation replace its phone logs. */
179
+ export const LOG_UPLOAD_WINDOW_MS = 5 * 60_000
180
+ let logUpload: {reportId: string; createdAt: number} | null = null
181
+ let logUploadInFlight = false
182
+
183
+ /** Phone logs only, as a manual bug report; repeated taps cannot spam reports. */
184
+ async function uploadLogs(): Promise<LogUploadResult> {
185
+ if (logUploadInFlight) return {status: "busy"}
186
+ logUploadInFlight = true
187
+ try {
188
+ const logs = logBuffer.getRecentLogs()
189
+ const now = Date.now()
190
+ if (logUpload && now - logUpload.createdAt < LOG_UPLOAD_WINDOW_MS) {
191
+ await cloudClientService.core.reports.addLogs(logUpload.reportId, "phone", logs, {replace: true})
192
+ return {status: "updated", reportId: logUpload.reportId}
193
+ }
194
+ const context = await collectDiagnosticContext()
195
+ const res = await cloudClientService.core.reports.submit({
196
+ kind: "bug",
197
+ trigger: {type: "manual", source: "debug_console", reason: "log_upload"},
198
+ report: {actualBehavior: "Debug console log upload"},
199
+ context,
200
+ })
201
+ logUpload = {reportId: res.reportId, createdAt: now}
202
+ if (logs.length > 0) await cloudClientService.core.reports.addLogs(res.reportId, "phone", logs)
203
+ await cloudClientService.core.reports.complete(res.reportId)
204
+ return {status: "created", reportId: res.reportId}
205
+ } catch (error) {
206
+ return {status: "failed", error: error instanceof Error ? error.message : String(error)}
207
+ } finally {
208
+ logUploadInFlight = false
209
+ }
210
+ }
211
+
172
212
  export const reports = {
173
213
  submit(input: EngineSubmitReportInput): Promise<ReportSubmitResult> {
174
214
  return submitReportInternal(input)
175
215
  },
216
+ uploadLogs,
176
217
  }
177
218
 
178
219
  export function submitAutomaticReport(input: EngineSubmitAutomaticReportInput): Promise<ReportSubmitResult> {
@@ -2,7 +2,7 @@
2
2
  * settings facade — `engine.settings`: the typed keyed user-settings surface over
3
3
  * the engine-owned settings store. This is the OEM contract.
4
4
  *
5
- * Keys + their schema live in `SETTINGS` (theme, devMode, metric/twelveHourTime,
5
+ * Keys + their schema live in `SETTINGS` (theme, devMode, metric_system,
6
6
  * notifications, onboarding flags, …); `descriptor(key)`/`keys()` expose them.
7
7
  */
8
8
  import {useSettingsStore, SETTINGS} from "../stores/settings"
@@ -11,10 +11,10 @@ export interface EngineReleaseMetadata {
11
11
 
12
12
  export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
13
13
  "schemaVersion": 1,
14
- "familyBaseVersion": "0.4.0",
15
- "releaseIdentity": "0.4.0",
16
- "releaseSetId": "glassly-0.4.0",
17
- "sourceCommit": "b0de6d1f62b32238f0b7e916113ecc5164c4bd28",
14
+ "familyBaseVersion": "0.4.1",
15
+ "releaseIdentity": "0.4.1-dev.1137",
16
+ "releaseSetId": "glassly-0.4.1-dev.1137",
17
+ "sourceCommit": "71bec7fa9156ff40639ef61147d437c9f8db3c07",
18
18
  "otaManifestUrl": null,
19
19
  "otaManifestSha256": null
20
20
  })
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export type {
23
23
  } from "./facades/reports"
24
24
  export type {IslandNotification, IslandNotificationKind} from "./facades/notifications"
25
25
  export type {PresentationOwner} from "./services/notifications/notificationPolicy"
26
+ export type {MiniappNotificationEvent} from "./services/notifications/miniappNotifications"
26
27
  export {DEFAULT_HIDDEN_PACKAGES, isGatedByDevTools, isSystemMiniapp} from "./stores/appVisibility"
27
28
  export type {WifiSearchResult} from "./facades/glassesWifi"
28
29
  export type {IdentitySnapshot} from "./services/PairingIdentity"
@@ -230,6 +231,7 @@ export {
230
231
  authEntryUrl,
231
232
  callbackMatches,
232
233
  callbackQuery,
234
+ callbackShape,
233
235
  decryptBlob,
234
236
  isLoginCallback,
235
237
  login,
@@ -48,10 +48,7 @@ export type MentraLiveOtaScreen =
48
48
  | "disconnected"
49
49
 
50
50
  export type MentraLiveOtaErrorCode =
51
- | "check_failed"
52
- | "update_info_unavailable"
53
- | "install_failed"
54
- | "bes_restart_required"
51
+ "check_failed" | "update_info_unavailable" | "install_failed" | "bes_restart_required"
55
52
 
56
53
  export type MentraLiveOtaError = {
57
54
  code: MentraLiveOtaErrorCode
@@ -66,12 +66,7 @@ export type NavErrorEvent = {kind: "error"; message: string}
66
66
  /** Synthesized by the host when the trip is cancelled outside the miniapp (e.g. wearer exited nav mode on the glasses). */
67
67
  export type NavCancelledEvent = {kind: "cancelled"; reason?: string}
68
68
  export type NavUpdate =
69
- | NavManeuverEvent
70
- | NavOffRouteEvent
71
- | NavReroutingEvent
72
- | NavArrivedEvent
73
- | NavErrorEvent
74
- | NavCancelledEvent
69
+ NavManeuverEvent | NavOffRouteEvent | NavReroutingEvent | NavArrivedEvent | NavErrorEvent | NavCancelledEvent
75
70
 
76
71
  export type NavLocation = {
77
72
  lat: number
@@ -51,7 +51,7 @@ export const TIER_MODEL_OPTIONS: Record<AiTier, TierModelOption[]> = {
51
51
  ],
52
52
  }
53
53
 
54
- /** Which providers currently have a user key saved (Settings > API Keys). */
54
+ /** Which providers currently have a user key saved (Settings > Privacy > API Keys). */
55
55
  export type SavedLlmKeys = Record<AiTierProvider, boolean>
56
56
 
57
57
  /**
@@ -143,12 +143,19 @@ interface InstalledLma {
143
143
  }
144
144
 
145
145
  export interface MiniappReleaseIdentity {
146
- source: "direct_download" | "bundled_asset" | "preinstalled_registry" | "dev_snapshot" | "store"
146
+ source: "direct_download" | "bundled_asset" | "preinstalled_registry" | "dev_snapshot" | "store" | "sideload"
147
147
  releaseId?: string
148
148
  bundleSha256?: string
149
149
  channel?: string
150
150
  }
151
151
 
152
+ export interface MiniappZipManifest {
153
+ packageName: string
154
+ version: string
155
+ name: string
156
+ permissions: Array<{type: string; required?: boolean}>
157
+ }
158
+
152
159
  function releaseIdentityKey(packageName: string, version: string): string {
153
160
  return `miniapp_release_identity:${packageName}:${version}`
154
161
  }
@@ -480,6 +487,36 @@ class AppRegistry {
480
487
  })
481
488
  }
482
489
 
490
+ /** Reads miniapp.json out of a local zip without installing anything. */
491
+ public inspectLocalZip(zipPath: string): AsyncResult<MiniappZipManifest, Error> {
492
+ return Res.try_async(async () => {
493
+ const peekDir = new Directory(Paths.cache, "lma_peek")
494
+ if (peekDir.exists) peekDir.delete()
495
+ peekDir.create()
496
+ try {
497
+ await unzip(zipPath, peekDir.uri)
498
+ const manifestFile = new File(peekDir, "miniapp.json")
499
+ if (!manifestFile.exists) throw new Error("miniapp.json missing at zip root")
500
+ const manifest = JSON.parse(manifestFile.textSync()) as Record<string, unknown>
501
+ const {packageName, version, name} = manifest
502
+ if (typeof packageName !== "string" || !packageName) throw new Error("miniapp.json missing packageName")
503
+ if (typeof version !== "string" || !version) throw new Error("miniapp.json missing version")
504
+ const permissions = Array.isArray(manifest.permissions)
505
+ ? manifest.permissions.filter(
506
+ (entry): entry is {type: string; required?: boolean} => typeof entry?.type === "string",
507
+ )
508
+ : []
509
+ return {packageName, version, name: typeof name === "string" && name ? name : packageName, permissions}
510
+ } finally {
511
+ try {
512
+ peekDir.delete()
513
+ } catch {
514
+ // cache only
515
+ }
516
+ }
517
+ })
518
+ }
519
+
483
520
  /**
484
521
  * Post-install bookkeeping shared by every install path: clear stale dev
485
522
  * artifacts on release installs, point the active-version at the just-
@@ -1,9 +1,7 @@
1
1
  import type {ReportSubmitResult} from "../facades/reports"
2
2
 
3
3
  export type AutomaticReportSubmissionStatus =
4
- | {status: "filed"; reportId: string}
5
- | {status: "skipped"; reason: string}
6
- | {status: "failed"; error: string}
4
+ {status: "filed"; reportId: string} | {status: "skipped"; reason: string} | {status: "failed"; error: string}
7
5
 
8
6
  export function toAutomaticReportSubmissionStatus(result: ReportSubmitResult): AutomaticReportSubmissionStatus {
9
7
  if (result.status === "submitted") {
@@ -20,6 +20,7 @@ import type {
20
20
  StoreCatalogResponse,
21
21
  StoreListing,
22
22
  StoreReleasesResponse,
23
+ StoreHostOptions,
23
24
  } from "@glassly/cloud-client/react-native"
24
25
  import type {SubjectTokenType} from "@glassly/cloud-client"
25
26
  import {AppState, Platform} from "react-native"
@@ -190,9 +191,16 @@ function resolveEndpoints(): {core: string; runtime: string} {
190
191
  }
191
192
  }
192
193
 
194
+ // Via init(), never construct(): init() holds the disable-cloud gate, and a
195
+ // client built past it opens the runtime socket and retries it forever.
196
+ function requireClient(): CloudClient {
197
+ if (!client) cloudClientService.init()
198
+ if (!client) throw new Error("Glassly cloud is disabled")
199
+ return client
200
+ }
201
+
193
202
  function getCoreClient(): CloudCore {
194
- if (!client) construct()
195
- const core = client?.core
203
+ const core = requireClient().core
196
204
  if (!core) throw new Error("cloud client core not configured")
197
205
  return core
198
206
  }
@@ -207,9 +215,7 @@ function syncCoreAccessTokenToBluetooth(): Promise<string> {
207
215
  }
208
216
 
209
217
  async function syncCoreAccessTokenToBluetoothInternal(): Promise<string> {
210
- if (!client) construct()
211
- const c = client
212
- if (!c) throw new Error("cloud client not initialized")
218
+ const c = requireClient()
213
219
 
214
220
  const token = await c.auth.getCoreToken()
215
221
  if (c !== client) {
@@ -765,25 +771,25 @@ export const cloudClientService = {
765
771
  return c.core.miniapps.getRegistry()
766
772
  },
767
773
 
768
- async getStoreCatalog(): Promise<StoreCatalogResponse> {
774
+ async getStoreCatalog(opts?: StoreHostOptions): Promise<StoreCatalogResponse> {
769
775
  if (!client) this.init()
770
776
  const c = client
771
777
  if (!c?.core) throw new Error("cloud client core is unavailable")
772
- return c.core.store.list()
778
+ return c.core.store.list(opts)
773
779
  },
774
780
 
775
- async getStoreListing(packageName: string): Promise<StoreListing> {
781
+ async getStoreListing(packageName: string, opts?: StoreHostOptions): Promise<StoreListing> {
776
782
  if (!client) this.init()
777
783
  const c = client
778
784
  if (!c?.core) throw new Error("cloud client core is unavailable")
779
- return c.core.store.get(packageName)
785
+ return c.core.store.get(packageName, opts)
780
786
  },
781
787
 
782
- async getStoreReleases(packageName: string): Promise<StoreReleasesResponse> {
788
+ async getStoreReleases(packageName: string, opts?: StoreHostOptions): Promise<StoreReleasesResponse> {
783
789
  if (!client) this.init()
784
790
  const c = client
785
791
  if (!c?.core) throw new Error("cloud client core is unavailable")
786
- return c.core.store.releases(packageName)
792
+ return c.core.store.releases(packageName, opts)
787
793
  },
788
794
 
789
795
  /** Bytes of a store icon or screenshot; these routes need the user Bearer. */
@@ -21,6 +21,7 @@ import {
21
21
  NEX_PROFILE,
22
22
  RAYNEO_IO_PROFILE,
23
23
  MEMOMIND_ONE_PROFILE,
24
+ NIMO_PROFILE,
24
25
  RAYNEO_IO_CFW_PROFILE,
25
26
  TextMeasurer,
26
27
  TextWrapper,
@@ -206,6 +207,7 @@ const DEVICE_PROFILES: Partial<Record<DeviceTypes, DisplayProfile>> = {
206
207
  [DeviceTypes.MACH1]: Z100_PROFILE, // Mach1 uses same hardware as Vuzix Z100
207
208
  [DeviceTypes.RAYNEO_IO]: RAYNEO_IO_PROFILE,
208
209
  [DeviceTypes.MEMOMIND_ONE]: MEMOMIND_ONE_PROFILE,
210
+ [DeviceTypes.NIMO]: NIMO_PROFILE,
209
211
  }
210
212
 
211
213
  /**
@@ -50,7 +50,7 @@ const MIDDLE_FINGER_GLYPHS: Partial<Record<GestureDotGlyph, GestureDotGlyph>> =
50
50
 
51
51
  /** `glyph` as `finger` draws it: the middle finger's shapes are hollow. */
52
52
  function fingerGlyph(glyph: GestureDotGlyph, finger: number): GestureDotGlyph {
53
- return finger === 1 ? MIDDLE_FINGER_GLYPHS[glyph] ?? glyph : glyph
53
+ return finger === 1 ? (MIDDLE_FINGER_GLYPHS[glyph] ?? glyph) : glyph
54
54
  }
55
55
 
56
56
  function fingerKey(controllerId: string | undefined, finger: number): string {
@@ -1,7 +1,6 @@
1
1
  import BluetoothSdk, {isEnabledHotspotStatus} from "@glassly/bluetooth-sdk"
2
2
  import {otaServer} from "@glassly/bluetooth-sdk/ota-transport"
3
3
  // One coordinator selects the native Android or iOS staging implementation at runtime.
4
- // eslint-disable-next-line react-native/split-platform-components
5
4
  import {PermissionsAndroid, Platform} from "react-native"
6
5
  import type {OtaCheckCurrentGlassesResult} from "./OtaUpdateCheckService"
7
6
  import {
@@ -24,10 +23,7 @@ export type HotspotOtaProgress = {
24
23
  }
25
24
 
26
25
  export type HotspotOtaErrorCode =
27
- | "hotspot_wifi_permission_denied"
28
- | "hotspot_start_failed"
29
- | "hotspot_join_failed"
30
- | "hotspot_server_failed"
26
+ "hotspot_wifi_permission_denied" | "hotspot_start_failed" | "hotspot_join_failed" | "hotspot_server_failed"
31
27
 
32
28
  export class HotspotOtaTransportError extends Error {
33
29
  constructor(
@@ -381,6 +381,8 @@ export class JSRouter {
381
381
  BgTimer.clearTimeout(pending)
382
382
  this.respawnTimers.delete(packageName)
383
383
  }
384
+ miniappRunningRegistry.remove(packageName)
385
+ this.runtime.releaseWidgetHost(packageName)
384
386
  await this.runtime.notifyWillDisconnect(packageName, "stopped")
385
387
  this.spawnCache.delete(packageName)
386
388
  this.crashController?.onKill(packageName)
@@ -388,8 +390,6 @@ export class JSRouter {
388
390
  // drop out of native nav mode (crash-respawns don't take this path).
389
391
  this.runtime.unregisterApp(packageName, {endNavigation: true})
390
392
  this.registered.delete(packageName)
391
- // JSContext torn down → miniapp is no longer "running".
392
- miniappRunningRegistry.remove(packageName)
393
393
  if (this.crust.jsKill) {
394
394
  try {
395
395
  await this.crust.jsKill(packageName)
@@ -56,9 +56,10 @@ export interface LocalLlmDownloadStatus {
56
56
  }
57
57
 
58
58
  // XNNPACK 8da4w exports from software-mansion/react-native-executorch-qwen-3,
59
- // pinned to the v0.10.0 release tag that react-native-executorch 0.10.x
60
- // expects. modelBytes are the .pte sizes on the Hub; sizeBytes adds the
61
- // tokenizer files (~11 MB).
59
+ // pinned to the v0.10.0 release tag. They export every method the 0.11 text
60
+ // runner validates; moving to the v0.11.0 re-exports changes the file sizes and
61
+ // makes every existing download re-fetch. modelBytes are the .pte sizes on the
62
+ // Hub; sizeBytes adds the tokenizer files (~11 MB).
62
63
  const QWEN3_BASE = "https://huggingface.co/software-mansion/react-native-executorch-qwen-3/resolve/v0.10.0"
63
64
  const TOKENIZER_BYTES = 11_431_080
64
65
  const MB = 1024 * 1024
@@ -33,7 +33,7 @@ type Rne = typeof import("react-native-executorch")
33
33
  // The runner API lives on the `llm` namespace; derive its types from there so
34
34
  // the import stays a type-only reference (no native module at load time).
35
35
  type Tokenizer = ReturnType<Rne["nlp"]["loadTokenizer"]>
36
- type LLMRunner = ReturnType<Rne["llm"]["createLLMRunner"]>
36
+ type LLMRunner = Awaited<ReturnType<Rne["llm"]["createLLMRunner"]>>
37
37
  type LLMGenerationStats = ReturnType<LLMRunner["generate"]>
38
38
  type LLMGenerationConfig = NonNullable<Parameters<LLMRunner["generate"]>[1]>
39
39
 
@@ -238,15 +238,15 @@ class LocalLlmProvider {
238
238
 
239
239
  const elapsedMs = Date.now() - startedAt
240
240
  console.log(
241
- `${LOG_TAG}: ${model.modelId} prompt=${generated.stats.numPromptTokens} out=${generated.stats.numGeneratedTokens} tokens in ${elapsedMs} ms`,
241
+ `${LOG_TAG}: ${model.modelId} prompt=${generated.stats.prefill.numTokens} out=${generated.stats.numTokens} tokens in ${elapsedMs} ms`,
242
242
  )
243
243
 
244
244
  const seed = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`
245
245
  const completion = finalizeLocalResult(
246
246
  {
247
247
  text: generated.text,
248
- promptTokens: generated.stats.numPromptTokens,
249
- generatedTokens: generated.stats.numGeneratedTokens,
248
+ promptTokens: generated.stats.prefill.numTokens,
249
+ generatedTokens: generated.stats.numTokens,
250
250
  maxNewTokens,
251
251
  },
252
252
  {jsonMode, toolsOffered: Boolean(tools), makeToolCallId: (index) => `local_${seed}_${index}`},
@@ -290,11 +290,10 @@ class LocalLlmProvider {
290
290
  const template = new Template(tokenizerConfig.chatTemplate)
291
291
 
292
292
  const startedAt = Date.now()
293
- const createRunner = rne.wrapAsync(rne.llm.createLLMRunner)
294
- const runner = await createRunner(paths.model, paths.tokenizer, [])
293
+ const runner = await rne.llm.createLLMRunner(paths.model, paths.tokenizer, [])
295
294
  let tokenizer: Tokenizer | undefined
296
295
  try {
297
- const maxSeqLen = runner.getKVCacheState().maxSeqLen
296
+ const maxSeqLen = runner.getKVCacheState().maxContextLen
298
297
  if (!Number.isInteger(maxSeqLen) || maxSeqLen <= CONTEXT_MARGIN_TOKENS + 16) {
299
298
  throw new Error(`On-device model ${modelId} reported an invalid context size: ${maxSeqLen}`)
300
299
  }