@elizaos/plugin-facewear 2.0.3-beta.6 → 2.0.3-beta.7
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/dist/actions/display-text.d.ts +4 -0
- package/dist/actions/display-text.d.ts.map +1 -0
- package/dist/actions/display-text.js +90 -0
- package/dist/actions/display-text.js.map +1 -0
- package/dist/actions/facewear-connect.d.ts +3 -0
- package/dist/actions/facewear-connect.d.ts.map +1 -0
- package/dist/actions/facewear-connect.js +70 -0
- package/dist/actions/facewear-connect.js.map +1 -0
- package/dist/actions/facewear-control.d.ts +4 -0
- package/dist/actions/facewear-control.d.ts.map +1 -0
- package/dist/actions/facewear-control.js +627 -0
- package/dist/actions/facewear-control.js.map +1 -0
- package/dist/actions/facewear-debug.d.ts +3 -0
- package/dist/actions/facewear-debug.d.ts.map +1 -0
- package/dist/actions/facewear-debug.js +62 -0
- package/dist/actions/facewear-debug.js.map +1 -0
- package/dist/actions/facewear-status.d.ts +4 -0
- package/dist/actions/facewear-status.d.ts.map +1 -0
- package/dist/actions/facewear-status.js +66 -0
- package/dist/actions/facewear-status.js.map +1 -0
- package/dist/actions/microphone.d.ts +4 -0
- package/dist/actions/microphone.d.ts.map +1 -0
- package/dist/actions/microphone.js +63 -0
- package/dist/actions/microphone.js.map +1 -0
- package/dist/actions/view-actions.d.ts +23 -0
- package/dist/actions/view-actions.d.ts.map +1 -0
- package/dist/actions/view-actions.js +314 -0
- package/dist/actions/view-actions.js.map +1 -0
- package/dist/actions/vision-query.d.ts +4 -0
- package/dist/actions/vision-query.d.ts.map +1 -0
- package/dist/actions/vision-query.js +41 -0
- package/dist/actions/vision-query.js.map +1 -0
- package/dist/actions/xr-view-actions.d.ts +14 -0
- package/dist/actions/xr-view-actions.d.ts.map +1 -0
- package/dist/actions/xr-view-actions.js +29 -0
- package/dist/actions/xr-view-actions.js.map +1 -0
- package/dist/components/FacewearSpatialView.d.ts +50 -0
- package/dist/components/FacewearSpatialView.d.ts.map +1 -0
- package/dist/components/FacewearSpatialView.js +129 -0
- package/dist/components/FacewearSpatialView.js.map +1 -0
- package/dist/components/FacewearView.d.ts +17 -0
- package/dist/components/FacewearView.d.ts.map +1 -0
- package/dist/components/FacewearView.js +88 -0
- package/dist/components/FacewearView.js.map +1 -0
- package/dist/components/SmartglassesPanelView.d.ts +22 -0
- package/dist/components/SmartglassesPanelView.d.ts.map +1 -0
- package/dist/components/SmartglassesPanelView.js +140 -0
- package/dist/components/SmartglassesPanelView.js.map +1 -0
- package/dist/components/SmartglassesSpatialView.d.ts +46 -0
- package/dist/components/SmartglassesSpatialView.d.ts.map +1 -0
- package/dist/components/SmartglassesSpatialView.js +240 -0
- package/dist/components/SmartglassesSpatialView.js.map +1 -0
- package/dist/components/facewear-profiles.d.ts +27 -0
- package/dist/components/facewear-profiles.d.ts.map +1 -0
- package/dist/components/facewear-profiles.js +40 -0
- package/dist/components/facewear-profiles.js.map +1 -0
- package/dist/devices/apple-vision-pro.d.ts +7 -0
- package/dist/devices/apple-vision-pro.d.ts.map +1 -0
- package/dist/devices/apple-vision-pro.js +21 -0
- package/dist/devices/apple-vision-pro.js.map +1 -0
- package/dist/devices/even-realities.d.ts +7 -0
- package/dist/devices/even-realities.d.ts.map +1 -0
- package/dist/devices/even-realities.js +13 -0
- package/dist/devices/even-realities.js.map +1 -0
- package/dist/devices/meta-quest.d.ts +5 -0
- package/dist/devices/meta-quest.d.ts.map +1 -0
- package/dist/devices/meta-quest.js +21 -0
- package/dist/devices/meta-quest.js.map +1 -0
- package/dist/devices/registry.d.ts +19 -0
- package/dist/devices/registry.d.ts.map +1 -0
- package/dist/devices/registry.js +96 -0
- package/dist/devices/registry.js.map +1 -0
- package/dist/devices/xreal.d.ts +7 -0
- package/dist/devices/xreal.d.ts.map +1 -0
- package/dist/devices/xreal.js +19 -0
- package/dist/devices/xreal.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +260 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/smartglasses.d.ts +306 -0
- package/dist/protocol/smartglasses.d.ts.map +1 -0
- package/dist/protocol/smartglasses.js +1485 -0
- package/dist/protocol/smartglasses.js.map +1 -0
- package/dist/protocol/xr.d.ts +137 -0
- package/dist/protocol/xr.d.ts.map +1 -0
- package/dist/protocol/xr.js +18 -0
- package/dist/protocol/xr.js.map +1 -0
- package/dist/providers/facewear-context.d.ts +3 -0
- package/dist/providers/facewear-context.d.ts.map +1 -0
- package/dist/providers/facewear-context.js +59 -0
- package/dist/providers/facewear-context.js.map +1 -0
- package/dist/providers/smartglasses-status.d.ts +3 -0
- package/dist/providers/smartglasses-status.d.ts.map +1 -0
- package/dist/providers/smartglasses-status.js +33 -0
- package/dist/providers/smartglasses-status.js.map +1 -0
- package/dist/register-terminal-view.d.ts +21 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +70 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +8 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +116 -0
- package/dist/register.js.map +1 -0
- package/dist/routes/connect.d.ts +3 -0
- package/dist/routes/connect.d.ts.map +1 -0
- package/dist/routes/connect.js +86 -0
- package/dist/routes/connect.js.map +1 -0
- package/dist/routes/device-config.d.ts +5 -0
- package/dist/routes/device-config.d.ts.map +1 -0
- package/dist/routes/device-config.js +56 -0
- package/dist/routes/device-config.js.map +1 -0
- package/dist/routes/simulator-route.d.ts +8 -0
- package/dist/routes/simulator-route.d.ts.map +1 -0
- package/dist/routes/simulator-route.js +32 -0
- package/dist/routes/simulator-route.js.map +1 -0
- package/dist/routes/status.d.ts +3 -0
- package/dist/routes/status.d.ts.map +1 -0
- package/dist/routes/status.js +34 -0
- package/dist/routes/status.js.map +1 -0
- package/dist/routes/view-host.d.ts +24 -0
- package/dist/routes/view-host.d.ts.map +1 -0
- package/dist/routes/view-host.js +339 -0
- package/dist/routes/view-host.js.map +1 -0
- package/dist/routes/views.d.ts +8 -0
- package/dist/routes/views.d.ts.map +1 -0
- package/dist/routes/views.js +31 -0
- package/dist/routes/views.js.map +1 -0
- package/dist/services/audio-pipeline.d.ts +20 -0
- package/dist/services/audio-pipeline.d.ts.map +1 -0
- package/dist/services/audio-pipeline.js +87 -0
- package/dist/services/audio-pipeline.js.map +1 -0
- package/dist/services/facewear-service.d.ts +26 -0
- package/dist/services/facewear-service.d.ts.map +1 -0
- package/dist/services/facewear-service.js +45 -0
- package/dist/services/facewear-service.js.map +1 -0
- package/dist/services/smartglasses-service.d.ts +244 -0
- package/dist/services/smartglasses-service.d.ts.map +1 -0
- package/dist/services/smartglasses-service.js +821 -0
- package/dist/services/smartglasses-service.js.map +1 -0
- package/dist/services/vision-pipeline.d.ts +16 -0
- package/dist/services/vision-pipeline.d.ts.map +1 -0
- package/dist/services/vision-pipeline.js +39 -0
- package/dist/services/vision-pipeline.js.map +1 -0
- package/dist/services/xr-session-service.d.ts +54 -0
- package/dist/services/xr-session-service.d.ts.map +1 -0
- package/dist/services/xr-session-service.js +345 -0
- package/dist/services/xr-session-service.js.map +1 -0
- package/dist/status-format.d.ts +15 -0
- package/dist/status-format.d.ts.map +1 -0
- package/dist/status-format.js +89 -0
- package/dist/status-format.js.map +1 -0
- package/dist/transport/even-bridge.d.ts +69 -0
- package/dist/transport/even-bridge.d.ts.map +1 -0
- package/dist/transport/even-bridge.js +510 -0
- package/dist/transport/even-bridge.js.map +1 -0
- package/dist/transport/mock.d.ts +42 -0
- package/dist/transport/mock.d.ts.map +1 -0
- package/dist/transport/mock.js +124 -0
- package/dist/transport/mock.js.map +1 -0
- package/dist/transport/noble.d.ts +62 -0
- package/dist/transport/noble.d.ts.map +1 -0
- package/dist/transport/noble.js +256 -0
- package/dist/transport/noble.js.map +1 -0
- package/dist/transport/types.d.ts +36 -0
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/transport/types.js +1 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/transport/web-bluetooth.d.ts +58 -0
- package/dist/transport/web-bluetooth.d.ts.map +1 -0
- package/dist/transport/web-bluetooth.js +164 -0
- package/dist/transport/web-bluetooth.js.map +1 -0
- package/dist/ui/FacewearAppView.d.ts +4 -0
- package/dist/ui/FacewearAppView.d.ts.map +1 -0
- package/dist/ui/FacewearAppView.js +257 -0
- package/dist/ui/FacewearAppView.js.map +1 -0
- package/dist/ui/SmartglassesView.d.ts +10 -0
- package/dist/ui/SmartglassesView.d.ts.map +1 -0
- package/dist/ui/SmartglassesView.helpers.d.ts +104 -0
- package/dist/ui/SmartglassesView.helpers.d.ts.map +1 -0
- package/dist/ui/SmartglassesView.helpers.js +261 -0
- package/dist/ui/SmartglassesView.helpers.js.map +1 -0
- package/dist/ui/SmartglassesView.js +1189 -0
- package/dist/ui/SmartglassesView.js.map +1 -0
- package/dist/ui/facewear-view-bundle.d.ts +5 -0
- package/dist/ui/facewear-view-bundle.d.ts.map +1 -0
- package/dist/ui/facewear-view-bundle.js +17 -0
- package/dist/ui/facewear-view-bundle.js.map +1 -0
- package/dist/views/bundle.js +2950 -0
- package/dist/views/bundle.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/services/smartglasses-service.ts"],"sourcesContent":["import { type IAgentRuntime, logger, Service } from \"@elizaos/core\";\nimport {\n type DisplayPage,\n encodeAppWhitelist,\n encodeBatteryStatusRequest,\n encodeBmpTransfer,\n encodeBrightness,\n encodeClearScreen,\n encodeConnectionReady,\n encodeDashboard,\n encodeDashboardCalendarItem,\n encodeDashboardLayout,\n encodeDashboardPosition,\n encodeDashboardTimeWeather,\n encodeExitFunction,\n encodeG1MonochromeBmp,\n encodeG1Setup,\n encodeGetSerial,\n encodeGlassesWear,\n encodeHeadUpAngle,\n encodeHeartbeat,\n encodeMicCommand,\n encodeNavigationDirections,\n encodeNavigationEnd,\n encodeNavigationInit,\n encodeNavigationPoller,\n encodeNavigationPrimaryImage,\n encodeNavigationSecondaryImage,\n encodeNoteAdd,\n encodeNoteDelete,\n encodeNotification,\n encodeSilentMode,\n encodeStartAi,\n encodeTextPackets,\n encodeTranslateLanguages,\n encodeTranslateSetup,\n encodeTranslateStart,\n encodeTranslateText,\n encodeVoiceNoteDelete,\n encodeVoiceNoteDeleteAll,\n encodeVoiceNoteFetch,\n encodeVoiceNoteList,\n G1AiStatus,\n type G1ConnectionReadyMode,\n type G1DashboardLayout,\n type G1DashboardTimeWeatherPayload,\n type G1Event,\n type G1NavigationDirectionsPayload,\n type G1NotificationPayload,\n G1ScreenAction,\n G1SubCommand,\n G1TextStatus,\n type GlassSide,\n microphoneActionForInteractionEvent,\n paginateDisplayText,\n parseG1Notification,\n pcm16ToFloat32,\n type SmartglassesAudioEncoding,\n} from \"../protocol/smartglasses.js\";\nimport { getGlobalEvenBridgeTransport } from \"../transport/even-bridge.js\";\nimport { getNobleG1Transport } from \"../transport/noble.js\";\nimport type {\n SmartglassesConnectedLenses,\n SmartglassesTransport,\n SmartglassesWifiResult,\n} from \"../transport/types.js\";\nimport { getWebBluetoothG1Transport } from \"../transport/web-bluetooth.js\";\n\nexport const SMARTGLASSES_SERVICE_NAME = \"smartglasses\";\nexport const SMARTGLASSES_EVENT = \"SMARTGLASSES_EVENT\";\nexport const SMARTGLASSES_AUDIO_EVENT = \"SMARTGLASSES_AUDIO\";\nexport const SMARTGLASSES_TRANSCRIPT_EVENT = \"SMARTGLASSES_TRANSCRIPT\";\nexport const SMARTGLASSES_TRANSPORT_SETTING = \"SMARTGLASSES_TRANSPORT\";\nexport const SMARTGLASSES_SCAN_TIMEOUT_SETTING = \"SMARTGLASSES_SCAN_TIMEOUT_MS\";\nexport const SMARTGLASSES_AUTO_INIT_SETTING = \"SMARTGLASSES_AUTO_INIT\";\nexport const SMARTGLASSES_INIT_MODE_SETTING = \"SMARTGLASSES_INIT_MODE\";\nexport const FACEWEAR_SMARTGLASSES_TRANSPORT_SETTING =\n \"FACEWEAR_SMARTGLASSES_TRANSPORT\";\nexport const FACEWEAR_SCAN_TIMEOUT_SETTING = \"FACEWEAR_SCAN_TIMEOUT_MS\";\nexport const FACEWEAR_AUTO_INIT_SETTING = \"FACEWEAR_AUTO_INIT\";\nexport const FACEWEAR_INIT_MODE_SETTING = \"FACEWEAR_INIT_MODE\";\n\ntype PreferredTransport = \"auto\" | \"even-bridge\" | \"web-bluetooth\" | \"noble\";\nexport type SmartglassesDisplayMode = \"ai\" | \"text\";\nexport type SmartglassesWriteTarget = GlassSide | \"both\";\n\nexport interface SmartglassesRsvpOptions {\n wordsPerGroup?: number;\n wpm?: number;\n paddingChar?: string;\n mode?: SmartglassesDisplayMode;\n skipDelay?: boolean;\n}\n\nexport interface SmartglassesStatus {\n available: boolean;\n connected: boolean;\n transport: string | null;\n microphoneEnabled: boolean;\n heartbeatRunning: boolean;\n heartbeatIntervalMs: number | null;\n lastHeartbeatAt: number | null;\n lastEvent: G1Event | null;\n lastTranscript: string | null;\n audioChunksReceived: number;\n lastAudioEncoding: SmartglassesAudioEncoding | null;\n lastAudioSequence: number | null;\n audioSequenceGaps: number;\n physicalState: string | null;\n batteryState: string | null;\n batteryLevels: Partial<Record<GlassSide, number>>;\n batteryVoltagesMv: Partial<Record<GlassSide, number>>;\n deviceState: string | null;\n lastSerialNumber: string | null;\n connectedLenses: SmartglassesConnectedLenses;\n wifiAvailable: boolean;\n lastWifiStatus: SmartglassesWifiResult | null;\n}\n\ntype TranscriptCallback = (text: string, isFinal: boolean) => void;\ntype AudioCallback = (\n pcm: Float32Array,\n sampleRate: number,\n side: GlassSide,\n) => void;\ntype RawAudioCallback = (\n audio: Uint8Array,\n sampleRate: number,\n side: GlassSide,\n encoding: SmartglassesAudioEncoding,\n sequence?: number,\n) => void;\nexport type SmartglassesAudioDecoder = (\n audio: Uint8Array,\n context: {\n sampleRate: number;\n side: GlassSide;\n encoding: SmartglassesAudioEncoding;\n sequence?: number;\n },\n) => Uint8Array | null | undefined | Promise<Uint8Array | null | undefined>;\n\nlet injectedTransport: SmartglassesTransport | null = null;\nlet injectedAudioDecoder: SmartglassesAudioDecoder | null = null;\n\nexport function setSmartglassesTransportForRuntime(\n transport: SmartglassesTransport | null,\n): void {\n injectedTransport = transport;\n}\n\nexport function setSmartglassesAudioDecoderForRuntime(\n decoder: SmartglassesAudioDecoder | null,\n): void {\n injectedAudioDecoder = decoder;\n}\n\nexport class SmartglassesService extends Service {\n static serviceType = SMARTGLASSES_SERVICE_NAME;\n capabilityDescription =\n \"Controls Even Realities G1/G2 smartglasses display and microphone input, including side-tap mic toggles\";\n\n private transport: SmartglassesTransport | null = null;\n private microphoneEnabled = false;\n private lastEvent: G1Event | null = null;\n private lastTranscript: string | null = null;\n private audioChunksReceived = 0;\n private lastAudioEncoding: SmartglassesAudioEncoding | null = null;\n private lastAudioSequence: number | null = null;\n private audioSequenceGaps = 0;\n private physicalState: string | null = null;\n private batteryState: string | null = null;\n private batteryLevels: Partial<Record<GlassSide, number>> = {};\n private batteryVoltagesMv: Partial<Record<GlassSide, number>> = {};\n private deviceState: string | null = null;\n private lastSerialNumber: string | null = null;\n private lastWifiStatus: SmartglassesWifiResult | null = null;\n private displaySeq = 0;\n private heartbeatSeq = 0;\n private dashboardSeq = 0;\n private navigationSeq = 0;\n private navigationPollerSeq = 1;\n private translateSyncId = 0;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private heartbeatIntervalMs: number | null = null;\n private lastHeartbeatAt: number | null = null;\n private voiceNoteSyncId = 0;\n private readonly transcriptCallbacks = new Set<TranscriptCallback>();\n private readonly audioCallbacks = new Set<AudioCallback>();\n private readonly rawAudioCallbacks = new Set<RawAudioCallback>();\n private audioDecoder: SmartglassesAudioDecoder | null = injectedAudioDecoder;\n private disposers: Array<() => void> = [];\n\n static async start(runtime: IAgentRuntime): Promise<SmartglassesService> {\n const service = new SmartglassesService(runtime);\n service.transport = injectedTransport ?? (await chooseTransport(runtime));\n if (!service.transport) {\n logger.info(\n \"[plugin-facewear/smartglasses] no transport available; service loaded in offline/mockable mode\",\n );\n return service;\n }\n await service.connect();\n if (\n readBooleanSetting(\n runtime,\n [FACEWEAR_AUTO_INIT_SETTING, SMARTGLASSES_AUTO_INIT_SETTING],\n true,\n )\n ) {\n await service.sendConnectionReady(\n \"both\",\n readConnectionReadyModeSetting(runtime),\n );\n }\n return service;\n }\n\n setTransport(transport: SmartglassesTransport | null): void {\n void this.disconnect();\n this.transport = transport;\n }\n\n async connect(): Promise<void> {\n if (!this.transport)\n throw new Error(\"No smartglasses transport is configured\");\n if (this.transport.isConnected()) {\n this.attachTransportListeners();\n return;\n }\n await this.transport.connect();\n this.attachTransportListeners();\n }\n\n private attachTransportListeners(): void {\n if (!this.transport || this.disposers.length > 0) return;\n this.disposers.push(\n this.transport.onEvent((event) => void this.handleEvent(event)),\n );\n this.disposers.push(\n this.transport.onAudio(\n (audioData, sampleRate, side, encoding, sequence) => {\n const audioEncoding = encoding ?? \"pcm16\";\n void this.handleAudioChunk(\n audioData,\n sampleRate,\n side,\n audioEncoding,\n sequence,\n );\n },\n ),\n );\n if (this.transport.onTranscript) {\n this.disposers.push(\n this.transport.onTranscript((text, isFinal, metadata) => {\n this.receiveTranscript(text, isFinal, metadata);\n }),\n );\n }\n if (this.transport.onWifiStatus) {\n this.disposers.push(\n this.transport.onWifiStatus((status) => {\n this.lastWifiStatus = status;\n }),\n );\n }\n }\n\n async disconnect(): Promise<void> {\n this.stopHeartbeatLoop();\n for (const dispose of this.disposers.splice(0)) dispose();\n if (this.transport?.isConnected()) await this.transport.disconnect();\n this.microphoneEnabled = false;\n }\n\n async stop(): Promise<void> {\n await this.disconnect();\n }\n\n getStatus(): SmartglassesStatus {\n return {\n available: Boolean(this.transport),\n connected: this.transport?.isConnected() ?? false,\n transport: this.transport?.name ?? null,\n microphoneEnabled: this.microphoneEnabled,\n heartbeatRunning: Boolean(this.heartbeatTimer),\n heartbeatIntervalMs: this.heartbeatIntervalMs,\n lastHeartbeatAt: this.lastHeartbeatAt,\n lastEvent: this.lastEvent,\n lastTranscript: this.lastTranscript,\n audioChunksReceived: this.audioChunksReceived,\n lastAudioEncoding: this.lastAudioEncoding,\n lastAudioSequence: this.lastAudioSequence,\n audioSequenceGaps: this.audioSequenceGaps,\n physicalState: this.physicalState,\n batteryState: this.batteryState,\n batteryLevels: { ...this.batteryLevels },\n batteryVoltagesMv: { ...this.batteryVoltagesMv },\n deviceState: this.deviceState,\n lastSerialNumber: this.lastSerialNumber,\n connectedLenses: this.transport?.getConnectedLenses?.() ?? {},\n wifiAvailable: this.isWifiAvailable(),\n lastWifiStatus: this.lastWifiStatus,\n };\n }\n\n async displayText(\n text: string,\n options: {\n pageHoldMs?: number;\n completionDelayMs?: number;\n mode?: SmartglassesDisplayMode;\n } = {},\n ): Promise<{ pages: number }> {\n if (!this.transport)\n throw new Error(\"No smartglasses transport is configured\");\n if (!this.transport.isConnected()) await this.connect();\n const pages = paginateDisplayText(text);\n const mode = options.mode ?? \"ai\";\n for (const [index, page] of pages.entries()) {\n const seq = this.nextDisplaySeq();\n const streamingPage = withScreenStatus(\n page,\n streamingStatus(mode, index),\n );\n for (const packet of encodeTextPackets(streamingPage, seq)) {\n await this.transport.writeBoth(packet);\n }\n if (options.pageHoldMs && index < pages.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, options.pageHoldMs));\n }\n }\n if (mode === \"text\") return { pages: pages.length };\n if (options.completionDelayMs) {\n await new Promise((resolve) =>\n setTimeout(resolve, options.completionDelayMs),\n );\n }\n const lastPage = pages.at(-1);\n if (lastPage) {\n const seq = this.nextDisplaySeq();\n for (const packet of encodeTextPackets(\n withScreenStatus(lastPage, G1AiStatus.DisplayComplete),\n seq,\n )) {\n await this.transport.writeBoth(packet);\n }\n }\n return { pages: pages.length };\n }\n\n async displayRsvpText(\n text: string,\n options: SmartglassesRsvpOptions = {},\n ): Promise<{ groups: number; pages: number }> {\n const words = text\n .split(/\\s+/)\n .map((word) => word.trim())\n .filter(Boolean);\n if (words.length === 0) return { groups: 0, pages: 0 };\n\n const wordsPerGroup = positiveIntegerOrDefault(options.wordsPerGroup, 1);\n const paddingChar = options.paddingChar ?? \"...\";\n const groups: string[] = [];\n for (let offset = 0; offset < words.length; offset += wordsPerGroup) {\n const group = words.slice(offset, offset + wordsPerGroup);\n while (group.length < wordsPerGroup) group.push(paddingChar);\n groups.push(group.join(\" \"));\n }\n\n let pages = 0;\n const delayMs = rsvpDelayMs(options.wpm, wordsPerGroup);\n for (const group of groups) {\n const result = await this.displayText(group, { mode: options.mode });\n pages += result.pages;\n if (!options.skipDelay && delayMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n }\n return { groups: groups.length, pages };\n }\n\n async clearDisplay(): Promise<void> {\n await this.writeBoth(encodeClearScreen());\n }\n\n async sendHeartbeat(seq?: number): Promise<void> {\n const effectiveSeq = seq === undefined ? this.nextHeartbeatSeq() : seq;\n await this.writeBoth(encodeHeartbeat(effectiveSeq));\n this.lastHeartbeatAt = Date.now();\n }\n\n async requestBatteryStatus(\n side: SmartglassesWriteTarget = \"both\",\n ): Promise<void> {\n await this.sendRaw(encodeBatteryStatusRequest(), side);\n }\n\n startHeartbeatLoop(\n options: { intervalMs?: number; immediate?: boolean } = {},\n ): void {\n const intervalMs = positiveIntegerOrDefault(options.intervalMs, 8000);\n this.stopHeartbeatLoop();\n this.heartbeatIntervalMs = intervalMs;\n if (options.immediate !== false) void this.sendHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n void this.sendHeartbeat().catch((error) => {\n logger.warn(\n { error },\n \"[plugin-facewear/smartglasses] heartbeat failed\",\n );\n });\n }, intervalMs);\n this.heartbeatTimer.unref?.();\n }\n\n stopHeartbeatLoop(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n this.heartbeatIntervalMs = null;\n }\n\n async sendConnectionReady(\n side: SmartglassesWriteTarget = \"both\",\n mode: G1ConnectionReadyMode = \"lens-specific\",\n ): Promise<void> {\n if (side === \"both\") {\n await this.writeSide(\"left\", encodeConnectionReady(\"left\", mode));\n await this.writeSide(\"right\", encodeConnectionReady(\"right\", mode));\n return;\n }\n await this.writeSide(side, encodeConnectionReady(side, mode));\n }\n\n async sendStartAi(\n subcommand: G1SubCommand,\n param: Uint8Array = new Uint8Array(),\n ): Promise<void> {\n await this.writeBoth(encodeStartAi(subcommand, param));\n }\n\n async exitToDashboard(): Promise<void> {\n await this.sendStartAi(G1SubCommand.Exit);\n }\n\n async exitFunction(): Promise<void> {\n await this.writeBoth(encodeExitFunction());\n }\n\n async requestSerial(side: SmartglassesWriteTarget = \"both\"): Promise<void> {\n await this.sendRaw(encodeGetSerial(), side);\n }\n\n async sendAppWhitelist(\n whitelist: string | Record<string, unknown> | unknown[],\n side: SmartglassesWriteTarget = \"left\",\n ): Promise<{ packets: number }> {\n const packets = encodeAppWhitelist(whitelist);\n for (const packet of packets) await this.sendRaw(packet, side);\n return { packets: packets.length };\n }\n\n async sendG1Setup(\n payload: string | Record<string, unknown> | unknown[],\n side: SmartglassesWriteTarget = \"left\",\n ): Promise<{ packets: number }> {\n const packets = encodeG1Setup(payload);\n for (const packet of packets) await this.sendRaw(packet, side);\n return { packets: packets.length };\n }\n\n async sendRaw(\n packet: Uint8Array,\n side: SmartglassesWriteTarget = \"both\",\n ): Promise<void> {\n if (side === \"both\") {\n await this.writeBoth(packet);\n return;\n }\n await this.writeSide(side, packet);\n }\n\n async pageUp(): Promise<void> {\n await this.writeSide(\"left\", encodeStartAi(G1SubCommand.PageControl));\n }\n\n async pageDown(): Promise<void> {\n await this.writeSide(\"right\", encodeStartAi(G1SubCommand.PageControl));\n }\n\n async setMicrophoneEnabled(enabled: boolean): Promise<void> {\n if (!this.transport)\n throw new Error(\"No smartglasses transport is configured\");\n if (!this.transport.isConnected()) await this.connect();\n await this.transport.openMicrophone(enabled);\n this.microphoneEnabled = enabled;\n }\n\n async sendMicCommandPacket(enabled: boolean): Promise<void> {\n if (!this.transport)\n throw new Error(\"No smartglasses transport is configured\");\n if (!this.transport.isConnected()) await this.connect();\n await this.transport.write(\"right\", encodeMicCommand(enabled));\n this.microphoneEnabled = enabled;\n }\n\n async setSilentMode(enabled: boolean): Promise<void> {\n await this.writeBoth(encodeSilentMode(enabled));\n }\n\n async setBrightness(level: number, auto = false): Promise<void> {\n await this.writeBoth(encodeBrightness(level, auto));\n }\n\n async setDashboard(enabled: boolean, position = 0): Promise<void> {\n await this.writeBoth(encodeDashboard(enabled, position));\n }\n\n async setDashboardPosition(height: number, depth: number): Promise<void> {\n await this.writeBoth(\n encodeDashboardPosition(height, depth, this.nextDashboardSeq()),\n );\n }\n\n async setDashboardLayout(layout: G1DashboardLayout): Promise<void> {\n await this.writeBoth(encodeDashboardLayout(layout));\n }\n\n async sendDashboardCalendarItem(payload: {\n name: string;\n time: string;\n location: string;\n }): Promise<void> {\n await this.writeBoth(encodeDashboardCalendarItem(payload));\n }\n\n async sendDashboardTimeWeather(\n payload: Omit<G1DashboardTimeWeatherPayload, \"seqId\"> & {\n seqId?: number;\n },\n ): Promise<void> {\n await this.writeBoth(\n encodeDashboardTimeWeather({\n ...payload,\n seqId: payload.seqId ?? this.nextDashboardSeq(),\n }),\n );\n }\n\n async setHeadUpAngle(angle: number): Promise<void> {\n await this.writeBoth(encodeHeadUpAngle(angle));\n }\n\n async setGlassesWearDetection(enabled: boolean): Promise<void> {\n await this.writeBoth(encodeGlassesWear(enabled));\n }\n\n async scanWifi(): Promise<SmartglassesWifiResult> {\n const wifi = this.requireWifiCapability(\"scanWifi\");\n const result = await wifi.scanWifi();\n this.lastWifiStatus = result;\n return result;\n }\n\n async getWifiStatus(): Promise<SmartglassesWifiResult> {\n const wifi = this.requireWifiCapability(\"getWifiStatus\");\n const result = await wifi.getWifiStatus();\n this.lastWifiStatus = result;\n return result;\n }\n\n async configureWifi(\n ssid: string,\n password: string,\n ): Promise<SmartglassesWifiResult> {\n if (!ssid.trim()) throw new Error(\"Wi-Fi SSID is required\");\n const wifi = this.requireWifiCapability(\"configureWifi\");\n const result = await wifi.configureWifi(ssid.trim(), password);\n this.lastWifiStatus = result;\n return result;\n }\n\n async requestWifiSetup(reason?: string): Promise<SmartglassesWifiResult> {\n const wifi = this.requireWifiCapability(\"requestWifiSetup\");\n const result = await wifi.requestWifiSetup(reason);\n this.lastWifiStatus = result;\n return result;\n }\n\n async startNavigation(): Promise<void> {\n await this.writeBoth(encodeNavigationInit(this.nextNavigationSeq()));\n }\n\n async sendNavigationDirections(\n payload: Omit<G1NavigationDirectionsPayload, \"seqId\"> & {\n seqId?: number;\n },\n ): Promise<void> {\n await this.writeBoth(\n encodeNavigationDirections({\n ...payload,\n seqId: payload.seqId ?? this.nextNavigationSeq(),\n }),\n );\n }\n\n async sendNavigationPrimaryImage(\n image: ArrayLike<number>,\n overlay: ArrayLike<number>,\n ): Promise<{ packets: number }> {\n const packets = encodeNavigationPrimaryImage(\n image,\n overlay,\n this.navigationSeq,\n );\n this.navigationSeq = (this.navigationSeq + packets.length) & 0xff;\n for (const packet of packets) await this.writeBoth(packet);\n return { packets: packets.length };\n }\n\n async sendNavigationSecondaryImage(\n image: ArrayLike<number>,\n overlay: ArrayLike<number>,\n ): Promise<{ packets: number }> {\n const packets = encodeNavigationSecondaryImage(\n image,\n overlay,\n this.navigationSeq,\n );\n this.navigationSeq = (this.navigationSeq + packets.length) & 0xff;\n for (const packet of packets) await this.writeBoth(packet);\n return { packets: packets.length };\n }\n\n async sendNavigationPoller(): Promise<void> {\n await this.writeBoth(\n encodeNavigationPoller(\n this.nextNavigationSeq(),\n this.nextNavigationPollerSeq(),\n ),\n );\n }\n\n async endNavigation(): Promise<void> {\n await this.writeBoth(encodeNavigationEnd(this.nextNavigationSeq()));\n }\n\n async sendTranslateSetup(): Promise<void> {\n await this.writeBoth(encodeTranslateSetup());\n }\n\n async startTranslate(): Promise<void> {\n await this.writeSide(\"right\", encodeTranslateStart());\n }\n\n async setTranslateLanguages(\n fromLanguage: number,\n toLanguage: number,\n ): Promise<void> {\n await this.writeBoth(encodeTranslateLanguages(fromLanguage, toLanguage));\n }\n\n async sendTranslateText(\n kind: \"original\" | \"translated\",\n text: string,\n syncId?: number,\n ): Promise<{ syncId: number }> {\n const effectiveSyncId = syncId ?? this.nextTranslateSyncId();\n await this.writeBoth(encodeTranslateText(kind, text, effectiveSyncId));\n return { syncId: effectiveSyncId };\n }\n\n async addOrUpdateNote(\n noteNumber: number,\n title: string,\n text: string,\n ): Promise<void> {\n await this.writeBoth(encodeNoteAdd(noteNumber, title, text));\n }\n\n async deleteNote(noteNumber: number): Promise<void> {\n await this.writeBoth(encodeNoteDelete(noteNumber));\n }\n\n async requestVoiceNoteAudio(\n noteIndex: number,\n options: { syncId?: number; side?: GlassSide } = {},\n ): Promise<{ syncId: number }> {\n const syncId = options.syncId ?? this.nextVoiceNoteSyncId();\n await this.writeSide(\n options.side ?? \"right\",\n encodeVoiceNoteFetch(noteIndex, syncId),\n );\n return { syncId };\n }\n\n async requestVoiceNoteList(\n options: { syncId?: number; side?: GlassSide } = {},\n ): Promise<{ syncId: number }> {\n const syncId = options.syncId ?? this.nextVoiceNoteSyncId();\n await this.writeSide(options.side ?? \"right\", encodeVoiceNoteList(syncId));\n return { syncId };\n }\n\n async deleteVoiceNoteAudio(\n noteIndex: number,\n options: { syncId?: number; side?: GlassSide } = {},\n ): Promise<{ syncId: number }> {\n const syncId = options.syncId ?? this.nextVoiceNoteSyncId();\n await this.writeSide(\n options.side ?? \"right\",\n encodeVoiceNoteDelete(noteIndex, syncId),\n );\n return { syncId };\n }\n\n async deleteAllVoiceNoteAudio(\n options: { syncId?: number; side?: GlassSide } = {},\n ): Promise<{ syncId: number }> {\n const syncId = options.syncId ?? this.nextVoiceNoteSyncId();\n await this.writeSide(\n options.side ?? \"right\",\n encodeVoiceNoteDeleteAll(syncId),\n );\n return { syncId };\n }\n\n async sendNotification(\n payload: G1NotificationPayload,\n ): Promise<{ packets: number }> {\n const packets = encodeNotification(payload);\n for (const packet of packets) await this.writeBoth(packet);\n return { packets: packets.length };\n }\n\n async sendBmpImage(imageData: Uint8Array): Promise<{ packets: number }> {\n const packets = encodeBmpTransfer(imageData);\n for (const packet of packets) await this.writeBoth(packet);\n return { packets: packets.length };\n }\n\n async sendMonochromeBmpImage(\n pixels: ArrayLike<number> | Uint8Array,\n options: { width?: number; height?: number; threshold?: number } = {},\n ): Promise<{ packets: number; bytes: number }> {\n const imageData = encodeG1MonochromeBmp(pixels, options);\n const result = await this.sendBmpImage(imageData);\n return { ...result, bytes: imageData.length };\n }\n\n onTranscript(callback: TranscriptCallback): () => void {\n this.transcriptCallbacks.add(callback);\n return () => this.transcriptCallbacks.delete(callback);\n }\n\n onAudio(callback: AudioCallback): () => void {\n this.audioCallbacks.add(callback);\n return () => this.audioCallbacks.delete(callback);\n }\n\n onRawAudio(callback: RawAudioCallback): () => void {\n this.rawAudioCallbacks.add(callback);\n return () => this.rawAudioCallbacks.delete(callback);\n }\n\n setAudioDecoder(decoder: SmartglassesAudioDecoder | null): void {\n this.audioDecoder = decoder;\n }\n\n receiveTranscript(\n text: string,\n isFinal = true,\n metadata?: Record<string, unknown>,\n ): void {\n this.lastTranscript = text;\n for (const callback of this.transcriptCallbacks) callback(text, isFinal);\n void this.emitPluginEvent(SMARTGLASSES_TRANSCRIPT_EVENT, {\n text,\n isFinal,\n metadata,\n });\n }\n\n async receiveExternalRawEvent(\n side: GlassSide,\n data: Uint8Array,\n options: { applyControls?: boolean } = {},\n ): Promise<G1Event> {\n const event = parseG1Notification(side, data);\n await this.handleEvent(event, options);\n return event;\n }\n\n async receiveExternalAudioChunk(\n audioData: Uint8Array,\n options: {\n sampleRate?: number;\n side?: GlassSide;\n encoding?: SmartglassesAudioEncoding;\n sequence?: number;\n } = {},\n ): Promise<void> {\n await this.handleAudioChunk(\n audioData,\n options.sampleRate ?? 16_000,\n options.side ?? \"right\",\n options.encoding ?? \"lc3\",\n options.sequence,\n );\n }\n\n private async handleAudioChunk(\n audioData: Uint8Array,\n sampleRate: number,\n side: GlassSide,\n audioEncoding: SmartglassesAudioEncoding,\n sequence?: number,\n ): Promise<void> {\n this.audioChunksReceived += 1;\n this.lastAudioEncoding = audioEncoding;\n if (sequence !== undefined) {\n if (\n this.lastAudioSequence !== null &&\n ((this.lastAudioSequence + 1) & 0xff) !== sequence\n ) {\n this.audioSequenceGaps += 1;\n }\n this.lastAudioSequence = sequence;\n }\n for (const callback of this.rawAudioCallbacks)\n callback(audioData, sampleRate, side, audioEncoding, sequence);\n\n const payload: Record<string, unknown> = {\n side,\n sampleRate,\n audioData,\n audioEncoding,\n audioSequenceGaps: this.audioSequenceGaps,\n };\n if (sequence !== undefined) payload.sequence = sequence;\n\n const audioPcm =\n audioEncoding === \"pcm16\"\n ? audioData\n : await this.decodeAudioChunk(\n audioData,\n sampleRate,\n side,\n audioEncoding,\n sequence,\n );\n if (audioPcm) {\n const pcm = pcm16ToFloat32(audioPcm);\n for (const callback of this.audioCallbacks)\n callback(pcm, sampleRate, side);\n payload.audioPcm = audioPcm;\n payload.decodedAudioEncoding = \"pcm16\";\n }\n\n void this.emitPluginEvent(SMARTGLASSES_AUDIO_EVENT, {\n ...payload,\n });\n }\n\n private async decodeAudioChunk(\n audioData: Uint8Array,\n sampleRate: number,\n side: GlassSide,\n audioEncoding: SmartglassesAudioEncoding,\n sequence?: number,\n ): Promise<Uint8Array | null> {\n if (!this.audioDecoder) return null;\n try {\n return (\n (await this.audioDecoder(audioData, {\n sampleRate,\n side,\n encoding: audioEncoding,\n sequence,\n })) ?? null\n );\n } catch (error) {\n logger.warn(\n { error },\n \"[plugin-facewear/smartglasses] audio decoder failed; raw audio event preserved\",\n );\n return null;\n }\n }\n\n private async handleEvent(\n event: G1Event,\n options: { applyControls?: boolean } = {},\n ): Promise<void> {\n this.lastEvent = event;\n void this.emitPluginEvent(SMARTGLASSES_EVENT, { event });\n if (\n event.type === \"mic-response\" &&\n typeof event.micEnabled === \"boolean\"\n ) {\n this.microphoneEnabled = event.micEnabled;\n }\n if (event.type === \"state\") {\n if (event.stateCategory === \"physical\") {\n this.physicalState = event.stateName ?? event.label ?? null;\n } else if (event.stateCategory === \"battery\") {\n this.batteryState = event.stateName ?? event.label ?? null;\n } else if (event.stateCategory === \"device\") {\n this.deviceState = event.stateName ?? event.label ?? null;\n }\n const applyControls = options.applyControls !== false;\n const microphoneAction = microphoneActionForInteractionEvent(event);\n if (microphoneAction) {\n const enabled = microphoneAction === \"enable\";\n if (applyControls) await this.setMicrophoneEnabled(enabled);\n else this.microphoneEnabled = enabled;\n }\n if (applyControls && event.label === \"scroll_up\") await this.pageUp();\n if (applyControls && event.label === \"scroll_down\") await this.pageDown();\n }\n if (event.type === \"serial\" && event.serialNumber) {\n this.lastSerialNumber = event.serialNumber;\n }\n if (\n event.type === \"battery-status\" &&\n typeof event.batteryPercent === \"number\"\n ) {\n this.batteryLevels[event.side] = event.batteryPercent;\n if (typeof event.batteryVoltageMv === \"number\") {\n this.batteryVoltagesMv[event.side] = event.batteryVoltageMv;\n }\n }\n }\n\n private async writeBoth(packet: Uint8Array): Promise<void> {\n if (!this.transport)\n throw new Error(\"No smartglasses transport is configured\");\n if (!this.transport.isConnected()) await this.connect();\n await this.transport.writeBoth(packet);\n }\n\n private async writeSide(side: GlassSide, packet: Uint8Array): Promise<void> {\n if (!this.transport)\n throw new Error(\"No smartglasses transport is configured\");\n if (!this.transport.isConnected()) await this.connect();\n await this.transport.write(side, packet);\n }\n\n private requireWifiCapability<\n K extends\n | \"scanWifi\"\n | \"getWifiStatus\"\n | \"configureWifi\"\n | \"requestWifiSetup\",\n >(\n method: K,\n ): SmartglassesTransport & Required<Pick<SmartglassesTransport, K>> {\n if (!this.transport)\n throw new Error(\"No smartglasses transport is configured\");\n if (!this.isWifiAvailable() || !this.transport[method]) {\n throw new Error(\n \"Wi-Fi is only available through a native smartglasses bridge transport\",\n );\n }\n return this.transport as SmartglassesTransport &\n Required<Pick<SmartglassesTransport, K>>;\n }\n\n private isWifiAvailable(): boolean {\n if (!this.transport) return false;\n if (this.transport.supportsWifi) return this.transport.supportsWifi();\n return Boolean(\n this.transport.scanWifi ||\n this.transport.getWifiStatus ||\n this.transport.configureWifi ||\n this.transport.requestWifiSetup,\n );\n }\n\n private nextDisplaySeq(): number {\n const seq = this.displaySeq & 0xff;\n this.displaySeq = (this.displaySeq + 1) & 0xff;\n return seq;\n }\n\n private nextHeartbeatSeq(): number {\n const seq = this.heartbeatSeq & 0xff;\n this.heartbeatSeq = (this.heartbeatSeq + 1) & 0xff;\n return seq;\n }\n\n private nextDashboardSeq(): number {\n const seq = this.dashboardSeq & 0xff;\n this.dashboardSeq = (this.dashboardSeq + 1) & 0xff;\n return seq;\n }\n\n private nextNavigationSeq(): number {\n const seq = this.navigationSeq & 0xff;\n this.navigationSeq = (this.navigationSeq + 1) & 0xff;\n return seq;\n }\n\n private nextNavigationPollerSeq(): number {\n const seq = this.navigationPollerSeq & 0xff;\n this.navigationPollerSeq = (this.navigationPollerSeq + 1) & 0xff;\n return seq;\n }\n\n private nextVoiceNoteSyncId(): number {\n const syncId = this.voiceNoteSyncId & 0xff;\n this.voiceNoteSyncId = (this.voiceNoteSyncId + 1) & 0xff;\n return syncId;\n }\n\n private nextTranslateSyncId(): number {\n this.translateSyncId = (this.translateSyncId + 1) & 0xff;\n return this.translateSyncId;\n }\n\n private async emitPluginEvent(\n eventName: string,\n payload: Record<string, unknown>,\n ): Promise<void> {\n if (!this.runtime) return;\n await this.runtime.emitEvent(eventName, {\n runtime: this.runtime,\n source: \"@elizaos/plugin-facewear\",\n ...payload,\n });\n }\n}\n\nexport function getSmartglassesService(\n runtime: IAgentRuntime,\n): SmartglassesService | null {\n return (\n runtime.getService<SmartglassesService>(SMARTGLASSES_SERVICE_NAME) ?? null\n );\n}\n\nasync function chooseTransport(\n runtime: IAgentRuntime,\n): Promise<SmartglassesTransport | null> {\n const preferred = normalizePreferredTransport(\n readFirstSetting(runtime, [\n FACEWEAR_SMARTGLASSES_TRANSPORT_SETTING,\n SMARTGLASSES_TRANSPORT_SETTING,\n ]),\n );\n const scanTimeoutMs = readPositiveIntegerSetting(runtime, [\n FACEWEAR_SCAN_TIMEOUT_SETTING,\n SMARTGLASSES_SCAN_TIMEOUT_SETTING,\n ]);\n\n if (preferred === \"even-bridge\") return getGlobalEvenBridgeTransport();\n if (preferred === \"web-bluetooth\") return getWebBluetoothG1Transport();\n if (preferred === \"noble\") return getNobleG1Transport({ scanTimeoutMs });\n\n return (\n getGlobalEvenBridgeTransport() ??\n getWebBluetoothG1Transport() ??\n (await getNobleG1Transport({ scanTimeoutMs }))\n );\n}\n\nfunction readSetting(runtime: IAgentRuntime, key: string): unknown {\n return (\n runtime.getSetting?.(key) ??\n (typeof process !== \"undefined\" ? process.env[key] : undefined)\n );\n}\n\nfunction readFirstSetting(runtime: IAgentRuntime, keys: string[]): unknown {\n for (const key of keys) {\n const value = readSetting(runtime, key);\n if (value !== undefined && value !== null && value !== \"\") return value;\n }\n return undefined;\n}\n\nfunction normalizePreferredTransport(value: unknown): PreferredTransport {\n if (typeof value !== \"string\") return \"auto\";\n const normalized = value.trim().toLowerCase();\n if (\n normalized === \"even-bridge\" ||\n normalized === \"web-bluetooth\" ||\n normalized === \"noble\"\n ) {\n return normalized;\n }\n return \"auto\";\n}\n\nfunction readPositiveIntegerSetting(\n runtime: IAgentRuntime,\n keys: string | string[],\n): number | undefined {\n const value = Array.isArray(keys)\n ? readFirstSetting(runtime, keys)\n : readSetting(runtime, keys);\n const parsed =\n typeof value === \"number\"\n ? value\n : typeof value === \"string\"\n ? Number(value)\n : Number.NaN;\n return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction readBooleanSetting(\n runtime: IAgentRuntime,\n keys: string | string[],\n fallback: boolean,\n): boolean {\n const value = Array.isArray(keys)\n ? readFirstSetting(runtime, keys)\n : readSetting(runtime, keys);\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"number\") return value !== 0;\n if (typeof value === \"string\") {\n if (/^(false|0|no|off|disabled)$/i.test(value.trim())) return false;\n if (/^(true|1|yes|on|enabled)$/i.test(value.trim())) return true;\n }\n return fallback;\n}\n\nfunction readConnectionReadyModeSetting(\n runtime: IAgentRuntime,\n): G1ConnectionReadyMode {\n const value = String(\n readFirstSetting(runtime, [\n FACEWEAR_INIT_MODE_SETTING,\n SMARTGLASSES_INIT_MODE_SETTING,\n ]) ?? \"\",\n )\n .trim()\n .toLowerCase();\n if (\n value === \"official\" ||\n value === \"official-app\" ||\n value === \"even-demo-app\" ||\n value === \"same-init\"\n )\n return \"official\";\n if (\n value === \"android-f4\" ||\n value === \"android\" ||\n value === \"even-demo-android\" ||\n value === \"f4\"\n )\n return \"android-f4\";\n return \"lens-specific\";\n}\n\nfunction withScreenStatus(\n page: DisplayPage,\n screenStatus: number,\n): DisplayPage {\n return { ...page, screenStatus };\n}\n\nfunction streamingStatus(\n mode: SmartglassesDisplayMode,\n pageIndex: number,\n): number {\n if (mode === \"text\") return G1TextStatus.TextShow | G1ScreenAction.NewContent;\n return pageIndex === 0\n ? G1AiStatus.Displaying | G1ScreenAction.NewContent\n : G1AiStatus.Displaying;\n}\n\nfunction positiveIntegerOrDefault(value: unknown, fallback: number): number {\n return Number.isInteger(value) && Number(value) > 0\n ? Number(value)\n : fallback;\n}\n\nfunction rsvpDelayMs(wpm: unknown, wordsPerGroup: number): number {\n if (!Number.isFinite(wpm) || Number(wpm) <= 0) return 0;\n return Math.max(0, Math.round((60_000 / Number(wpm)) * wordsPerGroup));\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IAgentRuntime } from "@elizaos/core";
|
|
2
|
+
import type { XRFrameHeader } from "../protocol/xr.js";
|
|
3
|
+
export interface LatestFrame {
|
|
4
|
+
data: Buffer;
|
|
5
|
+
header: XRFrameHeader;
|
|
6
|
+
receivedAt: number;
|
|
7
|
+
}
|
|
8
|
+
export declare class VisionPipeline {
|
|
9
|
+
private latest;
|
|
10
|
+
storeFrame(connectionId: string, header: XRFrameHeader, data: Buffer): void;
|
|
11
|
+
getLatestFrame(connectionId: string): LatestFrame | undefined;
|
|
12
|
+
hasRecentFrame(connectionId: string): boolean;
|
|
13
|
+
describeFrame(runtime: IAgentRuntime, connectionId: string, prompt?: string): Promise<string | null>;
|
|
14
|
+
clear(connectionId: string): void;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=vision-pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision-pipeline.d.ts","sourceRoot":"","sources":["../../src/services/vision-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAkC;IAEhD,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI3E,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAO7D,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAIvC,aAAa,CACjB,OAAO,EAAE,aAAa,EACtB,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAkBzB,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;CAGlC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ModelType } from "@elizaos/core";
|
|
2
|
+
const FRAME_MAX_AGE_MS = 1e4;
|
|
3
|
+
class VisionPipeline {
|
|
4
|
+
latest = /* @__PURE__ */ new Map();
|
|
5
|
+
storeFrame(connectionId, header, data) {
|
|
6
|
+
this.latest.set(connectionId, { data, header, receivedAt: Date.now() });
|
|
7
|
+
}
|
|
8
|
+
getLatestFrame(connectionId) {
|
|
9
|
+
const frame = this.latest.get(connectionId);
|
|
10
|
+
if (!frame) return void 0;
|
|
11
|
+
if (Date.now() - frame.receivedAt > FRAME_MAX_AGE_MS) return void 0;
|
|
12
|
+
return frame;
|
|
13
|
+
}
|
|
14
|
+
hasRecentFrame(connectionId) {
|
|
15
|
+
return this.getLatestFrame(connectionId) !== void 0;
|
|
16
|
+
}
|
|
17
|
+
async describeFrame(runtime, connectionId, prompt) {
|
|
18
|
+
const frame = this.getLatestFrame(connectionId);
|
|
19
|
+
if (!frame) return null;
|
|
20
|
+
const dataUrl = `data:image/${frame.header.format};base64,${frame.data.toString("base64")}`;
|
|
21
|
+
try {
|
|
22
|
+
const description = await runtime.useModel(ModelType.IMAGE_DESCRIPTION, {
|
|
23
|
+
imageUrl: dataUrl,
|
|
24
|
+
prompt: prompt ?? "Describe what you see in this image concisely."
|
|
25
|
+
});
|
|
26
|
+
return typeof description === "string" ? description : null;
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error("[plugin-facewear/xr] vision error:", err);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
clear(connectionId) {
|
|
33
|
+
this.latest.delete(connectionId);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
VisionPipeline
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=vision-pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/services/vision-pipeline.ts"],"sourcesContent":["import type { IAgentRuntime } from \"@elizaos/core\";\nimport { ModelType } from \"@elizaos/core\";\nimport type { XRFrameHeader } from \"../protocol/xr.js\";\n\nexport interface LatestFrame {\n data: Buffer;\n header: XRFrameHeader;\n receivedAt: number;\n}\n\n// A frame older than this is considered stale and won't be described\nconst FRAME_MAX_AGE_MS = 10_000;\n\nexport class VisionPipeline {\n private latest = new Map<string, LatestFrame>();\n\n storeFrame(connectionId: string, header: XRFrameHeader, data: Buffer): void {\n this.latest.set(connectionId, { data, header, receivedAt: Date.now() });\n }\n\n getLatestFrame(connectionId: string): LatestFrame | undefined {\n const frame = this.latest.get(connectionId);\n if (!frame) return undefined;\n if (Date.now() - frame.receivedAt > FRAME_MAX_AGE_MS) return undefined;\n return frame;\n }\n\n hasRecentFrame(connectionId: string): boolean {\n return this.getLatestFrame(connectionId) !== undefined;\n }\n\n async describeFrame(\n runtime: IAgentRuntime,\n connectionId: string,\n prompt?: string,\n ): Promise<string | null> {\n const frame = this.getLatestFrame(connectionId);\n if (!frame) return null;\n\n const dataUrl = `data:image/${frame.header.format};base64,${frame.data.toString(\"base64\")}`;\n\n try {\n const description = await runtime.useModel(ModelType.IMAGE_DESCRIPTION, {\n imageUrl: dataUrl,\n prompt: prompt ?? \"Describe what you see in this image concisely.\",\n });\n return typeof description === \"string\" ? description : null;\n } catch (err) {\n console.error(\"[plugin-facewear/xr] vision error:\", err);\n return null;\n }\n }\n\n clear(connectionId: string): void {\n this.latest.delete(connectionId);\n }\n}\n"],"mappings":"AACA,SAAS,iBAAiB;AAU1B,MAAM,mBAAmB;AAElB,MAAM,eAAe;AAAA,EAClB,SAAS,oBAAI,IAAyB;AAAA,EAE9C,WAAW,cAAsB,QAAuB,MAAoB;AAC1E,SAAK,OAAO,IAAI,cAAc,EAAE,MAAM,QAAQ,YAAY,KAAK,IAAI,EAAE,CAAC;AAAA,EACxE;AAAA,EAEA,eAAe,cAA+C;AAC5D,UAAM,QAAQ,KAAK,OAAO,IAAI,YAAY;AAC1C,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,KAAK,IAAI,IAAI,MAAM,aAAa,iBAAkB,QAAO;AAC7D,WAAO;AAAA,EACT;AAAA,EAEA,eAAe,cAA+B;AAC5C,WAAO,KAAK,eAAe,YAAY,MAAM;AAAA,EAC/C;AAAA,EAEA,MAAM,cACJ,SACA,cACA,QACwB;AACxB,UAAM,QAAQ,KAAK,eAAe,YAAY;AAC9C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,UAAU,cAAc,MAAM,OAAO,MAAM,WAAW,MAAM,KAAK,SAAS,QAAQ,CAAC;AAEzF,QAAI;AACF,YAAM,cAAc,MAAM,QAAQ,SAAS,UAAU,mBAAmB;AAAA,QACtE,UAAU;AAAA,QACV,QAAQ,UAAU;AAAA,MACpB,CAAC;AACD,aAAO,OAAO,gBAAgB,WAAW,cAAc;AAAA,IACzD,SAAS,KAAK;AACZ,cAAQ,MAAM,sCAAsC,GAAG;AACvD,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,cAA4B;AAChC,SAAK,OAAO,OAAO,YAAY;AAAA,EACjC;AACF;","names":[]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { IAgentRuntime, UUID } from "@elizaos/core";
|
|
2
|
+
import { Service } from "@elizaos/core";
|
|
3
|
+
import { type WebSocket } from "ws";
|
|
4
|
+
import { type XRDeviceType, type XRPanelConfig, type XRServerControl } from "../protocol/xr.js";
|
|
5
|
+
import { VisionPipeline } from "./vision-pipeline.js";
|
|
6
|
+
export declare const XR_SERVICE_TYPE = "xr-session";
|
|
7
|
+
export declare const XR_WS_PORT_DEFAULT = 31338;
|
|
8
|
+
export declare const XR_WS_PORT_ENV = "XR_WS_PORT";
|
|
9
|
+
export interface XRConnection {
|
|
10
|
+
id: string;
|
|
11
|
+
ws: WebSocket;
|
|
12
|
+
deviceType: XRDeviceType;
|
|
13
|
+
entityId: UUID;
|
|
14
|
+
roomId: UUID;
|
|
15
|
+
connectedAt: Date;
|
|
16
|
+
}
|
|
17
|
+
export declare class XRSessionService extends Service {
|
|
18
|
+
static serviceType: string;
|
|
19
|
+
readonly capabilityDescription = "Streams audio and camera from XR headsets (Quest 3, XReal) to the agent and returns voice responses.";
|
|
20
|
+
private wss;
|
|
21
|
+
private connections;
|
|
22
|
+
private audioPipeline;
|
|
23
|
+
private visionPipeline;
|
|
24
|
+
static start(runtime: IAgentRuntime): Promise<Service>;
|
|
25
|
+
private initialize;
|
|
26
|
+
stop(): Promise<void>;
|
|
27
|
+
getConnections(): XRConnection[];
|
|
28
|
+
getWebSocketPort(): number | null;
|
|
29
|
+
hasActiveConnections(): boolean;
|
|
30
|
+
getVisionPipeline(): VisionPipeline;
|
|
31
|
+
sendText(connectionId: string, text: string): void;
|
|
32
|
+
sendControl(connectionId: string, msg: XRServerControl): void;
|
|
33
|
+
broadcastControl(msg: XRServerControl): void;
|
|
34
|
+
openView(connectionId: string, viewId: string, agentBaseUrl: string, config?: XRPanelConfig): void;
|
|
35
|
+
closeView(connectionId: string, viewId: string): void;
|
|
36
|
+
switchView(connectionId: string, viewId: string): void;
|
|
37
|
+
resizeView(connectionId: string, viewId: string, config: XRPanelConfig): void;
|
|
38
|
+
sendViewsCatalog(connectionId: string, views: Array<{
|
|
39
|
+
id: string;
|
|
40
|
+
label: string;
|
|
41
|
+
icon?: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
}>): void;
|
|
44
|
+
sendAudio(connectionId: string, audio: Buffer, sampleRate?: number): void;
|
|
45
|
+
private onConnect;
|
|
46
|
+
private handleTextMessage;
|
|
47
|
+
private handleSmartglassesRaw;
|
|
48
|
+
private handleSmartglassesAudio;
|
|
49
|
+
private sendSmartglassesControl;
|
|
50
|
+
private handleBinaryMessage;
|
|
51
|
+
private handleTranscript;
|
|
52
|
+
private ensureEntities;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=xr-session-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xr-session-service.d.ts","sourceRoot":"","sources":["../../src/services/xr-session-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,aAAa,EAEb,IAAI,EACL,MAAM,eAAe,CAAC;AACvB,OAAO,EAIL,OAAO,EACR,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,SAAS,EAAmB,MAAM,IAAI,CAAC;AAKrD,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,EAErB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,eAAO,MAAM,eAAe,eAAe,CAAC;AAC5C,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AACxC,eAAO,MAAM,cAAc,eAAe,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,SAAS,CAAC;IACd,UAAU,EAAE,YAAY,CAAC;IACzB,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE,IAAI,CAAC;IACb,WAAW,EAAE,IAAI,CAAC;CACnB;AAED,qBAAa,gBAAiB,SAAQ,OAAO;IAC3C,OAAgB,WAAW,SAAmB;IAE9C,QAAQ,CAAC,qBAAqB,0GAC2E;IAEzG,OAAO,CAAC,GAAG,CAAmB;IAC9B,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,aAAa,CAAiB;IACtC,OAAO,CAAC,cAAc,CAAkB;WAElB,KAAK,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;YAMvD,UAAU;IAuBT,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAYpC,cAAc,IAAI,YAAY,EAAE;IAIhC,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAKjC,oBAAoB,IAAI,OAAO;IAI/B,iBAAiB,IAAI,cAAc;IAInC,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMlD,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,IAAI;IAM7D,gBAAgB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IAQ5C,QAAQ,CACN,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,aAAa,GACrB,IAAI;IASP,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAIrD,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAItD,UAAU,CACR,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,GACpB,IAAI;IAIP,gBAAgB,CACd,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC,GACD,IAAI;IAIP,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,SAAQ,GAAG,IAAI;IAcxE,OAAO,CAAC,SAAS;IA4BjB,OAAO,CAAC,iBAAiB;YAmEX,qBAAqB;YAqBrB,uBAAuB;IA2BrC,OAAO,CAAC,uBAAuB;IAY/B,OAAO,CAAC,mBAAmB;YAmBb,gBAAgB;YA8EhB,cAAc;CAyB7B"}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChannelType,
|
|
3
|
+
createMessageMemory,
|
|
4
|
+
ModelType,
|
|
5
|
+
Service
|
|
6
|
+
} from "@elizaos/core";
|
|
7
|
+
import { WebSocketServer } from "ws";
|
|
8
|
+
import {
|
|
9
|
+
decodeBinaryFrame,
|
|
10
|
+
encodeBinaryFrame
|
|
11
|
+
} from "../protocol/xr.js";
|
|
12
|
+
import { AudioPipeline } from "./audio-pipeline.js";
|
|
13
|
+
import { getSmartglassesService } from "./smartglasses-service.js";
|
|
14
|
+
import { VisionPipeline } from "./vision-pipeline.js";
|
|
15
|
+
const XR_SERVICE_TYPE = "xr-session";
|
|
16
|
+
const XR_WS_PORT_DEFAULT = 31338;
|
|
17
|
+
const XR_WS_PORT_ENV = "XR_WS_PORT";
|
|
18
|
+
class XRSessionService extends Service {
|
|
19
|
+
static serviceType = XR_SERVICE_TYPE;
|
|
20
|
+
capabilityDescription = "Streams audio and camera from XR headsets (Quest 3, XReal) to the agent and returns voice responses.";
|
|
21
|
+
wss;
|
|
22
|
+
connections = /* @__PURE__ */ new Map();
|
|
23
|
+
audioPipeline;
|
|
24
|
+
visionPipeline;
|
|
25
|
+
static async start(runtime) {
|
|
26
|
+
const svc = new XRSessionService(runtime);
|
|
27
|
+
await svc.initialize(runtime);
|
|
28
|
+
return svc;
|
|
29
|
+
}
|
|
30
|
+
async initialize(runtime) {
|
|
31
|
+
this.visionPipeline = new VisionPipeline();
|
|
32
|
+
this.audioPipeline = new AudioPipeline(
|
|
33
|
+
runtime,
|
|
34
|
+
(connectionId, transcript) => this.handleTranscript(connectionId, transcript)
|
|
35
|
+
);
|
|
36
|
+
const port = Number(
|
|
37
|
+
runtime.getSetting(XR_WS_PORT_ENV) ?? XR_WS_PORT_DEFAULT
|
|
38
|
+
);
|
|
39
|
+
this.wss = new WebSocketServer({ port });
|
|
40
|
+
this.wss.on("connection", (ws) => this.onConnect(runtime, ws));
|
|
41
|
+
this.wss.on(
|
|
42
|
+
"error",
|
|
43
|
+
(err) => console.error("[plugin-facewear/xr] WebSocket server error:", err)
|
|
44
|
+
);
|
|
45
|
+
console.info(
|
|
46
|
+
`[plugin-facewear/xr] WebSocket server listening on ws://localhost:${port}`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
async stop() {
|
|
50
|
+
for (const conn of this.connections.values()) {
|
|
51
|
+
this.audioPipeline.clear(conn.id);
|
|
52
|
+
this.visionPipeline.clear(conn.id);
|
|
53
|
+
conn.ws.close();
|
|
54
|
+
}
|
|
55
|
+
this.connections.clear();
|
|
56
|
+
await new Promise((resolve) => this.wss.close(() => resolve()));
|
|
57
|
+
}
|
|
58
|
+
// ── Public accessors used by provider / action ──────────────────────────
|
|
59
|
+
getConnections() {
|
|
60
|
+
return [...this.connections.values()];
|
|
61
|
+
}
|
|
62
|
+
getWebSocketPort() {
|
|
63
|
+
const address = this.wss.address();
|
|
64
|
+
return typeof address === "object" && address ? address.port : null;
|
|
65
|
+
}
|
|
66
|
+
hasActiveConnections() {
|
|
67
|
+
return this.connections.size > 0;
|
|
68
|
+
}
|
|
69
|
+
getVisionPipeline() {
|
|
70
|
+
return this.visionPipeline;
|
|
71
|
+
}
|
|
72
|
+
sendText(connectionId, text) {
|
|
73
|
+
const conn = this.connections.get(connectionId);
|
|
74
|
+
if (!conn || conn.ws.readyState !== conn.ws.OPEN) return;
|
|
75
|
+
conn.ws.send(JSON.stringify({ type: "agent_text", text }));
|
|
76
|
+
}
|
|
77
|
+
sendControl(connectionId, msg) {
|
|
78
|
+
const conn = this.connections.get(connectionId);
|
|
79
|
+
if (!conn || conn.ws.readyState !== conn.ws.OPEN) return;
|
|
80
|
+
conn.ws.send(JSON.stringify(msg));
|
|
81
|
+
}
|
|
82
|
+
broadcastControl(msg) {
|
|
83
|
+
for (const conn of this.connections.values()) {
|
|
84
|
+
if (conn.ws.readyState === conn.ws.OPEN) {
|
|
85
|
+
conn.ws.send(JSON.stringify(msg));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
openView(connectionId, viewId, agentBaseUrl, config) {
|
|
90
|
+
this.sendControl(connectionId, {
|
|
91
|
+
type: "view_open",
|
|
92
|
+
viewId,
|
|
93
|
+
agentBaseUrl,
|
|
94
|
+
config
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
closeView(connectionId, viewId) {
|
|
98
|
+
this.sendControl(connectionId, { type: "view_close", viewId });
|
|
99
|
+
}
|
|
100
|
+
switchView(connectionId, viewId) {
|
|
101
|
+
this.sendControl(connectionId, { type: "view_switch", viewId });
|
|
102
|
+
}
|
|
103
|
+
resizeView(connectionId, viewId, config) {
|
|
104
|
+
this.sendControl(connectionId, { type: "view_resize", viewId, config });
|
|
105
|
+
}
|
|
106
|
+
sendViewsCatalog(connectionId, views) {
|
|
107
|
+
this.sendControl(connectionId, { type: "views_catalog", views });
|
|
108
|
+
}
|
|
109
|
+
sendAudio(connectionId, audio, sampleRate = 24e3) {
|
|
110
|
+
const conn = this.connections.get(connectionId);
|
|
111
|
+
if (!conn || conn.ws.readyState !== conn.ws.OPEN) return;
|
|
112
|
+
const header = {
|
|
113
|
+
type: "tts_audio",
|
|
114
|
+
sampleRate,
|
|
115
|
+
channels: 1,
|
|
116
|
+
encoding: "mp3"
|
|
117
|
+
};
|
|
118
|
+
conn.ws.send(encodeBinaryFrame(header, audio), { binary: true });
|
|
119
|
+
}
|
|
120
|
+
// ── WebSocket connection lifecycle ──────────────────────────────────────
|
|
121
|
+
onConnect(runtime, ws) {
|
|
122
|
+
const connId = crypto.randomUUID();
|
|
123
|
+
ws.on("message", (data, isBinary) => {
|
|
124
|
+
try {
|
|
125
|
+
if (isBinary) {
|
|
126
|
+
this.handleBinaryMessage(connId, data);
|
|
127
|
+
} else {
|
|
128
|
+
this.handleTextMessage(runtime, connId, ws, data.toString("utf8"));
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error("[plugin-facewear/xr] message handler error:", err);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
ws.on("close", () => {
|
|
135
|
+
this.audioPipeline.flush(connId);
|
|
136
|
+
this.audioPipeline.clear(connId);
|
|
137
|
+
this.visionPipeline.clear(connId);
|
|
138
|
+
this.connections.delete(connId);
|
|
139
|
+
console.info(`[plugin-facewear/xr] device disconnected: ${connId}`);
|
|
140
|
+
});
|
|
141
|
+
ws.on(
|
|
142
|
+
"error",
|
|
143
|
+
(err) => console.error(`[plugin-facewear/xr] ws error on ${connId}:`, err)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
handleTextMessage(runtime, connId, ws, raw) {
|
|
147
|
+
const msg = JSON.parse(raw);
|
|
148
|
+
if (msg.type === "hello") {
|
|
149
|
+
const entityId = crypto.randomUUID();
|
|
150
|
+
const roomId = crypto.randomUUID();
|
|
151
|
+
const conn = {
|
|
152
|
+
id: connId,
|
|
153
|
+
ws,
|
|
154
|
+
deviceType: msg.deviceType,
|
|
155
|
+
entityId,
|
|
156
|
+
roomId,
|
|
157
|
+
connectedAt: /* @__PURE__ */ new Date()
|
|
158
|
+
};
|
|
159
|
+
this.connections.set(connId, conn);
|
|
160
|
+
ws.send(JSON.stringify({ type: "ready", sessionId: connId }));
|
|
161
|
+
void this.ensureEntities(runtime, conn);
|
|
162
|
+
console.info(
|
|
163
|
+
`[plugin-facewear/xr] ${msg.deviceType} connected: ${connId}`
|
|
164
|
+
);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (msg.type === "ping") {
|
|
168
|
+
ws.send(JSON.stringify({ type: "pong" }));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (msg.type === "g1_raw") {
|
|
172
|
+
void this.handleSmartglassesRaw(runtime, connId, msg);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (msg.type === "mic_lc3" || msg.type === "mic_pcm") {
|
|
176
|
+
void this.handleSmartglassesAudio(runtime, msg);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (msg.type === "view_ready") {
|
|
180
|
+
console.info(
|
|
181
|
+
`[plugin-facewear/xr] view ready on ${connId}: ${msg.viewId}`
|
|
182
|
+
);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (msg.type === "view_closed") {
|
|
186
|
+
console.info(
|
|
187
|
+
`[plugin-facewear/xr] view closed on ${connId}: ${msg.viewId}`
|
|
188
|
+
);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (msg.type === "view_event") {
|
|
192
|
+
console.info(
|
|
193
|
+
`[plugin-facewear/xr] view event on ${connId}:`,
|
|
194
|
+
msg.viewId,
|
|
195
|
+
msg.event
|
|
196
|
+
);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async handleSmartglassesRaw(runtime, connId, msg) {
|
|
201
|
+
const service = getSmartglassesService(runtime);
|
|
202
|
+
if (!service) return;
|
|
203
|
+
const data = bytesFromMessagePayload(msg.data, msg.base64);
|
|
204
|
+
if (!data) return;
|
|
205
|
+
const side = msg.side ?? "right";
|
|
206
|
+
const event = await service.receiveExternalRawEvent(side, data, {
|
|
207
|
+
applyControls: false
|
|
208
|
+
});
|
|
209
|
+
this.sendSmartglassesControl(connId, event);
|
|
210
|
+
}
|
|
211
|
+
async handleSmartglassesAudio(runtime, msg) {
|
|
212
|
+
const service = getSmartglassesService(runtime);
|
|
213
|
+
if (!service) return;
|
|
214
|
+
const data = bytesFromMessagePayload(
|
|
215
|
+
msg.type === "mic_pcm" ? msg.pcm : msg.lc3,
|
|
216
|
+
msg.base64
|
|
217
|
+
);
|
|
218
|
+
if (!data) return;
|
|
219
|
+
await service.receiveExternalAudioChunk(data, {
|
|
220
|
+
sampleRate: msg.sampleRate,
|
|
221
|
+
side: msg.side ?? "right",
|
|
222
|
+
encoding: smartglassesEncodingForMessage(msg.type),
|
|
223
|
+
sequence: msg.sequence
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
sendSmartglassesControl(connId, event) {
|
|
227
|
+
const conn = this.connections.get(connId);
|
|
228
|
+
if (!conn || conn.ws.readyState !== conn.ws.OPEN) return;
|
|
229
|
+
if (conn.deviceType !== "even-realities") return;
|
|
230
|
+
if (event.label === "single_tap" || event.label === "long_press") {
|
|
231
|
+
conn.ws.send(JSON.stringify({ type: "mic_control", enabled: true }));
|
|
232
|
+
}
|
|
233
|
+
if (event.label === "double_tap" || event.label === "stop_ai_recording") {
|
|
234
|
+
conn.ws.send(JSON.stringify({ type: "mic_control", enabled: false }));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
handleBinaryMessage(connId, data) {
|
|
238
|
+
const conn = this.connections.get(connId);
|
|
239
|
+
if (!conn) return;
|
|
240
|
+
const { header, payload } = decodeBinaryFrame(data);
|
|
241
|
+
if (header.type === "audio") {
|
|
242
|
+
this.audioPipeline.push(connId, header, payload);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (header.type === "frame") {
|
|
246
|
+
this.visionPipeline.storeFrame(connId, header, payload);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// ── Message routing ─────────────────────────────────────────────────────
|
|
251
|
+
async handleTranscript(connectionId, transcript) {
|
|
252
|
+
const conn = this.connections.get(connectionId);
|
|
253
|
+
if (!conn) return;
|
|
254
|
+
const runtime = this.runtime;
|
|
255
|
+
conn.ws.send(
|
|
256
|
+
JSON.stringify({ type: "transcript", text: transcript, final: true })
|
|
257
|
+
);
|
|
258
|
+
const latestFrame = this.visionPipeline.getLatestFrame(connectionId);
|
|
259
|
+
const attachments = latestFrame ? [
|
|
260
|
+
{
|
|
261
|
+
id: crypto.randomUUID(),
|
|
262
|
+
url: `data:image/${latestFrame.header.format};base64,${latestFrame.data.toString("base64")}`,
|
|
263
|
+
title: "XR camera frame",
|
|
264
|
+
contentType: "image",
|
|
265
|
+
source: "xr-camera"
|
|
266
|
+
}
|
|
267
|
+
] : [];
|
|
268
|
+
const memory = createMessageMemory({
|
|
269
|
+
entityId: conn.entityId,
|
|
270
|
+
agentId: runtime.agentId,
|
|
271
|
+
roomId: conn.roomId,
|
|
272
|
+
content: {
|
|
273
|
+
text: transcript,
|
|
274
|
+
source: `xr-${conn.deviceType}`,
|
|
275
|
+
attachments
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
await runtime.createMemory(memory, "messages");
|
|
279
|
+
const callback = async (response) => {
|
|
280
|
+
const text = response.text?.trim() ?? "";
|
|
281
|
+
if (text.length === 0) return [];
|
|
282
|
+
conn.ws.send(JSON.stringify({ type: "agent_text", text }));
|
|
283
|
+
try {
|
|
284
|
+
const audio = await runtime.useModel(ModelType.TEXT_TO_SPEECH, text);
|
|
285
|
+
const audioBuf = Buffer.isBuffer(audio) ? audio : Buffer.from(audio);
|
|
286
|
+
this.sendAudio(connectionId, audioBuf);
|
|
287
|
+
} catch (err) {
|
|
288
|
+
console.error("[plugin-facewear/xr] TTS error:", err);
|
|
289
|
+
}
|
|
290
|
+
const responseMemory = createMessageMemory({
|
|
291
|
+
entityId: runtime.agentId,
|
|
292
|
+
agentId: runtime.agentId,
|
|
293
|
+
roomId: conn.roomId,
|
|
294
|
+
content: { text, source: `xr-agent`, inReplyTo: memory.id }
|
|
295
|
+
});
|
|
296
|
+
await runtime.createMemory(responseMemory, "messages");
|
|
297
|
+
return [responseMemory];
|
|
298
|
+
};
|
|
299
|
+
if (runtime.messageService) {
|
|
300
|
+
await runtime.messageService.handleMessage(runtime, memory, callback);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// ── Entity / room bootstrap ─────────────────────────────────────────────
|
|
304
|
+
async ensureEntities(runtime, conn) {
|
|
305
|
+
try {
|
|
306
|
+
await runtime.createEntity({
|
|
307
|
+
id: conn.entityId,
|
|
308
|
+
agentId: runtime.agentId,
|
|
309
|
+
names: [`XR-${conn.deviceType}`],
|
|
310
|
+
metadata: { source: `xr-${conn.deviceType}` }
|
|
311
|
+
});
|
|
312
|
+
await runtime.createRoom({
|
|
313
|
+
id: conn.roomId,
|
|
314
|
+
name: `XR session (${conn.deviceType} ${conn.id.slice(0, 8)})`,
|
|
315
|
+
source: "xr",
|
|
316
|
+
type: ChannelType.GROUP,
|
|
317
|
+
channelId: void 0,
|
|
318
|
+
messageServerId: void 0
|
|
319
|
+
});
|
|
320
|
+
await runtime.addParticipant(conn.entityId, conn.roomId);
|
|
321
|
+
await runtime.addParticipant(runtime.agentId, conn.roomId);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
console.error("[plugin-facewear/xr] entity setup error:", err);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function bytesFromMessagePayload(bytes, base64) {
|
|
328
|
+
if (Array.isArray(bytes) && bytes.every((value) => Number.isInteger(value))) {
|
|
329
|
+
return Uint8Array.from(bytes.map((value) => value & 255));
|
|
330
|
+
}
|
|
331
|
+
if (typeof base64 === "string" && base64.trim()) {
|
|
332
|
+
return Uint8Array.from(Buffer.from(base64, "base64"));
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
function smartglassesEncodingForMessage(type) {
|
|
337
|
+
return type === "mic_pcm" ? "pcm16" : "lc3";
|
|
338
|
+
}
|
|
339
|
+
export {
|
|
340
|
+
XRSessionService,
|
|
341
|
+
XR_SERVICE_TYPE,
|
|
342
|
+
XR_WS_PORT_DEFAULT,
|
|
343
|
+
XR_WS_PORT_ENV
|
|
344
|
+
};
|
|
345
|
+
//# sourceMappingURL=xr-session-service.js.map
|