@glassly/engine 0.3.5 → 0.3.6-beta.851

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 (206) hide show
  1. package/build/engine.d.ts +11 -1
  2. package/build/engine.d.ts.map +1 -1
  3. package/build/engine.js +13 -1
  4. package/build/engine.js.map +1 -1
  5. package/build/facades/ai.d.ts +3 -3
  6. package/build/facades/ai.d.ts.map +1 -1
  7. package/build/facades/ai.js +3 -3
  8. package/build/facades/ai.js.map +1 -1
  9. package/build/facades/glasses.d.ts +5 -1
  10. package/build/facades/glasses.d.ts.map +1 -1
  11. package/build/facades/glasses.js +39 -30
  12. package/build/facades/glasses.js.map +1 -1
  13. package/build/facades/memory.d.ts +10 -2
  14. package/build/facades/memory.d.ts.map +1 -1
  15. package/build/facades/memory.js +10 -0
  16. package/build/facades/memory.js.map +1 -1
  17. package/build/facades/miniapps.d.ts +2 -0
  18. package/build/facades/miniapps.d.ts.map +1 -1
  19. package/build/facades/miniapps.js +2 -0
  20. package/build/facades/miniapps.js.map +1 -1
  21. package/build/facades/pairing.d.ts.map +1 -1
  22. package/build/facades/pairing.js +3 -2
  23. package/build/facades/pairing.js.map +1 -1
  24. package/build/facades/providerKeys.d.ts +6 -2
  25. package/build/facades/providerKeys.d.ts.map +1 -1
  26. package/build/facades/providerKeys.js +6 -2
  27. package/build/facades/providerKeys.js.map +1 -1
  28. package/build/generated/releaseMetadata.js +4 -4
  29. package/build/generated/releaseMetadata.js.map +1 -1
  30. package/build/services/AiTierResolution.d.ts +3 -2
  31. package/build/services/AiTierResolution.d.ts.map +1 -1
  32. package/build/services/AiTierResolution.js +7 -2
  33. package/build/services/AiTierResolution.js.map +1 -1
  34. package/build/services/AppRegistry.d.ts +4 -1
  35. package/build/services/AppRegistry.d.ts.map +1 -1
  36. package/build/services/AppRegistry.js +16 -2
  37. package/build/services/AppRegistry.js.map +1 -1
  38. package/build/services/BlobStore.js +3 -3
  39. package/build/services/BlobStore.js.map +1 -1
  40. package/build/services/BluetoothRestoreLaunch.d.ts +8 -0
  41. package/build/services/BluetoothRestoreLaunch.d.ts.map +1 -0
  42. package/build/services/BluetoothRestoreLaunch.js +34 -0
  43. package/build/services/BluetoothRestoreLaunch.js.map +1 -0
  44. package/build/services/CloudClientService.d.ts +4 -4
  45. package/build/services/ForegroundLocationPermission.d.ts +1 -1
  46. package/build/services/ForegroundLocationPermission.d.ts.map +1 -1
  47. package/build/services/ForegroundLocationPermission.js.map +1 -1
  48. package/build/services/GestureDotsService.d.ts.map +1 -1
  49. package/build/services/GestureDotsService.js +2 -1
  50. package/build/services/GestureDotsService.js.map +1 -1
  51. package/build/services/GlassesTouchInput.d.ts +6 -4
  52. package/build/services/GlassesTouchInput.d.ts.map +1 -1
  53. package/build/services/GlassesTouchInput.js +81 -41
  54. package/build/services/GlassesTouchInput.js.map +1 -1
  55. package/build/services/LocalDisplayManager.d.ts +12 -4
  56. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  57. package/build/services/LocalDisplayManager.js +21 -7
  58. package/build/services/LocalDisplayManager.js.map +1 -1
  59. package/build/services/LocalLlmModelManager.d.ts +3 -1
  60. package/build/services/LocalLlmModelManager.d.ts.map +1 -1
  61. package/build/services/LocalLlmModelManager.js +13 -5
  62. package/build/services/LocalLlmModelManager.js.map +1 -1
  63. package/build/services/LocalLlmPrompt.d.ts +10 -9
  64. package/build/services/LocalLlmPrompt.d.ts.map +1 -1
  65. package/build/services/LocalLlmPrompt.js +43 -32
  66. package/build/services/LocalLlmPrompt.js.map +1 -1
  67. package/build/services/LocalLlmProvider.d.ts +1 -0
  68. package/build/services/LocalLlmProvider.d.ts.map +1 -1
  69. package/build/services/LocalLlmProvider.js +62 -22
  70. package/build/services/LocalLlmProvider.js.map +1 -1
  71. package/build/services/LocalMiniappRuntime.d.ts +28 -6
  72. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  73. package/build/services/LocalMiniappRuntime.js +190 -148
  74. package/build/services/LocalMiniappRuntime.js.map +1 -1
  75. package/build/services/MemoryStore.d.ts +13 -1
  76. package/build/services/MemoryStore.d.ts.map +1 -1
  77. package/build/services/MemoryStore.js +98 -1
  78. package/build/services/MemoryStore.js.map +1 -1
  79. package/build/services/MiniappExitGesture.d.ts +6 -3
  80. package/build/services/MiniappExitGesture.d.ts.map +1 -1
  81. package/build/services/MiniappExitGesture.js +4 -4
  82. package/build/services/MiniappExitGesture.js.map +1 -1
  83. package/build/services/MiniappWidgetMode.d.ts +6 -4
  84. package/build/services/MiniappWidgetMode.d.ts.map +1 -1
  85. package/build/services/MiniappWidgetMode.js +7 -6
  86. package/build/services/MiniappWidgetMode.js.map +1 -1
  87. package/build/services/ProviderKeyValidation.d.ts +2 -0
  88. package/build/services/ProviderKeyValidation.d.ts.map +1 -1
  89. package/build/services/ProviderKeyValidation.js +4 -0
  90. package/build/services/ProviderKeyValidation.js.map +1 -1
  91. package/build/services/SceneRenderer.d.ts.map +1 -1
  92. package/build/services/SceneRenderer.js +3 -1
  93. package/build/services/SceneRenderer.js.map +1 -1
  94. package/build/services/ScreenshotFixture.d.ts +56 -0
  95. package/build/services/ScreenshotFixture.d.ts.map +1 -0
  96. package/build/services/ScreenshotFixture.js +72 -0
  97. package/build/services/ScreenshotFixture.js.map +1 -0
  98. package/build/services/actionInvoke.d.ts +37 -0
  99. package/build/services/actionInvoke.d.ts.map +1 -0
  100. package/build/services/actionInvoke.js +89 -0
  101. package/build/services/actionInvoke.js.map +1 -0
  102. package/build/services/appleWatch/AppleWatchLink.d.ts +42 -0
  103. package/build/services/appleWatch/AppleWatchLink.d.ts.map +1 -0
  104. package/build/services/appleWatch/AppleWatchLink.js +259 -0
  105. package/build/services/appleWatch/AppleWatchLink.js.map +1 -0
  106. package/build/services/controllers.d.ts.map +1 -1
  107. package/build/services/controllers.js +16 -0
  108. package/build/services/controllers.js.map +1 -1
  109. package/build/services/manifestActions.d.ts +4 -2
  110. package/build/services/manifestActions.d.ts.map +1 -1
  111. package/build/services/manifestActions.js +9 -3
  112. package/build/services/manifestActions.js.map +1 -1
  113. package/build/services/memoryBackupFile.d.ts +9 -0
  114. package/build/services/memoryBackupFile.d.ts.map +1 -0
  115. package/build/services/memoryBackupFile.js +47 -0
  116. package/build/services/memoryBackupFile.js.map +1 -0
  117. package/build/services/settingsBridge.d.ts +4 -4
  118. package/build/services/settingsBridge.d.ts.map +1 -1
  119. package/build/services/settingsBridge.js +9 -4
  120. package/build/services/settingsBridge.js.map +1 -1
  121. package/build/services/widgetPlacements.d.ts +8 -7
  122. package/build/services/widgetPlacements.d.ts.map +1 -1
  123. package/build/services/widgetPlacements.js +7 -6
  124. package/build/services/widgetPlacements.js.map +1 -1
  125. package/build/stores/apps.d.ts +6 -0
  126. package/build/stores/apps.d.ts.map +1 -1
  127. package/build/stores/apps.js +29 -12
  128. package/build/stores/apps.js.map +1 -1
  129. package/build/stores/bluetoothSettingKeys.d.ts +2 -0
  130. package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
  131. package/build/stores/bluetoothSettingKeys.js +17 -0
  132. package/build/stores/bluetoothSettingKeys.js.map +1 -1
  133. package/build/stores/settings.d.ts.map +1 -1
  134. package/build/stores/settings.js +42 -2
  135. package/build/stores/settings.js.map +1 -1
  136. package/build/types/applet.d.ts +5 -1
  137. package/build/types/applet.d.ts.map +1 -1
  138. package/build/types/applet.js.map +1 -1
  139. package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
  140. package/build/types/capabilities/rayneo-io.js +2 -0
  141. package/build/types/capabilities/rayneo-io.js.map +1 -1
  142. package/build/types/enums.d.ts +2 -1
  143. package/build/types/enums.d.ts.map +1 -1
  144. package/build/types/enums.js +1 -0
  145. package/build/types/enums.js.map +1 -1
  146. package/build/utils/cloudClient/RnUdpAdapter.d.ts.map +1 -1
  147. package/build/utils/cloudClient/RnUdpAdapter.js +13 -2
  148. package/build/utils/cloudClient/RnUdpAdapter.js.map +1 -1
  149. package/build/utils/display/scene/index.d.ts +1 -1
  150. package/build/utils/display/scene/index.d.ts.map +1 -1
  151. package/build/utils/display/scene/index.js.map +1 -1
  152. package/build/utils/display/scene/process.d.ts.map +1 -1
  153. package/build/utils/display/scene/process.js +20 -1
  154. package/build/utils/display/scene/process.js.map +1 -1
  155. package/build/utils/display/scene/types.d.ts +1 -1
  156. package/build/utils/display/scene/types.d.ts.map +1 -1
  157. package/build/utils/display/scene/types.js.map +1 -1
  158. package/build/utils/miniappGlobals.d.ts +6 -0
  159. package/build/utils/miniappGlobals.d.ts.map +1 -1
  160. package/build/utils/miniappGlobals.js +28 -4
  161. package/build/utils/miniappGlobals.js.map +1 -1
  162. package/package.json +9 -9
  163. package/src/engine.ts +13 -1
  164. package/src/facades/ai.ts +3 -3
  165. package/src/facades/glasses.ts +65 -42
  166. package/src/facades/memory.ts +11 -2
  167. package/src/facades/miniapps.ts +3 -0
  168. package/src/facades/pairing.ts +4 -2
  169. package/src/facades/providerKeys.ts +6 -1
  170. package/src/generated/releaseMetadata.ts +4 -4
  171. package/src/services/AiTierResolution.ts +9 -3
  172. package/src/services/AppRegistry.ts +18 -3
  173. package/src/services/BlobStore.ts +3 -3
  174. package/src/services/BluetoothRestoreLaunch.ts +37 -0
  175. package/src/services/ForegroundLocationPermission.ts +1 -1
  176. package/src/services/GestureDotsService.ts +2 -1
  177. package/src/services/GlassesTouchInput.ts +86 -38
  178. package/src/services/LocalDisplayManager.ts +22 -8
  179. package/src/services/LocalLlmModelManager.ts +16 -6
  180. package/src/services/LocalLlmPrompt.ts +56 -37
  181. package/src/services/LocalLlmProvider.ts +74 -24
  182. package/src/services/LocalMiniappRuntime.ts +228 -147
  183. package/src/services/MemoryStore.ts +111 -2
  184. package/src/services/MiniappExitGesture.ts +8 -5
  185. package/src/services/MiniappWidgetMode.ts +9 -7
  186. package/src/services/ProviderKeyValidation.ts +5 -0
  187. package/src/services/SceneRenderer.ts +3 -1
  188. package/src/services/ScreenshotFixture.ts +113 -0
  189. package/src/services/actionInvoke.ts +105 -0
  190. package/src/services/appleWatch/AppleWatchLink.ts +297 -0
  191. package/src/services/controllers.ts +17 -0
  192. package/src/services/manifestActions.ts +16 -4
  193. package/src/services/memoryBackupFile.ts +50 -0
  194. package/src/services/settingsBridge.ts +9 -4
  195. package/src/services/widgetPlacements.ts +23 -9
  196. package/src/stores/apps.ts +35 -11
  197. package/src/stores/bluetoothSettingKeys.ts +20 -0
  198. package/src/stores/settings.ts +44 -2
  199. package/src/types/applet.ts +5 -1
  200. package/src/types/capabilities/rayneo-io.ts +2 -0
  201. package/src/types/enums.ts +1 -0
  202. package/src/utils/cloudClient/RnUdpAdapter.ts +13 -1
  203. package/src/utils/display/scene/index.ts +1 -0
  204. package/src/utils/display/scene/process.ts +17 -1
  205. package/src/utils/display/scene/types.ts +1 -0
  206. package/src/utils/miniappGlobals.ts +30 -4
package/src/facades/ai.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * ai facade — `engine.ai.local`: the on-device LLM model that serves
3
3
  * session.ai calls routed to the phone (forceLocal / localFallback). Thin
4
4
  * passthrough over LocalLlmModelManager and LocalLlmProvider so Settings >
5
- * AI Models can list, pick, download, and delete models without reaching
6
- * into engine internals.
5
+ * Offline Models can list, pick, download, and delete models without
6
+ * reaching into engine internals.
7
7
  */
8
8
  import localLlmModelManager, {
9
9
  LocalLlmModelManager,
@@ -17,7 +17,7 @@ export const ai = {
17
17
  local: {
18
18
  /** The catalog, in dropdown order. */
19
19
  models: (): LocalLlmModelConfig[] => localLlmModelManager.models(),
20
- /** The Settings > AI Models pick. */
20
+ /** The Settings > Offline Models pick. */
21
21
  selectedModelId: (): LocalLlmModelId => localLlmModelManager.selectedModelId(),
22
22
  select: (id: LocalLlmModelId): Promise<void> => localLlmModelManager.select(id),
23
23
  isDownloaded: (id?: LocalLlmModelId): Promise<boolean> => localLlmModelManager.isModelAvailable(id),
@@ -12,6 +12,7 @@
12
12
  // standalone build); jest moduleNameMapper + tsconfig both resolve it.
13
13
  import BluetoothSdk from "@glassly/bluetooth-sdk/internal"
14
14
  import type {
15
+ AppleWatchAvailability,
15
16
  ButtonPressEvent,
16
17
  ConnectOptions,
17
18
  ControllerStatus,
@@ -26,6 +27,13 @@ import {useSettingsStore, SETTINGS} from "../stores/settings"
26
27
  import {hasDefaultDevice} from "../services/DeviceStoreHydration"
27
28
  import micStateCoordinator from "../services/MicStateCoordinator"
28
29
  import {projectPairingIdentity} from "../services/PairingIdentity"
30
+ import {
31
+ fixtureControllerList,
32
+ fixtureControllerStatuses,
33
+ fixtureInfo,
34
+ fixtureStatus,
35
+ isScreenshotMode,
36
+ } from "../services/ScreenshotFixture"
29
37
  import {isGlassesConnected, isGlassesReady} from "../services/GlassesReadiness"
30
38
  import {pushAllBluetoothSettings} from "../services/GlassesSettingsSync"
31
39
  import {isControllerReady, pairedControllersFrom, readyControllers} from "../services/controllers"
@@ -42,25 +50,33 @@ import {glassesSettings} from "./glassesSettings"
42
50
 
43
51
  let micLevelSubscriptions = 0
44
52
 
53
+ // The screenshot fixture (ScreenshotFixture.ts) dresses these read-models
54
+ // while the identity is the simulated device; it is a no-op outside that build.
45
55
  function projectStatus() {
46
56
  const s = useGlassesStore.getState()
47
- return {
48
- state: s.connection.state,
49
- fullyBooted: isGlassesReady(s.connection),
50
- battery: s.batteryLevel,
51
- charging: s.charging,
52
- case: {battery: s.caseBatteryLevel, charging: s.caseCharging, open: s.caseOpen, removed: s.caseRemoved},
53
- signal: s.signalStrength,
54
- micEnabled: s.micEnabled,
55
- vadEnabled: s.voiceActivityDetectionEnabled,
56
- btClassic: s.bluetoothClassicConnected,
57
- // iOS: glasses may read the phone's ANCS stream ("Share System Notifications").
58
- ancsAuthorized: Boolean(s.ancsAuthorized),
59
- }
57
+ return fixtureStatus(
58
+ {
59
+ state: s.connection.state,
60
+ fullyBooted: isGlassesReady(s.connection),
61
+ battery: s.batteryLevel,
62
+ charging: s.charging,
63
+ case: {battery: s.caseBatteryLevel, charging: s.caseCharging, open: s.caseOpen, removed: s.caseRemoved},
64
+ signal: s.signalStrength,
65
+ micEnabled: s.micEnabled,
66
+ vadEnabled: s.voiceActivityDetectionEnabled,
67
+ btClassic: s.bluetoothClassicConnected,
68
+ // iOS: glasses may read the phone's ANCS stream ("Share System Notifications").
69
+ ancsAuthorized: Boolean(s.ancsAuthorized),
70
+ },
71
+ projectPairingIdentity(),
72
+ )
60
73
  }
61
74
 
62
75
  function projectControllerList(): PairedController[] {
63
- return pairedControllersFrom(useSettingsStore.getState().getSetting(SETTINGS.controllers.key))
76
+ return fixtureControllerList(
77
+ pairedControllersFrom(useSettingsStore.getState().getSetting(SETTINGS.controllers.key)),
78
+ projectPairingIdentity(),
79
+ )
64
80
  }
65
81
 
66
82
  // Copies: snapshots must not hand callers mutable references into the store.
@@ -69,7 +85,27 @@ function projectControllerStatuses(): Record<string, ControllerStatus> {
69
85
  for (const [id, status] of Object.entries(useGlassesStore.getState().controllers ?? {})) {
70
86
  statuses[id] = {...status}
71
87
  }
72
- return statuses
88
+ return fixtureControllerStatuses(statuses, projectPairingIdentity())
89
+ }
90
+
91
+ // Dedupe on the projected snapshot: the store updates on many fields; fire
92
+ // only when the projection changes. The fixture's read-models also follow the
93
+ // identity (a settings key), so that build watches the settings store too.
94
+ function subscribeProjection<T>(project: () => T, cb: (snapshot: T) => void): () => void {
95
+ let last = JSON.stringify(project())
96
+ const notify = () => {
97
+ const snap = project()
98
+ const key = JSON.stringify(snap)
99
+ if (key === last) return
100
+ last = key
101
+ cb(snap)
102
+ }
103
+ const unsubscribeGlasses = useGlassesStore.subscribe(notify)
104
+ const unsubscribeSettings = isScreenshotMode() ? useSettingsStore.subscribe(notify) : () => {}
105
+ return () => {
106
+ unsubscribeGlasses()
107
+ unsubscribeSettings()
108
+ }
73
109
  }
74
110
 
75
111
  function projectKeyboardStatus() {
@@ -88,6 +124,10 @@ function projectKeyboardStatus() {
88
124
 
89
125
  function projectInfo() {
90
126
  const s = useGlassesStore.getState()
127
+ return fixtureInfo(projectRawInfo(s), projectPairingIdentity())
128
+ }
129
+
130
+ function projectRawInfo(s: ReturnType<typeof useGlassesStore.getState>) {
91
131
  return {
92
132
  model: s.deviceModel,
93
133
  style: s.style,
@@ -190,29 +230,9 @@ export const glasses = {
190
230
 
191
231
  // --- read-model (projected from the engine-owned glasses store) ---
192
232
  status: (): GlassesStatusSnapshot => projectStatus(),
193
- onStatus: (cb: (status: GlassesStatusSnapshot) => void): (() => void) => {
194
- // Dedupe: the glasses store updates on many fields; only fire when the
195
- // projected status snapshot actually changes.
196
- let last = JSON.stringify(projectStatus())
197
- return useGlassesStore.subscribe(() => {
198
- const snap = projectStatus()
199
- const key = JSON.stringify(snap)
200
- if (key === last) return
201
- last = key
202
- cb(snap)
203
- })
204
- },
233
+ onStatus: (cb: (status: GlassesStatusSnapshot) => void): (() => void) => subscribeProjection(projectStatus, cb),
205
234
  info: (): GlassesInfoSnapshot => projectInfo(),
206
- onInfo: (cb: (info: GlassesInfoSnapshot) => void): (() => void) => {
207
- let last = JSON.stringify(projectInfo())
208
- return useGlassesStore.subscribe(() => {
209
- const snap = projectInfo()
210
- const key = JSON.stringify(snap)
211
- if (key === last) return
212
- last = key
213
- cb(snap)
214
- })
215
- },
235
+ onInfo: (cb: (info: GlassesInfoSnapshot) => void): (() => void) => subscribeProjection(projectInfo, cb),
216
236
  capabilities: () => getModelCapabilities(useGlassesStore.getState().deviceModel as DeviceTypes),
217
237
  /** Ask the glasses to report fresh firmware/version info (updates the store). */
218
238
  requestVersionInfo: () => BluetoothSdk.requestVersionInfo(),
@@ -283,14 +303,11 @@ export const glasses = {
283
303
  statuses: (): Record<string, ControllerStatus> => projectControllerStatuses(),
284
304
  status: (id: string): ControllerStatus | undefined => projectControllerStatuses()[id],
285
305
  onStatus: (cb: (statuses: Record<string, ControllerStatus>) => void): (() => void) =>
286
- useGlassesStore.subscribe(
287
- (s) => s.controllers,
288
- () => cb(projectControllerStatuses()),
289
- ),
306
+ subscribeProjection(projectControllerStatuses, cb),
290
307
  /** Controllers delivering input (linked, route or claim done). */
291
308
  connected: (): ControllerStatus[] => readyControllers(projectControllerStatuses()),
292
309
  isConnected: (model?: string): boolean =>
293
- Object.values(useGlassesStore.getState().controllers ?? {}).some(
310
+ Object.values(projectControllerStatuses()).some(
294
311
  (status) => isControllerReady(status) && (model === undefined || status.model === model),
295
312
  ),
296
313
  connect: (id: string): Promise<void> => BluetoothSdk.connectController(id),
@@ -329,6 +346,12 @@ export const glasses = {
329
346
  setHand: (id: string, hand: "left" | "right"): Promise<void> => BluetoothSdk.neuralBandSetHand(id, hand),
330
347
  },
331
348
 
349
+ // --- Apple Watch (iOS): pairs through the Glassly watch app over WatchConnectivity ---
350
+ appleWatch: {
351
+ /** Whether this iPhone has a paired watch and the Glassly watch app is on it. */
352
+ availability: (): Promise<AppleWatchAvailability> => BluetoothSdk.appleWatchAvailability(),
353
+ },
354
+
332
355
  // --- audio (glasses media-volume, own-app playback hint, live mic level) ---
333
356
  audio: {
334
357
  /** Read the glasses' current media volume. */
@@ -4,9 +4,10 @@
4
4
  * purge everything; the full timeline surface (lines, sessions, summaries)
5
5
  * is what miniapps reach through session.memory with the MEMORY permission.
6
6
  */
7
- import type {MemoryEntry, MemorySession} from "@glassly/miniapp"
7
+ import type {MemoryEntry, MemoryExportResult, MemoryImportResult, MemorySession} from "@glassly/miniapp"
8
8
 
9
- import type {MemoryStats} from "../services/MemoryStore"
9
+ import {exportMemoryFile, importMemoryFile} from "../services/memoryBackupFile"
10
+ import {MemoryBackupError, type MemoryStats} from "../services/MemoryStore"
10
11
  import {memoryStore} from "../services/memoryStoreInstance"
11
12
 
12
13
  export const memory = {
@@ -30,6 +31,14 @@ export const memory = {
30
31
  stats: (): Promise<MemoryStats> => memoryStore.stats(),
31
32
  /** Drop the entire timeline: every entry and every summary. */
32
33
  clearAll: (): Promise<void> => memoryStore.clear(),
34
+ /** Share the whole timeline as a JSON backup file. */
35
+ exportFile: (): Promise<MemoryExportResult> => exportMemoryFile(),
36
+ /**
37
+ * Pick a backup file and merge it in, skipping what's already stored.
38
+ * Resolves null on cancel; rejects with a MemoryBackupError for a non-backup file.
39
+ */
40
+ importFile: (): Promise<MemoryImportResult | null> => importMemoryFile(),
41
+ isBackupError: (err: unknown): boolean => err instanceof MemoryBackupError,
33
42
  /** Subscribe to store mutations (from miniapps or Settings). */
34
43
  onChanged: (listener: () => void): (() => void) => memoryStore.onChanged(listener),
35
44
  }
@@ -64,6 +64,9 @@ export const miniapps = {
64
64
  useAppStatusStore.getState().setHiddenStatus(...args),
65
65
  /** Mark a miniapp Always On (starts at launch, stops only from the switcher pill), persisted for the next boot. */
66
66
  setAlwaysOn: (...args: Parameters<AppsState["setAlwaysOn"]>) => useAppStatusStore.getState().setAlwaysOn(...args),
67
+ /** Whether a tap expands one of the miniapp's `EX` widgets to the full screen, persisted. */
68
+ setWidgetExpand: (...args: Parameters<AppsState["setWidgetExpand"]>) =>
69
+ useAppStatusStore.getState().setWidgetExpand(...args),
67
70
 
68
71
  /** Installed dashboard miniapps (manifest type "dashboard"); one runs at a time. */
69
72
  dashboards: (): ClientApp[] => useAppStatusStore.getState().apps.filter((app) => app.type === "dashboard"),
@@ -21,6 +21,7 @@ import {
21
21
  subscribePairingIdentity,
22
22
  type IdentitySnapshot,
23
23
  } from "../services/PairingIdentity"
24
+ import {fixtureIdentity} from "../services/ScreenshotFixture"
24
25
  import {isGlassesConnected, isGlassesLinkLayerBusy, isGlassesReady} from "../services/GlassesReadiness"
25
26
  import {
26
27
  logAutomaticReportSubmissionStatus,
@@ -278,10 +279,11 @@ export const pairing = {
278
279
  // --- pairing-identity lifecycle (the PairingIdentity read-model + the JS-owned
279
280
  // identity writes; promotion to `paired` only ever happens natively) ---
280
281
  /** The identity lifecycle snapshot: none | pending (chosen, never paired) | paired. */
281
- identity: (): IdentitySnapshot => projectPairingIdentity(),
282
+ identity: (): IdentitySnapshot => fixtureIdentity(projectPairingIdentity()),
282
283
  /** Subscribe to identity changes; fires only when the projected snapshot changes.
283
284
  * Returns an unsubscribe. */
284
- onIdentity: (cb: (identity: IdentitySnapshot) => void): (() => void) => subscribePairingIdentity(cb),
285
+ onIdentity: (cb: (identity: IdentitySnapshot) => void): (() => void) =>
286
+ subscribePairingIdentity((identity) => cb(fixtureIdentity(identity))),
285
287
  /** Mark the chosen model as the pending selection (the scan-entry write); the
286
288
  * host renders it as a finish-pairing affordance until pairing succeeds. */
287
289
  markPendingSelection: (model: string) => markPendingSelection(model),
@@ -1,11 +1,12 @@
1
1
  /**
2
- * providerKeys facade — `engine.providerKeys.{anthropic,gemini,elevenlabs}`:
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
4
  * (Settings > 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 {
8
8
  validateAnthropicApiKey,
9
+ validateDeepSeekApiKey,
9
10
  validateElevenLabsApiKey,
10
11
  validateGeminiApiKey,
11
12
  validateOpenAiApiKey,
@@ -24,6 +25,10 @@ export const providerKeys = {
24
25
  /** Probe a key against the OpenAI API without saving it. */
25
26
  validateKey: (...args: Parameters<typeof validateOpenAiApiKey>) => validateOpenAiApiKey(...args),
26
27
  },
28
+ deepseek: {
29
+ /** Probe a key against the DeepSeek API without saving it. */
30
+ validateKey: (...args: Parameters<typeof validateDeepSeekApiKey>) => validateDeepSeekApiKey(...args),
31
+ },
27
32
  elevenlabs: {
28
33
  /** Probe a key against the ElevenLabs API without saving it. */
29
34
  validateKey: (...args: Parameters<typeof validateElevenLabsApiKey>) => validateElevenLabsApiKey(...args),
@@ -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.3.5",
15
- "releaseIdentity": "0.3.5",
16
- "releaseSetId": "glassly-0.3.5",
17
- "sourceCommit": "dd27c8bbc0536d3479bdb50a288c2d63f2542290",
14
+ "familyBaseVersion": "0.3.6",
15
+ "releaseIdentity": "0.3.6-beta.851",
16
+ "releaseSetId": "glassly-0.3.6-beta.851",
17
+ "sourceCommit": "32a360c04986135f30d9d8886199d1e78f54f278",
18
18
  "otaManifestUrl": null,
19
19
  "otaManifestSha256": null
20
20
  })
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  export type AiTier = "quick" | "balanced" | "frontier"
14
- export type AiTierProvider = "anthropic" | "gemini" | "openai"
14
+ export type AiTierProvider = "anthropic" | "gemini" | "openai" | "deepseek"
15
15
 
16
16
  export const AI_TIERS: readonly AiTier[] = ["quick", "balanced", "frontier"]
17
17
 
@@ -27,23 +27,27 @@ export interface TierModelOption {
27
27
  * Curated per-tier model registry — the only place model ids live on the
28
28
  * phone. Order within a tier is the "auto" preference order: first entry
29
29
  * whose provider has a saved key wins. Anthropic leads (matching the cloud's
30
- * default provider), then Gemini (the pre-tier quick default), then OpenAI.
30
+ * default provider), then Gemini (the pre-tier quick default), then OpenAI,
31
+ * then DeepSeek.
31
32
  */
32
33
  export const TIER_MODEL_OPTIONS: Record<AiTier, TierModelOption[]> = {
33
34
  quick: [
34
35
  {provider: "anthropic", model: "claude-haiku-4-5", label: "Claude Haiku 4.5"},
35
36
  {provider: "gemini", model: "gemini-3.5-flash-lite", label: "Gemini 3.5 Flash Lite"},
36
37
  {provider: "openai", model: "gpt-5-mini", label: "GPT-5 mini"},
38
+ {provider: "deepseek", model: "deepseek-chat", label: "DeepSeek Chat"},
37
39
  ],
38
40
  balanced: [
39
41
  {provider: "anthropic", model: "claude-haiku-4-5", label: "Claude Haiku 4.5"},
40
42
  {provider: "gemini", model: "gemini-3.5-flash", label: "Gemini 3.5 Flash"},
41
43
  {provider: "openai", model: "gpt-5.1", label: "GPT-5.1"},
44
+ {provider: "deepseek", model: "deepseek-chat", label: "DeepSeek Chat"},
42
45
  ],
43
46
  frontier: [
44
47
  {provider: "anthropic", model: "claude-sonnet-5", label: "Claude Sonnet 5"},
45
48
  {provider: "gemini", model: "gemini-3.1-pro-preview", label: "Gemini 3.1 Pro"},
46
49
  {provider: "openai", model: "gpt-5.1", label: "GPT-5.1"},
50
+ {provider: "deepseek", model: "deepseek-reasoner", label: "DeepSeek Reasoner"},
47
51
  ],
48
52
  }
49
53
 
@@ -82,7 +86,9 @@ export function parseTierSetting(value: unknown): {provider: AiTierProvider; mod
82
86
  const stored = value.slice(sep + 1).trim()
83
87
  if (!stored) return null
84
88
  const model = RETIRED_MODELS[stored] ?? stored
85
- if (provider !== "anthropic" && provider !== "gemini" && provider !== "openai") return null
89
+ if (provider !== "anthropic" && provider !== "gemini" && provider !== "openai" && provider !== "deepseek") {
90
+ return null
91
+ }
86
92
  return {provider, model}
87
93
  }
88
94
 
@@ -22,7 +22,7 @@ import {unzip} from "react-native-zip-archive"
22
22
  import semver from "semver"
23
23
  import {AsyncResult, Result, result as Res} from "typesafe-ts"
24
24
 
25
- import type {AppletPermission, AppPermissionType, AppletType, ClientApp} from "../types/applet"
25
+ import type {AppletPermission, AppPermissionType, AppletType, ClientApp, DeclaredWidget} from "../types/applet"
26
26
  import {HardwareRequirement, HardwareRequirementLevel, HardwareType} from "../types"
27
27
  import {configuredDevHost} from "../utils/configuredDevHost"
28
28
  import {storage} from "../utils/storage/storage"
@@ -776,7 +776,7 @@ class AppRegistry {
776
776
 
777
777
  // Declared actions (for session.miniapps.list + invoke gating).
778
778
  const actions = normalizeManifestActions(manifest?.actions)
779
- const widgets = normalizeManifestWidgets(manifest?.widgets)
779
+ const widgets = withWidgetExpandChoices(lmaInfo.packageName, normalizeManifestWidgets(manifest?.widgets))
780
780
 
781
781
  // Dev miniapps live in the same lmas/ tree as installed ones, but
782
782
  // their version directory name starts with "dev-".
@@ -864,7 +864,9 @@ class AppRegistry {
864
864
  permissions,
865
865
  hardwareRequirements,
866
866
  actions: normalizeManifestActions(rec.actions),
867
- ...(normalizeManifestWidgets(rec.widgets).length > 0 ? {widgets: normalizeManifestWidgets(rec.widgets)} : {}),
867
+ ...(normalizeManifestWidgets(rec.widgets).length > 0
868
+ ? {widgets: withWidgetExpandChoices(rec.packageName, normalizeManifestWidgets(rec.widgets))}
869
+ : {}),
868
870
  isMiniappDev: true,
869
871
  devUrl: rec.devUrl,
870
872
  devPort: rec.devPort,
@@ -948,6 +950,19 @@ export function saveAlwaysOnOverride(packageName: string, status: boolean): void
948
950
  storage.save(`${packageName}_always_on`, status)
949
951
  }
950
952
 
953
+ /** Applies the wearer's tap-to-expand choice to each expandable widget; expansion is on until they turn it off. */
954
+ export function withWidgetExpandChoices(packageName: string, widgets: DeclaredWidget[]): DeclaredWidget[] {
955
+ return widgets.map((widget) => {
956
+ if (!widget.expandable) return widget
957
+ const res = storage.load<boolean>(`${packageName}_widget_expand_${widget.id}`)
958
+ return {...widget, expandOnTap: res.is_ok() ? res.value !== false : true}
959
+ })
960
+ }
961
+
962
+ export function saveWidgetExpandChoice(packageName: string, widgetId: string, expandOnTap: boolean): void {
963
+ storage.save(`${packageName}_widget_expand_${widgetId}`, expandOnTap)
964
+ }
965
+
951
966
  export function getLocalAppScreenshot(packageName: string): string | undefined {
952
967
  const key = `${packageName}_screenshot`
953
968
  const res = storage.load<string>(key)
@@ -383,7 +383,7 @@ export class BlobStore {
383
383
  return
384
384
  }
385
385
  up.handle.offset = payload.offset
386
- up.handle.writeBytes(bytes)
386
+ up.handle.writeBytesSync(bytes)
387
387
  up.handle.offset = up.bytes // restore append position
388
388
  this.hooks.sendResult(packageName, requestId, true, {bytesWritten: up.bytes})
389
389
  return
@@ -397,7 +397,7 @@ export class BlobStore {
397
397
  return
398
398
  }
399
399
  up.handle.offset = up.bytes
400
- up.handle.writeBytes(bytes)
400
+ up.handle.writeBytesSync(bytes)
401
401
  up.bytes += bytes.length
402
402
  this.hooks.sendResult(packageName, requestId, true, {bytesWritten: up.bytes})
403
403
  } catch (err) {
@@ -633,7 +633,7 @@ export class BlobStore {
633
633
  }
634
634
  try {
635
635
  const want = Math.max(0, Math.min(Number(payload.maxBytes) || 1024 * 1024, reader.remaining))
636
- const bytes = want > 0 ? reader.handle.readBytes(want) : new Uint8Array(0)
636
+ const bytes = want > 0 ? reader.handle.readBytesSync(want) : new Uint8Array(0)
637
637
  reader.remaining -= bytes.length
638
638
  const done = reader.remaining <= 0
639
639
  const base64 = bytes.length
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Reconnect the glasses when iOS relaunched the app for a preserved link.
3
+ *
4
+ * With CoreBluetooth state restoration, iOS keeps the lens connections after
5
+ * it terminates the app and relaunches it in the background on the next
6
+ * peripheral event. No screen mounts on that launch, so the host's usual
7
+ * "connect on the home screen" never runs; the engine asks native whether this
8
+ * is such a launch and connects the default wearable itself.
9
+ */
10
+ import BluetoothSdk from "@glassly/bluetooth-sdk/internal"
11
+
12
+ type Deps = {
13
+ launchedForRestore: () => Promise<boolean>
14
+ connectDefault: () => Promise<void>
15
+ }
16
+
17
+ export async function reconnectAfterBluetoothRestoreLaunch(deps: Deps): Promise<boolean> {
18
+ let launched = false
19
+ try {
20
+ launched = await deps.launchedForRestore()
21
+ } catch (error) {
22
+ console.warn("BluetoothRestoreLaunch: restore-launch query failed:", error instanceof Error ? error.message : error)
23
+ return false
24
+ }
25
+ if (!launched) return false
26
+ console.log("BluetoothRestoreLaunch: relaunched for a preserved glasses link; reconnecting")
27
+ try {
28
+ await deps.connectDefault()
29
+ } catch (error) {
30
+ console.warn("BluetoothRestoreLaunch: reconnect failed:", error instanceof Error ? error.message : error)
31
+ }
32
+ return true
33
+ }
34
+
35
+ export function nativeRestoreLaunchQuery(): () => Promise<boolean> {
36
+ return () => BluetoothSdk.consumeBluetoothRestoreLaunch()
37
+ }
@@ -13,7 +13,7 @@ export interface ForegroundLocationPermissionClient {
13
13
  */
14
14
  export async function resolveForegroundLocationPermission(
15
15
  client: ForegroundLocationPermissionClient,
16
- getAppState: () => string,
16
+ getAppState: () => string | null | undefined,
17
17
  ): Promise<ForegroundLocationPermissionResponse> {
18
18
  const current = await client.getForegroundPermissionsAsync()
19
19
  if (current.status === "granted" || getAppState() !== "active") {
@@ -132,7 +132,8 @@ class GestureDotsService {
132
132
  * `controllerId` keeps two controllers' fingers apart.
133
133
  */
134
134
  noteGesture(gestureName: string, claimed = false, fingerIndex?: number, controllerId?: string): void {
135
- if (!this.started || !this.enabled()) return
135
+ // A ring's scroll steps ride beside their stroke's one swipe, which already flashed.
136
+ if (!this.started || !this.enabled() || gestureName === "scroll_up" || gestureName === "scroll_down") return
136
137
  this.applyGesture(gestureName, claimed, fingerIndex, controllerId)
137
138
  this.trace("input", {gestureName, claimed})
138
139
  }