@glassly/engine 0.3.5-beta.760 → 0.3.5-beta.775
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.
- package/build/engine.d.ts +3 -0
- package/build/engine.d.ts.map +1 -1
- package/build/facades/memory.d.ts +10 -2
- package/build/facades/memory.d.ts.map +1 -1
- package/build/facades/memory.js +10 -0
- package/build/facades/memory.js.map +1 -1
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/BlobStore.js +3 -3
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/ForegroundLocationPermission.d.ts +1 -1
- package/build/services/ForegroundLocationPermission.d.ts.map +1 -1
- package/build/services/ForegroundLocationPermission.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +4 -2
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +51 -11
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MemoryStore.d.ts +13 -1
- package/build/services/MemoryStore.d.ts.map +1 -1
- package/build/services/MemoryStore.js +98 -1
- package/build/services/MemoryStore.js.map +1 -1
- package/build/services/manifestActions.d.ts +1 -1
- package/build/services/manifestActions.d.ts.map +1 -1
- package/build/services/manifestActions.js +3 -2
- package/build/services/manifestActions.js.map +1 -1
- package/build/services/memoryBackupFile.d.ts +9 -0
- package/build/services/memoryBackupFile.d.ts.map +1 -0
- package/build/services/memoryBackupFile.js +47 -0
- package/build/services/memoryBackupFile.js.map +1 -0
- package/build/services/widgetPlacements.d.ts +8 -7
- package/build/services/widgetPlacements.d.ts.map +1 -1
- package/build/services/widgetPlacements.js +7 -6
- package/build/services/widgetPlacements.js.map +1 -1
- package/build/types/applet.d.ts +3 -1
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js.map +1 -1
- package/package.json +9 -9
- package/src/facades/memory.ts +11 -2
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/services/BlobStore.ts +3 -3
- package/src/services/ForegroundLocationPermission.ts +1 -1
- package/src/services/LocalMiniappRuntime.ts +56 -11
- package/src/services/MemoryStore.ts +111 -2
- package/src/services/manifestActions.ts +10 -3
- package/src/services/memoryBackupFile.ts +50 -0
- package/src/services/widgetPlacements.ts +23 -9
- package/src/types/applet.ts +3 -1
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Who placed which widget where: the pure bookkeeping behind the runtime's
|
|
3
3
|
* widget routing. A placement is one host's box for one provider widget;
|
|
4
4
|
* a provider is placed while any host holds one of its widgets. Frames are
|
|
5
|
-
* kept per widget so a host that places one again gets the last
|
|
6
|
-
* at once.
|
|
5
|
+
* kept per widget and view so a host that places one again gets the last
|
|
6
|
+
* card and expanded frames at once.
|
|
7
7
|
*/
|
|
8
8
|
export const widgetKey = (providerPackageName, widgetId) => `${providerPackageName}/${widgetId}`;
|
|
9
9
|
const placementKey = (hostPackageName, providerPackageName, widgetId) => `${hostPackageName}|${widgetKey(providerPackageName, widgetId)}`;
|
|
@@ -41,13 +41,14 @@ export function dropProvider(state, providerPackageName) {
|
|
|
41
41
|
}
|
|
42
42
|
return placements.size === state.placements.size ? state : { ...state, placements };
|
|
43
43
|
}
|
|
44
|
-
export function setFrame(state, providerPackageName, widgetId, elements) {
|
|
44
|
+
export function setFrame(state, providerPackageName, widgetId, elements, view = "card") {
|
|
45
|
+
const key = widgetKey(providerPackageName, widgetId);
|
|
45
46
|
const frames = new Map(state.frames);
|
|
46
|
-
frames.set(
|
|
47
|
+
frames.set(key, { ...state.frames.get(key), [view]: elements });
|
|
47
48
|
return { ...state, frames };
|
|
48
49
|
}
|
|
49
|
-
export function frameOf(state, providerPackageName, widgetId) {
|
|
50
|
-
return state.frames.get(widgetKey(providerPackageName, widgetId)) ?? null;
|
|
50
|
+
export function frameOf(state, providerPackageName, widgetId, view = "card") {
|
|
51
|
+
return state.frames.get(widgetKey(providerPackageName, widgetId))?.[view] ?? null;
|
|
51
52
|
}
|
|
52
53
|
export function placementOf(state, hostPackageName, providerPackageName, widgetId) {
|
|
53
54
|
return state.placements.get(placementKey(hostPackageName, providerPackageName, widgetId)) ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widgetPlacements.js","sourceRoot":"","sources":["../../src/services/widgetPlacements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"widgetPlacements.js","sourceRoot":"","sources":["../../src/services/widgetPlacements.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Who placed which widget where: the pure bookkeeping behind the runtime's\n * widget routing. A placement is one host's box for one provider widget;\n * a provider is placed while any host holds one of its widgets. Frames are\n * kept per widget and view so a host that places one again gets the last\n * card and expanded frames at once.\n */\n\nexport type WidgetView = \"card\" | \"expanded\"\n\nexport interface WidgetBox {\n size: \"XS\" | \"XSM\" | \"SM\" | \"MD\" | \"LG\"\n width: number\n height: number\n}\n\nexport interface Placement {\n hostPackageName: string\n providerPackageName: string\n widgetId: string\n box: WidgetBox\n}\n\nexport interface WidgetPlacements {\n /** Keyed by `${host}|${provider}/${widgetId}`. */\n placements: ReadonlyMap<string, Placement>\n /** The latest frame per `${provider}/${widgetId}` and view. */\n frames: ReadonlyMap<string, Readonly<Partial<Record<WidgetView, unknown[]>>>>\n}\n\nexport const widgetKey = (providerPackageName: string, widgetId: string): string => `${providerPackageName}/${widgetId}`\nconst placementKey = (hostPackageName: string, providerPackageName: string, widgetId: string): string =>\n `${hostPackageName}|${widgetKey(providerPackageName, widgetId)}`\n\nexport function emptyWidgetPlacements(): WidgetPlacements {\n return {placements: new Map(), frames: new Map()}\n}\n\nexport function placeWidget(state: WidgetPlacements, placement: Placement): WidgetPlacements {\n const placements = new Map(state.placements)\n placements.set(placementKey(placement.hostPackageName, placement.providerPackageName, placement.widgetId), placement)\n return {...state, placements}\n}\n\nexport function unplaceWidget(\n state: WidgetPlacements,\n hostPackageName: string,\n providerPackageName: string,\n widgetId: string,\n): WidgetPlacements {\n const key = placementKey(hostPackageName, providerPackageName, widgetId)\n if (!state.placements.has(key)) return state\n const placements = new Map(state.placements)\n placements.delete(key)\n return {...state, placements}\n}\n\n/** A host went away: drop everything it placed. */\nexport function dropHost(state: WidgetPlacements, hostPackageName: string): WidgetPlacements {\n const placements = new Map(state.placements)\n for (const [key, placement] of state.placements) {\n if (placement.hostPackageName === hostPackageName) placements.delete(key)\n }\n return placements.size === state.placements.size ? state : {...state, placements}\n}\n\n/** A provider went away: drop its placements, keep its frames for the placeholder. */\nexport function dropProvider(state: WidgetPlacements, providerPackageName: string): WidgetPlacements {\n const placements = new Map(state.placements)\n for (const [key, placement] of state.placements) {\n if (placement.providerPackageName === providerPackageName) placements.delete(key)\n }\n return placements.size === state.placements.size ? state : {...state, placements}\n}\n\nexport function setFrame(\n state: WidgetPlacements,\n providerPackageName: string,\n widgetId: string,\n elements: unknown[],\n view: WidgetView = \"card\",\n): WidgetPlacements {\n const key = widgetKey(providerPackageName, widgetId)\n const frames = new Map(state.frames)\n frames.set(key, {...state.frames.get(key), [view]: elements})\n return {...state, frames}\n}\n\nexport function frameOf(\n state: WidgetPlacements,\n providerPackageName: string,\n widgetId: string,\n view: WidgetView = \"card\",\n): unknown[] | null {\n return state.frames.get(widgetKey(providerPackageName, widgetId))?.[view] ?? null\n}\n\nexport function placementOf(\n state: WidgetPlacements,\n hostPackageName: string,\n providerPackageName: string,\n widgetId: string,\n): Placement | null {\n return state.placements.get(placementKey(hostPackageName, providerPackageName, widgetId)) ?? null\n}\n\n/** Hosts holding this widget, each once. */\nexport function hostsOf(state: WidgetPlacements, providerPackageName: string, widgetId: string): string[] {\n const hosts = new Set<string>()\n for (const placement of state.placements.values()) {\n if (placement.providerPackageName === providerPackageName && placement.widgetId === widgetId) {\n hosts.add(placement.hostPackageName)\n }\n }\n return Array.from(hosts)\n}\n\n/** Providers with at least one widget placed. */\nexport function placedProviders(state: WidgetPlacements): Set<string> {\n const providers = new Set<string>()\n for (const placement of state.placements.values()) providers.add(placement.providerPackageName)\n return providers\n}\n\nexport function isProviderPlaced(state: WidgetPlacements, providerPackageName: string): boolean {\n for (const placement of state.placements.values()) {\n if (placement.providerPackageName === providerPackageName) return true\n }\n return false\n}\n\n/** The placements a host holds on a provider, for re-delivering boxes. */\nexport function placementsBy(state: WidgetPlacements, hostPackageName: string): Placement[] {\n return Array.from(state.placements.values()).filter((placement) => placement.hostPackageName === hostPackageName)\n}\n"]}
|
package/build/types/applet.d.ts
CHANGED
|
@@ -81,13 +81,15 @@ export interface DeclaredAction {
|
|
|
81
81
|
*/
|
|
82
82
|
visibility?: "public" | "system";
|
|
83
83
|
}
|
|
84
|
-
export type DeclaredWidgetSize = "XS" | "SM" | "MD" | "LG";
|
|
84
|
+
export type DeclaredWidgetSize = "XS" | "XSM" | "SM" | "MD" | "LG";
|
|
85
85
|
/** A dashboard widget a miniapp declares in `miniapp.json` and serves through `session.widgets`. */
|
|
86
86
|
export interface DeclaredWidget {
|
|
87
87
|
id: string;
|
|
88
88
|
name: string;
|
|
89
89
|
sizes: DeclaredWidgetSize[];
|
|
90
90
|
defaultSize: DeclaredWidgetSize;
|
|
91
|
+
/** A tap on the selected widget expands it to the full screen instead of activating the card. */
|
|
92
|
+
expandable: boolean;
|
|
91
93
|
}
|
|
92
94
|
export interface ClientApp extends AppletInterface {
|
|
93
95
|
offline: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applet.d.ts","sourceRoot":"","sources":["../../src/types/applet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,2BAA2B,CAAA;AACrE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAA;AACpC;;GAEG;AAEH,OAAO,EAAC,mBAAmB,EAAC,MAAM,YAAY,CAAA;AAE9C,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,4BAA4B,CAAA;AAEnE;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,WAAW,CAAA;AAEhE;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,sBAAsB,CAAA;AAE9D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,iBAAiB,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,SAAS,CAAA;IACzB,IAAI,EAAE,UAAU,CAAA;IAChB,WAAW,EAAE,gBAAgB,EAAE,CAAA;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,oBAAoB,EAAE,mBAAmB,EAAE,CAAA;IAC3C,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;GAQG;AACH;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC;;;OAGG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;CACjC;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"applet.d.ts","sourceRoot":"","sources":["../../src/types/applet.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applet.js","sourceRoot":"","sources":["../../src/types/applet.ts"],"names":[],"mappings":"","sourcesContent":["import type {ManifestPermissionType} from \"@glassly/miniapp/manifest\"\nimport type {ReactNode} from \"react\"\n/**\n * App and miniapp types owned by @glassly/engine.\n */\n\nimport {HardwareRequirement} from \"./hardware\"\n\nimport type {CompatibilityResult} from \"../utils/hardware/hardware\"\n\n/**\n * App execution model types\n */\nexport type AppletType = \"standard\" | \"background\" | \"dashboard\"\n\n/**\n * Permission types that apps can request: the manifest vocabulary from the\n * SDK (the single source the CLI validator reads too) plus the host-only ALL.\n */\nexport type AppPermissionType = \"ALL\" | ManifestPermissionType\n\n/**\n * Permission object with type and description\n */\nexport interface AppletPermission {\n type: AppPermissionType\n description?: string\n required?: boolean\n}\n\n/**\n * Minimal app interface for client home screen display\n * Optimized for fast rendering - only essential fields\n *\n * This is the client-facing interface used by mobile apps.\n * Internal cloud services use AppI from models (more fields).\n */\nexport interface AppletInterface {\n packageName: string\n name: string\n webviewUrl: string\n logoUrl: string\n iconComponent?: ReactNode\n type: AppletType\n permissions: AppletPermission[]\n running: boolean\n /**\n * True while this app's UI WebView is mounted in the Compositor overlay.\n * At most one app is foregrounded at a time. Distinct from `running`:\n * a foregrounded app is always running, but a running app may be\n * backgrounded (JSContext alive, WebView torn down). Normalized to a\n * concrete boolean by the apps store's projectApps; literal call sites\n * may omit it.\n */\n foregrounded?: boolean\n healthy: boolean\n hardwareRequirements: HardwareRequirement[]\n /** ISO date string when the app was installed */\n installedDate?: string\n /** ISO date string when the app was last run. Undefined if never run (app is \"new\") */\n lastActiveAt?: string\n}\n\n/**\n * Runtime applet shape used by the engine apps store and OEM hosts.\n *\n * Extends AppletInterface with the host-side fields the manager / OEM tracks\n * outside the manifest: offline routing, hardware compatibility result,\n * loading/hidden/local flags, lifecycle hooks, screenshot, and the\n * dev-miniapp escape hatches (`devUrl` / `isMiniappDev`) that surface a\n * `glassly dev` snapshot.\n */\n/**\n * A manifest-declared action a miniapp exposes for other miniapps to invoke\n * via `session.actions`. Populated from `miniapp.json`'s `actions` field.\n * Shape maps 1:1 onto an MCP tool (id→name, description, parameters→inputSchema).\n */\nexport interface DeclaredAction {\n id: string\n description: string\n /** JSON-Schema input descriptor (MCP-compatible subset). */\n parameters?: Record<string, unknown>\n /** JSON-Schema descriptor for the action's structured result. */\n outputSchema?: Record<string, unknown>\n /**\n * Who may invoke the action: \"public\" (default — any installed miniapp) or\n * \"system\" (system miniapps such as the AI assistant only).\n */\n visibility?: \"public\" | \"system\"\n}\n\nexport type DeclaredWidgetSize = \"XS\" | \"SM\" | \"MD\" | \"LG\"\n\n/** A dashboard widget a miniapp declares in `miniapp.json` and serves through `session.widgets`. */\nexport interface DeclaredWidget {\n id: string\n name: string\n sizes: DeclaredWidgetSize[]\n defaultSize: DeclaredWidgetSize\n}\n\nexport interface ClientApp extends AppletInterface {\n offline: boolean\n offlineRoute: string\n compatibility?: CompatibilityResult\n loading: boolean\n local: boolean\n hidden: boolean\n /**\n * Always On miniapp: the host starts it at launch and it only stops from\n * the app switcher's Always On pill; its card cannot be swiped away and\n * the capsule close minimizes it. Manifest `alwaysOn` gives the default;\n * the wearer's choice persists beside the hidden flag.\n */\n alwaysOn?: boolean\n /** Running, but paused while the paired glasses are disconnected (MiniappPauseCoordinator). */\n paused?: boolean\n onStart?: () => void\n onStop?: () => void\n screenshot?: string\n runtimePermissions?: string[]\n declaredPermissions?: string[]\n version?: string\n /** Manifest `description`: the paragraph the store and the info page show. */\n description?: string\n /** Manifest `tagline`: one line under the name. */\n tagline?: string\n /** Manifest `tags`: lowercase store tag slugs. */\n tags?: string[]\n needsPcm?: boolean\n needsTranscript?: boolean\n devUrl?: string\n devPort?: number\n isMiniappDev?: boolean\n /** Manifest-declared actions (for session.miniapps.list + invoke gating). */\n actions?: DeclaredAction[]\n /** Manifest-declared dashboard widgets. */\n widgets?: DeclaredWidget[]\n /**\n * Running only because a host placed one of its widgets: no card in the\n * app switcher, no frame of its own on the glasses. Opening the app from\n * the home screen promotes it to a normal run.\n */\n widgetOnly?: boolean\n}\n"]}
|
|
1
|
+
{"version":3,"file":"applet.js","sourceRoot":"","sources":["../../src/types/applet.ts"],"names":[],"mappings":"","sourcesContent":["import type {ManifestPermissionType} from \"@glassly/miniapp/manifest\"\nimport type {ReactNode} from \"react\"\n/**\n * App and miniapp types owned by @glassly/engine.\n */\n\nimport {HardwareRequirement} from \"./hardware\"\n\nimport type {CompatibilityResult} from \"../utils/hardware/hardware\"\n\n/**\n * App execution model types\n */\nexport type AppletType = \"standard\" | \"background\" | \"dashboard\"\n\n/**\n * Permission types that apps can request: the manifest vocabulary from the\n * SDK (the single source the CLI validator reads too) plus the host-only ALL.\n */\nexport type AppPermissionType = \"ALL\" | ManifestPermissionType\n\n/**\n * Permission object with type and description\n */\nexport interface AppletPermission {\n type: AppPermissionType\n description?: string\n required?: boolean\n}\n\n/**\n * Minimal app interface for client home screen display\n * Optimized for fast rendering - only essential fields\n *\n * This is the client-facing interface used by mobile apps.\n * Internal cloud services use AppI from models (more fields).\n */\nexport interface AppletInterface {\n packageName: string\n name: string\n webviewUrl: string\n logoUrl: string\n iconComponent?: ReactNode\n type: AppletType\n permissions: AppletPermission[]\n running: boolean\n /**\n * True while this app's UI WebView is mounted in the Compositor overlay.\n * At most one app is foregrounded at a time. Distinct from `running`:\n * a foregrounded app is always running, but a running app may be\n * backgrounded (JSContext alive, WebView torn down). Normalized to a\n * concrete boolean by the apps store's projectApps; literal call sites\n * may omit it.\n */\n foregrounded?: boolean\n healthy: boolean\n hardwareRequirements: HardwareRequirement[]\n /** ISO date string when the app was installed */\n installedDate?: string\n /** ISO date string when the app was last run. Undefined if never run (app is \"new\") */\n lastActiveAt?: string\n}\n\n/**\n * Runtime applet shape used by the engine apps store and OEM hosts.\n *\n * Extends AppletInterface with the host-side fields the manager / OEM tracks\n * outside the manifest: offline routing, hardware compatibility result,\n * loading/hidden/local flags, lifecycle hooks, screenshot, and the\n * dev-miniapp escape hatches (`devUrl` / `isMiniappDev`) that surface a\n * `glassly dev` snapshot.\n */\n/**\n * A manifest-declared action a miniapp exposes for other miniapps to invoke\n * via `session.actions`. Populated from `miniapp.json`'s `actions` field.\n * Shape maps 1:1 onto an MCP tool (id→name, description, parameters→inputSchema).\n */\nexport interface DeclaredAction {\n id: string\n description: string\n /** JSON-Schema input descriptor (MCP-compatible subset). */\n parameters?: Record<string, unknown>\n /** JSON-Schema descriptor for the action's structured result. */\n outputSchema?: Record<string, unknown>\n /**\n * Who may invoke the action: \"public\" (default — any installed miniapp) or\n * \"system\" (system miniapps such as the AI assistant only).\n */\n visibility?: \"public\" | \"system\"\n}\n\nexport type DeclaredWidgetSize = \"XS\" | \"XSM\" | \"SM\" | \"MD\" | \"LG\"\n\n/** A dashboard widget a miniapp declares in `miniapp.json` and serves through `session.widgets`. */\nexport interface DeclaredWidget {\n id: string\n name: string\n sizes: DeclaredWidgetSize[]\n defaultSize: DeclaredWidgetSize\n /** A tap on the selected widget expands it to the full screen instead of activating the card. */\n expandable: boolean\n}\n\nexport interface ClientApp extends AppletInterface {\n offline: boolean\n offlineRoute: string\n compatibility?: CompatibilityResult\n loading: boolean\n local: boolean\n hidden: boolean\n /**\n * Always On miniapp: the host starts it at launch and it only stops from\n * the app switcher's Always On pill; its card cannot be swiped away and\n * the capsule close minimizes it. Manifest `alwaysOn` gives the default;\n * the wearer's choice persists beside the hidden flag.\n */\n alwaysOn?: boolean\n /** Running, but paused while the paired glasses are disconnected (MiniappPauseCoordinator). */\n paused?: boolean\n onStart?: () => void\n onStop?: () => void\n screenshot?: string\n runtimePermissions?: string[]\n declaredPermissions?: string[]\n version?: string\n /** Manifest `description`: the paragraph the store and the info page show. */\n description?: string\n /** Manifest `tagline`: one line under the name. */\n tagline?: string\n /** Manifest `tags`: lowercase store tag slugs. */\n tags?: string[]\n needsPcm?: boolean\n needsTranscript?: boolean\n devUrl?: string\n devPort?: number\n isMiniappDev?: boolean\n /** Manifest-declared actions (for session.miniapps.list + invoke gating). */\n actions?: DeclaredAction[]\n /** Manifest-declared dashboard widgets. */\n widgets?: DeclaredWidget[]\n /**\n * Running only because a host placed one of its widgets: no card in the\n * app switcher, no frame of its own on the glasses. Opening the app from\n * the home screen promotes it to a normal run.\n */\n widgetOnly?: boolean\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/engine",
|
|
3
|
-
"version": "0.3.5-beta.
|
|
3
|
+
"version": "0.3.5-beta.775",
|
|
4
4
|
"description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -97,17 +97,17 @@
|
|
|
97
97
|
"registry": "https://registry.npmjs.org/"
|
|
98
98
|
},
|
|
99
99
|
"dependencies": {
|
|
100
|
-
"@glassly/bluetooth-sdk": "0.3.5-beta.
|
|
101
|
-
"@glassly/cloud-client": "0.3.5-beta.
|
|
102
|
-
"@glassly/cloud-protocol": "0.3.5-beta.
|
|
103
|
-
"@glassly/crust": "0.3.5-beta.
|
|
104
|
-
"@glassly/miniapp": "0.3.5-beta.
|
|
100
|
+
"@glassly/bluetooth-sdk": "0.3.5-beta.775",
|
|
101
|
+
"@glassly/cloud-client": "0.3.5-beta.775",
|
|
102
|
+
"@glassly/cloud-protocol": "0.3.5-beta.775",
|
|
103
|
+
"@glassly/crust": "0.3.5-beta.775",
|
|
104
|
+
"@glassly/miniapp": "0.3.5-beta.775",
|
|
105
105
|
"buffer": "^6.0.3",
|
|
106
106
|
"events": "^3.3.0",
|
|
107
107
|
"react-native-marked": "8.1.1",
|
|
108
108
|
"semver": "^7.7.4",
|
|
109
109
|
"typesafe-ts": "^1.7.1",
|
|
110
|
-
"@glassly/llm-providers": "0.3.5-beta.
|
|
110
|
+
"@glassly/llm-providers": "0.3.5-beta.775",
|
|
111
111
|
"@huggingface/jinja": "^0.5.9",
|
|
112
112
|
"jsonrepair": "^3.12.0"
|
|
113
113
|
},
|
|
@@ -115,8 +115,8 @@
|
|
|
115
115
|
"@glassly/llm-providers": "workspace:*",
|
|
116
116
|
"@types/semver": "^7.7.1",
|
|
117
117
|
"expo-module-scripts": "^56.0.3",
|
|
118
|
-
"expo-modules-core": "^
|
|
119
|
-
"react-native-nitro-modules": "^0.
|
|
118
|
+
"expo-modules-core": "^58.0.23",
|
|
119
|
+
"react-native-nitro-modules": "^0.37.1",
|
|
120
120
|
"typescript": "~5.9.2"
|
|
121
121
|
},
|
|
122
122
|
"peerDependencies": {
|
package/src/facades/memory.ts
CHANGED
|
@@ -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
|
|
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
|
}
|
|
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
|
|
|
12
12
|
export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "0.3.5",
|
|
15
|
-
"releaseIdentity": "0.3.5-beta.
|
|
16
|
-
"releaseSetId": "glassly-0.3.5-beta.
|
|
17
|
-
"sourceCommit": "
|
|
15
|
+
"releaseIdentity": "0.3.5-beta.775",
|
|
16
|
+
"releaseSetId": "glassly-0.3.5-beta.775",
|
|
17
|
+
"sourceCommit": "c48886d82edd42a7d012af3b8936251ac92ba949",
|
|
18
18
|
"otaManifestUrl": null,
|
|
19
19
|
"otaManifestSha256": null
|
|
20
20
|
})
|
|
@@ -383,7 +383,7 @@ export class BlobStore {
|
|
|
383
383
|
return
|
|
384
384
|
}
|
|
385
385
|
up.handle.offset = payload.offset
|
|
386
|
-
up.handle.
|
|
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.
|
|
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.
|
|
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
|
|
@@ -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") {
|
|
@@ -109,6 +109,7 @@ import {
|
|
|
109
109
|
unplaceWidget,
|
|
110
110
|
type WidgetBox,
|
|
111
111
|
type WidgetPlacements,
|
|
112
|
+
type WidgetView,
|
|
112
113
|
} from "./widgetPlacements"
|
|
113
114
|
import {
|
|
114
115
|
activeDashboard,
|
|
@@ -183,6 +184,8 @@ import {listPhoneCalendarEvents, PhoneCalendarError} from "./PhoneCalendarServic
|
|
|
183
184
|
import {LocalMiniappStorage} from "./LocalMiniappStorage"
|
|
184
185
|
import {captureDecisionFor, type CaptureCandidate} from "./MemoryCapture"
|
|
185
186
|
import {memoryStore} from "./memoryStoreInstance"
|
|
187
|
+
import {exportMemoryFile, importMemoryFile} from "./memoryBackupFile"
|
|
188
|
+
import {MemoryBackupError} from "./MemoryStore"
|
|
186
189
|
import {speakerProfileStore} from "./speakerProfileStoreInstance"
|
|
187
190
|
|
|
188
191
|
// =============================================================================
|
|
@@ -1591,6 +1594,8 @@ class LocalMiniappRuntime {
|
|
|
1591
1594
|
case MiniappRequestType.MEMORY_QUERY:
|
|
1592
1595
|
case MiniappRequestType.MEMORY_WINDOW:
|
|
1593
1596
|
case MiniappRequestType.MEMORY_EXPORT_TEXT:
|
|
1597
|
+
case MiniappRequestType.MEMORY_EXPORT_FILE:
|
|
1598
|
+
case MiniappRequestType.MEMORY_IMPORT_FILE:
|
|
1594
1599
|
case MiniappRequestType.MEMORY_SUMMARY_ADD:
|
|
1595
1600
|
case MiniappRequestType.MEMORY_SUMMARY_LIST:
|
|
1596
1601
|
case MiniappRequestType.MEMORY_PENDING_SUMMARY_HOUR:
|
|
@@ -4006,6 +4011,14 @@ class LocalMiniappRuntime {
|
|
|
4006
4011
|
this.sendResult(packageName, requestId, true, {text: await memoryStore.exportText(scope, num(payload.at))})
|
|
4007
4012
|
return
|
|
4008
4013
|
}
|
|
4014
|
+
case MiniappRequestType.MEMORY_EXPORT_FILE: {
|
|
4015
|
+
this.sendResult(packageName, requestId, true, await exportMemoryFile())
|
|
4016
|
+
return
|
|
4017
|
+
}
|
|
4018
|
+
case MiniappRequestType.MEMORY_IMPORT_FILE: {
|
|
4019
|
+
this.sendResult(packageName, requestId, true, {imported: await importMemoryFile()})
|
|
4020
|
+
return
|
|
4021
|
+
}
|
|
4009
4022
|
case MiniappRequestType.MEMORY_SUMMARY_ADD: {
|
|
4010
4023
|
const hourStart = num(payload.hourStart)
|
|
4011
4024
|
if (hourStart == null || typeof payload.text !== "string") {
|
|
@@ -4041,6 +4054,13 @@ class LocalMiniappRuntime {
|
|
|
4041
4054
|
})
|
|
4042
4055
|
}
|
|
4043
4056
|
} catch (err) {
|
|
4057
|
+
if (err instanceof MemoryBackupError) {
|
|
4058
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
4059
|
+
code: MiniappErrorCode.INVALID_ARGUMENT,
|
|
4060
|
+
message: err.message,
|
|
4061
|
+
})
|
|
4062
|
+
return
|
|
4063
|
+
}
|
|
4044
4064
|
console.error(`${LOG_TAG}: ${requestType} error:`, err)
|
|
4045
4065
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
4046
4066
|
code: MiniappErrorCode.INTERNAL,
|
|
@@ -4662,7 +4682,7 @@ class LocalMiniappRuntime {
|
|
|
4662
4682
|
if (base64) {
|
|
4663
4683
|
// File share via base64 — write to temp file then share
|
|
4664
4684
|
const tempFile = new File(Paths.cache, filename || "shared_file")
|
|
4665
|
-
tempFile.
|
|
4685
|
+
tempFile.writeSync(base64, {encoding: "base64"})
|
|
4666
4686
|
await Share.open({
|
|
4667
4687
|
url: tempFile.uri,
|
|
4668
4688
|
type: mimeType || "application/octet-stream",
|
|
@@ -4742,7 +4762,7 @@ class LocalMiniappRuntime {
|
|
|
4742
4762
|
let file: File
|
|
4743
4763
|
if (base64) {
|
|
4744
4764
|
file = new File(Paths.cache, name)
|
|
4745
|
-
file.
|
|
4765
|
+
file.writeSync(base64, {encoding: "base64"})
|
|
4746
4766
|
} else if (url) {
|
|
4747
4767
|
file = await File.downloadFileAsync(url, new File(Paths.cache, name), {idempotent: true})
|
|
4748
4768
|
} else {
|
|
@@ -6298,6 +6318,8 @@ class LocalMiniappRuntime {
|
|
|
6298
6318
|
private widgetCallSeq = 0
|
|
6299
6319
|
/** Elements per widget frame; a card is small, and the link is slow. */
|
|
6300
6320
|
private static readonly WIDGET_FRAME_ELEMENT_CAP = 64
|
|
6321
|
+
/** Elements per expanded widget frame: the full screen, on the same link. */
|
|
6322
|
+
private static readonly WIDGET_EXPANDED_FRAME_ELEMENT_CAP = 128
|
|
6301
6323
|
|
|
6302
6324
|
private requireWidgetHost(packageName: string, requestId: string | undefined, op: string): boolean {
|
|
6303
6325
|
return this.requirePermission(packageName, requestId, "HOST_WIDGETS", `to ${op} widgets`)
|
|
@@ -6346,6 +6368,7 @@ class LocalMiniappRuntime {
|
|
|
6346
6368
|
name: widget.name,
|
|
6347
6369
|
sizes: widget.sizes,
|
|
6348
6370
|
defaultSize: widget.defaultSize,
|
|
6371
|
+
expandable: widget.expandable,
|
|
6349
6372
|
running: app.running,
|
|
6350
6373
|
compatible: app.compatibility?.isCompatible !== false,
|
|
6351
6374
|
})
|
|
@@ -6357,7 +6380,8 @@ class LocalMiniappRuntime {
|
|
|
6357
6380
|
private parseWidgetBox(raw: unknown): WidgetBox | null {
|
|
6358
6381
|
const box = raw as {size?: unknown; width?: unknown; height?: unknown} | null
|
|
6359
6382
|
if (!box || typeof box !== "object") return null
|
|
6360
|
-
if (box.size !== "XS" && box.size !== "SM" && box.size !== "MD" && box.size !== "LG")
|
|
6383
|
+
if (box.size !== "XS" && box.size !== "XSM" && box.size !== "SM" && box.size !== "MD" && box.size !== "LG")
|
|
6384
|
+
return null
|
|
6361
6385
|
const width = Number(box.width)
|
|
6362
6386
|
const height = Number(box.height)
|
|
6363
6387
|
if (!(width > 0) || !(height > 0)) return null
|
|
@@ -6383,7 +6407,7 @@ class LocalMiniappRuntime {
|
|
|
6383
6407
|
/**
|
|
6384
6408
|
* session.widgets.place: record the host's box, wake the provider in widget
|
|
6385
6409
|
* mode when it is not running, hand it the box, and answer with the last
|
|
6386
|
-
*
|
|
6410
|
+
* card and expanded frames the provider pushed for the widget.
|
|
6387
6411
|
*/
|
|
6388
6412
|
private async handleWidgetPlace(
|
|
6389
6413
|
hostPackageName: string,
|
|
@@ -6432,7 +6456,10 @@ class LocalMiniappRuntime {
|
|
|
6432
6456
|
if (placementOf(this.widgetPlacements, hostPackageName, provider, widgetId) && this.connectedApps.has(provider)) {
|
|
6433
6457
|
this.sendWidgetCall(provider, widgetId, {kind: "box", box})
|
|
6434
6458
|
}
|
|
6435
|
-
this.sendResult(hostPackageName, requestId, true, {
|
|
6459
|
+
this.sendResult(hostPackageName, requestId, true, {
|
|
6460
|
+
frame: widgetFrameOf(this.widgetPlacements, provider, widgetId),
|
|
6461
|
+
expandedFrame: widgetFrameOf(this.widgetPlacements, provider, widgetId, "expanded"),
|
|
6462
|
+
})
|
|
6436
6463
|
}
|
|
6437
6464
|
|
|
6438
6465
|
private async handleWidgetUnplace(
|
|
@@ -6550,20 +6577,38 @@ class LocalMiniappRuntime {
|
|
|
6550
6577
|
}
|
|
6551
6578
|
}
|
|
6552
6579
|
|
|
6553
|
-
/** Provider → host, one-shot: cache the frame and push it to every host holding the widget. */
|
|
6580
|
+
/** Provider → host, one-shot: cache the frame for its view and push it to every host holding the widget. */
|
|
6554
6581
|
private handleWidgetUpdate(providerPackageName: string, payload: Record<string, unknown>): void {
|
|
6555
6582
|
const widgetId = payload.widgetId as string | undefined
|
|
6556
6583
|
const elements = payload.elements
|
|
6557
6584
|
if (!widgetId || !Array.isArray(elements)) return
|
|
6558
6585
|
const app = this.interopApps().find((a) => a.packageName === providerPackageName)
|
|
6559
|
-
|
|
6560
|
-
if (
|
|
6561
|
-
|
|
6586
|
+
const widget = app ? this.declaredWidget(app, widgetId) : undefined
|
|
6587
|
+
if (!widget) return
|
|
6588
|
+
const view: WidgetView = payload.view === "expanded" ? "expanded" : "card"
|
|
6589
|
+
if (view === "expanded" && !widget.expandable) {
|
|
6590
|
+
console.warn(
|
|
6591
|
+
`${LOG_TAG}: dropping expanded frame from ${providerPackageName}/${widgetId}: not declared expandable`,
|
|
6592
|
+
)
|
|
6562
6593
|
return
|
|
6563
6594
|
}
|
|
6564
|
-
|
|
6595
|
+
const cap =
|
|
6596
|
+
view === "expanded"
|
|
6597
|
+
? LocalMiniappRuntime.WIDGET_EXPANDED_FRAME_ELEMENT_CAP
|
|
6598
|
+
: LocalMiniappRuntime.WIDGET_FRAME_ELEMENT_CAP
|
|
6599
|
+
if (elements.length > cap || this.actionPayloadTooLarge(elements)) {
|
|
6600
|
+
console.warn(`${LOG_TAG}: dropping oversized ${view} widget frame from ${providerPackageName}/${widgetId}`)
|
|
6601
|
+
return
|
|
6602
|
+
}
|
|
6603
|
+
this.widgetPlacements = setWidgetFrame(this.widgetPlacements, providerPackageName, widgetId, elements, view)
|
|
6565
6604
|
for (const host of widgetHostsOf(this.widgetPlacements, providerPackageName, widgetId)) {
|
|
6566
|
-
this.sendToMiniapp(host, {
|
|
6605
|
+
this.sendToMiniapp(host, {
|
|
6606
|
+
type: MiniappResponseType.WIDGET_FRAME,
|
|
6607
|
+
packageName: providerPackageName,
|
|
6608
|
+
widgetId,
|
|
6609
|
+
elements,
|
|
6610
|
+
...(view === "expanded" ? {view} : {}),
|
|
6611
|
+
})
|
|
6567
6612
|
}
|
|
6568
6613
|
}
|
|
6569
6614
|
|
|
@@ -17,7 +17,14 @@
|
|
|
17
17
|
* user-scoped keys so existing history survives the move.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
MemoryBackup,
|
|
22
|
+
MemoryEntry,
|
|
23
|
+
MemoryImportResult,
|
|
24
|
+
MemoryNoteSource,
|
|
25
|
+
MemorySession,
|
|
26
|
+
MemorySummary,
|
|
27
|
+
} from "@glassly/miniapp"
|
|
21
28
|
|
|
22
29
|
import {searchMemoryEntries} from "../utils/memory/search"
|
|
23
30
|
|
|
@@ -91,6 +98,14 @@ export interface MemoryStats {
|
|
|
91
98
|
oldestAt: number | null
|
|
92
99
|
}
|
|
93
100
|
|
|
101
|
+
/** Thrown by importBackup for anything that isn't a backup this build can read. */
|
|
102
|
+
export class MemoryBackupError extends Error {
|
|
103
|
+
constructor(message: string) {
|
|
104
|
+
super(message)
|
|
105
|
+
this.name = "MemoryBackupError"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
export function hourStartOf(ms: number): number {
|
|
95
110
|
return Math.floor(ms / HOUR_MS) * HOUR_MS
|
|
96
111
|
}
|
|
@@ -359,6 +374,65 @@ export class MemoryStore {
|
|
|
359
374
|
return Math.max(...hours)
|
|
360
375
|
}
|
|
361
376
|
|
|
377
|
+
/** The whole timeline as a backup file body (Settings > Memory, session.memory.exportFile). */
|
|
378
|
+
async exportBackup(): Promise<MemoryBackup> {
|
|
379
|
+
await this.ensureLoaded()
|
|
380
|
+
return {
|
|
381
|
+
format: "glassly-memory",
|
|
382
|
+
version: 1,
|
|
383
|
+
exportedAt: Date.now(),
|
|
384
|
+
entries: [...this.entries],
|
|
385
|
+
summaries: [...this.summaries].sort((a, b) => a.hourStart - b.hourStart),
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Merge a backup into the timeline. Skips entries whose id is stored, notes
|
|
391
|
+
* whose text is already in the same hour, and hours that already have a
|
|
392
|
+
* summary. Throws MemoryBackupError when `data` isn't a memory backup.
|
|
393
|
+
*/
|
|
394
|
+
async importBackup(data: unknown): Promise<MemoryImportResult> {
|
|
395
|
+
const backup = parseBackup(data)
|
|
396
|
+
await this.ensureLoaded()
|
|
397
|
+
const result: MemoryImportResult = {entries: 0, summaries: 0, skipped: 0}
|
|
398
|
+
|
|
399
|
+
const ids = new Set(this.entries.map((e) => e.id))
|
|
400
|
+
const noteKeys = new Set(this.entries.filter((e) => e.kind === "note").map(noteKey))
|
|
401
|
+
for (const entry of backup.entries) {
|
|
402
|
+
const key = entry.kind === "note" ? noteKey(entry) : null
|
|
403
|
+
if (ids.has(entry.id) || (key != null && noteKeys.has(key))) {
|
|
404
|
+
result.skipped++
|
|
405
|
+
continue
|
|
406
|
+
}
|
|
407
|
+
ids.add(entry.id)
|
|
408
|
+
if (key != null) noteKeys.add(key)
|
|
409
|
+
this.entries.push(entry)
|
|
410
|
+
result.entries++
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const byHour = new Map(this.summaries.map((s) => [s.hourStart, s]))
|
|
414
|
+
for (const summary of backup.summaries) {
|
|
415
|
+
if (byHour.get(summary.hourStart)?.text.trim()) {
|
|
416
|
+
result.skipped++
|
|
417
|
+
continue
|
|
418
|
+
}
|
|
419
|
+
byHour.set(summary.hourStart, summary)
|
|
420
|
+
result.summaries++
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (result.entries > 0) {
|
|
424
|
+
this.entries.sort((a, b) => a.at - b.at)
|
|
425
|
+
this.prune()
|
|
426
|
+
await this.persistLog()
|
|
427
|
+
}
|
|
428
|
+
if (result.summaries > 0) {
|
|
429
|
+
this.summaries = [...byHour.values()].sort((a, b) => a.hourStart - b.hourStart).slice(-MEMORY_BOUNDS.maxSummaries)
|
|
430
|
+
await this.persistSummaries()
|
|
431
|
+
}
|
|
432
|
+
if (result.entries > 0 || result.summaries > 0) this.emitChanged()
|
|
433
|
+
return result
|
|
434
|
+
}
|
|
435
|
+
|
|
362
436
|
/** Plain-text export of a scope, for the OS share sheet. */
|
|
363
437
|
async exportText(scope: "session" | "day" | "all", at?: number): Promise<string> {
|
|
364
438
|
const sessions = (await this.sessionList()).filter((s) => {
|
|
@@ -563,10 +637,45 @@ function parseArray(raw: unknown): unknown[] {
|
|
|
563
637
|
function normalize(text: string): string {
|
|
564
638
|
return text
|
|
565
639
|
.toLowerCase()
|
|
566
|
-
.replace(/[
|
|
640
|
+
.replace(/[^\p{L}\p{N}]+/gu, " ")
|
|
567
641
|
.trim()
|
|
568
642
|
}
|
|
569
643
|
|
|
644
|
+
function noteKey(entry: MemoryEntry): string {
|
|
645
|
+
return `${hourStartOf(entry.at)}:${normalize(entry.text)}`
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function parseBackup(data: unknown): {entries: MemoryEntry[]; summaries: MemorySummary[]} {
|
|
649
|
+
const raw = data as Record<string, unknown> | null
|
|
650
|
+
if (!raw || typeof raw !== "object" || raw.format !== "glassly-memory" || !Array.isArray(raw.entries)) {
|
|
651
|
+
throw new MemoryBackupError("Not a Glassly memory backup")
|
|
652
|
+
}
|
|
653
|
+
if (typeof raw.version !== "number" || raw.version > 1) {
|
|
654
|
+
throw new MemoryBackupError(`Unsupported memory backup version ${String(raw.version)}`)
|
|
655
|
+
}
|
|
656
|
+
const entries: MemoryEntry[] = []
|
|
657
|
+
for (const v of raw.entries) {
|
|
658
|
+
if (!isEntry(v) || !v.id || !v.text.trim() || !Number.isFinite(v.at)) continue
|
|
659
|
+
entries.push({
|
|
660
|
+
id: v.id,
|
|
661
|
+
kind: v.kind,
|
|
662
|
+
text: v.text.trim(),
|
|
663
|
+
at: v.at,
|
|
664
|
+
speakerId: typeof v.speakerId === "string" ? v.speakerId : null,
|
|
665
|
+
...(typeof v.resolvedSpeakerName === "string" && v.resolvedSpeakerName
|
|
666
|
+
? {resolvedSpeakerName: v.resolvedSpeakerName}
|
|
667
|
+
: {}),
|
|
668
|
+
language: typeof v.language === "string" ? v.language : null,
|
|
669
|
+
...(v.kind === "note" ? {source: v.source === "voice" || v.source === "auto" ? v.source : "ui"} : {}),
|
|
670
|
+
})
|
|
671
|
+
}
|
|
672
|
+
const summaries = (Array.isArray(raw.summaries) ? raw.summaries : [])
|
|
673
|
+
.filter(isSummary)
|
|
674
|
+
.filter((s) => s.text.trim() && Number.isFinite(s.hourStart))
|
|
675
|
+
.map((s) => ({hourStart: s.hourStart, text: s.text, createdAt: s.createdAt}))
|
|
676
|
+
return {entries, summaries}
|
|
677
|
+
}
|
|
678
|
+
|
|
570
679
|
function isEntry(v: unknown): v is MemoryEntry {
|
|
571
680
|
if (!v || typeof v !== "object") return false
|
|
572
681
|
const e = v as Record<string, unknown>
|
|
@@ -79,20 +79,26 @@ export function normalizeManifestActions(raw: unknown): DeclaredAction[] {
|
|
|
79
79
|
return out
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
const WIDGET_SIZES: readonly DeclaredWidgetSize[] = ["XS", "SM", "MD", "LG"]
|
|
82
|
+
const WIDGET_SIZES: readonly DeclaredWidgetSize[] = ["XS", "XSM", "SM", "MD", "LG"]
|
|
83
83
|
const isWidgetSize = (value: unknown): value is DeclaredWidgetSize => (WIDGET_SIZES as readonly unknown[]).includes(value)
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* Normalize manifest widgets for installed and dev-sideloaded bundles: an
|
|
87
87
|
* entry without an id, a name or a usable size is dropped, repeats of a
|
|
88
88
|
* size are folded, and a default size the widget does not offer becomes
|
|
89
|
-
* its first size.
|
|
89
|
+
* its first size. Only `expandable: true` makes a widget expandable.
|
|
90
90
|
*/
|
|
91
91
|
export function normalizeManifestWidgets(raw: unknown): DeclaredWidget[] {
|
|
92
92
|
if (!Array.isArray(raw)) return []
|
|
93
93
|
const out: DeclaredWidget[] = []
|
|
94
94
|
const seen = new Set<string>()
|
|
95
|
-
for (const w of raw as Array<{
|
|
95
|
+
for (const w of raw as Array<{
|
|
96
|
+
id?: unknown
|
|
97
|
+
name?: unknown
|
|
98
|
+
sizes?: unknown
|
|
99
|
+
defaultSize?: unknown
|
|
100
|
+
expandable?: unknown
|
|
101
|
+
}>) {
|
|
96
102
|
if (!w || typeof w.id !== "string" || !w.id || typeof w.name !== "string" || !w.name || seen.has(w.id)) continue
|
|
97
103
|
const sizes = Array.isArray(w.sizes) ? Array.from(new Set(w.sizes.filter(isWidgetSize))) : []
|
|
98
104
|
if (sizes.length === 0) continue
|
|
@@ -102,6 +108,7 @@ export function normalizeManifestWidgets(raw: unknown): DeclaredWidget[] {
|
|
|
102
108
|
name: w.name,
|
|
103
109
|
sizes,
|
|
104
110
|
defaultSize: isWidgetSize(w.defaultSize) && sizes.includes(w.defaultSize) ? w.defaultSize : sizes[0],
|
|
111
|
+
expandable: w.expandable === true,
|
|
105
112
|
})
|
|
106
113
|
}
|
|
107
114
|
return out
|